applesauce-wallet 0.0.0-next-20251209200210 → 0.0.0-next-20251220152312
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.
- package/dist/actions/common.d.ts +4 -0
- package/dist/actions/common.js +15 -0
- package/dist/actions/index.d.ts +2 -2
- package/dist/actions/index.js +2 -2
- package/dist/actions/{zap-info.d.ts → nutzap-info.d.ts} +20 -3
- package/dist/actions/nutzap-info.js +117 -0
- package/dist/actions/nutzaps.d.ts +24 -0
- package/dist/actions/nutzaps.js +154 -0
- package/dist/actions/tokens.d.ts +77 -7
- package/dist/actions/tokens.js +332 -69
- package/dist/actions/wallet.d.ts +18 -3
- package/dist/actions/wallet.js +74 -32
- package/dist/blueprints/history.d.ts +1 -1
- package/dist/blueprints/history.js +1 -1
- package/dist/blueprints/wallet.d.ts +5 -1
- package/dist/blueprints/wallet.js +6 -3
- package/dist/casts/__register__.d.ts +20 -0
- package/dist/casts/__register__.js +41 -0
- package/dist/casts/index.d.ts +6 -0
- package/dist/casts/index.js +6 -0
- package/dist/casts/nutzap-info.d.ts +14 -0
- package/dist/casts/nutzap-info.js +22 -0
- package/dist/casts/nutzap.d.ts +16 -0
- package/dist/casts/nutzap.js +37 -0
- package/dist/casts/wallet-history.d.ts +16 -0
- package/dist/casts/wallet-history.js +40 -0
- package/dist/casts/wallet-token.d.ts +29 -0
- package/dist/casts/wallet-token.js +52 -0
- package/dist/casts/wallet.d.ts +27 -0
- package/dist/casts/wallet.js +62 -0
- package/dist/helpers/cashu.d.ts +21 -0
- package/dist/helpers/cashu.js +105 -0
- package/dist/helpers/couch.d.ts +11 -0
- package/dist/helpers/couch.js +1 -0
- package/dist/helpers/history.d.ts +5 -1
- package/dist/helpers/history.js +13 -4
- package/dist/helpers/index.d.ts +5 -1
- package/dist/helpers/index.js +5 -1
- package/dist/helpers/indexed-db-couch.d.ts +34 -0
- package/dist/helpers/indexed-db-couch.js +119 -0
- package/dist/helpers/local-storage-couch.d.ts +29 -0
- package/dist/helpers/local-storage-couch.js +78 -0
- package/dist/helpers/{zap-info.d.ts → nutzap-info.d.ts} +10 -1
- package/dist/helpers/{zap-info.js → nutzap-info.js} +22 -10
- package/dist/helpers/nutzap.d.ts +15 -0
- package/dist/helpers/nutzap.js +57 -3
- package/dist/helpers/tokens.d.ts +9 -18
- package/dist/helpers/tokens.js +64 -94
- package/dist/helpers/wallet.d.ts +16 -6
- package/dist/helpers/wallet.js +40 -14
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/models/history.d.ts +1 -1
- package/dist/models/history.js +7 -10
- package/dist/models/index.d.ts +0 -1
- package/dist/models/index.js +0 -1
- package/dist/models/nutzap.d.ts +2 -0
- package/dist/models/nutzap.js +8 -0
- package/dist/models/tokens.d.ts +2 -2
- package/dist/models/tokens.js +14 -17
- package/dist/operations/history.js +1 -1
- package/dist/operations/index.d.ts +1 -1
- package/dist/operations/index.js +1 -1
- package/dist/operations/nutzap-info.d.ts +21 -0
- package/dist/operations/nutzap-info.js +71 -0
- package/dist/operations/wallet.d.ts +10 -1
- package/dist/operations/wallet.js +33 -3
- package/package.json +15 -6
- package/dist/actions/zap-info.js +0 -83
- package/dist/actions/zaps.d.ts +0 -8
- package/dist/actions/zaps.js +0 -30
- package/dist/models/wallet.d.ts +0 -13
- package/dist/models/wallet.js +0 -21
- package/dist/operations/zap-info.d.ts +0 -10
- package/dist/operations/zap-info.js +0 -17
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { Token } from "@cashu/cashu-ts";
|
|
2
|
+
import type { Couch } from "./couch.js";
|
|
3
|
+
/**
|
|
4
|
+
* A simple IndexedDB-based implementation of the Couch interface.
|
|
5
|
+
* Stores tokens in the browser's IndexedDB for better performance with larger datasets.
|
|
6
|
+
*/
|
|
7
|
+
export declare class IndexedDBCouch implements Couch {
|
|
8
|
+
private dbName;
|
|
9
|
+
private storeName;
|
|
10
|
+
private db;
|
|
11
|
+
private initPromise;
|
|
12
|
+
constructor(dbName?: string, storeName?: string);
|
|
13
|
+
/**
|
|
14
|
+
* Generate a unique ID for each stored token.
|
|
15
|
+
*/
|
|
16
|
+
private generateId;
|
|
17
|
+
/**
|
|
18
|
+
* Initialize the IndexedDB database and object store.
|
|
19
|
+
*/
|
|
20
|
+
private init;
|
|
21
|
+
/**
|
|
22
|
+
* Store a token in the couch.
|
|
23
|
+
* Returns a function that can be called to remove this specific token.
|
|
24
|
+
*/
|
|
25
|
+
store(token: Token): Promise<() => Promise<void>>;
|
|
26
|
+
/**
|
|
27
|
+
* Clear all tokens from the couch.
|
|
28
|
+
*/
|
|
29
|
+
clear(): Promise<void>;
|
|
30
|
+
/**
|
|
31
|
+
* Get all tokens currently stored in the couch.
|
|
32
|
+
*/
|
|
33
|
+
getAll(): Promise<Token[]>;
|
|
34
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { getEncodedToken, getDecodedToken } from "@cashu/cashu-ts";
|
|
2
|
+
const DB_NAME = "applesauce-wallet-couch";
|
|
3
|
+
const STORE_NAME = "tokens";
|
|
4
|
+
const DB_VERSION = 1;
|
|
5
|
+
/**
|
|
6
|
+
* A simple IndexedDB-based implementation of the Couch interface.
|
|
7
|
+
* Stores tokens in the browser's IndexedDB for better performance with larger datasets.
|
|
8
|
+
*/
|
|
9
|
+
export class IndexedDBCouch {
|
|
10
|
+
dbName;
|
|
11
|
+
storeName;
|
|
12
|
+
db = null;
|
|
13
|
+
initPromise = null;
|
|
14
|
+
constructor(dbName = DB_NAME, storeName = STORE_NAME) {
|
|
15
|
+
this.dbName = dbName;
|
|
16
|
+
this.storeName = storeName;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Generate a unique ID for each stored token.
|
|
20
|
+
*/
|
|
21
|
+
generateId() {
|
|
22
|
+
return `${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Initialize the IndexedDB database and object store.
|
|
26
|
+
*/
|
|
27
|
+
async init() {
|
|
28
|
+
if (this.db)
|
|
29
|
+
return this.db;
|
|
30
|
+
if (this.initPromise)
|
|
31
|
+
return this.initPromise;
|
|
32
|
+
this.initPromise = new Promise((resolve, reject) => {
|
|
33
|
+
if (typeof window === "undefined" || !window.indexedDB) {
|
|
34
|
+
reject(new Error("IndexedDB is not available"));
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
const request = window.indexedDB.open(this.dbName, DB_VERSION);
|
|
38
|
+
request.onerror = () => reject(request.error);
|
|
39
|
+
request.onsuccess = () => {
|
|
40
|
+
this.db = request.result;
|
|
41
|
+
resolve(this.db);
|
|
42
|
+
};
|
|
43
|
+
request.onupgradeneeded = (event) => {
|
|
44
|
+
const db = event.target.result;
|
|
45
|
+
if (!db.objectStoreNames.contains(this.storeName)) {
|
|
46
|
+
db.createObjectStore(this.storeName, { keyPath: "id" });
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
});
|
|
50
|
+
return this.initPromise;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Store a token in the couch.
|
|
54
|
+
* Returns a function that can be called to remove this specific token.
|
|
55
|
+
*/
|
|
56
|
+
async store(token) {
|
|
57
|
+
const db = await this.init();
|
|
58
|
+
const id = this.generateId();
|
|
59
|
+
const encodedToken = getEncodedToken(token);
|
|
60
|
+
return new Promise((resolve, reject) => {
|
|
61
|
+
const transaction = db.transaction([this.storeName], "readwrite");
|
|
62
|
+
const store = transaction.objectStore(this.storeName);
|
|
63
|
+
const request = store.add({ id, encodedToken });
|
|
64
|
+
request.onsuccess = () => {
|
|
65
|
+
// Return a function to remove this specific token by ID
|
|
66
|
+
resolve(async () => {
|
|
67
|
+
const removeDb = await this.init();
|
|
68
|
+
const removeTransaction = removeDb.transaction([this.storeName], "readwrite");
|
|
69
|
+
const removeStore = removeTransaction.objectStore(this.storeName);
|
|
70
|
+
const removeRequest = removeStore.delete(id);
|
|
71
|
+
await new Promise((resolve, reject) => {
|
|
72
|
+
removeRequest.onsuccess = () => resolve();
|
|
73
|
+
removeRequest.onerror = () => reject(removeRequest.error);
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
};
|
|
77
|
+
request.onerror = () => reject(request.error);
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Clear all tokens from the couch.
|
|
82
|
+
*/
|
|
83
|
+
async clear() {
|
|
84
|
+
const db = await this.init();
|
|
85
|
+
return new Promise((resolve, reject) => {
|
|
86
|
+
const transaction = db.transaction([this.storeName], "readwrite");
|
|
87
|
+
const store = transaction.objectStore(this.storeName);
|
|
88
|
+
const request = store.clear();
|
|
89
|
+
request.onsuccess = () => resolve();
|
|
90
|
+
request.onerror = () => reject(request.error);
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Get all tokens currently stored in the couch.
|
|
95
|
+
*/
|
|
96
|
+
async getAll() {
|
|
97
|
+
const db = await this.init();
|
|
98
|
+
return new Promise((resolve, reject) => {
|
|
99
|
+
const transaction = db.transaction([this.storeName], "readonly");
|
|
100
|
+
const store = transaction.objectStore(this.storeName);
|
|
101
|
+
const request = store.getAll();
|
|
102
|
+
request.onsuccess = () => {
|
|
103
|
+
const results = request.result;
|
|
104
|
+
const tokens = results
|
|
105
|
+
.map((item) => {
|
|
106
|
+
try {
|
|
107
|
+
return getDecodedToken(item.encodedToken);
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
})
|
|
113
|
+
.filter((token) => token !== null);
|
|
114
|
+
resolve(tokens);
|
|
115
|
+
};
|
|
116
|
+
request.onerror = () => reject(request.error);
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Token } from "@cashu/cashu-ts";
|
|
2
|
+
import type { Couch } from "./couch.js";
|
|
3
|
+
/**
|
|
4
|
+
* A simple localStorage-based implementation of the Couch interface.
|
|
5
|
+
* Stores tokens in the browser's localStorage.
|
|
6
|
+
*/
|
|
7
|
+
export declare class LocalStorageCouch implements Couch {
|
|
8
|
+
private storageKey;
|
|
9
|
+
constructor(storageKey?: string);
|
|
10
|
+
/**
|
|
11
|
+
* Generate a unique ID for each stored token.
|
|
12
|
+
*/
|
|
13
|
+
private generateId;
|
|
14
|
+
/**
|
|
15
|
+
* Store a token in the couch.
|
|
16
|
+
* Returns a function that can be called to remove this specific token.
|
|
17
|
+
*/
|
|
18
|
+
store(token: Token): () => void;
|
|
19
|
+
/**
|
|
20
|
+
* Clear all tokens from the couch.
|
|
21
|
+
*/
|
|
22
|
+
clear(): void;
|
|
23
|
+
/**
|
|
24
|
+
* Get all tokens currently stored in the couch.
|
|
25
|
+
*/
|
|
26
|
+
getAll(): Token[];
|
|
27
|
+
private getStoredTokens;
|
|
28
|
+
private saveStoredTokens;
|
|
29
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { getEncodedToken, getDecodedToken } from "@cashu/cashu-ts";
|
|
2
|
+
const STORAGE_KEY = "applesauce:couch:tokens";
|
|
3
|
+
/**
|
|
4
|
+
* A simple localStorage-based implementation of the Couch interface.
|
|
5
|
+
* Stores tokens in the browser's localStorage.
|
|
6
|
+
*/
|
|
7
|
+
export class LocalStorageCouch {
|
|
8
|
+
storageKey;
|
|
9
|
+
constructor(storageKey = STORAGE_KEY) {
|
|
10
|
+
this.storageKey = storageKey;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Generate a unique ID for each stored token.
|
|
14
|
+
*/
|
|
15
|
+
generateId() {
|
|
16
|
+
return `${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Store a token in the couch.
|
|
20
|
+
* Returns a function that can be called to remove this specific token.
|
|
21
|
+
*/
|
|
22
|
+
store(token) {
|
|
23
|
+
const id = this.generateId();
|
|
24
|
+
const encodedToken = getEncodedToken(token);
|
|
25
|
+
const stored = this.getStoredTokens();
|
|
26
|
+
stored.push({ id, encodedToken });
|
|
27
|
+
this.saveStoredTokens(stored);
|
|
28
|
+
// Return a function to remove this specific token by ID
|
|
29
|
+
return () => {
|
|
30
|
+
const currentStored = this.getStoredTokens();
|
|
31
|
+
const filtered = currentStored.filter((item) => item.id !== id);
|
|
32
|
+
this.saveStoredTokens(filtered);
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Clear all tokens from the couch.
|
|
37
|
+
*/
|
|
38
|
+
clear() {
|
|
39
|
+
if (typeof window !== "undefined" && window.localStorage) {
|
|
40
|
+
window.localStorage.removeItem(this.storageKey);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Get all tokens currently stored in the couch.
|
|
45
|
+
*/
|
|
46
|
+
getAll() {
|
|
47
|
+
const stored = this.getStoredTokens();
|
|
48
|
+
return stored
|
|
49
|
+
.map((item) => {
|
|
50
|
+
try {
|
|
51
|
+
return getDecodedToken(item.encodedToken);
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
})
|
|
57
|
+
.filter((token) => token !== null);
|
|
58
|
+
}
|
|
59
|
+
getStoredTokens() {
|
|
60
|
+
if (typeof window === "undefined" || !window.localStorage) {
|
|
61
|
+
return [];
|
|
62
|
+
}
|
|
63
|
+
try {
|
|
64
|
+
const stored = window.localStorage.getItem(this.storageKey);
|
|
65
|
+
if (!stored)
|
|
66
|
+
return [];
|
|
67
|
+
return JSON.parse(stored);
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
return [];
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
saveStoredTokens(tokens) {
|
|
74
|
+
if (typeof window !== "undefined" && window.localStorage) {
|
|
75
|
+
window.localStorage.setItem(this.storageKey, JSON.stringify(tokens));
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import { Proof } from "@cashu/cashu-ts";
|
|
2
|
-
import {
|
|
2
|
+
import { NameValueTag } from "applesauce-core/helpers";
|
|
3
|
+
import { KnownEvent, NostrEvent } from "applesauce-core/helpers/event";
|
|
3
4
|
export declare const NUTZAP_INFO_KIND = 10019;
|
|
5
|
+
/** Validated nutzap info event */
|
|
6
|
+
export type NutzapInfoEvent = KnownEvent<typeof NUTZAP_INFO_KIND>;
|
|
7
|
+
/** Checks if an event is a valid nutzap info event */
|
|
8
|
+
export declare function isValidNutzapInfo(event: NostrEvent): event is NutzapInfoEvent;
|
|
4
9
|
export declare const NutzapMintsSymbol: unique symbol;
|
|
5
10
|
export declare const NutzapRelaysSymbol: unique symbol;
|
|
6
11
|
/** Returns the relay URLs from a kind:10019 nutzap info event */
|
|
@@ -12,8 +17,12 @@ export declare function getNutzapInfoMints(event: NostrEvent): {
|
|
|
12
17
|
}[];
|
|
13
18
|
/** Returns the pubkey for P2PK-locking from a kind:10019 nutzap info event */
|
|
14
19
|
export declare function getNutzapInfoPubkey(event: NostrEvent): string | undefined;
|
|
20
|
+
/** Returns the P2PK pubkey from a kind:10019 nutzap info event */
|
|
21
|
+
export declare function getNutzapInfoP2PKPubkey(event: NostrEvent): string | undefined;
|
|
15
22
|
/**
|
|
16
23
|
* verfies if proofs are locked to nutzap info
|
|
17
24
|
* @throws {Error} if proofs are not locked to nutzap info
|
|
18
25
|
*/
|
|
19
26
|
export declare function verifyProofsLocked(proofs: Proof[], info: NostrEvent): void;
|
|
27
|
+
/** Creates a pubkey tag for a kind:10019 nutzap info event from a private key */
|
|
28
|
+
export declare function createNutzapInfoPubkeyTag(key: Uint8Array): NameValueTag<"pubkey">;
|
|
@@ -1,5 +1,10 @@
|
|
|
1
|
-
import { getOrComputeCachedValue,
|
|
1
|
+
import { getOrComputeCachedValue, getPublicKey, mergeRelaySets } from "applesauce-core/helpers";
|
|
2
|
+
import { getProofP2PKPubkey } from "./cashu.js";
|
|
2
3
|
export const NUTZAP_INFO_KIND = 10019;
|
|
4
|
+
/** Checks if an event is a valid nutzap info event */
|
|
5
|
+
export function isValidNutzapInfo(event) {
|
|
6
|
+
return event.kind === NUTZAP_INFO_KIND;
|
|
7
|
+
}
|
|
3
8
|
// Symbols for caching computed values
|
|
4
9
|
export const NutzapMintsSymbol = Symbol.for("nutzap-mints");
|
|
5
10
|
export const NutzapRelaysSymbol = Symbol.for("nutzap-relays");
|
|
@@ -17,7 +22,14 @@ export function getNutzapInfoMints(event) {
|
|
|
17
22
|
}
|
|
18
23
|
/** Returns the pubkey for P2PK-locking from a kind:10019 nutzap info event */
|
|
19
24
|
export function getNutzapInfoPubkey(event) {
|
|
20
|
-
|
|
25
|
+
const pubkey = event.tags.find((t) => t[0] === "pubkey")?.[1];
|
|
26
|
+
if (!pubkey)
|
|
27
|
+
return undefined;
|
|
28
|
+
return pubkey.length === 64 ? `02${pubkey}` : pubkey;
|
|
29
|
+
}
|
|
30
|
+
/** Returns the P2PK pubkey from a kind:10019 nutzap info event */
|
|
31
|
+
export function getNutzapInfoP2PKPubkey(event) {
|
|
32
|
+
return getNutzapInfoPubkey(event);
|
|
21
33
|
}
|
|
22
34
|
/**
|
|
23
35
|
* verfies if proofs are locked to nutzap info
|
|
@@ -27,16 +39,16 @@ export function verifyProofsLocked(proofs, info) {
|
|
|
27
39
|
const pubkey = getNutzapInfoPubkey(info);
|
|
28
40
|
if (!pubkey)
|
|
29
41
|
throw new Error("Nutzap info must have a pubkey");
|
|
30
|
-
const fullPubkey = pubkey.length === 64 ? `02${pubkey}` : pubkey;
|
|
31
42
|
for (const proof of proofs) {
|
|
32
|
-
const
|
|
33
|
-
if (!
|
|
34
|
-
throw new Error(`Cashu token must have a spending condition`);
|
|
35
|
-
if (!Array.isArray(secret))
|
|
36
|
-
throw new Error("Invalid spending condition");
|
|
37
|
-
if (secret[0] !== "P2PK")
|
|
43
|
+
const proofPubkey = getProofP2PKPubkey(proof);
|
|
44
|
+
if (!proofPubkey)
|
|
38
45
|
throw new Error("Token proofs must be P2PK locked");
|
|
39
|
-
if (
|
|
46
|
+
if (proofPubkey !== pubkey)
|
|
40
47
|
throw new Error("Token proofs must be P2PK locked to the recipient's nutzap pubkey");
|
|
41
48
|
}
|
|
42
49
|
}
|
|
50
|
+
/** Creates a pubkey tag for a kind:10019 nutzap info event from a private key */
|
|
51
|
+
export function createNutzapInfoPubkeyTag(key) {
|
|
52
|
+
const pubkey = "02" + getPublicKey(key);
|
|
53
|
+
return ["pubkey", pubkey];
|
|
54
|
+
}
|
package/dist/helpers/nutzap.d.ts
CHANGED
|
@@ -31,3 +31,18 @@ export declare function getNutzapAmount(event: NostrEvent): number | undefined;
|
|
|
31
31
|
export declare function isValidNutzap(nutzap: NostrEvent): nutzap is NutzapEvent;
|
|
32
32
|
/** Checks if a nutzap event has already been redeemed based on kind:7376 wallet history events */
|
|
33
33
|
export declare function isNutzapRedeemed(nutzapId: string, history: NostrEvent[]): boolean;
|
|
34
|
+
/**
|
|
35
|
+
* Extracts the P2PK locking pubkey from proofs in a nutzap event
|
|
36
|
+
* @param nutzap the nutzap event containing P2PK-locked proofs
|
|
37
|
+
* @returns the pubkey that the proofs are locked to, or undefined if not found
|
|
38
|
+
* @throws {Error} if proofs are not P2PK locked or have inconsistent pubkeys
|
|
39
|
+
*/
|
|
40
|
+
export declare function getNutzapP2PKPubkey(nutzap: NostrEvent): string | undefined;
|
|
41
|
+
/**
|
|
42
|
+
* Finds the matching private key for the P2PK lock in a nutzap event's proofs
|
|
43
|
+
* @param nutzap the nutzap event containing P2PK-locked proofs
|
|
44
|
+
* @param privateKeys array of private keys to search through
|
|
45
|
+
* @returns the matching private key, or undefined if none match
|
|
46
|
+
* @throws {Error} if proofs are not P2PK locked or have inconsistent pubkeys
|
|
47
|
+
*/
|
|
48
|
+
export declare function findMatchingPrivateKeyForNutzap(nutzap: NostrEvent, privateKeys: Uint8Array[]): Uint8Array | undefined;
|
package/dist/helpers/nutzap.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { getAddressPointerFromATag, getEventPointerFromETag, getOrComputeCachedValue, getProfilePointerFromPTag, getTagValue, isPTag, processTags, safeParse, } from "applesauce-core/helpers";
|
|
1
|
+
import { getAddressPointerFromATag, getEventPointerFromETag, getOrComputeCachedValue, getProfilePointerFromPTag, getPublicKey, getTagValue, isPTag, processTags, safeParse, } from "applesauce-core/helpers";
|
|
2
2
|
import { getHistoryRedeemed } from "./history.js";
|
|
3
|
+
import { getProofP2PKPubkey } from "./cashu.js";
|
|
3
4
|
export const NUTZAP_KIND = 9321;
|
|
4
5
|
// Symbols for caching computed values
|
|
5
6
|
export const NutzapProofsSymbol = Symbol.for("nutzap-proofs");
|
|
@@ -54,8 +55,6 @@ export function isValidNutzap(nutzap) {
|
|
|
54
55
|
if (nutzap.kind !== NUTZAP_KIND)
|
|
55
56
|
return false;
|
|
56
57
|
// Check if the nutzap has a mint, recipient, and proofs
|
|
57
|
-
if (getNutzapPointer(nutzap) === undefined)
|
|
58
|
-
return false;
|
|
59
58
|
if (getNutzapMint(nutzap) === undefined)
|
|
60
59
|
return false;
|
|
61
60
|
if (getNutzapRecipient(nutzap) === undefined)
|
|
@@ -68,3 +67,58 @@ export function isValidNutzap(nutzap) {
|
|
|
68
67
|
export function isNutzapRedeemed(nutzapId, history) {
|
|
69
68
|
return history.some((entry) => getHistoryRedeemed(entry).includes(nutzapId));
|
|
70
69
|
}
|
|
70
|
+
/**
|
|
71
|
+
* Extracts the P2PK locking pubkey from proofs in a nutzap event
|
|
72
|
+
* @param nutzap the nutzap event containing P2PK-locked proofs
|
|
73
|
+
* @returns the pubkey that the proofs are locked to, or undefined if not found
|
|
74
|
+
* @throws {Error} if proofs are not P2PK locked or have inconsistent pubkeys
|
|
75
|
+
*/
|
|
76
|
+
export function getNutzapP2PKPubkey(nutzap) {
|
|
77
|
+
const proofs = getNutzapProofs(nutzap);
|
|
78
|
+
if (proofs.length === 0)
|
|
79
|
+
return undefined;
|
|
80
|
+
let p2pkPubkey;
|
|
81
|
+
for (const proof of proofs) {
|
|
82
|
+
const proofPubkey = getProofP2PKPubkey(proof);
|
|
83
|
+
if (!proofPubkey)
|
|
84
|
+
throw new Error("Proof is not P2PK locked");
|
|
85
|
+
if (!p2pkPubkey) {
|
|
86
|
+
p2pkPubkey = proofPubkey;
|
|
87
|
+
}
|
|
88
|
+
else if (p2pkPubkey !== proofPubkey) {
|
|
89
|
+
throw new Error("Proofs are locked to different pubkeys");
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return p2pkPubkey;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Finds the matching private key for the P2PK lock in a nutzap event's proofs
|
|
96
|
+
* @param nutzap the nutzap event containing P2PK-locked proofs
|
|
97
|
+
* @param privateKeys array of private keys to search through
|
|
98
|
+
* @returns the matching private key, or undefined if none match
|
|
99
|
+
* @throws {Error} if proofs are not P2PK locked or have inconsistent pubkeys
|
|
100
|
+
*/
|
|
101
|
+
export function findMatchingPrivateKeyForNutzap(nutzap, privateKeys) {
|
|
102
|
+
const p2pkPubkey = getNutzapP2PKPubkey(nutzap);
|
|
103
|
+
if (!p2pkPubkey)
|
|
104
|
+
return undefined;
|
|
105
|
+
// Normalize target pubkey to full format (with 02 prefix) for comparison
|
|
106
|
+
// getNutzapP2PKPubkey already normalizes to full format, so p2pkPubkey is 66 chars
|
|
107
|
+
const targetPubkeyFull = p2pkPubkey;
|
|
108
|
+
const targetPubkeyXOnly = p2pkPubkey.length === 66 ? p2pkPubkey.slice(2) : p2pkPubkey;
|
|
109
|
+
for (const privateKey of privateKeys) {
|
|
110
|
+
try {
|
|
111
|
+
// Derive public key from private key (returns 64-char x-only format)
|
|
112
|
+
const derivedPubkey = getPublicKey(privateKey);
|
|
113
|
+
// Compare: derived pubkey is x-only, so compare both formats
|
|
114
|
+
if (derivedPubkey === targetPubkeyXOnly || `02${derivedPubkey}` === targetPubkeyFull) {
|
|
115
|
+
return privateKey;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
catch (error) {
|
|
119
|
+
// Skip invalid private keys
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return undefined;
|
|
124
|
+
}
|
package/dist/helpers/tokens.d.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { Proof
|
|
1
|
+
import { Proof } from "@cashu/cashu-ts";
|
|
2
2
|
import { HiddenContentSigner, UnlockedHiddenContent } from "applesauce-core/helpers";
|
|
3
|
-
import { NostrEvent } from "applesauce-core/helpers/event";
|
|
3
|
+
import { KnownEvent, NostrEvent } from "applesauce-core/helpers/event";
|
|
4
4
|
export declare const WALLET_TOKEN_KIND = 7375;
|
|
5
|
-
/**
|
|
6
|
-
export
|
|
7
|
-
/**
|
|
8
|
-
export declare function
|
|
5
|
+
/** Validated wallet token event */
|
|
6
|
+
export type WalletTokenEvent = KnownEvent<typeof WALLET_TOKEN_KIND>;
|
|
7
|
+
/** Checks if an event is a valid wallet token event */
|
|
8
|
+
export declare function isValidWalletToken(event: NostrEvent): event is WalletTokenEvent;
|
|
9
9
|
export type TokenContent = {
|
|
10
10
|
/** Cashu mint for the proofs */
|
|
11
11
|
mint: string;
|
|
@@ -27,14 +27,14 @@ export declare const TokenContentSymbol: unique symbol;
|
|
|
27
27
|
export type UnlockedTokenContent = UnlockedHiddenContent & {
|
|
28
28
|
[TokenContentSymbol]: TokenContent;
|
|
29
29
|
};
|
|
30
|
+
/** Returns if token details are locked */
|
|
31
|
+
export declare function isTokenContentUnlocked<T extends NostrEvent>(token: T): token is T & UnlockedTokenContent;
|
|
30
32
|
/**
|
|
31
33
|
* Returns the decrypted and parsed details of a 7375 token event
|
|
32
34
|
* @throws {Error} If the token content is invalid
|
|
33
35
|
*/
|
|
34
36
|
export declare function getTokenContent(token: UnlockedTokenContent): TokenContent;
|
|
35
37
|
export declare function getTokenContent(token: NostrEvent): TokenContent | undefined;
|
|
36
|
-
/** Returns if token details are locked */
|
|
37
|
-
export declare function isTokenContentUnlocked<T extends NostrEvent>(token: T): token is T & UnlockedTokenContent;
|
|
38
38
|
/** Decrypts a k:7375 token event */
|
|
39
39
|
export declare function unlockTokenContent(token: NostrEvent, signer: HiddenContentSigner): Promise<TokenContent>;
|
|
40
40
|
/** Removes the unencrypted hidden content */
|
|
@@ -47,19 +47,10 @@ export declare function getTokenProofsTotal(token: UnlockedTokenContent): number
|
|
|
47
47
|
export declare function getTokenProofsTotal(token: NostrEvent): number | undefined;
|
|
48
48
|
/**
|
|
49
49
|
* Selects oldest tokens and proofs that total up to more than the min amount
|
|
50
|
+
* If mint is undefined, finds a mint with sufficient balance and selects only from that mint
|
|
50
51
|
* @throws {Error} If there are insufficient funds
|
|
51
52
|
*/
|
|
52
53
|
export declare function dumbTokenSelection(tokens: NostrEvent[], minAmount: number, mint?: string): {
|
|
53
54
|
events: NostrEvent[];
|
|
54
55
|
proofs: Proof[];
|
|
55
56
|
};
|
|
56
|
-
/**
|
|
57
|
-
* Returns a decoded cashu token inside an unicode emoji
|
|
58
|
-
* @see https://github.com/cashubtc/cashu.me/blob/1194a7b9ee2f43305e38304de7bef8839601ff4d/src/components/ReceiveTokenDialog.vue#L387
|
|
59
|
-
*/
|
|
60
|
-
export declare function decodeTokenFromEmojiString(str: string): Token | undefined;
|
|
61
|
-
/**
|
|
62
|
-
* Encodes a token into an emoji char
|
|
63
|
-
* @see https://github.com/cashubtc/cashu.me/blob/1194a7b9ee2f43305e38304de7bef8839601ff4d/src/components/SendTokenDialog.vue#L710
|
|
64
|
-
*/
|
|
65
|
-
export declare function encodeTokenToEmoji(token: Token | string, emoji?: string): string;
|