server

package
v0.1.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 7, 2026 License: MIT Imports: 21 Imported by: 0

Documentation

Overview

Package server is the merchant/server side of chit — the half that charges callers over ATXP. It is a clean-room Go port of the MIT-licensed @atxp/server TypeScript SDK (© Circuit and Chisel); chit is unofficial and not affiliated with or endorsed by them.

Like the client, the merchant side does no on-chain crypto: settlement is delegated to the ATXP authorization server over HTTP. The only local crypto is the HMAC that signs the opaque identity carried through an MPP retry.

The flow a merchant wires up:

  • CheckToken authenticates the caller's OAuth bearer token (RFC 7662 introspection) and yields its subject.
  • RequirePayment gates a metered operation: it attempts an on-demand charge and, failing that, returns a Challenge to emit as an MCP/JSON-RPC payment error. A nil Challenge with a nil error means the caller has paid.
  • On a push-payment retry, Verify/Settle finalize the presented credential, and RecoverOpaqueIdentity re-derives the caller identity that the bearer token can no longer carry.

Connection tokens and bearer tokens are wallet-grade secrets: chit never logs them, and the default Logger discards everything.

Index

Constants

This section is empty.

Variables

View Source
var CAIP2Networks = map[string]string{
	"base":          "eip155:8453",
	"base_sepolia":  "eip155:84532",
	"solana":        "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp",
	"solana_devnet": "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1",
}

CAIP2Networks maps a human-readable network name to its CAIP-2 identifier, for the chains the CDP facilitator supports.

Ported verbatim from @atxp/common constants.ts (CAIP2_NETWORKS). Source: https://docs.cdp.coinbase.com/x402/network-support

View Source
var USDCAddresses = map[string]string{
	"base":          "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
	"base_sepolia":  "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
	"eip155:8453":   "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
	"eip155:84532":  "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
	"solana":        "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
	"solana_devnet": "4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU",
	"solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
	"solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1": "4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU",
}

USDCAddresses maps a network identifier (human-readable name and CAIP-2 form) to the USDC contract / mint address on that network.

Ported verbatim from @atxp/common constants.ts (USDC_ADDRESSES). Source: https://developers.circle.com/stablecoins/usdc-on-main-networks

Functions

func ExtractX402PayerAddress

func ExtractX402PayerAddress(credential string) (string, error)

ExtractX402PayerAddress returns the actual signing address from an x402 credential's authorization.from field. Unlike a PaymentRequest's User field (sourceAccountId), this address is cryptographically tied to the EIP-3009 signature and cannot be forged, so it is the right thing to rate-limit, cap spend on, or blocklist against on the merchant's own side.

See docs/PROTOCOL.md's fraud-block bypass note: ATXP's account-standing checks (fraud_blocked, etc.) are not enforced on sourceAccountId for the x402 settlement path, so merchants that need their own abuse protection should gate on this address, not on anything from PaymentRequest.User.

Returns an error if the credential is not a parseable x402 credential or carries no authorization.from field (e.g. it is not the "exact" scheme).

Types

type ATXPPaymentServer

type ATXPPaymentServer struct {
	// contains filtered or unexported fields
}

ATXPPaymentServer talks to one authorization server. Ported from paymentServer.ts ATXPPaymentServer.

func (*ATXPPaymentServer) Charge

func (s *ATXPPaymentServer) Charge(ctx context.Context, req ChargeRequest) (bool, error)

func (*ATXPPaymentServer) CreatePaymentRequest

func (s *ATXPPaymentServer) CreatePaymentRequest(ctx context.Context, req ChargeRequest) (string, error)

func (*ATXPPaymentServer) GetBalance

func (s *ATXPPaymentServer) GetBalance(ctx context.Context, req BalanceRequest) (Amount, error)

type Amount

type Amount struct {
	// contains filtered or unexported fields
}

Amount is a non-negative USDC money value held as an exact integer count of micro-units (6 decimals). Money is never represented as a float: every amount that crosses the wire — the chargeAmount string, the x402 integer amount, the MPP amounts — is derived from this exact representation.

The TS SDK uses BigNumber for the same role; Amount is the Go equivalent constrained to the one currency chit settles (USDC/pathUSD, both 6-decimal).

func AmountFromMicroString

func AmountFromMicroString(s string) (Amount, error)

AmountFromMicroString parses an atomic micro-unit integer string (e.g. the x402/Solana-MPP on-chain amount, "10000") into an Amount. Unlike ParseAmount (which takes a human-readable decimal), this expects no decimal point.

func AmountFromMicros

func AmountFromMicros(micros int64) Amount

AmountFromMicros builds an Amount directly from a micro-unit count. A negative count is clamped to zero rather than producing an invalid (negative) Amount.

func Max

func Max(a, b Amount) Amount

Max returns whichever of a, b is larger.

func Min

func Min(a, b Amount) Amount

Min returns whichever of a, b is smaller.

func ParseAmount

func ParseAmount(s string) (Amount, error)

ParseAmount parses a decimal USDC string (e.g. "0.01", "1", "0.000001") into an exact Amount. It rejects negatives, blanks, non-numeric input, and any value with more than 6 decimal places (which cannot be represented on-chain).

Rejecting rather than rounding is deliberate: silently truncating a sub-micro fraction would let a caller request a price the merchant cannot actually charge.

func (Amount) Add

func (a Amount) Add(b Amount) Amount

Add returns a + b.

func (Amount) GreaterThan

func (a Amount) GreaterThan(b Amount) bool

GreaterThan reports whether a > b.

func (Amount) IsPositive

func (a Amount) IsPositive() bool

IsPositive reports whether the amount is strictly greater than zero.

func (Amount) IsZero

func (a Amount) IsZero() bool

IsZero reports whether the amount is exactly zero.

func (Amount) MicroString

func (a Amount) MicroString() string

MicroString renders the amount as its integer micro-unit count, e.g. 0.01 USDC -> "10000". This is the x402 / Solana-MPP on-chain amount (BigNumber.times(1e6).toFixed(0) in the TS SDK).

func (Amount) String

func (a Amount) String() string

String renders the amount as a minimal decimal USDC string, e.g. "0.01", "0.1", "1" — matching BigNumber.toString() so the chargeAmount embedded in a challenge is byte-identical to what the reference SDK would emit.

type AtxpMcpChallengeData

type AtxpMcpChallengeData struct {
	PaymentRequestID  string `json:"paymentRequestId"`
	PaymentRequestURL string `json:"paymentRequestUrl"`
	ChargeAmount      string `json:"chargeAmount,omitempty"`
}

AtxpMcpChallengeData is the ATXP-native portion of a challenge. Ported from protocol.ts AtxpMcpChallengeData.

type BalanceRequest

type BalanceRequest struct {
	SourceAccountID      string `json:"sourceAccountId"`
	DestinationAccountID string `json:"destinationAccountId"`
	SourceAccountToken   string `json:"sourceAccountToken,omitempty"`
}

BalanceRequest is the body for POST /balance. Ported from @atxp/server.

type Challenge

type Challenge struct {
	Code    int
	Message string
	Data    map[string]any

	// Structured views of the same data, for callers that want typed access
	// (e.g. to emit an HTTP 402 instead of an MCP error).
	AtxpMcp AtxpMcpChallengeData
	X402    X402PaymentRequirements
	MPP     []MppChallengeData
}

Challenge is a built omni-challenge ready to be returned to a caller as a JSON-RPC / MCP error. Code and Message are the MCP error fields; Data is the error.data object carrying all three protocols' challenge data.

type ChargeRequest

type ChargeRequest struct {
	Options              []chargeOptionWire `json:"options"`
	SourceAccountID      string             `json:"sourceAccountId"`
	DestinationAccountID string             `json:"destinationAccountId"`
	PayeeName            string             `json:"payeeName"`
	// SourceAccountToken is the caller's OAuth/connection token for on-demand
	// (pull-mode) charging. Wallet-grade; never logged.
	SourceAccountToken string `json:"sourceAccountToken,omitempty"`
	// PaymentRequestID ties /settle and the follow-up /charge to one payment.
	PaymentRequestID string `json:"paymentRequestId,omitempty"`
}

ChargeRequest is the body for POST /charge and POST /payment-request. Ported from the @atxp/server Charge type. When talking to the authorization server the merchant does not send resource/resourceName — the AS already knows them and must not trust the merchant to self-report them.

type Config

type Config struct {
	// Destination is where payments are received. Required.
	Destination Destination

	// ConnectionToken is the merchant's own ATXP connection token. It is sent
	// (over HTTPS only) as the X-ATXP-TOKEN header during dynamic client
	// registration so the registered client is bound to the merchant's ATXP
	// account. Wallet-grade secret — never logged or echoed. Optional only if
	// the authorization server permits unauthenticated registration.
	ConnectionToken string

	// AuthServer is the ATXP authorization server base URL. Defaults to
	// https://auth.atxp.ai.
	AuthServer string

	// PayeeName labels the merchant in challenges and metadata, and doubles as
	// the dynamic-client-registration client_name sent to the authorization
	// server. Defaults to "An ATXP Server"; set this to something distinctive
	// in production. The auth server treats client_name as claimed once
	// registered; with the default Store (in-memory, lost on restart) a second
	// process registering under the same unset-default name, whether a
	// restart of this merchant or an unrelated one reusing the same
	// ConnectionToken, gets a permanent 409 for that name, with no way to
	// recover the original client_secret. See Store's doc comment.
	PayeeName string

	// Currency is the settlement currency. Defaults to "USDC".
	Currency string

	// MinimumPayment is a price floor applied to every charge. Must be <= $1.00.
	// The zero Amount means no floor.
	MinimumPayment Amount

	// AppName is an observability label (1–64 chars of [a-zA-Z0-9._-]) sent on
	// settle/verify calls. Untrusted by auth; do not use for billing.
	AppName string

	// ExpectedAudience, when set, is required to appear in an introspected
	// token's `aud`. Leaving it empty disables the audience check (the TS
	// default), but setting it to this resource's URL is strongly recommended.
	ExpectedAudience string

	// Store persists DCR client credentials (keyed by AS issuer). Defaults to a
	// process-local in-memory store, which loses its client_secret on every
	// restart. The authorization server does not let a new registration
	// reclaim an already-claimed client_name, so a restarted (or
	// ConnectionToken-sharing) merchant that doesn't set a distinctive
	// PayeeName will permanently fail dynamic client registration with a 409.
	// Production deployments should supply a persistent Store implementation.
	Store atxp.Store

	// HTTPClient is used for all authorization-server calls. Optional.
	HTTPClient *http.Client

	// Logger receives operational messages. Never receives secrets. Defaults to
	// a no-op logger.
	Logger Logger

	// OpaqueKey is the HMAC key for opaque-identity signing. When nil, the key
	// is loaded from ATXP_OPAQUE_KEY (base64) or randomly generated per process.
	OpaqueKey []byte

	// AllowHTTP permits plaintext HTTP to the authorization server. For local
	// development and tests ONLY — it disables the HTTPS requirement that keeps
	// the connection token from traversing the network in the clear.
	AllowHTTP bool
}

Config configures a Merchant. Destination is the only required field.

type CredentialDetection

type CredentialDetection struct {
	Protocol         Protocol
	Credential       string
	PaymentRequestID string
}

CredentialDetection is the result of sniffing a retry request's headers for a payment credential. Ported from protocol.ts CredentialDetection.

func DetectProtocol

func DetectProtocol(h interface{ Get(string) string }) *CredentialDetection

DetectProtocol inspects inbound request headers on a retry and reports which payment rail (if any) the caller used. Ported from protocol.ts detectProtocol.

Header precedence is preserved exactly: X-ATXP-PAYMENT (atxp) > PAYMENT-SIGNATURE/X-PAYMENT (x402) > Authorization: Payment (mpp).

h.Get is case-insensitive (net/http canonicalizes header keys), matching the lowercased keys the TS version reads.

type Destination

type Destination interface {
	AccountID(ctx context.Context) (string, error)
	Sources(ctx context.Context, include []string) ([]Source, error)
}

Destination is where the merchant receives payment. It maps to the @atxp/common PaymentDestination interface (only the two methods the merchant side needs).

AccountID returns a fully-qualified "network:address" id (e.g. "atxp:<uuid>" for a hosted account, or "base:0x..." for a chain account). Sources returns the per-chain receive addresses used to build x402 and MPP challenge options; returning an empty slice is fine — the challenge then advertises only the ATXP-native rail, which is always included.

type InvalidAccountIDError

type InvalidAccountIDError struct{ AccountID string }

InvalidAccountIDError is returned when an account id is not "network:address".

func (*InvalidAccountIDError) Error

func (e *InvalidAccountIDError) Error() string

type Logger

type Logger interface {
	Debugf(format string, args ...any)
	Infof(format string, args ...any)
	Warnf(format string, args ...any)
	Errorf(format string, args ...any)
}

Logger is the minimal logging surface the merchant side uses. It mirrors the TS SDK's Logger (debug/info/warn/error).

Security note: chit never passes a connection string, bearer token, opaque HMAC key, or payment credential to any Logger method. Implementations may send log output anywhere; the contract is that nothing handed to a Logger is a secret, so a custom Logger cannot accidentally exfiltrate one.

type Merchant

type Merchant struct {
	// contains filtered or unexported fields
}

Merchant charges callers over ATXP. Construct one with New and reuse it; it is safe for concurrent use.

func New

func New(cfg Config) (*Merchant, error)

New builds a Merchant from a Config.

func (*Merchant) CheckRequest

func (m *Merchant) CheckRequest(r *http.Request) TokenCheck

CheckRequest is CheckToken applied to an *http.Request, deriving the resource URL from the request.

func (*Merchant) CheckToken

func (m *Merchant) CheckToken(ctx context.Context, resourceURL *url.URL, authHeader string) TokenCheck

CheckToken authenticates a caller's Authorization header against the authorization server. resourceURL is this resource's URL (used to build the WWW-Authenticate metadata pointer and, when ExpectedAudience is set, to bound the audience). A passing TokenCheck carries the caller's identity in Data.Sub.

func (*Merchant) CloseSession

func (m *Merchant) CloseSession(ctx context.Context, session *PaymentSession) error

CloseSession settles a payment session at most once, for the amount actually charged (Spent), and is safe to call more than once (e.g. from a deferred call on every return path) — later calls are no-ops once settled.

Two protocol-shape-dependent rules apply, mirroring settlePaymentSession:

  • An MPP session (channel) credential settles even at Spent()==0, since that close refunds the deposit locked at authorize. One-shot ATXP/x402 credentials have nothing to settle at zero spend and no-op instead.
  • On settle failure, a channel credential is left unsettled so a later call can re-drive the on-chain close (idempotent); one-shot credentials are marked settled regardless (nothing to re-drive; reconcile from the returned error).

func (*Merchant) OpenPaymentSession

func (m *Merchant) OpenPaymentSession(detected CredentialDetection, sctx SettlementContext) *PaymentSession

OpenPaymentSession opens a payment session for a detected retry credential. Deriving the cap is local (no network call) — it parses the credential (and, for x402, resolves the matching accept from sctx.PaymentRequirements) to find the authorized amount. Ported from atxpContext.ts openPaymentSession / paymentSession.ts buildPaymentSession.

func (*Merchant) ProtectedResourceMetadata

func (m *Merchant) ProtectedResourceMetadata(resource string) ProtectedResourceMetadata

ProtectedResourceMetadata builds the RFC 9728 document for a resource URL.

func (*Merchant) RecoverOpaqueIdentity

func (m *Merchant) RecoverOpaqueIdentity(opaque map[string]any, challengeID string) (string, bool)

RecoverOpaqueIdentity verifies the opaque identity echoed back in an MPP retry and returns the caller's subject. The caller extracts the opaque object from the presented credential and supplies the challenge id it was bound to. Returns ("", false) on any mismatch — fail closed.

func (*Merchant) RequirePayment

func (m *Merchant) RequirePayment(ctx context.Context, pr PaymentRequest) (*Challenge, error)

RequirePayment gates a metered operation. It returns:

  • (nil, nil) the charge settled; the caller may proceed.
  • (*Challenge, nil) payment is required; emit the Challenge as an MCP/JSON-RPC error (or HTTP 402) and do not proceed.
  • (nil, error) an infrastructure error; do not proceed.

func (*Merchant) Settlement

func (m *Merchant) Settlement(ctx context.Context) (*ProtocolSettlement, error)

Settlement returns a ProtocolSettlement bound to the merchant's destination account, for finalizing push-payment credentials on a retry.

type MppChallengeData

type MppChallengeData struct {
	ID        string         `json:"id"`
	Method    string         `json:"method"`
	Intent    string         `json:"intent"`
	Amount    string         `json:"amount"`
	Currency  string         `json:"currency"`
	Network   string         `json:"network"`
	Recipient string         `json:"recipient"`
	Expires   string         `json:"expires,omitempty"`
	Resource  *resourceRef   `json:"resource,omitempty"`
	Request   map[string]any `json:"request,omitempty"`
	Opaque    map[string]any `json:"opaque,omitempty"`
}

MppChallengeData is one MPP challenge (one supported chain). JSON tags match the wire shape in protocol.ts so the emitted `data.mpp[]` is identical to the reference SDK.

The `amount` encoding is chain-dependent (see omnichallenge.go):

  • method "solana": micro-units integer string, e.g. "10000"
  • method "tempo": human-readable decimal string, e.g. "0.01"

type MppSessionSupport

type MppSessionSupport struct {
	EscrowContract   string `json:"escrowContract"`
	AuthorizedSigner string `json:"authorizedSigner"`
	Operator         string `json:"operator"`
	ChainID          int    `json:"chainId"`
}

MppSessionSupport is the Tempo MPP session (TIP-1034) params advertised by the authorization server's GET /mpp/supported: the channel's authorized signer + operator (auth's settler key), the escrow precompile, and the chain id. Ported from omniChallenge.ts MppSessionSupport.

type OAuthChallengeResponse

type OAuthChallengeResponse struct {
	Status  int
	Headers map[string]string
	Body    string
}

OAuthChallengeResponse is the HTTP response a resource server returns when a token check fails. Ported from core/oauth.ts createOAuthChallengeResponseCore.

func ChallengeResponse

func ChallengeResponse(tc TokenCheck) *OAuthChallengeResponse

ChallengeResponse maps a failed TokenCheck to the RFC 6750 HTTP challenge a resource server should return. Returns nil when the check passed.

type PaymentRequest

type PaymentRequest struct {
	// Price is what to charge for this call. The amount actually charged is
	// max(Price, the merchant's MinimumPayment).
	Price Amount

	// User is the caller's account id — the `sub` from a passing CheckToken.
	// Required; without an authenticated caller there is nobody to charge.
	User string

	// SourceAccountToken is the caller's connection/OAuth token for on-demand
	// (pull-mode) charging. Wallet-grade secret. Optional: without it, the
	// caller is sent a challenge to pay out-of-band.
	SourceAccountToken string

	// PaymentRequestID ties this charge to an existing payment lifecycle
	// (idempotency). Optional.
	PaymentRequestID string

	// Session, when set, charges locally against the request-scoped payment
	// session (opened from a detected retry credential via
	// Merchant.OpenPaymentSession) instead of issuing a network /charge call.
	// Multiple RequirePayment calls sharing a Session settle once, for the
	// sum actually charged, when the caller closes it via
	// Merchant.CloseSession. Optional — nil preserves the on-demand /charge
	// behavior.
	Session *PaymentSession

	// Resource is the resource URL recorded in the challenge for activity labels.
	// Optional.
	Resource string

	// ExistingPaymentID, when set, is consulted before creating a new payment
	// request so a re-challenge reuses an in-flight payment. Optional.
	ExistingPaymentID func(ctx context.Context) (string, error)
}

PaymentRequest describes one metered call to gate.

type PaymentServer

type PaymentServer interface {
	// Charge attempts an on-demand pull. Returns true when the charge settled
	// (HTTP 200 or 202), false when payment is still required (HTTP 402), and a
	// non-nil error for every other outcome — including network failures — so a
	// caller that treats (false, nil) as "unpaid" never mistakes an
	// infrastructure error for a definite "unpaid".
	Charge(ctx context.Context, req ChargeRequest) (bool, error)
	// CreatePaymentRequest registers a payment request and returns its id.
	CreatePaymentRequest(ctx context.Context, req ChargeRequest) (string, error)
	// GetBalance returns the caller's available USDC balance.
	GetBalance(ctx context.Context, req BalanceRequest) (Amount, error)
}

PaymentServer is the merchant's HTTP client to the ATXP authorization server's money endpoints. Ported from the @atxp/server PaymentServer interface.

All settlement is delegated to the AS over HTTP; chit performs no on-chain crypto here. Charge reports whether the pull-mode charge settled.

type PaymentSession

type PaymentSession struct {
	// contains filtered or unexported fields
}

PaymentSession accumulates local charges against one detected payment credential across multiple RequirePayment calls, so N calls within one request settle once (for the sum actually charged) instead of issuing N network round trips. Ported from paymentSession.ts PaymentSessionState.

Unlike the TS reference (whose Express middleware opens/closes a session implicitly per request via AsyncLocalStorage), chit has no framework layer: the caller opens a session from a detected retry credential (Merchant.OpenPaymentSession), threads it into each PaymentRequest.Session, and closes it exactly once — typically via defer — when its request scope ends (Merchant.CloseSession).

func (*PaymentSession) Cap

func (s *PaymentSession) Cap() (cap Amount, unlimited bool)

Cap returns the session's authorized ceiling and whether it is unlimited (the credential's amount could not be derived).

func (*PaymentSession) Charge

func (s *PaymentSession) Charge(cost Amount) bool

Charge records a charge of cost against the session. It returns false (and leaves the accumulated total unchanged) if this charge would exceed the credential's authorized cap — the caller should then fall through to building a new payment challenge, exactly as an on-demand charge decline would. Ported from PaymentSessionState.charge.

func (*PaymentSession) SettleResult added in v0.1.2

func (s *PaymentSession) SettleResult() (SettleResult, bool)

SettleResult returns the result of the settle call CloseSession made, and whether one has happened yet (false before Close, or if Close no-op'd on a zero-spend one-shot credential). Check this after CloseSession succeeds before trusting the session as paid for its full intended amount: for the x402 "exact" scheme in particular, chit cannot verify that a credential's self-reported accepted.amount matches what the payer actually signed in authorization.value. The facilitator only ever settles the real signed value; SettledAmount is the actual amount that moved on-chain. Compare it against your own expected price before crediting anything.

func (*PaymentSession) Spent

func (s *PaymentSession) Spent() Amount

Spent returns the sum of charges recorded so far.

type ProtectedResourceMetadata

type ProtectedResourceMetadata struct {
	Resource               string   `json:"resource"`
	ResourceName           string   `json:"resource_name"`
	AuthorizationServers   []string `json:"authorization_servers"`
	BearerMethodsSupported []string `json:"bearer_methods_supported"`
	ScopesSupported        []string `json:"scopes_supported"`
}

ProtectedResourceMetadata is the RFC 9728 document a resource server serves at /.well-known/oauth-protected-resource. Ported from protectedResourceMetadata.ts.

type Protocol

type Protocol string

Protocol identifies one of the three inbound payment rails. Ported from @atxp/common PaymentProtocolEnum.

const (
	ProtocolATXP Protocol = "atxp"
	ProtocolX402 Protocol = "x402"
	ProtocolMPP  Protocol = "mpp"
)

type ProtocolSettlement

type ProtocolSettlement struct {
	// contains filtered or unexported fields
}

ProtocolSettlement calls the AS verify/settle endpoints. Ported from protocol.ts ProtocolSettlement.

func (*ProtocolSettlement) Settle

func (p *ProtocolSettlement) Settle(ctx context.Context, protocol Protocol, credential string, sctx *SettlementContext, actualAmount *Amount) (SettleResult, error)

Settle finalizes a payment at request end. A non-2xx response is an error (the payment did not settle). actualAmount, when non-nil, is the metered "up-to" amount actually spent (e.g. the sum of a PaymentSession's charges): for x402 (upto scheme only) and MPP session credentials, this settles that actual amount (≤ the authorized cap) instead of the cap. nil settles the cap, as before. Ported from ProtocolSettlement.settle.

func (*ProtocolSettlement) Verify

func (p *ProtocolSettlement) Verify(ctx context.Context, protocol Protocol, credential string, sctx *SettlementContext) (VerifyResult, error)

Verify checks a payment credential at request start. Returns {valid:false} on any non-2xx response — fail closed. Ported from ProtocolSettlement.verify.

type SettleResult

type SettleResult struct {
	TxHash         *string `json:"txHash"`
	SettledAmount  string  `json:"settledAmount"`
	AlreadySettled bool    `json:"alreadySettled,omitempty"`
}

SettleResult is the /settle response. TxHash is nil when the payment was already settled by a prior call. Ported from protocol.ts SettleResult.

type SettlementContext

type SettlementContext struct {
	PaymentRequirements  *X402PaymentRequirements
	PaymentRequestID     string
	SourceAccountID      string
	DestinationAccountID string
	Options              any
}

SettlementContext carries the data verify/settle need to build protocol bodies. Ported from protocol.ts SettlementContext.

type SolanaMppSessionSupport

type SolanaMppSessionSupport struct {
	AuthorizedSigner string `json:"authorizedSigner"`
}

SolanaMppSessionSupport is the Solana MPP session-channel params advertised by GET /mpp/supported. accounts opens the channel and uses its own operator/fee-payer, so the SDK only needs auth's Solana authorizedSigner. Ported from omniChallenge.ts SolanaMppSessionSupport.

type Source

type Source struct {
	Chain   string `json:"chain"`
	Address string `json:"address"`
}

Source is a destination chain address the merchant can receive USDC at. Ported from the @atxp/common Source shape (only the fields the challenge builder uses).

type StaticDestination

type StaticDestination struct {
	ID        string   // "network:address"
	Addresses []Source // per-chain receive addresses (may be empty)
}

StaticDestination is a Destination with a fixed account id and address set. Suitable for a merchant that knows its receive addresses up front.

func (StaticDestination) AccountID

func (d StaticDestination) AccountID(context.Context) (string, error)

func (StaticDestination) Sources

func (d StaticDestination) Sources(context.Context, []string) ([]Source, error)

type StdLogger

type StdLogger struct {
	// contains filtered or unexported fields
}

StdLogger writes warn- and error-level lines to the standard library logger (stderr by default) and drops debug/info. It is a convenience for merchants that want operational visibility without a logging dependency.

func NewStdLogger

func NewStdLogger() *StdLogger

NewStdLogger returns a StdLogger writing to stderr.

func (*StdLogger) Debugf

func (s *StdLogger) Debugf(string, ...any)

func (*StdLogger) Errorf

func (s *StdLogger) Errorf(format string, args ...any)

func (*StdLogger) Infof

func (s *StdLogger) Infof(string, ...any)

func (*StdLogger) Warnf

func (s *StdLogger) Warnf(format string, args ...any)

type TokenCheck

type TokenCheck struct {
	Passes              bool
	Problem             TokenProblem
	Token               string
	Data                *TokenData
	ResourceMetadataURL string
}

TokenCheck is the result of CheckToken. Passes reports whether the caller is authenticated; on failure Problem says why and ResourceMetadataURL is the value for the WWW-Authenticate challenge.

type TokenData

type TokenData struct {
	Active bool          `json:"active"`
	Scope  string        `json:"scope,omitempty"`
	Sub    string        `json:"sub,omitempty"`
	Aud    stringOrSlice `json:"aud,omitempty"`
	Exp    int64         `json:"exp,omitempty"`
}

TokenData is the RFC 7662 introspection result. Ported from @atxp/common TokenData. `Exp` is a Unix timestamp in SECONDS when the AS provides it.

type TokenProblem

type TokenProblem string

TokenProblem categorizes why a token check failed. Ported from @atxp/server TokenProblem.

const (
	ProblemNoToken       TokenProblem = "NO-TOKEN"
	ProblemNonBearer     TokenProblem = "NON-BEARER-AUTH-HEADER"
	ProblemInvalidToken  TokenProblem = "INVALID-TOKEN"
	ProblemInvalidAud    TokenProblem = "INVALID-AUDIENCE"
	ProblemNSF           TokenProblem = "NON-SUFFICIENT-FUNDS"
	ProblemIntrospectErr TokenProblem = "INTROSPECT-ERROR"
)

type UnderpaymentError added in v0.1.2

type UnderpaymentError struct {
	Spent   Amount
	Settled Amount
	// ParseError is set instead of a meaningful Settled when the settle
	// response's amount could not be parsed at all. Treated as a failure
	// too, since an amount that can't be verified isn't a verified amount.
	ParseError error
}

UnderpaymentError is returned by CloseSession when a settle call succeeded but for less than the amount actually charged locally (Spent). Real money moved, so there is nothing to retry, but the caller must not treat this as a completed payment: do not serve the resource or credit anything for it.

This is deliberately not a network/infrastructure failure: it means the settle response itself came back clean, just short. See CloseSession's doc comment for why this check exists (the x402 "exact" scheme in particular can settle for less than a credential claims elsewhere in the same payload).

Confirmed live against production (2026-08-07, Base mainnet): for x402 "exact", auth.atxp.ai's own /settle/x402 already rejects a credential whose accepted.amount doesn't match its actual signed authorization.value (HTTP 400, nothing settles), so this error path did not fire for that specific attack shape; the AS closed it one layer down. This check is still real defense-in-depth (a merchant-side pricing bug, or any future change in the AS's behavior, would still need it), but for x402 exact its load-bearing-ness is unconfirmed rather than demonstrated. Whether the AS enforces the same consistency for the ATXP-native protocol or MPP is untested. ATXP-native's trust model differs (the AS computes the charge itself against its own ledger rather than verifying a third-party signature), so the same attack shape may not even apply there; MPP is simply unverified. Don't assume either has the same backend guarantee x402 exact was shown to have.

func (*UnderpaymentError) Error added in v0.1.2

func (e *UnderpaymentError) Error() string

func (*UnderpaymentError) Unwrap added in v0.1.2

func (e *UnderpaymentError) Unwrap() error

type VerifyResult

type VerifyResult struct {
	Valid bool `json:"valid"`
}

VerifyResult is the /verify response. Ported from protocol.ts VerifyResult.

type X402PaymentOption

type X402PaymentOption struct {
	Scheme            string         `json:"scheme"`
	Network           string         `json:"network"`
	Amount            string         `json:"amount"`
	Resource          string         `json:"resource"`
	Description       string         `json:"description"`
	MimeType          string         `json:"mimeType,omitempty"`
	PayTo             string         `json:"payTo"`
	MaxTimeoutSeconds int            `json:"maxTimeoutSeconds,omitempty"`
	Asset             string         `json:"asset,omitempty"`
	Extra             map[string]any `json:"extra,omitempty"`
}

X402PaymentOption is one entry of an x402 `accepts` array. Ported from protocol.ts X402PaymentOption.

type X402PaymentRequirements

type X402PaymentRequirements struct {
	X402Version int                 `json:"x402Version"`
	Accepts     []X402PaymentOption `json:"accepts"`
}

X402PaymentRequirements is the x402 challenge body. Ported from protocol.ts.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL