Skip to content

Instantly share code, notes, and snippets.

@michaeljklein
Last active September 20, 2019 20:52
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save michaeljklein/30f51bc9e495394d6f7aab6bcd397ca1 to your computer and use it in GitHub Desktop.
Save michaeljklein/30f51bc9e495394d6f7aab6bcd397ca1 to your computer and use it in GitHub Desktop.

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:

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

❯❯❯ 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment