Skip to content

ExtractPaymentDetails always takes Accepts[0] — never matches the client's chain #16

Description

@VickyXAI

Problem

ExtractPaymentDetails unconditionally returns the first option, and its own doc comment says so:

// x402.go:243-250
// ExtractPaymentDetails extracts payment details from a PaymentRequirement.
// Returns the first payment option if multiple are available.
func ExtractPaymentDetails(req *PaymentRequirement) (*PaymentOption, error) {
	...
	option := req.Accepts[0]

createPaymentPayload then dispatches purely on the client's own chain and never reads option.Network:

// base_client.go:203-205
if bc.isSolana() {
	return CreateSolanaPaymentPayload(bc.solanaKey, option, resourceURL, description, extensions, bc.solanaRPCURL)
}

Nothing ever checks that the selected option is one this client can actually pay.

Impact

Multi-option 402s break. If a gateway offers [base, solana] in that order, a Solana client takes the Base option, hands an ERC-20 hex address to CreateSolanaPaymentPayload, and dies at invalid asset mint — even though a payable Solana option was sitting right there in Accepts[1].

The error message actively misleads. A Solana client receiving a Base-only 402 (mis-set BLOCKRUN_SOLANA_API_URL, a WithAPIURL override, or a multi-chain gateway) now fails with:

feePayer is required in payment requirement extra for Solana transactions

That blames the server for a client-side chain mismatch and sends users hunting a gateway bug that doesn't exist. Before #9 the same case stopped earlier at "no wallet is configured" — also wrong, but it at least pointed at the client.

Fix

  1. Have ExtractPaymentDetails select the option matching the client's chain rather than always taking [0] (it needs the chain passed in, or the selection moves to createPaymentPayload).
  2. When no option matches, fail explicitly:
return "", &PaymentError{Message: fmt.Sprintf(
    "client is configured for Solana but the 402 only offers %q", option.Network)}

Scope note: this is about selecting a payable option and reporting mismatches, not about capping spend — per-call spend limits are a settled no.

Provenance

Surfaced independently by Codex and the adversarial pass during /review of #9.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions