diff --git a/.env.example b/.env.example index 7e351bb..d236455 100644 --- a/.env.example +++ b/.env.example @@ -1,7 +1,6 @@ # LP TERMINAL — copy to `.env` in the REPO ROOT (next to package.json). # Vite reads it via envDir/envPrefix (see vite.config.ts); the indexer reads -# `RPC` from the same file. Every key here is OPTIONAL — with no .env at all -# the app runs against the chain's public RPC and the public Kyber aggregator. +# `RPC` from the same file. # Private JSON-RPC URL for Robinhood Chain (chainId 4663), e.g. a provider URL # with its key path. SECRET: Vite BAKES this into the JS bundle, so set it only @@ -10,19 +9,21 @@ # Unset -> https://rpc.mainnet.chain.robinhood.com (keyless, slower). RPC= -# KyberSwap aggregator base. Absolute URL = browser calls Kyber directly. -# A path like /kyber = route through your own same-origin reverse proxy. +# REQUIRED (the build fails without it): address that receives the terminal +# output fee — currently 0 bps on direct swaps, 9 bps on ZAP's embedded swap +# leg. Use an address you control. (Legacy env name.) +KYBERSWAP_FEE_RECEIVER= + +# KyberSwap base for READ-ONLY USD valuation (UP rewards, APR, simulations). +# MARKET and ZAP never call it, and the app has no Kyber transaction builder. +# Absolute URL = browser calls Kyber directly; a path like /kyber = route +# through your own same-origin reverse proxy. KYBERSWAP_AGGREGATOR_API_BASE_URL=https://aggregator-api.kyberswap.com KYBERSWAP_CHAIN=robinhood -# WHITELIST: the only address swap calldata is ever sent to. The app hard-fails -# a quote whose routerAddress differs from this. Change only if Kyber migrates. -KYBERSWAP_ROUTER_ADDRESS=0x6131B5fae19EA4f9D964eAc0408E4408b66337b5 - -# Optional platform fee on Kyber swaps, charged on the output token and paid by -# the Kyber router. BOTH must be set to activate; leave blank for no fee. -# KYBERSWAP_FEE_BPS=10 -# KYBERSWAP_FEE_RECEIVER=0x... +# Swap-solver quote API (cross-venue split routing, ready-to-sign Settler tx). +# Defaults to the hosted public instance; point it at your own if you run one. +# VITE_SOLVER_URL= # Optional: only WalletConnect QR pairing needs this. Injected wallets # (MetaMask / Rabby / OKX) work without it. Get one at cloud.walletconnect.com diff --git a/.gitignore b/.gitignore index 5b099b1..79b9ac7 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,18 @@ indexer-dist tsconfig.tsbuildinfo *.log .vite + +# local tool caches/artifacts +.codegraph/ +graphify-out/ +# Claude Code worktrees — nested checkouts of THIS repo; committing one would +# bury a duplicate copy of the tree. Project config under .claude/ stays tracked. +.claude/worktrees/ + +# Internal notes stay internal. Docs are written for the private tree and get +# copied in by mistake during a sync; ignoring them means `git add -A` can never +# publish one. Publishing a doc is opt-in: add it below, deliberately. +*.md +!README.md +# The contract map is on-chain fact, and the source comments cite it by path. +!docs/up33-contract-map.md diff --git a/package-lock.json b/package-lock.json index 0987a73..1eb27d3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,11 +1,11 @@ { - "name": "lp-terminal", + "name": "up33-terminal", "version": "0.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "lp-terminal", + "name": "up33-terminal", "version": "0.1.0", "dependencies": { "@rainbow-me/rainbowkit": "2.2.11", diff --git a/package.json b/package.json index 62fa5ec..975b480 100644 --- a/package.json +++ b/package.json @@ -1,15 +1,18 @@ { - "name": "lp-terminal", + "name": "up33-terminal", + "private": true, "version": "0.1.0", - "license": "MIT", "type": "module", "scripts": { "dev": "vite", "build": "tsc && vite build", "preview": "vite preview", "typecheck": "tsc --noEmit", + "test": "node --experimental-test-module-mocks --import=tsx --test indexer/*.test.ts src/lib/*.test.ts src/lib/bridge/*.test.ts", "smoke": "tsx scripts/smoke.ts", + "smoke:bridge": "tsx scripts/bridge-smoke.ts", "smoke:uni": "tsx scripts/uni-browse-smoke.ts", + "smoke:logtail": "tsx scripts/logtail-smoke.ts", "indexer": "tsx indexer/main.ts" }, "dependencies": { diff --git a/vite.config.ts b/vite.config.ts index dce68d1..330a785 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -6,16 +6,20 @@ import { fileURLToPath } from 'node:url' // envPrefix exposes exactly those keys to the client bundle. // // SECRET RULE: `RPC` (private key-bearing URL) is for personal/local builds only — -// a build meant for public serving must NOT have it set (see README "Chain reads"). +// a build meant for public serving must NOT have it set (see README "Deploy"). // A public build reads the chain through same-origin `/rpc` instead; the dev and // preview servers below emulate that reverse proxy so the mode is testable locally. export default defineConfig(({ mode }) => { const envDir = fileURLToPath(new URL('.', import.meta.url)) - const env = loadEnv(mode, envDir, ['RPC']) + const env = loadEnv(mode, envDir, ['RPC', 'KYBERSWAP_']) + const feeReceiver = (process.env.KYBERSWAP_FEE_RECEIVER ?? env.KYBERSWAP_FEE_RECEIVER ?? '').trim() + if (!/^0x[0-9a-fA-F]{40}$/.test(feeReceiver)) { + throw new Error('KYBERSWAP_FEE_RECEIVER must be a valid address; the swap/zap fee sweep requires a configured receiver (see .env.example)') + } // local emulation of the production reverse proxies, so the server deployment // mode is fully testable via `RPC="" npm run build && npm run preview`: // - /rpc -> the .env RPC (or RPC_PROXY_TARGET override); key stays in node - // - /kyber -> the public kyber aggregator + // - /kyber -> read-only Kyber valuation endpoint const upstream = (process.env.RPC_PROXY_TARGET ?? env.RPC ?? '').trim() const passthru = (prefix: string, target: string) => ({ target, @@ -23,7 +27,6 @@ export default defineConfig(({ mode }) => { rewrite: (p: string) => p.replace(new RegExp(`^${prefix}`), ''), }) const proxy: Record = { - '/kyber-setting': passthru('/kyber-setting', 'https://ks-setting.kyberswap.com'), '/kyber': passthru('/kyber', 'https://aggregator-api.kyberswap.com'), '/dexscreener': passthru('/dexscreener', 'https://api.dexscreener.com'), '/goldsky': passthru('/goldsky', 'https://api.goldsky.com'),