Run it locally
This page is for developers who want to build TIDE end to end on their own machine: the Foundry contracts (deployed and seeded against a local anvil mainnet fork, which carries the real Uniswap V4 infrastructure) and the Next.js dapp pointed at that local chain.
For the canonical live deployment and the contract addresses the hosted dapp
uses, see Addresses & network. For the contract-side deploy and
hook-mining mechanics in depth, the source of truth is
/home/forkprism/forkprism/docs/DEPLOYMENT.md.
Part 1 — Contracts (Foundry)
Section titled “Part 1 — Contracts (Foundry)”Toolchain & build
Section titled “Toolchain & build”TIDE compiles with solc 0.8.26 and a fully vendored Uniswap V4
dependency tree. The golden rule from docs/DEPLOYMENT.md:
The helper script scripts/setup-contracts.sh bootstraps this for you. It is
idempotent and re-runnable, and it (1) fetches solc 0.8.26 via svm, (2)
recursively clones and verifies the vendored v4-periphery dependency tree if it
is incomplete, and (3) produces a clean forge build, ending with a STATUS=
line.
bash scripts/setup-contracts.shA direct build:
cd contractsforge buildThe CREATE2 deployer used by every script is the hardcoded constant
0x4e59b44847b379578588920cA78FbF26c0B4956C (the same on every EVM chain), and
bytecode_hash = "none" in foundry.toml keeps the init code stable so the
mined hook address is reproducible.
Start a local anvil mainnet fork
Section titled “Start a local anvil mainnet fork”TIDE depends on the real Uniswap V4 PoolManager, PositionManager, and
Permit2. Forking mainnet makes all three available at their canonical
addresses with no extra deployment.
anvil --fork-url https://ethereum-rpc.publicnode.com --chain-id 31337 --silent \ > /tmp/anvil.log 2>&1 &Wait until it answers before deploying:
cast block-number --rpc-url http://127.0.0.1:8545scripts/anvil-keepalive.sh runs this same fork and restarts it if it dies; it
targets http://127.0.0.1:8545.
Deploy + seed in one shot — DeployLocal.s.sol
Section titled “Deploy + seed in one shot — DeployLocal.s.sol”script/DeployLocal.s.sol is fully self-contained: it needs no env vars. In
a single run it mines the hook address, CREATE2-deploys TideHook (which itself
deploys TideMirror, TideArt, and the Treasury), sizes the single-sided
liquidity, calls seed() at the upper tick of the range [-20000, 0] with
owner = anvil account #0, and additionally deploys a v4-core PoolSwapTest
router so the pool is immediately swappable.
The signer is anvil account #0 (the hardcoded OWNER in DeployLocal):
cd contractsforge script script/DeployLocal.s.sol:DeployLocal \ --rpc-url http://127.0.0.1:8545 \ --private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 \ --broadcastIt logs HOOK, MIRROR, SWAP_ROUTER, POSITION_TOKEN_ID, TOTAL_SHARES,
and TOTAL_MINTED. The local shell can drop large stdout, so it is safer to
redirect to a file and grep the addresses out:
forge script script/DeployLocal.s.sol:DeployLocal \ --rpc-url http://127.0.0.1:8545 \ --private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 \ --broadcast > /tmp/deploy.log 2>&1grep -aE "HOOK|MIRROR|SWAP_ROUTER|Error|revert" /tmp/deploy.logThe two funded dev accounts on the fork are:
| Role | Address | Private key |
|---|---|---|
anvil #0 (deployer / owner) | 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 | 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 |
anvil #1 (holder) | 0x70997970C51812dc3A010C7d01b50e0d17dc79C8 | 0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d |
Split deploy + seed (to mirror the production flow)
Section titled “Split deploy + seed (to mirror the production flow)”To rehearse the production two-step on a fork, run Deploy.s.sol:Deploy then
Seed.s.sol:Seed. Deploy reads optional env vars (each via vm.envOr, so all
fall back to mainnet defaults); Seed reads the SEED_* and TIDE_HOOK
variables:
| Variable | Used by | Meaning |
|---|---|---|
POOL_MANAGER | Deploy | Uniswap V4 PoolManager |
POSITION_MANAGER | Deploy | Uniswap V4 PositionManager |
PERMIT2 | Deploy | Permit2 |
TIDE_OWNER | Deploy | _owner for the TideHook constructor (defaults to msg.sender) |
TIDE_HOOK | Seed | Address of the deployed TideHook (printed by Deploy) |
SEED_SQRT_PRICE | Seed | Initial pool price sqrtPriceX96 (Q64.96) |
SEED_TICK_LOWER | Seed | Lower tick (multiple of 200) |
SEED_TICK_UPPER | Seed | Upper tick (multiple of 200) |
SEED_LIQUIDITY | Seed | uint128 liquidity for the single-sided position |
The lifecycle durations are constructor immutables read from the environment
at deploy time (FEE_W1, FEE_W2, VEST_SECS, PRIZE_W_SECS). Because they
are part of the CREATE2 init code, changing any of them changes the mined hook
address. See Prod vs test durations for the production defaults
versus the compressed values the live Sepolia deployment runs.
Companion drivers push activity through the seeded pool for local testing:
Swaps.s.sol and ActivityLocal.s.sol route swaps through the PoolSwapTest
router, and LotteryDemo.s.sol / ForkActivity.s.sol exercise later
lifecycle stages (forfeiture, prizes, expiry).
Generating the frontend ABIs
Section titled “Generating the frontend ABIs”The dapp ABIs in frontend/src/lib/abi/ (tideHook.ts, tideMirror.ts,
treasury.ts, poolSwapTest.ts) are generated from the compiled Foundry
artifacts under contracts/out/*.json — for example the abi field of
contracts/out/TideHook.sol/TideHook.json. Regenerate them from those artifacts
after any contract change; do not hand-edit them.
Part 2 — Frontend (Next 14 static export)
Section titled “Part 2 — Frontend (Next 14 static export)”The dapp is a Next.js 14 App Router static export (output: 'export' →
out/) using wagmi/viem/RainbowKit + react-query. The package manager is
pnpm (packageManager: pnpm@10.30.3).
Install
Section titled “Install”cd frontendpnpm installEnvironment variables
Section titled “Environment variables”All runtime config is driven by NEXT_PUBLIC_TIDE_* variables. The live
Sepolia defaults are baked into frontend/src/lib/tide.ts via an
env(key, fallback) helper, so a build works with no env file at all. The
committed frontend/.env.local mirrors those same live Sepolia values:
NEXT_PUBLIC_TIDE_CHAIN=11155111NEXT_PUBLIC_TIDE_RPC=https://ethereum-sepolia-rpc.publicnode.comNEXT_PUBLIC_TIDE_HOOK=0xF6F88E408Ea5df8a809a3b4232b9Ef7f2a9d40c0NEXT_PUBLIC_TIDE_MIRROR=0x4c08F0B24254BE677924C5655Ca1706Feb8259F4NEXT_PUBLIC_TIDE_TREASURY=0x076f29063199DB470D260E5cE2cb560Af98cfBd3NEXT_PUBLIC_TIDE_SWAP_ROUTER=0x9B6b46e2c869aa39918Db7f52f5557FE577B6eEeNEXT_PUBLIC_TIDE_STATE_VIEW=0xe1Dd9c3fa50EDB962E442f60DfBc432e24537E4CNEXT_PUBLIC_TIDE_POOL_MANAGER=0xE03A1074c86CFeDd5C142C4F04F1a1536e203543NEXT_PUBLIC_WALLETCONNECT_ID=Point the dapp at your local fork
Section titled “Point the dapp at your local fork”To run the UI against the local anvil fork, override the chain, RPC, and the
local contract addresses in frontend/.env.local. Use the HOOK, MIRROR, and
SWAP_ROUTER values that DeployLocal printed, and set the RPC to the port
anvil is actually on:
NEXT_PUBLIC_TIDE_CHAIN=31337NEXT_PUBLIC_TIDE_RPC=http://127.0.0.1:8545NEXT_PUBLIC_TIDE_HOOK=<HOOK from DeployLocal>NEXT_PUBLIC_TIDE_MIRROR=<MIRROR from DeployLocal>NEXT_PUBLIC_TIDE_SWAP_ROUTER=<SWAP_ROUTER from DeployLocal>In tide.ts, tideChain only resolves to viem’s sepolia when
CHAIN_ID === 11155111; otherwise it builds a generated anvilFork chain from
CHAIN_ID + RPC_URL. isLocalRpc is auto-detected from
127.0.0.1 | localhost | 0.0.0.0. The POOL_KEY (and therefore the poolId,
price reads, and swap routing) derives currency1/hooks from
NEXT_PUBLIC_TIDE_HOOK, so pointing at the local hook automatically follows the
local pool.
Dev server & static export
Section titled “Dev server & static export”# hot-reload dev serverpnpm dev
# production static export -> out/pnpm buildpnpm build emits the static site to out/. To serve that build locally:
pnpm preview # serves out/ on http://localhost:3000When you only need to verify types (for example when the disk is too full for a full build):
pnpm typecheck # tsc --noEmitEnd-to-end testing (Playwright)
Section titled “End-to-end testing (Playwright)”End-to-end coverage runs Playwright (chromium, headless) against the running
frontend wired to the local anvil fork. Note that the Playwright suite is driven
by the build workflow scripts (scripts/wf-finish.js) rather than a committed
pnpm test script — playwright-core is present as a frontend dev dependency,
but there is no checked-in playwright.config.ts or e2e/ directory in the
repo today. The workflow generates the config and specs against the running app,
installs the browser, and runs the suite:
# (run by the workflow, from frontend/)pnpm add -D @playwright/testnpx playwright install --with-deps chromiumnpx playwright test --reporter=lineHelper scripts at a glance
Section titled “Helper scripts at a glance”| Script | Purpose |
|---|---|
scripts/setup-contracts.sh | Fetch solc 0.8.26, populate the vendored V4 dep tree, produce a clean forge build |
scripts/anvil-keepalive.sh | Run the anvil mainnet fork on 127.0.0.1:8545 and restart it if it dies |
scripts/go-live-v2.sh | Current Sepolia v2 go-live (atomic deploy + seed + bootstrap buy; sets compressed VEST_SECS=300 PRIZE_W_SECS=600) |
scripts/go-live-sepolia.sh | Older Sepolia go-live driver |
For deploying to Sepolia or mainnet rather than a local fork, see Addresses & network and Mainnet migration.