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, 2026 9 min readdesignengineering

The first version of this launchpad was a bonding-curve protocol. You can still read it in contracts/BondingCurvePool.sol and contracts/LaunchpadFactory.sol; we left the files in the repository so the history is not lost mid-migration. 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 code still compiles and nothing imports it. Once the V3 path has traded on mainnet we will delete it.