Pytezos Quickstart

FA1.2.1 with PyTezos

This is a brief introduction to interacting with FA1.2.1 using PyTezos.

Install PyTezos

If on Linux, install dependencies with apt:

❯❯❯ sudo apt install libsodium-dev libsecp256k1-dev libgmp-dev

Otherwise, if on Mac OS, install them with brew:

❯❯❯ brew tap cuber/homebrew-libsecp256k1
❯❯❯ brew install libsodium libsecp256k1 gmp

Next, install PyTezos with pip3:

❯❯❯ pip3 install pytezos

Originate the contract

Assuming that $ALICE_ADDRESS is defined, we can originate a copy of FA1.2.1 in much the same way as with lorentz-contracts.

Note: See the Client Setup Guide to make a wallet and define $ALICE_ADDRESS.

We begin by opening python3, setting the key and shell, and specifying fa121 as a contract:

Note: replace ~/Downloads/tz1R3vJ5TV8Y5pVj8dicBR23Zv8JArusDkYr.json with the path to your wallet .json file.

❯❯❯ python3
Python 3.7.4 (default, Sep 7 2019, 18:27:02)
[Clang 10.0.1 (clang-1001.0.46.4)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> from pytezos import pytezos
>>> pytezos = pytezos.using(key='~/Downloads/tz1R3vJ5TV8Y5pVj8dicBR23Zv8JArusDkYr.json', shell='alphanet')
>>> alice_address = 'tz1R3vJ5TV8Y5pVj8dicBR23Zv8JArusDkYr'
>>> bob_address = 'tz1bDCu64RmcpWahdn9bWrDMi6cu7mXZynHm'
>>> nat_storage_address = 'KT1J4mhVAaMYAPC4aPEkhC9i48PHBHxavkJ2'

Next, we define a Contract from a copy of ManagedLedgerAthens.tz with annotations:

>>> from pytezos import Contract
>>> import requests
>>> contract_url = 'https://gist.githubusercontent.com/michaeljklein/f07abd00324633fc6554809179601104/raw/918db534066317085520a1d6d1157338101573d3/ManagedLedgerAthens.tz'
>>> fa121_code = Contract.from_michelson(requests.get(contract_url).text)

We need to create the initial storage to originate the contract.

To help with this, we can view the contract's storage schema:

>>> fa121_code.storage
<pytezos.michelson.contract.ContractStorage object at 0x1114c6c10>
$storage:
[ { $address : $0_item , ... } /* big_map */ , $address , bool , $nat , $4 ]
$4:
{ "proxy_admin": $address /* proxy_admin */ } ||
{ "proxy_contract": $address /* proxy_contract */ }
$0_item:
{
"value": $nat,
"approval_ledger": { $address : $nat , ... }
}
$address:
string /* Base58 encoded `tz` or `KT` address */
$nat:
int /* Natural number */
Helpers
.big_map_decode()
.big_map_diff_decode()
.big_map_diff_encode()
.big_map_query()
.decode()
.default()
.encode()

Next, we choose initial storage values:

[{} , alice_address, False , 24 , {'proxy_admin': alice_address}]
[ledger, admin_address, is_paused, total_supply, proxy_admin_or_contract ]

And encode it in PyTezos:

>>> init_storage = fa121_code.storage.encode([{}, alice_address, False, 24, {'proxy_admin': alice_address}])
>>> import json
>>> print(json.dumps(init_storage, indent=2))
{
"prim": "Pair",
"args": [
[],
{
"prim": "Pair",
"args": [
{
"prim": "Pair",
"args": [
{
"string": "tz1R3vJ5TV8Y5pVj8dicBR23Zv8JArusDkYr"
},
{
"prim": "False"
}
]
},
{
"prim": "Pair",
"args": [
{
"int": "24"
},
{
"prim": "Left",
"args": [
{
"string": "tz1R3vJ5TV8Y5pVj8dicBR23Zv8JArusDkYr"
}
]
}
]
}
]
}
]
}

Next, we originate the contract:

>>> origination_op = pytezos.origination(script={'code': fa121_code.code, 'storage': init_storage}).autofill().sign().inject()
>>> origination_op
'oojjqXPb4BEmTi8ZLHvQa8wL3s8hXNcAfnLqJx1Ld4KjhRJZ6zf'

However, the origination does not return the resulting contract's address, so we need to find the completed operation in the network and derive the contract's address from it:

>>> opg = pytezos.shell.blocks[-5:].find_operation(origination_op)
>>> contract_id = opg['contents'][0]['metadata']['operation_result']['originated_contracts'][0]
>>> contract_id
'KT1GV7wkUgZpEznQ48VR1UUSgXAU5Lk1SfpH'
>>> fa121 = pytezos.contract(contract_id)

Note: the find_operation command can fail if the operation has not yet been processed:

>>> opg = pytezos.shell.blocks[-5:].find_operation(origination_op)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/local/lib/python3.7/site-packages/pytezos/tools/docstring.py", line 67, in __call__
return method(self.class_instance, *args, **kwargs)
File "/usr/local/lib/python3.7/site-packages/pytezos/rpc/search.py", line 207, in find_operation
raise StopIteration(operation_group_hash)
StopIteration: oonhxpbH9Jc7fpbjfzvfbetppEBnjUTFmkbziVFV3B5KhjVdzpd

Inspect the Schema

We can view the parameter schema to find out which entry points the contract supports and what their arguments are:

>>> fa121.contract.parameter
<pytezos.michelson.contract.ContractParameter object at 0x11106bc90>
$parameter:
{ "transfer": $transfer } ||
{ "transfer_via_proxy": $transfer_via_proxy } ||
{ "approve": $approve } ||
{ "approve_via_proxy": $approve_via_proxy } ||
{ "get_allowance": $get_allowance } ||
{ "get_balance": $get_balance } ||
{ "get_total_supply": $get_total_supply } ||
{ "set_pause": bool /* setPause */ } ||
{ "set_administrator": $address /* setAdministrator */ } ||
{ "get_administrator": $get_administrator } ||
{ "mint": $mint } ||
{ "burn": $burn } ||
{ "set_proxy": $address /* setProxy */ }
$burn:
{
"from": $address,
"value": $nat
}
$mint:
{
"to": $address,
"value": $nat
}
$get_administrator:
{
"get_administrator": $unit,
"administrator_callback": $contract (address)
}
$get_total_supply:
{
"get_total_supply": $unit,
"total_supply_callback": $contract (nat)
}
$get_balance:
{
"owner": $address,
"balance_callback": $contract (nat)
}
$get_allowance:
{
"owner": $address,
"spender": $address,
"allowance_callback": $contract (nat)
}
$approve_via_proxy:
{
"sender": $address,
"spender": $address,
"value": $nat
}
$approve:
{
"spender": $address,
"value": $nat
}
$transfer_via_proxy:
{
"sender": $address,
"from": $address,
"to": $address,
"value": $nat
}
$transfer:
{
"from": $address,
"to": $address,
"value": $nat
}
$address:
string /* Base58 encoded `tz` or `KT` address */
$contract:
string /* Base58 encoded `KT` address */
$unit:
None /* Void */
$nat:
int /* Natural number */
Helpers
.decode()
.encode()
.entries()

Interacting with FA1.2.1

Finally, using the parameter schema, we can perform a transfer using the following steps:

  • Mint 5 tokens to alice_address
  • Transfer 3 tokens from alice_address to bob_address
  • Store bob_address's balance in nat_storage_address
  • Get the storage value of nat_storage_address
>>> fa121.mint(to=alice_address, value=5).inject()
'oo6zxZbjQTKyvw4KWs98AvtRdRcF4CMTzQ5np23ZevdGk15pQCh'
>>> fa121.transfer(**{'from': alice_address, 'to': bob_address, 'value': 3}).inject()
'ooQRsxqkV3vEtaDdarMzvzFuZLeKe6qfJkZEx9PntzqHGxVo8FU'
>>> fa121.get_balance(bob_address, nat_storage_address).inject()
'opUVaphfSFgeUTACVmspr6bXrY82RLJ4mSf4DgT2kNZ6SiEcuh6'
>>> pytezos.contract(nat_storage_address).storage()
3

Note: Why .transfer(**{'from': ..}) instead of .transfer(from=..)? In Python, from is a keyword and may not be used to specify an argument so we use **{..} to convert a dictionary into keyword arguments.