Skip to main content
Version: main branch

Transfer Tokens

Once your wallet contains some fungible and non-fungible tokens, you can try sending these tokens from one wallet account to another. This tutorial also demonstrates how to join fungible tokens of the same type into a single unit and how to update an NFT's data field.

Transfer Fungible Tokens

  1. Open the help output to find out what is needed to send fungible tokens:

    ./abwallet wallet token send fungible -h
    send fungible token

    Usage:
    abwallet wallet token send fungible [flags]

    Flags:
    -a, --address string compressed secp256k1 public key of the receiver in hexadecimal format, must start with 0x and be 68 characters in length
    --amount string amount, must be bigger than 0 and is interpreted according to token type precision (decimals)
    --bearer-clause-input string input to satisfy the bearer clause. Valid values are:
    [ true | false | empty ] - these will esentially mean "no argument"
    [ ptpkh | ptpkh:n ] - creates argument for the ptpkh predicate template using either default account key or account n key respectively
    @<filename> - load argument from file, the file content will be used as-is.
    (default "ptpkh")
    -h, --help help for fungible
    --inherit-bearer-input strings input to satisfy the owner predicates inherited from types. Valid values are:
    [ true | false | empty ] - these will esentially mean "no argument"
    [ ptpkh | ptpkh:n ] - creates argument for the ptpkh predicate template using either default account key or account n key respectively
    @<filename> - load argument from file, the file content will be used as-is.
    (default [true])
    -k, --key uint which key to use for sending the transaction (default 1)
    --type hex type unit identifier

    Global Flags:
    --config string config file URL (default is $AB_HOME/config.props)
    --home string set the AB_HOME for this invocation (default is /home/user/.alphabill)
    --log-file string log file path or one of the special values: stdout, stderr, discard
    --log-format string log format, one of: text, json, console
    --log-level string logging level, one of: DEBUG, INFO, WARN, ERROR
    --max-fee string maximum fee per transaction (in tema) (default "10")
    -p, --password password (interactive from prompt)
    --pn string password (non-interactive from args)
    --proof-output string save transaction proof to the file (if the file already exists it will be overwritten). This flag implicitly sets "wait-for-confirmation" to "true"
    -r, --rpc-url string rpc node url (default "localhost:28866")
    -w, --wait-for-confirmation string waits for transaction confirmation on the blockchain, otherwise just broadcasts the transaction (default "true")
    -l, --wallet-location string wallet home directory (default $AB_HOME/wallet)

    As seen in the help output, you must provide the following information to transfer fungible tokens:

    • --address: public key of the receiver.
    • --amount: amount of tokens to transfer.
    • --inherit-bearer-input: tokens inherit bearer clauses from their types, and possibly even the parents of their types. Therefore, you need a way to provide inputs to those predicates, all of which need to be satisfied. Use this flag to do so, and you can apply it multiple times as needed for the specific token type.
    • --key: your wallet's public key to use for sending the transaction.
    • --type: token type identifier of the tokens you are transferring. Remember, all tokens have exactly one type, but the symbol may be reused. Specify the token type identifier to be sure you are transferring the right kind of tokens.
  2. Transfer some of the tokens from your wallet's default account to the second account. The command to transfer tokens must always include an inherited bearer clause, even if it's just set to true. For the transaction to succeed, add inherited bearer inputs to those predicates—one for the parent and one for the subtype.

    ./abwallet wallet token send fungible \
    --address WALLET_PUBKEY_#2 \
    --amount 20.5 \
    --inherit-bearer-input true,true \
    --key 1 \
    --type FT_SUBTYPE_ID
    Example response:
    Paid 0.000'000'03 fees for transaction(s).
    important

    The order of the flags is important. If you switch the order of the --inherit-bearer-input and --key flags, it won't work. Within the --inherit-bearer-input line, the input for the most immediate "younger parent" must come first, followed by the input for the "grandparent". In this case, both inputs are the same, but as you will see later, this is not always the case.

  3. The following is required for the next step, where you join several fungible tokens into one unit. Transfer some additional tokens of the same type to your wallet's second account. Run the following command a total of five times:

    ./abwallet wallet token send fungible \
    --address WALLET_PUBKEY_#2 \
    --amount 1.25 \
    --inherit-bearer-input true,true \
    --key 1 \
    --type FT_SUBTYPE_ID

    This will result in transferring five fungible tokens from your wallet's default account to the second account, each with an amount of 1.25.

  4. Once the transactions succeed, check tokens in your wallet:

    • Tokens owned by your default account

      ./abwallet wallet token list --key 1
      Example response:
      Tokens owned by account #1
      ID='117A1F1712F2E07AC7157FE03A8C22D87661F5DC3158B3070052ED96EA20853803', symbol='FT_SUBTYPE', amount='73.250'000'00', token-type='0C7215CB12A2F76AF93315DCC1780BE7FD0F390CE84E5C91FA2BB5B91AFED05D01', locked='' (fungible)
      ID='0B78A411D497A66A96CCF8DAAE91B769DB4C9B46D7ADB61E6D00814EE26ABF9904', symbol='NFT_SUBTYPE', name='MY_NFT', token-type='66EE618CE91B8D78DFA7AEED9B7C9FCBA990D92E9FB5C21B20ADEE3FD297FFEB02', locked='' (nft)
    • Tokens owned by your second account

      ./abwallet wallet token list --key 2
      Example response:
      Tokens owned by account #2
      ID='B1E1F69C71C44BF9DFA38C8439EF6CB76708C493AB089DADAA61A4DBF70C562E03', symbol='FT_SUBTYPE', amount='20.500'000'00', token-type='0C7215CB12A2F76AF93315DCC1780BE7FD0F390CE84E5C91FA2BB5B91AFED05D01', locked='' (fungible)
      ID='2B33457E1010C49DC48DB5641B8F18BAB0352BD68FE007EE1BD0F5C9CFD38ED803', symbol='FT_SUBTYPE', amount='1.250'000'00', token-type='0C7215CB12A2F76AF93315DCC1780BE7FD0F390CE84E5C91FA2BB5B91AFED05D01', locked='' (fungible)
      ID='21A19C225843DBE79AF9BCB4D90412A57F42965B8810CBEC61F6D7B1B1159A5303', symbol='FT_SUBTYPE', amount='1.250'000'00', token-type='0C7215CB12A2F76AF93315DCC1780BE7FD0F390CE84E5C91FA2BB5B91AFED05D01', locked='' (fungible)
      ID='887DF5BD787045334446C07377CB5A20F59EC247B6E063D751052333BCF95CA103', symbol='FT_SUBTYPE', amount='1.250'000'00', token-type='0C7215CB12A2F76AF93315DCC1780BE7FD0F390CE84E5C91FA2BB5B91AFED05D01', locked='' (fungible)
      ID='EBD09BF453616262504EFA6E19DB04A4F2BBF2C346316E2EACCCB5801B23C69E03', symbol='FT_SUBTYPE', amount='1.250'000'00', token-type='0C7215CB12A2F76AF93315DCC1780BE7FD0F390CE84E5C91FA2BB5B91AFED05D01', locked='' (fungible)
      ID='6182138E5C31363A9B947396CF1EDE87D125AA988F44856540323B2F3B91072403', symbol='FT_SUBTYPE', amount='1.250'000'00', token-type='0C7215CB12A2F76AF93315DCC1780BE7FD0F390CE84E5C91FA2BB5B91AFED05D01', locked='' (fungible)

Join Fungible Tokens

Alphabill provides a feature to join fungible tokens of the same type into a single unit. This process, known as "dust collection", replaces smaller token units with a single unit that has the same total amount of tokens. Alphabill uses an inbuilt swap mechanism to allow users to reduce the number of tokens their wallet needs to hold. This reduces the amount of dust in the system, resulting in better network performance and less space used by user wallets.

  1. Open the help output to see available options for the wallet token collect-dust subcommand:

    ./abwallet wallet token collect-dust -h
    join fungible tokens into one unit

    Usage:
    abwallet wallet token collect-dust [flags]

    Flags:
    --bearer-clause-input string input to satisfy the bearer clause. Valid values are:
    [ true | false | empty ] - these will esentially mean "no argument"
    [ ptpkh | ptpkh:n ] - creates argument for the ptpkh predicate template using either default account key or account n key respectively
    @<filename> - load argument from file, the file content will be used as-is.
    (default "ptpkh")
    -h, --help help for collect-dust
    --inherit-bearer-input strings input to satisfy the owner predicates inherited from types. Valid values are:
    [ true | false | empty ] - these will esentially mean "no argument"
    [ ptpkh | ptpkh:n ] - creates argument for the ptpkh predicate template using either default account key or account n key respectively
    @<filename> - load argument from file, the file content will be used as-is.
    (default [true])
    -k, --key uint which key to use for dust collection, 0 for all tokens from all accounts
    --type strings type unit identifier (hex)

    Global Flags:
    --config string config file URL (default is $AB_HOME/config.props)
    --home string set the AB_HOME for this invocation (default is /home/user/.alphabill)
    --log-file string log file path or one of the special values: stdout, stderr, discard
    --log-format string log format, one of: text, json, console
    --log-level string logging level, one of: DEBUG, INFO, WARN, ERROR
    --max-fee string maximum fee per transaction (in tema) (default "10")
    -p, --password password (interactive from prompt)
    --pn string password (non-interactive from args)
    --proof-output string save transaction proof to the file (if the file already exists it will be overwritten). This flag implicitly sets "wait-for-confirmation" to "true"
    -r, --rpc-url string rpc node url (default "localhost:28866")
    -w, --wait-for-confirmation string waits for transaction confirmation on the blockchain, otherwise just broadcasts the transaction (default "true")
    -l, --wallet-location string wallet home directory (default $AB_HOME/wallet)

    As the output shows, you must satisfy the type's invariant clause, specify that the dust collection must be done with tokens owned by your wallet's second account, and provide the token type ID.

  2. Check the tokens owned by your second account:

    ./abwallet wallet token list --key 2
    Example response:
    Tokens owned by account #2
    ID='B1E1F69C71C44BF9DFA38C8439EF6CB76708C493AB089DADAA61A4DBF70C562E03', symbol='FT_SUBTYPE', amount='20.500'000'00', token-type='0C7215CB12A2F76AF93315DCC1780BE7FD0F390CE84E5C91FA2BB5B91AFED05D01', locked='' (fungible)
    ID='2B33457E1010C49DC48DB5641B8F18BAB0352BD68FE007EE1BD0F5C9CFD38ED803', symbol='FT_SUBTYPE', amount='1.250'000'00', token-type='0C7215CB12A2F76AF93315DCC1780BE7FD0F390CE84E5C91FA2BB5B91AFED05D01', locked='' (fungible)
    ID='21A19C225843DBE79AF9BCB4D90412A57F42965B8810CBEC61F6D7B1B1159A5303', symbol='FT_SUBTYPE', amount='1.250'000'00', token-type='0C7215CB12A2F76AF93315DCC1780BE7FD0F390CE84E5C91FA2BB5B91AFED05D01', locked='' (fungible)
    ID='887DF5BD787045334446C07377CB5A20F59EC247B6E063D751052333BCF95CA103', symbol='FT_SUBTYPE', amount='1.250'000'00', token-type='0C7215CB12A2F76AF93315DCC1780BE7FD0F390CE84E5C91FA2BB5B91AFED05D01', locked='' (fungible)
    ID='EBD09BF453616262504EFA6E19DB04A4F2BBF2C346316E2EACCCB5801B23C69E03', symbol='FT_SUBTYPE', amount='1.250'000'00', token-type='0C7215CB12A2F76AF93315DCC1780BE7FD0F390CE84E5C91FA2BB5B91AFED05D01', locked='' (fungible)
    ID='6182138E5C31363A9B947396CF1EDE87D125AA988F44856540323B2F3B91072403', symbol='FT_SUBTYPE', amount='1.250'000'00', token-type='0C7215CB12A2F76AF93315DCC1780BE7FD0F390CE84E5C91FA2BB5B91AFED05D01', locked='' (fungible)
  3. Before you can join these fungible tokens, add some funds and then fee credit to your second account:

    • Transfer funds to your wallet's second account:

      ./abwallet wallet send \
      --address WALLET_PUBKEY_#2 \
      --amount 2
    • Add funds to the fee credit balance of your wallet's second account

      ./abwallet wallet fees add \
      --partition tokens \
      --key 2
  4. Run the following command to join these tokens into a single unit:

    ./abwallet wallet token collect-dust \
    --inherit-bearer-input true,true \
    --key 2 \
    --type FT_SUBTYPE_ID
    Example response:
    Paid 0.000'000'20 fees for dust collection on Account number 2.
  5. Now check the balance again to confirm that tokens are joined into a single unit:

    ./abwallet wallet token list --key 2
    Example response:
    Tokens owned by account #2
    ID='B1E1F69C71C44BF9DFA38C8439EF6CB76708C493AB089DADAA61A4DBF70C562E03', symbol='FT_SUBTYPE', amount='26.750'000'00', token-type='0C7215CB12A2F76AF93315DCC1780BE7FD0F390CE84E5C91FA2BB5B91AFED05D01', locked='' (fungible)

Transfer Non-Fungible Tokens

  1. Open the help output to see what options are available for transferring non-fungible tokens:

    ./abwallet wallet token send non-fungible -h
    transfer non-fungible token

    Usage:
    abwallet wallet token send non-fungible [flags]

    Flags:
    -a, --address string compressed secp256k1 public key of the receiver in hexadecimal format, must start with 0x and be 68 characters in length
    --bearer-clause-input string input to satisfy the bearer clause. Valid values are:
    [ true | false | empty ] - these will esentially mean "no argument"
    [ ptpkh | ptpkh:n ] - creates argument for the ptpkh predicate template using either default account key or account n key respectively
    @<filename> - load argument from file, the file content will be used as-is.
    (default "ptpkh")
    -h, --help help for non-fungible
    --inherit-bearer-input strings input to satisfy the owner predicates inherited from types. Valid values are:
    [ true | false | empty ] - these will esentially mean "no argument"
    [ ptpkh | ptpkh:n ] - creates argument for the ptpkh predicate template using either default account key or account n key respectively
    @<filename> - load argument from file, the file content will be used as-is.
    (default [true])
    -k, --key uint which key to use for sending the transaction (default 1)
    --token-identifier hex token identifier

    Global Flags:
    --config string config file URL (default is $AB_HOME/config.props)
    --home string set the AB_HOME for this invocation (default is /home/user/.alphabill)
    --log-file string log file path or one of the special values: stdout, stderr, discard
    --log-format string log format, one of: text, json, console
    --log-level string logging level, one of: DEBUG, INFO, WARN, ERROR
    --max-fee string maximum fee per transaction (in tema) (default "10")
    -p, --password password (interactive from prompt)
    --pn string password (non-interactive from args)
    --proof-output string save transaction proof to the file (if the file already exists it will be overwritten). This flag implicitly sets "wait-for-confirmation" to "true"
    -r, --rpc-url string rpc node url (default "localhost:28866")
    -w, --wait-for-confirmation string waits for transaction confirmation on the blockchain, otherwise just broadcasts the transaction (default "true")
    -l, --wallet-location string wallet home directory (default $AB_HOME/wallet)

    The difference from fungible tokens is that with NFTs there is no --amount and --type flags, but instead you must provide the ID of the NFT (--token-identifier) you want to transfer.

  2. Transfer the NFT_SUBTYPE to your wallet's second account:

    ./abwallet wallet token send non-fungible \
    --address WALLET_PUBKEY_#2 \
    --inherit-bearer-input true,true \
    --token-identifier NFT_ID
    Example response:
    Paid 0.000'000'03 fees for transaction(s).
  3. Confirm the transaction:

    ./abwallet wallet token list --key 2
    Example response:
    Tokens owned by account #2
    ID='B1E1F69C71C44BF9DFA38C8439EF6CB76708C493AB089DADAA61A4DBF70C562E03', symbol='FT_SUBTYPE', amount='26.750'000'00', token-type='0C7215CB12A2F76AF93315DCC1780BE7FD0F390CE84E5C91FA2BB5B91AFED05D01', locked='' (fungible)
    ID='0B78A411D497A66A96CCF8DAAE91B769DB4C9B46D7ADB61E6D00814EE26ABF9904', symbol='NFT_SUBTYPE', name='MY_NFT', token-type='66EE618CE91B8D78DFA7AEED9B7C9FCBA990D92E9FB5C21B20ADEE3FD297FFEB02', locked='' (nft)

Update Non-Fungible Tokens

NFT types have a mutable data field, which can be changed if the data update predicate is satisfied. You can use the data field to implement a state machine and enforce business processes or procedures. However, the full functionality of this clause will only be realized once a fully programmable predicate language is introduced in the future.

  1. Check the help output to see which information you need to provide:

    ./abwallet wallet token update -h
    update the data field on a non-fungible token

    Usage:
    abwallet wallet token update [flags]

    Flags:
    --data hex custom data (hex). Alternatively flag "data-file" can be used to add data.
    --data-file string data file (max 64Kb) path. Alternatively flag "data" can be used to add data.
    --data-update-input string input to satisfy the token's data-update clause. Valid values are:
    [ true | false | empty ] - these will esentially mean "no argument"
    [ ptpkh | ptpkh:n ] - creates argument for the ptpkh predicate template using either default account key or account n key respectively
    @<filename> - load argument from file, the file content will be used as-is.
    (default "true")
    -h, --help help for update
    --inherit-data-update-input strings input to satisfy the data-update clauses of inherited types. Valid values are:
    [ true | false | empty ] - these will esentially mean "no argument"
    [ ptpkh | ptpkh:n ] - creates argument for the ptpkh predicate template using either default account key or account n key respectively
    @<filename> - load argument from file, the file content will be used as-is.
    (default [true])
    -k, --key uint which key to use for sending the transaction (default 1)
    --token-identifier hex token identifier

    Global Flags:
    --config string config file URL (default is $AB_HOME/config.props)
    --home string set the AB_HOME for this invocation (default is /home/user/.alphabill)
    --log-file string log file path or one of the special values: stdout, stderr, discard
    --log-format string log format, one of: text, json, console
    --log-level string logging level, one of: DEBUG, INFO, WARN, ERROR
    --max-fee string maximum fee per transaction (in tema) (default "10")
    -p, --password password (interactive from prompt)
    --pn string password (non-interactive from args)
    --proof-output string save transaction proof to the file (if the file already exists it will be overwritten). This flag implicitly sets "wait-for-confirmation" to "true"
    -r, --rpc-url string rpc node url (default "localhost:28866")
    -w, --wait-for-confirmation string waits for transaction confirmation on the blockchain, otherwise just broadcasts the transaction (default "true")
    -l, --wallet-location string wallet home directory (default $AB_HOME/wallet)

    Using the --data flag allowed you attaching arbitrary binary data to an NFT. Now it's also possible to change it using the same flag.

  2. The data field is not displayed, as it can be up to 64 Kb in length. But you can update it using the following command:

    ./abwallet wallet token update \
    --data CUSTOM_HEX_STRING \
    --data-update-input ptpkh,true,true \
    --inherit-data-update-input true,true \
    --token-identifier NFT_ID
    Example response:
    Paid 0.000'000'03 fees for transaction(s).

    You can see that in the --data-update-input there are three inputs. This is because the token itself has a data update clause, which was ptpkh as specified during creation. The other two true inputs are for the NFT_SUBTYPE, and the NFT_PARENT_TYPE, which were both created with default true clauses.

    Currently, there is no way to view the data installed. However, since the transaction completed, we know that the update also occurred.