Atomic Swaps

Complete examples for implementing atomic swaps using Hash Time-Locked Contracts (HTLC) on the BSV blockchain, enabling trustless peer-to-peer exchanges.

Overview

Atomic swaps allow two parties to exchange digital assets without requiring a trusted third party. Using Hash Time-Locked Contracts (HTLC), participants can either complete the swap or safely recover their funds if the counterparty fails to fulfill their obligations. This ensures atomicity: either both parties receive their assets or neither does.

Related SDK Components:

HTLC Script Builder

import { Transaction, PrivateKey, PublicKey, Script, Hash, OP } from '@bsv/sdk'

/**
 * Hash Time-Locked Contract (HTLC) Builder
 *
 * Creates and manages HTLC scripts for atomic swaps.
 */
class HTLCBuilder {
  /**
   * Create an HTLC locking script
   *
   * The HTLC allows spending in two ways:
   * 1. With the secret preimage (before timeout) - for the recipient
   * 2. After timeout with refund key - for the sender
   *
   * @param recipientPubKey - Public key of the recipient
   * @param refundPubKey - Public key for refund (sender)
   * @param secretHash - Hash of the secret (SHA256)
   * @param lockTime - Unix timestamp for timeout
   * @returns HTLC locking script
   */
  createHTLCScript(
    recipientPubKey: PublicKey,
    refundPubKey: PublicKey,
    secretHash: string,
    lockTime: number
  ): Script {
    try {
      // HTLC Script:
      // IF
      //   OP_SHA256 <secretHash> OP_EQUALVERIFY <recipientPubKey> OP_CHECKSIG
      // ELSE
      //   <lockTime> OP_CHECKLOCKTIMEVERIFY OP_DROP <refundPubKey> OP_CHECKSIG
      // ENDIF

      const script = new Script()

      // IF branch (claim with secret)
      script.chunks.push({ op: OP.OP_IF })
      script.chunks.push({ op: OP.OP_SHA256 })
      script.chunks.push({ data: Buffer.from(secretHash, 'hex') })
      script.chunks.push({ op: OP.OP_EQUALVERIFY })
      script.chunks.push({ data: recipientPubKey.toBuffer() })
      script.chunks.push({ op: OP.OP_CHECKSIG })

      // ELSE branch (refund after timeout)
      script.chunks.push({ op: OP.OP_ELSE })
      script.chunks.push({ data: this.numberToBuffer(lockTime) })
      script.chunks.push({ op: OP.OP_CHECKLOCKTIMEVERIFY })
      script.chunks.push({ op: OP.OP_DROP })
      script.chunks.push({ data: refundPubKey.toBuffer() })
      script.chunks.push({ op: OP.OP_CHECKSIG })

      // End IF
      script.chunks.push({ op: OP.OP_ENDIF })

      return script
    } catch (error) {
      throw new Error(`HTLC script creation failed: ${error.message}`)
    }
  }

  /**
   * Create unlocking script to claim with secret
   */
  createClaimScript(
    secret: string,
    signature: Buffer,
    recipientPubKey: PublicKey
  ): Script {
    const script = new Script()

    // Push signature
    script.chunks.push({ data: signature })

    // Push secret preimage
    script.chunks.push({ data: Buffer.from(secret, 'hex') })

    // Push TRUE to select IF branch
    script.chunks.push({ op: OP.OP_TRUE })

    return script
  }

  /**
   * Create unlocking script for refund
   */
  createRefundScript(
    signature: Buffer,
    refundPubKey: PublicKey
  ): Script {
    const script = new Script()

    // Push signature
    script.chunks.push({ data: signature })

    // Push FALSE to select ELSE branch
    script.chunks.push({ op: OP.OP_FALSE })

    return script
  }

  /**
   * Generate a random secret
   */
  generateSecret(): { secret: string; hash: string } {
    const secret = Buffer.from(PrivateKey.fromRandom().toHex(), 'hex')
    const hash = Hash.sha256(secret)

    return {
      secret: secret.toString('hex'),
      hash: hash.toString('hex')
    }
  }

  /**
   * Convert number to buffer for script
   */
  private numberToBuffer(num: number): Buffer {
    if (num === 0) return Buffer.from([])
    if (num === -1) return Buffer.from([0x81])

    const isNegative = num < 0
    const absNum = Math.abs(num)
    const bytes: number[] = []

    let n = absNum
    while (n > 0) {
      bytes.push(n & 0xff)
      n >>= 8
    }

    if (bytes[bytes.length - 1] & 0x80) {
      bytes.push(isNegative ? 0x80 : 0x00)
    } else if (isNegative) {
      bytes[bytes.length - 1] |= 0x80
    }

    return Buffer.from(bytes)
  }
}

/**
 * Usage Example
 */
async function htlcExample() {
  const builder = new HTLCBuilder()

  // Generate keys
  const recipientKey = PrivateKey.fromRandom()
  const refundKey = PrivateKey.fromRandom()

  // Generate secret
  const { secret, hash } = builder.generateSecret()

  // Lock time: 24 hours from now
  const lockTime = Math.floor(Date.now() / 1000) + (24 * 60 * 60)

  // Create HTLC script
  const htlcScript = builder.createHTLCScript(
    recipientKey.toPublicKey(),
    refundKey.toPublicKey(),
    hash,
    lockTime
  )

  console.log('HTLC Script created')
  console.log('Secret hash:', hash)
  console.log('Lock time:', new Date(lockTime * 1000).toISOString())
}

Atomic Swap Implementation

Cross-Chain Atomic Swap

Swap Safety and Recovery

See Also

SDK Components:

Learning Paths:

Last updated