feat(rwallet): show EncryptID identity card with both CRDT and EVM wallets
Every EncryptID identity has two wallets: a CRDT wallet (DID-based for local tokens) and an EVM wallet (passkey-derived for on-chain tokens). The My Wallets tab now always shows both in a dedicated EncryptID card with distinct sections, rather than burying CRDT tokens as a subsection that only appears when balances are non-empty. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
a4a4175e9f
commit
8af4309b41
|
|
@ -101,6 +101,7 @@ class FolkWalletViewer extends HTMLElement {
|
|||
// Linked wallets state
|
||||
private isAuthenticated = false;
|
||||
private passKeyEOA = "";
|
||||
private userDID = "";
|
||||
private linkedWallets: LinkedWallet[] = [];
|
||||
private showProviderPicker = false;
|
||||
private discoveredProviders: DiscoveredProvider[] = [];
|
||||
|
|
@ -193,6 +194,7 @@ class FolkWalletViewer extends HTMLElement {
|
|||
this.isAuthenticated = true;
|
||||
this.topTab = "my-wallets";
|
||||
this.passKeyEOA = parsed.claims?.eid?.walletAddress || "";
|
||||
this.userDID = parsed.claims?.did || "";
|
||||
this.loadLinkedWallets().then(() => this.loadMyWalletBalances());
|
||||
this.loadCRDTBalances();
|
||||
}
|
||||
|
|
@ -1235,6 +1237,24 @@ class FolkWalletViewer extends HTMLElement {
|
|||
font-size: 13px; color: var(--rs-text-primary);
|
||||
}
|
||||
|
||||
/* ── EncryptID identity card ── */
|
||||
.encryptid-card { border-color: rgba(168,85,247,0.25); }
|
||||
.encryptid-wallets { display: flex; flex-direction: column; gap: 12px; }
|
||||
.encryptid-wallet-section {
|
||||
padding: 10px 12px; border-radius: 8px;
|
||||
background: var(--rs-bg-hover); border: 1px solid var(--rs-border-subtle);
|
||||
}
|
||||
.encryptid-wallet-label {
|
||||
display: flex; align-items: center; gap: 6px;
|
||||
font-size: 12px; font-weight: 600; text-transform: uppercase;
|
||||
letter-spacing: 0.5px; color: var(--rs-text-secondary); margin-bottom: 8px;
|
||||
}
|
||||
.wallet-type-dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; }
|
||||
.wallet-type-addr {
|
||||
font-family: monospace; font-weight: 400; font-size: 11px;
|
||||
color: var(--rs-text-muted); margin-left: auto; text-transform: none; letter-spacing: 0;
|
||||
}
|
||||
|
||||
/* ── Aggregate stats ── */
|
||||
.aggregate-stats {
|
||||
display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
|
||||
|
|
@ -1421,20 +1441,15 @@ class FolkWalletViewer extends HTMLElement {
|
|||
|
||||
let html = '<div class="my-wallets-grid">';
|
||||
|
||||
// EncryptID wallet card
|
||||
if (this.passKeyEOA) {
|
||||
html += this.renderWalletCard(this.passKeyEOA, "EncryptID", "encryptid", true);
|
||||
}
|
||||
// EncryptID identity card — always shown for authenticated users
|
||||
// Every EncryptID identity has both a CRDT wallet (DID) and an EVM wallet (passkey-derived)
|
||||
html += this.renderEncryptIdCard();
|
||||
|
||||
// Linked wallet cards
|
||||
// Linked wallet cards (external wallets)
|
||||
for (const w of this.linkedWallets) {
|
||||
html += this.renderWalletCard(w.address, w.providerName || w.type.toUpperCase(), w.type, false, w.id);
|
||||
}
|
||||
|
||||
if (!this.passKeyEOA && this.linkedWallets.length === 0) {
|
||||
html += '<div style="text-align:center;color:var(--rs-text-muted);padding:24px;font-size:13px">No wallets linked yet. Link a browser wallet to get started.</div>';
|
||||
}
|
||||
|
||||
html += '</div>';
|
||||
|
||||
// Aggregate total
|
||||
|
|
@ -1450,7 +1465,104 @@ class FolkWalletViewer extends HTMLElement {
|
|||
return html;
|
||||
}
|
||||
|
||||
private renderWalletCard(address: string, label: string, badgeClass: string, isEncryptId: boolean, walletId?: string): string {
|
||||
private renderEncryptIdCard(): string {
|
||||
// ── CRDT Wallet (DID) ──
|
||||
const didShort = this.userDID
|
||||
? `${this.userDID.slice(0, 16)}...${this.userDID.slice(-6)}`
|
||||
: "loading...";
|
||||
|
||||
let crdtRows = "";
|
||||
if (this.crdtLoading) {
|
||||
crdtRows = `<div style="padding:8px;color:var(--rs-text-muted);font-size:12px"><span class="spinner" style="width:12px;height:12px;border-width:2px;display:inline-block;vertical-align:middle;margin-right:6px"></span> Loading...</div>`;
|
||||
} else if (this.crdtBalances.length > 0) {
|
||||
crdtRows = this.crdtBalances.map(t => {
|
||||
const formatted = (t.balance / Math.pow(10, t.decimals)).toFixed(2);
|
||||
return `<div class="crdt-row">
|
||||
<span style="font-size:1.1em">${t.icon || '\u{1FA99}'}</span>
|
||||
<strong>${this.esc(t.symbol)}</strong>
|
||||
<span class="token-name" style="margin-left:4px">${this.esc(t.name)}</span>
|
||||
<span style="margin-left:auto;font-family:monospace;font-weight:600">${formatted}</span>
|
||||
</div>`;
|
||||
}).join("");
|
||||
} else {
|
||||
crdtRows = `<div style="padding:8px;color:var(--rs-text-muted);font-size:12px">No CRDT token balances</div>`;
|
||||
}
|
||||
|
||||
// ── EVM Wallet (passkey-derived) ──
|
||||
const chainBalances = this.passKeyEOA
|
||||
? (this.myWalletBalances.get(this.passKeyEOA.toLowerCase()) || [])
|
||||
: [];
|
||||
|
||||
const allBals: Array<BalanceItem & { chainId: string; chainName: string }> = [];
|
||||
for (const ch of chainBalances) {
|
||||
for (const b of ch.balances) {
|
||||
allBals.push({ ...b, chainId: ch.chainId, chainName: ch.chainName });
|
||||
}
|
||||
}
|
||||
|
||||
const sorted = allBals
|
||||
.filter(b => parseFloat(b.fiatBalance || "0") > 0.01 || BigInt(b.balance || "0") > 0n)
|
||||
.sort((a, b) => parseFloat(b.fiatBalance || "0") - parseFloat(a.fiatBalance || "0"));
|
||||
|
||||
const totalUSD = sorted.reduce((sum, b) => sum + parseFloat(b.fiatBalance || "0"), 0);
|
||||
|
||||
let evmBalanceRows = "";
|
||||
if (sorted.length > 0) {
|
||||
evmBalanceRows = sorted.slice(0, 10).map(b => {
|
||||
const color = CHAIN_COLORS[b.chainId] || "#888";
|
||||
return `<tr>
|
||||
<td><div class="chain-cell"><span class="chain-dot-sm" style="background:${color}"></span>${this.esc(b.chainName)}</div></td>
|
||||
<td><span class="token-symbol">${this.esc(b.token?.symbol || "ETH")}</span></td>
|
||||
<td class="amount-cell">${this.formatBalance(b.balance, b.token?.decimals || 18)}</td>
|
||||
<td class="amount-cell fiat">${this.formatUSD(b.fiatBalance)}</td>
|
||||
</tr>`;
|
||||
}).join("");
|
||||
if (sorted.length > 10) {
|
||||
evmBalanceRows += `<tr><td colspan="4" style="text-align:center;color:var(--rs-text-muted);font-size:11px;padding:8px">+ ${sorted.length - 10} more tokens</td></tr>`;
|
||||
}
|
||||
}
|
||||
|
||||
return `
|
||||
<div class="wallet-card encryptid-card">
|
||||
<div class="wallet-card-header">
|
||||
<div>
|
||||
<span class="wallet-badge encryptid">EncryptID</span>
|
||||
<span class="wallet-card-total" style="margin-left:12px">${this.formatUSD(String(totalUSD))}</span>
|
||||
</div>
|
||||
${this.passKeyEOA ? `<button class="view-flows-btn" data-view-in-viz="${this.esc(this.passKeyEOA)}">View Flows →</button>` : ""}
|
||||
</div>
|
||||
|
||||
<div class="encryptid-wallets">
|
||||
<!-- CRDT Wallet -->
|
||||
<div class="encryptid-wallet-section">
|
||||
<div class="encryptid-wallet-label">
|
||||
<span class="wallet-type-dot" style="background:#2775ca"></span>
|
||||
CRDT Wallet
|
||||
<span class="wallet-type-addr">${this.esc(didShort)}</span>
|
||||
</div>
|
||||
${crdtRows}
|
||||
</div>
|
||||
|
||||
<!-- EVM Wallet -->
|
||||
<div class="encryptid-wallet-section">
|
||||
<div class="encryptid-wallet-label">
|
||||
<span class="wallet-type-dot" style="background:#627eea"></span>
|
||||
EVM Wallet
|
||||
<span class="wallet-type-addr">${this.passKeyEOA ? this.shortenAddress(this.passKeyEOA) : "not derived"}</span>
|
||||
</div>
|
||||
${evmBalanceRows ? `
|
||||
<table class="balance-table compact">
|
||||
<thead>
|
||||
<tr><th>Chain</th><th>Token</th><th class="amount-cell">Balance</th><th class="amount-cell">USD</th></tr>
|
||||
</thead>
|
||||
<tbody>${evmBalanceRows}</tbody>
|
||||
</table>` : `<div style="padding:8px;color:var(--rs-text-muted);font-size:12px">No on-chain balances found</div>`}
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
private renderWalletCard(address: string, label: string, badgeClass: string, _isEncryptId: boolean, walletId?: string): string {
|
||||
const chainBalances = this.myWalletBalances.get(address.toLowerCase()) || [];
|
||||
|
||||
// Flatten all balances for this wallet
|
||||
|
|
@ -1483,24 +1595,6 @@ class FolkWalletViewer extends HTMLElement {
|
|||
}
|
||||
}
|
||||
|
||||
// CRDT tokens for EncryptID wallet
|
||||
let crdtSection = "";
|
||||
if (isEncryptId && this.crdtBalances.length > 0) {
|
||||
const crdtRows = this.crdtBalances.map(t => {
|
||||
const formatted = (t.balance / Math.pow(10, t.decimals)).toFixed(2);
|
||||
return `<div class="crdt-row">
|
||||
<span style="font-size:1.1em">${t.icon || '\u{1FA99}'}</span>
|
||||
<strong>${this.esc(t.symbol)}</strong>
|
||||
<span style="margin-left:auto;font-family:monospace;font-weight:600">${formatted}</span>
|
||||
</div>`;
|
||||
}).join("");
|
||||
crdtSection = `
|
||||
<div class="crdt-section">
|
||||
<div class="crdt-label">Local Tokens (CRDT)</div>
|
||||
${crdtRows}
|
||||
</div>`;
|
||||
}
|
||||
|
||||
return `
|
||||
<div class="wallet-card">
|
||||
<div class="wallet-card-header">
|
||||
|
|
@ -1521,7 +1615,6 @@ class FolkWalletViewer extends HTMLElement {
|
|||
</thead>
|
||||
<tbody>${balanceRows}</tbody>
|
||||
</table>` : `<div style="padding:12px;text-align:center;color:var(--rs-text-muted);font-size:12px">No on-chain balances found</div>`}
|
||||
${crdtSection}
|
||||
</div>`;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -840,7 +840,7 @@ function renderWallet(spaceSlug: string, initialView?: string) {
|
|||
modules: getModuleInfoList(),
|
||||
theme: "dark",
|
||||
body: `<folk-wallet-viewer${viewAttr}></folk-wallet-viewer>`,
|
||||
scripts: `<script type="module" src="/modules/rwallet/folk-wallet-viewer.js?v=7"></script>`,
|
||||
scripts: `<script type="module" src="/modules/rwallet/folk-wallet-viewer.js?v=8"></script>`,
|
||||
styles: `<link rel="stylesheet" href="/modules/rwallet/wallet.css">`,
|
||||
});
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue