Compare commits
3 Commits
11c292eb90
...
b585f70d31
| Author | SHA1 | Date | |
|---|---|---|---|
| b585f70d31 | |||
| b7eb8ece97 | |||
| d27efd4b90 |
10
AGENTS.md
10
AGENTS.md
@ -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`.
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
847
index.html
847
index.html
File diff suppressed because it is too large
Load Diff
@ -26,7 +26,7 @@ const CHAIN_IDS = {
|
||||
avalanche: 43114,
|
||||
bsc: 56,
|
||||
fantom: 250,
|
||||
hyperliquide: 998,
|
||||
hyperevm: 999,
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
26
wallets.js
26
wallets.js
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user