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
|
// Linked wallets state
|
||||||
private isAuthenticated = false;
|
private isAuthenticated = false;
|
||||||
private passKeyEOA = "";
|
private passKeyEOA = "";
|
||||||
|
private userDID = "";
|
||||||
private linkedWallets: LinkedWallet[] = [];
|
private linkedWallets: LinkedWallet[] = [];
|
||||||
private showProviderPicker = false;
|
private showProviderPicker = false;
|
||||||
private discoveredProviders: DiscoveredProvider[] = [];
|
private discoveredProviders: DiscoveredProvider[] = [];
|
||||||
|
|
@ -193,6 +194,7 @@ class FolkWalletViewer extends HTMLElement {
|
||||||
this.isAuthenticated = true;
|
this.isAuthenticated = true;
|
||||||
this.topTab = "my-wallets";
|
this.topTab = "my-wallets";
|
||||||
this.passKeyEOA = parsed.claims?.eid?.walletAddress || "";
|
this.passKeyEOA = parsed.claims?.eid?.walletAddress || "";
|
||||||
|
this.userDID = parsed.claims?.did || "";
|
||||||
this.loadLinkedWallets().then(() => this.loadMyWalletBalances());
|
this.loadLinkedWallets().then(() => this.loadMyWalletBalances());
|
||||||
this.loadCRDTBalances();
|
this.loadCRDTBalances();
|
||||||
}
|
}
|
||||||
|
|
@ -1235,6 +1237,24 @@ class FolkWalletViewer extends HTMLElement {
|
||||||
font-size: 13px; color: var(--rs-text-primary);
|
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 ── */
|
||||||
.aggregate-stats {
|
.aggregate-stats {
|
||||||
display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
|
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">';
|
let html = '<div class="my-wallets-grid">';
|
||||||
|
|
||||||
// EncryptID wallet card
|
// EncryptID identity card — always shown for authenticated users
|
||||||
if (this.passKeyEOA) {
|
// Every EncryptID identity has both a CRDT wallet (DID) and an EVM wallet (passkey-derived)
|
||||||
html += this.renderWalletCard(this.passKeyEOA, "EncryptID", "encryptid", true);
|
html += this.renderEncryptIdCard();
|
||||||
}
|
|
||||||
|
|
||||||
// Linked wallet cards
|
// Linked wallet cards (external wallets)
|
||||||
for (const w of this.linkedWallets) {
|
for (const w of this.linkedWallets) {
|
||||||
html += this.renderWalletCard(w.address, w.providerName || w.type.toUpperCase(), w.type, false, w.id);
|
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>';
|
html += '</div>';
|
||||||
|
|
||||||
// Aggregate total
|
// Aggregate total
|
||||||
|
|
@ -1450,7 +1465,104 @@ class FolkWalletViewer extends HTMLElement {
|
||||||
return html;
|
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()) || [];
|
const chainBalances = this.myWalletBalances.get(address.toLowerCase()) || [];
|
||||||
|
|
||||||
// Flatten all balances for this wallet
|
// 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 `
|
return `
|
||||||
<div class="wallet-card">
|
<div class="wallet-card">
|
||||||
<div class="wallet-card-header">
|
<div class="wallet-card-header">
|
||||||
|
|
@ -1521,7 +1615,6 @@ class FolkWalletViewer extends HTMLElement {
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>${balanceRows}</tbody>
|
<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>`}
|
</table>` : `<div style="padding:12px;text-align:center;color:var(--rs-text-muted);font-size:12px">No on-chain balances found</div>`}
|
||||||
${crdtSection}
|
|
||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -840,7 +840,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=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">`,
|
styles: `<link rel="stylesheet" href="/modules/rwallet/wallet.css">`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue