mirror of
https://github.com/purrgrammer/grimoire.git
synced 2026-06-08 05:39:52 +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 { Alert, AlertDescription } from "@/components/ui/alert";
|
||||||
import { useMemo, useState, useCallback } from "react";
|
import { useMemo, useState, useCallback } from "react";
|
||||||
import accountManager from "@/services/accounts";
|
import accountManager from "@/services/accounts";
|
||||||
|
import pool from "@/services/relay-pool";
|
||||||
|
import type { NostrEvent } from "nostr-tools";
|
||||||
import {
|
import {
|
||||||
decryptWalletConfig,
|
decryptWalletConfig,
|
||||||
decryptUnspentTokens,
|
decryptUnspentTokens,
|
||||||
@@ -110,6 +112,66 @@ export function WalletViewer({ pubkey }: WalletViewerProps) {
|
|||||||
// Get active account from accountManager
|
// Get active account from accountManager
|
||||||
const activeAccount = use$(accountManager.active$);
|
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
|
// Manual decrypt function
|
||||||
const decryptWalletData = useCallback(async () => {
|
const decryptWalletData = useCallback(async () => {
|
||||||
if (!activeAccount?.nip44) {
|
if (!activeAccount?.nip44) {
|
||||||
@@ -130,48 +192,88 @@ export function WalletViewer({ pubkey }: WalletViewerProps) {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// Decrypt wallet config
|
// Decrypt wallet config
|
||||||
|
let config: WalletConfig | null = null;
|
||||||
if (walletConfigEvent) {
|
if (walletConfigEvent) {
|
||||||
console.log("[WalletViewer] Decrypting wallet config...");
|
console.log("[WalletViewer] Decrypting wallet config...");
|
||||||
const config = await decryptWalletConfig(
|
config = await decryptWalletConfig(walletConfigEvent, activeAccount);
|
||||||
walletConfigEvent,
|
|
||||||
activeAccount,
|
|
||||||
);
|
|
||||||
if (config) {
|
if (config) {
|
||||||
console.log(
|
console.log(
|
||||||
"[WalletViewer] Wallet config decrypted successfully:",
|
"[WalletViewer] Wallet config decrypted successfully:",
|
||||||
config,
|
config,
|
||||||
);
|
);
|
||||||
setWalletConfig(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 {
|
} else {
|
||||||
console.warn("[WalletViewer] Wallet config decryption returned null");
|
console.warn("[WalletViewer] Wallet config decryption returned null");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Decrypt token events
|
// Fall back to decrypting token events from default timeline if no wallet relays
|
||||||
if (tokenEvents && tokenEvents.length > 0) {
|
if (!config || !config.relays || config.relays.length === 0) {
|
||||||
console.log(
|
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) {
|
// Decrypt token events from default timeline
|
||||||
const tokens = await decryptUnspentTokens(event, activeAccount);
|
if (tokenEvents && tokenEvents.length > 0) {
|
||||||
if (tokens) {
|
console.log(
|
||||||
console.log("[WalletViewer] Token event decrypted:", tokens);
|
`[WalletViewer] Decrypting ${tokenEvents.length} token event(s)...`,
|
||||||
decryptedTokens.push(tokens);
|
);
|
||||||
} else {
|
const decryptedTokens: UnspentTokens[] = [];
|
||||||
console.warn(
|
for (const event of tokenEvents) {
|
||||||
"[WalletViewer] Token event decryption returned null:",
|
const tokens = await decryptUnspentTokens(event, activeAccount);
|
||||||
event.id,
|
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
|
// Decrypt history events
|
||||||
|
|||||||
Reference in New Issue
Block a user