From 4351ec435f56b1d97621ed8ed7f1c742f9b0c0b3 Mon Sep 17 00:00:00 2001 From: pablof7z Date: Mon, 21 Oct 2024 16:55:19 +0100 Subject: [PATCH] provide a way to create nuts of specific denomincation(s) --- .changeset/beige-news-kick.md | 5 +++ ndk-wallet/src/cashu/pay/nut.ts | 7 ++- ndk-wallet/src/cashu/proofs.ts | 80 ++++++++++++++++++++++++++++++++- ndk-wallet/src/cashu/wallet.ts | 18 ++++++++ 4 files changed, 108 insertions(+), 2 deletions(-) create mode 100644 .changeset/beige-news-kick.md diff --git a/.changeset/beige-news-kick.md b/.changeset/beige-news-kick.md new file mode 100644 index 00000000..4c1ea291 --- /dev/null +++ b/.changeset/beige-news-kick.md @@ -0,0 +1,5 @@ +--- +"@nostr-dev-kit/ndk-wallet": patch +--- + +provide a way to create nuts of specific denomincation(s) diff --git a/ndk-wallet/src/cashu/pay/nut.ts b/ndk-wallet/src/cashu/pay/nut.ts index b01d5ace..693c63c7 100644 --- a/ndk-wallet/src/cashu/pay/nut.ts +++ b/ndk-wallet/src/cashu/pay/nut.ts @@ -2,10 +2,15 @@ import type { Proof } from "@cashu/cashu-ts"; import { CashuMint, CashuWallet } from "@cashu/cashu-ts"; import type { MintUrl } from "../mint/utils"; import type { NDKCashuPay } from "../pay"; -import { chooseProofsForAmount, rollOverProofs } from "../proofs"; +import { chooseProofsForAmount, chooseProofsForAmounts, rollOverProofs } from "../proofs"; +import { NDKCashuWallet } from "../wallet"; export type NutPayment = { amount: number; unit: string; mints: MintUrl[]; p2pk?: string }; +export async function mintNuts(this: NDKCashuWallet, amounts: number[], unit: string) { + return await chooseProofsForAmounts(amounts, this); +} + /** * Generates proof to satisfy a payment. * Note that this function doesn't send the proofs to the recipient. diff --git a/ndk-wallet/src/cashu/proofs.ts b/ndk-wallet/src/cashu/proofs.ts index e85bff19..251159cb 100644 --- a/ndk-wallet/src/cashu/proofs.ts +++ b/ndk-wallet/src/cashu/proofs.ts @@ -113,6 +113,84 @@ function chooseProofsForQuote( return { ...res, quote }; } +export function chooseProofsForAmounts(amounts: number[], wallet: NDKCashuWallet): TokenSelection & { needsSwap: boolean } | undefined { + let missingAmounts: number[] = [...amounts]; + let tokenSelection: TokenSelection = { + usedProofs: [], + movedProofs: [], + usedTokens: [], + mint: "" + }; + const reset = () => { + tokenSelection = { + usedProofs: [], + movedProofs: [], + usedTokens: [], + mint: "" + }; + missingAmounts = [...amounts]; + } + + // try to find all proofs from the same mint + for (const [mint, tokens] of Object.entries(wallet.mintTokens)) { + reset(); + tokenSelection.mint = mint; + + for (const token of tokens) { + let tokenUsed = false; + for (const proof of token.proofs) { + if (missingAmounts.includes(proof.amount)) { + missingAmounts.splice(missingAmounts.indexOf(proof.amount), 1); + tokenSelection.usedProofs.push(proof); + tokenUsed = true; + } else { + tokenSelection.movedProofs.push(proof); + } + } + + if (tokenUsed) { + tokenSelection.usedTokens.push(token); + } + } + + if (missingAmounts.length === 0) { + d("found all proofs using mint %s without having to swap", mint); + return { ...tokenSelection, needsSwap: false }; + } + } + + for (const [mint, tokens] of Object.entries(wallet.mintTokens)) { + reset(); + tokenSelection.mint = mint; + let missingAmount = missingAmounts.reduce((a, b) => a + b, 0); + + for (const token of tokens) { + let tokenUsed = false; + for (const proof of token.proofs) { + if (missingAmount > 0) { + missingAmount -= proof.amount; + tokenSelection.usedProofs.push(proof); + tokenUsed = true; + } else { + tokenSelection.movedProofs.push(proof); + } + } + + if (tokenUsed) { + tokenSelection.usedTokens.push(token); + } + + if (missingAmount <= 0) { + d("found all proofs using mint %s, will need to swap", mint); + return { ...tokenSelection, needsSwap: true }; + } + } + } + + d("could not find all proofs for the requested amounts"); + return; +} + export type ROLL_OVER_RESULT = { destroyedTokens: NDKCashuToken[], createdToken: NDKCashuToken | undefined @@ -130,7 +208,7 @@ export async function rollOverProofs( const relaySet = wallet.relaySet; if (proofs.usedTokens.length > 0) { - console.trace("rolling over proofs for mint %s %d tokens", mint, proofs.usedTokens.length); + // console.trace("rolling over proofs for mint %s %d tokens", mint, proofs.usedTokens.length); const deleteEvent = new NDKEvent(wallet.event.ndk); deleteEvent.kind = NDKKind.EventDeletion; diff --git a/ndk-wallet/src/cashu/wallet.ts b/ndk-wallet/src/cashu/wallet.ts index e4d39c48..cffaf59c 100644 --- a/ndk-wallet/src/cashu/wallet.ts +++ b/ndk-wallet/src/cashu/wallet.ts @@ -21,6 +21,7 @@ import { checkTokenProofs } from "./validate.js"; import { NDKWallet, NDKWalletBalance, NDKWalletEvents, NDKWalletStatus } from "../wallet/index.js"; import { EventEmitter } from "tseep"; import { decrypt } from "./decrypt.js"; +import { chooseProofsForAmounts, rollOverProofs } from "./proofs.js"; const d = createDebug("ndk-wallet:cashu:wallet"); @@ -98,6 +99,23 @@ export class NDKCashuWallet extends EventEmitter implements NDK public checkProofs = checkTokenProofs.bind(this); + async mintNuts(amounts: number[], unit: string) { + const tokenSelection = await chooseProofsForAmounts(amounts, this); + if (tokenSelection?.needsSwap) { + const wallet = new CashuWallet(new CashuMint(tokenSelection.mint)); + const proofs = await wallet.send(0, tokenSelection.usedProofs, { + preference: amounts.map((a) => ({ amount: a, count: 1 })), + }); + + await rollOverProofs(tokenSelection, proofs.returnChange, tokenSelection.mint, this); + + return proofs.send; + } else if (tokenSelection) { + await rollOverProofs(tokenSelection, [], tokenSelection.mint, this); + } + return tokenSelection?.usedProofs; + } + static async from(event: NDKEvent): Promise { if (!event.ndk) throw new Error("no ndk instance on event"); const wallet = new NDKCashuWallet(event.ndk, event);