Skip to content

Commit

Permalink
Breaking apart NDKCashuWallet
Browse files Browse the repository at this point in the history
  • Loading branch information
pablof7z committed Dec 20, 2024
1 parent 2ae1d17 commit 4b5fa3d
Show file tree
Hide file tree
Showing 22 changed files with 953 additions and 1,167 deletions.
24 changes: 16 additions & 8 deletions ndk-mobile/src/providers/session/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ const NDKSessionProvider = ({ children, ...opts }: PropsWithChildren<NDKSessionP
let followEvent: NDKEvent | undefined;
const balances = useWalletStore((state) => state.balances);
const setBalances = useWalletStore((state) => state.setBalances);

const setNutzapMonitor = useWalletStore((state) => state.setNutzapMonitor);
const processFollowEvent = (event: NDKEvent, relay: NDKRelay) => {
if (followEvent && followEvent.created_at! > event.created_at!) return;

Expand Down Expand Up @@ -88,20 +88,19 @@ const NDKSessionProvider = ({ children, ...opts }: PropsWithChildren<NDKSessionP
save = true
) => {
ndk.wallet = wallet;
if (wallet instanceof NDKCashuWallet) {
setBalances(wallet.balance());
}

const updateBalance = () => {
if (!wallet) return;
console.log('Updating balance from balance_updated event')
setBalances(wallet.balance());
}

wallet?.on("ready", () => {
setBalances(wallet.balance());
});

if (wallet) {
wallet.on("ready", () => {
console.log('Updating balance from ready event')
setBalances(wallet.balance());
});

wallet.on('balance_updated', () => {
updateBalance();
});
Expand All @@ -118,6 +117,8 @@ const NDKSessionProvider = ({ children, ...opts }: PropsWithChildren<NDKSessionP
monitor.on('redeem', (zap) => {
console.log('zap redeemed', zap.rawEvent());
});
setNutzapMonitor(monitor);

monitor.start();
}
}
Expand Down Expand Up @@ -226,6 +227,12 @@ async function loadWallet(ndk: NDK, settingsStore: SettingsStore, setActiveWalle

// Load remotely
const freshEvent = await ndk.fetchEvent(event.encode(), { cacheUsage: NDKSubscriptionCacheUsage.ONLY_RELAY }, relaySet);
if (!freshEvent) {
console.log("Refreshing the event came back empty, has the wallet been deleted?")
setActiveWallet(null);
return null;
}

if (freshEvent.hasTag('deleted')) {
alert('This wallet has been deleted');
setActiveWallet(null);
Expand All @@ -244,6 +251,7 @@ async function loadWallet(ndk: NDK, settingsStore: SettingsStore, setActiveWalle
return wallet;
} catch (e) {
console.error('Error activating wallet', e);
console.log(payload)
}
}

Expand Down
8 changes: 7 additions & 1 deletion ndk-mobile/src/stores/wallet.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { NDKWallet, NDKWalletBalance } from "@nostr-dev-kit/ndk-wallet"
import { NDKNutzapMonitor, NDKWallet, NDKWalletBalance } from "@nostr-dev-kit/ndk-wallet"
import { create } from "zustand"

interface WalletState {
Expand All @@ -7,6 +7,9 @@ interface WalletState {

balances: NDKWalletBalance[],
setBalances: (balances: NDKWalletBalance[]) => void

nutzapMonitor: NDKNutzapMonitor | undefined
setNutzapMonitor: (monitor: NDKNutzapMonitor) => void
}

export const useWalletStore = create<WalletState>((set) => ({
Expand All @@ -18,4 +21,7 @@ export const useWalletStore = create<WalletState>((set) => ({
console.log('Setting balances to:', balances);
set({ balances });
},

nutzapMonitor: undefined,
setNutzapMonitor: (monitor: NDKNutzapMonitor) => set({ nutzapMonitor: monitor }),
}))
6 changes: 4 additions & 2 deletions ndk-wallet/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
export * from "./nutzap-monitor/index.js";
export * from "./wallets/index.js";

export * from "./wallets/cashu/wallet.js";
export * from "./wallets/cashu/wallet/index.js";
export * from "./wallets/cashu/token.js";
export * from "./wallets/cashu/deposit.js";
export * from "./wallets/cashu/history.js";
Expand All @@ -10,4 +10,6 @@ export * from "./wallets/cashu/mint/utils";
export * from "./wallets/webln/index.js";

export * from "./wallets/nwc/index.js";
export * from "./wallets/nwc/types.js";
export * from "./wallets/nwc/types.js";

export * from "./utils/ln.js";
97 changes: 42 additions & 55 deletions ndk-wallet/src/nutzap-monitor/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import NDK, {
} from "@nostr-dev-kit/ndk";
import { EventEmitter } from "tseep";
import createDebug from "debug";
import { NDKCashuWallet } from "../wallets/cashu/wallet";
import { NDKCashuWallet } from "../wallets/cashu/wallet/index.js";
import { Proof } from "@cashu/cashu-ts";

const d = createDebug("ndk-wallet:nutzap-monitor");
Expand All @@ -33,7 +33,7 @@ export class NDKNutzapMonitor extends EventEmitter<{
/**
* Emitted when a new nutzap is successfully redeemed
*/
redeem: (event: NDKNutzap) => void;
redeem: (event: NDKNutzap, amount: number) => void;

/**
* Emitted when a nutzap has been seen
Expand Down Expand Up @@ -87,6 +87,7 @@ export class NDKNutzapMonitor extends EventEmitter<{
* Start the monitor.
*/
public async start(mintList?: NDKCashuMintList) {
const authors = [this.user.pubkey];
// if we are already running, stop the current subscription
if (this.sub) {
this.sub.stop();
Expand All @@ -95,15 +96,25 @@ export class NDKNutzapMonitor extends EventEmitter<{
// if we don't have a mint list, we need to get one
if (!mintList) {
const list = await this.ndk.fetchEvent([
{ kinds: [NDKKind.CashuMintList], authors: [this.user.pubkey] },
]);
if (!list) {
return false;
}
{ kinds: [NDKKind.CashuMintList], authors },
], { groupable: false, closeOnEose: true });
if (!list) return false;

mintList = NDKCashuMintList.from(list);
}

// get the most recent token even
let wallet: NDKCashuWallet | undefined;
let since: number | undefined;

if (mintList?.p2pk) {
wallet = this.walletByP2pk.get(mintList.p2pk)
const mostRecentToken = await this.ndk.fetchEvent([
{ kinds: [NDKKind.CashuToken], authors, limit: 1 },
], { closeOnEose: true, groupable: false }, wallet?.relaySet)
if (mostRecentToken) since = mostRecentToken.created_at!;
}

// set the relay set
this.relaySet = mintList.relaySet;

Expand All @@ -112,8 +123,10 @@ export class NDKNutzapMonitor extends EventEmitter<{
throw new Error("no relay set provided");
}

console.log('starting nutzap monitor with', { since })

this.sub = this.ndk.subscribe(
{ kinds: [NDKKind.Nutzap], "#p": [this.user.pubkey] },
{ kinds: [NDKKind.Nutzap], "#p": [this.user.pubkey], since },
{
subId: "ndk-wallet:nutzap-monitor",
cacheUsage: NDKSubscriptionCacheUsage.ONLY_RELAY,
Expand All @@ -135,7 +148,6 @@ export class NDKNutzapMonitor extends EventEmitter<{

private eoseHandler() {
this.eosed = true;
console.log('eose');

this.redeemQueue.forEach(nutzap => {
this.redeem(nutzap);
Expand Down Expand Up @@ -176,68 +188,43 @@ export class NDKNutzapMonitor extends EventEmitter<{
const { proofs, mint } = nutzap;
d('nutzap has %d proofs: %o', proofs.length, proofs);

let privkey: string | undefined;
let wallet: NDKCashuWallet | undefined;

if (nutzap.p2pk) {
wallet = this.findWalletForNutzap(nutzap);

if (!wallet) {
// if nutzap is p2pk to the active user, check if we have the private key
if (nutzap.p2pk === this.user.pubkey) {
if (this.ndk.signer instanceof NDKPrivateKeySigner)
privkey = (this.ndk.signer as NDKPrivateKeySigner).privateKey;
else {
throw new Error("nutzap p2pk to the active user directly and we don't have access to the private key");
}
}

// find the wallet that has one of these mints
const normalizedMint = normalizeUrl(mint);
wallet = this.allWallets.find(w => w.mints
.map(normalizeUrl)
.includes(normalizedMint));
wallet = this.findWalletForNutzap(nutzap);
if (!wallet) throw new Error("wallet not found for nutzap");

if (!wallet) throw new Error("wallet not found for nutzap (mint: " + normalizedMint + ")");
await wallet.redeemNutzap(
nutzap,
{
onRedeemed: (res) => {
const amount = res.reduce((acc, proof) => acc + proof.amount, 0);
this.emit("redeem", nutzap, amount);
}
}
}

if (!wallet) throw new Error("wallet not found for nutzap");
privkey = wallet.privkey;

const _wallet = await wallet.walletForMint(mint);
if (!_wallet) throw new Error("unable to load wallet for mint " + mint);
const proofsWeHave = wallet.proofsForMint(mint);

try {
const res = await _wallet.receive(
{ proofs, mint, },
{ privkey, proofsWeHave }
);
d("redeemed nutzap %o", nutzap.rawEvent());
this.emit("redeem", nutzap);

const receivedAmount = computeBalanceDifference(res, proofsWeHave);

// save new proofs in wallet
wallet.saveProofs(res, mint, { nutzap, amount: receivedAmount });
} catch (e: any) {
console.log("failed to redeem nutzap", nutzap.id, e.message);
this.emit("failed", nutzap, e.message);
}
);
} catch (e: any) {
console.trace(e);
this.emit("failed", nutzap, e.message);
}
}

private findWalletForNutzap(nutzap: NDKNutzap): NDKCashuWallet | undefined {
const p2pk = nutzap.p2pk;
const {p2pk, mint} = nutzap;
let wallet: NDKCashuWallet | undefined;

if (p2pk) wallet = this.walletByP2pk.get(p2pk);
wallet ??= this.walletByP2pk.values().next().value;

if (!wallet) {
// find the wallet that has one of these mints
const normalizedMint = normalizeUrl(mint);
wallet = this.allWallets.find(w => w.mints
.map(normalizeUrl)
.includes(normalizedMint));

if (!wallet) throw new Error("wallet not found for nutzap (mint: " + normalizedMint + ")");
}

return wallet;
}
}
Expand Down
12 changes: 12 additions & 0 deletions ndk-wallet/src/utils/ln.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,15 @@ export function getBolt11ExpiresAt(bolt11: string): number | undefined {

return undefined;
}

export function getBolt11Amount(bolt11: string): number | undefined {
const decoded = decodeBolt11(bolt11);
const val = decoded.sections.find((section: { name: string; }) => section.name === 'amount')?.value;
return Number(val);
}

export function getBolt11Description(bolt11: string): string | undefined {
const decoded = decodeBolt11(bolt11);
const val = decoded.sections.find((section: { name: string; }) => section.name === 'description')?.value;
return val;
}
Loading

0 comments on commit 4b5fa3d

Please sign in to comment.