Transaction building
Create and structure Stacks transactions for any use case
Transactions are the fundamental way to interact with the Stacks blockchain. This guide covers how to build, structure, and broadcast transactions using the Stacks.js transaction builder API.
Transaction anatomy
Every Stacks transaction shares a common structure with required components that define how it executes on the blockchain.
interface StacksTransaction {version: TransactionVersion; // Mainnet or testnetchainId: ChainID; // Network chain IDauth: Authorization; // Signature(s)anchorMode: AnchorMode; // Block anchoring strategypostConditionMode: PostConditionMode; // Strict or allowpostConditions: PostCondition[]; // Security constraintspayload: TransactionPayload; // The actual operation}
The payload determines the transaction type: STX transfer, contract deployment, or contract call. Each component plays a critical role in transaction validation and execution.
Building your first transaction
The simplest transaction type is an STX transfer. Here's how to create and broadcast one with proper error handling.
import {makeSTXTokenTransfer,broadcastTransaction,AnchorMode,PostConditionMode} from '@stacks/transactions';import { STACKS_TESTNET } from '@stacks/network';async function sendSTX() {const network = STACKS_TESTNET;const txOptions = {recipient: 'ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM',amount: 1000000n, // 1 STX = 1,000,000 microSTXsenderKey: 'your-private-key-here',network,memo: 'First transaction!',fee: 200n, // Set fee or estimate dynamicallynonce: 0n, // Set manually or fetch from APIanchorMode: AnchorMode.Any,postConditionMode: PostConditionMode.Deny};const transaction = await makeSTXTokenTransfer(txOptions);// Broadcast to networkconst broadcastResponse = await broadcastTransaction({ transaction });if (broadcastResponse.error) {throw new Error(`Broadcast failed: ${broadcastResponse.reason}`);}console.log('Transaction ID:', broadcastResponse.txid);return broadcastResponse.txid;}
Key transaction parameters
// Amount and fee use bigint for precisionamount: 1000000n // Always in microSTX (1 STX = 1,000,000 microSTX)fee: 200n // Transaction fee in microSTXnonce: 0n // Sequential counter for account transactions// Network determines API endpoints and chain IDnetwork: STACKS_TESTNET // or STACKS_MAINNET// Anchor mode affects confirmation timeanchorMode: AnchorMode.Any // Most flexible option
Transaction options
All transaction builder functions accept a common set of options that control execution behavior.
interface TransactionOptions {network: StacksNetwork; // Target network configurationanchorMode: AnchorMode; // Block anchoring strategyfee?: bigint; // Transaction fee in microSTXnonce?: bigint; // Account sequence numberpostConditions?: PostCondition[]; // Asset transfer constraintspostConditionMode?: PostConditionMode; // Security modesponsored?: boolean; // Whether fee is paid by sponsor}
Working with post-conditions
Post-conditions protect users by ensuring specific asset transfers occur. Use the Pc
helper for type-safe construction.
import { Pc, PostConditionMode } from '@stacks/transactions';// Define addresses for the exampleconst senderAddress = 'ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM';const contractAddress = 'ST000000000000000000002AMW42H';const txOptions = {// ... other optionspostConditions: [// Ensure sender sends exactly 1 STXPc.principal(senderAddress).willSendEq(1000000).ustx(),// Ensure contract sends tokens to recipientPc.principal(contractAddress).willSendGte(100).ft(contractAddress + '.my-token', 'my-token')],postConditionMode: PostConditionMode.Deny // Strict mode};
Anchor modes explained
enum AnchorMode {OnChainOnly = 0x01, // Only in Bitcoin-anchored blocks (slower)OffChainOnly = 0x02, // Only in microblocks (faster, less secure)Any = 0x03, // Either type (recommended)}
Use AnchorMode.Any
unless you have specific security requirements. OnChainOnly provides maximum security but slower confirmation.
Fee estimation
Accurate fee estimation ensures transactions confirm quickly without overpaying. Here's how to build transactions with dynamic fee estimation.
import {fetchFeeEstimateTransaction,makeSTXTokenTransfer,setFee,broadcastTransaction} from '@stacks/transactions';async function buildTransactionWithEstimatedFee() {// Step 1: Build transaction with minimal feeconst transaction = await makeSTXTokenTransfer({recipient: 'ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM',amount: 1000000n,senderKey: privateKey,network,fee: 1n, // Minimal fee for estimationnonce: 0n,anchorMode: AnchorMode.Any,});// Step 2: Estimate appropriate feeconst feeEstimate = await fetchFeeEstimateTransaction({transaction,network});// Step 3: Update transaction with estimated feesetFee(transaction, feeEstimate.medium);// Step 4: Broadcast with accurate feeconst result = await broadcastTransaction({ transaction });return result.txid;}
Building with fee rate preferences
async function buildWithFeePreference(priority: 'low' | 'medium' | 'high') {// Build initial transactionconst tempTx = await makeSTXTokenTransfer({recipient: 'ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM',amount: 5000000n, // 5 STXsenderKey: privateKey,network,fee: 1n,nonce: await getCurrentNonce(senderAddress),});// Get fee estimatesconst fees = await fetchFeeEstimateTransaction({transaction: tempTx,network});// Rebuild transaction with selected feeconst finalTx = await makeSTXTokenTransfer({recipient: 'ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM',amount: 5000000n,senderKey: privateKey,network,fee: fees[priority], // Use selected prioritynonce: await getCurrentNonce(senderAddress),});return broadcastTransaction({ transaction: finalTx });}// Usageconst txid = await buildWithFeePreference('high'); // Fast confirmation
Transaction serialization
Serialize transactions for storage, transmission, or offline signing workflows. This enables building transactions in one environment and signing/broadcasting in another.
import {makeUnsignedSTXTokenTransfer,deserializeTransaction,TransactionSigner,BytesReader} from '@stacks/transactions';import { bytesToHex, hexToBytes } from '@stacks/common';import { getPublicKeyFromPrivate } from '@stacks/encryption';// Complete offline signing workflowasync function buildForOfflineSigning() {// Step 1: Create unsigned transaction with public keyconst publicKey = getPublicKeyFromPrivate(privateKey);const unsignedTx = await makeUnsignedSTXTokenTransfer({recipient: 'ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM',amount: 2500000n, // 2.5 STXnetwork,publicKey,nonce: 10n,fee: 180n,});// Step 2: Serialize for transportconst serialized = unsignedTx.serialize();const txHex = bytesToHex(serialized);return txHex; // Send to offline signer}// On the offline signing devicefunction signOffline(txHex: string, privateKey: string) {// Step 1: Deserialize the transactionconst serialized = hexToBytes(txHex);const bytesReader = new BytesReader(serialized);const unsignedTx = deserializeTransaction(bytesReader);// Step 2: Sign the transactionconst signer = new TransactionSigner(unsignedTx);signer.signOrigin(privateKey);// Step 3: Serialize signed transactionconst signedTx = signer.transaction;const signedSerialized = signedTx.serialize();return bytesToHex(signedSerialized);}// Back online: broadcast the signed transactionasync function broadcastSigned(signedTxHex: string) {const serialized = hexToBytes(signedTxHex);const bytesReader = new BytesReader(serialized);const signedTx = deserializeTransaction(bytesReader);const result = await broadcastTransaction({ transaction: signedTx });if (result.error) {throw new Error(`Broadcast failed: ${result.reason}`);}return result.txid;}
Building multi-signature transactions
async function buildMultisigTransaction() {// Build unsigned transaction for 2-of-3 multisigconst unsignedTx = await makeUnsignedSTXTokenTransfer({recipient: 'ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM',amount: 10000000n, // 10 STXnetwork,numSignatures: 2, // Required signaturespublicKeys: [publicKey1, publicKey2, publicKey3],nonce: 0n,fee: 300n,});// First signerconst signer1 = new TransactionSigner(unsignedTx);signer1.signOrigin(privateKey1);// Serialize partially signed txconst partialHex = bytesToHex(signer1.transaction.serialize());// Second signer (potentially different device/time)const partialTx = deserializeTransaction(new BytesReader(hexToBytes(partialHex)));const signer2 = new TransactionSigner(partialTx);signer2.appendOrigin(privateKey2);// Now ready to broadcastreturn broadcastTransaction({ transaction: signer2.transaction });}
Nonce management
The nonce is a sequential counter that prevents transaction replay. Every transaction from an account must have a unique, incrementing nonce.
import { fetchNonce } from '@stacks/transactions';const senderAddress = 'ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM';// Fetch current nonce from networkconst nonce = await fetchNonce({address: senderAddress,network});// Build transaction with fetched nonceconst transaction = await makeSTXTokenTransfer({recipient: 'ST3NBRSFKX28FQ2ZJ1MAKX58HKHSDGNV5N7R21XCP',amount: 1500000n,senderKey: privateKey,network,nonce,fee: 180n,});const result = await broadcastTransaction({ transaction });