Asset Transfer
Description
Section titled “Description”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
Section titled “Prerequisites”- LocalNet running (via
algokit localnet start)
Run This Example
Section titled “Run This Example”From the repository root:
cd examplesnpm run example transact/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);});