Documentation
¶
Index ¶
- Constants
- Variables
- func BuildExtensionsForAsset(asset AssetInfo) map[string]any
- func BuildV1Requirement(chain ChainInfo, amount, recipientAddress string) x402types.PaymentRequirementsV1
- func BuildV2Requirement(chain ChainInfo, amount, recipientAddress string) x402types.PaymentRequirements
- func BuildV2RequirementWithAsset(chain ChainInfo, asset AssetInfo, amount, recipientAddress string) x402types.PaymentRequirements
- func EnsureVerifier(cfg *config.Config) error
- func NewForwardAuthMiddleware(cfg ForwardAuthConfig, requirements []x402types.PaymentRequirements) func(http.Handler) http.Handler
- func NormalizeNetworkID(network string) string
- func PopulateCABundle(cfg *config.Config)
- func Setup(cfg *config.Config, wallet, chain, facilitatorURL string) error
- func SupportedTokens() []string
- func TokenSupportedOnChain(tokenName, chainName string) bool
- func ValidateFacilitatorURL(u string) error
- func ValidateWallet(addr string) error
- func WatchConfig(ctx context.Context, path string, v *Verifier, interval time.Duration)
- func WatchConfigWithHandler(ctx context.Context, path string, interval time.Duration, ...)
- func WatchServiceOffers(ctx context.Context, cfg *rest.Config, apply func([]RouteRule) error) error
- type AssetInfo
- type ChainInfo
- type ConfigAccumulator
- type ForwardAuthConfig
- type PricingConfig
- type RouteRule
- type TokenEntry
- type Verifier
- func (v *Verifier) HandleHealthz(w http.ResponseWriter, r *http.Request)
- func (v *Verifier) HandleProxy(w http.ResponseWriter, r *http.Request)
- func (v *Verifier) HandleReadyz(w http.ResponseWriter, r *http.Request)
- func (v *Verifier) HandleVerify(w http.ResponseWriter, r *http.Request)
- func (v *Verifier) MetricsHandler() http.Handler
- func (v *Verifier) Reload(cfg *PricingConfig) error
Constants ¶
const ( // DefaultFacilitatorURL is the Obol-operated x402 facilitator for payment // verification and settlement. Supports Base Mainnet and Base Sepolia. DefaultFacilitatorURL = "https://x402.gcp.obol.tech" )
Variables ¶
var ( ChainBaseMainnet = ChainInfo{ Name: "base", NetworkID: "base", CAIP2Network: "eip155:8453", USDCAddress: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", Decimals: 6, EIP3009Name: "USD Coin", EIP3009Version: "2", } ChainBaseSepolia = ChainInfo{ Name: "base-sepolia", NetworkID: "base-sepolia", CAIP2Network: "eip155:84532", USDCAddress: "0x036CbD53842c5426634e7929541eC2318f3dCF7e", Decimals: 6, EIP3009Name: "USD Coin", EIP3009Version: "2", } ChainEthereumMainnet = ChainInfo{ Name: "ethereum", NetworkID: "ethereum", CAIP2Network: "eip155:1", USDCAddress: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", Decimals: 6, EIP3009Name: "USD Coin", EIP3009Version: "2", } ChainPolygonMainnet = ChainInfo{ Name: "polygon", NetworkID: "polygon", CAIP2Network: "eip155:137", USDCAddress: "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359", Decimals: 6, EIP3009Name: "USD Coin", EIP3009Version: "2", } ChainPolygonAmoy = ChainInfo{ Name: "polygon-amoy", NetworkID: "polygon-amoy", CAIP2Network: "eip155:80002", USDCAddress: "0x41E94Eb019C0762f9Bfcf9Fb1E58725BfB0e7582", Decimals: 6, EIP3009Name: "USD Coin", EIP3009Version: "2", } ChainAvalancheMainnet = ChainInfo{ Name: "avalanche", NetworkID: "avalanche", CAIP2Network: "eip155:43114", USDCAddress: "0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E", Decimals: 6, EIP3009Name: "USD Coin", EIP3009Version: "2", } ChainAvalancheFuji = ChainInfo{ Name: "avalanche-fuji", NetworkID: "avalanche-fuji", CAIP2Network: "eip155:43113", USDCAddress: "0x5425890298aed601595a70AB815c96711a31Bc65", Decimals: 6, EIP3009Name: "USD Coin", EIP3009Version: "2", } ChainArbitrumOne = ChainInfo{ Name: "arbitrum-one", NetworkID: "arbitrum-one", CAIP2Network: "eip155:42161", USDCAddress: "0xaf88d065e77c8cC2239327C5EDb3A432268e5831", Decimals: 6, EIP3009Name: "USD Coin", EIP3009Version: "2", } ChainArbitrumSepolia = ChainInfo{ Name: "arbitrum-sepolia", NetworkID: "arbitrum-sepolia", CAIP2Network: "eip155:421614", USDCAddress: "0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d", Decimals: 6, EIP3009Name: "USD Coin", EIP3009Version: "2", } )
Chain constants — USDC addresses verified against coinbase/x402/go v2.7.0 mechanisms/evm/constants.go and on-chain contract deployments.
Functions ¶
func BuildExtensionsForAsset ¶
BuildExtensionsForAsset returns the top-level x402 extensions that should be advertised on the 402 response for a given asset. Currently sets `eip2612GasSponsoring` when the asset supports gasless approve via Permit2 + ERC20Permit (e.g. OBOL on mainnet). Returns nil when no extensions apply — the caller's `omitempty` JSON tag drops the field entirely.
func BuildV1Requirement ¶
func BuildV1Requirement(chain ChainInfo, amount, recipientAddress string) x402types.PaymentRequirementsV1
BuildV1Requirement creates a v1 PaymentRequirementsV1 for USDC payment on the given chain. amount is the decimal USDC amount (e.g., "0.001" = $0.001).
func BuildV2Requirement ¶
func BuildV2Requirement(chain ChainInfo, amount, recipientAddress string) x402types.PaymentRequirements
BuildV2Requirement creates a v2 PaymentRequirements for USDC payment on the given chain. amount is the decimal USDC amount (e.g. "0.001" = $0.001).
func BuildV2RequirementWithAsset ¶
func BuildV2RequirementWithAsset(chain ChainInfo, asset AssetInfo, amount, recipientAddress string) x402types.PaymentRequirements
BuildV2RequirementWithAsset creates a v2 PaymentRequirements for the given chain and settlement asset.
func EnsureVerifier ¶
EnsureVerifier deploys the x402 verifier subsystem if it doesn't exist. Idempotent — kubectl apply is safe to run multiple times.
func NewForwardAuthMiddleware ¶
func NewForwardAuthMiddleware(cfg ForwardAuthConfig, requirements []x402types.PaymentRequirements) func(http.Handler) http.Handler
NewForwardAuthMiddleware creates an x402 payment-gating middleware compatible with the v1 wire format. It checks the X-PAYMENT header, verifies the payment with the facilitator, and optionally settles after a successful downstream response.
When VerifyOnly is true (Traefik ForwardAuth path), settlement is skipped. When VerifyOnly is false (standalone gateway path), settlement runs only after the inner handler returns a success status (< 400).
func NormalizeNetworkID ¶
NormalizeNetworkID maps a human-friendly chain name to its CAIP-2 network identifier. Already-normalized CAIP-2 values are returned as-is.
func PopulateCABundle ¶
PopulateCABundle reads the host's CA certificate bundle and replaces the ca-certificates ConfigMap in the x402 namespace. Call this whenever the x402 verifier is deployed or updated without going through EnsureVerifier. Silently skips if no CA bundle is found on the host.
func Setup ¶
Setup configures x402 pricing in the cluster by patching the ConfigMap and Secret. Stakater Reloader auto-restarts the verifier pod. If facilitatorURL is empty, the Obol-operated facilitator is used.
func SupportedTokens ¶
func SupportedTokens() []string
SupportedTokens returns a sorted slice of all registered token symbols.
func TokenSupportedOnChain ¶
TokenSupportedOnChain reports whether a named token is registered for a chain.
func ValidateFacilitatorURL ¶
ValidateFacilitatorURL checks that the facilitator URL uses HTTPS. Payment proofs sent over plain HTTP could be intercepted. Loopback addresses (localhost, 127.0.0.1, [::1]) and k3d/Docker internal addresses are exempted for local development and testing.
func ValidateWallet ¶
ValidateWallet checks that addr is a valid 0x-prefixed 20-byte hex Ethereum address.
func WatchConfig ¶
WatchConfig polls a YAML config file for changes and reloads the Verifier when the file is modified. It checks the file's modification time every interval. This handles ConfigMap volume mount updates (kubelet symlink swaps) without requiring fsnotify.
WatchConfig blocks until the context is cancelled.
func WatchConfigWithHandler ¶
Types ¶
type AssetInfo ¶
type AssetInfo struct {
Address string
Symbol string
Decimals int
TransferMethod string
EIP712Name string
EIP712Version string
// EIP2612GasSponsoring is true when the token supports ERC20Permit and
// the facilitator can batch permit() with transferFrom on settle, letting
// buyers skip the one-time approve(Permit2, max) tx. Surfaced to buyers
// via the top-level `extensions.eip2612GasSponsoring` field on the 402
// response.
EIP2612GasSponsoring bool
}
AssetInfo describes the token and EIP-712 metadata used for x402 settlement.
func ResolveAssetInfo ¶
ResolveAssetInfo applies any route-level asset overrides on top of the chain's default settlement asset.
type ChainInfo ¶
type ChainInfo struct {
// Name is the human-friendly identifier used by the CLI (e.g., "base-sepolia").
Name string
// NetworkID is the v1 wire-format network name sent to the facilitator.
NetworkID string
// CAIP2Network is the v2 wire-format network identifier.
CAIP2Network string
// USDCAddress is the USDC token contract address on this chain.
USDCAddress string
// Decimals is the token decimal precision (6 for USDC).
Decimals int
// EIP3009Name is the EIP-712 domain name for TransferWithAuthorization.
EIP3009Name string
// EIP3009Version is the EIP-712 domain version.
EIP3009Version string
}
ChainInfo holds chain-specific configuration for x402 payment gating. It maps a human-friendly chain name to the on-wire identifiers the facilitator expects (v1 network names, USDC contract address, EIP-3009 domain parameters).
func ResolveChainInfo ¶
ResolveChainInfo maps a human-friendly chain name to its ChainInfo. Phase 2 renames this to ResolveChain after deleting the old one in config.go.
func (ChainInfo) DefaultAsset ¶
DefaultAsset returns the default settlement asset for a chain.
type ConfigAccumulator ¶
type ConfigAccumulator struct {
// contains filtered or unexported fields
}
func NewConfigAccumulator ¶
func NewConfigAccumulator(base *PricingConfig, verifier *Verifier) *ConfigAccumulator
func (*ConfigAccumulator) SetBase ¶
func (a *ConfigAccumulator) SetBase(base *PricingConfig) error
func (*ConfigAccumulator) SetRoutes ¶
func (a *ConfigAccumulator) SetRoutes(routes []RouteRule) error
type ForwardAuthConfig ¶
type ForwardAuthConfig struct {
// FacilitatorURL is the x402 facilitator service URL (e.g., "https://x402.org/facilitator").
FacilitatorURL string
// VerifyOnly skips blockchain settlement when true. Used by the Traefik
// ForwardAuth verifier where only payment verification is needed.
//
// INVARIANT: VerifyOnly MUST be true whenever this middleware is used
// behind Traefik ForwardAuth. The auth hop runs before the upstream is
// contacted and cannot observe the upstream's status; settling there
// debits the payer before the upstream has proven it served the request.
// VerifyOnly=false is only safe for in-process middleware (e.g. the
// standalone inference gateway) that sees the real upstream status.
//
// NewForwardAuthMiddleware logs a loud warning when VerifyOnly is false
// so operators who flip this in x402-pricing.yaml notice in logs.
VerifyOnly bool
// Extensions, if non-nil, is emitted as the top-level `extensions` field
// on 402 responses. Used to advertise capabilities like
// `eip2612GasSponsoring` (gasless Permit2 approve) so buyers take the
// matching flow. See BuildExtensionsForAsset for how this is populated.
Extensions map[string]any
}
ForwardAuthConfig configures the ForwardAuth x402 middleware.
type PricingConfig ¶
type PricingConfig struct {
// Wallet is the USDC recipient address for all payments.
Wallet string `yaml:"wallet"`
// Chain is the blockchain network name (e.g., "base-sepolia", "base").
Chain string `yaml:"chain"`
// FacilitatorURL is the x402 facilitator service URL.
FacilitatorURL string `yaml:"facilitatorURL"`
// VerifyOnly skips blockchain settlement after successful verification.
VerifyOnly bool `yaml:"verifyOnly"`
// Routes defines per-route pricing rules. First match wins.
Routes []RouteRule `yaml:"routes"`
}
PricingConfig is the top-level configuration for the x402 ForwardAuth verifier. It defines global payment parameters and per-route pricing rules.
func GetPricingConfig ¶
func GetPricingConfig(cfg *config.Config) (*PricingConfig, error)
GetPricingConfig reads the current x402 pricing ConfigMap from the cluster.
func LoadConfig ¶
func LoadConfig(path string) (*PricingConfig, error)
LoadConfig reads and parses a pricing configuration YAML file.
type RouteRule ¶
type RouteRule struct {
// Pattern is a path matching pattern. Supports:
// - Exact match: "/health"
// - Prefix match: "/rpc/*" (matches /rpc/anything)
// - Glob match: "/inference-*/v1/*"
Pattern string `yaml:"pattern"`
// Price is the USDC amount per request (e.g., "0.0001").
Price string `yaml:"price"`
// Description is a human-readable label for this route (optional).
Description string `yaml:"description"`
// PayTo overrides the global wallet for this route (x402: payTo).
// If empty, falls back to PricingConfig.Wallet.
PayTo string `yaml:"payTo,omitempty"`
// Network overrides the global chain for this route (human-friendly).
// If empty, falls back to PricingConfig.Chain.
Network string `yaml:"network,omitempty"`
// AssetAddress overrides the default token contract address for this route.
AssetAddress string `yaml:"assetAddress,omitempty"`
// AssetSymbol records the token symbol used for this route.
AssetSymbol string `yaml:"assetSymbol,omitempty"`
// AssetDecimals overrides the token decimals for this route.
AssetDecimals int `yaml:"assetDecimals,omitempty"`
// AssetTransferMethod overrides the x402 transfer method for this route.
AssetTransferMethod string `yaml:"assetTransferMethod,omitempty"`
// EIP712Name overrides the EIP-712 domain name for this route's asset.
EIP712Name string `yaml:"eip712Name,omitempty"`
// EIP712Version overrides the EIP-712 domain version for this route's asset.
EIP712Version string `yaml:"eip712Version,omitempty"`
// UpstreamAuth is injected as the Authorization header on approved requests.
// The shared seller gateway injects this header on approved upstream
// requests. This lets the upstream (e.g., LiteLLM) authenticate the request
// without exposing the key to buyers.
UpstreamAuth string `yaml:"upstreamAuth,omitempty"`
// UpstreamURL is the in-cluster HTTP base URL for the protected upstream.
// The shared seller gateway proxies matched paid routes to this target after
// successful payment verification.
UpstreamURL string `yaml:"upstreamURL,omitempty"`
// StripPrefix is removed from the incoming request path before proxying to
// the upstream. For ServiceOffers this is usually /services/<offer-name>.
StripPrefix string `yaml:"stripPrefix,omitempty"`
// PriceModel records which price field produced the enforced request price.
// It is metadata only; the verifier always enforces Price.
PriceModel string `yaml:"priceModel,omitempty"`
// PerMTok stores the original per-million-token price when Price was
// approximated for phase 1 request-based gating.
PerMTok string `yaml:"perMTok,omitempty"`
// ApproxTokensPerRequest stores the fixed token estimate used to derive
// Price from PerMTok during phase 1.
ApproxTokensPerRequest int `yaml:"approxTokensPerRequest,omitempty"`
// OfferNamespace identifies the originating ServiceOffer namespace.
OfferNamespace string `yaml:"offerNamespace,omitempty"`
// OfferName identifies the originating ServiceOffer name.
OfferName string `yaml:"offerName,omitempty"`
}
RouteRule maps a URL pattern to x402 payment requirements. Per-route fields (PayTo, Network) override the global PricingConfig values when set, enabling multiple ServiceOffers with different wallets/chains.
type TokenEntry ¶
type TokenEntry struct {
// Address is the ERC-20 contract address on the target chain.
Address string
// Symbol is the human-friendly token symbol (e.g. "USDC", "OBOL").
Symbol string
// Decimals is the token precision in atomic units (6 for USDC, 18 for OBOL).
Decimals int
// TransferMethod is the x402 asset transfer method.
// "eip3009" — token natively implements transferWithAuthorization (USDC, EURC).
// "permit2" — uses Uniswap Permit2 for authorization (any ERC-20).
TransferMethod string
// EIP712Name is the EIP-712 domain name for signing.
EIP712Name string
// EIP712Version is the EIP-712 domain version for signing.
EIP712Version string
// EIP2612GasSponsoring is true when the token implements EIP-2612
// (ERC20Permit) and the configured facilitator can batch the permit() call
// with the on-chain transferFrom during settlement, sponsoring gas. When
// true, the seller's 402 advertises `eip2612GasSponsoring` in extensions
// so buyers skip the one-time approve(Permit2, max) step. Only relevant
// for permit2 transfer methods.
EIP2612GasSponsoring bool
}
TokenEntry defines a whitelisted token's per-chain metadata. Each entry carries everything the sell-side, verifier, and buy-side need to construct and validate x402 payment requirements for this token.
func ResolveToken ¶
func ResolveToken(tokenName, chainName string) (TokenEntry, bool)
ResolveToken looks up a token by name and chain. Returns the token entry and true if found, or a zero entry and false if the token is not registered for the given chain. Token name is matched case-insensitively.
type Verifier ¶
type Verifier struct {
// contains filtered or unexported fields
}
Verifier is a ForwardAuth-compatible HTTP handler that enforces x402 micropayments on a per-route basis. Traefik sends every incoming request to /verify; the Verifier either returns 200 (allow) or 402 (pay-wall).
func NewVerifier ¶
func NewVerifier(cfg *PricingConfig) (*Verifier, error)
NewVerifier creates a Verifier with the given initial configuration.
func (*Verifier) HandleHealthz ¶
func (v *Verifier) HandleHealthz(w http.ResponseWriter, r *http.Request)
HandleHealthz returns 200 OK for liveness probes.
func (*Verifier) HandleProxy ¶
func (v *Verifier) HandleProxy(w http.ResponseWriter, r *http.Request)
HandleProxy serves the seller-owned paid route directly. It matches the incoming path to a ServiceOffer-derived route rule, verifies the payment, proxies to the real upstream, and settles only after the upstream succeeds.
func (*Verifier) HandleReadyz ¶
func (v *Verifier) HandleReadyz(w http.ResponseWriter, r *http.Request)
HandleReadyz returns 200 OK if pricing config is loaded, 503 otherwise.
func (*Verifier) HandleVerify ¶
func (v *Verifier) HandleVerify(w http.ResponseWriter, r *http.Request)
HandleVerify is the ForwardAuth endpoint. Traefik forwards the original request headers; the Verifier inspects X-Forwarded-Uri to determine which pricing rule applies.
Response semantics (ForwardAuth contract):
- 200: allow the request through to the backend
- 402: deny with x402 payment requirements in the response body
- 500: internal error (Traefik returns 500 to the client)
func (*Verifier) MetricsHandler ¶
MetricsHandler exposes Prometheus metrics for the verifier.
func (*Verifier) Reload ¶
func (v *Verifier) Reload(cfg *PricingConfig) error
Reload atomically swaps the pricing configuration.