Asset Operations
Algorand Standard Assets (ASA) enable you to tokenize any type of asset on the Algorand blockchain. This guide covers the essential operations for managing these assets: creation, modification, transfer, and deletion. You’ll also learn about opt-in mechanics, asset freezing, and clawback functionality. Each operation requires specific permissions and can be performed using AlgoKit Utils or the Goal CLI.
Creating Assets
Creating an ASA lets you mint digital tokens on the Algorand blockchain. You can set the total supply, decimals, unit name, asset name, and add metadata through an optional URL. The asset requires special control addresses: a manager to modify configuration, a reserve for custody, a freeze address to control transferability, and a clawback address to revoke tokens. Every new asset receives a unique identifier on the blockchain.
Transaction Authorizer: Any account with sufficient Algo balance
Create assets using either Algokit Utils or goal
. When using Algokit Utils, 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.
/** * Send an asset create transaction creating a fungible ASA with 10 million units * * Parameters for creating a new asset: * - sender: The address of the account that will send the transaction * - total: The total amount of the smallest divisible unit to create * - decimals: The amount of decimal places the asset should have, defaults to undefined * - defaultFrozen: Whether the asset is frozen by default in the creator address, defaults to undefined * - manager: The address that can change the manager, reserve, clawback, and freeze addresses, defaults to undefined * - reserve: The address that holds the uncirculated supply, defaults to undefined * - freeze: The address that can freeze the asset in any account, defaults to undefined * - clawback: The address that can clawback the asset from any account, defaults to undefined * - unitName: The short ticker name for the asset, defaults to undefined * - assetName: The full name of the asset, defaults to undefined */ const createFungibleResult = await algorand.send.assetCreate({ sender: randomAccountA.addr, total: 10_000_000n, decimals: 6, defaultFrozen: false, manager: randomAccountA.addr, reserve: randomAccountA.addr, freeze: randomAccountA.addr, clawback: randomAccountA.addr, unitName: 'MYA', assetName: 'My Asset', })
console.log('Fungible asset created with ID:', createFungibleResult.assetId)
/** * Send an asset create transaction creating a 1 to 1 unique NFT */ const createNFTResult = await algorand.send.assetCreate({ sender: randomAccountA.addr, total: 1n, assetName: 'My NFT', unitName: 'MNFT', decimals: 0, url: 'metadata URL', metadataHash: new Uint8Array(Buffer.from('Hash of the metadata URL')), })
console.log('NFT created with ID:', createNFTResult.assetId)
""" Send an asset create transaction creating a fungible ASA with 10 million units
Parameters for creating a new asset. - sender: The address of the account that will send the transaction - total: The total amount of the smallest divisible unit to create - decimals: The amount of decimal places the asset should have, defaults to None - default_frozen: Whether the asset is frozen by default in the creator address, defaults to None - manager: The address that can change the manager, reserve, clawback, and freeze addresses, defaults to None - reserve: The address that holds the uncirculated supply, defaults to None - freeze: The address that can freeze the asset in any account, defaults to None - clawback: The address that can clawback the asset from any account, defaults to None - unit_name: The short ticker name for the asset, defaults to None - asset_name: The full name of the asset, defaults to None """ txn_result = algorand_client.send.asset_create( AssetCreateParams( sender=account_a.address, total=10_000_000, decimals=6, default_frozen=False, # optional manager=account_a.address, # optional. Can be permanently disabled by setting to None reserve=account_a.address, # optional. Can be permanently disabled by setting to None freeze=account_a.address, # optional. Can be permanently disabled by setting to None clawback=account_a.address, # optional. Can be permanently disabled by setting to None unit_name="MYA", asset_name="My Asset", ) )
""" Send an asset create transaction creating a 1 to 1 unique NFT """ txn_result = algorand_client.send.asset_create( AssetCreateParams( sender=account_a.address, total=1, asset_name="My NFT", unit_name="MNFT", decimals=0, url="metadata URL", metadata_hash=b"Hash of the metadata URL", ) )
goal asset create --creator <address> --total 1000 --unitname <unit-name> --asseturl "https://path/to/my/asset/details" --decimals 0 -d data
Updating Assets
After creation, an ASA’s configuration can be modified, but only certain parameters are mutable. The manager address can update the asset’s control addresses: manager, reserve, freeze, and clawback. All other parameters like total supply and decimals are immutable. Setting any control address to empty permanently removes that capability from the asset.
Authorized by: Asset Manager Account
To update an asset’s configuration, the current manager account must sign the transaction. Each control address can be modified independently, and changes take effect immediately. Use caution when clearing addresses by setting them to empty strings, as this permanently removes the associated capability from the asset with no way to restore it.
/** * Send an asset config transaction updating four mutable fields of an asset: * manager, reserve, freeze, clawback. This operation is only possible if the sender is * the asset manager and the asset has all four mutable fields set. * * Parameters for configuring an existing asset: * - sender: The address of the account that will send the transaction * - assetId: ID of the asset * - manager: The address that can change the manager, reserve, clawback, and freeze addresses, defaults to undefined * - reserve: The address that holds the uncirculated supply, defaults to undefined * - freeze: The address that can freeze the asset in any account, defaults to undefined * - clawback: The address that can clawback the asset from any account, defaults to undefined */ const txnResult = await algorand.send.assetConfig({ sender: randomAccountA.addr, assetId: 1234n, manager: randomAccountB.addr, reserve: randomAccountB.addr, freeze: randomAccountB.addr, clawback: randomAccountB.addr, })
console.log('Asset update transaction ID:', txnResult.transaction.txID)
""" Send an asset config transaction updating four mutable fields of an asset: manager, reserve, freeze, clawback. This operation is only possible if the sender is the asset manager and the asset has all four mutable fields set.
Parameters for configuring an existing asset. - sender: The address of the account that will send the transaction - asset_id: ID of the asset - manager: The address that can change the manager, reserve, clawback, and freeze addresses, defaults to None - reserve: The address that holds the uncirculated supply, defaults to None - freeze: The address that can freeze the asset in any account, defaults to None - clawback: The address that can clawback the asset from any account, defaults to None """ txn_result = algorand_client.send.asset_config( AssetConfigParams( sender=account_a.address, asset_id=1234, manager=account_b.address, reserve=account_b.address, freeze=account_b.address, clawback=account_b.address, ) )
goal asset config --manager <address> --new-reserve <address> --assetid <asset-id> -d data
Deleting Assets
Destroying an ASA permanently removes it from the Algorand blockchain. This operation requires specific conditions: the asset manager must initiate the deletion, and all units of the asset must be held by the creator account. Once deleted, the asset ID becomes invalid and the creator’s minimum balance requirement for the asset is removed.
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.
/** * Send an asset destroy transaction destroying an asset with asset id 1234 * All of the assets must be owned by the creator of the asset before the asset can be deleted. * * Parameters for destroying an asset: * - sender: The address of the account that will send the transaction * - assetId: ID of the asset */ const destroyResult = await algorand.send.assetDestroy({ sender: randomAccountA.addr, assetId: 1234n, })
console.log('Asset destroy transaction ID:', destroyResult.transaction.txID)
""" Send an asset destroy transaction destroying an asset with asset id 1234 All of the assets must be owned by the creator of the asset before the asset can be deleted.
Parameters for destroying an asset. - sender: The address of the account that will send the transaction - asset_id: ID of the asset """ txn_result = algorand_client.send.asset_destroy( AssetDestroyParams( sender=account_a.address, asset_id=1234, ) )
goal asset destroy --creator <creator-address> --manager <asset-manager-address> --asset <asset-name> -d data
Opting In and Out of Assets
Before an account can receive an ASA, it must explicitly opt in to hold that asset. This security feature ensures accounts only hold assets they choose to accept. Opting in requires a minimum balance increase of 0.1 Algo per asset, while opting out releases this requirement. Both operations must be authorized by the account performing the action.
Authorized by: The account opting out
The asset management functions include opting in and out of assets, which are fundamental to asset interaction in a blockchain environment.
optIn
Authorized by: The account opting in
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 microAlgo).
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.
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.
/** * Send an asset opt in transaction for randomAccountA opting in to asset with asset id 1234 * * Parameters for an asset opt in transaction: * - sender: The address of the account that will opt in to the asset * - assetId: ID of the asset */ const optInResult = await algorand.send.assetOptIn({ sender: randomAccountA.addr, assetId: 1234n, })
console.log('Asset opt-in transaction ID:', optInResult.transaction.txID)
""" Send an asset opt in transaction for account_a opting in to asset with asset id 1234
Parameters for an asset opt in transaction. - sender: The address of the account that will opt in to the asset - asset_id: ID of the asset """ txn_result = algorand_client.send.asset_opt_in( AssetOptInParams( sender=account_a.address, asset_id=1234, ) )
goal asset send -a 0 --asset <asset-name> -f <opt-in-account> -t <opt-in-account> --creator <asset-creator> -d data
assetBulkOptIn
The assetBulkOptIn
function facilitates the opt-in process for an account to multiple assets, allowing the account to receive and hold those assets.
/** * Opt an account out of a list of Algorand Standard Assets. * * Transactions will be sent in batches of 16 as transaction groups. * * @param account The account to opt-in * @param assetIds The list of asset IDs to opt-out of * @param options Any parameters to control the transaction or execution of the transaction * * @returns An array of records matching asset ID to transaction ID of the opt in */ const bulkOptInResult = await algorand.asset.bulkOptIn(randomAccountA.addr, [1234n, 5678n])
console.log( 'Asset bulk opt-in transaction IDs:', bulkOptInResult.map((r) => r.transactionId), )
""" Opt an account in to a list of Algorand Standard Assets.
:param account: The account to opt-in :param asset_ids: The list of asset IDs to opt-in to :param signer: The signer to use for the transaction, defaults to None :param rekey_to: The address to rekey the account to, defaults to None :param note: The note to include in the transaction, defaults to None :param lease: The lease to include in the transaction, defaults to None :param static_fee: The static fee to include in the transaction, defaults to None :param extra_fee: The extra fee to include in the transaction, defaults to None :param max_fee: The maximum fee to include in the transaction, defaults to None :param validity_window: The validity window to include in the transaction, defaults to None :param first_valid_round: The first valid round to include in the transaction, defaults to None :param last_valid_round: The last valid round to include in the transaction, defaults to None :param send_params: The send parameters to use for the transaction, defaults to None :return: An array of records matching asset ID to transaction ID of the opt in """ txn_results = algorand_client.asset.bulk_opt_in( account=account_a.address, asset_ids=[1234, 5678], )
print(txn_results[0].transaction_id, txn_results[1].transaction_id)
optOut
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 0.1 Algo Minimum Balance Requirement for the asset.
/** * Send an asset opt out transaction for randomAccountA opting out of asset with asset id 1234 * * Parameters for an asset opt out transaction: * - sender: The address of the account that will opt out of the asset * - assetId: ID of the asset * - creator: The creator address of the asset * - ensureZeroBalance: Check if account has zero balance before opt-out, defaults to true */ const optOutResult = await algorand.send.assetOptOut({ sender: randomAccountA.addr, assetId: 1234n, creator: randomAccountB.addr, ensureZeroBalance: true, })
console.log('Asset opt-out transaction ID:', optOutResult.transaction.txID)
""" Send an asset opt out transaction for account_a opting out of asset with asset id 1234
Parameters for an asset opt out transaction. - sender: The address of the account that will opt out of the asset - asset_id: ID of the asset - creator: The creator address of the asset - ensure_zero_balance: Check if account has zero balance before opt-out, defaults to True """ txn_result = algorand_client.send.asset_opt_out( params=AssetOptOutParams( sender=account_a.address, asset_id=1234, creator=account_b.address, ), ensure_zero_balance=True, )
assetBulkOptOut
The assetBulkOptOut
function manages the opt-out process for a number of assets, permitting the account to discontinue holding a group of assets.
/** * Opt an account out of a list of Algorand Standard Assets. * * Transactions will be sent in batches of 16 as transaction groups. * * @param account The account to opt-in * @param assetIds The list of asset IDs to opt-out of * @param options Any parameters to control the transaction or execution of the transaction * * @returns An array of records matching asset ID to transaction ID of the opt out */ const bulkOptOutResult = await algorand.asset.bulkOptOut(randomAccountA.addr, [1234n, 5678n])
console.log( 'Asset bulk opt-out transaction IDs:', bulkOptOutResult.map((r) => r.transactionId), )
""" Opt an account out of a list of Algorand Standard Assets.
:param account: The account to opt-out :param asset_ids: The list of asset IDs to opt-out of :param ensure_zero_balance: Whether to check if the account has a zero balance first, defaults to True :param signer: The signer to use for the transaction, defaults to None :param rekey_to: The address to rekey the account to, defaults to None :param note: The note to include in the transaction, defaults to None :param lease: The lease to include in the transaction, defaults to None :param static_fee: The static fee to include in the transaction, defaults to None :param extra_fee: The extra fee to include in the transaction, defaults to None :param max_fee: The maximum fee to include in the transaction, defaults to None :param validity_window: The validity window to include in the transaction, defaults to None :param first_valid_round: The first valid round to include in the transaction, defaults to None :param last_valid_round: The last valid round to include in the transaction, defaults to None :param send_params: The send parameters to use for the transaction, defaults to None :raises ValueError: If ensure_zero_balance is True and account has non-zero balance or is not opted in :return: An array of records matching asset ID to transaction ID of the opt out """ txn_results = algorand_client.asset.bulk_opt_out( account=account_a.address, asset_ids=[1234, 5678], )
print(txn_results[0].transaction_id, txn_results[1].transaction_id)
Transferring Assets
Asset transfers are a fundamental operation in the Algorand ecosystem, enabling the movement of ASAs between accounts. These transactions form the backbone of token economics, allowing for trading, distribution, and general circulation of assets on the blockchain. Each transfer must respect the opt-in status of the receiving account and any freeze constraints that may be in place.
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.
/** * Send an asset transfer transaction of 1 asset with asset id 1234 from randomAccountA to randomAccountB * * Parameters for an asset transfer transaction: * - sender: The address of the account that will send the asset * - assetId: The asset id of the asset to transfer * - amount: Amount of the asset to transfer (smallest divisible unit) * - receiver: The address of the account to send the asset to */ const transferResult = await algorand.send.assetTransfer({ sender: randomAccountA.addr, assetId: 1234n, receiver: randomAccountB.addr, amount: 1n, })
console.log('Asset transfer transaction ID:', transferResult.transaction.txID)
""" Send an asset transfer transaction of 1 asset with asset id 1234 from account_a to account_b
Parameters for an asset transfer transaction. - sender: The address of the account that will send the asset - asset_id: The asset id of the asset to transfer - amount: Amount of the asset to transfer (smallest divisible unit) - receiver: The address of the account to send the asset to """ txn_result = algorand_client.send.asset_transfer( AssetTransferParams( sender=account_a.address, asset_id=1234, receiver=account_b.address, amount=1, ) )
goal asset send -a <asset-amount> --asset <asset-name> -f <asset-sender> -t <asset-receiver> --creator <asset-creator> -d data
Clawback Assets
The clawback feature provides a mechanism for asset issuers to maintain control over their tokens after distribution. This powerful capability enables compliance with regulatory requirements, enforcement of trading restrictions, or recovery of assets in case of compromised accounts. When configured, the designated clawback address has the authority to revoke assets from any holder’s account and redirect them to another address.
Authorized by: Asset Clawback Address
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.
/** * An asset clawback transaction is an asset transfer transaction with the * `clawbackTarget` set to the account that is being clawed back from. * * Parameters for an asset transfer transaction: * - sender: The address of the account that will send the transaction * - assetId: ID of the asset * - amount: Amount of the asset to transfer (smallest divisible unit) * - receiver: The account to send the asset to * - clawbackTarget: The account to take the asset from, defaults to undefined */ const txnResult = await algorand.send.assetTransfer({ sender: randomAccountA.addr, // Must be the clawback address for the asset assetId: 1234n, amount: 1n, receiver: randomAccountA.addr, clawbackTarget: randomAccountB.addr, // account that is being clawed back from })
console.log('Asset clawback transaction ID:', txnResult.transaction.txID)
""" An asset clawback transaction is an asset transfer transaction with the `clawback_target` set to the account that is being clawed back from.
Parameters for an asset transfer transaction. - sender: The address of the account that will send the transaction - asset_id: ID of the asset - amount: Amount of the asset to transfer (smallest divisible unit) - receiver: The account to send the asset to - clawback_target: The account to take the asset from, defaults to None """
txn_result = algorand_client.send.asset_transfer( AssetTransferParams( sender=manager.address, asset_id=1234, amount=1, receiver=manager.address, clawback_target=account_to_be_clawbacked.address, # account that is being clawed back from ) )
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
Freezing Assets
The freeze capability allows asset issuers to temporarily suspend the transfer of their assets for specific accounts. This feature is particularly useful for assets that require periodic compliance checks, need to enforce trading restrictions, or must respond to security incidents. Once an account is frozen, it cannot transfer the asset until the freeze is lifted by the designated freeze address.
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.
/** * Send an asset freeze transaction freezing an asset with asset id 1234 * * Parameters for freezing an asset: * - sender: The address of the account that will send the transaction * - assetId: The ID of the asset * - account: The account to freeze or unfreeze * - frozen: Whether the assets in the account should be frozen */ const freezeResult = await algorand.send.assetFreeze({ sender: randomAccountA.addr, assetId: 1234n, account: randomAccountB.addr, // The account to freeze or unfreeze frozen: true, })
console.log('Asset freeze transaction ID:', freezeResult.transaction.txID)
/** * Send an asset unfreeze transaction unfreezing an asset with asset id 1234 */ const unfreezeResult = await algorand.send.assetFreeze({ sender: randomAccountA.addr, assetId: 1234n, account: randomAccountB.addr, // The account to freeze or unfreeze frozen: false, })
console.log('Asset unfreeze transaction ID:', unfreezeResult.transaction.txID)
""" Send an asset freeze transaction freezing an asset with asset id 1234
Parameters for freezing an asset. - sender: The address of the account that will send the transaction - asset_id: The ID of the asset - account: The account to freeze or unfreeze - frozen: Whether the assets in the account should be frozen """ txn_result = algorand_client.send.asset_freeze( AssetFreezeParams( sender=account_a.address, asset_id=1234, account=account_b.address, # The account to freeze or unfreeze frozen=True, ) )
""" Send an asset unfreeze transaction unfreezing an asset with asset id 1234 """ txn_result = algorand_client.send.asset_freeze( AssetFreezeParams( sender=account_a.address, asset_id=1234, account=account_b.address, # The account to freeze or unfreeze frozen=False, ) )