Skip to content

Blog

Why we dropped the bonding curve

We shipped a constant-product bonding curve with anti-snipe tax, TWAP caps, and an atomic migration to a V2 pool. Then we deleted it from the code path. A post-mortem on a design that worked and still lost.

September 10, 20269 min readdesignengineering

The first version of this launchpad was a bonding-curve protocol. You can still read it in the git history (contracts/BondingCurvePool.sol, contracts/LaunchpadFactory.sol and the TypeScript that settled trades against it). It had virtual reserves of 2 ETH and 1.073 billion tokens, a 5 ETH graduation goal, a 1% fee split 70/30, a 99% anti-snipe tax decaying cubically over three seconds, per-wallet caps under a 15-minute TWAP window for “crowd” launches, and an atomic graduation that added liquidity to a V2 router and burned the LP to 0xdEaD. It had tests. The curve maths was cross-checked between Solidity and TypeScript against the same inputs.

It was fine. Here is why it is gone.

1. The curve was a custody period

From the first buy until graduation, every buyer’s ETH lived in our pool contract. Our code, not Uniswap’s, was what stood between that ETH and a bug. We had a reentrancy guard and a fee-push fallback for recipients that could not accept ETH within a gas stipend — we had found that a creator wallet with a receive hook could brick the pool for everyone, and fixed it by booking pending fees instead of reverting the trade. Every one of those fixes was a reminder that we were running a DEX, badly, for the duration of the curve.

2. Graduation was a moment, and moments fail

The migration was one transaction: withdraw, approve, addLiquidityETH, burn LP. On mainnet Robinhood Chain there is a real Uniswap V2 router. On testnet there is not — the addresses the explorer returns under that name are other people’s test deployments — so on testnet pools graduated without migrating liquidity at all, which meant the exact code path that mattered most was the one we could least rehearse. We also had to guard against a router that took custody of everything and did something unexpected. That is a lot of surface for an event that fires once per token.

3. Two price regimes, two of everything

Pre-graduation, price came from our reserves. Post-graduation, from the V2 pair. The indexer needed both. The chart needed both. The swap panel needed both. Our indexer re-read reserves from the pool on every pass instead of trusting event deltas — a good rule — but it was a rule we needed only because we had invented a second source of truth.

4. The graduation goal was a tuning knob nobody could tune well

We originally set 10 ETH and found launches stalling halfway up. On a constant-product curve with 2 ETH virtual reserve, 10 ETH raised means the quote reserve goes 2 → 12, a 6× on reserves and a 36× on price, before anything could graduate. We cut it to 5 ETH (a 12.25× price move). It was better. It was still a number we were picking on behalf of every creator, with a cliff on the far side of it.

What replaced it

pons, another launchpad on Robinhood Chain, had already demonstrated the alternative: deposit the whole supply straight into a one-sided Uniswap V3 position and lock the NFT. A one-sided V3 range is a bonding curve — the same constant-product relationship, with V3 doing the arithmetic in square-root space — and it lives on the DEX from block one. There is nothing to migrate because it is already there.

Bonding curve (retired)Locked V3 position (current)
Custodian pre-graduationOur pool contractUniswap V3
GraduationMigration transactionA threshold read; nothing moves
Price sourcesTwo (reserves, then V2 pair)One (slot0)
Fee mechanismCustom, pushed on tradeUniswap fee tier, collected on claim
Anti-snipeTime-decaying tax + TWAP capsBlock-keyed caps for two blocks
Testnet rehearsalPartial (no V2)None (no V3)
Contract surface we wrote~3 contracts + curve maths3 contracts adapted from verified source

We lost some things. The anti-snipe tax, which stayed in the reserve and deepened the LP instead of becoming revenue, was a design we liked. The crowd-launch TWAP window is gone. The opening buy used to be capped at 5% of supply; on the new design it is not capped at all, which we discuss in another post. These are real trade-offs, and we made them for one reason: the amount of code we have to be right about went down by more than the amount of behaviour we gave up.

The retired application code is gone: the database-settled API routes, the curve maths, its indexer and its tests were deleted once nothing imported them. What remains is the git history and the contracts themselves, kept as a record of the design this post is about.