mirror of
https://github.com/purrgrammer/grimoire.git
synced 2026-04-10 23:47:12 +02:00
Add relay-specific token fetching for NIP-60 wallets
Implement fetching token events from wallet-configured relays: - Add fetchTokensFromRelays() function to query specific relays - Use pool.subscription() with eventStore integration - After unlocking wallet config, fetch tokens from wallet.relays if present - Fall back to default timeline if no wallet relays configured - Add detailed logging for relay fetching and decryption - Handle EOSE events and subscription cleanup - 10 second timeout for token fetching This allows wallets to specify their preferred relays for token storage according to NIP-60 spec, enabling proper balance calculation from the correct relay sources. According to NIP-60, clients should: 1. Use relays specified in wallet config (wallet.relays) 2. Fall back to NIP-65 relay lists if not specified 3. Query for kind:7375 token events from those relays
This commit is contained in:
@@ -23,6 +23,8 @@ import {
|
||||
import { Alert, AlertDescription } from "@/components/ui/alert";
|
||||
import { useMemo, useState, useCallback } from "react";
|
||||
import accountManager from "@/services/accounts";
|
||||
import pool from "@/services/relay-pool";
|
||||
import type { NostrEvent } from "nostr-tools";
|
||||
import {
|
||||
decryptWalletConfig,
|
||||
decryptUnspentTokens,
|
||||
@@ -110,6 +112,66 @@ export function WalletViewer({ pubkey }: WalletViewerProps) {
|
||||
// Get active account from accountManager
|
||||
const activeAccount = use$(accountManager.active$);
|
||||
|
||||
// Fetch token events from specific relays
|
||||
const fetchTokensFromRelays = useCallback(
|
||||
async (relays: string[], pubkey: string) => {
|
||||
console.log(
|
||||
`[WalletViewer] Fetching tokens from ${relays.length} relay(s)...`,
|
||||
);
|
||||
|
||||
return new Promise<NostrEvent[]>((resolve) => {
|
||||
const events: NostrEvent[] = [];
|
||||
const timeout = setTimeout(() => {
|
||||
console.log(
|
||||
`[WalletViewer] Fetch timeout, got ${events.length} events`,
|
||||
);
|
||||
resolve(events);
|
||||
}, 10000); // 10 second timeout
|
||||
|
||||
const observable = pool.subscription(
|
||||
relays,
|
||||
[
|
||||
{
|
||||
kinds: [7375],
|
||||
authors: [pubkey],
|
||||
},
|
||||
],
|
||||
{
|
||||
eventStore: eventStore,
|
||||
},
|
||||
);
|
||||
|
||||
const subscription = observable.subscribe({
|
||||
next: (event: NostrEvent | string) => {
|
||||
if (typeof event === "string") {
|
||||
// EOSE marker
|
||||
if (event === "EOSE") {
|
||||
console.log(
|
||||
`[WalletViewer] EOSE received, got ${events.length} total events`,
|
||||
);
|
||||
clearTimeout(timeout);
|
||||
subscription.unsubscribe();
|
||||
resolve(events);
|
||||
}
|
||||
} else {
|
||||
console.log(
|
||||
"[WalletViewer] Got token event from relay:",
|
||||
event.id,
|
||||
);
|
||||
events.push(event);
|
||||
}
|
||||
},
|
||||
error: (error) => {
|
||||
console.error("[WalletViewer] Subscription error:", error);
|
||||
clearTimeout(timeout);
|
||||
resolve(events);
|
||||
},
|
||||
});
|
||||
});
|
||||
},
|
||||
[eventStore],
|
||||
);
|
||||
|
||||
// Manual decrypt function
|
||||
const decryptWalletData = useCallback(async () => {
|
||||
if (!activeAccount?.nip44) {
|
||||
@@ -130,48 +192,88 @@ export function WalletViewer({ pubkey }: WalletViewerProps) {
|
||||
|
||||
try {
|
||||
// Decrypt wallet config
|
||||
let config: WalletConfig | null = null;
|
||||
if (walletConfigEvent) {
|
||||
console.log("[WalletViewer] Decrypting wallet config...");
|
||||
const config = await decryptWalletConfig(
|
||||
walletConfigEvent,
|
||||
activeAccount,
|
||||
);
|
||||
config = await decryptWalletConfig(walletConfigEvent, activeAccount);
|
||||
if (config) {
|
||||
console.log(
|
||||
"[WalletViewer] Wallet config decrypted successfully:",
|
||||
config,
|
||||
);
|
||||
setWalletConfig(config);
|
||||
|
||||
// If wallet has specific relays configured, fetch token events from those relays
|
||||
if (config.relays && config.relays.length > 0 && resolvedPubkey) {
|
||||
console.log(
|
||||
`[WalletViewer] Fetching token events from ${config.relays.length} wallet relay(s)...`,
|
||||
);
|
||||
const fetchedEvents = await fetchTokensFromRelays(
|
||||
config.relays,
|
||||
resolvedPubkey,
|
||||
);
|
||||
console.log(
|
||||
`[WalletViewer] Fetched ${fetchedEvents.length} token event(s) from wallet relays`,
|
||||
);
|
||||
|
||||
// Decrypt the fetched events
|
||||
if (fetchedEvents.length > 0) {
|
||||
const decryptedTokens: UnspentTokens[] = [];
|
||||
for (const event of fetchedEvents) {
|
||||
const tokens = await decryptUnspentTokens(event, activeAccount);
|
||||
if (tokens) {
|
||||
console.log("[WalletViewer] Token event decrypted:", tokens);
|
||||
decryptedTokens.push(tokens);
|
||||
} else {
|
||||
console.warn(
|
||||
"[WalletViewer] Token event decryption returned null:",
|
||||
event.id,
|
||||
);
|
||||
}
|
||||
}
|
||||
setUnspentTokens(decryptedTokens);
|
||||
console.log(
|
||||
`[WalletViewer] Total ${decryptedTokens.length} token event(s) decrypted from wallet relays`,
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.warn("[WalletViewer] Wallet config decryption returned null");
|
||||
}
|
||||
}
|
||||
|
||||
// Decrypt token events
|
||||
if (tokenEvents && tokenEvents.length > 0) {
|
||||
// Fall back to decrypting token events from default timeline if no wallet relays
|
||||
if (!config || !config.relays || config.relays.length === 0) {
|
||||
console.log(
|
||||
`[WalletViewer] Decrypting ${tokenEvents.length} token event(s)...`,
|
||||
"[WalletViewer] No wallet relays, using default token events",
|
||||
);
|
||||
const decryptedTokens: UnspentTokens[] = [];
|
||||
for (const event of tokenEvents) {
|
||||
const tokens = await decryptUnspentTokens(event, activeAccount);
|
||||
if (tokens) {
|
||||
console.log("[WalletViewer] Token event decrypted:", tokens);
|
||||
decryptedTokens.push(tokens);
|
||||
} else {
|
||||
console.warn(
|
||||
"[WalletViewer] Token event decryption returned null:",
|
||||
event.id,
|
||||
);
|
||||
|
||||
// Decrypt token events from default timeline
|
||||
if (tokenEvents && tokenEvents.length > 0) {
|
||||
console.log(
|
||||
`[WalletViewer] Decrypting ${tokenEvents.length} token event(s)...`,
|
||||
);
|
||||
const decryptedTokens: UnspentTokens[] = [];
|
||||
for (const event of tokenEvents) {
|
||||
const tokens = await decryptUnspentTokens(event, activeAccount);
|
||||
if (tokens) {
|
||||
console.log("[WalletViewer] Token event decrypted:", tokens);
|
||||
decryptedTokens.push(tokens);
|
||||
} else {
|
||||
console.warn(
|
||||
"[WalletViewer] Token event decryption returned null:",
|
||||
event.id,
|
||||
);
|
||||
}
|
||||
}
|
||||
setUnspentTokens(decryptedTokens);
|
||||
console.log(
|
||||
`[WalletViewer] Total ${decryptedTokens.length} token event(s) decrypted`,
|
||||
);
|
||||
} else {
|
||||
console.log("[WalletViewer] No token events to decrypt");
|
||||
setUnspentTokens([]);
|
||||
}
|
||||
setUnspentTokens(decryptedTokens);
|
||||
console.log(
|
||||
`[WalletViewer] Total ${decryptedTokens.length} token event(s) decrypted`,
|
||||
);
|
||||
} else {
|
||||
console.log("[WalletViewer] No token events to decrypt");
|
||||
setUnspentTokens([]);
|
||||
}
|
||||
|
||||
// Decrypt history events
|
||||
|
||||
Reference in New Issue
Block a user