X402.Verify.AuthCaptureEVM (X402 v0.9.0)

Copy Markdown View Source

Verification of auth-capture payments on EVM networks.

Runs the spec's client-payload and lifecycle-payload checklists at one of three depths, mirroring X402.Verify.EVM:

  • :structural — shape, scheme, network, extra, operator fields, method routing, deadlines, collector and token match, amount and fee. Needs nothing.
  • :signature — additionally recomputes the signatureNonce and bound salt, verifies the client's ECDSA signature (or gates a counterfactual ERC-6492 envelope on the factory allowlist), and recovers every authorizer signature. Needs the optional ex_keccak / ex_secp256k1 dependencies.
  • :full — additionally cross-checks the chain id, classifies the payer (EOA, ERC-1271, counterfactual), checks the payer balance, reads paymentState for lifecycle preconditions and single-use enforcement, and simulates the exact escrow call. Needs :rpc.

A level never silently downgrades: a missing dependency or RPC is an error, not a pass.

Facilitator policy

The checklist's operator admission depends on facilitator state, passed as options: :submitters (the addresses the facilitator submits from, for "delegated"), :operators (the "custom" allowlist), and :refund_funding (whether a delegated refund has a funding agreement). A server-side caller that only wants the protocol checks leaves :submitters unset to skip admission.

Receiver-authorizer delegation is not supported. Completed charges and lifecycle payloads require an explicit signature. Operator control alone is not receiver consent. Refund funding remains a separate application authorization decision, not something inferred from that signature.

Full verification requires EIP-1898 block-hash references. All state reads use one canonical block after checking the chain, with no fallback to latest. This trusts the RPC; it is not proof of finality.

Capture-plus-void requires eth_simulateV1 with a block-hash parent and successful sequential calls. Unsupported RPCs fail closed. Custom operators have only structural/signature support; full verification rejects them until their collect outcome assertions are implemented. Direct callers are subject to a 64 KiB limit per signature and 78 digits per decimal value, independent of HTTP header limits.

Results

{:ok, verification} describes the operation the payload settles as, the reconstructed PaymentInfo and its hash, the resolved deployment, the escrow call (target, calldata), and — for a capture-and-void — the second leg's void_calldata. {:error, {:invalid, reason}} carries a reason atom reason_string/1 maps onto the spec's wire strings.

Summary

Types

Where an operation's consent comes from.

Why a verification failed.

Requested verification depth.

How the client's signature was (or would be) verified.

A successful verification.

Functions

Classifies a node revert onto the spec's typed reasons (X402.AuthCapture.EVM.classify_revert/1).

Converts a reason atom to its canonical wire string (X402.AuthCapture.reason_string/1).

Validates static requirements without signing or contacting a node.

Checks only the payload shape (checklist step 1) — the offline validation X402.Scheme.AuthCaptureEVM.validate_payload/3 runs.

Verifies an auth-capture payment or lifecycle payload against its requirements.

Verifies a lifecycle consent using pinned EOA/ERC-1271 account rules.

Types

authorizer()

@type authorizer() :: :signed | :none

Where an operation's consent comes from.

error()

@type error() ::
  {:invalid, X402.AuthCapture.reason()}
  | :missing_dependency
  | :rpc_not_configured
  | {:rpc_error, X402.RPC.error()}
  | {:chain_id_mismatch, non_neg_integer(), non_neg_integer()}

Why a verification failed.

level()

@type level() :: :structural | :signature | :full

Requested verification depth.

signature_type()

@type signature_type() :: :eoa | :erc1271 | :erc6492_counterfactual

How the client's signature was (or would be) verified.

verification()

@type verification() :: %{
  operation: X402.AuthCapture.operation(),
  level: level(),
  payer: String.t(),
  flow: X402.AuthCapture.payment_flow(),
  operator_type: X402.AuthCapture.operator_type(),
  method: :eip3009 | :permit2 | nil,
  chain_id: non_neg_integer(),
  deployment: X402.AuthCapture.EVM.deployment(),
  payment_info: X402.AuthCapture.EVM.payment_info(),
  payment_info_hash: String.t() | nil,
  signature_type: signature_type() | nil,
  authorizer: authorizer(),
  amount: non_neg_integer(),
  fee: non_neg_integer() | nil,
  fee_receiver: String.t() | nil,
  target: String.t(),
  submitter: String.t(),
  calldata: binary() | nil,
  void_calldata: binary() | nil,
  payment_state: X402.AuthCapture.EVM.payment_state() | nil
}

A successful verification.

Functions

classify_revert(error)

(since 0.9.0)
@spec classify_revert(map()) :: atom() | nil

Classifies a node revert onto the spec's typed reasons (X402.AuthCapture.EVM.classify_revert/1).

reason_string(reason)

(since 0.9.0)
@spec reason_string(X402.AuthCapture.reason()) :: String.t()

Converts a reason atom to its canonical wire string (X402.AuthCapture.reason_string/1).

validate_requirements(requirements)

(since 0.9.0)
@spec validate_requirements(map()) :: :ok | {:error, error()}

Validates static requirements without signing or contacting a node.

Checks amounts, Solidity widths, deployment, operator fields, transfer method, payment flow and capture mode. Does not establish operator admission, current deadlines, or onchain funding.

Examples

iex> X402.Verify.AuthCaptureEVM.validate_requirements(%{})
{:error, {:invalid, :scheme}}

validate_shape(payment_payload, requirements)

(since 0.9.0)
@spec validate_shape(map(), map()) ::
  :ok | {:error, {:invalid, X402.AuthCapture.reason()}}

Checks only the payload shape (checklist step 1) — the offline validation X402.Scheme.AuthCaptureEVM.validate_payload/3 runs.

verify(payment_payload, requirements, opts \\ [])

(since 0.9.0)
@spec verify(map(), map(), keyword()) :: {:ok, verification()} | {:error, error()}

Verifies an auth-capture payment or lifecycle payload against its requirements.

Options

  • :level - Verification depth (see the module documentation). The default value is :full.

  • :rpc - An X402.RPC configuration. Required for level :full.

  • :now (non_neg_integer/0) - Unix seconds used by the deadline checks (default: current time).

  • :skew_seconds (non_neg_integer/0) - Clock-skew floor for deadline checks. The default value is 6.

  • :simulate (boolean/0) - Whether level :full simulates the escrow call. Counterfactual payers are always simulated, since the collector deploys the wallet before the token validates the signature. The default value is true.

  • :verify_chain_id (boolean/0) - Whether level :full cross-checks eth_chainId against the CAIP-2 network. The default value is true.

  • :eip6492_allowed_factories (list of String.t/0) - Factory addresses (case-insensitive) a counterfactual payer may deploy through. The default value is [].

  • :submitters - Addresses the facilitator submits from. A "delegated" capture authorizer must be one of them (operator_not_admitted otherwise). nil skips the admission check for server-side verification. The default value is nil.

  • :operators (list of map/0) - The "custom" operator allowlist: maps with :address (or "*") and :operator_type. Empty admits no contract operator. The default value is [].

  • :refund_funding (boolean/0) - Whether a "delegated" refund has an out-of-band funding agreement. The default value is false.

  • :settlement (boolean/0) - Require the completed charge form (the /settle rule). The default value is false.

verify_consent(envelope, requirements, opts \\ [])

(since 0.9.0)
@spec verify_consent(map(), map(), keyword()) ::
  {:ok, verification()} | {:error, term()}

Verifies a lifecycle consent using pinned EOA/ERC-1271 account rules.

Requires RPC and a capture, void, or refund envelope. Unlike verify/3 at full level, this does not require the future escrow state or simulate the operation. It cannot authorize execution or prove refund liquidity.