Compare commits

...

5 Commits

Author SHA1 Message Date
Michał Sałaban 91a533e6eb Bump up to 0.7.6
3 years ago
Michał Sałaban b1959398bf Update docs on default backend shortcut
3 years ago
Michał Sałaban a30ff4ddc9 Put more elaborate test names
3 years ago
Jeffrey Ryan 4906933f3c Updated docs
3 years ago
Jeffrey Ryan e929312e1a Default backends for Daemon and Wallet
3 years ago

@ -14,7 +14,7 @@ Python Monero module
A comprehensive Python module for handling Monero cryptocurrency.
* release 0.7.5
* release 0.7.6
* open source: https://github.com/monero-ecosystem/monero-python
* works with Monero 0.13.x and `the latest source`_ (at least we try to keep up)
* Python 2.x and 3.x compatible

@ -1,8 +1,9 @@
Backends
========
Backends are the protocols and methods used to communicate with the Monero daemon and interact with
the wallet. As of the time of this writing, the only backends available in this library are:
The module comes with possibility of replacing the underlying backend. Backends are the protocols
and methods used to communicate with the Monero daemon or wallet. As of the time of this writing,
the module offers the following options:
* ``jsonrpc`` for the HTTP based RPC server,
* ``offline`` for running the wallet without Internet connection and even without the wallet file.
@ -18,6 +19,14 @@ The Python `requests`_ library is used in order to facilitate HTTP requests to t
interface. It makes POST requests and passes proper headers, parameters, and payload data as per
the official `Wallet RPC`_ documentation.
Also, ``jsonrpc`` backend is the default choice and both ``Wallet`` and ``Daemon`` classes
can be invoked in a simple form with no ``backend`` argument given. They will assume connection to
the default *mainnet* port on *localhost*, like below:
.. code-block:: python
In [1]: wallet = Wallet() # is equivalent to: wallet = Wallet(JSONRPCWallet(host='localhost', port=18081)
.. _`requests`: http://docs.python-requests.org/
.. _`Wallet RPC`: https://getmonero.org/resources/developer-guides/wallet-rpc.html

@ -55,9 +55,9 @@ author = 'Michal Salaban'
# built documents.
#
# The short X.Y version.
version = '0.7.5'
version = '0.7.6'
# The full version, including alpha/beta/rc tags.
release = '0.7.5'
release = '0.7.6'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.

@ -1,34 +1,34 @@
Interacting with daemon
=======================
The module offers an interface to interact with Monero daemon. For the time being, like with
wallet, the only available backend is JSON RPC.
The module offers an interface to interact with Monero daemon. For the time being, the only
available method to connnect to a daemon is by JSON RPC commands but the module allows for
providing a :doc:`custom backend <backends>`. The initializer
accepts keywords including, but not limited to, ``host``, ``port``, ``user``, and ``password``.
.. code-block:: python
In [1]: from monero.daemon import Daemon
In [2]: from monero.backends.jsonrpc import JSONRPCDaemon
In [2]: daemon = Daemon(port=28081)
In [3]: daemon = Daemon(JSONRPCDaemon(port=28081))
In [4]: daemon.height()
Out[4]: 1099108
In [3]: daemon.height()
Out[3]: 1099108
Also, the ``info()`` method will return a dictionary with details about the current daemon status.
Connecting via proxy (or TOR)
-----------------------------
The backend also accepts optional ``proxy_url`` keyword. A prime example of use is to route
``Daemon`` also accepts optional ``proxy_url`` keyword. A prime example of use is to route
your traffic via TOR:
.. code-block:: python
In [3]: daemon = Daemon(JSONRPCDaemon(host='xmrag4hf5xlabmob.onion', proxy_url='socks5h://127.0.0.1:9050'))
In [4]: daemon = Daemon(host='xmrag4hf5xlabmob.onion', proxy_url='socks5h://127.0.0.1:9050')
In [4]: daemon.height()
Out[4]: 1999790
In [5]: daemon.height()
Out[5]: 1999790
Please refer to the docs of underlying `requests`_ library for more info on proxies.
@ -46,27 +46,27 @@ The only difference is that now you want to add the ``relay=False`` argument.
.. code-block:: python
In [5]: from monero.wallet import Wallet
In [6]: from monero.wallet import Wallet
In [6]: from monero.backends.jsonrpc import JSONRPCWallet
In [7]: from monero.backends.jsonrpc import JSONRPCWallet
In [7]: wallet = Wallet(JSONRPCWallet(port=28088))
In [8]: wallet = Wallet(JSONRPCWallet(port=28088))
In [8]: wallet.balance()
Out[8]: Decimal('17.642325205670')
In [9]: wallet.balance()
Out[9]: Decimal('17.642325205670')
In [9]: txs = wallet.transfer('Bg1nUjsEx6UUByxr68o6gXcQRF58BpQyKauoZSo2HwubGErEnz9x6AS9o5ybmk3QmgeUpX3Msgm74QkwZKx2CeVWFrrZZqt', 10, relay=False)
In [10]: txs = wallet.transfer('Bg1nUjsEx6UUByxr68o6gXcQRF58BpQyKauoZSo2HwubGErEnz9x6AS9o5ybmk3QmgeUpX3Msgm74QkwZKx2CeVWFrrZZqt', 10, relay=False)
Now the return value is a list of resulting transactions (usually just one) which may be inspected
and validated.
.. code-block:: python
In [10]: txs
Out[10]: [38964a0c8c3be041051464b413996ad8d696223dc34925d98156848ed76a3ae3]
In [11]: txs
Out[11]: [38964a0c8c3be041051464b413996ad8d696223dc34925d98156848ed76a3ae3]
In [11]: txs[0].fee
Out[11]: Decimal('0.003766080000')
In [12]: txs[0].fee
Out[12]: Decimal('0.003766080000')
If anything is not OK, just discard the transaction and create a new one. There's no need to clean
up anything in the wallet.
@ -75,10 +75,10 @@ Once you have the transaction accepted, it's time to post it to the daemon:
.. code-block:: python
In [12]: result = daemon.send_transaction(txs[0])
In [13]: result = daemon.send_transaction(txs[0])
In [13]: result
Out[13]:
In [14]: result
Out[14]:
{'double_spend': False,
'fee_too_low': False,
'invalid_input': False,
@ -102,17 +102,17 @@ The following example shows such behavior:
.. code-block:: python
In [14]: txs1 = wallet.transfer('BYSXsmmK44xdjNVMGprUW5Yau9tsc9SAMJrQsANjGgpk2RB83cvVhWjZAgYNwLgmhdPawATh5q1CTEoLGKZSeZqtThefV7D', 1, relay=False)
In [15]: txs1 = wallet.transfer('BYSXsmmK44xdjNVMGprUW5Yau9tsc9SAMJrQsANjGgpk2RB83cvVhWjZAgYNwLgmhdPawATh5q1CTEoLGKZSeZqtThefV7D', 1, relay=False)
In [15]: txs2 = wallet.transfer('Bd5m5wTjWdYSaLBKe4i2avJjuFLYMEUKpiiE86F83NFiDXKE7QseSRvS7efTtJu5xHiHm5XmxgB2mfLu7NFrG7e3UTYRzEf', 2, relay=False)
In [16]: txs2 = wallet.transfer('Bd5m5wTjWdYSaLBKe4i2avJjuFLYMEUKpiiE86F83NFiDXKE7QseSRvS7efTtJu5xHiHm5XmxgB2mfLu7NFrG7e3UTYRzEf', 2, relay=False)
In [16]: txs1, txs2
Out[16]:
In [17]: txs1, txs2
Out[17]:
([315901f250a1018e89e1fc2b3953bd5acfdfa759f843cf5a38306a2255de6d54],
[2bd978172226b486badc8a9dcbafb04acb4760c3f2a5794c694fee8575739c6e])
In [17]: daemon.send_transaction(txs1[0])
Out[17]:
In [18]: daemon.send_transaction(txs1[0])
Out[18]:
{'double_spend': False,
'fee_too_low': False,
'invalid_input': False,
@ -125,7 +125,7 @@ The following example shows such behavior:
'status': 'OK',
'too_big': False}
In [18]: daemon.send_transaction(txs2[0])
In [19]: daemon.send_transaction(txs2[0])
---------------------------------------------------------------------------
TransactionBroadcastError Traceback (most recent call last)
<ipython-input-22-f24dc5d51c78> in <module>()

@ -1,25 +1,23 @@
Using wallet and accounts
=========================
Since Monero 'Helium Hydra' (0.11.x) the wallet handles accounts and deterministically
generated addresses, known as *subaddresses*.
The Wallet class provides an abstraction layer to retrieve wallet information, manage accounts and
subaddresses, and of course send transfers.
The wallet
----------
The following example shows how to create and retrieve wallet's accounts and
addresses:
addresses via the default JSON RPC backend:
.. code-block:: python
In [1]: from monero.wallet import Wallet
In [2]: from monero.backends.jsonrpc import JSONRPCWallet
In [2]: w = Wallet(port=28088)
In [3]: w = Wallet(JSONRPCWallet(port=28088))
In [4]: w.address()
Out[4]: A2GmyHHJ9jtUhPiwoAbR2tXU9LJu2U6fJjcsv3rxgkVRWU6tEYcn6C1NBc7wqCv5V7NW3zeYuzKf6RGGgZTFTpVC4QxAiAX
In [3]: w.address()
Out[3]: A2GmyHHJ9jtUhPiwoAbR2tXU9LJu2U6fJjcsv3rxgkVRWU6tEYcn6C1NBc7wqCv5V7NW3zeYuzKf6RGGgZTFTpVC4QxAiAX
Accounts and subaddresses
-------------------------
@ -32,17 +30,17 @@ client.
.. code-block:: python
In [5]: len(w.accounts)
Out[5]: 1
In [4]: len(w.accounts)
Out[4]: 1
In [6]: w.accounts[0]
Out[6]: <monero.account.Account at 0x7f78992d6898>
In [5]: w.accounts[0]
Out[5]: <monero.account.Account at 0x7f78992d6898>
In [7]: w.accounts[0].address()
Out[7]: A2GmyHHJ9jtUhPiwoAbR2tXU9LJu2U6fJjcsv3rxgkVRWU6tEYcn6C1NBc7wqCv5V7NW3zeYuzKf6RGGgZTFTpVC4QxAiAX
In [6]: w.accounts[0].address()
Out[6]: A2GmyHHJ9jtUhPiwoAbR2tXU9LJu2U6fJjcsv3rxgkVRWU6tEYcn6C1NBc7wqCv5V7NW3zeYuzKf6RGGgZTFTpVC4QxAiAX
In [8]: w.addresses()
Out[8]: [A2GmyHHJ9jtUhPiwoAbR2tXU9LJu2U6fJjcsv3rxgkVRWU6tEYcn6C1NBc7wqCv5V7NW3zeYuzKf6RGGgZTFTpVC4QxAiAX]
In [7]: w.addresses()
Out[7]: [A2GmyHHJ9jtUhPiwoAbR2tXU9LJu2U6fJjcsv3rxgkVRWU6tEYcn6C1NBc7wqCv5V7NW3zeYuzKf6RGGgZTFTpVC4QxAiAX]
Creating accounts and addresses
@ -55,28 +53,28 @@ and the subaddress index within the account.
.. code-block:: python
In [9]: w.new_address()
Out[9]: (BenuGf8eyVhjZwdcxEJY1MHrUfqHjPvE3d7Pi4XY5vQz53VnVpB38bCBsf8AS5rJuZhuYrqdG9URc2eFoCNPwLXtLENT4R7, 1)
In [8]: w.new_address()
Out[8]: (BenuGf8eyVhjZwdcxEJY1MHrUfqHjPvE3d7Pi4XY5vQz53VnVpB38bCBsf8AS5rJuZhuYrqdG9URc2eFoCNPwLXtLENT4R7, 1)
In [10]: w.addresses()
Out[10]:
In [9]: w.addresses()
Out[9]:
[A2GmyHHJ9jtUhPiwoAbR2tXU9LJu2U6fJjcsv3rxgkVRWU6tEYcn6C1NBc7wqCv5V7NW3zeYuzKf6RGGgZTFTpVC4QxAiAX,
BenuGf8eyVhjZwdcxEJY1MHrUfqHjPvE3d7Pi4XY5vQz53VnVpB38bCBsf8AS5rJuZhuYrqdG9URc2eFoCNPwLXtLENT4R7]
In [11]: w.new_account()
Out[11]: <monero.account.Account at 0x7f7894dffbe0>
In [10]: w.new_account()
Out[10]: <monero.account.Account at 0x7f7894dffbe0>
In [12]: len(w.accounts)
Out[12]: 2
In [11]: len(w.accounts)
Out[11]: 2
In [13]: w.accounts[1].address()
Out[13]: Bhd3PRVCnq5T5jjNey2hDSM8DxUgFpNjLUrKAa2iYVhYX71RuCGTekDKZKXoJPAGL763kEXaDSAsvDYb8bV77YT7Jo19GKY
In [12]: w.accounts[1].address()
Out[12]: Bhd3PRVCnq5T5jjNey2hDSM8DxUgFpNjLUrKAa2iYVhYX71RuCGTekDKZKXoJPAGL763kEXaDSAsvDYb8bV77YT7Jo19GKY
In [14]: w.accounts[1].new_address()
Out[14]: (Bbz5uCtnn3Gaj1YAizaHw1FPeJ6T7kk7uQoeY48SWjezEAyrWScozLxYbqGxsV5L6VJkvw5VwECAuLVJKQtHpA3GFXJNPYu, 1)
In [13]: w.accounts[1].new_address()
Out[13]: (Bbz5uCtnn3Gaj1YAizaHw1FPeJ6T7kk7uQoeY48SWjezEAyrWScozLxYbqGxsV5L6VJkvw5VwECAuLVJKQtHpA3GFXJNPYu, 1)
In [15]: w.accounts[1].addresses()
Out[15]:
In [14]: w.accounts[1].addresses()
Out[14]:
[Bhd3PRVCnq5T5jjNey2hDSM8DxUgFpNjLUrKAa2iYVhYX71RuCGTekDKZKXoJPAGL763kEXaDSAsvDYb8bV77YT7Jo19GKY,
Bbz5uCtnn3Gaj1YAizaHw1FPeJ6T7kk7uQoeY48SWjezEAyrWScozLxYbqGxsV5L6VJkvw5VwECAuLVJKQtHpA3GFXJNPYu]

@ -1,3 +1,3 @@
from . import address, account, const, daemon, wallet, numbers, wordlists, seed
__version__ = "0.7.5"
__version__ = "0.7.6"

@ -1,14 +1,21 @@
import six
from .backends.jsonrpc import JSONRPCDaemon
class Daemon(object):
"""Monero daemon.
Provides interface do a daemon instance.
Provides interface to a daemon instance.
:param backend: a daemon backend
:param \\**kwargs: arguments to initialize a :class:`JSONRPCDaemon <monero.backends.jsonrpc.JSONRPCDaemon>`
instance if no backend is given
"""
def __init__(self, backend):
self._backend = backend
def __init__(self, backend=None, **kwargs):
if backend and len(kwargs):
raise ValueError('backend already given, other arguments are extraneous')
self._backend = backend if backend else JSONRPCDaemon(**kwargs)
def info(self):
"""

@ -3,13 +3,13 @@ from sha3 import keccak_256
import struct
from . import address
from .backends.jsonrpc import JSONRPCWallet
from . import base58
from . import const
from . import ed25519
from . import numbers
from .transaction import Payment, PaymentManager
class Wallet(object):
"""
Monero wallet.
@ -26,11 +26,16 @@ class Wallet(object):
The wallet exposes a number of methods that operate on the default account (of index 0).
:param backend: a wallet backend
:param \\**kwargs: arguments to initialize a :class:`JSONRPCWallet <monero.backends.jsonrpc.JSONRPCWallet>`
instance if no backend is given
"""
accounts = None
def __init__(self, backend):
self._backend = backend
def __init__(self, backend=None, **kwargs):
if backend and len(kwargs):
raise ValueError('backend already given, other arguments are extraneous')
self._backend = backend if backend else JSONRPCWallet(**kwargs)
self.incoming = PaymentManager(0, backend, 'in')
self.outgoing = PaymentManager(0, backend, 'out')
self.refresh()

@ -147,3 +147,10 @@ class JSONRPCDaemonTestCase(JSONTestCase):
blk = self.daemon.block(height=693324)
self.assertEqual(len(blk.transactions), 105)
self.assertEqual(len(set(blk.transactions)), 105)
def test_init_default_backend(self):
daemon1 = Daemon(host='localhost')
daemon2 = Daemon()
with self.assertRaises(ValueError):
daemon3 = Daemon(backend=JSONRPCDaemon(), port=18089)

@ -1241,3 +1241,15 @@ class JSONRPCWalletTestCase(JSONTestCase):
self.wallet = Wallet(JSONRPCWallet())
pmts = self.wallet.incoming(tx_id='1a75f3aa57f7912313e90ab1188b7a102dbb619a324c3db51bb856a2f40503f1')
self.assertEqual(len(pmts), 1)
@responses.activate
def test_init_default_backend(self):
responses.add(responses.POST, self.jsonrpc_url,
json=self._read('test_incoming_from_self__issue_71-00-get_accounts.json'),
status=200)
wallet1 = Wallet(host='127.0.0.1')
wallet2 = Wallet()
with self.assertRaises(ValueError):
wallet3 = Wallet(backend=JSONRPCWallet(), port=18089)

Loading…
Cancel
Save