@receptum/verify
v0.2.1
Published
CLI and static page: hash a file, find its on-chain receipt, show who delivered what, when and for how much.
Maintainers
Readme
@receptum/verify
Library and CLI to verify a Receptum receipt at every level (SPEC §6):
- File ↔ receipt — SHA-256 of the delivered file equals
outputSha256(offline). Without a file this level isskipped, and the receipt can be at bestPARTIALLY VERIFIED. - Receipt ↔ seller — the signed-receipt envelope is exact (
receipt,receiptHash,proof, optionalbindings), the receipt is valid RRF v1,receiptHashis recomputed, and the detached JWS (I-JSON header without duplicate members, canonical base64url,S < L) verifies against the seller's Ed25519did:key(offline).- L2.5 Seller controls payee — an account binding in
bindings(SPEC §4.1) proves the seller's did:key controlspayment.payee. EVM and Stellar are offline; XRPL also checks the account's current master/regular key online (validated ledger only; a failed lookup isunavailable). With a payee but no binding the result isPARTIALLY VERIFIED(exit 3); pass--allow-unboundto accept legacy receipts. Invalid bindings always fail.
- L2.5 Seller controls payee — an account binding in
- Receipt ↔ chain — the payment settled on its rail and
receiptHashis committed on-chain:x402:exactsettlement transfers (EVM, Stellar and XRPL — a validatedPaymentwhosedelivered_amountmatches — XRP drops, or an issued token's value in 10^-15 units with the same currency identity and issuer, SPEC §7.3) plus a mined anchor, since x402 cannot commit the hash;ReceptumEscrowstate with the committedreceiptHashread from contract storage, never from events (EVM and Soroban: genuine code — a missing contract fails —, a trusted deployment, matching terms,released), XRPL escrows (assets by protocol currency bytes; buyer mode needs aConditionand a delivery leaving at leastreviewWindowSecondsbeforeCancelAfter; evaluator mode passes only when theEscrowFinishwas submitted by thexrpl:evaluator account itself, and isunavailablefor a non-XRPL evaluator; TokenEscrow amounts in 10^-15 units; incomplete history isunavailable), Stellar claimable-balance escrows (SPEC §7.5: exact balance-id reference and Receptum claimant shape, else fail; history-derived delivery and settlement); every XRPL check refuses a server that serves another NetworkID (unavailable); anchors on EVM, XRPL and Stellar (<caip2>:<tx>, SPEC §7.1) — testnets and, read-only, mainnets.
receptum-verify <receipt.json> [delivered-file] [--anchor <caip2>:<tx>]... [--trust-escrow <address|contract-id>]... [--allow-unbound] [--offline] [--json]receipt.json is a bare signed receipt or a wrapper { "signedReceipt": …, "anchor": "<caip2>:<tx>" | [ … ], … } (SPEC §6.1). Every anchor in the wrapper is checked, together with every --anchor; the wrapper's other members are informational and never trusted. The file must be I-JSON: duplicate member names (which JSON.parse would silently collapse), lone surrogates, out-of-range numbers and invalid UTF-8 are refused with exit 2.
Each check is pass, fail, pending (genuine but not final, e.g. an escrow awaiting release), unavailable (could not be performed: unsupported rail or network, RPC error, account not found, non-validated ledger — never a pass, never a failure) or skipped.
| Verdict | When | Exit |
| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---- |
| VERIFIED | nothing failed, pending or unavailable; L1 passed (file given); L2 passed; L2.5 passed or skipped (no payee, or --allow-unbound); L3 payment passed and receiptHash is committed (by the escrow rail, or by a mined anchor for x402) | 0 |
| NOT VERIFIED | any check failed | 1 |
| PARTIALLY VERIFIED | otherwise; each missing piece is printed (missing: …) | 3 |
EVM lookups go to viem's default public RPC for the network (the primary). When its transaction index doesn't know an anchor or x402 settlement hash — some public RPCs prune that index, as Arc testnet's own endpoints have done since October 2026 — the network's history RPCs (EVM_HISTORY_RPCS in @receptum/adapter-evm; Arc testnet: https://rpc.drpc.testnet.arc.network, then https://arc-testnet.drpc.org) are used only to locate the block: a history RPC must report the receipt's chain id, and only the block number and block hash are taken from it. The block and its receipts are then read from the primary, whose block hash must match and whose block must hold the transaction; every checked field (calldata, sender, value, status, logs) comes from the primary, and the check says block located via history RPC …. When no history RPC leads to the transaction on the primary it still fails. Explorer APIs are never used.
Exit 2 is a usage or input error. The library returns the same verdict: verify(signed, options) → { verdict, ok, complete, missing, checks, … }, and parseReceiptInput(bytes) → { signed, anchors } applies the input rules. Results across all chains: E2E_RESULTS.md.
Mainnet receipts
Verification is read-only, so mainnet receipts need no opt-in: x402 exact settlements on Base (eip155:8453), Arc (eip155:5042), Arbitrum One (eip155:42161), XRPL (xrpl:0) and Stellar pubnet are checked out of the box against public endpoints (the RPC's chain id / NetworkID / passphrase is still checked against the receipt). Every report carries network and networkClass (mainnet | testnet | unknown), and the CLI's first line labels them distinctly — === MAINNET receipt (eip155:8453) — real funds === vs === TESTNET receipt (eip155:84532) — test tokens, no real value === (networkLabel(report)).
TRUSTED_ESCROWS lists the testnet deployments (Arc eip155:5042002, Arbitrum Sepolia eip155:421614, Soroban stellar:testnet) and has entries for eip155:8453, eip155:5042, eip155:42161 and stellar:pubnet that are empty: no ReceptumEscrow is deployed to a mainnet yet (escrows go to mainnet only after an independent audit, see docs/MAINNET.md). Until a deployment is published there, a mainnet escrow receipt is pending with untrusted deployment: … (--trust-escrow accepts one explicitly).
