Skip to content

Asset Transfer

← Back to Transactions

This example demonstrates the full asset transfer flow using the transact package: 1. Create a new Algorand Standard Asset (ASA) 2. Opt-in: receiver sends 0 amount of the asset to themselves 3. Transfer assets from creator to the opted-in receiver 4. Verify receiver’s asset balance after transfer Uses Transaction class with TransactionType.AssetTransfer and AssetTransferTransactionFields.

  • LocalNet running (via algokit localnet start)

From the repository root:

Terminal window
cd examples
npm run example transact/04-asset-transfer.ts

View source on GitHub

04-asset-transfer.ts
/**
* Example: Asset Transfer
*
* This example demonstrates the full asset transfer flow using the transact package:
* 1. Create a new Algorand Standard Asset (ASA)
* 2. Opt-in: receiver sends 0 amount of the asset to themselves
* 3. Transfer assets from creator to the opted-in receiver
* 4. Verify receiver's asset balance after transfer
*
* Uses Transaction class with TransactionType.AssetTransfer and AssetTransferTransactionFields.
*
* Prerequisites:
* - LocalNet running (via `algokit localnet start`)
*/
import { AlgorandClient } from '@algorandfoundation/algokit-utils'
import type { PendingTransactionResponse } from '@algorandfoundation/algokit-utils/algod-client'
import {
Transaction,
TransactionType,
assignFee,
type AssetConfigTransactionFields,
type AssetTransferTransactionFields,
type PaymentTransactionFields,
} from '@algorandfoundation/algokit-utils/transact'
import {
createAlgodClient,
printHeader,
printInfo,
printStep,
printSuccess,
shortenAddress,
waitForConfirmation,
} from '../shared/utils.js'
/**
* Gets a funded account from LocalNet's KMD wallet
*/
async function getLocalNetFundedAccount(algorand: AlgorandClient) {
return await algorand.account.kmd.getLocalNetDispenserAccount()
}
async function main() {
printHeader('Asset Transfer Example')
// Step 1: Initialize clients
printStep(1, 'Initialize Algod Client')
const algod = createAlgodClient()
const algorand = AlgorandClient.defaultLocalNet()
printInfo('Connected to LocalNet Algod')
// Step 2: Get creator account from KMD
printStep(2, 'Get Creator Account from KMD')
const creator = await getLocalNetFundedAccount(algorand)
printInfo(`Creator address: ${shortenAddress(creator.addr.toString())}`)
// Step 3: Get suggested transaction parameters
printStep(3, 'Get Suggested Transaction Parameters')
const suggestedParams = await algod.suggestedParams()
printInfo(`First valid round: ${suggestedParams.firstValid}`)
printInfo(`Last valid round: ${suggestedParams.lastValid}`)
printInfo(`Min fee: ${suggestedParams.minFee} microALGO`)
// Step 4: Generate and fund receiver account using AlgorandClient helper
printStep(4, 'Generate and Fund Receiver Account')
// Generate a new account for the receiver using AlgorandClient helper
const receiver = algorand.account.random()
printInfo(`Receiver address: ${shortenAddress(receiver.addr.toString())}`)
// Fund the receiver with enough ALGO to cover transaction fees using low-level transaction
const fundingAmount = 1_000_000n // 1 ALGO in microALGO
const fundPaymentFields: PaymentTransactionFields = {
receiver: receiver.addr,
amount: fundingAmount,
}
const fundTx = new Transaction({
type: TransactionType.Payment,
sender: creator.addr,
firstValid: suggestedParams.firstValid,
lastValid: suggestedParams.lastValid,
genesisHash: suggestedParams.genesisHash,
genesisId: suggestedParams.genesisId,
payment: fundPaymentFields,
})
const fundTxWithFee = assignFee(fundTx, {
feePerByte: suggestedParams.fee,
minFee: suggestedParams.minFee,
})
const signedFundTx = await creator.signer([fundTxWithFee], [0])
await algod.sendRawTransaction(signedFundTx)
await waitForConfirmation(algod, fundTxWithFee.txId())
printInfo('Funded receiver with 1 ALGO for transaction fees')
// Step 5: Create a new asset
printStep(5, 'Create New Asset')
// Asset parameters
const assetTotal = 10_000_000_000n // 10,000 units with 6 decimals
const assetDecimals = 6
const assetName = 'Transfer Test Token'
const assetUnitName = 'TTT'
const assetConfigFields: AssetConfigTransactionFields = {
assetId: 0n, // 0 indicates asset creation
total: assetTotal,
decimals: assetDecimals,
defaultFrozen: false,
assetName: assetName,
unitName: assetUnitName,
url: 'https://example.com/transfer-token',
manager: creator.addr,
reserve: creator.addr,
freeze: creator.addr,
clawback: creator.addr,
}
printInfo(`Creating asset: ${assetName} (${assetUnitName})`)
printInfo(`Total supply: ${assetTotal} (${Number(assetTotal) / Math.pow(10, assetDecimals)} ${assetUnitName})`)
// Create asset config transaction
const createAssetTx = new Transaction({
type: TransactionType.AssetConfig,
sender: creator.addr,
firstValid: suggestedParams.firstValid,
lastValid: suggestedParams.lastValid,
genesisHash: suggestedParams.genesisHash,
genesisId: suggestedParams.genesisId,
assetConfig: assetConfigFields,
})
const createAssetTxWithFee = assignFee(createAssetTx, {
feePerByte: suggestedParams.fee,
minFee: suggestedParams.minFee,
})
// Sign and submit asset creation transaction
const signedCreateTx = await creator.signer([createAssetTxWithFee], [0])
const createTxId = createAssetTxWithFee.txId()
await algod.sendRawTransaction(signedCreateTx)
printInfo(`Asset creation transaction submitted: ${createTxId}`)
const createPendingInfo = (await waitForConfirmation(algod, createTxId)) as PendingTransactionResponse
const assetId = createPendingInfo.assetId
if (!assetId) {
throw new Error('Asset ID not found in pending transaction response')
}
printInfo(`Asset created with ID: ${assetId}`)
printSuccess(`Asset ${assetName} (ID: ${assetId}) created successfully!`)
// Step 6: Opt-in - Receiver sends 0 amount to themselves
printStep(6, 'Opt-in: Receiver Opts Into the Asset')
printInfo('Opt-in is done by sending 0 amount of the asset to yourself')
// Refresh suggested params for the new transaction
const optInSuggestedParams = await algod.suggestedParams()
const optInFields: AssetTransferTransactionFields = {
assetId: assetId,
receiver: receiver.addr, // Receiver sends to themselves
amount: 0n, // 0 amount for opt-in
}
const optInTx = new Transaction({
type: TransactionType.AssetTransfer,
sender: receiver.addr, // Receiver is the sender for opt-in
firstValid: optInSuggestedParams.firstValid,
lastValid: optInSuggestedParams.lastValid,
genesisHash: optInSuggestedParams.genesisHash,
genesisId: optInSuggestedParams.genesisId,
assetTransfer: optInFields,
})
const optInTxWithFee = assignFee(optInTx, {
feePerByte: optInSuggestedParams.fee,
minFee: optInSuggestedParams.minFee,
})
// Sign and submit opt-in transaction
const signedOptInTx = await receiver.signer([optInTxWithFee], [0])
const optInTxId = optInTxWithFee.txId()
await algod.sendRawTransaction(signedOptInTx)
printInfo(`Opt-in transaction submitted: ${optInTxId}`)
await waitForConfirmation(algod, optInTxId)
printInfo(`Receiver opted into asset ID: ${assetId}`)
printSuccess('Receiver successfully opted into the asset!')
// Verify receiver has 0 balance after opt-in
const receiverAssetInfoAfterOptIn = await algod.accountAssetInformation(receiver.addr.toString(), assetId)
printInfo(`Receiver asset balance after opt-in: ${receiverAssetInfoAfterOptIn.assetHolding?.amount ?? 0n}`)
// Step 7: Transfer assets from creator to receiver
printStep(7, 'Transfer Assets from Creator to Receiver')
const transferAmount = 1_000_000_000n // 1,000 units (with 6 decimals)
printInfo(`Transferring ${transferAmount} (${Number(transferAmount) / Math.pow(10, assetDecimals)} ${assetUnitName}) to receiver`)
// Refresh suggested params for the transfer transaction
const transferSuggestedParams = await algod.suggestedParams()
const transferFields: AssetTransferTransactionFields = {
assetId: assetId,
receiver: receiver.addr,
amount: transferAmount,
}
const transferTx = new Transaction({
type: TransactionType.AssetTransfer,
sender: creator.addr, // Creator sends the assets
firstValid: transferSuggestedParams.firstValid,
lastValid: transferSuggestedParams.lastValid,
genesisHash: transferSuggestedParams.genesisHash,
genesisId: transferSuggestedParams.genesisId,
assetTransfer: transferFields,
})
const transferTxWithFee = assignFee(transferTx, {
feePerByte: transferSuggestedParams.fee,
minFee: transferSuggestedParams.minFee,
})
// Sign and submit transfer transaction
const signedTransferTx = await creator.signer([transferTxWithFee], [0])
const transferTxId = transferTxWithFee.txId()
await algod.sendRawTransaction(signedTransferTx)
printInfo(`Transfer transaction submitted: ${transferTxId}`)
await waitForConfirmation(algod, transferTxId)
printSuccess(`Transferred ${Number(transferAmount) / Math.pow(10, assetDecimals)} ${assetUnitName} to receiver!`)
// Step 8: Verify receiver's asset balance after transfer
printStep(8, 'Verify Receiver Asset Balance After Transfer')
const receiverAssetInfoAfterTransfer = await algod.accountAssetInformation(receiver.addr.toString(), assetId)
const receiverBalance = receiverAssetInfoAfterTransfer.assetHolding?.amount ?? 0n
printInfo(`Receiver asset balance: ${receiverBalance} (${Number(receiverBalance) / Math.pow(10, assetDecimals)} ${assetUnitName})`)
if (receiverBalance !== transferAmount) {
throw new Error(`Balance mismatch: expected ${transferAmount}, got ${receiverBalance}`)
}
printSuccess(`Receiver balance verified: ${Number(receiverBalance) / Math.pow(10, assetDecimals)} ${assetUnitName}`)
// Also verify creator's remaining balance
const creatorAssetInfoAfterTransfer = await algod.accountAssetInformation(creator.addr.toString(), assetId)
const creatorBalance = creatorAssetInfoAfterTransfer.assetHolding?.amount ?? 0n
const expectedCreatorBalance = assetTotal - transferAmount
printInfo(`Creator remaining balance: ${creatorBalance} (${Number(creatorBalance) / Math.pow(10, assetDecimals)} ${assetUnitName})`)
if (creatorBalance !== expectedCreatorBalance) {
throw new Error(`Creator balance mismatch: expected ${expectedCreatorBalance}, got ${creatorBalance}`)
}
printSuccess(`Creator balance verified: ${Number(creatorBalance) / Math.pow(10, assetDecimals)} ${assetUnitName}`)
printSuccess('Asset transfer example completed successfully!')
}
main().catch((error) => {
console.error('Error:', error)
process.exit(1)
})