Skip to content

Commit

Permalink
Updating tutorials
Browse files Browse the repository at this point in the history
  • Loading branch information
TjadenFroyda committed Jul 11, 2021
1 parent 8a60b71 commit 5175156
Show file tree
Hide file tree
Showing 5 changed files with 230 additions and 5 deletions.
2 changes: 1 addition & 1 deletion integration_tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ def _get_spendable_transactions(
wallet_name=wallet_name, account_name='account 0', min_confirmations=min_confirmations
)
spendable_transactions = [x for x in spendable_transactions.transactions]
sorted_spendable_transactions = sorted(spendable_transactions, key=lambda x: int(x.amount))
sorted_spendable_transactions = sorted(spendable_transactions, key=lambda x: x.amount)
amount_to_send = amount
op_return_amount = op_return_amount
transactions = []
Expand Down
2 changes: 1 addition & 1 deletion pystratis/api/wallet/requestmodels/verifymessagerequest.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ class VerifyMessageRequest(Model):
"""A request model used for the /wallet/verifymessage endpoint.
Args:
signature (str): The signature is to be verified.
signature (str): The signature to be verified.
external_address (Address): The address of the signer.
message (str): The message that was signed.
"""
Expand Down
2 changes: 1 addition & 1 deletion pystratis/api/wallet/wallet.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ def verify_message(self,
"""Verifies the signature of a message.
Args:
signature (str): The signature is to be verified.
signature (str): The signature to be verified.
external_address (Address, str): The address of the signer.
message (str): The message that was signed.
**kwargs: Extra keyword arguments.
Expand Down
138 changes: 137 additions & 1 deletion tutorials/SendingTransaction.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,138 @@
Sending a Standard Transaction with Pystratis
=============================================
=============================================
When you used a wallet GUI to send a transaction, several steps to build the transaction are hidden from the user.
These include:
- Retrieving a list of spendable outputs
- Building the transaction
- Signing the transaction
- Broadcasting the transaction. Details about performing these steps will be covered in this tutorial.

## Retrieving spendable utxo
The spendable_transactions method returns a [SpendableTransactionsModel](https://pystratis.readthedocs.io/en/latest/source/pystratis.api.wallet.html?pystratis.api.wallet.responsemodels.spendabletransactionsmodel.SpendableTransactionsModel#pystratis.api.wallet.responsemodels.spendabletransactionsmodel.SpendableTransactionsModel),
which contains a list of [SpendableTransactionModel](https://pystratis.readthedocs.io/en/latest/source/pystratis.api.wallet.html?pystratis.api.wallet.responsemodels.spendabletransactionsmodel.SpendableTransactionsModel#pystratis.api.wallet.responsemodels.spendabletransactionsmodel.SpendableTransactionModel).

```python
from pystratis.nodes import StraxNode
from pystratis.api.wallet.responsemodels import SpendableTransactionsModel
node = StraxNode()
s_txs: SpendableTransactionsModel = node.wallet.spendable_transactions(wallet_name='ExampleWallet')

# Re-order the spendable transactions smallest to largest to preferentially use low value utxos.
s_txs = [x for x in s_txs.transactions]
s_txs = sorted(s_txs, key=lambda x: x.amount)
```
## Building and signing a transaction
In this example we are going to send a transaction to an unused address on our node.

See [Recipient](https://pystratis.readthedocs.io/en/latest/source/pystratis.core.html#module-pystratis.core.recipient) and [Outpoint](https://pystratis.readthedocs.io/en/latest/source/pystratis.core.html#module-pystratis.core.outpoint) for details on usage below.

The result of the build_transaction call is a [BuildTransactionModel](https://pystratis.readthedocs.io/en/latest/source/pystratis.api.global_responsemodels.html#pystratis.api.global_responsemodels.buildtransactionmodel.BuildTransactionModel). The transaction hex is then ready for broadcasting.
```python
from pystratis.core import Outpoint, Recipient
from pystratis.core.types import Money
# First we want to define the destination address.
destination_address = node.wallet.unused_address(wallet_name='ExampleWallet')
# The change address is optional. In this case, we are sending any change back to an adress that has a balance.
change_address = node.wallet.balance(
wallet_name='ExampleWallet', include_balance_by_address=True
).balances[0].addresses[0].address

# Here we are setting a fee (rather than using the fee estimation API call).
fee_amount = Money(0.0001)
amount_to_send = Money(1)

# After declaring the amount being sent, we need to include enough utxos as transaction
# inputs such that the sum of the input amounts >= the amount being sent.
# The for loop below is one way to accomplish this.
transactions = []
trxid_amount = Money(0)
for spendable_transaction in s_txs:
transactions.append(spendable_transaction)
trxid_amount += spendable_transaction.amount
if trxid_amount >= amount_to_send: # Can add fee here if not subtracting from amount below.
break

# The last elements of the transaction to build are the outpoints and the recipients.
# The outpoints are built from the utxos in the previous step. The list comprehension below does this efficiently.
# The recipient list can be 1 or more recipient, but the sum must be less than the amount contained in included utxos.
response = node.wallet.build_transaction(
fee_amount=fee_amount,
password='abc123',
segwit_change_address=False,
wallet_name='ExampleWallet',
account_name='account 0',
outpoints=[Outpoint(transaction_id=x.transaction_id, index=x.index) for x in transactions],
recipients=[Recipient(destination_address=destination_address, subtraction_fee_from_amount=True, amount=amount_to_send)],
allow_unconfirmed=False,
shuffle_outputs=True,
change_address=change_address
)

# Broadcast the successfully built transaction
response = node.wallet.send_transaction(transaction_hex=response.hex)
```
### Special case: Offline signing
Using the same `s_txs`, `amount_to_send`, `destination_address`, and `change_address` as the last example, we are going to build a transaction that can be signed offline.
```python
response = node.wallet.build_offline_sign_request(
fee_amount=fee_amount,
wallet_name='Test',
account_name='account 0',
outpoints=[Outpoint(transaction_id=x.transaction_id, index=x.index) for x in transactions],
recipients=[Recipient(destination_address=destination_address, subtraction_fee_from_amount=True, amount=amount_to_send)],
allow_unconfirmed=False,
shuffle_outputs=True,
change_address=change_address
)
```
Once the offline sign request is built, you'll need to do the next step on your offline device.

Underneath the hood, pystratis uses [pydantic datastructures](https://pydantic-docs.helpmanual.io/). We'll use `pydantic` to help move our data to the offline device for signing.

First, you'll need to serialize the response from the api request (a [BuildOfflineSignModel](https://pystratis.readthedocs.io/en/latest/source/pystratis.api.global_responsemodels.html#pystratis.api.global_responsemodels.buildofflinesignmodel.BuildOfflineSignModel)).
```python
serialized_response = response.json()

# Write the serialized data to a file that can be moved over to the offline device with a thumbdrive.
offline_sign_file = 'offline_sign_model_file'
with open(offline_sign_file, 'w') as f:
f.write(serialized_response)
```

The next steps assume you are on the offline device (this should be done with swagger or the UI, but including here for completeness).
```python
from pystratis.nodes import StraxNode
from pystratis.api.wallet.responsemodels import BuildOfflineSignModel
import json
offline_node = StraxNode()
with open('offline_sign_model_file', 'r') as f:
data = json.load(f)
# Restore the json in to a BuildOfflineSignModel
offline_sign_model = BuildOfflineSignModel(**data)

response = offline_node.wallet.offline_sign_request(
wallet_password='password',
wallet_name='Test',
wallet_account='account 0',
unsigned_transaction=offline_sign_model.unsigned_transaction,
fee=offline_sign_model.fee,
utxos=offline_sign_model.utxos,
addresses=offline_sign_model.addresses
)

# The response of the offline_sign_request is a BuildTransactionModel.
# We are going to save the hex for importing back to the online computer for broadcasting.
signed_transaction_file = 'signed_transaction_file'
with open(signed_transaction_file, 'w') as f:
f.write(response.hex)
```

Back on the online node.
```python
from pystratis.core.types import hexstr
with open('signed_transaction_file') as f:
offline_transaction_hex = hexstr(f.readline())

# Broadcast the successfully built offline transaction
response = node.wallet.send_transaction(transaction_hex=offline_transaction_hex)
```
91 changes: 90 additions & 1 deletion tutorials/WalletBasics.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,91 @@
Wallet Basics with Pystratis
============================
============================
The Strax/Cirrus wallet is a full-featured HD wallet. This tutorial will cover some basics for setting up a new wallet on a StratisFullNode using pystratis.

Note: The same wallet will be used in all of the examples. The tutorial on [sending transactions can be found here.](https://github.com/stratisproject/pyStratis/blob/master/tutorials/SendingTransaction.md)

## Creating a wallet
```python
from pystratis.nodes import StraxNode
from typing import List

node = StraxNode()
# Returns the mnemonic representing the HD wallet seed.
mnemonic: List[str] = node.wallet.create(name='ExampleWallet', password='abc123')
```
## Loading a wallet
```python
# Loads a known wallet from the data dir with the given name and decrypts the wallet.
node.wallet.load(name='ExampleWallet', password='abc123')
```
## Recovering a wallet
```python
node.wallet.recover(mnemonic=mnemonic, password='abc123', name='RecoveredWallet')
```
## Listing wallets
```python
wallets: dict = node.wallet.list_wallets()
```
## Creating an account
```python
account_name = node.wallet.account(wallet_name='ExampleWallet', password='abc123')
```
## Listing wallet accounts
```python
node.wallet.accounts(wallet_name='ExampleWallet')
```
## Getting an unused address
```python
unused_address = node.wallet.unused_address(wallet_name='ExampleWallet')
```
## Getting all addresses
```python
addresses = node.wallet.addresses(wallet_name='ExampleWallet')
```
## Getting general information about a wallet
```python
node.wallet.general_info(name='ExampleWallet')
```
## Getting a wallet history
```python
history = node.wallet.history(wallet_name='ExampleWallet')
```
## Resyncing wallet
```python
node.wallet.remove_transactions(
wallet_name='ExampleWallet',
remove_all=True,
resync=True
)
```
## Getting the balance of a wallet
```python
wallet_balance = node.wallet.balance(
wallet_name='ExampleWallet',
include_balance_by_address=False
)
```
## Getting the balance of a specific wallet address
Note: The wallet does not track value in non-owned addresses. If you need to check the balance of a specific address on the blockchain:
- Ensure `addressindex=1` in node configuration.
- Use `blockstore.get_addresses_balances` or `blockstore.get_verbose_addresses_balances` API method.
```python
wallet_balance = node.wallet.balance(
wallet_name='ExampleWallet',
include_balance_by_address=True
)
```
## Signing and verifying a message
```python
signature = node.wallet.sign_message(
wallet_name='ExampleWallet',
password='abc123',
external_address=unused_address,
message='Blockchain made easy.'
)
assert node.wallet.verify_message(
signature=signature,
external_address=unused_address,
message='Blockchain made easy.'
)
```

0 comments on commit 5175156

Please sign in to comment.