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. 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 * 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) * works with Monero 0.13.x and `the latest source`_ (at least we try to keep up)
* Python 2.x and 3.x compatible * Python 2.x and 3.x compatible

@ -1,8 +1,9 @@
Backends Backends
======== ========
Backends are the protocols and methods used to communicate with the Monero daemon and interact with The module comes with possibility of replacing the underlying backend. Backends are the protocols
the wallet. As of the time of this writing, the only backends available in this library are: 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, * ``jsonrpc`` for the HTTP based RPC server,
* ``offline`` for running the wallet without Internet connection and even without the wallet file. * ``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 interface. It makes POST requests and passes proper headers, parameters, and payload data as per
the official `Wallet RPC`_ documentation. 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/ .. _`requests`: http://docs.python-requests.org/
.. _`Wallet RPC`: https://getmonero.org/resources/developer-guides/wallet-rpc.html .. _`Wallet RPC`: https://getmonero.org/resources/developer-guides/wallet-rpc.html

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

@ -1,34 +1,34 @@
Interacting with daemon Interacting with daemon
======================= =======================
The module offers an interface to interact with Monero daemon. For the time being, like with The module offers an interface to interact with Monero daemon. For the time being, the only
wallet, the only available backend is JSON RPC. 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 .. code-block:: python
In [1]: from monero.daemon import Daemon 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 [3]: daemon.height()
Out[3]: 1099108
In [4]: daemon.height()
Out[4]: 1099108
Also, the ``info()`` method will return a dictionary with details about the current daemon status. Also, the ``info()`` method will return a dictionary with details about the current daemon status.
Connecting via proxy (or TOR) 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: your traffic via TOR:
.. code-block:: python .. 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() In [5]: daemon.height()
Out[4]: 1999790 Out[5]: 1999790
Please refer to the docs of underlying `requests`_ library for more info on proxies. 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 .. 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() In [9]: wallet.balance()
Out[8]: Decimal('17.642325205670') 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 Now the return value is a list of resulting transactions (usually just one) which may be inspected
and validated. and validated.
.. code-block:: python .. code-block:: python
In [10]: txs In [11]: txs
Out[10]: [38964a0c8c3be041051464b413996ad8d696223dc34925d98156848ed76a3ae3] Out[11]: [38964a0c8c3be041051464b413996ad8d696223dc34925d98156848ed76a3ae3]
In [11]: txs[0].fee In [12]: txs[0].fee
Out[11]: Decimal('0.003766080000') 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 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. 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 .. code-block:: python
In [12]: result = daemon.send_transaction(txs[0]) In [13]: result = daemon.send_transaction(txs[0])
In [13]: result In [14]: result
Out[13]: Out[14]:
{'double_spend': False, {'double_spend': False,
'fee_too_low': False, 'fee_too_low': False,
'invalid_input': False, 'invalid_input': False,
@ -102,17 +102,17 @@ The following example shows such behavior:
.. code-block:: python .. 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 In [17]: txs1, txs2
Out[16]: Out[17]:
([315901f250a1018e89e1fc2b3953bd5acfdfa759f843cf5a38306a2255de6d54], ([315901f250a1018e89e1fc2b3953bd5acfdfa759f843cf5a38306a2255de6d54],
[2bd978172226b486badc8a9dcbafb04acb4760c3f2a5794c694fee8575739c6e]) [2bd978172226b486badc8a9dcbafb04acb4760c3f2a5794c694fee8575739c6e])
In [17]: daemon.send_transaction(txs1[0]) In [18]: daemon.send_transaction(txs1[0])
Out[17]: Out[18]:
{'double_spend': False, {'double_spend': False,
'fee_too_low': False, 'fee_too_low': False,
'invalid_input': False, 'invalid_input': False,
@ -125,7 +125,7 @@ The following example shows such behavior:
'status': 'OK', 'status': 'OK',
'too_big': False} 'too_big': False}
In [18]: daemon.send_transaction(txs2[0]) In [19]: daemon.send_transaction(txs2[0])
--------------------------------------------------------------------------- ---------------------------------------------------------------------------
TransactionBroadcastError Traceback (most recent call last) TransactionBroadcastError Traceback (most recent call last)
<ipython-input-22-f24dc5d51c78> in <module>() <ipython-input-22-f24dc5d51c78> in <module>()

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

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

@ -1,14 +1,21 @@
import six import six
from .backends.jsonrpc import JSONRPCDaemon
class Daemon(object): class Daemon(object):
"""Monero daemon. """Monero daemon.
Provides interface do a daemon instance. Provides interface to a daemon instance.
:param backend: a daemon backend :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): def __init__(self, backend=None, **kwargs):
self._backend = backend 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): def info(self):
""" """

@ -3,13 +3,13 @@ from sha3 import keccak_256
import struct import struct
from . import address from . import address
from .backends.jsonrpc import JSONRPCWallet
from . import base58 from . import base58
from . import const from . import const
from . import ed25519 from . import ed25519
from . import numbers from . import numbers
from .transaction import Payment, PaymentManager from .transaction import Payment, PaymentManager
class Wallet(object): class Wallet(object):
""" """
Monero wallet. 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). The wallet exposes a number of methods that operate on the default account (of index 0).
:param backend: a wallet backend :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 accounts = None
def __init__(self, backend): def __init__(self, backend=None, **kwargs):
self._backend = backend 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.incoming = PaymentManager(0, backend, 'in')
self.outgoing = PaymentManager(0, backend, 'out') self.outgoing = PaymentManager(0, backend, 'out')
self.refresh() self.refresh()

@ -147,3 +147,10 @@ class JSONRPCDaemonTestCase(JSONTestCase):
blk = self.daemon.block(height=693324) blk = self.daemon.block(height=693324)
self.assertEqual(len(blk.transactions), 105) self.assertEqual(len(blk.transactions), 105)
self.assertEqual(len(set(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()) self.wallet = Wallet(JSONRPCWallet())
pmts = self.wallet.incoming(tx_id='1a75f3aa57f7912313e90ab1188b7a102dbb619a324c3db51bb856a2f40503f1') pmts = self.wallet.incoming(tx_id='1a75f3aa57f7912313e90ab1188b7a102dbb619a324c3db51bb856a2f40503f1')
self.assertEqual(len(pmts), 1) 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