Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.geode.ag/llms.txt

Use this file to discover all available pages before exploring further.

Architecture Overview

Geode is a single smart contract (GeodeHook.sol, ~1,260 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 50% 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 normal Uniswap swap that passes through the hook. The beforeSwap callback intercepts the swap and charges a fee (directSwapFeeBps — 0.2% for standard pools, 1% for launch pools). The fee is split:
  • 50% → protocol treasury (via poolManager.take())
  • 50% → per-pool surplus (held by the hook as ERC20 balance)
The surplus funds settler gas reimbursement. Direct swappers subsidize the liveness of intent settlement.
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)
  ├─ Record batch-open anchor sqrtPrice (for residual price protection)
  ├─ Compute clearing price + fills
  │   ├─ Launch 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
              │   ├─ AMM swap for residual (with anchor price protection)
              │   ├─ 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

Anchor Price

Before computing the clearing price, the hook records the current AMM sqrtPrice as the batch-open anchor. This anchor is used later to cap residual swap slippage — preventing manipulation between batch submission and execution.
3

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.
4

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.
5

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)
else:
    internalMatch = netBuyInput (all buyers matched)
    residual = netSellInput - matchedSells (excess sells → AMM)
Internally matched flow executes at the clearing price with zero AMM spread.
6

Residual AMM Swap

Unmatched flow routes through poolManager.swap(). The swap uses the batch-open anchor price as a slippage limit (capped by maxResidualDeviationBps, default 5%).If the anchor price limit stops the swap early (partial fill), unconsumed input goes to the pool’s surplus.
7

Token Distribution

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

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.

Residual Price Protection

To prevent manipulation between batch submission and settlement, the hook uses a batch-open anchor price:
  1. At the start of geodeSettleBatch(), the current AMM sqrtPrice is recorded
  2. When the residual routes through the AMM, the swap’s sqrtPriceLimitX96 is set to the anchor ± maxResidualDeviationBps (default 5%)
  3. If the AMM price has moved beyond this limit (e.g., from a manipulation attempt), the swap partially fills and the unconsumed input goes to surplus
This bounds the maximum price impact that a manipulator can extract from the residual flow.

Direct Swap Fee Capture

When a non-intent user swaps through a 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. Half goes to the protocol treasury; half accumulates as surplus for settler gas reimbursement.
Read next: Intents for the signing and Permit2 integration, or Economics for the full fee model.