On this page
EIP-3009, "Transfer With Authorization", is a draft ERC created in 2020. It adds functions to an ERC-20 token so a holder can authorize a transfer by signing a typed message, and anyone holding that signature can submit it on chain. The signer never sends a transaction or pays gas. The signature itself follows EIP-712, the standard for hashing and signing typed structured data.
In x402, EIP-3009 is the default asset transfer method of the exact scheme on EVM chains, recommended for tokens that implement transferWithAuthorization natively, with USDC as the specification's example. Tokens without it use Permit2, and smart accounts can use ERC-7710 delegation; both have a different payload and are out of scope here.
What transferWithAuthorization does
The token contract exposes this function. It checks the authorization and the signature, marks the nonce as used and moves value from from to to:
function transferWithAuthorization(
address from,
address to,
uint256 value,
uint256 validAfter,
uint256 validBefore,
bytes32 nonce,
uint8 v,
bytes32 r,
bytes32 s
) external;
function authorizationState(address authorizer, bytes32 nonce)
external view returns (bool);
event AuthorizationUsed(address indexed authorizer, bytes32 indexed nonce);
The EIP also defines receiveWithAuthorization, which additionally requires the caller to be the payee, and an optional cancelAuthorization that lets the payer cancel an authorization before it is used.
The authorization fields
These six values are what the payer signs. In an x402 payload they travel as strings in payload.authorization, next to the 65-byte signature.
| Field | Type | Meaning | Enforced on chain |
|---|---|---|---|
from | address | The payer, who signs. | The recovered signer must equal from. |
to | address | The payee. | Funds go here and nowhere else. |
value | uint256 | Amount in the token's atomic units. | Exactly this amount is transferred. |
validAfter | uint256 | Unix time after which the authorization is valid. | now > validAfter |
validBefore | uint256 | Unix time before which it is valid. | now < validBefore |
nonce | bytes32 | A random 32-byte value chosen by the payer. | Must be unused for this payer, then marked used. |
The nonces are random rather than sequential, one of the two differences the EIP lists against EIP-2612 permits; the other is that tokens move directly, without the ERC-20 allowance step. With sequential nonces, pending authorizations fail when they are submitted out of order; random nonces let a payer create many authorizations in parallel without that risk. Anyone can ask authorizationState(from, nonce) whether a nonce has been used.
The EIP-712 domain
EIP-712 signs a hash of typed data inside a domain, so a signature made for one contract on one chain cannot be replayed elsewhere. EIP-3009 uses four domain fields:
name: the human-readable name of the signing domain, here the token's EIP-712 name.version: the current major version of the domain. Signatures from different versions are not compatible.chainId: the EIP-155 chain id.verifyingContract: the contract that verifies the signature, here the token itself.
The digest the payer signs combines the domain separator with the hash of the message:
domainSeparator = keccak256(abi.encode(
keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
keccak256(name), keccak256(version), chainId, verifyingContract))
structHash = keccak256(abi.encode(
TRANSFER_WITH_AUTHORIZATION_TYPEHASH,
from, to, value, validAfter, validBefore, nonce))
digest = keccak256(0x1901 ‖ domainSeparator ‖ structHash)
TRANSFER_WITH_AUTHORIZATION_TYPEHASH =
keccak256("TransferWithAuthorization(address from,address to,uint256 value,uint256 validAfter,uint256 validBefore,bytes32 nonce)")
= 0x7c7c6cdb67a18743f49ec6fa9b35f50d52ed05cbed4cc592e13b44501c1a2267
Wallets produce this signature with eth_signTypedData_v4, given the domain, the type definitions and the message.
How the x402 exact EVM scheme uses it
An x402 payment requirement carries everything needed to rebuild the domain, and the payload carries the message. The facilitator, which submits the transaction and pays the gas, cannot change the amount or the destination: both are inside the signed message.
| x402 field | Role in the signature |
|---|---|
extra.name | Signed as the domain name. Required for EIP-3009: a wrong name produces a signature the token rejects. |
extra.version | Signed as the domain version. Required for EIP-3009, for the same reason. |
network | Its reference is the domain chainId: eip155:84532 signs chain 84532. Binds the signature to one chain. |
asset | Signed as the domain verifyingContract. Binds the signature to one token contract. |
payTo | Must equal the message's to, or the payment goes elsewhere. |
amount | Must equal the message's value. The core specification lists invalid_exact_evm_payload_authorization_value_mismatch for this case (see the x402 error codes). |
maxTimeoutSeconds | Not signed. It is the maximum time allowed for payment completion; keep the authorization window short as well. |
According to the exact EVM specification, a facilitator verifying an EIP-3009 payment checks that the signature recovers to authorization.from, that the payer has enough balance, that the amount and validity window meet the requirement, that the token and network match, and then simulates transferWithAuthorization. Settlement calls that function with the signature and the authorization. The core specification adds that EIP-3009 contracts prevent nonce reuse on chain, which is what stops a replayed authorization.
Worked example: the specification's own payment
The PAYMENT-SIGNATURE example in the x402 V2 HTTP transport specification is a real signature. Settlens' verifier, run when this page was built, rebuilds the domain from the example requirement and recovers the payer:
| Value | Result |
|---|---|
| Domain | name "USDC", version "2", chainId 84532, verifyingContract 0x036CbD53842c5426634e7929541eC2318f3dCF7e |
| Domain separator | 0x71f17a3b2ff373b803d70a5a07c046c1a2bc8e89c09ef722fcb047abe94c9818 |
| Struct hash | 0x49ad644555c7bdf3ea4fc022a4940ae5589b230b1dde0065c5504fa19bbe39a4 |
| Digest | 0xf256992871671abcb27ff92885a7afa46218724e5fc0bac35d050115aa1d22e6 |
| Recovered signer | 0x857b06519e91e3a54538791bdbb0e22373e36b66, equal to authorization.from |
The authorization was valid for 65 seconds on February 27, 2025, between validAfter 1740672089 and validBefore 1740672154. Checked at a time inside that window, for example with the MCP tool verify_payment_signature and its now argument, every property holds:
{
"scheme": "exact",
"recovered": "0x857b06519e91e3a54538791bdbb0e22373e36b66",
"signatureValid": true,
"recipientBound": true,
"amountBound": true,
"fresh": true,
"verified": true,
"reasons": [],
"checkedAt": 1740672100
}
Checked with the CLI at a time on October 13, 2026 (--now; without it the CLI uses its clock), long after the window closed, the same file is rejected for that reason alone:
settlens verify-payment payment.json --now 2026-10-13T10:00:00Z
REJECTED exact EVM payment authorization
Recovered signer: 0x857b06519e91e3a54538791bdbb0e22373e36b66
Signature valid: true
Recipient bound: true
Amount bound: true
Within window: false (at 1791885600)
Reasons:
- authorization is outside its validAfter/validBefore window (EIP-3009 requires validAfter < now < validBefore).
Change only the network in the requirement to Base mainnet (eip155:8453) and the rebuilt domain has a different chainId. The digest changes, so the signature recovers a different, meaningless address. This is the cross-chain replay protection EIP-712 provides:
settlens verify-payment payment-base-mainnet.json --now 2026-10-13T10:00:00Z
REJECTED exact EVM payment authorization
Recovered signer: 0x46e5af7a18131f1bc0947cf44151ee84b58c92a9
Signature valid: false
Recipient bound: true
Amount bound: true
Within window: false (at 1791885600)
Reasons:
- Recovered signer 0x46e5af7a18131f1bc0947cf44151ee84b58c92a9 does not match authorization.from 0x857b06519E91e3A54538791bDbb0E22373e36b66.
- authorization is outside its validAfter/validBefore window (EIP-3009 requires validAfter < now < validBefore).
What can be verified offline, and what needs the chain
A signature check proves that the payer agreed to specific terms. It does not prove the payment will settle. The split matters when you decide what an offline verification, such as Settlens' verify-payment, can tell you.
| Property | Offline, no RPC | Needs chain state |
|---|---|---|
| Signature recovers to authorization.from | ||
| to equals the advertised payTo | ||
| value equals the advertised amount | ||
| Bound to the advertised token and chain | Yes, through the domain | |
| Inside validAfter and validBefore | Yes, against a clock | |
| extra.name and extra.version are the token's real domain | ||
| Payer balance covers value | ||
| Nonce not used yet (authorizationState) | ||
| Token implements transferWithAuthorization | ||
| Transfer settled |
Settlens' verify-payment covers the first five rows and stops there by design: it needs no RPC, wallet or key, and it never checks balances, nonce use or settlement. Read more on the Verify product page or in the CLI reference. To check one payment without installing anything, use the EIP-3009 signature verifier.
Security notes for implementers
- Reject the zero address.
ecrecoverreturns the zero address for a malformed signature; EIP-3009 requires rejecting it, otherwise funds of the zero address could be "authorized". - Prefer canonical signatures. EIP-2 made transaction signatures with an
svalue abovesecp256k1n/2invalid, but theecrecoverprecompile still accepts them, so each signature has a malleable twin. Settlens rejects high-s signatures. - Use short windows and random nonces. Anyone who obtains a signed authorization can submit it until
validBefore, even if the resource was never delivered. Generate each nonce randomly. - Check the domain hints. An offline check compares the signature with the domain the server advertised. If
extra.nameorextra.versionare wrong, the signature verifies offline and still fails on chain. - Contracts should receive, not transfer. EIP-3009 warns that a contract wrapping
transferWithAuthorizationcan be front-run from the mempool, and recommendsreceiveWithAuthorization, which checks that the caller is the payee.
Checklist
These points, and the server and facilitator behaviors around them, are part of the x402 security checklist.
Frequently asked questions
How is EIP-3009 different from an EIP-2612 permit?
An EIP-2612 permit approves an allowance with a sequential nonce, and a second transaction spends it with transferFrom. EIP-3009 moves the tokens directly in one call and uses random 32-byte nonces, so several authorizations can be pending at once.
Can the facilitator change the amount or the recipient?
No. to and value are part of the signed message. Changing either changes the digest, so the signature no longer recovers to the payer and the token rejects the transfer.
Does a valid signature mean the payment will settle?
No. Settlement also needs enough balance, an unused nonce, a token that implements EIP-3009 with the advertised domain, and a successful transaction. Those depend on chain state.
Why does x402 put the token name and version in extra?
They are part of the EIP-712 domain. Without them the client cannot build the digest to sign, and a verifier cannot rebuild it to check the signature without calling the chain.