X402.Client.Finch (X402 v0.9.0)

Copy Markdown View Source

Finch-backed payer client with an automatic 402 → sign → retry flow.

request/3 performs an HTTP request; when the server answers 402 with a PAYMENT-REQUIRED header, it decodes the payment requirements, builds and signs a payment via X402.Client.build_payment/3, and retries the request once with the PAYMENT-SIGNATURE header. A request is never paid twice: at most one payment retry is made, and requests that already carry a payment-signature header are refused.

Requires the optional finch dependency; without it every call returns {:error, :missing_dependency}. Start your own Finch pool (with TLS peer verification — see X402.Facilitator.HTTP.secure_pool_opts/0) and pass its name.

Example

{:ok, signer} = X402.Signer.LocalKey.new(System.fetch_env!("PAYER_KEY"))

{:ok, %{status: 200, body: body, payment_response: receipt}} =
  X402.Client.Finch.request(MyApp.Finch, "https://api.example.com/paid",
    signer: signer,
    max_amount: "10000",
    on_payment_required: fn payment_required ->
      IO.inspect(payment_required["accepts"], label: "about to pay")
      :ok
    end
  )

Spend controls

Cap what an automated payer signs with :max_amount (per payment), :policies (X402.Client.Policy filters on network, asset, scheme, or anything else), and :budget (an X402.Client.Budget shared across requests). With none of them configured a warning is logged once.

Sign-In-With-X

With siwx: [chain_id: :auto] the client answers a server's sign-in-with-x challenge before paying: a returning payer whose address the server remembers gets the resource without a new payment (siwx_authenticated: true in the response); otherwise the payment flow runs as usual, with the proof attached to the paid request too.

Security

Like the facilitator client, URLs must use https:// — payment authorizations must never travel in plaintext. Loopback hosts (localhost, 127.0.0.1, ::1) are exempt for local development.

Summary

Types

A Finch pool name or pid.

A completed HTTP response.

Functions

Performs an HTTP request, paying for the resource if it requires payment.

Types

finch_name()

@type finch_name() :: atom() | pid() | {:via, module(), term()}

A Finch pool name or pid.

request_error()

@type request_error() ::
  :missing_dependency
  | :insecure_url
  | :payment_cancelled
  | :payment_already_attempted
  | {:transport_error, term()}
  | {:invalid_payment_required, term()}
  | {:siwx, X402.Client.SIWX.reason()}
  | X402.Client.Budget.reserve_error()
  | X402.Client.build_error()

response()

@type response() :: %{
  status: non_neg_integer(),
  headers: [{String.t(), String.t()}],
  body: binary(),
  payment_response: map() | nil,
  siwx_authenticated: boolean()
}

A completed HTTP response.

:payment_response holds the decoded PAYMENT-RESPONSE header (the settlement receipt) when the server sent a valid one, otherwise nil. :siwx_authenticated is true when the server accepted a Sign-In-With-X proof instead of a payment (see the :siwx option).

Functions

request(finch_name, url, opts)

(since 0.6.0)
@spec request(finch_name(), String.t(), keyword()) ::
  {:ok, response()} | {:error, request_error()}

Performs an HTTP request, paying for the resource if it requires payment.

Flow:

  1. Perform the request. Anything other than a 402 with a PAYMENT-REQUIRED header is returned as-is.
  2. Decode the PAYMENT-REQUIRED header (X402.PaymentRequired.decode/1).
  3. With :siwx configured and a sign-in-with-x challenge advertised, sign it (X402.Client.SIWX.authenticate/4) and retry the request with the SIGN-IN-WITH-X header and no payment. Anything other than a 402 with a PAYMENT-REQUIRED header is returned as-is, with siwx_authenticated: true for a 2xx. On a second 402 the flow continues with the new payment requirements; the paid request carries a proof for the new challenge (when advertised) so the server can record the payer.
  4. Invoke the :on_payment_required hook, which may cancel.
  5. Build and sign a payment (X402.Client.build_payment/3), reserve its amount against :budget (when given), and retry the request once with the PAYMENT-SIGNATURE header.
  6. Return the retried response with the decoded PAYMENT-RESPONSE settlement receipt, when present. A second 402 is returned as-is — the payment is never re-signed or re-sent — and the budget reservation is released unless the response is 2xx or carries a successful receipt.

When neither :max_amount, :policies, nor :budget is given a warning is logged once per VM: the client will then sign any amount a server asks for.

Options

  • :signer - Required. A struct implementing X402.Signer, used to sign the payment.

  • :method - HTTP request method. The default value is :get.

  • :headers - Additional {name, value} request headers. The default value is [].

  • :body - Request body. The default value is nil.

  • :receive_timeout_ms (non_neg_integer/0) - Finch receive timeout per attempt, in milliseconds. The default value is 5000.

  • :network (String.t/0) - Payment selection filter — see X402.Client.select_requirements/2.

  • :scheme (String.t/0) - Payment selection filter — see X402.Client.select_requirements/2.

  • :asset (String.t/0) - Payment selection filter — see X402.Client.select_requirements/2.

  • :max_amount - Maximum amount (atomic units) this client will pay — the budget guard for automated payers. Requirements above it are never selected.

  • :policies (list of function of arity 2) - Selection policies forwarded to X402.Client.select_requirements/2 — see X402.Client.Policy. The default value is [].

  • :budget - An X402.Client.Budget the selected amount is reserved against before the paid retry is sent. See X402.Client.Budget for what counts as spent. The default value is nil.

  • :hooks - X402.Client.Hooks module forwarded to X402.Client.build_payment/3. The default value is X402.Client.Hooks.Default.

  • :siwx - Automatic Sign-In-With-X: a keyword list of X402.Client.SIWX options (chain_id: required, or :auto). nil/false disables it. The default value is nil.

  • :valid_after_buffer (non_neg_integer/0) - Clock-skew buffer for the authorization's validAfter, in seconds. The default value is 60.

  • :auth_capture - Auth-capture signing options forwarded to X402.Client.build_payment/3. The default value is [].

  • :extensions (list of function of arity 2) - Client extension enrichers forwarded to X402.Client.build_payment/3 — see its :extensions option. The default value is [].

  • :schemes - Additional X402.Scheme modules forwarded to X402.Client.build_payment/3 — see its :schemes option. The default value is [].

  • :on_payment_required - Budget/consent hook invoked with the decoded PaymentRequired map before any payment is signed. Return :cancel to abort with {:error, :payment_cancelled}; any other return value continues. The default value is nil.