X402.Plug.Facilitator (X402 v0.6.0)

Copy Markdown View Source

Plug scaffold exposing one or more facilitator engines as the facilitator HTTP API.

Serves the x402 v2 facilitator endpoints over any Plug-compatible server (Bandit, Cowboy, or mounted inside a Phoenix endpoint):

EndpointBehaviour
POST /verifythe matching engine's verify/3
POST /settlethe matching engine's settle/3
GET /supportedthe engines' supported/1, merged
GET /discovery/resources404 — bazaar discovery serving is not included

Usage

{:ok, engine} =
  X402.Facilitator.Engine.new(
    rpc: rpc,
    signer: signer,
    networks: ["eip155:84532"]
  )

children = [
  {Finch, name: MyApp.Finch},
  {Bandit, plug: {X402.Plug.Facilitator, engine: engine}, port: 4022}
]

Mount the plug at the root of its listener (or behind a forward that strips the prefix): it answers 404 for paths it does not serve.

Multiple engines

A facilitator serving several chain families passes :engines instead of :engine — for example an EVM X402.Facilitator.Engine next to an SVM X402.Facilitator.SVMEngine:

{X402.Plug.Facilitator, engines: [evm_engine, svm_engine]}

Exactly one of :engine and :engines must be given. POST /verify and POST /settle dispatch to the first engine whose supported/1 kinds contain the request's paymentRequirements (scheme, network) pair; when none matches, the request is answered with a 200 protocol rejection (unsupported_scheme, or invalid_network when some engine serves the scheme on other networks). GET /supported merges the engines' responses — kinds concatenated, extensions unioned, signer families merged. Any struct whose module exports verify/3, settle/3, and supported/1 with the engine wire contract can be listed.

Request/response contract

POST /verify and POST /settle require exactly the v2 facilitator wire object — {"x402Version": 2, "paymentPayload": {...}, "paymentRequirements": {...}} — and reject anything else with 400. Following the facilitator API convention, protocol-level rejections are 200 responses ({"isValid": false, ...} / {"success": false, ...}); non-2xx statuses are reserved for transport-level problems:

StatusMeaning
200Engine verdict (including invalid / failed payments)
400Malformed body: bad JSON or not the v2 wire object
401Missing/wrong bearer token (when :auth_token is configured)
404Unknown path (including /discovery/resources)
405Known path, wrong method
413Body larger than :max_body_bytes
500Engine infrastructure error — opaque body, details are logged

Authentication

The optional :auth_token enables a minimal bearer-token check (constant-time comparison) on every endpoint. It is a convenience for private deployments — put real authentication, TLS termination, and rate limiting in front of a production facilitator.

Summary

Types

Validated plug options.

Functions

Dispatches a facilitator API request — see the module documentation for the endpoint and status contract.

Validates the plug options.

Types

options()

@type options() :: %{
  engines: [struct()],
  auth_token: String.t() | nil,
  max_body_bytes: pos_integer()
}

Validated plug options.

Functions

call(conn, options)

(since 0.6.0)
@spec call(Plug.Conn.t(), options()) :: Plug.Conn.t()

Dispatches a facilitator API request — see the module documentation for the endpoint and status contract.

init(opts)

(since 0.6.0)
@spec init(keyword()) :: options()

Validates the plug options.

Exactly one of :engine and :engines must be given; both are normalized to a list of engines internally.

Options

  • :engine - A single X402.Facilitator.Engine configuration built with Engine.new/1. Exactly one of :engine or :engines must be given.

  • :engines - A non-empty list of engine structs (X402.Facilitator.Engine, X402.Facilitator.SVMEngine, or any struct whose module exports verify/3, settle/3, and supported/1), dispatched by the request's (scheme, network). Exactly one of :engine or :engines must be given.

  • :auth_token - Optional bearer token required on every request (compared in constant time). nil disables authentication — front a production deployment with real auth instead. The default value is nil.

  • :max_body_bytes (pos_integer/0) - Maximum accepted request body size, consistent with the SDK's 8KB encoded-header caps. Larger bodies answer 413. The default value is 8192.