Version: 4.0
Date: August 5, 2026
Status: Production (Ethereum Mainnet Live)
Core Stack: Uniswap v4 Hooks · React + Vite · Node.js · Fastify · PostgreSQL · Ethereum Mainnet
- Executive Summary
- Project Vision
- System Overview
- Smart Contract Architecture
- Token Launch Flow
- Fee & Revenue Model
- Frontend Architecture
- Backend & API Layer
- Indexing & Data Pipeline
- Database Schema
- Security Architecture
- Mainnet Deployment
- Engineering Standards
- Implementation Status
- Roadmap
Hookify is a token launch infrastructure protocol built entirely on top of Uniswap v4's hook system. It enables creators to launch ERC-20 tokens with programmable lifecycle management from the first trade to permanent liquidity enforced fully on-chain with no admin keys, no upgradeable contracts, and no external keepers.
| Metric | Value |
|---|---|
| Protocol | Uniswap v4 Hooks (Custom Accounting) |
| Live Network | Ethereum Mainnet (chainId 1) |
| Token Supply | Fixed 1,000,000,000 (1B) per launch |
| Protocol Fee | Hard-capped at 0.15% (immutable) |
| Creator Fee | Creator-configurable (up to ~3%) |
| LP Lock | Permanent (no admin override possible) |
| Anti-Sniper | Static on-chain, zero off-chain keepers |
What makes Hookify different:
- Tokens trade inside a Uniswap v4 pool from block one no separate pre-DEX bonding curve phase
- Liquidity lock is enforced by contract immutability, not a timer or trusted multisig
- The hook manages the entire token lifecycle (BC mode → AMM mode) atomically without any external calls
- Creators earn trading fees and can claim them permissionlessly from their own vault
Hookify is designed as a Protocol Operating System for Uniswap v4 Hooks not a token launchpad template.
The product philosophy:
"The protocol is the product. The user should feel they are operating protocol infrastructure, not filling out forms on a website."
Core design rules:
- Launch flow works even if the API/indexer is offline frontend builds calldata directly from ABIs
- All state that matters lives on-chain; databases are rebuildable caches
- No contract can be paused, upgraded, or admin-controlled immutability is the product
- Fees are transparent and fixed at the time of launch no setter functions
flowchart TB
subgraph CLIENTS["Client Layer"]
WEB["Web3 DApp\napp.hookify.fun\nReact + Vite"]
MKT["Marketing Site\nhookify.fun\nStatic"]
end
subgraph EDGE["Edge / API Layer"]
API["API Server\nFastify + Node.js\nPort: ENV-assigned"]
AUTH["Auth\nSIWE / JWT"]
end
subgraph DATA["Data Layer"]
IDX["Event Indexer\nNode.js Workers"]
DB[("PostgreSQL\nRebuildable Cache")]
end
subgraph CHAIN["Ethereum Mainnet (chainId 1)"]
PM["Uniswap v4\nPoolManager\n0x000000000004444c..."]
HOOK["EthCreatorFeeHookV3\n0xC5C72B770396..."]
LAUNCHER["ClassicLauncher\n0xE3faC65ce486..."]
VAULT["FeeSplitVaultV1\nper-token"]
FACTORY["FeeSplitVaultFactoryV1\n0x4a178da4a496..."]
end
WEB -->|REST / SSE| API
WEB -->|wagmi + viem direct RPC| CHAIN
MKT -->|Static| EDGE
API --> DB
IDX --> DB
IDX -->|eth_getLogs polling| CHAIN
API -->|RPC reads| CHAIN
API --> AUTH
LAUNCHER -->|initialize pool| PM
PM -->|beforeSwap / afterSwap| HOOK
HOOK -->|NativeSwapFeesAccrued| IDX
HOOK -->|fees flush| VAULT
FACTORY -->|deploy| VAULT
LAUNCHER -->|register| VAULT
| Layer | Responsibility | Technology |
|---|---|---|
| Contracts | Token creation, v4 pool management, fee accrual, LP lock | Solidity ^0.8.26, Foundry, Uniswap v4 |
| Frontend DApp | Explore, Launch, Trade, Dashboard, Claim | React 18, Vite, wagmi v2, viem, Privy |
| Marketing Site | Landing page, docs, SEO | Static HTML / Next.js SSG |
| API Server | Token discovery, metadata, creator data, auth | Node.js 20, Fastify, Drizzle ORM |
| Indexer Workers | On-chain event indexing (Launched, Swap, FeeAccrued) | Node.js, viem, PostgreSQL |
| Database | Rebuildable cache of on-chain state | PostgreSQL 16, Drizzle ORM |
flowchart LR
CREATOR["Creator Wallet"] -->|"launch(params)\n{value: initialBuyEth}"| LAUNCHER
LAUNCHER -->|"deploy token"| TOKEN["HookToken\nFixed 1B supply\nERC-20"]
LAUNCHER -->|"initializePool"| PM["Uniswap v4\nPoolManager"]
LAUNCHER -->|"register"| VAULT_FAC["FeeSplitVaultFactory"]
VAULT_FAC -->|"CREATE2 deploy"| VAULT["FeeSplitVaultV1\n(per-token)"]
PM -->|"beforeSwap\nafterSwap"| HOOK["EthCreatorFeeHookV3"]
HOOK -->|"NativeSwapFeesAccrued event"| LOG["Event Log"]
HOOK -->|"claimCreatorFees(poolId)"| VAULT
TRADER["Trader"] -->|"swap via\nPoolSwapTest"| PM
CREATOR -->|"1. claimCreatorFees(poolId)"| HOOK
HOOK -->|"flush ETH"| VAULT
CREATOR -->|"2. claim()"| VAULT
VAULT -->|"ETH"| CREATOR
| Contract | Address | Role |
|---|---|---|
| Uniswap v4 PoolManager | 0x000000000004444c5dc75cB358380D2e3dE08A90 |
Core AMM singleton |
| PoolSwapTest (our deploy) | 0xa8D342A66DC7936338bA2420a0293ab4789ccD0C |
Swap executor on mainnet |
| FeeSplitVaultFactoryV1 | 0x4a178da4a496032103e77d92EAC25b39502852b9 |
Deploys per-token vaults |
| EthCreatorFeeHookFactoryV3 | 0xBe540C9319853833ABdaA2D2486D13E1B497a54A |
Deploys hooks |
| EthCreatorFeeHookV3 | 0xC5C72B770396050C088BBc111B1A448Ec3Dce0cc |
Active hook (fee routing) |
| ClassicLaunchPolicyV1 | 0xE364D0bdbEDdB308E602Bed1B28B176Be9f02cdB |
Launch policy rules |
| ClassicCtoAuthorityV1 | 0x3f11E06Eb1bA20670dFE28567359DADaEf490377 |
CTO transfer authority |
| ClassicInitialBuyVestingWalletFactoryV1 | 0xA68c3543d1206Fc29Ee4c851A01c1Fac1dbf6153 |
Vesting wallets |
| ClassicLauncher | 0xE3faC65ce48695988584a46feAc505fa6B53a854 |
Primary launch contract |
Deployment note: Uniswap v4 PoolSwapTest is NOT deployed on Ethereum mainnet by Uniswap. Hookify deployed it at the address above as part of
DeployClassic.s.sol. Hook address was mined using CREATE2 (1,511 iterations, salt0x...05e7) to satisfy Uniswap v4's required permission bit pattern.
The EthCreatorFeeHookV3 hook address encodes permission bits in its lower bytes (Uniswap v4 requirement):
| Permission | Required | Reason |
|---|---|---|
beforeSwap |
✅ | Custom fee logic |
afterSwap |
✅ | Fee accrual after each swap |
beforeSwapReturnDelta |
✅ | Custom Accounting (V4 delta pricing) |
Hook flags: 0x20CC
sequenceDiagram
participant T as Trader
participant PST as PoolSwapTest
participant PM as PoolManager
participant H as EthCreatorFeeHookV3
participant V as FeeSplitVaultV1
T->>PST: swap(key, params, testSettings)
PST->>PM: unlock(calldata)
PM->>PST: unlockCallback()
PST->>PM: swap(key, params)
PM->>H: beforeSwap(key, params)
H-->>PM: hookData (fee override)
PM-->>PST: BalanceDelta
PST->>PM: settle(currency0/1)
PM->>H: afterSwap(key, delta, hookData)
H->>H: compute fee split (creator / protocol)
H->>H: emit NativeSwapFeesAccrued(poolId, trader, isBuy, feeBps, grossAmt, creatorFee, launcherFee)
Note over H: ETH fees tracked internally in hook storage<br/>Not yet in vault — requires explicit flush
Note over T,V: — LATER: Creator Claims —
T->>H: claimCreatorFees(poolId)
H->>PM: unlock(calldata) → take ETH from PM
H->>V: send ETH to vault
T->>V: claim()
V-->>T: ETH transferred
Important: In V4 Custom Accounting,
eth_getBalance(hookAddress)always shows0. The ETH is tracked as ERC-6909 claims in the PoolManager (40,000+ ETH in PM singleton).claimCreatorFees(poolId)converts these claims to real ETH and sends them to the vault.
| # | Invariant | Enforcement |
|---|---|---|
| I-1 | Token supply fixed at exactly 1,000,000,000 — no mint, no admin burn | HookToken constructor |
| I-2 | Protocol fee hard-capped at 0.15% — immutable constant, no setter | FeeMath |
| I-3 | Fees cannot be changed after launch | Hook has no fee setter |
| I-4 | No contract is upgradeable; no admin can pause trading | Contract immutability |
| I-5 | Beneficiaries can only claim their own accrued balance | FeeSplitVaultV1 |
| I-6 | LP lock enforced by contract immutability — no removeLiquidity |
LaunchRegistry |
The Programmable Classic model launches a token directly into a Uniswap v4 pool with the EthCreatorFeeHookV3 hook active from block one. There is no bonding curve phase.
flowchart TD
A["Creator: Fill Launch Form\nname / symbol / image / fees / social links"] --> B["Frontend: Build LaunchParams\nclient-side via lib/contracts.ts"]
B --> C{"API Online?"}
C -->|Yes| D["API: Pre-flight validation\nmetadata upload → IPFS CID"]
C -->|No - offline mode| E["Client-side only path\n(Rule R6 — API never required)"]
D --> F["Frontend: Summary Review\nfee breakdown + risk disclosure"]
E --> F
F --> G["User Signs Single Transaction\nmsg.value = initialBuyEth"]
G --> H["ClassicLauncher.launch(params)"]
H --> I["Deploy HookToken\n1B fixed supply\nCREATE2 deterministic address"]
H --> J["Initialize v4 Pool\nPoolManager.initialize(key, sqrtPriceX96)"]
H --> K["FeeSplitVaultFactory.deploy()\nper-token vault → beneficiaries configured"]
H --> L["Register pool with EthCreatorFeeHookV3\nhook registers poolId → vault mapping"]
H --> M["Initial Buy Executed\nfirst swap in the pool"]
I & J & K & L & M --> N["Launched Event Emitted\n(token, poolId, vault, creator, launchId)"]
N --> O["Indexer picks up Launched event\ntoken row created in DB"]
O --> P["Token appears in Explorer\nReal-time via SSE stream"]
| Parameter | Type | Description |
|---|---|---|
name |
string |
Token name (e.g. "Luna Q") |
symbol |
string |
Ticker (e.g. "LNQ") |
metadataUri |
string |
IPFS CID of metadata JSON (image, description, social) |
initialBuyEth |
uint256 |
ETH to spend on the initial buy (msg.value) |
maxWalletBps |
uint16 |
Max wallet size in basis points (e.g. 100 = 1%) |
buyFeeBps |
uint16 |
Creator fee on buys (e.g. 300 = 3%) |
sellFeeBps |
uint16 |
Creator fee on sells |
rewardWallets |
address[] |
Fee beneficiaries (1–5 addresses) |
rewardSharesBps |
uint16[] |
Share per beneficiary (must sum to 10000) |
Single transaction: all of the above — token deploy, pool init, vault deploy, hook registration, and initial buy — execute atomically in one ClassicLauncher.launch() call.
flowchart LR
DASH["Creator Dashboard\nShows Available Revenue"] --> CHECK{"fee_balances\nempty?"}
CHECK -->|"Yes (current state)"| FALLBACK["Fallback: use\nSUM(trades.fee_collected_wei)\nas claimable amount"]
CHECK -->|"No"| DB_VAL["Use fee_balances.claimable_wei"]
FALLBACK & DB_VAL --> DISPLAY["Display claimable ETH\nEnables Claim Button"]
DISPLAY --> SIGN["User clicks 'Claim Revenue'\nWallet switches to mainnet"]
SIGN --> TX1["TX 1: hook.claimCreatorFees(poolId)\nFlushes ETH from hook → vault\nGas: 200k"]
TX1 --> WAIT["Wait for receipt\npublicClient.waitForTransactionReceipt"]
WAIT --> TX2["TX 2: vault.claim()\nSends ETH to creator wallet\nGas: 200k"]
TX2 --> DONE["ETH received\nDashboard refreshes"]
Note on current claim state:
claimCreatorFees(bytes32)selector is0xaf8d60b5. Investigation in progress on mainnet revert — ETH is held as ERC-6909 claims in PoolManager (V4 custom accounting), not as direct hook ETH balance. Hook balance reads as 0 but fees ARE accumulated.
flowchart LR
SWAP["Swap\n(ETH amount)"] --> TOTAL["Total Fee\ne.g. 3.15% on buy"]
TOTAL --> CREATOR["Creator Fee\ne.g. 3.00%\n→ FeeSplitVaultV1"]
TOTAL --> PROTOCOL["Protocol Fee\n0.15% fixed\n→ Protocol Treasury"]
CREATOR --> BENEFICIARIES["Split by rewardWallets\ne.g. 100% to creator\nor 50/50 to team"]
| Parameter | Range | Default | Notes |
|---|---|---|---|
| Buy Fee | 0 – 10% | Creator choice | Paid by buyer |
| Sell Fee | 0 – 10% | Creator choice | Paid by seller |
| Protocol Fee | 0.15% fixed | 0.15% | Immutable constant |
| Max Wallet | 0.01% – 100% | 1% (100 bps) | Applied first N blocks |
| Metric | Source | Update Frequency |
|---|---|---|
| Lifetime Fees | SUM(trades.fee_collected_wei) |
Per trade (indexer) |
| Available to Claim | fee_balances.claimable_wei OR trades fallback |
Per trade |
| Volume | SUM(trades.amount_eth) |
Per trade |
| Trades Count | COUNT(trades) |
Per trade |
| Revenue Trend | fee_collected_wei grouped by day |
Per trade |
| Concern | Choice | Notes |
|---|---|---|
| Framework | React 18 + Vite | SPA, path-based routing |
| Language | TypeScript (strict) | No any without justification |
| Styling | Tailwind CSS + CSS variables | Dark-first (.dark on <html>) |
| Wallet | Privy + wagmi v2 + viem | Embedded wallet support |
| State / Cache | TanStack Query v5 | Auto-invalidation on mutations |
| Charts | TradingView Lightweight Charts | OHLCV candles |
| Codegen | Orval → typed API client | From openapi.yaml |
| Auth | SIWE (EIP-4361) → JWT | 15-min sliding access token |
flowchart TD
subgraph APP["artifacts/app (Hookify DApp)"]
EX["/explore\nToken feed, filters,\nlive price table"]
TD["/token/:chain/:address\nChart + trades + swap panel\n+ creator info"]
LA["/launch\nProtocol Initializer\nSection 00-05 + Summary"]
DA["/dashboard\nCreator revenue + claim\nactive tokens"]
PO["/portfolio\nHolder positions + PnL"]
SE["/settings\nWallet prefs, RPC, theme"]
DO["/docs\nDocumentation"]
end
subgraph MKT["artifacts/hookify-web (Marketing)"]
HM["/ (Landing)\nHero + features + stats"]
end
NAV["Floating Nav Bar\n(pill-shaped, glass)"] --> EX & LA & DA & PO & SE & DO
artifacts/app/src/
├── pages/
│ ├── explore.tsx # Token table, trending, live SSE feed
│ ├── token-detail.tsx # Chart, trades, swap panel, info panel
│ ├── launch.tsx # 6-section launch form + summary sidebar
│ ├── dashboard.tsx # Creator revenue, claim flow, token cards
│ ├── portfolio.tsx # Holder positions
│ └── settings.tsx # App preferences
├── hooks/
│ ├── use-token-swap.ts # Buy/sell swap flow (PoolSwapTest)
│ ├── use-swap-quote.ts # Price quotes from on-chain
│ └── use-onchain-price.ts # Live price reads
├── lib/
│ ├── contracts.ts # ABIs + chain addresses
│ ├── api-client.ts # Generated from openapi.yaml (Orval)
│ └── format.ts # Number/price formatting
└── components/
├── ui/ # shadcn/ui base components
└── charts/ # TradingView wrappers
sequenceDiagram
participant U as User
participant FE as Frontend
participant PST as PoolSwapTest
participant PM as PoolManager
participant H as Hook
U->>FE: Click Buy (amount ETH)
FE->>FE: useSwapQuote() — estimate output
FE->>FE: Show: amount out, fee, price impact
U->>FE: Confirm
FE->>FE: Privy: wallet.getEthereumProvider()
FE->>PST: swap(poolKey, {zeroForOne, amtSpec, sqrtLimit}, {takeClaims, settleUsingBurn})
Note over FE,PST: Classic uses PoolSwapTest 0xa8D342...<br/>selector: 0x2229d0b4
PST->>PM: unlock + swap callback
PM->>H: beforeSwap / afterSwap
H->>H: NativeSwapFeesAccrued emitted
PM-->>FE: BalanceDelta (tokens received)
FE->>U: Toast: "Bought X LNQ"
| Chain | Status | Chain ID | RPC Env Var |
|---|---|---|---|
| Ethereum Mainnet | ✅ Active | 1 | VITE_RPC_URL |
| Base | 🔜 Coming Soon | 8453 | — |
| Robinhood Chain | 🔜 Coming Soon | 8453001 | — |
| Service | File | Responsibility |
|---|---|---|
| HTTP Server | app.ts / index.ts |
Fastify + CORS + auth middleware |
| Token Routes | routes/tokens.ts |
Feed, detail, candles, trades, holders |
| Creator Routes | routes/creators.ts |
Dashboard, fee timeseries |
| Auth Routes | routes/auth.ts |
SIWE login/refresh |
| Launch Routes | routes/launch.ts |
Pre-flight validation |
| Upload Routes | routes/uploads.ts |
Image upload (serves static files) |
| Stream Routes | routes/stream.ts |
SSE live token feed |
| Launched Indexer | workers/launched-indexer.ts |
ClassicLauncher Launched events |
| Trade Indexer | workers/trade-indexer.ts |
PoolManager Swap events |
| Fee Indexer | workers/fee-indexer.ts |
Hook NativeSwapFeesAccrued events |
| Method | Path | Auth | Description |
|---|---|---|---|
GET |
/api/v1/health |
No | Health check |
GET |
/api/v1/chains |
No | Active chains |
GET |
/api/v1/tokens |
No | Token feed (paginated, sorted) |
GET |
/api/v1/tokens/preview |
No | SSE preview (landing hero) |
GET |
/api/v1/tokens/:chain/:addr |
No | Token detail |
GET |
/api/v1/tokens/:chain/:addr/trades |
No | Trade history |
GET |
/api/v1/tokens/:chain/:addr/candles |
No | OHLCV candles |
GET |
/api/v1/tokens/:chain/:addr/holders |
No | Holder list |
POST |
/api/v1/auth/login |
No | SIWE login → JWT |
POST |
/api/v1/auth/refresh |
JWT | Refresh access token |
GET |
/api/v1/creators/:addr/dashboard |
JWT | Creator revenue dashboard |
GET |
/api/v1/creators/:addr/fee-timeseries |
JWT | Daily fee history |
GET |
/api/v1/portfolio/:addr |
No | Holder positions |
POST |
/api/v1/uploads/image |
JWT | Token image upload |
GET |
/api/v1/stream |
No | SSE live launch + trade feed |
sequenceDiagram
participant FE as Frontend
participant API as API Server
participant DB as PostgreSQL
FE->>API: POST /auth/login { message, signature }
API->>API: verifyMessage(message, signature) → wallet address
API->>DB: upsert siwe_sessions(address, nonce, expires_at)
API-->>FE: { accessToken (15m), refreshToken (24h, single-use) }
Note over FE: Stored in memory (access) + localStorage (refresh)
FE->>API: GET /creators/:addr/dashboard\nAuthorization: Bearer <accessToken>
API->>API: verifyJWT → confirm address matches param
API-->>FE: Dashboard data
Note over FE: < 3 min remaining on access token
FE->>API: POST /auth/refresh { refreshToken }
API->>DB: rotate refresh token (invalidate old)
API-->>FE: { new accessToken, new refreshToken }
flowchart TD
subgraph WORKERS["Worker Processes (always-on)"]
LI["launched-indexer.ts\nPolls ClassicLauncher\nfor Launched events\nevery ~12s"]
TI["trade-indexer.ts\nPolls PoolManager\nfor Swap events\nevery ~12s"]
FI["fee-indexer.ts\nPolls EthCreatorFeeHookV3\nfor NativeSwapFeesAccrued\nevery ~12s"]
end
subgraph CHECKPOINTS["Checkpoint System"]
CP[("indexer_checkpoints\ntable\n{chain, event_type,\nlast_block}")]
end
subgraph PROCESSING["Processing"]
DECODE["decodeEventLog(ABI, log)"]
UPSERT["Idempotent upsert\nUNIQUE(chain, tx_hash, log_index)"]
end
ETH["Ethereum Mainnet\n(Alchemy RPC)"] -->|eth_getLogs| WORKERS
WORKERS --> CHECKPOINTS
WORKERS --> DECODE
DECODE --> UPSERT
UPSERT --> DB[("PostgreSQL")]
DB -->|"cache for API reads"| API["Fastify API"]
API -->|"SSE push"| FE["Frontend"]
Checkpoint reset procedure (used when DEPLOY_BLOCK is wrong):
UPDATE indexer_checkpoints
SET last_block = 25687631
WHERE chain = 'ethereum';Critical: Always set DEPLOY_BLOCK env var to the actual hook deployment block before starting indexers. If set too low, indexers start millions of blocks behind mainnet.
| Event | Contract | Worker | Table Written |
|---|---|---|---|
Launched |
ClassicLauncher | launched-indexer.ts |
tokens |
Swap |
PoolManager | trade-indexer.ts |
trades, ohlcv_candles |
NativeSwapFeesAccrued |
EthCreatorFeeHookV3 | fee-indexer.ts |
trades.fee_collected_wei |
Important: In V4 with Custom Accounting, the
Swapevent always hasamount0 = amount1 = 0. Actual amounts must be extracted differently:
| Amount | Source |
|---|---|
| ETH in (buy) | NativeSwapFeesAccrued.grossNativeAmount |
| ETH out (sell) | NativeSwapFeesAccrued.grossNativeAmount |
| Creator fee | NativeSwapFeesAccrued.creatorFee |
| Protocol fee | NativeSwapFeesAccrued.launcherFee |
| Trader address | tx.from (not in event logs) |
Correct Swap topic hash: 0x40e9cecb... (NOT the standard 0xc42... topic)
OHLCV candles are computed from raw trades by the trade indexer on write:
| Interval | Use Case |
|---|---|
1m |
Live chart |
5m |
Short-term chart |
15m |
Medium chart |
1h |
Standard chart |
4h |
Aggregated (requires post-processing) |
1d |
Daily chart |
1w |
Weekly chart |
erDiagram
chains ||--o{ tokens : "chain"
tokens ||--o{ trades : "token_id"
tokens ||--o{ ohlcv_candles : "token_id"
tokens ||--o{ fee_accruals : "token_id"
tokens ||--o{ fee_balances : "token_id"
tokens ||--o{ fee_claims : "token_id"
tokens ||--o{ token_holders : "token_id"
tokens ||--o{ trending_scores : "token_id"
chains {
varchar chain PK
integer chain_id
varchar display_name
varchar native_currency
varchar pool_manager_addr
varchar hook_factory_addr
bigint deploy_block
boolean is_active
}
tokens {
uuid id PK
varchar chain FK
varchar address
varchar pool_id
varchar launch_id
varchar creator_address
varchar name
varchar symbol
varchar phase
varchar model_type
smallint buy_fee_bps
smallint sell_fee_bps
varchar vault_address
numeric volume_total_eth_wei
numeric price_eth_wei
integer holder_count
timestamptz launched_at
}
trades {
uuid id PK
varchar chain FK
uuid token_id FK
varchar tx_hash
varchar trader_address
varchar side
numeric amount_in_wei
numeric amount_out_wei
numeric fee_collected_wei
numeric price_eth_wei
timestamptz block_timestamp
}
fee_balances {
serial id PK
uuid token_id FK
varchar vault_address
varchar beneficiary_address
numeric accrued_wei
numeric claimed_wei
numeric claimable_wei
}
Populated at deploy time. One row per supported chain.
| Column | Type | Notes |
|---|---|---|
chain |
VARCHAR(16) PK |
'ethereum', 'base', 'robinhood' |
chain_id |
INTEGER |
EVM chainId (1, 8453, 8453001) |
pool_manager_addr |
VARCHAR(66) |
Uniswap v4 PoolManager |
hook_factory_addr |
VARCHAR(66) |
Hookify hook factory |
deploy_block |
BIGINT |
First Hookify deployment block |
is_active |
BOOLEAN |
Whether chain is live |
One row per Launched event. Central table.
| Column | Type | Notes |
|---|---|---|
id |
UUID PK |
Internal identifier |
chain |
VARCHAR(16) |
Chain key |
address |
VARCHAR(66) |
ERC-20 address (lowercase) |
pool_id |
VARCHAR(66) |
Uniswap v4 PoolId (bytes32) |
vault_address |
VARCHAR(66) |
FeeSplitVaultV1 address |
phase |
ENUM |
'curve' | 'graduated' | 'classic' |
buy_fee_bps |
SMALLINT |
Creator buy fee (e.g. 300 = 3%) |
volume_total_eth_wei |
NUMERIC(38,0) |
Cumulative trade volume in wei |
price_eth_wei |
NUMERIC(38,18) |
Latest price per token in wei |
holder_count |
INTEGER |
Approximate holder count |
One row per Swap event. Idempotent upsert on (chain, tx_hash, log_index).
| Column | Type | Notes |
|---|---|---|
fee_collected_wei |
NUMERIC(38,0) |
Creator fee in wei (from NativeSwapFeesAccrued.creatorFee) |
amount_eth |
NUMERIC |
ETH gross amount (computed) |
side |
ENUM |
'buy' | 'sell' |
phase |
ENUM |
'curve' | 'dex' |
Materialized from fee accruals. claimable_wei is a generated column (accrued_wei - claimed_wei).
Current state:
fee_accrualsevents are not yet being indexed →fee_balancesis always empty. The API falls back toSUM(trades.fee_collected_wei)as the claimable amount (seecreator-repository.ts).
Tracks last processed block per event type per chain.
| Column | Notes |
|---|---|
chain |
Chain key |
event_type |
'Launched', 'Swap', 'NativeSwapFeesAccrued' |
last_block |
Last processed block number |
| # | Rule |
|---|---|
| R1 | No mainnet contract deploys without a published audit from a reputable firm |
| R2 | No upgradeable contracts. No proxies, no admin pause, no hidden setters |
| R3 | Bots and backend services never hold private keys, never sign user transactions |
| R4 | On-chain is the source of truth. DB is a rebuildable cache. Chain wins on conflict |
| R5 | Protocol fee hard-capped at 0.15% in immutable constant |
| R6 | Launches must work with API offline — frontend builds all calldata client-side |
| R7 | No payment flows, no custody, no fiat rails |
| R8 | Every deployment is reproducible from a tagged commit |
| R9 | User-facing numbers come from chain or indexed chain data — never hardcoded |
| R10 | When in doubt, choose the option that protects user funds |
flowchart LR
subgraph ATTACK_SURFACE["Eliminated Attack Surfaces"]
A["❌ Admin key exploit\n(no admin keys)"]
B["❌ Upgrade exploit\n(no proxies)"]
C["❌ Graduation MEV\n(atomic, no graduate() tx)"]
D["❌ LP drain\n(no removeLiquidity)"]
E["❌ Fee rug\n(immutable fee constants)"]
end
subgraph PROTECTIONS["Active Protections"]
P1["✅ 1-block sell delay at graduation\n(MEV guard)"]
P2["✅ 1% max wallet first 5 blocks\n(anti-sniper)"]
P3["✅ Time-decay sell tax\n(fully on-chain)"]
P4["✅ Reentrancy guards\non all vault claim paths"]
P5["✅ Checks-effects-interactions\non all external calls"]
end
| Concern | Implementation |
|---|---|
| Wallet signing | Privy embedded wallet + wagmi (never backend-signed) |
| Address normalization | All on-chain addresses .toLowerCase() before DB storage / navigation |
| Chain switch | wallet.switchChain(mainnet.id) before any claim tx |
| RPC | Alchemy mainnet via VITE_RPC_URL env var |
| Secrets | Replit Secrets manager — never in code or .env files |
| SIWE auth | EIP-4361 message signing — wallet proves ownership |
| Severity | Definition | Response Time | Example |
|---|---|---|---|
| SEV-1 | User funds at risk | 15-min ack, war room | Vault exploit, fee miscalc |
| SEV-2 | Core flow broken, no funds at risk | 4h fix/rollback | Launch down, trade broken |
| SEV-3 | Degraded experience | Next business day | Indexer lag, slow UI |
Because contracts are immutable (R2), incident response is about transparency and frontend mitigation, not admin intervention.
Chain: Ethereum Mainnet (chainId 1)
Deployer: 0xA48416A848ce870bC158A9c9aEEA8902B353ddc9
Deploy TX gas: ~19.9M gas @ 0.251 gwei ≈ 0.005 ETH
Hook salt: 0x00000000000000000000000000000000000000000000000000000000000005e7
Salt iterations: 1,511
| Property | Value |
|---|---|
| Token Address | 0x783a17b3dac99c9f2e3325dc163724cbcaaacdf0 |
| Pool ID | 0xa67cf14c0823e8486cf6e5379008c52d554cedd1973321cd3c557d8f76d9b73e |
| Vault Address | 0x9b67627365f6b63c87725be01d27c86cdd034ae9 |
| Launch Block | 25,690,436 |
| Indexer Start Block | 25,687,631 |
| Variable | Scope | Purpose |
|---|---|---|
RPC_URL |
Backend | Alchemy mainnet RPC |
DATABASE_URL |
Backend | PostgreSQL connection |
SESSION_SECRET |
Backend | SIWE JWT signing |
CHAIN_KEY |
Backend | Active chain ('ethereum') |
HOOK_ADDRESS |
Backend | 0xC5C72B770396050C088BBc111B1A448Ec3Dce0cc |
LAUNCHER_ADDRESS |
Backend | 0xE3faC65ce48695988584a46feAc505fa6B53a854 |
POOL_MANAGER_ADDRESS |
Backend | 0x000000000004444c5dc75cB358380D2e3dE08A90 |
DEPLOY_BLOCK |
Backend | 25687632 (indexer start) |
VITE_RPC_URL |
Frontend | Alchemy mainnet RPC |
VITE_CHAIN_ID |
Frontend | 1 |
VITE_CONTRACT_FEE_HOOK |
Frontend | Hook address |
VITE_CONTRACT_LAUNCHER |
Frontend | Launcher address |
VITE_CONTRACT_POOL_SWAP_TEST |
Frontend | PoolSwapTest address |
ETHERSCAN_API_KEY |
Backend | Contract verification |
| Rule | Standard |
|---|---|
| Language | TypeScript strict mode throughout |
any type |
Requires eslint-disable comment with justification |
| Contract writes | All go through lib/contracts.ts — no scattered encodeFunctionData |
| Address format | Always lowercase in DB/state; checksummed only for on-chain calls |
| Number formatting | lib/format.ts only — consistent K/M/B, subscript zeros |
| Error handling | Structured JSON { code, message, details } — never leak stack traces |
| API versioning | All endpoints at /api/v1 — breaking changes go to /v2 |
// Correct pattern (Privy + wagmi)
const provider = await wallet.getEthereumProvider(); // Privy primary
const walletClient = createWalletClient({ chain: mainnet, transport: custom(provider) });
const publicClient = createPublicClient({ chain: mainnet, transport: http(MAINNET_RPC) });
const [account] = await walletClient.getAddresses();
// Chain switch before any mainnet tx
if (chainId !== mainnet.id) {
await wallet.switchChain(mainnet.id);
}All indexed events use UNIQUE(chain, tx_hash, log_index) and are written with ON CONFLICT DO NOTHING:
await db.insert(trades).values(row)
.onConflictDoNothing({ target: [trades.chain, trades.txHash, trades.logIndex] });This ensures full reindexing from genesis is always safe.
| Pitfall | Correct Approach |
|---|---|
V4 Swap event has amount0=amount1=0 |
Read amounts from NativeSwapFeesAccrued event |
| Hook ETH balance always 0 | ETH tracked as ERC-6909 in PoolManager — V4 Custom Accounting |
claimCreatorFees needs PoolManager unlock |
Requires EOA tx to hook directly (not a view) |
decodeEventLog returns checksummed addresses |
Always .toLowerCase() before DB/state |
Drizzle .default(0n) breaks migrations |
Use .default(sql\0`)` instead |
| Dark mode not applied | Add class="dark" to <html> in index.html |
Orval codegen duplicates index.ts exports |
Clean before each codegen run |
| DEPLOY_BLOCK wrong → indexer 14M blocks behind | Set env var to actual hook deploy block |
| System | Status | Notes |
|---|---|---|
| ClassicLauncher (mainnet) | ✅ Live | Token LNQ successfully launched |
| EthCreatorFeeHookV3 | ✅ Live | Emitting NativeSwapFeesAccrued events |
| FeeSplitVaultV1 | ✅ Deployed | Per-token vault exists |
| Trade Indexer | ✅ Running | Indexing all PoolManager Swaps |
| Launched Indexer | ✅ Running | Indexing ClassicLauncher events |
| Fee Indexer | 🟡 Partial | Emitting correct events; fee_accruals table not yet populated |
| Creator Dashboard | ✅ Working | Revenue via trades fallback |
| Claim Revenue flow | 🟡 Debugging | 2-TX flow (flush + claim) implemented; investigating claimCreatorFees revert |
| Explore page | ✅ Working | Live trades, correct chain, timeAgo display |
| Token Detail page | ✅ Working | Chart, trades, swap panel |
| Swap (buy/sell) | ✅ Working | Via PoolSwapTest on mainnet |
| Launch form | ✅ Working | Network selector, Ethereum Mainnet only |
| OHLCV Charts | ✅ Working | 1m/5m/15m/1h/1d/1w (4h needs aggregation) |
| Holder tracking | 🟡 Partial | Percentages show 0 (no Transfer indexer) |
| Audit tracking | ⚪ Planned | Show "audited / not audited" labels |
| Social links on Explore | ⚪ Planned | Twitter/Telegram links from token metadata |
| Bonding Curve mode | ⚪ Planned | Architecture designed; Classic V3 is current model |
| Base chain | ⚪ Planned | Phase 2 |
| Robinhood Chain | ⚪ Planned | Phase 2 |
- Deploy Classic V3 contracts to Ethereum mainnet
- Token launch flow (single transaction)
- Trade indexing + OHLCV charts
- Creator dashboard with fee visibility
- Explore page with live token feed
| Priority | Task |
|---|---|
| 🔴 Critical | Fix claimCreatorFees revert — understand V4 ERC-6909 claim pattern |
| 🔴 Critical | Implement fee_accruals indexing from NativeSwapFeesAccrued events |
| 🔴 Critical | Fix holder percentages (index Transfer events) |
| 🟠 High | Keep price charts live as new trades arrive (real-time candle updates) |
| 🟠 High | 4H chart timeframe with aggregated candle data |
| 🟠 High | Show beneficiary fee breakdown on dashboard |
| 🟠 High | Social links on Explore cards |
| 🟡 Medium | Audit label on token detail (verified / not audited) |
| 🟡 Medium | Revenue trend accuracy across all timeframes |
| 🟡 Medium | Claimable ETH per-token view on dashboard |
- Base chain deployment + indexer
- Robinhood Chain deployment + indexer
- Cross-chain token feed
- UnifiedLaunchHook (Native BC→AMM) model
- Auto-graduation on-chain (atomic, no keeper)
- Social bot layer (Twitter/X, Farcaster, Telegram) — deferred from MVP
- $HOOK governance token (TBD, pending legal review)
- Immunefi bug bounty program (target: $100K critical)
- Third-party smart contract audit
- Solana adapter (Meteora DBC)
- Builder Program (third-party hook developers)
- AI Copilot (launch name/description generation)
- Dividend Vault for top token holders
| Concept | Description |
|---|---|
| PoolManager | Singleton contract holding all V4 pool state and liquidity |
| Hook | Contract called before/after swaps — enables custom logic |
| Custom Accounting | Hook uses BeforeSwapDelta to override pricing; amounts are 0 in Swap event |
| ERC-6909 Claims | V4 tracks balances as fungible claims in PoolManager (not direct ETH transfers) |
take() |
Hook pulls ETH from PoolManager to itself during afterSwap |
settle() |
Hook sends ETH to PoolManager |
| DYNAMIC_FEE_FLAG | Pool flag required for hook to override swap fees |
| PoolId | keccak256(token0, token1, fee, tickSpacing, hooks) — unique pool identifier |
| PoolKey | Struct {currency0, currency1, fee, tickSpacing, hooks} — used for all operations |
totalFeeWei = swapAmountWei × (buyFeeBps + protocolFeeBps) / 10000
creatorFeeWei = totalFeeWei × buyFeeBps / (buyFeeBps + protocolFeeBps)
protocolFeeWei = totalFeeWei × protocolFeeBps / (buyFeeBps + protocolFeeBps)
// Example: 1 ETH swap, 3% buy fee, 0.15% protocol fee
totalFee = 1e18 × 315 / 10000 = 0.0315 ETH
creatorFee = 0.0315 × 300/315 = 0.030 ETH (3%)
protocolFee = 0.0315 × 15/315 = 0.0015 ETH (0.15%)
| Term | Definition |
|---|---|
| Hookify | Token launch infrastructure built on Uniswap v4 hooks |
| Classic | Launch model: direct v4 pool with fee hook, no bonding curve phase |
| Native BC→AMM | Launch model: bonding curve inside hook, auto-graduates to AMM (planned) |
| PoolSwapTest | Uniswap v4 test contract used as swap router on mainnet (no Universal Router on mainnet) |
| FeeVault / FeeSplitVaultV1 | Per-token contract holding creator fees pending claim |
| claimCreatorFees | Hook function that flushes accumulated ETH from hook → vault |
| Custom Accounting | V4 hook feature: hook intercepts price/amounts via BeforeSwapDelta |
| NativeSwapFeesAccrued | Hook event emitted on every swap — contains fee amounts and trader info |
| SIWE | Sign-In With Ethereum (EIP-4361) — wallet-based auth |
| Phase | Token lifecycle stage: curve (bonding curve) | graduated | classic |
| PoolId | 32-byte hash identifying a Uniswap v4 pool |
| Pool Key | Struct identifying a pool: {token0, token1, fee, tickSpacing, hookAddress} |
This document is the single source of truth for Hookify engineering. When source code differs from this document, update this document first, then implement.
Maintained by: Hookify Engineering
Last updated: August 5, 2026