Aerodrome Finance
Builder-facing reference for writing software against Aerodrome on Base — the ABI surface, event topology, factory layout, and integration patterns. This page is for the moment you open a TypeScript file and reach for viem; holder-facing material (what a holder earns, how voting feels) is not the goal here.
This page is for the developer — read events, claim rewards, build a relay, price a Slipstream position, automate voting. Holder-mechanics ("what is my locked AERO earning me?") is a separate audience and not the goal here.
What it is
Aerodrome is a ve(3,3) DEX on Base: a Velodrome v2 fork with a Slipstream concentrated-liquidity book grafted on top of the same veAERO governance + bribe market. Chain ID 8453.
As an integration target, three things define the surface area: the weekly epoch, the factory layout, and the actor/contract role split. Get those three right and the rest of the protocol is mechanical.
Three pool types coexist on the same Voter contract:
- vAMM (volatile) —
x * y = k, Uniswap-v2 math. For uncorrelated pairs (AERO/USDC, WETH/USDC). Default 0.30% swap fee, configurable per-pool. - sAMM (stable) —
x^3 * y + y^3 * x = k, Curve-style invariant for tightly correlated assets (USDC/USDbC, USDC/DAI). Lower slippage near the peg, default 0.05% fee. - Slipstream (CL) — concentrated liquidity, Uniswap-v3 fork. LPs pick a tick range; capital outside the range is idle.
tickSpacingdefines the fee tier (1/50/100/200/2000). The CL gauge is a separate contract from the legacy v2-style gauge; both vote types coexist on the same Voter. Most "real" volume on Base now sits in Slipstream — that's where the fees are.
At a glance
Canonical contracts on Base mainnet (chain ID 8453). New factories ship over time, so for production integration don't hardcode — pull FactoryRegistry on init and let it tell you the live factories.
Core ve(3,3) (from aerodrome-finance/contracts):
| Contract | Address |
|---|---|
| AERO (ERC-20) | 0x940181a94A35A4569E4529A3CDfB74e38FD98631 |
| VotingEscrow (veAERO NFT) | 0xeBf418Fe2512e7E6bd9b87a8F0f294aCDC67e6B4 |
| Voter | 0x16613524e02ad97eDfeF371bC883F2F5d6C480A5 |
| Router | 0xcF77a3Ba9A5CA399B7c97c74d54e5b1Beb874E43 |
| PoolFactory (vAMM/sAMM) | 0x420DD381b31aEf6683db6B902084cB0FFECe40Da |
| FactoryRegistry | 0x5C3F18F06CC09CA1910767A34a20F771039E37C0 |
| RewardsDistributor | 0x227f65131A261548b057215bB1D5Ab2997964C7d |
| Minter | 0xeB018363F0a9Af8f91F06FEe6613a751b2A33FE5 |
| GaugeFactory | 0x35f35cA5B132CaDf2916BaB57639128eAC5bbcb5 |
| VotingRewardsFactory | 0x45cA74858C579E717ee29A86042E0d53B252B504 |
| ManagedRewardsFactory | 0xFdA1fb5A2a5B23638C7017950506a36dcFD2bDC3 |
| ArtProxy | 0xE9992487b2EE03b7a91241695A58E0ef3654643E |
| Forwarder | 0x15e62707FCA7352fbE35F51a8D6b0F8066A05DCc |
Slipstream (CL) — from aerodrome-finance/slipstream, Gauges V3 deployment:
| Contract | Address |
|---|---|
| CLFactory (Slipstream PoolFactory) | 0xf8f2eB4940CFE7d13603DDDD87f123820Fc061Ef |
| CLGaugeFactory | 0x385293CaE378C813F16f0C1334d774AdDDf56AbB |
| NonfungiblePositionManager | 0xe1f8cd9AC4e4A65F54f38a5CdAfCA44f6dD68b53 |
| SwapRouter | 0x698Cb2b6dd822994581fEa6eA4Fc755d1363A92F |
| MixedQuoterV3 | 0xCd2A7D98e82D6107eac1828ce8DeAA6acB65b555 |
| DynamicSwapFeeModule | 0x87D8f999BBa9343E8099552426775B51C338E8CB |
| UnstakedFeeModule | 0xc2cc3256434AfbC36Bb5e815e1Bb2151310a1a0b |
| Redistributor | 0xEe5b3C7b333e2870B746b3B2b168EF0958e55e15 |
Per-pool Gauge, FeesVotingReward, and BribeVotingReward contracts are deployed by the factories — query them at runtime via the Voter for any given pool.
How to integrate
The epoch — the only clock that matters
The protocol runs on a weekly epoch ending Thursday 00:00 UTC. Every other mechanic — emissions, votes, bribes, fees — is a function of the current epoch.
Thu 00:00 UTC ── epoch flip ──┐
├─ snapshot voter weights
├─ distribute prior-epoch fees + bribes to last week's voters
└─ stream AERO emissions to gauges per this week's votes
↓
Mon-Wed ── voting + bribe deposits ──
↓
Thu 00:00 UTC ── epoch flip ──
The current epoch is block.timestamp / 1 weeks (Solidity), or equivalently Math.floor(now / 604800) (JS). The Voter contract exposes the epoch start as epochStart(block.timestamp) and epochNext(block.timestamp). Any cron in your stack — claim, sweep, snapshot, notify — should be anchored to the Thursday flip, not midnight UTC and not a chain block number.
Three token surfaces
AERO — 0x940181a94A35A4569E4529A3CDfB74e38FD98631. ERC-20, emitted weekly to gauges. The yield-bearing rewards token for LPs.
veAERO — an NFT (ERC-721) minted by locking AERO in the VotingEscrow contract for up to 4 years. Boost is linear with lock time: 4-year lock gives 1.0 voting power per AERO; 1-year gives 0.25. Lock decays second-by-second toward zero unless extended (increase_unlock_time) or topped up (increase_amount). Locks are transferable as NFTs — that's how vault managers run "managed" veNFTs on behalf of depositors.
Pool LP tokens — each pool (vAMM / sAMM / Slipstream CL) mints its own LP receipt. Stake the receipt into the pool's gauge to earn AERO emissions; don't stake and you only earn the swap fees from your slice of the pool. Most LPs stake.
Actors and the contracts they touch
To write code against Aerodrome you have to know which contract each actor reads or writes. Misattributing a call is the #1 integration bug.
| Actor | Reads | Writes | Earns from |
|---|---|---|---|
| Unstaked LP | Pool |
Pool.mint / Pool.burn (add/remove) |
Pool swap fees on their share |
| Staked LP | Pool, Gauge |
Gauge.deposit (stake LP), Gauge.getReward (claim AERO) |
Gauge AERO emissions |
| veAERO voter | Voter, VotingEscrow, FeesVotingReward, BribeVotingReward |
Voter.vote(tokenId, pools[], weights[]), Voter.claimFees / claimBribes |
Pool fees + bribes for every voted gauge |
| Briber (protocol team) | Voter, BribeVotingReward |
BribeVotingReward.notifyRewardAmount(token, amount) |
— |
| Manager (relay op) | VotingEscrow, Voter, *Reward |
All voter writes against a managed tokenId |
Manager fee taken from harvest |
The flywheel: Emissions chase votes. Votes chase fees + bribes. Fees + bribes chase liquidity. Liquidity chases emissions. If you're modeling AERO price as an integration backbone, this is the loop to track — when bribe yield falls below emission decay, the loop slows.
Relays — the managed veNFT pattern
A relay is a veAERO NFT held by a third-party manager contract that does the weekly work: voting on pools with the highest bribe yield, harvesting rewards, and either re-locking or distributing. From the protocol's view it's just another tokenId calling Voter.vote(...); the relay logic — vote optimization, harvest sequencing, fee collection — lives in the manager contract on top.
Two pattern variants:
- AERO relay — depositors send AERO → manager locks it into the relay's veNFT → manager votes weekly → harvests fees + bribes → swaps everything back to AERO → either re-locks or compounds into the relay share. Depositor holds a yield-bearing ERC-20 (
stkAERO-style) representing pro-rata claim on the relay's veNFT + pending rewards. - USDC relay — same mechanic, but harvested rewards are swapped to USDC and either streamed or claimable. Cashflow rail, not compounding rail.
Why this matters for builders. If you're shipping a relay or relay-adjacent product (Paid Daily is in this space), the design choices that matter most:
- veNFT custody model. Relay holds the NFT (depositor has no direct claim → easier UX, harder exit) vs. depositor holds the NFT and delegates vote rights (harder UX, custody stays with user, cleaner regulatory story). Paid Daily picks the latter.
- Vote-optimization policy. Sorting bribes by gross emissions-per-vote is the naive answer; real relays adjust for token-price slippage on swap-back, gas, and depositor preferences (AERO compound vs USDC out).
- Harvest cadence. Weekly minimum (epoch flip), but a relay can sweep mid-epoch on bribe top-ups. Every harvest is gas; balance against AUM.
- Manager-fee shape. Per-epoch flat, percentage of harvest, performance hurdle. Stakeholders care; pick before mainnet.
- Exit liquidity. If the relay token is the only exit, depositors are bound to its secondary-market depth. Some relays add a "withdraw underlying after lock decays" escape hatch with a cooldown; design it explicitly or your support inbox will tell you to.
Bribe-market reads + writes
The bribe market is the data layer that prices AERO. As a builder you'll integrate against it for one of three reasons: (a) you're computing pending voter rewards for a UI, (b) you're picking which gauges your relay should vote, (c) you're depositing bribes to direct liquidity to your own protocol.
On-chain reads:
Voter.gauges(pool)→ the gauge contract for a pool.Voter.gaugeToBribe(gauge)→ theBribeVotingRewardcontract that holds the epoch's bribes for that gauge.Voter.gaugeToFees(gauge)→ theFeesVotingRewardcontract for that gauge's pool-fee share.BribeVotingReward.rewardsList()+BribeVotingReward.earned(token, tokenId)→ what a specific veNFT has claimable from that gauge's bribes.Voter.weights(pool)→ total vote weight on a pool this epoch.Voter.usedWeights(tokenId)andVoter.lastVoted(tokenId)→ has this veNFT voted this epoch.
Subgraph / indexer: Aerodrome publishes a public subgraph (check github.com/aerodrome-finance for the current URL — the location has moved more than once). The Goldsky and Subsquid communities also maintain Aerodrome indexers; pick the one with the freshest blocks for your use case. For per-epoch bribe-aggregation queries the subgraph is much cheaper than walking events via RPC.
Bribe depositing (write path): BribeVotingReward.notifyRewardAmount(token, amount) after IERC20(token).approve(bribeContract, amount). The token must be on the BribeVotingReward.rewardsList() for the gauge, or you'll need to add it via the gauge's addReward (gated to allowed tokens). Bribes deposited during epoch N are claimable by voters who voted in epoch N, after the epoch flip.
Dashboards as ground truth (not integration targets): aerodrome.finance/vote, veaero.com, DefiLlama's "Aerodrome bribes" view. Cross-check your computed numbers against aerodrome.finance/vote for any specific gauge — if your number disagrees with theirs, theirs is right and you have a bug.
Reading state with viem
All the read selectors above are well-typed by @aerodrome-finance/abis if available, or generate types from the contract artifacts under aerodrome-finance/contracts/abi/. Default pattern is createPublicClient({ chain: base, transport: http(CDP_RPC_URL) }) then .readContract({ address, abi, functionName, args }). Batch via multicall (@viem/utils) — a relay touching 30 gauges per epoch will rate-limit you out of a free RPC tier otherwise.
Subscribing to events
Key signatures to filter on:
Voter.Voted(voter, pool, weight, totalWeight, timestamp)— when a veNFT votes.Voter.GaugeCreated(poolFactory, votingRewardsFactory, gaugeFactory, pool, bribeVotingReward, feesVotingReward, gauge, creator)— a new gauge ships, discover via this rather than scanning factories.Voter.DistributeReward(sender, gauge, amount)— emissions distributed.Gauge.NotifyReward(from, token, amount)andGauge.ClaimRewards(from, amount)— emissions in/out.BribeVotingReward.NotifyReward(from, reward, epoch, amount)— bribe deposited.
Forking Base mainnet for tests
Anvil: anvil --fork-url $BASE_RPC_URL --fork-block-number <recent-block>. Hardhat: same idea with forking in config. Skip-block to the Thursday flip with anvil_setNextBlockTimestamp + evm_mine to test epoch-rollover logic locally. The vote-state at the forked block is the live mainnet state — you can mint veNFTs to test addresses with anvil_impersonateAccount + VotingEscrow.create_lock.
Gotchas
- Staked LPs earn AERO emissions but NOT pool swap fees. Fees are routed to voters.
Pool.fees(token)does not show "fees earned by LPs of this pool" — it shows fees pending distribution to voters of this gauge. Get this wrong and your dashboard's "LP yield" number is wrong. - Clock drift around epoch flip. Idempotency on
claimis your friend — your scheduler clock can drift seconds vs chain time, and a double-call near the Thursday boundary can either fail or apply twice in worst cases. Test againstblock.timestamp, notDate.now(). - Aerodrome Voter is high-volume. Subscribing via
eth_newFilteron a free RPC will hit rate limits fast. Either run your own op-geth + op-node, use a paid Alchemy/QuickNode tier, or batch via subgraph. - Free-RPC rate limiting during anvil fork hydration. Point at a paid endpoint or run a local op-geth — anvil's repeated
eth_calls during state hydration will burn through a free tier in minutes. - Don't hardcode factory addresses. Aerodrome has shipped new factories (Slipstream is one) and will ship more. Resolve via
FactoryRegistry.poolFactories()at runtime — hardcoded factory addresses are a 12-month time bomb.
Risks
- veAERO lock illiquidity. If your product custodies user funds in long locks, design the exit before you ship. Relay-token secondary market depth is your real liquidity floor, not the lock duration.
- Bribe-market thinning. Bribe yield is cyclical. Has happened on Velodrome; will happen here. Don't promise users a yield number that assumes 12-month-trailing bribe rates — quote current epoch only or use a conservative rolling median.
- CL out-of-range silence. Slipstream positions earn nothing while out of range. A "your APY this week was 0%" UI moment will surprise users. Either auto-re-range, surface the in-range %, or pick wider ranges.
- Governance concentration. A small number of veAERO holders direct the bulk of emissions. Track
Voter.usedWeights(tokenId)for the top-10 NFTs each epoch — your "expected gauge weight" model needs to handle a concentrated whale changing their vote. - Sequencer dependency. Base has one sequencer (Coinbase). If your product is time-sensitive (claim before epoch rollover, liquidate before re-range), a sequencer pause is downtime. Document the degraded mode.
- Audit boundary (load-bearing). Aerodrome's audits cover Aerodrome. They do not cover your wrapping contract, your relay manager, your batched claim-and-swap path, or your custody model. The
web3-no-prod-capital-without-auditrule applies: Slither + Echidna + invariant tests, then paid audit before mainnet capital. Don't skip this because the underlying protocol is audited.
Related
- Base — the chain Aerodrome lives on. RPC, predeploys, calldata cost model.
- Coinbase Developer Platform — default RPC + wallet + paymaster stack for any Aerodrome integration.
