DEX Scanner Adapters: Quotes, Capabilities and Execution

한국어로 읽기

TECH INSIGHT / EXPLAINED

The adapter pattern lets one scanner reason about different DEX protocols without pretending that their quote and execution mechanics are identical. Scaling to dozens of venues is an architectural target here, not a verified claim that 24 production integrations were operated.

Three adult colleagues explain dex adapters
AI-generated teaching illustration, not an actual operating screen or performance record.

Local execution of the actual project

These captures show the existing project UI or functions executed locally with non-private sample inputs. Each caption states the scope of the execution.

The real project adapter-list dialog running locally. Entries are DEMO responses illustrating adapter types, not evidence of connected DEXs or executable routes.
The real project adapter-list dialog running locally. Entries are DEMO responses illustrating adapter types, not evidence of connected DEXs or executable routes.

Make capabilities part of the contract

A quote should carry chain ID, pool address, input/output token, raw integer amount, fee model, block number/hash, expected output and expiry. Execution additionally requires minimum output, recipient, deadline and allowed spender/callback policy. A protocol may support off-chain quoting but not an on-chain view quote. Model that capability explicitly instead of forcing every implementation into the same Solidity view function.

QuoteRequest { chain_id, pool, token_in, token_out,
               amount_in_raw, block_hash }
QuoteResult  { amount_out_raw, protocol_version,
               fee_model, block_hash, expires_at }
Execution    { route_id, amount_in_raw, min_out_raw,
               recipient, deadline }

V3 is the useful counterexample

The original Uniswap V3 Quoter invokes swap logic and uses reverts to obtain a result; its quote methods are not ordinary view functions even when called off-chain with eth_call. Calling it under a Solidity STATICCALL is a different operation. Off-chain tick simulation is also possible with complete state and correct integer math. V2 reserves cannot be used as an approximation that reliably ranks unrelated V3 pools.

Registration is a trusted change

Adding a name to a map is not enough. Verify factory/pool identity, deployed code, token order, chain ID, supported fee tiers and callback authorization. An adapter may receive approvals or move assets, so registry changes need authenticated governance, change history and a disabled-until-tested state. Do not make an arbitrary adapter address callable just because an owner registered it.

Compare a size-specific quote and its execution

For a pool with 1,000 input reserve, a “at most 2%” size guard accepts 20 input units, but this does not promise a maximum price impact across all curve families. For V3, active liquidity changes at tick boundaries; for stable curves, the curve parameters matter. Test each adapter on fixed-block fork fixtures, compare predicted net receipts with actual balance deltas, and include token order, decimals, depleted liquidity, expiry and minimum-output rejection cases.

A number cannot describe every adapter failure

If every quote error becomes amountOut=0, a scanner cannot distinguish an unsupported capability from a calculation that actually returns zero. Represent outcomes such as quoted, unsupported, no_route, stale_state, and rpc_error separately, and read an output amount only from quoted. These names are an application design example, not a protocol standard.

An exact-output request sent to an adapter without that capability should return unsupported. A timed-out RPC request should not become an empty route, which suggests absent liquidity. Their retry policies also differ: a temporary transport problem may justify a bounded retry, whereas repeatedly calling an unsupported operation cannot add the missing capability.

Make the cache key describe the complete question

Even for one pool, changing the amount, direction, exact-input/output mode, fee tier, or price limit creates a different question. Include the adapter version and state block hash in the cache key, and verify that the result matches the requested hash. Using the UI string “1” alone can confuse 1,000,000 raw units for a 6-decimal token with 10^18 units for an 18-decimal token.

EIP-1898 defines block-hash selection and requireCanonical for supported RPC calls. If a provider lacks that capability, expose the limitation. Silently falling back to latest after a failed request can make competing quotes refer to different states. Giving them the same expiry timestamp does not repair that mismatch or make their prices comparable.

Validate the callback against the active execution

Knowing that a callback came from a recognized pool should not authorize every payment request. At execution start, record the expected pool, input token, maximum payment, and route identifier. Validate the callback against that execution. An idle → executing → callback → settled lifecycle makes it clear where to reject callbacks that arrive without an active execution or after settlement.

V3 CallbackValidation verifies the pool address derived from the factory, tokens, and fee. Combining that identity check with application payment limits and execution state is a separate design responsibility. Do not blindly trust an amount supplied in callback calldata or treat the application's entire balance as the authorized payment budget.

Distinguish Quoter lock failures from ordinary read failures

The original V3 pool sets unlocked to false during a swap. Calling a Quoter against that same pool from its active swap callback can therefore fail with LOK. A quote that succeeds in an independent off-chain call is not evidence that it will work inside the callback. The execution sequence in the pool implementation explains the difference.

This does not mean every other pool is locked. It is also separate from the distinction between eth_call and STATICCALL. Include call location in the test specification and separate same-pool reentry from calls to another pool. The independent Python fixture only checks that changed amounts or block hashes produce distinct cache keys and that a settled execution cannot reenter the callback state. Actual EVM callback and lock behavior requires a separate integration experiment.

Documentation and next steps

Content and examples reviewed: 2026-10-01

댓글