Ward Protocol v0.2.4
Protocol Specification
Technical reference for Ward Protocol: architecture, 9-step claim validation, VaultMonitor, escrow settlement, and all 15 attack-vector mitigations.
204/204 Python · 40/40 Rust · 45/45 TypeScriptSDK v0.2.415 AVs Mitigated
1. Overview
Ward Protocol is an open specification and SDK for deterministic default protection on XLS-66 institutional lending vaults on the XRP Ledger. Core invariant: ward_signed = False Ward constructs unsigned transactions. Institutions sign; XRPL settles. Ward never holds, touches, or stores private keys.
2. Architecture
Five modules: Module 1 — WardClient High-level SDK entrypoint Module 2 — VaultMonitor WebSocket default detection (3-ledger confirmation) Module 3 — ClaimValidator 9-step on-chain claim validation Module 4 — EscrowSettlement PREIMAGE-SHA-256 escrow lifecycle Module 5 — PoolHealthMonitor Coverage ratio + reserve accounting Shared: ward/primitives.py, ward/constants.py, ward/tx_builder.py
3. 9-Step Claim Validation
Step 1NFT existence & taxon (WARD_POLICY_TAXON = 281)
Step 2Policy expiry — XRPL ledger close_time, never server clock
Step 3Vault address binding — metadata vault == defaulted_vault
Step 4LSF_LOAN_DEFAULT flag on LedgerEntry(index=loan_id)
Step 5Vault loss > 0 drops
Step 6Pool coverage breach — usable = balance − XRPL reserve ≥ 0
Step 7Replay protection — NFT still live (burn-on-settlement)
Step 8Claimant holds NFT — AccountNFTs(account=claimant_address)
Step 9Pool solvency + rate limit (≤ 3/NFT/300 s, ratio ≥ 1.5×)
4. VaultMonitor
Subscribes to XRPL ledger stream via wss:// (TLS required). Default detection: 1. WebSocket transaction message received (hint only) 2. _verify_default_on_chain(): LedgerEntry(index=loan_id) via independent RPC 3. 3 consecutive ledger closes with LSF_LOAN_DEFAULT set → VerifiedDefault Reconnect: exponential backoff (1 s → 60 s max) Heartbeat: reconnects if no ledger event in MONITOR_HEARTBEAT_TIMEOUT_S (60 s) URL allow-list: ALLOWED_WS_URLS — rejects unknown or non-TLS endpoints
5. Escrow Settlement
PREIMAGE-SHA-256 (RFC 3230 / IETF Crypto-Conditions): 1. Claimant: preimage = secrets.token_bytes(32) 2. Claimant: condition_hex, fulfillment_hex = make_preimage_condition(preimage) 3. Claimant sends condition_hex to Ward API — preimage never transmitted 4. Ward builds unsigned EscrowCreate (pool → claimant, condition=condition_hex) 5. Pool institution signs + submits EscrowCreate 6. Ward builds unsigned EscrowFinish (fulfillment=fulfillment_hex) 7. Claimant signs + submits EscrowFinish 8. Policy NFT burned (NFTokenBurn) — replay protection ward_signed = False at every step
6. Attack Vector Mitigations
AV 2.1 Policy Forgery — NFTokenTaxon == 281 enforced at step 1 AV 2.2 Replay / Double-Spend — NFT burned on settlement; step 1 re-checks AV 2.3 Policy Transfer — TF_TRANSFERABLE (0x8) absent; TF_BURNABLE only AV 2.4 Signal Manipulation — Independent LedgerEntry RPC on every event AV 2.5 Clock Manipulation — XRPL ledger close_time; no time.time() AV 2.6 Front-Running Escrow — Ward never receives or stores preimage AV 2.7 Monitor Spoofing — wss:// + ALLOWED_WS_URLS allow-list AV 2.8 Pool Drainage — Step 6 + Step 9 dual solvency checks AV 2.9 Coverage Ratio Manip — Health ratio re-fetched from ledger at step 4 AV 2.10 Address Injection — validate_xrpl_address() at every API boundary AV 2.11 Key Exfiltration — WardClient stores no wallet; per-call only AV 2.12 Rate Limit Bypass — Sliding window: 3 attempts/NFT/300 s AV 2.13 NFT Taxon Spoofing — _WRONG_TAXON sentinel; taxon check at step 1 AV 2.14 Drops Unit Confusion — validate_drops() rejects floats, bools, negatives AV 2.15 Silent Network Failure — asyncio.wait_for heartbeat; 60 s timeout
7. Protocol Constants
WARD_POLICY_TAXON = 281 # XLS-20 NFT taxon for policy NFTs
WARD_CREDENTIAL_TAXON = 282 # XLS-70 credential NFT taxon
TF_BURNABLE = 0x00000001
TF_TRANSFERABLE = 0x00000008 # deliberately ABSENT from policy NFTs
LSF_LOAN_DEFAULT = 0x00010000
MIN_COVERAGE_RATIO = 1.5
CLAIM_RATE_LIMIT_MAX = 3
CLAIM_RATE_LIMIT_WINDOW_S = 300
MONITOR_HEARTBEAT_TIMEOUT_S = 60
XRPL_BASE_RESERVE_DROPS = 2_000_000
XRPL_OWNER_RESERVE_DROPS = 200_000
RIPPLE_EPOCH_OFFSET = 946_684_800