From ea6d4264a79502ec4ef401557dfb071be582fcc3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Sa=C5=82aban?= Date: Thu, 18 Jan 2018 02:59:09 +0100 Subject: [PATCH] Add quick start tutorial and docs on wallet, address --- docs/source/address.rst | 172 +++++++++++++++++++++++++++++++++++-- docs/source/backends.rst | 5 ++ docs/source/index.rst | 15 +++- docs/source/quickstart.rst | 79 +++++++++++++++++ docs/source/wallet.rst | 92 ++++++++++++++++++++ 5 files changed, 355 insertions(+), 8 deletions(-) create mode 100644 docs/source/backends.rst create mode 100644 docs/source/quickstart.rst diff --git a/docs/source/address.rst b/docs/source/address.rst index 1c839c8..6ca705c 100644 --- a/docs/source/address.rst +++ b/docs/source/address.rst @@ -1,8 +1,170 @@ -Addresses, amounts and payment IDs -================================== +Addresses and payment IDs +========================= + +In Monero v0.11.x the wallet had only one address. This is changing now. A +concept of **subaddress** has been introduced. + +The first, original address of the wallet is usually known as the *master +address*. All others are just *subaddresses*, even if they represent a separate +account within the wallet. + +Monero addresses are base58-encoded strings. You may disassemble each of them +using the excellent `address analysis tool`_ from *luigi1111*. + +While the ordinary string representation is perfectly valid to use, you may +want to use validation and other features provided by the ``monero.address`` +package. + +.. _`address analysis tool`: https://xmr.llcoins.net/addresstests.html + +Address validation and instatination +------------------------------------ + +The function ``monero.address.address()`` will recognize and validate Monero +address, returning an instance that provides additional functionality. + +The following example uses addresses from the wallet :doc:`we have generated in +the previous chapter `. + +Let's start with the master address: + +.. code-block:: python + + In [1]: from monero.address import address + + In [2]: a = address('A2GmyHHJ9jtUhPiwoAbR2tXU9LJu2U6fJjcsv3rxgkVRWU6tEYcn6C1NBc7wqCv5V7NW3zeYuzKf6RGGgZTFTpVC4QxAiAX') + + In [3]: a.is_testnet() + Out[3]: True + + In [4]: a.get_spend_key() + Out[4]: 'f0481b63cb937fa5960529247ebf6db627ff1b0bb88de9feccc3c504c16aa4b0' + + In [5]: a.get_view_key() + Out[5]: '2c5ba76d22e48a7ea4ddabea3cce66808ba0cc91265371910f893962e977af1e' + + In [6]: type(a) + Out[6]: monero.address.Address + +We may use a subaddress too: + +.. code-block:: python + + In [7]: b = address('BenuGf8eyVhjZwdcxEJY1MHrUfqHjPvE3d7Pi4XY5vQz53VnVpB38bCBsf8AS5rJuZhuYrqdG9URc2eFoCNPwLXtLENT4R7') + + In [8]: b.is_testnet() + Out[8]: True + + In [9]: b.get_spend_key() + Out[9]: 'ae7e136f46f618fe7f4a6b323ed60864c20070bf110978d7e3868686d5677318' + + In [10]: b.get_view_key() + Out[10]: '2bf801cdaf3a8b41020098a6d5e194f48fa62129fe9d8f09d19fee9260665baa' + + In [11]: type(b) + Out[11]: monero.address.SubAddress + +These two classes, ``Address`` and ``SubAddress`` have similar functionality +but one significant difference. Only the former may form *integrated address*. + +Payment IDs and integrated addresses +------------------------------------ + +Each Monero transaction may carry a **payment ID**. It is a 64 or 256-bit long +number that carries additional information between parties. For example, a +merchant can generate a payment ID for each order, or an exchange can assign +one to each user, so they would know what is the purpose of incoming payment. + +A short, 64-bit payment ID can be integrated into an address, creating, well... +an **integrated address**. + +.. code-block:: python + + In [12]: ia = a.with_payment_id(0xfeedbadbeef) + + In [13]: ia + Out[13]: ABySz66nm1QUhPiwoAbR2tXU9LJu2U6fJjcsv3rxgkVRWU6tEYcn6C1NBc7wqCv5V7NW3zeYuzKf6RGGgZTFTpVC623BT1ptXvVU2GjR1B + + In [14]: ia.get_base_address() + Out[14]: A2GmyHHJ9jtUhPiwoAbR2tXU9LJu2U6fJjcsv3rxgkVRWU6tEYcn6C1NBc7wqCv5V7NW3zeYuzKf6RGGgZTFTpVC4QxAiAX + + In [15]: ia.get_base_address() == a + Out[15]: True + + In [16]: ia.get_payment_id() + Out[16]: 00000feedbadbeef + + +Since subaddresses have been introduced, merchants may generate a separate +address for each order, user or any other object they expect the payments +coming to. Therefore, it has been decided that `subaddresses cannot generate +integrated addresses`_. + +.. _`subaddresses cannot generate integrated addresses`: https://monero.stackexchange.com/questions/6606/how-to-make-an-integrated-address-based-on-a-subaddress + +.. code-block:: python + + In [17]: b.with_payment_id(0xfeedbadbeef) + --------------------------------------------------------------------------- + TypeError Traceback (most recent call last) + in () + ----> 1 b.with_payment_id(0xfeedbadbeef) + + ~/devel/monero-python/monero/address.py in with_payment_id(self, _) + 99 + 100 def with_payment_id(self, _): + --> 101 raise TypeError("SubAddress cannot be integrated with payment ID") + 102 + 103 + + TypeError: SubAddress cannot be integrated with payment ID + +The ``monero.numbers.PaymentID`` class validates payment IDs. It accepts both +integer and hexadecimal string representations. + +.. code-block:: python + + In [18]: from monero.numbers import PaymentID + + In [19]: p1 = PaymentID(0xfeedbadbeef) + + In [20]: p2 = PaymentID('feedbadbeef') + + In [21]: p1 == p2 + Out[21]: True + + In [22]: p1.is_short() + Out[22]: True + + In [23]: p3 = PaymentID('1234567890abcdef0') + + In [24]: p3 + Out[24]: 000000000000000000000000000000000000000000000001234567890abcdef0 + + In [25]: p3.is_short() + Out[25]: False + +Long payment IDs cannot be integrated: + +.. code-block:: python + + In [26]: a.with_payment_id(p3) + --------------------------------------------------------------------------- + TypeError Traceback (most recent call last) + in () + ----> 1 a.with_payment_id(p3) + + ~/devel/monero-python/monero/address.py in with_payment_id(self, payment_id) + 73 payment_id = numbers.PaymentID(payment_id) + 74 if not payment_id.is_short(): + ---> 75 raise TypeError("Payment ID {0} has more than 64 bits and cannot be integrated".format(payment_id)) + 76 prefix = 54 if self.is_testnet() else 19 + 77 data = bytearray([prefix]) + self._decoded[1:65] + struct.pack('>Q', int(payment_id)) + + TypeError: Payment ID 000000000000000000000000000000000000000000000001234567890abcdef0 has more than 64 bits and cannot be integrated + +API reference +============= .. automodule:: monero.address :members: - -.. automodule:: monero.numbers - :members: diff --git a/docs/source/backends.rst b/docs/source/backends.rst new file mode 100644 index 0000000..0892269 --- /dev/null +++ b/docs/source/backends.rst @@ -0,0 +1,5 @@ +JSON RPC backend +================ + +.. automodule:: monero.backends.jsonrpc + :members: diff --git a/docs/source/index.rst b/docs/source/index.rst index b33b4c6..d65e66f 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -1,22 +1,31 @@ Python module for Monero -- docs ================================================ -Here Monero meets Python. +Welcome to the documentation for the `monero` Python module. + +The aim of this project is to offer a set of tools for interacting with Monero +cryptocurrency in Python. It provides higher level classes representing objects +from the Monero environment, like wallets, accounts, addresses, transactions. + +Currently it operates over JSON RPC protocol, however other backends are +planned as well. .. toctree:: - :maxdepth: 2 + :maxdepth: 1 :caption: Contents: + quickstart wallet address daemon + backends exceptions license authors Indices and tables -================== +------------------ * :ref:`genindex` * :ref:`modindex` diff --git a/docs/source/quickstart.rst b/docs/source/quickstart.rst new file mode 100644 index 0000000..99a9123 --- /dev/null +++ b/docs/source/quickstart.rst @@ -0,0 +1,79 @@ +Quick start +=========== + +This quick start tutorial will guide you through the first steps of connecting +to the Monero wallet. We assume you: + + * have basic knowledge of Monero concepts of the wallet and daemon, + * know how to use CLI (*command line interface*), + * have experience with Python. + +Connect to testnet for your own safety +-------------------------------------- + +The testnet is another Monero network where worthless coins circulate and +where, as the name suggests, all tests are supposed to be run. It's also a +place for early deployment of future features of the currency itself. You may +read `a brief explanation at stackexchange`_. + +**Please run all tests on testnet.** The code presented in these docs will +perform the requested operations right away, without asking for confirmation. +This is live code, not a wallet application that makes sure the user has not +made a mistake. **Running on the live net, if you make a mistake, you may lose +money.** + +.. _a brief explanation at stackexchange: https://monero.stackexchange.com/questions/1591/what-is-the-monero-testnet-how-can-i-participate-in-it + +Start the daemon and create a wallet +------------------------------------ + +In order to connect to the testnet network you need to start the daemon: + +.. code-block:: shell + + $ monerod --testnet + + +If you haven't used testnet before, it will begin downloading the blockchain, +exactly like it does on the live network. In January 2018 the testnet +blockchain was slightly over 2 GiB. It may take some time to get it. + +You may however create a wallet in the meantime: + +.. code-block:: shell + + $ monero-wallet-cli --testnet --generate-new-wallet testwallet + +For now you may leave the password empty (testnet coins are worthless). + +Start the RPC server +-------------------- + +The RPC server is a small utility that will operate on the wallet, exposing +a JSON RPC interface. Start it by typing: + +.. code-block:: shell + + $ monero-wallet-rpc --testnet --wallet-file testwallet --password "" --rpc-bind-port 28088 --disable-rpc-login + +Now everything is ready to start using Python. + +Connect to the wallet +--------------------- + +.. code-block:: python + + In [1]: from monero.wallet import Wallet + + In [2]: from monero.backends.jsonrpc import JSONRPCWallet + + In [3]: w = Wallet(JSONRPCWallet(port=28088)) + + In [4]: w.get_address() + Out[4]: A2GmyHHJ9jtUhPiwoAbR2tXU9LJu2U6fJjcsv3rxgkVRWU6tEYcn6C1NBc7wqCv5V7NW3zeYuzKf6RGGgZTFTpVC4QxAiAX + + In [5]: w.get_balance() + Out[5]: Decimal('0E-12') + +Congratulations! You have connected to the wallet. You may now proceed to the +next part, which will tell you about :doc:`interaction with wallet and accounts `. diff --git a/docs/source/wallet.rst b/docs/source/wallet.rst index 46b3505..08ce6e6 100644 --- a/docs/source/wallet.rst +++ b/docs/source/wallet.rst @@ -1,6 +1,98 @@ Using wallet and accounts ========================= +The wallet, up to Monero 'Helium Hydra' (0.11.x) release, had only single +address and no concept of accounts. This will change with the next version +which is planned to be published in March 2018 and already is available for +testing. + +The wallet +---------- + +The following example shows how to create and retrieve wallet's accounts and +addresses: + +.. code-block:: python + + In [1]: from monero.wallet import Wallet + + In [2]: from monero.backends.jsonrpc import JSONRPCWallet + + In [3]: w = Wallet(JSONRPCWallet(port=28088)) + + In [4]: w.get_address() + Out[4]: A2GmyHHJ9jtUhPiwoAbR2tXU9LJu2U6fJjcsv3rxgkVRWU6tEYcn6C1NBc7wqCv5V7NW3zeYuzKf6RGGgZTFTpVC4QxAiAX + +Accounts and subaddresses +------------------------- + +The following part may look strange if you are still using v0.11.x, because the +concept of multiple accounts and subaddresses didn't exist back then. + +The accounts are stored in wallet's ``accounts`` attribute, which is a list. + +Regardless of the version, **the wallet by default operates on its account of +index 0**, which makes it consistent with the behavior of the CLI wallet +client. On v0.11 the following code will work, even though it doesn't make much +sense. + +.. code-block:: python + + In [5]: len(w.accounts) + Out[5]: 1 + + In [6]: w.accounts[0] + Out[6]: + + In [7]: w.accounts[0].get_address() + Out[7]: A2GmyHHJ9jtUhPiwoAbR2tXU9LJu2U6fJjcsv3rxgkVRWU6tEYcn6C1NBc7wqCv5V7NW3zeYuzKf6RGGgZTFTpVC4QxAiAX + + In [8]: w.get_addresses() + Out[8]: [A2GmyHHJ9jtUhPiwoAbR2tXU9LJu2U6fJjcsv3rxgkVRWU6tEYcn6C1NBc7wqCv5V7NW3zeYuzKf6RGGgZTFTpVC4QxAiAX] + + +Creating accounts and addresses +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Every wallet can have separate accounts and each account can have numerous +addresses. The ``Wallet.new_account()`` and ``Account.new_address()`` will +create new instances. + +(This snippet, will fail on Monero v0.11.x) + +.. code-block:: python + + In [9]: w.new_address() + Out[9]: BenuGf8eyVhjZwdcxEJY1MHrUfqHjPvE3d7Pi4XY5vQz53VnVpB38bCBsf8AS5rJuZhuYrqdG9URc2eFoCNPwLXtLENT4R7 + + In [10]: w.get_addresses() + Out[10]: + [A2GmyHHJ9jtUhPiwoAbR2tXU9LJu2U6fJjcsv3rxgkVRWU6tEYcn6C1NBc7wqCv5V7NW3zeYuzKf6RGGgZTFTpVC4QxAiAX, + BenuGf8eyVhjZwdcxEJY1MHrUfqHjPvE3d7Pi4XY5vQz53VnVpB38bCBsf8AS5rJuZhuYrqdG9URc2eFoCNPwLXtLENT4R7] + + In [11]: w.new_account() + Out[11]: + + In [12]: len(w.accounts) + Out[12]: 2 + + In [13]: w.accounts[1].get_address() + Out[13]: Bhd3PRVCnq5T5jjNey2hDSM8DxUgFpNjLUrKAa2iYVhYX71RuCGTekDKZKXoJPAGL763kEXaDSAsvDYb8bV77YT7Jo19GKY + + In [14]: w.accounts[1].new_address() + Out[14]: Bbz5uCtnn3Gaj1YAizaHw1FPeJ6T7kk7uQoeY48SWjezEAyrWScozLxYbqGxsV5L6VJkvw5VwECAuLVJKQtHpA3GFXJNPYu + + In [15]: w.accounts[1].get_addresses() + Out[15]: + [Bhd3PRVCnq5T5jjNey2hDSM8DxUgFpNjLUrKAa2iYVhYX71RuCGTekDKZKXoJPAGL763kEXaDSAsvDYb8bV77YT7Jo19GKY, + Bbz5uCtnn3Gaj1YAizaHw1FPeJ6T7kk7uQoeY48SWjezEAyrWScozLxYbqGxsV5L6VJkvw5VwECAuLVJKQtHpA3GFXJNPYu] + + +As mentioned above, the wallet by default operates on the first account, so +``w.new_address()`` is equivalent to ``w.accounts[0].new_address()``. + +In the next chapter we will :doc:`learn about addresses
`. + .. automodule:: monero.wallet :members: