Create Transaction (Unsigned Transactions)
Description
Section titled “Description”This example demonstrates how to create unsigned transactions without immediately sending them, which is useful for:
- Transaction inspection and debugging
- Multi-party signing workflows
- Custom signing flows (hardware wallets, HSMs, etc.)
- Modifying transaction fields before signing
- Building transaction groups for atomic transactions Key concepts:
- algorand.createTransaction.payment() creates unsigned payment transactions
- algorand.createTransaction.assetCreate() creates unsigned asset creation
- algorand.createTransaction.assetTransfer() creates unsigned asset transfers
- algorand.createTransaction.appCall() creates unsigned app calls
- Transaction objects have properties like txId(), fee, firstValid, lastValid
- Manual signing with account.signer() function
- Sending signed transactions via algorand.client.algod.sendRawTransaction()
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 algorand_client/09-create-transaction.ts/** * Example: Create Transaction (Unsigned Transactions) * * This example demonstrates how to create unsigned transactions without immediately * sending them, which is useful for: * - Transaction inspection and debugging * - Multi-party signing workflows * - Custom signing flows (hardware wallets, HSMs, etc.) * - Modifying transaction fields before signing * - Building transaction groups for atomic transactions * * Key concepts: * - algorand.createTransaction.payment() creates unsigned payment transactions * - algorand.createTransaction.assetCreate() creates unsigned asset creation * - algorand.createTransaction.assetTransfer() creates unsigned asset transfers * - algorand.createTransaction.appCall() creates unsigned app calls * - Transaction objects have properties like txId(), fee, firstValid, lastValid * - Manual signing with account.signer() function * - Sending signed transactions via algorand.client.algod.sendRawTransaction() * * Prerequisites: * - LocalNet running (via `algokit localnet start`) */
import { AlgorandClient, algo } from '@algorandfoundation/algokit-utils';import { formatAlgo, loadTealSource, printError, printHeader, printInfo, printStep, printSuccess, shortenAddress,} from '../shared/utils.js';
// Simple approval and clear state programs for demonstrationconst APPROVAL_PROGRAM = loadTealSource('simple-approve.teal');const CLEAR_STATE_PROGRAM = loadTealSource('clear-state-approve.teal');
/** * Helper function to wait for transaction confirmation * Returns the full PendingTransactionResponse with proper typing */async function waitForTransactionConfirmation( algorand: AlgorandClient, txId: string, maxRounds: number = 5,) { const status = await algorand.client.algod.status(); let currentRound = status.lastRound; const endRound = currentRound + BigInt(maxRounds);
while (currentRound < endRound) { const pendingInfo = await algorand.client.algod.pendingTransactionInformation(txId);
if (pendingInfo.confirmedRound && pendingInfo.confirmedRound > 0n) { return pendingInfo; }
if (pendingInfo.poolError && pendingInfo.poolError.length > 0) { throw new Error(`Transaction rejected: ${pendingInfo.poolError}`); }
await algorand.client.algod.statusAfterBlock(currentRound); currentRound++; }
throw new Error(`Transaction ${txId} not confirmed after ${maxRounds} rounds`);}
async function main() { printHeader('Create Transaction Example');
// Initialize client and verify LocalNet is running const algorand = AlgorandClient.defaultLocalNet();
try { await algorand.client.algod.status(); printSuccess('Connected to LocalNet'); } catch (error) { printError( `Failed to connect to LocalNet: ${error instanceof Error ? error.message : String(error)}`, ); printInfo('Make sure LocalNet is running (e.g., algokit localnet start)'); return; }
// Step 1: Create and fund test accounts printStep(1, 'Create and fund test accounts'); printInfo('Creating accounts for transaction creation demonstrations');
const sender = algorand.account.random(); const receiver = algorand.account.random();
printInfo(`\nCreated accounts:`); printInfo(` Sender: ${shortenAddress(sender.addr.toString())}`); printInfo(` Receiver: ${shortenAddress(receiver.addr.toString())}`);
// Fund the sender account await algorand.account.ensureFundedFromEnvironment(sender.addr, algo(10)); printSuccess('Created and funded test accounts');
// Step 2: Create unsigned payment transaction printStep(2, 'Create unsigned payment with algorand.createTransaction.payment()'); printInfo('Creating a payment transaction WITHOUT immediately sending it'); printInfo('This allows inspection, modification, and custom signing flows');
const paymentTxn = await algorand.createTransaction.payment({ sender: sender.addr, receiver: receiver.addr, amount: algo(1), note: 'Unsigned payment transaction', });
printInfo(`\nUnsigned Payment Transaction created:`); printInfo(` Transaction ID: ${paymentTxn.txId()}`); printInfo(` Type: ${paymentTxn.type}`); printInfo(` Sender: ${shortenAddress(paymentTxn.sender.toString())}`); printInfo(` Receiver: ${shortenAddress(paymentTxn.payment?.receiver.toString() ?? 'N/A')}`); printInfo(` Amount: ${formatAlgo(paymentTxn.payment?.amount ?? 0n)}`); printInfo(` Fee: ${paymentTxn.fee ?? 0n} µALGO`); printInfo(` First Valid: ${paymentTxn.firstValid}`); printInfo(` Last Valid: ${paymentTxn.lastValid}`); printInfo(` Genesis ID: ${paymentTxn.genesisId}`);
printSuccess('Unsigned payment transaction created');
// Step 3: Examine Transaction object properties printStep(3, 'Examine Transaction object properties and methods'); printInfo('The Transaction object provides several useful properties and methods');
printInfo(`\nTransaction properties:`); printInfo(` txId(): string - Unique transaction identifier`); printInfo(` Example: ${paymentTxn.txId()}`); printInfo(``); printInfo(` type: TransactionType - Transaction type (pay, axfer, appl, etc.)`); printInfo(` Example: ${paymentTxn.type}`); printInfo(``); printInfo(` sender: Address - The sender's address`); printInfo(` Example: ${shortenAddress(paymentTxn.sender.toString())}`); printInfo(``); printInfo(` fee: bigint - Transaction fee in microALGO`); printInfo(` Example: ${paymentTxn.fee ?? 0n} µALGO`); printInfo(``); printInfo(` firstValid/lastValid: bigint - Validity window (rounds)`); printInfo(` Example: ${paymentTxn.firstValid} to ${paymentTxn.lastValid}`); printInfo(``); printInfo(` note: Uint8Array - Optional note field`); printInfo(` Example: ${paymentTxn.note ? new TextDecoder().decode(paymentTxn.note) : 'N/A'}`); printInfo(``); printInfo(` genesisHash/genesisId: Network identification`); printInfo(` Genesis ID: ${paymentTxn.genesisId}`);
printSuccess('Transaction properties examined');
// Step 4: Create unsigned asset creation transaction printStep(4, 'Create unsigned asset creation with algorand.createTransaction.assetCreate()'); printInfo('Creating an asset creation transaction without sending it');
const assetCreateTxn = await algorand.createTransaction.assetCreate({ sender: sender.addr, total: 1_000_000n, decimals: 2, assetName: 'Example Token', unitName: 'EXT', url: 'https://example.com', manager: sender.addr, reserve: sender.addr, freeze: sender.addr, clawback: sender.addr, });
printInfo(`\nUnsigned Asset Create Transaction:`); printInfo(` Transaction ID: ${assetCreateTxn.txId()}`); printInfo(` Type: ${assetCreateTxn.type}`); printInfo(` Total supply: ${assetCreateTxn.assetConfig?.total ?? 0n} units`); printInfo(` Decimals: ${assetCreateTxn.assetConfig?.decimals ?? 0}`); printInfo(` Asset name: ${assetCreateTxn.assetConfig?.assetName ?? 'N/A'}`); printInfo(` Unit name: ${assetCreateTxn.assetConfig?.unitName ?? 'N/A'}`); printInfo(` Fee: ${assetCreateTxn.fee ?? 0n} µALGO`);
printSuccess('Unsigned asset creation transaction created');
// Step 5: Manually sign a transaction printStep(5, 'Manually sign a transaction with account.signer()'); printInfo("Signing the payment transaction using the sender's signer function"); printInfo(''); printInfo('The signer function signature:'); printInfo(' signer(txnGroup: Transaction[], indexesToSign: number[]): Promise<Uint8Array[]>'); printInfo(''); printInfo('Parameters:'); printInfo(' txnGroup: Array of transactions (can be a single transaction)'); printInfo(' indexesToSign: Indices of transactions to sign in the group');
// Sign the transaction const signedTxns = await sender.signer([paymentTxn], [0]);
printInfo(`\nTransaction signed:`); printInfo(` Number of signed transactions: ${signedTxns.length}`); printInfo(` Signed transaction size: ${signedTxns[0].length} bytes`); printInfo(` Transaction ID (unchanged): ${paymentTxn.txId()}`);
printSuccess('Transaction signed manually');
// Step 6: Send a manually signed transaction printStep(6, 'Send a manually signed transaction'); printInfo('Sending the signed transaction using algorand.client.algod.sendRawTransaction()');
const submitResult = await algorand.client.algod.sendRawTransaction(signedTxns); printInfo(`\nTransaction submitted:`); printInfo(` Transaction ID: ${submitResult.txId}`);
// Wait for confirmation const confirmation = await waitForTransactionConfirmation(algorand, paymentTxn.txId()); printInfo(` Confirmed in round: ${confirmation.confirmedRound}`);
// Verify the transfer const receiverInfo = await algorand.account.getInformation(receiver.addr); printInfo(` Receiver balance: ${formatAlgo(receiverInfo.balance)}`);
printSuccess('Manually signed transaction sent and confirmed');
// Step 7: Create and send unsigned asset creation (with signing) printStep(7, 'Create, sign, and send asset creation transaction'); printInfo('Demonstrating the full workflow: create -> sign -> send');
// Sign the asset creation transaction const signedAssetCreate = await sender.signer([assetCreateTxn], [0]);
// Send it await algorand.client.algod.sendRawTransaction(signedAssetCreate); const assetConfirmation = await waitForTransactionConfirmation(algorand, assetCreateTxn.txId());
// Get the asset ID from the confirmation (assetId is populated for asset create transactions) const assetId = assetConfirmation.assetId!;
printInfo(`\nAsset created:`); printInfo(` Asset ID: ${assetId}`); printInfo(` Transaction ID: ${assetCreateTxn.txId()}`); printInfo(` Confirmed in round: ${assetConfirmation.confirmedRound}`);
printSuccess('Asset creation completed via manual signing');
// Step 8: Create unsigned asset transfer transaction printStep(8, 'Create unsigned asset transfer with algorand.createTransaction.assetTransfer()'); printInfo('Creating an asset opt-in transaction (transfer to self with amount 0)');
// First, opt-in the receiver to the asset const optInTxn = await algorand.createTransaction.assetOptIn({ sender: receiver.addr, assetId: assetId, });
printInfo(`\nUnsigned Asset Opt-In Transaction:`); printInfo(` Transaction ID: ${optInTxn.txId()}`); printInfo(` Type: ${optInTxn.type}`); printInfo(` Asset ID: ${optInTxn.assetTransfer?.assetId ?? 'N/A'}`); printInfo(` Sender: ${shortenAddress(optInTxn.sender.toString())}`); printInfo(` Receiver: ${shortenAddress(optInTxn.assetTransfer?.receiver.toString() ?? 'N/A')}`); printInfo(` Amount: ${optInTxn.assetTransfer?.amount ?? 0n} (0 for opt-in)`);
// Fund receiver and sign/send opt-in await algorand.account.ensureFundedFromEnvironment(receiver.addr, algo(1)); const signedOptIn = await receiver.signer([optInTxn], [0]); await algorand.client.algod.sendRawTransaction(signedOptIn); await waitForTransactionConfirmation(algorand, optInTxn.txId());
printInfo(`\nOpt-in completed`);
// Now create an asset transfer const assetTransferTxn = await algorand.createTransaction.assetTransfer({ sender: sender.addr, receiver: receiver.addr, assetId: assetId, amount: 100n, note: 'Asset transfer via unsigned transaction', });
printInfo(`\nUnsigned Asset Transfer Transaction:`); printInfo(` Transaction ID: ${assetTransferTxn.txId()}`); printInfo(` Asset ID: ${assetTransferTxn.assetTransfer?.assetId ?? 'N/A'}`); printInfo(` Amount: ${assetTransferTxn.assetTransfer?.amount ?? 0n} units`);
// Sign and send const signedAssetTransfer = await sender.signer([assetTransferTxn], [0]); await algorand.client.algod.sendRawTransaction(signedAssetTransfer); await waitForTransactionConfirmation(algorand, assetTransferTxn.txId());
printInfo(` Transfer completed successfully`);
printSuccess('Unsigned asset transfer demonstrated');
// Step 9: Create unsigned app call transaction printStep(9, 'Create unsigned app call with algorand.createTransaction.appCall()'); printInfo('First, create an app to call');
// Create the app first (using send for simplicity) const appCreateResult = await algorand.send.appCreate({ sender: sender.addr, approvalProgram: APPROVAL_PROGRAM, clearStateProgram: CLEAR_STATE_PROGRAM, });
const appId = appCreateResult.appId; printInfo(`\nApp created with ID: ${appId}`);
// Now create an unsigned app call const appCallTxn = await algorand.createTransaction.appCall({ sender: sender.addr, appId: appId, args: [new TextEncoder().encode('hello'), new TextEncoder().encode('world')], note: 'Unsigned app call', });
printInfo(`\nUnsigned App Call Transaction:`); printInfo(` Transaction ID: ${appCallTxn.txId()}`); printInfo(` Type: ${appCallTxn.type}`); printInfo(` App ID: ${appCallTxn.appCall?.appId ?? 'N/A'}`); printInfo(` On Complete: ${appCallTxn.appCall?.onComplete ?? 0} (NoOp)`); printInfo(` Args count: ${appCallTxn.appCall?.args?.length ?? 0}`); printInfo(` Fee: ${appCallTxn.fee ?? 0n} µALGO`);
// Sign and send const signedAppCall = await sender.signer([appCallTxn], [0]); await algorand.client.algod.sendRawTransaction(signedAppCall); await waitForTransactionConfirmation(algorand, appCallTxn.txId());
printInfo(` App call completed successfully`);
printSuccess('Unsigned app call demonstrated');
// Step 10: Demonstrate modifying transaction fields before signing printStep(10, 'Demonstrate transaction inspection before signing'); printInfo('You can inspect transaction fields before deciding to sign'); printInfo(''); printInfo('Use cases for unsigned transactions:'); printInfo(''); printInfo('1. Transaction Inspection:'); printInfo(' - Verify sender, receiver, and amount before signing'); printInfo(' - Check validity window (firstValid to lastValid)'); printInfo(' - Ensure correct fees'); printInfo(''); printInfo('2. Multi-Party Signing:'); printInfo(' - Create transaction on one device'); printInfo(' - Send unsigned txn to another party for signing'); printInfo(' - Useful for multi-sig wallets'); printInfo(''); printInfo('3. Custom Signing Flows:'); printInfo(' - Hardware wallet integration'); printInfo(' - HSM (Hardware Security Module) signing'); printInfo(' - Air-gapped signing workflows'); printInfo(''); printInfo('4. Transaction Groups (Atomic Transactions):'); printInfo(' - Create multiple unsigned transactions'); printInfo(' - Group them together for atomic execution'); printInfo(' - Sign all transactions in the group'); printInfo(''); printInfo('5. Simulation and Testing:'); printInfo(' - Create transactions to simulate their effects'); printInfo(' - Test transaction validity before signing');
// Example: inspect before signing const inspectTxn = await algorand.createTransaction.payment({ sender: sender.addr, receiver: receiver.addr, amount: algo(0.5), });
printInfo(`\nExample - Inspecting before signing:`); printInfo(` Will send: ${formatAlgo(inspectTxn.payment?.amount ?? 0n)}`); printInfo(` To: ${shortenAddress(inspectTxn.payment?.receiver.toString() ?? 'N/A')}`); printInfo(` Fee: ${inspectTxn.fee ?? 0n} µALGO`); printInfo(` Valid until round: ${inspectTxn.lastValid}`);
// Decide to sign based on inspection const shouldSign = (inspectTxn.payment?.amount ?? 0n) <= algo(1).microAlgo; if (shouldSign) { printInfo(` Decision: Amount is acceptable, proceeding with signing`); const signed = await sender.signer([inspectTxn], [0]); await algorand.client.algod.sendRawTransaction(signed); await waitForTransactionConfirmation(algorand, inspectTxn.txId()); printInfo(` Transaction confirmed`); }
printSuccess('Transaction inspection demonstrated');
// Step 11: Summary printStep(11, 'Summary - Create Transaction API'); printInfo('Transaction creation methods available through algorand.createTransaction:'); printInfo(''); printInfo('Payment:'); printInfo(' createTransaction.payment({ sender, receiver, amount, ... })'); printInfo(' Returns: Promise<Transaction>'); printInfo(''); printInfo('Asset Operations:'); printInfo(' createTransaction.assetCreate({ sender, total, decimals, ... })'); printInfo(' createTransaction.assetConfig({ sender, assetId, ... })'); printInfo(' createTransaction.assetTransfer({ sender, receiver, assetId, amount })'); printInfo(' createTransaction.assetOptIn({ sender, assetId })'); printInfo(' createTransaction.assetOptOut({ sender, assetId, creator })'); printInfo(' createTransaction.assetFreeze({ sender, assetId, freezeTarget, frozen })'); printInfo(' createTransaction.assetDestroy({ sender, assetId })'); printInfo(''); printInfo('Application Operations:'); printInfo(' createTransaction.appCreate({ sender, approvalProgram, clearStateProgram })'); printInfo(' createTransaction.appUpdate({ sender, appId, approvalProgram, clearStateProgram })'); printInfo(' createTransaction.appCall({ sender, appId, args, onComplete })'); printInfo(' createTransaction.appDelete({ sender, appId })'); printInfo(''); printInfo('Transaction Object Properties:'); printInfo(' txId(): string - Get the transaction ID'); printInfo(' type: TransactionType - Transaction type'); printInfo(' sender: Address - Sender address'); printInfo(' fee: bigint - Fee in microALGO'); printInfo(' firstValid/lastValid: bigint - Validity window'); printInfo(' note: Uint8Array - Note field'); printInfo(' genesisId/genesisHash: Network identification'); printInfo(''); printInfo('Manual Signing:'); printInfo(' const signedTxns = await account.signer([transaction], [0])'); printInfo(' Returns: Promise<Uint8Array[]> (encoded signed transactions)'); printInfo(''); printInfo('Sending Signed Transactions:'); printInfo(' await algorand.client.algod.sendRawTransaction(signedTxns)'); printInfo(' Returns: { txId: string }');
// Clean up await algorand.send.appDelete({ sender: sender.addr, appId: appId, });
printSuccess('Create Transaction example completed!');}
main().catch(error => { printError(`Unhandled error: ${error instanceof Error ? error.message : String(error)}`); process.exit(1);});Other examples in Algorand Client
Section titled “Other examples in Algorand Client”- Client Instantiation
- AlgoAmount Utility
- Signer Configuration
- Suggested Params Configuration
- Account Manager
- Send Payment
- Send Asset Operations
- Send Application Operations
- Create Transaction (Unsigned Transactions)
- Transaction Composer (Atomic Transaction Groups)
- Asset Manager
- App Manager
- App Deployer
- Client Manager
- Error Transformers
- Transaction Leases