aero-vote-radar
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@aero-vote-radarrecommend vote allocation for 25000 veAERO"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
aero-vote-radar
You hold veAERO. Where does this week's vote go?
Type your balance into aero.deftools.xyz and get back whole percentages you can paste straight into Aerodrome's voting UI — ranked by what each pool really pays per vote, and priced after your own vote dilutes it.

What it does for you
The page opens on one question -- how much veAERO you have -- with the four expert controls folded behind Settings on defaults that work, and it prices the suggestion against the weight each pool usually settles at rather than the weight showing at this moment. That last choice is why there is no longer a paragraph under the allocation explaining that the dollars above are about to fall: they are already the post-refill ones. Replaying past epochs, the three bases earn within a few percent of each other with no consistent winner (measured 2026-09-15, see below), so among equals the page now shows the figure it does not have to disclaim.
Hands you a castable vote, not a leaderboard. Whole percentages summing to exactly 100 — the only form Aerodrome's UI accepts — with a copy button and a link straight to the voting page.
Prices your own dilution. The moment you add votes to a pool, your own $/vote there falls. The allocator models that explicitly instead of dumping everything into whatever shows the highest APR.
Tells you how much of itself to believe. Of the ten pools a list like this puts on top, roughly six were still on top when the epoch closed — and only two once near-empty gauges are counted in. Measured, from the scan history, and printed on the page above the ranking rather than left as a warning that the numbers "move".
Tells you when it expires. A vote only counts toward the epoch it is cast in, so every recommendation carries the deadline and a countdown running off your own clock.
Flags the traps rather than ranking them. A single one-off bribe five weeks ago, a pool whose vote weight gets refilled in the last hours of the epoch, a pool too new to have a record — each is marked, because each looks like an opportunity and isn't.
Never touches your wallet. No connection request, no signature, no keys. It prints numbers; you cast the vote. Reading voting power from an address is a read-only chain call.
Three ways to run it
Hosted page | aero.deftools.xyz — nothing to install. A scheduled job re-scans the chain every 6 hours; the allocation itself runs in your browser. |
CLI |
|
MCP server |
|
Everything below is the long version: why the obvious strategy loses money, what each number means, and — at least as important — what this tool cannot tell you.
Why
Aerodrome pools receive weekly bribes + trading fees, split pro-rata among everyone who voted for that pool with their veAERO. Four things make "just vote for the highest-APR pool" a bad strategy:
Self-dilution. The moment you add votes to a pool, you've changed its vote total, and your own share (and everyone else's) goes down. A pool that looks great before your vote can look mediocre after it, especially if you have a large veAERO balance relative to the pool's existing votes.
The vote weight you divide by is not final. Votes carry over between epochs on Aerodrome, so a pool's mid-week weight is largely inherited from last week and gets rewritten as holders re-vote — and they re-vote late. Measured against this repo's own six-hourly snapshot history, roughly 29-30% of all vote weight across the protocol is added or removed in the final five hours before an epoch closes. A per-vote figure computed against the current weight is therefore a figure computed against a denominator that has not settled.
Lag. The leaderboard everyone looks at is last epoch's result. Pools where incentives are trending up faster than votes have caught up are where the actual opportunity is — by the time it's obvious, the vote weight has already caught up too.
One-off bribes masquerading as opportunities. A pool that got a single $600 bribe five weeks ago and nothing since has the same trailing average as one that pays $100 like clockwork — but voting into the first is a bet that a one-week event repeats.
aero-vote-radar addresses all four: it ranks pools using a trailing-average trend estimate instead of just the latest epoch, scores how steady each pool's incentives have actually been, judges every pool against the vote weight it usually settles at rather than the one it happens to be showing, and allocates using a greedy marginal-value algorithm that models dilution explicitly. It also ships a backtest command that replays past epochs so the claim "this beats naive APR-chasing" can be checked rather than taken on faith.
Related MCP server: recon-crypto-mcp
How it works
Every number comes from Aerodrome's own official on-chain "Sugar" contracts and the Voter contract, read live off Base mainnet, plus DefiLlama's free price API to convert bribe and fee tokens to USD. No API keys, no backend, no database.
Data | Source | Contract (Base mainnet) |
Which pools can receive votes, which gauges are alive |
|
|
Pool token/symbol metadata | the pool contracts themselves (ERC20-like) | (per-pool) |
Weekly epoch history — votes, bribes, fees |
|
|
Your veAERO locks & voting power |
|
|
USD pricing for bribe/fee tokens | DefiLlama | — |
All addresses are pulled from Aerodrome/Velodrome's own official Sugar deployment file and cross-checked against verified contract names on Basescan.
Two things worth calling out honestly, so the tool isn't oversold:
"Predicted" is a trailing average, not machine learning.
predictedValuePerVote= mean(last 6 epochs' USD value) ÷ current votes. It's a simple, transparent heuristic for "which pools are trending," not a forecast model.The best allocation is often one pool at 100%, and
--max-weightis there for when that is more concentration than you want. On live data, 25,000 veAERO uncapped goes entirely into a single pool for an expected $352.81; capped at 25% it spreads over nine pools for $212.04. The cap is not free and the tool does not pretend otherwise — it is a risk preference, priced. A cap too tight for the pools that qualified (three pools capped at 20% can hold 60% of a budget) makes the CLI, the MCP tool and the web page refuse rather than print a vote: the rounding step normalises whatever it is handed back to 100%, so a part-spent allocation would silently hand back the concentration the cap was set to avoid.A recommendation expires. A vote only counts toward the epoch it is cast in, so
recommendnow closes with how long that epoch has left and the exact UTC instant it flips, and the web page carries the same countdown (recomputed from the visitor's own clock, not from the up-to-six-hours-old snapshot). The snapshot JSON publishesepochEndsAtfor anything else reading it. The boundary belongs to the epoch opening there, so a vote cast at the instant of the flip has a full week, not zero seconds.The allocator allocates in whole percentage points, because that is what a vote is. Aerodrome's UI takes whole percentages, so the budget is handed out in 100 steps of one castable point each. This is not a resolution setting turned down: finer steps produce fractions of a point that can only be rounded away afterwards, and on a 1,000,000 veAERO run that meant nine of fifteen recommended pools landed under 0.5% and vanished, leaving a table whose expected total no real vote could earn. Quantising costs nothing in return — each pool's
R*x/(V+x)is concave, and for a sum of concave functions, giving each identical indivisible unit to the best marginal is exactly optimal on that lattice. The consequence you can check: the quoted total and the vote-ready total are now the same number.The allocator assumes other voters' votes stay put. The greedy marginal algorithm optimizes your allocation against the current snapshot of everyone else's votes. It doesn't (and can't) predict how other voters will react to your vote.
The headline per-vote figure divides by a weight that has not settled, so there is a second one beside it.
predictedValuePerVoteistrailingAvgUsd / currentVotes: a six-epoch average numerator over the live mid-week vote tally. Votes carry over between epochs, so that tally is last week's weight part-way through being rewritten — it has shed the holders who already moved on and not yet gained the ones who vote late.dilutionAdjustedValuePerVoteinstead divides by the weight the pool settled at last epoch, and that is what the allocator prices against. Both are always published, so the gap is visible rather than quietly applied.The basis was chosen by measurement, not by argument, after an earlier one picked by argument turned out to be worse than doing nothing. Using this repo's own six-hourly snapshot history, every scan taken inside a since-settled epoch is a real mid-week vantage point; scoring each candidate predictor against the weight that epoch actually settled at (5,142 observations over two epochs, log-scale error since payout depends on weight multiplicatively):
predictor
median error
bias
closest on
last epoch's settled weight (default)
17%
none
2,728
the live mid-week tally (
--vote-basis current)18%
none
1,568
larger of tally and typical weight (
--vote-basis typical)26%
+4% high
846
The third was the default for a few hours and is kept only so the finding stays reproducible. It took
Math.maxof the live tally and the pool's median past weight, on the reasoning that a pool below its usual weight will be refilled and so the larger figure "cannot be wrong in the voter's favour".Math.maxis one-sided: it can only raise an estimate, so an upward bias is built in by construction, and an estimator's bias is not a safety margin — it is an error the allocator then spreads over every pool it prices. On pools under 10k votes, where it was supposed to help most, it was worst by a wide margin (71% error against 35% and 29%).Two limits worth stating. Only two settled epochs have snapshot coverage (the history starts 2026-08-07), and the observations within a pool are not independent, so call it a few hundred independent points rather than five thousand — the effect size is large and consistent across every pool-size bucket, which is why it is trusted, but it is not a long record. And
backtestcannot settle this question: replaying a closed epoch has no mid-week tally to work from, so the previous settled weight stands in for both bases and they become the same number there. Separating them needs a mid-week vantage point, which only the snapshot history provides.This table has already moved, and moved enough to flip the ranking. The table above is the measurement from when this section was written; the same measurement, refreshed weekly against a growing history, is committed to
docs/data/timing.json'saccuracyfield. As of the copy committed there on 2026-09-10 (13,048 observations over 5 settled epochs),currenthas the lower median error, notprevious— see "Checking the vote basis yourself" below for the numbers and what they do and don't mean.consistencyis a description of the past, not a promise about the future. It's1 / (1 + coefficient of variation)over the observed epochs: 1.00 means the pool paid the same every epoch, lower means spikier. A pool with only one observed epoch scores 0 rather than 1 — a single data point can't demonstrate steadiness, and scoring it as perfect would flatter brand-new pools exactly where the tool should be most cautious. UseepochsObservedto tell "unproven" apart from "genuinely erratic".momentumcompares halves of the history, and ignores the epoch in progress. It's the recent finished epochs' average over the older ones', minus 1 — enough to separate a pool ramping from $50 to $500 from one decaying $500 to $50, whichtrailingAvgUsdandconsistencyboth score identically. Two details keep it honest:RewardsSugarreturns the current epoch at index 0, so a mid-week scan sees a part-week that would make every pool look like it was fading — that entry is charted but excluded from the comparison (currentEpochPartialsays which snapshots have one). And the older half has to clear the sameMIN_TRAILING_USDfloor the rankings use, because a ratio explodes when its denominator is loose change: on live data, an unguarded version topped the "ramping up" list with a pool going from $0.02 to $5.75 an epoch (+29,286%), ahead of one genuinely going from $54 to $417. Only the older half is floored — a pool collapsing from $200 an epoch to $12 has a small recent half, and that is the finding. Four finished epochs are the minimum; below that it'snull.The backtest is a small sample with survivorship bias. It only sees pools whose gauge is still alive today, and it assumes your votes wouldn't have changed anyone else's behaviour. Treat a handful of epochs as a sanity check, not a track record.
A backtest without
--min-consistencytests a strategy you may not be voting. If you vote with a consistency filter, pass the same one tobacktest— otherwise the report scores the unfiltered strategy and quietly credits it to yours. The filter is re-derived for each tested epoch from that epoch's own trailing window, never from today's history, so it cannot smuggle the outcome into the decision. The naive baseline stays unfiltered on purpose: a baseline that inherits the radar's judgement is no longer a baseline, and its uplift would not be comparable with an unfiltered run.candidatesExcludedByConsistencyreports what the filter cost you in choice.Concentrated-liquidity (Slipstream) pools are ranked too, and until 2026-09-15 they were not. They have no
symbol()of their own, and pool discovery required one, so every single one was dropped in silence. The scan returned 107 pools where 359 exist, and put the whole protocol's incentives for the epoch of 2026-09-03 at $121,034 against an actual $2,131,626 — 5.7% of the money and 9.6% of the vote weight. They are now named from the pair they hold (CL-WETH/cbBTC), which is enough: everything downstream keys on the address. Seven of the top eight pools by predicted $/vote are ones the ranking could not previously see.Voter.pools()currently lists ~1,830 pools that have ever had a gauge; only ~96–108 pass a minimum trailing-value floor (pools below ~$10/epoch trailing value are excluded — at that size a single small one-off bribe swings the "edge" percentage wildly without being a meaningful signal). That range is measured, not guessed: every scheduled scan'spoolCountis committed todocs/data/snapshot.json, and it has held in that band across every scan in the project's history so far (109 scans spanning 28+ days as of this writing) — checkgit log -- docs/data/snapshot.jsonfor the current count, since this range moves as the history grows. A few hundred Voter-registered entries fail basic ERC20 calls (non-standard/likely cross-chain relay entries) and are skipped with a warning rather than failing the whole run.
Web app
aero.deftools.xyz is the same ranking and the same allocator, without installing anything.
It exists because the two halves of this tool have very different costs. The scan is heavy — one epoch-history call per live-gauge pool, several hundred of them — which a visitor's browser cannot do against a public RPC without being rate-limited into uselessness. The allocation is just arithmetic over an already-scanned list, and is instant.
So they're split:
A scheduled job (
.github/workflows/snapshot.yml, every 6 hours) runsnpm run snapshot, which performs the full live scan and writesdocs/data/snapshot.json. If the file changed, the job commits it. A scan that returns zero pools throws instead of publishing, so a rate-limited run can't blank out yesterday's perfectly good data.docs/index.htmlfetches that JSON and runsallocateAcrossCandidates+toWholePercentWeights— ported verbatim fromsrc/allocator.ts— in the browser, per keystroke.
Nothing is sent anywhere: the page is a static file plus a JSON file, with no backend and no analytics. Because the personalised half runs client-side, changing your veAERO amount doesn't trigger a re-scan, and the snapshot can be shared by every visitor.
The page also:
Reads your voting power from an address. Paste a wallet and it sums your locks' live voting power. This is read-only — the page never requests a wallet connection or a signature.
Charts each pool's epoch history and which way it's moving. Every row in the full ranking carries a six-epoch bar chart and a trend figure, and a Which way incentives are moving panel lists the five pools ramping hardest and the five fading hardest. The bar for the epoch still running is drawn as a dashed outline rather than a solid bar, because it is a part-week: showing it solid would draw a cliff on every pool in the table. Each pool is scaled to its own maximum — the column answers "which way is this pool going", not "is it bigger than that one", which the dollar columns beside it already answer.
Shows how far each pool's live weight sits from its usual one. A Refill column carries that ratio, and the Vote weight to expect control switches the ranking and the suggested allocation between last epoch's settled weight (the default) and the live tally. The Refill figure is a warning worth reading — a pool whose weight swings by an order of magnitude between epochs is a different proposition from a steady one — but it deliberately no longer drives the denominator: see the measurement above for why letting it do so made the tool worse.
Shows what the whole pot is paying. A protocol-wide table of every pool's incentives over every pool's votes, epoch by epoch. The per-pool ranking is about choosing between pools and looks identical whether the pot it divides is growing or collapsing — and across the five finished epochs in the snapshot this was written against, a veAERO vote went from $0.00100 to $0.00066. Reward tokens are valued at today's prices even for older epochs and only pools with a live gauge today are counted, which both push the older figures down, so a decline shown there is a floor on the real one.
Copies the weights as
93% sAMM-WETH/msETHlines, so they can go straight into Aerodrome's UI instead of being retyped from the screen.Labels each pool
vAMMorsAMM— volatile (x·y=k, for tokens whose prices move independently) versus stable (a flatter curve for pairs meant to hold the same value). Each row links to the pool contract on Basescan.
Why this reads VotingEscrow directly instead of VeSugar.byAccount: that method returns one large struct per lock, including every vote each lock has cast, and on a public RPC it reverts for wallets holding many locks — 0xbde0…ea5a, with 22 locks, fails outright. Both the web page and the CLI's --address/my-veaero instead make three plain calls against the VotingEscrow contract (balanceOf → ownerToNFTokenIdList → balanceOfNFT), all of which return single integers. Verified equal to the VeSugar path where VeSugar works (0xde86…ee31: both report 81.50845401781879), and working on the 22-lock wallet where it doesn't. The contract address is read from Voter.ve() rather than copied from documentation. Note that voting power decays continuously toward a lock's expiry, so two reads seconds apart legitimately differ.
The site is served by GitHub Pages from the docs/ folder on main. To regenerate the snapshot by hand:
npm run snapshot # writes docs/data/snapshot.json
npm run snapshot -- some/other.json # or somewhere elseVerified parity: for 25,000 veAERO with no consistency filter, the page and npm run cli -- recommend --veaero 25000 --vote-ready produce the same weights (76/20/2/1/1) and the same expected total, to the cent.
Checking the vote basis yourself
npm run predict-check # walks the committed snapshot history
npm run predict-check some/dir # or a directory of snapshot JSON filesThis is the measurement that decides --vote-basis, kept as a command rather
than as a number in this README, because the reason the tool once shipped the
wrong default was that measuring it looked like an afternoon's work. It is one
command.
It works by exploiting something the backtest cannot reach. Replaying a closed
epoch offers no mid-week vote tally, so previous and current become the same
number there and the backtest is blind to the difference. But every scan ever
committed to docs/data/snapshot.json recorded the live tally at that moment,
six-hourly — so a scan taken inside an epoch that has since settled is a real
mid-week vantage point with the answer now known. Each basis is scored against
what those epochs actually settled at, with the trailing window it would have
had at the time, and the same MIN_TRAILING_USD gate the rankings use, so the
result describes the pools the tool would really vote into.
Output at the time of writing — 5,142 observations, 104 pools, epochs of 2026-08-06 and 2026-08-13:
basis | error | bias | closest on |
| 17% | none | 2,728 |
| 18% | none | 1,568 |
| 26% | +4% | 846 |
and on pools under 10k votes, 29% / 35% / 71% respectively, the last of them running +24% high. Error is the median absolute miss on a log scale, because payout depends on weight multiplicatively; bias is reported separately because a predictor can be no noisier than another and still be systematically high, and the allocator divides by it, so the bias reaches every pool it prices.
Two things the numbers do not carry: observations within one pool are correlated (the same pool appears at every scan time), so this is fewer independent points than 5,142; and only epochs with snapshot coverage can be scored, which today means the history since 2026-08-07.
This table is a point-in-time snapshot, not a live figure, and it does not
just drift — it can reorder. The same scoring code runs weekly in CI
(timing-cli.ts) against the growing snapshot history and is committed to
docs/data/timing.json's accuracy field — no RPC of
your own required to read it. As of the measurement committed there on
2026-09-10 (13,048 observations, 5 settled epochs), current has overtaken
previous on median error — current 26.5% against previous 28.5% — and the
same flip shows up in three of the four pool-size buckets, including the "under
10k votes" one this section singles out above: current 22% against previous
26%, versus previous 29% against current 35% when this table was written.
previous still wins closestOn — the count of observations where it was the
single closest guess — by 6,069 to 5,323, so the two measures of "better" have
themselves come apart. None of this is a reason to distrust the method; it is
the method working as designed and exactly why it's a command and not a
hardcoded default: read docs/data/timing.json or re-run predict-check
before leaning on the specific numbers above.
Where the default basis is not the one the evidence favours
The measurement above scores each basis on how accurately it predicts the weight
an epoch settles at, and previous wins it. That is why it is the default. But
"most accurate over all pools" and "earns the most money" are different
questions, and on replayed epochs they come apart — in the voter's own size.
Replaying the last five epochs at a range of budgets against the same live scan
(2026-08-29), typical against the default:
veAERO | default $ | typical $ | difference |
5,000 | 23.77 | 50.83 | +114% |
25,000 | 109.47 | 217.49 | +99% |
100,000 | 440.07 | 722.91 | +64% |
200,000 | 870.92 | 1,312.56 | +51% |
500,000 | 2,012.20 | 2,480.58 | +23% |
750,000 | 2,919.51 | 3,089.50 | +6% |
1,000,000 | 3,702.07 | 3,552.75 | -4% |
2,000,000 | 6,166.97 | 4,703.89 | -24% |
5,000,000 | 10,268.27 | 6,045.21 | -41% |
The two findings are not in conflict about the facts. A median accuracy figure
weights every pool equally; an allocator does not. It deliberately picks the
pools whose weight looks lowest relative to their incentives, which is exactly
the tail where last epoch's settled weight is wrong and where typical — the
larger of the live tally and the pool's usual weight — is protective. As the
budget grows, dilution rather than pool-picking decides the outcome, that
protection turns into an over-estimate on every pool at once, and the default
pulls ahead. The crossover sits near 1,000,000 veAERO.
Re-measured 2026-09-15 — twice, and the first attempt was wrong.
The first re-run found the advantage gone: +4.2% at 2,000 veAERO, -11.4% at 5,000, +4.0% at 25,000, -3.1% at 100,000, sign alternating. It was measured on a pool universe missing most of Aerodrome — concentrated-liquidity pools were being dropped in discovery, so the replay chose from 107 pools where 359 exist and could not see the largest vote weights on the chain at all.
With them restored, same command, same five epochs, --min-consistency 0.5:
veAERO | default $ | typical $ | difference | typical ahead in |
2,000 | 57.43 | 87.64 | +52.6% | 5 of 5 epochs |
5,000 | 102.33 | 180.70 | +76.6% | 5 of 5 epochs |
10,000 | 178.12 | 287.42 | +61.4% | 5 of 5 epochs |
25,000 | 585.04 | 559.32 | -4.4% | 4 of 5 |
50,000 | 1,201.74 | 991.38 | -17.5% | 4 of 5 |
100,000 | 2,105.05 | 1,606.93 | -23.7% | 4 of 5 |
The crossover is real — below it typical wins every epoch, not a majority —
and it sits near 20,000 veAERO, not the 1,000,000 this file carried for
three weeks. That figure was never a measurement of Aerodrome; it was a
measurement of the twentieth of it the tool could see.
So the page prices on typical by default and says nothing below the
crossover, because there is nothing to warn about: that is the basis the
evidence favours at that size. Above it, a run on typical gets the caveat
pointing the other way, for a structural reason rather than a replay that can
move — past that size a budget is large enough to shift the very weight
typical assumes will arrive.
The lesson is about measurement, not about typical. A number quoted as
settled fact in prose goes stale in silence. But re-measuring it on a broken
universe is not a check on the number either — it is the same mistake with a
fresh date on it. backtest --veaero <your amount> scores all three on the
reader's own size, which is the only version that cannot go quietly wrong.
How much of the ranking survives the week
Every dashboard in this corner of Base, this one included, ranks pools by a rate whose denominator — vote weight — is still moving while you read it. The usual treatment is a sentence warning that the figure is unstable. This repo can do better than a warning, because it has been committing the live tally every six hours since 7 August: pair each of those scans with the weight its epoch went on to settle at, and the question becomes answerable. Of the ten pools a list put on top at that moment, how many were still on top at the close?
npm run timing # walks the committed snapshot history, writes docs/data/timing.json
npm run timing some/dir out.json # or a directory of snapshot JSON files, to another pathMeasured over 104 scans of the four settled epochs the history reaches (6, 13, 20 and 27 August), 10,560 pool readings:
ranked over | top ten still on top at close |
pools paying $1,000+ an epoch | 6 of 10 |
pools with 50,000+ votes behind them | 3 of 10 |
every pool, near-empty gauges included | 1–2 of 10 |
Two things make that number honest rather than flattering:
Both lists are ranked with the epoch's settled rewards. The earlier ranking is handed perfect foresight about payouts it could not have had, so every place it loses is caused purely by votes moving. Whatever a real forecast does to it, the figure is a ceiling.
It is reported over three sets of pools, not one. The single number across everything is dominated by gauges so thin that one voter's arrival halves the rate. Reporting only that would tell a voter their list is worthless when the part of it they can actually use holds up reasonably well — a different kind of lie.
The same job measures how far the vote weight the allocation assumes typically lands from where the epoch settles (about 20% on last epoch's final weight, 19% on the live weight), which is what turns the headline "expected next epoch" figure into a range instead of a point. Neither basis is a large edge over the other, and by the "closest of the two" measure last epoch's weight wins outright — the page says so, because a tool that hides the size of its own error is asking to be trusted rather than checked.
Limits worth stating: four settled epochs is a small sample and grows by one a week; the scans within a pool are correlated, so the reading count is not a count of independent observations; and only epochs the snapshot history actually reaches can be scored at all.
Casting the vote: unsigned calldata
Everything above ends in a table of percentages that somebody then retypes into a web form, one row at a time. That is where an allocation computed to the percentage point loses accuracy — to a mistyped digit, or a row skipped because the form scrolled.
--calldata closes that gap by printing the same vote as the exact bytes
Voter.vote expects — and so does the hosted page, under "Cast this from your own
wallet" below the allocation, where a veNFT id turns the table on screen into the same
unsigned transaction. The page encodes it by hand, because it ships as one static file with
no build step; test/site-parity.test.ts holds that hand-port to viem's own encoding on
fixtures from one pool to a hundred, and the page checks itself against a known vector on
load and withdraws the button rather than offering bytes it cannot verify.
npx tsx src/cli.ts recommend --veaero 25000 --min-consistency 0.5 --calldata --nft 17324
npx tsx src/cli.ts recommend --address 0xYourAddress --calldata # single lock: id read for you
npx tsx src/cli.ts recommend --veaero 25000 --calldata --nft 17324 --json # for a signer, not a humanUnsigned vote from veNFT #17324 — vote(uint256,address[],uint256[]) on Base (chain 8453):
60% vAMM-cbBTC/EDGE 0x594b83709464A83F37aC77E69DDD447a8A33c0EE
27% vAMM-WETH/GHST 0x0DFb9Cb66A18468850d6216fCc691aa20ad1e091
6% vAMM-cbETH/SPECTRA 0xEf70044C0249a07234E07D673606e09b364dC977
5% vAMM-WETH/CHAMP 0x26029Ed89A19B919b5698F10cf776a4f4AEA1529
2% vAMM-WETH/CXT 0x923BeBD9FbCAf0F85251a3d5A11BF5BFAa19942F
to: 0x16613524e02ad97eDfeF371bC883F2F5d6C480A5 (Voter)
value: 0x0
data: 0x7ac09bf700000000000000000000000000000000000000000000000000000000000043ac...Nothing is signed and nothing is sent. This project holds no keys, requests
no wallet connection and makes no eth_sendTransaction call — the output is
inert until its owner signs it, and only the owner of that veNFT can. The pools
and weights are printed beside the blob on purpose: a hex string is unreadable,
and a voter should be able to check that the bytes say what they were told they
say before signing. The MCP server exposes the same thing as
prepare_vote_calldata.
A few details that are deliberate rather than incidental:
The function signature was verified, not transcribed. The selector for
vote(uint256,address[],uint256[])—0x7ac09bf7— is present in the deployed Voter's runtime bytecode on Base, checked alongside a control: a plausible four-argument variant and an invented function name are both absent, so "found it in the bytecode" is evidence rather than a coincidence.Weights that do not total 100 are refused, not normalised.
Voter.votedivides by the sum it is given, so 60/30 would cast a perfectly valid 2:1 vote that nobody was shown. A total other than 100 means a bug upstream, and repairing it silently would break the one guarantee this feature has: the bytes match the table.A repeated pool is refused — the contract would sum the two rows into a weight nobody chose — and so is an empty allocation, since
votewith empty arrays clears that NFT's votes for the epoch, which is the opposite of what someone asking for a recommendation wants.A wallet with several locks is asked, not guessed. A vote is cast from one veNFT; locks differ in size and expiry, so with more than one the CLI lists them and stops rather than committing a position on its owner's behalf. With exactly one lock,
--addresssupplies the id by itself.veNFT ids are strings all the way through. They exceed
Number.MAX_SAFE_INTEGER, so a number would silently round one to a different NFT.
When the weekly epoch stops being the unit
Aerodrome has announced Predictive Allocation — continuous, rolling allocation in place of the weekly vote — as coming this year, with no date committed on aero.xyz at the time of writing. Every figure in this tool is currently counted over a weekly epoch, so it is worth being plain about which part of that is a real assumption and which was an accident of arithmetic.
The accident: "epoch" was two facts wearing one word — a length (one week) and an alignment (Thursday 00:00 UTC) — and the code got both from a single floor operation, because 604,800 seconds divides Unix time evenly and the Unix epoch itself began on a Thursday. That trick works for a week and for nothing else: a 48-hour window offset from Thursday cannot be produced by flooring at all.
So the period is now a named thing rather than a constant of the universe.
RewardPeriod in src/trend.ts carries a length, an anchor and
a name; WEEKLY_EPOCH is the only value in use; and periodStartOf,
periodEndOf and isPeriodInProgress take one. epochStartOf, epochEndOf
and isEpochInProgress still exist and still mean exactly what they meant —
they are now wrappers passing WEEKLY_EPOCH, and a test asserts they agree with
the general functions on every boundary case, because a silent shift of an epoch
boundary would mis-date every snapshot already committed.
What this is not. It is a seam, not support for anything else yet: no other
period exists, the rankings still compare week-sized buckets, and the rest of
the codebase still says "epoch" where it means "the stretch we count over".
Nothing here predicts what Predictive Allocation will actually look like. The
claim is narrower and checkable — when the shape is known, a different window
should be a value of RewardPeriod rather than a rewrite of the functions that
use it.
What a snapshot cannot currently tell you
Every dollar figure here is priced at the instant the scan ran. That is the only
thing a price source can offer, and it is fine for the question this tool was
built to answer: which pool, out of the ones on offer right now, pays best per
vote. It quietly fails at a different question — what did this pool earn
between Tuesday and Thursday — because subtracting one scan's latestEpochUsd
from the next measures newly accrued rewards and the repricing of the
rewards that were already there, added together and no longer separable.
How badly, measured on the committed history rather than guessed at: across the snapshots between 2026-08-07 and 2026-08-27, 43% of 48-hour windows come out negative. Not because pools un-earned anything, but because reward tokens moved further in two days than two days of incentives accrued. Stretch the window and the accrual climbs out from under the noise — negatives fall to 36% at 72 hours, 31% at 96, 27% at 120 — which is the signature of a real signal buried in price movement rather than of no signal at all.
Two consequences worth stating plainly:
A short-horizon ranking built on these dollar figures would be mostly noise. On this history, the rank correlation between a pool's current accrual rate and what it went on to earn over the next 48 hours is approximately zero, and often slightly negative. "Follow the money that is flowing right now" is not, on the evidence here, a strategy.
The fix is data, not modelling. From 2026-08-28 each snapshot publishes the raw token amounts behind
latestEpochUsd—latestEpochBribesandlatestEpochFeesper pool as[address, amount]pairs, withrewardTokensat the root carrying each token's decimals and the price this scan used. Amounts do not reprice. With them, a later reader can rebuild the total exactly as the scan saw it, or revalue the same amounts at any other price, and so tell accrual apart from a price move. Bribes are kept separate from fees because they arrive differently: fees trickle in with trading, bribes land in lumps.
None of this changes a figure the tool already published; it adds the parts those figures were made of. History before 2026-08-28 has the totals only, so the measurement above cannot be redone on it — which is the reason the fields went in before the question became urgent rather than after.
What the amounts turned out to say
The section above ends by saying the fix is data and the data is now being
published. It is, and npm run accrual is the reader it was published for. The
answer it came back with is not the one the field was added in anticipation of,
so it is worth writing down plainly.
npm run accrual # walks the committed history, writes docs/data/accrual.json
npm run accrual some/dir out.json # or a directory of snapshot JSON files, to another pathThe method has no modelling in it. Take two scans inside the same epoch, value both sets of raw amounts at the later scan's prices, and the difference is accrual with the price move removed by construction. Value each at its own scan's prices instead and you get the naive figure the dollar series would have shown. The gap between the two is what the repricing was worth, and it is reported rather than quietly applied. Pairs that straddle an epoch boundary are skipped, because amounts reset there and the reset would read as a pool losing everything it had earned.
Over the 69 scans carrying amounts, 2026-08-27 to 2026-09-15:
window | pool-windows | any accrual at all | negative, naive | negative, accrual | median accrual where it moved |
48h | 5,094 | 4% | 53% | 0% | $477.59 |
72h | 4,140 | 7% | 53% | 0% | $482.25 |
96h | 2,973 | 8% | 50% | 0% | $500.47 |
120h | 1,915 | 10% | 50% | 0% | $558.22 |
Read the first two number columns together, because that is the finding: about half of all 48-hour windows moved down in dollars, and only four in a hundred had any change in the underlying amounts at all. The dollar series was not a signal buried in noise. Inside an epoch there was, for 96% of pool-windows, nothing underneath it to find — the incentives for the epoch a scan is reporting are essentially fixed by the time the scan can see them, so every cent of movement between two scans is the reward tokens repricing.
That is a sharper statement than the earlier section's, and it changes the advice rather than just quantifying it. "Follow the money that is flowing right now" does not fail because the flow is hard to measure. Within an epoch there is mostly no flow: there is a pot, set near the start, and a price moving on top of it.
Three details that keep the number honest:
Nothing un-earns. Once the price move is removed, not one pool-window in the whole history came out negative, and no raw amount ever fell inside an epoch. A method that produced negative earnings would be reporting its own bug; this one does not, which is the main reason to trust the rest of it.
When amounts do move, they move a lot. The median is not a rounding artefact at roughly $480 to $560. The pools this happens to are real and few, and
didAccruein the JSON names them.The two can happen at once, and only this separates them. Over the longest window
vAMM-WETH/VVVearned $7,197.51 while its reward tokens fell $1,242.74 — a dollar reader would have seen about $5,955 and had no way to know which part was which. In the same stretchvAMM-USDC/AEROswung $7,259.27 on pure price, with amounts that never budged at all.
One caveat on the scope, not the method. Every scan this was measured over predates the 2026-09-15 coverage fix, so each covered about 102 pools against the 359 that exist — concentrated-liquidity pools were being dropped in discovery. The mechanism it found is a fact about how an epoch's incentives are posted, not about which pools were in the sample, so it is unlikely to change. But "unlikely" is not "measured", and this will be worth re-running once a few weeks of full-coverage scans have accumulated.
Tokens the scan had no price for count as $0 on both sides, exactly as they did
in the scan's own totals, and withUnpriced per window says how many rows that
touches (52 of 5,094 at 48 hours) so the figure is never quietly better than its
inputs.
Install
Requires Node.js 18.18 or newer (the test suite's node --import tsx invocation depends on the --import flag, added in 18.18).
From npm — nothing to clone, and npx will fetch it on first use anyway:
npm install -g aero-vote-radar # or skip this and use npx below
aero-vote-radar recommend --veaero 25000 --vote-readyFrom source, if you want the tests, the snapshot history, or to change the strategy:
git clone https://github.com/araxis33/aero-vote-radar
cd aero-vote-radar
npm install
npm run buildOptionally set your own Base RPC for a much higher rate-limit ceiling than the shared public endpoints:
export BASE_RPC_URL="https://your-rpc-provider.example/..."CLI usage
npx aero-vote-radar pools --top 10
npx aero-vote-radar pools --top 10 --min-consistency 0.5
npx aero-vote-radar recommend --veaero 25000
npx aero-vote-radar recommend --address 0xYourAddress --vote-ready
npx aero-vote-radar recommend --veaero 25000 --max-weight 0.25 # no pool above 25% of your vote
npx aero-vote-radar recommend --veaero 25000 --vote-basis current # judge pools on the live mid-week tally instead of last epoch's settled weight
npx aero-vote-radar recommend --veaero 25000 --calldata --nft 17324 # the same vote as unsigned Voter.vote calldata to sign yourself
npx aero-vote-radar backtest --veaero 25000 --epochs 5
npx aero-vote-radar backtest --veaero 25000 --min-consistency 0.5 # test the filtered strategy you actually vote
npx aero-vote-radar review --address 0xYourAddress # score the vote you already cast, against what the epoch settled at
npx aero-vote-radar my-veaero 0xYourAddress
npx aero-vote-radar my-veaero 0xYourAddress --jsonWorking from a clone instead of the published package? Every command above is the same with
npx tsx src/cli.ts in place of npx aero-vote-radar — that is the form the sample runs
below were captured in.
Pass --json to any command for machine-readable output instead of a table — useful for piping into other scripts or tools.
recommend and backtest take either --veaero <amount> or --address <0x...>; with an address they read your live voting power off-chain so you don't have to look it up and retype it.
Real output from live runs (Base mainnet, no mocking):
$ npx tsx src/cli.ts pools --top 8
Top 8 Aerodrome pools by predicted $/veAERO vote (of 101 live-gauge pools with votes):
Symbol Votes(veAERO) Latest $/vote Predicted $/vote Edge Consistency
vAMM-cbBTC/CHAMP 7,681 $0.000963 $0.1 10440.0% 0.35
vAMM-1000X/WETH 893 $0.003128 $0.02 478.0% 0.54
vAMM-WETH/FAI 1,242 $0.01 $0.01 -3.6% 0.54
sAMM-WETH/msETH 494,716 $0.007752 $0.01 33.7% 0.68
vAMM-USDC/SEND 1,344 $0.009698 $0.01 5.0% 0.60
vAMM-cbBTC/EDGE 67,411 $0.002594 $0.009726 275.0% 0.41
vAMM-WETH/EURC 6,197 $0.000258 $0.006274 2334.5% 0.57
vAMM-YFI/wstETH 146,052 $0.002505 $0.006189 147.1% 0.44That top row is exactly why consistency exists: a 10,440% "edge" on 0.35 consistency is one big one-off bribe, not a repeatable weekly opportunity. --min-consistency 0.5 filters that class of pool out entirely.
--vote-ready prints whole percentages that sum to exactly 100, which is what Aerodrome's voting UI accepts — rounding each weight independently tends to total 101% or 102% and leaves you fudging the last row by hand:
$ npx tsx src/cli.ts recommend --address 0x28aa...0108 --vote-ready --min-consistency 0.5
Using 11,491,441.934 veAERO of live voting power from 0x28aa...0108 (2 lock(s)).
Vote-ready weights for 11,491,442 veAERO — whole percentages, summing to exactly 100:
51% vAMM-VIRTUAL/WETH
25% sAMM-WETH/msETH
18% vAMM-WETH/MET
2% vAMM-WETH/DRV
2% vAMM-wBLT/BMX
1% vAMM-WETH/GHST
1% vAMM-Anon/USDC
Enter these directly on aerodrome.finance. Expected next epoch: $11,013.66.(Captured before recommend grew its epoch-deadline line — see "A recommendation expires" above — so current output adds the deadline line here, This tool never touches your wallet or keys., and — under 1,000,000 veAERO — the vote-basis caveat described in "Where the default basis is not the one the evidence favours".)
$ npx tsx src/cli.ts backtest --veaero 25000 --epochs 5
Backtest over the last 5 epoch(s) with 25,000 veAERO:
EpochsAgo Radar $ (current) Naive $ Naive picked
0 $137.3 $2.7 vAMM-1000X/WETH
1 $12.82 $13.32 vAMM-WETH/RWAX
2 $16.84 $3.34 vAMM-WETH/AIXCB
3 $17.02 $5.17 vAMM-WETH/FAI
4 $16.28 $6.39 vAMM-USDC/SQD
Total: radar $200.26 vs naive $30.91 — uplift 547.8%
Radar earned more in 4 of 5 epoch(s).This example predates the vote-basis comparison described above: current output also prints a Radar $ (typical) column beside Radar $ (current), and a closing Vote basis: typical ... vs current ... — ... line — the same comparison predict-check measures, applied to whatever epochs this specific run replayed.
Read that with the caveats above in mind: 4-of-5 epochs is encouraging, but one epoch (0) supplies most of the total, and five weekly epochs is a small sample. The point of the command is that you can re-run it yourself rather than trust the claim.
$ npx tsx src/cli.ts my-veaero 0x28aa4F9ffe21365473B64C161b566C3CdeAD0108
veAERO locks for 0x28aa4F9ffe21365473B64C161b566C3CdeAD0108:
NFT #6: 11,362,738.622 veAERO voting power, expires never (permanent lock)
NFT #17324: 107,871.726 veAERO voting power, expires never (permanent lock)
Total voting power: 11,470,610.348 veAEROAs an MCP server
There is also a SKILL.md at the repo root: the short version an
agent should read before using any of this, including which command to run first
and the four ways to misread the output.
npx aero-vote-radar-mcp # from a clone: npx tsx src/mcp-server.tsPoint any MCP-capable agent (Claude, etc.) at that command — in Claude Code, claude mcp add aero-vote-radar -- npx -y aero-vote-radar-mcp. It exposes five tools:
list_pool_efficiency— ranked pools with current + predicted $/vote, predictive edge, consistency, and momentum (recent-vs-older completed-epoch trend). OptionalminConsistencyfilter.recommend_allocation— given a veAERO amount or a walletaddressto read it from, returns weights, whole-percent vote weights, and expected USD per pool.backtest_strategy— replays past epochs and compares this strategy against naive APR-chasing.get_my_veaero— looks up an account's veAERO locks and total voting power.prepare_vote_calldata— turns a recommendation'svotePercentsinto the unsignedVoter.votetransaction for the veNFT's owner to sign in their own wallet. Signs nothing, sends nothing.
Example agent prompts:
"Using aero-vote-radar, recommend an allocation for my 25,000 veAERO."
"Work out my veAERO from 0xMyAddress and give me vote weights I can type straight into Aerodrome, skipping pools with consistency below 0.5."
"Backtest the aero-vote-radar strategy over the last 6 epochs with 25,000 veAERO."
Project layout
src/
constants.ts verified Base mainnet contract addresses
abi.ts minimal ABIs (only the methods this project calls)
chain.ts viem public client (Base RPC, retry/batch configured)
prices.ts DefiLlama USD price lookup + cache
pools.ts pool discovery (Voter) + epoch history (RewardsSugar)
efficiency.ts current & trend-predicted $/vote ranking + consistency scoring + epoch series
trend.ts reward-period boundaries (RewardPeriod/WEEKLY_EPOCH) + momentum over completed epochs only
dilution.ts typical settled vote weight, refill ratio, and the previous-epoch vote basis
allocator.ts greedy marginal ("water-filling") allocation + whole-percent vote weights
backtest.ts replays past epochs to score this strategy against naive APR-chasing
calldata.ts unsigned Voter.vote transaction built from a vote-ready allocation
veAero.ts VotingEscrow wrapper for a user's voting power
util.ts address/concurrency helpers + shared error-message formatting
snapshot.ts builds the JSON snapshot the web app reads
mcp-server.ts MCP stdio server entrypoint
cli.ts CLI entrypoint
snapshot-cli.ts entrypoint for the scheduled snapshot job
predict.ts pure scoring for which vote-basis predictor is actually most accurate
predict-cli.ts entrypoint for `npm run predict-check`
settled.ts the committed scan history + the settled on-chain answer, shared by both measurements
timing.ts pure scoring for how much of a ranking survives to the epoch's close
timing-cli.ts entrypoint for `npm run timing`, writes docs/data/timing.json
voted.ts what a lock actually voted for, and what that vote earned once the epoch settled
accrual.ts pure separation of real accrual from reward-token repricing, using the published raw amounts
accrual-cli.ts entrypoint for `npm run accrual`, writes docs/data/accrual.json
docs/ the web app, served by GitHub Pages
index.html static page: reads the snapshot, runs the allocator client-side
data/
snapshot.json latest scan, refreshed every 6 hours by CI
timing.json survival + accuracy measurement, refreshed weekly by CI
accrual.json accrual-vs-repricing measurement, refreshed weekly by CI
test/
allocator.test.ts unit tests for the greedy marginal-allocation algorithm and percentage rounding
backtest.test.ts unit tests for the backtester, including that it never peeks at the epoch under test
efficiency.test.ts unit tests for per-pool efficiency math (trailing average, $/vote, predictive edge, consistency)
trend.test.ts unit tests for epoch boundaries and momentum, including the part-week exclusion
dilution.test.ts unit tests for typical settled weight, refill ratio, and the previous-epoch vote basis
prices.test.ts unit tests for DefiLlama price lookup, batching, and USD conversion
pools.test.ts unit tests for pool discovery's alive-gauge filter and failed-call skip logic
calldata.test.ts unit tests for the unsigned vote transaction, decoding the bytes back rather than matching a stored hex string
veAero.test.ts unit tests for the veAERO NFT summary mapping (toVeNftSummary)
util.test.ts unit tests for isValidAddress, mapWithConcurrency, and formatError
cli.test.ts unit tests for CLI flag parsing
snapshot.test.ts unit tests for the published snapshot shape
mcp-server.test.ts unit tests for the MCP tools' veAero/address budget resolution
predict.test.ts unit tests for predictor scoring (log-scale error, bias, closest-of, pool-size buckets)
predict-cli.test.ts unit tests for snapshotsFromDir, including that a malformed file is skipped rather than crashing the run
timing.test.ts unit tests for top-ten survival, the window a scan falls in, and the late-mover threshold
timing-cli.test.ts unit tests for buildAccuracyReport, including that an empty pool-size bucket is dropped rather than published as NaN
voted.test.ts unit tests for scoring a cast vote against settled weight, including the unscorable-pool and zero-weight cases
accrual.test.ts unit tests for revaluing at one price vector, the epoch-boundary skip, and the pools-that-only-repriced split
site-parity.test.ts runs docs/index.html's hand-ported allocator/countdown/vote-basis logic against src/ on the same inputsTesting
npm testTests cover the allocator (recommendAllocation) with synthetic pool data — budget conservation, that a single candidate gets 100% of the allocation, that topK is actually respected, and specifically that self-dilution works: two pools with identical incentives and existing votes get split roughly evenly under a large budget instead of an APR-only optimizer dumping everything into one. toWholePercentWeights is tested to always total exactly 100 (six equal weights land on 4x17 + 2x16, not six 17s summing to 102), including on real recommendAllocation output. The backtester (runBacktest) is tested for the property that matters most in a backtest — no lookahead: a pool that pays a $10,000 jackpot in the epoch under test but was worth $0 in every epoch before it must not be picked, and its jackpot must not appear in the result. It's also checked against the dilution maths directly (budget equal to a pool's existing votes earns exactly half the pot), for beating the naive all-in baseline when the budget is large relative to pool votes, and for returning null uplift rather than dividing by a zero baseline. trend.ts is tested for the property the feature lives or dies on — that a mid-week scan's part-week epoch cannot manufacture a trend: a steady $200/epoch pool caught three days into an epoch worth $60 so far must report level movement, and the same series read as fully closed must report a decline. Epoch boundaries are asserted to land on Thursday 00:00 UTC, the weekly period is asserted to reproduce the epoch wrappers exactly while a 48-hour window anchored away from Unix time zero is checked to count its own boundaries, an odd-length history is checked to drop its middle epoch so both halves weigh equally, and a zero-paying older half is checked to report null rather than infinite growth. The pure per-pool efficiency math (computePoolEfficiency/epochUsd/computeConsistency in efficiency.ts) is covered the same way — trailing-average computation, the MIN_TRAILING_USD cutoff, the zero-votes exclusion, the predictiveEdge divide-by-zero guard, and that a $600-then-nothing pool scores far below a steady $100/epoch pool with the same average — plus DefiLlama pricing/batching (prices.ts), the veAERO NFT summary mapping (toVeNftSummary in veAero.ts, including permanent-lock and large-id precision handling), CLI flag parsing (cli.ts), the MCP tools' shared resolveVeAeroBudget (both-provided and neither-provided rejections, and the plain-amount pass-through — the same branches cli.ts's resolveBudget already covers, since both wrap the same veAero/address contract for their respective tools), and the address/concurrency/error-formatting helpers (util.ts), all with synthetic inputs and no network access. formatError (in util.ts) reduces a thrown error to a single clean line — preferring viem's concise .shortMessage over its multi-paragraph .message — and is shared by both the CLI's top-level error output and every MCP tool handler's error result, so a failed RPC call surfaces the same readable message however the tool is invoked. Pool discovery's own filtering logic — filterAlivePools (drop pools whose gauge isn't alive, keeping the paired gauge address aligned) and resolvePoolInfo (skip a pool if any of its symbol/token0/token1 calls failed, rather than letting one non-standard pool take down the whole scan) — is unit-tested the same way. The remaining on-chain data-fetching code (fetchActivePools/fetchPoolEpochs in pools.ts, fetchVeAeroPositions in veAero.ts) is exercised live against Base mainnet via the CLI instead — see the real example output above.
voteBasisCaveat is tested for the property that makes it worth printing at all — that it fires on exactly the side of the measured crossover where the chosen basis is the unfavoured one, and stays silent otherwise — and site-parity.test.ts runs the page's copy of it against src/'s across a range of budgets and all three bases, so the page cannot go on reassuring a voter the CLI would warn. The unsigned vote transaction (buildVoteCalldata) is tested by decoding its own output back with viem rather than by comparing it to a hex string captured from an earlier run — which would only prove the function still does whatever it did last time — covering the token id beyond Number.MAX_SAFE_INTEGER, checksummed pool addresses, and every refusal: a weight total other than 100, a repeated pool, a fractional or non-positive weight, an empty allocation (which would clear the NFT's votes rather than cast them) and a malformed id. chooseVoteTokenId is tested for the case that has no safe default — a wallet holding several locks, where it names them and stops. wrapText is checked to break only on spaces, to never add, drop or reorder a word, and to leave a word longer than the width whole rather than cutting an address in half.
CI (.github/workflows/ci.yml) runs the typecheck, build, and test suite on every push.
License
MIT
This server cannot be deployed
Maintenance
Related MCP Connectors
Official Aave MCP for V3 and V4 markets, positions, governance, and transaction preparation.
MCP server for Boson Protocol — on-chain agentic commerce for physical & digital goods.
Deterministic MCP utilities, validation, evidence verification, and x402 commerce on Base.
Self-hosted MCP server: 26 deterministic dev, security, and EVM tools.
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceMCP (Model Context Protocol) server for the MAIN DEX on Base. Provides AI agents (Claude, Cursor, etc.) with tools to interact with the protocol: swap tokens, manage liquidity, enter/exit ALM strategies(10% APY), and more.MIT
- AlicenseAqualityAmaintenanceSelf-custodial crypto portfolio and DeFi MCP server. Read balances and positions (Aave, Compound, Morpho, Uniswap V3, Lido, EigenLayer) across Ethereum, Arbitrum, Polygon, and Base, and prepare transactions for approval on a Ledger via WalletConnect.100120 npm4Business Source 1.1
- FlicenseNot gradedqualityDmaintenanceAutonomous MCP server for DeFi yield optimization, enabling yield opportunity discovery, treasury snapshots, market signals, and portfolio rebalancing.-
- FlicenseAqualityBmaintenanceMCP server that forecasts next-epoch demand for Aerodrome pools on Base and turns it into concrete incentive-allocation recommendations.68-