- AuthCaptureEscrow: singleton escrow that holds funds and gates lifecycle actions on the
captureAuthorizer(committed on-chain asPaymentInfo.operator). - ERC3009PaymentCollector: collects funds via signed ERC-3009
receiveWithAuthorization. - Permit2PaymentCollector: collects funds via Uniswap Permit2
permitTransferFrom.
| Contract | Canonical address |
|---|---|
AuthCaptureEscrow | 0xBdEA0D1bcC5966192B070Fdf62aB4EF5b4420cff |
ERC3009PaymentCollector | 0x0E3dF9510de65469C4518D7843919c0b8C7A7757 |
Permit2PaymentCollector | 0x992476B9Ee81d52a5BdA0622C333938D0Af0aB26 |
AuthCaptureEscrow
Core escrow contract for holding ERC-20 tokens during the payment lifecycle.Payment State Machine
Key methods
authorize()
Pulls funds into escrow via the token collector. Only thecaptureAuthorizer (typically a facilitator EOA, or a smart contract acting as captureAuthorizer) can call it.
tokenCollector is ERC3009PaymentCollector or Permit2PaymentCollector depending on assetTransferMethod in the scheme extra. collectorData carries the raw ERC-3009 signature or the ABI-encoded Permit2 signature.
charge()
Single-shot atomic settlement: pulls funds and transfers directly to the receiver, no escrow hold.capture()
Releases escrowed funds to the receiver, minus fees.void()
Returns all escrowed funds to the payer. Full-only:void() empties the authorization in one transaction.
reclaim()
Payer-only: gated byonlySender(paymentInfo.payer). The payer can pull funds back out of escrow after captureDeadline if the captureAuthorizer never captured. No third party (including the operator or arbiter) can call reclaim on the payer’s behalf.
refund()
Returns funds to the payer after capture, sourced via a token collector (typically pulled from the merchant’s balance).Access control
Lifecycle actions (authorize, charge, capture, void, refund) check msg.sender against PaymentInfo.operator (the captureAuthorizer). Anyone can call reclaim after captureDeadline.
The escrow has no global “operator whitelist.” Access is per-payment, governed by the signed PaymentInfo.
Security features
- Replay prevention: each payment has a unique nonce derived from
(chainId, escrowAddress, paymentInfoHash), consumed on-chain at settlement - Fee bounds enforcement: the client signs
minFeeBps/maxFeeBps/feeReceiverinPaymentInfo; the escrow rejects out-of-bounds captures/charges - Expiry ordering: contract enforces
preApprovalExpiry <= authorizationExpiry <= refundExpiry - Reentrancy protection on all state-changing entry points
ERC3009PaymentCollector
Collects ERC-20 tokens into escrow using the client’s off-chain ERC-3009 signature. The payer never submits a transaction.How it works
The escrow calls the token collector duringauthorize() or charge(), passing the client’s signature as collectorData. The collector executes receiveWithAuthorization (ERC-3009) to pull tokens from the payer.
Features
- ERC-3009
receiveWithAuthorization(): gasless token transfers via signed messages - EIP-6492 support: handles smart wallet clients with deployment bytecode in signatures (via
ERC6492SignatureHandler) - Nonce-based replay protection: each authorization can run only once; the nonce is the payer-agnostic
PaymentInfohash - Deadline-based expiry:
validBefore(typicallynow + maxTimeoutSeconds) blocks stale authorizations
ERC-3009 signature
The client signs an EIP-712 typed data message with primary typeReceiveWithAuthorization:
The auth-capture scheme uses
receiveWithAuthorization (not transferWithAuthorization). The token collector is the to address, which then routes tokens into the escrow.Permit2PaymentCollector
Collects ERC-20 tokens through Uniswap Permit2permitTransferFrom. The operator selects this collector when assetTransferMethod === "permit2" in the scheme extra. Any ERC-20 the payer has approved Permit2 for becomes spendable through this collector.
The client signs a Permit2 PermitTransferFrom; the deterministic nonce binds the merchant address, removing the need for a separate witness struct.
See the auth-capture wire format for the full Permit2 wire format.