hash becomes the condition for fund release.
import { createHash, randomBytes } from 'crypto';
export interface LockCommitment {
hashLock: string;
preimage: string;
expiryTimestamp: number; // Unix timestamp in seconds
}
export function generateCommitment(timeoutHours: number = 24): LockCommitment {
const preimage = randomBytes(32).toString('hex');
const hashLock = createHash('sha256').update(preimage).digest('hex');
const expiryTimestamp = Math.floor(Date.now() / 1000) + (timeoutHours * 3600);
return { hashLock, preimage, expiryTimestamp };
}
Step 2: Implement the settlement engine with state transitions
The engine tracks the lifecycle of a swap. It enforces that funds can only be released via the correct preimage or refunded after the timeout expires.
export type SwapState = 'LOCKED' | 'CLAIMED' | 'REFUNDED' | 'EXPIRED';
export interface SwapRecord {
id: string;
sourceChain: string;
targetChain: string;
commitment: LockCommitment;
state: SwapState;
lockedAt: number;
}
export class AtomicSwapEngine {
private swaps: Map<string, SwapRecord> = new Map();
public initiateSwap(
swapId: string,
sourceChain: string,
targetChain: string,
timeoutHours: number
): SwapRecord {
const commitment = generateCommitment(timeoutHours);
const record: SwapRecord = {
id: swapId,
sourceChain,
targetChain,
commitment,
state: 'LOCKED',
lockedAt: Math.floor(Date.now() / 1000)
};
this.swaps.set(swapId, record);
return record;
}
public verifyAndClaim(swapId: string, submittedPreimage: string): boolean {
const record = this.swaps.get(swapId);
if (!record || record.state !== 'LOCKED') return false;
const computedHash = createHash('sha256').update(submittedPreimage).digest('hex');
if (computedHash !== record.commitment.hashLock) {
throw new Error('Invalid preimage: hash mismatch');
}
record.state = 'CLAIMED';
return true;
}
public checkTimeout(swapId: string): SwapState {
const record = this.swaps.get(swapId);
if (!record) throw new Error('Swap not found');
const currentTime = Math.floor(Date.now() / 1000);
if (currentTime >= record.commitment.expiryTimestamp && record.state === 'LOCKED') {
record.state = 'REFUNDED';
}
return record.state;
}
}
Step 3: Architecture decisions and rationale
- Explicit timeout windows over indefinite locks: Timeouts are not just safety valves; they enable forward-dated settlement. By setting a 24-hour window, you create a T+24 atomic contract where price discovery happens at lock time, but execution is enforced on-chain later.
- Separate claim and refund paths: Cross-chain networks have different block times and finality guarantees. Ethereum may confirm in 12 seconds, while Bitcoin requires multiple confirmations. The engine must allow the counterparty to claim on the faster chain while the slower chainâs timeout remains open, preventing deadlock.
- State machine over simple conditionals: Tracking
LOCKED, CLAIMED, REFUNDED, and EXPIRED states prevents race conditions where a swap is claimed after a refund transaction is broadcast.
- Hash algorithm selection: SHA-256 is standard, but the implementation must account for quantum resistance roadmaps. In production, consider Keccak-256 or post-quantum hash functions if targeting long-lived contracts.
Pitfall Guide
-
Cross-Chain Block Time Mismatch
Explanation: Assuming both chains confirm transactions at the same speed leads to failed atomicity. If Chain A confirms in 15 seconds and Chain B takes 10 minutes, the party on Chain A can claim funds before the other party locks theirs.
Fix: Set the timeout on the faster chain to be significantly shorter than the slower chainâs timeout. The slower chainâs lock must expire after the faster chainâs claim window closes, ensuring the claiming party has already revealed the preimage.
-
Capital Lockup Inefficiency
Explanation: HTLCs require full collateral to be locked for the entire timeout duration. This ties up liquidity and increases opportunity cost, especially for high-frequency agent workflows.
Fix: Implement dynamic timeout sizing based on historical block time variance. Use liquidity pools or fractional collateralization where protocol design allows, and prioritize shorter windows for low-value swaps.
-
Timeout Coordination Failure
Explanation: Relying on wall-clock time instead of block height or chain-specific timestamps causes drift. Network latency or node sync issues can trigger premature refunds or missed claims.
Fix: Anchor timeouts to block numbers or chain-native timestamps. Implement a buffer period (e.g., +15% of expected block time) to account for network congestion before marking a swap as refundable.
-
Hash Preimage Leakage
Explanation: Broadcasting the preimage on-chain to claim funds exposes it to MEV bots or competing agents who can front-run the claim on the counterparty chain.
Fix: Use commit-reveal schemes for the preimage generation phase. Consider zero-knowledge proofs or threshold signatures if operating in high-competition environments. Never log or cache preimages in plaintext.
-
Ignoring Chain Finality vs. Block Confirmation
Explanation: Treating a single block confirmation as final is dangerous on chains prone to reorgs. An agent might claim funds on a chain that later reorganizes, invalidating the lock.
Fix: Define finality thresholds per chain (e.g., 12 confirmations for Ethereum, 6 for Bitcoin). Only transition state to CLAIMED after the required confirmation depth is reached.
-
Missing Refund Automation
Explanation: If a swap expires and the refund transaction isnât broadcast automatically, funds remain stranded in the contract until manual intervention.
Fix: Deploy a background watcher service that monitors expired locks and submits refund transactions with appropriate gas pricing. Implement idempotent refund handlers to prevent double-spending attempts.
-
Overcomplicating the Quote/RFQ Phase
Explanation: Attempting to embed complex pricing logic or multi-step negotiations inside the settlement contract bloats gas costs and increases attack surface.
Fix: Keep the settlement primitive minimal. Handle quote requests, sealed bids, and counterparty filtering off-chain or in a separate orchestration layer. The HTLC should only enforce the final agreed terms.
Production Bundle
Action Checklist
Decision Matrix
| Scenario | Recommended Approach | Why | Cost Impact |
|---|
| Agent purchasing API access or cloud services | Payment Rail (e.g., x402) | Direct merchant integration, low latency, familiar compliance | Low (transaction fees only) |
| Agent swapping ETH for BTC with another agent | HTLC Trustless Settlement | Eliminates custodial risk, atomic execution across chains | Medium (capital lockup + gas) |
| Multi-agent consortium requiring forward contracts | HTLC with extended timeout | Enforces T+N delivery without clearinghouse intermediation | High (prolonged capital commitment) |
| High-frequency micro-transactions between agents | Payment Rail with batch settlement | HTLC overhead is prohibitive for sub-cent values | Very Low (aggregated fees) |
| Cross-chain liquidity provisioning | HTLC + Liquidity Pool | Balances trustless execution with capital efficiency | Medium-High (pool management + lockup) |
Configuration Template
// settlement.config.ts
export interface ChainFinalityConfig {
chainId: number | string;
requiredConfirmations: number;
blockTimeSeconds: number;
timeoutBufferMultiplier: number; // e.g., 1.2 for 20% buffer
}
export interface SettlementEngineConfig {
chains: Record<string, ChainFinalityConfig>;
defaultTimeoutHours: number;
maxConcurrentSwaps: number;
gasStrategy: 'standard' | 'aggressive' | 'eip1559';
preimageSecurity: 'software' | 'hsm' | 'mcp';
refundWatcherIntervalMs: number;
}
export const defaultConfig: SettlementEngineConfig = {
chains: {
ethereum: { chainId: 1, requiredConfirmations: 12, blockTimeSeconds: 12, timeoutBufferMultiplier: 1.15 },
bitcoin: { chainId: 'btc', requiredConfirmations: 6, blockTimeSeconds: 600, timeoutBufferMultiplier: 1.25 },
sui: { chainId: 'sui', requiredConfirmations: 1, blockTimeSeconds: 2, timeoutBufferMultiplier: 1.1 }
},
defaultTimeoutHours: 24,
maxConcurrentSwaps: 50,
gasStrategy: 'eip1559',
preimageSecurity: 'hsm',
refundWatcherIntervalMs: 30000
};
Quick Start Guide
- Initialize the engine: Import the
AtomicSwapEngine and load your chain-specific finality configuration. Ensure timeout buffers account for the slowest network in your swap pair.
- Generate a commitment: Call
generateCommitment() to create a SHA-256 hash and random preimage. Share only the hash with the counterparty agent.
- Lock funds on-chain: Deploy or interact with your HTLC contract using the hash and timeout. Broadcast the lock transaction on the source chain.
- Monitor and claim: Run the background watcher to track block confirmations. Once the counterparty reveals the preimage on their chain, submit the claim transaction with appropriate gas pricing.
- Handle expiration: If the timeout expires before a claim, the watcher automatically submits the refund transaction. Verify the state transitions to
REFUNDED and reconcile ledger balances.