# DeFi LP True PnL Scanner

全鏈 EVM **LP true PnL** 掃描器（Cloudflare Pages）。按交易重構每條 LP：淨本金、現值、無常損失、不算獎勵盈虧、已領+待領獎勵、總盈虧、年化。缺數顯示「—」，唔發明。

Live: https://defi-lp-pnl-scanner.pages.dev

## UX

- 貼 `0x…` → **掃描**；鏈 chip：全部／ETH／Base／Arb／BSC／OP／HyperEVM／Robinhood／Arc
- 進度階段：發現鏈 → 盤點 LP NFT → 現值 → 歷史入金 → 獎勵 → 完成
- 結果表欄：Position｜淨本金｜現值｜無常損失｜不計獎勵的 LP 盈虧｜獎勵（已領 + 待領）｜總盈虧｜總收益年化
- **Demo 模式**：種子 `0x5c0258e935813C4210a09736eB995aC428545Dc4`
- **Live**：`POST /api/scan` 分 phase（discover → inventory → value → history → rewards）

## Phase A vs B

| Phase | 內容 | 需要 |
|-------|------|------|
| **A** | 公開 RPC `balanceOf` 盤點；Demo 種子現值 | 無 key |
| **B** | Alchemy RPC／getLogs + 可選 Etherscan V2 `tokennfttx` → 逐 NFT 現值、淨本金、IL、年化 | `ALCHEMY_API_KEY`（主）、`ETHERSCAN_API_KEY`（可選加速） |

Secrets（Pages → Production；永不回傳）：

| Name | 用途 |
|------|------|
| `ALCHEMY_API_KEY` | `eth/base/arb/bnb/opt` + 可選 `robinhood-mainnet`／`arc-mainnet` RPC + getLogs |
| `ETHERSCAN_API_KEY` | Etherscan API **V2**（`chainid` 含 BSC）tokennfttx／txlist |

`GET /api/keys-status` 只回報有／無。

## API

```bash
curl -s https://defi-lp-pnl-scanner.pages.dev/api/scan \
  -H 'content-type: application/json' \
  -d '{"address":"0x5c02…","phase":"discover","chains":["bsc"]}'
```

Phases: `discover` | `inventory` | `value` | `history` | `rewards` | `full`

## Deploy

```bash
cd /workspace/defi-lp-pnl-scanner
npx wrangler pages deploy . --project-name=defi-lp-pnl-scanner --commit-dirty=true
# secrets:
#   echo -n "$KEY" | npx wrangler pages secret put ALCHEMY_API_KEY --project-name=defi-lp-pnl-scanner
#   echo -n "$KEY" | npx wrangler pages secret put ETHERSCAN_API_KEY --project-name=defi-lp-pnl-scanner
```

## 已知限制

1. Active chains only：ETH／Base／Arb／BSC／OP／HyperEVM／Robinhood／Arc（無 Avalanche／Polygon）
2. Alchemy `bnb-mainnet` 若 403／530 → BSC `eth_call` 靠公開 RPC；**getLogs 無法做** → 淨本金／無常損失維持「—」
3. Etherscan V2／BscScan 可能被 CF 擋（回 HTML）→ NFT 列表退回 `tokenOfOwnerByIndex`
4. 已平倉大量 NFT 預設彙總；外加 Merkl／NEST claimed 未全自動
5. HyperEVM 無 Alchemy；Nest NPM 未配置則跳過

NFA — 唔構成投資建議。

## UniV4 PositionManager

Robinhood／Arc 的 UniV4 POSM 是 ERC-721（`UNI-V4-POSM`），但**不是** ERC721Enumerable：
`tokenOfOwnerByIndex` 會 revert。盤點靠 Alchemy `getAssetTransfers` + `getPoolAndPositionInfo`／`getPositionLiquidity`。

## Robinhood Chain RPC

Pages Functions prefer Alchemy `robinhood-mainnet` when `ALCHEMY_API_KEY` is set.
If the Alchemy app has not enabled **ROBINHOOD_MAINNET**, enable it at
[Alchemy Networks](https://dashboard.alchemy.com/) (error text links the app).

Fallback public RPCs (non-CF-first): publicnode, thirdweb, GlobalStake, Pocket, etc.
Official `rpc.mainnet.chain.robinhood.com` is last because Cloudflare Pages egress to
CF-fronted RH was previously unreliable.

- Probe: `GET /api/rpc-probe?chain=robinhood`
- Browser Phase A uses `POST /api/rpc` for Robinhood (no direct browser→RH public RPC).


## Phase B history (true PnL)

Alchemy free tier caps `eth_getLogs` at **10 blocks**, so we do **not** walk wide log ranges.

Instead, for each UniV3/PCS V3 NFT:

1. `alchemy_getAssetTransfers` ERC-721 mints → `eth_getTransactionReceipt` (IncreaseLiquidity even via router)
2. Owner → NPM `external` txs → receipts (DecreaseLiquidity / Collect)
3. Llama historical prices at deposit timestamps for **淨本金**; spot for **現值** / **無常損失** (HODL of original deposits vs LP value)
4. Etherscan V2 free plan does **not** cover BSC; BNB history relies on Alchemy receipts

UI labels: Traditional Chinese (香港書面語). Active chains only (no Avax/Polygon).

## Fables (Robinhood UniV4 hooks)

Fables LP positions are **not** UniV4 PositionManager NFTs. They are hook `range_id` shares
read via Fables Lens `userRanges` + Envio indexer (`fables.fi/api/indexer`). Venue column/tag shows **Fables**.
