applesauce-wallet 0.0.0-next-20250315140539 → 0.12.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,12 +1,15 @@
1
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";
2
5
  import { EventStore } from "applesauce-core";
3
6
  import { EventFactory } from "applesauce-factory";
4
7
  import { ActionHub } from "applesauce-actions";
8
+ import { bytesToHex } from "@noble/hashes/utils";
5
9
  import { FakeUser } from "../../__tests__/fake-user.js";
6
- import { CreateWallet } from "../wallet.js";
7
- import { WALLET_BACKUP_KIND } from "../../helpers/wallet.js";
8
- import { unlockHiddenTags } from "applesauce-core/helpers";
9
- import { lastValueFrom } from "rxjs";
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";
10
13
  const user = new FakeUser();
11
14
  let events;
12
15
  let factory;
@@ -30,3 +33,24 @@ describe("CreateWallet", () => {
30
33
  expect(hiddenTags).toEqual(expect.arrayContaining([["mint", "https://mint.money.com"]]));
31
34
  });
32
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,6 +1,11 @@
1
1
  import { Action } from "applesauce-actions";
2
2
  /** An action that creates a new 17375 wallet event and 375 wallet backup */
3
3
  export declare function CreateWallet(mints: string[], privateKey?: Uint8Array): Action;
4
+ /**
5
+ * Adds a private key to a wallet event
6
+ * @throws if the wallet does not exist or is locked
7
+ */
8
+ export declare function WalletAddPrivateKey(privateKey: Uint8Array): Action;
4
9
  /** Unlocks the wallet event and optionally the tokens and history events */
5
10
  export declare function UnlockWallet(unlock?: {
6
11
  history?: boolean;
@@ -1,21 +1,42 @@
1
- import { generateSecretKey } from "nostr-tools";
2
- import { isWalletLocked, unlockWallet, WALLET_KIND } from "../helpers/wallet.js";
1
+ import { getWalletMints, getWalletPrivateKey, isWalletLocked, unlockWallet, WALLET_KIND } from "../helpers/wallet.js";
3
2
  import { WalletBackupBlueprint, WalletBlueprint } from "../blueprints/wallet.js";
4
3
  import { isTokenContentLocked, unlockTokenContent, WALLET_TOKEN_KIND } from "../helpers/tokens.js";
5
4
  import { isHistoryContentLocked, unlockHistoryContent, WALLET_HISTORY_KIND } from "../helpers/history.js";
6
5
  /** An action that creates a new 17375 wallet event and 375 wallet backup */
7
- export function CreateWallet(mints, privateKey = generateSecretKey()) {
6
+ export function CreateWallet(mints, privateKey) {
8
7
  return async function* ({ events, factory, self }) {
9
8
  const existing = events.getReplaceable(WALLET_KIND, self);
10
9
  if (existing)
11
10
  throw new Error("Wallet already exists");
12
- const wallet = await factory.sign(await factory.create(WalletBlueprint, privateKey, mints));
11
+ const wallet = await factory.sign(await factory.create(WalletBlueprint, mints, privateKey));
13
12
  const backup = await factory.sign(await factory.create(WalletBackupBlueprint, wallet));
14
13
  // publish the backup first
15
14
  yield backup;
16
15
  yield wallet;
17
16
  };
18
17
  }
18
+ /**
19
+ * Adds a private key to a wallet event
20
+ * @throws if the wallet does not exist or is locked
21
+ */
22
+ export function WalletAddPrivateKey(privateKey) {
23
+ return async function* ({ events, self, factory }) {
24
+ const wallet = events.getReplaceable(WALLET_KIND, self);
25
+ if (!wallet)
26
+ throw new Error("Wallet does not exist");
27
+ if (isWalletLocked(wallet))
28
+ throw new Error("Wallet is locked");
29
+ if (getWalletPrivateKey(wallet))
30
+ throw new Error("Wallet already has a private key");
31
+ const draft = await factory.create(WalletBlueprint, getWalletMints(wallet), privateKey);
32
+ const signed = await factory.sign(draft);
33
+ // create backup event for wallet
34
+ const backup = await factory.sign(await factory.create(WalletBackupBlueprint, signed));
35
+ // publish events
36
+ yield backup;
37
+ yield signed;
38
+ };
39
+ }
19
40
  /** Unlocks the wallet event and optionally the tokens and history events */
20
41
  export function UnlockWallet(unlock) {
21
42
  return async function* ({ events, self, factory }) {
@@ -1,6 +1,6 @@
1
1
  import { EventBlueprint } from "applesauce-factory";
2
2
  import { NostrEvent } from "nostr-tools";
3
3
  /** A blueprint to create a new 17375 wallet */
4
- export declare function WalletBlueprint(privateKey: Uint8Array, mints: string[]): EventBlueprint;
4
+ export declare function WalletBlueprint(mints: string[], privateKey?: Uint8Array): EventBlueprint;
5
5
  /** A blueprint that creates a new 375 wallet backup event */
6
6
  export declare function WalletBackupBlueprint(wallet: NostrEvent): EventBlueprint;
@@ -4,8 +4,8 @@ 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
- export function WalletBlueprint(privateKey, mints) {
8
- return (ctx) => EventFactory.runProcess({ kind: WALLET_KIND }, ctx, modifyHiddenTags(setPrivateKeyTag(privateKey), setMintTags(mints)));
7
+ export function WalletBlueprint(mints, privateKey) {
8
+ return (ctx) => EventFactory.runProcess({ kind: WALLET_KIND }, ctx, 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,4 +12,4 @@ export declare function lockWallet(wallet: NostrEvent): void;
12
12
  /** Returns the wallets mints */
13
13
  export declare function getWalletMints(wallet: NostrEvent): string[];
14
14
  /** Returns the wallets private key as a string */
15
- export declare function getWalletPrivateKey(wallet: NostrEvent): Uint8Array;
15
+ export declare function getWalletPrivateKey(wallet: NostrEvent): Uint8Array | undefined;
@@ -33,8 +33,6 @@ export function getWalletPrivateKey(wallet) {
33
33
  if (!tags)
34
34
  throw new Error("Wallet is locked");
35
35
  const key = tags.find((t) => t[0] === "privkey" && t[1])?.[1];
36
- if (!key)
37
- throw new Error("Wallet missing private key");
38
- return hexToBytes(key);
36
+ return key ? hexToBytes(key) : undefined;
39
37
  });
40
38
  }
@@ -14,12 +14,12 @@ describe("setWalletBackupContent", () => {
14
14
  await expect(setWalletBackupContent(note)({ kind: WALLET_BACKUP_KIND, tags: [], created_at: unixNow(), content: "" }, factory.context)).rejects.toThrow();
15
15
  });
16
16
  it("should throw if pubkey does not match", async () => {
17
- const wallet = await factory.sign(await factory.create(WalletBlueprint, generateSecretKey(), []));
17
+ const wallet = await factory.sign(await factory.create(WalletBlueprint, [], generateSecretKey()));
18
18
  const user2 = new FakeUser();
19
19
  await expect(setWalletBackupContent(wallet)({ kind: WALLET_BACKUP_KIND, tags: [], created_at: unixNow(), content: "" }, { signer: user2 })).rejects.toThrow();
20
20
  });
21
21
  it("should copy the content of the wallet event", async () => {
22
- const wallet = await factory.sign(await factory.create(WalletBlueprint, generateSecretKey(), []));
22
+ const wallet = await factory.sign(await factory.create(WalletBlueprint, [], generateSecretKey()));
23
23
  expect(await setWalletBackupContent(wallet)({ kind: WALLET_BACKUP_KIND, tags: [], created_at: unixNow(), content: "" }, factory.context)).toEqual(expect.objectContaining({ content: wallet.content }));
24
24
  });
25
25
  });
@@ -17,7 +17,7 @@ beforeEach(() => {
17
17
  });
18
18
  describe("WalletQuery", () => {
19
19
  it("it should update when event is unlocked", async () => {
20
- const wallet = await user.signEvent(await factory.create(WalletBlueprint, generateSecretKey(), []));
20
+ const wallet = await user.signEvent(await factory.create(WalletBlueprint, [], generateSecretKey()));
21
21
  lockWallet(wallet);
22
22
  events.add(wallet);
23
23
  const spy = subscribeSpyTo(queries.createQuery(WalletQuery, await user.getPublicKey()));
@@ -6,7 +6,7 @@ export type WalletInfo = {
6
6
  } | {
7
7
  locked: false;
8
8
  event: NostrEvent;
9
- privateKey: Uint8Array;
9
+ privateKey?: Uint8Array;
10
10
  mints: string[];
11
11
  };
12
12
  /** A query to get the state of a NIP-60 wallet */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "applesauce-wallet",
3
- "version": "0.0.0-next-20250315140539",
3
+ "version": "0.12.0",
4
4
  "description": "",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -80,16 +80,16 @@
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.0.0-next-20250315140539",
84
- "applesauce-core": "0.0.0-next-20250315140539",
85
- "applesauce-factory": "0.0.0-next-20250315140539",
83
+ "applesauce-actions": "^0.12.0",
84
+ "applesauce-core": "^0.12.0",
85
+ "applesauce-factory": "^0.12.0",
86
86
  "nostr-tools": "^2.10.4",
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.0.0-next-20250315140539",
92
+ "applesauce-signers": "^0.12.0",
93
93
  "typescript": "^5.7.3",
94
94
  "vitest": "^3.0.5"
95
95
  },
@@ -1 +0,0 @@
1
- export {};
@@ -1,25 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import { setWalletBackupContent } from "../wallet.js";
3
- import { FakeUser } from "../../__tests__/fake-user.js";
4
- import { EventFactory } from "applesauce-factory";
5
- import { WalletBlueprint } from "../../blueprints/wallet.js";
6
- import { generateSecretKey } from "nostr-tools";
7
- import { WALLET_BACKUP_KIND } from "../../helpers/wallet.js";
8
- import { unixNow } from "applesauce-core/helpers";
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,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 * from "./wallet.js";
@@ -1 +0,0 @@
1
- export * from "./wallet.js";
@@ -1,4 +0,0 @@
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 setWalletBackupContent(wallet: NostrEvent): EventOperation;
@@ -1,14 +0,0 @@
1
- import { WALLET_KIND } from "../../helpers/wallet.js";
2
- /** Sets the content of a kind 375 wallet backup event */
3
- export function setWalletBackupContent(wallet) {
4
- return async (draft, ctx) => {
5
- if (wallet.kind !== WALLET_KIND)
6
- throw new Error(`Cant create a wallet backup from kind ${wallet.kind}`);
7
- if (!wallet.content)
8
- throw new Error("Wallet missing content");
9
- const pubkey = await ctx.signer?.getPublicKey();
10
- if (wallet.pubkey !== pubkey)
11
- throw new Error("Wallet pubkey dose not match signer pubkey");
12
- return { ...draft, content: wallet.content };
13
- };
14
- }
@@ -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 * from "./wallet.js";
@@ -1 +0,0 @@
1
- export * from "./wallet.js";
@@ -1,5 +0,0 @@
1
- import { TagOperation } from "applesauce-factory";
2
- /** Sets the "mint" tags in a wallet event */
3
- export declare function setMintTags(mints: string[]): TagOperation;
4
- /** Sets the "privkey" tag on a wallet event */
5
- export declare function setPrivateKeyTag(privateKey: Uint8Array): TagOperation;
@@ -1,15 +0,0 @@
1
- import { bytesToHex } from "@noble/hashes/utils";
2
- import { ensureSingletonTag } from "applesauce-factory/helpers";
3
- /** Sets the "mint" tags in a wallet event */
4
- export function setMintTags(mints) {
5
- return (tags) => [
6
- // remove all existing mint tags
7
- ...tags.filter((t) => t[0] !== "mint"),
8
- // add new mint tags
9
- ...mints.map((mint) => ["mint", mint]),
10
- ];
11
- }
12
- /** Sets the "privkey" tag on a wallet event */
13
- export function setPrivateKeyTag(privateKey) {
14
- return (tags) => ensureSingletonTag(tags, ["privkey", bytesToHex(privateKey)], true);
15
- }
@@ -1,4 +0,0 @@
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 setWalletBackupContent(wallet: NostrEvent): EventOperation;
@@ -1,14 +0,0 @@
1
- import { WALLET_KIND } from "../helpers/wallet.js";
2
- /** Sets the content of a kind 375 wallet backup event */
3
- export function setWalletBackupContent(wallet) {
4
- return async (draft, ctx) => {
5
- if (wallet.kind !== WALLET_KIND)
6
- throw new Error(`Cant create a wallet backup from kind ${wallet.kind}`);
7
- if (!wallet.content)
8
- throw new Error("Wallet missing content");
9
- const pubkey = await ctx.signer?.getPublicKey();
10
- if (wallet.pubkey !== pubkey)
11
- throw new Error("Wallet pubkey dose not match signer pubkey");
12
- return { ...draft, content: wallet.content };
13
- };
14
- }