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_refbinds the responsetxHashto the requested amount and currency plus an injected timestamp, so atxHashcan no longer be re-bound to a different amount.settlement_refissha256:+ 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
Built Distributions
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
- Download URL: algovoi_x402_receipt_adapter-0.1.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl
- Upload date:
- Size: 372.7 kB
- Tags: CPython 3.13, manylinux: glibc 2.28+ x86-64, manylinux: glibc 2.5+ x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/6.2.0 CPython/3.12.10
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f863cb5862eb9c725eb4285552bbe0e4491beb7800ad0019c3d1dea8f14261a7
|
|
| MD5 |
1cbacfcb9a6dbb2719db8a913e26810d
|
|
| BLAKE2b-256 |
81ab3ef32ac2c4b28ed7590f2b1eda873dae9ea839ed14395d4c12bd4b74d328
|
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
- Download URL: algovoi_x402_receipt_adapter-0.1.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl
- Upload date:
- Size: 358.4 kB
- Tags: CPython 3.12, manylinux: glibc 2.28+ x86-64, manylinux: glibc 2.5+ x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/6.2.0 CPython/3.12.10
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e44c03a6bf694bf2b8532b733a01162f8374b25571b53437320bb475711344a6
|
|
| MD5 |
df71530d2672e31668b1875ee342a554
|
|
| BLAKE2b-256 |
1644dfb406220f1410b25611d179650f7031707fcfad0c80f3b0bd539028ee6f
|
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
- Download URL: algovoi_x402_receipt_adapter-0.1.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl
- Upload date:
- Size: 371.8 kB
- Tags: CPython 3.11, manylinux: glibc 2.28+ x86-64, manylinux: glibc 2.5+ x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/6.2.0 CPython/3.12.10
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2b82e91d520109ef926e5f9ffaa5dc3b40e83f6cd73b65f77f36b16849e2899a
|
|
| MD5 |
a296d4a80683bddcac081a66e675e418
|
|
| BLAKE2b-256 |
40cf1e83f7d7a04560f412ed5b9f9ac9d0d4c22a1178e7e9feccc7998007887b
|