# `X402.Facilitator.Engine`
[🔗](https://github.com/cardotrejos/x402/blob/v0.9.0/lib/x402/facilitator/engine.ex#L1)

Facilitator-role engine: verify and settle x402 payments yourself.

While `X402.Facilitator` is the *client* of a remote facilitator, this
module is the facilitator itself. It assembles the SDK's local
verification core (`X402.Verify.EVM`), JSON-RPC client (`X402.RPC`),
transaction encoder (`X402.Transaction`), and signer behaviour
(`X402.Signer`) into the engine behind the facilitator API's three
operations, speaking the same wire shapes as the reference facilitators:

* `verify/3` — the full exact-EVM / upto-EVM verify checklist at `:full`
  level, returning the `POST /verify` response
  (`%{"isValid" => true, "payer" => ...}` /
  `%{"isValid" => false, "invalidReason" => ..., "payer" => ...}`).
* `settle/3` — **re-verifies independently** (the schemes' normative
  requirement), then broadcasts the settlement transaction and awaits
  its receipt, returning the `POST /settle` response
  (`%{"success" => true, "transaction" => ..., ...}` /
  `%{"success" => false, "errorReason" => ..., ...}`).
* `supported/1` — the `GET /supported` response derived from the
  configured networks.

Expose the engine over HTTP with `X402.Plug.Facilitator`, or call it
directly from your own transport.

## Payment kinds

The engine settles every EVM flow `X402.Verify.EVM` verifies:

| Scheme  | `extra.assetTransferMethod` | Settlement transaction                          |
| ------- | --------------------------- | ----------------------------------------------- |
| `exact` | absent / `"eip3009"`        | `asset.transferWithAuthorization(...)`          |
| `exact` | `"permit2"`                 | `x402ExactPermit2Proxy.settle(...)`             |
| `upto`  | —                           | `x402UptoPermit2Proxy.settle(...)` for `amount` |

The requirements select the authorization used throughout verification,
settlement, payer reporting, pending lookup, and receipt checking. Extra
unsigned authorization objects do not change that selection; a missing
selected authorization never falls back to another transfer method.

For `upto` the requirements' `extra.facilitatorAddress` must be this
engine's signer address (the proxy only accepts the witness facilitator
as sender); other facilitators' payments are rejected with
`upto_facilitator_mismatch`. The settled `amount` is the requirements'
`amount`, which re-verification proves to be at most the signed
`permitted.amount`.

## Fee-payer safety

The facilitator's signing key pays gas, so what it signs is structurally
constrained: settlement transactions are always built by this module with
`to` set to the verified requirements' `asset` (EIP-3009) or the fixed
x402 Permit2 proxy address (Permit2), `value` `0`, and calldata produced
exclusively by `X402.EIP3009.transfer_calldata/3` /
`X402.Permit2.exact_settle_calldata/2` / `X402.Permit2.upto_settle_calldata/3`
from the authorization fields the signature verification just proved.
The single exception is ERC-6492 *counterfactual* settlement: the engine signs
caller-supplied calldata ONLY toward explicitly allowlisted factory
addresses (`:eip6492_allowed_factories`), capped by
`:max_deploy_gas_limit` — with the default empty allowlist it never does,
and counterfactual payments are rejected fail-closed at verify **and** at
settle's re-verify. Deployed ERC-1271 smart wallets are fully supported
either way.

## Settlement pipeline

1. Reconcile: with a `:pending_settlement_store` configured, a payload
   whose earlier attempt already broadcast a transaction is re-awaited
   instead of re-verified and re-broadcast.
2. Re-verify via `X402.Verify.EVM` at `:full` level (transfer simulation
   off by default — verify already simulated; see `:simulate_in_settle` —
   while the atomic ERC-6492 counterfactual simulation always runs, being
   the only possible proof of a counterfactual signature).
3. For a verified counterfactual payment whose wallet is still
   undeployed, broadcast the wrapper's factory calldata (allowlist-gated,
   `:max_deploy_gas_limit`-capped) and require a successful deploy
   receipt first.
4. Build the settlement calldata — `transferWithAuthorization` or the
   proxy's `settle` (shared with verification's simulation encoding).
5. One batched RPC round-trip: `eth_estimateGas` (with a safety margin),
   `eth_maxPriorityFeePerGas` + `eth_feeHistory` (falling back to
   `eth_gasPrice` on nodes without EIP-1559 fee APIs), and
   `eth_getTransactionCount` (`pending`).
6. Encode the EIP-1559 transaction (`X402.Transaction`), sign its keccak
   digest through the configured `X402.Signer` (the signer must support
   raw digest signing — `X402.Signer.LocalKey` does; the 27/28 recovery id
   it returns is normalized to the EIP-1559 `yParity`), and broadcast via
   `eth_sendRawTransaction`.
7. Poll `eth_getTransactionReceipt` until confirmation or timeout. A
   confirmed receipt must also carry the matching ERC-20 `Transfer` event
   before success is reported. A broadcast whose confirmation cannot be
   established returns the spec's non-terminal `"settlement_pending"`
   with the transaction hash so callers can reconcile on chain, recording
   the attempt in the `:pending_settlement_store` when one is configured.

## Hooks

`X402.Hooks` callbacks wrap both operations, mirroring the reference
facilitator's lifecycle hooks: `before_*` returning `{:halt, reason}`
turns into a rejected wire response (not an exception), `after_*` runs on
successful results and may replace them, and `on_*_failure` runs for both
rejected wire responses and infrastructure errors and may `{:recover,
result}` with a replacement response. The internal re-verify inside
`settle/3` runs without hooks — the settle hooks already wrap it.

## Example

    {:ok, rpc} = X402.RPC.new(rpc_url: "https://sepolia.base.org", finch: MyApp.Finch)
    {:ok, signer} = X402.Signer.LocalKey.new(System.fetch_env!("PRIVATE_KEY"))

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

    {:ok, %{"isValid" => true, "payer" => payer}} =
      X402.Facilitator.Engine.verify(engine, payment_payload, requirements)

    {:ok, %{"success" => true, "transaction" => tx_hash}} =
      X402.Facilitator.Engine.settle(engine, payment_payload, requirements)

## Telemetry

Emits `[:x402, :facilitator_engine, :verify]` and
`[:x402, :facilitator_engine, :settle]` with `:status` metadata.

# `verify`
*since 0.6.0* 

```elixir
@spec verify(t(), map(), map()) :: {:ok, wire_response()} | {:error, term()}
```

Verifies a payment payload against requirements, returning the
`POST /verify` wire response.

Returns `{:ok, response}` for every *protocol-level* outcome — including
rejected payments, which come back as
`%{"isValid" => false, "invalidReason" => reason, "payer" => payer}` with
the canonical cross-SDK reason string (`X402.Verify.EVM.reason_string/1`).
`{:error, reason}` is reserved for infrastructure failures (RPC transport
errors, missing crypto dependencies, hook crashes) where no verdict about
the payment exists; transports should map it to an opaque 500.

# `settle`
*since 0.6.0* 

```elixir
@spec settle(t(), map(), map()) :: {:ok, wire_response()} | {:error, term()}
```

Settles a payment, returning the `POST /settle` wire response.

Independently re-verifies the payment first (normative for the exact-EVM
scheme), then broadcasts `transferWithAuthorization` and awaits the
receipt — see the module documentation for the full pipeline. Rejected or
failed settlements come back as `{:ok, %{"success" => false,
"errorReason" => reason, ...}}`; a broadcast whose confirmation could not
be established returns the non-terminal `"settlement_pending"` reason
with the transaction hash. `{:error, reason}` is reserved for
infrastructure failures where nothing was broadcast.

# `supported`
*since 0.6.0* 

```elixir
@spec supported(t()) :: wire_response()
```

Returns the `GET /supported` wire response for this engine.

One `exact` and one `upto` kind per configured network, no extensions,
and the signer's address under the `eip155:*` family. Resource servers
advertising `upto` requirements forward that signer address as
`extra.facilitatorAddress` — the engine only settles upto payments whose
witness binds it.

## Examples

    {:ok, engine} = X402.Facilitator.Engine.new(rpc: rpc, signer: signer, networks: ["eip155:84532"])
    X402.Facilitator.Engine.supported(engine)
    #=> %{
    #     "kinds" => [
    #       %{"x402Version" => 2, "scheme" => "exact", "network" => "eip155:84532"},
    #       %{"x402Version" => 2, "scheme" => "upto", "network" => "eip155:84532"}
    #     ],
    #     "extensions" => [],
    #     "signers" => %{"eip155:*" => ["0x..."]}
    #   }

# `t`

```elixir
@type t() :: %X402.Facilitator.Engine{
  eip6492_allowed_factories: [String.t()],
  gas_limit_margin_percent: non_neg_integer(),
  hooks: module(),
  max_deploy_gas_limit: pos_integer(),
  max_gas_limit: pos_integer(),
  networks: [String.t()],
  nonce_manager: GenServer.server() | nil,
  pending_settlement_store:
    X402.Facilitator.PendingSettlementStore.adapter() | nil,
  receipt_interval_ms: pos_integer(),
  receipt_timeout_ms: pos_integer(),
  rpc: X402.RPC.t(),
  signer: X402.Signer.t(),
  simulate: boolean(),
  simulate_in_settle: boolean(),
  verify_chain_id: boolean()
}
```

A validated engine configuration built by `new/1`.

# `wire_response`

```elixir
@type wire_response() :: %{optional(String.t()) =&gt; term()}
```

A facilitator wire response (`/verify` or `/settle` shape).

# `new`
*since 0.6.0* 

```elixir
@spec new(keyword()) :: {:ok, t()} | {:error, NimbleOptions.ValidationError.t()}
```

Builds a validated engine configuration.

## Options

* `:rpc` - Required. An `X402.RPC` configuration for the served network.

* `:signer` - Required. The fee-payer signer (a struct implementing `X402.Signer`). Its key
  pays settlement gas and must support signing raw 32-byte digests.

* `:networks` - Required. Non-empty list of CAIP-2 networks this engine serves (currently
  `eip155:<chainId>` only). Verify and settle requests for other
  networks are rejected with `invalid_network`.

* `:hooks` - Lifecycle hook module implementing `X402.Hooks`. The default value is `X402.Hooks.Default`.

* `:simulate` (`t:boolean/0`) - Whether `verify/3` simulates `transferWithAuthorization` via
  `eth_call`. Even when `false`, verify keeps the atomic ERC-6492
  counterfactual simulation — the only possible proof of a
  counterfactual signature — so verify predicts settle, whose re-verify
  always keeps that proof too. The default value is `true`.

* `:simulate_in_settle` (`t:boolean/0`) - Whether the independent re-verify inside `settle/3` also simulates.
  Off by default, matching the reference facilitators — verify already
  simulated, and `eth_estimateGas` re-simulates right before broadcast.
  Even when off, the re-verify keeps the atomic ERC-6492 counterfactual
  simulation, the only possible proof of a counterfactual signature. The default value is `false`.

* `:verify_chain_id` (`t:boolean/0`) - Whether verification cross-checks `eth_chainId` against the CAIP-2 network. The default value is `true`.

* `:gas_limit_margin_percent` (`t:non_neg_integer/0`) - Safety margin added to `eth_estimateGas` (percent). The default value is `20`.

* `:receipt_timeout_ms` (`t:pos_integer/0`) - How long `settle/3` waits for the transaction receipt before returning
  the non-terminal `"settlement_pending"` response. The default value is `60000`.

* `:receipt_interval_ms` (`t:pos_integer/0`) - Interval between `eth_getTransactionReceipt` polls. The default value is `1000`.

* `:nonce_manager` - Optional `X402.Facilitator.NonceManager` (pid/name) serializing
  fee-payer nonces. Without it, each settlement reads the pending nonce
  from the node, which races under concurrent settles — configure the
  manager for any deployment that settles concurrently. The default value is `nil`.

* `:max_gas_limit` (`t:pos_integer/0`) - Absolute gas ceiling per settlement transaction (margin included). A
  legitimate `transferWithAuthorization` costs well under 100k gas; an
  estimate above this ceiling means the asset contract is burning the
  fee payer's gas and the settlement is refused — the fee payer never
  broadcasts unbounded-gas transactions against unvetted bytecode. The default value is `200000`.

* `:eip6492_allowed_factories` (list of `t:String.t/0`) - Factory contract addresses (case-insensitive) trusted to deploy
  counterfactual ERC-6492 smart wallets during settlement. Threaded
  into verification so verify predicts settle. The default empty list
  keeps the fail-closed behavior: every counterfactual payment is
  rejected and the engine never signs caller-supplied factory calldata. The default value is `[]`.

* `:max_deploy_gas_limit` (`t:pos_integer/0`) - Absolute gas ceiling for the ERC-6492 factory deployment transaction
  (margin included). Smart-account deployments legitimately cost far
  more than a transfer (~300k gas), so the deployment carries its own
  ceiling; the transfer keeps `:max_gas_limit`. The default value is `600000`.

* `:pending_settlement_store` - Optional `{module, store}` adapter implementing
  `X402.Facilitator.PendingSettlementStore`. When configured, `settle/3`
  records broadcasts whose confirmation could not be established and
  reconciles a retried payload against the already-broadcast transaction
  instead of broadcasting a second one. The default value is `nil`.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
