Go SDK Reference#
Go SDK Reference (for exact, aggr_deferred)#
Packages#
| Package | Description |
|---|---|
github.com/okx/payments/go/x402 | Core: client, server, facilitator, type definitions |
.../go/x402/mechanisms/evm | EVM mechanisms: exact, aggr_deferred |
.../go/x402/mechanisms/svm | Solana mechanisms: exact |
.../go/x402/http/gin | Gin middleware (seller side) |
.../go/x402/http/echo | Echo middleware (seller side) |
.../go/x402/http/nethttp | net/http middleware (seller side) |
.../go/x402/http | HTTP client wrapper (buyer side) |
Core types#
Network#
type Network string
// CAIP-2 format, e.g., "eip155:196", "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp"
func (n Network) Parse() (namespace, reference string, err error)
func (n Network) Match(pattern Network) bool
Price / AssetAmount#
type Price interface{}
// Can be string ("$0.01"), number (0.01), or map (AssetAmount)
type AssetAmount struct {
Asset string `json:"asset"` // Token contract address
Amount string `json:"amount"` // Smallest unit amount
Extra map[string]interface{} `json:"extra,omitempty"`
}
ResourceConfig#
type ResourceConfig struct {
Scheme string `json:"scheme"` // "exact" | "aggr_deferred" | "upto"
PayTo string `json:"payTo"` // Recipient wallet
Price Price `json:"price"` // "$0.01" or AssetAmount
Network Network `json:"network"` // "eip155:196"
MaxTimeoutSeconds int `json:"maxTimeoutSeconds,omitempty"` // Default: 60
Extra map[string]interface{} `json:"extra,omitempty"`
}
VerifyResponse#
type VerifyResponse struct {
IsValid bool `json:"isValid"`
InvalidReason string `json:"invalidReason,omitempty"`
InvalidMessage string `json:"invalidMessage,omitempty"`
Payer string `json:"payer,omitempty"`
}
SettleResponse#
type SettleResponse struct {
Success bool `json:"success"`
ErrorReason string `json:"errorReason,omitempty"`
ErrorMessage string `json:"errorMessage,omitempty"`
Payer string `json:"payer,omitempty"`
Transaction string `json:"transaction"`
Network Network `json:"network"`
Status string `json:"status,omitempty"` // OKX: "pending" | "success" | "timeout"
}
SupportedResponse#
type SupportedKind struct {
X402Version int `json:"x402Version"`
Scheme string `json:"scheme"`
Network Network `json:"network"`
Extra map[string]interface{} `json:"extra,omitempty"`
}
type SupportedResponse struct {
Kinds []SupportedKind `json:"kinds"`
Extensions []string `json:"extensions"`
Signers map[string][]string `json:"signers"` // CAIP family → addresses
}
SettlementOverrides#
type SettlementOverrides struct {
Amount string `json:"amount,omitempty"` // Atomic units, percentage, or dollar
}
View interfaces#
A unified view over V1/V2 payment data:
type PaymentRequirementsView interface {
GetScheme() string
GetNetwork() string
GetAsset() string
GetAmount() string
GetPayTo() string
GetMaxTimeoutSeconds() int
GetExtra() map[string]interface{}
}
type PaymentPayloadView interface {
GetVersion() int
GetScheme() string
GetNetwork() string
GetPayload() map[string]interface{}
}
Client API (x402Client)#
Constructor#
import x402 "github.com/okx/payments/go/x402"
client := x402.Newx402Client(opts ...ClientOption)
ClientOption functions:
| Option | Description |
|---|---|
WithPaymentSelector(selector) | Custom payment-requirements selector |
WithPolicy(policy) | Add a payment-filter policy |
Registration methods (chainable)#
func (c *x402Client) Register(network Network, client SchemeNetworkClient) *x402Client
func (c *x402Client) RegisterV1(network Network, client SchemeNetworkClientV1) *x402Client
func (c *x402Client) RegisterPolicy(policy PaymentPolicy) *x402Client
func (c *x402Client) RegisterExtension(ext ClientExtension) *x402Client
Payment creation#
// Select best payment option from requirements list
func (c *x402Client) SelectPaymentRequirements(
requirements []types.PaymentRequirements,
) (types.PaymentRequirements, error)
// Create signed payment payload
func (c *x402Client) CreatePaymentPayload(
ctx context.Context,
requirements types.PaymentRequirements,
resource *types.ResourceInfo,
extensions map[string]interface{},
) (types.PaymentPayload, error)
Lifecycle hooks (chainable)#
func (c *x402Client) OnBeforePaymentCreation(hook BeforePaymentCreationHook) *x402Client
func (c *x402Client) OnAfterPaymentCreation(hook AfterPaymentCreationHook) *x402Client
func (c *x402Client) OnPaymentCreationFailure(hook OnPaymentCreationFailureHook) *x402Client
Policy and selector types#
type PaymentRequirementsSelector func(requirements []PaymentRequirementsView) PaymentRequirementsView
type PaymentPolicy func(requirements []PaymentRequirementsView) []PaymentRequirementsView
func DefaultPaymentSelector(requirements []PaymentRequirementsView) PaymentRequirementsView
Server API (x402ResourceServer)#
Constructor#
server := x402.Newx402ResourceServer(opts ...ResourceServerOption)
ResourceServerOption functions:
| Option | Description |
|---|---|
WithFacilitatorClient(client) | Set the facilitator client |
WithSchemeServer(network, server) | Register a scheme |
WithCacheTTL(ttl) | Set the supported-kinds cache TTL |
Registration methods (chainable)#
func (s *x402ResourceServer) Register(network Network, server SchemeNetworkServer) *x402ResourceServer
func (s *x402ResourceServer) RegisterExtension(ext types.ResourceServerExtension) *x402ResourceServer
Initialization#
func (s *x402ResourceServer) Initialize(ctx context.Context) error
Payment operations#
// Build requirements from config
func (s *x402ResourceServer) BuildPaymentRequirementsFromConfig(
ctx context.Context,
config ResourceConfig,
) ([]types.PaymentRequirements, error)
// Build single requirement
func (s *x402ResourceServer) BuildPaymentRequirements(
ctx context.Context,
config ResourceConfig,
supportedKind types.SupportedKind,
extensions []string,
) (types.PaymentRequirements, error)
// Create 402 response
func (s *x402ResourceServer) CreatePaymentRequiredResponse(
requirements []types.PaymentRequirements,
resourceInfo *types.ResourceInfo,
errorMsg string,
extensions map[string]interface{},
) types.PaymentRequired
// Find matching requirements for a payment payload
func (s *x402ResourceServer) FindMatchingRequirements(
available []types.PaymentRequirements,
payload types.PaymentPayload,
) *types.PaymentRequirements
// Verify payment signature
func (s *x402ResourceServer) VerifyPayment(
ctx context.Context,
payload types.PaymentPayload,
requirements types.PaymentRequirements,
) (*VerifyResponse, error)
// Settle payment on-chain
func (s *x402ResourceServer) SettlePayment(
ctx context.Context,
payload types.PaymentPayload,
requirements types.PaymentRequirements,
overrides *SettlementOverrides,
) (*SettleResponse, error)
// Check registered support
func (s *x402ResourceServer) HasRegisteredScheme(network Network, scheme string) bool
func (s *x402ResourceServer) HasFacilitatorSupport(network Network, scheme string) bool
Server lifecycle hooks (chainable)#
func (s *x402ResourceServer) OnBeforeVerify(hook BeforeVerifyHook) *x402ResourceServer
func (s *x402ResourceServer) OnAfterVerify(hook AfterVerifyHook) *x402ResourceServer
func (s *x402ResourceServer) OnVerifyFailure(hook OnVerifyFailureHook) *x402ResourceServer
func (s *x402ResourceServer) OnBeforeSettle(hook BeforeSettleHook) *x402ResourceServer
func (s *x402ResourceServer) OnAfterSettle(hook AfterSettleHook) *x402ResourceServer
func (s *x402ResourceServer) OnSettleFailure(hook OnSettleFailureHook) *x402ResourceServer
Scheme interfaces#
SchemeNetworkClient (buyer side)#
type SchemeNetworkClient interface {
Scheme() string
CreatePaymentPayload(ctx context.Context, requirements types.PaymentRequirements) (types.PaymentPayload, error)
}
type ExtensionAwareClient interface {
SchemeNetworkClient
CreatePaymentPayloadWithExtensions(ctx context.Context, requirements types.PaymentRequirements, extensions map[string]interface{}) (types.PaymentPayload, error)
}
SchemeNetworkServer (seller side)#
type SchemeNetworkServer interface {
Scheme() string
ParsePrice(price Price, network Network) (AssetAmount, error)
EnhancePaymentRequirements(ctx context.Context, requirements types.PaymentRequirements, supportedKind types.SupportedKind, extensions []string) (types.PaymentRequirements, error)
}
SchemeNetworkFacilitator#
type SchemeNetworkFacilitator interface {
Scheme() string
CaipFamily() string
GetExtra(network Network) map[string]interface{}
GetSigners(network Network) []string
Verify(ctx context.Context, payload types.PaymentPayload, requirements types.PaymentRequirements, fctx *FacilitatorContext) (*VerifyResponse, error)
Settle(ctx context.Context, payload types.PaymentPayload, requirements types.PaymentRequirements, fctx *FacilitatorContext) (*SettleResponse, error)
}
FacilitatorClient (network boundary)#
type FacilitatorClient interface {
Verify(ctx context.Context, payloadBytes []byte, requirementsBytes []byte) (*VerifyResponse, error)
Settle(ctx context.Context, payloadBytes []byte, requirementsBytes []byte) (*SettleResponse, error)
GetSupported(ctx context.Context) (SupportedResponse, error)
}
ClientExtension#
type ClientExtension interface {
Key() string
EnrichPaymentPayload(ctx context.Context, payload types.PaymentPayload, required types.PaymentRequired) (types.PaymentPayload, error)
}
MoneyParser#
type MoneyParser func(amount float64, network Network) (*AssetAmount, error)
Middleware reference#
All Go middleware packages provide three layers of construction plus builder shortcuts.
Gin (go/x402/http/gin)#
import x402gin "github.com/okx/payments/go/x402/http/gin"
// Standard (recommended)
r.Use(x402gin.PaymentMiddleware(routes, server, opts ...MiddlewareOption))
// From HTTP server
r.Use(x402gin.PaymentMiddlewareFromHTTPServer(httpServer, opts ...MiddlewareOption))
// From config
r.Use(x402gin.PaymentMiddlewareFromConfig(routes, opts ...MiddlewareOption))
// Builder shortcut
r.Use(x402gin.X402Payment(Config{ Routes: routes, Facilitator: facilitator, Schemes: schemes }))
// Simple one-liner
r.Use(x402gin.SimpleX402Payment(payTo, price, network, facilitatorURL))
MiddlewareOption functions:
| Option | Type | Description |
|---|---|---|
WithFacilitatorClient(client) | FacilitatorClient | Facilitator used for verification / settlement |
WithScheme(network, server) | Network, SchemeNetworkServer | Register a scheme |
WithPaywallConfig(config) | *PaywallConfig | Browser paywall settings |
WithSyncFacilitatorOnStart(bool) | bool | Fetch supported kinds on startup |
WithErrorHandler(fn) | func(*gin.Context, error) | Custom error handler |
WithSettlementHandler(fn) | func(*gin.Context, *SettleResponse) | Custom settlement handler |
WithTimeout(duration) | time.Duration | Request timeout |
Config struct (builder pattern):
type Config struct {
Routes x402http.RoutesConfig
Facilitator x402.FacilitatorClient
Facilitators []x402.FacilitatorClient
Schemes []SchemeConfig
PaywallConfig *x402http.PaywallConfig
SyncFacilitatorOnStart bool
Timeout time.Duration
ErrorHandler func(*gin.Context, error)
SettlementHandler func(*gin.Context, *x402.SettleResponse)
}
type SchemeConfig struct {
Network x402.Network
Server x402.SchemeNetworkServer
}
Echo (go/x402/http/echo)#
Same API surface as Gin, but using echo.Context:
import x402echo "github.com/okx/payments/go/x402/http/echo"
e.Use(x402echo.PaymentMiddleware(routes, server, opts ...MiddlewareOption))
e.Use(x402echo.X402Payment(Config{ ... }))
e.Use(x402echo.SimpleX402Payment(payTo, price, network, facilitatorURL))
net/http (go/x402/http/nethttp)#
Returns func(http.Handler) http.Handler:
import x402nethttp "github.com/okx/payments/go/x402/http/nethttp"
handler := x402nethttp.PaymentMiddleware(routes, server, opts ...MiddlewareOption)(yourHandler)
handler := x402nethttp.X402Payment(Config{ ... })(yourHandler)
handler := x402nethttp.SimpleX402Payment(payTo, price, network, facilitatorURL)(yourHandler)
http.ListenAndServe(":4021", handler)
HTTP client (buyer side)#
import x402http "github.com/okx/payments/go/x402/http"
// Create HTTP client with payment support
httpClient := x402http.Newx402HTTPClient(x402Client)
// Wrap existing http.Client
wrappedClient := x402http.WrapHTTPClientWithPayment(http.DefaultClient, httpClient)
// Direct methods
resp, err := httpClient.GetWithPayment(ctx, url)
resp, err := httpClient.PostWithPayment(ctx, url, body)
resp, err := httpClient.DoWithPayment(ctx, req)
// Convenience functions
resp, err := x402http.Get(ctx, url, httpClient)
resp, err := x402http.Post(ctx, url, body, httpClient)
resp, err := x402http.Do(ctx, req, httpClient)
EVM mechanisms#
ExactEvmScheme (client)#
import (
"github.com/okx/payments/go/x402/mechanisms/evm"
evmsigners "github.com/okx/payments/go/x402/mechanisms/evm/signers"
evmclient "github.com/okx/payments/go/x402/mechanisms/evm/exact/client"
)
signer, err := evmsigners.NewClientSignerFromPrivateKey("0x...")
scheme := evmclient.NewExactEvmScheme(signer, config)
scheme.Scheme() // "exact"
ClientEvmSigner interface:
type ClientEvmSigner interface {
Address() string
SignTypedData(ctx context.Context, domain TypedDataDomain, types map[string][]TypedDataField, primaryType string, message map[string]interface{}) ([]byte, error)
}
ExactEvmScheme (server)#
import evmserver "github.com/okx/payments/go/x402/mechanisms/evm/exact/server"
scheme := evmserver.NewExactEvmScheme()
scheme.RegisterMoneyParser(customParser) // Optional: custom price parsing
scheme.Scheme() // "exact"
ExactEvmScheme (facilitator)#
import evmfacilitator "github.com/okx/payments/go/x402/mechanisms/evm/exact/facilitator"
scheme := evmfacilitator.NewExactEvmScheme(signer, &ExactEvmSchemeConfig{
DeployERC4337WithEIP6492: false, // Auto-deploy smart wallets
SimulateInSettle: false, // Re-simulate during settle
})
EVM types#
type TypedDataDomain struct {
Name string `json:"name"`
Version string `json:"version"`
ChainID *big.Int `json:"chainId"`
VerifyingContract string `json:"verifyingContract"`
}
type TypedDataField struct {
Name string `json:"name"`
Type string `json:"type"`
}
type AssetTransferMethod string
const (
AssetTransferMethodEIP3009 AssetTransferMethod = "eip3009"
AssetTransferMethodPermit2 AssetTransferMethod = "permit2"
)
type AssetInfo struct {
Address string
Name string
Version string
Decimals int
AssetTransferMethod AssetTransferMethod
SupportsEip2612 bool
}
SVM mechanisms#
ExactSvmScheme (client)#
import (
svmsigners "github.com/okx/payments/go/x402/mechanisms/svm/signers"
svmclient "github.com/okx/payments/go/x402/mechanisms/svm/exact/client"
)
signer, err := svmsigners.NewClientSignerFromPrivateKey(solanaPrivateKey)
scheme := svmclient.NewExactSvmScheme(signer, config)
ExactSvmScheme (server)#
import svmserver "github.com/okx/payments/go/x402/mechanisms/svm/exact/server"
scheme := svmserver.NewExactSvmScheme()
scheme.RegisterMoneyParser(customParser)
Facilitator hooks#
type FacilitatorVerifyContext struct {
Ctx context.Context
Payload PaymentPayloadView
Requirements PaymentRequirementsView
PayloadBytes []byte
RequirementsBytes []byte
}
type FacilitatorBeforeHookResult struct {
Abort bool
Reason string
Message string
}
type FacilitatorBeforeVerifyHook func(FacilitatorVerifyContext) (*FacilitatorBeforeHookResult, error)
type FacilitatorAfterVerifyHook func(FacilitatorVerifyResultContext) error
type FacilitatorOnVerifyFailureHook func(FacilitatorVerifyFailureContext) (*FacilitatorVerifyFailureHookResult, error)
type FacilitatorBeforeSettleHook func(FacilitatorSettleContext) (*FacilitatorBeforeHookResult, error)
type FacilitatorAfterSettleHook func(FacilitatorSettleResultContext) error
type FacilitatorOnSettleFailureHook func(FacilitatorSettleFailureContext) (*FacilitatorSettleFailureHookResult, error)
Go SDK Reference (for charge, session)#
Packages#
| Package | Import path | Description |
|---|---|---|
server | github.com/okx/payments/go/mpp/server | High-level MPP coordinator: Mpp, EVMConfig, ChargeRouteConfig, SessionRouteConfig |
evm | github.com/okx/payments/go/mpp/evm | EVM payment methods: EVMChargeMethod / EVMSessionMethod, EIP-712 sign/verify, Voucher, Authorization |
protocol | github.com/okx/payments/go/mpp/protocol | Protocol layer: challenge / credential / receipt parsing & formatting, ChargeVerifier / SessionVerifier interfaces, error codes |
saclient | github.com/okx/payments/go/mpp/saclient | SA-API client: SAClient interface and OKXSAClient implementation, HMAC-SHA256 auth |
store | github.com/okx/payments/go/mpp/store | Generic key-value store: Store[T] interface, MemoryStore, FileStore, ChannelState |
http/gin | github.com/okx/payments/go/mpp/http/gin | Gin framework middleware: ChargeMiddleware / SessionMiddleware |
http/echo | github.com/okx/payments/go/mpp/http/echo | Echo framework middleware: ChargeMiddleware / SessionMiddleware |
http/nethttp | github.com/okx/payments/go/mpp/http/nethttp | Standard net/http middleware: ChargeMiddleware / SessionMiddleware |
adapters | github.com/okx/payments/go/mpp/adapters | Payment Router adapter: MppAdapter implements paymentrouter.ProtocolAdapter |
sse | github.com/okx/payments/go/mpp/sse | Server-Sent Events helpers: Event formatting/parsing, Serve streaming push |
errors | github.com/okx/payments/go/mpp/errors | Error types: MppError, MppErrorCode, RFC 9457 PaymentErrorDetails |
Core types (evm package)#
Constants#
const DefaultExpiresMinutes = 5
const MethodNameEVM = "evm"
const MaxSplits = 10
// Default EIP-712 domain constants
const DefaultDomainName = "EVM Payment Channel"
const DefaultDomainVersion = "1"
// Default Escrow contract address (X Layer)
const DefaultEscrowContract = "0x5E550002e64FaF79B41D89fE8439eEb1be66CE3b"
// EIP-712 proof domain constants
const ProofDomainName = "MPP"
const ProofDomainVersion = "1"
// X Layer chain ID
const XLayerChainID uint64 = 196
// Session action constants
const (
ActionOpen = "open"
ActionTopUp = "topUp"
ActionVoucher = "voucher"
ActionClose = "close"
ActionSettle = "settle"
)
// Session receipt status constants
const (
StatusOpen = "open"
StatusClosed = "closed"
)
Split#
A split entry in a Charge challenge (absolute amount).
type Split struct {
Amount string `json:"amount"`
Memo *string `json:"memo,omitempty"`
Recipient string `json:"recipient"`
}
EVMMethodDetails#
The methodDetails field of a Charge request (embedded in the challenge request base64url JSON).
type EVMMethodDetails struct {
ChainID *uint64 `json:"chainId,omitempty"`
FeePayer *bool `json:"feePayer,omitempty"`
Memo *string `json:"memo,omitempty"`
Splits []Split `json:"splits,omitempty"`
}
func (d *EVMMethodDetails) IsFeePayer() bool
SessionSplit#
A Session split entry (basis-point ratio).
// Constraints: bps in [1, 9999]; sum(splits[].bps) < 10000.
type SessionSplit struct {
Recipient string `json:"recipient"`
Bps uint32 `json:"bps"`
Memo *string `json:"memo,omitempty"`
}
EVMSessionMethodDetails#
The methodDetails field of a Session request.
type EVMSessionMethodDetails struct {
EscrowContract string `json:"escrowContract"`
ChannelID *string `json:"channelId,omitempty"`
MinVoucherDelta *string `json:"minVoucherDelta,omitempty"`
ChainID *uint64 `json:"chainId,omitempty"`
FeePayer *bool `json:"feePayer,omitempty"`
Splits []SessionSplit `json:"splits,omitempty"`
}
func (d *EVMSessionMethodDetails) IsFeePayer() bool
SessionReceipt (EVM)#
The EVM-layer session receipt, including channel-specific fields.
type SessionReceipt struct {
ChannelID string `json:"channelId"`
CumulativeAmount string `json:"cumulativeAmount"`
EscrowContract string `json:"escrowContract"`
Status string `json:"status"`
Reference string `json:"reference,omitempty"`
}
func NewSessionReceipt(channelID, cumulativeAmount, escrowContract, status, reference string) *SessionReceipt
func (r *SessionReceipt) ToBaseReceipt(challengeID string) (*protocol.Receipt, error)
Payload types#
// Base payload struct
type Payload struct {
Action string `json:"action"`
}
type OpenPayload struct {
Payload
Type string `json:"type"` // "transaction" | "hash"
ChannelID string `json:"channelId"`
Salt string `json:"salt"`
CumulativeAmount string `json:"cumulativeAmount"`
Signature string `json:"signature"`
Authorization *OpenAuthorization `json:"authorization,omitempty"`
Hash string `json:"hash,omitempty"`
VoucherSignature string `json:"voucherSignature,omitempty"`
AuthorizedSigner string `json:"authorizedSigner,omitempty"`
Deposit string `json:"deposit,omitempty"`
}
type TopUpPayload struct {
Payload
Type string `json:"type"`
ChannelID string `json:"channelId"`
AdditionalDeposit string `json:"additionalDeposit"`
Authorization *OpenAuthorization `json:"authorization,omitempty"`
Hash string `json:"hash,omitempty"`
Signature string `json:"signature,omitempty"`
TopUpSalt string `json:"topUpSalt,omitempty"`
}
type VoucherPayload struct {
Payload
ChannelID string `json:"channelId"`
CumulativeAmount string `json:"cumulativeAmount"`
Signature string `json:"signature"`
}
type ClosePayload struct {
Payload
ChannelID string `json:"channelId"`
CumulativeAmount string `json:"cumulativeAmount"`
Signature string `json:"signature"`
}
Each payload has a Validate() error method that checks required fields.
Signer interface#
// Signer signs arbitrary message hashes and EIP-712 typed data.
type Signer interface {
Sign(hash []byte) ([]byte, error)
SignTypedData(typedData apitypes.TypedData) ([]byte, error)
Address() common.Address
}
PrivateKeySigner#
type PrivateKeySigner struct { /* private */ }
func NewPrivateKeySigner(key *ecdsa.PrivateKey) *PrivateKeySigner
func NewPrivateKeySignerFromHex(hexKey string) (*PrivateKeySigner, error)
Outputs a 65-byte r||s||v signature where v is 27 or 28.
NonceProvider interface#
// NonceProvider allocates a nonce for settle/close authorizations.
type NonceProvider interface {
Allocate(payee common.Address, channelID [32]byte) (*big.Int, error)
}
// UuidNonceProvider — default impl: UUID v4 → U256 (128-bit random, stateless, multi-instance/restart safe).
type UuidNonceProvider struct{}
func NewUuidNonceProvider() *UuidNonceProvider
The on-chain used-nonce set is keyed by (payee, channelId, nonce); reuse reverts with NonceAlreadyUsed. The SDK only allocates nonces unlikely to have been used before; it does not track the used set.
Charge — EVMChargeMethod#
Implements protocol.ChargeVerifier, forwarding the credential to SA-API.
type EVMChargeMethod struct { /* private */ }
func NewEVMChargeMethod() *EVMChargeMethod
// Builder methods (chainable)
func (m *EVMChargeMethod) WithChainID(chainID uint64) *EVMChargeMethod
func (m *EVMChargeMethod) WithRecipient(recipient string) *EVMChargeMethod
func (m *EVMChargeMethod) WithFeePayer(feePayer bool) *EVMChargeMethod
func (m *EVMChargeMethod) WithSAClient(c saclient.SAClient) *EVMChargeMethod
// ChargeVerifier interface implementation
func (m *EVMChargeMethod) Method() string
func (m *EVMChargeMethod) PrepareRequest(request protocol.ChargeRequest, _ *protocol.PaymentCredential) protocol.ChargeRequest
func (m *EVMChargeMethod) Verify(ctx context.Context, cred *protocol.PaymentCredential, request *protocol.ChargeRequest) (*protocol.Receipt, error)
payload.type routing:
"transaction"—Settle(SA-API broadcaststransferWithAuthorizationon-chain)"hash"—VerifyHash(client already broadcast; SA-API verifies the tx hash)"proof"— verify proof source, then callSettle
Session — EVMSessionMethod#
Implements protocol.SessionVerifier. Maintains local channel state, supports voucher local verification + cumulative deduction, and merchant-initiated settle/close.
Configuration & construction#
type EVMSessionMethodConfig struct {
// Required
Recipient string // Payee wallet address
SAClient saclient.SAClient // SA API client
// Optional (zero value = use default)
ChainID uint64 // default: 196 (X Layer)
EscrowContract string // default: DefaultEscrowContract
Signer Signer // Payee signer (required for settle/close); nil = settle/close disabled
Store store.Store[store.ChannelState] // default: MemoryStore
PerRequestCost *big.Int // amount deducted per request
MinVoucherDelta *big.Int // minimum increment between consecutive vouchers
NonceProvider NonceProvider // default: UuidNonceProvider
Deadline *big.Int // default: U256 MAX (never expires)
DomainName string // default: "EVM Payment Channel"
DomainVersion string // default: "1"
FeePayer bool // whether the payee covers gas
}
func NewEVMSessionMethod(cfg EVMSessionMethodConfig) (*EVMSessionMethod, error)
SessionVerifier interface implementation#
func (m *EVMSessionMethod) Method() string
func (m *EVMSessionMethod) VerifySession(ctx context.Context, cred *protocol.PaymentCredential, request *protocol.SessionRequest) (*protocol.Receipt, error)
func (m *EVMSessionMethod) ChallengeMethodDetails() json.RawMessage
func (m *EVMSessionMethod) Respond(cred *protocol.PaymentCredential, receipt *protocol.Receipt) any
Business methods#
// Get the underlying channel store.
func (m *EVMSessionMethod) ChannelStore() store.Store[store.ChannelState]
// Deduct from the channel's available balance.
func (m *EVMSessionMethod) DeductFromSession(ctx context.Context, channelID string, amount *big.Int) (*store.ChannelState, error)
// Mid-stream settle: read store's highest voucher -> sign SettleAuthorization -> call SA API.
func (m *EVMSessionMethod) SettleWithAuthorization(ctx context.Context, channelID string) (*saclient.SessionReceipt, error)
// Close channel: sign CloseAuthorization -> call SA API -> remove from store on success.
func (m *EVMSessionMethod) CloseWithAuthorization(ctx context.Context, channelID string) (*saclient.SessionReceipt, error)
Session action routing (inside VerifySession)#
payload.action | Behavior |
|---|---|
"open" | Validate payload -> call SA session/open -> write local store |
"topUp" | Validate -> call SA session/topUp -> add to local deposit |
"voucher" | Local verify + bump highest -> deduct perRequestCost |
"close" | Verify voucher sig -> sign CloseAuthorization -> call SA /session/close |
server package#
EVMConfig#
type EVMConfig struct {
ChainID uint64 // EVM chain ID (e.g. 196 = X Layer)
Recipient string // Recipient address (hex, with or without 0x prefix)
SecretKey string // HMAC key used to sign and verify the challenge ID
Realm string // WWW-Authenticate protection realm; default "mpp"
}
Mpp#
High-level payment coordinator that wires together EVMConfig, charge verifier, and session verifier.
type Mpp struct { /* private */ }
func NewMpp(cfg EVMConfig, charge protocol.ChargeVerifier, session protocol.SessionVerifier) *Mpp
Either verifier may be nil (when only one intent is needed).
Charge methods#
// Charge generates a WWW-Authenticate challenge header from a ChargeRouteConfig.
// Amount is human-readable decimal; Decimals is the token's decimal places.
func (m *Mpp) Charge(ctx context.Context, cfg ChargeRouteConfig) (string, error)
// ChargeWithOptions — low-level API; pass ChargeRequest + ChargeOptions directly.
func (m *Mpp) ChargeWithOptions(ctx context.Context, req protocol.ChargeRequest, opts ChargeOptions) (string, error)
// VerifyCredential validates a charge credential and returns a Receipt on success.
func (m *Mpp) VerifyCredential(ctx context.Context, challengeHeader string, authHeader string) (*protocol.Receipt, error)
Session methods#
// SessionChallenge generates a session challenge header from a SessionRouteConfig.
func (m *Mpp) SessionChallenge(ctx context.Context, cfg SessionRouteConfig) (string, error)
// SessionChallengeWithDetails — low-level API; pass SessionRequest + SessionChallengeOptions directly.
func (m *Mpp) SessionChallengeWithDetails(ctx context.Context, req protocol.SessionRequest, opts SessionChallengeOptions) (string, error)
// VerifySession validates a session credential and returns a SessionVerifyResult on success.
func (m *Mpp) VerifySession(ctx context.Context, challengeHeader string, authHeader string) (*protocol.SessionVerifyResult, error)
ChargeRouteConfig#
type ChargeRouteConfig struct {
Amount string // Human-readable decimal (e.g. "0.01")
Currency string // ERC-20 token contract address
Decimals uint32 // Token decimals (e.g. USDC=6)
Description string // Optional: human-readable description
ExternalID string // Optional: caller identifier
Splits []evm.Split // Splits, max 10 entries
}
SessionRouteConfig#
type SessionRouteConfig struct {
Amount string // Human-readable decimal (e.g. "0.001")
Currency string // ERC-20 token contract address
Decimals uint32 // Token decimals
Description string // Optional: human-readable description
ExternalID string // Optional: caller identifier
UnitType string // Billing unit (e.g. "request", "second", "byte")
SuggestedDeposit string // Suggested initial deposit (base units)
}
ChargeOptions / SessionChallengeOptions#
type ChargeOptions struct {
EVMConfig EVMConfig
Description string
Realm string
}
type SessionChallengeOptions struct {
EVMConfig EVMConfig
Description string
Realm string
}
ReceiptContextKey#
const ReceiptContextKey = "mpp_payment_receipt"
All framework adapters (gin/echo/nethttp) store the *protocol.Receipt under this context key after successful verification.
protocol package#
Constants#
const IntentCharge = "charge"
const IntentSession = "session"
const WWWAuthenticateHeader = "WWW-Authenticate"
const AuthorizationHeader = "Authorization"
const PaymentReceiptHeader = "Payment-Receipt"
const PaymentScheme = "Payment"
PayloadType#
type PayloadType string
const (
PayloadTypeTransaction PayloadType = "transaction"
PayloadTypeHash PayloadType = "hash"
PayloadTypeProof PayloadType = "proof"
)
PaymentChallenge#
The parsed WWW-Authenticate: Payment challenge.
type PaymentChallenge struct {
ID string `json:"id"`
Realm string `json:"realm"`
Method string `json:"method"`
Intent string `json:"intent"`
Request string `json:"request"` // base64url-encoded JSON
Expires uint64 `json:"expires,omitempty"`
Description string `json:"description,omitempty"`
Digest string `json:"digest,omitempty"`
Opaque string `json:"opaque,omitempty"`
SecretKey string `json:"-"` // not serialized
}
func NewPaymentChallenge(realm, method, intent, request string) *PaymentChallenge
func PaymentChallengeFromHeader(header string) (*PaymentChallenge, error)
func (c *PaymentChallenge) WithSecretKey(key string) *PaymentChallenge
func (c *PaymentChallenge) WithExpires(expires uint64) *PaymentChallenge
func (c *PaymentChallenge) WithDescription(desc string) *PaymentChallenge
func (c *PaymentChallenge) ToHeader() (string, error)
func (c *PaymentChallenge) Verify(cred *PaymentCredential) error
func (c *PaymentChallenge) IsExpired() bool
func (c *PaymentChallenge) ValidateForCharge() error
func (c *PaymentChallenge) ValidateForSession() error
ChallengeEcho#
The echo copy of the Challenge, embedded in the Authorization header.
type ChallengeEcho struct {
ID string `json:"id"`
Realm string `json:"realm"`
Method string `json:"method"`
Intent string `json:"intent"`
Request string `json:"request"`
Expires string `json:"expires,omitempty"`
Description string `json:"description,omitempty"`
Digest string `json:"digest,omitempty"`
Opaque string `json:"opaque,omitempty"`
}
func (e *ChallengeEcho) ParseExpiresUnix() uint64
PaymentCredential#
The credential carried in Authorization: Payment <base64url-encoded JSON>.
type PaymentCredential struct {
Echo *ChallengeEcho `json:"echo"`
Source string `json:"source,omitempty"`
Payload *PaymentPayload `json:"payload"`
}
func NewPaymentCredential(echo *ChallengeEcho, payload *PaymentPayload) *PaymentCredential
func NewPaymentCredentialWithSource(echo *ChallengeEcho, source string, payload *PaymentPayload) *PaymentCredential
PaymentPayload#
type PaymentPayload struct {
Type PayloadType `json:"payload_type"`
Payload string `json:"payload"` // raw JSON string
}
func NewTransactionPayload(payload string) *PaymentPayload
func NewHashPayload(payload string) *PaymentPayload
func NewProofPayload(payload string) *PaymentPayload
Receipt#
type Receipt struct {
ID string `json:"id"`
Status ReceiptStatus `json:"status"`
Method MethodName `json:"method"`
Intent IntentName `json:"intent"`
Settlement string `json:"settlement"`
}
func NewSuccessReceipt(id string, method MethodName, intent IntentName, settlement string) *Receipt
func (r *Receipt) ToHeader() (string, error)
SessionVerifyResult#
type SessionVerifyResult struct {
Receipt *Receipt
// When ManagementResponse is non-nil, this is a management op (open/topUp/close);
// the caller should return it as the response body instead of continuing the request.
// When nil (voucher/replay), the caller should continue serving the resource.
ManagementResponse any
}
ChargeRequest / SessionRequest#
type ChargeRequest struct {
Amount string `json:"amount"`
Currency string `json:"currency"`
Decimals *uint8 `json:"decimals,omitempty"`
Recipient *string `json:"recipient,omitempty"`
Description *string `json:"description,omitempty"`
ExternalID *string `json:"externalId,omitempty"`
MethodDetails json.RawMessage `json:"methodDetails,omitempty"`
}
func (r *ChargeRequest) WithBaseUnits() (*ChargeRequest, error)
func (r *ChargeRequest) ParseAmountBigInt() (*big.Int, error)
func (r *ChargeRequest) ValidateMaxAmount(max string) error
type SessionRequest struct {
Amount string `json:"amount"`
UnitType *string `json:"unitType,omitempty"`
Currency string `json:"currency"`
Decimals *uint8 `json:"decimals,omitempty"`
Recipient *string `json:"recipient,omitempty"`
SuggestedDeposit *string `json:"suggestedDeposit,omitempty"`
Description *string `json:"description,omitempty"`
ExternalID *string `json:"externalId,omitempty"`
MethodDetails json.RawMessage `json:"methodDetails,omitempty"`
}
func (r *SessionRequest) WithBaseUnits() (*SessionRequest, error)
func (r *SessionRequest) ValidateMaxAmount(max string) error
ChargeVerifier interface#
type ChargeVerifier interface {
Method() string
PrepareRequest(request ChargeRequest, cred *PaymentCredential) ChargeRequest
Verify(ctx context.Context, cred *PaymentCredential, request *ChargeRequest) (*Receipt, error)
}
SessionVerifier interface#
type SessionVerifier interface {
Method() string
VerifySession(ctx context.Context, cred *PaymentCredential, request *SessionRequest) (*Receipt, error)
ChallengeMethodDetails() json.RawMessage
Respond(cred *PaymentCredential, receipt *Receipt) any
}
Header parsing / formatting#
func ParseWWWAuthenticate(header string) (*PaymentChallenge, error)
func FormatWWWAuthenticate(c *PaymentChallenge) (string, error)
func ParseAuthorization(header string) (*PaymentCredential, error)
func FormatAuthorization(c *PaymentCredential) (string, error)
func ParseReceipt(header string) (*Receipt, error)
func FormatReceipt(r *Receipt) (string, error)
func ComputeChallengeID(secretKey, realm, method, intent, request string, expires uint64, digest, opaque string) string
Serialization helpers#
func SerializeRequest(v interface{}) (string, error)
func DeserializeRequest(encoded string) (json.RawMessage, error)
func DeserializeRequestTyped(encoded string, v interface{}) error
func RequestFromChallenge(c *PaymentChallenge) (json.RawMessage, error)
func RequestFromChallengeTyped(c *PaymentChallenge, v interface{}) error
func Base64URLEncode(data []byte) string
func Base64URLDecode(input string) ([]byte, error)
func ParseUnits(amount string, decimals uint8) (string, error)
VerificationError#
type VerificationError struct {
Message string `json:"message"`
Code ErrorCode `json:"code,omitempty"`
Retryable bool `json:"retryable"`
}
func (e *VerificationError) Error() string
func (e *VerificationError) HTTPStatus() int // 400 | 402 | 410
func (e *VerificationError) WithRetryable() *VerificationError
ErrorCode#
type ErrorCode string
const (
ErrorCodeExpired ErrorCode = "expired"
ErrorCodeInvalidAmount ErrorCode = "invalid-amount"
ErrorCodeInvalidRecipient ErrorCode = "invalid-recipient"
ErrorCodeTransactionFailed ErrorCode = "transaction-failed"
ErrorCodeNotFound ErrorCode = "not-found"
ErrorCodeInvalidCredential ErrorCode = "invalid-credential"
ErrorCodeNetworkError ErrorCode = "network-error"
ErrorCodeChainIdMismatch ErrorCode = "chain-id-mismatch"
ErrorCodeCredentialMismatch ErrorCode = "credential-mismatch"
ErrorCodeChannelNotFound ErrorCode = "channel-not-found"
ErrorCodeChannelClosed ErrorCode = "channel-closed"
ErrorCodeInsufficientBalance ErrorCode = "insufficient-balance"
ErrorCodeInvalidPayload ErrorCode = "invalid-payload"
ErrorCodeInvalidSignature ErrorCode = "invalid-signature"
ErrorCodeAmountExceedsDeposit ErrorCode = "amount-exceeds-deposit"
ErrorCodeDeltaTooSmall ErrorCode = "delta-too-small"
)
Convenience error constructors:
func ErrCredential(msg string) *VerificationError
func ErrPayload(msg string) *VerificationError
func ErrSig(msg string) *VerificationError
func ErrAmount(msg string) *VerificationError
func ErrNetwork(msg string) *VerificationError
func ErrChannelNotFound(channelID string) *VerificationError
func ErrChannelClosed() *VerificationError
func ErrExceedsDeposit(amount, deposit string) *VerificationError
func ErrDeltaTooSmall(delta, min string) *VerificationError
func ErrInsufficientBalance(available, requested string) *VerificationError
func ErrChainMismatch(expected, got uint64) *VerificationError
saclient package#
SAClient interface#
A pluggable SA-API client interface; default implementation is OKXSAClient.
type SAClient interface {
// Charge (client-facing — forward credential)
Settle(ctx context.Context, req *ChargeSettleRequest) (*ChargeReceipt, error)
VerifyHash(ctx context.Context, req *ChargeVerifyHashRequest) (*ChargeReceipt, error)
// Session (client-facing — forward credential)
SessionOpen(ctx context.Context, req *SessionOpenRequest) (*SessionReceipt, error)
SessionTopUp(ctx context.Context, req *SessionTopUpRequest) (*SessionReceipt, error)
// Session (merchant-facing — server builds request)
SessionSettle(ctx context.Context, req *SessionSettleRequest) (*SessionReceipt, error)
SessionClose(ctx context.Context, req *SessionCloseRequest) (*SessionReceipt, error)
// Session (read-only)
SessionStatus(ctx context.Context, channelID string) (*SessionStatus, error)
}
OKXSAClient#
type OKXSAClient struct { /* private */ }
func NewOKXSAClient(baseURL, apiKey, secretKey, passphrase string, opts ...Option) *OKXSAClient
Option functions:
func WithHTTPClient(c *http.Client) Option
Endpoints invoked#
| SAClient method | OKX path |
|---|---|
Settle() | POST /api/v6/pay/mpp/charge/settle |
VerifyHash() | POST /api/v6/pay/mpp/charge/verifyHash |
SessionOpen() | POST /api/v6/pay/mpp/session/open |
SessionTopUp() | POST /api/v6/pay/mpp/session/topUp |
SessionSettle() | POST /api/v6/pay/mpp/session/settle |
SessionClose() | POST /api/v6/pay/mpp/session/close |
SessionStatus() | GET /api/v6/pay/mpp/session/status?channelId=... |
OKX responses are wrapped in {"code": 0, "data": {...}, "msg": ""} and the client unwraps automatically.
SAResponse#
type SAResponse struct {
Code int `json:"code"`
Msg string `json:"msg"`
Data json.RawMessage `json:"data"`
}
HMAC auth header constants#
const headerAPIKey = "OK-ACCESS-KEY"
const headerSign = "OK-ACCESS-SIGN"
const headerTimestamp = "OK-ACCESS-TIMESTAMP"
const headerPassphrase = "OK-ACCESS-PASSPHRASE"
Signing algorithm: Base64(HMAC-SHA256(secretKey, timestamp + METHOD + requestPath + body))
Generic request wrapper#
type CredentialRequest[P any] struct {
Challenge *protocol.ChallengeEcho `json:"challenge,omitempty"`
Payload P `json:"payload"`
Source string `json:"source,omitempty"`
}
// Type aliases
type ChargeSettleRequest = CredentialRequest[ChargeTransactionPayload]
type ChargeVerifyHashRequest = CredentialRequest[ChargeHashPayload]
type SessionOpenRequest = CredentialRequest[SessionOpenPayload]
type SessionTopUpRequest = CredentialRequest[SessionTopUpPayload]
type SessionSettleRequest = CredentialRequest[SessionSettlePayload]
type SessionCloseRequest = CredentialRequest[SessionClosePayload]
Charge payload types#
type Eip3009Authorization struct {
Type string `json:"type"` // "eip-3009"
From string `json:"from"`
To string `json:"to"`
Value string `json:"value"`
ValidAfter string `json:"validAfter"`
ValidBefore string `json:"validBefore"`
Nonce string `json:"nonce"`
Signature string `json:"signature,omitempty"`
Splits []Eip3009Authorization `json:"splits,omitempty"`
}
type ChargeTransactionPayload struct {
Type string `json:"type"` // "transaction"
Authorization Eip3009Authorization `json:"authorization"`
}
type ChargeHashPayload struct {
Type string `json:"type"` // "hash"
Hash string `json:"hash"`
}
Session payload types#
// Client-facing (forward credential)
type SessionOpenPayload struct {
Action string `json:"action"`
Type string `json:"type"`
ChannelID string `json:"channelId"`
Authorization *Eip3009Authorization `json:"authorization,omitempty"`
Signature string `json:"signature,omitempty"`
Hash string `json:"hash,omitempty"`
Salt string `json:"salt"`
AuthorizedSigner string `json:"authorizedSigner,omitempty"`
}
type SessionTopUpPayload struct {
Action string `json:"action"`
Type string `json:"type"`
ChannelID string `json:"channelId"`
Authorization *Eip3009Authorization `json:"authorization,omitempty"`
Signature string `json:"signature,omitempty"`
Hash string `json:"hash,omitempty"`
AdditionalDeposit string `json:"additionalDeposit"`
TopUpSalt string `json:"topUpSalt,omitempty"`
}
// Merchant-facing (server builds request)
type SessionSettlePayload struct {
Action string `json:"action,omitempty"`
ChannelID string `json:"channelId"`
CumulativeAmount string `json:"cumulativeAmount"`
VoucherSignature string `json:"voucherSignature"`
PayeeSignature string `json:"payeeSignature"`
Nonce string `json:"nonce"`
Deadline string `json:"deadline"`
}
type SessionClosePayload struct {
Action string `json:"action,omitempty"`
ChannelID string `json:"channelId"`
CumulativeAmount string `json:"cumulativeAmount"`
VoucherSignature string `json:"voucherSignature"`
PayeeSignature string `json:"payeeSignature"`
Nonce string `json:"nonce"`
Deadline string `json:"deadline"`
}
Response types#
type ChargeReceipt struct {
Method string `json:"method"`
Reference string `json:"reference"`
Status string `json:"status"`
Timestamp string `json:"timestamp"`
ChainID uint64 `json:"chainId"`
ChallengeID string `json:"challengeId"`
ExternalID string `json:"externalId"`
}
type SessionReceipt struct {
Method string `json:"method"`
Intent string `json:"intent"`
Status string `json:"status"`
Timestamp string `json:"timestamp"`
ChannelID string `json:"channelId"`
ChainID uint64 `json:"chainId"`
Reference string `json:"reference"`
Deposit string `json:"deposit"`
}
type SessionStatus struct {
ChannelID string `json:"channelId"`
Payer string `json:"payer"`
Payee string `json:"payee"`
Token string `json:"token"`
Deposit string `json:"deposit"`
CumulativeAmount string `json:"cumulativeAmount"`
SettledOnChain string `json:"settledOnChain"`
RemainingBalance string `json:"remainingBalance"`
SessionStatus string `json:"sessionStatus"` // OPEN, CLOSING, CLOSED
}
SA error codes#
type SAErrorCode int
const (
SACodeSuccess SAErrorCode = 0
SACodeUnsupportedChain SAErrorCode = 70001
SACodePayerBlocked SAErrorCode = 70002
SACodeInvalidCredential SAErrorCode = 70003
SACodeInvalidSignature SAErrorCode = 70004
SACodeInsufficientBalance SAErrorCode = 70005
SACodeAmountExceedsDeposit SAErrorCode = 70006
SACodeTxNotConfirmed SAErrorCode = 70007
SACodeChannelNotFound SAErrorCode = 70008
SACodeChannelClosed SAErrorCode = 70009
SACodeDeltaTooSmall SAErrorCode = 70010
SACodeGracePeriodTooShort SAErrorCode = 70011
SACodeSignerMismatch SAErrorCode = 70012
SACodeDeltaBelowMinimum SAErrorCode = 70013
SACodeChannelClosing SAErrorCode = 70014
SACodeInternalError SAErrorCode = 8000
)
store package#
Store[T] interface#
type Store[T any] interface {
Get(ctx context.Context, key string) (*T, error)
Put(ctx context.Context, key string, value *T) error
Delete(ctx context.Context, key string) error
}
ChannelState#
type ChannelState struct {
ChannelID string `json:"channelId"`
ChainID uint64 `json:"chainId"`
EscrowContract string `json:"escrowContract"`
Payer string `json:"payer"`
Payee string `json:"payee"`
Token string `json:"token"`
AuthorizedSigner string `json:"authorizedSigner"`
Deposit *big.Int `json:"deposit"`
HighestVoucherAmount *big.Int `json:"highestVoucherAmount"`
HighestVoucherSignature []byte `json:"highestVoucherSignature,omitempty"`
MinVoucherDelta *big.Int `json:"minVoucherDelta,omitempty"`
Spent *big.Int `json:"spent"`
Units uint64 `json:"units"`
Finalized bool `json:"finalized"`
CloseRequestedAt uint64 `json:"closeRequestedAt"`
CreatedAt string `json:"createdAt"`
}
Invariants:
HighestVoucherAmountis monotonically non-decreasingSpentis monotonically non-decreasingavailable = HighestVoucherAmount - Spent
DeductFromChannel#
// Atomic deduct: available = HighestVoucherAmount - Spent; returns error if insufficient.
// The caller must hold the necessary lock to ensure atomicity.
func DeductFromChannel(ctx context.Context, s Store[ChannelState], channelID string, amount *big.Int) (*ChannelState, error)
MemoryStore#
type MemoryStore[T any] struct { /* private */ }
func NewMemoryStore[T any]() *MemoryStore[T]
In-process HashMap, suitable for most single-process deployments. Values are deep-copied via JSON round-trip to prevent caller mutation. Two caveats:
- Lost on restart: process restart / crash loses all channel state. If the business can't accept that, implement a persistent store and inject it.
- Abandoned channel accumulation: when the payer never calls close, records stick around — merchants should have a cleanup strategy.
FileStore#
type FileStore[T any] struct { /* private */ }
func NewFileStore[T any](dir string) (*FileStore[T], error)
A filesystem-backed Store; each key maps to a JSON file.
- Auto
mkdir -pon creation - Per-key mutex for concurrency safety
- Keys are auto-sanitized (only letters / digits /
-/_retained; others replaced with_) - Suitable for local dev, testing, or low-volume channel scenarios
- Data is pretty-printed JSON
HTTP middleware#
The middleware signatures are identical across the three frameworks: take a *server.Mpp + per-route config, return the framework-specific middleware type.
Gin (http/gin)#
func ChargeMiddleware(m *server.Mpp, cfg server.ChargeRouteConfig) gin.HandlerFunc
func SessionMiddleware(m *server.Mpp, cfg server.SessionRouteConfig) gin.HandlerFunc
func GetReceipt(c *gin.Context) *protocol.Receipt
Echo (http/echo)#
func ChargeMiddleware(m *server.Mpp, cfg server.ChargeRouteConfig) echo.MiddlewareFunc
func SessionMiddleware(m *server.Mpp, cfg server.SessionRouteConfig) echo.MiddlewareFunc
func GetReceipt(c echo.Context) *protocol.Receipt
net/http (http/nethttp)#
func ChargeMiddleware(m *server.Mpp, cfg server.ChargeRouteConfig) func(http.Handler) http.Handler
func SessionMiddleware(m *server.Mpp, cfg server.SessionRouteConfig) func(http.Handler) http.Handler
func GetReceipt(r *http.Request) *protocol.Receipt
Middleware behavior#
- No Authorization header: generate a challenge, return
402 Payment Required+WWW-Authenticateheader - Authorization header present:
- Reconstruct the challenge header from the Authorization header
- Call
Mpp.VerifyCredential(charge) orMpp.VerifySession(session) to verify - On success: set the
Payment-Receiptheader and store the receipt in context - Session management ops (open/topUp/close): return JSON response directly without continuing the handler
- Voucher: continue the downstream handler (deliver the resource)
- Verification failure: return the corresponding HTTP error code (400/402/410)
adapters package — Payment Router integration#
MppAdapter#
Implements paymentrouter.ProtocolAdapter, letting Payment Router support the MPP protocol.
type MppAdapter struct { /* private */ }
func NewMppAdapter(mpp *server.Mpp) *MppAdapter
func (a *MppAdapter) Name() string // "mpp"
func (a *MppAdapter) Priority() int // 10
func (a *MppAdapter) Detect(r *http.Request) bool
func (a *MppAdapter) GetChallenge(ctx context.Context, r *http.Request, cfg any) (http.Header, error)
func (a *MppAdapter) Handle(w http.ResponseWriter, r *http.Request, cfg any) error
MppRouteConfig#
Per-route Payment Router config used as the cfg parameter of GetChallenge.
type MppRouteConfig struct {
Intent string `json:"intent"` // "charge" | "session"
Amount string `json:"amount"`
Currency string `json:"currency"`
Decimals uint32 `json:"decimals"`
Description string `json:"description,omitempty"`
ExternalID string `json:"externalId,omitempty"`
Realm string `json:"realm,omitempty"`
UnitType string `json:"unitType,omitempty"`
SuggestedDeposit string `json:"suggestedDeposit,omitempty"`
}
EIP-712 signing (evm package)#
Domain#
const DefaultDomainName = "EVM Payment Channel"
const DefaultDomainVersion = "1"
The EIP-712 domain consists of (name, version, chainId, verifyingContract), where verifyingContract is the escrow contract address.
Voucher sign / verify#
EIP-712 typed struct:
struct Voucher {
bytes32 channelId;
uint128 cumulativeAmount;
}// Sign a voucher; returns a 65-byte signature.
func SignVoucher(
signer Signer,
channelID [32]byte,
cumulativeAmount *big.Int,
escrowContract common.Address,
chainID uint64,
domainName, domainVersion string,
) ([]byte, error)
// Verify voucher: EIP-712 digest + ecrecover + address comparison.
func VerifyVoucher(
escrowContract common.Address,
chainID uint64,
channelID [32]byte,
cumulativeAmount *big.Int,
sig []byte,
expectedSigner common.Address,
domainName, domainVersion string,
) bool
// Length + low-s pre-checks.
func ValidateVoucherSignature(sig []byte) error
// Compute deterministic channel ID: keccak256(abi.encode(payer, payee, token, salt, authorizedSigner, escrowContract, chainID))
func ComputeChannelID(
payer, payee, token common.Address,
salt [32]byte,
authorizedSigner, escrowContract common.Address,
chainID uint64,
) [32]byte
SettleAuthorization / CloseAuthorization signing#
EIP-712 typed structs:
struct SettleAuthorization {
bytes32 channelId;
uint128 cumulativeAmount;
uint256 nonce;
uint256 deadline;
}
struct CloseAuthorization {
bytes32 channelId;
uint128 cumulativeAmount;
uint256 nonce;
uint256 deadline;
}type SignedAuthorization struct {
ChannelID [32]byte
CumulativeAmount *big.Int
Nonce *big.Int
Deadline *big.Int
Signature []byte // 65-byte r||s||v
}
// primaryType must be either "SettleAuthorization" or "CloseAuthorization".
func SignAuthorization(
signer Signer,
primaryType string,
channelID [32]byte,
cumulativeAmount *big.Int,
nonce *big.Int,
deadline *big.Int,
escrowContract common.Address,
chainID uint64,
domainName, domainVersion string,
) (*SignedAuthorization, error)
sse package#
Server-Sent Events helpers.
type Event struct {
Name string // event type (SSE "event:" field)
Data any // string / []byte / any JSON-serializable value
ID string // event ID (SSE "id:" field)
Retry int // reconnection interval (ms, SSE "retry:" field)
}
func (e Event) Format() (string, error)
func ParseEvent(block string) (*Event, error)
// HTTP response helpers
func SetHeaders(w http.ResponseWriter)
func IsEventStream(r *http.Request) bool
func Serve(w http.ResponseWriter, r *http.Request, events <-chan Event)
Serve continuously reads events from the channel and writes them to the response (requires http.Flusher) until the channel is closed or the client disconnects.
Error types (errors package)#
MppError#
type MppErrorCode string
type MppError struct {
Code MppErrorCode `json:"code"`
Message string `json:"message"`
Reason string `json:"reason,omitempty"`
}
func (e *MppError) Error() string
func (e *MppError) ToProblemDetails(challengeID string) *PaymentErrorDetails
PaymentErrorDetails (RFC 9457)#
type PaymentErrorDetails struct {
ProblemType string `json:"type"`
Title string `json:"title"`
Status int `json:"status"`
Detail string `json:"detail"`
ChallengeID string `json:"challengeId,omitempty"`
}
func CorePaymentError(suffix string) *PaymentErrorDetails
func SessionPaymentError(suffix string) *PaymentErrorDetails
Problem-type URI prefixes:
- Core:
https://payment-auth.org/problems/ - Session:
https://payment-auth.org/problems/session/
MppErrorCode list#
| Code | Meaning |
|---|---|
AmountExceedsMax | Amount exceeds maximum |
InvalidAmount | Invalid amount format |
InvalidConfig | Invalid configuration |
Http | HTTP request failed |
ChainIdMismatch | Chain ID mismatch |
Json | JSON serialize/deserialize failed |
HexDecode | Hex decode failed |
Base64Decode | Base64 decode failed |
UnsupportedPaymentMethod | Unsupported payment method |
MissingHeader | Required header missing |
InvalidBase64Url | Invalid Base64URL |
MalformedCredential | Malformed credential |
InvalidChallenge | Invalid challenge |
VerificationFailed | Verification failed |
PaymentExpired | Payment expired |
PaymentRequired | Payment required |
InvalidPayload | Invalid payload |
BadRequest | Bad request |
InsufficientBalance | Insufficient balance |
InvalidSignature | Invalid signature |
SignerMismatch | Signer mismatch |
AmountExceedsDeposit | Amount exceeds deposit |
DeltaTooSmall | Delta too small |
ChannelNotFound | Channel not found |
ChannelClosed | Channel closed |
Io | IO error |
InvalidUtf8 | Invalid UTF-8 |
SystemTime | System time error |
Internal | Internal error |
SA-API error code mapping#
| SA code | Meaning | Mapped MppErrorCode |
|---|---|---|
| 8000 | Internal API service error | Internal |
| 70001 | Chain not in supported list | Internal |
| 70002 | Payer is blocklisted | MalformedCredential |
| 70003 | source missing, feePayer=true incompatible with hash mode, or txHash already used | MalformedCredential |
| 70004 | Signature verification failed | InvalidSignature |
| 70005 | Insufficient balance | InsufficientBalance |
| 70006 | Amount exceeds deposit | AmountExceedsDeposit |
| 70007 | Transaction not confirmed on-chain | Internal |
| 70008 | channelId not found | ChannelNotFound |
| 70009 | Channel closed | ChannelClosed |
| 70010 | Delta too small | DeltaTooSmall |
| 70011 | Escrow contract grace period configuration not met; channel open rejected | Internal |
| 70012 | Signer mismatch | SignerMismatch |
| 70013 | Voucher increment below minVoucherDelta | DeltaTooSmall |
| 70014 | Channel in CLOSING state, won't accept new Vouchers | ChannelClosed |
- Go SDK Reference (for exact, aggr_deferred)PackagesCore typesView interfacesClient API (x402Client)Server API (x402ResourceServer)Scheme interfacesMiddleware referenceHTTP client (buyer side)EVM mechanismsSVM mechanismsFacilitator hooksGo SDK Reference (for charge, session)PackagesCore types (evm package)Signer interfaceNonceProvider interfaceCharge — EVMChargeMethodSession — EVMSessionMethodserver packageprotocol packagesaclient packagestore packageHTTP middlewareadapters package — Payment Router integrationEIP-712 signing (evm package)sse packageError types (errors package)SA-API error code mapping