Skip to content

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.


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.

Terminal window
bash scripts/setup-contracts.sh

A direct build:

Terminal window
cd contracts
forge build

The 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.

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.

Terminal window
anvil --fork-url https://ethereum-rpc.publicnode.com --chain-id 31337 --silent \
> /tmp/anvil.log 2>&1 &

Wait until it answers before deploying:

Terminal window
cast block-number --rpc-url http://127.0.0.1:8545

scripts/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):

Terminal window
cd contracts
forge script script/DeployLocal.s.sol:DeployLocal \
--rpc-url http://127.0.0.1:8545 \
--private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 \
--broadcast

It 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:

Terminal window
forge script script/DeployLocal.s.sol:DeployLocal \
--rpc-url http://127.0.0.1:8545 \
--private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 \
--broadcast > /tmp/deploy.log 2>&1
grep -aE "HOOK|MIRROR|SWAP_ROUTER|Error|revert" /tmp/deploy.log

The two funded dev accounts on the fork are:

RoleAddressPrivate key
anvil #0 (deployer / owner)0xf39Fd6e51aad88F6F4ce6aB8827279cffFb922660xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
anvil #1 (holder)0x70997970C51812dc3A010C7d01b50e0d17dc79C80x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d

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:

VariableUsed byMeaning
POOL_MANAGERDeployUniswap V4 PoolManager
POSITION_MANAGERDeployUniswap V4 PositionManager
PERMIT2DeployPermit2
TIDE_OWNERDeploy_owner for the TideHook constructor (defaults to msg.sender)
TIDE_HOOKSeedAddress of the deployed TideHook (printed by Deploy)
SEED_SQRT_PRICESeedInitial pool price sqrtPriceX96 (Q64.96)
SEED_TICK_LOWERSeedLower tick (multiple of 200)
SEED_TICK_UPPERSeedUpper tick (multiple of 200)
SEED_LIQUIDITYSeeduint128 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).

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).

Terminal window
cd frontend
pnpm install

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:

Terminal window
NEXT_PUBLIC_TIDE_CHAIN=11155111
NEXT_PUBLIC_TIDE_RPC=https://ethereum-sepolia-rpc.publicnode.com
NEXT_PUBLIC_TIDE_HOOK=0xF6F88E408Ea5df8a809a3b4232b9Ef7f2a9d40c0
NEXT_PUBLIC_TIDE_MIRROR=0x4c08F0B24254BE677924C5655Ca1706Feb8259F4
NEXT_PUBLIC_TIDE_TREASURY=0x076f29063199DB470D260E5cE2cb560Af98cfBd3
NEXT_PUBLIC_TIDE_SWAP_ROUTER=0x9B6b46e2c869aa39918Db7f52f5557FE577B6eEe
NEXT_PUBLIC_TIDE_STATE_VIEW=0xe1Dd9c3fa50EDB962E442f60DfBc432e24537E4C
NEXT_PUBLIC_TIDE_POOL_MANAGER=0xE03A1074c86CFeDd5C142C4F04F1a1536e203543
NEXT_PUBLIC_WALLETCONNECT_ID=

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:

Terminal window
NEXT_PUBLIC_TIDE_CHAIN=31337
NEXT_PUBLIC_TIDE_RPC=http://127.0.0.1:8545
NEXT_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.

Terminal window
# hot-reload dev server
pnpm dev
# production static export -> out/
pnpm build

pnpm build emits the static site to out/. To serve that build locally:

Terminal window
pnpm preview # serves out/ on http://localhost:3000

When you only need to verify types (for example when the disk is too full for a full build):

Terminal window
pnpm typecheck # tsc --noEmit

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:

Terminal window
# (run by the workflow, from frontend/)
pnpm add -D @playwright/test
npx playwright install --with-deps chromium
npx playwright test --reporter=line

ScriptPurpose
scripts/setup-contracts.shFetch solc 0.8.26, populate the vendored V4 dep tree, produce a clean forge build
scripts/anvil-keepalive.shRun the anvil mainnet fork on 127.0.0.1:8545 and restart it if it dies
scripts/go-live-v2.shCurrent Sepolia v2 go-live (atomic deploy + seed + bootstrap buy; sets compressed VEST_SECS=300 PRIZE_W_SECS=600)
scripts/go-live-sepolia.shOlder Sepolia go-live driver

For deploying to Sepolia or mainnet rather than a local fork, see Addresses & network and Mainnet migration.