Transaction Builder
The Transaction Builder (@pact-toolbox/transaction
) provides a powerful, type-safe API for building and executing Pact transactions on the Kadena blockchain. It features a fluent interface, automatic signing, and seamless wallet integration.
Features
- 🔨 Fluent API - Chainable methods for intuitive transaction building
- 📝 Type Safety - Full TypeScript support with auto-completion
- 💼 Multi-Wallet Support - Works with all Kadena wallets
- ⚡ Auto-signing - Automatic transaction signing and submission
- 🔄 Batch Operations - Efficient handling of multiple transactions
- 🛡️ Built-in Validation - Comprehensive input validation
- 📊 Gas Optimization - Smart gas limit calculation
- 🔍 Transaction Tracking - Monitor transaction lifecycle
Installation
# npm
npm install @pact-toolbox/transaction
# pnpm
pnpm add @pact-toolbox/transaction
# yarn
yarn add @pact-toolbox/transaction
Quick Start
import { execution } from '@pact-toolbox/transaction'
// Create a simple transfer transaction
const result = await execution('(coin.transfer "alice" "bob" 1.0)')
.withChainId('0')
.withSigner('alice-public-key', (signFor) => [
signFor('coin.TRANSFER', 'alice', 'bob', 1.0)
])
.withGasLimit(1000)
.sign()
.submitAndListen()
console.log('Transfer successful:', result)
Core Concepts
Transaction Builder
The transaction builder provides a fluent interface for constructing Pact transactions:
const tx = execution('(+ 1 2)') // Pact code to execute
.withData('x', 10) // Add data to environment
.withData('y', 20)
.withChainId('0') // Target chain
.withGasLimit(1000) // Gas limit
.withGasPrice(0.000001) // Gas price
.withTTL(600) // Time to live (seconds)
.withNonce('unique-nonce') // Transaction nonce
Signing and Submission
// Sign with default wallet
const signed = await tx.sign()
// Submit to network
const requestKey = await signed.submit()
// Submit and wait for result
const result = await signed.submitAndListen()
// Submit with custom timeout
const result = await signed.submitAndListen({
timeout: 30000 // 30 seconds
})
Advanced Usage
Working with Capabilities
const tx = createTransaction()
.code('(coin.transfer "alice" "bob" 1.0)')
.capability('coin.TRANSFER', 'alice', 'bob', { decimal: '1.0' })
.signer({
pubKey: 'alice-public-key',
scheme: 'ED25519',
caps: [
{ name: 'coin.TRANSFER', args: ['alice', 'bob', { decimal: '1.0' }] },
{ name: 'coin.GAS', args: [] }
]
})
Multi-Signature Transactions
const tx = createTransaction()
.code('(free.multi-sig-action)')
.signer({
pubKey: 'alice-key',
scheme: 'ED25519',
caps: []
})
.signer({
pubKey: 'bob-key',
scheme: 'ED25519',
caps: []
})
.signer({
pubKey: 'charlie-key',
scheme: 'ED25519',
caps: []
})
Continuation Transactions
// Initial transaction with pact-id
const step1 = await createTransaction()
.code('(free.multi-step.start)')
.pactId('unique-pact-id')
.step(0)
.rollback(false)
.sign()
.submitAndListen()
// Continuation
const step2 = await createTransaction()
.continuation({
pactId: 'unique-pact-id',
step: 1,
rollback: false,
data: { additionalData: 'value' }
})
.sign()
.submitAndListen()
Batch Transactions
import { batchTransaction } from '@pact-toolbox/transaction'
// Create multiple transactions
const transactions = [
createTransaction().code('(+ 1 1)'),
createTransaction().code('(+ 2 2)'),
createTransaction().code('(+ 3 3)')
]
// Execute in batch
const results = await batchTransaction(transactions)
.chainId('0')
.sender('batch-sender')
.gasLimit(1000)
.sign()
.submitAndListen()
console.log('Batch results:', results)
Integration with Pact Contracts
Type-Safe Contract Calls
When used with the unplugin, you get fully typed contract methods:
import { todos } from './contracts/todos.pact'
// Automatic type inference
const result = await todos
.createTodo({
id: 'todo-1',
title: 'Learn Pact',
completed: false
})
.sign()
.submitAndListen()
// TypeScript knows result.data has the todo structure
console.log(result.data.id) // Type-safe access
Custom Transaction Options
const result = await todos
.createTodo({ id: '1', title: 'Task', completed: false })
.withOptions({
sender: 'alice',
gasLimit: 2000,
gasPrice: 0.000001,
chainId: '1',
ttl: 1800 // 30 minutes
})
.sign()
.submitAndListen()
Wallet Integration
Automatic Wallet Detection
The transaction builder automatically detects and uses available wallets:
// Automatically uses connected wallet
const result = await createTransaction()
.code('(coin.transfer "alice" "bob" 1.0)')
.sign() // Prompts user in their wallet
.submitAndListen()
Specific Wallet Usage
import { ChainweaverWallet, EckoWallet } from '@pact-toolbox/wallet-adapters'
// Use specific wallet
const chainweaver = new ChainweaverWallet()
const result = await createTransaction()
.code('(coin.transfer "alice" "bob" 1.0)')
.sign({ wallet: chainweaver })
.submitAndListen()
Manual Signing
import { sign } from '@pact-toolbox/crypto'
// Build unsigned transaction
const unsigned = createTransaction()
.code('(+ 1 2)')
.build()
// Sign manually
const signature = sign(unsigned.hash, privateKey)
// Add signature
const signed = unsigned.addSignature({
sig: signature,
pubKey: publicKey,
scheme: 'ED25519'
})
// Submit
const result = await signed.submitAndListen()
Gas Estimation
Automatic Gas Estimation
const tx = await createTransaction()
.code('(complex-operation)')
.estimateGas() // Automatically sets optimal gas limit
console.log('Estimated gas:', tx.gasLimit)
Manual Gas Configuration
const tx = createTransaction()
.code('(simple-operation)')
.gasLimit(500) // Manual limit
.gasPrice(0.000001) // 1e-6 KDA per gas
.gasPayer('gas-station') // Third-party gas payer
.gasPayerCaps(['coin.GAS']) // Gas payer capabilities
Error Handling
Transaction Errors
try {
const result = await createTransaction()
.code('(invalid-function)')
.sign()
.submitAndListen()
} catch (error) {
if (error instanceof TransactionError) {
console.error('Transaction failed:', error.message)
console.error('Request key:', error.requestKey)
console.error('Result:', error.result)
}
}
Validation Errors
try {
const tx = createTransaction()
.code('') // Empty code
.build()
} catch (error) {
if (error instanceof ValidationError) {
console.error('Validation failed:', error.field, error.message)
}
}
Timeout Handling
const result = await createTransaction()
.code('(long-running-operation)')
.sign()
.submitAndListen({
timeout: 60000, // 60 seconds
onTimeout: () => {
console.log('Transaction is taking longer than expected...')
// Can still continue waiting or abort
}
})
Transaction Lifecycle
Status Monitoring
const tx = createTransaction()
.code('(coin.transfer "alice" "bob" 1.0)')
.sign()
// Submit and get request key
const requestKey = await tx.submit()
console.log('Submitted:', requestKey)
// Poll for status
const status = await tx.poll(requestKey)
console.log('Status:', status)
// Wait for completion
const result = await tx.listen(requestKey)
console.log('Completed:', result)
Event Handling
const result = await createTransaction()
.code('(emit-event "transfer" { from: "alice", to: "bob", amount: 1.0 })')
.onEvent('transfer', (event) => {
console.log('Transfer event:', event)
})
.sign()
.submitAndListen()
Configuration
Global Configuration
import { configureTransaction } from '@pact-toolbox/transaction'
// Set global defaults
configureTransaction({
networkId: 'testnet04',
chainId: '0',
gasLimit: 1000,
gasPrice: 0.000001,
ttl: 600,
sender: 'default-sender'
})
// All transactions will use these defaults
const tx = createTransaction()
.code('(+ 1 2)')
// No need to set chainId, gasLimit, etc.
Network-Specific Configuration
import { createTransactionForNetwork } from '@pact-toolbox/transaction'
// Create transaction for specific network
const mainnetTx = createTransactionForNetwork('mainnet01')
.code('(coin.get-balance "alice")')
const testnetTx = createTransactionForNetwork('testnet04')
.code('(coin.get-balance "alice")')
Best Practices
1. Always Set Appropriate TTL
// Short TTL for time-sensitive operations
const urgentTx = createTransaction()
.code('(time-sensitive-operation)')
.ttl(60) // 1 minute
// Longer TTL for complex operations
const complexTx = createTransaction()
.code('(complex-operation)')
.ttl(3600) // 1 hour
2. Use Meaningful Nonces
const tx = createTransaction()
.code('(operation)')
.nonce(`user-${userId}-${Date.now()}`)
3. Handle Capabilities Properly
// Always request only required capabilities
const tx = createTransaction()
.code('(coin.transfer "alice" "bob" 1.0)')
.capability('coin.TRANSFER', 'alice', 'bob', { decimal: '1.0' })
// Don't add unnecessary capabilities
4. Validate Before Submission
const tx = createTransaction()
.code('(complex-operation)')
// Validate locally first
const validation = await tx.validate()
if (validation.valid) {
const result = await tx.sign().submitAndListen()
} else {
console.error('Validation errors:', validation.errors)
}
API Reference
createTransaction()
Creates a new transaction builder instance.
Transaction Builder Methods
code(pactCode: string)
- Set Pact code
data(envData: object)
- Set environment data
sender(account: string)
- Set sender account
chainId(id: string)
- Set target chain
gasLimit(limit: number)
- Set gas limit
gasPrice(price: number)
- Set gas price
ttl(seconds: number)
- Set time to live
nonce(value: string)
- Set nonce
capability(name: string, ...args: any[])
- Add capability
signer(signerInfo: SignerInfo)
- Add signer
build()
- Build unsigned transaction
sign(options?: SignOptions)
- Sign transaction
submit()
- Submit to network
submitAndListen(options?: ListenOptions)
- Submit and wait
Types
interface TransactionBuilder {
// Builder methods...
}
interface SignOptions {
wallet?: Wallet
signers?: Signer[]
}
interface ListenOptions {
timeout?: number
pollInterval?: number
onTimeout?: () => void
}
interface TransactionResult {
requestKey: string
status: 'success' | 'failure'
data: any
events: Event[]
gas: number
logs: string
continuation?: Continuation
}