applesauce-wallet-connect 0.0.0-next-20250808173123
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/LICENSE +21 -0
- package/README.md +112 -0
- package/dist/actions/index.d.ts +1 -0
- package/dist/actions/index.js +1 -0
- package/dist/actions/tokens.d.ts +17 -0
- package/dist/actions/tokens.js +110 -0
- package/dist/actions/wallet.d.ts +13 -0
- package/dist/actions/wallet.js +64 -0
- package/dist/actions/zap-info.d.ts +22 -0
- package/dist/actions/zap-info.js +83 -0
- package/dist/actions/zaps.d.ts +8 -0
- package/dist/actions/zaps.js +30 -0
- package/dist/blueprints/history.d.ts +4 -0
- package/dist/blueprints/history.js +11 -0
- package/dist/blueprints/index.d.ts +4 -0
- package/dist/blueprints/index.js +4 -0
- package/dist/blueprints/info.d.ts +4 -0
- package/dist/blueprints/info.js +8 -0
- package/dist/blueprints/notification.d.ts +15 -0
- package/dist/blueprints/notification.js +21 -0
- package/dist/blueprints/request.d.ts +9 -0
- package/dist/blueprints/request.js +12 -0
- package/dist/blueprints/response.d.ts +5 -0
- package/dist/blueprints/response.js +10 -0
- package/dist/blueprints/support.d.ts +4 -0
- package/dist/blueprints/support.js +8 -0
- package/dist/blueprints/tokens.d.ts +7 -0
- package/dist/blueprints/tokens.js +11 -0
- package/dist/blueprints/wallet.d.ts +5 -0
- package/dist/blueprints/wallet.js +11 -0
- package/dist/blueprints/zaps.d.ts +8 -0
- package/dist/blueprints/zaps.js +12 -0
- package/dist/helpers/animated-qr.d.ts +30 -0
- package/dist/helpers/animated-qr.js +71 -0
- package/dist/helpers/connect-uri.d.ts +12 -0
- package/dist/helpers/connect-uri.js +23 -0
- package/dist/helpers/encryption.d.ts +2 -0
- package/dist/helpers/encryption.js +1 -0
- package/dist/helpers/error.d.ts +55 -0
- package/dist/helpers/error.js +81 -0
- package/dist/helpers/history.d.ts +26 -0
- package/dist/helpers/history.js +47 -0
- package/dist/helpers/index.d.ts +7 -0
- package/dist/helpers/index.js +7 -0
- package/dist/helpers/info.d.ts +34 -0
- package/dist/helpers/info.js +97 -0
- package/dist/helpers/methods.d.ts +1 -0
- package/dist/helpers/methods.js +1 -0
- package/dist/helpers/notification.d.ts +28 -0
- package/dist/helpers/notification.js +28 -0
- package/dist/helpers/nutzap.d.ts +27 -0
- package/dist/helpers/nutzap.js +66 -0
- package/dist/helpers/request.d.ts +131 -0
- package/dist/helpers/request.js +51 -0
- package/dist/helpers/response.d.ts +138 -0
- package/dist/helpers/response.js +35 -0
- package/dist/helpers/support.d.ts +34 -0
- package/dist/helpers/support.js +97 -0
- package/dist/helpers/tokens.d.ts +58 -0
- package/dist/helpers/tokens.js +162 -0
- package/dist/helpers/wallet.d.ts +15 -0
- package/dist/helpers/wallet.js +41 -0
- package/dist/helpers/zap-info.d.ts +19 -0
- package/dist/helpers/zap-info.js +42 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +5 -0
- package/dist/interface.d.ts +6 -0
- package/dist/interface.js +1 -0
- package/dist/models/history.d.ts +6 -0
- package/dist/models/history.js +21 -0
- package/dist/models/index.d.ts +4 -0
- package/dist/models/index.js +4 -0
- package/dist/models/nutzap.d.ts +6 -0
- package/dist/models/nutzap.js +16 -0
- package/dist/models/tokens.d.ts +6 -0
- package/dist/models/tokens.js +58 -0
- package/dist/models/wallet.d.ts +13 -0
- package/dist/models/wallet.js +18 -0
- package/dist/operations/history.d.ts +7 -0
- package/dist/operations/history.js +34 -0
- package/dist/operations/index.d.ts +5 -0
- package/dist/operations/index.js +5 -0
- package/dist/operations/nutzap.d.ts +14 -0
- package/dist/operations/nutzap.js +33 -0
- package/dist/operations/tokens.d.ts +4 -0
- package/dist/operations/tokens.js +24 -0
- package/dist/operations/wallet.d.ts +8 -0
- package/dist/operations/wallet.js +30 -0
- package/dist/operations/zap-info.d.ts +10 -0
- package/dist/operations/zap-info.js +17 -0
- package/dist/types.d.ts +6 -0
- package/dist/types.js +1 -0
- package/dist/wallet-connect.d.ts +111 -0
- package/dist/wallet-connect.js +271 -0
- package/dist/wallet-service.d.ts +111 -0
- package/dist/wallet-service.js +270 -0
- package/package.json +83 -0
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { getDecodedToken, getEncodedToken } from "@cashu/cashu-ts";
|
|
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");
|
|
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
|
+
export const TokenContentSymbol = Symbol.for("token-content");
|
|
23
|
+
/**
|
|
24
|
+
* Returns the decrypted and parsed details of a 7375 token event
|
|
25
|
+
* @throws
|
|
26
|
+
*/
|
|
27
|
+
export function getTokenContent(token) {
|
|
28
|
+
if (isHiddenTagsLocked(token))
|
|
29
|
+
return undefined;
|
|
30
|
+
return getOrComputeCachedValue(token, TokenContentSymbol, () => {
|
|
31
|
+
const plaintext = getHiddenContent(token);
|
|
32
|
+
if (!plaintext)
|
|
33
|
+
throw new Error("Token is locked");
|
|
34
|
+
const details = JSON.parse(plaintext);
|
|
35
|
+
if (!details.mint)
|
|
36
|
+
throw new Error("Token missing mint");
|
|
37
|
+
if (!details.proofs)
|
|
38
|
+
throw new Error("Token missing proofs");
|
|
39
|
+
if (!details.del)
|
|
40
|
+
details.del = [];
|
|
41
|
+
return details;
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
/** Returns if token details are locked */
|
|
45
|
+
export function isTokenContentLocked(token) {
|
|
46
|
+
return isHiddenContentLocked(token);
|
|
47
|
+
}
|
|
48
|
+
/** Decrypts a k:7375 token event */
|
|
49
|
+
export async function unlockTokenContent(token, signer) {
|
|
50
|
+
if (isHiddenContentLocked(token))
|
|
51
|
+
await unlockHiddenContent(token, signer);
|
|
52
|
+
return getTokenContent(token);
|
|
53
|
+
}
|
|
54
|
+
/** Removes the unencrypted hidden content */
|
|
55
|
+
export function lockTokenContent(token) {
|
|
56
|
+
Reflect.deleteProperty(token, TokenContentSymbol);
|
|
57
|
+
lockHiddenContent(token);
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Gets the totaled amount of proofs in a token event
|
|
61
|
+
* @param token The token event to calculate the total
|
|
62
|
+
*/
|
|
63
|
+
export function getTokenProofsTotal(token) {
|
|
64
|
+
if (isTokenContentLocked(token))
|
|
65
|
+
return undefined;
|
|
66
|
+
const content = getTokenContent(token);
|
|
67
|
+
return content.proofs.reduce((t, p) => t + p.amount, 0);
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Selects oldest tokens and proofs that total up to more than the min amount
|
|
71
|
+
* @throws
|
|
72
|
+
*/
|
|
73
|
+
export function dumbTokenSelection(tokens, minAmount, mint) {
|
|
74
|
+
// sort newest to oldest
|
|
75
|
+
const sorted = tokens
|
|
76
|
+
.filter((token) => !isTokenContentLocked(token) && (mint ? getTokenContent(token).mint === mint : true))
|
|
77
|
+
.sort((a, b) => b.created_at - a.created_at);
|
|
78
|
+
let amount = 0;
|
|
79
|
+
const seen = new Set();
|
|
80
|
+
const selectedTokens = [];
|
|
81
|
+
const selectedProofs = [];
|
|
82
|
+
while (amount < minAmount) {
|
|
83
|
+
const token = sorted.pop();
|
|
84
|
+
if (!token)
|
|
85
|
+
throw new Error("Insufficient funds");
|
|
86
|
+
const proofs = getTokenContent(token).proofs.filter(ignoreDuplicateProofs(seen));
|
|
87
|
+
const total = proofs.reduce((t, p) => t + p.amount, 0);
|
|
88
|
+
selectedTokens.push(token);
|
|
89
|
+
selectedProofs.push(...proofs);
|
|
90
|
+
amount += total;
|
|
91
|
+
}
|
|
92
|
+
return { events: selectedTokens, proofs: selectedProofs };
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Returns a decoded cashu token inside an unicode emoji
|
|
96
|
+
* @see https://github.com/cashubtc/cashu.me/blob/1194a7b9ee2f43305e38304de7bef8839601ff4d/src/components/ReceiveTokenDialog.vue#L387
|
|
97
|
+
*/
|
|
98
|
+
export function decodeTokenFromEmojiString(str) {
|
|
99
|
+
try {
|
|
100
|
+
let decoded = [];
|
|
101
|
+
const chars = Array.from(str);
|
|
102
|
+
if (!chars.length)
|
|
103
|
+
return undefined;
|
|
104
|
+
const fromVariationSelector = function (char) {
|
|
105
|
+
const codePoint = char.codePointAt(0);
|
|
106
|
+
if (codePoint === undefined)
|
|
107
|
+
return null;
|
|
108
|
+
// Handle Variation Selectors (VS1-VS16): U+FE00 to U+FE0F
|
|
109
|
+
if (codePoint >= 0xfe00 && codePoint <= 0xfe0f) {
|
|
110
|
+
// Maps FE00->0, FE01->1, ..., FE0F->15
|
|
111
|
+
const byteValue = codePoint - 0xfe00;
|
|
112
|
+
return String.fromCharCode(byteValue);
|
|
113
|
+
}
|
|
114
|
+
// Handle Variation Selectors Supplement (VS17-VS256): U+E0100 to U+E01EF
|
|
115
|
+
if (codePoint >= 0xe0100 && codePoint <= 0xe01ef) {
|
|
116
|
+
// Maps E0100->16, E0101->17, ..., E01EF->255
|
|
117
|
+
const byteValue = codePoint - 0xe0100 + 16;
|
|
118
|
+
return String.fromCharCode(byteValue);
|
|
119
|
+
}
|
|
120
|
+
// No Variation Selector
|
|
121
|
+
return null;
|
|
122
|
+
};
|
|
123
|
+
// Check all input chars for peanut data
|
|
124
|
+
for (const char of chars) {
|
|
125
|
+
let byte = fromVariationSelector(char);
|
|
126
|
+
if (byte === null && decoded.length > 0) {
|
|
127
|
+
break;
|
|
128
|
+
}
|
|
129
|
+
else if (byte === null) {
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
decoded.push(byte); // got some
|
|
133
|
+
}
|
|
134
|
+
// Switch out token if we found peanut data
|
|
135
|
+
let decodedString = decoded.join("");
|
|
136
|
+
return getDecodedToken(decodedString);
|
|
137
|
+
}
|
|
138
|
+
catch (error) {
|
|
139
|
+
return undefined;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Encodes a token into an emoji char
|
|
144
|
+
* @see https://github.com/cashubtc/cashu.me/blob/1194a7b9ee2f43305e38304de7bef8839601ff4d/src/components/SendTokenDialog.vue#L710
|
|
145
|
+
*/
|
|
146
|
+
export function encodeTokenToEmoji(token, emoji = "🥜") {
|
|
147
|
+
return (emoji +
|
|
148
|
+
Array.from(typeof token === "string" ? token : getEncodedToken(token))
|
|
149
|
+
.map((char) => {
|
|
150
|
+
const byteValue = char.charCodeAt(0);
|
|
151
|
+
// For byte values 0-15, use Variation Selectors (VS1-VS16): U+FE00 to U+FE0F
|
|
152
|
+
if (byteValue >= 0 && byteValue <= 15) {
|
|
153
|
+
return String.fromCodePoint(0xfe00 + byteValue);
|
|
154
|
+
}
|
|
155
|
+
// For byte values 16-255, use Variation Selectors Supplement (VS17-VS256): U+E0100 to U+E01EF
|
|
156
|
+
if (byteValue >= 16 && byteValue <= 255) {
|
|
157
|
+
return String.fromCodePoint(0xe0100 + (byteValue - 16));
|
|
158
|
+
}
|
|
159
|
+
return "";
|
|
160
|
+
})
|
|
161
|
+
.join(""));
|
|
162
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { HiddenContentSigner } from "applesauce-core/helpers";
|
|
2
|
+
import { NostrEvent } from "nostr-tools";
|
|
3
|
+
export declare const WALLET_KIND = 17375;
|
|
4
|
+
export declare const WALLET_BACKUP_KIND = 375;
|
|
5
|
+
export declare const WalletPrivateKeySymbol: unique symbol;
|
|
6
|
+
export declare const WalletMintsSymbol: unique symbol;
|
|
7
|
+
/** Returns if a wallet is locked */
|
|
8
|
+
export declare function isWalletLocked(wallet: NostrEvent): boolean;
|
|
9
|
+
/** Unlocks a wallet and returns the hidden tags */
|
|
10
|
+
export declare function unlockWallet(wallet: NostrEvent, signer: HiddenContentSigner): Promise<string[][]>;
|
|
11
|
+
export declare function lockWallet(wallet: NostrEvent): void;
|
|
12
|
+
/** Returns the wallets mints */
|
|
13
|
+
export declare function getWalletMints(wallet: NostrEvent): string[];
|
|
14
|
+
/** Returns the wallets private key as a string */
|
|
15
|
+
export declare function getWalletPrivateKey(wallet: NostrEvent): Uint8Array | undefined;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { hexToBytes } from "@noble/hashes/utils";
|
|
2
|
+
import { getHiddenTags, getOrComputeCachedValue, isHiddenTagsLocked, lockHiddenTags, setHiddenTagsEncryptionMethod, unlockHiddenTags, } from "applesauce-core/helpers";
|
|
3
|
+
export const WALLET_KIND = 17375;
|
|
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");
|
|
8
|
+
export const WalletPrivateKeySymbol = Symbol.for("wallet-private-key");
|
|
9
|
+
export const WalletMintsSymbol = Symbol.for("wallet-mints");
|
|
10
|
+
/** Returns if a wallet is locked */
|
|
11
|
+
export function isWalletLocked(wallet) {
|
|
12
|
+
return isHiddenTagsLocked(wallet);
|
|
13
|
+
}
|
|
14
|
+
/** Unlocks a wallet and returns the hidden tags */
|
|
15
|
+
export async function unlockWallet(wallet, signer) {
|
|
16
|
+
return await unlockHiddenTags(wallet, signer);
|
|
17
|
+
}
|
|
18
|
+
export function lockWallet(wallet) {
|
|
19
|
+
Reflect.deleteProperty(wallet, WalletPrivateKeySymbol);
|
|
20
|
+
Reflect.deleteProperty(wallet, WalletMintsSymbol);
|
|
21
|
+
lockHiddenTags(wallet);
|
|
22
|
+
}
|
|
23
|
+
/** Returns the wallets mints */
|
|
24
|
+
export function getWalletMints(wallet) {
|
|
25
|
+
return getOrComputeCachedValue(wallet, WalletMintsSymbol, () => {
|
|
26
|
+
const tags = getHiddenTags(wallet);
|
|
27
|
+
if (!tags)
|
|
28
|
+
throw new Error("Wallet is locked");
|
|
29
|
+
return tags.filter((t) => t[0] === "mint").map((t) => t[1]);
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
/** Returns the wallets private key as a string */
|
|
33
|
+
export function getWalletPrivateKey(wallet) {
|
|
34
|
+
return getOrComputeCachedValue(wallet, WalletPrivateKeySymbol, () => {
|
|
35
|
+
const tags = getHiddenTags(wallet);
|
|
36
|
+
if (!tags)
|
|
37
|
+
throw new Error("Wallet is locked");
|
|
38
|
+
const key = tags.find((t) => t[0] === "privkey" && t[1])?.[1];
|
|
39
|
+
return key ? hexToBytes(key) : undefined;
|
|
40
|
+
});
|
|
41
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Proof } from "@cashu/cashu-ts";
|
|
2
|
+
import { NostrEvent } from "nostr-tools";
|
|
3
|
+
export declare const NUTZAP_INFO_KIND = 10019;
|
|
4
|
+
export declare const NutzapMintsSymbol: unique symbol;
|
|
5
|
+
export declare const NutzapRelaysSymbol: unique symbol;
|
|
6
|
+
/** Returns the relay URLs from a kind:10019 nutzap info event */
|
|
7
|
+
export declare function getNutzapInfoRelays(event: NostrEvent): string[];
|
|
8
|
+
/** Returns the mint URLs from a kind:10019 nutzap info event */
|
|
9
|
+
export declare function getNutzapInfoMints(event: NostrEvent): {
|
|
10
|
+
mint: string;
|
|
11
|
+
units?: string[];
|
|
12
|
+
}[];
|
|
13
|
+
/** Returns the pubkey for P2PK-locking from a kind:10019 nutzap info event */
|
|
14
|
+
export declare function getNutzapInfoPubkey(event: NostrEvent): string | undefined;
|
|
15
|
+
/**
|
|
16
|
+
* verfies if proofs are locked to nutzap info
|
|
17
|
+
* @throws {Error} if proofs are not locked to nutzap info
|
|
18
|
+
*/
|
|
19
|
+
export declare function verifyProofsLocked(proofs: Proof[], info: NostrEvent): void;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { getOrComputeCachedValue, mergeRelaySets, safeParse } from "applesauce-core/helpers";
|
|
2
|
+
export const NUTZAP_INFO_KIND = 10019;
|
|
3
|
+
// Symbols for caching computed values
|
|
4
|
+
export const NutzapMintsSymbol = Symbol.for("nutzap-mints");
|
|
5
|
+
export const NutzapRelaysSymbol = Symbol.for("nutzap-relays");
|
|
6
|
+
/** Returns the relay URLs from a kind:10019 nutzap info event */
|
|
7
|
+
export function getNutzapInfoRelays(event) {
|
|
8
|
+
return getOrComputeCachedValue(event, NutzapRelaysSymbol, () => {
|
|
9
|
+
return mergeRelaySets(event.tags.filter((t) => t[0] === "relay").map((t) => t[1]));
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
/** Returns the mint URLs from a kind:10019 nutzap info event */
|
|
13
|
+
export function getNutzapInfoMints(event) {
|
|
14
|
+
return getOrComputeCachedValue(event, NutzapMintsSymbol, () => {
|
|
15
|
+
return event.tags.filter((t) => t[0] === "mint").map((t) => ({ mint: t[1], units: t.slice(2).filter(Boolean) }));
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
/** Returns the pubkey for P2PK-locking from a kind:10019 nutzap info event */
|
|
19
|
+
export function getNutzapInfoPubkey(event) {
|
|
20
|
+
return event.tags.find((t) => t[0] === "pubkey")?.[1];
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* verfies if proofs are locked to nutzap info
|
|
24
|
+
* @throws {Error} if proofs are not locked to nutzap info
|
|
25
|
+
*/
|
|
26
|
+
export function verifyProofsLocked(proofs, info) {
|
|
27
|
+
const pubkey = getNutzapInfoPubkey(info);
|
|
28
|
+
if (!pubkey)
|
|
29
|
+
throw new Error("Nutzap info must have a pubkey");
|
|
30
|
+
const fullPubkey = pubkey.length === 64 ? `02${pubkey}` : pubkey;
|
|
31
|
+
for (const proof of proofs) {
|
|
32
|
+
const secret = safeParse(proof.secret);
|
|
33
|
+
if (!secret)
|
|
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")
|
|
38
|
+
throw new Error("Token proofs must be P2PK locked");
|
|
39
|
+
if (secret[1].data !== fullPubkey)
|
|
40
|
+
throw new Error("Token proofs must be P2PK locked to the recipient's nutzap pubkey");
|
|
41
|
+
}
|
|
42
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { Filter, NostrEvent } from "nostr-tools";
|
|
2
|
+
import { Observable } from "rxjs";
|
|
3
|
+
/** A method used to subscribe to events on a set of relays */
|
|
4
|
+
export type NostrSubscriptionMethod = (relays: string[], filters: Filter[]) => Observable<NostrEvent | string>;
|
|
5
|
+
/** A method used for publishing an event, can return a Promise that completes when published or an Observable that completes when published*/
|
|
6
|
+
export type NostrPublishMethod = (relays: string[], event: NostrEvent) => Promise<any> | Observable<any>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -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 returns all nutzap events for an event */
|
|
4
|
+
export declare function EventNutZapzModel(event: NostrEvent): Model<NostrEvent[]>;
|
|
5
|
+
/** A model that returns all nutzaps for a users profile */
|
|
6
|
+
export declare function ProfileNutZapzModel(pubkey: string): Model<NostrEvent[]>;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { getReplaceableAddress, isReplaceable } from "applesauce-core/helpers";
|
|
2
|
+
import { map } from "rxjs";
|
|
3
|
+
import { getNutzapPointer, NUTZAP_KIND } from "../helpers/nutzap.js";
|
|
4
|
+
/** A model that returns all nutzap events for an event */
|
|
5
|
+
export function EventNutZapzModel(event) {
|
|
6
|
+
return (events) => isReplaceable(event.kind)
|
|
7
|
+
? events.timeline({ kinds: [NUTZAP_KIND], "#e": [event.id] })
|
|
8
|
+
: events.timeline({ kinds: [NUTZAP_KIND], "#a": [getReplaceableAddress(event)] });
|
|
9
|
+
}
|
|
10
|
+
/** A model that returns all nutzaps for a users profile */
|
|
11
|
+
export function ProfileNutZapzModel(pubkey) {
|
|
12
|
+
return (events) => events
|
|
13
|
+
.timeline({ kinds: [NUTZAP_KIND], "#p": [pubkey] })
|
|
14
|
+
// filter out nutzaps that are for events
|
|
15
|
+
.pipe(map((zaps) => zaps.filter((zap) => getNutzapPointer(zap) === undefined)));
|
|
16
|
+
}
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Model } from "applesauce-core";
|
|
2
|
+
import { NostrEvent } from "nostr-tools";
|
|
3
|
+
export type WalletInfo = {
|
|
4
|
+
locked: true;
|
|
5
|
+
event: NostrEvent;
|
|
6
|
+
} | {
|
|
7
|
+
locked: false;
|
|
8
|
+
event: NostrEvent;
|
|
9
|
+
privateKey?: Uint8Array;
|
|
10
|
+
mints: string[];
|
|
11
|
+
};
|
|
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
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { EventOperation } from "applesauce-factory";
|
|
2
|
+
import { EventPointer } from "nostr-tools/nip19";
|
|
3
|
+
import { HistoryContent } from "../helpers/history.js";
|
|
4
|
+
/** Sets the encrypted tags of a wallet history event */
|
|
5
|
+
export declare function setHistoryContent(content: HistoryContent): EventOperation;
|
|
6
|
+
/** Sets the "e" "redeemed" tags on a wallet history event */
|
|
7
|
+
export declare function setHistoryRedeemed(redeemed: (string | EventPointer)[]): EventOperation;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { ensureMarkedEventPointerTag } from "applesauce-factory/helpers";
|
|
2
|
+
import { modifyHiddenTags, modifyPublicTags } from "applesauce-factory/operations";
|
|
3
|
+
import { setSingletonTag } from "applesauce-factory/operations/tag";
|
|
4
|
+
/** Includes "e" "created" tags in wallet history tags */
|
|
5
|
+
function includeHistoryCreatedTags(created) {
|
|
6
|
+
return (tags) => {
|
|
7
|
+
for (const id of created) {
|
|
8
|
+
tags = ensureMarkedEventPointerTag(tags, typeof id === "string" ? { id } : id, "created");
|
|
9
|
+
}
|
|
10
|
+
return tags;
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
/** Sets the encrypted tags of a wallet history event */
|
|
14
|
+
export function setHistoryContent(content) {
|
|
15
|
+
const operations = [
|
|
16
|
+
setSingletonTag(["direction", content.direction], true),
|
|
17
|
+
setSingletonTag(["amount", String(content.amount)], true),
|
|
18
|
+
includeHistoryCreatedTags(content.created),
|
|
19
|
+
];
|
|
20
|
+
if (content.fee !== undefined)
|
|
21
|
+
operations.push(setSingletonTag(["fee", String(content.fee)], true));
|
|
22
|
+
if (content.mint !== undefined)
|
|
23
|
+
operations.push(setSingletonTag(["mint", content.mint], true));
|
|
24
|
+
return modifyHiddenTags(...operations);
|
|
25
|
+
}
|
|
26
|
+
/** Sets the "e" "redeemed" tags on a wallet history event */
|
|
27
|
+
export function setHistoryRedeemed(redeemed) {
|
|
28
|
+
return modifyPublicTags((tags) => {
|
|
29
|
+
for (const id of redeemed) {
|
|
30
|
+
tags = ensureMarkedEventPointerTag(tags, typeof id === "string" ? { id } : id, "redeemed");
|
|
31
|
+
}
|
|
32
|
+
return tags;
|
|
33
|
+
});
|
|
34
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Proof } from "@cashu/cashu-ts";
|
|
2
|
+
import { EventOperation } from "applesauce-factory";
|
|
3
|
+
import { NostrEvent } from "nostr-tools";
|
|
4
|
+
import { AddressPointer, EventPointer, ProfilePointer } from "nostr-tools/nip19";
|
|
5
|
+
/** Sets the cashu proofs for a nutzap event */
|
|
6
|
+
export declare function setProofs(proofs: Proof[]): EventOperation;
|
|
7
|
+
/** Sets the mint URL for a nutzap event */
|
|
8
|
+
export declare function setMint(mint: string): EventOperation;
|
|
9
|
+
/** Sets the recipient of a nutzap event */
|
|
10
|
+
export declare function setRecipient(recipient: string | ProfilePointer): EventOperation;
|
|
11
|
+
/** Sets the event that is being nutzapped */
|
|
12
|
+
export declare function setEvent(event: EventPointer | AddressPointer | NostrEvent): EventOperation;
|
|
13
|
+
/** Sets the comment content for a nutzap event */
|
|
14
|
+
export declare function setComment(comment: string): EventOperation;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { getReplaceableAddress, isAddressPointer, isEvent, isReplaceable } from "applesauce-core/helpers";
|
|
2
|
+
import { modifyPublicTags } from "applesauce-factory/operations";
|
|
3
|
+
import { setContent } from "applesauce-factory/operations/content";
|
|
4
|
+
import { addAddressTag, addEventTag, addPubkeyTag, setSingletonTag } from "applesauce-factory/operations/tag";
|
|
5
|
+
/** Sets the cashu proofs for a nutzap event */
|
|
6
|
+
export function setProofs(proofs) {
|
|
7
|
+
// Create an operation to append proof tags
|
|
8
|
+
const operation = (tags) => [...tags, ...proofs.map((proof) => ["proof", JSON.stringify(proof)])];
|
|
9
|
+
return modifyPublicTags(operation);
|
|
10
|
+
}
|
|
11
|
+
/** Sets the mint URL for a nutzap event */
|
|
12
|
+
export function setMint(mint) {
|
|
13
|
+
return modifyPublicTags(setSingletonTag(["u", mint]));
|
|
14
|
+
}
|
|
15
|
+
/** Sets the recipient of a nutzap event */
|
|
16
|
+
export function setRecipient(recipient) {
|
|
17
|
+
return modifyPublicTags(addPubkeyTag(recipient, true));
|
|
18
|
+
}
|
|
19
|
+
/** Sets the event that is being nutzapped */
|
|
20
|
+
export function setEvent(event) {
|
|
21
|
+
let operation;
|
|
22
|
+
if (isEvent(event))
|
|
23
|
+
operation = isReplaceable(event.kind) ? addEventTag(event.id) : addAddressTag(getReplaceableAddress(event));
|
|
24
|
+
else if (isAddressPointer(event))
|
|
25
|
+
operation = addAddressTag(event);
|
|
26
|
+
else
|
|
27
|
+
operation = addEventTag(event);
|
|
28
|
+
return modifyPublicTags(operation);
|
|
29
|
+
}
|
|
30
|
+
/** Sets the comment content for a nutzap event */
|
|
31
|
+
export function setComment(comment) {
|
|
32
|
+
return setContent(comment);
|
|
33
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { EventContentEncryptionMethod } from "applesauce-core/helpers";
|
|
2
|
+
import { setEncryptedContent } from "applesauce-factory/operations/content";
|
|
3
|
+
/** Sets the content of a 7375 token event */
|
|
4
|
+
export function setToken(token, del = []) {
|
|
5
|
+
return async (draft, ctx) => {
|
|
6
|
+
if (!ctx.signer)
|
|
7
|
+
throw new Error(`Missing signer`);
|
|
8
|
+
const pubkey = await ctx.signer.getPublicKey();
|
|
9
|
+
const method = EventContentEncryptionMethod[draft.kind];
|
|
10
|
+
if (!method)
|
|
11
|
+
throw new Error("Failed to find encryption method");
|
|
12
|
+
if (!token.mint)
|
|
13
|
+
throw new Error("Token mint is required");
|
|
14
|
+
if (!token.proofs || token.proofs.length === 0)
|
|
15
|
+
throw new Error("Token proofs are required");
|
|
16
|
+
const content = {
|
|
17
|
+
mint: token.mint,
|
|
18
|
+
proofs: token.proofs,
|
|
19
|
+
unit: token.unit,
|
|
20
|
+
del,
|
|
21
|
+
};
|
|
22
|
+
return await setEncryptedContent(pubkey, JSON.stringify(content), method)(draft, ctx);
|
|
23
|
+
};
|
|
24
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { EventOperation } from "applesauce-factory";
|
|
2
|
+
import { NostrEvent } from "nostr-tools";
|
|
3
|
+
/** Sets the content of a kind 375 wallet backup event */
|
|
4
|
+
export declare function setBackupContent(wallet: NostrEvent): EventOperation;
|
|
5
|
+
/** Sets the "mint" tags in a wallet event */
|
|
6
|
+
export declare function setMints(mints: string[]): EventOperation;
|
|
7
|
+
/** Sets the "privkey" tag on a wallet event */
|
|
8
|
+
export declare function setPrivateKey(privateKey: Uint8Array): EventOperation;
|