IAMUVIN

Web3 Development

Building an ERC-7683 Solver: Four Calls, Three Risks

Uvin Vindula·September 15, 2026·15 min read
Share

TL;DR

ERC-7683 gives you four calls and nothing else. On the origin chain, IOriginSettler exposes resolve(), resolveFor(), open() and openFor(). On the destination chain, IDestinationSettler exposes a single fill(). That is the whole solver surface, and the spec at erc7683.org is live across Across, UniswapX, Eco, LI.FI and Symbiosis. What the standard does not give you is a risk model. It leaves three things entirely to the implementer: you are exposed to a destination-chain reorg between the moment you fill and the moment you are repaid; the gap between fillDeadline and real origin-chain finality is yours to carry, and on Arbitrum One that window is 6 days 8 hours of challenge plus a 2-day execution delay against a fill that Across completes in about 2 seconds; and cross-chain inventory rebalancing is the actual operating cost of a solver, which is why solver sets stay small. Pair that with the volume: CoW Protocol did $4.276B in January 2026 and $2.400B in July 2026, a 44% fall — part of it a deliberate consequence of the volume fees CoW introduced during 2026 to purge low-value flow. This is a maturing interface on a market that is smaller and more fee-selective than the category narrative suggests.


How to Build an ERC-7683 Solver: The Four Calls

Start with the shape, because the shape is smaller than the explainers suggest.

A cross-chain intent under ERC-7683 has two participants who write code. The user signs an order off-chain. You, the solver, submit it, fill it on the destination chain, and claim repayment on the origin chain. There is no bridge in that sentence. You front the user's funds out of your own inventory on the destination chain and get made whole on the origin chain afterwards. Every risk in this article comes from the word "afterwards".

The spec defines two interfaces. Here is the full surface, with parameter names as the spec gives them:

text
IOriginSettler
  resolve(order)                              -> ResolvedCrossChainOrder
  resolveFor(order, originFillerData)         -> ResolvedCrossChainOrder
  open(order)
  openFor(order, signature, originFillerData)

IDestinationSettler
  fill(orderId, originData, fillerData)

Check the exact Solidity types against the spec before you compile — this table is the call graph, not a header file.

Your loop is four steps:

  1. Read the order and price it. Call resolve() or resolveFor() against the origin settler. This is a view call. It returns a ResolvedCrossChainOrder, which is the only place in the standard where your liabilities and your receipts are written down explicitly.
  2. Open it on the origin chain. For a user-signed order you call openFor(order, signature, originFillerData) and submit the user's signature yourself. The user pays no gas on the origin chain. For an order the user already opened themselves, open(order) was their call, not yours, and you skip this step.
  3. Fill on the destination chain. Call fill(orderId, originData, fillerData) on the destination settler, once per destination leg. Your capital leaves here.
  4. Settle. The origin settler repays you. This step is not in the interface, and that is deliberate — the standard leaves settlement to the implementation, which is why Across, UniswapX and Eco all settle differently.

Notice which of those four steps carries your money and which carries your proof. Step 3 spends. Step 4 repays. The interval between them is unpriced by the standard and is where a solver either makes a spread or loses inventory.

Why the two-struct split exists

There are two order structs because there are two ways a user can enter.

GaslessCrossChainOrder is the one that matters commercially. The user signs it off-chain and never touches the origin chain. Its fields are originSettler, user, nonce, originChainId, openDeadline, fillDeadline, orderDataType and orderData. Two deadlines, not one: openDeadline bounds how long you may sit on a signature before submitting it, and fillDeadline bounds how long you have to deliver on the destination side.

OnchainCrossChainOrder is the self-service path. The user has already transacted on the origin chain, so there is no signature to relay and no openDeadline to enforce. Its fields are only fillDeadline, orderDataType and orderData.

orderDataType and orderData are the extension point. The standard does not tell you what a swap, a bridge or a multi-leg route looks like — it gives you a type tag and an opaque blob, and every settlement system fills them differently. That is the honest reading of "ERC-7683 is a standard": it standardises the envelope and the settlement handshake, not the order semantics. If you support three settlement systems, you are decoding three orderData formats.


resolve() Is the Only Call That Prices Your Risk

resolve() returns a ResolvedCrossChainOrder. Three parts of it decide whether you take the order:

FieldWhat it tells you
Output[] maxSpentThe upper bound of what you will pay out, per destination chain. This is your liability.
Output[] minReceivedThe floor of what you will be repaid on settlement. This is your receipt.
FillInstruction[]One instruction per destination leg: where to call, and with what. This is your execution plan.

maxSpent minus minReceived, converted at your own marks, is your gross spread. That number is not your profit. Subtract destination gas, origin gas for openFor, the cost of capital locked for the settlement interval, and your rebalancing cost, which I get to below. On most routes that residual is a few basis points, and on a bad route it is negative.

Two implementation notes that cost people money.

First, maxSpent is a maximum, not a quote. Treat it as the worst case you have committed to, and re-derive your own expected fill cost independently. If your pricing engine trusts maxSpent as the fill amount, you have handed the order originator a free option on your inventory.

Second, resolve() is a view call on the origin chain against origin-chain state. fill() executes on the destination chain against destination-chain state. Nothing in the standard keeps those two states consistent. A price that was true when you resolved can be false when you fill, and on a chain with 2-second fills you will not notice the difference by reading logs afterwards.


Risk One: Destination-Chain Reorg Between Fill and Settlement

This is the risk the explainers skip entirely.

You call fill() on the destination chain. Your tokens move. The origin settler then repays you on the basis that the fill happened. If the destination chain reorgs your fill transaction out after the origin chain has already acted on it, you have paid twice or you have been repaid for a fill that no longer exists. Which of those two happens depends on the settlement design, not on the standard — ERC-7683 says nothing about it.

Three concrete mitigations, in the order I would apply them:

  • Wait for destination confirmations before letting settlement fire, and make the wait a function of the chain, not a constant. A single global "wait 12 blocks" is wrong on a chain whose force-inclusion path through L1 can take 12 to 24 hours depending on the stack, and wasteful on one where it is not.
  • Bound your exposure per destination chain, not per order. Reorg risk is correlated across every order you are carrying on the same chain at the same moment. A per-order limit does not cap a correlated loss.
  • Make your fill idempotent at the orderId level. fill() takes an orderId. If your bot retries after an RPC timeout and the first fill later lands, you want the second one to revert, not to double-pay.

The reason this is not in most write-ups is that it never bites on a happy path and it is invisible in a testnet integration. It bites once, on a chain you added last month because the route looked profitable.

I go through the wider category of cross-chain trust assumptions in cross-chain applications on Layer 2, and the stage framework that tells you how much a given chain's "confirmed" is worth in what Stage 1 and Stage 2 actually mean.


Risk Two: The Finality Gap Between fillDeadline and Origin Settlement

Here is the number that makes this concrete.

Across advertises "~2 second fills on mainnet and support for 23 chains" in its own documentation. Now look at what "settled" means on the chains you are filling into and out of, measured on L2BEAT's risk table on 14 September 2026:

ChainState validationChallenge windowExecution delay
Arbitrum OneInteractive fraud proofs (BoLD)6 days 8 hours2 days
OP MainnetFraud proofs3 days 12 hours3 days 12 hours
BaseFraud proofs (1R, ZK)5 daysNone listed; exit window: None

Two seconds on one side. Eight days on the other. That interval is the finality gap, and the standard hands it to you with no guidance.

It matters in two directions.

If you fill fast and settle against an optimistic rollup's proven state, your capital is locked for the challenge window. Your return on that route is your spread annualised over the days your inventory is unavailable — spread multiplied by 365 and divided by the lock, not the spread on its own. A 5 basis point spread over an 8-day lock is not the same business as 5 basis points over 2 seconds, and a solver that prices both the same way is choosing the wrong routes on purpose.

If instead you settle against unproven state to free your capital faster — which most production systems do, because nobody can run an 8-day capital cycle profitably — then you have taken on the rollup's liveness and upgrade risk directly, and you should know what that risk is. Base's L2BEAT risk row on 14 September 2026 reads Exit Window: "There is no window for users to exit in case of an unwanted upgrade since contracts are instantly upgradable. Upgrades need to be approved by 2 parties: the Base Coordinator Multisig and the Base Security Council." Check it before you rely on it — the approving parties change. Base is Stage 1 and holds $14.679B of the $42.05B total value secured across all tracked L2s, 35% of the entire category. It is also a chain where a two-party multisig can change the contracts under your settlement with no exit window. Both of those are true, and a solver needs to hold them at the same time.

Set your fillDeadline handling from this, not from a default. If you cannot reach destination confirmation plus your own safety margin before fillDeadline, do not open the order.


Risk Three: Cross-Chain Inventory Rebalancing

This is the real cost of running a solver, and it is the reason solver sets stay small.

Every fill moves your inventory in one direction. You hold USDC on Base, a user wants USDC on Arbitrum, you pay out on Arbitrum and get repaid on Base. Do that a hundred times in the direction the flow is running and you have too much on Base and nothing on Arbitrum. Your inventory does not rebalance itself. You rebalance it, and you pay for the privilege — a canonical bridge withdrawal at optimistic-rollup speed, a third-party bridge at a fee, or a market trade at a spread.

Three consequences that shape the whole business:

  • Flow is directional, so rebalancing cost is not symmetric. The popular direction is the one you run dry in. Your true spread on that route is the quoted spread minus the amortised cost of getting inventory back.
  • Your capital requirement is set by the worst imbalance you tolerate, not by your average volume. This is why the barrier to entry is capital, not code. The four calls above are a weekend. Funding eight chains deeply enough to quote competitively on all of them is not.
  • Gas is now a rounding error and rebalancing is not. Execution gas for a 21,000-gas transfer on the major L2s runs from a small fraction of a cent to a few cents depending on the chain and the moment — check l2fees.info before you price a route. Execution cost also excludes the L1 data fee that OP-stack chains add per transaction, so treat any execution-only figure as a floor. Even so: when a fill costs a hundredth of a cent in gas and moving inventory home costs basis points, the optimisation target is not gas.

That last point inverts what most solver tutorials optimise. I cover why L1 data costs stopped being the binding constraint in Fusaka, blobs and rollup economics.

The rebalancing question to answer before you write code

Write down, per route you intend to quote: expected daily net flow in the dominant direction, the cost to move that amount back, and the frequency at which you will do it. If the amortised cost of that round trip is larger than your spread on the route, you are not a solver on that route, you are a bridge subsidy.


Consuming Intents Is Trivial. Supplying Capital Is Not.

There is a split here that almost every article blurs, and it decides whether this standard is relevant to you at all.

If you are an application developer who wants cross-chain transfers inside your product, you do not implement any of the above. On Across, the integration path is three steps: request a route from the Swap API with your transfer parameters, execute token approvals, send the swap transaction. Funds arrive in about 2 seconds. That is a REST call and two transactions. You never touch IOriginSettler.

If you want to be the one earning the spread, you run a relayer against the SpokePool contracts, and that is a different project entirely — capital, inventory management, reorg policy, monitoring, and a pricing engine that is right more often than the other solvers' engines.

The interface being standardised makes the first job trivial. It does not make the second job easier, because the second job was never about the interface. If someone tells you ERC-7683 lowered the barrier to becoming a solver, ask them which of the three risks above the standard priced. The answer is none of them.


The Volume Reality: A Maturing Interface on a Shrinking Market

Intents are described everywhere as a growth category. The measurements point the other way.

CoW Protocol is one of the largest MEV-protected intent DEXes and publishes enough on-chain data to check. Read the series below with one caveat: CoW introduced volume fees during 2026 that, on its own Q1 reporting, deliberately purged low-value flow, so part of the fall is a pricing decision rather than lost demand. Monthly volume through 2026, from the community Dune dashboard:

Month (2026)Volume
January$4.276B
February$4.566B
March$3.831B
April$3.755B
May$2.358B
June$3.135B
July$2.400B

Across the seven complete months in that series, volume fell 44%, from $4.276B in January to $2.400B in July, while the category is still described as processing billions in monthly volume and growing. Cumulative figures for context: about $197.9B of volume since April 2021 and about $898.3M of cumulative trader surplus. These come from a community dashboard rather than CoW's own reporting, and the August 2026 month is partial, so treat the series as medium confidence — but the shape across seven complete months is not a rounding artefact.

The wider backdrop is the same shape. Total DeFi TVL fell from $114.4B on 1 January 2026 to $88.6B on 13 September 2026, a 23% decline, against ETH falling from $2,941 to $2,505 over the same window. Splitting that decline between price and capital leaving would need the asset mix of the remaining TVL, which the headline figure does not give you, so I am not putting a number on it.

One thing worth separating: "Uniswap Auctions" on DefiLlama is not a renamed UniswapX. It is Continuous Clearing Auctions, the token-distribution and liquidity-bootstrapping protocol Uniswap shipped into its web app in June 2026, so a $0 volume reading on that slug tells you nothing about UniswapX. Uniswap still documents UniswapX as its live intent-based, MEV-protected swap protocol at x.uniswap.org.

None of this means do not build. It means size the opportunity from measured volume rather than from a category narrative, and be honest that you are entering a market that got smaller through 2026, not larger. If you are weighing an intent-based design against a conventional pool, the structural comparison is in decentralized exchange architecture.


Where the Inventory Model Might Be Going: 1inch Aqua

The inventory problem is structural, so the interesting designs are the ones that attack it directly.

1inch shipped Aqua as a shared-liquidity layer in which, in the default non-custodial configuration, capital stays in the user's wallet — 1inch notes that custodial and shared-balance setups can also be built on it. The same capital backs multiple strategies at once through atomic pull and push execution — "nothing moves until both sides of the transaction are complete" — so assets sit in the owner's wallet rather than in a pool. The deployed contracts are Aqua at 0x499943e74fb0ce105688beee8ef2abec5d936d31 and SwapVM at 0x8fdd04dbf6111437b44bbca99c28882434e0958f, with a TypeScript SDK, published repositories and bounties up to $100,000. DefiLlama showed 1inch Aqua at $105.2M of 24-hour volume and $1.59B over 30 days on 14 September 2026, with a one-month change of +1,074%. The 24-hour figure is roughly double the 30-day run rate, so read it as a spiky snapshot rather than a level.

Read that one-month figure carefully. A four-figure percentage change on a young protocol is a base effect, not a trend, and the volume numbers are medium confidence. What is worth your attention is the mechanism rather than the number: if capital can back several strategies without being locked into any of them, the capital requirement that keeps solver sets small gets smaller. Whether that survives contact with adversarial flow is not yet answerable from public data.


A Pre-Capital Checklist

Before you put real inventory behind a solver, have an answer to each of these. If you cannot answer one, that is the thing that will cost you.

  1. Which settlement systems' `orderData` formats do you decode, and what happens when one adds a field? You are decoding an opaque blob per system. Version it.
  2. What is your per-destination-chain reorg wait, and who signs off on changing it? Not a global constant.
  3. What is your maximum simultaneous unsettled exposure per destination chain? Correlated risk needs a correlated cap.
  4. Do you settle against proven state or unproven state, and have you written down the risk you accepted by choosing the faster one? On Arbitrum One that choice is worth 6 days 8 hours plus a 2-day execution delay of capital efficiency.
  5. Is `fill()` idempotent on `orderId` in your bot as well as in the contract? Retries are the normal case, not the exception.
  6. What is the amortised rebalancing cost per route, and is it inside your spread? If you have not measured it, your profit and loss is a guess.
  7. What is your kill switch, and how fast is it? Every solver eventually quotes something wrong.

MEV exposure sits underneath all seven. Your fills and your rebalancing transactions are both visible before inclusion, and block building on Ethereum is more concentrated in 2026 than most people think — I go through the measurements in MEV and restaking in 2026, and the defensive patterns in front-running and MEV protection.

If you are also designing the user-facing account that signs these orders, the account abstraction stack matters more than the intent standard does — I measured what is actually in use in account abstraction, measured on-chain.


Key Takeaways

  • The solver surface is four calls. resolve() and resolveFor() to price, open() or openFor() to start, fill() on each destination leg, then a settlement step the standard deliberately leaves to the implementation.
  • `ResolvedCrossChainOrder` is where your risk is written down. Output[] maxSpent is your liability, Output[] minReceived is your receipt, FillInstruction[] is your execution plan. Never treat maxSpent as a quote.
  • The finality gap is the number nobody quotes. Across fills in about 2 seconds across 23 chains; Arbitrum One's BoLD challenge window is 6 days 8 hours plus a 2-day execution delay, measured on L2BEAT on 14 September 2026.
  • Base is Stage 1 with Exit Window: None. It holds $14.679B of $42.05B total L2 value secured and its contracts are instantly upgradable behind the Base Coordinator Multisig and the Base Security Council.
  • Rebalancing is the business, not gas. Execution gas for a 21,000-gas transfer on the major L2s is a fraction of a cent to a few cents; moving inventory back across chains costs basis points.
  • The market shrank in 2026. CoW Protocol went from $4.276B in January to $2.400B in July, a 44% fall, though CoW's own Q1 reporting attributes part of that to volume fees introduced during 2026 that deliberately purged low-value flow. Total DeFi TVL fell 23% from $114.4B to $88.6B over the same period.
  • Consuming intents is a REST call. Supplying capital is a capital business. The standard made the first trivial and changed nothing about the second.

About the Author

I'm Uvin Vindula — a Web3 and AI engineer based between Sri Lanka and the UK. I build and audit production smart contracts and cross-chain systems. The numbers in this piece come from named public sources — the ERC-7683 spec, Across's own documentation, L2BEAT, a community Dune dashboard and DefiLlama — each linked where it is used, so you can check them yourself. You can see my work at iamuvin.com or reach out about a project at hello@iamuvin.com.

If you are designing a cross-chain settlement path and want the risk model written down before the capital goes in, let's talk about your project.

Working on a Web3 or AI project?

Share

More in Web3 Development

All Web3 Development articles
Uvin Vindula

Uvin Vindula

Web3 and AI engineer based in Sri Lanka and the UK. Author of The Rise of Bitcoin. Founder of ASI Research Labs. Director of Blockchain and Software Solutions at Terra Labz. Founder of uvin.lk — Sri Lanka's Bitcoin education platform with 10,000+ learners.