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,30 @@
|
|
|
1
|
+
import { bytesToHex } from "@noble/hashes/utils";
|
|
2
|
+
import { modifyHiddenTags } from "applesauce-factory/operations";
|
|
3
|
+
import { setSingletonTag } from "applesauce-factory/operations/tag";
|
|
4
|
+
import { WALLET_KIND } from "../helpers/wallet.js";
|
|
5
|
+
/** Sets the content of a kind 375 wallet backup event */
|
|
6
|
+
export function setBackupContent(wallet) {
|
|
7
|
+
return async (draft, ctx) => {
|
|
8
|
+
if (wallet.kind !== WALLET_KIND)
|
|
9
|
+
throw new Error(`Cant create a wallet backup from kind ${wallet.kind}`);
|
|
10
|
+
if (!wallet.content)
|
|
11
|
+
throw new Error("Wallet missing content");
|
|
12
|
+
const pubkey = await ctx.signer?.getPublicKey();
|
|
13
|
+
if (wallet.pubkey !== pubkey)
|
|
14
|
+
throw new Error("Wallet pubkey dose not match signer pubkey");
|
|
15
|
+
return { ...draft, content: wallet.content };
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
/** Sets the "mint" tags in a wallet event */
|
|
19
|
+
export function setMints(mints) {
|
|
20
|
+
return modifyHiddenTags((tags) => [
|
|
21
|
+
// remove all existing mint tags
|
|
22
|
+
...tags.filter((t) => t[0] !== "mint"),
|
|
23
|
+
// add new mint tags
|
|
24
|
+
...mints.map((mint) => ["mint", mint]),
|
|
25
|
+
]);
|
|
26
|
+
}
|
|
27
|
+
/** Sets the "privkey" tag on a wallet event */
|
|
28
|
+
export function setPrivateKey(privateKey) {
|
|
29
|
+
return modifyHiddenTags(setSingletonTag(["privkey", bytesToHex(privateKey)], true));
|
|
30
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { EventOperation } from "applesauce-factory";
|
|
2
|
+
/** Sets the relays for a nutzap info event */
|
|
3
|
+
export declare function setNutzapInfoRelays(relays: string[]): EventOperation;
|
|
4
|
+
/** Sets the mints for a nutzap info event */
|
|
5
|
+
export declare function setNutzapInfoMints(mints: Array<{
|
|
6
|
+
url: string;
|
|
7
|
+
units?: string[];
|
|
8
|
+
}>): EventOperation;
|
|
9
|
+
/** Sets the pubkey for a nutzap info event */
|
|
10
|
+
export declare function setNutzapInfoPubkey(pubkey: string): EventOperation;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { modifyPublicTags } from "applesauce-factory/operations";
|
|
2
|
+
import { addNameValueTag, setSingletonTag } from "applesauce-factory/operations/tag";
|
|
3
|
+
/** Sets the relays for a nutzap info event */
|
|
4
|
+
export function setNutzapInfoRelays(relays) {
|
|
5
|
+
return modifyPublicTags(...relays.map((relay) => addNameValueTag(["relay", relay], false)));
|
|
6
|
+
}
|
|
7
|
+
/** Sets the mints for a nutzap info event */
|
|
8
|
+
export function setNutzapInfoMints(mints) {
|
|
9
|
+
return modifyPublicTags(...mints.map((mint) => {
|
|
10
|
+
const tag = mint.units ? ["mint", mint.url, ...mint.units] : ["mint", mint.url];
|
|
11
|
+
return addNameValueTag(tag, false);
|
|
12
|
+
}));
|
|
13
|
+
}
|
|
14
|
+
/** Sets the pubkey for a nutzap info event */
|
|
15
|
+
export function setNutzapInfoPubkey(pubkey) {
|
|
16
|
+
return modifyPublicTags(setSingletonTag(["pubkey", pubkey], true));
|
|
17
|
+
}
|
package/dist/types.d.ts
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>;
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { EventSigner } from "applesauce-factory";
|
|
2
|
+
import { NostrEvent } from "nostr-tools";
|
|
3
|
+
import { Observable, Subscription } from "rxjs";
|
|
4
|
+
import { EncryptionMethod } from "applesauce-core/helpers";
|
|
5
|
+
import { GetBalanceResult, GetInfoResult, ListTransactionsResult, LookupInvoiceResult, MakeInvoiceParams, MakeInvoiceResult, NotificationType, PayInvoiceResult, PayKeysendResult, WalletConnectEncryptionMethod, WalletConnectURI, WalletMethod, WalletNotification, WalletRequest, WalletResponse, WalletSupport } from "./helpers/index.js";
|
|
6
|
+
import { NostrPublishMethod, NostrSubscriptionMethod } from "./types.js";
|
|
7
|
+
export type SerializedWalletConnect = WalletConnectURI;
|
|
8
|
+
export type WalletConnectOptions = {
|
|
9
|
+
/** A method for subscribing to relays */
|
|
10
|
+
subscriptionMethod?: NostrSubscriptionMethod;
|
|
11
|
+
/** A method for publishing events */
|
|
12
|
+
publishMethod?: NostrPublishMethod;
|
|
13
|
+
/** Default timeout for RPC requests in milliseconds */
|
|
14
|
+
timeout?: number;
|
|
15
|
+
};
|
|
16
|
+
export declare class WalletConnect {
|
|
17
|
+
/** A fallback method to use for subscriptionMethod if none is passed in when creating the client */
|
|
18
|
+
static subscriptionMethod: NostrSubscriptionMethod | undefined;
|
|
19
|
+
/** A fallback method to use for publishMethod if none is passed in when creating the client */
|
|
20
|
+
static publishMethod: NostrPublishMethod | undefined;
|
|
21
|
+
/** A method that is called when an event needs to be published */
|
|
22
|
+
protected publishMethod: NostrPublishMethod;
|
|
23
|
+
/** The active nostr subscription method */
|
|
24
|
+
protected subscriptionMethod: NostrSubscriptionMethod;
|
|
25
|
+
/** The local client signer */
|
|
26
|
+
readonly secret: Uint8Array;
|
|
27
|
+
protected readonly signer: EventSigner;
|
|
28
|
+
/** The relays to use for the connection */
|
|
29
|
+
readonly relays: string[];
|
|
30
|
+
/** The wallet service public key */
|
|
31
|
+
readonly service: string;
|
|
32
|
+
/** Default timeout for requests */
|
|
33
|
+
defaultTimeout: number;
|
|
34
|
+
/** Observable for wallet info updates */
|
|
35
|
+
support$: Observable<WalletSupport | null>;
|
|
36
|
+
/** The preferred encryption method for the wallet */
|
|
37
|
+
encryption$: Observable<WalletConnectEncryptionMethod>;
|
|
38
|
+
/** Shared observable for all wallet response events and notifications */
|
|
39
|
+
protected events$: Observable<NostrEvent>;
|
|
40
|
+
/** Shared observable for all wallet notifications */
|
|
41
|
+
notifications$: Observable<WalletNotification>;
|
|
42
|
+
constructor(secret: Uint8Array, service: string, relays: string[], options?: WalletConnectOptions);
|
|
43
|
+
/** Process response events and return WalletResponse or throw error */
|
|
44
|
+
protected handleResponseEvent(event: NostrEvent, encryption?: EncryptionMethod): Promise<WalletResponse>;
|
|
45
|
+
/** Handle notification events */
|
|
46
|
+
protected handleNotificationEvent(event: NostrEvent): Promise<WalletNotification>;
|
|
47
|
+
/** Core RPC method that makes a request and returns the response */
|
|
48
|
+
request(request: WalletRequest, options?: {
|
|
49
|
+
timeout?: number;
|
|
50
|
+
}): Observable<WalletResponse>;
|
|
51
|
+
/**
|
|
52
|
+
* Listen for a type of notification
|
|
53
|
+
* @returns a method to unsubscribe the listener
|
|
54
|
+
*/
|
|
55
|
+
notification<T extends WalletNotification>(type: T["notification_type"], listener: (notification: T["notification"]) => any): Subscription;
|
|
56
|
+
/** Get the wallet support info */
|
|
57
|
+
getSupport(): Promise<WalletSupport | null>;
|
|
58
|
+
/** Check if the wallet supports a method */
|
|
59
|
+
supportsMethod(method: WalletMethod): Promise<boolean>;
|
|
60
|
+
/** Check if the wallet supports notifications */
|
|
61
|
+
supportsNotifications(): Promise<boolean>;
|
|
62
|
+
/** Check if the wallet supports a notification type */
|
|
63
|
+
supportsNotificationType(type: NotificationType): Promise<boolean>;
|
|
64
|
+
/** Pay a lightning invoice */
|
|
65
|
+
payInvoice(invoice: string, amount?: number): Promise<PayInvoiceResult>;
|
|
66
|
+
/** Pay multiple lightning invoices */
|
|
67
|
+
payMultipleInvoices(invoices: Array<{
|
|
68
|
+
id?: string;
|
|
69
|
+
invoice: string;
|
|
70
|
+
amount?: number;
|
|
71
|
+
}>): Promise<PayInvoiceResult[]>;
|
|
72
|
+
/** Send a keysend payment */
|
|
73
|
+
payKeysend(pubkey: string, amount: number, preimage?: string, tlv_records?: Array<{
|
|
74
|
+
type: number;
|
|
75
|
+
value: string;
|
|
76
|
+
}>): Promise<PayKeysendResult>;
|
|
77
|
+
/** Send multiple keysend payments */
|
|
78
|
+
payMultipleKeysend(keysends: Array<{
|
|
79
|
+
id?: string;
|
|
80
|
+
pubkey: string;
|
|
81
|
+
amount: number;
|
|
82
|
+
preimage?: string;
|
|
83
|
+
tlv_records?: Array<{
|
|
84
|
+
type: number;
|
|
85
|
+
value: string;
|
|
86
|
+
}>;
|
|
87
|
+
}>): Promise<PayKeysendResult[]>;
|
|
88
|
+
/** Create a new invoice */
|
|
89
|
+
makeInvoice(amount: number, options?: Omit<MakeInvoiceParams, "amount">): Promise<MakeInvoiceResult>;
|
|
90
|
+
/** Look up an invoice by payment hash or invoice string */
|
|
91
|
+
lookupInvoice(payment_hash?: string, invoice?: string): Promise<LookupInvoiceResult>;
|
|
92
|
+
/** List transactions */
|
|
93
|
+
listTransactions(params?: {
|
|
94
|
+
from?: number;
|
|
95
|
+
until?: number;
|
|
96
|
+
limit?: number;
|
|
97
|
+
offset?: number;
|
|
98
|
+
unpaid?: boolean;
|
|
99
|
+
type?: "incoming" | "outgoing";
|
|
100
|
+
}): Promise<ListTransactionsResult>;
|
|
101
|
+
/** Get wallet balance */
|
|
102
|
+
getBalance(): Promise<GetBalanceResult>;
|
|
103
|
+
/** Get wallet info */
|
|
104
|
+
getInfo(): Promise<GetInfoResult>;
|
|
105
|
+
/** Serialize the WalletConnect instance */
|
|
106
|
+
toJSON(): SerializedWalletConnect;
|
|
107
|
+
/** Create a new WalletConnect instance from a serialized object */
|
|
108
|
+
static fromJSON(json: SerializedWalletConnect, options?: WalletConnectOptions): WalletConnect;
|
|
109
|
+
/** Create a new WalletConnect instance from a connection string */
|
|
110
|
+
static fromConnectionString(connectionString: string, options?: WalletConnectOptions): WalletConnect;
|
|
111
|
+
}
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
import { bytesToHex, hexToBytes } from "@noble/hashes/utils";
|
|
2
|
+
import { simpleTimeout } from "applesauce-core";
|
|
3
|
+
import { create } from "applesauce-factory";
|
|
4
|
+
import { finalizeEvent, getPublicKey, nip04, nip44, verifyEvent } from "nostr-tools";
|
|
5
|
+
import { defer, filter, firstValueFrom, from, ignoreElements, lastValueFrom, map, merge, mergeMap, ReplaySubject, share, switchMap, timer, toArray, } from "rxjs";
|
|
6
|
+
import { WalletRequestBlueprint } from "./blueprints/index.js";
|
|
7
|
+
import { createWalletError } from "./helpers/error.js";
|
|
8
|
+
import { getPreferredEncryption, getWalletNotification, getWalletRequestEncryption, getWalletResponse, getWalletResponseRequestId, getWalletSupport, isWalletNotificationLocked, isWalletResponseLocked, parseWalletConnectURI, supportsMethod, supportsNotifications, supportsNotificationType, unlockWalletNotification, unlockWalletResponse, WALLET_INFO_KIND, WALLET_LEGACY_NOTIFICATION_KIND, WALLET_NOTIFICATION_KIND, WALLET_RESPONSE_KIND, } from "./helpers/index.js";
|
|
9
|
+
export class WalletConnect {
|
|
10
|
+
/** A fallback method to use for subscriptionMethod if none is passed in when creating the client */
|
|
11
|
+
static subscriptionMethod = undefined;
|
|
12
|
+
/** A fallback method to use for publishMethod if none is passed in when creating the client */
|
|
13
|
+
static publishMethod = undefined;
|
|
14
|
+
/** A method that is called when an event needs to be published */
|
|
15
|
+
publishMethod;
|
|
16
|
+
/** The active nostr subscription method */
|
|
17
|
+
subscriptionMethod;
|
|
18
|
+
/** The local client signer */
|
|
19
|
+
secret;
|
|
20
|
+
signer;
|
|
21
|
+
/** The relays to use for the connection */
|
|
22
|
+
relays;
|
|
23
|
+
/** The wallet service public key */
|
|
24
|
+
service;
|
|
25
|
+
/** Default timeout for requests */
|
|
26
|
+
defaultTimeout;
|
|
27
|
+
/** Observable for wallet info updates */
|
|
28
|
+
support$;
|
|
29
|
+
/** The preferred encryption method for the wallet */
|
|
30
|
+
encryption$;
|
|
31
|
+
/** Shared observable for all wallet response events and notifications */
|
|
32
|
+
events$;
|
|
33
|
+
/** Shared observable for all wallet notifications */
|
|
34
|
+
notifications$;
|
|
35
|
+
constructor(secret, service, relays, options = {}) {
|
|
36
|
+
this.service = service;
|
|
37
|
+
this.secret = secret;
|
|
38
|
+
this.relays = relays;
|
|
39
|
+
this.defaultTimeout = options.timeout || 30000; // 30 second default timeout
|
|
40
|
+
// Create a signer for the factory
|
|
41
|
+
this.signer = {
|
|
42
|
+
getPublicKey: async () => getPublicKey(this.secret),
|
|
43
|
+
signEvent: async (draft) => finalizeEvent(draft, this.secret),
|
|
44
|
+
nip04: {
|
|
45
|
+
encrypt: async (pubkey, plaintext) => nip04.encrypt(this.secret, pubkey, plaintext),
|
|
46
|
+
decrypt: async (pubkey, ciphertext) => nip04.decrypt(this.secret, pubkey, ciphertext),
|
|
47
|
+
},
|
|
48
|
+
nip44: {
|
|
49
|
+
encrypt: async (pubkey, plaintext) => nip44.encrypt(plaintext, nip44.getConversationKey(this.secret, pubkey)),
|
|
50
|
+
decrypt: async (pubkey, ciphertext) => nip44.decrypt(ciphertext, nip44.getConversationKey(this.secret, pubkey)),
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
const subscriptionMethod = options.subscriptionMethod || WalletConnect.subscriptionMethod;
|
|
54
|
+
if (!subscriptionMethod)
|
|
55
|
+
throw new Error("Missing subscriptionMethod, either pass a method or set WalletConnect.subscriptionMethod");
|
|
56
|
+
const publishMethod = options.publishMethod || WalletConnect.publishMethod;
|
|
57
|
+
if (!publishMethod)
|
|
58
|
+
throw new Error("Missing publishMethod, either pass a method or set WalletConnect.publishMethod");
|
|
59
|
+
this.subscriptionMethod = subscriptionMethod;
|
|
60
|
+
this.publishMethod = publishMethod;
|
|
61
|
+
// Create shared response observable with ref counting and timer
|
|
62
|
+
this.events$ = defer(() => this.signer.getPublicKey()).pipe(switchMap((client) => this.subscriptionMethod(this.relays, [
|
|
63
|
+
// Subscribe to response events
|
|
64
|
+
{
|
|
65
|
+
kinds: [WALLET_RESPONSE_KIND, WALLET_NOTIFICATION_KIND, WALLET_LEGACY_NOTIFICATION_KIND],
|
|
66
|
+
"#p": [client],
|
|
67
|
+
authors: [this.service],
|
|
68
|
+
},
|
|
69
|
+
// Subscribe to wallet info events
|
|
70
|
+
{ kinds: [WALLET_INFO_KIND], authors: [this.service] },
|
|
71
|
+
])),
|
|
72
|
+
// Ingore strings (support for applesauce-relay)
|
|
73
|
+
filter((event) => typeof event !== "string"),
|
|
74
|
+
// Only include events from the wallet service
|
|
75
|
+
filter((event) => event.pubkey === this.service),
|
|
76
|
+
// Only create a single subscription to the relays
|
|
77
|
+
share({
|
|
78
|
+
resetOnRefCountZero: () => timer(60000), // Keep subscription open for 1 minute after last unsubscribe
|
|
79
|
+
}));
|
|
80
|
+
this.support$ = this.events$.pipe(filter((event) => event.kind === WALLET_INFO_KIND), map((event) => getWalletSupport(event)), share({
|
|
81
|
+
connector: () => new ReplaySubject(1),
|
|
82
|
+
resetOnRefCountZero: () => timer(60000), // Keep info observable around for 1 minute after last unsubscribe
|
|
83
|
+
}));
|
|
84
|
+
this.encryption$ = this.support$.pipe(map((info) => (info ? getPreferredEncryption(info) : "nip04")));
|
|
85
|
+
this.notifications$ = this.events$.pipe(filter((event) => event.kind === WALLET_NOTIFICATION_KIND), mergeMap((event) => this.handleNotificationEvent(event)));
|
|
86
|
+
}
|
|
87
|
+
/** Process response events and return WalletResponse or throw error */
|
|
88
|
+
async handleResponseEvent(event, encryption) {
|
|
89
|
+
if (!verifyEvent(event))
|
|
90
|
+
throw new Error("Invalid response event signature");
|
|
91
|
+
const requestId = getWalletResponseRequestId(event);
|
|
92
|
+
if (!requestId)
|
|
93
|
+
throw new Error("Response missing request ID");
|
|
94
|
+
let response;
|
|
95
|
+
if (isWalletResponseLocked(event))
|
|
96
|
+
response = await unlockWalletResponse(event, this.signer, encryption);
|
|
97
|
+
else
|
|
98
|
+
response = getWalletResponse(event);
|
|
99
|
+
if (!response)
|
|
100
|
+
throw new Error("Failed to decrypt or parse response");
|
|
101
|
+
return response;
|
|
102
|
+
}
|
|
103
|
+
/** Handle notification events */
|
|
104
|
+
async handleNotificationEvent(event) {
|
|
105
|
+
if (!verifyEvent(event))
|
|
106
|
+
throw new Error("Invalid notification event signature");
|
|
107
|
+
let notification;
|
|
108
|
+
if (isWalletNotificationLocked(event))
|
|
109
|
+
notification = await unlockWalletNotification(event, this.signer);
|
|
110
|
+
else
|
|
111
|
+
notification = getWalletNotification(event);
|
|
112
|
+
if (!notification)
|
|
113
|
+
throw new Error("Failed to decrypt or parse notification");
|
|
114
|
+
return notification;
|
|
115
|
+
}
|
|
116
|
+
/** Core RPC method that makes a request and returns the response */
|
|
117
|
+
request(request, options = {}) {
|
|
118
|
+
// Create the request evnet
|
|
119
|
+
return defer(async () => {
|
|
120
|
+
// Get the preferred encryption method for the wallet
|
|
121
|
+
const encryption = await firstValueFrom(this.encryption$);
|
|
122
|
+
// Create the request event
|
|
123
|
+
const draft = await create({ signer: this.signer }, WalletRequestBlueprint, this.service, request, encryption);
|
|
124
|
+
// Sign the request event
|
|
125
|
+
return await this.signer.signEvent(draft);
|
|
126
|
+
}).pipe(
|
|
127
|
+
// Then switch to the request observable
|
|
128
|
+
switchMap((requestEvent) => {
|
|
129
|
+
const encryption = getWalletRequestEncryption(requestEvent) === "nip44_v2" ? "nip44" : "nip04";
|
|
130
|
+
// Create an observable that publishes the request event when subscribed to
|
|
131
|
+
const request$ = defer(() => from(this.publishMethod(this.relays, requestEvent))).pipe(ignoreElements());
|
|
132
|
+
// Create an observable that listens for response events
|
|
133
|
+
const responses$ = this.events$.pipe(filter((response) => response.kind === WALLET_RESPONSE_KIND && getWalletResponseRequestId(response) === requestEvent.id), mergeMap((response) => this.handleResponseEvent(response, encryption)),
|
|
134
|
+
// Set timeout for response events
|
|
135
|
+
simpleTimeout(options.timeout || this.defaultTimeout));
|
|
136
|
+
return merge(request$, responses$);
|
|
137
|
+
}));
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Listen for a type of notification
|
|
141
|
+
* @returns a method to unsubscribe the listener
|
|
142
|
+
*/
|
|
143
|
+
notification(type, listener) {
|
|
144
|
+
return this.notifications$.subscribe((notification) => {
|
|
145
|
+
if (notification.notification_type === type)
|
|
146
|
+
listener(notification.notification);
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
// Convenience methods that return promises for easy API usage
|
|
150
|
+
/** Get the wallet support info */
|
|
151
|
+
getSupport() {
|
|
152
|
+
return firstValueFrom(this.support$);
|
|
153
|
+
}
|
|
154
|
+
/** Check if the wallet supports a method */
|
|
155
|
+
async supportsMethod(method) {
|
|
156
|
+
const support = await this.getSupport();
|
|
157
|
+
return support ? supportsMethod(support, method) : false;
|
|
158
|
+
}
|
|
159
|
+
/** Check if the wallet supports notifications */
|
|
160
|
+
async supportsNotifications() {
|
|
161
|
+
const support = await this.getSupport();
|
|
162
|
+
return support ? supportsNotifications(support) : false;
|
|
163
|
+
}
|
|
164
|
+
/** Check if the wallet supports a notification type */
|
|
165
|
+
async supportsNotificationType(type) {
|
|
166
|
+
const support = await this.getSupport();
|
|
167
|
+
return support ? supportsNotificationType(support, type) : false;
|
|
168
|
+
}
|
|
169
|
+
/** Pay a lightning invoice */
|
|
170
|
+
async payInvoice(invoice, amount) {
|
|
171
|
+
const response = await firstValueFrom(this.request({ method: "pay_invoice", params: { invoice, amount } }));
|
|
172
|
+
if (response.result_type !== "pay_invoice")
|
|
173
|
+
throw new Error(`Unexpected response type: ${response.result_type}`);
|
|
174
|
+
if (response.error)
|
|
175
|
+
throw createWalletError(response.error.type, response.error.message);
|
|
176
|
+
return response.result;
|
|
177
|
+
}
|
|
178
|
+
/** Pay multiple lightning invoices */
|
|
179
|
+
async payMultipleInvoices(invoices) {
|
|
180
|
+
return await lastValueFrom(this.request({ method: "multi_pay_invoice", params: { invoices } })
|
|
181
|
+
.pipe(map((response) => {
|
|
182
|
+
if (response.result_type !== "multi_pay_invoice")
|
|
183
|
+
throw new Error(`Unexpected response type: ${response.result_type}`);
|
|
184
|
+
if (response.error)
|
|
185
|
+
throw createWalletError(response.error.type, response.error.message);
|
|
186
|
+
return response.result;
|
|
187
|
+
}))
|
|
188
|
+
.pipe(toArray()));
|
|
189
|
+
}
|
|
190
|
+
/** Send a keysend payment */
|
|
191
|
+
async payKeysend(pubkey, amount, preimage, tlv_records) {
|
|
192
|
+
const response = await firstValueFrom(this.request({ method: "pay_keysend", params: { pubkey, amount, preimage, tlv_records } }));
|
|
193
|
+
if (response.result_type !== "pay_keysend")
|
|
194
|
+
throw new Error(`Unexpected response type: ${response.result_type}`);
|
|
195
|
+
if (response.error)
|
|
196
|
+
throw createWalletError(response.error.type, response.error.message);
|
|
197
|
+
return response.result;
|
|
198
|
+
}
|
|
199
|
+
/** Send multiple keysend payments */
|
|
200
|
+
async payMultipleKeysend(keysends) {
|
|
201
|
+
return lastValueFrom(this.request({ method: "multi_pay_keysend", params: { keysends } }).pipe(map((response) => {
|
|
202
|
+
if (response.result_type !== "multi_pay_keysend")
|
|
203
|
+
throw new Error(`Unexpected response type: ${response.result_type}`);
|
|
204
|
+
if (response.error)
|
|
205
|
+
throw createWalletError(response.error.type, response.error.message);
|
|
206
|
+
return response.result;
|
|
207
|
+
}), toArray()));
|
|
208
|
+
}
|
|
209
|
+
/** Create a new invoice */
|
|
210
|
+
async makeInvoice(amount, options) {
|
|
211
|
+
const response = await firstValueFrom(this.request({ method: "make_invoice", params: { amount, ...options } }));
|
|
212
|
+
if (response.result_type !== "make_invoice")
|
|
213
|
+
throw new Error(`Unexpected response type: ${response.result_type}`);
|
|
214
|
+
if (response.error)
|
|
215
|
+
throw createWalletError(response.error.type, response.error.message);
|
|
216
|
+
return response.result;
|
|
217
|
+
}
|
|
218
|
+
/** Look up an invoice by payment hash or invoice string */
|
|
219
|
+
async lookupInvoice(payment_hash, invoice) {
|
|
220
|
+
const response = await firstValueFrom(this.request({ method: "lookup_invoice", params: { payment_hash, invoice } }));
|
|
221
|
+
if (response.result_type !== "lookup_invoice")
|
|
222
|
+
throw new Error(`Unexpected response type: ${response.result_type}`);
|
|
223
|
+
if (response.error)
|
|
224
|
+
throw createWalletError(response.error.type, response.error.message);
|
|
225
|
+
return response.result;
|
|
226
|
+
}
|
|
227
|
+
/** List transactions */
|
|
228
|
+
async listTransactions(params) {
|
|
229
|
+
const response = await firstValueFrom(this.request({ method: "list_transactions", params: params || {} }));
|
|
230
|
+
if (response.result_type !== "list_transactions")
|
|
231
|
+
throw new Error(`Unexpected response type: ${response.result_type}`);
|
|
232
|
+
if (response.error)
|
|
233
|
+
throw createWalletError(response.error.type, response.error.message);
|
|
234
|
+
return response.result;
|
|
235
|
+
}
|
|
236
|
+
/** Get wallet balance */
|
|
237
|
+
async getBalance() {
|
|
238
|
+
const response = await firstValueFrom(this.request({ method: "get_balance", params: {} }));
|
|
239
|
+
if (response.result_type !== "get_balance")
|
|
240
|
+
throw new Error(`Unexpected response type: ${response.result_type}`);
|
|
241
|
+
if (response.error)
|
|
242
|
+
throw createWalletError(response.error.type, response.error.message);
|
|
243
|
+
return response.result;
|
|
244
|
+
}
|
|
245
|
+
/** Get wallet info */
|
|
246
|
+
async getInfo() {
|
|
247
|
+
const response = await firstValueFrom(this.request({ method: "get_info", params: {} }));
|
|
248
|
+
if (response.result_type !== "get_info")
|
|
249
|
+
throw new Error(`Unexpected response type: ${response.result_type}`);
|
|
250
|
+
if (response.error)
|
|
251
|
+
throw createWalletError(response.error.type, response.error.message);
|
|
252
|
+
return response.result;
|
|
253
|
+
}
|
|
254
|
+
/** Serialize the WalletConnect instance */
|
|
255
|
+
toJSON() {
|
|
256
|
+
return {
|
|
257
|
+
secret: bytesToHex(this.secret),
|
|
258
|
+
service: this.service,
|
|
259
|
+
relays: this.relays,
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
/** Create a new WalletConnect instance from a serialized object */
|
|
263
|
+
static fromJSON(json, options) {
|
|
264
|
+
return new WalletConnect(hexToBytes(json.secret), json.service, json.relays, options);
|
|
265
|
+
}
|
|
266
|
+
/** Create a new WalletConnect instance from a connection string */
|
|
267
|
+
static fromConnectionString(connectionString, options) {
|
|
268
|
+
const { secret, service, relays } = parseWalletConnectURI(connectionString);
|
|
269
|
+
return new WalletConnect(hexToBytes(secret), service, relays, options);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { EventSigner } from "applesauce-factory";
|
|
2
|
+
import { NostrEvent } from "nostr-tools";
|
|
3
|
+
import { Observable, Subscription } from "rxjs";
|
|
4
|
+
import { WalletErrorCode } from "./helpers/error.js";
|
|
5
|
+
import { NotificationType, WalletNotification } from "./helpers/notification.js";
|
|
6
|
+
import { GetBalanceParams, GetInfoParams, ListTransactionsParams, LookupInvoiceParams, MakeInvoiceParams, MultiPayInvoiceParams, MultiPayKeysendParams, PayInvoiceParams, PayKeysendParams, WalletRequest } from "./helpers/request.js";
|
|
7
|
+
import { GetBalanceResult, GetInfoResult, ListTransactionsResult, LookupInvoiceResult, MakeInvoiceResult, MultiPayInvoiceResult, MultiPayKeysendResult, PayInvoiceResult, PayKeysendResult, WalletResponse } from "./helpers/response.js";
|
|
8
|
+
import { WalletSupport } from "./helpers/support.js";
|
|
9
|
+
import { NostrPublishMethod, NostrSubscriptionMethod } from "./types.js";
|
|
10
|
+
/** Handler function for pay_invoice method */
|
|
11
|
+
export type PayInvoiceHandler = (params: PayInvoiceParams) => Promise<PayInvoiceResult>;
|
|
12
|
+
/** Handler function for multi_pay_invoice method */
|
|
13
|
+
export type MultiPayInvoiceHandler = (params: MultiPayInvoiceParams) => Promise<MultiPayInvoiceResult[]>;
|
|
14
|
+
/** Handler function for pay_keysend method */
|
|
15
|
+
export type PayKeysendHandler = (params: PayKeysendParams) => Promise<PayKeysendResult>;
|
|
16
|
+
/** Handler function for multi_pay_keysend method */
|
|
17
|
+
export type MultiPayKeysendHandler = (params: MultiPayKeysendParams) => Promise<MultiPayKeysendResult[]>;
|
|
18
|
+
/** Handler function for make_invoice method */
|
|
19
|
+
export type MakeInvoiceHandler = (params: MakeInvoiceParams) => Promise<MakeInvoiceResult>;
|
|
20
|
+
/** Handler function for lookup_invoice method */
|
|
21
|
+
export type LookupInvoiceHandler = (params: LookupInvoiceParams) => Promise<LookupInvoiceResult>;
|
|
22
|
+
/** Handler function for list_transactions method */
|
|
23
|
+
export type ListTransactionsHandler = (params: ListTransactionsParams) => Promise<ListTransactionsResult>;
|
|
24
|
+
/** Handler function for get_balance method */
|
|
25
|
+
export type GetBalanceHandler = (params: GetBalanceParams) => Promise<GetBalanceResult>;
|
|
26
|
+
/** Handler function for get_info method */
|
|
27
|
+
export type GetInfoHandler = (params: GetInfoParams) => Promise<GetInfoResult>;
|
|
28
|
+
/** Map of method handlers for the wallet service */
|
|
29
|
+
export interface WalletServiceHandlers {
|
|
30
|
+
pay_invoice?: PayInvoiceHandler;
|
|
31
|
+
multi_pay_invoice?: MultiPayInvoiceHandler;
|
|
32
|
+
pay_keysend?: PayKeysendHandler;
|
|
33
|
+
multi_pay_keysend?: MultiPayKeysendHandler;
|
|
34
|
+
make_invoice?: MakeInvoiceHandler;
|
|
35
|
+
lookup_invoice?: LookupInvoiceHandler;
|
|
36
|
+
list_transactions?: ListTransactionsHandler;
|
|
37
|
+
get_balance?: GetBalanceHandler;
|
|
38
|
+
get_info?: GetInfoHandler;
|
|
39
|
+
}
|
|
40
|
+
/** Options for creating a WalletService */
|
|
41
|
+
export interface WalletServiceOptions {
|
|
42
|
+
/** The relays to use for the service */
|
|
43
|
+
relays: string[];
|
|
44
|
+
/** The signer to use for creating and unlocking events */
|
|
45
|
+
signer: EventSigner;
|
|
46
|
+
/** The client's secret key */
|
|
47
|
+
secret?: Uint8Array;
|
|
48
|
+
/** Map of method handlers */
|
|
49
|
+
handlers: WalletServiceHandlers;
|
|
50
|
+
/** An array of notifications this wallet supports */
|
|
51
|
+
notifications?: NotificationType[];
|
|
52
|
+
/** An optional method for subscribing to relays */
|
|
53
|
+
subscriptionMethod?: NostrSubscriptionMethod;
|
|
54
|
+
/** An optional method for publishing events */
|
|
55
|
+
publishMethod?: NostrPublishMethod;
|
|
56
|
+
}
|
|
57
|
+
/** NIP-47 Wallet Service implementation */
|
|
58
|
+
export declare class WalletService {
|
|
59
|
+
/** A fallback method to use for subscriptionMethod if none is passed in when creating the client */
|
|
60
|
+
static subscriptionMethod: NostrSubscriptionMethod | undefined;
|
|
61
|
+
/** A fallback method to use for publishMethod if none is passed in when creating the client */
|
|
62
|
+
static publishMethod: NostrPublishMethod | undefined;
|
|
63
|
+
/** A method for subscribing to relays */
|
|
64
|
+
protected readonly subscriptionMethod: NostrSubscriptionMethod;
|
|
65
|
+
/** A method for publishing events */
|
|
66
|
+
protected readonly publishMethod: NostrPublishMethod;
|
|
67
|
+
protected log: import("debug").Debugger;
|
|
68
|
+
/** The relays to use for the service */
|
|
69
|
+
readonly relays: string[];
|
|
70
|
+
/** The signer used for creating and unlocking events */
|
|
71
|
+
protected readonly signer: EventSigner;
|
|
72
|
+
/** Map of method handlers */
|
|
73
|
+
protected readonly handlers: WalletServiceHandlers;
|
|
74
|
+
/** Wallet support information */
|
|
75
|
+
protected readonly support: WalletSupport;
|
|
76
|
+
/** The service's public key */
|
|
77
|
+
pubkey: string | null;
|
|
78
|
+
/** The client's secret key */
|
|
79
|
+
protected secret: Uint8Array;
|
|
80
|
+
/** Shared observable for all wallet request events */
|
|
81
|
+
protected events$: Observable<NostrEvent> | null;
|
|
82
|
+
/** Subscription to the events observable */
|
|
83
|
+
protected subscription: Subscription | null;
|
|
84
|
+
/** Whether the service is currently running */
|
|
85
|
+
running: boolean;
|
|
86
|
+
/** Get the clients public key */
|
|
87
|
+
get client(): string;
|
|
88
|
+
constructor(options: WalletServiceOptions);
|
|
89
|
+
/** Start the wallet service */
|
|
90
|
+
start(): Promise<void>;
|
|
91
|
+
/** Stop the wallet service */
|
|
92
|
+
stop(): void;
|
|
93
|
+
/** Check if the service is running */
|
|
94
|
+
isRunning(): boolean;
|
|
95
|
+
/** Get the connection string for the service */
|
|
96
|
+
getConnectionString(): string;
|
|
97
|
+
/** Send a notification to the client */
|
|
98
|
+
notify<T extends WalletNotification>(type: T["notification_type"], notification: T["notification"], legacy?: boolean): Promise<void>;
|
|
99
|
+
/** Publish the wallet support event */
|
|
100
|
+
protected publishSupportEvent(): Promise<void>;
|
|
101
|
+
/** Handle a wallet request event */
|
|
102
|
+
protected handleRequestEvent(requestEvent: NostrEvent): Promise<void>;
|
|
103
|
+
/** Process a decrypted wallet request */
|
|
104
|
+
protected processRequest(requestEvent: NostrEvent, request: WalletRequest): Promise<void>;
|
|
105
|
+
/** Send a success response */
|
|
106
|
+
protected sendSuccessResponse<T extends WalletResponse>(requestEvent: NostrEvent, method: T["result_type"], result: T["result"]): Promise<void>;
|
|
107
|
+
/** Send an error response */
|
|
108
|
+
protected sendErrorResponse(requestEvent: NostrEvent, errorType: WalletErrorCode, errorMessage: string): Promise<void>;
|
|
109
|
+
/** Send a response event */
|
|
110
|
+
protected sendResponse(requestEvent: NostrEvent, response: WalletResponse): Promise<void>;
|
|
111
|
+
}
|