Skip to main content

Vision

The direction is known; the destination is not. This file records the grand plan as it stands so that it does not live in a transcript. It is not a roadmap and it is not a commitment — it is the set of things we intend to be able to do, and the seams that keep them possible.

Read it alongside CLAUDE.md's design laws, which it must never contradict.

The product: a launchpad for experiments

Settled 15 Sep 2026 (D26). A launchpad like every other is not a business. Ours is where people launch experiments — tokens whose point is a novel mechanic. ../888 is the model: fees fed a slot machine, and holding the token was itself a balancing game.

We build capabilities; experiments build mechanics. Holder payouts, time weighting, gacha packs, a slot machine — each is one experiment's feature, never ours. Ours is what lets them plug in: a hook of their own, fees routed where they choose, a view of balances moving, and isolation from every other launch.

Stocks and composable pairs are not experiments — they are standard, part of the launchpad itself.

Order: finish the launchpad core as an MVP on the local stack (D27). Then three separate efforts: stocks, hooks, and the modular infrastructure the experiments need.

The sections below on fee policy, lots and gacha packs were written as our roadmap. Read them as examples of experiments the infrastructure must be able to host.

Milestone one

The first thing that has to exist end to end:

  1. Tokens launch on a bonding curve
  2. They trade on it
  3. At a threshold they graduate into a Uniswap v4 pool
  4. Tokens can be paired with tokenised stocks — "memestocks"
  5. Composable pairing — a token paired against a basket, e.g. 50% HOOD / 50% TSLA
  6. A few v4 hooks. Amended by D15: fee redistribution is not one of them — it is a strategy behind FeeVault, and the hook only observes so that per-swap behaviour is possible at all

The load-bearing distinction: mechanism vs policy

The v4 hook is part of the PoolKey and can never be changed for the life of a pool. So the architecture divides in two, and the division is the whole design:

fixed at launch, foreverswappable afterwards
Mechanism — which currency is skimmed, at what capped rate, which callbacks fire
Policy — what happens to the money once it is skimmed

Everything we want to experiment with is policy. That is why we do not need to know the destination: the hook stays dumb, capped and boring, and the interesting part is a pointer.

The three seams

Every future feature has to arrive through one of these. If it needs a fourth, that is a design conversation, not an implementation detail.

seamwhat it swapstemplate
Fee strategywhat happens to collected feesFeeVault.strategy — built, here
Token observerwhat watches balances changeone nullable address called in _update
Quote assetwhat a token is paired againstper-launch, from an approved set

Fee strategy

One address, one narrow interface, failure swallowed, gas capped. A strategy is safe to allow — eventually from outside developers — exactly when it cannot:

  1. revert a swap
  2. reach another launch's money
  3. block an exit from the curve or the pool

With those three, a malicious strategy harms only the token that chose it. Malice becomes a disclosure problem rather than a theft.

Condition 2 is not met today. The seam is built and 1 and 3 are proved — a broken or looping strategy cannot revert a trade — but FeeVault accounts per currency, so distribute hands a strategy every launch's fees in that asset at once. Until O17 is answered a strategy is ours to write, never an outside developer's.

Token observer

The ERC20 gets one extension point and then is frozen forever: a single nullable observer called from _update, wrapped, gas-capped, and guarded on address(this).code.length so a self-call during construction cannot revert uncatchably.

This is the answer to "the token template will need updating as features arrive." It will not. The observer is what gets versioned. ../888 proved the failure mode in the other direction: a token that notified a registry gave the registry the power to brick the token, and through it the pool.

Quote asset

Per-launch, chosen from a protocol-approved set. This is where stocks and baskets enter, and it keeps them entirely outside the launchpad core.

Fee policy — the directions we expect to explore

All of these are strategies behind the first seam. None requires a different hook.

  • Fees pushed directly to holders' wallets, no claim, no keeper — ../concept proved this is possible on borrowed gas
  • Fees to the top N holders, or to holders above a threshold share
  • Time-weighted distribution, by how long a position has been held
  • A holder score ("FICO"): time held, position size, entry and exit count, volume
  • Fees converted into gacha packs rather than cash

The line D1 draws through the holder score

The score is two features wearing one name:

Time held, position size, entries and exits, volume through our own poolsThe protocol observes this itself, on the swap that pays. Legal, keeper-free, and the actual differentiator
Their other positions, wallet balance, activity elsewhere on chainNot computable on chain. An indexer, and therefore off-chain — so no distribution may depend on it

The second half may exist as display, or as a signed attestation nothing is settled against. The moment a payout reads it, the backend is consensus and D1 is broken.

Positions — the lot system

Every buy opens a lot. Lots are what the score and the weighting are computed from.

Lifecycle

  • A buy, or an incoming transfer, opens a new live lot: amount and timestamp.
  • Lots are consumed newest-first.
  • Selling — or transferring out — takes from the dead lot first if one exists, until it is empty; only then does it touch the newest live lot, which dies the instant any of it is taken.
  • A dead lot keeps its remaining tokens and stops earning. It exists to absorb further selling so the live lots behind it stay untouched. It "dies softly": technically dead, still there as a shield.
  • Lots do not survive a plain transfer. The sender's lots are consumed exactly as on a sale; the recipient receives one fresh lot dated now. Self-transfer is therefore strictly destructive, which is what closes the wash-reset.

Why this shape: trimming a position costs you your most recent entry's standing and nothing more, while the dead lot has tokens left to give. A long-term holder who takes some profit is not reset to zero; a nibbler burns through their shield and starts eating their real position.

The constraint that has to be designed, not discovered: a lot list is unbounded, and an unbounded list is not a cost problem on an L2 — it is a liveness problem. A wallet with enough lots is a wallet whose transfers cannot execute at any gas price, and if that wallet is the pool, the pool is bricked. So: a hard cap on lots per wallet, with a merge rule at the cap.

Still open: what happens when a buy lands behind a dead lot — a live lot newer than the shield. Consuming the dead lot first keeps the invariant "at most one dead lot per wallet"; LIFO strictly would not. See notes/DECISIONS.md O11.

Gacha packs

Fees mint pack NFTs to the swapper instead of paying out; the money goes to a vault, and packs are opened for items of widely varying value.

../888 is this problem, already solved and live-tested on Arbitrum. Three of its findings carry over without needing to be rediscovered:

  • A pack cannot be opened in the swap that mints it. The player owns that transaction: they wrap the call and revert on a bad outcome, or set a gas limit that only suffices on a good one. No entropy source fixes this — it is not a randomness problem. Mint on the swap, open in a later transaction, resolved by ArbSys.arbBlockHash(commitBlock + 12) — exactly 3 seconds, with a 64-second settlement window and no keeper.
  • A prize must be a fraction of the vault, never a fixed amount. Multiplying by (1−f) cannot reach zero, so ruin is impossible rather than unlikely. "An item worth $1000" is a fixed amount and puts the vault back at risk.
  • Do not denominate packs in dollars. "$1 of fee = one pack" needs a price oracle. Denominate in the quote asset and the dependency disappears.

Composable pairing

A v4 pool holds exactly two currencies, so a 50/50 HOOD/TSLA pairing is an ERC20 wrapper that holds both, and the pool pairs against the wrapper.

Compose it once, in units1 BASKET = 0.5 HOOD + 0.5 TSLA — and mint and redeem are pure token arithmetic, needing no price feed and no keeper. Arbitrage keeps it honest. Per D14 the units set only the first deposit; after that a basket is a pro-rata share of what the wrapper actually holds, which is what stops an in-kind dividend or a rebasing constituent being trapped.

The payoff: to the launchpad, a basket is just another approved quote asset. Composable pairing becomes a separate product, versioned independently, and the launchpad core never learns what a stock is.

Transfer restrictions do not disappear — a wrapper around gated stocks is itself gated, so those launches still need the permissioned-pool hook family. That is one extra hook family, not a second architecture.

Hook families

Fee policy does not need new hooks. Pairing does.

familywhy it is separate
Ordinaryskim, cap, hand to the strategy
Permissionedtransfer-gated quote assets need different permission bits and a different graduation path

Keep the count this small for as long as possible. Every family is an address to mine, a pool identity to get right once, and a permanent commitment for every token launched into it.

Cohorts, not upgrades

A launched token's hook and quote asset are permanent. The launchpad can evolve; the tokens already launched cannot. So a version ships as new components behind one registry, and the registry maps every token to the stack it was created against.

Built, and the shape is narrower than "a new factory each time": one upgradeable LaunchFactory holds the registry, a new beacon carries new curve logic (D12) and a new migrator carries a new pool shape (D16). Both are pointed at by factory config and reach only launches made after them. launchOf records the beacon; a token's migrator is read from its own curve.

pons states the same rule from experience: resolve a token's stack from what launched it, never from what is current.