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 thesignatureNonceand 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 optionalex_keccak/ex_secp256k1dependencies.:full— additionally cross-checks the chain id, classifies the payer (EOA, ERC-1271, counterfactual), checks the payer balance, readspaymentStatefor 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
@type authorizer() :: :signed | :none
Where an operation's consent comes from.
@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.
@type level() :: :structural | :signature | :full
Requested verification depth.
@type signature_type() :: :eoa | :erc1271 | :erc6492_counterfactual
How the client's signature was (or would be) verified.
@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
Classifies a node revert onto the spec's typed reasons
(X402.AuthCapture.EVM.classify_revert/1).
@spec reason_string(X402.AuthCapture.reason()) :: String.t()
Converts a reason atom to its canonical wire string
(X402.AuthCapture.reason_string/1).
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}}
@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.
@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- AnX402.RPCconfiguration. 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 is6.:simulate(boolean/0) - Whether level:fullsimulates the escrow call. Counterfactual payers are always simulated, since the collector deploys the wallet before the token validates the signature. The default value istrue.:verify_chain_id(boolean/0) - Whether level:fullcross-checkseth_chainIdagainst the CAIP-2 network. The default value istrue.:eip6492_allowed_factories(list ofString.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_admittedotherwise).nilskips the admission check for server-side verification. The default value isnil.:operators(list ofmap/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 isfalse.:settlement(boolean/0) - Require the completedchargeform (the/settlerule). The default value isfalse.
@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.