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.
Contract-side prerequisites
Section titled “Contract-side prerequisites”These must happen on-chain before the dapp can be repointed.
| Prerequisite | What it means |
|---|---|
| External audit | The 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 address | The hook address is a CREATE2 address mined so its low bits encode the declared permission flags (`beforeSwap |
| Set production durations | The live Sepolia deployment runs compressed lifecycle durations (see below). Mainnet must deploy with the production immutables. |
| Resolve accepted limitations | The 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 deploy | Mainnet 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.
| Immutable | Env var | Live Sepolia (compressed) | Production (mainnet target) |
|---|---|---|---|
feeWindow1 | FEE_W1 | 300 s (5 min) | 300 s (5 min) |
feeWindow2 | FEE_W2 | 480 s (8 min) | 480 s (8 min) |
vestingDuration | VEST_SECS | 300 s (~5 min) | 259200 s (72 h) |
prizeActivationWindow | PRIZE_W_SECS | 600 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.
1. The router
Section titled “1. The router”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 amountOutMinimumPlan 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).
2. The pool
Section titled “2. The pool”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 ETHcurrency1 = hook // TIDEfee = DYNAMIC_FEE_FLAGtickSpacing = 200hooks = hookBecause 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.
Environment variables to flip
Section titled “Environment variables to flip”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.
| Var | Sepolia (current) | Mainnet action |
|---|---|---|
NEXT_PUBLIC_TIDE_CHAIN | 11155111 | 1 (Ethereum) or the L2 chain id (Base = 8453) |
NEXT_PUBLIC_TIDE_RPC | publicnode Sepolia | a mainnet RPC for the target chain |
NEXT_PUBLIC_TIDE_HOOK | 0xF6F88E408Ea5df8a809a3b4232b9Ef7f2a9d40c0 | the redeployed mainnet hook (re-mined address) |
NEXT_PUBLIC_TIDE_MIRROR | 0x4c08F0B24254BE677924C5655Ca1706Feb8259F4 | mainnet mirror |
NEXT_PUBLIC_TIDE_TREASURY | 0x076f29063199DB470D260E5cE2cb560Af98cfBd3 | mainnet treasury |
NEXT_PUBLIC_TIDE_SWAP_ROUTER | PoolSwapTest | a real router (UniversalRouter) — see above |
NEXT_PUBLIC_TIDE_STATE_VIEW | Sepolia StateView | the target network’s V4 StateView |
NEXT_PUBLIC_TIDE_POOL_MANAGER | Sepolia PoolManager | the target network’s V4 PoolManager |
NEXT_PUBLIC_SITE_URL | pages.dev placeholder | the real production URL (OG / Twitter cards) |
NEXT_PUBLIC_WALLETCONNECT_ID | empty | a WalletConnect Cloud project id (recommended on mainnet) |
Code touch-points
Section titled “Code touch-points”Beyond env vars, these files need edits:
| File | Change |
|---|---|
src/components/swap/SwapWidget.tsx | Rewrite the buy/sell path for the real router; keep the slippage field, map it to both sqrtPriceLimitX96 and amountOutMinimum. |
src/lib/abi/poolSwapTest.ts | Replace with the chosen router’s ABI (UniversalRouter) plus the Permit2 approval flow for selling TIDE. |
src/lib/tide.ts | tideChain 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.tsx | The “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.json | Update network, live, and addresses. |
App migration checklist
Section titled “App migration checklist”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 rewriteSwapWidgetbuy/sell + Permit2 approval. - Point all
NEXT_PUBLIC_TIDE_*env vars at the mainnet deployment. - Add the target chain to
tideChain; generalizeEXPLORER_BASEand the Uniswap link. - Verify the derived
POOL_KEY(feeflag,tickSpacing) matches the mainnet pool; confirmgetSlot0price reads andSwapvolume logs resolve against the newpoolId. - Confirm the fee schedule / vesting / lottery windows reflect production durations (read live; no hardcoding needed).
- Set
NEXT_PUBLIC_SITE_URLto the real domain and re-verify the OG / Twitter card. - Obtain a WalletConnect project id.
- Re-run
pnpm buildagainst the mainnet config and smoke-test buy / sell / claim on a fork.