Go SDK Reference#

Go SDK Reference (for exact, aggr_deferred)#

Packages#

PackageDescription
github.com/okx/payments/go/x402Core: client, server, facilitator, type definitions
.../go/x402/mechanisms/evmEVM mechanisms: exact, aggr_deferred
.../go/x402/mechanisms/svmSolana mechanisms: exact
.../go/x402/http/ginGin middleware (seller side)
.../go/x402/http/echoEcho middleware (seller side)
.../go/x402/http/nethttpnet/http middleware (seller side)
.../go/x402/httpHTTP client wrapper (buyer side)

Core types#

Network#

go
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#

go
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#

go
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#

go
type VerifyResponse struct {
    IsValid        bool   `json:"isValid"`
    InvalidReason  string `json:"invalidReason,omitempty"`
    InvalidMessage string `json:"invalidMessage,omitempty"`
    Payer          string `json:"payer,omitempty"`
}

SettleResponse#

go
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#

go
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#

go
type SettlementOverrides struct {
    Amount string `json:"amount,omitempty"` // Atomic units, percentage, or dollar
}

View interfaces#

A unified view over V1/V2 payment data:

go
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#

go
import x402 "github.com/okx/payments/go/x402"

client := x402.Newx402Client(opts ...ClientOption)

ClientOption functions:

OptionDescription
WithPaymentSelector(selector)Custom payment-requirements selector
WithPolicy(policy)Add a payment-filter policy

Registration methods (chainable)#

go
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#

go
// 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)#

go
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#

go
type PaymentRequirementsSelector func(requirements []PaymentRequirementsView) PaymentRequirementsView
type PaymentPolicy func(requirements []PaymentRequirementsView) []PaymentRequirementsView

func DefaultPaymentSelector(requirements []PaymentRequirementsView) PaymentRequirementsView

Server API (x402ResourceServer)#

Constructor#

go
server := x402.Newx402ResourceServer(opts ...ResourceServerOption)

ResourceServerOption functions:

OptionDescription
WithFacilitatorClient(client)Set the facilitator client
WithSchemeServer(network, server)Register a scheme
WithCacheTTL(ttl)Set the supported-kinds cache TTL

Registration methods (chainable)#

go
func (s *x402ResourceServer) Register(network Network, server SchemeNetworkServer) *x402ResourceServer
func (s *x402ResourceServer) RegisterExtension(ext types.ResourceServerExtension) *x402ResourceServer

Initialization#

go
func (s *x402ResourceServer) Initialize(ctx context.Context) error

Payment operations#

go
// 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)#

go
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)#

go
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)#

go
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#

go
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)#

go
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#

go
type ClientExtension interface {
    Key() string
    EnrichPaymentPayload(ctx context.Context, payload types.PaymentPayload, required types.PaymentRequired) (types.PaymentPayload, error)
}

MoneyParser#

go
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)#

go
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:

OptionTypeDescription
WithFacilitatorClient(client)FacilitatorClientFacilitator used for verification / settlement
WithScheme(network, server)Network, SchemeNetworkServerRegister a scheme
WithPaywallConfig(config)*PaywallConfigBrowser paywall settings
WithSyncFacilitatorOnStart(bool)boolFetch 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.DurationRequest timeout

Config struct (builder pattern):

go
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:

go
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:

go
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)#

go
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)#

go
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:

go
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)#

go
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)#

go
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#

go
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)#

go
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)#

go
import svmserver "github.com/okx/payments/go/x402/mechanisms/svm/exact/server"

scheme := svmserver.NewExactSvmScheme()
scheme.RegisterMoneyParser(customParser)

Facilitator hooks#

go
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#

PackageImport pathDescription
servergithub.com/okx/payments/go/mpp/serverHigh-level MPP coordinator: Mpp, EVMConfig, ChargeRouteConfig, SessionRouteConfig
evmgithub.com/okx/payments/go/mpp/evmEVM payment methods: EVMChargeMethod / EVMSessionMethod, EIP-712 sign/verify, Voucher, Authorization
protocolgithub.com/okx/payments/go/mpp/protocolProtocol layer: challenge / credential / receipt parsing & formatting, ChargeVerifier / SessionVerifier interfaces, error codes
saclientgithub.com/okx/payments/go/mpp/saclientSA-API client: SAClient interface and OKXSAClient implementation, HMAC-SHA256 auth
storegithub.com/okx/payments/go/mpp/storeGeneric key-value store: Store[T] interface, MemoryStore, FileStore, ChannelState
http/gingithub.com/okx/payments/go/mpp/http/ginGin framework middleware: ChargeMiddleware / SessionMiddleware
http/echogithub.com/okx/payments/go/mpp/http/echoEcho framework middleware: ChargeMiddleware / SessionMiddleware
http/nethttpgithub.com/okx/payments/go/mpp/http/nethttpStandard net/http middleware: ChargeMiddleware / SessionMiddleware
adaptersgithub.com/okx/payments/go/mpp/adaptersPayment Router adapter: MppAdapter implements paymentrouter.ProtocolAdapter
ssegithub.com/okx/payments/go/mpp/sseServer-Sent Events helpers: Event formatting/parsing, Serve streaming push
errorsgithub.com/okx/payments/go/mpp/errorsError types: MppError, MppErrorCode, RFC 9457 PaymentErrorDetails

Core types (evm package)#

Constants#

go
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).

go
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).

go
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).

go
// 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.

go
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.

go
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#

go
// 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#

go
// 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#

go
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#

go
// 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.

go
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 broadcasts transferWithAuthorization on-chain)
  • "hash"VerifyHash (client already broadcast; SA-API verifies the tx hash)
  • "proof" — verify proof source, then call Settle

Session — EVMSessionMethod#

Implements protocol.SessionVerifier. Maintains local channel state, supports voucher local verification + cumulative deduction, and merchant-initiated settle/close.

Configuration & construction#

go
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#

go
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#

go
// 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.actionBehavior
"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#

go
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.

go
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#

go
// 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#

go
// 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#

go
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#

go
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#

go
type ChargeOptions struct {
    EVMConfig   EVMConfig
    Description string
    Realm       string
}

type SessionChallengeOptions struct {
    EVMConfig   EVMConfig
    Description string
    Realm       string
}

ReceiptContextKey#

go
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#

go
const IntentCharge  = "charge"
const IntentSession = "session"

const WWWAuthenticateHeader = "WWW-Authenticate"
const AuthorizationHeader   = "Authorization"
const PaymentReceiptHeader  = "Payment-Receipt"
const PaymentScheme         = "Payment"

PayloadType#

go
type PayloadType string

const (
    PayloadTypeTransaction PayloadType = "transaction"
    PayloadTypeHash        PayloadType = "hash"
    PayloadTypeProof       PayloadType = "proof"
)

PaymentChallenge#

The parsed WWW-Authenticate: Payment challenge.

go
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.

go
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>.

go
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#

go
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#

go
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#

go
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#

go
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#

go
type ChargeVerifier interface {
    Method() string
    PrepareRequest(request ChargeRequest, cred *PaymentCredential) ChargeRequest
    Verify(ctx context.Context, cred *PaymentCredential, request *ChargeRequest) (*Receipt, error)
}

SessionVerifier interface#

go
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#

go
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#

go
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#

go
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#

go
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:

go
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.

go
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#

go
type OKXSAClient struct { /* private */ }

func NewOKXSAClient(baseURL, apiKey, secretKey, passphrase string, opts ...Option) *OKXSAClient

Option functions:

go
func WithHTTPClient(c *http.Client) Option

Endpoints invoked#

SAClient methodOKX 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#

go
type SAResponse struct {
    Code int             `json:"code"`
    Msg  string          `json:"msg"`
    Data json.RawMessage `json:"data"`
}

HMAC auth header constants#

go
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#

go
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#

go
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#

go
// 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#

go
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#

go
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#

go
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#

go
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:

  • HighestVoucherAmount is monotonically non-decreasing
  • Spent is monotonically non-decreasing
  • available = HighestVoucherAmount - Spent

DeductFromChannel#

go
// 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#

go
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#

go
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 -p on 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)#

go
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)#

go
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)#

go
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#

  1. No Authorization header: generate a challenge, return 402 Payment Required + WWW-Authenticate header
  2. Authorization header present:
    • Reconstruct the challenge header from the Authorization header
    • Call Mpp.VerifyCredential (charge) or Mpp.VerifySession (session) to verify
    • On success: set the Payment-Receipt header 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)
  3. 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.

go
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.

go
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#

go
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;
}
go
// 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;
}
go
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.

go
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#

go
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)#

go
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#

CodeMeaning
AmountExceedsMaxAmount exceeds maximum
InvalidAmountInvalid amount format
InvalidConfigInvalid configuration
HttpHTTP request failed
ChainIdMismatchChain ID mismatch
JsonJSON serialize/deserialize failed
HexDecodeHex decode failed
Base64DecodeBase64 decode failed
UnsupportedPaymentMethodUnsupported payment method
MissingHeaderRequired header missing
InvalidBase64UrlInvalid Base64URL
MalformedCredentialMalformed credential
InvalidChallengeInvalid challenge
VerificationFailedVerification failed
PaymentExpiredPayment expired
PaymentRequiredPayment required
InvalidPayloadInvalid payload
BadRequestBad request
InsufficientBalanceInsufficient balance
InvalidSignatureInvalid signature
SignerMismatchSigner mismatch
AmountExceedsDepositAmount exceeds deposit
DeltaTooSmallDelta too small
ChannelNotFoundChannel not found
ChannelClosedChannel closed
IoIO error
InvalidUtf8Invalid UTF-8
SystemTimeSystem time error
InternalInternal error

SA-API error code mapping#

SA codeMeaningMapped MppErrorCode
8000Internal API service errorInternal
70001Chain not in supported listInternal
70002Payer is blocklistedMalformedCredential
70003source missing, feePayer=true incompatible with hash mode, or txHash already usedMalformedCredential
70004Signature verification failedInvalidSignature
70005Insufficient balanceInsufficientBalance
70006Amount exceeds depositAmountExceedsDeposit
70007Transaction not confirmed on-chainInternal
70008channelId not foundChannelNotFound
70009Channel closedChannelClosed
70010Delta too smallDeltaTooSmall
70011Escrow contract grace period configuration not met; channel open rejectedInternal
70012Signer mismatchSignerMismatch
70013Voucher increment below minVoucherDeltaDeltaTooSmall
70014Channel in CLOSING state, won't accept new VouchersChannelClosed