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
package/dist/helpers/tokens.js
CHANGED
|
@@ -1,28 +1,25 @@
|
|
|
1
|
-
import { getDecodedToken, getEncodedToken } from "@cashu/cashu-ts";
|
|
2
1
|
import { getHiddenContent, isHiddenContentUnlocked, lockHiddenContent, notifyEventUpdate, setHiddenContentEncryptionMethod, unlockHiddenContent, } from "applesauce-core/helpers";
|
|
2
|
+
import { ignoreDuplicateProofs } from "./cashu.js";
|
|
3
3
|
export const WALLET_TOKEN_KIND = 7375;
|
|
4
|
+
/** Checks if an event is a valid wallet token event */
|
|
5
|
+
export function isValidWalletToken(event) {
|
|
6
|
+
return event.kind === WALLET_TOKEN_KIND;
|
|
7
|
+
}
|
|
4
8
|
// Enable hidden content for wallet token kind
|
|
5
9
|
setHiddenContentEncryptionMethod(WALLET_TOKEN_KIND, "nip44");
|
|
6
|
-
/** Internal method for creating a unique id for each proof */
|
|
7
|
-
export function getProofUID(proof) {
|
|
8
|
-
return proof.id + proof.amount + proof.C + proof.secret;
|
|
9
|
-
}
|
|
10
|
-
/** Internal method to filter out duplicate proofs */
|
|
11
|
-
export function ignoreDuplicateProofs(seen = new Set()) {
|
|
12
|
-
return (proof) => {
|
|
13
|
-
const id = getProofUID(proof);
|
|
14
|
-
if (seen.has(id))
|
|
15
|
-
return false;
|
|
16
|
-
else {
|
|
17
|
-
seen.add(id);
|
|
18
|
-
return true;
|
|
19
|
-
}
|
|
20
|
-
};
|
|
21
|
-
}
|
|
22
10
|
/** Symbol for caching token content */
|
|
23
11
|
export const TokenContentSymbol = Symbol.for("token-content");
|
|
12
|
+
/** Returns if token details are locked */
|
|
13
|
+
export function isTokenContentUnlocked(token) {
|
|
14
|
+
// Wrap in try catch to avoid throwing validation errors
|
|
15
|
+
try {
|
|
16
|
+
return TokenContentSymbol in token || (isHiddenContentUnlocked(token) && getTokenContent(token) !== undefined);
|
|
17
|
+
}
|
|
18
|
+
catch { }
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
24
21
|
export function getTokenContent(token) {
|
|
25
|
-
if (
|
|
22
|
+
if (TokenContentSymbol in token)
|
|
26
23
|
return token[TokenContentSymbol];
|
|
27
24
|
// Get the hidden content
|
|
28
25
|
const plaintext = getHiddenContent(token);
|
|
@@ -41,13 +38,9 @@ export function getTokenContent(token) {
|
|
|
41
38
|
Reflect.set(token, TokenContentSymbol, details);
|
|
42
39
|
return details;
|
|
43
40
|
}
|
|
44
|
-
/** Returns if token details are locked */
|
|
45
|
-
export function isTokenContentUnlocked(token) {
|
|
46
|
-
return isHiddenContentUnlocked(token) && Reflect.has(token, TokenContentSymbol) === true;
|
|
47
|
-
}
|
|
48
41
|
/** Decrypts a k:7375 token event */
|
|
49
42
|
export async function unlockTokenContent(token, signer) {
|
|
50
|
-
if (
|
|
43
|
+
if (TokenContentSymbol in token)
|
|
51
44
|
return token[TokenContentSymbol];
|
|
52
45
|
// Unlock the hidden content
|
|
53
46
|
await unlockHiddenContent(token, signer);
|
|
@@ -72,12 +65,54 @@ export function getTokenProofsTotal(token) {
|
|
|
72
65
|
}
|
|
73
66
|
/**
|
|
74
67
|
* Selects oldest tokens and proofs that total up to more than the min amount
|
|
68
|
+
* If mint is undefined, finds a mint with sufficient balance and selects only from that mint
|
|
75
69
|
* @throws {Error} If there are insufficient funds
|
|
76
70
|
*/
|
|
77
71
|
export function dumbTokenSelection(tokens, minAmount, mint) {
|
|
78
|
-
//
|
|
72
|
+
// If mint is not specified, find a mint with sufficient balance
|
|
73
|
+
let targetMint = mint;
|
|
74
|
+
if (!targetMint) {
|
|
75
|
+
// Group tokens by mint and calculate total balance per mint
|
|
76
|
+
const tokensByMint = new Map();
|
|
77
|
+
for (const token of tokens) {
|
|
78
|
+
const content = getTokenContent(token);
|
|
79
|
+
if (!content)
|
|
80
|
+
continue; // Skip locked tokens
|
|
81
|
+
const tokenMint = content.mint;
|
|
82
|
+
if (!tokenMint)
|
|
83
|
+
continue;
|
|
84
|
+
if (!tokensByMint.has(tokenMint)) {
|
|
85
|
+
tokensByMint.set(tokenMint, []);
|
|
86
|
+
}
|
|
87
|
+
tokensByMint.get(tokenMint).push(token);
|
|
88
|
+
}
|
|
89
|
+
// Find a mint with sufficient balance
|
|
90
|
+
let foundMint;
|
|
91
|
+
for (const [mintKey, mintTokens] of tokensByMint) {
|
|
92
|
+
const seen = new Set();
|
|
93
|
+
let total = 0;
|
|
94
|
+
for (const token of mintTokens) {
|
|
95
|
+
const content = getTokenContent(token);
|
|
96
|
+
if (!content)
|
|
97
|
+
continue;
|
|
98
|
+
const proofs = content.proofs.filter(ignoreDuplicateProofs(seen));
|
|
99
|
+
total += proofs.reduce((t, p) => t + p.amount, 0);
|
|
100
|
+
}
|
|
101
|
+
if (total >= minAmount) {
|
|
102
|
+
foundMint = mintKey;
|
|
103
|
+
break;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
if (!foundMint)
|
|
107
|
+
throw new Error("Insufficient funds in any mint");
|
|
108
|
+
targetMint = foundMint;
|
|
109
|
+
}
|
|
110
|
+
// Filter tokens by the target mint and sort newest to oldest
|
|
79
111
|
const sorted = tokens
|
|
80
|
-
.filter((token) =>
|
|
112
|
+
.filter((token) => {
|
|
113
|
+
const content = getTokenContent(token);
|
|
114
|
+
return content?.mint === targetMint;
|
|
115
|
+
})
|
|
81
116
|
.sort((a, b) => b.created_at - a.created_at);
|
|
82
117
|
let amount = 0;
|
|
83
118
|
const seen = new Set();
|
|
@@ -91,6 +126,10 @@ export function dumbTokenSelection(tokens, minAmount, mint) {
|
|
|
91
126
|
// Skip locked tokens
|
|
92
127
|
if (!content)
|
|
93
128
|
continue;
|
|
129
|
+
// Verify mint matches (should always match due to filter, but double-check for safety)
|
|
130
|
+
if (content.mint !== targetMint) {
|
|
131
|
+
throw new Error(`Token mint mismatch: expected ${targetMint}, got ${content.mint}`);
|
|
132
|
+
}
|
|
94
133
|
// Get proofs and total
|
|
95
134
|
const proofs = content.proofs.filter(ignoreDuplicateProofs(seen));
|
|
96
135
|
const total = proofs.reduce((t, p) => t + p.amount, 0);
|
|
@@ -100,72 +139,3 @@ export function dumbTokenSelection(tokens, minAmount, mint) {
|
|
|
100
139
|
}
|
|
101
140
|
return { events: selectedTokens, proofs: selectedProofs };
|
|
102
141
|
}
|
|
103
|
-
/**
|
|
104
|
-
* Returns a decoded cashu token inside an unicode emoji
|
|
105
|
-
* @see https://github.com/cashubtc/cashu.me/blob/1194a7b9ee2f43305e38304de7bef8839601ff4d/src/components/ReceiveTokenDialog.vue#L387
|
|
106
|
-
*/
|
|
107
|
-
export function decodeTokenFromEmojiString(str) {
|
|
108
|
-
try {
|
|
109
|
-
let decoded = [];
|
|
110
|
-
const chars = Array.from(str);
|
|
111
|
-
if (!chars.length)
|
|
112
|
-
return undefined;
|
|
113
|
-
const fromVariationSelector = function (char) {
|
|
114
|
-
const codePoint = char.codePointAt(0);
|
|
115
|
-
if (codePoint === undefined)
|
|
116
|
-
return null;
|
|
117
|
-
// Handle Variation Selectors (VS1-VS16): U+FE00 to U+FE0F
|
|
118
|
-
if (codePoint >= 0xfe00 && codePoint <= 0xfe0f) {
|
|
119
|
-
// Maps FE00->0, FE01->1, ..., FE0F->15
|
|
120
|
-
const byteValue = codePoint - 0xfe00;
|
|
121
|
-
return String.fromCharCode(byteValue);
|
|
122
|
-
}
|
|
123
|
-
// Handle Variation Selectors Supplement (VS17-VS256): U+E0100 to U+E01EF
|
|
124
|
-
if (codePoint >= 0xe0100 && codePoint <= 0xe01ef) {
|
|
125
|
-
// Maps E0100->16, E0101->17, ..., E01EF->255
|
|
126
|
-
const byteValue = codePoint - 0xe0100 + 16;
|
|
127
|
-
return String.fromCharCode(byteValue);
|
|
128
|
-
}
|
|
129
|
-
// No Variation Selector
|
|
130
|
-
return null;
|
|
131
|
-
};
|
|
132
|
-
// Check all input chars for peanut data
|
|
133
|
-
for (const char of chars) {
|
|
134
|
-
let byte = fromVariationSelector(char);
|
|
135
|
-
if (byte === null && decoded.length > 0) {
|
|
136
|
-
break;
|
|
137
|
-
}
|
|
138
|
-
else if (byte === null) {
|
|
139
|
-
continue;
|
|
140
|
-
}
|
|
141
|
-
decoded.push(byte); // got some
|
|
142
|
-
}
|
|
143
|
-
// Switch out token if we found peanut data
|
|
144
|
-
let decodedString = decoded.join("");
|
|
145
|
-
return getDecodedToken(decodedString);
|
|
146
|
-
}
|
|
147
|
-
catch (error) {
|
|
148
|
-
return undefined;
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
/**
|
|
152
|
-
* Encodes a token into an emoji char
|
|
153
|
-
* @see https://github.com/cashubtc/cashu.me/blob/1194a7b9ee2f43305e38304de7bef8839601ff4d/src/components/SendTokenDialog.vue#L710
|
|
154
|
-
*/
|
|
155
|
-
export function encodeTokenToEmoji(token, emoji = "🥜") {
|
|
156
|
-
return (emoji +
|
|
157
|
-
Array.from(typeof token === "string" ? token : getEncodedToken(token))
|
|
158
|
-
.map((char) => {
|
|
159
|
-
const byteValue = char.charCodeAt(0);
|
|
160
|
-
// For byte values 0-15, use Variation Selectors (VS1-VS16): U+FE00 to U+FE0F
|
|
161
|
-
if (byteValue >= 0 && byteValue <= 15) {
|
|
162
|
-
return String.fromCodePoint(0xfe00 + byteValue);
|
|
163
|
-
}
|
|
164
|
-
// For byte values 16-255, use Variation Selectors Supplement (VS17-VS256): U+E0100 to U+E01EF
|
|
165
|
-
if (byteValue >= 16 && byteValue <= 255) {
|
|
166
|
-
return String.fromCodePoint(0xe0100 + (byteValue - 16));
|
|
167
|
-
}
|
|
168
|
-
return "";
|
|
169
|
-
})
|
|
170
|
-
.join(""));
|
|
171
|
-
}
|
package/dist/helpers/wallet.d.ts
CHANGED
|
@@ -1,13 +1,19 @@
|
|
|
1
1
|
import { HiddenContentSigner, UnlockedHiddenTags } from "applesauce-core/helpers";
|
|
2
|
-
import { NostrEvent } from "applesauce-core/helpers/event";
|
|
2
|
+
import { KnownEvent, NostrEvent } from "applesauce-core/helpers/event";
|
|
3
3
|
export declare const WALLET_KIND = 17375;
|
|
4
4
|
export declare const WALLET_BACKUP_KIND = 375;
|
|
5
|
+
/** Validated wallet event */
|
|
6
|
+
export type WalletEvent = KnownEvent<typeof WALLET_KIND>;
|
|
7
|
+
/** Checks if an event is a valid wallet event */
|
|
8
|
+
export declare function isValidWallet(event: NostrEvent): event is WalletEvent;
|
|
5
9
|
export declare const WalletPrivateKeySymbol: unique symbol;
|
|
6
10
|
export declare const WalletMintsSymbol: unique symbol;
|
|
11
|
+
export declare const WalletRelaysSymbol: unique symbol;
|
|
7
12
|
/** Type for unlocked wallet events */
|
|
8
13
|
export type UnlockedWallet = UnlockedHiddenTags & {
|
|
9
|
-
[WalletPrivateKeySymbol]
|
|
10
|
-
[WalletMintsSymbol]
|
|
14
|
+
[WalletPrivateKeySymbol]?: Uint8Array | null;
|
|
15
|
+
[WalletMintsSymbol]?: string[];
|
|
16
|
+
[WalletRelaysSymbol]?: string[];
|
|
11
17
|
};
|
|
12
18
|
/** Returns if a wallet is unlocked */
|
|
13
19
|
export declare function isWalletUnlocked<T extends NostrEvent>(wallet: T): wallet is T & UnlockedWallet;
|
|
@@ -15,12 +21,16 @@ export declare function isWalletUnlocked<T extends NostrEvent>(wallet: T): walle
|
|
|
15
21
|
export declare function getWalletMints(wallet: UnlockedWallet): string[];
|
|
16
22
|
export declare function getWalletMints(wallet: NostrEvent): string[];
|
|
17
23
|
/** Returns the wallets private key as a string */
|
|
18
|
-
export declare function getWalletPrivateKey(wallet: UnlockedWallet): Uint8Array;
|
|
19
|
-
export declare function getWalletPrivateKey(wallet: NostrEvent): Uint8Array | undefined;
|
|
24
|
+
export declare function getWalletPrivateKey(wallet: UnlockedWallet): Uint8Array | null;
|
|
25
|
+
export declare function getWalletPrivateKey(wallet: NostrEvent): Uint8Array | undefined | null;
|
|
26
|
+
/** Returns the wallets relays */
|
|
27
|
+
export declare function getWalletRelays(wallet: UnlockedWallet): string[];
|
|
28
|
+
export declare function getWalletRelays(wallet: NostrEvent): string[] | undefined;
|
|
20
29
|
/** Unlocks a wallet and returns the hidden tags */
|
|
21
30
|
export declare function unlockWallet(wallet: NostrEvent, signer: HiddenContentSigner): Promise<{
|
|
22
31
|
mints: string[];
|
|
23
|
-
privateKey
|
|
32
|
+
privateKey: Uint8Array | null;
|
|
33
|
+
relays: string[];
|
|
24
34
|
}>;
|
|
25
35
|
/** Locks a wallet event */
|
|
26
36
|
export declare function lockWallet(wallet: NostrEvent): void;
|
package/dist/helpers/wallet.js
CHANGED
|
@@ -1,22 +1,26 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { getHiddenTags, hasHiddenTags, isHiddenTagsUnlocked, lockHiddenTags, notifyEventUpdate, relaySet, setHiddenTagsEncryptionMethod, unlockHiddenTags, } from "applesauce-core/helpers";
|
|
2
|
+
import { hexToBytes } from "applesauce-core/helpers/event";
|
|
3
3
|
export const WALLET_KIND = 17375;
|
|
4
4
|
export const WALLET_BACKUP_KIND = 375;
|
|
5
|
+
/** Checks if an event is a valid wallet event */
|
|
6
|
+
export function isValidWallet(event) {
|
|
7
|
+
return event.kind === WALLET_KIND && hasHiddenTags(event);
|
|
8
|
+
}
|
|
5
9
|
// Enable hidden content for wallet kinds
|
|
6
10
|
setHiddenTagsEncryptionMethod(WALLET_KIND, "nip44");
|
|
7
11
|
setHiddenTagsEncryptionMethod(WALLET_BACKUP_KIND, "nip44");
|
|
8
12
|
export const WalletPrivateKeySymbol = Symbol.for("wallet-private-key");
|
|
9
13
|
export const WalletMintsSymbol = Symbol.for("wallet-mints");
|
|
14
|
+
export const WalletRelaysSymbol = Symbol.for("wallet-relays");
|
|
10
15
|
/** Returns if a wallet is unlocked */
|
|
11
16
|
export function isWalletUnlocked(wallet) {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
Reflect.has(wallet, WalletMintsSymbol));
|
|
17
|
+
// No need for try catch or proactivly parsing here since it only depends on hidden tags
|
|
18
|
+
return isHiddenTagsUnlocked(wallet);
|
|
15
19
|
}
|
|
16
20
|
export function getWalletMints(wallet) {
|
|
17
21
|
// Return cached value if it exists
|
|
18
|
-
if (
|
|
19
|
-
return
|
|
22
|
+
if (WalletMintsSymbol in wallet)
|
|
23
|
+
return wallet[WalletMintsSymbol];
|
|
20
24
|
// Get hidden tags
|
|
21
25
|
const tags = getHiddenTags(wallet);
|
|
22
26
|
if (!tags)
|
|
@@ -28,33 +32,55 @@ export function getWalletMints(wallet) {
|
|
|
28
32
|
return mints;
|
|
29
33
|
}
|
|
30
34
|
export function getWalletPrivateKey(wallet) {
|
|
31
|
-
if (
|
|
32
|
-
return
|
|
35
|
+
if (WalletPrivateKeySymbol in wallet)
|
|
36
|
+
return wallet[WalletPrivateKeySymbol];
|
|
33
37
|
// Get hidden tags
|
|
34
38
|
const tags = getHiddenTags(wallet);
|
|
35
39
|
if (!tags)
|
|
36
40
|
return undefined;
|
|
37
41
|
// Parse private key
|
|
38
42
|
const privkey = tags.find((t) => t[0] === "privkey" && t[1])?.[1];
|
|
39
|
-
const key = privkey ? hexToBytes(privkey) :
|
|
43
|
+
const key = privkey ? hexToBytes(privkey) : null;
|
|
40
44
|
// Set the cached value
|
|
41
45
|
Reflect.set(wallet, WalletPrivateKeySymbol, key);
|
|
42
46
|
return key;
|
|
43
47
|
}
|
|
48
|
+
export function getWalletRelays(wallet) {
|
|
49
|
+
// Return cached value if it exists
|
|
50
|
+
if (WalletRelaysSymbol in wallet)
|
|
51
|
+
return wallet[WalletRelaysSymbol];
|
|
52
|
+
// Get hidden tags
|
|
53
|
+
const tags = getHiddenTags(wallet);
|
|
54
|
+
if (!tags)
|
|
55
|
+
return undefined;
|
|
56
|
+
// Get relays
|
|
57
|
+
const urls = tags.filter((t) => t[0] === "relay" && t[1]).map((t) => t[1]);
|
|
58
|
+
const relays = relaySet(urls);
|
|
59
|
+
// Set the cached value
|
|
60
|
+
Reflect.set(wallet, WalletRelaysSymbol, relays);
|
|
61
|
+
return relays;
|
|
62
|
+
}
|
|
44
63
|
/** Unlocks a wallet and returns the hidden tags */
|
|
45
64
|
export async function unlockWallet(wallet, signer) {
|
|
46
|
-
if (
|
|
47
|
-
return {
|
|
65
|
+
if (WalletPrivateKeySymbol in wallet && WalletMintsSymbol in wallet && WalletRelaysSymbol in wallet)
|
|
66
|
+
return {
|
|
67
|
+
mints: wallet[WalletMintsSymbol],
|
|
68
|
+
privateKey: wallet[WalletPrivateKeySymbol],
|
|
69
|
+
relays: wallet[WalletRelaysSymbol],
|
|
70
|
+
};
|
|
48
71
|
// Unlock hidden tags if needed
|
|
49
72
|
await unlockHiddenTags(wallet, signer);
|
|
50
73
|
// Read the wallet mints and private key
|
|
51
74
|
const mints = getWalletMints(wallet);
|
|
52
75
|
if (!mints)
|
|
53
76
|
throw new Error("Failed to unlock wallet mints");
|
|
54
|
-
const
|
|
77
|
+
const relays = getWalletRelays(wallet);
|
|
78
|
+
if (!relays)
|
|
79
|
+
throw new Error("Failed to unlock wallet relays");
|
|
80
|
+
const key = getWalletPrivateKey(wallet) ?? null;
|
|
55
81
|
// Notify the event store
|
|
56
82
|
notifyEventUpdate(wallet);
|
|
57
|
-
return { mints, privateKey: key };
|
|
83
|
+
return { mints, privateKey: key, relays };
|
|
58
84
|
}
|
|
59
85
|
/** Locks a wallet event */
|
|
60
86
|
export function lockWallet(wallet) {
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export * as Actions from "./actions/index.js";
|
|
2
2
|
export * as Blueprints from "./blueprints/index.js";
|
|
3
|
+
export * as Casts from "./casts/index.js";
|
|
3
4
|
export * as Helpers from "./helpers/index.js";
|
|
4
5
|
export * as Models from "./models/index.js";
|
|
5
6
|
export * as Operations from "./operations/index.js";
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export * as Actions from "./actions/index.js";
|
|
2
2
|
export * as Blueprints from "./blueprints/index.js";
|
|
3
|
+
export * as Casts from "./casts/index.js";
|
|
3
4
|
export * as Helpers from "./helpers/index.js";
|
|
4
5
|
export * as Models from "./models/index.js";
|
|
5
6
|
export * as Operations from "./operations/index.js";
|
package/dist/models/history.d.ts
CHANGED
|
@@ -3,4 +3,4 @@ import { NostrEvent } from "applesauce-core/helpers/event";
|
|
|
3
3
|
/** A model that returns an array of redeemed event ids for a wallet */
|
|
4
4
|
export declare function WalletRedeemedModel(pubkey: string): Model<string[]>;
|
|
5
5
|
/** A model that returns a timeline of wallet history events */
|
|
6
|
-
export declare function WalletHistoryModel(pubkey: string,
|
|
6
|
+
export declare function WalletHistoryModel(pubkey: string, unlocked?: boolean | undefined): Model<NostrEvent[]>;
|
package/dist/models/history.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { identity, map, scan } from "rxjs";
|
|
2
2
|
import { getHistoryRedeemed, isHistoryContentUnlocked, WALLET_HISTORY_KIND } from "../helpers/history.js";
|
|
3
3
|
/** A model that returns an array of redeemed event ids for a wallet */
|
|
4
4
|
export function WalletRedeemedModel(pubkey) {
|
|
@@ -7,15 +7,12 @@ export function WalletRedeemedModel(pubkey) {
|
|
|
7
7
|
.pipe(scan((ids, history) => [...ids, ...getHistoryRedeemed(history)], []));
|
|
8
8
|
}
|
|
9
9
|
/** A model that returns a timeline of wallet history events */
|
|
10
|
-
export function WalletHistoryModel(pubkey,
|
|
10
|
+
export function WalletHistoryModel(pubkey, unlocked) {
|
|
11
11
|
return (events) => {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
else
|
|
18
|
-
return history.filter((entry) => isHistoryContentUnlocked(entry) === locked);
|
|
19
|
-
}));
|
|
12
|
+
return events
|
|
13
|
+
.timeline({ kinds: [WALLET_HISTORY_KIND], authors: [pubkey] })
|
|
14
|
+
.pipe(unlocked !== undefined
|
|
15
|
+
? map((events) => events.filter((e) => isHistoryContentUnlocked(e) === unlocked))
|
|
16
|
+
: identity);
|
|
20
17
|
};
|
|
21
18
|
}
|
package/dist/models/index.d.ts
CHANGED
package/dist/models/index.js
CHANGED
package/dist/models/nutzap.d.ts
CHANGED
|
@@ -6,3 +6,5 @@ import { NUTZAP_KIND } from "../helpers/nutzap.js";
|
|
|
6
6
|
export declare function EventNutZapzModel(event: NostrEvent): Model<KnownEvent<typeof NUTZAP_KIND>[]>;
|
|
7
7
|
/** A model that returns all nutzaps for a users profile */
|
|
8
8
|
export declare function ProfileNutZapzModel(pubkey: string): Model<KnownEvent<typeof NUTZAP_KIND>[]>;
|
|
9
|
+
/** A model that returns all nutzap event ids received by a user */
|
|
10
|
+
export declare function ReceivedNutzapsModel(pubkey: string): Model<string[]>;
|
package/dist/models/nutzap.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { map } from "rxjs";
|
|
2
2
|
import { buildCommonEventRelationFilters } from "applesauce-core/helpers/model";
|
|
3
|
+
import { getHistoryRedeemed } from "../helpers/history.js";
|
|
3
4
|
import { getNutzapPointer, isValidNutzap, NUTZAP_KIND } from "../helpers/nutzap.js";
|
|
5
|
+
import { WalletHistoryModel } from "./history.js";
|
|
4
6
|
/** A model that returns all nutzap events for an event */
|
|
5
7
|
export function EventNutZapzModel(event) {
|
|
6
8
|
return (events) => events.timeline(buildCommonEventRelationFilters({ kinds: [NUTZAP_KIND] }, event)).pipe(map((events) =>
|
|
@@ -15,3 +17,9 @@ export function ProfileNutZapzModel(pubkey) {
|
|
|
15
17
|
// filter out nutzaps that are for events
|
|
16
18
|
map((zaps) => zaps.filter((zap) => getNutzapPointer(zap) === undefined)));
|
|
17
19
|
}
|
|
20
|
+
/** A model that returns all nutzap event ids received by a user */
|
|
21
|
+
export function ReceivedNutzapsModel(pubkey) {
|
|
22
|
+
return (events) => events
|
|
23
|
+
.model(WalletHistoryModel, pubkey, true)
|
|
24
|
+
.pipe(map((entries) => Array.from(new Set(entries.flatMap(getHistoryRedeemed)))));
|
|
25
|
+
}
|
package/dist/models/tokens.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Model } from "applesauce-core";
|
|
2
2
|
import { NostrEvent } from "applesauce-core/helpers/event";
|
|
3
|
-
/** A model that subscribes to all token events for a wallet, passing
|
|
4
|
-
export declare function WalletTokensModel(pubkey: string,
|
|
3
|
+
/** A model that subscribes to all token events for a wallet, passing unlocked will filter by token unlocked status */
|
|
4
|
+
export declare function WalletTokensModel(pubkey: string, unlocked?: boolean | undefined): Model<NostrEvent[]>;
|
|
5
5
|
/** A model that returns the visible balance of a wallet for each mint */
|
|
6
6
|
export declare function WalletBalanceModel(pubkey: string): Model<Record<string, number>>;
|
package/dist/models/tokens.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { watchEventsUpdates } from "applesauce-core";
|
|
2
|
+
import { identity, map } from "rxjs";
|
|
3
|
+
import { ignoreDuplicateProofs } from "../helpers/cashu.js";
|
|
4
|
+
import { getTokenContent, isTokenContentUnlocked, WALLET_TOKEN_KIND } from "../helpers/tokens.js";
|
|
3
5
|
/** removes deleted events from sorted array */
|
|
4
6
|
function filterDeleted(tokens) {
|
|
5
7
|
const deleted = new Set();
|
|
@@ -10,7 +12,7 @@ function filterDeleted(tokens) {
|
|
|
10
12
|
if (deleted.has(token.id))
|
|
11
13
|
return false;
|
|
12
14
|
// add ids to deleted array
|
|
13
|
-
if (
|
|
15
|
+
if (isTokenContentUnlocked(token)) {
|
|
14
16
|
const details = getTokenContent(token);
|
|
15
17
|
for (const id of details.del)
|
|
16
18
|
deleted.add(id);
|
|
@@ -19,19 +21,12 @@ function filterDeleted(tokens) {
|
|
|
19
21
|
})
|
|
20
22
|
.reverse();
|
|
21
23
|
}
|
|
22
|
-
/** A model that subscribes to all token events for a wallet, passing
|
|
23
|
-
export function WalletTokensModel(pubkey,
|
|
24
|
+
/** A model that subscribes to all token events for a wallet, passing unlocked will filter by token unlocked status */
|
|
25
|
+
export function WalletTokensModel(pubkey, unlocked) {
|
|
24
26
|
return (events) => {
|
|
25
|
-
|
|
26
|
-
const timeline = events.timeline({ kinds: [WALLET_TOKEN_KIND], authors: [pubkey] });
|
|
27
|
-
return combineLatest([updates, timeline]).pipe(
|
|
27
|
+
return events.timeline({ kinds: [WALLET_TOKEN_KIND], authors: [pubkey] }).pipe(
|
|
28
28
|
// filter out locked tokens
|
|
29
|
-
map((
|
|
30
|
-
if (locked === undefined)
|
|
31
|
-
return tokens;
|
|
32
|
-
else
|
|
33
|
-
return tokens.filter((t) => isTokenContentUnlocked(t) === locked);
|
|
34
|
-
}),
|
|
29
|
+
unlocked !== undefined ? map((tokens) => tokens.filter((t) => isTokenContentUnlocked(t) === unlocked)) : identity,
|
|
35
30
|
// remove deleted events
|
|
36
31
|
map(filterDeleted));
|
|
37
32
|
};
|
|
@@ -39,9 +34,11 @@ export function WalletTokensModel(pubkey, locked) {
|
|
|
39
34
|
/** A model that returns the visible balance of a wallet for each mint */
|
|
40
35
|
export function WalletBalanceModel(pubkey) {
|
|
41
36
|
return (events) => {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
37
|
+
return events.timeline({ kinds: [WALLET_TOKEN_KIND], authors: [pubkey] }).pipe(
|
|
38
|
+
// Watch for updates to the tokens
|
|
39
|
+
watchEventsUpdates(events),
|
|
40
|
+
// Ignore locked tokens
|
|
41
|
+
map((tokens) => tokens.filter((t) => isTokenContentUnlocked(t))),
|
|
45
42
|
// filter out deleted tokens
|
|
46
43
|
map(filterDeleted),
|
|
47
44
|
// map tokens to totals
|
|
@@ -17,7 +17,7 @@ export function setHistoryContent(content) {
|
|
|
17
17
|
setSingletonTag(["amount", String(content.amount)], true),
|
|
18
18
|
includeHistoryCreatedTags(content.created),
|
|
19
19
|
];
|
|
20
|
-
if (content.fee !== undefined)
|
|
20
|
+
if (content.fee !== undefined && content.fee > 0)
|
|
21
21
|
operations.push(setSingletonTag(["fee", String(content.fee)], true));
|
|
22
22
|
if (content.mint !== undefined)
|
|
23
23
|
operations.push(setSingletonTag(["mint", content.mint], true));
|
package/dist/operations/index.js
CHANGED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { EventOperation } from "applesauce-core";
|
|
2
|
+
/** Sets the relays for a nutzap info event, replacing all existing relay tags */
|
|
3
|
+
export declare function setNutzapInfoRelays(relays: string[]): EventOperation;
|
|
4
|
+
/** Sets the mints for a nutzap info event, replacing all existing mint tags */
|
|
5
|
+
export declare function setNutzapInfoMints(mints: Array<{
|
|
6
|
+
url: string;
|
|
7
|
+
units?: string[];
|
|
8
|
+
}>): EventOperation;
|
|
9
|
+
/** Adds a relay tag to a nutzap info event */
|
|
10
|
+
export declare function addNutzapInfoRelay(url: string | URL): EventOperation;
|
|
11
|
+
/** Removes a relay tag from a nutzap info event */
|
|
12
|
+
export declare function removeNutzapInfoRelay(url: string | URL): EventOperation;
|
|
13
|
+
/** Adds a mint tag to a nutzap info event */
|
|
14
|
+
export declare function addNutzapInfoMint(mint: {
|
|
15
|
+
url: string;
|
|
16
|
+
units?: string[];
|
|
17
|
+
}): EventOperation;
|
|
18
|
+
/** Removes a mint tag from a nutzap info event */
|
|
19
|
+
export declare function removeNutzapInfoMint(url: string): EventOperation;
|
|
20
|
+
/** Sets the pubkey for a nutzap info event */
|
|
21
|
+
export declare function setNutzapInfoPubkey(key: Uint8Array): EventOperation;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { modifyPublicTags } from "applesauce-core/operations";
|
|
2
|
+
import { removeNameValueTag, setSingletonTag } from "applesauce-core/operations/tag/common";
|
|
3
|
+
import { addRelayTag, removeRelayTag } from "applesauce-core/operations/tag/relay";
|
|
4
|
+
import { normalizeURL } from "applesauce-core/helpers/url";
|
|
5
|
+
import { createNutzapInfoPubkeyTag } from "../helpers/nutzap-info.js";
|
|
6
|
+
/** Sets the relays for a nutzap info event, replacing all existing relay tags */
|
|
7
|
+
export function setNutzapInfoRelays(relays) {
|
|
8
|
+
return modifyPublicTags((tags) => [
|
|
9
|
+
// remove all existing relay tags
|
|
10
|
+
...tags.filter((t) => t[0] !== "relay"),
|
|
11
|
+
// add new relay tags
|
|
12
|
+
...relays.map((relay) => ["relay", relay]),
|
|
13
|
+
]);
|
|
14
|
+
}
|
|
15
|
+
/** Sets the mints for a nutzap info event, replacing all existing mint tags */
|
|
16
|
+
export function setNutzapInfoMints(mints) {
|
|
17
|
+
return modifyPublicTags((tags) => [
|
|
18
|
+
// remove all existing mint tags
|
|
19
|
+
...tags.filter((t) => t[0] !== "mint"),
|
|
20
|
+
// add new mint tags
|
|
21
|
+
...mints.map((mint) => {
|
|
22
|
+
return mint.units ? ["mint", mint.url, ...mint.units] : ["mint", mint.url];
|
|
23
|
+
}),
|
|
24
|
+
]);
|
|
25
|
+
}
|
|
26
|
+
/** Adds a relay tag to a nutzap info event */
|
|
27
|
+
export function addNutzapInfoRelay(url) {
|
|
28
|
+
url = normalizeURL(url).toString();
|
|
29
|
+
return modifyPublicTags(addRelayTag(url, "relay", false));
|
|
30
|
+
}
|
|
31
|
+
/** Removes a relay tag from a nutzap info event */
|
|
32
|
+
export function removeNutzapInfoRelay(url) {
|
|
33
|
+
url = normalizeURL(url).toString();
|
|
34
|
+
return modifyPublicTags(removeRelayTag(url, "relay"));
|
|
35
|
+
}
|
|
36
|
+
/** Adds a mint tag to a nutzap info event */
|
|
37
|
+
export function addNutzapInfoMint(mint) {
|
|
38
|
+
return modifyPublicTags((tags) => {
|
|
39
|
+
// Find existing mint tag with the same URL
|
|
40
|
+
const existingIndex = tags.findIndex((t) => t[0] === "mint" && t[1] === mint.url);
|
|
41
|
+
if (existingIndex !== -1) {
|
|
42
|
+
// Merge units if mint tag already exists
|
|
43
|
+
const existingTag = tags[existingIndex];
|
|
44
|
+
const existingUnits = existingTag.slice(2); // Get units from existing tag (everything after ["mint", url])
|
|
45
|
+
const newUnits = mint.units || [];
|
|
46
|
+
// Merge units, removing duplicates while preserving order
|
|
47
|
+
const mergedUnits = [...existingUnits];
|
|
48
|
+
for (const unit of newUnits) {
|
|
49
|
+
if (!mergedUnits.includes(unit)) {
|
|
50
|
+
mergedUnits.push(unit);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
// Replace existing tag with merged tag
|
|
54
|
+
const mergedTag = mergedUnits.length > 0 ? ["mint", mint.url, ...mergedUnits] : ["mint", mint.url];
|
|
55
|
+
return tags.map((t, i) => (i === existingIndex ? mergedTag : t));
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
// Add new tag if it doesn't exist
|
|
59
|
+
const tag = mint.units ? ["mint", mint.url, ...mint.units] : ["mint", mint.url];
|
|
60
|
+
return [...tags, tag];
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
/** Removes a mint tag from a nutzap info event */
|
|
65
|
+
export function removeNutzapInfoMint(url) {
|
|
66
|
+
return modifyPublicTags(removeNameValueTag(["mint", url]));
|
|
67
|
+
}
|
|
68
|
+
/** Sets the pubkey for a nutzap info event */
|
|
69
|
+
export function setNutzapInfoPubkey(key) {
|
|
70
|
+
return modifyPublicTags(setSingletonTag(createNutzapInfoPubkeyTag(key), true));
|
|
71
|
+
}
|
|
@@ -1,8 +1,17 @@
|
|
|
1
|
-
import { EventOperation } from "applesauce-core/event-factory";
|
|
1
|
+
import { EventOperation, TagOperation } from "applesauce-core/event-factory";
|
|
2
2
|
import { NostrEvent } from "applesauce-core/helpers/event";
|
|
3
3
|
/** Sets the content of a kind 375 wallet backup event */
|
|
4
4
|
export declare function setBackupContent(wallet: NostrEvent): EventOperation;
|
|
5
5
|
/** Sets the "mint" tags in a wallet event */
|
|
6
|
+
export declare function setMintTags(mints: string[]): TagOperation;
|
|
6
7
|
export declare function setMints(mints: string[]): EventOperation;
|
|
7
8
|
/** Sets the "privkey" tag on a wallet event */
|
|
9
|
+
export declare function setPrivateKeyTag(privateKey: Uint8Array): TagOperation;
|
|
8
10
|
export declare function setPrivateKey(privateKey: Uint8Array): EventOperation;
|
|
11
|
+
/** Adds a relay tag to a wallet event */
|
|
12
|
+
export declare function addWalletRelay(url: string | URL): EventOperation;
|
|
13
|
+
/** Removes a relay tag from a wallet event */
|
|
14
|
+
export declare function removeWalletRelay(url: string | URL): EventOperation;
|
|
15
|
+
/** Sets the relay tags on a wallet event, replacing all existing relay tags */
|
|
16
|
+
export declare function setRelayTags(relays: (string | URL)[]): TagOperation;
|
|
17
|
+
export declare function setRelays(relays: (string | URL)[]): EventOperation;
|