fix(rwallet): populate yield APY stats, hide top tabs on yield view

- Fix DeFi Llama field mapping: use apyMean30d + apyPct7D (apyMean7d
  doesn't exist in their API)
- Add apyBase, apy30d fields to YieldOpportunity type
- Deduplicate rates table (best APY per protocol+chain+asset)
- Hide "My Wallets / Wallet Visualizer" top tab bar on yield page
- Color-code APY values, better TVL formatting (B/M)
- Bump JS cache to v=11

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Jeff Emmett 2026-03-15 12:41:11 -07:00
parent f9d4164f28
commit fdf2a429f5
4 changed files with 32 additions and 12 deletions

View File

@ -90,7 +90,9 @@ interface YieldRate {
assetAddress: string; assetAddress: string;
vaultAddress: string; vaultAddress: string;
apy: number; apy: number;
apyBase?: number;
apy7d?: number; apy7d?: number;
apy30d?: number;
tvl?: number; tvl?: number;
vaultName?: string; vaultName?: string;
} }
@ -2043,6 +2045,15 @@ class FolkWalletViewer extends HTMLElement {
// ── Rates table ── // ── Rates table ──
if (this.yieldRates.length > 0) { if (this.yieldRates.length > 0) {
// Deduplicate: keep highest-APY entry per protocol+chain+asset
const deduped = new Map<string, YieldRate>();
const sorted = [...this.yieldRates].sort((a, b) => b.apy - a.apy);
for (const r of sorted) {
const key = `${r.protocol}:${r.chainId}:${r.asset}`;
if (!deduped.has(key)) deduped.set(key, r);
}
const rates = [...deduped.values()];
html += `<div class="yield-section"> html += `<div class="yield-section">
<h3 class="yield-section-title">Available Rates</h3> <h3 class="yield-section-title">Available Rates</h3>
<table class="balance-table"> <table class="balance-table">
@ -2051,20 +2062,21 @@ class FolkWalletViewer extends HTMLElement {
<th>Chain</th> <th>Chain</th>
<th>Asset</th> <th>Asset</th>
<th class="amount-cell">APY</th> <th class="amount-cell">APY</th>
<th class="amount-cell">7d Avg</th> <th class="amount-cell">30d Avg</th>
<th class="amount-cell">TVL</th> <th class="amount-cell">TVL</th>
</tr></thead> </tr></thead>
<tbody>`; <tbody>`;
const sorted = [...this.yieldRates].sort((a, b) => b.apy - a.apy); for (const r of rates) {
for (const r of sorted) {
const protocolLabel = r.protocol === "aave-v3" ? "Aave V3" : (r.vaultName || "Morpho"); const protocolLabel = r.protocol === "aave-v3" ? "Aave V3" : (r.vaultName || "Morpho");
const chainColor = r.chainId === "1" ? "#627eea" : "#0052ff";
const apyColor = r.apy >= 3 ? "var(--rs-success)" : r.apy >= 1.5 ? "#ffa726" : "var(--rs-text-secondary)";
html += `<tr> html += `<tr>
<td><span class="yield-protocol-badge ${r.protocol}">${protocolLabel}</span></td> <td><span class="yield-protocol-badge ${r.protocol}">${protocolLabel}</span></td>
<td>${chainNames[r.chainId] || r.chainId}</td> <td><span class="yield-chain-badge" style="background:${chainColor}22;color:${chainColor}">${chainNames[r.chainId] || r.chainId}</span></td>
<td><span class="token-symbol">${this.esc(r.asset)}</span></td> <td><span class="token-symbol">${this.esc(r.asset)}</span></td>
<td class="amount-cell" style="color:var(--rs-success);font-weight:600">${r.apy.toFixed(2)}%</td> <td class="amount-cell" style="color:${apyColor};font-weight:700">${r.apy.toFixed(2)}%</td>
<td class="amount-cell">${r.apy7d ? r.apy7d.toFixed(2) + "%" : "-"}</td> <td class="amount-cell">${r.apy30d != null ? r.apy30d.toFixed(2) + "%" : "-"}</td>
<td class="amount-cell">${r.tvl ? "$" + (r.tvl / 1e6).toFixed(1) + "M" : "-"}</td> <td class="amount-cell">${r.tvl ? "$" + (r.tvl >= 1e9 ? (r.tvl / 1e9).toFixed(1) + "B" : (r.tvl / 1e6).toFixed(0) + "M") : "-"}</td>
</tr>`; </tr>`;
} }
html += `</tbody></table></div>`; html += `</tbody></table></div>`;
@ -2329,11 +2341,12 @@ class FolkWalletViewer extends HTMLElement {
private render() { private render() {
const isMyWallets = this.topTab === "my-wallets" && this.isAuthenticated; const isMyWallets = this.topTab === "my-wallets" && this.isAuthenticated;
const isYield = this.activeView === "yield";
this.shadow.innerHTML = ` this.shadow.innerHTML = `
${this.renderStyles()} ${this.renderStyles()}
${this.isAuthenticated ? this.renderTopTabBar() : ''} ${this.isAuthenticated && !isYield ? this.renderTopTabBar() : ''}
${isMyWallets ? this.renderMyWalletsTab() : this.renderVisualizerTab()} ${isMyWallets && !isYield ? this.renderMyWalletsTab() : this.renderVisualizerTab()}
`; `;
// Top tab listeners // Top tab listeners

View File

@ -16,6 +16,8 @@ export interface YieldOpportunity {
vaultAddress: string; // aToken for Aave, vault for Morpho vaultAddress: string; // aToken for Aave, vault for Morpho
apy: number; apy: number;
apy7d?: number; apy7d?: number;
apy30d?: number;
apyBase?: number;
tvl?: number; tvl?: number;
poolId?: string; // DeFi Llama pool ID poolId?: string; // DeFi Llama pool ID
vaultName?: string; vaultName?: string;

View File

@ -63,7 +63,10 @@ interface LlamaPool {
symbol: string; symbol: string;
tvlUsd: number; tvlUsd: number;
apy: number; apy: number;
apyMean7d?: number; apyBase?: number;
apyReward?: number | null;
apyPct7D?: number;
apyMean30d?: number;
underlyingTokens?: string[]; underlyingTokens?: string[];
} }
@ -119,7 +122,9 @@ async function fetchDefiLlamaRates(): Promise<YieldOpportunity[]> {
assetAddress, assetAddress,
vaultAddress, vaultAddress,
apy: pool.apy || 0, apy: pool.apy || 0,
apy7d: pool.apyMean7d, apyBase: pool.apyBase ?? undefined,
apy7d: pool.apyPct7D != null ? pool.apy + pool.apyPct7D : undefined,
apy30d: pool.apyMean30d ?? undefined,
tvl: pool.tvlUsd, tvl: pool.tvlUsd,
poolId: pool.pool, poolId: pool.pool,
vaultName: protocol === "morpho-blue" vaultName: protocol === "morpho-blue"

View File

@ -1014,7 +1014,7 @@ function renderWallet(spaceSlug: string, initialView?: string) {
modules: getModuleInfoList(), modules: getModuleInfoList(),
theme: "dark", theme: "dark",
body: `<folk-wallet-viewer${viewAttr}></folk-wallet-viewer>`, body: `<folk-wallet-viewer${viewAttr}></folk-wallet-viewer>`,
scripts: `<script type="module" src="/modules/rwallet/folk-wallet-viewer.js?v=10"></script>`, scripts: `<script type="module" src="/modules/rwallet/folk-wallet-viewer.js?v=11"></script>`,
styles: `<link rel="stylesheet" href="/modules/rwallet/wallet.css">`, styles: `<link rel="stylesheet" href="/modules/rwallet/wallet.css">`,
}); });
} }