Skip to content

Mainnet migration

TIDE is live on Ethereum Sepolia today (see addresses.md). This page describes the plan for moving the protocol and the dapp to a production mainnet — Ethereum L1 (chainId 1) or an L2 such as Base (chainId 8453).

There are two tracks: a contract-side track (re-mine the hook, set production durations, audit, fund and deploy) and an app-side track (frontend/, the focus of docs/migration-mainnet.md). The headline of the app track is that the swap must be repointed to a different pool and router — it is a rewrite, not a config flip.

These must happen on-chain before the dapp can be repointed.

PrerequisiteWhat it means
External auditThe contracts are independent and self-reviewed (one medium bug found and fixed internally), and not yet third-party audited. An external audit is a prerequisite before mainnet, not optional. See security/known-limitations.md.
Re-mine the hook addressThe hook address is a CREATE2 address mined so its low bits encode the declared permission flags (`beforeSwap
Set production durationsThe live Sepolia deployment runs compressed lifecycle durations (see below). Mainnet must deploy with the production immutables.
Resolve accepted limitationsThe pool-initialization griefing vector and the constant-gas lottery fix were deliberately left for the testnet stage and must be revisited before mainnet. See security/known-limitations.md.
Fund the deployMainnet gas + the single-sided seed liquidity must be budgeted.

Durations: compressed (Sepolia) vs production (mainnet)

Section titled “Durations: compressed (Sepolia) vs production (mainnet)”

The four lifecycle durations are constructor immutables on TideHook and are part of the CREATE2 init code, so changing any of them changes the mined hook address. They are not settable after deploy. The current Sepolia deployment compresses vesting and the prize window so the full lifecycle can be watched in real time; mainnet must use the production values.

ImmutableEnv varLive Sepolia (compressed)Production (mainnet target)
feeWindow1FEE_W1300 s (5 min)300 s (5 min)
feeWindow2FEE_W2480 s (8 min)480 s (8 min)
vestingDurationVEST_SECS300 s (~5 min)259200 s (72 h)
prizeActivationWindowPRIZE_W_SECS600 s (~10 min)172800 s (48 h)

The swap must be repointed (headline item)

Section titled “The swap must be repointed (headline item)”

The in-app swap (src/components/swap/SwapWidget.tsx) currently routes through the canonical Sepolia PoolSwapTest router. This is the single biggest app-side change, on two axes.

PoolSwapTest is a test-only router: it enforces no minOut, refunds leftover ETH, and exists only on testnets. It does not exist on mainnet. The swap must instead go through a real router — the Uniswap V4 UniversalRouter (or a custom V4 router).

The current call shape is incompatible and cannot be config-swapped:

// today (Sepolia, src/lib/abi/poolSwapTest.ts):
PoolSwapTest.swap(PoolKey, SwapParams, TestSettings, hookData)
// UniversalRouter is a different encoding entirely:
// Commands + Inputs, settled via Permit2, with a real amountOutMinimum

Plan for a rewrite of SwapWidget’s onBuy / onSell, plus the Permit2 approval flow for selling TIDE. Keep the existing custom slippage field, but map it to both a sqrtPriceLimitX96 price bound and the router’s real amountOutMinimum (defense in depth — see security/known-limitations.md on buyback/claim MEV).

The pool the swap targets is derived from the hook address in POOL_KEY (src/lib/tide.ts):

// POOL_KEY (derived from NEXT_PUBLIC_TIDE_HOOK):
currency0 = 0x0 // native ETH
currency1 = hook // TIDE
fee = DYNAMIC_FEE_FLAG
tickSpacing = 200
hooks = hook

Because the mainnet hook is redeployed at a new address, currency1 / hooks update automatically once NEXT_PUBLIC_TIDE_HOOK points at it, and the derived poolId (keccak256(abiEncode(POOL_KEY))) changes too. As a result, price reads (StateView.getSlot0) and the volume query (PoolManager Swap logs filtered by poolId) follow the new pool automatically — provided the V4 infra addresses below point at the target network.

All are NEXT_PUBLIC_* (public, no secrets). On Cloudflare Pages, set these for the production deployment; locally, override in .env.local. The defaults baked into tide.ts today are Sepolia.

VarSepolia (current)Mainnet action
NEXT_PUBLIC_TIDE_CHAIN111551111 (Ethereum) or the L2 chain id (Base = 8453)
NEXT_PUBLIC_TIDE_RPCpublicnode Sepoliaa mainnet RPC for the target chain
NEXT_PUBLIC_TIDE_HOOK0xF6F88E408Ea5df8a809a3b4232b9Ef7f2a9d40c0the redeployed mainnet hook (re-mined address)
NEXT_PUBLIC_TIDE_MIRROR0x4c08F0B24254BE677924C5655Ca1706Feb8259F4mainnet mirror
NEXT_PUBLIC_TIDE_TREASURY0x076f29063199DB470D260E5cE2cb560Af98cfBd3mainnet treasury
NEXT_PUBLIC_TIDE_SWAP_ROUTERPoolSwapTesta real router (UniversalRouter) — see above
NEXT_PUBLIC_TIDE_STATE_VIEWSepolia StateViewthe target network’s V4 StateView
NEXT_PUBLIC_TIDE_POOL_MANAGERSepolia PoolManagerthe target network’s V4 PoolManager
NEXT_PUBLIC_SITE_URLpages.dev placeholderthe real production URL (OG / Twitter cards)
NEXT_PUBLIC_WALLETCONNECT_IDemptya WalletConnect Cloud project id (recommended on mainnet)

Beyond env vars, these files need edits:

FileChange
src/components/swap/SwapWidget.tsxRewrite the buy/sell path for the real router; keep the slippage field, map it to both sqrtPriceLimitX96 and amountOutMinimum.
src/lib/abi/poolSwapTest.tsReplace with the chosen router’s ABI (UniversalRouter) plus the Permit2 approval flow for selling TIDE.
src/lib/tide.tstideChain only resolves to viem’s sepolia when CHAIN_ID === 11155111; add the mainnet/L2 chain. Generalize EXPLORER_BASE (currently hardcodes Sepolia Etherscan for 11155111) and the LINKS.uniswap chain= param.
src/components/layout/Header.tsxThe “Sepolia testnet” chip is gated on CHAIN_ID !== 1; it hides automatically on Ethereum L1, but update the label/logic for an L2.
public/deployment.jsonUpdate network, live, and addresses.

A condensed version of the plan, in execution order:

  • Complete an external audit and resolve the accepted limitations flagged in security/known-limitations.md.
  • Re-mine the hook address and deploy the contracts with production durations (72 h vesting / 48 h prize window).
  • Pick the target chain (Ethereum L1 vs Base/L2) and gather its V4 infra addresses.
  • Choose the production router (UniversalRouter) and rewrite SwapWidget buy/sell + Permit2 approval.
  • Point all NEXT_PUBLIC_TIDE_* env vars at the mainnet deployment.
  • Add the target chain to tideChain; generalize EXPLORER_BASE and the Uniswap link.
  • Verify the derived POOL_KEY (fee flag, tickSpacing) matches the mainnet pool; confirm getSlot0 price reads and Swap volume logs resolve against the new poolId.
  • Confirm the fee schedule / vesting / lottery windows reflect production durations (read live; no hardcoding needed).
  • Set NEXT_PUBLIC_SITE_URL to the real domain and re-verify the OG / Twitter card.
  • Obtain a WalletConnect project id.
  • Re-run pnpm build against the mainnet config and smoke-test buy / sell / claim on a fork.