XRPL Hooks for Cross-Chain Logic | XRPL Interoperability | XRP Academy - XRP Academy
3 free lessons remaining this month

Free preview access resets monthly

Upgrade for Unlimited
Skip to main content
advanced55 min

XRPL Hooks for Cross-Chain Logic

Learning Objectives

Explain the XRPL Hooks architecture and how it enables programmability

Design cross-chain logic patterns using Hooks

Implement basic Hook-based interoperability functions

Compare Hooks vs. EVM sidechain for different cross-chain use cases

Assess Hooks' role in XRPL's interoperability future

XRPL was designed for efficiency and reliability, deliberately limiting programmability to prevent the attack surface and complexity that comes with general smart contracts. But this created a tradeoff: XRPL couldn't participate in sophisticated cross-chain workflows without external coordination.

Hooks change this equation.

XRPL PROGRAMMABILITY EVOLUTION

Before Hooks:
├── Fixed transaction types (Payment, Offer, Escrow, etc.)
├── Limited conditional logic (multi-sig, escrow conditions)
├── No custom on-chain logic
├── External coordination required for complex operations
└── "Smart" operations happen off-chain

With Hooks:
├── Custom logic executes on-chain
├── Transactions can be modified, rejected, or augmented
├── Event-driven automation
├── Cross-chain coordination primitives
└── Native XRPL programmability (not sidechain)

Technical Definition:

XRPL HOOKS

Definition:
Small pieces of code (WebAssembly) that execute before and after 
XRPL transactions, enabling custom logic at the protocol level.

Execution Model:
├── Before: Hook executes BEFORE transaction processes
│   └── Can ACCEPT (allow), REJECT (block), or MODIFY
│
└── After: Hook executes AFTER transaction completes
    └── Can emit additional transactions, update state

Language: C compiled to WebAssembly (WASM)
├── C for efficiency and determinism
├── WASM for sandboxed execution
├── No gas model (fee-based instead)
└── Strict resource limits

Attachment: Hooks attach to XRPL accounts
├── Incoming transactions trigger hooks
├── Outgoing transactions can trigger hooks
├── Multiple hooks per account possible
└── Hook behavior defined by code + parameters
HOOK EXECUTION FLOW

Transaction Submitted:


┌──────────────────────────────────────────────────────────┐
│ BEFORE HOOKS │
│ │
│ For each Hook on destination account: │
│ 1. Load Hook WASM code │
│ 2. Execute with transaction context │
│ 3. Hook returns: ACCEPT, REJECT, or ROLLBACK │
│ │
│ If ANY Hook returns REJECT/ROLLBACK → Transaction fails │
└──────────────────────────────────────────────────────────┘

│ All Hooks ACCEPT

┌──────────────────────────────────────────────────────────┐
│ TRANSACTION PROCESSING │
│ │
│ Standard XRPL transaction execution │
└──────────────────────────────────────────────────────────┘


┌──────────────────────────────────────────────────────────┐
│ AFTER HOOKS │
│ │
│ For each Hook on accounts involved: │
│ 1. Execute with completed transaction context │
│ 2. Can emit new transactions │
│ 3. Can update Hook state │
│ │
│ Emitted transactions processed in same ledger │
└──────────────────────────────────────────────────────────┘
```

HOOK STATE MODEL

State Storage:
├── Key-value store per Hook
├── 256-bit keys
├── Variable-length values (max ~256 bytes)
├── Persists across transactions
└── Costs reserves (like owning objects)

State Types:
├── Local state: Specific to Hook instance on account
├── Namespace state: Shared across all instances of Hook
└── Transaction-scoped: Temporary within execution

Example Uses:
├── Track cumulative payments
├── Store oracle prices
├── Maintain allowlists/blocklists
├── Record cross-chain message IDs
└── Count rate-limiting windows
```

WHAT HOOKS CAN DO

✓ ACCEPT/REJECT transactions based on custom logic
✓ Read transaction details (amount, type, parties)
✓ Access ledger state (balances, offers, escrows)
✓ Store and retrieve persistent state
✓ Emit new transactions (limited)
✓ Perform cryptographic operations
✓ Interact with oracles (via state)

WHAT HOOKS CANNOT DO

✗ Unbounded computation (strict limits)
✗ External network calls (no HTTP, etc.)
✗ Access off-chain data directly
✗ Modify transactions arbitrarily
✗ Run indefinitely (timeouts enforced)
✗ Exceed memory limits (~1MB)
```


Cross-Chain Price Oracle:

// price_oracle_hook.c
// A Hook that validates payments against an oracle price feed

#include "hookapi.h"

int64_t hook(uint32_t reserved) {
    // Get the transaction amount
    int64_t amount;
    if (otxn_field(&amount, sizeof(amount), sfAmount) != sizeof(amount))
        REJECT("Oracle: Could not read amount", 30);

// Read oracle price from state
    // State key format: "PRICE:" + currency_code
    uint8_t price_key[32] = "PRICE:XRP/USD";

uint8_t price_data[8];
    int64_t price_len = state(SBUF(price_data), SBUF(price_key));

if (price_len < 0)
        REJECT("Oracle: Price not available", 40);

// Decode price (stored as fixed-point)
    int64_t oracle_price = *((int64_t*)price_data);

// Example: Ensure payment is within 5% of oracle price
    // (Logic would depend on specific use case)

// For cross-chain: Oracle price set by external service
    // that monitors other chain prices

ACCEPT("Oracle: Price check passed", 50);
}

// Companion function to update oracle price
// Called by authorized oracle updater account
int64_t cbak(uint32_t reserved) {
    // Verify caller is authorized oracle
    uint8_t oracle_account[20];
    hook_param(SBUF(oracle_account), "ORACLE", 6);

uint8_t sender[20];
    otxn_field(SBUF(sender), sfAccount);

if (BUFFER_NOTEQUAL(oracle_account, sender))
        REJECT("Oracle: Unauthorized updater", 20);

// Extract new price from transaction memo
    uint8_t new_price[8];
    // ... extract from memo field ...

// Update state
    uint8_t price_key[32] = "PRICE:XRP/USD";
    state_set(SBUF(new_price), SBUF(price_key));

ACCEPT("Oracle: Price updated", 30);
}

Verifying External Chain Events:

// cross_chain_verifier.c
// Hook that verifies messages from other chains via attestations

#include "hookapi.h"

// Verify that a threshold of attesters have signed a message
int64_t hook(uint32_t reserved) {
    // Read attestation from transaction memo
    uint8_t memo_data[256];
    int64_t memo_len = otxn_field(SBUF(memo_data), sfMemos);

if (memo_len < 64)
        REJECT("Verifier: Invalid attestation", 20);

// Attestation format:
    // - 32 bytes: message hash
    // - 32 bytes: signature
    // - 20 bytes: attester address

uint8_t message_hash[32];
    uint8_t signature[32];
    uint8_t attester[20];

// ... parse memo_data ...

// Check if attester is in approved set
    // Read approved attesters from Hook state
    uint8_t attester_key[32];
    COPY(attester_key, attester);

uint8_t approved;
    if (state(&approved, 1, SBUF(attester_key)) != 1)
        REJECT("Verifier: Attester not approved", 30);

if (approved != 1)
        REJECT("Verifier: Attester not approved", 31);

// Verify signature
    // XRPL hooks have limited crypto, may need pre-verification

// Count attestations for this message
    uint8_t count_key[34] = "COUNT:";
    COPY(count_key + 6, message_hash);

uint8_t count_data[1];
    int64_t count = 0;
    if (state(SBUF(count_data), SBUF(count_key)) == 1)
        count = count_data[0];

count++;
    count_data[0] = count;
    state_set(SBUF(count_data), SBUF(count_key));

// Check if threshold reached
    uint8_t threshold;
    hook_param(&threshold, 1, "THRESHOLD", 9);

if (count >= threshold) {
        // Message verified! Allow associated action
        // Could emit a transaction here
        ACCEPT("Verifier: Threshold reached, message verified", 50);
    }

ACCEPT("Verifier: Attestation recorded", 40);
}

Hook-Based Bridge Receiver:

// bridge_receiver.c
// Automatically processes incoming bridge deposits

#include "hookapi.h"

int64_t hook(uint32_t reserved) {
    // This hook runs on bridge account
    // When funds arrive with valid attestation, forward to user

// Read sender (should be bridge operator)
    uint8_t sender[20];
    otxn_field(SBUF(sender), sfAccount);

// Verify sender is authorized bridge operator
    uint8_t authorized[20];
    hook_param(SBUF(authorized), "OPERATOR", 8);

if (BUFFER_NOTEQUAL(sender, authorized))
        REJECT("Bridge: Unauthorized deposit", 20);

// Read destination from memo
    uint8_t memo[256];
    int64_t memo_len = otxn_field(SBUF(memo), sfMemos);

// Parse memo for:
    // - Final recipient address (20 bytes)
    // - Source chain transaction hash (32 bytes)
    // - Amount (8 bytes)

uint8_t recipient[20];
    // ... parse recipient from memo ...

// Check if this source tx has already been processed
    uint8_t processed_key[36] = "PROC:";
    // ... add source tx hash to key ...

uint8_t processed;
    if (state(&processed, 1, SBUF(processed_key)) == 1)
        REJECT("Bridge: Already processed", 30);

// Mark as processed
    processed = 1;
    state_set(&processed, 1, SBUF(processed_key));

// Get incoming amount
    int64_t amount;
    otxn_field(&amount, sizeof(amount), sfAmount);

// Emit payment to final recipient
    // Reserve some for fees
    int64_t fee = 1000; // 0.001 XRP
    int64_t forward_amount = amount - fee;

if (forward_amount <= 0)
        REJECT("Bridge: Amount too small", 40);

// Prepare emitted transaction
    uint8_t emitted_tx[256];
    int64_t e = etxn_reserve(1); // Reserve 1 emitted tx slot

// ... construct Payment transaction to recipient ...
    // ... set amount, destination, etc ...

int64_t emit_result = emit(SBUF(emitted_tx));
    if (emit_result < 0)
        REJECT("Bridge: Failed to emit payment", 50);

ACCEPT("Bridge: Deposit forwarded", 60);
}

Protection Against Bridge Exploits:

// rate_limiter.c
// Limit cross-chain operations to prevent exploit damage

#include "hookapi.h"

int64_t hook(uint32_t reserved) {
    // Rate limiting: max X XRP per Y ledgers

int64_t max_per_window;
    hook_param(&max_per_window, 8, "MAX_AMOUNT", 10);

int64_t window_size;
    hook_param(&window_size, 8, "WINDOW", 6);

// Get current ledger
    int64_t current_ledger = ledger_seq();
    int64_t window_start = (current_ledger / window_size) * window_size;

// Build state key for this window
    uint8_t window_key[16] = "WINDOW:";
    *((int64_t*)(window_key + 7)) = window_start;

// Read cumulative amount this window
    int64_t cumulative = 0;
    uint8_t cum_data[8];
    if (state(SBUF(cum_data), SBUF(window_key)) == 8)
        cumulative = *((int64_t*)cum_data);

// Get transaction amount
    int64_t tx_amount;
    otxn_field(&tx_amount, sizeof(tx_amount), sfAmount);

// Check if this would exceed limit
    if (cumulative + tx_amount > max_per_window) {
        REJECT("RateLimit: Window limit exceeded", 20);
    }

// Update cumulative
    cumulative += tx_amount;
    *((int64_t*)cum_data) = cumulative;
    state_set(SBUF(cum_data), SBUF(window_key));

ACCEPT("RateLimit: Within limits", 30);
}

HOOKS vs EVM SIDECHAIN COMPARISON

Dimension Hooks EVM Sidechain
────────────────────────────────────────────────────────────────────
Location XRPL Mainnet Separate chain
Language C (→ WASM) Solidity
Execution Model Before/after txs Transaction-based
Composability Limited Full (DeFi Legos)
Computation Very limited Unrestricted
State Simple key-value Full EVM state
Security Mainnet security Sidechain security
Speed Same as mainnet ~2-3s blocks
Cost Very low Low (XRP gas)
Ecosystem New/growing Full EVM tooling
Smart Contracts No (hooks only) Yes (full)
```

WHEN TO USE HOOKS vs SIDECHAIN

USE HOOKS FOR:
├── Transaction filtering/validation
├── Simple automations
├── Oracle integration points
├── Rate limiting and security
├── Payment modifications
├── Native XRPL feature extension
└── Scenarios requiring mainnet security

USE EVM SIDECHAIN FOR:
├── Complex DeFi protocols (AMMs, lending)
├── NFT marketplaces
├── Full smart contract applications
├── Standard Solidity development
├── Integration with EVM ecosystem
├── LayerZero/Axelar connectivity
└── Scenarios requiring composability

COMBINED APPROACH:
┌─────────────────────────────────────────────────────────────┐
│ XRPL MAINNET │
│ │
│ [Hooks] │
│ ├── Validate incoming bridge operations │
│ ├── Rate limit large transactions │
│ ├── Oracle price feeds │
│ └── Security guardrails │
│ │
│ │ │
│ [Bridge] │
│ │ │
│ ▼ │
│ │
│ EVM SIDECHAIN │
│ │
│ [Smart Contracts] │
│ ├── AMM pools │
│ ├── Lending protocols │
│ ├── Cross-chain messaging │
│ └── Complex DeFi logic │
└─────────────────────────────────────────────────────────────┘

The combination provides:
├── Mainnet security for core operations
├── Complex programmability when needed
├── Best of both worlds
└── Flexible architecture
```

SECURITY COMPARISON

HOOKS (MAINNET):
├── Executes on 150+ validator network
├── Same security as XRP transfers
├── Limited attack surface (constrained)
├── Bug = account-level impact
├── No bridge risk
└── Battle-tested ledger underneath

EVM SIDECHAIN:
├── Fewer validators (PoA initially)
├── New, less tested
├── Larger attack surface (full EVM)
├── Bug = potentially cross-system impact
├── Bridge risk between mainnet/sidechain
└── More powerful = more potential issues

RISK ASSESSMENT:
For security-critical, simple operations → Hooks preferred
For complex, composable applications → Sidechain acceptable
For maximum security → Keep on mainnet, use Hooks sparingly
```


Development Environment:

# Install Hook development tools
# (As of 2024, Hooks are on XRPL testnet, not mainnet)

# Clone hooks builder
git clone https://github.com/xrpl-labs/xrpl-hooks-builder 
cd xrpl-hooks-builder

# Install dependencies
npm install

# Hook scaffold
npx create-hook my-hook
cd my-hook

# Project structure:
# my-hook/
# ├── src/
# │   └── my-hook.c      # Hook source code
# ├── build/
# │   └── my-hook.wasm   # Compiled WASM
# ├── test/
# │   └── my-hook.test.js
# └── package.json

Payment Router Based on External State:

// payment_router.c
// Route payments based on cross-chain oracle data

#include "hookapi.h"

// Hook that checks external oracle state before allowing payment
// Oracle state updated by off-chain service monitoring other chains

int64_t hook(uint32_t reserved) {
    // Get destination tag (used to encode cross-chain destination)
    uint32_t dest_tag;
    if (otxn_field(&dest_tag, sizeof(dest_tag), sfDestinationTag) != 4) {
        // No destination tag, allow normal payment
        ACCEPT("Router: Normal payment", 10);
    }

// Destination tag encodes target chain
    // 1 = Ethereum, 2 = Polygon, 3 = Arbitrum, etc.
    uint8_t target_chain = (dest_tag >> 24) & 0xFF;

// Check if target chain is currently operational
    // (State set by oracle monitoring chain health)
    uint8_t chain_key[8] = "CHAIN:";
    chain_key[6] = target_chain;

uint8_t chain_status;
    if (state(&chain_status, 1, SBUF(chain_key)) != 1) {
        REJECT("Router: Unknown target chain", 20);
    }

// Status codes: 0=offline, 1=online, 2=degraded
    if (chain_status == 0) {
        REJECT("Router: Target chain offline", 30);
    }

if (chain_status == 2) {
        // Degraded - only allow small payments
        int64_t amount;
        otxn_field(&amount, sizeof(amount), sfAmount);

int64_t max_degraded = 100000000; // 100 XRP
        if (amount > max_degraded) {
            REJECT("Router: Amount too large for degraded chain", 40);
        }
    }

// Get minimum amount for cross-chain (covers fees)
    int64_t min_amount;
    hook_param(&min_amount, 8, "MIN_AMOUNT", 10);

int64_t amount;
    otxn_field(&amount, sizeof(amount), sfAmount);

if (amount < min_amount) {
        REJECT("Router: Below minimum for cross-chain", 50);
    }

// Log for off-chain processing
    trace("Router: Cross-chain payment approved", 35, 0);
    trace_num("Target chain", target_chain);
    trace_num("Amount", amount);

ACCEPT("Router: Approved for cross-chain", 60);
}

Test Framework:

// test/payment_router.test.js
const { XrplClient } = require('xrpl-hooks-toolkit');
const { compile } = require('xrpl-hooks-builder');

describe('Payment Router Hook', () => {
  let client;
  let hookAccount;
  let userAccount;

before(async () => {
    // Connect to Hooks testnet
    client = new XrplClient('wss://hooks-testnet.xrpl-labs.com');
    await client.connect();

// Create test accounts
    hookAccount = await client.createTestAccount();
    userAccount = await client.createTestAccount();

// Compile and deploy hook
    const wasm = await compile('src/payment_router.c');
    await client.setHook(hookAccount, wasm, {
      params: {
        MIN_AMOUNT: 10000000 // 10 XRP minimum
      }
    });

// Set initial chain status (chain 1 = online)
    await client.setHookState(hookAccount, 'CHAIN:\x01', '\x01');
  });

it('should accept payment to online chain', async () => {
    const result = await client.payment(
      userAccount,
      hookAccount.address,
      '50000000', // 50 XRP
      {
        destinationTag: 0x01000001 // Chain 1, user 1
      }
    );

expect(result.meta.TransactionResult).to.equal('tesSUCCESS');
  });

it('should reject payment to offline chain', async () => {
    // Set chain 2 as offline
    await client.setHookState(hookAccount, 'CHAIN:\x02', '\x00');

const result = await client.payment(
      userAccount,
      hookAccount.address,
      '50000000',
      {
        destinationTag: 0x02000001 // Chain 2, user 1
      }
    );

expect(result.meta.TransactionResult).to.equal('tecHOOK_REJECTED');
  });

it('should reject payment below minimum', async () => {
    const result = await client.payment(
      userAccount,
      hookAccount.address,
      '5000000', // 5 XRP (below 10 XRP minimum)
      {
        destinationTag: 0x01000001
      }
    );

expect(result.meta.TransactionResult).to.equal('tecHOOK_REJECTED');
  });

after(async () => {
    await client.disconnect();
  });
});

FUTURE HOOK APPLICATIONS FOR INTEROPERABILITY
  1. NATIVE BRIDGE VERIFICATION

Status: Possible with crypto improvements

  1. CROSS-CHAIN ATOMIC EXECUTION

Status: Partially possible now

  1. ORACLE AGGREGATION

Status: Possible now

  1. PROGRAMMABLE BRIDGES

Status: Building blocks available

  1. CROSS-CHAIN DAO

Status: Complex, future possibility
```

HOOKS ECOSYSTEM STATUS (Late 2024)

DEVELOPMENT STATUS:
├── Hooks enabled on XRPL testnet
├── Mainnet activation: TBD (requires amendment)
├── Active development by XRPL Labs and community
└── Growing documentation and tooling

KEY MILESTONES AHEAD:
├── Mainnet amendment proposal
├── Validator adoption
├── Production deployments
└── Cross-chain application launches

ECOSYSTEM NEEDS:
├── More developer documentation
├── Audit frameworks for Hooks
├── Standard Hook libraries
├── Integration patterns
└── Cross-chain testing tools

WATCH FOR:
├── Amendment progress
├── Testnet activity/bugs
├── Developer adoption
├── First production use cases
└── Security incidents (if any)
```


Hooks are a significant technical achievement that bring real programmability to XRPL mainnet. For cross-chain applications, they enable on-chain oracle integration, automated bridge operations, and security mechanisms that weren't previously possible. However, Hooks are not yet on mainnet, and their capabilities are intentionally limited compared to full smart contracts. They complement rather than replace the EVM sidechain—use Hooks for mainnet security-critical simple operations, sidechain for complex DeFi. Monitor amendment progress before building production systems that depend on Hooks.


Assignment: Design and prototype a Hook-based cross-chain application.

Requirements:

  • Oracle-gated bridge operations

  • Cross-chain rate limiter

  • Multi-signature cross-chain approval

  • Conditional payment router

  • Define Hook inputs and outputs

  • Specify state requirements

  • Document parameters

  • Design failure modes

  • Write Hook code in C

  • Include test suite

  • Deploy to Hooks testnet

  • Document deployment process

  • How Hook integrates with off-chain components

  • Oracle update mechanism

  • Cross-chain coordination flow

  • Error handling

  • Attack vectors specific to your Hook

  • Mitigation strategies

  • Audit considerations

  • Comparison to alternative approaches

  • Design quality (25%)

  • Implementation correctness (25%)

  • Security analysis (20%)

  • Integration design (15%)

  • Documentation (15%)

Time investment: 8-12 hours
Value: Hands-on experience with XRPL's most advanced programmability feature.


Knowledge Check

Question 1 of 5

(Tests Knowledge):

  • WebAssembly specification
  • XRPL amendment process
  • Hook API reference
  • XRPL Hooks Discord
  • Developer forums
  • Testnet explorers

For Next Lesson:
Prepare for Lesson 15 where we'll examine DeFi composability across chains, building on our understanding of Hooks, sidechain, and cross-chain messaging.


End of Lesson 14

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

Key Takeaways

1

Hooks bring programmability to mainnet:

Execute custom logic before/after transactions, enabling automation, validation, and cross-chain coordination directly on XRPL.

2

Different from EVM sidechain:

Hooks are limited but run on mainnet with full validator security. Sidechain offers full smart contracts but with different trust assumptions.

3

Cross-chain use cases:

Oracle integration, bridge verification, rate limiting, and automated routing are all enabled by Hooks. More advanced patterns require combination with off-chain systems.

4

Not yet on mainnet:

Hooks work on testnet but require amendment approval for mainnet. Timeline uncertain—don't build production systems on uncertain foundations.

5

Complementary approach:

Best strategy uses Hooks for security-critical simple logic on mainnet, EVM sidechain for complex DeFi, and bridges for external connectivity. ---