A technical look at how a new Ethereum standard enables a payments primitive that has been out of reach: an invoice that is its own address, on every chain at once, that pays itself out the moment funds arrive.
The shape of the idea
Reconciliation, at its core, is the question: did this specific invoice get paid? Every system that processes payments has to answer it, and almost every system answers it by maintaining state — a database table mapping invoices to expected amounts, a webhook listener watching for incoming transfers, a cron job that retries stuck payments, a back-office tool that lets a human resolve the cases the automation got wrong.
ERC-8211 makes a different answer possible. It lets you collapse the entire question into a single, observable property of the chain itself:
One invoice = one address. If funds landed on the address, the invoice is paid. If they didn't, it isn't.
That sentence is the whole reconciliation system. There is no database. There is no payment-status enum. There is no "pending → bridged → settled → reconciled" state machine. There is an address, and there is a balance at that address. The address is the invoice. The balance is the answer.
This kind of design has been almost possible for years — counterfactual smart accounts give you an address on every chain before you deploy, and account abstraction gives you the means to spend from it. What was missing was the ability to say, in a single signed intent: "on whichever chain this address ends up funded, however much actually arrived, forward it to the merchant." Without that, you still need a watcher and a database to figure out which chain the money landed on and how much was really there.
ERC-8211 is the standard that closes the loop. It introduces two primitives — runtime parameter injection and fire-and-forget multi-chain dispatch — that compose with the ephemeral-key smart-account pattern to make "one address = one invoice" not a slogan but a working architecture.
The pieces, technically
There are four moving parts. Each one exists in some form independently. ERC-8211 is what lets them combine into a single coherent product.
1. The ephemeral key
Every invoice begins with a fresh cryptographic keypair, generated in-memory on the merchant's tab. This key has exactly one job: to sign the settlement intent for this one invoice. It never leaves the browser. It is never written to disk. It dies when the tab closes.
In pseudocode:
on issue invoice:
key = generate ephemeral keypair
invoice_address = derive smart-account address from key
show invoice_address to payer
The ephemeral key is what makes the rest of the design safe. If a long-lived key were used, every invoice address would derive from the same root, and a single key compromise would expose every invoice's funds. With an ephemeral key, the blast radius of a compromise is one invoice. And because the key is discarded the moment the settlement intent is signed and dispatched, even that blast radius narrows to a window measured in seconds.
The ephemeral key is also why the merchant doesn't custody anything. There is no wallet to connect, no signing device to plug in, no seed phrase to back up. The merchant generates a key, derives an address, and has no further responsibility for it. The address is, for the lifetime of the invoice, a black hole that funds enter and the merchant exits.
2. Counterfactual deployment: the same address on every chain
A smart-contract account address is a deterministic function of the deployer, the implementation, and the salt. If you fix all three across chains, the address is the same on every chain, before it has been deployed anywhere.
The merchant's invoice address is derived from the ephemeral key in exactly this way. The same string of characters identifies the invoice on Ethereum mainnet, on Arbitrum, on Optimism, on Base, and on Polygon. There is no "the Arbitrum invoice address" and "the Base invoice address." There is one address. It exists, mathematically, on every chain. It just hasn't been deployed yet.
This is the architectural keystone. Because the address is the same on every chain, the payer can send funds to it from whichever chain they prefer, and the merchant can ask one universal question — did anyone send money here? — without needing to know which chain to ask it on.
In pseudocode the property looks like this:
invoice_address ==
derive(key, mainnet) ==
derive(key, arbitrum) ==
derive(key, optimism) ==
derive(key, base) ==
derive(key, polygon)
It's the same address. Five chains. One invoice.
3. Runtime parameter injection
This is the first ERC-8211 primitive, and it is the one that makes "however much actually arrived" expressible.
In a traditional batched transaction, every parameter is a literal value, frozen at the moment the user signs. The amount to forward, the recipient, the allowance — all of it baked into the calldata. If the world moves between signing and execution (a bridge fee changes, a relayer fills with a slightly different amount, an oracle ticks), the batch either reverts or strands value.
ERC-8211 adds a second kind of parameter: a fetcher. A fetcher isn't a value; it's an instruction to read a value at execution time. The standard defines fetchers for the common cases that payments care about: read an ERC-20 balance, read an allowance, read a native balance, or — for anything outside that — call a view function on any contract and use what comes back.
Each fetcher carries constraints: comparators (>=, <=, ==) that the resolved value has to satisfy. The constraint is not advisory. It is a gate. If the value the fetcher reads doesn't satisfy the constraint, the call doesn't fire. The executor waits and retries until the constraint passes or the deadline expires.
For our invoice flow, the relevant fetcher reads the live USDC balance at invoice_address on the destination chain at the moment of execution:
forward step:
from: invoice_address on payout_chain
to: merchant_payout_address
token: USDC
amount: balance_of(USDC, invoice_address) at runtime
gate: amount >= invoice_amount_minus_acceptable_slippage
Two things to notice. First, the amount field isn't a number. It's a read. Whatever USDC has actually accumulated at the address — net of bridge fees, relayer behavior, fee drift — is what gets forwarded. The merchant receives exactly what arrived, not an estimate. Second, the gate is the reconciliation predicate itself: "fire only if there's enough money here to be a real payment." If the balance is short — because nobody has paid yet — the gate fails and the step does not fire. If the balance never arrives, the step never fires, and the deadline cleans up after it.
The runtime read isn't a workaround. It is the standard's way of expressing the truth that, in payments, the amount you can spend is the amount you actually have.
4. Fire-and-forget multi-chain dispatch
The second ERC-8211 primitive turns reconciliation from a tracking problem into a dispatch problem.
Once a step's execution can be gated on real-world state, you can author multiple batches in advance and dispatch all of them, knowing that only the relevant ones will fire. The other ones won't revert noisily; they won't leave residue. They will simply never satisfy their gates and will time out at their deadlines.
For the invoice flow, this manifests as: at settlement time, build one supertransaction per supported origin chain and dispatch them in parallel. Each one says, in effect: "if the funds arrived here, on this chain, do whatever it takes to get them to the merchant."
For the chain that matches the merchant's payout chain, the supertransaction has one step:
batch (same-chain):
forward USDC
from: invoice_address on payout_chain
to: merchant_payout_address
amount: balance_of(USDC, invoice_address) at runtime
gate: amount >= invoice_amount
For every other chain, the supertransaction has three steps:
batch (cross-chain, origin != payout_chain):
on origin_chain:
approve bridge to spend invoice_amount
deposit invoice_amount into bridge,
destination = invoice_address on payout_chain
on payout_chain:
forward USDC
from: invoice_address on payout_chain
to: merchant_payout_address
amount: balance_of(USDC, invoice_address) at runtime
gate: amount >= acceptable_amount_after_bridge_fees
All five batches are signed by the ephemeral key in one pass and dispatched simultaneously. From this point onward, the merchant tab has nothing to do. The ephemeral key, having served its purpose, is dropped.
What happens next is the elegant part. The payer pays from one chain — say, Arbitrum. The Arbitrum batch's gate ("did funds arrive at invoice_address on Arbitrum?") passes; the bridge step fires, then waits for the cross-chain fill, then the runtime balance read on the payout chain passes, and the forward step delivers the actually-received amount to the merchant.
The other four batches are still alive in the executor. They're watching their respective invoice_addresses on Mainnet, Optimism, Base, and Polygon. Those addresses have no balance. Their gates can never pass. When their deadlines arrive, those batches expire silently, leaving no on-chain residue.
There is no central watcher coordinating this. There is no service deciding which chain "wins." The standard's gating semantics resolve the question at the batch level, in parallel, with no shared state.
Why the composition is what matters
Each of the four pieces — ephemeral keys, counterfactual addresses, runtime injection, multi-chain dispatch — is interesting on its own. The reason they matter together is that they make a single observation true:
The presence of money at invoice_address is the entire payment record.Walk through what this implies:
Detection is implicit. You don't need a chain watcher to know an invoice was paid. The act of paying is the act of putting funds at the address. The act of putting funds at the address is what triggers the gate. The trigger is the detection.
Reconciliation is geometric. The merchant's question — "did invoice X get paid?" — reduces to a balance read at a known address. Any RPC node, any block explorer, any backend service can answer it without coordination with anything else. There is no "source of truth" to keep in sync because the chain is the source of truth, and the address is the index.
Recovery is a first-class concern. In production, the smart-account implementation behind invoice_address can be configured with a designated recovery address — a merchant-controlled wallet that retains the authority to sweep the account if funds ever end up stuck (a late payment after the ephemeral key was dropped, a payer sending to a settled invoice, an unexpected token landing at the address). The recovery path lives at the account level, independent of the ephemeral key's lifecycle, so funds can never be permanently orphaned. This is not part of the reference demo to keep the codebase minimal, but it's the production-grade configuration: ephemeral key for normal settlement, recovery address as the safety net.
Double-spend is impossible at the protocol level. An invoice can only be paid once because the forward step's gate is satisfied once. A second payment to the same address would arrive after the dispatched batches have either fired or expired, with no remaining intent authorized to spend from the address. The address is a one-shot mailbox.
Cross-chain doesn't fragment the abstraction. Because the address is identical on every chain, "paid on Arbitrum" and "paid on Base" are not two different states. They are the same state — paid — observed from different chains. The reconciliation model has no concept of origin. It only has an invoice and a payout.
The pseudocode of the whole thing
Stripped to its skeleton, the entire flow is a few dozen lines of intent.
ISSUE INVOICE:
key = generate ephemeral keypair in browser tab
invoice_address = derive smart-account address from key
return invoice_address, supported_chains
PAYER (out of band):
send USDC to invoice_address
on whichever supported chain is convenient
SETTLE INVOICE:
for each supported_chain:
if supported_chain == payout_chain:
batch = [
forward USDC
from invoice_address on payout_chain
to merchant_payout
amount: balance_of(USDC, invoice_address) at runtime
gate: amount >= invoice_amount
]
else:
quote = fetch bridge fee quote
batch = [
approve bridge on supported_chain,
deposit invoice_amount into bridge,
destination = invoice_address on payout_chain,
forward USDC on payout_chain
from invoice_address
to merchant_payout
amount: balance_of(USDC, invoice_address) at runtime
gate: amount >= invoice_amount - acceptable_slippage
]
sign batch with ephemeral key
dispatch batch
drop ephemeral key
RECONCILE:
is balance at invoice_address still zero on every chain?
not paid.
has the forward step fired anywhere?
paid.
Everything that was previously a service — the chain watcher, the payment-status database, the cross-chain coordinator, the dust sweeper, the stuck-payment recovery script — has been replaced by a property of the standard. The standard guarantees that the forward step fires when, and only when, funds are actually present at the invoice address. The merchant doesn't have to verify that; the executor does, by construction.
What 8211 specifically contributes
It's worth being precise about what part of this is new.
Counterfactual addresses have been around since CREATE2. Ephemeral keys are a pattern, not a protocol. Smart accounts and account abstraction are years old. Bridges have been working for a long time.
What ERC-8211 adds — and what no prior standard provided — is the ability to express conditional, state-dependent execution inside a signed batch, in a form that is portable across executors and doesn't require a custom on-chain contract per use case. Specifically:
- A signed batch can contain parameters that resolve at execution time, not signing time.
- Those parameters can be gated by constraints that the executor enforces.
- Those constraints can reference live on-chain state — balances, allowances, arbitrary view-function results.
- The same batch can span multiple chains, with later steps gated on the outcomes of earlier steps.
- A batch that cannot satisfy its constraints expires, instead of reverting noisily.
Strip those properties out and the invoice-as-address pattern collapses. Without runtime injection, the forward step's amount has to be a static guess, and you're back to padding for slippage and stranding dust. Without constraints, the forward step would fire whether or not money arrived, and you'd lose the gating that makes reconciliation a one-bit question. Without multi-chain dispatch, you'd have to choose a single chain in advance, and you'd be back to "please bridge first" UX. Without graceful expiration, you'd have to track and clean up the four batches that didn't fire.
ERC-8211 is the smallest set of additions to the batching model that makes all of those properties simultaneously expressible. That's why this product fits inside the standard, instead of around it.
The product in one sentence
A merchant generates an ephemeral key, derives an address, and shows that address to a payer. The payer sends USDC to it from any supported chain. The merchant signs five conditional batches — one per chain — that say "if there are funds at this address here, forward what's actually there to my treasury, on the chain my treasury lives on." Whichever chain the funds landed on, that batch fires; the others expire. The merchant's reconciliation is a single balance read at a single address, against a single number.
Everything between "the payer pays" and "the merchant receives" is the standard doing its job. The ephemeral key handles authority. The counterfactual address handles ubiquity. The runtime parameter injection handles the unknown-amount problem. The fire-and-forget dispatch handles the unknown-chain problem. The composition handles reconciliation by removing the need for it.
Read the standard at erc8211.com. One invoice is one address. The chain is the ledger. The standard is the system.