Skip to content

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)

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)

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)

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)

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),
)

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)

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),
)

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)

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)

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)