diff --git a/docs/tutorials.rst b/docs/tutorials.rst index 3cf87c6..067a4bb 100644 --- a/docs/tutorials.rst +++ b/docs/tutorials.rst @@ -6,6 +6,10 @@ walkthrough examples of this section, you will be a master of PyOTA. In each section below, a code snippet will be shown and discussed in detail to help you understand how to carry out specific tasks with PyOTA. +The example scripts displayed here can also be found under ``examples/tutorials/`` +directory in the repository. Run them in a Python environment that has PyOTA +installed. See :ref:`Install PyOTA` for more info. + If you feel that something is missing or not clear, please post your questions and suggestions in the `PyOTA Bug Tracker`_. @@ -228,7 +232,282 @@ method to drop values we can't decode using ``utf-8``, or if the raw trytes can't be decoded into legit bytes. A possible reason for the latter can be if the attribute contains a signature rather than a message. +4.a Generate Address +-------------------- + +In this example, you will learn how to: + +- **Generate a random seed.** +- **Generate an IOTA address that belongs to your seed.** +- **Acquire free devnet IOTA tokens that you can use to play around with.** + +Code +~~~~ +.. literalinclude:: ../examples/tutorials/04a_gen_address.py + :linenos: + +Discussion +~~~~~~~~~~ +.. literalinclude:: ../examples/tutorials/04a_gen_address.py + :lines: 1-7 + :lineno-start: 1 + +We start off by generating a random seed with the help of the library. You are +also free to use your own seed, just uncomment line 6 and put it there. + +If you choose to generate one, your seed is written to the console so that you +can save it for later. Be prepared to do so, because you will have to use it +in the following tutorials. + +.. literalinclude:: ../examples/tutorials/04a_gen_address.py + :lines: 9-14 + :lineno-start: 9 + +Notice, how we pass the ``seed`` argument to the API class's init method. +Whenever the API needs to work with addresses or private keys, it will derive +them from this seed. + +.. important:: + + Your seed never leaves the library and your computer. Treat your (mainnet) + seed like any other password for a financial service: safe. If your seed is + compromised, attackers can steal your funds. + +.. literalinclude:: ../examples/tutorials/04a_gen_address.py + :lines: 16-20 + :lineno-start: 16 + +To generate a new address, we call :py:meth:`~Iota.get_new_addresses` +extended API method. Without arguments, this will return a ``dict`` with the +first unused address starting from ``index`` 0. An unused address is address +that has no transactions referencing it on the Tangle and was never spent from. + +If we were to generate more addresses starting from a desired index, +we could specify the ``start`` and ``count`` parameters. Read more about how to +generate addresses in PyOTA at :ref:`Generating Addresses`. + +On line 20 we access the first element of the list of addresses in the response +dictionary. + +.. literalinclude:: ../examples/tutorials/04a_gen_address.py + :lines: 22-23 + :lineno-start: 22 + +Lastly, the address is printed to the console, so that you can copy it. +Visit https://faucet.devnet.iota.org/ and enter the address to receive free +devnet tokens of 1000i. + +You might need to wait 1-2 minutes until the sum arrives to you address. To +check your balance, go to `4.b Check Balance`_ or `4.c Get Account Data`_. + +4.b Check Balance +----------------- + +In this example, you will learn how to: + +- **Check the balance of a specific IOTA address.** + +Code +~~~~ +.. literalinclude:: ../examples/tutorials/04b_check_balance.py + :linenos: + +Discussion +~~~~~~~~~~ +.. literalinclude:: ../examples/tutorials/04b_check_balance.py + :lines: 1-8 + :lineno-start: 1 + +The first step to check the balance of an address is to actually have an +address. Exchange the sample address on line 5 with your generated address from +`4.a Generate Address`_. + +Since we don't need to generate an address, there is no need for a seed to be +employed in the API object. Note the ``time`` import, we need it for later. + +.. literalinclude:: ../examples/tutorials/04b_check_balance.py + :lines: 10-25 + :lineno-start: 10 + +Our script will poll the network for the address balance as long as the returned +balance is zero. Therefore, the address you declared as ``my_address`` should +have some balance. If you see the ``Zero balance found...`` message a couple of +times, head over to https://faucet.devnet.iota.org/ and load up your address. + +:py:meth:`~Iota.get_balances` returns the confirmed balance of the address. +You could supply multiple addresses at the same time and get their respective +balances in a single call. Don't forget, that the method returns a ``dict``. +More details about it can be found at :py:meth:`~Iota.get_balances`. + +4.c Get Account Data +-------------------- + +In this example, you will learn how to: + +- **Gather addresses, balance and bundles associated with your seed on the Tangle.** + +.. warning:: + + **Account** in the context of this example is not to be confused with the + `Account Module`_, that is a feature yet to be implemented in PyOTA. + + **Account** here simply means the addresses and funds that belong to your + seed. + +Code +~~~~ +.. literalinclude:: ../examples/tutorials/04c_get_acc_data.py + :linenos: + +Discussion +~~~~~~~~~~ +.. literalinclude:: ../examples/tutorials/04c_get_acc_data.py + :lines: 1-3 + :lineno-start: 1 + +We will need ``pprint`` for a prettified output of the response ``dict`` and +``time`` for polling until we find non-zero balance. + +.. literalinclude:: ../examples/tutorials/04c_get_acc_data.py + :lines: 5-13 + :lineno-start: 5 + +Copy your seed from `4.a Generate Address`_ onto line 6. The API will use your +seed to generate addresses and look for corresponding transactions on the +Tangle. + +.. literalinclude:: ../examples/tutorials/04c_get_acc_data.py + :lines: 15-30 + :lineno-start: 15 + +Just like in the prevoius example, we will poll for information until we find +a non-zero balance. :py:meth:`~Iota.get_account_data` without arguments +generates addresses from ``index`` 0 until it finds the first unused. Then, it +queries the node about bundles of those addresses and sums up their balance. + +.. note:: + + If you read :py:meth:`~Iota.get_account_data` documentation carefully, you + notice that you can gain control over which addresses are checked during + the call by specifying the ``start`` and ``stop`` index parameters. + + This can be useful when your addresses with funds do not follow each other + in the address namespace, or a snapshot removed transactions from the + Tangle. It is recommended that you keep a local database of your already + used address indices. + + Once implemented in PyOTA, `Account Module`_ will address the aforementioned + problems. + +The response ``dict`` contains the addresses, bundles and total balance of +your seed. + +5. Send Tokens +-------------- + +In this example, you will learn how to: + +- **Construct a value transfer with PyOTA.** +- **Send a value transfer to an arbitrary IOTA address.** +- **Analyze a bundle of transactions on the Tangle.** + +.. note:: + + As a prerequisite to this tutorial, you need to have completed + `4.a Generate Address`_, and have a seed that owns devnet tokens. + +Code +~~~~ +.. literalinclude:: ../examples/tutorials/05_send_tokens.py + :linenos: + +Discussion +~~~~~~~~~~ +.. literalinclude:: ../examples/tutorials/05_send_tokens.py + :lines: 1-11 + :lineno-start: 1 + +We are going to send a value transaction, that requires us to prove that we +own the address containg the funds to spend. Therefore, we need our seed from +which the address was generated. + +Put your seed from `4.a Generate Address`_ onto line 4. We pass this seed to +the API object, that will utilize it for signing the transfer. + +.. literalinclude:: ../examples/tutorials/05_send_tokens.py + :lines: 13-16 + :lineno-start: 13 + +In IOTA, funds move accross addresses, therefore we need to define a **receiver +address**. For testing value transfers, you should send the funds only to +addresses that you control; if you use a randomly-generated receiver address, +you won't be able to recover the funds afterward! +Re-run `4.a Generate Address`_ for a new seed and a new address, or just paste +a valid IOTA address that you own onto line 16. + +.. literalinclude:: ../examples/tutorials/05_send_tokens.py + :lines: 18-25 + :lineno-start: 18 + +We declare a :py:class:`ProposedTransaction` object like we did before, but +this time, with ``value=1`` parameter. The smallest value you can send is 1 +iota ("1i"), there is no way to break it into smaller chunks. It is a really small +value anyway. You can also attach a message to the transaction, for example a +little note to the beneficiary of the payment. + +.. literalinclude:: ../examples/tutorials/05_send_tokens.py + :lines: 27-29 + :lineno-start: 27 + +To actually send the transfer, all you need to do is call +:py:meth:`~Iota.send_transfer` extended API method. This method will take care +of: + +- Gathering ``inputs`` (addresses you own and have funds) to fund the 1i transfer. +- Generating a new ``change_address``, and automatically sending the remaining + funds (``balance of chosen inputs`` - 1i) from ``inputs`` to ``change_address``. + + .. warning:: + + This step is extremely important, as it prevents you from `spending twice + from the same address`_. + + When an address is used as an input, all tokens will be withdrawn. Part + of the tokens will be used to fund your transaction, the rest will be + transferred to ``change_address``. + +- Constructing the transfer bundle with necessary input and output transactions. +- Finalizing the bundle and signing the spending transactions. +- Doing proof-of-work for each transaction in the bundle and sending it to the + network. + +.. literalinclude:: ../examples/tutorials/05_send_tokens.py + :lines: 31-32 + :lineno-start: 31 + +Open the link and observe the bundle you have just sent to the Tangle. Probably +it will take a couple of seconds for the network to confirm it. + +What you see is a bundle with 4 transactions in total, 1 input and 3 outputs. +But why are there so many transactions? + +- There is one transaction that withdraws iotas, this has negative value. + To authorize this spending, a valid signature is included in the transaction's + ``signature_message_fragment`` field. The signature however is too long to + fit into one transaction, therefore the library appends a new, zero-value + transaction to the bundle that holds the second part of the signature. This + you see on the output side of the bundle. +- A 1i transaction to the receiver address spends part of the withdrawn amount. +- The rest is transfered to ``change_address`` in a new output transaction. + +Once the bundle is confirmed, try rerunning the script from +`4.c Get Account Data`_ with the same seed as in this tutorial. Your balance +should be decremented by 1i, and you should see a new address, which was +actually the ``change_address``. + .. _PyOTA Bug Tracker: https://github.com/iotaledger/iota.py/issues .. _bytestring: https://docs.python.org/3/library/stdtypes.html#bytes .. _tryte alphabet: https://docs.iota.org/docs/getting-started/0.1/introduction/ternary#tryte-encoding -.. _Tangle Explorer: https://utils.iota.org \ No newline at end of file +.. _Tangle Explorer: https://utils.iota.org +.. _Account Module: https://docs.iota.org/docs/client-libraries/0.1/account-module/introduction/overview +.. _spending twice from the same address: https://docs.iota.org/docs/getting-started/0.1/clients/addresses#spent-addresses \ No newline at end of file diff --git a/examples/tutorials/04a_gen_address.py b/examples/tutorials/04a_gen_address.py new file mode 100644 index 0000000..0559c0c --- /dev/null +++ b/examples/tutorials/04a_gen_address.py @@ -0,0 +1,23 @@ +from iota import Iota, Seed + +# Generate a random seed, or use one you already have (for the devnet) +print('Generating a random seed...') +my_seed = Seed.random() +# my_seed = Seed(b'MYCUSTOMSEED') +print('Your seed is: ' + str(my_seed)) + +# Declare an API object +api = Iota( + adapter='https://nodes.devnet.iota.org:443', + seed=my_seed, + testnet=True, +) + +print('Generating the first unused address...') +# Generate the first unused address from the seed +response = api.get_new_addresses() + +addy = response['addresses'][0] + +print('Your new address is: ' + str(addy)) +print('Go to https://faucet.devnet.iota.org/ and enter you address to receive free devnet tokens.') \ No newline at end of file diff --git a/examples/tutorials/04b_check_balance.py b/examples/tutorials/04b_check_balance.py new file mode 100644 index 0000000..9f7353e --- /dev/null +++ b/examples/tutorials/04b_check_balance.py @@ -0,0 +1,25 @@ +from iota import Iota, Address +import time + +# Put your address from Tutorial 4.a here +my_address = Address(b'YOURADDRESSFROMTHEPREVIOUSTUTORIAL') + +# Declare an API object +api = Iota(adapter='https://nodes.devnet.iota.org:443', testnet=True) + +# Script actually runs until you load up your address +success = False + +while not success: + print('Checking balance on the Tangle for a specific address...') + # API method to check balance + response = api.get_balances(addresses=[my_address]) + + # response['balances'] is a list! + if response['balances'][0]: + print('Found the following information for address ' + str(my_address) + ':') + print('Balance: ' + str(response['balances'][0]) + 'i') + success = True + else: + print('Zero balance found, retrying in 30 seconds...') + time.sleep(30) \ No newline at end of file diff --git a/examples/tutorials/04c_get_acc_data.py b/examples/tutorials/04c_get_acc_data.py new file mode 100644 index 0000000..4061d2e --- /dev/null +++ b/examples/tutorials/04c_get_acc_data.py @@ -0,0 +1,30 @@ +from iota import Iota, Seed +from pprint import pprint +import time + +# Put your seed from Tutorial 4.a here +my_seed = Seed(b'YOURSEEDFROMTHEPREVIOUSTUTORIAL99999999999999999999999999999999999999999999999999') + +# Declare an API object +api = Iota( + adapter='https://nodes.devnet.iota.org:443', + seed=my_seed, + testnet=True +) + +# Script actually runs until it finds balance +success = False + +while not success: + print('Checking account information on the Tangle...') + # Gather addresses, balance and bundles + response = api.get_account_data() + + # response['balance'] is an integer! + if response['balance']: + print('Found the following information based on your seed:') + pprint(response) + success = True + else: + print('Zero balance found, retrying in 30 seconds...') + time.sleep(30) \ No newline at end of file diff --git a/examples/tutorials/05_send_tokens.py b/examples/tutorials/05_send_tokens.py new file mode 100644 index 0000000..bec20d8 --- /dev/null +++ b/examples/tutorials/05_send_tokens.py @@ -0,0 +1,32 @@ +from iota import Iota, Seed, Address, TryteString, ProposedTransaction, Tag + +# Put your seed here from Tutorial 4.a, or a seed that owns tokens (devnet) +my_seed = Seed(b'YOURSEEDFROMTHEPREVIOUSTUTORIAL') + +# Declare an API object +api = Iota( + adapter='https://nodes.devnet.iota.org:443', + seed=my_seed, + testnet=True, +) + +# Addres to receive 1i +# Feel free to replace it. For example, run the code from Tutorial 4.a +# and use that newly generated address with a 'fresh' seed. +receiver = Address(b'WWUTQBO99YDCBVBPAPVCANW9ATSNUPPLCPGDQXGQEVLUBSFHCEWOA9DIYYOXJONDIRHYPXQXOYXDPHREZ') + +print('Constructing transfer of 1i...') +# Create the transfer object +tx = ProposedTransaction( + address=receiver, + value=1, + message=TryteString.from_unicode('I just sent you 1i, use it wisely!'), + tag=Tag('VALUETX'), +) + +print('Preparing bundle and sending it to the network...') +# Prepare the transfer and send it to the network +response = api.send_transfer(transfers=[tx]) + +print('Check your transaction on the Tangle!') +print('https://utils.iota.org/bundle/%s/devnet' % response['bundle'].hash) \ No newline at end of file