Skip to content

Create Transaction (Unsigned Transactions)

← Back to Algorand Client

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()
  • LocalNet running (via algokit localnet start)

From the repository root:

Terminal window
cd examples
npm run example algorand_client/09-create-transaction.ts

View source on GitHub

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 demonstration
const 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)
})