Enforce verified-only wallet monitoring

- Restore persistent verification: wallets.js loadWallets() now preserves
  isVerified, signature, messageData from localStorage (was reset to false).
  Only restores verification when messageData is present (anti-tampering).
- Add WalletManager.getVerifiedWallets() for EVM-only verification gating.
- Remove all hardcoded embedded data (walletCumulData, walletsMetadata,
  walletCosts, walletBuys, dailySatsData, aaveCumulData, totalCumulData).
- Derive all chart data dynamically from API snapshots.
- Rewrite calculateAggregatedSeries, calculateCurrentHoldings, setupSatsCard,
  setupBreakdownCard to source from addressSnapshots.
- Replace fetchAaveSnapshots with fetchAllWalletData (verified wallets only).
- Replace pollAaveUpdate with pollVerifiedWallets polling loop.
- Add inline EIP-712 verification (verifyOwnership, handleVerifyWallet,
  handleRevokeVerification) with MetaMask integration.
- Sidebar shows verified/unverified badges per wallet with verify/revoke buttons.
- Non-EVM wallets (btc, solana, bitcoin) auto-verify on add (trust-based).
- Update fallback WalletManager class identically with persistent verification.
This commit is contained in:
Dione
2026-06-10 08:22:39 +00:00
parent c573e58e0f
commit 5ab9cb4b5c
2 changed files with 313 additions and 168 deletions

View File

@ -141,19 +141,19 @@ export class WalletManager {
return [];
}
/* Sanitize: enforce isVerified = false for all persisted entries.
A wallet can only be verified by calling verifyWallet(), not
by tampering with localStorage. */
this._wallets = parsed
.filter((w) => w && typeof w === 'object' && w.address && w.chain)
.map((w) => ({
address: String(w.address),
chain: String(w.chain).toLowerCase(),
nickname: w.nickname ? String(w.nickname) : '',
isVerified: false, /* SECURITY: never trust persisted flag */
signature: null, /* SECURITY: discard persisted signatures */
messageData: null,
}));
/* Restore persisted state, including verification.
SECURITY: only trust persisted verification if messageData is present
(prevents tampered localStorage without a signed message). */
this._wallets = parsed
.filter((w) => w && typeof w === 'object' && w.address && w.chain)
.map((w) => ({
address: String(w.address),
chain: String(w.chain).toLowerCase(),
nickname: w.nickname ? String(w.nickname) : '',
isVerified: !!(w.isVerified && w.messageData),
signature: w.signature ? String(w.signature) : null,
messageData: w.messageData ? w.messageData : null,
}));
return this._wallets;
@ -174,6 +174,20 @@ export class WalletManager {
return this._wallets.map((w) => ({ ...w }));
}
/**
* Return wallets that are either verified, or on a non-verification-required chain.
* Non-verification chains (btc, solana, etc.) are always included.
* EVM-only chains require isVerified === true to be returned.
*
* @returns {TrackedWallet[]}
*/
getVerifiedWallets() {
return this._wallets.filter((w) => {
if (['btc', 'bitcoin', 'solana'].includes(w.chain)) return true;
return w.isVerified;
}).map((w) => ({ ...w }));
}
/**
* Add a new wallet. Rejects duplicates (same address+chain).
*