Hash Time-Locked Contracts on XRPL | XRPL Interoperability | XRP Academy - XRP Academy
3 free lessons remaining this month

Free preview access resets monthly

Upgrade for Unlimited
Skip to main content
advanced50 min

Hash Time-Locked Contracts on XRPL

Learning Objectives

Explain the cryptographic mechanics of Hash Time-Locked Contracts

Implement XRPL Escrow transactions for HTLC functionality

Construct cross-chain atomic swaps between XRPL and other chains

Analyze the security guarantees and limitations of HTLC-based swaps

Evaluate when HTLCs are appropriate vs. other cross-chain methods

Every bridge solution we've examined requires trusting someone—validators, custodians, witnesses. What if we could swap assets across chains without trusting anyone?

HTLCs offer this promise.

  • Both parties can claim their assets (successful swap), or
  • Both parties get their original assets back (failed/expired swap)

No third party can steal funds. No bridge can be exploited. The security comes from mathematics, not economics or reputation.

But there are tradeoffs. HTLCs require both parties to be online, have timing constraints, and don't scale well for complex multi-party scenarios. Understanding both the power and limitations is essential.


What is an HTLC?

HASH TIME-LOCKED CONTRACT

Components:
├── HASH LOCK: Cryptographic puzzle (hash preimage)
│   └── Funds locked until someone provides the secret
│
├── TIME LOCK: Expiration deadline
│   └── If unclaimed by deadline, funds return to sender
│
└── CONDITIONAL RELEASE: Either claim with secret OR timeout

Key Properties:
├── Atomic: All-or-nothing execution
├── Trustless: No third party needed
├── Self-enforcing: Protocol guarantees outcomes
└── Reversible: Timeout returns funds if swap fails

The Cryptographic Puzzle:

HASH LOCK MECHANICS

1. Alice generates random secret (preimage): S = "secret123..."
2. Alice computes hash: H = SHA256(S) = "a1b2c3d4..."
3. Alice shares ONLY the hash H, keeps secret S private

1. Funds locked with condition: "Release if someone provides X where SHA256(X) = H"

1. Only someone who knows S can satisfy SHA256(S) = H
2. Once S is revealed to claim funds, it's public
3. Other party can use revealed S to claim their side

Security:
├── Hash is one-way: Can't derive S from H
├── Hash is collision-resistant: Can't find different S' where SHA256(S') = H
├── Preimage must match exactly
└── Based on SHA256 security (cryptographically secure)

Two-Party Cross-Chain Swap:

ATOMIC SWAP: ALICE (XRP) ↔ BOB (ETH)

Alice has: 1000 XRP on XRPL
Bob has: 0.5 ETH on Ethereum
They want to swap trustlessly.

PROTOCOL:

Phase 1: Setup
┌─────────────────────────────────────────────────────────────┐
│ 1. Alice generates secret S │
│ 2. Alice computes H = SHA256(S) │
│ 3. Alice shares H with Bob │
└─────────────────────────────────────────────────────────────┘

Phase 2: Lock (Alice goes first)
┌─────────────────────────────────────────────────────────────┐
│ 4. Alice creates HTLC on XRPL: │
│ - Lock: 1000 XRP │
│ - Hash: H │
│ - Timeout: 48 hours │
│ - Recipient: Bob │
│ │
│ 5. Bob verifies Alice's HTLC on XRPL │
└─────────────────────────────────────────────────────────────┘

Phase 3: Counter-Lock (Bob responds)
┌─────────────────────────────────────────────────────────────┐
│ 6. Bob creates HTLC on Ethereum: │
│ - Lock: 0.5 ETH │
│ - Hash: H (same hash!) │
│ - Timeout: 24 hours (SHORTER than Alice's) │
│ - Recipient: Alice │
│ │
│ 7. Alice verifies Bob's HTLC on Ethereum │
└─────────────────────────────────────────────────────────────┘

Phase 4: Claim (Alice reveals secret)
┌─────────────────────────────────────────────────────────────┐
│ 8. Alice claims Bob's ETH by revealing S │
│ - Alice submits S to Ethereum HTLC │
│ - Contract verifies SHA256(S) = H │
│ - Alice receives 0.5 ETH │
│ - S is now PUBLIC on Ethereum blockchain │
└─────────────────────────────────────────────────────────────┘

Phase 5: Counter-Claim (Bob uses revealed secret)
┌─────────────────────────────────────────────────────────────┐
│ 9. Bob sees S revealed on Ethereum │
│ 10. Bob claims Alice's XRP using same S │
│ - Bob submits S to XRPL Escrow │
│ - Escrow verifies hash condition │
│ - Bob receives 1000 XRP │
└─────────────────────────────────────────────────────────────┘

RESULT: Trustless swap completed!
Alice: Had 1000 XRP → Now has 0.5 ETH
Bob: Had 0.5 ETH → Now has 1000 XRP
```

The timing is critical:

TIMEOUT STRUCTURE

Alice's HTLC (XRPL):    48 hours
Bob's HTLC (Ethereum):  24 hours

WHY BOB'S TIMEOUT IS SHORTER:

  1. Alice creates HTLC with 24h timeout
  2. Bob creates HTLC with 24h timeout
  3. Alice waits until hour 23
  4. Alice claims Bob's ETH, revealing S
  5. Bob tries to claim XRP but Alice's HTLC expired!
  6. Alice gets BOTH the ETH AND her XRP back

ATTACK PREVENTED:
By making Bob's timeout shorter, Alice must reveal S
with enough time for Bob to claim before Alice's HTLC expires.

SAFE TIMING:
├── First HTLC: T hours
├── Second HTLC: T/2 hours (or less)
├── Buffer for blockchain confirmation times
└── Account for potential network congestion


---

Native HTLC Support:

XRPL has built-in Escrow functionality that supports both time-based and crypto-condition-based releases—perfect for HTLCs.

XRPL ESCROW FEATURES

Transaction Types:
├── EscrowCreate: Lock XRP with conditions
├── EscrowFinish: Claim XRP by meeting conditions
└── EscrowCancel: Return XRP after timeout

Condition Types:
├── Time-based: FinishAfter, CancelAfter
├── Crypto-condition: Condition + Fulfillment
└── Combined: Both time AND crypto conditions

Crypto-Condition Standard:
├── Uses IETF crypto-conditions (RFC)
├── Supports PREIMAGE-SHA-256
├── Condition = encoded hash
├── Fulfillment = encoded preimage
└── Standard format for interoperability

EscrowCreate Transaction:

// Create HTLC on XRPL using xrpl.js
const xrpl = require('xrpl');
const crypto = require('crypto');
const cc = require('five-bells-condition');

async function createHTLC() {
  // Connect to XRPL
  const client = new xrpl.Client('wss://s1.ripple.com');
  await client.connect();

// Alice's wallet
  const alice = xrpl.Wallet.fromSeed('sAliceSecret...');

// Generate secret and condition
  const secret = crypto.randomBytes(32);
  const preimageCondition = new cc.PreimageSha256();
  preimageCondition.setPreimage(secret);

const condition = preimageCondition
    .getConditionBinary()
    .toString('hex')
    .toUpperCase();

const fulfillment = preimageCondition
    .serializeBinary()
    .toString('hex')
    .toUpperCase();

console.log('Secret (keep private):', secret.toString('hex'));
  console.log('Condition (share with Bob):', condition);
  console.log('Fulfillment (to claim):', fulfillment);

// Calculate timeouts
  const now = Math.floor(Date.now() / 1000);
  const xrplEpoch = 946684800; // XRPL epoch offset
  const cancelAfter = now - xrplEpoch + (48 * 60 * 60); // 48 hours
  const finishAfter = now - xrplEpoch + (60); // Can finish after 1 minute

// Create Escrow
  const escrowCreate = {
    TransactionType: 'EscrowCreate',
    Account: alice.address,
    Amount: xrpl.xrpToDrops('1000'), // 1000 XRP
    Destination: 'rBobAddress...', // Bob's XRPL address
    Condition: condition,
    CancelAfter: cancelAfter,
    FinishAfter: finishAfter
  };

const prepared = await client.autofill(escrowCreate);
  const signed = alice.sign(prepared);
  const result = await client.submitAndWait(signed.tx_blob);

console.log('Escrow created!');
  console.log('Transaction:', result.result.hash);
  console.log('Sequence:', prepared.Sequence); // Needed for claiming

await client.disconnect();

return {
    condition,
    fulfillment,
    secret: secret.toString('hex'),
    sequence: prepared.Sequence,
    owner: alice.address
  };
}

EscrowFinish Transaction:

// Bob claims the escrow using the revealed fulfillment
async function claimHTLC(owner, sequence, fulfillment, condition) {
  const client = new xrpl.Client('wss://s1.ripple.com');
  await client.connect();

// Bob's wallet
  const bob = xrpl.Wallet.fromSeed('sBobSecret...');

// Finish (claim) the Escrow
  const escrowFinish = {
    TransactionType: 'EscrowFinish',
    Account: bob.address,
    Owner: owner, // Alice's address (escrow creator)
    OfferSequence: sequence, // From EscrowCreate
    Condition: condition,
    Fulfillment: fulfillment // The revealed secret
  };

const prepared = await client.autofill(escrowFinish);
  const signed = bob.sign(prepared);
  const result = await client.submitAndWait(signed.tx_blob);

if (result.result.meta.TransactionResult === 'tesSUCCESS') {
    console.log('Escrow claimed successfully!');
    console.log('Bob received 1000 XRP');
  } else {
    console.log('Claim failed:', result.result.meta.TransactionResult);
  }

await client.disconnect();
}

EscrowCancel Transaction:

// Cancel expired escrow (return funds to Alice)
async function cancelExpiredHTLC(owner, sequence) {
  const client = new xrpl.Client('wss://s1.ripple.com');
  await client.connect();

// Anyone can cancel an expired escrow
  // but funds always return to original owner
  const anyWallet = xrpl.Wallet.fromSeed('sAnySecret...');

const escrowCancel = {
    TransactionType: 'EscrowCancel',
    Account: anyWallet.address,
    Owner: owner,
    OfferSequence: sequence
  };

const prepared = await client.autofill(escrowCancel);
  const signed = anyWallet.sign(prepared);
  const result = await client.submitAndWait(signed.tx_blob);

console.log('Escrow cancelled, funds returned to:', owner);

await client.disconnect();
}

Complete Implementation:

// atomic-swap.js - Full cross-chain atomic swap
const xrpl = require('xrpl');
const { ethers } = require('ethers');
const crypto = require('crypto');
const cc = require('five-bells-condition');

// Ethereum HTLC Contract ABI (simplified)
const HTLC_ABI = [
  "function newContract(bytes32 _hashlock, uint256 _timelock, address _receiver) payable returns (bytes32 contractId)",
  "function withdraw(bytes32 _contractId, bytes32 _preimage) returns (bool)",
  "function refund(bytes32 _contractId) returns (bool)",
  "function getContract(bytes32 _contractId) view returns (address sender, address receiver, uint256 amount, bytes32 hashlock, uint256 timelock, bool withdrawn, bool refunded, bytes32 preimage)",
  "event HTLCERC20New(bytes32 indexed contractId, address indexed sender, address indexed receiver, uint256 amount, bytes32 hashlock, uint256 timelock)",
  "event HTLCERC20Withdraw(bytes32 indexed contractId)",
  "event HTLCERC20Refund(bytes32 indexed contractId)"
];

class AtomicSwap {
  constructor(config) {
    this.xrplClient = null;
    this.ethProvider = new ethers.JsonRpcProvider(config.ethRpcUrl);
    this.htlcContract = new ethers.Contract(
      config.htlcAddress,
      HTLC_ABI,
      this.ethProvider
    );
  }

async connect() {
    this.xrplClient = new xrpl.Client('wss://s1.ripple.com');
    await this.xrplClient.connect();
  }

async disconnect() {
    await this.xrplClient.disconnect();
  }

/**
   * Generate secret and hash for HTLC
   */
  generateSecretHash() {
    // Generate 32-byte random secret
    const secret = crypto.randomBytes(32);

// Create hash using SHA256
    const hash = crypto.createHash('sha256').update(secret).digest();

// Create crypto-condition format for XRPL
    const preimageCondition = new cc.PreimageSha256();
    preimageCondition.setPreimage(secret);

return {
      secret: secret,
      secretHex: '0x' + secret.toString('hex'),
      hashHex: '0x' + hash.toString('hex'),
      xrplCondition: preimageCondition.getConditionBinary().toString('hex').toUpperCase(),
      xrplFulfillment: preimageCondition.serializeBinary().toString('hex').toUpperCase()
    };
  }

/**
   * Alice: Create HTLC on XRPL (Step 1)
   */
  async createXrplHtlc(aliceWallet, bobXrplAddress, amountXrp, secretHash, timeoutHours) {
    const now = Math.floor(Date.now() / 1000);
    const xrplEpoch = 946684800;

const escrowCreate = {
      TransactionType: 'EscrowCreate',
      Account: aliceWallet.address,
      Amount: xrpl.xrpToDrops(amountXrp),
      Destination: bobXrplAddress,
      Condition: secretHash.xrplCondition,
      CancelAfter: now - xrplEpoch + (timeoutHours * 60 * 60),
      FinishAfter: now - xrplEpoch + 60 // 1 minute minimum
    };

const prepared = await this.xrplClient.autofill(escrowCreate);
    const signed = aliceWallet.sign(prepared);
    const result = await this.xrplClient.submitAndWait(signed.tx_blob);

return {
      success: result.result.meta.TransactionResult === 'tesSUCCESS',
      txHash: result.result.hash,
      sequence: prepared.Sequence,
      owner: aliceWallet.address
    };
  }

/**
   * Bob: Verify Alice's XRPL HTLC (Step 2)
   */
  async verifyXrplHtlc(owner, sequence, expectedAmount, expectedHash, expectedDestination) {
    // Get escrow details from XRPL
    const escrowInfo = await this.xrplClient.request({
      command: 'account_objects',
      account: owner,
      type: 'escrow'
    });

// Find the specific escrow
    const escrow = escrowInfo.result.account_objects.find(
      obj => obj.Sequence === sequence
    );

if (!escrow) {
      return { valid: false, reason: 'Escrow not found' };
    }

// Verify all conditions
    const checks = {
      amount: escrow.Amount === xrpl.xrpToDrops(expectedAmount),
      destination: escrow.Destination === expectedDestination,
      condition: escrow.Condition === expectedHash,
      notExpired: escrow.CancelAfter > (Math.floor(Date.now() / 1000) - 946684800)
    };

const valid = Object.values(checks).every(v => v);

return {
      valid,
      checks,
      escrow
    };
  }

/**
   * Bob: Create counter-HTLC on Ethereum (Step 3)
   */
  async createEthHtlc(bobSigner, aliceEthAddress, amountEth, hashHex, timeoutHours) {
    const htlcWithSigner = this.htlcContract.connect(bobSigner);

const timelock = Math.floor(Date.now() / 1000) + (timeoutHours * 60 * 60);

const tx = await htlcWithSigner.newContract(
      hashHex,
      timelock,
      aliceEthAddress,
      { value: ethers.parseEther(amountEth.toString()) }
    );

const receipt = await tx.wait();

// Extract contractId from event
    const event = receipt.logs.find(
      log => log.topics[0] === ethers.id("HTLCERC20New(bytes32,address,address,uint256,bytes32,uint256)")
    );

const contractId = event.topics[1];

return {
      success: receipt.status === 1,
      txHash: receipt.hash,
      contractId
    };
  }

/**
   * Alice: Claim ETH by revealing secret (Step 4)
   */
  async claimEthHtlc(aliceSigner, contractId, secretHex) {
    const htlcWithSigner = this.htlcContract.connect(aliceSigner);

const tx = await htlcWithSigner.withdraw(contractId, secretHex);
    const receipt = await tx.wait();

return {
      success: receipt.status === 1,
      txHash: receipt.hash
    };
  }

/**
   * Bob: Extract revealed secret from Ethereum (Step 5)
   */
  async extractRevealedSecret(contractId) {
    const contract = await this.htlcContract.getContract(contractId);

if (contract.withdrawn) {
      return {
        revealed: true,
        preimage: contract.preimage
      };
    }

return { revealed: false };
  }

/**
   * Bob: Claim XRP using revealed secret (Step 6)
   */
  async claimXrplHtlc(bobWallet, owner, sequence, condition, fulfillment) {
    const escrowFinish = {
      TransactionType: 'EscrowFinish',
      Account: bobWallet.address,
      Owner: owner,
      OfferSequence: sequence,
      Condition: condition,
      Fulfillment: fulfillment
    };

const prepared = await this.xrplClient.autofill(escrowFinish);
    const signed = bobWallet.sign(prepared);
    const result = await this.xrplClient.submitAndWait(signed.tx_blob);

return {
      success: result.result.meta.TransactionResult === 'tesSUCCESS',
      txHash: result.result.hash
    };
  }
}

// Usage example
async function executeAtomicSwap() {
  const swap = new AtomicSwap({
    ethRpcUrl: 'https://mainnet.infura.io/v3/YOUR_KEY', 
    htlcAddress: '0x...' // Deployed HTLC contract
  });

await swap.connect();

// 1. Alice generates secret
  const secretHash = swap.generateSecretHash();
  console.log('Secret generated (Alice keeps private)');

// 2. Alice creates XRPL HTLC
  const aliceWallet = xrpl.Wallet.fromSeed('sAlice...');
  const xrplHtlc = await swap.createXrplHtlc(
    aliceWallet,
    'rBobXrplAddress',
    1000,  // 1000 XRP
    secretHash,
    48     // 48 hour timeout
  );
  console.log('Alice created XRPL HTLC:', xrplHtlc.txHash);

// 3. Bob verifies and creates Ethereum HTLC
  // (Bob would do this after verifying Alice's HTLC)
  const bobEthWallet = new ethers.Wallet('0xBobPrivateKey', swap.ethProvider);
  const ethHtlc = await swap.createEthHtlc(
    bobEthWallet,
    '0xAliceEthAddress',
    0.5,   // 0.5 ETH
    secretHash.hashHex,
    24     // 24 hour timeout (shorter!)
  );
  console.log('Bob created Ethereum HTLC:', ethHtlc.txHash);

// 4. Alice claims ETH (reveals secret)
  const aliceEthWallet = new ethers.Wallet('0xAlicePrivateKey', swap.ethProvider);
  const ethClaim = await swap.claimEthHtlc(
    aliceEthWallet,
    ethHtlc.contractId,
    secretHash.secretHex
  );
  console.log('Alice claimed ETH:', ethClaim.txHash);

// 5. Bob extracts revealed secret and claims XRP
  const revealed = await swap.extractRevealedSecret(ethHtlc.contractId);
  const bobWallet = xrpl.Wallet.fromSeed('sBob...');
  const xrpClaim = await swap.claimXrplHtlc(
    bobWallet,
    xrplHtlc.owner,
    xrplHtlc.sequence,
    secretHash.xrplCondition,
    secretHash.xrplFulfillment
  );
  console.log('Bob claimed XRP:', xrpClaim.txHash);

console.log('Atomic swap completed!');

await swap.disconnect();
}

Solidity Implementation:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

/**
 * @title HashedTimeLockContract
 * @dev HTLC for atomic swaps with XRPL
 */
contract HashedTimeLockContract {
    struct LockContract {
        address sender;
        address receiver;
        uint256 amount;
        bytes32 hashlock;
        uint256 timelock;
        bool withdrawn;
        bool refunded;
        bytes32 preimage;
    }

mapping(bytes32 => LockContract) public contracts;

event HTLCERC20New(
        bytes32 indexed contractId,
        address indexed sender,
        address indexed receiver,
        uint256 amount,
        bytes32 hashlock,
        uint256 timelock
    );

event HTLCERC20Withdraw(bytes32 indexed contractId);
    event HTLCERC20Refund(bytes32 indexed contractId);

modifier contractExists(bytes32 _contractId) {
        require(contracts[_contractId].sender != address(0), "Contract does not exist");
        _;
    }

modifier hashlockMatches(bytes32 _contractId, bytes32 _preimage) {
        require(
            contracts[_contractId].hashlock == sha256(abi.encodePacked(_preimage)),
            "Hashlock mismatch"
        );
        _;
    }

modifier withdrawable(bytes32 _contractId) {
        require(!contracts[_contractId].withdrawn, "Already withdrawn");
        require(!contracts[_contractId].refunded, "Already refunded");
        _;
    }

modifier refundable(bytes32 _contractId) {
        require(!contracts[_contractId].withdrawn, "Already withdrawn");
        require(!contracts[_contractId].refunded, "Already refunded");
        require(
            block.timestamp >= contracts[_contractId].timelock,
            "Timelock not yet passed"
        );
        _;
    }

/**
     * @dev Create new HTLC
     */
    function newContract(
        bytes32 _hashlock,
        uint256 _timelock,
        address _receiver
    ) external payable returns (bytes32 contractId) {
        require(msg.value > 0, "Amount must be > 0");
        require(_timelock > block.timestamp, "Timelock must be in future");
        require(_receiver != address(0), "Invalid receiver");

contractId = keccak256(
            abi.encodePacked(
                msg.sender,
                _receiver,
                msg.value,
                _hashlock,
                _timelock
            )
        );

require(contracts[contractId].sender == address(0), "Contract already exists");

contracts[contractId] = LockContract({
            sender: msg.sender,
            receiver: _receiver,
            amount: msg.value,
            hashlock: _hashlock,
            timelock: _timelock,
            withdrawn: false,
            refunded: false,
            preimage: bytes32(0)
        });

emit HTLCERC20New(
            contractId,
            msg.sender,
            _receiver,
            msg.value,
            _hashlock,
            _timelock
        );
    }

/**
     * @dev Withdraw funds by providing preimage
     */
    function withdraw(bytes32 _contractId, bytes32 _preimage)
        external
        contractExists(_contractId)
        hashlockMatches(_contractId, _preimage)
        withdrawable(_contractId)
        returns (bool)
    {
        LockContract storage c = contracts[_contractId];

c.preimage = _preimage;
        c.withdrawn = true;

payable(c.receiver).transfer(c.amount);

emit HTLCERC20Withdraw(_contractId);
        return true;
    }

/**
     * @dev Refund after timelock expires
     */
    function refund(bytes32 _contractId)
        external
        contractExists(_contractId)
        refundable(_contractId)
        returns (bool)
    {
        LockContract storage c = contracts[_contractId];

c.refunded = true;

payable(c.sender).transfer(c.amount);

emit HTLCERC20Refund(_contractId);
        return true;
    }

/**
     * @dev Get contract details
     */
    function getContract(bytes32 _contractId)
        external
        view
        returns (
            address sender,
            address receiver,
            uint256 amount,
            bytes32 hashlock,
            uint256 timelock,
            bool withdrawn,
            bool refunded,
            bytes32 preimage
        )
    {
        LockContract storage c = contracts[_contractId];
        return (
            c.sender,
            c.receiver,
            c.amount,
            c.hashlock,
            c.timelock,
            c.withdrawn,
            c.refunded,
            c.preimage
        );
    }
}

HTLC SECURITY PROPERTIES

✓ ATOMIC EXECUTION
Either both parties get funds, or neither does.
No partial execution possible.
Enforced by cryptography + timeouts.

✓ NO TRUSTED THIRD PARTY
No bridge, custodian, or validator needed.
Security from mathematics, not reputation.
Self-custodial throughout.

✓ NO COUNTERPARTY RISK
Funds locked in contracts, not sent to anyone.
Worst case: timeout and get original funds back.
Cannot lose funds to dishonest counterparty.

✓ VERIFIABLE
Both parties can verify HTLCs before proceeding.
On-chain data is transparent.
No hidden conditions.
```

HTLC ATTACK ANALYSIS

ATTACK: Timing manipulation
─────────────────────────────────────────────────────────────────
Description: Alice waits until Bob's HTLC almost expires,
then claims, leaving Bob no time.

Mitigation: Bob's timeout must be significantly shorter.
Build in buffer for network congestion.
Monitor and claim immediately when secret revealed.

ATTACK: Front-running
─────────────────────────────────────────────────────────────────
Description: Miner/validator sees Alice's claim transaction,
extracts secret, uses it before Alice's tx confirms.

Mitigation: On XRPL, less of an issue (no mempool MEV).
On Ethereum, use private mempools (Flashbots).
Account for this in timeout calculations.

ATTACK: Hash collision
─────────────────────────────────────────────────────────────────
Description: Find different preimage that produces same hash.

Mitigation: Use strong hash function (SHA256).
256-bit collision resistance is sufficient.
No practical attack known.

ATTACK: Secret prediction
─────────────────────────────────────────────────────────────────
Description: Guess or predict Alice's secret.

Mitigation: Use cryptographically secure random generation.
32 bytes = 256 bits of entropy.
Infeasible to brute force.

ATTACK: Griefing
─────────────────────────────────────────────────────────────────
Description: Alice creates HTLC, Bob responds, Alice never claims.
Bob's funds locked until timeout.

Mitigation: Reputation systems for repeated swappers.
Shorter timeouts (accept tradeoff).
Only swap with known counterparties.
```

HTLC LIMITATIONS
  1. ONLINE REQUIREMENT
  1. TIMING CONSTRAINTS
  1. NO PARTIAL FILLS
  1. COUNTERPARTY DISCOVERY
  1. SCALABILITY
  1. FINALITY REQUIREMENTS

HTLC USE CASES

GOOD FIT:
├── Large value swaps where trust is critical
├── Swaps with unknown counterparties (no reputation)
├── Technical users comfortable with complexity
├── Situations where bridges are unavailable/unsuitable
├── Privacy-conscious swaps (no third party sees)
└── Programmatic/automated swap systems

POOR FIT:
├── Small retail transactions (overhead too high)
├── Non-technical users (complexity too high)
├── High-frequency trading (too slow)
├── Situations requiring partial fills
├── When counterparty discovery is difficult
└── Casual, occasional swaps
```

HTLC vs BRIDGE COMPARISON

Dimension HTLC Bridge (e.g., Axelar)
────────────────────────────────────────────────────────────────────
Trust model Trustless (crypto) Trust validators/custodians
Third party None Yes (bridge operators)
Speed Slow (coordination) Fast (minutes)
Ease of use Complex Simple
Counterparty Need to find Swap with liquidity pool
Partial fills No Yes (via DEX)
Gas costs Multiple txs both chains Single bridge tx
Capital efficiency Funds locked during swap Liquidity pools
Scalability Limited Good
Failure mode Timeout (refund) Bridge exploit possible

BOTTOM LINE:
HTLCs are theoretically superior (trustless) but practically
inferior for most use cases. Bridges win on convenience.
HTLCs make sense for large, high-stakes swaps where trust matters.
```

PRODUCTION HTLC CONSIDERATIONS

AUTOMATION REQUIREMENTS:
├── Monitoring service for both chains
├── Automatic claim when secret revealed
├── Timeout tracking and alerts
├── Transaction retry logic
└── Secure key management

OPERATIONAL CONCERNS:
├── Gas price management (avoid stuck txs)
├── Chain congestion handling
├── Failure recovery procedures
├── Logging and audit trails
└── Alerting for anomalies

SECURITY MEASURES:
├── Hardware security modules for keys
├── Multi-sig for large amounts
├── Rate limiting
├── Anomaly detection
└── Regular security audits
```


HTLCs are elegant and trustless—the gold standard for cross-chain swaps from a security perspective. However, their complexity and coordination requirements make them impractical for most users and use cases. They're best suited for large, high-stakes swaps where the overhead is justified, or for automated systems that can handle the coordination. For everyday use, bridges (despite their trust requirements) offer a better user experience. HTLCs are important to understand but shouldn't be the default recommendation.


Assignment: Build a complete atomic swap system between XRPL and an EVM chain.

Requirements:

  • Implement Escrow creation with crypto-conditions

  • Implement Escrow claim with fulfillment

  • Implement Escrow cancellation

  • Test on XRPL testnet

  • Deploy HTLC smart contract (provided or modified)

  • Implement contract creation, withdrawal, refund

  • Test on Goerli/Sepolia testnet

  • Build monitoring for both chains

  • Implement automatic secret extraction

  • Implement automatic claim upon secret reveal

  • Handle timeouts and refunds

  • Simple UI for swap initiation

  • Status display for ongoing swaps

  • Error handling and recovery options

  • Security analysis of your implementation

  • Timeout recommendations with rationale

  • Operational procedures document

  • Known limitations and risks

  • Technical correctness (30%)

  • Security considerations (25%)

  • Code quality (20%)

  • Documentation (15%)

  • Testing coverage (10%)

Time investment: 8-12 hours
Value: Deep understanding of trustless cross-chain mechanics.


Knowledge Check

Question 1 of 5

(Tests Knowledge):

  • Original atomic swap paper (Tier Nolan, 2013)
  • Bitcoin Wiki: Atomic cross-chain trading
  • "Atomic Swaps" technical explainers
  • XRPL Escrow documentation
  • Crypto-conditions (IETF RFC)
  • five-bells-condition library
  • Submarine Swaps (Lightning)
  • Komodo AtomicDEX
  • THORChain (different model but related)
  • "On the Security of Two-Party Atomic Swaps"
  • MEV and front-running research
  • Timing attack analysis papers

For Next Lesson:
Prepare for Lesson 13 on the Interledger Protocol, which offers a different approach to cross-chain/cross-network payments.


End of Lesson 12

Total words: ~6,500
Estimated completion time: 50 minutes reading + 8-12 hours for deliverable

Key Takeaways

1

HTLCs are trustless:

Use cryptographic puzzles and time locks to enable atomic swaps without any third party. Either both parties succeed or both get refunds.

2

XRPL has native support:

Escrow transactions with crypto-conditions provide built-in HTLC capability. No smart contracts needed on the XRPL side.

3

Timing is critical:

Second HTLC must have shorter timeout than first. Buffer for network delays. Timing attacks are the main vulnerability.

4

Complexity limits adoption:

Multiple transactions, online requirements, and coordination overhead make HTLCs impractical for casual users. Better for automated systems.

5

Use cases are specific:

HTLCs make sense for large value swaps, trustless requirements, and programmatic systems. Bridges are better for convenience. ---