Files
dione/AGENTS.md

154 lines
8.2 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# cbBTC Treasury Dashboard
**What it is:** A single-page HTML dashboard tracking cbBTC acquisition across wallets. No build step, no JS framework, no tests — just one `index.html` served by nginx.
## Remote Git
**Repository:** `https://git.kapuscinski.pl/p1otek/dione.git`
Credentials are stored in `~/.config/opencode/.env`:
```
GIT_USERNAME=p1otek
GIT_TOKEN=<PAT>
GIT_ADDRESS=https://git.kapuscinski.pl/
```
All git push/pull commands must use these credentials — do **not** hardcode the token in scripts or commands. Use the token via environment variable `GIT_TOKEN` or via the remote URL (`https://p1otek:${GIT_TOKEN}@git.kapuscinski.pl/p1otek/dione.git`).
## How to run
```bash
docker build -t cbBTC-dashboard . && docker run -p 8080:80 cbBTC-dashboard
```
Or just open `index.html` in a browser (without the `/api/` proxy, only the Kraken BTC price chart will work).
## Architecture
The codebase is split into three vanilla ESM modules plus the single-page dashboard:
| Module | Purpose |
|---|---|
| `wallets.js` | **`WalletManager`** — localStorage state, chain-specific address validation, verification lifecycle |
| `verifier.js` | **`WalletVerifier`** — EIP-712 typed-data signing via `window.ethereum`, user rejection handling |
| `stream.js` | **`WalletStreamManager`** — single WebSocket with BroadcastChannel leader election, exponential backoff + jitter |
## File layout
| File | Purpose |
|---|---|
| `index.html` | Dashboard UI. Embedded chart data, ApexCharts, TailwindCSS. Loads modules via `<script type="module">`. |
| `wallets.js` | `WalletManager` — anonymous localStorage wallet state, add/remove/verify/revoke |
| `verifier.js` | `WalletVerifier``window.ethereum` connection + EIP-712 signature flow |
| `stream.js` | `WalletStreamManager` — single WS, cross-tab leader election, event routing |
| `Dockerfile` | 4 lines. Copies `index.html` and `default.conf` into `nginx:alpine`. |
| `default.conf` | Reverse proxy: `/api/``http://192.168.1.102:8000/api/`. |
| `aave_portfolio.md` / `wallet_portfolio.md` | Reference data dumps. NOT consumed by the app. |
## Key facts for editing `index.html`
- **Data is embedded as JS constants** near the top of the `<script>` block:
- `walletCumulData` — time-series of cumulative BTC per wallet
- `dailySatsData` — rolling sats/day with price info
- `walletsMetadata` — wallet id → address, name, color, chain
- `walletCosts` / `walletBuys` — cost basis per wallet
- **API endpoints** (proxied through nginx):
- `GET /api/v1/prices/{symbol}/history?range=N` — price history
- `GET /api/v1/portfolio/{address}/{chain}/aave` — Aave portfolio snapshots (chain-parameterized)
- **BTC price** fetched directly from Kraken: `https://api.kraken.com/0/public/OHLC?pair=XBTUSD&interval=1440`
- **Polling:** `setInterval` every 30s with a 60s throttle on the Aave endpoint
- **Tracked wallet:** `0x0c1a4a060e119f981412e323104d1c134d413dba` ("penguin", Base)
- **Token decimals:** cbBTC=8, WETH=18, USDC=6
## Module API
Each module is loaded via `<script type="module">` in `index.html`:
```js
import { WalletManager } from './wallets.js';
import { WalletVerifier } from './verifier.js';
import { WalletStreamManager } from './stream.js';
const wm = new WalletManager();
wm.loadWallets();
const verifier = new WalletVerifier(wm);
const stream = new WalletStreamManager(wm);
stream.connect();
```
### WalletManager
| Method | Description |
|---|---|
| `loadWallets()` | Reads from localStorage, sanitizes — preserves `isVerified` only when `messageData` is present; btc/bitcoin/solana always kept; unverified EVM wallets discarded |
| `addWallet(address, chain, nickname)` | Validates format, appends, persists |
| `removeWallet(address, chain)` | Drops from state, persists |
| `verifyWallet(address, chain, signature, messageData)` | Sets `isVerified: true` |
| `revokeVerification(address, chain)` | Resets to unverified |
| `findWallet(address, chain)` | Lookup helper |
| `getWallets()` | Returns deep copy of state |
| `setWalletOrder(addresses)` | Reorders internal array to match address order, persists |
### WalletVerifier
| Method | Description |
|---|---|
| `triggerWalletVerification(chain, nickname)` | Full flow: eth_requestAccounts → EIP-712 sign → add + verify |
| `connect()` | Quick connect check (no signature) |
EIP-712 domain: `{ name: "Anonymous Wallet Tracker", version: "1", chainId }` where `chainId` is per-chain (ethereum=1, base=8453, arbitrum=42161, optimism=10, polygon=137, avalanche=43114, bsc=56, fantom=250, hyperevm=999; fallback 1).
### WalletStreamManager
| Method | Description |
|---|---|
| `connect()` | Triggers BroadcastChannel leader election → opens single WS |
| `disconnect()` | Closes WS + BC, cancels timers |
| `on(event, callback)` | Register callback per event type |
| `subscribeToNewWallet(wallet)` | Dynamic subscribe (leader → WS, follower → BC) |
| `unsubscribeFromWallet(address, chain)` | Dynamic unsubscribe |
Cross-tab leader election uses `BroadcastChannel("dione_shared_stream")` with a 200ms timeout. Leader-death broadcast triggers failover election. Reconnect uses exponential backoff (1s → 30s) with ±25% jitter.
## Gotchas
- `apiBase = window.location.origin` — the dashboard relies on nginx proxy to reach the backend at `192.168.1.102:8000`. Running without Docker/proxy means the Aave table and price charts fail silently.
- No linting, no type-checking, no CI. Validate changes by opening the HTML in a browser.
- Adding chart data? Update the embedded arrays (`walletCumulData`, etc.) — they are plain JS arrays of `[timestampMs, value]` pairs.
- `wallets.js` preserves `isVerified` on `loadWallets()` only when `messageData` is present — unverified EVM wallets are discarded on reload. btc/bitcoin/solana wallets are always kept. Verification must go through `WalletVerifier`.
## Transaction Ledger Table — Data Flow
How the table gets its rows and values:
1. **Fetch:** `fetchWalletAaveData(address, chain)` hits `/api/v1/portfolio/{address}/{chain}/aave` (chain-parameterized, defaults `base`), returns array of snapshot events.
2. **Deduplicate:** `snapshotsToDaily(events)` collapses events to one per day (keeps latest `block_timestamp` per day). **Result is sorted newest-first.**
3. **Store:** Deduplicated snapshots land in `addressSnapshots[address]`.
4. **Prices:** `fetchPrices(symbols, oldestDateMs)` fetches `/api/v1/prices/{symbol}/history?range=N` where N = days between now and oldest snapshot. **Critical:** you must scan all snapshots for the *lowest* `block_timestamp` to compute the correct range. `snapshots[0]` is the *newest* — never assume the array is oldest-first. The `aavePriceMap` is keyed `[priceSymbol][dateStr] = closePrice`.
5. **Render:** `renderCombinedTable()` iterates `addressSnapshots`, computes row values:
- `getTokenAmount(raw, symbol)` divides by token decimals (cbBTC /1e8, WETH /1e18, USDC /1e6)
- `priceForToken(symbol, dateStr)` looks up `aavePriceMap` for the matching date (falls back to nearest prior date)
- Each row shows USD = `amount × price` for wallet (cold), collateral, and debt columns
6. **Filter:** `updateDashboard()` hides/shows rows based on wallet-type and ledger-wallet checkbox filters.
If table rows show `$0` for non-recent dates, check that `aavePriceMap` covers the full historical range — the `fetchPrices()` call must use the *oldest* snapshot timestamp, not the newest.
## Coding Guidelines
You are an expert senior Web3 frontend engineer specializing in high-scale performance and stateless cryptographic security.
We are building the client-side code for an anonymous multi-wallet tracker. The application handles up to 100k concurrent connections, so code efficiency, memory management, and avoiding redundant network overhead are paramount.
1. **Modern TypeScript/JavaScript** — clean, modular, and well-commented.
2. **No bloated third-party UI frameworks** — stick to vanilla methods or standard state hooks.
3. **Production-ready error handling** — bulletproof handling for wallet rejections and network drops.
## After every `index.html` edit
Always rebuild and restart the Docker container so changes take effect:
```bash
docker build -t cbbtc-dashboard . && docker stop dione-dashboard && docker rm dione-dashboard && docker run -d --name dione-dashboard -p 8080:80 cbbtc-dashboard
```