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
- Algorand Python SDK
- Algorand Sandbox Installation or set up a node as described here.
- Three funded accounts and the corresponding addresses, and private keys or mnemonic phrases. See Create Accounts Tutorial
Optional:
- API testing tool such as Postman
- Use VS Code with Python
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.
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, wait_for_confirmation
# 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
# 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, wait_for_confirmation
# 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)
# 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
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