applesauce-wallet 0.12.0 → 2.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 (44) hide show
  1. package/README.md +1 -1
  2. package/dist/actions/tokens.js +1 -1
  3. package/dist/blueprints/history.d.ts +1 -2
  4. package/dist/blueprints/history.js +2 -2
  5. package/dist/blueprints/tokens.d.ts +1 -2
  6. package/dist/blueprints/tokens.js +2 -2
  7. package/dist/blueprints/wallet.d.ts +2 -3
  8. package/dist/blueprints/wallet.js +3 -3
  9. package/dist/helpers/history.js +3 -1
  10. package/dist/helpers/tokens.d.ts +1 -1
  11. package/dist/helpers/tokens.js +4 -2
  12. package/dist/helpers/wallet.js +4 -1
  13. package/dist/index.d.ts +1 -1
  14. package/dist/index.js +1 -1
  15. package/dist/models/history.d.ts +6 -0
  16. package/dist/models/history.js +21 -0
  17. package/dist/models/tokens.d.ts +6 -0
  18. package/dist/models/tokens.js +58 -0
  19. package/dist/{queries → models}/wallet.d.ts +3 -3
  20. package/dist/models/wallet.js +18 -0
  21. package/package.json +16 -16
  22. package/dist/__tests__/fake-user.d.ts +0 -10
  23. package/dist/__tests__/fake-user.js +0 -31
  24. package/dist/actions/__tests__/tokens.test.d.ts +0 -1
  25. package/dist/actions/__tests__/tokens.test.js +0 -139
  26. package/dist/actions/__tests__/wallet.test.d.ts +0 -1
  27. package/dist/actions/__tests__/wallet.test.js +0 -56
  28. package/dist/helpers/__tests__/animated-qr.test.d.ts +0 -1
  29. package/dist/helpers/__tests__/animated-qr.test.js +0 -44
  30. package/dist/helpers/__tests__/tokens.test.d.ts +0 -1
  31. package/dist/helpers/__tests__/tokens.test.js +0 -127
  32. package/dist/operations/event/__tests__/wallet.test.d.ts +0 -1
  33. package/dist/operations/event/__tests__/wallet.test.js +0 -25
  34. package/dist/operations/tag/__tests__/wallet.test.d.ts +0 -1
  35. package/dist/operations/tag/__tests__/wallet.test.js +0 -16
  36. package/dist/queries/__tests__/wallet.test.d.ts +0 -1
  37. package/dist/queries/__tests__/wallet.test.js +0 -30
  38. package/dist/queries/history.d.ts +0 -6
  39. package/dist/queries/history.js +0 -27
  40. package/dist/queries/tokens.d.ts +0 -6
  41. package/dist/queries/tokens.js +0 -64
  42. package/dist/queries/wallet.js +0 -21
  43. /package/dist/{queries → models}/index.d.ts +0 -0
  44. /package/dist/{queries → models}/index.js +0 -0
package/README.md CHANGED
@@ -1,3 +1,3 @@
1
1
  # Applesauce Wallet
2
2
 
3
- The `applesauce-wallet` package is a package of helpers, queries, blueprints and other useful stuff for [NIP-60](https://github.com/nostr-protocol/nips/blob/master/60.md) wallets and [NIP-61](https://github.com/nostr-protocol/nips/blob/master/61.md) nutzaps
3
+ The `applesauce-wallet` package is a package of helpers, models, blueprints and other useful stuff for [NIP-60](https://github.com/nostr-protocol/nips/blob/master/60.md) wallets and [NIP-61](https://github.com/nostr-protocol/nips/blob/master/61.md) nutzaps
@@ -66,7 +66,7 @@ export function CompleteSpend(spent, change) {
66
66
  /** Combines all unlocked token events into a single event per mint */
67
67
  export function ConsolidateTokens(opts) {
68
68
  return async function* ({ events, factory, self }) {
69
- const tokens = Array.from(events.getAll({ kinds: [WALLET_TOKEN_KIND], authors: [self] })).filter((token) => {
69
+ const tokens = Array.from(events.getByFilters({ kinds: [WALLET_TOKEN_KIND], authors: [self] })).filter((token) => {
70
70
  if (isTokenContentLocked(token)) {
71
71
  if (opts?.ignoreLocked)
72
72
  return false;
@@ -1,5 +1,4 @@
1
1
  import { EventPointer } from "nostr-tools/nip19";
2
2
  import { HistoryContent } from "../helpers/history.js";
3
- import { EventBlueprint } from "applesauce-factory";
4
3
  /** A blueprint that creates a wallet history event */
5
- export declare function WalletHistoryBlueprint(content: HistoryContent, redeemed: (string | EventPointer)[]): EventBlueprint;
4
+ export declare function WalletHistoryBlueprint(content: HistoryContent, redeemed: (string | EventPointer)[]): import("applesauce-factory").EventBlueprint;
@@ -1,9 +1,9 @@
1
+ import { blueprint } from "applesauce-factory";
1
2
  import { WALLET_HISTORY_KIND } from "../helpers/history.js";
2
- import { EventFactory } from "applesauce-factory";
3
3
  import { setHistoryContent, setHistoryRedeemed } from "../operations/event/history.js";
4
4
  /** A blueprint that creates a wallet history event */
5
5
  export function WalletHistoryBlueprint(content, redeemed) {
6
- return (ctx) => EventFactory.runProcess({ kind: WALLET_HISTORY_KIND }, ctx,
6
+ return blueprint(WALLET_HISTORY_KIND,
7
7
  // set the encrypted tags on the event
8
8
  setHistoryContent(content),
9
9
  // set the public redeemed tags
@@ -1,8 +1,7 @@
1
1
  import { Token } from "@cashu/cashu-ts";
2
- import { EventBlueprint } from "applesauce-factory";
3
2
  /**
4
3
  * A blueprint for a wallet token event, takes a cashu token and previous deleted token event ids
5
4
  * @param token the cashu token to store
6
5
  * @param [del=[]] an array of previous token event ids that are deleted
7
6
  */
8
- export declare function WalletTokenBlueprint(token: Token, del?: string[]): EventBlueprint;
7
+ export declare function WalletTokenBlueprint(token: Token, del?: string[]): import("applesauce-factory").EventBlueprint;
@@ -1,4 +1,4 @@
1
- import { EventFactory } from "applesauce-factory";
1
+ import { blueprint } from "applesauce-factory";
2
2
  import { WALLET_TOKEN_KIND } from "../helpers/tokens.js";
3
3
  import { setTokenContent } from "../operations/event/tokens.js";
4
4
  /**
@@ -7,5 +7,5 @@ import { setTokenContent } from "../operations/event/tokens.js";
7
7
  * @param [del=[]] an array of previous token event ids that are deleted
8
8
  */
9
9
  export function WalletTokenBlueprint(token, del = []) {
10
- return (ctx) => EventFactory.runProcess({ kind: WALLET_TOKEN_KIND }, ctx, setTokenContent(token, del));
10
+ return blueprint(WALLET_TOKEN_KIND, setTokenContent(token, del));
11
11
  }
@@ -1,6 +1,5 @@
1
- import { EventBlueprint } from "applesauce-factory";
2
1
  import { NostrEvent } from "nostr-tools";
3
2
  /** A blueprint to create a new 17375 wallet */
4
- export declare function WalletBlueprint(mints: string[], privateKey?: Uint8Array): EventBlueprint;
3
+ export declare function WalletBlueprint(mints: string[], privateKey?: Uint8Array): import("applesauce-factory").EventBlueprint;
5
4
  /** A blueprint that creates a new 375 wallet backup event */
6
- export declare function WalletBackupBlueprint(wallet: NostrEvent): EventBlueprint;
5
+ export declare function WalletBackupBlueprint(wallet: NostrEvent): import("applesauce-factory").EventBlueprint;
@@ -1,13 +1,13 @@
1
- import { EventFactory } from "applesauce-factory";
1
+ import { blueprint } from "applesauce-factory";
2
2
  import { modifyHiddenTags } from "applesauce-factory/operations/event";
3
3
  import { WALLET_BACKUP_KIND, WALLET_KIND } from "../helpers/wallet.js";
4
4
  import { setWalletBackupContent } from "../operations/event/wallet.js";
5
5
  import { setMintTags, setPrivateKeyTag } from "../operations/tag/wallet.js";
6
6
  /** A blueprint to create a new 17375 wallet */
7
7
  export function WalletBlueprint(mints, privateKey) {
8
- return (ctx) => EventFactory.runProcess({ kind: WALLET_KIND }, ctx, modifyHiddenTags(privateKey ? setPrivateKeyTag(privateKey) : undefined, setMintTags(mints)));
8
+ return blueprint(WALLET_KIND, modifyHiddenTags(privateKey ? setPrivateKeyTag(privateKey) : undefined, setMintTags(mints)));
9
9
  }
10
10
  /** A blueprint that creates a new 375 wallet backup event */
11
11
  export function WalletBackupBlueprint(wallet) {
12
- return (ctx) => EventFactory.runProcess({ kind: WALLET_BACKUP_KIND }, ctx, setWalletBackupContent(wallet));
12
+ return blueprint(WALLET_BACKUP_KIND, setWalletBackupContent(wallet));
13
13
  }
@@ -1,5 +1,7 @@
1
- import { getHiddenTags, getOrComputeCachedValue, isETag, isHiddenContentLocked, isHiddenTagsLocked, lockHiddenTags, unlockHiddenTags, } from "applesauce-core/helpers";
1
+ import { getHiddenTags, getOrComputeCachedValue, isETag, isHiddenContentLocked, isHiddenTagsLocked, lockHiddenTags, setHiddenTagsEncryptionMethod, unlockHiddenTags, } from "applesauce-core/helpers";
2
2
  export const WALLET_HISTORY_KIND = 7376;
3
+ // Enable hidden content for wallet history kind
4
+ setHiddenTagsEncryptionMethod(WALLET_HISTORY_KIND, "nip44");
3
5
  export const HistoryContentSymbol = Symbol.for("history-content");
4
6
  /** returns an array of redeemed event ids in a history event */
5
7
  export function getHistoryRedeemed(history) {
@@ -1,11 +1,11 @@
1
1
  import { Proof, Token } from "@cashu/cashu-ts";
2
2
  import { HiddenContentSigner } from "applesauce-core/helpers";
3
3
  import { NostrEvent } from "nostr-tools";
4
+ export declare const WALLET_TOKEN_KIND = 7375;
4
5
  /** Internal method for creating a unique id for each proof */
5
6
  export declare function getProofUID(proof: Proof): string;
6
7
  /** Internal method to filter out duplicate proofs */
7
8
  export declare function ignoreDuplicateProofs(seen?: Set<string>): (proof: Proof) => boolean;
8
- export declare const WALLET_TOKEN_KIND = 7375;
9
9
  export type TokenContent = {
10
10
  /** Cashu mint for the proofs */
11
11
  mint: string;
@@ -1,5 +1,8 @@
1
1
  import { getDecodedToken, getEncodedToken } from "@cashu/cashu-ts";
2
- import { getHiddenContent, getOrComputeCachedValue, isHiddenContentLocked, isHiddenTagsLocked, lockHiddenContent, unlockHiddenContent, } from "applesauce-core/helpers";
2
+ import { getHiddenContent, getOrComputeCachedValue, isHiddenContentLocked, isHiddenTagsLocked, lockHiddenContent, setHiddenContentEncryptionMethod, unlockHiddenContent, } from "applesauce-core/helpers";
3
+ export const WALLET_TOKEN_KIND = 7375;
4
+ // Enable hidden content for wallet token kind
5
+ setHiddenContentEncryptionMethod(WALLET_TOKEN_KIND, "nip44");
3
6
  /** Internal method for creating a unique id for each proof */
4
7
  export function getProofUID(proof) {
5
8
  return proof.id + proof.amount + proof.C + proof.secret;
@@ -16,7 +19,6 @@ export function ignoreDuplicateProofs(seen = new Set()) {
16
19
  }
17
20
  };
18
21
  }
19
- export const WALLET_TOKEN_KIND = 7375;
20
22
  export const TokenContentSymbol = Symbol.for("token-content");
21
23
  /**
22
24
  * Returns the decrypted and parsed details of a 7375 token event
@@ -1,7 +1,10 @@
1
1
  import { hexToBytes } from "@noble/hashes/utils";
2
- import { getHiddenTags, getOrComputeCachedValue, isHiddenTagsLocked, lockHiddenTags, unlockHiddenTags, } from "applesauce-core/helpers";
2
+ import { getHiddenTags, getOrComputeCachedValue, isHiddenTagsLocked, lockHiddenTags, setHiddenTagsEncryptionMethod, unlockHiddenTags, } from "applesauce-core/helpers";
3
3
  export const WALLET_KIND = 17375;
4
4
  export const WALLET_BACKUP_KIND = 375;
5
+ // Enable hidden content for wallet kinds
6
+ setHiddenTagsEncryptionMethod(WALLET_KIND, "nip44");
7
+ setHiddenTagsEncryptionMethod(WALLET_BACKUP_KIND, "nip44");
5
8
  export const WalletPrivateKeySymbol = Symbol.for("wallet-private-key");
6
9
  export const WalletMintsSymbol = Symbol.for("wallet-mints");
7
10
  /** Returns if a wallet is locked */
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export * as Actions from "./actions/index.js";
2
2
  export * as Blueprints from "./blueprints/index.js";
3
3
  export * as Helpers from "./helpers/index.js";
4
- export * as Queries from "./queries/index.js";
4
+ export * as Models from "./models/index.js";
5
5
  export * from "./operations/index.js";
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  export * as Actions from "./actions/index.js";
2
2
  export * as Blueprints from "./blueprints/index.js";
3
3
  export * as Helpers from "./helpers/index.js";
4
- export * as Queries from "./queries/index.js";
4
+ export * as Models from "./models/index.js";
5
5
  export * from "./operations/index.js";
@@ -0,0 +1,6 @@
1
+ import { Model } from "applesauce-core";
2
+ import { NostrEvent } from "nostr-tools";
3
+ /** A model that returns an array of redeemed event ids for a wallet */
4
+ export declare function WalletRedeemedModel(pubkey: string): Model<string[]>;
5
+ /** A model that returns a timeline of wallet history events */
6
+ export declare function WalletHistoryModel(pubkey: string, locked?: boolean | undefined): Model<NostrEvent[]>;
@@ -0,0 +1,21 @@
1
+ import { combineLatest, filter, map, scan, startWith } from "rxjs";
2
+ import { getHistoryRedeemed, isHistoryContentLocked, WALLET_HISTORY_KIND } from "../helpers/history.js";
3
+ /** A model that returns an array of redeemed event ids for a wallet */
4
+ export function WalletRedeemedModel(pubkey) {
5
+ return (events) => events
6
+ .filters({ kinds: [WALLET_HISTORY_KIND], authors: [pubkey] })
7
+ .pipe(scan((ids, history) => [...ids, ...getHistoryRedeemed(history)], []));
8
+ }
9
+ /** A model that returns a timeline of wallet history events */
10
+ export function WalletHistoryModel(pubkey, locked) {
11
+ return (events) => {
12
+ const updates = events.update$.pipe(filter((e) => e.kind === WALLET_HISTORY_KIND && e.pubkey === pubkey), startWith(undefined));
13
+ const timeline = events.timeline({ kinds: [WALLET_HISTORY_KIND], authors: [pubkey] });
14
+ return combineLatest([updates, timeline]).pipe(map(([_, history]) => {
15
+ if (locked === undefined)
16
+ return history;
17
+ else
18
+ return history.filter((entry) => isHistoryContentLocked(entry) === locked);
19
+ }));
20
+ };
21
+ }
@@ -0,0 +1,6 @@
1
+ import { Model } from "applesauce-core";
2
+ import { NostrEvent } from "nostr-tools";
3
+ /** A model that subscribes to all token events for a wallet, passing locked will filter by token locked status */
4
+ export declare function WalletTokensModel(pubkey: string, locked?: boolean | undefined): Model<NostrEvent[]>;
5
+ /** A model that returns the visible balance of a wallet for each mint */
6
+ export declare function WalletBalanceModel(pubkey: string): Model<Record<string, number>>;
@@ -0,0 +1,58 @@
1
+ import { combineLatest, filter, map, startWith } from "rxjs";
2
+ import { getTokenContent, ignoreDuplicateProofs, isTokenContentLocked, WALLET_TOKEN_KIND } from "../helpers/tokens.js";
3
+ /** removes deleted events from sorted array */
4
+ function filterDeleted(tokens) {
5
+ const deleted = new Set();
6
+ return Array.from(tokens)
7
+ .reverse()
8
+ .filter((token) => {
9
+ // skip this event if it a newer event says its deleted
10
+ if (deleted.has(token.id))
11
+ return false;
12
+ // add ids to deleted array
13
+ if (!isTokenContentLocked(token)) {
14
+ const details = getTokenContent(token);
15
+ for (const id of details.del)
16
+ deleted.add(id);
17
+ }
18
+ return true;
19
+ })
20
+ .reverse();
21
+ }
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
+ return (events) => {
25
+ const updates = events.update$.pipe(filter((e) => e.kind === WALLET_TOKEN_KIND && e.pubkey === pubkey), startWith(undefined));
26
+ const timeline = events.timeline({ kinds: [WALLET_TOKEN_KIND], authors: [pubkey] });
27
+ return combineLatest([updates, timeline]).pipe(
28
+ // filter out locked tokens
29
+ map(([_, tokens]) => {
30
+ if (locked === undefined)
31
+ return tokens;
32
+ else
33
+ return tokens.filter((t) => isTokenContentLocked(t) === locked);
34
+ }),
35
+ // remove deleted events
36
+ map(filterDeleted));
37
+ };
38
+ }
39
+ /** A model that returns the visible balance of a wallet for each mint */
40
+ export function WalletBalanceModel(pubkey) {
41
+ return (events) => {
42
+ const updates = events.update$.pipe(filter((e) => e.kind === WALLET_TOKEN_KIND && e.pubkey === pubkey), startWith(undefined));
43
+ const timeline = events.timeline({ kinds: [WALLET_TOKEN_KIND], authors: [pubkey] });
44
+ return combineLatest([updates, timeline]).pipe(map(([_, tokens]) => tokens.filter((t) => !isTokenContentLocked(t))),
45
+ // filter out deleted tokens
46
+ map(filterDeleted),
47
+ // map tokens to totals
48
+ map((tokens) => {
49
+ // ignore duplicate proofs
50
+ const seen = new Set();
51
+ return tokens.reduce((totals, token) => {
52
+ const details = getTokenContent(token);
53
+ const total = details.proofs.filter(ignoreDuplicateProofs(seen)).reduce((t, p) => t + p.amount, 0);
54
+ return { ...totals, [details.mint]: (totals[details.mint] ?? 0) + total };
55
+ }, {});
56
+ }));
57
+ };
58
+ }
@@ -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>;
@@ -0,0 +1,18 @@
1
+ import { filter, map, merge } from "rxjs";
2
+ import { getWalletMints, getWalletPrivateKey, isWalletLocked, WALLET_KIND } from "../helpers/wallet.js";
3
+ /** A model to get the state of a NIP-60 wallet */
4
+ export function WalletModel(pubkey) {
5
+ return (events) => merge(
6
+ // get the latest replaceable event
7
+ events.replaceable(WALLET_KIND, pubkey),
8
+ // and listen for any updates to matching events
9
+ events.update$.pipe(filter((e) => e.kind === WALLET_KIND && e.pubkey === pubkey))).pipe(map((wallet) => {
10
+ if (!wallet)
11
+ return;
12
+ if (isWalletLocked(wallet))
13
+ return { locked: true, event: wallet };
14
+ const mints = getWalletMints(wallet);
15
+ const privateKey = getWalletPrivateKey(wallet);
16
+ return { locked: false, mints, privateKey, event: wallet };
17
+ }));
18
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "applesauce-wallet",
3
- "version": "0.12.0",
3
+ "version": "2.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",
@@ -80,18 +80,18 @@
80
80
  "@cashu/cashu-ts": "2.0.0-rc1",
81
81
  "@gandlaf21/bc-ur": "^1.1.12",
82
82
  "@noble/hashes": "^1.7.1",
83
- "applesauce-actions": "^0.12.0",
84
- "applesauce-core": "^0.12.0",
85
- "applesauce-factory": "^0.12.0",
86
- "nostr-tools": "^2.10.4",
83
+ "applesauce-actions": "^2.0.0",
84
+ "applesauce-core": "^2.0.0",
85
+ "applesauce-factory": "^2.0.0",
86
+ "nostr-tools": "^2.13",
87
87
  "rxjs": "^7.8.1"
88
88
  },
89
89
  "devDependencies": {
90
90
  "@hirez_io/observer-spy": "^2.2.0",
91
91
  "@types/debug": "^4.1.12",
92
- "applesauce-signers": "^0.12.0",
93
- "typescript": "^5.7.3",
94
- "vitest": "^3.0.5"
92
+ "applesauce-signers": "^2.0.0",
93
+ "typescript": "^5.8.3",
94
+ "vitest": "^3.2.3"
95
95
  },
96
96
  "funding": {
97
97
  "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 {};
@@ -1,127 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import { EventFactory } from "applesauce-factory";
3
- import { FakeUser } from "../../__tests__/fake-user.js";
4
- import { WalletTokenBlueprint } from "../../blueprints/tokens.js";
5
- import { decodeTokenFromEmojiString, dumbTokenSelection, encodeTokenToEmoji, unlockTokenContent } from "../tokens.js";
6
- import { HiddenContentSymbol, unixNow } from "applesauce-core/helpers";
7
- const user = new FakeUser();
8
- const factory = new EventFactory({ signer: user });
9
- describe("dumbTokenSelection", () => {
10
- it("should select old tokens first", async () => {
11
- const a = await user.signEvent(await factory.create(WalletTokenBlueprint, {
12
- mint: "https://money.com",
13
- proofs: [{ secret: "A", C: "A", id: "A", amount: 100 }],
14
- }));
15
- await unlockTokenContent(a, user);
16
- const bDraft = await factory.create(WalletTokenBlueprint, {
17
- mint: "https://money.com",
18
- proofs: [{ secret: "B", C: "B", id: "B", amount: 50 }],
19
- });
20
- bDraft.created_at -= 60 * 60 * 7;
21
- const b = await user.signEvent(bDraft);
22
- await unlockTokenContent(b, user);
23
- expect(dumbTokenSelection([a, b], 40).events).toEqual([b]);
24
- });
25
- it("should select enough tokens to total min amount", async () => {
26
- const a = await user.signEvent(await factory.create(WalletTokenBlueprint, {
27
- mint: "https://money.com",
28
- proofs: [{ secret: "A", C: "A", id: "A", amount: 100 }],
29
- }));
30
- await unlockTokenContent(a, user);
31
- const bDraft = await factory.create(WalletTokenBlueprint, {
32
- mint: "https://money.com",
33
- proofs: [{ secret: "B", C: "B", id: "B", amount: 50 }],
34
- });
35
- bDraft.created_at -= 60 * 60 * 7;
36
- const b = await user.signEvent(bDraft);
37
- await unlockTokenContent(b, user);
38
- expect(dumbTokenSelection([a, b], 120).events).toEqual(expect.arrayContaining([a, b]));
39
- });
40
- it("should throw if not enough funds", async () => {
41
- const a = await user.signEvent(await factory.create(WalletTokenBlueprint, {
42
- mint: "https://money.com",
43
- proofs: [{ secret: "A", C: "A", id: "A", amount: 100 }],
44
- }));
45
- await unlockTokenContent(a, user);
46
- expect(() => dumbTokenSelection([a], 120)).toThrow();
47
- });
48
- it("should ignore locked tokens", async () => {
49
- const a = await user.signEvent(await factory.create(WalletTokenBlueprint, {
50
- mint: "https://money.com",
51
- proofs: [{ secret: "A", C: "A", id: "A", amount: 100 }],
52
- }));
53
- await unlockTokenContent(a, user);
54
- const bDraft = await factory.create(WalletTokenBlueprint, {
55
- mint: "https://money.com",
56
- proofs: [{ secret: "B", C: "B", id: "B", amount: 50 }],
57
- });
58
- bDraft.created_at -= 60 * 60 * 7;
59
- const b = await user.signEvent(bDraft);
60
- // manually remove the hidden content to lock it again
61
- Reflect.deleteProperty(b, HiddenContentSymbol);
62
- expect(dumbTokenSelection([a, b], 20).events).toEqual([a]);
63
- });
64
- it("should ignore duplicate proofs", async () => {
65
- const a = await user.signEvent(await factory.create(WalletTokenBlueprint, {
66
- mint: "https://money.com",
67
- proofs: [{ secret: "A", C: "A", id: "A", amount: 100 }],
68
- }));
69
- // create a second event with the same proofs
70
- const b = await user.signEvent(await factory.create(WalletTokenBlueprint, {
71
- mint: "https://money.com",
72
- proofs: [{ secret: "A", C: "A", id: "A", amount: 100 }],
73
- }));
74
- expect(() => dumbTokenSelection([a, b], 150)).toThrow();
75
- });
76
- it("should include duplicate token events and ignore duplicate proofs", async () => {
77
- const A = { secret: "A", C: "A", id: "A", amount: 100 };
78
- const a = await user.signEvent({
79
- ...(await factory.create(WalletTokenBlueprint, {
80
- mint: "https://money.com",
81
- proofs: [A],
82
- })),
83
- // make event older
84
- created_at: unixNow() - 100,
85
- });
86
- // create a second event with the same proofs
87
- const a2 = await user.signEvent({
88
- ...(await factory.create(WalletTokenBlueprint, {
89
- mint: "https://money.com",
90
- proofs: [A],
91
- })),
92
- // make event older
93
- created_at: a.created_at - 200,
94
- });
95
- const B = { secret: "B", C: "B", id: "B", amount: 50 };
96
- const b = await user.signEvent(await factory.create(WalletTokenBlueprint, {
97
- mint: "https://money.com",
98
- proofs: [B],
99
- }));
100
- const result = dumbTokenSelection([a, a2, b], 150);
101
- expect(result.events.map((e) => e.id)).toEqual(expect.arrayContaining([a.id, a2.id, b.id]));
102
- expect(result.proofs).toEqual([A, B]);
103
- });
104
- });
105
- describe("encodeTokenToEmoji", () => {
106
- it("should encode token into emoji string", () => {
107
- const token = "cashuBo2FteBtodHRwczovL3Rlc3RudXQuY2FzaHUuc3BhY2VhdWNzYXRhdIGiYWlIAJofKTJT5B5hcIGkYWEBYXN4QDdlZDBkMzk3NGQ5ZWM2OTc2YTAzYmZmYjdkMTA4NzIzZTBiMDRjMzRhNDc3MjlmNjMwOGJlODc3OTA2NTY0NDVhY1ghA36iYyOHCe4CnTxzORbcXFVeAbkMUFE6FqPWInujnAOcYWSjYWVYIJmHRwCQ0Uopkd3P5xb0MdcWQEaZz9hXWtcn-FMhZj8LYXNYIF4X9ybXxg5Pp0KSowfu4y_Aovo9iy3TXlLSaKyVJzz2YXJYIC_UFkoC5U9BpSgBTGUQgsjfz_emv5xykDiavZUfRN8E";
108
- expect(encodeTokenToEmoji(token).length).toBeGreaterThan(token.length);
109
- });
110
- });
111
- const emoji = "🥜󠅓󠅑󠅣󠅘󠅥󠄲󠅟󠄢󠄶󠅤󠅕󠄲󠅤󠅟󠅔󠄸󠅂󠅧󠅓󠅪󠅟󠅦󠄼󠄣󠅂󠅜󠅓󠄣󠅂󠅥󠅔󠅈󠅁󠅥󠅉󠄢󠄶󠅪󠅑󠄸󠅅󠅥󠅓󠄣󠄲󠅘󠅉󠄢󠅆󠅘󠅔󠅇󠄾󠅪󠅉󠅈󠅂󠅘󠅔󠄹󠄷󠅙󠅉󠅇󠅜󠄹󠄱󠄺󠅟󠅖󠄻󠅄󠄺󠅄󠄥󠄲󠄥󠅘󠅓󠄹󠄷󠅛󠅉󠅇󠄵󠄲󠅉󠅈󠄾󠄤󠅁󠄴󠅔󠅜󠅊󠄴󠄲󠅛󠄽󠅪󠅛󠄣󠄾󠄷󠅁󠄥󠅊󠅇󠄽󠄢󠄿󠅄󠅓󠄢󠅉󠅄󠄱󠅪󠅉󠅝󠅊󠅝󠅉󠅚󠅔󠅛󠄽󠅄󠄱󠄤󠄾󠅪󠄹󠅪󠅊󠅄󠄲󠅙󠄽󠄴󠅂󠅚󠄽󠅪󠅂󠅘󠄾󠄴󠅓󠄣󠄽󠅚󠅜󠅝󠄾󠅚󠄽󠅧󠄿󠄷󠄺󠅜󠄿󠄴󠅓󠄣󠄿󠅄󠄱󠄢󠄾󠅄󠅉󠄠󠄾󠄴󠅆󠅘󠅉󠄡󠅗󠅘󠄱󠄣󠄦󠅙󠅉󠅩󠄿󠄸󠄳󠅕󠄤󠄳󠅞󠅄󠅨󠅪󠄿󠅂󠅒󠅓󠅈󠄶󠅆󠅕󠄱󠅒󠅛󠄽󠅅󠄶󠄵󠄦󠄶󠅡󠅀󠅇󠄹󠅞󠅥󠅚󠅞󠄱󠄿󠅓󠅉󠅇󠅃󠅚󠅉󠅇󠅆󠅉󠄹󠄺󠅝󠄸󠅂󠅧󠄳󠅁󠄠󠅅󠅟󠅠󠅛󠅔󠄣󠅀󠄥󠅨󠅒󠄠󠄽󠅔󠅓󠅇󠅁󠄵󠅑󠅊󠅪󠄩󠅘󠅈󠅇󠅤󠅓󠅞󠄝󠄶󠄽󠅘󠅊󠅚󠄨󠄼󠅉󠅈󠄾󠅉󠄹󠄶󠄤󠅈󠄩󠅩󠅒󠅈󠅨󠅗󠄥󠅀󠅠󠄠󠄻󠅃󠅟󠅧󠅖󠅥󠄤󠅩󠅏󠄱󠅟󠅦󠅟󠄩󠅙󠅩󠄣󠅄󠅈󠅜󠄼󠅃󠅑󠄻󠅩󠅆󠄺󠅪󠅪󠄢󠅉󠅈󠄺󠅉󠄹󠄳󠅏󠅅󠄶󠅛󠅟󠄳󠄥󠅅󠄩󠄲󠅠󠅃󠅗󠄲󠅄󠄷󠅅󠅁󠅗󠅣󠅚󠅖󠅪󠅏󠅕󠅝󠅦󠄥󠅨󠅩󠅛󠄴󠅙󠅑󠅦󠅊󠅅󠅖󠅂󠄾󠄨󠄵";
112
- describe("decodeTokenFromEmojiString", () => {
113
- it("should decode single emoji", () => {
114
- expect(decodeTokenFromEmojiString(emoji)).toEqual(expect.objectContaining({
115
- mint: "https://testnut.cashu.space",
116
- proofs: [expect.any(Object)],
117
- unit: "sat",
118
- }));
119
- });
120
- it("should decode an emoji in text", () => {
121
- expect(decodeTokenFromEmojiString("the money is in the emoji, " + emoji + " you can redeem it using cashu.me")).toEqual(expect.objectContaining({
122
- mint: "https://testnut.cashu.space",
123
- proofs: [expect.any(Object)],
124
- unit: "sat",
125
- }));
126
- });
127
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,25 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import { setWalletBackupContent } from "../wallet.js";
3
- import { EventFactory } from "applesauce-factory";
4
- import { generateSecretKey } from "nostr-tools";
5
- import { unixNow } from "applesauce-core/helpers";
6
- import { WALLET_BACKUP_KIND } from "../../../helpers/wallet.js";
7
- import { FakeUser } from "../../../__tests__/fake-user.js";
8
- import { WalletBlueprint } from "../../../blueprints/wallet.js";
9
- const user = new FakeUser();
10
- const factory = new EventFactory({ signer: user });
11
- describe("setWalletBackupContent", () => {
12
- it("should throw if kind is not wallet kind", async () => {
13
- const note = user.note();
14
- await expect(setWalletBackupContent(note)({ kind: WALLET_BACKUP_KIND, tags: [], created_at: unixNow(), content: "" }, factory.context)).rejects.toThrow();
15
- });
16
- it("should throw if pubkey does not match", async () => {
17
- const wallet = await factory.sign(await factory.create(WalletBlueprint, [], generateSecretKey()));
18
- const user2 = new FakeUser();
19
- await expect(setWalletBackupContent(wallet)({ kind: WALLET_BACKUP_KIND, tags: [], created_at: unixNow(), content: "" }, { signer: user2 })).rejects.toThrow();
20
- });
21
- it("should copy the content of the wallet event", async () => {
22
- const wallet = await factory.sign(await factory.create(WalletBlueprint, [], generateSecretKey()));
23
- expect(await setWalletBackupContent(wallet)({ kind: WALLET_BACKUP_KIND, tags: [], created_at: unixNow(), content: "" }, factory.context)).toEqual(expect.objectContaining({ content: wallet.content }));
24
- });
25
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,16 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
- import { setMintTags } from "../wallet.js";
3
- describe("setMintTags", () => {
4
- it("should replace existing mint tags", () => {
5
- expect(setMintTags(["https://mint.com"])([["mint", "https://other.mint.com"]], {})).toEqual(expect.arrayContaining([["mint", "https://mint.com"]]));
6
- });
7
- it("should ignore other tags", () => {
8
- expect(setMintTags(["https://mint.com"])([
9
- ["mint", "https://other.mint.com"],
10
- ["privkey", "00000000"],
11
- ], {})).toEqual(expect.arrayContaining([
12
- ["mint", "https://mint.com"],
13
- ["privkey", "00000000"],
14
- ]));
15
- });
16
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,30 +0,0 @@
1
- import { EventStore, QueryStore } from "applesauce-core";
2
- import { beforeEach, describe, expect, it } from "vitest";
3
- import { EventFactory } from "applesauce-factory";
4
- import { generateSecretKey } from "nostr-tools";
5
- import { subscribeSpyTo } from "@hirez_io/observer-spy";
6
- import { FakeUser } from "../../__tests__/fake-user.js";
7
- import { WalletBlueprint } from "../../blueprints/wallet.js";
8
- import { WalletQuery } from "../wallet.js";
9
- import { lockWallet, unlockWallet } from "../../helpers/wallet.js";
10
- const user = new FakeUser();
11
- const factory = new EventFactory({ signer: user });
12
- let events;
13
- let queries;
14
- beforeEach(() => {
15
- events = new EventStore();
16
- queries = new QueryStore(events);
17
- });
18
- describe("WalletQuery", () => {
19
- it("it should update when event is unlocked", async () => {
20
- const wallet = await user.signEvent(await factory.create(WalletBlueprint, [], generateSecretKey()));
21
- lockWallet(wallet);
22
- events.add(wallet);
23
- const spy = subscribeSpyTo(queries.createQuery(WalletQuery, await user.getPublicKey()));
24
- await unlockWallet(wallet, user);
25
- expect(spy.getValues()).toEqual([
26
- expect.objectContaining({ locked: true }),
27
- expect.objectContaining({ locked: false }),
28
- ]);
29
- });
30
- });
@@ -1,6 +0,0 @@
1
- import { Query } from "applesauce-core";
2
- import { NostrEvent } from "nostr-tools";
3
- /** Query that returns an array of redeemed event ids for a wallet */
4
- export declare function WalletRedeemedQuery(pubkey: string): Query<string[]>;
5
- /** A query that returns a timeline of wallet history events */
6
- export declare function WalletHistoryQuery(pubkey: string, locked?: boolean | undefined): Query<NostrEvent[]>;
@@ -1,27 +0,0 @@
1
- import { combineLatest, filter, map, scan, startWith } from "rxjs";
2
- import { getHistoryRedeemed, isHistoryContentLocked, WALLET_HISTORY_KIND } from "../helpers/history.js";
3
- /** Query that returns an array of redeemed event ids for a wallet */
4
- export function WalletRedeemedQuery(pubkey) {
5
- return {
6
- key: pubkey,
7
- run: (events) => events
8
- .filters({ kinds: [WALLET_HISTORY_KIND], authors: [pubkey] })
9
- .pipe(scan((ids, history) => [...ids, ...getHistoryRedeemed(history)], [])),
10
- };
11
- }
12
- /** A query that returns a timeline of wallet history events */
13
- export function WalletHistoryQuery(pubkey, locked) {
14
- return {
15
- key: pubkey,
16
- run: (events) => {
17
- const updates = events.updates.pipe(filter((e) => e.kind === WALLET_HISTORY_KIND && e.pubkey === pubkey), startWith(undefined));
18
- const timeline = events.timeline({ kinds: [WALLET_HISTORY_KIND], authors: [pubkey] });
19
- return combineLatest([updates, timeline]).pipe(map(([_, history]) => {
20
- if (locked === undefined)
21
- return history;
22
- else
23
- return history.filter((entry) => isHistoryContentLocked(entry) === locked);
24
- }));
25
- },
26
- };
27
- }
@@ -1,6 +0,0 @@
1
- import { Query } from "applesauce-core";
2
- import { NostrEvent } from "nostr-tools";
3
- /** A query that subscribes to all token events for a wallet, passing locked will filter by token locked status */
4
- export declare function WalletTokensQuery(pubkey: string, locked?: boolean | undefined): Query<NostrEvent[]>;
5
- /** A query that returns the visible balance of a wallet for each mint */
6
- export declare function WalletBalanceQuery(pubkey: string): Query<Record<string, number>>;
@@ -1,64 +0,0 @@
1
- import { combineLatest, filter, map, startWith } from "rxjs";
2
- import { getTokenContent, ignoreDuplicateProofs, isTokenContentLocked, WALLET_TOKEN_KIND } from "../helpers/tokens.js";
3
- /** removes deleted events from sorted array */
4
- function filterDeleted(tokens) {
5
- const deleted = new Set();
6
- return Array.from(tokens)
7
- .reverse()
8
- .filter((token) => {
9
- // skip this event if it a newer event says its deleted
10
- if (deleted.has(token.id))
11
- return false;
12
- // add ids to deleted array
13
- if (!isTokenContentLocked(token)) {
14
- const details = getTokenContent(token);
15
- for (const id of details.del)
16
- deleted.add(id);
17
- }
18
- return true;
19
- })
20
- .reverse();
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) {
24
- return {
25
- key: pubkey + locked,
26
- run: (events) => {
27
- const updates = events.updates.pipe(filter((e) => e.kind === WALLET_TOKEN_KIND && e.pubkey === pubkey), startWith(undefined));
28
- const timeline = events.timeline({ kinds: [WALLET_TOKEN_KIND], authors: [pubkey] });
29
- return combineLatest([updates, timeline]).pipe(
30
- // filter out locked tokens
31
- map(([_, tokens]) => {
32
- if (locked === undefined)
33
- return tokens;
34
- else
35
- return tokens.filter((t) => isTokenContentLocked(t) === locked);
36
- }),
37
- // remove deleted events
38
- map(filterDeleted));
39
- },
40
- };
41
- }
42
- /** A query that returns the visible balance of a wallet for each mint */
43
- export function WalletBalanceQuery(pubkey) {
44
- return {
45
- key: pubkey,
46
- run: (events) => {
47
- const updates = events.updates.pipe(filter((e) => e.kind === WALLET_TOKEN_KIND && e.pubkey === pubkey), startWith(undefined));
48
- const timeline = events.timeline({ kinds: [WALLET_TOKEN_KIND], authors: [pubkey] });
49
- return combineLatest([updates, timeline]).pipe(map(([_, tokens]) => tokens.filter((t) => !isTokenContentLocked(t))),
50
- // filter out deleted tokens
51
- map(filterDeleted),
52
- // map tokens to totals
53
- map((tokens) => {
54
- // ignore duplicate proofs
55
- const seen = new Set();
56
- return tokens.reduce((totals, token) => {
57
- const details = getTokenContent(token);
58
- const total = details.proofs.filter(ignoreDuplicateProofs(seen)).reduce((t, p) => t + p.amount, 0);
59
- return { ...totals, [details.mint]: (totals[details.mint] ?? 0) + total };
60
- }, {});
61
- }));
62
- },
63
- };
64
- }
@@ -1,21 +0,0 @@
1
- import { filter, map, merge } from "rxjs";
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) {
5
- return {
6
- key: pubkey,
7
- run: (events) => merge(
8
- // get the latest replaceable event
9
- events.replaceable(WALLET_KIND, pubkey),
10
- // and listen for any updates to matching events
11
- events.updates.pipe(filter((e) => e.kind === WALLET_KIND && e.pubkey === pubkey))).pipe(map((wallet) => {
12
- if (!wallet)
13
- return;
14
- if (isWalletLocked(wallet))
15
- return { locked: true, event: wallet };
16
- const mints = getWalletMints(wallet);
17
- const privateKey = getWalletPrivateKey(wallet);
18
- return { locked: false, mints, privateKey, event: wallet };
19
- })),
20
- };
21
- }
File without changes
File without changes