Documentation
¶
Overview ¶
Package x402 is pay-per-request over HTTP 402: quote a price, take the payment, serve the resource.
The full cycle is challenge → the client pays → proof submitted → verify → serve → settle, native to the Hanzo cloud binary.
FLOW. A priced resource answers 402 with PaymentRequirements (what to pay + the recipient wallet). The client signs an ERC-3009 transferWithAuthorization over those terms and retries with the X-Payment header. The subsystem VERIFIES the EIP-712 signature (secp256k1 recovery via luxfi/crypto — the SAME primitive the wallets custody signs with), rejects a REPLAYED authorization (nonce dedup), SETTLES exactly once (payer debit through the metering spine so paid usage appears in billing/usage like any metered spend, plus a credit to the recipient wallet's ledger), and serves.
SEAMS. The marketplace registry (another subsystem) owns the mapping resource→Terms (price + recipient wallet); x402 only enforces it (Registry + Publish). Recipient resolution rides the wallets subsystem (wallets.ResolvePaymentTarget). On-chain broadcast of the authorization is a Settler seam; the LIVE default is ledger settlement.
Index ¶
- Constants
- Variables
- func Enforce() zip.Handler
- func Mount(app cloud.Router, deps cloud.Deps) error
- func Publish(r Registry)
- func Settle(ctx context.Context, resource string) error
- func Shutdown() error
- func Verify(req PaymentRequirements, p Proof, tokenName, tokenVersion string, now int64) error
- type Config
- type PaymentRequirements
- type Proof
- type Receipt
- type Registry
- type Settlement
- type Terms
Constants ¶
const ( // Version is the x402 protocol version this subsystem speaks. Version = "1" // Scheme is the payment scheme: an ERC-3009 transferWithAuthorization the // client signs off-chain (EIP-712), settled on the ledger or on-chain. Scheme = "erc3009" // HeaderRequirements carries the PaymentRequirements on a 402 response. HeaderRequirements = plane.HeaderRequirements // HeaderProof carries the client's signed Proof on the retry. HeaderProof = plane.HeaderProof // HeaderReceipt carries the settlement Receipt on a served (2xx) response. HeaderReceipt = plane.HeaderReceipt // DefaultValidFor is the default authorization validity window, in seconds. DefaultValidFor = 300 )
const ( DefaultTokenName = "USD Coin" DefaultTokenVersion = "2" )
TokenName / TokenVersion are the ERC-3009 EIP-712 domain fields. USDC uses ("USD Coin", "2"); operators pin them per token in Config.
const ( // DefaultNetwork / DefaultChainID pin the challenge's settlement network when // neither the resource's Terms nor the operator config names one. Default to // the Hanzo L1 (its EVM chainID == its network id), matching wallets. DefaultNetwork = "hanzo" DefaultChainID = 36963 // DefaultTokenDecimals is USDC's precision (the smallest-unit scale the client // signs over). Operators override per token via CLOUD_X402_TOKEN_DECIMALS. DefaultTokenDecimals = 6 )
Variables ¶
var ( // ErrPaymentRequired — the caller has not paid: no proof, an authorization that // does not verify, or a replayed nonce. The CHALLENGE is on the response's // X-Payment-Required header, so a client can pay and retry. ErrPaymentRequired = errors.New("x402: payment required") // this process, the price table or the recipient wallet did not resolve, the // caller has no billable identity, or settlement failed. ErrUnavailable = errors.New("x402: payment enforcement unavailable") )
Sentinel outcomes of Settle — the typed half of the same flow Enforce renders as an HTTP response. BOTH are fail-closed: a caller that gets either must refuse the call, never serve it. A price that cannot be enforced is not a free price.
Functions ¶
func Enforce ¶
Enforce is the pay-per-use middleware a priced route group applies, keyed on the request PATH.
Applying it is a DECLARATION that the group is for sale, so anything that leaves x402 unable to answer what the group costs is a refusal, never a passthrough. It refuses when x402 is not mounted in this process and when no price table has been published — the two ways "I cannot enforce payment here" arises — because the alternative renders both as "free", permanently and silently.
That is not hypothetical. The published Registry is a process-global installed by marketplace.Mount, and the shipped topology is one binary per app (manifest/apps.go; Dockerfile builds a plugin per row; cmd/cloud loads each as a child process), so a process that mounts x402 does NOT have marketplace in it and the table is nil. The fleet has been bitten by exactly this once already — see resource_billing_peer.go: "Splitting apps into their own binaries turned every priced create free without changing a line of billing code."
The TOOL path asks a different question — it offers EVERY dispatch to the seam, free ones included, so it must be able to answer "this costs nothing" — but it reaches the same safe answer by asking the process that OWNS the table (peer.go) rather than by reading its own absence as free. A middleware is applied only to what is FOR SALE and can refuse on sight; the tool seam is applied to everything and has to look. Neither ever renders "I cannot tell" as "free".
func Mount ¶
Mount wires /v1/x402 and the settlement store. Direct construction (not cloud.Mount) because it holds the package singleton the middleware reaches.
func Publish ¶
func Publish(r Registry)
Publish installs the marketplace registry x402 enforces against. Passing nil detaches it (Enforce reverts to passthrough). One registry, process-wide.
func Settle ¶ added in v1.801.350
Settle enforces payment for one resource against the REQUEST bound to ctx — the same flow Enforce runs, for a caller that identifies the priced thing itself rather than by path (the tool plane prices a TOOL, and every tool call arrives on the one /v1/tools/call route).
Free resource → nil, and no request is needed: an unpriced call off the HTTP path (the CLI's LocalInvoke) still runs. A PRICED one always needs the request, because the payer is the attested principal on it and the proof rides its headers.
Unpaid → the challenge is written to the response's X-Payment-Required header and ErrPaymentRequired is returned, so the caller refuses 402 and the client can pay and retry. Paid → settled exactly once, the receipt is on X-Payment-Receipt, nil.
func Verify ¶
func Verify(req PaymentRequirements, p Proof, tokenName, tokenVersion string, now int64) error
Verify checks that p is a well-formed, in-window, unaltered authorization for the requirements req: the To/Value match, the time window holds at now, and the EIP-712 signature recovers to p.From. It does NOT check replay (the store does) nor settle (the settler does) — one concern each.
Types ¶
type Config ¶
type Config struct {
Token string // ERC-3009 token contract (EIP-712 verifyingContract)
TokenName string // EIP-712 domain name (default "USD Coin")
TokenVersion string // EIP-712 domain version (default "2")
TokenDecimals int // token smallest-unit scale (default 6)
Network string // settlement network label (default "hanzo")
ChainID int64 // settlement chain id (default 36963)
ValidFor int64 // authorization validity window, seconds (default 300)
}
Config is the operator-pinned x402 settlement config. Token + its EIP-712 domain (name/version/decimals) MUST match the token the client signs against, or every signature fails to recover. Values are read from env in Mount; tests inject Config directly.
type PaymentRequirements ¶
type PaymentRequirements struct {
Version string `json:"version"`
Scheme string `json:"scheme"`
Resource string `json:"resource"` // the priced resource id (canonical path)
Network string `json:"network"`
ChainID int64 `json:"chainId"`
Token string `json:"token"` // ERC-3009 token contract (e.g. USDC)
Payee string `json:"payee"` // recipient wallet address
Amount string `json:"amount"` // token smallest-unit amount (decimal string)
ValidFor int64 `json:"validFor"` // seconds the authorization may remain valid
}
PaymentRequirements is the server's 402 challenge: exactly what the client must pay, and to whom, to access the resource. It is emitted both as the response body and the X-Payment-Required header.
type Proof ¶
type Proof struct {
From string `json:"from"` // payer address (recovers from Signature)
To string `json:"to"` // must equal the requirements Payee
Value string `json:"value"` // must equal the requirements Amount
ValidAfter int64 `json:"validAfter"` // unix seconds
ValidBefore int64 `json:"validBefore"` // unix seconds
Nonce string `json:"nonce"` // 32-byte hex (0x optional)
Signature string `json:"signature"` // 65-byte EIP-712 signature (hex)
}
Proof is the client's signed ERC-3009 authorization — the "payment". Nonce is a client-chosen 32-byte value and is the REPLAY ANCHOR: an authorization settles at most once per (From, Nonce).
func ParseProof ¶
ParseProof decodes a Proof from the X-Payment header value.
func Sign ¶ added in v1.801.350
func Sign(req PaymentRequirements, key *ecdsa.PrivateKey, nonce string, validAfter, validBefore int64, tokenName, tokenVersion string) (Proof, error)
Sign is the CLIENT half of the protocol and the exact mirror of Verify: it produces the ERC-3009 authorization a payer submits on the X-Payment header, bound to exactly the requirements it was challenged with. Empty tokenName / tokenVersion take the USDC defaults, the same substitution Config makes.
It lives here, beside Verify, because the EIP-712 encoding is ONE encoding: a signer that wrote it out a second time would be free to drift from the verifier and would fail only in production, where a real payer's signature stops recovering. One encoding, two directions.
type Receipt ¶
type Receipt struct {
ID string `json:"id"`
Resource string `json:"resource"`
Payer string `json:"payer"` // payer ORG (the debited ledger)
From string `json:"from"` // payer address
Payee string `json:"payee"` // recipient address
PayeeOrg string `json:"payeeOrg"`
Amount string `json:"amount"` // exact 18-dp USD (money.Amount string)
Nonce string `json:"nonce"`
SettledVia string `json:"settledVia"` // "ledger" (live) | "chain" (seam)
TxHash string `json:"txHash,omitempty"`
SettledAt int64 `json:"settledAt"`
}
Receipt is the settlement record returned on the X-Payment-Receipt header and looked up via GET /v1/x402/settlements/:id.
type Registry ¶
type Registry interface {
Price(ctx context.Context, resource string) (terms Terms, ok bool, err error)
}
Registry resolves a resource's payment Terms. ok=false means the resource is FREE — no enforcement. err is a real lookup failure (fail closed). The marketplace subsystem implements this and injects it via Publish.
type Settlement ¶
type Settlement struct {
ID string // deterministic: settlementID(from, nonce)
PayerOrg string
From string
Nonce string
Resource string
Payee string
PayeeOrg string
Amount string // exact 18-dp USD (money.Amount.IntString)
SettledVia string
TxHash string
CreatedAt int64
}
Settlement is the recorded outcome of one settled x402 authorization.
type Terms ¶
type Terms struct {
Amount money.Amount // exact 18-dp USD price
RecipientOrg string // the recipient wallet's org
RecipientWalletID string // the wallet that receives payment
Token string // optional per-resource token override
Network string // optional per-resource network override
ChainID int64 // optional per-resource chain override
}
Terms are a priced resource's payment terms: the price and the wallet that receives it. The marketplace registry OWNS the resource→Terms mapping; x402 resolves the recipient wallet (via wallets) and enforces payment against these.