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
- NATIVE BRIDGE VERIFICATION
Status: Possible with crypto improvements
- CROSS-CHAIN ATOMIC EXECUTION
Status: Partially possible now
- ORACLE AGGREGATION
Status: Possible now
- PROGRAMMABLE BRIDGES
Status: Building blocks available
- 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):
- XRPL Hooks Documentation: https://xrpl-hooks.readme.io/
- Hooks Builder: https://github.com/xrpl-labs/xrpl-hooks-builder
- Hooks Examples: https://github.com/xrpl-labs/xrpl-hooks-examples
- 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
Hooks bring programmability to mainnet:
Execute custom logic before/after transactions, enabling automation, validation, and cross-chain coordination directly on XRPL.
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.
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.
Not yet on mainnet:
Hooks work on testnet but require amendment approval for mainnet. Timeline uncertain—don't build production systems on uncertain foundations.
Complementary approach:
Best strategy uses Hooks for security-critical simple logic on mainnet, EVM sidechain for complex DeFi, and bridges for external connectivity. ---