Expand description
Complete Developer Guide
This comprehensive guide covers everything you need to develop smart escrows using the XRPL WebAssembly Standard Library, from getting started to advanced development.
All internal links work properly within this single documentation page.
§XRPL WebAssembly Standard Library - Complete Guide
This comprehensive guide covers everything you need to develop smart escrows using the XRPL WebAssembly Standard Library.
§Table of Contents
- XRPL WebAssembly Standard Library - Complete Guide
§Getting Started
§Prerequisites
Before building smart escrows, ensure you have:
- Rust toolchain (stable or nightly)
- WASM target (
wasm32v1-none) - Node.js (for testing tools)
- Basic understanding of XRPL concepts
Quick setup:
# Run the automated setup script
./scripts/setup.sh
# Or install manually:
# Follow the instructions at https://rust-lang.org/tools/install/
rustup target add wasm32v1-none
npm install§Installation
-
Clone the repository:
git clone https://github.com/XRPLF/xrpl-wasm-stdlib.git cd xrpl-wasm-stdlib -
Run setup script:
./scripts/setup.sh -
Verify installation:
./scripts/run-tests.sh examples/smart-escrows/hello_world
§Your First Contract
Let’s create a simple escrow that releases funds when an account balance exceeds 10 XRP:
use xrpl_wasm_stdlib::core::current_tx::escrow_finish::EscrowFinish;
use xrpl_wasm_stdlib::core::current_tx::traits::TransactionCommonFields;
use xrpl_wasm_stdlib::core::ledger_objects::account_root::get_account_balance;
use xrpl_wasm_stdlib::core::types::amount::Amount;
use xrpl_wasm_stdlib::host::Result::{Ok, Err};
#[unsafe(no_mangle)]
pub extern "C" fn finish() -> i32 {
let tx = EscrowFinish;
// Get the account trying to finish the escrow
let account = match tx.get_account() {
Ok(acc) => acc,
Err(_) => return 0, // Invalid transaction
};
// Check account balance
match get_account_balance(&account) {
Ok(Some(Amount::XRP { num_drops })) if num_drops > 10_000_000 => 1, // Release (>10 XRP)
_ => 0, // Keep locked
}
}Build and test:
# Add the contract code above to src/lib.rs
# Configure Cargo.toml:
[package]
name = "my-escrow"
version = "0.1.0"
edition = "2021"
[dependencies]
xrpl-wasm-stdlib = { path = "../xrpl-wasm-stdlib" }
[lib]
crate-type = ["cdylib"]
[profile.release]
opt-level = "s"
lto = true
panic = "abort"
# Build the contract
cargo build --target wasm32v1-none --release
# Test with provided tools
node ../examples/smart-escrows/hello_world/run_test.js§Core Concepts
§Smart Escrow Basics
Smart escrows are conditional payment contracts that:
- Lock XRP between parties
- Execute custom logic to determine release conditions
- Run deterministically across all network validators
- Have read-only access to ledger data
§Contract Structure
Every smart escrow must:
- Export a
finish()function with signatureextern "C" fn finish() -> i32 - Return 1 to release funds or 0 to keep locked
- Be deterministic - same inputs always produce same outputs
- Use
#![no_std]- no standard library available (use ours instead 😉)
§Host Environment
Smart escrows run in a constrained WebAssembly environment:
- No heap allocation - stack-based memory only
- No file system or network access
- Limited execution time and memory
- Read-only ledger access (except for escrow state updates)
§API Reference
⚠️ IMPORTANT: The code examples in this API reference section are illustrative only and may not compile due to API changes.
For working, tested code that is guaranteed to compile and run correctly, please refer to the complete examples.
The examples below demonstrate concepts and patterns, but the actual API may have changed. Always refer to the working examples for copy-pastable code.
§Transaction Access
The XRPL WASM Standard Library provides type-safe access to transaction data through the current_tx module.
§EscrowFinish Transaction
use xrpl_wasm_stdlib::core::current_tx::escrow_finish::EscrowFinish;
let tx = EscrowFinish;
// Get the account finishing the escrow
let account = tx.get_account().unwrap();
// Get the destination account (receives funds if released)
let destination = tx.get_destination().unwrap();
// Get the escrow sequence number
let escrow_sequence = tx.get_escrow_sequence().unwrap();§Field Access
use xrpl_wasm_stdlib::core::current_tx::escrow_finish::EscrowFinish;
use xrpl_wasm_stdlib::core::current_tx::traits::TransactionCommonFields;
use xrpl_wasm_stdlib::sfield;
let tx = EscrowFinish;
// Access transaction fields using trait methods
let fee_amount = tx.get_fee().ok(); // Returns Amount
let account_id = tx.get_account().ok(); // Returns AccountID
let sequence = tx.get_sequence().ok(); // Returns u32
// EscrowFinish-specific fields (when using EscrowFinishFields trait)
// let owner = tx.get_owner().ok();
// let offer_sequence = tx.get_offer_sequence().ok();§Ledger Objects
Access current ledger state through the ledger_objects module.
§Account Information
use xrpl_wasm_stdlib::core::ledger_objects::account_root::get_account_balance;
use xrpl_wasm_stdlib::core::types::account_id::AccountID;
use xrpl_wasm_stdlib::sfield;
let account = AccountID::from([0u8; 20]); // Replace with real account
// Get XRP balance (in drops) - returns Option<Amount>
let balance = get_account_balance(&account);
// Use host functions to get account fields directly
// (Note: Specific helper functions may vary based on current API)§NFT Objects
// NFT functionality uses the basic get_nft function
use xrpl_wasm_stdlib::core::ledger_objects::nft::get_nft;
use xrpl_wasm_stdlib::core::types::account_id::AccountID;
use xrpl_wasm_stdlib::types::NFT; // [u8; 32]
let owner = AccountID::from([0u8; 20]);
let nft: NFT = [0u8; 32]; // 32-byte NFT identifier
// Get NFT data
let nft_data = get_nft(&owner, &nft);
// Note: Higher-level NFT functions like ownership checking
// and page queries are not yet implemented§Type System
§Core Types
use xrpl_wasm_stdlib::core::types::{
account_id::AccountID, // 20-byte XRPL account identifier
amount::Amount, // Token amounts (XRP, IOU, MPT)
};
use xrpl_wasm_stdlib::types::NFT; // [u8; 32] NFT identifier
// Create AccountID from r-address (if r_address macro exists)
// let account = xrpl_wasm_stdlib::r_address!("rN7n7otQDd6FczFgLdSqtcsAUxDkw6fzRH");
// Create from raw bytes
let account = AccountID::from([0u8; 20]);
// NFT as byte array
let nft: NFT = [0u8; 32];
// Note: High-level string parsing functions may not be available
// Use the working examples for guaranteed compilable code§Keylet Generation
Keylets are used to locate objects in the ledger:
use xrpl_wasm_stdlib::core::types::keylets::{
account_keylet,
line_keylet,
escrow_keylet,
oracle_keylet,
};
use xrpl_wasm_stdlib::core::types::account_id::AccountID;
use xrpl_wasm_stdlib::core::types::amount::asset::Asset;
let account = AccountID::from([0u8; 20]);
let sequence = 12345i32;
// Account keylet
let keylet = account_keylet(&account);
// Trust line keylet (requires Asset types)
let asset1 = Asset::XRP(XrpAsset {});
let asset2 = Asset::IOU(IouAsset::new(issuer, currency));
let keylet = line_keylet(&account, &asset1, &asset2);
// Escrow keylet
let keylet = escrow_keylet(&account, sequence);
// Oracle keylet
let document_id = 1i32;
let keylet = oracle_keylet(&account, document_id);§Host Functions
Low-level host function access through the host module.
§Ledger Access
// Use the high-level trait methods instead of low-level host functions
use xrpl_wasm_stdlib::core::ledger_objects::account_root::AccountRoot;
use xrpl_wasm_stdlib::core::ledger_objects::traits::AccountFields;
use xrpl_wasm_stdlib::core::types::account_id::AccountID;
use xrpl_wasm_stdlib::core::types::keylets::account_keylet;
use xrpl_wasm_stdlib::host::cache_ledger_obj;
use xrpl_wasm_stdlib::host::Error;
// The correct approach is to use the trait methods
fn main() {
let account = AccountID::from(*b"\xd5\xb9\x84VP\x9f \xb5'\x9d\x1eJ.\xe8\xb2\xaa\x82\xaec\xe3");
let account_keylet = account_keylet(&account).unwrap_or_panic();
let slot = unsafe { cache_ledger_obj(account_keylet.as_ptr(), account_keylet.len(), 0) };
if slot < 0 {
return;
}
let account_root = AccountRoot { slot_num: slot };
let balance = account_root.balance(); // Returns Option<Amount>
let sequence = account_root.sequence(); // Returns u32
}§Transaction Fields
// Use the high-level trait methods instead of low-level host functions
use xrpl_wasm_stdlib::core::current_tx::escrow_finish::EscrowFinish;
use xrpl_wasm_stdlib::core::current_tx::traits::{TransactionCommonFields, EscrowFinishFields};
fn main() {
let tx = EscrowFinish;
// Access common transaction fields
let account = tx.get_account(); // AccountID
let fee = tx.get_fee(); // Amount
let sequence = tx.get_sequence(); // u32
// Access EscrowFinish-specific fields
let owner = tx.get_owner(); // AccountID
let offer_sequence = tx.get_offer_sequence(); // u32
let condition = tx.get_condition(); // Option<Condition>
}§Error Handling
The library uses custom Result types for comprehensive error handling:
use xrpl_wasm_stdlib::core::current_tx::escrow_finish::EscrowFinish;
use xrpl_wasm_stdlib::core::current_tx::traits::TransactionCommonFields;
use xrpl_wasm_stdlib::core::ledger_objects::account_root::{get_account_balance, AccountRoot};
use xrpl_wasm_stdlib::core::ledger_objects::traits::AccountFields;
use xrpl_wasm_stdlib::core::types::account_id::AccountID;
use xrpl_wasm_stdlib::core::types::amount::Amount;
use xrpl_wasm_stdlib::core::types::keylets::account_keylet;
use xrpl_wasm_stdlib::host::{cache_ledger_obj, Error, Result};
use xrpl_wasm_stdlib::host::Result::{Ok, Err};
fn process_escrow() -> Result<i32> {
let tx = EscrowFinish;
// Chain operations with ?
let account = match tx.get_account() {
Ok(acc) => acc,
Err(e) => return Err(e), // Invalid transaction
};
let balance = get_account_balance(&account);
// Handle specific errors - create AccountRoot to access account fields
let account_keylet = match account_keylet(&account) {
Ok(keylet) => keylet,
Err(e) => return Err(e), // Invalid account
};
let slot = unsafe { cache_ledger_obj(account_keylet.as_ptr(), account_keylet.len(), 0) };
if slot < 0 {
return Err(Error::from_code(slot));
}
let account_root = AccountRoot { slot_num: slot };
match account_root.sequence() {
Ok(sequence) => {
// Use sequence
},
Err(e) => {
// Handle missing field or other error
return Err(e);
},
}
return Ok(match balance {
Ok(Some(Amount::XRP { num_drops })) if num_drops > 10_000_000 => 1,
_ => 0,
})
}Common error patterns:
WasmError::ObjectNotFound- Ledger object doesn’t existWasmError::FieldNotFound- Required field missingWasmError::InvalidField- Field data malformedWasmError::BufferTooSmall- Output buffer insufficientWasmError::CacheSlotNotFound- Cached object evicted
§Examples
§Hello World
The simplest possible smart escrow that demonstrates basic concepts.
📁 View complete example: examples/smart-escrows/hello_world/
Key learning points:
- Basic contract structure with
#![no_std]and#![no_main] - Using
#[unsafe(no_mangle)]for the entry point function - Simple error handling with pattern matching
- Trace logging for debugging
Files:
src/lib.rs- Main contract codeREADME.md- Detailed explanationrun_test.js- Integration test
§Oracle Example
A price-based escrow that releases funds when an asset price meets conditions.
📁 View complete example: examples/smart-escrows/oracle/
Key concepts demonstrated:
- External data integration through oracles
- Price threshold logic
- Error handling for missing oracle data
- Real-world conditional logic
Files:
src/lib.rs- Oracle price checking logicREADME.md- Oracle integration guiderun_test.js- Price simulation test
§KYC Example
A compliance-focused escrow that requires credential verification.
📁 View complete example: examples/smart-escrows/kyc/
Key concepts demonstrated:
- Credential-based verification
- Trusted issuer validation
- Signature verification
- Expiration checking
- Compliance patterns
Files:
src/lib.rs- KYC credential verificationREADME.md- Compliance implementation guiderun_test.js- Credential verification test
§Advanced Examples
§Multi-Signature Notary
📁 examples/smart-escrows/notary/
- Requires multiple signature approvals
- Implements threshold signing logic
- Demonstrates complex authorization patterns
§NFT Ownership Verification
📁 examples/smart-escrows/nft_owner/
- Releases funds based on NFT ownership
- Shows how to query NFT ledger objects
- Demonstrates asset-based conditions
§Time-Based Ledger Sequence
📁 examples/smart-escrows/ledger_sqn/
- Uses ledger sequence numbers for timing
- Implements time-locked escrows
- Shows sequence-based logic
§Testing and Debugging
§Test Networks
| Network | Endpoint | Purpose |
|---|---|---|
| WASM Devnet | wss://wasm.devnet.rippletest.net:51233 | Integration testing |
| Local Node | ws://localhost:6006 | Local Development |
Follow the instructions here with this branch if you would like to build and run rippled locally.
§Test Using the Web UI
🌐 Open the web UI: https://ripple.github.io/xrpl-wasm-stdlib/ui/
The web UI allows you to:
- Upload and test any WASM contract directly
- Configure test transactions and ledger state
- Execute contracts and see results with trace output
- Test on different networks (Devnet, Testnet)
- Debug without local setup
-
Build your contract:
cargo build --target wasm32v1-none --release -
Upload your WASM file:
- Open the testing interface in your browser
- Click “Choose File” and select your
.wasmfile fromtarget/wasm32v1-none/release/ - The contract will be loaded automatically
-
Test your contract:
- Set up test scenarios using the interface
- Configure transaction data and ledger state
- Execute and see results with debug output
§Performance Optimization
§Binary Size Optimization
Cargo.toml optimizations:
[profile.release]
opt-level = "s" # Optimize for size over speed
lto = true # Link-time optimization
panic = "abort" # Remove panic handling code
codegen-units = 1 # Single codegen unit for better optimization
strip = true # Strip debug symbolsCode patterns for smaller binaries:
// Use fixed-size arrays instead of vectors
let mut buffer = [0u8; 32]; // Stack allocation
// Minimize string usage
const ERROR_MSG: &str = "Error"; // Use constants
// Efficient error handling
match operation() {
Ok(result) => result,
Err(_) => return 0, // Simple error path
}§Runtime Optimization
Minimize host function calls:
// Good: Call once, use cached result
let account = tx.get_account();
let balance = get_account_balance(&account);
// Create AccountRoot to access account fields
let account_keylet = account_keylet(&account);
let slot = cache_ledger_obj(&account_keylet);
let account_root = AccountRoot { slot_num: slot };
let sequence = account_root.sequence();
// Bad: Multiple calls for same data
let balance = get_account_balance(&tx.get_account());
// Bad: Multiple calls - should cache the account and keylet
let account_keylet = account_keylet(&tx.get_account());
let slot = cache_ledger_obj(&account_keylet);
let account_root = AccountRoot { slot_num: slot };
let sequence = account_root.sequence();Efficient ledger object access:
// Cache ledger objects for multiple field access using traits
let account = AccountID::from(*b"\xd5\xb9\x84VP\x9f \xb5'\x9d\x1eJ.\xe8\xb2\xaa\x82\xaec\xe3");
let account_keylet = account_keylet(&account).unwrap_or_panic();
let slot = unsafe { cache_ledger_obj(account_keylet.as_ptr(), account_keylet.len(), 0) };
let account_root = AccountRoot { slot_num: slot };
// Use trait methods to access fields efficiently
let balance = account_root.balance(); // Option<Amount>
let sequence = account_root.sequence(); // u32
let owner_count = account_root.owner_count(); // u32Memory usage optimization:
// Use stack-based allocation
let mut accounts = [AccountID::default(); 10];
// Reuse buffers for transaction fields
let mut buffer = [0u8; 64];
let len1 = unsafe { get_tx_field(sfield::Account, buffer[..20].as_mut_ptr(), 20) };
let len2 = unsafe { get_tx_field(sfield::Destination, buffer[20..40].as_mut_ptr(), 20) };§Troubleshooting
§Common Build Issues
| Issue | Solution |
|---|---|
wasm32v1-none target not found | rustup target add wasm32v1-none |
| Link errors | Check crate-type = ["cdylib"] in Cargo.toml |
| Binary too large | Use release profile optimizations |
| Missing exports | Ensure #[unsafe(no_mangle)] on finish() function |
| Compilation errors | Check #![no_std] and avoid std library usage |
§Common Runtime Issues
| Issue | Cause | Solution |
|---|---|---|
| Function not found | WASM export missing | Check #[unsafe(no_mangle)] on entry point |
| Memory access violation | Buffer overflow | Verify buffer sizes and bounds |
| Cache full (NoFreeSlots) | Too many cached objects | Minimize cache_ledger_obj calls |
| Field not found | Missing ledger field | Handle FieldNotFound errors |
| Invalid field data | Malformed field | Validate input data |
§Debugging Techniques
Add trace statements:
use xrpl_wasm_stdlib::host::trace::{trace, trace_data, trace_num, DataRepr};
use xrpl_wasm_stdlib::core::current_tx::escrow_finish::EscrowFinish;
use xrpl_wasm_stdlib::core::current_tx::traits::TransactionCommonFields;
use xrpl_wasm_stdlib::host::Result::{Ok, Err};
#[unsafe(no_mangle)]
pub extern "C" fn finish() -> i32 {
trace("Contract starting").ok();
let tx = EscrowFinish;
let account = match tx.get_account() {
Ok(acc) => {
trace_data("Account", &acc.0, DataRepr::AsHex).ok();
acc
},
Err(e) => {
trace_num("Error getting account: {:?}", e as i64).ok();
return 0;
}
};
// More logic with tracing...
1 // Return 1 to complete the function
}Inspect WASM binary:
# Detailed binary analysis
wasm-objdump -x target/wasm32v1-none/release/my_escrow.wasm
# Size analysis
wasm-objdump -h target/wasm32v1-none/release/my_escrow.wasm§Additional Resources
The XRPL WebAssembly Standard Library is designed to make smart escrow development accessible while maintaining the security and determinism required for production use on the XRPL network.
For additional help:
- Review the examples in
examples/smart-escrows/ - Check the API documentation generated by
cargo doc - Join the XRPL developer community
- Submit issues or questions on GitHub
§Contributing
If you’re interested in contributing to the XRPL WebAssembly Standard Library, please see our CONTRIBUTING.md for detailed guidelines on:
- Development setup and workflow
- Code standards and style guidelines
- Pull request process
- Testing requirements
- Release procedures
We welcome contributions of all kinds, from bug fixes and documentation improvements to new examples and library features!