applesauce-wallet 0.0.0-next-20251209200210 → 0.0.0-next-20251231055351
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 +3 -2
- package/dist/actions/index.js +3 -2
- package/dist/actions/mint-recomendation.d.ts +30 -0
- package/dist/actions/mint-recomendation.js +96 -0
- 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/index.d.ts +1 -0
- package/dist/blueprints/index.js +1 -0
- package/dist/blueprints/mint-recommendation.d.ts +16 -0
- package/dist/blueprints/mint-recommendation.js +11 -0
- package/dist/blueprints/wallet.d.ts +5 -1
- package/dist/blueprints/wallet.js +6 -3
- package/dist/casts/__register__.d.ts +22 -0
- package/dist/casts/__register__.js +52 -0
- package/dist/casts/index.d.ts +8 -0
- package/dist/casts/index.js +8 -0
- package/dist/casts/mint-info.d.ts +18 -0
- package/dist/casts/mint-info.js +42 -0
- package/dist/casts/mint-recommendation.d.ts +16 -0
- package/dist/casts/mint-recommendation.js +29 -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 +7 -1
- package/dist/helpers/index.js +7 -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/mint-info.d.ts +40 -0
- package/dist/helpers/mint-info.js +80 -0
- package/dist/helpers/mint-recommendation.d.ts +41 -0
- package/dist/helpers/mint-recommendation.js +54 -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 +2 -1
- package/dist/operations/index.js +2 -1
- package/dist/operations/mint-recommendation.d.ts +13 -0
- package/dist/operations/mint-recommendation.js +26 -0
- 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 +37 -28
- 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,15 @@
|
|
|
1
|
+
import { unlockWallet } from "../helpers/wallet.js";
|
|
2
|
+
// Make sure the wallet$ is registered on the user class
|
|
3
|
+
import "../casts/__register__.js";
|
|
4
|
+
export async function getUnlockedWallet(user, signer) {
|
|
5
|
+
// NOTE: hard coding the timeout here isn't ideal, but no idea where else to put it
|
|
6
|
+
const wallet = await user.wallet$.$first(5000, undefined);
|
|
7
|
+
if (!wallet)
|
|
8
|
+
throw new Error("Unable to find wallet");
|
|
9
|
+
if (!wallet.unlocked) {
|
|
10
|
+
if (!signer)
|
|
11
|
+
throw new Error("Missing signer");
|
|
12
|
+
await unlockWallet(wallet.event, signer);
|
|
13
|
+
}
|
|
14
|
+
return wallet;
|
|
15
|
+
}
|
package/dist/actions/index.d.ts
CHANGED
package/dist/actions/index.js
CHANGED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Wallet } from "@cashu/cashu-ts";
|
|
2
|
+
import { Action } from "applesauce-actions";
|
|
3
|
+
import { NostrEvent } from "applesauce-core/helpers/event";
|
|
4
|
+
import "../casts/__register__.js";
|
|
5
|
+
/**
|
|
6
|
+
* Options for reviewing a mint
|
|
7
|
+
*/
|
|
8
|
+
export type ReviewMintOptions = {
|
|
9
|
+
/** Optional review/comment content */
|
|
10
|
+
comment?: string;
|
|
11
|
+
/** Optional relays to publish to */
|
|
12
|
+
relays?: string[];
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* An action that creates and publishes a mint recommendation event.
|
|
16
|
+
* Can accept a mint URL, Wallet instance, or MintInfo event.
|
|
17
|
+
*/
|
|
18
|
+
export declare function RecommendMint(input: string | Wallet | NostrEvent, options?: ReviewMintOptions): Action;
|
|
19
|
+
/**
|
|
20
|
+
* Options for updating a mint recommendation
|
|
21
|
+
*/
|
|
22
|
+
export type UpdateMintRecommendationOptions = {
|
|
23
|
+
/** Optional relays to publish to */
|
|
24
|
+
relays?: string[];
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* An action that updates the comment on an existing mint recommendation event.
|
|
28
|
+
* Can accept a mint pubkey (to find the recommendation) or the recommendation event directly.
|
|
29
|
+
*/
|
|
30
|
+
export declare function UpdateMintRecommendation(input: string | NostrEvent, comment: string, options?: UpdateMintRecommendationOptions): Action;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { Wallet } from "@cashu/cashu-ts";
|
|
2
|
+
import { MintRecommendationBlueprint } from "../blueprints/mint-recommendation.js";
|
|
3
|
+
import { getCashuMintPubkey, getCashuMintURL, isValidCashuMintInfo } from "../helpers/mint-info.js";
|
|
4
|
+
import { MINT_RECOMMENDATION_KIND, getRecommendationMintPubkey, isValidMintRecommendation, } from "../helpers/mint-recommendation.js";
|
|
5
|
+
import { setComment } from "../operations/mint-recommendation.js";
|
|
6
|
+
// Helper function to fetch mint pubkey from URL
|
|
7
|
+
async function fetchMintPubkey(mintUrl) {
|
|
8
|
+
// Fetch mint info directly from the /v1/info endpoint
|
|
9
|
+
const infoUrl = new URL("/v1/info", mintUrl).toString();
|
|
10
|
+
const response = await fetch(infoUrl);
|
|
11
|
+
if (!response.ok) {
|
|
12
|
+
throw new Error(`Failed to fetch mint info from ${mintUrl}: ${response.statusText}`);
|
|
13
|
+
}
|
|
14
|
+
const mintInfo = (await response.json());
|
|
15
|
+
return mintInfo.pubkey;
|
|
16
|
+
}
|
|
17
|
+
// Make sure the mint recommendation cast is registered
|
|
18
|
+
import "../casts/__register__.js";
|
|
19
|
+
/**
|
|
20
|
+
* An action that creates and publishes a mint recommendation event.
|
|
21
|
+
* Can accept a mint URL, Wallet instance, or MintInfo event.
|
|
22
|
+
*/
|
|
23
|
+
export function RecommendMint(input, options) {
|
|
24
|
+
return async ({ factory, sign, publish }) => {
|
|
25
|
+
let mintPubkey;
|
|
26
|
+
let url;
|
|
27
|
+
let addressPointer = undefined;
|
|
28
|
+
// Handle different input types
|
|
29
|
+
if (typeof input === "string") {
|
|
30
|
+
if (!URL.canParse(input))
|
|
31
|
+
throw new Error("Invalid mint URL");
|
|
32
|
+
// Input is a mint URL - fetch info directly
|
|
33
|
+
url = input;
|
|
34
|
+
mintPubkey = await fetchMintPubkey(url);
|
|
35
|
+
}
|
|
36
|
+
else if (input instanceof Wallet) {
|
|
37
|
+
const info = input.getMintInfo();
|
|
38
|
+
mintPubkey = info.pubkey;
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
// Input is a MintInfo event (kind:38172)
|
|
42
|
+
if (!isValidCashuMintInfo(input))
|
|
43
|
+
throw new Error("Invalid mint info event. Expected kind:38172 with required `d` and `u` tags.");
|
|
44
|
+
mintPubkey = getCashuMintPubkey(input);
|
|
45
|
+
url = getCashuMintURL(input);
|
|
46
|
+
// Use the event itself as address pointer (blueprint will convert it)
|
|
47
|
+
addressPointer = input;
|
|
48
|
+
}
|
|
49
|
+
// Create the mint recommendation event
|
|
50
|
+
const recommendation = await factory
|
|
51
|
+
.create(MintRecommendationBlueprint, {
|
|
52
|
+
mintPubkey,
|
|
53
|
+
url,
|
|
54
|
+
addressPointer,
|
|
55
|
+
comment: options?.comment,
|
|
56
|
+
})
|
|
57
|
+
.then(sign);
|
|
58
|
+
// Publish the event
|
|
59
|
+
await publish(recommendation, options?.relays);
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* An action that updates the comment on an existing mint recommendation event.
|
|
64
|
+
* Can accept a mint pubkey (to find the recommendation) or the recommendation event directly.
|
|
65
|
+
*/
|
|
66
|
+
export function UpdateMintRecommendation(input, comment, options) {
|
|
67
|
+
return async ({ events, factory, self, sign, publish }) => {
|
|
68
|
+
let recommendation;
|
|
69
|
+
// Handle different input types
|
|
70
|
+
if (typeof input === "string") {
|
|
71
|
+
// Input is a mint pubkey - find the recommendation event
|
|
72
|
+
const recommendations = events.getTimeline({ kinds: [MINT_RECOMMENDATION_KIND], authors: [self] });
|
|
73
|
+
recommendation = recommendations.find((rec) => {
|
|
74
|
+
if (!isValidMintRecommendation(rec))
|
|
75
|
+
return false;
|
|
76
|
+
const recMintPubkey = getRecommendationMintPubkey(rec);
|
|
77
|
+
return recMintPubkey === input;
|
|
78
|
+
});
|
|
79
|
+
if (!recommendation)
|
|
80
|
+
throw new Error(`No mint recommendation found for mint pubkey: ${input}`);
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
// Input is a recommendation event
|
|
84
|
+
if (!isValidMintRecommendation(input))
|
|
85
|
+
throw new Error("Invalid mint recommendation event");
|
|
86
|
+
// Verify the event belongs to the current user
|
|
87
|
+
if (input.pubkey !== self)
|
|
88
|
+
throw new Error("Cannot update a mint recommendation that belongs to another user");
|
|
89
|
+
recommendation = input;
|
|
90
|
+
}
|
|
91
|
+
// Update the comment
|
|
92
|
+
const updated = await factory.modify(recommendation, setComment(comment)).then(sign);
|
|
93
|
+
// Publish the updated event
|
|
94
|
+
await publish(updated, options?.relays);
|
|
95
|
+
};
|
|
96
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Action } from "applesauce-actions";
|
|
2
|
+
import "../casts/__register__.js";
|
|
2
3
|
/** An action to add a relay to the kind 10019 nutzap info event */
|
|
3
4
|
export declare function AddNutzapInfoRelay(relay: string | string[]): Action;
|
|
4
5
|
/** An action to remove a relay from the kind 10019 nutzap info event */
|
|
@@ -13,10 +14,26 @@ export declare function AddNutzapInfoMint(mint: {
|
|
|
13
14
|
}>): Action;
|
|
14
15
|
/** An action to remove a mint from the kind 10019 nutzap info event */
|
|
15
16
|
export declare function RemoveNutzapInfoMint(mint: string | string[]): Action;
|
|
16
|
-
/** An action to set the pubkey for the kind 10019 nutzap info event */
|
|
17
|
-
export declare function SetNutzapInfoPubkey(pubkey: string): Action;
|
|
18
17
|
/** An action to update the entire nutzap info event */
|
|
19
18
|
export declare function UpdateNutzapInfo(relays: string[], mints: Array<{
|
|
20
19
|
url: string;
|
|
21
20
|
units?: string[];
|
|
22
|
-
}
|
|
21
|
+
}>): Action;
|
|
22
|
+
/**
|
|
23
|
+
* Sets the mints on a nutzap info event
|
|
24
|
+
* @throws if the nutzap info does not exist
|
|
25
|
+
*/
|
|
26
|
+
export declare function SetNutzapInfoMints(mints: Array<{
|
|
27
|
+
url: string;
|
|
28
|
+
units?: string[];
|
|
29
|
+
}>): Action;
|
|
30
|
+
/**
|
|
31
|
+
* Sets the relays on a nutzap info event
|
|
32
|
+
* @throws if the nutzap info does not exist
|
|
33
|
+
*/
|
|
34
|
+
export declare function SetNutzapInfoRelays(relays: string[]): Action;
|
|
35
|
+
/**
|
|
36
|
+
* Sets the pubkey on a nutzap info event
|
|
37
|
+
* @throws if the nutzap info does not exist
|
|
38
|
+
*/
|
|
39
|
+
export declare function SetNutzapInfoPubkey(privateKey: Uint8Array): Action;
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { getNutzapInfoRelays, NUTZAP_INFO_KIND } from "../helpers/nutzap-info.js";
|
|
2
|
+
import { addNutzapInfoMint, addNutzapInfoRelay, removeNutzapInfoMint, removeNutzapInfoRelay, setNutzapInfoMints, setNutzapInfoPubkey, setNutzapInfoRelays, } from "../operations/nutzap-info.js";
|
|
3
|
+
// Make sure the nutzap$ is registered on the user class
|
|
4
|
+
import "../casts/__register__.js";
|
|
5
|
+
/** An action to add a relay to the kind 10019 nutzap info event */
|
|
6
|
+
export function AddNutzapInfoRelay(relay) {
|
|
7
|
+
return async ({ events, factory, self, sign, publish }) => {
|
|
8
|
+
if (typeof relay === "string")
|
|
9
|
+
relay = [relay];
|
|
10
|
+
const operations = relay.map((r) => addNutzapInfoRelay(r));
|
|
11
|
+
const nutzapInfo = events.getReplaceable(NUTZAP_INFO_KIND, self);
|
|
12
|
+
const signed = nutzapInfo
|
|
13
|
+
? await factory.modify(nutzapInfo, ...operations).then(sign)
|
|
14
|
+
: await factory.build({ kind: NUTZAP_INFO_KIND }, ...operations).then(sign);
|
|
15
|
+
// Use relays from the updated event
|
|
16
|
+
const relays = getNutzapInfoRelays(signed);
|
|
17
|
+
await publish(signed, relays);
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
/** An action to remove a relay from the kind 10019 nutzap info event */
|
|
21
|
+
export function RemoveNutzapInfoRelay(relay) {
|
|
22
|
+
return async ({ events, factory, self, sign, publish }) => {
|
|
23
|
+
if (typeof relay === "string")
|
|
24
|
+
relay = [relay];
|
|
25
|
+
const nutzapInfo = events.getReplaceable(NUTZAP_INFO_KIND, self);
|
|
26
|
+
if (!nutzapInfo)
|
|
27
|
+
return;
|
|
28
|
+
const operations = relay.map((r) => removeNutzapInfoRelay(r));
|
|
29
|
+
const signed = await factory.modify(nutzapInfo, ...operations).then(sign);
|
|
30
|
+
// Use relays from the updated event
|
|
31
|
+
const relays = getNutzapInfoRelays(signed);
|
|
32
|
+
await publish(signed, relays);
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
/** An action to add a mint to the kind 10019 nutzap info event */
|
|
36
|
+
export function AddNutzapInfoMint(mint) {
|
|
37
|
+
return async ({ events, factory, self, sign, publish }) => {
|
|
38
|
+
const mints = Array.isArray(mint) ? mint : [mint];
|
|
39
|
+
const operations = mints.map((m) => addNutzapInfoMint(m));
|
|
40
|
+
const nutzapInfo = events.getReplaceable(NUTZAP_INFO_KIND, self);
|
|
41
|
+
const signed = nutzapInfo
|
|
42
|
+
? await factory.modify(nutzapInfo, ...operations).then(sign)
|
|
43
|
+
: await factory.build({ kind: NUTZAP_INFO_KIND }, ...operations).then(sign);
|
|
44
|
+
// Use relays from the updated event
|
|
45
|
+
const relays = getNutzapInfoRelays(signed);
|
|
46
|
+
await publish(signed, relays);
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
/** An action to remove a mint from the kind 10019 nutzap info event */
|
|
50
|
+
export function RemoveNutzapInfoMint(mint) {
|
|
51
|
+
return async ({ events, factory, self, sign, publish }) => {
|
|
52
|
+
if (typeof mint === "string")
|
|
53
|
+
mint = [mint];
|
|
54
|
+
const nutzapInfo = events.getReplaceable(NUTZAP_INFO_KIND, self);
|
|
55
|
+
if (!nutzapInfo)
|
|
56
|
+
return;
|
|
57
|
+
const operations = mint.map((m) => removeNutzapInfoMint(m));
|
|
58
|
+
const signed = await factory.modify(nutzapInfo, ...operations).then(sign);
|
|
59
|
+
// Use relays from the updated event
|
|
60
|
+
const relays = getNutzapInfoRelays(signed);
|
|
61
|
+
await publish(signed, relays);
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
/** An action to update the entire nutzap info event */
|
|
65
|
+
export function UpdateNutzapInfo(relays, mints) {
|
|
66
|
+
return async ({ events, factory, self, sign, publish }) => {
|
|
67
|
+
const operations = [setNutzapInfoRelays(relays), setNutzapInfoMints(mints)];
|
|
68
|
+
const nutzapInfo = events.getReplaceable(NUTZAP_INFO_KIND, self);
|
|
69
|
+
const signed = nutzapInfo
|
|
70
|
+
? await factory.modify(nutzapInfo, ...operations).then(sign)
|
|
71
|
+
: await factory.build({ kind: NUTZAP_INFO_KIND }, ...operations).then(sign);
|
|
72
|
+
await publish(signed, relays);
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Sets the mints on a nutzap info event
|
|
77
|
+
* @throws if the nutzap info does not exist
|
|
78
|
+
*/
|
|
79
|
+
export function SetNutzapInfoMints(mints) {
|
|
80
|
+
return async ({ events, self, factory, sign, publish }) => {
|
|
81
|
+
const nutzapInfo = events.getReplaceable(NUTZAP_INFO_KIND, self);
|
|
82
|
+
if (!nutzapInfo)
|
|
83
|
+
throw new Error("Nutzap info does not exist");
|
|
84
|
+
const signed = await factory.modify(nutzapInfo, setNutzapInfoMints(mints)).then(sign);
|
|
85
|
+
// Use relays from the updated event
|
|
86
|
+
const relays = getNutzapInfoRelays(signed);
|
|
87
|
+
await publish(signed, relays);
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Sets the relays on a nutzap info event
|
|
92
|
+
* @throws if the nutzap info does not exist
|
|
93
|
+
*/
|
|
94
|
+
export function SetNutzapInfoRelays(relays) {
|
|
95
|
+
return async ({ events, self, factory, sign, publish }) => {
|
|
96
|
+
const nutzapInfo = events.getReplaceable(NUTZAP_INFO_KIND, self);
|
|
97
|
+
if (!nutzapInfo)
|
|
98
|
+
throw new Error("Nutzap info does not exist");
|
|
99
|
+
const signed = await factory.modify(nutzapInfo, setNutzapInfoRelays(relays)).then(sign);
|
|
100
|
+
await publish(signed, relays);
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Sets the pubkey on a nutzap info event
|
|
105
|
+
* @throws if the nutzap info does not exist
|
|
106
|
+
*/
|
|
107
|
+
export function SetNutzapInfoPubkey(privateKey) {
|
|
108
|
+
return async ({ events, self, factory, sign, publish }) => {
|
|
109
|
+
const nutzapInfo = events.getReplaceable(NUTZAP_INFO_KIND, self);
|
|
110
|
+
if (!nutzapInfo)
|
|
111
|
+
throw new Error("Nutzap info does not exist");
|
|
112
|
+
const signed = await factory.modify(nutzapInfo, setNutzapInfoPubkey(privateKey)).then(sign);
|
|
113
|
+
// Use relays from the updated event
|
|
114
|
+
const relays = getNutzapInfoRelays(signed);
|
|
115
|
+
await publish(signed, relays);
|
|
116
|
+
};
|
|
117
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Token } from "@cashu/cashu-ts";
|
|
2
|
+
import { Action } from "applesauce-actions";
|
|
3
|
+
import { NostrEvent } from "applesauce-core/helpers/event";
|
|
4
|
+
import { ProfilePointer } from "applesauce-core/helpers/pointers";
|
|
5
|
+
import { Couch } from "../helpers/couch.js";
|
|
6
|
+
import "../casts/__register__.js";
|
|
7
|
+
/** Creates a NIP-61 nutzap event for an event with a token */
|
|
8
|
+
export declare function NutzapEvent(event: NostrEvent, token: Token, options?: {
|
|
9
|
+
comment?: string;
|
|
10
|
+
couch?: Couch;
|
|
11
|
+
}): Action;
|
|
12
|
+
/** Creates a NIP-61 nutzap event to a users profile */
|
|
13
|
+
export declare function NutzapProfile(user: string | ProfilePointer, token: Token, options?: {
|
|
14
|
+
comment?: string;
|
|
15
|
+
couch?: Couch;
|
|
16
|
+
}): Action;
|
|
17
|
+
/**
|
|
18
|
+
* Receives a P2PK-locked cashu token from a nutzap event(s) by unlocking it with the wallet's private key
|
|
19
|
+
* and marks the nutzap event(s) as redeemed
|
|
20
|
+
* Supports nutzaps with different mints by grouping them by mint and redeeming each group separately
|
|
21
|
+
* @param nutzaps single nutzap event or array of nutzap events
|
|
22
|
+
* @param couch optional couch interface for temporarily storing tokens during the operation
|
|
23
|
+
*/
|
|
24
|
+
export declare function ReceiveNutzaps(nutzaps: NostrEvent | NostrEvent[], couch?: Couch): Action;
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { sumProofs, Wallet } from "@cashu/cashu-ts";
|
|
2
|
+
import { castUser } from "applesauce-common/casts";
|
|
3
|
+
import { bytesToHex } from "applesauce-core/helpers/event";
|
|
4
|
+
import { WalletHistoryBlueprint } from "../blueprints/history.js";
|
|
5
|
+
import { WalletTokenBlueprint } from "../blueprints/tokens.js";
|
|
6
|
+
import { NutzapBlueprint, ProfileNutzapBlueprint } from "../blueprints/zaps.js";
|
|
7
|
+
import { verifyProofsLocked } from "../helpers/nutzap-info.js";
|
|
8
|
+
import { getNutzapMint, getNutzapProofs, isValidNutzap } from "../helpers/nutzap.js";
|
|
9
|
+
import { getUnlockedWallet } from "./common.js";
|
|
10
|
+
// Make sure the nutzap$ is registered on the user class
|
|
11
|
+
import "../casts/__register__.js";
|
|
12
|
+
/** Creates a NIP-61 nutzap event for an event with a token */
|
|
13
|
+
export function NutzapEvent(event, token, options) {
|
|
14
|
+
return async ({ events, factory, user, signer, sign, publish }) => {
|
|
15
|
+
const { comment, couch } = options ?? {};
|
|
16
|
+
const clearStoredToken = await couch?.store(token);
|
|
17
|
+
try {
|
|
18
|
+
const recipient = castUser(event.pubkey, events);
|
|
19
|
+
// Get the recipient's nutzap info
|
|
20
|
+
const info = await recipient.nutzap$.$first(5000, undefined);
|
|
21
|
+
if (!info)
|
|
22
|
+
throw new Error("Nutzap info not found");
|
|
23
|
+
// Get the users wallet
|
|
24
|
+
const wallet = await getUnlockedWallet(user, signer);
|
|
25
|
+
// Verify all tokens are p2pk locked
|
|
26
|
+
verifyProofsLocked(token.proofs, info.event);
|
|
27
|
+
// Create the nutzap event
|
|
28
|
+
const nutzap = await factory.create(NutzapBlueprint, event, token, comment || token.memo).then(sign);
|
|
29
|
+
// Publish the nutzap event
|
|
30
|
+
await publish(nutzap, wallet.relays);
|
|
31
|
+
}
|
|
32
|
+
catch { }
|
|
33
|
+
await clearStoredToken?.();
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
/** Creates a NIP-61 nutzap event to a users profile */
|
|
37
|
+
export function NutzapProfile(user, token, options) {
|
|
38
|
+
return async ({ events, factory, sign, publish }) => {
|
|
39
|
+
const { comment, couch } = options ?? {};
|
|
40
|
+
const clearStoredToken = await couch?.store(token);
|
|
41
|
+
try {
|
|
42
|
+
const recipient = castUser(user, events);
|
|
43
|
+
// Get the target's nutzap info
|
|
44
|
+
const info = await recipient.nutzap$.$first(5000, undefined);
|
|
45
|
+
if (!info)
|
|
46
|
+
throw new Error("Nutzap info not found");
|
|
47
|
+
// Verify all tokens are p2pk locked
|
|
48
|
+
verifyProofsLocked(token.proofs, info.event);
|
|
49
|
+
// Create the nutzap event
|
|
50
|
+
const nutzap = await factory.create(ProfileNutzapBlueprint, recipient, token, comment || token.memo).then(sign);
|
|
51
|
+
// Publish the nutzap event
|
|
52
|
+
await publish(nutzap, info.relays);
|
|
53
|
+
}
|
|
54
|
+
catch { }
|
|
55
|
+
await clearStoredToken?.();
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Receives a P2PK-locked cashu token from a nutzap event(s) by unlocking it with the wallet's private key
|
|
60
|
+
* and marks the nutzap event(s) as redeemed
|
|
61
|
+
* Supports nutzaps with different mints by grouping them by mint and redeeming each group separately
|
|
62
|
+
* @param nutzaps single nutzap event or array of nutzap events
|
|
63
|
+
* @param couch optional couch interface for temporarily storing tokens during the operation
|
|
64
|
+
*/
|
|
65
|
+
export function ReceiveNutzaps(nutzaps, couch) {
|
|
66
|
+
return async ({ factory, user, signer, sign, publish }) => {
|
|
67
|
+
if (!signer)
|
|
68
|
+
throw new Error("Missing signer");
|
|
69
|
+
// Normalize to array
|
|
70
|
+
nutzaps = Array.isArray(nutzaps) ? nutzaps : [nutzaps];
|
|
71
|
+
if (nutzaps.length === 0)
|
|
72
|
+
throw new Error("No nutzap events provided");
|
|
73
|
+
// Filter out nutzaps without mints or proofs (ignore them)
|
|
74
|
+
const validNutzaps = nutzaps.filter((n) => isValidNutzap(n));
|
|
75
|
+
if (validNutzaps.length === 0)
|
|
76
|
+
throw new Error("No valid nutzaps with mints and proofs found");
|
|
77
|
+
// Get private key from current wallet event
|
|
78
|
+
const wallet = await getUnlockedWallet(user, signer);
|
|
79
|
+
// Group nutzaps by mint
|
|
80
|
+
const nutzapsByMint = new Map();
|
|
81
|
+
for (const nutzap of validNutzaps) {
|
|
82
|
+
const mint = getNutzapMint(nutzap);
|
|
83
|
+
if (!mint)
|
|
84
|
+
continue; // Should not happen after filtering, but TypeScript needs this
|
|
85
|
+
if (!nutzapsByMint.has(mint)) {
|
|
86
|
+
nutzapsByMint.set(mint, []);
|
|
87
|
+
}
|
|
88
|
+
nutzapsByMint.get(mint).push(nutzap);
|
|
89
|
+
}
|
|
90
|
+
if (nutzapsByMint.size === 0)
|
|
91
|
+
throw new Error("No valid nutzaps with mints found");
|
|
92
|
+
if (!wallet.privateKey)
|
|
93
|
+
throw new Error("No private key found in wallet");
|
|
94
|
+
// Convert private key to hex string for cashu-ts
|
|
95
|
+
const privkeyHex = bytesToHex(wallet.privateKey);
|
|
96
|
+
// Track clear methods for all stored tokens
|
|
97
|
+
const clearMethods = [];
|
|
98
|
+
try {
|
|
99
|
+
// Process each mint group separately
|
|
100
|
+
for (const [mint, mintNutzaps] of nutzapsByMint) {
|
|
101
|
+
// Extract all proofs from nutzaps for this mint
|
|
102
|
+
const allProofs = mintNutzaps.flatMap(getNutzapProofs);
|
|
103
|
+
if (allProofs.length === 0)
|
|
104
|
+
continue;
|
|
105
|
+
// Construct token from nutzap proofs
|
|
106
|
+
const token = {
|
|
107
|
+
mint,
|
|
108
|
+
proofs: allProofs,
|
|
109
|
+
unit: "sat",
|
|
110
|
+
};
|
|
111
|
+
// Use cashu-ts to receive/unlock the P2PK-locked token
|
|
112
|
+
const cashuWallet = new Wallet(mint);
|
|
113
|
+
await cashuWallet.loadMint();
|
|
114
|
+
// Receive the token using the new wallet.ops API
|
|
115
|
+
// This will swap P2PK-locked proofs with unlocked proofs
|
|
116
|
+
let receivedProofs;
|
|
117
|
+
try {
|
|
118
|
+
receivedProofs = await cashuWallet.ops.receive(token).privkey(privkeyHex).run();
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
throw new Error(`Failed to receive token for mint ${mint}: ${error instanceof Error ? error.message : String(error)}`);
|
|
122
|
+
}
|
|
123
|
+
// Calculate total amount
|
|
124
|
+
const amount = sumProofs(receivedProofs);
|
|
125
|
+
// Create token event with received unlocked proofs
|
|
126
|
+
const receivedToken = {
|
|
127
|
+
mint,
|
|
128
|
+
proofs: receivedProofs,
|
|
129
|
+
unit: "sat",
|
|
130
|
+
};
|
|
131
|
+
// Store token in couch immediately after receiving it
|
|
132
|
+
const clearStoredToken = await couch?.store(receivedToken);
|
|
133
|
+
if (clearStoredToken) {
|
|
134
|
+
clearMethods.push(clearStoredToken);
|
|
135
|
+
}
|
|
136
|
+
const tokenEvent = await factory.create(WalletTokenBlueprint, receivedToken, []).then(sign);
|
|
137
|
+
// Create history event marking nutzap events as redeemed
|
|
138
|
+
const nutzapIds = mintNutzaps.map((n) => n.id);
|
|
139
|
+
const history = await factory
|
|
140
|
+
.create(WalletHistoryBlueprint, { direction: "in", amount, mint, created: [tokenEvent.id] }, nutzapIds)
|
|
141
|
+
.then(sign);
|
|
142
|
+
// Publish events
|
|
143
|
+
await publish(tokenEvent, wallet.relays);
|
|
144
|
+
await publish(history, wallet.relays);
|
|
145
|
+
}
|
|
146
|
+
// Clear all stored tokens from the couch after successfully publishing all events for all mints
|
|
147
|
+
await Promise.all(clearMethods.map((clear) => clear()));
|
|
148
|
+
}
|
|
149
|
+
catch (error) {
|
|
150
|
+
// If an error occurs, don't clear the couch (tokens remain for recovery)
|
|
151
|
+
throw error;
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
}
|
package/dist/actions/tokens.d.ts
CHANGED
|
@@ -1,17 +1,87 @@
|
|
|
1
|
-
import { Token } from "@cashu/cashu-ts";
|
|
1
|
+
import { Proof, Token, Wallet } from "@cashu/cashu-ts";
|
|
2
2
|
import { Action } from "applesauce-actions";
|
|
3
3
|
import { NostrEvent } from "applesauce-core/helpers/event";
|
|
4
|
+
import { Couch } from "../helpers/couch.js";
|
|
5
|
+
import "../casts/__register__.js";
|
|
4
6
|
/**
|
|
5
|
-
* Adds a cashu token to the wallet and
|
|
7
|
+
* Adds a cashu token to the wallet and creates a history event
|
|
6
8
|
* @param token the cashu token to add
|
|
7
|
-
* @param redeemed an array of
|
|
9
|
+
* @param redeemed an array of event ids to mark as redeemed
|
|
8
10
|
*/
|
|
9
|
-
export declare function
|
|
11
|
+
export declare function AddToken(token: Token, options?: {
|
|
12
|
+
redeemed?: string[];
|
|
13
|
+
fee?: number;
|
|
14
|
+
addHistory?: boolean;
|
|
15
|
+
}): Action;
|
|
16
|
+
/** Similar to the AddToken action but swaps the tokens before receiving them */
|
|
17
|
+
export declare function ReceiveToken(token: Token, options?: {
|
|
18
|
+
addHistory?: boolean;
|
|
19
|
+
couch?: Couch;
|
|
20
|
+
}): Action;
|
|
10
21
|
/** An action that deletes old tokens and creates a new one but does not add a history event */
|
|
11
22
|
export declare function RolloverTokens(tokens: NostrEvent[], token: Token): Action;
|
|
12
23
|
/** An action that deletes old token events and adds a spend history item */
|
|
13
|
-
export declare function CompleteSpend(spent: NostrEvent[], change: Token): Action;
|
|
24
|
+
export declare function CompleteSpend(spent: NostrEvent[], change: Token, couch?: Couch): Action;
|
|
14
25
|
/** Combines all unlocked token events into a single event per mint */
|
|
15
|
-
export declare function ConsolidateTokens(
|
|
16
|
-
|
|
26
|
+
export declare function ConsolidateTokens(options?: {
|
|
27
|
+
unlockTokens?: boolean;
|
|
28
|
+
}): Action;
|
|
29
|
+
/**
|
|
30
|
+
* Recovers tokens from a couch by checking if they exist in the wallet,
|
|
31
|
+
* verifying they are unspent, and creating token events for any recoverable tokens
|
|
32
|
+
* @param couch the couch interface to recover tokens from
|
|
33
|
+
*/
|
|
34
|
+
export declare function RecoverFromCouch(couch: Couch): Action;
|
|
35
|
+
/**
|
|
36
|
+
* Token selection function type that matches dumbTokenSelection signature.
|
|
37
|
+
* Must return tokens from a single mint and ensure all selected tokens are from that mint.
|
|
38
|
+
* If mint is undefined, the function should find a mint with sufficient balance.
|
|
39
|
+
*/
|
|
40
|
+
export type TokenSelectionFunction = (tokens: NostrEvent[], minAmount: number, mint?: string) => {
|
|
41
|
+
events: NostrEvent[];
|
|
42
|
+
proofs: Proof[];
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* A generic action that safely selects tokens, performs an async operation, and handles change.
|
|
46
|
+
* This action requires a couch for safety - tokens are stored in the couch before the operation
|
|
47
|
+
* and can be recovered if something goes wrong.
|
|
48
|
+
*
|
|
49
|
+
* @param minAmount The minimum amount of tokens to select (in sats)
|
|
50
|
+
* @param operation An async function that receives selected proofs and performs the operation.
|
|
51
|
+
* Should return any change proofs. All selected proofs are considered used.
|
|
52
|
+
* @param options Configuration options including mint filter, required couch, and optional custom token selection
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* // Use with NutzapProfile
|
|
56
|
+
* await run(TokensOperation, 100, async ({ selectedProofs, mint, cashuWallet }) => {
|
|
57
|
+
* const { keep, send } = await cashuWallet.ops.send(100, selectedProofs).asP2PK({ pubkey }).run();
|
|
58
|
+
* await run(NutzapProfile, recipient, { mint, proofs: send, unit: "sat" });
|
|
59
|
+
* return { change: keep };
|
|
60
|
+
* }, { couch });
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* // Use with melt
|
|
64
|
+
* await run(TokensOperation, meltAmount + feeReserve, async ({ selectedProofs, mint, cashuWallet }) => {
|
|
65
|
+
* const meltQuote = await cashuWallet.createMeltQuoteBolt11(invoice);
|
|
66
|
+
* const { keep, send } = await cashuWallet.send(meltAmount + meltQuote.fee_reserve, selectedProofs, { includeFees: true });
|
|
67
|
+
* const meltResponse = await cashuWallet.meltProofs(meltQuote, send);
|
|
68
|
+
* return { change: meltResponse.change };
|
|
69
|
+
* }, { couch });
|
|
70
|
+
*
|
|
71
|
+
* @example
|
|
72
|
+
* // Use with custom token selection
|
|
73
|
+
* await run(TokensOperation, 100, async ({ selectedProofs, mint, cashuWallet }) => {
|
|
74
|
+
* // ... operation
|
|
75
|
+
* }, { couch, tokenSelection: myCustomSelectionFunction });
|
|
76
|
+
*/
|
|
77
|
+
export declare function TokensOperation(minAmount: number, operation: (params: {
|
|
78
|
+
selectedProofs: Proof[];
|
|
79
|
+
mint: string;
|
|
80
|
+
cashuWallet: Wallet;
|
|
81
|
+
}) => Promise<{
|
|
82
|
+
change?: Proof[];
|
|
83
|
+
}>, options: {
|
|
84
|
+
mint?: string;
|
|
85
|
+
couch: Couch;
|
|
86
|
+
tokenSelection?: TokenSelectionFunction;
|
|
17
87
|
}): Action;
|