Building Transactions
Create and customize all types of Stacks transactions with precise control over signing, sponsorship, and multi-signature scenarios
Building transactions programmatically gives you complete control over how your application interacts with the Stacks blockchain. You can create signed transactions for immediate broadcast, unsigned transactions for multi-signature workflows, sponsored transactions to pay fees for users, and complex multi-signature transactions.
This approach is essential for backends, automated systems, and advanced dApps that need precise transaction control.
Feature overview
Stacks.js provides transaction builders for every blockchain operation:
STX transfers: Send native tokens between accounts with memo support Contract deployment: Deploy Clarity smart contracts with version control Contract calls: Execute functions with proper argument validation and post-conditions Sponsored transactions: Pay transaction fees for other users Multi-signature transactions: Require multiple signatures for enhanced security Fee and nonce management: Automatic estimation or explicit control
Required packages
Install the core transaction building package:
npm install @stacks/transactions@latest
The @stacks/network
package is optional in v7 - you can use string literals for networks.
Building signed transactions
Signed transactions are ready to broadcast immediately. The private key signs the transaction during creation.
STX token transfer
Send STX tokens between accounts:
import { makeSTXTokenTransfer, broadcastTransaction } from '@stacks/transactions';const transaction = await makeSTXTokenTransfer({recipient: 'ST2CY5V39NHDPWSXMW9QDT3HC3GD6Q6XX4CFRK9AG',amount: '1000000', // 1 STX in microSTXsenderKey: 'your-private-key-hex',network: 'testnet',memo: 'Payment for services',// nonce and fee are auto-fetched/estimated if not provided});const result = await broadcastTransaction({transaction,network: 'testnet'});console.log('Transaction ID:', result.txid);
Smart contract deployment
Deploy Clarity contracts to the blockchain:
import { makeContractDeploy } from '@stacks/transactions';import { readFileSync } from 'fs';const contractCode = readFileSync('./my-contract.clar', 'utf8');const transaction = await makeContractDeploy({contractName: 'my-awesome-contract',codeBody: contractCode,senderKey: 'your-private-key-hex',network: 'testnet',clarityVersion: 3, // Defaults to latest if not specified});const result = await broadcastTransaction({transaction,network: 'testnet'});
Smart contract function call
Execute contract functions with arguments and post-conditions:
import { makeContractCall, Cl } from '@stacks/transactions';const transaction = await makeContractCall({contractAddress: 'ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM',contractName: 'hello-world',functionName: 'set-value',functionArgs: [Cl.uint(42),Cl.stringUtf8('Hello Stacks!')],senderKey: 'your-private-key-hex',network: 'testnet',validateWithAbi: true, // Validates against contract ABIpostConditions: [], // Add safety conditions});
Building unsigned transactions
Unsigned transactions require a separate signing step. Use these for multi-signature workflows or when you want to inspect transactions before signing.
Unsigned STX transfer
import { makeUnsignedSTXTokenTransfer } from '@stacks/transactions';import { privateKeyToPublicKey } from '@stacks/transactions';const publicKey = privateKeyToPublicKey('your-private-key-hex');const unsignedTx = await makeUnsignedSTXTokenTransfer({recipient: 'ST2CY5V39NHDPWSXMW9QDT3HC3GD6Q6XX4CFRK9AG',amount: '1000000',publicKey: publicKey,network: 'testnet',memo: 'Unsigned transfer',});// Sign separatelyimport { TransactionSigner } from '@stacks/transactions';const signer = new TransactionSigner(unsignedTx);signer.signOrigin('your-private-key-hex');// Now ready to broadcastconst signedTx = signer.getTxInComplete();
Unsigned contract call
import { makeUnsignedContractCall } from '@stacks/transactions';const unsignedTx = await makeUnsignedContractCall({contractAddress: 'ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM',contractName: 'my-contract',functionName: 'my-function',functionArgs: [Cl.uint(100)],publicKey: publicKey,network: 'testnet',});// Sign with TransactionSigner as shown above
Building sponsored transactions
Sponsored transactions let you pay fees for other users. The origin creates and signs the transaction, then the sponsor adds their signature and fee.
Create sponsored transaction (origin side)
import { makeContractCall, Cl } from '@stacks/transactions';// Origin creates transaction with sponsored: trueconst sponsoredTx = await makeContractCall({contractAddress: 'ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM',contractName: 'my-contract',functionName: 'user-action',functionArgs: [Cl.standardPrincipal('ST2CY5V39NHDPWSXMW9QDT3HC3GD6Q6XX4CFRK9AG')],senderKey: 'origin-private-key',network: 'testnet',sponsored: true, // This makes it a sponsored transaction});// Serialize for the sponsorconst serializedTx = sponsoredTx.serialize();
Complete sponsored transaction (sponsor side)
import { sponsorTransaction, deserializeTransaction } from '@stacks/transactions';// Sponsor receives the serialized transactionconst deserializedTx = deserializeTransaction(serializedTx);// Sponsor adds fee and signatureconst completedTx = await sponsorTransaction({transaction: deserializedTx,sponsorPrivateKey: 'sponsor-private-key',fee: '2000', // Sponsor pays the feenetwork: 'testnet',});// Sponsor broadcasts the completed transactionconst result = await broadcastTransaction({transaction: completedTx,network: 'testnet'});
Building multi-signature transactions
Multi-sig transactions require multiple signatures. Create unsigned transactions and collect signatures from multiple parties.
Create multi-sig transaction
import {makeUnsignedSTXTokenTransfer,TransactionSigner,privateKeyToPublicKey} from '@stacks/transactions';// Collect public keys from all participantsconst privateKeys = ['private-key-1','private-key-2','private-key-3'];const publicKeys = privateKeys.map(privateKeyToPublicKey);// Create unsigned multi-sig transactionconst multiSigTx = await makeUnsignedSTXTokenTransfer({recipient: 'ST2CY5V39NHDPWSXMW9QDT3HC3GD6Q6XX4CFRK9AG',amount: '5000000',numSignatures: 2, // Require 2 of 3 signaturespublicKeys: publicKeys,network: 'testnet',});
Collect signatures
const signer = new TransactionSigner(multiSigTx);// First participant signssigner.signOrigin(privateKeys[0]);// Second participant signssigner.signOrigin(privateKeys[1]);// Add remaining public key (unsigned participant)signer.appendOrigin(publicKeys[2]);// Get completed transactionconst signedMultiSigTx = signer.getTxInComplete();
Configuration options
Custom network endpoints
Connect to custom Stacks nodes:
const transaction = await makeSTXTokenTransfer({recipient: 'ST2CY5V39NHDPWSXMW9QDT3HC3GD6Q6XX4CFRK9AG',amount: '1000000',senderKey: 'your-private-key',network: 'testnet',client: { baseUrl: 'http://localhost:3999' } // Custom devnet});
Explicit fee and nonce control
Override automatic fee estimation and nonce fetching:
const transaction = await makeContractCall({contractAddress: 'ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM',contractName: 'my-contract',functionName: 'my-function',functionArgs: [],senderKey: 'your-private-key',network: 'testnet',nonce: '42', // Explicit noncefee: '5000', // Explicit fee in microSTXanchorMode: 'any' // Transaction timing preference});
Advanced post-conditions
Add safety constraints using the new v7 post-condition format:
const stxPostCondition = {type: 'stx-postcondition',address: 'SP2JXKMSH007NPYAQHKJPQMAQYAD90NQGTVJVQ02B',condition: 'gte',amount: '1000000'};const ftPostCondition = {type: 'ft-postcondition',address: 'SP2JXKMSH007NPYAQHKJPQMAQYAD90NQGTVJVQ02B',condition: 'eq',amount: '100',asset: 'SP3D6PV2ACBPEKYJTCMH7HEN02KP87QSP8KTEH335.my-token::token'};const transaction = await makeContractCall({// ... other optionspostConditions: [stxPostCondition, ftPostCondition]});
Fee estimation
Get fee estimates before building transactions:
import { fetchFeeEstimate } from '@stacks/transactions';// Estimate for a specific transactionconst feeEstimate = await fetchFeeEstimate({transaction,network: 'testnet'});// Use the estimateconst newTransaction = await makeSTXTokenTransfer({// ... other optionsfee: feeEstimate.toString()});
Working with Clarity values
Use the Cl
helper for building type-safe Clarity arguments:
import { Cl } from '@stacks/transactions';const functionArgs = [Cl.uint(42), // unsigned integerCl.int(-10), // signed integerCl.bool(true), // booleanCl.stringUtf8('Hello World'), // UTF-8 stringCl.stringAscii('ASCII only'), // ASCII stringCl.buffer(new Uint8Array([1, 2, 3])), // bufferCl.principal('ST2CY5V39NHDPWSXMW9QDT3HC3GD6Q6XX4CFRK9AG'), // principalCl.contractPrincipal('ST123...', 'my-contract'), // contract principalCl.list([Cl.uint(1), Cl.uint(2)]), // listCl.tuple({ name: Cl.stringUtf8('Alice') }), // tupleCl.some(Cl.uint(42)), // optional (some)Cl.none(), // optional (none)Cl.ok(Cl.uint(100)), // response (ok)Cl.error(Cl.uint(404)) // response (error)];
Error handling
Handle broadcast errors properly with the consistent v7 response format:
try {const result = await broadcastTransaction({transaction,network: 'testnet'});if (result.error) {console.error('Transaction rejected:', result.reason);console.error('Details:', result.reason_data);} else {console.log('Success:', result.txid);}} catch (error) {console.error('Network or serialization error:', error);}
Best practices
Transaction serialization
Transactions serialize to hex strings in v7 (not byte arrays):
const transaction = await makeSTXTokenTransfer({ /* options */ });const serialized = transaction.serialize(); // hex stringconsole.log('Serialized tx:', serialized);// For bytes (if needed for compatibility)const bytes = transaction.serializeBytes(); // Uint8Array
Nonce management
For multiple transactions, manage nonces carefully:
import { fetchNonce } from '@stacks/transactions';let currentNonce = await fetchNonce({address: 'ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM',network: 'testnet'});// Send multiple transactions with incremented noncesfor (const recipient of recipients) {const transaction = await makeSTXTokenTransfer({recipient,amount: '1000000',senderKey: privateKey,network: 'testnet',nonce: currentNonce.toString()});await broadcastTransaction({ transaction, network: 'testnet' });currentNonce++;}
ABI validation
Always validate contract calls against ABIs:
const transaction = await makeContractCall({contractAddress: 'ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM',contractName: 'my-contract',functionName: 'my-function',functionArgs: [Cl.uint(42)],senderKey: privateKey,network: 'testnet',validateWithAbi: true // Fetches ABI and validates arguments});
This comprehensive approach to transaction building gives you the flexibility to handle any blockchain interaction scenario while maintaining type safety and proper error handling.