Skip to main content

algovoi-x402-receipt-adapter

Turn an x402 payment into an offline verifiable receipt.

x402 proves a payment happened with a single txHash in the PAYMENT-RESPONSE header. The amount and currency live in the upstream PAYMENT-REQUIRED header, nothing binds the two together, and verifying the payment means hitting the chain. There is no receipt.

This adapter takes both headers and produces a settlement_attestation_v1: a JSON object canonicalised under RFC 8785 (JCS) and content addressed with SHA-256. Any party recomputes the reference and confirms the bytes with a JSON parser, JCS, and SHA-256 alone. No chain call, no issuer service, no account.

What it produces

PAYMENT-REQUIRED   ->  maxAmountRequired, asset, payTo, network
PAYMENT-RESPONSE   ->  txHash, networkId, payer, success
                          |
      x402_payment_ref = sha256(JCS({txHash, networkId, payer,
                                     amount, currency, settled_at_ms}))
                          |
      settlement_attestation_v1   (settlement_ref content addresses it)
  • x402_payment_ref binds the response txHash to the requested amount and currency plus an injected timestamp, so a txHash can no longer be re-bound to a different amount.
  • settlement_ref is sha256: + SHA-256(JCS(attestation)), the content address of the whole receipt.

Install

pip install algovoi-x402-receipt-adapter

Use

from algovoi_x402_receipt_adapter import build_x402_settlement_attestation

payment_required = {
    "scheme": "exact",
    "network": "base",
    "maxAmountRequired": "1000000",   # atomic minor units (USDC, 6dp)
    "asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
    "payTo": "0x7D01d268636c835d9E56164A24A9587D82B8B186",
}
payment_response = {
    "success": True,
    "txHash": "0x7ae7b2750c6de4507af6a06305e403b82a31d1090d6330f57d83707800ac6acb",
    "networkId": "base",
    "payer": "0x1234...",
}

result = build_x402_settlement_attestation(
    payment_required,
    payment_response,
    settled_at_ms=1751472000000,
    provider_did="did:web:your-service.example",
    jurisdiction_flags=["UK", "EU"],
)

result["settlement_ref"]      # "sha256:..."
result["x402_payment_ref"]    # "sha256:..."
result["attestation"]         # the 8-field settlement_attestation_v1

Either header may be passed as a decoded dict or as the raw base64(JSON) header value. The settlement fields follow the x402 Foundation SettleResponse model (transaction, network, payer); the legacy names txHash and networkId are accepted as aliases, so the adapter reads a PAYMENT-RESPONSE header regardless of which naming a gateway emits.

Verify offline

from algovoi_x402_receipt_adapter import verify_settlement_ref

verify_settlement_ref(result["attestation"], result["settlement_ref"])  # True

The check is pure and deterministic: sha256: + SHA-256(JCS(attestation)). Recompute it in any language with an RFC 8785 canonicaliser and a SHA-256, and the reference matches byte for byte.

Determinism

The adapter injects no clock and asserts no identity of its own: settled_at_ms and provider_did are explicit arguments. The same headers with the same arguments always produce the same references. A frozen conformance vector ships with the source tests.

Fields

settlement_attestation_v1 carries exactly eight fields, sorted lexicographically by JCS:

field meaning
canon_version jcs-rfc8785-v1
jurisdiction_flags ordered jurisdiction codes
settled_payment_ref the x402_payment_ref
settlement_amount {amount_minor, asset_id}
settlement_chain e.g. ethereum:8453
settlement_provider_did the attesting party (you)
settlement_result SETTLED / PENDING_FINALITY / REVERSED
settlement_timestamp_ms integer epoch milliseconds

Three ways to capture

Option Function Use when
A. Facilitator side receipt_from_settle you are the x402 facilitator (you ran /settle), or a resource server points at yours
B. Origin side capture_x402_settlement behind a managed gateway (Cloudflare); re-verify on chain
C. Direct build_x402_settlement_attestation you hold both headers and trust them

All three yield the same settlement_attestation_v1, and to_keystone_binding binds any of them into the Keystone. All settlement fields accept the x402 Foundation SettleResponse names (transaction, network, payer), with the legacy txHash / networkId accepted as aliases.

A. Facilitator side

from algovoi_x402_receipt_adapter import receipt_from_settle

result = receipt_from_settle(
    payment_requirements,          # x402 PaymentRequirements (maxAmountRequired, asset, payTo)
    settle_response,               # your facilitator's SettleResponse (success, transaction, network, payer, amount)
    settled_at_ms=1751472000000,
    provider_did="did:web:your-facilitator.example",
    keystone_context={"action_ref": "…", "transition_hash": "…", "retention_chain_ref": "sha256:…"},
)
result["settlement_ref"]     # same value option B or C would produce
result["confirmed_amount"]   # actual settled amount (SettleResponse.amount when present)

As the facilitator you settled the payment, so the result is trusted by default; pass reverify for an optional on-chain check.

B. Secure capture, origin side (recommended for managed gateways)

A settlement gateway (for example Cloudflare, which returns the result inline in a PAYMENT-RESPONSE header and exposes no settlement API or webhook) hands you the two headers on the paid request. Do not trust them at face value: a forged or replayed PAYMENT-RESPONSE would otherwise mint a false receipt. capture_x402_settlement closes that hole by re-verifying the transaction on chain before it builds or binds anything:

from algovoi_x402_receipt_adapter import capture_x402_settlement

def verify(network_id, tx_id, expected_receiver, expected_amount_minor, expected_asset):
    # wire to the AlgoVoi facilitator POST /verify, or an in-process verifier.
    # MUST return {"verified": bool, "confirmed_amount": int}.
    ...

result = capture_x402_settlement(
    payment_required, payment_response,
    verify=verify,
    settled_at_ms=1751472000000,
    provider_did="did:web:your-service.example",
    keystone_context={"action_ref": "…", "transition_hash": "…", "retention_chain_ref": "sha256:…"},
    already_bound=lambda tx: store.get(tx),   # idempotency on txHash
)
result["binding_ref"]        # the payment, verified and bound into the Keystone
result["confirmed_amount"]   # the actual on-chain amount recorded

Security properties: fail closed (no on-chain confirmation, no binding), unforgeable (binding requires a real payment to your payTo), replay safe (idempotent on txHash). The attestation records the confirmed on-chain amount, not the quoted maximum. Run this at your origin, ideally behind authenticated origin pull so a spoofed request cannot reach it in the first place.

Bind it into the Keystone

The settlement this adapter produces can be bound into the AlgoVoi Keystone accountability chain, so a payment is tied to the verified agent action it settled. This uses the published algovoi-substrate binding primitive and introduces no new hashing:

from algovoi_x402_receipt_adapter import (
    build_x402_settlement_attestation, to_keystone_binding,
)

result = build_x402_settlement_attestation(
    payment_required, payment_response,
    settled_at_ms=1751472000000, provider_did="did:web:your-service.example",
)

binding_ref = to_keystone_binding(
    result,
    action_ref="…",            # the verified agent action (bare 64-char hex)
    transition_hash="…",       # the COMMITTED lifecycle transition (bare hex)
    retention_chain_ref="sha256:…",  # tamper-evident chain position
)
binding_ref = sha256(JCS({ action_ref, transition_hash,
                           settlement_ref, retention_chain_ref }))

binding_ref proves which action the payment settled: changing the settlement, the action, the transition, or the chain position changes it. Recompute it offline with a JCS canonicaliser and SHA-256, the same as settlement_ref.

Making the payment a Keystone execution step (recipe)

To place the payment as the execution step of a full Keystone chain (passport -> mandate -> policy -> decision -> execution -> trust_query), map the settlement into an execution_ref record with the algovoi-execution-ref primitive and carry the settlement attestation as that step's evidence:

from algovoi_execution_ref import execution_ref   # optional extra dependency

exec_ref = execution_ref(
    decision_ref=decision_ref,                     # the ALLOW that authorised the spend
    action_type="payment.settle",
    scope=f"{result['attestation']['settlement_chain']}:{payment_response['txHash']}",
    outcome="COMMITTED",
    executed_at_ms=1751472000000,
)

This adapter does not depend on algovoi-execution-ref; the recipe is opt-in so the package stays lean.

Networks

networkId maps to the AlgoVoi settlement_chain form (for example base maps to ethereum:8453, base-sepolia to ethereum:84532, solana-devnet to solana:devnet). Any eip155:<chainId> is mapped automatically. Pass network_map={...} or settlement_chain=... to override, or strict=False to pass an unknown id through unchanged.

Licence

Apache License 2.0. See LICENSE and NOTICE.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distributions

No source distribution files available for this release.See tutorial on generating distribution archives.

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

algovoi_x402_receipt_adapter-0.1.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl (372.7 kB view details)

Uploaded CPython 3.13manylinux: glibc 2.28+ x86-64manylinux: glibc 2.5+ x86-64

algovoi_x402_receipt_adapter-0.1.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl (358.4 kB view details)

Uploaded CPython 3.12manylinux: glibc 2.28+ x86-64manylinux: glibc 2.5+ x86-64

algovoi_x402_receipt_adapter-0.1.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl (371.8 kB view details)

Uploaded CPython 3.11manylinux: glibc 2.28+ x86-64manylinux: glibc 2.5+ x86-64

File details

Details for the file algovoi_x402_receipt_adapter-0.1.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl.

File metadata

File hashes

Hashes for algovoi_x402_receipt_adapter-0.1.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl
Algorithm Hash digest
SHA256 f863cb5862eb9c725eb4285552bbe0e4491beb7800ad0019c3d1dea8f14261a7
MD5 1cbacfcb9a6dbb2719db8a913e26810d
BLAKE2b-256 81ab3ef32ac2c4b28ed7590f2b1eda873dae9ea839ed14395d4c12bd4b74d328

See more details on using hashes here.

File details

Details for the file algovoi_x402_receipt_adapter-0.1.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl.

File metadata

File hashes

Hashes for algovoi_x402_receipt_adapter-0.1.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl
Algorithm Hash digest
SHA256 e44c03a6bf694bf2b8532b733a01162f8374b25571b53437320bb475711344a6
MD5 df71530d2672e31668b1875ee342a554
BLAKE2b-256 1644dfb406220f1410b25611d179650f7031707fcfad0c80f3b0bd539028ee6f

See more details on using hashes here.

File details

Details for the file algovoi_x402_receipt_adapter-0.1.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl.

File metadata

File hashes

Hashes for algovoi_x402_receipt_adapter-0.1.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl
Algorithm Hash digest
SHA256 2b82e91d520109ef926e5f9ffaa5dc3b40e83f6cd73b65f77f36b16849e2899a
MD5 a296d4a80683bddcac081a66e675e418
BLAKE2b-256 40cf1e83f7d7a04560f412ed5b9f9ac9d0d4c22a1178e7e9feccc7998007887b

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

0.1.0 This release

3 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page