Algorand Standard Assets (ASAs)
The Algorand protocol supports the creation of on-chain assets that benefit from the same security, compatibility, speed and ease of use as the Algo. The official name for assets on Algorand is Algorand Standard Assets (ASA).
With Algorand Standard Assets you can represent stablecoins, loyalty points, system credits, and in-game points, just to name a few examples. You can also represent single, unique assets like a deed for a house, collectable items, unique parts on a supply chain, etc. There is also ABI_CODEC functionality to place transfer restrictions on an asset that help support securities, compliance, and certification use cases.
This section begins with an overview of the asset implementation on Algorand including a review of all asset parameters. This is followed by how-tos in the SDKs and goal
for all on-chain asset functions.
Quick start videos
If you prefer videos, take a look at this 7 minute guide to learn about Introduction to Assets.
Assets overview
Here are several things to be aware of before getting started with assets.
- For every asset an account creates or owns, its minimum balance is increased by 0.1 Algos (100,000 microAlgos).
- This minimum balance requirement will be placed on the original creator as long as the asset has not been destroyed. Transferring the asset does not alleviate the creator’s minimum balance requirement.
- Before a new asset can be transferred to a specific account the receiver must opt-in to receive the asset. This process is described below in Receiving an asset.
- If any transaction is issued that would violate the minimum balance requirements, the transaction will fail.
Asset parameters
The type of asset that is created will depend on the parameters that are passed during asset creation and sometimes during asset re-configuration. View the full list of asset parameters in the Asset Parameters Reference.
Immutable asset parameters
These eight parameters can only be specified when an asset is created.
- Creator (required)
- AssetName (optional, but recommended)
- UnitName (optional, but recommended)
- Total (required)
- Decimals (required)
- DefaultFrozen (required)
- URL (optional)
- MetaDataHash (optional)
Mutable asset parameters
There are four parameters that correspond to addresses that can authorize specific functionality for an asset. These addresses must be specified on creation but they can also be modified after creation. Alternatively, these addresses can be set as empty strings, which will irrevocably lock the function that they would have had authority over.
Here are the four address types.
The manager account is the only account that can authorize transactions to re-configure or destroy an asset.
Specifying a reserve account signifies that non-minted assets will reside in that account instead of the default creator account. Assets transferred from this account are “minted” units of the asset. If you specify a new reserve address, you must make sure the new account has opted into the asset and then issue a transaction to transfer all assets to the new reserve.
The freeze account is allowed to freeze or unfreeze the asset holdings for a specific account. When an account is frozen it cannot send or receive the frozen asset. In traditional finance, freezing assets may be performed to restrict liquidation of company stock, to investigate suspected criminal activity or to deny-list certain accounts. If the DefaultFrozen state is set to True, you can use the unfreeze action to authorize certain accounts to trade the asset (such as after passing KYC/AML checks).
The clawback address represents an account that is allowed to transfer assets from and to any asset holder (assuming they have opted-in). Use this if you need the option to revoke assets from an account (like if they breach certain contractual obligations tied to holding the asset). In traditional finance, this sort of transaction is referred to as a clawback.
If any of these four addresses is set to ""
that address will be cleared and can never be reset for the life of the asset. This will also effectively disable the feature of that address. For example setting the freeze address to ""
will prevent the asset from ever being frozen.
Asset functions
Quick start videos
If you prefer videos, take a look at this 8 minute guide to learn about Building Solutions Using ASAs.
Creating an asset
Transaction Authorizer: Any account with sufficient Algo balance
Create assets using either the SDKs or goal
. When using the SDKs supply all creation parameters. With goal
, managing the various addresses associated with the asset must be done after executing an asset creation. See Modifying an Asset in the next section for more details on changing addresses for the asset.
# Account 1 creates an asset called `rug` with a total supply# of 1000 units and sets itself to the freeze/clawback/manager/reserve rolessp = algod_client.suggested_params()txn = transaction.AssetConfigTxn( sender=acct1.address, sp=sp, default_frozen=False, unit_name="rug", asset_name="Really Useful Gift", manager=acct1.address, reserve=acct1.address, freeze=acct1.address, clawback=acct1.address, url="https://path/to/my/asset/details", total=1000, decimals=0,)
# Sign with secret key of creatorstxn = txn.sign(acct1.private_key)# Send the transaction to the network and retrieve the txid.txid = algod_client.send_transaction(stxn)print(f"Sent asset create transaction with txid: {txid}")# Wait for the transaction to be confirmedresults = transaction.wait_for_confirmation(algod_client, txid, 4)print(f"Result confirmed in round: {results['confirmed-round']}")
# grab the asset id for the asset we just createdcreated_asset = results["asset-index"]print(f"Asset ID created: {created_asset}")
const suggestedParams = await algodClient.getTransactionParams().do();const txn = algosdk.makeAssetCreateTxnWithSuggestedParamsFromObject({ from: creator.addr, suggestedParams, defaultFrozen: false, unitName: 'rug', assetName: 'Really Useful Gift', manager: creator.addr, reserve: creator.addr, freeze: creator.addr, clawback: creator.addr, assetURL: 'http://path/to/my/asset/details', total: 1000, decimals: 0,});
const signedTxn = txn.signTxn(creator.privateKey);await algodClient.sendRawTransaction(signedTxn).do();const result = await algosdk.waitForConfirmation( algodClient, txn.txID().toString(), 3);
const assetIndex = result['asset-index'];console.log(`Asset ID created: ${assetIndex}`);
goal asset create --creator <address> --total 1000 --unitname <unit-name> --asseturl "https://path/to/my/asset/details" --decimals 0 -d data
See also
Modifying an asset
Authorized by: Asset Manager Account
After an asset has been created only the manager, reserve, freeze and clawback accounts can be changed. All other parameters are locked for the life of the asset. If any of these addresses are set to ""
that address will be cleared and can never be reset for the life of the asset. Only the manager account can make configuration changes and must authorize the transaction.
sp = algod_client.suggested_params()# Create a config transaction that wipes the# reserve address for the assettxn = transaction.AssetConfigTxn( sender=acct1.address, sp=sp, manager=acct1.address, reserve=None, freeze=acct1.address, clawback=acct1.address, strict_empty_address_check=False,)# Sign with secret key of managerstxn = txn.sign(acct1.private_key)# Send the transaction to the network and retrieve the txid.txid = algod_client.send_transaction(stxn)print(f"Sent asset config transaction with txid: {txid}")# Wait for the transaction to be confirmedresults = transaction.wait_for_confirmation(algod_client, txid, 4)print(f"Result confirmed in round: {results['confirmed-round']}")
const manager = accounts[1];
const configTxn = algosdk.makeAssetConfigTxnWithSuggestedParamsFromObject({ from: creator.addr, manager: manager.addr, freeze: manager.addr, clawback: manager.addr, reserve: undefined, suggestedParams, assetIndex, // don't throw error if freeze, clawback, or manager are empty strictEmptyAddressChecking: false,});
const signedConfigTxn = configTxn.signTxn(creator.privateKey);await algodClient.sendRawTransaction(signedConfigTxn).do();const configResult = await algosdk.waitForConfirmation( algodClient, txn.txID().toString(), 3);console.log(`Result confirmed in round: ${configResult['confirmed-round']}`);
goal asset config --manager <address> --new-reserve <address> --assetid <asset-id> -d data
See also
Receiving an asset
Authorized by: The account opting in
Before an account can receive a specific asset it must opt-in to receive it. An opt-in transaction places an asset holding of 0 into the account and increases its minimum balance by 100,000 microAlgos. An opt-in transaction is simply an asset transfer with an amount of 0, both to and from the account opting in. The following code illustrates this transaction.
sp = algod_client.suggested_params()# Create opt-in transaction# asset transfer from me to me for asset id we want to opt-in to with amt==0optin_txn = transaction.AssetOptInTxn( sender=acct2.address, sp=sp, index=created_asset)signed_optin_txn = optin_txn.sign(acct2.private_key)txid = algod_client.send_transaction(signed_optin_txn)print(f"Sent opt in transaction with txid: {txid}")
# Wait for the transaction to be confirmedresults = transaction.wait_for_confirmation(algod_client, txid, 4)print(f"Result confirmed in round: {results['confirmed-round']}")
// opt-in is simply a 0 amount transfer of the asset to oneselfconst optInTxn = algosdk.makeAssetTransferTxnWithSuggestedParamsFromObject({ from: receiver.addr, to: receiver.addr, suggestedParams, assetIndex, amount: 0,});
const signedOptInTxn = optInTxn.signTxn(receiver.privateKey);await algodClient.sendRawTransaction(signedOptInTxn).do();await algosdk.waitForConfirmation(algodClient, optInTxn.txID().toString(), 3);
goal asset send -a 0 --asset <asset-name> -f <opt-in-account> -t <opt-in-account> --creator <asset-creator> -d data
See also
Transferring an asset
Authorized by: The account that holds the asset to be transferred.
Assets can be transferred between accounts that have opted-in to receiving the asset. These are analogous to standard payment transactions but for Algorand Standard Assets.
sp = algod_client.suggested_params()# Create transfer transactionxfer_txn = transaction.AssetTransferTxn( sender=acct1.address, sp=sp, receiver=acct2.address, amt=1, index=created_asset,)signed_xfer_txn = xfer_txn.sign(acct1.private_key)txid = algod_client.send_transaction(signed_xfer_txn)print(f"Sent transfer transaction with txid: {txid}")
results = transaction.wait_for_confirmation(algod_client, txid, 4)print(f"Result confirmed in round: {results['confirmed-round']}")
const xferTxn = algosdk.makeAssetTransferTxnWithSuggestedParamsFromObject({ from: creator.addr, to: receiver.addr, suggestedParams, assetIndex, amount: 1,});
const signedXferTxn = xferTxn.signTxn(creator.privateKey);await algodClient.sendRawTransaction(signedXferTxn).do();await algosdk.waitForConfirmation(algodClient, xferTxn.txID().toString(), 3);
goal asset send -a <asset-amount> --asset <asset-name> -f <asset-sender> -t <asset-receiver> --creator <asset-creator> -d data
See also
Freezing an asset
Authorized by: Asset Freeze Address
Freezing or unfreezing an asset for an account requires a transaction that is signed by the freeze account. The code below illustrates the freeze transaction.
sp = algod_client.suggested_params()# Create freeze transaction to freeze the asset in acct2 balancefreeze_txn = transaction.AssetFreezeTxn( sender=acct1.address, sp=sp, index=created_asset, target=acct2.address, new_freeze_state=True,)signed_freeze_txn = freeze_txn.sign(acct1.private_key)txid = algod_client.send_transaction(signed_freeze_txn)print(f"Sent freeze transaction with txid: {txid}")
results = transaction.wait_for_confirmation(algod_client, txid, 4)print(f"Result confirmed in round: {results['confirmed-round']}")
const freezeTxn = algosdk.makeAssetFreezeTxnWithSuggestedParamsFromObject({ from: manager.addr, suggestedParams, assetIndex, // freezeState: false would unfreeze the account's asset holding freezeState: true, // freezeTarget is the account that is being frozen or unfrozen freezeTarget: receiver.addr,});
const signedFreezeTxn = freezeTxn.signTxn(manager.privateKey);await algodClient.sendRawTransaction(signedFreezeTxn).do();await algosdk.waitForConfirmation( algodClient, freezeTxn.txID().toString(), 3);
goal asset freeze --freezer <asset-freeze-account> --freeze=true --account <account-to-freeze> --creator <asset-creator> --asset <asset-name> -d data
See also
Revoking an asset
Authorized by: Asset Clawback Address
Revoking an asset for an account removes a specific number of the asset from the revoke target account. Revoking an asset from an account requires specifying an asset sender (the revoke target account) and an asset receiver (the account to transfer the funds back to). The code below illustrates the clawback transaction.
sp = algod_client.suggested_params()# Create clawback transaction to freeze the asset in acct2 balanceclawback_txn = transaction.AssetTransferTxn( sender=acct1.address, sp=sp, receiver=acct1.address, amt=1, index=created_asset, revocation_target=acct2.address,)signed_clawback_txn = clawback_txn.sign(acct1.private_key)txid = algod_client.send_transaction(signed_clawback_txn)print(f"Sent clawback transaction with txid: {txid}")
results = transaction.wait_for_confirmation(algod_client, txid, 4)print(f"Result confirmed in round: {results['confirmed-round']}")
const clawbackTxn = algosdk.makeAssetTransferTxnWithSuggestedParamsFromObject( { from: manager.addr, to: creator.addr, // revocationTarget is the account that is being clawed back from revocationTarget: receiver.addr, suggestedParams, assetIndex, amount: 1, });
const signedClawbackTxn = clawbackTxn.signTxn(manager.privateKey);await algodClient.sendRawTransaction(signedClawbackTxn).do();await algosdk.waitForConfirmation( algodClient, clawbackTxn.txID().toString(), 3);
goal asset send -a <amount-to-revoke> --asset <asset-name> -f <address-of-revoke-target> -t <address-to-send-assets-to> --clawback <clawback-address> --creator <creator-address> -d data
See also
Opting Out of an Asset
Authorized by: The account opting out
An account can opt out of an asset at any time. This means that the account will no longer hold the asset, and the account will no longer be able to receive the asset. The account also recovers the Minimum Balance Requirement for the asset (0.1A).
sp = algod_client.suggested_params()opt_out_txn = transaction.AssetTransferTxn( sender=acct2.address, sp=sp, index=created_asset, receiver=acct1.address, # an opt out transaction sets its close_asset_to parameter # it is always possible to close an asset to the creator close_assets_to=acct1.address, amt=0,)signed_opt_out = opt_out_txn.sign(acct2.private_key)txid = algod_client.send_transaction(signed_opt_out)print(f"Sent opt out transaction with txid: {txid}")
results = transaction.wait_for_confirmation(algod_client, txid, 4)print(f"Result confirmed in round: {results['confirmed-round']}")
// opt-out is an amount transfer with the `closeRemainderTo` field set to// any account that can receive the asset.// note that closing to the asset creator will always succeedconst optOutTxn = algosdk.makeAssetTransferTxnWithSuggestedParamsFromObject({ from: receiver.addr, to: creator.addr, closeRemainderTo: creator.addr, suggestedParams, assetIndex, amount: 0,});
const signedOptOutTxn = optOutTxn.signTxn(receiver.privateKey);await algodClient.sendRawTransaction(signedOptOutTxn).do();await algosdk.waitForConfirmation( algodClient, optOutTxn.txID().toString(), 3);
Destroying an asset
Authorized by: Asset Manager
Created assets can be destroyed only by the asset manager account. All of the assets must be owned by the creator of the asset before the asset can be deleted.
sp = algod_client.suggested_params()# Create asset destroy transaction to destroy the assetdestroy_txn = transaction.AssetDestroyTxn( sender=acct1.address, sp=sp, index=created_asset,)signed_destroy_txn = destroy_txn.sign(acct1.private_key)txid = algod_client.send_transaction(signed_destroy_txn)print(f"Sent destroy transaction with txid: {txid}")
results = transaction.wait_for_confirmation(algod_client, txid, 4)print(f"Result confirmed in round: {results['confirmed-round']}")
# now, trying to fetch the asset info should result in an errortry: info = algod_client.asset_info(created_asset)except Exception as e: print("Expected Error:", e)
const deleteTxn = algosdk.makeAssetDestroyTxnWithSuggestedParamsFromObject({ from: manager.addr, suggestedParams, assetIndex,});
const signedDeleteTxn = deleteTxn.signTxn(manager.privateKey);await algodClient.sendRawTransaction(signedDeleteTxn).do();await algosdk.waitForConfirmation( algodClient, deleteTxn.txID().toString(), 3);
goal asset destroy --creator <creator-address> --manager <asset-manager-address> --asset <asset-name> -d data
See also
Retrieve asset information
Retrieve an asset’s configuration information from the network using the SDKs or goal
. Additional details are also added to the accounts that own the specific asset and can be listed with standard account information calls.
# Retrieve the asset info of the newly created assetasset_info = algod_client.asset_info(created_asset)asset_params: Dict[str, Any] = asset_info["params"]print(f"Asset Name: {asset_params['name']}")print(f"Asset params: {list(asset_params.keys())}")
const assetInfo = await algodClient.getAssetByID(assetIndex).do();console.log(`Asset Name: ${assetInfo.params.name}`);console.log(`Asset Params: ${assetInfo.params}`);
goal asset info --creator <creator-address> --asset unitname -d ~/node/data -w testwallAsset ID: <created-asset-id>Creator: <creator-address>Asset name: testtokenUnit name: unitnameMaximum issue: 12 unitnameReserve amount: 12 unitnameIssued: 0 unitnameDecimals: 0Default frozen: falseManager address: <creator-address>Reserve address: <reserve-address>Freeze address: <freeze-address>Clawback address: <clawback-address>
Assets
The asset management functions include opting in and out of assets, which are fundamental to asset interaction in a blockchain environment. To see some usage examples check out the automated tests.
Transfer
transferAsset
The key function to facilitate asset transfers is transferAsset(transfer, algod)
, which returns a SendTransactionResult
and takes a TransferAssetParams
:
- All properties in
SendTransactionParams
from: SendTransactionFrom
- The account that will send the assetto: SendTransactionFrom | string
- The account / account address that will receive the assetassetId: number
- The asset id that will be transferedamount: number | bigint
- The amount to send in the smallest divisible unittransactionParams?: SuggestedParams
- The optional transaction parametersclawbackFrom: SendTransactionFrom | string
- An optional address of a target account from which to perform a clawback operation. Please note, in such cases senderAccount must be equal to clawback field on ASA metadata.note?: TransactionNote
- The transaction notelease?: string | Uint8Array
: A lease to assign to the transaction to enforce a mutually exclusive transaction (useful to prevent double-posting and other scenarios)
Opt-in/out
Before an account can receive a specific asset, it must opt-in
to receive it. An opt-in transaction places an asset holding of 0 into the account and increases its minimum balance by 100,000 microAlgos.
An account can opt out of an asset at any time. This means that the account will no longer hold the asset, and the account will no longer be able to receive the asset. The account also recovers the Minimum Balance Requirement for the asset (100,000 microAlgos).
When opting-out you generally want to be careful to ensure you have a zero-balance otherwise you will forfeit the balance you do have. By default, AlgoKit Utils protects you from making this mistake by checking you have a zero-balance before issuing the opt-out transaction. You can turn this check off if you want to avoid the extra calls to Algorand and are confident in what you are doing.
AlgoKit Utils gives you functions that allow you to do opt-ins in bulk or as a single operation. The bulk operations give you less control over the sending semantics as they automatically send the transactions to Algorand in the most optimal way using transaction groups.
assetOptIn
To opt-in an account to a single asset you can use the algokit.assetOptIn(optIn, algod)
function. The optIn
argument is an object containing:
- All properties in
SendTransactionParams
account: SendTransactionFrom
- The account that will opt-in to the assetassetId: number
- The asset id that will be opted-in totransactionParams: SuggestedParams
- The optional transaction parametersnote: TransactionNote
- The optional transaction notelease: string | Uint8Array
: A lease to assign to the transaction to enforce a mutually exclusive transaction (useful to prevent double-posting and other scenarios)
1// Example2await algokit.assetOptIn({3 account: account,4 assetId: 12345,5 // Can optionally also specify transactionParams, note, lease and other send params6})
assetOptOut
To opt-out an account from a single asset you can use the algokit.assetOptOut(optOut, algod)
function. The optOut
argument is an object containing:
- All properties from
assetOptIn
assetCreatorAddress: string
- The address of the creator account for the asset; if unspecified then it looks it up using algodensureZeroBalance: boolean
- Whether or not to validate the account has a zero-balance before issuing the opt-out; defaults to true
1// Example2await algokit.assetOptOut({3 account: account,4 assetId: 12345,5 assetCreatorAddress: creator,6 // Can optionally also specify ensureZeroBalance, transactionParams, note, lease and other send params7})
assetBulkOptIn
The assetBulkOptIn
function facilitates the opt-in process for an account to multiple assets, allowing the account to receive and hold those assets.
1// Example2await algokit.assetBulkOptIn(3 {4 account: account,5 assetIds: [12354, 673453],6 // Can optionally also specify validateBalances, transactionParams, note7 },8 algod,9)
assetBulkOptOut
The assetBulkOptOut
function manages the opt-out process for a number of assets, permitting the account to discontinue holding a group of assets.
1// Example2await algokit.assetBulkOptOut(3 {4 account: account,5 assetIds: [12354, 673453],6 // Can optionally also specify validateBalances, transactionParams, note7 },8 algod,9)