Open Net Worth
Open Net Worth is an open-source personal AI wealth assistant you run yourself: one ledger for your whole financial life, worked by an AI agent you control.
翻譯準備中
尚未偵測到更新。GitHub 發布版本後會顯示在這裡。
個人 GitHub 帳號下公開儲存庫的擁有者可以登入並驗證所有權,自行管理介紹,無需管理員核准。
登入並產生驗證檔案,在 30 分鐘內將其提交至此儲存庫的預設分支。其他分支或分叉儲存庫中的檔案無效。然後在下方檢查檔案,即可無需管理員核准直接管理專案。請保留檔案以維持管理權限。
如需登入,將前往登入頁面。登入後請回到此處,再次點擊所選的驗證按鈕。
在專案資料夾中執行 /nomorevibe verify,即可驗證所有權並自行管理介紹。
一分鐘完成驗證Plan your Metric LP position: pick a pool, amount and range, get estimated fees, APR and the exact bins to fill.
翻譯準備中
正在載入完整 README。
檢視完整 README ↗An unofficial fee calculator and bin planner for liquidity providers on Metric. Metric pools are "active pools": liquidity sits in percentage bins around an oracle reference price. Think Poolfish (uniswap.fish) for Metric.
Unofficial community tool, not affiliated with Metric. Not financial advice.
analytics24h.aprBps) or 7d (aprBps7d); TVL filter Any / ≥$10k / ≥$100k / ≥$1M; chain filter./metadata has no 7d volume, so "no 7d volume" = 7d fee APR 0/missing and no 24h volume.psort, pdir, papr, ptvl, pchain, pinactive, pprivate=1 to show private pools; only non-defaults, never in share links) and in localStorage. A URL with picker params wins over storage.src/lib/activity.ts: 24h volume 0 → "No trades today" (it may have traded earlier this week); 7d fee APR 0/missing too → "No trades 7d".
+1, 1, -2 and −2, reject other characters with an inline hint (keeping the last valid value), clamp to the pool's bins with a visible note, and have −/+ steppers and the bin's % edge underneath.pnlAprBps (historical, can be negative).tradingPnlAprBps7d) next to the fee APR. Trading PnL already includes fees ("includes Edge"), so the two are never added./api/og renders a 1200×630 PNG of the plan from the same URL params (Next.js ImageResponse). It's used as the page's OG/Twitter image, so shared links preview the result, and the Share menu offers Copy link, Download image and Post on X.
public, max-age=3600, s-maxage=86400, stale-while-revalidate=604800. Plan-only fallbacks are cached for 60s, so live numbers replace them quickly.htmlLimitedBots: /.*/ keeps og/twitter tags in <head> for every user agent. Bump OG_VERSION in src/app/page.tsx to make X refetch cached images.sf est. fees (USD), sa fee APR %, sn net APR %, sd date, plus optional sp pair and sw bin width./api/og renders those exact values with no Metric call, and shows "as of …". The tweet text is built from the same formatted values.src/lib/share.ts: numbers only, bounded ranges, real dates no later than today, token-symbol pairs. Any invalid value voids the snapshot, so the image can't be made to render arbitrary text.s=<6 chars> so X treats it as a new URL. It is excluded from the og:image URL, which is canonical (normalized plan params, then snapshot params, then v).npm run check-share -- https://your-deploy simulates X's crawler: it checks the tags are in <head>, the image URL is absolute, and /api/og returns 200 image/png at 1200×630 for both HEAD and GET.prefers-reduced-motion.?chain=56&pool=0x..&amount=1000&days=30&from=-8&to=7&strategy=uniform&period=7d. A preset=80|90|95 parameter keeps an auto preset live.prefers-reduced-motion. Lighthouse accessibility: 100 on every page (mobile and desktop), no layout shift on load.A text box at the top of the planner: type e.g. "Deposit $200 on AI/USDG with a medium range for 60 days" and press Fill planner (or Enter). It's a rule-based parser, with no AI and no API keys. It only fills the form; every number afterwards comes from the planner's own calc.
$200, 200$, 200 dollars/usd/bucks, 1.5k, 2,000; € is read as USD, with a note;AI/USDG, AI-USDG, ai usdg, AI and USDG, or a single token like "on SKHYB" (largest pool with it). Case-insensitive, ≤ 1 typo on symbols of 3+ chars;#-3 to #2, bins -3 to 2, from -3 to +2;±0.1% or 0.1% around the price (bins whose edges cover that around the oracle price);60 days, 60d, 2 months, a quarter, 1 week, a year;using 24h data, based on last week, 30 days of data./api/pools includes every pool (minTvlFiat=0). A submission made before the pool list has loaded waits for it./compare)Up to three ranges (A, B, C) side by side for the same deposit, duration and data period. Open it from the planner's "Compare ranges" link; it starts with Narrow 80%, Medium 90% and Wide 95% of the window's volume.
/compare?chain=56&pool=0x..&amount=1000&days=30&period=7d&cmp=-3:1:u,-5:3:u,-8:6:c. Entries are <from>:<to>:<strategy> or p<80|90|95>:<strategy> (u/c/f), optionally followed by :<chainId>:<0xpool>. Invalid entries are dropped.cp, cc, cdays, sd, ca1..3, cb, cf, cn, ce, cq), validated like the other snapshots, and renders a 2–3 column card.src/lib/compare.ts (pure, reuses calc.ts)./pools)Every Metric pool ranked by net APR (7d). tradingPnlAprBps7d is fees + price moves and already includes fees, so the two are never added.
/api/pools-ranking pulls /evm/metadata?include24h=true&minTvlFiat=0 per chain, all chains in parallel.
total.staleChains); only a chain with no fallback is reported in failedChains.s-maxage=300, stale-while-revalidate=86400. Incomplete ones are no-store, so a cold-start failure is never cached at the edge.tp1..5, tn1..5, tf1..5, optional tch, sd). They're validated like the other snapshots in src/lib/share.ts, and /api/og renders them as a ranked card./portfolio?wallet=0x…)Paste any wallet to see its Metric LP positions. It's read-only: no wallet connection, no signatures, and only public onchain data plus Metric's public API.
eth_getLogs on https://rpc.metric.xyz/{chainId} from block 0.
[[LiquidityAdded, LiquidityRemoved], wallet]./evm/metadata?minTvlFiat=0 are dropped.(pool, salt), covering every bin it ever touched.0x83d66c5934b1525ee82e6e772c76c7acc778bbb7.
positionBinShares, binTotalShares and binState.eth_calls in JSON-RPC batches.positionBinShares / binTotalShares × (token0.reserveFiat + token1.reserveFiat), using /analytics/bins?period=7d.Σ value_b × (aprBps_b / 10000) × heldDays / 365.
value_b = share_b × (reserveFiat0_b + reserveFiat1_b), with shares at the current block.heldDays runs from the first deposit's block timestamp, capped at the 7d window. When the position is older than 7d, the label reads "last 7d".Σ value_b × aprBps_b / Σ value_b. It's consistent with the fee estimate (fees = APR × value × days / 365).pnlAprBps (bins without it are skipped), shown with the $ it implies over your time held. Pool history, not your result.PnL / HODL value / days held × 365, shown after 1 day).LiquidityAdded − Σ|Δ| from , per token, in "scaled" units.Environment variables (all optional):
| Var | Purpose |
|---|---|
NEXT_PUBLIC_EXAMPLE_WALLET | Wallet behind the "Try an example" button. The button is hidden when unset. Keep it out of the repo. |
METRIC_API_BASE, METRIC_RPC_BASE | Point the server at a local mock instead of api.metric.xyz / rpc.metric.xyz (testing only). |
npm install
npm run dev # http://localhost:3000
npm test # Vitest unit tests (src/lib)
npm run build # production build
Deploy on Vercel by importing the repo. No environment variables are needed.
All data comes from the public Metric API (https://api.metric.xyz/public/v1), which needs no auth. Every call goes through Next.js route handlers in src/app/api/*. This avoids CORS and adds caching.
| Route | Upstream | Cache |
|---|---|---|
/api/pools | /evm/metadata?include24h=true&count=500&sortBy=tvl&chainIds={id}, one request per supported chain; pages with offset=nextOffset until null; envelope { data, total, nextOffset } | 300s |
/api/pools/{chain}/{pool} | /evm/{chain}/{pool}/metadata?contract_version=v1&include24h=true | 300s |
/api/pools/{chain}/{pool}/bins?period= | /evm/{chain}/{pool}/analytics/bins?contract_version=v1&period= (undocumented, used by app.metric.xyz) | 60s |
/api/chains | /chains → { chains: [{ type, chainId, name }] } (keyed on chainId; name casing varies) | 300s |
Error handling:
429 is passed through with retryAfter, and the UI shows a Retry button.analytics24h.feesFiat (per day of its window) shared pro-rata against analytics24h.tvlFiat.chainIds filter or URL path), never from its chain field.aprBps7d, tradingPnlAprBps7d, pauseLevel, curBinIdx, extensionDetails, tvlFiat, analytics24h) are omitted when absent.token*FiatPrice can be null. Token amounts for that token are then shown as unavailable.bin and swapCount in the bins response are strings, and are parsed to numbers.green (Best Practices), yellow (Neutral), red (High Risk), black (Delisted by Metric) and unclassified. Red and black pools get a prominent warning, and pools with depositWhitelistingEnabled show "Private" instead of a tier.priceProviderCooldownSecs = 2^64−1 are parsed with BigInt, and anything ≥ 2^53 is shown as "never".Docs: https://docs.metric.xyz/llms-full.txt
Everything is in src/lib/calc.ts, as pure functions with tests in tests/.
windowDays = (toTs − fromTs) / 86400. The window is capped at the pool's age (e.g. 30d and all both gave 14.02 days for a young pool), so the code never assumes 7 or 30.i spans [i·w, (i+1)·w] with w = lengthE6 / 1e6 (100 → 0.01%), and its mid is (i + 0.5)·w. So #-8 = -0.075% and #7 = +0.075%, matching the official app.fees_b = token0.lpFeesFiat + token1.lpFeesFiatliq_b = token0.avgLiquidityFiat + token1.avgLiquidityFiat, or the reserves if that sum is 0dailyFees_b = fees_b / windowDaysuserDaily = Σ_b dailyFees_b × U_b / (liq_b + U_b), where U_b is your USD in bin b.userDaily × 365 / deposit. Bin APR = dailyFees_b × 365 / (liq_b + U_b).pnlAprBps is Metric's trading PnL APR per bin, which already includes fees.
netDaily_b = (pnlAprBps_b / 10000) × liq_b / 365.userNet = Σ netDaily_b × U_b / (liq_b + U_b) and netAPR = userNet × 365 / deposit.pnlAprBps = null are skipped (they count as 0, and the UI says how many were skipped). The net figure is simple, with no compounding, and can be negative.r = userDaily / deposit, value = D × (1 + r)^days − D.U_b → 0, dailyFees_b × 365 / liq_b should equal Metric's aprBps / 10000.
tests/calc.test.ts checks this on synthetic data.tests/fixtures.test.ts checks it to within 1% on real SKHYB/USDT (BSC) data in tests/fixtures/: a 24h /analytics/bins excerpt, plus 7d per-bin totals. npm run fixtures downloads full, current responses next to them.Estimates assume past volume and liquidity continue and that the price stays in your range. Fees ≠ profit: price moves can outweigh earned fees.
src/lib/calc.ts fee model, presets, distributions, formatting (pure)
src/lib/normalize.ts API → Pool normalization (exact Metric response shapes, typed in types.ts)
src/lib/num.ts safe number / BigInt parsing
src/lib/risk.ts tier + reason-code copy
src/lib/urlState.ts shareable URL state
src/lib/plan.ts URL state + API data → range and estimate (shared by the page and /api/og)
src/lib/usePlanner.ts client hook: URL sync, data fetching, derived estimate
src/app/api/** proxy route handlers + /api/og share image
src/lib/rangeInputs.ts bin input parsing/clamping, preset summaries, Wider, tokens needed (pure)
src/lib/poolList.ts pool picker: sort, filters, APR period, URL/localStorage prefs (pure)
src/lib/activity.ts "No trades" badge rule (pure)
src/lib/noTrades.ts empty state for windows with no trades (pure)
src/lib/intent.ts "Describe your plan" rule-based parser + pool matching (pure)
src/lib/landing.ts landing stats, trending picks, auto-typing placeholder (pure)
src/lib/ruler.ts bin ruler gestures/keys/tint and histogram zoom (pure)
src/lib/palette.ts ⌘K pool + action search (pure)
src/lib/compare.ts compare ranges: metrics, best per row, verdicts, cmp URL encoding (pure)
src/lib/ranking.ts top pools: rows, filters, sorting, highlights, weekly post (pure)
src/lib/portfolio/ portfolio: ABIs, pure model (decoding, positions, value, HODL, status, insights), server loader
src/components/** UI (Planner, ConfigPanel, Hero, Histogram, BinDetails, Risk, ShareMenu, PoolPicker, compare/*, portfolio/*)
src/lib/intent.ts: parseText → matchPool → parseIntent(text, pools) behind an IntentParser interface, so a fallback parser could be added later without UI changes. No AI is used today.src/lib/ranking.tstests/ranking.test.tsLiquidityRemovedbinState balances use the same scaled units, and analytics gives the same bins' reserveFiat. So reserveFiat / balanceScaled is a USD rate per scaled unit, with no assumption about token decimals.scaled = amount × 10^exp) is reported./api/og?pv=…&pp=…&pa=…&pc=…&sd=… shows totals only. The wallet is never part of the image URL./api/positions?wallet=&chain= is cached for 30s (s-maxage=30).