Compare commits

...

3 Commits

Author SHA1 Message Date
b585f70d31 feat: import wallet support, HYPE tracking, bug fixes
- Fix pollVerifiedWallets: route import wallets to /wallet endpoint and reconstruct running-balance ledger
- Fix generateRandomAddress: append 'IMPORT' suffix to prevent fake EVM addresses
- Add import chain: CSV upload via multipart, IMPORT_ONLY wallet registration
- Add HYPE summary cards with holdings, PnL, avg buy price, total invested
- Add BTC and SATS to TOKENS config
- Add backend DELETE on wallet removal (skip for import wallets)
- Fix chain name: hyperliquide → hyperevm (chainId 999)
- Add HSTS and security headers to nginx config
2026-06-17 20:34:54 +00:00
b7eb8ece97 docs: reconcile AGENTS.md with actual code (EIP-712 chainId, loadWallets verification, Aave endpoint) 2026-06-15 16:40:58 +00:00
d27efd4b90 Fix wallet pill filters and drag-and-drop for multi-chain wallets
- Fix ReferenceError: _dragAddr → _dragKey in pill checkbox onchange handler,
  restoring the ability to toggle wallet filters in the nav-bar
- Replace toggleWalletPill with setWalletChecked(key, this.checked) so pill
  checkboxes directly update filter state, sync all checkboxes, and refresh
  charts (matching the ledger-header checkbox behavior)
- Drag-and-drop reorder: switch from plain addresses to compound keys
  ("addr:chain") in dropWalletReorder and setWalletOrder, fixing clones
  when dragging wallets that share an address on different chains
- Update inline fallback WalletManager with the same compound-key logic
2026-06-15 06:27:57 +00:00
5 changed files with 548 additions and 344 deletions

View File

@ -55,7 +55,7 @@ The codebase is split into three vanilla ESM modules plus the single-page dashbo
- `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
- `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)
@ -82,7 +82,7 @@ stream.connect();
| Method | Description |
|---|---|
| `loadWallets()` | Reads from localStorage, sanitizes (always `isVerified: false`) |
| `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` |
@ -98,7 +98,7 @@ stream.connect();
| `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 }`
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
@ -117,13 +117,13 @@ Cross-tab leader election uses `BroadcastChannel("dione_shared_stream")` with a
- `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`.
- `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:** `fetchAllWalletData()``fetchWalletAaveData(address)` hits `/api/v1/portfolio/{address}/base/aave`, returns array of snapshot events.
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`.

View File

@ -1,12 +1,19 @@
server {
listen 80;
add_header Strict-Transport-Security "max-age=31536000" always;
add_header X-Content-Type-Options "nosniff" always;
location / {
root /usr/share/nginx/html;
index index.html;
}
location /api/ {
proxy_pass http://192.168.1.102:8000/api/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -26,7 +26,7 @@ const CHAIN_IDS = {
avalanche: 43114,
bsc: 56,
fantom: 250,
hyperliquide: 998,
hyperevm: 999,
};
/**

View File

@ -39,7 +39,7 @@ const LS_KEY = 'tracked_wallets';
const VALID_CHAINS = new Set([
'base', 'ethereum', 'bitcoin', 'arbitrum', 'optimism',
'polygon', 'solana', 'avalanche', 'bsc', 'fantom', 'btc',
'hyperliquide',
'hyperevm', 'import',
]);
/* Regex patterns for address validation per chain */
@ -53,7 +53,7 @@ const ADDRESS_PATTERNS = {
avalanche: /^(0x)?[0-9a-fA-F]{40}$/i,
bsc: /^(0x)?[0-9a-fA-F]{40}$/i,
fantom: /^(0x)?[0-9a-fA-F]{40}$/i,
hyperliquide: /^(0x)?[0-9a-fA-F]{40}$/i,
hyperevm: /^(0x)?[0-9a-fA-F]{40}$/i,
/* Bitcoin — bech32 or legacy pubkey hash */
bitcoin: /^(bc1[a-z0-9]{25,39}|1[a-km-zA-HJ-NP-Z1-9]{25,34})$/,
@ -61,6 +61,9 @@ const ADDRESS_PATTERNS = {
/* Solana — base58, 32-44 chars */
solana: /^[1-9A-HJ-NP-Za-km-z]{32,44}$/,
/* Import — accepts any address format (EVM, BTC, Solana, etc.) */
import: /^(0x[0-9a-fA-F]{40}|bc1[a-z0-9]{25,62}|[13][a-km-zA-HJ-NP-Z1-9]{25,34}|[1-9A-HJ-NP-Za-km-z]{32,44})$/,
};
/* ------------------------------------------------------------------ */
@ -363,17 +366,18 @@ export class WalletManager {
}
/**
* Reorder wallets to match the given address sequence.
* Addresses listed first are moved to front; remaining keep their original relative order.
* Only recognized addresses are kept in the new order.
* @param {string[]} addresses
* Reorder wallets to match the given compound-key sequence ("addr:chain").
* Keys listed first are moved to front; remaining keep their original relative order.
* Also accepts plain addresses for backward compatibility.
* @param {string[]} keys
*/
setWalletOrder(addresses) {
const addrSet = this._wallets.map(w => w.address);
setWalletOrder(keys) {
const keySet = this._wallets.map(w => `${w.address}:${w.chain}`);
const ordered = [];
for (const addr of addresses) {
if (addrSet.includes(addr)) {
const w = this._wallets.find(x => x.address === addr);
for (const k of keys) {
if (keySet.includes(k)) {
const [addr, chain] = k.split(':');
const w = this._wallets.find(x => x.address === addr && x.chain === chain);
if (w) ordered.push(w);
}
}