applesauce-wallet 6.1.0 → 6.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,9 +1,11 @@
1
- import { Token } from "@cashu/cashu-ts";
1
+ import { Token, Wallet } from "@cashu/cashu-ts";
2
2
  import { Action } from "applesauce-actions";
3
3
  import { NostrEvent } from "applesauce-core/helpers/event";
4
4
  import { ProfilePointer } from "applesauce-core/helpers/pointers";
5
5
  import { Couch } from "../helpers/couch.js";
6
6
  import "../casts/__register__.js";
7
+ /** A function that returns a loaded cashu {@link Wallet} for a mint url */
8
+ type CashuWalletProvider = (mint: string) => Promise<Wallet>;
7
9
  /** Creates a NIP-61 nutzap event for an event with a token */
8
10
  export declare function NutzapEvent(event: NostrEvent, token: Token, options?: {
9
11
  comment?: string;
@@ -20,5 +22,7 @@ export declare function NutzapProfile(user: string | ProfilePointer, token: Toke
20
22
  * Supports nutzaps with different mints by grouping them by mint and redeeming each group separately
21
23
  * @param nutzaps single nutzap event or array of nutzap events
22
24
  * @param couch optional couch interface for temporarily storing tokens during the operation
25
+ * @param getCashuWallet optional provider returning a cached cashu Wallet for a mint
23
26
  */
24
- export declare function ReceiveNutzaps(nutzaps: NostrEvent | NostrEvent[], couch?: Couch): Action;
27
+ export declare function ReceiveNutzaps(nutzaps: NostrEvent | NostrEvent[], couch?: Couch, getCashuWallet?: CashuWalletProvider): Action;
28
+ export {};
@@ -4,11 +4,22 @@ import { bytesToHex } from "applesauce-core/helpers/event";
4
4
  import { WalletHistoryFactory } from "../factories/history.js";
5
5
  import { WalletTokenFactory } from "../factories/tokens.js";
6
6
  import { NutzapFactory } from "../factories/nutzap.js";
7
- import { verifyProofsLocked } from "../helpers/nutzap-info.js";
7
+ import { getNutzapInfoMints, verifyProofsLocked } from "../helpers/nutzap-info.js";
8
8
  import { getNutzapMint, getNutzapProofs, isValidNutzap } from "../helpers/nutzap.js";
9
9
  import { getUnlockedWallet } from "./common.js";
10
10
  // Make sure the nutzap$ is registered on the user class
11
11
  import "../casts/__register__.js";
12
+ /**
13
+ * Returns a loaded cashu {@link Wallet} for a mint url. Uses the `getCashuWallet` provider when given
14
+ * (so a caller can supply a cached, wallet-specific instance), otherwise creates and loads a fresh wallet.
15
+ */
16
+ async function loadCashuWallet(mint, options) {
17
+ if (options?.getCashuWallet)
18
+ return options.getCashuWallet(mint);
19
+ const wallet = new Wallet(mint);
20
+ await wallet.loadMint();
21
+ return wallet;
22
+ }
12
23
  /** Creates a NIP-61 nutzap event for an event with a token */
13
24
  export function NutzapEvent(event, token, options) {
14
25
  return async ({ events, signer, user, publish }) => {
@@ -22,17 +33,20 @@ export function NutzapEvent(event, token, options) {
22
33
  const info = await recipient.nutzap$.$first(5000, undefined);
23
34
  if (!info)
24
35
  throw new Error("Nutzap info not found");
25
- // Get the users wallet
26
- const wallet = await getUnlockedWallet(user, signer);
36
+ // Ensure the user's wallet exists and is unlocked
37
+ await getUnlockedWallet(user, signer);
38
+ if (!getNutzapInfoMints(info.event).some(({ mint }) => mint === token.mint))
39
+ throw new Error("Token mint is not listed in the recipient's nutzap info");
27
40
  // Verify all tokens are p2pk locked
28
41
  verifyProofsLocked(token.proofs, info.event);
29
42
  // Create the nutzap event
30
43
  const nutzap = await NutzapFactory.forEvent(event, token, comment || token.memo).sign(signer);
31
44
  // Publish the nutzap event
32
- await publish(nutzap, wallet.relays);
45
+ await publish(nutzap, info.relays);
46
+ }
47
+ finally {
48
+ await clearStoredToken?.();
33
49
  }
34
- catch { }
35
- await clearStoredToken?.();
36
50
  };
37
51
  }
38
52
  /** Creates a NIP-61 nutzap event to a users profile */
@@ -48,6 +62,8 @@ export function NutzapProfile(user, token, options) {
48
62
  const info = await recipient.nutzap$.$first(5000, undefined);
49
63
  if (!info)
50
64
  throw new Error("Nutzap info not found");
65
+ if (!getNutzapInfoMints(info.event).some(({ mint }) => mint === token.mint))
66
+ throw new Error("Token mint is not listed in the recipient's nutzap info");
51
67
  // Verify all tokens are p2pk locked
52
68
  verifyProofsLocked(token.proofs, info.event);
53
69
  // Create the nutzap event
@@ -55,8 +71,9 @@ export function NutzapProfile(user, token, options) {
55
71
  // Publish the nutzap event
56
72
  await publish(nutzap, info.relays);
57
73
  }
58
- catch { }
59
- await clearStoredToken?.();
74
+ finally {
75
+ await clearStoredToken?.();
76
+ }
60
77
  };
61
78
  }
62
79
  /**
@@ -65,8 +82,9 @@ export function NutzapProfile(user, token, options) {
65
82
  * Supports nutzaps with different mints by grouping them by mint and redeeming each group separately
66
83
  * @param nutzaps single nutzap event or array of nutzap events
67
84
  * @param couch optional couch interface for temporarily storing tokens during the operation
85
+ * @param getCashuWallet optional provider returning a cached cashu Wallet for a mint
68
86
  */
69
- export function ReceiveNutzaps(nutzaps, couch) {
87
+ export function ReceiveNutzaps(nutzaps, couch, getCashuWallet) {
70
88
  return async ({ signer, user, publish }) => {
71
89
  if (!signer)
72
90
  throw new Error("Missing signer");
@@ -113,8 +131,7 @@ export function ReceiveNutzaps(nutzaps, couch) {
113
131
  unit: "sat",
114
132
  };
115
133
  // Use cashu-ts to receive/unlock the P2PK-locked token
116
- const cashuWallet = new Wallet(mint);
117
- await cashuWallet.loadMint();
134
+ const cashuWallet = await loadCashuWallet(mint, { getCashuWallet });
118
135
  // Receive the token using the new wallet.ops API
119
136
  // This will swap P2PK-locked proofs with unlocked proofs
120
137
  let receivedProofs;
@@ -125,7 +142,7 @@ export function ReceiveNutzaps(nutzaps, couch) {
125
142
  throw new Error(`Failed to receive token for mint ${mint}: ${error instanceof Error ? error.message : String(error)}`);
126
143
  }
127
144
  // Calculate total amount
128
- const amount = sumProofs(receivedProofs);
145
+ const amount = sumProofs(receivedProofs).toNumber();
129
146
  // Create token event with received unlocked proofs
130
147
  const receivedToken = {
131
148
  mint,
@@ -1,8 +1,10 @@
1
- import { Proof, Token, Wallet } from "@cashu/cashu-ts";
1
+ import { MintQuoteBolt11Response, Proof, ProofLike, Token, Wallet } from "@cashu/cashu-ts";
2
2
  import { Action } from "applesauce-actions";
3
3
  import { NostrEvent } from "applesauce-core/helpers/event";
4
4
  import { Couch } from "../helpers/couch.js";
5
5
  import "../casts/__register__.js";
6
+ /** A function that returns a loaded cashu {@link Wallet} for a mint url */
7
+ type CashuWalletProvider = (mint: string) => Promise<Wallet>;
6
8
  /**
7
9
  * Adds a cashu token to the wallet and creates a history event
8
10
  * @param token the cashu token to add
@@ -17,21 +19,102 @@ export declare function AddToken(token: Token, options?: {
17
19
  export declare function ReceiveToken(token: Token, options?: {
18
20
  addHistory?: boolean;
19
21
  couch?: Couch;
22
+ wallet?: Wallet;
23
+ getCashuWallet?: CashuWalletProvider;
20
24
  }): Action;
21
- /** An action that deletes old tokens and creates a new one but does not add a history event */
22
- export declare function RolloverTokens(tokens: NostrEvent[], token: Token): Action;
23
- /** An action that deletes old token events and adds a spend history item */
24
- export declare function CompleteSpend(spent: NostrEvent[], change: Token, couch?: Couch): Action;
25
- /** Combines all unlocked token events into a single event per mint */
25
+ /**
26
+ * A method-agnostic action that mints new proofs from a paid mint quote and adds them to the wallet.
27
+ * The `mintProofs` callback performs the payment-method-specific mint call (bolt11, bolt12, onchain, …)
28
+ * against the loaded cashu {@link Wallet}; everything else (couch safety + token/history bookkeeping) is
29
+ * shared. {@link MintTokens} is the bolt11 wrapper over this action.
30
+ * @param mint the mint url to mint the proofs from
31
+ * @param mintProofs a callback that mints and returns the new proofs for the paid quote
32
+ * @param options.couch optional couch interface for temporarily storing the minted token
33
+ * @param options.wallet optional pre-loaded cashu Wallet for the mint
34
+ * @param options.getCashuWallet optional provider returning a cached cashu Wallet for a mint
35
+ */
36
+ export declare function MintProofs(mint: string, mintProofs: (cashuWallet: Wallet) => Promise<Proof[]>, options?: {
37
+ couch?: Couch;
38
+ wallet?: Wallet;
39
+ getCashuWallet?: CashuWalletProvider;
40
+ }): Action;
41
+ /**
42
+ * Mints new proofs from an already-paid bolt11 mint quote and adds them to the wallet.
43
+ * A thin bolt11 wrapper over {@link MintProofs}.
44
+ * @param mint the mint url to mint the proofs from
45
+ * @param amount the amount of the paid mint quote in sats
46
+ * @param quote the paid mint quote id or response
47
+ * @param options.couch optional couch interface for temporarily storing the minted token
48
+ * @param options.wallet optional pre-loaded cashu Wallet for the mint
49
+ * @param options.getCashuWallet optional provider returning a cached cashu Wallet for a mint
50
+ */
51
+ export declare function MintTokens(mint: string, amount: number, quote: string | MintQuoteBolt11Response, options?: {
52
+ couch?: Couch;
53
+ wallet?: Wallet;
54
+ getCashuWallet?: CashuWalletProvider;
55
+ }): Action;
56
+ /**
57
+ * An action that deletes old token events and adds a spend history item
58
+ * @param options.createDeleteEvents whether to publish a NIP-09 delete event for the spent token events
59
+ * (default true). The change token event always references the spent ids in its `del` field, so the
60
+ * wallet can reconcile its balance without the delete event; skipping it saves a signing operation at
61
+ * the cost of leaving the spent token events on relays until {@link CleanupDeletedTokens}.
62
+ * @param options.couch optional couch interface for temporarily storing the change token
63
+ */
64
+ export declare function CompleteSpend(spent: NostrEvent[], change: Token, options?: {
65
+ couch?: Couch;
66
+ createDeleteEvents?: boolean;
67
+ }): Action;
68
+ /**
69
+ * Combines all unlocked token events into a single event per mint
70
+ * @param options.createDeleteEvents whether to publish a single batched NIP-09 delete event for the old
71
+ * token events (default true). The new token events always reference the old ids in their `del` field.
72
+ */
26
73
  export declare function ConsolidateTokens(options?: {
27
74
  unlockTokens?: boolean;
75
+ getCashuWallet?: CashuWalletProvider;
76
+ createDeleteEvents?: boolean;
77
+ }): Action;
78
+ /**
79
+ * Rolls every unlocked token over to a fresh cashu token. For each mint the proofs are swapped at the mint
80
+ * (rotating the secrets) and a single new token event is created whose `del` field references the
81
+ * rolled-over token events. Token events that another token has already marked as deleted are skipped so a
82
+ * repeated rollover does not try to swap already-spent proofs.
83
+ *
84
+ * To keep signer operations to a minimum, every new token event is published together with a *single*
85
+ * batched NIP-09 delete event covering the rolled-over token events across *all* mints — never one delete
86
+ * per mint.
87
+ *
88
+ * @param options.createDeleteEvents whether to publish the single batched NIP-09 delete event (default true).
89
+ * The new token events always reference the old ids in their `del` field, so the wallet can reconcile its
90
+ * balance without the delete event; skipping it leaves the spent token events on relays until
91
+ * {@link CleanupDeletedTokens} removes them.
92
+ * @param options.couch optional couch used to keep each fresh token safe until its token event is published
93
+ */
94
+ export declare function RolloverTokens(options?: {
95
+ unlockTokens?: boolean;
96
+ couch?: Couch;
97
+ getCashuWallet?: CashuWalletProvider;
98
+ createDeleteEvents?: boolean;
28
99
  }): Action;
29
100
  /**
30
- * Recovers tokens from a couch by checking if they exist in the wallet,
31
- * verifying they are unspent, and creating token events for any recoverable tokens
101
+ * Detects token events that have been marked as deleted by a newer token event's `del` field but are
102
+ * still present, and publishes a single NIP-09 delete event for them. Use this to clean up the old
103
+ * token events left behind when operations run with `createDeleteEvents: false`, batching what would
104
+ * otherwise be many per-operation delete events into a single signing operation.
105
+ */
106
+ export declare function CleanupDeletedTokens(): Action;
107
+ /**
108
+ * Recovers tokens from a couch by checking if they exist in the wallet, verifying they are unspent, and
109
+ * creating token events for any recoverable proofs. Once every token has been processed the couch is
110
+ * cleared, so tokens that are already in the wallet or have been spent are cleaned out too. Any token
111
+ * whose recovered token event failed to publish is left in the couch so it can be retried on a later run.
32
112
  * @param couch the couch interface to recover tokens from
113
+ * @param options.getCashuWallet optional provider returning a cached cashu Wallet for a mint
33
114
  */
34
- export declare function RecoverFromCouch(couch: Couch): Action;
115
+ export declare function RecoverFromCouch(couch: Couch, options?: {
116
+ getCashuWallet?: CashuWalletProvider;
117
+ }): Action;
35
118
  /**
36
119
  * Token selection function type that matches dumbTokenSelection signature.
37
120
  * Must return tokens from a single mint and ensure all selected tokens are from that mint.
@@ -39,7 +122,7 @@ export declare function RecoverFromCouch(couch: Couch): Action;
39
122
  */
40
123
  export type TokenSelectionFunction = (tokens: NostrEvent[], minAmount: number, mint?: string) => {
41
124
  events: NostrEvent[];
42
- proofs: Proof[];
125
+ proofs: ProofLike[];
43
126
  };
44
127
  /**
45
128
  * A generic action that safely selects tokens, performs an async operation, and handles change.
@@ -75,7 +158,7 @@ export type TokenSelectionFunction = (tokens: NostrEvent[], minAmount: number, m
75
158
  * }, { couch, tokenSelection: myCustomSelectionFunction });
76
159
  */
77
160
  export declare function TokensOperation(minAmount: number, operation: (params: {
78
- selectedProofs: Proof[];
161
+ selectedProofs: ProofLike[];
79
162
  mint: string;
80
163
  cashuWallet: Wallet;
81
164
  }) => Promise<{
@@ -84,4 +167,8 @@ export declare function TokensOperation(minAmount: number, operation: (params: {
84
167
  mint?: string;
85
168
  couch: Couch;
86
169
  tokenSelection?: TokenSelectionFunction;
170
+ wallet?: Wallet;
171
+ getCashuWallet?: CashuWalletProvider;
172
+ createDeleteEvents?: boolean;
87
173
  }): Action;
174
+ export {};