From 0ad8f03990cfe82aeaea43a436bbde58da67de06 Mon Sep 17 00:00:00 2001 From: Dione Date: Wed, 10 Jun 2026 18:52:03 +0000 Subject: [PATCH] fix: 30-day rolling average for Avg BTC acquired/day chart with continuous daily timeline --- index.html | 148 ++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 124 insertions(+), 24 deletions(-) diff --git a/index.html b/index.html index 3aff9dc..dc92f78 100644 --- a/index.html +++ b/index.html @@ -47,6 +47,11 @@ window.WalletManager = WalletManager; .wallet-filter-toggle { appearance: none; width: 12px; height: 12px; border-radius: 2px; border: 1px solid rgba(255,255,255,0.2); cursor: pointer; position: relative; transition: all 0.2s; } .wallet-filter-toggle:checked { background: var(--wallet-color); border-color: transparent; box-shadow: 0 0 8px color-mix(in srgb, var(--wallet-color) 40%, transparent); } + .platform-checkbox { appearance: none; width: 14px; height: 14px; border-radius: 3px; border: 1.5px solid rgba(255,255,255,0.15); cursor: pointer; position: relative; transition: all 0.2s; flex-shrink: 0; } + .platform-checkbox:checked { border-color: transparent; } + .platform-checkbox.platform-aave:checked { background: #9896FF; box-shadow: 0 0 10px rgba(152, 150, 255, 0.5); } + .platform-checkbox.platform-hyperlend:checked { background: #caeae5; box-shadow: 0 0 10px rgba(202, 234, 229, 0.5); } + .wallet-color-dot { width: 16px; height: 16px; border-radius: 50%; cursor: pointer; flex-shrink: 0; transition: transform 0.15s, box-shadow 0.15s; @@ -143,6 +148,10 @@ window.WalletManager = WalletManager; +
+ +
+
@@ -624,6 +633,8 @@ function handleRenameNickname(input) { Add Wallet Modal =================================================================== */ let _addWalletSelectedColor = null; +let _addWalletSelectedPlatform = 'aave'; +const HYPERLIQUIDE_CHAINS = ['hyperliquide']; function openAddWalletModal() { if (!window.ethereum) { @@ -640,13 +651,17 @@ function openAddWalletModal() { document.getElementById('input-chain-display').value = ''; document.getElementById('input-chain-hidden').value = 'base'; _addWalletSelectedColor = autoPickColor(); + _addWalletSelectedPlatform = 'aave'; renderAddWalletColorPicker(); window.ethereum.request({ method: 'eth_chainId' }) - .then(chainIdHex => { - const chainName = CHAIN_IDS_REVERSED[chainIdHex] || 'base'; - document.getElementById('input-chain-display').value = chainName.charAt(0).toUpperCase() + chainName.slice(1); - document.getElementById('input-chain-hidden').value = chainName; + .then(rawChainId => { + const chainIdDecimal = parseInt(rawChainId, 16).toString(); + const chainName = CHAIN_IDS_REVERSED[chainIdDecimal]; + const displayText = chainName ? chainName.charAt(0).toUpperCase() + chainName.slice(1) : `Unknown (${chainIdDecimal})`; + document.getElementById('input-chain-display').value = displayText; + document.getElementById('input-chain-hidden').value = chainName || 'unknown'; + renderLendingPlatformSelector(chainName || null); }); document.getElementById('add-wallet-error').classList.add('hidden'); @@ -682,8 +697,47 @@ function renderAddWalletColorPicker() { }); } -/* Reverse map: chainId → chain name */ -const CHAIN_IDS_REVERSED = { '1': 'ethereum', '8453': 'base', '42161': 'arbitrum', '10': 'optimism', '137': 'polygon', '43114': 'avalanche', '56': 'bsc', '250': 'fantom', '998': 'hyperliquide' }; +function renderLendingPlatformSelector(chainName) { + const container = document.getElementById('lending-platform-selector'); + container.innerHTML = ''; + + const isHyperliquide = HYPERLIQUIDE_CHAINS.includes(chainName); + + if (isHyperliquide) { + const platform = 'hyperlend'; + const logoUrl = 'https://app.hyperlend.finance/assets/header-logo-CiRKYBzy.svg'; + const color = '#caeae5'; + const label = 'HyperLend'; + const cbClass = 'platform-hyperlend'; + + const item = document.createElement('label'); + item.className = 'flex items-center gap-3 bg-[#05070B] border border-[#1A1F2C] rounded-lg px-3 py-2 cursor-pointer transition'; + item.innerHTML = + '' + + '' + label + '' + + '' + label + ''; + container.appendChild(item); + _addWalletSelectedPlatform = 'hyperlend'; + } else { + const platform = 'aave'; + const logoUrl = 'https://app.aave.com/aave-com-logo-header.svg'; + const color = '#9896FF'; + const label = 'Aave'; + const cbClass = 'platform-aave'; + + const item = document.createElement('label'); + item.className = 'flex items-center gap-3 bg-[#05070B] border border-[#1A1F2C] rounded-lg px-3 py-2 cursor-pointer transition'; + item.innerHTML = + '' + + '' + label + '' + + '' + label + ''; + container.appendChild(item); + _addWalletSelectedPlatform = 'aave'; + } +} + +/* Reverse map: chainId (decimal) → chain name */ +const CHAIN_IDS_REVERSED = { '1': 'ethereum', '8453': 'base', '42161': 'arbitrum', '10': 'optimism', '137': 'polygon', '43114': 'avalanche', '56': 'bsc', '250': 'fantom', '999': 'hyperliquide' }; function fetchWalletAddress() { if (!window.ethereum) { @@ -699,14 +753,18 @@ function fetchWalletAddress() { /* Auto-detect chain from wallet */ window.ethereum.request({ method: 'eth_chainId' }) - .then(chainIdHex => { - const chainName = CHAIN_IDS_REVERSED[chainIdHex] || 'base'; - document.getElementById('input-chain-display').value = chainName.charAt(0).toUpperCase() + chainName.slice(1); - document.getElementById('input-chain-hidden').value = chainName; + .then(rawChainId => { + const chainIdDecimal = parseInt(rawChainId, 16).toString(); + const chainName = CHAIN_IDS_REVERSED[chainIdDecimal]; + const displayText = chainName ? chainName.charAt(0).toUpperCase() + chainName.slice(1) : `Unknown (${chainIdDecimal})`; + document.getElementById('input-chain-display').value = displayText; + document.getElementById('input-chain-hidden').value = chainName || 'unknown'; + renderLendingPlatformSelector(chainName || null); }); /* Auto-assign a color not used by existing wallets */ _addWalletSelectedColor = autoPickColor(); + _addWalletSelectedPlatform = 'aave'; renderAddWalletColorPicker(); } }) @@ -725,6 +783,7 @@ async function handleAddWallet(e) { let address = document.getElementById('input-address').value.trim().toLowerCase(); let chain = document.getElementById('input-chain-hidden').value || 'base'; const nickname = document.getElementById('input-nickname').value.trim(); + const lendingPlatform = _addWalletSelectedPlatform || 'aave'; const errEl = document.getElementById('add-wallet-error'); /* If no address, connect wallet first */ @@ -746,9 +805,12 @@ async function handleAddWallet(e) { /* Auto-detect chain */ const chainIdHex = await window.ethereum.request({ method: 'eth_chainId' }); - chain = CHAIN_IDS_REVERSED[chainIdHex] || 'base'; + const chainIdDecimal = parseInt(chainIdHex, 16).toString(); + const resolvedChain = CHAIN_IDS_REVERSED[chainIdDecimal]; + chain = resolvedChain || 'unknown'; document.getElementById('input-chain-hidden').value = chain; - document.getElementById('input-chain-display').value = chain.charAt(0).toUpperCase() + chain.slice(1); + document.getElementById('input-chain-display').value = resolvedChain ? resolvedChain.charAt(0).toUpperCase() + resolvedChain.slice(1) : `Unknown (${chainIdDecimal})`; + renderLendingPlatformSelector(resolvedChain || null); _addWalletSelectedColor = autoPickColor(); renderAddWalletColorPicker(); @@ -772,7 +834,7 @@ async function handleAddWallet(e) { /* Auto-verify non-EVM wallets */ if (!isEVM) { const autoChain = isSolana ? 'solana' : 'btc'; - const addResult = wm.addWallet(address, autoChain, nickname); + const addResult = wm.addWallet(address, autoChain, nickname, lendingPlatform); if (!addResult.success) { errEl.textContent = addResult.error; errEl.classList.remove('hidden'); return false; } wm.verifyWallet(address, autoChain, '', { action: 'Auto-verified (non-verification chain)', walletAddress: address }); walletSyncState[address] = 'synced'; @@ -783,7 +845,7 @@ async function handleAddWallet(e) { } /* Register EVM wallet first so verifyOwnership can find it */ - const addResult = wm.addWallet(address, chain, nickname); + const addResult = wm.addWallet(address, chain, nickname, lendingPlatform); if (!addResult.success) { errEl.textContent = addResult.error; errEl.classList.remove('hidden'); return false; } /* One-shot: verify or remove */ @@ -811,7 +873,7 @@ async function handleAddWallet(e) { EIP-712 Verification =================================================================== */ /* Chain name → numeric chainId for EIP-712 domain */ -const CHAIN_IDS = { ethereum: 1, base: 8453, arbitrum: 42161, optimism: 10, polygon: 137, avalanche: 43114, bsc: 56, fantom: 250, hyperliquide: 998 }; +const CHAIN_IDS = { ethereum: 1, base: 8453, arbitrum: 42161, optimism: 10, polygon: 137, avalanche: 43114, bsc: 56, fantom: 250, hyperliquide: 999 }; const VERIFY_TYPES = { VerifyTracking: [ { name: 'action', type: 'string' }, @@ -903,6 +965,15 @@ function handleDeleteWallet(address, chain, idx) { /* =================================================================== Wallet Pills (main dashboard selection) =================================================================== */ +function getPlatformBadge(platform) { + if (!platform) return ''; + const p = (platform || 'aave').toLowerCase(); + if (p === 'hyperlend') { + return ''; + } + return ''; +} + function renderWalletPills() { const wallets = wm.getWallets(); const container = document.getElementById('wallet-pills'); @@ -920,10 +991,11 @@ function renderWalletPills() { : ''; const nick = w.nickname || 'Wallet'; const isActive = isWalletInLedger(w.address); + const platformBadge = getPlatformBadge(w.lendingPlatform); html += ''; @@ -1498,22 +1570,50 @@ function setupCumulCard(cutoff) { } function setupSatsCard(cutoff) { - /* Derive daily BTC from all verified wallet snapshots */ - const dailyBtc = {}; + const DAY_MS = 86400000; + /* Derive daily balances (latest snapshot per day) */ + const dailyBalances = {}; const selectedAddr = getSelectedAddresses() || []; selectedAddr.forEach(addr => { const snaps = addressSnapshots[addr] || []; snaps.forEach(snap => { - const ts = new Date(snap.block_timestamp).getTime(); const dateStr = snap.block_timestamp.slice(0, 10); const btcVal = getTokenAmount(snap?.wallet?.cbBTC, 'cbBTC') + getTokenAmount(snap?.collateral?.cbBTC, 'cbBTC'); - if (!dailyBtc[ts]) dailyBtc[ts] = { ts, btcVal: 0, dateStr }; - dailyBtc[ts].btcVal += btcVal; + const ts = new Date(snap.block_timestamp).getTime(); + if (!dailyBalances[dateStr] || ts > dailyBalances[dateStr].ts) { + dailyBalances[dateStr] = { ts, dateStr, btcVal }; + } else { + dailyBalances[dateStr].btcVal += btcVal; + } }); }); - const dailyEntries = Object.values(dailyBtc).sort((a, b) => a.ts - b.ts); - const filtered = dailyEntries.filter(d => d.ts >= cutoff); - const rollingAvg = filtered.map(d => [d.ts, Math.round(d.btcVal * 1e8)]); + + /* Sort by date, compute daily delta (acquisition), then fill continuous timeline */ + const sortedDays = Object.values(dailyBalances).sort((a, b) => a.dateStr.localeCompare(b.dateStr)); + const dailyDeltas = {}; + sortedDays.forEach((d, i) => { + const prevBalance = i > 0 ? sortedDays[i - 1].btcVal : 0; + dailyDeltas[d.dateStr] = d.btcVal - prevBalance; + }); + + /* Fill continuous daily timeline so no days are skipped */ + const firstDate = new Date(sortedDays[0].dateStr).getTime(); + const lastDate = new Date(sortedDays[sortedDays.length - 1].dateStr).getTime(); + const continuousDays = []; + for (let t = firstDate; t <= lastDate; t += DAY_MS) { + const ds = new Date(t).toISOString().slice(0, 10); + continuousDays.push({ ts: t, dateStr: ds, delta: dailyDeltas[ds] || 0 }); + } + + /* 30-day rolling average of daily deltas */ + const filtered = continuousDays.filter(d => d.ts >= cutoff); + const rollingAvg = []; + for (let i = 0; i < filtered.length; i++) { + const window = filtered.slice(Math.max(0, i - 29), i + 1); + const avgSats = Math.round(window.reduce((s, d) => s + d.delta, 0) / window.length * 1e8); + rollingAvg.push([filtered[i].ts, avgSats]); + } + const latestAvg = rollingAvg.length > 0 ? rollingAvg[rollingAvg.length - 1][1] : 0; document.getElementById('avg-sats-val').innerText = new Intl.NumberFormat('en-US').format(Math.round(latestAvg)); const options = {