Tutorials
No Results
Tutorial

Beginner · 1 hour

Working with ASA using Python

This tutorial demonstrates the steps involved in working with a basic Algorand Standard Asset (ASA) using the Python SDK. This tutorial includes Asset Reconfigure, Opt-In, Asset Transfer, Asset Freeze, Asset Revoke, and Asset Destroy.

Completed code for this tutorial can be found here asset_example.py.

Requirements

Optional:

Background

Algorand Standard Assets (ASA) allow developers to create fungible and non-fungible assets with a few method calls. ASA’s are highly customizable with parameters that allow developers to define total issuance of an asset, name of the asset, units of an asset, as well as access controls privileges over an asset.

There are a few items to be aware of before getting started with assets as documented in the ASA feature guide overview on the developer portal.

Also, see more information on decimals and fees.

Steps

Part 1: Create an Algorand Standard Asset

This part demonstrates the steps involved in creating an asset.

Step 1-1. Instantiate Algod Wrapper and Recover Accounts

In this step, we are passing in the token, server, and port values from a local node or local sandbox instance. You can also connect to remote node using a third-party serice.

Hardcoding and recovering accounts in this way is not advised, for security purposes, but is sufficient for this tutorial.

Note: If you have not already done so, use this tutorial to Create 3 Accounts

This tutorial will use TestNet accounts that have been pre-created in the Create Accounts tutorial. Be sure to dispense Algos to these accounts before continuing, using the TestNet Dispenser.

import json
from algosdk.v2client import algod
from algosdk import account, mnemonic
from algosdk.future.transaction import AssetConfigTxn, AssetTransferTxn, AssetFreezeTxn

# Shown for demonstration purposes. NEVER reveal secret mnemonics in practice.
# Change these values with your mnemonics
mnemonic1 = "PASTE your phrase for account 1"
mnemonic2 = "PASTE your phrase for account 2"
mnemonic3 = "PASTE your phrase for account 3"

# For ease of reference, add account public and private keys to
# an accounts dict.
accounts = {}
counter = 1
for m in [mnemonic1, mnemonic2, mnemonic3]:
    accounts[counter] = {}
    accounts[counter]['pk'] = mnemonic.to_public_key(m)
    accounts[counter]['sk'] = mnemonic.to_private_key(m)
    counter += 1

# Specify your node address and token. This must be updated.

algod_address = "http://localhost:4001"
algod_token = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"

# Initialize an algod client
algod_client = algod.AlgodClient(algod_token=algod_token, algod_address=algod_address)


Learn More
- Connect to an Algorand Node
- Algorand Node Setup

Step 1-2. Print Accounts, utility functions and structures

def wait_for_confirmation(client, txid):
    """
    Utility function to wait until the transaction is
    confirmed before proceeding.
    """
    last_round = client.status().get('last-round')
    txinfo = client.pending_transaction_info(txid)
    while not (txinfo.get('confirmed-round') and txinfo.get('confirmed-round') > 0):
        print("Waiting for confirmation")
        last_round += 1
        client.status_after_block(last_round)
        txinfo = client.pending_transaction_info(txid)
    print("Transaction {} confirmed in round {}.".format(txid, txinfo.get('confirmed-round')))
    return txinfo

#   Utility function used to print created asset for account and assetid
def print_created_asset(algodclient, account, assetid):    
    # note: if you have an indexer instance available it is easier to just use this
    # response = myindexer.accounts(asset_id = assetid)
    # then use 'account_info['created-assets'][0] to get info on the created asset
    account_info = algodclient.account_info(account)
    idx = 0;
    for my_account_info in account_info['created-assets']:
        scrutinized_asset = account_info['created-assets'][idx]
        idx = idx + 1       
        if (scrutinized_asset['index'] == assetid):
            print("Asset ID: {}".format(scrutinized_asset['index']))
            print(json.dumps(my_account_info['params'], indent=4))
            break

#   Utility function used to print asset holding for account and assetid
def print_asset_holding(algodclient, account, assetid):
    # note: if you have an indexer instance available it is easier to just use this
    # response = myindexer.accounts(asset_id = assetid)
    # then loop thru the accounts returned and match the account you are looking for
    account_info = algodclient.account_info(account)
    idx = 0
    for my_account_info in account_info['assets']:
        scrutinized_asset = account_info['assets'][idx]
        idx = idx + 1        
        if (scrutinized_asset['asset-id'] == assetid):
            print("Asset ID: {}".format(scrutinized_asset['asset-id']))
            print(json.dumps(scrutinized_asset, indent=4))
            break

print("Account 1 address: {}".format(accounts[1]['pk']))
print("Account 2 address: {}".format(accounts[2]['pk']))
print("Account 3 address: {}".format(accounts[3]['pk']))


Learn More
- Algorand Standalone Account Creation Methods

Step 1-3. Create Transaction

We need to define the Create Asset Transaction parameters.

  • Configure fields for creating the asset.

  • Account 1 creates an asset called latinum and sets Account 2 as the manager, reserve, freeze, and clawback address.

# CREATE ASSET
# Get network params for transactions before every transaction.
params = algod_client.suggested_params()
# comment these two lines if you want to use suggested params
params.fee = 1000
params.flat_fee = True

# Account 1 creates an asset called latinum and
# sets Account 2 as the manager, reserve, freeze, and clawback address.
# Asset Creation transaction

txn = AssetConfigTxn(
    sender=accounts[1]['pk'],
    sp=params,
    total=1000,
    default_frozen=False,
    unit_name="LATINUM",
    asset_name="latinum",
    manager=accounts[2]['pk'],
    reserve=accounts[2]['pk'],
    freeze=accounts[2]['pk'],
    clawback=accounts[2]['pk'],
    url="https://path/to/my/asset/details", 
    decimals=0)


Learn More
- Algorand Asset Parameters
- Common Transaction Fields

Step 1-4. Sign Transaction

Sign with the secret key of the creator, Account 1.

# Sign with secret key of creator
stxn = txn.sign(accounts[1]['sk'])

Step 1-5. Send Create ASA Transaction to the Blockchain and print the Tx ID

Send the transaction to the network and retrieve the txid. Copy off the Asset ID for use in subsequent parts.

# Send the transaction to the network and retrieve the txid.
txid = algod_client.send_transaction(stxn)
print(txid)

# Retrieve the asset ID of the newly created asset by first
# ensuring that the creation transaction was confirmed,
# then grabbing the asset id from the transaction.

# Wait for the transaction to be confirmed
wait_for_confirmation(algod_client,txid)

try:
    # Pull account info for the creator
    # account_info = algod_client.account_info(accounts[1]['pk'])
    # get asset_id from tx
    # Get the new asset's information from the creator account
    ptx = algod_client.pending_transaction_info(txid)
    asset_id = ptx["asset-index"]
    print_created_asset(algod_client, accounts[1]['pk'], asset_id)
    print_asset_holding(algod_client, accounts[1]['pk'], asset_id)
except Exception as e:
    print(e)

Step 1-6. Check the transaction on a block explorer

Once you’ve completed these steps your output should look like this:

# your terminal output should look similar to the following
Account 1 address: ATTR6RUEHHBHXKUHT4GUOYWNBVDV2GJ5FHUWCSFZLHD55EVKZWOWSM7ABQ
Account 2 address: AK6Q33PDO4RJZQPHEMODC6PUE5AR2UD4FBU6TNEJOU4UR4KC6XL5PWW5K4
Account 3 address: IWR4CLLCN2TIVX2QPVVKVR5ER5OZGMWAV5QB2UIPYMPKBPLJZX4C37C4AA
Transaction WVG5HSCU7OIMFHLQGMPJF3NZ56A6FE3DMFUNBUKH73ZUMIU3N3HA confirmed in round 3982822.
Waiting for confirmation...
Transaction DDDNZWERVG54J32PLCJQENLC5FAFIFYY6ZSYRY25C3J26TJKJ5IA confirmed in round 3982906.
Asset ID: 2653870
{
    "clawback": "AK6Q33PDO4RJZQPHEMODC6PUE5AR2UD4FBU6TNEJOU4UR4KC6XL5PWW5K4",
    "creator": "ATTR6RUEHHBHXKUHT4GUOYWNBVDV2GJ5FHUWCSFZLHD55EVKZWOWSM7ABQ",
    "decimals": 0,
    "default-frozen": false,
    "freeze": "AK6Q33PDO4RJZQPHEMODC6PUE5AR2UD4FBU6TNEJOU4UR4KC6XL5PWW5K4",
    "manager": "AK6Q33PDO4RJZQPHEMODC6PUE5AR2UD4FBU6TNEJOU4UR4KC6XL5PWW5K4",
    "metadata-hash": "MTZlZmFhMzkyNGE2ZmQ5ZDNhNDgyNDc5OWE0YWM2NWQ=",
    "name": "latinum",
    "reserve": "AK6Q33PDO4RJZQPHEMODC6PUE5AR2UD4FBU6TNEJOU4UR4KC6XL5PWW5K4",
    "total": 1000,
    "unit-name": "LATINUM",
    "url": "https://path/to/my/asset/details"
}
Asset ID: 2653870
{
    "amount": 1000,
    "asset-id": 2653870,
    "creator": "ATTR6RUEHHBHXKUHT4GUOYWNBVDV2GJ5FHUWCSFZLHD55EVKZWOWSM7ABQ",
    "is-frozen": false
}

You can check the asset creation transaction on a block explorer for reference.

Note: Copy off the AssetID , as it will be used in subsequent steps


Learn More
- Algorand Block Explorers

Step 1-7. Complete Example

Here is the completed code to create an Asset.

import json
from algosdk.v2client import algod
from algosdk import account, mnemonic
from algosdk.future.transaction import AssetConfigTxn, AssetTransferTxn, AssetFreezeTxn


# Shown for demonstration purposes. NEVER reveal secret mnemonics in practice.
# Change these values with your mnemonics
mnemonic1 = "PASTE your phrase for account 1"
mnemonic2 = "PASTE your phrase for account 2"
mnemonic3 = "PASTE your phrase for account 3"


# For ease of reference, add account public and private keys to
# an accounts dict.
accounts = {}
counter = 1
for m in [mnemonic1, mnemonic2, mnemonic3]:
    accounts[counter] = {}
    accounts[counter]['pk'] = mnemonic.to_public_key(m)
    accounts[counter]['sk'] = mnemonic.to_private_key(m)
    counter += 1

# Specify your node address and token. This must be updated.
# algod_address = ""  # ADD ADDRESS
# algod_token = ""  # ADD TOKEN

algod_address = "http://localhost:4001"
algod_token = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"

# Initialize an algod client
algod_client = algod.AlgodClient(algod_token=algod_token, algod_address=algod_address)

def wait_for_confirmation(client, txid):
    """
    Utility function to wait until the transaction is
    confirmed before proceeding.
    """
    last_round = client.status().get('last-round')
    txinfo = client.pending_transaction_info(txid)
    while not (txinfo.get('confirmed-round') and txinfo.get('confirmed-round') > 0):
        print("Waiting for confirmation")
        last_round += 1
        client.status_after_block(last_round)
        txinfo = client.pending_transaction_info(txid)
    print("Transaction {} confirmed in round {}.".format(txid, txinfo.get('confirmed-round')))
    return txinfo

#   Utility function used to print created asset for account and assetid
def print_created_asset(algodclient, account, assetid):    
    # note: if you have an indexer instance available it is easier to just use this
    # response = myindexer.accounts(asset_id = assetid)
    # then use 'account_info['created-assets'][0] to get info on the created asset
    account_info = algodclient.account_info(account)
    idx = 0;
    for my_account_info in account_info['created-assets']:
        scrutinized_asset = account_info['created-assets'][idx]
        idx = idx + 1       
        if (scrutinized_asset['index'] == assetid):
            print("Asset ID: {}".format(scrutinized_asset['index']))
            print(json.dumps(my_account_info['params'], indent=4))
            break

#   Utility function used to print asset holding for account and assetid
def print_asset_holding(algodclient, account, assetid):
    # note: if you have an indexer instance available it is easier to just use this
    # response = myindexer.accounts(asset_id = assetid)
    # then loop thru the accounts returned and match the account you are looking for
    account_info = algodclient.account_info(account)
    idx = 0
    for my_account_info in account_info['assets']:
        scrutinized_asset = account_info['assets'][idx]
        idx = idx + 1        
        if (scrutinized_asset['asset-id'] == assetid):
            print("Asset ID: {}".format(scrutinized_asset['asset-id']))
            print(json.dumps(scrutinized_asset, indent=4))
            break

print("Account 1 address: {}".format(accounts[1]['pk']))
print("Account 2 address: {}".format(accounts[2]['pk']))
print("Account 3 address: {}".format(accounts[3]['pk']))



# CREATE ASSET
# Get network params for transactions before every transaction.
params = algod_client.suggested_params()
# comment these two lines if you want to use suggested params
params.fee = 1000
params.flat_fee = True

# Account 1 creates an asset called latinum and
# sets Account 2 as the manager, reserve, freeze, and clawback address.
# Asset Creation transaction

txn = AssetConfigTxn(
    sender=accounts[1]['pk'],
    sp=params,
    total=1000,
    default_frozen=False,
    unit_name="LATINUM",
    asset_name="latinum",
    manager=accounts[2]['pk'],
    reserve=accounts[2]['pk'],
    freeze=accounts[2]['pk'],
    clawback=accounts[2]['pk'],
    url="https://path/to/my/asset/details", 
    decimals=0)
# Sign with secret key of creator
stxn = txn.sign(accounts[1]['sk'])

# Send the transaction to the network and retrieve the txid.
txid = algod_client.send_transaction(stxn)
print(txid)

# Retrieve the asset ID of the newly created asset by first
# ensuring that the creation transaction was confirmed,
# then grabbing the asset id from the transaction.

# Wait for the transaction to be confirmed
wait_for_confirmation(algod_client,txid)

try:
    # Pull account info for the creator
    # account_info = algod_client.account_info(accounts[1]['pk'])
    # get asset_id from tx
    # Get the new asset's information from the creator account
    ptx = algod_client.pending_transaction_info(txid)
    asset_id = ptx["asset-index"]
    print_created_asset(algod_client, accounts[1]['pk'], asset_id)
    print_asset_holding(algod_client, accounts[1]['pk'], asset_id)
except Exception as e:
    print(e)

Note: Copy off the AssetID , as it will be used in subsequent Parts.

Part 2: Change Manager Role

This part demonstrates the steps involved in changing / reconfiguring an asset, specifically the asset manager role.

Requirements

  • The AssetID of an Asset created in Part 1.

Background

ASA’s are highly customizable. Of the many characteristics, an asset has certain privileges associated with it including manger, freeze, clawback and reserve functionality. The manager of an asset is the only Algorand account that can destroy an asset and is the only account that can reconfigure the other admin roles of an asset. All other parameters are locked for the life of the asset.

Asset reconfiguration allows the address specified as manager to change any of the special addresses for the asset, such as the reserve address. To keep an address the same, it must be re-specified in each new configuration transaction. Supplying an empty address is the same as turning the associated feature off for this asset. Once a special address is set to the empty address, it can never change again. For example, if an asset configuration transaction specifying clawback=”” were issued, the associated asset could never be revoked from asset holders, and clawback=”” would be true for all time. The strict_empty_address_check argument can help with this behavior: when set to its default true, AssetConfigTxn will throw an error if any undefined management addresses are passed.

Step 2-1. Create Asset Configuration Transaction

The parameter that needs to be defined for an asset configuration transaction is the manager variable which is set to the new account that will become the new manager of the asset. Paste in your assetID from Part 1. The current manager (Account 2) issues an asset configuration transaction that assigns Account 1 as the new manager. Keep reserve, freeze, and clawback address the same as before, i.e. Account 2.

# The current manager(Account 2) issues an asset configuration transaction that assigns Account 1 as the new manager.
# Keep reserve, freeze, and clawback address same as before, i.e. account 2
params = algod_client.suggested_params()
# comment these two lines if you want to use suggested params
params.fee = 1000
params.flat_fee = True

asset_id = (your-Asset-ID-from-part-1);

txn = AssetConfigTxn(
    sender=accounts[2]['pk'],
    sp=params,
    index=asset_id, 
    manager=accounts[1]['pk'],
    reserve=accounts[2]['pk'],
    freeze=accounts[2]['pk'],
    clawback=accounts[2]['pk'])


Learn More
- Asset Configuration

Step 2-2. Sign Transaction

Sign by the current manager (Account 2)

# sign by the current manager - Account 2
stxn = txn.sign(accounts[2]['sk'])

Step 2-3. Send Transaction to the network

Broadcast the transaction to the blockchain.

txid = algod_client.send_transaction(stxn)
print(txid)

# Wait for the transaction to be confirmed
wait_for_confirmation(algod_client, txid)

Step 2-4. Print Asset Information

Check asset info to view change in management.

# Check asset info to view change in management. manager should now be account 1
print_created_asset(algod_client, accounts[1]['pk'], asset_id)

# 6. Check the transaction on the block explorer
Once you've completed these steps you're output should look something like this:

```zsh
Transaction Y7EYBJNFP7YPGCV7ZD47PMJZHXB2PRT3SZ534M7BZE7G55IMPKUA confirmed in round 3982910.
Asset ID: 2653870
{
    "clawback": "AK6Q33PDO4RJZQPHEMODC6PUE5AR2UD4FBU6TNEJOU4UR4KC6XL5PWW5K4",
    "creator": "ATTR6RUEHHBHXKUHT4GUOYWNBVDV2GJ5FHUWCSFZLHD55EVKZWOWSM7ABQ",
    "decimals": 0,
    "default-frozen": false,
    "freeze": "AK6Q33PDO4RJZQPHEMODC6PUE5AR2UD4FBU6TNEJOU4UR4KC6XL5PWW5K4",
    "manager": "ATTR6RUEHHBHXKUHT4GUOYWNBVDV2GJ5FHUWCSFZLHD55EVKZWOWSM7ABQ",
    "metadata-hash": "MTZlZmFhMzkyNGE2ZmQ5ZDNhNDgyNDc5OWE0YWM2NWQ=",
    "name": "latinum",
    "reserve": "AK6Q33PDO4RJZQPHEMODC6PUE5AR2UD4FBU6TNEJOU4UR4KC6XL5PWW5K4",
    "total": 1000,
    "unit-name": "LATINUM",
    "url": "https://path/to/my/asset/details"
}

You can check the asset config transaction on a block explorer for reference.


Learn More
- Algorand Block Explorers

Step 2-5. Complete Example

# CHANGE MANAGER

# The current manager(Account 2) issues an asset configuration transaction that assigns Account 1 as the new manager.
# Keep reserve, freeze, and clawback address same as before, i.e. account 2
params = algod_client.suggested_params()
# comment these two lines if you want to use suggested params
params.fee = 1000
params.flat_fee = True

# asset_id = 328952;

txn = AssetConfigTxn(
    sender=accounts[2]['pk'],
    sp=params,
    index=asset_id, 
    manager=accounts[1]['pk'],
    reserve=accounts[2]['pk'],
    freeze=accounts[2]['pk'],
    clawback=accounts[2]['pk'])
# sign by the current manager - Account 2
stxn = txn.sign(accounts[2]['sk'])
txid = algod_client.send_transaction(stxn)
print(txid)

# Wait for the transaction to be confirmed
wait_for_confirmation(algod_client, txid)

# Check asset info to view change in management. manager should now be account 1
print_created_asset(algod_client, accounts[1]['pk'], asset_id)

Part 3: Opt-in

Overview

This part demonstrates the steps involved in “opting” in to receive an Algorand Standard Asset (ASA) To receive an Algorand asset, you must explicitly “opt-in” to receive the asset using the method AssetTransferTxn. This sends a 0 amount of the asset to yourself (to the account wanting to receive the asset).


Learn More
- Minimum Account Balance Requirement

Requirements

  • The AssetID of an Asset created in Part 1.

Background

Holding an asset increases the size of your balance record. When you hold an asset, your balance record has an entry in its Assets map. map AssetIndex => AssetHolding . The balance record is the row in the database associated with an Algorand account. So having an asset increases the minimum balance requirement for your account by 0.1 ALGO’s. Having someone else increase your minimum balance would be a massive security compromise, so an account must elect to increase the size of their own balance record.

Steps

Step 3-1. Define Opt-In Transaction

An asset can be reference by its assetName or its assetID which is generated when you create the asset. Given that the assetName is not unique, it is always recommended to reference an asset by its assetID, which is a unique value. This value was derived in Part 1.

# OPT-IN

# Check if asset_id is in account 3's asset holdings prior
# to opt-in
params = algod_client.suggested_params()
# comment these two lines if you want to use suggested params
params.fee = 1000
params.flat_fee = True

account_info = algod_client.account_info(accounts[3]['pk'])
holding = None
idx = 0
for my_account_info in account_info['assets']:
    scrutinized_asset = account_info['assets'][idx]
    idx = idx + 1    
    if (scrutinized_asset['asset-id'] == asset_id):
        holding = True
        break

if not holding:

    # Use the AssetTransferTxn class to transfer assets and opt-in
    txn = AssetTransferTxn(
        sender=accounts[3]['pk'],
        sp=params,
        receiver=accounts[3]["pk"],
        amt=0,
        index=asset_id)

Step 3-2. Sign Transaction

The transaction must be signed by the account wishing to opt-in to the asset, in this case - Account 3.

   stxn = txn.sign(accounts[3]['sk'])

Step 3-3. Send the Transaction to the network

Send the transaction to the network and print off account information.

    txid = algod_client.send_transaction(stxn)
    print(txid)
    # Wait for the transaction to be confirmed
    wait_for_confirmation(algod_client, txid)
    # Now check the asset holding for that account.
    # This should now show a holding with a balance of 0.
    print_asset_holding(algod_client, accounts[3]['pk'], asset_id)

Step 3-4. Check the transaction on a block explorer

Once you’ve completed these steps your output should look something like this:

Transaction ACYWQVRO6XKQNIHHGH7PDIPKPGURES6YA7OCI654PTR75RKTL4FA confirmed in round 3982915.
Asset ID: 2653870
{
    "amount": 0,
    "asset-id": 2653870,
    "creator": "ATTR6RUEHHBHXKUHT4GUOYWNBVDV2GJ5FHUWCSFZLHD55EVKZWOWSM7ABQ",
    "is-frozen": false
}

You can check the opt-in transaction on a block explorer for reference.


Learn More
- Algorand Block Explorers

Step 3-5. Complete Example

# OPT-IN

# Check if asset_id is in account 3's asset holdings prior
# to opt-in
params = algod_client.suggested_params()
# comment these two lines if you want to use suggested params
params.fee = 1000
params.flat_fee = True

account_info = algod_client.account_info(accounts[3]['pk'])
holding = None
idx = 0
for my_account_info in account_info['assets']:
    scrutinized_asset = account_info['assets'][idx]
    idx = idx + 1    
    if (scrutinized_asset['asset-id'] == asset_id):
        holding = True
        break

if not holding:

    # Use the AssetTransferTxn class to transfer assets and opt-in
    txn = AssetTransferTxn(
        sender=accounts[3]['pk'],
        sp=params,
        receiver=accounts[3]["pk"],
        amt=0,
        index=asset_id)
    stxn = txn.sign(accounts[3]['sk'])
    txid = algod_client.send_transaction(stxn)
    print(txid)
    # Wait for the transaction to be confirmed
    wait_for_confirmation(algod_client, txid)
    # Now check the asset holding for that account.
    # This should now show a holding with a balance of 0.
    print_asset_holding(algod_client, accounts[3]['pk'], asset_id)

Part 4: Transfer

This part demonstrates the steps involved in transferring an asset from one account to another. Transfers are authorized by the account that holds the asset to be transferred. Asset transfers are analogous to standard payment transactions but for Algorand Standard Assets.

Requirements

  • The AssetID of an Asset created in Part 1.

Background

Transfering an asset allows users to transact with assets, after they have issued asset acceptance transactions. The optional closeRemainderTo argument can be used to stop transacting with a particular asset. Now that the opt-in has been done on a potential receiving account in the Part 3, assets can be transferred.

Step 4-1. Transfer Transaction

This code has Account 1 sending 10 assets to Account 3. Set asset-id, amt, sender and receiver.

# TRANSFER ASSET

# transfer asset of 10 from account 1 to account 3
params = algod_client.suggested_params()
# comment these two lines if you want to use suggested params
params.fee = 1000
params.flat_fee = True
txn = AssetTransferTxn(
    sender=accounts[1]['pk'],
    sp=params,
    receiver=accounts[3]["pk"],
    amt=10,
    index=asset_id)


Learn More
- Transferring an Asset

Step 4-2. Sign Transfer Transaction

The transaction must be signed by the sender account.

stxn = txn.sign(accounts[1]['sk'])

Step 4-3. Send Transfer Transaction and Print Account Information

Submit the transaction and list the account amount for acct3.
You should see that it now has 10 of the new asset.

txid = algod_client.send_transaction(stxn)
print(txid)
# Wait for the transaction to be confirmed
wait_for_confirmation(algod_client, txid)
# The balance should now be 10.
print_asset_holding(algod_client, accounts[3]['pk'], asset_id)

Step 4-4. Check the transaction on a block explorer

Once you’ve completed these steps your output should look something like this:

Transaction AYL3FKK6IUWRV2RRCWFBZYO3STX2D74XML6HFWH4EELSDLMLUCCQ confirmed in round 3982920.
Asset ID: 2653870
{
    "amount": 10,
    "asset-id": 2653870,
    "creator": "ATTR6RUEHHBHXKUHT4GUOYWNBVDV2GJ5FHUWCSFZLHD55EVKZWOWSM7ABQ",
    "is-frozen": false
}

You can check the transaction on a block explorer for reference.


Learn More
- Algorand Block Explorers

Step 4-5. Complete Example

This example assumes that the receiver account has already opted in to receiving the asset. If the account has not already opted in for this asset, it will throw an error.

# TRANSFER ASSET

# transfer asset of 10 from account 1 to account 3
params = algod_client.suggested_params()
# comment these two lines if you want to use suggested params
params.fee = 1000
params.flat_fee = True
txn = AssetTransferTxn(
    sender=accounts[1]['pk'],
    sp=params,
    receiver=accounts[3]["pk"],
    amt=10,
    index=asset_id)
stxn = txn.sign(accounts[1]['sk'])
txid = algod_client.send_transaction(stxn)
print(txid)
# Wait for the transaction to be confirmed
wait_for_confirmation(algod_client, txid)
# The balance should now be 10.
print_asset_holding(algod_client, accounts[3]['pk'], asset_id)

Part 5: Freeze

This part demonstrates how to freeze an asset holding of a particular account. Freezing an asset means that the asset can no longer be sent to or from that account.

An example use case for this functionality is if you suspect fraudulent activity related to your asset, you can issue a freeze transaction against the offending account’s asset holding while you take the time to investigate. If the account checks out okay, you can issue a follow-up transaction to unfreeze the account so they can resume trade.

Note that the sender of a freeze or unfreeze transaction must be the Freeze Manager, which is specified in the asset’s on-chain configuration. Read more about Asset Freeze Transactions in the docs.

Requirements

  • The AssetID of an Asset created in Part 1.

Background

Algorand Standard Assets are built on layer-1 and benefit from the same speed, ease of use, and security as the Algorand blockchain’s native token. Read all about Algorand Standard Assets in the docs.

One of the characteristics of an ASA is the ability to make it freezable, or not. Making an asset freezable means that an account that has an asset in its balance record can be made frozen for that particular asset and will not be able to make asset transfer transactions with that asset. The corresponding account that would trigger a freeze transaction is called the freeze address. If the asset is created without a freeze address, then the asset is forever “un-freezable.”

Note: A frozen account can always close out to the asset creator.

Step 5-1. Freeze Transaction

In this first step, we need to define a freeze target as well as a new_freeze_state. This asset was made “freezable” when in Part 1. Setting an address to the freeze parameter in the AssetConfigTxn method makes the asset “freezable.” Setting the freeze address parameter to “”, would make the asset unfreezable and that characteristic cannot be changed retroactively.

# FREEZE ASSET

params = algod_client.suggested_params()
# comment these two lines if you want to use suggested params
params.fee = 1000
params.flat_fee = True
# The freeze address (Account 2) freezes Account 3's latinum holdings.

txn = AssetFreezeTxn(
    sender=accounts[2]['pk'],
    sp=params,
    index=asset_id,
    target=accounts[3]["pk"],
    new_freeze_state=True   
    )


Learn More
- Asset Freeze Transaction

Step 5-2. Sign Freeze Transaction

The transaction must be signed by the freeze account.

stxn = txn.sign(accounts[2]['sk'])

Step 5-3. Send Freeze Transaction and Print Account Information

Send the transaction to the network. After Sent, Account 3 should have all of its asset holdings frozen and should not be able to trigger a spend transaction of the asset that was frozen.

txid = algod_client.send_transaction(stxn)
print(txid)
# Wait for the transaction to be confirmed
wait_for_confirmation(algod_client, txid)
# The balance should now be 10 with frozen set to true.
print_asset_holding(algod_client, accounts[3]['pk'], asset_id)

Step 5-4. Check the transaction on a block explorer

Once you’ve completed these steps, the output should look something like this with frozen set to true:

# Terminal output should look similar to this wih a frozen value of true...
Transaction 5NFHUQ4GEQMT4EFPMIIBTHNOX4LS5GQLZRKCKCA2GAUVAS4PAGJQ confirmed in round 3982928.
Asset ID: 2653870
{
    "amount": 10,
    "asset-id": 2653870,
    "creator": "ATTR6RUEHHBHXKUHT4GUOYWNBVDV2GJ5FHUWCSFZLHD55EVKZWOWSM7ABQ",
    "is-frozen": true
}

You can check the transaction on a block explorer for reference.


Learn More
- Algorand Block Explorers

Step 5-5. Complete Example

This example assumes that the freeze target account has the asset in its balance record.

# FREEZE ASSET

params = algod_client.suggested_params()
# comment these two lines if you want to use suggested params
params.fee = 1000
params.flat_fee = True
# The freeze address (Account 2) freezes Account 3's latinum holdings.

txn = AssetFreezeTxn(
    sender=accounts[2]['pk'],
    sp=params,
    index=asset_id,
    target=accounts[3]["pk"],
    new_freeze_state=True   
    )
stxn = txn.sign(accounts[2]['sk'])
txid = algod_client.send_transaction(stxn)
print(txid)
# Wait for the transaction to be confirmed
wait_for_confirmation(algod_client, txid)
# The balance should now be 10 with frozen set to true.
print_asset_holding(algod_client, accounts[3]['pk'], asset_id)

Part 6: Revoke

This part demonstrates the steps involved in revoking an asset. Asset revocation is also referred to as clawback functionality. Revoking an asset allows an asset’s revocation manager to transfer assets on behalf of another user. It will only work when issued by the asset’s revocation manager.

When an asset is created, the parameter in the AssetTransferTxn function that allows an asset to be “revocable” is called clawback address. If that parameter is set to “”, the asset is “un-revocable” and cannot be retroactively changed to being “revocable”.


Learn More
- [Asset Parameters]../../docs/reference/transactions/#asset-parameters)

Requirements

  • The AssetID of an Asset created in Part 1.

Background

One of the characteristics of an ASA is the ability to make the asset revocable or not. This functionality is called clawback in the code. The corresponding account that would trigger a revoke transaction is called the clawback address.

Note: A creator account can clawback from a frozen account.

Steps

Step 6-1. Revoke Transaction

In this step, we need to define a revocation target. In this case, we are revoking assets from Account 3, who’s assets were previously frozen. The clawback address, Account 2, revokes 10 latinum from revocation target, Account 3, and places it back with the receiver, Account 1.

# REVOKE ASSET

# The clawback address (Account 2) revokes 10 latinum from Account 3 and places it back with Account 1.
params = algod_client.suggested_params()
# comment these two lines if you want to use suggested params
params.fee = 1000
params.flat_fee = True

# Must be signed by the account that is the Asset's clawback address
txn = AssetTransferTxn(
    sender=accounts[2]['pk'],
    sp=params,
    receiver=accounts[1]["pk"],
    amt=10,
    index=asset_id,
    revocation_target=accounts[3]['pk']
    )

Step 6-2. Sign Revoke Asset Transaction

Account 2 is the clawback address, meaning that this is the account that approves and sends the transaction and also pays the flat_fee. Must be signed by the account that is the clawback address.

stxn = txn.sign(accounts[2]['sk'])

Step 6-3. Send Revoke Asset Transaction and Print Account Information

Broadcast the transaction to the blockchain.

txid = algod_client.send_transaction(stxn)
print(txid)
# Wait for the transaction to be confirmed
wait_for_confirmation(algod_client, txid)
# The balance of account 3 should now be 0.
# account_info = algod_client.account_info(accounts[3]['pk'])
print("Account 3")
print_asset_holding(algod_client, accounts[3]['pk'], asset_id)

# The balance of account 1 should increase by 10 to 1000.
print("Account 1")
print_asset_holding(algod_client, accounts[1]['pk'], asset_id)

Step 6-4. Check the transaction on a block explorer

Once you’ve completed these steps you’re output should look something like this:

Transaction 4UFNTECSEBAGJT52XLIBM7BQXHBTXUHLZ2U4M4XTZUAVE4VLKURQ confirmed in round 3982932.
Account 3
Asset ID: 2653870
{
    "amount": 0,
    "asset-id": 2653870,
    "creator": "ATTR6RUEHHBHXKUHT4GUOYWNBVDV2GJ5FHUWCSFZLHD55EVKZWOWSM7ABQ",
    "is-frozen": true
}
Account 1
Asset ID: 2653870
{
    "amount": 1000,
    "asset-id": 2653870,
    "creator": "ATTR6RUEHHBHXKUHT4GUOYWNBVDV2GJ5FHUWCSFZLHD55EVKZWOWSM7ABQ",
    "is-frozen": false
}


Learn More
- Algorand Block Explorers

Step 6-5. Complete Example

Here is the completed code.

# REVOKE ASSET

# The clawback address (Account 2) revokes 10 latinum from Account 3 and places it back with Account 1.
params = algod_client.suggested_params()
# comment these two lines if you want to use suggested params
params.fee = 1000
params.flat_fee = True

# Must be signed by the account that is the Asset's clawback address
txn = AssetTransferTxn(
    sender=accounts[2]['pk'],
    sp=params,
    receiver=accounts[1]["pk"],
    amt=10,
    index=asset_id,
    revocation_target=accounts[3]['pk']
    )
stxn = txn.sign(accounts[2]['sk'])
txid = algod_client.send_transaction(stxn)
print(txid)
# Wait for the transaction to be confirmed
wait_for_confirmation(algod_client, txid)
# The balance of account 3 should now be 0.
# account_info = algod_client.account_info(accounts[3]['pk'])
print("Account 3")
print_asset_holding(algod_client, accounts[3]['pk'], asset_id)

# The balance of account 1 should increase by 10 to 1000.
print("Account 1")
print_asset_holding(algod_client, accounts[1]['pk'], asset_id)

Part 7: Destroy

Overview

This part demonstrates the steps involved in destroying an asset. In order to trigger a destroy asset transaction, the original creator of the asset must be in possession (must have in its balance record) all units of the asset.


Learn More

Destroy an Asset

Requirements

  • The AssetID of an Asset created in Part 1.
  • The creator account must have all of the assets.

Background

To trigger a destroy asset transaction, the original creator of the asset must be in possession (must have in its balance record) all units of the asset.

Step 7-1.Destroy Transaction

The only parameter that needs to be defined when conducting an asset destroy operation is the sender address, which is the manager address of the asset, or Account 1.

# DESTROY ASSET
# With all assets back in the creator's account,
# the manager (Account 1) destroys the asset.
params = algod_client.suggested_params()
# comment these two lines if you want to use suggested params
params.fee = 1000
params.flat_fee = True

# Asset destroy transaction
txn = AssetConfigTxn(
    sender=accounts[1]['pk'],
    sp=params,
    index=asset_id,
    strict_empty_address_check=False
    )

Step 7-2. Sign Destroy Asset Transaction

The transaction must be signed by the manager.

# Sign with secret key of creator
stxn = txn.sign(accounts[1]['sk'])

Step 7-3. Send Destroy Asset Transaction and Print Account Information

# Send the transaction to the network and retrieve the txid.
txid = algod_client.send_transaction(stxn)
print(txid)
# Wait for the transaction to be confirmed
wait_for_confirmation(algod_client, txid)

# Asset was deleted.
try:
    print("Account 3 must do a transaction for an amount of 0, " )
    print("with a close_assets_to to the creator account, to clear it from its accountholdings")
    print("For Account 1, nothing should print after this as the asset is destroyed on the creator account")

    print_asset_holding(algod_client, accounts[1]['pk'], asset_id)
    print_created_asset(algod_client, accounts[1]['pk'], asset_id)
    # asset_info = algod_client.asset_info(asset_id)
except Exception as e:
    print(e)

Step 7-4. Check the transaction on the block explorer

Your output should look similar to this:

Transaction C7BOB7ZNVIJ477LEAIJYDNXIIFZTY7ETTB3QEV3GWRJ7BGOZMSGA confirmed in round 3983117.
Account 3 must do a transaction for an amount of 0,
with a close_assets_to to the creator account, to clear it from its accountholdings
For Account 1, nothing should print after this if the asset is destroyed on the creator account

Notice that although the asset was destroyed, the asset id and associated metadata still exists in the account balance record. When you destroy an asset, the global parameters associated with that asset (manager addresses, name, etc.) are deleted from the creator’s balance record. However, holdings are not deleted automatically – users still need to close out of the deleted asset. This is necessary for technical reasons because we can’t have a single transaction touch potentially thousands of accounts (all the holdings that would need to be deleted).


Learn More
- Algorand Block Explorers

Step 7-5. Complete Example

Here is the completed code.

# DESTROY ASSET
# With all assets back in the creator's account,
# the manager (Account 1) destroys the asset.
params = algod_client.suggested_params()
# comment these two lines if you want to use suggested params
params.fee = 1000
params.flat_fee = True

# Asset destroy transaction
txn = AssetConfigTxn(
    sender=accounts[1]['pk'],
    sp=params,
    index=asset_id,
    strict_empty_address_check=False
    )

# Sign with secret key of creator
stxn = txn.sign(accounts[1]['sk'])
# Send the transaction to the network and retrieve the txid.
txid = algod_client.send_transaction(stxn)
print(txid)
# Wait for the transaction to be confirmed
wait_for_confirmation(algod_client, txid)

# Asset was deleted.
try:
    print("Account 3 must do a transaction for an amount of 0, " )
    print("with a close_assets_to to the creator account, to clear it from its accountholdings")
    print("For Account 1, nothing should print after this as the asset is destroyed on the creator account")

    print_asset_holding(algod_client, accounts[1]['pk'], asset_id)
    print_created_asset(algod_client, accounts[1]['pk'], asset_id)
    # asset_info = algod_client.asset_info(asset_id)
except Exception as e:
    print(e)

The complete code for this example can be found here. asset_example.py

python

asset freeze

Asset Manager

asset opt-in

asset creation

asset transfer

asa

asset destroy

clawback asset

asset management

November 19, 2020