The agent wallet that does not exist yet: ERC-6492 in x402
An agent can pay for its first request from a wallet that has never been deployed. The signature carries the deployment instructions, and the facilitator executes them on the way to settlement.
The x402 exact scheme specification for EVM is explicit about what the client sends. The payload field is described as "the 65-byte signature of the transferWithAuthorization operation", and the facilitator's first verification step is to check that the signature "is valid and recovers to the authorization.from address". Both sentences describe an externally owned account. Sixty-five bytes is r, s, v. "Recovers" is ecrecover.
Now read the implementations. The x402-foundation/x402 repository ships ERC-6492 handling in Go, Python and TypeScript, across the exact scheme and the batch-settlement scheme. There is a dedicated erc6492.go, an erc6492_deploy.go, an erc6492.py, and test files for each. On 5 September 2026 two pull requests landed to optimise the path further. None of it appears in specs/schemes/exact/scheme_exact_evm.md.
That gap matters for anyone building agent infrastructure, because the counterfactual wallet is the single most useful account shape for an autonomous agent, and the rules governing it live in implementation code rather than in a normative document.
The wrapper: what an ERC-6492 signature actually is
ERC-6492, "Signature Validation for Predeploy Contracts", is Final. It was authored by Ivo Georgiev and Agustin Aguilar, created on 10 February 2023, and it requires ERC-1271.
The problem it solves is narrow. A smart contract account proves signatures through ERC-1271: the verifier calls isValidSignature(bytes32,bytes) on the account and accepts the answer if the account returns the magic value 0x1626ba7e. That call is impossible if the account has no code. But a CREATE2 account has a deterministic address before deployment. It can receive USDC, appear in a block explorer, and be handed to an agent as its identity, all while being an empty address.
ERC-6492 wraps the inner ERC-1271 signature together with the instructions needed to bring the account into existence:
// ERC-6492 wire format
abi.encode((address factory, bytes factoryCalldata, bytes signature)) + magicBytes
// magicBytes = bytes32(uint256(keccak256("erc6492.invalid.signature")) - 1)
0x6492649264926492649264926492649264926492649264926492649264926492
The suffix ends in 0x92, which is not a valid v value, so a wrapped signature can never be mistaken for a packed ECDSA signature. x402 carries the same constant in its EVM constants module, with the keccak derivation written out in a comment.
The parser in python/x402/mechanisms/evm/erc6492.py is deliberately total. parse_erc6492_signature checks the last 32 bytes; if the magic is absent it returns the original bytes as inner_signature with a zero factory and empty calldata. Every signature that enters the facilitator becomes an ERC6492SignatureData, wrapped or not. Two predicates then classify it: is_eoa_signature (65 bytes and zero factory) and has_deployment_info (non-zero factory and non-empty calldata).
This is the same shape as ERC-4337's initCode, relocated. In 4337 the deployment instructions ride in the UserOperation. In 6492 they ride in the signature, which means any verifier that accepts bytes can accept a counterfactual account without knowing anything about bundlers or entry points. That portability is why Base Account signatures include the ERC-6492 wrapper by default, so they can be verified before the wallet contract is deployed.
Routing by code, not by signature shape
The core of the audit is verify_universal_signature. Its docstring states the rule in one line: routing is determined by code.length, not by signature shape. The function always issues an eth_getCode against the payer, then branches.
code = signer.get_code(signer_address)
is_deployed = len(code) > 0
sig_data.code_deployed = is_deployed
if not is_deployed:
if has_deployment_info(sig_data):
# ERC-6492 counterfactual — defer to simulation/settle
return (False, sig_data)
if len(sig_data.inner_signature) == 65:
return (verify_eoa_signature(hash, sig_data.inner_signature, signer_address), sig_data)
return (False, sig_data)
# Has code (contract OR ERC-7702 delegation) — strict EIP-1271, no ECDSA fallback
return (verify_eip1271_signature(signer, signer_address, hash, sig_data.inner_signature), sig_data)
The comment above that get_code call is an artefact of a real production bug, and it is worth quoting because it explains why the unconditional read exists: "The old is_eoa_signature fast-path skipped this for 65-byte sigs, causing pre-verify to return valid for 7702 EOAs whose delegate rejects raw ECDSA on-chain." Skipping a network round trip for signatures that looked like EOA signatures was correct until EIP-7702 made a 65-byte signature and an address with code compatible facts. We audited that collision when it first surfaced in the 7702 smart-EOA signature path; the fix here is the same principle applied one layer down.
The counterfactual branch deserves attention because of what it returns. The function returns (False, sig_data) — not valid — and the docstring explains that this mirrors Go's (false, sigData, nil) and means "deferred to simulation/settle". The code then warns callers directly: anyone who checks valid == True to accept a payment must explicitly handle this case, because the payment is not valid until simulation confirms that the factory deploys the wallet and the transfer succeeds.
Verify: one eth_call that deploys and pays
Because the undeployed branch defers, something else has to decide. That something is a simulation, and it is more interesting than the usual eth_call.
When the classification carries deployment info, simulate_eip3009_transfer_result builds two calls and sends them through Multicall3 at 0xcA11bde05977b3631167028862bE2a173976CA11: first the factory call from the signature wrapper, then transferWithAuthorization against the token. One eth_call, one EVM execution context, state carried from the first sub-call into the second. Success is defined narrowly — the code checks results[1].success, the transfer, and surfaces the transfer's decoded revert when it fails.
This mirrors what ERC-6492's own universal validator contract does on-chain, where deployment requires a CALL rather than a STATICCALL and the reference implementation reverts to discard side effects. x402 gets the same isolation for free by never sending the simulation as a transaction.
The result is a strong pre-check. It answers the only question that matters: if this factory runs and then this authorization is submitted, does the money move? A wallet whose validator set only exists after deployment is evaluated in its deployed state, not its empty one.
Settle: two transactions, no atomicity
Settlement does not preserve that property. The flow in exact/facilitator.py is:
verify_result, classification = self._verify(payload, requirements,
simulate=self._config.simulate_in_settle)
...
if has_deployment_info(sig_data):
if not sig_data.code_deployed:
factory_addr = bytes_to_hex(sig_data.factory)
if factory_addr.lower() not in allowed:
return SettleResponse(success=False, error_reason=ERR_FACTORY_NOT_ALLOWED, ...)
self._deploy_smart_wallet(sig_data) # tx 1: factory call, wait for receipt
tx_hash = execute_transfer_with_authorization(...) # tx 2: the actual payment
Two separate transactions, sequenced by a receipt wait. The Go helper SendFactoryDeployTransaction is explicit about the guard it does apply: it waits for the receipt and returns an error if receipt.Status != TxStatusSuccess. What neither implementation does is re-simulate after the deploy, and the reasoning is documented at length in the source: a standalone eth_call issued right after a real deploy transaction can race state propagation across load-balanced RPC nodes, which was producing false "inner signature unsupported" rejections for wallets that were in fact fine. The comment names Coinbase Smart Wallet as an example. The on-chain transferWithAuthorization is treated as the definitive check instead.
That is a defensible engineering decision — a false rejection after a successful deploy is worse than an honest revert — but it changes the guarantee. The simulation proved that deploy-then-transfer succeeds atomically. Execution performs deploy-then-transfer sequentially, across two blocks, with an arbitrary gap. Anything that can change between them is outside the proof: the authorization nonce can be consumed elsewhere, the payer's balance can move, validBefore can expire.
There is a second, larger gap. The simulate flag passed by settle is self._config.simulate_in_settle, and that field defaults to False. The atomic Multicall3 simulation runs during the /verify endpoint by default, not during /settle. In a typical deployment those are two separate HTTP calls, made by the resource server at two different moments. The "single authoritative pre-check" the settle path relies on may have happened seconds or minutes earlier, against a different chain state. The design of the facilitator's verify and settle endpoints assumes that separation everywhere; the counterfactual path simply inherits more consequence from it.
The exposure is asymmetric but bounded. Funds cannot be misdirected: transferWithAuthorization encodes to and value inside the signed EIP-712 message, and the facilitator remains a broadcaster with no ability to alter either, which is the whole point of the EIP-3009 primitive. What is exposed is gas. If the deploy lands and the transfer reverts, the facilitator has paid for a smart account deployment and collected nothing.
The allowlist is the entire security model
An ERC-6492 wrapper is, structurally, an instruction telling the facilitator to send a transaction to an address of the payer's choosing with calldata of the payer's choosing, signed by the facilitator's key and paid from the facilitator's balance. Stated that way, the risk is obvious.
x402 gates it with one configuration field. The docstring in the scheme config is unusually direct:
Allowlist of factory contract addresses. A non-empty list enables ERC-4337 smart wallet deployment via EIP-6492. An empty list (the default) denies all factory deployment calls. Facilitators must explicitly list every factory they trust to prevent arbitrary transaction injection via attacker-controlled ERC-6492 signatures.
Three properties are worth noting. The default is deny — a facilitator that never configures the field cannot be induced to deploy anything. The check is enforced twice, in verify and again in settle, with a comment explaining why: verify must not pass a payment that settle will reject. And the failure is a distinct error code, eip6492_factory_not_allowed, rather than a generic invalid-signature response, alongside invalid_exact_evm_payload_undeployed_smart_wallet and smart_wallet_deployment_failed. Go's IsFactoryAllowed repeats the same case-insensitive comparison so the routing is identical across SDKs.
The allowlist bounds what can be deployed. It does not bound how often. A factory on the list, called with calldata that produces a valid CREATE2 address and a wallet whose deployed validator then rejects the inner signature, costs the facilitator a deployment and yields no payment. The simulation is what stops this in practice, since the same wallet would fail the atomic eth_call — but only when the simulation actually ran, and only if chain state has not moved since. ERC-6492's own security section notes the property that keeps this from becoming worse: because addresses are CREATE2-derived, changing the factory calldata changes the deployed address and therefore breaks verification. An attacker cannot swap in arbitrary bytecode and keep the same payer address.
What the support matrix admits
x402 documented all of this on 25 June 2026 in a wallet compatibility page that classifies payers into five types: plain EOA, deployed smart account, ERC-6492 counterfactual, 7702 with a permissive delegate, and 7702 with a strict delegate. The matrix is honest about where the counterfactual case fails.
It works for exact with EIP-3009, and for batch-settlement ERC-3009 deposits, in both cases only with a configured factory allowlist. It does not work for any Permit2 flow — exact via Permit2, upto, or Permit2 deposits. The reason is structural: permitWitnessTransferFrom calls isValidSignature on the payer at settlement, and the Permit2 path never deploys the wallet first, so the call reverts against an address with no code. Permit2 has no counterfactual mechanism. An agent using the metered upto scheme must have a deployed wallet, full stop.
The page also documents the failure that survives the allowlist. If a wallet installs its verifying validator lazily rather than during the factory call — the shape used by some ERC-7579 and Kernel session-key setups, which deploy with only a root validator — then the deployed wallet can reject the inner signature and the transfer reverts. The wallet now exists, so a retry usually settles, because a deployed wallet can produce a standard ERC-1271 signature. The first payment is the tax.
One more item from the ERC itself belongs in any threat model. Counterfactual signatures can be validated on a different network as long as the signature was valid at deployment time and the wallet can be deployed with the same factory address and bytecode there. In x402 the payment itself is bound by the EIP-712 domain, which pins chainId and the token's verifyingContract, so a payment authorization does not replay across chains. But the deployment instruction inside the wrapper is chain-agnostic. A facilitator operating on many networks is holding a portable instruction to deploy an account, gated only by its own allowlist.
The optimisation that prompted this audit
The two pull requests merged on 5 September 2026 are small and reveal the shape of the problem. Both remove a redundant eth_getCode: verify now returns its signature classification so that settle can reuse the payer's deployment status instead of asking the chain a second time. The changeset explains the constraint that made this safe rather than dangerous — both reads happen within one settle call and before any deploy transaction, so this is not the post-deploy re-read that races RPC state propagation.
That is the same hazard the "do not re-simulate" comment guards against, appearing in a different place. A facilitator that touches the chain after it has changed the chain gets inconsistent answers from load-balanced RPC. The counterfactual path is the only x402 flow where the facilitator deploys a contract mid-settlement, which is why it accumulates these rules.
What it means for LLM4Agents
The counterfactual wallet is the correct default account shape for an autonomous agent, and this is the mechanism that makes it usable.
Consider the onboarding sequence without it. Create an agent, derive a smart account address, fund it with USDC, then send a deployment transaction — which requires native gas at that address, from an account that was supposed to be gasless. The agent cannot pay for its own existence with the stablecoins it holds. Every workaround (a paymaster, a sponsored bundler, a manual top-up) adds a dependency before the agent has done anything. We walked through that dependency chain in the account abstraction piece; ERC-6492 removes it for the specific case of the first payment.
With it, the sequence collapses. Derive the address, fund it with USDC, sign an EIP-3009 authorization wrapped with the factory calldata, send the request. The facilitator deploys the account and moves the money. The agent has never held native gas and never sent a transaction.
For the LLM4Agents gateway this cuts two ways. On the buyer side it is a straightforward win: an agent registered on the platform can be issued an address it can be funded at immediately, and its first paid inference is also its wallet's first block of existence. On any facilitator surface we operate, it is a liability that has to be configured. The default deny is right. Turning it on means naming specific factories, accepting that we pay deployment gas out of band with no field in the x402 payload to charge it back, and accepting that a wallet with a lazily installed validator can burn that gas without producing a payment.
The wider point is about where the rules live. A resource server reading only the specification would implement ecrecover against a 65-byte signature and reject every smart account and every counterfactual wallet — the majority of agent wallets that matter. The behaviour that decides whether a payment works is in three SDK implementations and one documentation page, not in the normative text. Anyone integrating x402 from the spec alone is integrating the wrong protocol.
Staying on the frontier
Concrete steps, in order.
First, treat verify's boolean as three-valued. Any code path we own that consumes a facilitator verification must distinguish valid, invalid, and deferred. Collapsing "not yet" into "no" is the single highest-frequency integration bug in this area, and it manifests as smart-wallet users being told their signature is malformed.
Second, decide the factory allowlist deliberately. If we run a facilitator surface, the allowlist should contain the factories behind the wallets our agents actually use, verified by reading the factory contract, and nothing else. Every entry is a standing authorisation to spend our gas on a payer's instruction. The list belongs in reviewed configuration, not in an environment variable someone can widen.
Third, instrument the counterfactual path separately. Deployment transactions, their gas cost, and the rate at which a deploy is followed by a reverted transfer are a distinct metric from settlement success. That ratio is the early warning for the lazily-installed-validator failure, and it is the number that tells us whether sponsoring deployments is economically sane at our volumes.
Fourth, set simulate_in_settle honestly. The default of False is a latency optimisation that assumes verify ran recently against comparable state. For counterfactual payers specifically, where settle will send a real deployment transaction, re-running the atomic simulation at settle time is worth the extra eth_call. Measure it, then choose.
Fifth, keep counterfactual wallets away from Permit2 flows. Routing is not a runtime discovery problem; it is a precondition. If an agent's wallet has no code, EIP-3009 is the only asset transfer method that can work, and any client library we ship should refuse the Permit2 path rather than emit a payment that reverts on-chain.
Sixth, push the behaviour upstream. The compatibility matrix is documentation, and documentation is not what other facilitators implement against. The counterfactual path — the wrapper, the allowlist requirement, the deferred verification state, the error codes — belongs in the exact scheme specification. Until it is there, every facilitator implementation is a slightly different protocol, and agents will discover the differences by having payments fail.
Pay from a wallet that does not exist yet
OpenAI-compatible inference, settled per call in stablecoins.
Register an agent