{
  "kind": "receipt_verification",
  "type": "counterparty_delivery_receipt",
  "version": "ars-evidence/0.1",
  "status": "PROPOSED evidence format (v0.1); Lumi issues one per test-buy of a seller.",
  "where_receipts_are": "Paid only: https://api.lumitrades.io/v1/trust/vendors/{id}/report ($0.02; paid_test_buys.history[].receipt) and paid catalog lookups, POST https://api.lumitrades.io/v1/feed/lookup on the Feed and Feed Pro plans (results[].entries[].purchases[].receipt). https://api.lumitrades.io/v1/trust/check returns a verdict, not receipts. Free lookups stay answer-only.",
  "verify_online": "POST https://api.lumitrades.io/v1/trust/receipts/verify with the receipt JSON as the body (free). Returns hash/signature checks and, with ?ledger=1, an on-chain check of the payment.",
  "verify_yourself": [
    "1. receipt_hash: remove receipt_hash and signer, serialise the rest as RFC 8785-style canonical JSON (keys sorted, no whitespace), SHA-256, lowercase hex. It must equal receipt_hash.",
    "2. message: signer.message must equal `lumi-receipt:<payment.network>:<payment.tx_hash>:<receipt_hash>`.",
    "3. signature: XRPL (key_type ripple-keypairs): ripple-keypairs verify(<UTF-8 bytes of message as uppercase hex>, signature, public_key), and deriveAddress(public_key) = signer.address. Base (key_type eip191): EIP-191 personal_sign recovery of message gives signer.address. Stellar (key_type stellar-ed25519): ed25519 verify of the UTF-8 message bytes with the base64 signature against the public key inside signer.address (G-address, StrKey).",
    "4. signer.address must equal payment.payer.",
    "5. ledger: fetch payment.tx_hash. XRPL: validated, tesSUCCESS, Account = payer, Destination = seller.pay_to, delivered_amount = amount/asset, and SigningPubKey = signer.public_key. Base: status success with a USDC (0x8335…2913) Transfer from payer to seller.pay_to for amount. Stellar: Horizon /transactions/<tx_hash> successful, and its operations show a USDC (issuer GA5Z…KZVN, SAC CCW67…MI75) transfer from payer to seller.pay_to for amount.",
    "6. delivery.* is the tester's signed statement about what the paid call returned. It cannot be proven on-chain; the seller can answer via the right to reply."
  ],
  "example_code_js": [
    "import { verify, deriveAddress } from 'ripple-keypairs'; import { recoverMessageAddress, sha256, toHex } from 'viem';",
    "const canon = (v) => v === null || typeof v !== 'object' ? JSON.stringify(v) : Array.isArray(v) ? '[' + v.map(canon).join(',') + ']' : '{' + Object.keys(v).filter(k => v[k] !== undefined).sort().map(k => JSON.stringify(k) + ':' + canon(v[k])).join(',') + '}';",
    "const { receipt_hash, signer, ...body } = receipt; const h = sha256(toHex(canon(body))).slice(2); // must equal receipt_hash",
    "// XRPL: verify(Buffer.from(signer.message, 'utf8').toString('hex').toUpperCase(), signer.signature, signer.public_key) && deriveAddress(signer.public_key) === signer.address",
    "// Base: (await recoverMessageAddress({ message: signer.message, signature: signer.signature })).toLowerCase() === signer.address.toLowerCase()",
    "// Stellar: import { Keypair } from '@stellar/stellar-sdk'; Keypair.fromPublicKey(signer.address).verify(Buffer.from(signer.message, 'utf8'), Buffer.from(signer.signature, 'base64'))"
  ],
  "keys": "Signed on Lumi's operator box with the wallet that paid. Lumi's API never holds or exposes wallet keys.",
  "methodology": "https://api.lumitrades.io/v1/trust/methodology"
}