Files
dione/AGENTS.md
Dione c573e58e0f feat: add neon color picker for wallet management
- Replace muted Tailwind palette with 16-color neon palette
- Add clickable color dot in sidebar that opens a popup swatch grid
- Allow color override for all wallets (including embedded)
- Fix getColorForWallet to respect localStorage override
- Fix duplicate renameWallet in wallets.js
- Update Dockerfile to serve ESM modules via nginx
2026-06-10 07:10:24 +00:00

123 lines
5.8 KiB
Markdown

# 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.
## 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}/base/aave` — Aave portfolio snapshots
- **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 (always `isVerified: false`) |
| `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 |
### 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: 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` always forces `isVerified: false` on `loadWallets()` — persisted signatures are discarded. Verification must go through `WalletVerifier`.
## 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
```