Skip to Content
⚔ BattleMeme docs · early preview · expect rough edges
ProtocolTokenomics

Tokenomics

The math behind every BattleMeme token: bonding curve, fees, supply lifecycle, scoring, and graduation LP shape.

Headline numbers

Initial mint per token1,600,000,000 (1.6B) — all seeded into the 5 LP rungs
Final winner supply≤ 1.6B — leftover unsold tokens are burned at graduation; only mints after that go to ClaimManager for loser claim coverage
LP rungs5 × 320M tokens each
Tick range[127320, 173220] = ~99× price growth
Max ETH cap~990 ETH if curve fully consumed
Queue trigger5 ETH raised (creator’s 0.01 ETH pre-buy counts)
Early finish950 ETH in pool at commenceBattle
Battle duration~19 hours = 4 × 1h trade + 3 × 5h rest (last round finalizes immediately)
Pool fee1.3% — split on sweep: 1.0% LP fee + 0.3% creator (perpetual)
Creation fee0.01 ETH — used as an atomic creator pre-buy at the pool’s initial price
Creator upfront grant0% — only what the pre-buy bought at floor
NetworkEthereum mainnet

1. Bonding curve — laddered single-sided LP

BattleMeme uses 5 single-sided concentrated LP positions chained together on Uniswap v4 instead of a custom curve via BeforeSwapDelta. Curve dynamics emerge from AMM math (no hardcoded formula); every swap runs natively through Uniswap UI / aggregators / DexScreener.

Pool configuration

ParamValue
PairETH (currency0) / TOKEN (currency1) on Uniswap v4
Pool fee13_000 = 1.3% — accrues to LP, swept then split 1.0% LP fee + 0.3% creator
Tick spacing60

Raw price = TOKEN per ETH:

Ptop=1.000117322033.3M (initial)Pbottom=1.0001127320336k (final)PtopPbottom99×P_\text{top} = 1.0001^{173220} \approx 33.3\text{M (initial)} \qquad P_\text{bottom} = 1.0001^{127320} \approx 336\text{k (final)} \qquad \frac{P_\text{top}}{P_\text{bottom}} \approx 99\times

Bootstrap flow

Position layout

Each rung is 9180 ticks wide (≈ 2.51× price growth), stored under salt = bytes32(i) for i ∈ 0..4.

RungTick rangeTokensNote
#0[164040, 173220]320Minitial — pool starts at top
#1[154860, 164040]320M
#2[145680, 154860]320M
#3[136500, 145680]320M
#4[127320, 136500]320Mgraduation edge

Mechanics

  • Pool initialized at sqrtPriceX96 corresponding to tick 173220 (top of position #0).
  • At bootstrap, all 5 positions are “above range” → each holds 100% TOKEN.
  • A buy (ETH → TOKEN, zeroForOne swap) consumes token1 from position #0 first; price walks down.
  • When price crosses 164040 (bottom of #0), #0 is fully sold and #1 starts being consumed.
  • Continues through to #4’s bottom at tick 127320 → graduation edge.

Capital efficiency

5 chained narrow positions raise ~2× more ETH than 1 single LP covering the same range with the same tokens:

ArchitectureTokensRangeETH max raise
Single LP (same tokens & range)1.6B99×~480 ETH
Laddered 5 positions1.6B99×~990 ETH (~2×)

Why: each narrow position has higher concentrated liquidity (L), so each unit of L supplies more amount0 in its sub-range. Sum of 5 narrow positions raises more ETH than 1 wide position for the same tokens.

Position breakdown

(initial price = 0.000075atETH=0.000075 at ETH = 2500, range 100× total, scaled by ×8 from the canonical 200M model since LP_MINT = 1.6B)

Pos #TokensPrice fromPrice toMult start → endETH consumedCum ETH
#0320M$0.0000750$0.0001881.00× → 2.51×≈ 15.2015.20
#1320M$0.000188$0.0004732.51× → 6.31×≈ 38.2453.44
#2320M$0.000473$0.0011886.31× → 15.85×≈ 96.00149.44
#3320M$0.001188$0.00298415.85× → 39.81×≈ 241.12390.56
#4320M$0.002984$0.007539.81× → 100.00×≈ 605.68996.24
TOTAL1.6B$0.000075$0.00751× → 100×≈ 996 ETH

(USD prices assume ETH = $2,500; actual ETH-per-position is the deterministic value.)

Curve characteristics

  • Smooth FOMO early on — narrow positions, price climbs slowly
  • Parabolic near graduation — later positions have higher ETH caps, FOMO intensifies
  • No quadratic formula — shape emerges from the concatenation of 5 AMM curves
  • Capital efficient — ~2× ETH cap vs single LP at same range
  • Native AMM — DexScreener / Uniswap UI / aggregators work out of the box

2. Phase lifecycle

The token’s state machine has 9 states. See State machine for the table; here’s the high-level flow:

NONE → INIT → QUEUE → WARMUP → BATTLE ⇄ RESTING → ELIMINATED/GRADUATED → LOST / (stays GRADUATED)

Phase 1: INIT (created → 5 ETH raised)

  • Pool: laddered LP active, all positions hold 100% TOKEN
  • Trading: open buy/sell via Uniswap UI / router / aggregators
  • Hook tracks totalETHRaised, ethRaisedInPool
  • Trigger to queue: totalETHRaised ≥ INIT_ETH_THRESHOLD (5 ETH)
    • 5 ETH falls inside position #0’s range (which caps at ~15.2 ETH)
    • Price multiplier at trigger ≈ 1.3–1.5×

Phase 2: QUEUE (frozen, up to 24h)

  • Trading: beforeSwap reverts TokenFrozenForTrade
  • Token appended to orchestrator’s queue
  • Auto-start when full (10 tokens) or 24h timeout reached with ≥ 2 in queue
  • < 2 in queue at timeout → unlock (back to INIT)

Phase 3: WARMUP (10 minutes)

  • All selected tokens transition QUEUE → WARMUP simultaneously
  • Trading still frozen; UI shows countdown (warmupEndsAt) so creators can rally
  • After 10 minutes, anyone can call commenceBattle(battleId) → WARMUP → BATTLE

EARLY_FINISH check @ 950 ETH: at commenceBattle, if any active token already has ethRaisedInPool ≥ EARLY_FINISH_THRESHOLD, orchestrator finalizes immediately with that token as winner (skipping all rounds).

Phase 4: BATTLE (4 rounds, fixed timing, ~19h total)

Total duration:

Tbattle=4TRADE_WINDOW+3REST_WINDOW=4+15=19 hoursT_\text{battle} = 4 \cdot \text{TRADE\_WINDOW} + 3 \cdot \text{REST\_WINDOW} = 4 + 15 = 19\ \text{hours}

(last round has no trailing rest — it finalizes immediately on cut.)

Constants:

ConstantValue
TRADE_WINDOW1 hour
REST_WINDOW5 hours
ROUND_GAP (= TRADE + REST)6 hours
TOTAL_ROUNDS4
WARMUP_DURATION10 minutes
QUEUE_TIMEOUT24 hours
UNLOCK_COOLDOWN24 hours

Battle scoring — live ETH in pool

score(token)=hook.getEthReserves(poolId)=ethRaisedInPool\text{score}(\text{token}) = \text{hook.getEthReserves}(\text{poolId}) = \text{ethRaisedInPool}

The live, signed net ETH balance across the 5 LP positions. No composite formula. No hidden weights. Sells reduce score immediately; buys add immediately. Pure capital-driven.

Cut size per round

See Bracket cut table for the full lookup. R4 always reduces to top 1.

Tiebreaker

Ties go to the earliest-indexed token in active[] (= queue insertion order). No time-to-5-ETH or other side rule.

Random source

The protocol has no on-chain randomness:

  • Round gap: constant ROUND_GAP
  • No VRF, no block.prevrandao
  • Chainlink VRF is NOT used. Only Chainlink Automation (keeper-driven upkeep), itself permissionless.

Phase 5: Resolution

Winner → GRADUATED:

  1. Snapshot winner’s sqrtPriceX96 from slot0
  2. hook.withdrawForOrchestrator(winner) → unwind all 5 LPs; ETH → orchestrator (winnerEth)
  3. For each ELIMINATED loser, compute ratio = winnerPriceQ96 × 1e18 / loserPriceQ96. Mint winnerCover to ClaimManager via hook.provideTokens. Register with ClaimManager.
  4. hook.burnLeftoverTokens(winner) — burn 100% leftover excess
  5. hook.disableMintFor(winner) — permanent mint freeze
  6. hook.transitionToGraduated(winner)
  7. GraduationLP.deployForGraduation(...) builds the post-grad wide + wall LP (see §5)

Loser → ELIMINATED → LOST:

  1. During battle: mid-cut → state → ELIMINATED, eliminationPrice snapshotted, ETH escrowed
  2. At finalize: registered with ClaimManager → state → LOST
  3. Transfers frozen except through the hook’s burn-to-claim path
  4. Holders can claim winner tokens at any time. No deadline.

3. Loser → Winner compensation

ClaimManager.claim(loserToken, amount) converts loser tokens to winner tokens at the locked-in ratio:

winnerAmount=amount×ratio1018ratio=winnerPriceQ96×1018loserPriceQ96\text{winnerAmount} = \frac{\text{amount} \times \text{ratio}}{10^{18}} \qquad \text{ratio} = \frac{\text{winnerPriceQ96} \times 10^{18}}{\text{loserPriceQ96}}

The ratio is USD-fair at-the-moment-of-elimination: a loser’s tokens are converted to winner tokens at the price ratio when the loser exited. Subsequent winner price moves don’t change past claims.

No deadline. Holders may claim indefinitely. The winner-token reserve was pre-minted to ClaimManager at finalize, so claiming is just a transfer (no further interaction with hook needed).

4. Graduation LP

At graduation, GraduationLP deploys two positions per battle, both custodied by GraduationLP itself, locked for GRADUATION_LP_LOCK:

Anchor tick

anchorTick = nearest TICK_SPACING to current pool slot0 tick (post-unwind).

Wide LP — price discovery

Range: [anchor − 16080 (clamped), anchor + min(GRAD_WIDE_UPSIDE_TICKS, headroom)] ≈ ±5× downside / up to ~1000× upside Funded: winnerEth + winner tokens (minted via hook.provideTokens) Purpose: two-sided liquidity for normal buying / selling

GRAD_WIDE_HALF_WIDTH = 16080, GRAD_WIDE_UPSIDE_TICKS = 69120. The 69,120-tick upside ≈ 1.0001^69120 ≈ 1000× pump headroom above graduation price. Covers PEPE-tier (100×), WIF-tier (1000×). Beyond that (DOGE/SHIB-tier) would exhaust into the ceiling — once-a-decade events.

Wall LP — buy support from loser ETH

Range: [anchor + TICK_SPACING, anchor + TICK_SPACING + 540] ≈ 5%-deep band just ABOVE current tick (Buys push tick DOWN, sells push tick UP → wall is hit by sells first) Funded: loserEth only (single-sided) Purpose: absorbs sell pressure with strong ETH support

GRAD_WALL_DEPTH = 540.

LP locking

  • In-protocol lock — GraduationLP custodies positions in mapping(battleId => GradLock). After unlockAt = creation + GRADUATION_LP_LOCK, anyone can call unlock(battleId, recipient) to withdraw principal to recipient.
  • During lock, anyone may call collectFees(battleId) → fees split 1.0% → factory.feeRecipient() + 0.3% → winner’s creator (same split as pre-grad sweep).
  • UNCX is NOT integrated. The interface IUNCXV4Locker exists as a future migration target; all locking is custom in-protocol today.

Behavior post-grad

  • Original 5 laddered LPs are fully unwound at finalize — they no longer exist
  • The wide + wall LP is the only protocol-owned liquidity for the winner token (third-party LPs may join after grad)
  • Sells push tick UP into the wall (sellers receive ETH from the wall); wall depletes first, then wide LP carries → gradual slippage, no crash to 0

5. Voucher campaigns (separate creator promo feature)

VoucherManager is independent of the battle lifecycle. It is not part of loser-side compensation, not linked to any battle state transition, not consulted by the orchestrator.

Two modes:

  • Link mode — creator signs off-chain ECDSA vouchers, hands them out (DM/QR/email)
  • Whitelist mode — creator publishes a Merkle root; wallets self-redeem

See VoucherManager for full API.

6. Total token supply

Lifecycle of the supply

Finalize — what happens to the winner’s tokens

Final winner supply

totalSupplywinner=users’ wallets+wide LP+ClaimManager reserve    1.6B\text{totalSupply}_\text{winner} = \text{users' wallets} + \text{wide LP} + \text{ClaimManager reserve} \;\leq\; 1.6\text{B}

Typically less than 1.6B — leftover LP tokens are burned. Expect 0.3–1.6B circulating post-grad depending on how much of the curve was consumed.

Loser supply

  • State → LOST; transfers frozen except via the burn-to-claim path.
  • Loser totalSupply shrinks over time as holders call ClaimManager.claim (each call burns their loser balance).
  • hook.burnLeftoverTokens also runs on losers post-elimination to burn any LP-returned tokens still on the hook.

7. Edge cases & math safety

Position fully consumed mid-battle

When an LP position runs dry (price crosses its tickLower), Uniswap v4 auto-switches to the next position. No special handling needed.

LP fully consumed (price hits graduation tick 127320)

Pool enters “below all ranges” state. Further buys can’t consume liquidity → swaps revert. In practice, EARLY_FINISH (ethRaisedInPool ≥ 950 ETH, checked at commenceBattle) would have already triggered before this.

Single-token-in-queue edge case

Queue needs ≥ MIN_TOKENS_PER_BATTLE (= 2) to start a battle. If only 1 token at QUEUE_TIMEOUT → unlock back to INIT, stamp unlockedCooldownUntil. The cooldown is stamped but never enforced — the token can immediately re-qualify for queue (TODO if behavior is intended).

Battle with all-zero scores

Score = getEthReserves ≥ 0 always (clamped). If all active tokens have 0 ETH in pool (e.g., all dumped fully), tiebreaker = first-index in active[].

All public entrypoints (tickQueue, commenceBattle, runRound) are permissionless — anyone can call them once the time gate passes. Chainlink is a fallback automator, not a trust assumption.

sqrtPrice extreme

Pool sqrtPrice initialized at tick 173220. Tick range 127320–173220 well within v4 limits (max ±887272). No overflow concerns.

Composite-score plumbing unused

BattleScoring.sol library, BuyerData.firstSeenAt / hold, hook’s roundScores[i] array, and commitRoundScore function all exist but are not exercised by the orchestrator. They’re scaffolding for an optional future composite score.

8. Constants quick reference

// Money / supply CREATION_FEE = 0.01 ether INIT_ETH_THRESHOLD = 5 ether EARLY_FINISH_THRESHOLD = 950 ether LP_MINT = 1_600_000_000 ether MIN_BUYER_ETH = 0.01 ether // Pool POOL_FEE = 13_000 // 1.3% PLATFORM_FEE_BPS = 10_000 // 1.0% — factory.feeRecipient CREATOR_FEE_BPS = 3_000 // 0.3% — BattleMemeToken.creator TICK_SPACING = 60 LP_TICK_UPPER = 173_220 LP_TICK_LOWER = 127_320 TICK_WIDTH_PER_POSITION = 9_180 N_POSITIONS = 5 TOKENS_PER_POSITION = LP_MINT / 5 // 320M // Queue / Warmup QUEUE_TIMEOUT = 24 hours UNLOCK_COOLDOWN = 24 hours WARMUP_DURATION = 10 minutes MIN_TOKENS_PER_BATTLE = 2 MAX_TOKENS_PER_BATTLE = 10 // Battle TRADE_WINDOW = 1 hour REST_WINDOW = 5 hours ROUND_GAP = TRADE + REST // = 6 hours TOTAL_ROUNDS = 4 MIN_BUYER_HOLD = 30 minutes // unused (reserved for future) // Graduation GRADUATION_LP_LOCK = (set in BattleConfig) GRAD_WIDE_HALF_WIDTH = 16_080 // ticks GRAD_WALL_DEPTH = 540 // ticks GRAD_WIDE_UPSIDE_TICKS = 69_120 // ticks (~1000× upside) // Vouchers MIN_ETH_PER_VOUCHER = 0.001 ether MAX_VOUCHERS_PER_CAMPAIGN = 1000

See also: Architecture, State machine, Cut table, Graduation LP, Security.