applesauce-wallet 1.0.0 → 3.0.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.
Files changed (82) hide show
  1. package/README.md +1 -1
  2. package/dist/actions/index.d.ts +2 -0
  3. package/dist/actions/index.js +2 -0
  4. package/dist/actions/tokens.js +1 -1
  5. package/dist/actions/zap-info.d.ts +22 -0
  6. package/dist/actions/zap-info.js +83 -0
  7. package/dist/actions/zaps.d.ts +8 -0
  8. package/dist/actions/zaps.js +30 -0
  9. package/dist/blueprints/history.d.ts +1 -2
  10. package/dist/blueprints/history.js +3 -3
  11. package/dist/blueprints/index.d.ts +3 -1
  12. package/dist/blueprints/index.js +3 -1
  13. package/dist/blueprints/tokens.d.ts +1 -2
  14. package/dist/blueprints/tokens.js +3 -3
  15. package/dist/blueprints/wallet.d.ts +2 -3
  16. package/dist/blueprints/wallet.js +4 -6
  17. package/dist/blueprints/zaps.d.ts +8 -0
  18. package/dist/blueprints/zaps.js +12 -0
  19. package/dist/helpers/history.js +3 -1
  20. package/dist/helpers/index.d.ts +5 -3
  21. package/dist/helpers/index.js +5 -3
  22. package/dist/helpers/nutzap.d.ts +27 -0
  23. package/dist/helpers/nutzap.js +66 -0
  24. package/dist/helpers/tokens.d.ts +1 -1
  25. package/dist/helpers/tokens.js +4 -2
  26. package/dist/helpers/wallet.js +4 -1
  27. package/dist/helpers/zap-info.d.ts +19 -0
  28. package/dist/helpers/zap-info.js +42 -0
  29. package/dist/index.d.ts +2 -2
  30. package/dist/index.js +2 -2
  31. package/dist/models/history.d.ts +6 -0
  32. package/dist/{queries → models}/history.js +5 -5
  33. package/dist/{operations/event → models}/index.d.ts +3 -2
  34. package/dist/{operations/event → models}/index.js +3 -2
  35. package/dist/models/nutzap.d.ts +6 -0
  36. package/dist/models/nutzap.js +16 -0
  37. package/dist/models/tokens.d.ts +6 -0
  38. package/dist/{queries → models}/tokens.js +6 -6
  39. package/dist/{queries → models}/wallet.d.ts +3 -3
  40. package/dist/{queries → models}/wallet.js +3 -3
  41. package/dist/operations/{event/history.d.ts → history.d.ts} +2 -2
  42. package/dist/operations/history.js +34 -0
  43. package/dist/operations/index.d.ts +5 -2
  44. package/dist/operations/index.js +5 -2
  45. package/dist/operations/nutzap.d.ts +14 -0
  46. package/dist/operations/nutzap.js +33 -0
  47. package/dist/operations/{event/tokens.d.ts → tokens.d.ts} +1 -1
  48. package/dist/operations/{event/tokens.js → tokens.js} +2 -2
  49. package/dist/operations/wallet.d.ts +8 -0
  50. package/dist/operations/wallet.js +30 -0
  51. package/dist/operations/zap-info.d.ts +10 -0
  52. package/dist/operations/zap-info.js +17 -0
  53. package/package.json +19 -24
  54. package/dist/__tests__/fake-user.d.ts +0 -10
  55. package/dist/__tests__/fake-user.js +0 -31
  56. package/dist/actions/__tests__/tokens.test.d.ts +0 -1
  57. package/dist/actions/__tests__/tokens.test.js +0 -139
  58. package/dist/actions/__tests__/wallet.test.d.ts +0 -1
  59. package/dist/actions/__tests__/wallet.test.js +0 -56
  60. package/dist/helpers/__tests__/animated-qr.test.d.ts +0 -1
  61. package/dist/helpers/__tests__/animated-qr.test.js +0 -44
  62. package/dist/helpers/__tests__/tokens.test.d.ts +0 -1
  63. package/dist/helpers/__tests__/tokens.test.js +0 -127
  64. package/dist/operations/event/__tests__/wallet.test.d.ts +0 -1
  65. package/dist/operations/event/__tests__/wallet.test.js +0 -25
  66. package/dist/operations/event/history.js +0 -19
  67. package/dist/operations/event/wallet.d.ts +0 -4
  68. package/dist/operations/event/wallet.js +0 -14
  69. package/dist/operations/tag/__tests__/wallet.test.d.ts +0 -1
  70. package/dist/operations/tag/__tests__/wallet.test.js +0 -16
  71. package/dist/operations/tag/history.d.ts +0 -14
  72. package/dist/operations/tag/history.js +0 -34
  73. package/dist/operations/tag/index.d.ts +0 -2
  74. package/dist/operations/tag/index.js +0 -2
  75. package/dist/operations/tag/wallet.d.ts +0 -5
  76. package/dist/operations/tag/wallet.js +0 -15
  77. package/dist/queries/__tests__/wallet.test.d.ts +0 -1
  78. package/dist/queries/__tests__/wallet.test.js +0 -30
  79. package/dist/queries/history.d.ts +0 -6
  80. package/dist/queries/index.d.ts +0 -3
  81. package/dist/queries/index.js +0 -3
  82. package/dist/queries/tokens.d.ts +0 -6
@@ -19,10 +19,10 @@ function filterDeleted(tokens) {
19
19
  })
20
20
  .reverse();
21
21
  }
22
- /** A query that subscribes to all token events for a wallet, passing locked will filter by token locked status */
23
- export function WalletTokensQuery(pubkey, locked) {
22
+ /** A model that subscribes to all token events for a wallet, passing locked will filter by token locked status */
23
+ export function WalletTokensModel(pubkey, locked) {
24
24
  return (events) => {
25
- const updates = events.updates.pipe(filter((e) => e.kind === WALLET_TOKEN_KIND && e.pubkey === pubkey), startWith(undefined));
25
+ const updates = events.update$.pipe(filter((e) => e.kind === WALLET_TOKEN_KIND && e.pubkey === pubkey), startWith(undefined));
26
26
  const timeline = events.timeline({ kinds: [WALLET_TOKEN_KIND], authors: [pubkey] });
27
27
  return combineLatest([updates, timeline]).pipe(
28
28
  // filter out locked tokens
@@ -36,10 +36,10 @@ export function WalletTokensQuery(pubkey, locked) {
36
36
  map(filterDeleted));
37
37
  };
38
38
  }
39
- /** A query that returns the visible balance of a wallet for each mint */
40
- export function WalletBalanceQuery(pubkey) {
39
+ /** A model that returns the visible balance of a wallet for each mint */
40
+ export function WalletBalanceModel(pubkey) {
41
41
  return (events) => {
42
- const updates = events.updates.pipe(filter((e) => e.kind === WALLET_TOKEN_KIND && e.pubkey === pubkey), startWith(undefined));
42
+ const updates = events.update$.pipe(filter((e) => e.kind === WALLET_TOKEN_KIND && e.pubkey === pubkey), startWith(undefined));
43
43
  const timeline = events.timeline({ kinds: [WALLET_TOKEN_KIND], authors: [pubkey] });
44
44
  return combineLatest([updates, timeline]).pipe(map(([_, tokens]) => tokens.filter((t) => !isTokenContentLocked(t))),
45
45
  // filter out deleted tokens
@@ -1,4 +1,4 @@
1
- import { Query } from "applesauce-core";
1
+ import { Model } from "applesauce-core";
2
2
  import { NostrEvent } from "nostr-tools";
3
3
  export type WalletInfo = {
4
4
  locked: true;
@@ -9,5 +9,5 @@ export type WalletInfo = {
9
9
  privateKey?: Uint8Array;
10
10
  mints: string[];
11
11
  };
12
- /** A query to get the state of a NIP-60 wallet */
13
- export declare function WalletQuery(pubkey: string): Query<WalletInfo | undefined>;
12
+ /** A model to get the state of a NIP-60 wallet */
13
+ export declare function WalletModel(pubkey: string): Model<WalletInfo | undefined>;
@@ -1,12 +1,12 @@
1
1
  import { filter, map, merge } from "rxjs";
2
2
  import { getWalletMints, getWalletPrivateKey, isWalletLocked, WALLET_KIND } from "../helpers/wallet.js";
3
- /** A query to get the state of a NIP-60 wallet */
4
- export function WalletQuery(pubkey) {
3
+ /** A model to get the state of a NIP-60 wallet */
4
+ export function WalletModel(pubkey) {
5
5
  return (events) => merge(
6
6
  // get the latest replaceable event
7
7
  events.replaceable(WALLET_KIND, pubkey),
8
8
  // and listen for any updates to matching events
9
- events.updates.pipe(filter((e) => e.kind === WALLET_KIND && e.pubkey === pubkey))).pipe(map((wallet) => {
9
+ events.update$.pipe(filter((e) => e.kind === WALLET_KIND && e.pubkey === pubkey))).pipe(map((wallet) => {
10
10
  if (!wallet)
11
11
  return;
12
12
  if (isWalletLocked(wallet))
@@ -1,7 +1,7 @@
1
1
  import { EventOperation } from "applesauce-factory";
2
- import { HistoryContent } from "../../helpers/history.js";
3
2
  import { EventPointer } from "nostr-tools/nip19";
3
+ import { HistoryContent } from "../helpers/history.js";
4
4
  /** Sets the encrypted tags of a wallet history event */
5
5
  export declare function setHistoryContent(content: HistoryContent): EventOperation;
6
- /** Sets the "redeemed" tags on a wallet history event */
6
+ /** Sets the "e" "redeemed" tags on a wallet history event */
7
7
  export declare function setHistoryRedeemed(redeemed: (string | EventPointer)[]): EventOperation;
@@ -0,0 +1,34 @@
1
+ import { ensureMarkedEventPointerTag } from "applesauce-factory/helpers";
2
+ import { modifyHiddenTags, modifyPublicTags } from "applesauce-factory/operations";
3
+ import { setSingletonTag } from "applesauce-factory/operations/tag";
4
+ /** Includes "e" "created" tags in wallet history tags */
5
+ function includeHistoryCreatedTags(created) {
6
+ return (tags) => {
7
+ for (const id of created) {
8
+ tags = ensureMarkedEventPointerTag(tags, typeof id === "string" ? { id } : id, "created");
9
+ }
10
+ return tags;
11
+ };
12
+ }
13
+ /** Sets the encrypted tags of a wallet history event */
14
+ export function setHistoryContent(content) {
15
+ const operations = [
16
+ setSingletonTag(["direction", content.direction], true),
17
+ setSingletonTag(["amount", String(content.amount)], true),
18
+ includeHistoryCreatedTags(content.created),
19
+ ];
20
+ if (content.fee !== undefined)
21
+ operations.push(setSingletonTag(["fee", String(content.fee)], true));
22
+ if (content.mint !== undefined)
23
+ operations.push(setSingletonTag(["mint", content.mint], true));
24
+ return modifyHiddenTags(...operations);
25
+ }
26
+ /** Sets the "e" "redeemed" tags on a wallet history event */
27
+ export function setHistoryRedeemed(redeemed) {
28
+ return modifyPublicTags((tags) => {
29
+ for (const id of redeemed) {
30
+ tags = ensureMarkedEventPointerTag(tags, typeof id === "string" ? { id } : id, "redeemed");
31
+ }
32
+ return tags;
33
+ });
34
+ }
@@ -1,2 +1,5 @@
1
- export * as TagOperations from "./tag/index.js";
2
- export * as EventOperations from "./event/wallet.js";
1
+ export * as History from "./history.js";
2
+ export * as Tokens from "./tokens.js";
3
+ export * as Wallet from "./wallet.js";
4
+ export * as ZapInfo from "./zap-info.js";
5
+ export * as NutZap from "./nutzap.js";
@@ -1,2 +1,5 @@
1
- export * as TagOperations from "./tag/index.js";
2
- export * as EventOperations from "./event/wallet.js";
1
+ export * as History from "./history.js";
2
+ export * as Tokens from "./tokens.js";
3
+ export * as Wallet from "./wallet.js";
4
+ export * as ZapInfo from "./zap-info.js";
5
+ export * as NutZap from "./nutzap.js";
@@ -0,0 +1,14 @@
1
+ import { Proof } from "@cashu/cashu-ts";
2
+ import { EventOperation } from "applesauce-factory";
3
+ import { NostrEvent } from "nostr-tools";
4
+ import { AddressPointer, EventPointer, ProfilePointer } from "nostr-tools/nip19";
5
+ /** Sets the cashu proofs for a nutzap event */
6
+ export declare function setProofs(proofs: Proof[]): EventOperation;
7
+ /** Sets the mint URL for a nutzap event */
8
+ export declare function setMint(mint: string): EventOperation;
9
+ /** Sets the recipient of a nutzap event */
10
+ export declare function setRecipient(recipient: string | ProfilePointer): EventOperation;
11
+ /** Sets the event that is being nutzapped */
12
+ export declare function setEvent(event: EventPointer | AddressPointer | NostrEvent): EventOperation;
13
+ /** Sets the comment content for a nutzap event */
14
+ export declare function setComment(comment: string): EventOperation;
@@ -0,0 +1,33 @@
1
+ import { getReplaceableAddress, isAddressPointer, isEvent, isReplaceable } from "applesauce-core/helpers";
2
+ import { modifyPublicTags } from "applesauce-factory/operations";
3
+ import { setContent } from "applesauce-factory/operations/content";
4
+ import { addAddressTag, addEventTag, addPubkeyTag, setSingletonTag } from "applesauce-factory/operations/tag";
5
+ /** Sets the cashu proofs for a nutzap event */
6
+ export function setProofs(proofs) {
7
+ // Create an operation to append proof tags
8
+ const operation = (tags) => [...tags, ...proofs.map((proof) => ["proof", JSON.stringify(proof)])];
9
+ return modifyPublicTags(operation);
10
+ }
11
+ /** Sets the mint URL for a nutzap event */
12
+ export function setMint(mint) {
13
+ return modifyPublicTags(setSingletonTag(["u", mint]));
14
+ }
15
+ /** Sets the recipient of a nutzap event */
16
+ export function setRecipient(recipient) {
17
+ return modifyPublicTags(addPubkeyTag(recipient, true));
18
+ }
19
+ /** Sets the event that is being nutzapped */
20
+ export function setEvent(event) {
21
+ let operation;
22
+ if (isEvent(event))
23
+ operation = isReplaceable(event.kind) ? addEventTag(event.id) : addAddressTag(getReplaceableAddress(event));
24
+ else if (isAddressPointer(event))
25
+ operation = addAddressTag(event);
26
+ else
27
+ operation = addEventTag(event);
28
+ return modifyPublicTags(operation);
29
+ }
30
+ /** Sets the comment content for a nutzap event */
31
+ export function setComment(comment) {
32
+ return setContent(comment);
33
+ }
@@ -1,4 +1,4 @@
1
1
  import { Token } from "@cashu/cashu-ts";
2
2
  import { EventOperation } from "applesauce-factory";
3
3
  /** Sets the content of a 7375 token event */
4
- export declare function setTokenContent(token: Token, del?: string[]): EventOperation;
4
+ export declare function setToken(token: Token, del?: string[]): EventOperation;
@@ -1,7 +1,7 @@
1
1
  import { EventContentEncryptionMethod } from "applesauce-core/helpers";
2
- import { setEncryptedContent } from "applesauce-factory/operations/event";
2
+ import { setEncryptedContent } from "applesauce-factory/operations/content";
3
3
  /** Sets the content of a 7375 token event */
4
- export function setTokenContent(token, del = []) {
4
+ export function setToken(token, del = []) {
5
5
  return async (draft, ctx) => {
6
6
  if (!ctx.signer)
7
7
  throw new Error(`Missing signer`);
@@ -0,0 +1,8 @@
1
+ import { EventOperation } from "applesauce-factory";
2
+ import { NostrEvent } from "nostr-tools";
3
+ /** Sets the content of a kind 375 wallet backup event */
4
+ export declare function setBackupContent(wallet: NostrEvent): EventOperation;
5
+ /** Sets the "mint" tags in a wallet event */
6
+ export declare function setMints(mints: string[]): EventOperation;
7
+ /** Sets the "privkey" tag on a wallet event */
8
+ export declare function setPrivateKey(privateKey: Uint8Array): EventOperation;
@@ -0,0 +1,30 @@
1
+ import { bytesToHex } from "@noble/hashes/utils";
2
+ import { modifyHiddenTags } from "applesauce-factory/operations";
3
+ import { setSingletonTag } from "applesauce-factory/operations/tag";
4
+ import { WALLET_KIND } from "../helpers/wallet.js";
5
+ /** Sets the content of a kind 375 wallet backup event */
6
+ export function setBackupContent(wallet) {
7
+ return async (draft, ctx) => {
8
+ if (wallet.kind !== WALLET_KIND)
9
+ throw new Error(`Cant create a wallet backup from kind ${wallet.kind}`);
10
+ if (!wallet.content)
11
+ throw new Error("Wallet missing content");
12
+ const pubkey = await ctx.signer?.getPublicKey();
13
+ if (wallet.pubkey !== pubkey)
14
+ throw new Error("Wallet pubkey dose not match signer pubkey");
15
+ return { ...draft, content: wallet.content };
16
+ };
17
+ }
18
+ /** Sets the "mint" tags in a wallet event */
19
+ export function setMints(mints) {
20
+ return modifyHiddenTags((tags) => [
21
+ // remove all existing mint tags
22
+ ...tags.filter((t) => t[0] !== "mint"),
23
+ // add new mint tags
24
+ ...mints.map((mint) => ["mint", mint]),
25
+ ]);
26
+ }
27
+ /** Sets the "privkey" tag on a wallet event */
28
+ export function setPrivateKey(privateKey) {
29
+ return modifyHiddenTags(setSingletonTag(["privkey", bytesToHex(privateKey)], true));
30
+ }
@@ -0,0 +1,10 @@
1
+ import { EventOperation } from "applesauce-factory";
2
+ /** Sets the relays for a nutzap info event */
3
+ export declare function setNutzapInfoRelays(relays: string[]): EventOperation;
4
+ /** Sets the mints for a nutzap info event */
5
+ export declare function setNutzapInfoMints(mints: Array<{
6
+ url: string;
7
+ units?: string[];
8
+ }>): EventOperation;
9
+ /** Sets the pubkey for a nutzap info event */
10
+ export declare function setNutzapInfoPubkey(pubkey: string): EventOperation;
@@ -0,0 +1,17 @@
1
+ import { modifyPublicTags } from "applesauce-factory/operations";
2
+ import { addNameValueTag, setSingletonTag } from "applesauce-factory/operations/tag";
3
+ /** Sets the relays for a nutzap info event */
4
+ export function setNutzapInfoRelays(relays) {
5
+ return modifyPublicTags(...relays.map((relay) => addNameValueTag(["relay", relay], false)));
6
+ }
7
+ /** Sets the mints for a nutzap info event */
8
+ export function setNutzapInfoMints(mints) {
9
+ return modifyPublicTags(...mints.map((mint) => {
10
+ const tag = mint.units ? ["mint", mint.url, ...mint.units] : ["mint", mint.url];
11
+ return addNameValueTag(tag, false);
12
+ }));
13
+ }
14
+ /** Sets the pubkey for a nutzap info event */
15
+ export function setNutzapInfoPubkey(pubkey) {
16
+ return modifyPublicTags(setSingletonTag(["pubkey", pubkey], true));
17
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "applesauce-wallet",
3
- "version": "1.0.0",
3
+ "version": "3.0.0",
4
4
  "description": "",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -30,15 +30,15 @@
30
30
  "require": "./dist/helpers/*.js",
31
31
  "types": "./dist/helpers/*.d.ts"
32
32
  },
33
- "./queries": {
34
- "import": "./dist/queries/index.js",
35
- "require": "./dist/queries/index.js",
36
- "types": "./dist/queries/index.d.ts"
33
+ "./models": {
34
+ "import": "./dist/models/index.js",
35
+ "require": "./dist/models/index.js",
36
+ "types": "./dist/models/index.d.ts"
37
37
  },
38
- "./queries/*": {
39
- "import": "./dist/queries/*.js",
40
- "require": "./dist/queries/*.js",
41
- "types": "./dist/queries/*.d.ts"
38
+ "./models/*": {
39
+ "import": "./dist/models/*.js",
40
+ "require": "./dist/models/*.js",
41
+ "types": "./dist/models/*.d.ts"
42
42
  },
43
43
  "./blueprints": {
44
44
  "import": "./dist/blueprints/index.js",
@@ -55,15 +55,10 @@
55
55
  "require": "./dist/operations/index.js",
56
56
  "types": "./dist/operations/index.d.ts"
57
57
  },
58
- "./operations/tag/*": {
59
- "import": "./dist/operations/tag/*.js",
60
- "require": "./dist/operations/tag/*.js",
61
- "types": "./dist/operations/tag/*.d.ts"
62
- },
63
- "./operations/event/*": {
64
- "import": "./dist/operations/event/*.js",
65
- "require": "./dist/operations/event/*.js",
66
- "types": "./dist/operations/event/*.d.ts"
58
+ "./operations/*": {
59
+ "import": "./dist/operations/*.js",
60
+ "require": "./dist/operations/*.js",
61
+ "types": "./dist/operations/*.d.ts"
67
62
  },
68
63
  "./actions": {
69
64
  "import": "./dist/actions/index.js",
@@ -80,18 +75,18 @@
80
75
  "@cashu/cashu-ts": "2.0.0-rc1",
81
76
  "@gandlaf21/bc-ur": "^1.1.12",
82
77
  "@noble/hashes": "^1.7.1",
83
- "applesauce-actions": "^1.0.0",
84
- "applesauce-core": "^1.0.0",
85
- "applesauce-factory": "^1.0.0",
86
- "nostr-tools": "^2.10.4",
78
+ "applesauce-actions": "^3.0.0",
79
+ "applesauce-core": "^3.0.0",
80
+ "applesauce-factory": "^3.0.0",
81
+ "nostr-tools": "^2.13",
87
82
  "rxjs": "^7.8.1"
88
83
  },
89
84
  "devDependencies": {
90
85
  "@hirez_io/observer-spy": "^2.2.0",
91
86
  "@types/debug": "^4.1.12",
92
- "applesauce-signers": "^1.0.0",
87
+ "applesauce-signers": "^3.0.0",
93
88
  "typescript": "^5.8.3",
94
- "vitest": "^3.1.1"
89
+ "vitest": "^3.2.3"
95
90
  },
96
91
  "funding": {
97
92
  "type": "lightning",
@@ -1,10 +0,0 @@
1
- import { SimpleSigner } from "applesauce-signers/signers/simple-signer";
2
- import type { NostrEvent } from "nostr-tools";
3
- export declare class FakeUser extends SimpleSigner {
4
- pubkey: string;
5
- event(data?: Partial<NostrEvent>): NostrEvent;
6
- note(content?: string, extra?: Partial<NostrEvent>): import("nostr-tools").Event;
7
- profile(profile: any, extra?: Partial<NostrEvent>): import("nostr-tools").Event;
8
- contacts(pubkeys?: string[]): import("nostr-tools").Event;
9
- list(tags?: string[][], extra?: Partial<NostrEvent>): import("nostr-tools").Event;
10
- }
@@ -1,31 +0,0 @@
1
- import { unixNow } from "applesauce-core/helpers";
2
- import { SimpleSigner } from "applesauce-signers/signers/simple-signer";
3
- import { finalizeEvent, getPublicKey, kinds } from "nostr-tools";
4
- export class FakeUser extends SimpleSigner {
5
- pubkey = getPublicKey(this.key);
6
- event(data) {
7
- return finalizeEvent({
8
- kind: data?.kind ?? kinds.ShortTextNote,
9
- content: data?.content || "",
10
- created_at: data?.created_at ?? unixNow(),
11
- tags: data?.tags || [],
12
- }, this.key);
13
- }
14
- note(content = "Hello World", extra) {
15
- return this.event({ kind: kinds.ShortTextNote, content, ...extra });
16
- }
17
- profile(profile, extra) {
18
- return this.event({ kind: kinds.Metadata, content: JSON.stringify({ ...profile }), ...extra });
19
- }
20
- contacts(pubkeys = []) {
21
- return this.event({ kind: kinds.Contacts, tags: pubkeys.map((p) => ["p", p]) });
22
- }
23
- list(tags = [], extra) {
24
- return this.event({
25
- kind: kinds.Bookmarksets,
26
- content: "",
27
- tags: [["d", String(Math.round(Math.random() * 10000))], ...tags],
28
- ...extra,
29
- });
30
- }
31
- }
@@ -1 +0,0 @@
1
- export {};
@@ -1,139 +0,0 @@
1
- import { describe, it, expect, beforeEach, vitest } from "vitest";
2
- import { EventStore } from "applesauce-core";
3
- import { EventFactory } from "applesauce-factory";
4
- import { ActionHub } from "applesauce-actions";
5
- import { CheckStateEnum } from "@cashu/cashu-ts";
6
- import { subscribeSpyTo } from "@hirez_io/observer-spy";
7
- import { FakeUser } from "../../__tests__/fake-user.js";
8
- import { ConsolidateTokens } from "../tokens.js";
9
- import { WalletTokenBlueprint } from "../../blueprints/tokens.js";
10
- import { unlockTokenContent, WALLET_TOKEN_KIND } from "../../helpers/tokens.js";
11
- // Update the mock to allow controlling the states
12
- const mockCheckProofsStates = vitest.fn();
13
- vitest.mock("@cashu/cashu-ts", () => ({
14
- CashuMint: vitest.fn(),
15
- CashuWallet: vitest.fn().mockImplementation(() => ({
16
- checkProofsStates: mockCheckProofsStates,
17
- })),
18
- CheckStateEnum: { UNSPENT: "UNSPENT", SPENT: "SPENT" },
19
- }));
20
- const user = new FakeUser();
21
- const testMint = "https://mint.test.com";
22
- let events;
23
- let factory;
24
- let hub;
25
- beforeEach(() => {
26
- events = new EventStore();
27
- factory = new EventFactory({ signer: user });
28
- hub = new ActionHub(events, factory);
29
- // Reset the mock before each test
30
- mockCheckProofsStates.mockReset();
31
- });
32
- describe("ConsolidateTokens", () => {
33
- it("should combine multiple token events into a single event", async () => {
34
- // Set all proofs to be unspent
35
- mockCheckProofsStates.mockResolvedValue([{ state: CheckStateEnum.UNSPENT }, { state: CheckStateEnum.UNSPENT }]);
36
- // Create two token events with different proofs
37
- const token1 = await factory.sign(await factory.create(WalletTokenBlueprint, {
38
- mint: testMint,
39
- proofs: [{ amount: 10, secret: "secret1", C: "C", id: "id" }],
40
- }));
41
- const token2 = await factory.sign(await factory.create(WalletTokenBlueprint, {
42
- mint: testMint,
43
- proofs: [{ amount: 20, secret: "secret2", C: "C", id: "id" }],
44
- }));
45
- // Add tokens to event store
46
- events.add(token1);
47
- events.add(token2);
48
- // Run consolidate action
49
- const spy = subscribeSpyTo(hub.exec(ConsolidateTokens));
50
- await spy.onComplete();
51
- // First event should be the new consolidated token
52
- expect(spy.getValueAt(0).kind).toBe(WALLET_TOKEN_KIND);
53
- // Extract token content and verify proofs were combined
54
- const content = await unlockTokenContent(spy.getValueAt(0), user);
55
- expect(content.proofs).toHaveLength(2);
56
- expect(content.proofs).toEqual(expect.arrayContaining([
57
- expect.objectContaining({ amount: 10, secret: "secret1" }),
58
- expect.objectContaining({ amount: 20, secret: "secret2" }),
59
- ]));
60
- expect(content.mint).toBe(testMint);
61
- });
62
- it("should handle duplicate proofs", async () => {
63
- // Set all proofs to be unspent
64
- mockCheckProofsStates.mockResolvedValue([{ state: CheckStateEnum.UNSPENT }, { state: CheckStateEnum.UNSPENT }]);
65
- // Create two token events with different proofs
66
- const token1 = await factory.sign(await factory.create(WalletTokenBlueprint, {
67
- mint: testMint,
68
- proofs: [{ amount: 10, secret: "secret1", C: "C", id: "id" }],
69
- }));
70
- const token2 = await factory.sign(await factory.create(WalletTokenBlueprint, {
71
- mint: testMint,
72
- proofs: [
73
- { amount: 20, secret: "secret2", C: "C", id: "id" },
74
- { amount: 10, secret: "secret1", C: "C", id: "id" },
75
- ],
76
- }));
77
- // Add tokens to event store
78
- events.add(token1);
79
- events.add(token2);
80
- // Run consolidate action
81
- const spy = subscribeSpyTo(hub.exec(ConsolidateTokens));
82
- await spy.onComplete();
83
- // First event should be the new consolidated token
84
- expect(spy.getValueAt(0).kind).toBe(WALLET_TOKEN_KIND);
85
- // Extract token content and verify proofs were combined
86
- const content = await unlockTokenContent(spy.getValueAt(0), user);
87
- expect(content.proofs).toHaveLength(2);
88
- expect(content.proofs).toEqual(expect.arrayContaining([
89
- expect.objectContaining({ amount: 10, secret: "secret1" }),
90
- expect.objectContaining({ amount: 20, secret: "secret2" }),
91
- ]));
92
- expect(content.mint).toBe(testMint);
93
- });
94
- it("should filter out spent proofs", async () => {
95
- // Create token events with multiple proofs
96
- const token1 = await factory.sign(await factory.create(WalletTokenBlueprint, {
97
- mint: testMint,
98
- proofs: [
99
- { amount: 10, secret: "secret1", C: "C", id: "id" },
100
- { amount: 20, secret: "secret2", C: "C", id: "id" },
101
- ],
102
- }, []));
103
- const token2 = await factory.sign(await factory.create(WalletTokenBlueprint, {
104
- mint: testMint,
105
- proofs: [
106
- { amount: 30, secret: "secret3", C: "C", id: "id" },
107
- { amount: 40, secret: "secret4", C: "C", id: "id" },
108
- ],
109
- }, []));
110
- // Add tokens to event store
111
- events.add(token1);
112
- events.add(token2);
113
- // Mock some proofs as spent
114
- mockCheckProofsStates.mockResolvedValue([
115
- { state: CheckStateEnum.UNSPENT }, // secret1
116
- { state: CheckStateEnum.SPENT }, // secret2
117
- { state: CheckStateEnum.SPENT }, // secret3
118
- { state: CheckStateEnum.UNSPENT }, // secret4
119
- ]);
120
- // Run consolidate action
121
- const spy = subscribeSpyTo(hub.exec(ConsolidateTokens));
122
- await spy.onComplete();
123
- // Verify the consolidated token only contains unspent proofs
124
- const content = await unlockTokenContent(spy.getValueAt(0), user);
125
- expect(content.proofs).toHaveLength(2);
126
- expect(content.proofs).toEqual(expect.arrayContaining([
127
- expect.objectContaining({ amount: 10, secret: "secret1" }),
128
- expect.objectContaining({ amount: 40, secret: "secret4" }),
129
- ]));
130
- expect(content.mint).toBe(testMint);
131
- // Verify checkProofsStates was called with all proofs
132
- expect(mockCheckProofsStates).toHaveBeenCalledWith(expect.arrayContaining([
133
- expect.objectContaining({ amount: 10, secret: "secret1" }),
134
- expect.objectContaining({ amount: 20, secret: "secret2" }),
135
- expect.objectContaining({ amount: 30, secret: "secret3" }),
136
- expect.objectContaining({ amount: 40, secret: "secret4" }),
137
- ]));
138
- });
139
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,56 +0,0 @@
1
- import { describe, it, expect, beforeEach, vitest } from "vitest";
2
- import { unlockHiddenTags } from "applesauce-core/helpers";
3
- import { lastValueFrom } from "rxjs";
4
- import { generateSecretKey } from "nostr-tools";
5
- import { EventStore } from "applesauce-core";
6
- import { EventFactory } from "applesauce-factory";
7
- import { ActionHub } from "applesauce-actions";
8
- import { bytesToHex } from "@noble/hashes/utils";
9
- import { FakeUser } from "../../__tests__/fake-user.js";
10
- import { WalletAddPrivateKey, CreateWallet } from "../wallet.js";
11
- import { getWalletPrivateKey, unlockWallet, WALLET_BACKUP_KIND } from "../../helpers/wallet.js";
12
- import { WalletBlueprint } from "../../blueprints/wallet.js";
13
- const user = new FakeUser();
14
- let events;
15
- let factory;
16
- let publish;
17
- let hub;
18
- beforeEach(() => {
19
- events = new EventStore();
20
- factory = new EventFactory({ signer: user });
21
- publish = vitest.fn().mockResolvedValue(undefined);
22
- hub = new ActionHub(events, factory, publish);
23
- });
24
- describe("CreateWallet", () => {
25
- it("should publish a wallet backup event", async () => {
26
- await hub.run(CreateWallet, ["https://mint.money.com"]);
27
- expect(publish).toHaveBeenCalledWith(expect.objectContaining({ kind: WALLET_BACKUP_KIND }));
28
- });
29
- it("should publish a wallet event with mints", async () => {
30
- const event = await lastValueFrom(hub.exec(CreateWallet, ["https://mint.money.com"]));
31
- const hiddenTags = await unlockHiddenTags(event, user);
32
- // the second call should be the wallet event
33
- expect(hiddenTags).toEqual(expect.arrayContaining([["mint", "https://mint.money.com"]]));
34
- });
35
- });
36
- describe("WalletAddPrivateKey", () => {
37
- it("should add a private key to an existing wallet event without a private key", async () => {
38
- const walletEvent = await factory.sign(await factory.create(WalletBlueprint, ["https://mint.money.com"]));
39
- await events.add(walletEvent);
40
- const privateKey = generateSecretKey();
41
- const updatedWallet = await lastValueFrom(hub.exec(WalletAddPrivateKey, privateKey));
42
- await unlockWallet(updatedWallet, user);
43
- const key = getWalletPrivateKey(updatedWallet);
44
- expect(key).toBeDefined();
45
- expect(bytesToHex(key)).toEqual(bytesToHex(privateKey));
46
- });
47
- it("should throw an error if a wallet event already has a private key", async () => {
48
- const walletEvent = await factory.sign(await factory.create(WalletBlueprint, ["https://mint.money.com"], generateSecretKey()));
49
- await events.add(walletEvent);
50
- await expect(hub.run(WalletAddPrivateKey, generateSecretKey())).rejects.toThrow("Wallet already has a private key");
51
- });
52
- it("should throw an error if the wallet event does not exist", async () => {
53
- const privateKey = generateSecretKey();
54
- await expect(hub.run(WalletAddPrivateKey, privateKey)).rejects.toThrow("Wallet does not exist");
55
- });
56
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,44 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import { getDecodedToken } from "@cashu/cashu-ts";
3
- import { lastValueFrom, map, take, timer } from "rxjs";
4
- import { subscribeSpyTo } from "@hirez_io/observer-spy";
5
- import { receiveAnimated, sendAnimated } from "../animated-qr.js";
6
- const tokenStr = "cashuBo2FteBtodHRwczovL3Rlc3RudXQuY2FzaHUuc3BhY2VhdWNzYXRhdIGiYWlIAJofKTJT5B5hcIOkYWEQYXN4QDA2M2VlYjgwZDZjODM4NWU4NzYwODVhN2E4NzdkOTkyY2U4N2QwNTRmY2RjYzNiODMwMzhjOWY3MmNmMDY1ZGVhY1ghAynsxZ-OuZfZDcqYTLPYfCqHO7jkGjn97aolgtSYhYykYWSjYWVYIGbp_9B9aztSlZxz7g6Tqx5M_1PuFzJCVEMOVd8XAF8wYXNYINTOU77ODcj28v04pq7ektdf6sq2XuxvMjVE0wK6jFolYXJYIM_gZnUGT5jDOyZiQ-2vG9zYnuWaY8vPoWGe_3sXvrvbpGFhBGFzeEA0MWMwZDk1YTU5ZjkxNTdlYTc5NTJlNGFlMzYzYTI3NTMxNTllNmQ1NGJiNzExMTg5ZDk5YjU1MmYzYjIzZTJiYWNYIQM-49gen_1nPchxbaAiKprVr78VmMRVpHH_Tu9P8TO5mGFko2FlWCB9j7rlpdBH_m7tNYnLpzPhn-nGmS1CcbUfnPzjxy6G92FzWCDdsby7fGM5324T5UEoV858YWzZ9MCY59KgKP362fJDfmFyWCDL73v4FRo7iMe83bfMuEy3RJPtC1Vr1jdOpw2-x-7EAaRhYQFhc3hANjRhZDI3NmExOGNmNDhiMDZmYjdiMGYwOWFiMTU4ZTA0ZmM0NmIxYzA4YzMyNjJlODUxNzZkYTMzMTgyYzQ3YWFjWCECMDpCbNbrgA9FcQEIYxobU7ik_pTl8sByPqHDmkY4azxhZKNhZVggqHGaff9M270EU8LGxRpG_G4rn2bMgjyk3hFFg78ZXRVhc1ggP6DsNsWykwKE94yZF23gpCyapcoqh6DDZdVu0lKn2Z5hclggmPKig-lObsuxi_1XCm7_Y_tqaCcqEDz8eCwVhJ8gq9M";
7
- const token = getDecodedToken(tokenStr);
8
- describe("sendAnimated", () => {
9
- it("should loop", async () => {
10
- const qr$ = sendAnimated(token, { interval: 0 });
11
- const spy = subscribeSpyTo(qr$);
12
- // wait 100ms
13
- await lastValueFrom(timer(100));
14
- // should not have competed
15
- expect(spy.receivedComplete()).toBeFalsy();
16
- spy.unsubscribe();
17
- });
18
- it("should emit parts", async () => {
19
- const qr$ = sendAnimated(token, { interval: 0 }).pipe(take(6));
20
- const spy = subscribeSpyTo(qr$);
21
- // wait 100ms
22
- await lastValueFrom(qr$);
23
- // should not have competed
24
- expect(spy.getValues()).toEqual(Array(6)
25
- .fill(0)
26
- .map((_, i) => expect.stringContaining(`ur:bytes/${i + 1}-6/`)));
27
- });
28
- });
29
- describe("receiveAnimated", () => {
30
- it("should decode animated qr", async () => {
31
- const qr$ = sendAnimated(token, { interval: 0 }).pipe(receiveAnimated, map((part) => (typeof part === "string" ? getDecodedToken(part) : part)));
32
- const spy = subscribeSpyTo(qr$);
33
- await lastValueFrom(qr$);
34
- expect(spy.getValues()).toEqual([
35
- expect.any(Number),
36
- expect.any(Number),
37
- expect.any(Number),
38
- expect.any(Number),
39
- expect.any(Number),
40
- expect.any(Number),
41
- token,
42
- ]);
43
- });
44
- });
@@ -1 +0,0 @@
1
- export {};