LP TERMINAL — terminal-style LP frontend for Robinhood Chain

Static SPA over UP33 (ve(3,3)) + the official Uniswap v2/v3 deployments:
browse and add liquidity across all three, manage CL/v2 positions, one-token
ZAP adds, aggregator vs native swaps, and LP-based limit orders. Ships with a
zero-dependency pool indexer for uniswap discovery.

Browser-wallet signing only; no key material anywhere.
This commit is contained in:
labrinyang
2026-07-17 19:10:30 +08:00
commit 4e317eff6d
77 changed files with 22678 additions and 0 deletions
+224
View File
@@ -0,0 +1,224 @@
// SQLite store (node:sqlite — built into node ≥22.13, zero dependencies).
// bigints are stored as TEXT and travel as strings through the API; REAL
// columns are display/ranking data only, never used to build transactions.
import { mkdirSync } from 'node:fs'
import { dirname } from 'node:path'
import { DatabaseSync } from 'node:sqlite'
import { DB_PATH, now } from './config'
mkdirSync(dirname(DB_PATH), { recursive: true })
export const db = new DatabaseSync(DB_PATH)
db.exec(`
PRAGMA journal_mode = WAL;
PRAGMA synchronous = NORMAL;
CREATE TABLE IF NOT EXISTS pools (
address TEXT PRIMARY KEY, -- lowercase
proto TEXT NOT NULL, -- 'univ2' | 'univ3'
token0 TEXT NOT NULL, -- lowercase
token1 TEXT NOT NULL,
fee_ppm INTEGER NOT NULL, -- univ2 fixed 3000 (0.30%)
tick_spacing INTEGER, -- univ3 only
created_block INTEGER, -- univ3 only (from PoolCreated)
pair_index INTEGER, -- univ2 only (allPairs index)
added_ts INTEGER NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_pools_t0 ON pools(token0);
CREATE INDEX IF NOT EXISTS idx_pools_t1 ON pools(token1);
CREATE TABLE IF NOT EXISTS tokens (
address TEXT PRIMARY KEY,
symbol TEXT NOT NULL DEFAULT '?',
decimals INTEGER NOT NULL DEFAULT 18,
meta_ok INTEGER NOT NULL DEFAULT 0, -- 0 = symbol/decimals defaulted (call reverted)
price_usd REAL,
price_depth_usd REAL NOT NULL DEFAULT 0, -- USD depth backing the price (bigger wins)
price_src TEXT, -- 'gt' | 'pool' | 'anchor'
price_updated INTEGER
);
CREATE TABLE IF NOT EXISTS pool_state (
address TEXT PRIMARY KEY,
sqrt_price TEXT, -- univ3
tick INTEGER, -- univ3
liquidity TEXT, -- univ3 in-range L
reserve0 TEXT NOT NULL DEFAULT '0', -- univ2: reserves; univ3: erc20 balances (TVL basis)
reserve1 TEXT NOT NULL DEFAULT '0',
total_supply TEXT, -- univ2 LP supply
tvl_usd REAL,
tvl_approx INTEGER NOT NULL DEFAULT 0, -- 1 = only one side priced (tvl = 2× that side)
updated INTEGER NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_state_tvl ON pool_state(tvl_usd);
CREATE TABLE IF NOT EXISTS pool_stats (
address TEXT PRIMARY KEY,
vol24h_usd REAL,
txns24h INTEGER,
liq_usd REAL, -- GT's own reserve figure (cross-check; tvl_usd is chain-derived)
source TEXT NOT NULL,
updated INTEGER NOT NULL
);
CREATE TABLE IF NOT EXISTS kv (k TEXT PRIMARY KEY, v TEXT NOT NULL);
`)
// ---- kv ----
const kvGetQ = db.prepare('SELECT v FROM kv WHERE k = ?')
const kvSetQ = db.prepare('INSERT INTO kv (k, v) VALUES (?, ?) ON CONFLICT(k) DO UPDATE SET v = excluded.v')
export const kvGet = (k: string): string | undefined => (kvGetQ.get(k) as { v: string } | undefined)?.v
export const kvSet = (k: string, v: string) => void kvSetQ.run(k, v)
// ---- pools ----
const insPoolQ = db.prepare(`
INSERT OR IGNORE INTO pools (address, proto, token0, token1, fee_ppm, tick_spacing, created_block, pair_index, added_ts)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`)
/** returns true when the pool is new */
export function insertPool(p: {
address: string
proto: 'univ2' | 'univ3'
token0: string
token1: string
feePpm: number
tickSpacing?: number
createdBlock?: number
pairIndex?: number
}): boolean {
const r = insPoolQ.run(
p.address.toLowerCase(),
p.proto,
p.token0.toLowerCase(),
p.token1.toLowerCase(),
p.feePpm,
p.tickSpacing ?? null,
p.createdBlock ?? null,
p.pairIndex ?? null,
now(),
)
return Number(r.changes) > 0
}
export type PoolRow = {
address: string
proto: 'univ2' | 'univ3'
token0: string
token1: string
fee_ppm: number
tick_spacing: number | null
}
const poolsByAddrQ = db.prepare('SELECT address, proto, token0, token1, fee_ppm, tick_spacing FROM pools WHERE address = ?')
export const poolRow = (addr: string) => poolsByAddrQ.get(addr.toLowerCase()) as PoolRow | undefined
export const allPoolAddrs = (): string[] =>
(db.prepare('SELECT address FROM pools').all() as { address: string }[]).map((r) => r.address)
export const poolCounts = () =>
db.prepare(`SELECT proto, COUNT(*) AS n FROM pools GROUP BY proto`).all() as { proto: string; n: number }[]
// ---- tokens ----
const insTokenQ = db.prepare(`
INSERT INTO tokens (address, symbol, decimals, meta_ok) VALUES (?, ?, ?, ?)
ON CONFLICT(address) DO UPDATE SET symbol = excluded.symbol, decimals = excluded.decimals, meta_ok = excluded.meta_ok`)
export const upsertTokenMeta = (addr: string, symbol: string, decimals: number, metaOk: boolean) =>
void insTokenQ.run(addr.toLowerCase(), symbol, decimals, metaOk ? 1 : 0)
const priceQ = db.prepare(`
INSERT INTO tokens (address, price_usd, price_depth_usd, price_src, price_updated) VALUES (?, ?, ?, ?, ?)
ON CONFLICT(address) DO UPDATE SET price_usd = excluded.price_usd, price_depth_usd = excluded.price_depth_usd,
price_src = excluded.price_src, price_updated = excluded.price_updated`)
export const setTokenPrice = (addr: string, usd: number, depthUsd: number, src: string) =>
void priceQ.run(addr.toLowerCase(), usd, depthUsd, src, now())
export type TokenRow = {
address: string
symbol: string
decimals: number
meta_ok: number
price_usd: number | null
price_depth_usd: number
price_src: string | null
price_updated: number | null
}
export const allTokens = () => db.prepare('SELECT * FROM tokens').all() as TokenRow[]
export const missingMetaTokens = (): string[] =>
(
db
.prepare(
`SELECT DISTINCT u.addr FROM (SELECT token0 AS addr FROM pools UNION SELECT token1 FROM pools) u
LEFT JOIN tokens t ON t.address = u.addr WHERE t.address IS NULL`,
)
.all() as { addr: string }[]
).map((r) => r.addr)
// ---- pool_state ----
const upStateQ = db.prepare(`
INSERT INTO pool_state (address, sqrt_price, tick, liquidity, reserve0, reserve1, total_supply, updated)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
ON CONFLICT(address) DO UPDATE SET sqrt_price = excluded.sqrt_price, tick = excluded.tick,
liquidity = excluded.liquidity, reserve0 = excluded.reserve0, reserve1 = excluded.reserve1,
total_supply = excluded.total_supply, updated = excluded.updated`)
export const upsertState = (
addr: string,
s: { sqrtPrice?: bigint; tick?: number; liquidity?: bigint; reserve0: bigint; reserve1: bigint; totalSupply?: bigint },
) =>
void upStateQ.run(
addr.toLowerCase(),
s.sqrtPrice !== undefined ? String(s.sqrtPrice) : null,
s.tick ?? null,
s.liquidity !== undefined ? String(s.liquidity) : null,
String(s.reserve0),
String(s.reserve1),
s.totalSupply !== undefined ? String(s.totalSupply) : null,
now(),
)
const tvlQ = db.prepare('UPDATE pool_state SET tvl_usd = ?, tvl_approx = ? WHERE address = ?')
export const setTvl = (addr: string, tvl: number | null, approx: boolean) =>
void tvlQ.run(tvl, approx ? 1 : 0, addr.toLowerCase())
// ---- pool_stats ----
const upStatsQ = db.prepare(`
INSERT INTO pool_stats (address, vol24h_usd, txns24h, liq_usd, source, updated) VALUES (?, ?, ?, ?, ?, ?)
ON CONFLICT(address) DO UPDATE SET vol24h_usd = excluded.vol24h_usd, txns24h = excluded.txns24h,
liq_usd = excluded.liq_usd, source = excluded.source, updated = excluded.updated`)
export const upsertStats = (addr: string, vol24h: number | null, txns24h: number | null, liqUsd: number | null, source: string) =>
void upStatsQ.run(addr.toLowerCase(), vol24h, txns24h, liqUsd, source, now())
/** hot set: real TVL, or GT-visible activity, or freshly created */
export const hotAddrs = (): string[] =>
(
db
.prepare(
`SELECT address FROM pool_state WHERE tvl_usd >= ?
UNION SELECT address FROM pool_stats WHERE vol24h_usd > 0
UNION SELECT address FROM pools WHERE added_ts > ?`,
)
.all(10_000, now() - 3_600) as { address: string }[]
).map((r) => r.address)
/**
* active set for the hourly sweep: anything that ever showed ≥$100 TVL plus
* everything younger than 48h. The launchpads mint ~20k dust pools/day — the
* 6-hourly census (allPoolAddrs) keeps their state honest, the hourly sweep
* stays bounded by real liquidity instead of catalog size.
*/
export const activeAddrs = (): string[] =>
(
db
.prepare(
`SELECT address FROM pool_state WHERE tvl_usd >= ?
UNION SELECT address FROM pools WHERE added_ts > ?`,
)
.all(100, now() - 172_800) as { address: string }[]
).map((r) => r.address)
export const tx = (fn: () => void) => {
db.exec('BEGIN')
try {
fn()
db.exec('COMMIT')
} catch (e) {
db.exec('ROLLBACK')
throw e
}
}