Skip to main content

Architecture Overview

Geode is a single smart contract (GeodeHook.sol, ~1,190 lines) that implements the Uniswap v4 IHooks interface. It intercepts pool activity via the beforeSwap callback and provides batch settlement through a separate entry point. Hook flags: BEFORE_SWAP | BEFORE_SWAP_RETURNS_DELTA The hook has four immutables set at deployment:
ImmutablePurpose
poolManagerUniswap v4 PoolManager — the core swap and liquidity engine
permit2Canonical Permit2 contract — handles gasless token pulls
protocolTreasuryReceives ~1/3 of direct swap fees
factoryGeodeFactory address — authorized to register launch pools

Two Swap Paths

Every swap on a Geode-configured pool takes one of two paths:

Path 1: Direct Swap

A swap that passes through the hook’s beforeSwap callback. Behavior depends on pool type: Curve pools (launched tokens): The hook computes the bonding curve output and returns the full delta via BEFORE_SWAP_RETURNS_DELTA. The v4 pool has no liquidity — the hook IS the market maker. A 0.3% fee is split three ways:
  • ~1/3 → curve ETH reserve (permanently increases the floor price)
  • ~1/3 → protocol treasury
  • ~1/3 → per-pool surplus (funds settler gas reimbursement)
Standard pools (existing AMM liquidity): The hook charges a fee on the unspecified (output) side of the swap:
  • ~1/3 → protocol treasury (via poolManager.take())
  • ~2/3 → per-pool surplus (held by the hook as ERC20 balance)
When the hook routes residual flow through the AMM during batch settlement (sender == address(this)), no direct swap fee is charged. Only external swappers pay.

Path 2: Intent Batch Settlement

The core mechanism. A permissionless settler calls geodeSettleBatch() with arrays of signed buy and sell intents.

Settlement Flow

The complete call flow from entry to final token distribution:
geodeSettleBatch(key, buys[], sells[], buySigs[], sellSigs[])

  ├─ Validate batch interval (≥ 1 block since last settlement)
  ├─ Validate intent bindings (poolId, deadlines)
  ├─ Compute clearing price + fills
  │   ├─ Curve mode → ConstantProductCurveLib.computeLaunchSettlement()
  │   └─ Standard mode → ClearingPriceLib.computeClearingPrice()

  ├─ Update batch state, emit GeodeIntentFilled events

  └─ poolManager.unlock(callbackData)
      └─ unlockCallback()
          └─ _settlementUnlockCallback()

              ├─ Pull tokens from each user via Permit2
              │   └─ permitWitnessTransferFrom(owner → poolManager)

              ├─ Route and distribute
              │   ├─ Standard: AMM swap for residual
              │   ├─ Curve: dispense/absorb via bonding curve
              │   ├─ Distribute currency1 to buyers (pro-rata by amountIn)
              │   └─ Distribute currency0 to sellers (pro-rata by amountIn)

              └─ Pay settler
                  ├─ Settlement fees (from unrouted input deposits)
                  └─ Gas reimbursement (from surplus, capped)

Step-by-Step Breakdown

1

Batch Validation

The settler submits arrays of buy intents, sell intents, and their Permit2 signatures. The hook checks:
  • Pool is configured (poolInitialized[poolId])
  • Enough blocks have passed since last settlement (batchInterval)
  • All intents target the correct pool (poolId binding)
  • All deadlines are in the future
2

Clearing Price Computation

The clearing price equals the AMM spot price (v1 simplification). For each intent:
  • Buy intent fills if its implicit limit price ≥ clearing price
    • Limit price = amountIn × Q128 / minAmountOut
  • Sell intent fills if its implicit limit price ≤ clearing price
    • Limit price = minAmountOut × Q128 / amountIn
Intents that don’t meet the clearing price are skipped — they keep their tokens.
3

Fee Deduction

Settlement fees are deducted from each filled intent’s input before any routing:
grossInput = intent.amountIn
fee = grossInput × settlementFeeBps / 10,000
netInput = grossInput - fee
The fee amount stays in the PoolManager as unrouted balance for the settler to claim.
4

Internal Matching

The algorithm determines how much buy and sell flow can cross internally:
sellVolumeInQuote = netSellInput × clearingPrice / Q128

if netBuyInput ≥ sellVolumeInQuote:
    internalMatch = sellVolumeInQuote (all sellers matched)
    residual = netBuyInput - sellVolumeInQuote (excess buys → AMM/curve)
else:
    internalMatch = netBuyInput (all buyers matched)
    residual = netSellInput - matchedSells (excess sells → AMM/curve)
Internally matched flow executes at the clearing price with zero AMM spread.
5

Residual Routing

Unmatched flow routes through the appropriate venue:
  • Standard pools: poolManager.swap() against the AMM
  • Curve pools: Dispense/absorb via the bonding curve (tokens from/to hook reserve)
6

Token Distribution

Output tokens are distributed pro-rata to filled intents based on each intent’s amountIn:
  • Buyers receive currency1 (from internal match + residual output)
  • Sellers receive currency0 (from internal match + residual output)
Distribution uses remainder-to-last for dust rounding.
7

Settler Payment

The settler receives:
  1. Settlement fees in both currencies (from the unrouted input deposits)
  2. Gas reimbursement drawn from surplus (capped at maxGasReimbursement, default 0.01 ETH)
A SettlerPaid event emits the exact per-currency breakdown for on-chain auditing.

Flash Accounting

Settlement happens inside Uniswap v4’s unlock() callback — the “flash accounting” context. During this window:
  • The PoolManager tracks credits and debits for each currency
  • Token pulls (via Permit2) create positive deltas
  • Token distributions (via take()) create negative deltas
  • AMM swaps create paired deltas
  • All deltas must net to zero when unlock() returns
If they don’t, the entire transaction reverts. This is Uniswap v4’s core safety guarantee — nothing can be created or destroyed, only moved.

Direct Curve Swap Mechanics

For curve pools, beforeSwap handles the entire swap through the bonding curve:
  1. The hook computes the curve output using ConstantProductCurveLib.tokensForEth() (buy) or ethReturnOnSell() (sell)
  2. A 0.3% fee is deducted from the input and split three ways (reserve, treasury, surplus)
  3. For buys: ETH goes to launchEthReserve, tokens are dispensed from launchTokenReserve
  4. For sells: Tokens are absorbed back, ETH is returned from reserve
  5. The hook returns the full delta via BeforeSwapDelta, so the AMM processes nothing
The reserve fee portion permanently increases the curve’s floor price — every direct swap ratchets the minimum value upward.

Direct Swap Fee Capture (Standard Pools)

When a non-intent user swaps through a standard Geode-configured pool, the hook charges a fee using paired delta accounting:
  1. poolManager.take(unspecified, hook, feeAmount) — creates a negative hook delta
  2. BeforeSwapDelta(0, +feeAmount) — creates a positive hook delta charged to the swapper
The two net to zero for the hook. The swapper pays the fee as reduced output. ~1/3 goes to the protocol treasury; ~2/3 accumulates as surplus for settler gas reimbursement.
Read next: Intents for the signing and Permit2 integration, or Economics for the full fee model.