applesauce-wallet-connect 3.1.0 → 4.1.0
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/README.md +2 -2
- package/dist/blueprints/request.d.ts +2 -2
- package/dist/blueprints/response.d.ts +2 -2
- package/dist/blueprints/support.d.ts +8 -2
- package/dist/blueprints/support.js +8 -3
- package/dist/helpers/auth-uri.d.ts +7 -9
- package/dist/helpers/auth-uri.js +1 -3
- package/dist/helpers/index.d.ts +1 -0
- package/dist/helpers/index.js +1 -0
- package/dist/helpers/methods.d.ts +234 -0
- package/dist/helpers/methods.js +1 -0
- package/dist/helpers/notification.d.ts +13 -5
- package/dist/helpers/notification.js +18 -12
- package/dist/helpers/request.d.ts +13 -115
- package/dist/helpers/request.js +19 -14
- package/dist/helpers/response.d.ts +14 -126
- package/dist/helpers/response.js +20 -14
- package/dist/helpers/support.d.ts +7 -7
- package/dist/helpers/support.js +1 -14
- package/dist/wallet-connect.d.ts +48 -20
- package/dist/wallet-connect.js +71 -79
- package/dist/wallet-service.d.ts +34 -47
- package/dist/wallet-service.js +43 -49
- package/package.json +7 -5
package/dist/wallet-connect.js
CHANGED
|
@@ -2,10 +2,10 @@ import { bytesToHex, hexToBytes } from "@noble/hashes/utils";
|
|
|
2
2
|
import { simpleTimeout } from "applesauce-core";
|
|
3
3
|
import { create } from "applesauce-factory";
|
|
4
4
|
import { finalizeEvent, getPublicKey, nip04, nip44, verifyEvent } from "nostr-tools";
|
|
5
|
-
import { BehaviorSubject, defer, filter, firstValueFrom, from, fromEvent, identity, ignoreElements, lastValueFrom, map, merge, mergeMap, repeat, ReplaySubject, retry, share, switchMap, takeUntil, tap, timer, toArray, } from "rxjs";
|
|
5
|
+
import { BehaviorSubject, combineLatest, defer, filter, firstValueFrom, from, fromEvent, identity, ignoreElements, lastValueFrom, map, merge, mergeMap, of, repeat, ReplaySubject, retry, share, switchMap, take, takeUntil, tap, timer, toArray, } from "rxjs";
|
|
6
6
|
import { WalletRequestBlueprint } from "./blueprints/index.js";
|
|
7
7
|
import { createWalletError } from "./helpers/error.js";
|
|
8
|
-
import { createWalletAuthURI, getPreferredEncryption,
|
|
8
|
+
import { createWalletAuthURI, getPreferredEncryption, getWalletRequestEncryption, getWalletResponseRequestId, getWalletSupport, isValidWalletNotification, isValidWalletResponse, parseWalletConnectURI, supportsMethod, supportsNotifications, supportsNotificationType, unlockWalletNotification, unlockWalletResponse, WALLET_INFO_KIND, WALLET_LEGACY_NOTIFICATION_KIND, WALLET_NOTIFICATION_KIND, WALLET_RESPONSE_KIND, } from "./helpers/index.js";
|
|
9
9
|
import { getConnectionMethods, } from "./interop.js";
|
|
10
10
|
export class WalletConnect {
|
|
11
11
|
/** A fallback method to use for subscriptionMethod if none is passed in when creating the client */
|
|
@@ -22,7 +22,12 @@ export class WalletConnect {
|
|
|
22
22
|
secret;
|
|
23
23
|
signer;
|
|
24
24
|
/** The relays to use for the connection */
|
|
25
|
-
relays;
|
|
25
|
+
relays$ = new BehaviorSubject([]);
|
|
26
|
+
get relays() {
|
|
27
|
+
return this.relays$.value;
|
|
28
|
+
}
|
|
29
|
+
/** Whether to accept the relay hint from the wallet service */
|
|
30
|
+
acceptRelayHint;
|
|
26
31
|
/** The wallet service public key ( unset if waiting for service ) */
|
|
27
32
|
service$ = new BehaviorSubject(undefined);
|
|
28
33
|
get service() {
|
|
@@ -42,7 +47,8 @@ export class WalletConnect {
|
|
|
42
47
|
waitForService$;
|
|
43
48
|
constructor(options) {
|
|
44
49
|
this.secret = options.secret;
|
|
45
|
-
this.relays
|
|
50
|
+
this.relays$.next(options.relays);
|
|
51
|
+
this.acceptRelayHint = options.acceptRelayHint ?? true;
|
|
46
52
|
this.service$.next(options.service);
|
|
47
53
|
this.defaultTimeout = options.timeout || 30000; // 30 second default timeout
|
|
48
54
|
// Create a signer for the factory
|
|
@@ -64,18 +70,18 @@ export class WalletConnect {
|
|
|
64
70
|
this.subscriptionMethod = (relays, filters) => subscriptionMethod(relays, filters);
|
|
65
71
|
this.publishMethod = (relays, event) => publishMethod(relays, event);
|
|
66
72
|
// Create shared observable for all wallet events
|
|
67
|
-
this.events$ = this.service
|
|
73
|
+
this.events$ = combineLatest([this.service$, this.relays$]).pipe(switchMap(([service, relays]) => {
|
|
68
74
|
const client = getPublicKey(this.secret);
|
|
69
75
|
// If the service is not known yet, subscribe to a wallet info event tagging the client
|
|
70
76
|
if (!service)
|
|
71
|
-
return from(this.subscriptionMethod(
|
|
77
|
+
return from(this.subscriptionMethod(relays, [{ kinds: [WALLET_INFO_KIND], "#p": [client] }])).pipe(
|
|
72
78
|
// Keep the connection open indefinitely
|
|
73
79
|
repeat(),
|
|
74
80
|
// Retry on connection failure
|
|
75
81
|
retry(),
|
|
76
82
|
// Ignore strings (support for applesauce-relay)
|
|
77
83
|
filter((event) => typeof event !== "string"));
|
|
78
|
-
return from(this.subscriptionMethod(
|
|
84
|
+
return from(this.subscriptionMethod(relays, [
|
|
79
85
|
// Subscribe to response events
|
|
80
86
|
{
|
|
81
87
|
kinds: [WALLET_RESPONSE_KIND, WALLET_NOTIFICATION_KIND, WALLET_LEGACY_NOTIFICATION_KIND],
|
|
@@ -103,19 +109,30 @@ export class WalletConnect {
|
|
|
103
109
|
resetOnRefCountZero: () => timer(60000), // Keep info observable around for 1 minute after last unsubscribe
|
|
104
110
|
}));
|
|
105
111
|
this.encryption$ = this.support$.pipe(map((info) => (info ? getPreferredEncryption(info) : "nip04")));
|
|
106
|
-
this.notifications$ = this.events$.pipe(filter((event) => event
|
|
107
|
-
this.waitForService$ =
|
|
108
|
-
//
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
112
|
+
this.notifications$ = this.events$.pipe(filter((event) => isValidWalletNotification(event)), mergeMap((event) => this.handleNotificationEvent(event)));
|
|
113
|
+
this.waitForService$ = defer(() =>
|
|
114
|
+
// If service is already set, return it
|
|
115
|
+
this.service$.value
|
|
116
|
+
? of(this.service$.value)
|
|
117
|
+
: // Otherwise listen for new wallet info events
|
|
118
|
+
this.events$.pipe(
|
|
119
|
+
// Only listen for wallet info events
|
|
120
|
+
filter((event) => event.kind === WALLET_INFO_KIND),
|
|
121
|
+
// Set the service to the pubkey of the wallet info event
|
|
122
|
+
tap((event) => {
|
|
123
|
+
// Set the service to the pubkey of the wallet info event
|
|
124
|
+
this.service$.next(event.pubkey);
|
|
125
|
+
// Switch to the relay from the service if its set
|
|
126
|
+
if (this.acceptRelayHint) {
|
|
127
|
+
const relay = event.tags.find((t) => t[0] === "p" && t[2])?.[2];
|
|
128
|
+
if (relay)
|
|
129
|
+
this.relays$.next([relay]);
|
|
130
|
+
}
|
|
131
|
+
}),
|
|
132
|
+
// Get the service pubkey from the event
|
|
133
|
+
map((event) => event.pubkey),
|
|
134
|
+
// Complete after the first value
|
|
135
|
+
take(1))).pipe(
|
|
119
136
|
// Only create a single subscription to avoid multiple side effects
|
|
120
137
|
share());
|
|
121
138
|
}
|
|
@@ -126,11 +143,7 @@ export class WalletConnect {
|
|
|
126
143
|
const requestId = getWalletResponseRequestId(event);
|
|
127
144
|
if (!requestId)
|
|
128
145
|
throw new Error("Response missing request ID");
|
|
129
|
-
|
|
130
|
-
if (isWalletResponseLocked(event))
|
|
131
|
-
response = await unlockWalletResponse(event, this.signer, encryption);
|
|
132
|
-
else
|
|
133
|
-
response = getWalletResponse(event);
|
|
146
|
+
const response = await unlockWalletResponse(event, this.signer, encryption);
|
|
134
147
|
if (!response)
|
|
135
148
|
throw new Error("Failed to decrypt or parse response");
|
|
136
149
|
return response;
|
|
@@ -139,25 +152,21 @@ export class WalletConnect {
|
|
|
139
152
|
async handleNotificationEvent(event) {
|
|
140
153
|
if (!verifyEvent(event))
|
|
141
154
|
throw new Error("Invalid notification event signature");
|
|
142
|
-
|
|
143
|
-
if (isWalletNotificationLocked(event))
|
|
144
|
-
notification = await unlockWalletNotification(event, this.signer);
|
|
145
|
-
else
|
|
146
|
-
notification = getWalletNotification(event);
|
|
155
|
+
const notification = await unlockWalletNotification(event, this.signer);
|
|
147
156
|
if (!notification)
|
|
148
157
|
throw new Error("Failed to decrypt or parse notification");
|
|
149
158
|
return notification;
|
|
150
159
|
}
|
|
151
|
-
/**
|
|
152
|
-
|
|
160
|
+
/** Generic call method with generic type */
|
|
161
|
+
genericCall(method, params, options = {}) {
|
|
153
162
|
if (!this.service)
|
|
154
163
|
throw new Error("WalletConnect is not connected to a service");
|
|
155
|
-
// Create the request
|
|
164
|
+
// Create the request event
|
|
156
165
|
return defer(async () => {
|
|
157
166
|
// Get the preferred encryption method for the wallet
|
|
158
167
|
const encryption = await firstValueFrom(this.encryption$);
|
|
159
168
|
// Create the request event
|
|
160
|
-
const draft = await create({ signer: this.signer }, WalletRequestBlueprint, this.service,
|
|
169
|
+
const draft = await create({ signer: this.signer }, WalletRequestBlueprint, this.service, { method, params }, encryption);
|
|
161
170
|
// Sign the request event
|
|
162
171
|
return await this.signer.signEvent(draft);
|
|
163
172
|
}).pipe(
|
|
@@ -167,12 +176,29 @@ export class WalletConnect {
|
|
|
167
176
|
// Create an observable that publishes the request event when subscribed to
|
|
168
177
|
const request$ = defer(() => from(this.publishMethod(this.relays, requestEvent))).pipe(ignoreElements());
|
|
169
178
|
// Create an observable that listens for response events
|
|
170
|
-
const responses$ = this.events$.pipe(filter((response) =>
|
|
179
|
+
const responses$ = this.events$.pipe(filter(isValidWalletResponse), filter((response) => getWalletResponseRequestId(response) === requestEvent.id), mergeMap((response) => this.handleResponseEvent(response, encryption)),
|
|
171
180
|
// Set timeout for response events
|
|
172
181
|
simpleTimeout(options.timeout || this.defaultTimeout));
|
|
173
182
|
return merge(request$, responses$);
|
|
174
183
|
}));
|
|
175
184
|
}
|
|
185
|
+
/** Request method with generic type */
|
|
186
|
+
async genericRequest(method, params, options = {}) {
|
|
187
|
+
const result = await firstValueFrom(this.genericCall(method, params, options));
|
|
188
|
+
if (result.result_type !== method)
|
|
189
|
+
throw new Error(`Unexpected response type: ${result.result_type}`);
|
|
190
|
+
if (result.error)
|
|
191
|
+
throw createWalletError(result.error.type, result.error.message);
|
|
192
|
+
return result.result;
|
|
193
|
+
}
|
|
194
|
+
/** Call a method and return an observable of results */
|
|
195
|
+
call(method, params, options = {}) {
|
|
196
|
+
return this.genericCall(method, params, options);
|
|
197
|
+
}
|
|
198
|
+
/** Typed request method, returns the result or throws and error */
|
|
199
|
+
async request(method, params, options = {}) {
|
|
200
|
+
return this.genericRequest(method, params, options);
|
|
201
|
+
}
|
|
176
202
|
/**
|
|
177
203
|
* Listen for a type of notification
|
|
178
204
|
* @returns a method to unsubscribe the listener
|
|
@@ -215,18 +241,14 @@ export class WalletConnect {
|
|
|
215
241
|
const support = await this.getSupport();
|
|
216
242
|
return support ? supportsNotificationType(support, type) : false;
|
|
217
243
|
}
|
|
244
|
+
// Methods for common types
|
|
218
245
|
/** Pay a lightning invoice */
|
|
219
246
|
async payInvoice(invoice, amount) {
|
|
220
|
-
|
|
221
|
-
if (response.result_type !== "pay_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;
|
|
247
|
+
return await this.genericRequest("pay_invoice", { invoice, amount });
|
|
226
248
|
}
|
|
227
249
|
/** Pay multiple lightning invoices */
|
|
228
250
|
async payMultipleInvoices(invoices) {
|
|
229
|
-
return await lastValueFrom(this.
|
|
251
|
+
return await lastValueFrom(this.genericCall("multi_pay_invoice", { invoices })
|
|
230
252
|
.pipe(map((response) => {
|
|
231
253
|
if (response.result_type !== "multi_pay_invoice")
|
|
232
254
|
throw new Error(`Unexpected response type: ${response.result_type}`);
|
|
@@ -238,16 +260,11 @@ export class WalletConnect {
|
|
|
238
260
|
}
|
|
239
261
|
/** Send a keysend payment */
|
|
240
262
|
async payKeysend(pubkey, amount, preimage, tlv_records) {
|
|
241
|
-
|
|
242
|
-
if (response.result_type !== "pay_keysend")
|
|
243
|
-
throw new Error(`Unexpected response type: ${response.result_type}`);
|
|
244
|
-
if (response.error)
|
|
245
|
-
throw createWalletError(response.error.type, response.error.message);
|
|
246
|
-
return response.result;
|
|
263
|
+
return await this.genericRequest("pay_keysend", { pubkey, amount, preimage, tlv_records });
|
|
247
264
|
}
|
|
248
265
|
/** Send multiple keysend payments */
|
|
249
266
|
async payMultipleKeysend(keysends) {
|
|
250
|
-
return lastValueFrom(this.
|
|
267
|
+
return lastValueFrom(this.genericCall("multi_pay_keysend", { keysends }).pipe(map((response) => {
|
|
251
268
|
if (response.result_type !== "multi_pay_keysend")
|
|
252
269
|
throw new Error(`Unexpected response type: ${response.result_type}`);
|
|
253
270
|
if (response.error)
|
|
@@ -257,48 +274,23 @@ export class WalletConnect {
|
|
|
257
274
|
}
|
|
258
275
|
/** Create a new invoice */
|
|
259
276
|
async makeInvoice(amount, options) {
|
|
260
|
-
|
|
261
|
-
if (response.result_type !== "make_invoice")
|
|
262
|
-
throw new Error(`Unexpected response type: ${response.result_type}`);
|
|
263
|
-
if (response.error)
|
|
264
|
-
throw createWalletError(response.error.type, response.error.message);
|
|
265
|
-
return response.result;
|
|
277
|
+
return await this.genericRequest("make_invoice", { amount, ...options });
|
|
266
278
|
}
|
|
267
279
|
/** Look up an invoice by payment hash or invoice string */
|
|
268
280
|
async lookupInvoice(payment_hash, invoice) {
|
|
269
|
-
|
|
270
|
-
if (response.result_type !== "lookup_invoice")
|
|
271
|
-
throw new Error(`Unexpected response type: ${response.result_type}`);
|
|
272
|
-
if (response.error)
|
|
273
|
-
throw createWalletError(response.error.type, response.error.message);
|
|
274
|
-
return response.result;
|
|
281
|
+
return await this.genericRequest("lookup_invoice", { payment_hash, invoice });
|
|
275
282
|
}
|
|
276
283
|
/** List transactions */
|
|
277
284
|
async listTransactions(params) {
|
|
278
|
-
|
|
279
|
-
if (response.result_type !== "list_transactions")
|
|
280
|
-
throw new Error(`Unexpected response type: ${response.result_type}`);
|
|
281
|
-
if (response.error)
|
|
282
|
-
throw createWalletError(response.error.type, response.error.message);
|
|
283
|
-
return response.result;
|
|
285
|
+
return await this.genericRequest("list_transactions", params || {});
|
|
284
286
|
}
|
|
285
287
|
/** Get wallet balance */
|
|
286
288
|
async getBalance() {
|
|
287
|
-
|
|
288
|
-
if (response.result_type !== "get_balance")
|
|
289
|
-
throw new Error(`Unexpected response type: ${response.result_type}`);
|
|
290
|
-
if (response.error)
|
|
291
|
-
throw createWalletError(response.error.type, response.error.message);
|
|
292
|
-
return response.result;
|
|
289
|
+
return await this.genericRequest("get_balance", {});
|
|
293
290
|
}
|
|
294
291
|
/** Get wallet info */
|
|
295
292
|
async getInfo() {
|
|
296
|
-
|
|
297
|
-
if (response.result_type !== "get_info")
|
|
298
|
-
throw new Error(`Unexpected response type: ${response.result_type}`);
|
|
299
|
-
if (response.error)
|
|
300
|
-
throw createWalletError(response.error.type, response.error.message);
|
|
301
|
-
return response.result;
|
|
293
|
+
return await this.genericRequest("get_info", {});
|
|
302
294
|
}
|
|
303
295
|
/** Serialize the WalletConnect instance */
|
|
304
296
|
toJSON() {
|
package/dist/wallet-service.d.ts
CHANGED
|
@@ -1,51 +1,31 @@
|
|
|
1
1
|
import { EventSigner } from "applesauce-factory";
|
|
2
|
-
import { NostrEvent } from "nostr-tools";
|
|
3
2
|
import { Observable, Subscription } from "rxjs";
|
|
4
3
|
import { WalletAuthURI } from "./helpers/auth-uri.js";
|
|
5
4
|
import { WalletErrorCode } from "./helpers/error.js";
|
|
5
|
+
import { CommonWalletMethods, TWalletMethod, WalletInfo } from "./helpers/methods.js";
|
|
6
6
|
import { NotificationType, WalletNotification } from "./helpers/notification.js";
|
|
7
|
-
import {
|
|
8
|
-
import { GetBalanceResult, GetInfoResult, ListTransactionsResult, LookupInvoiceResult, MakeInvoiceResult, MultiPayInvoiceResult, MultiPayKeysendResult, PayInvoiceResult, PayKeysendResult, WalletResponse } from "./helpers/response.js";
|
|
7
|
+
import { WalletRequestEvent } from "./helpers/request.js";
|
|
9
8
|
import { WalletSupport } from "./helpers/support.js";
|
|
10
9
|
import { NostrConnectionMethodsOptions, NostrPool, NostrPublishMethod, NostrSubscriptionMethod } from "./interop.js";
|
|
11
|
-
/**
|
|
12
|
-
export type
|
|
13
|
-
/**
|
|
14
|
-
export type
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
export type MakeInvoiceHandler = (params: MakeInvoiceParams) => Promise<MakeInvoiceResult>;
|
|
21
|
-
/** Handler function for lookup_invoice method */
|
|
22
|
-
export type LookupInvoiceHandler = (params: LookupInvoiceParams) => Promise<LookupInvoiceResult>;
|
|
23
|
-
/** Handler function for list_transactions method */
|
|
24
|
-
export type ListTransactionsHandler = (params: ListTransactionsParams) => Promise<ListTransactionsResult>;
|
|
25
|
-
/** Handler function for get_balance method */
|
|
26
|
-
export type GetBalanceHandler = (params: GetBalanceParams) => Promise<GetBalanceResult>;
|
|
27
|
-
/** Handler function for get_info method */
|
|
28
|
-
export type GetInfoHandler = (params: GetInfoParams) => Promise<GetInfoResult>;
|
|
29
|
-
/** Map of method handlers for the wallet service */
|
|
30
|
-
export interface WalletServiceHandlers {
|
|
31
|
-
pay_invoice?: PayInvoiceHandler;
|
|
32
|
-
multi_pay_invoice?: MultiPayInvoiceHandler;
|
|
33
|
-
pay_keysend?: PayKeysendHandler;
|
|
34
|
-
multi_pay_keysend?: MultiPayKeysendHandler;
|
|
35
|
-
make_invoice?: MakeInvoiceHandler;
|
|
36
|
-
lookup_invoice?: LookupInvoiceHandler;
|
|
37
|
-
list_transactions?: ListTransactionsHandler;
|
|
38
|
-
get_balance?: GetBalanceHandler;
|
|
39
|
-
get_info?: GetInfoHandler;
|
|
40
|
-
}
|
|
10
|
+
/** Generic type for wallet method handlers */
|
|
11
|
+
export type WalletMethodHandler<Method extends TWalletMethod> = (params: Method["request"]["params"]) => Promise<Method["response"]["result"]>;
|
|
12
|
+
/** Map of method handlers for the wallet service for a specific set of methods */
|
|
13
|
+
export type WalletServiceHandlers<Methods extends TWalletMethod = TWalletMethod> = {
|
|
14
|
+
[K in Methods["method"]]?: WalletMethodHandler<Extract<Methods, {
|
|
15
|
+
method: K;
|
|
16
|
+
}>>;
|
|
17
|
+
};
|
|
18
|
+
/** Serialized wallet service */
|
|
41
19
|
export type SerializedWalletService = {
|
|
42
20
|
/** The client's public key */
|
|
43
21
|
client: string;
|
|
44
22
|
/** The relays to use for the service */
|
|
45
23
|
relays: string[];
|
|
46
24
|
};
|
|
25
|
+
/** Only the necessary info for the getInfo method on wallet service */
|
|
26
|
+
export type WalletServiceInfo = Partial<Omit<WalletInfo, "methods" | "notifications">>;
|
|
47
27
|
/** Options for creating a WalletService */
|
|
48
|
-
export interface WalletServiceOptions extends NostrConnectionMethodsOptions {
|
|
28
|
+
export interface WalletServiceOptions<Methods extends TWalletMethod = CommonWalletMethods> extends NostrConnectionMethodsOptions {
|
|
49
29
|
/** The relays to use for the service */
|
|
50
30
|
relays: string[];
|
|
51
31
|
/** The signer to use for creating and unlocking events */
|
|
@@ -54,13 +34,15 @@ export interface WalletServiceOptions extends NostrConnectionMethodsOptions {
|
|
|
54
34
|
secret?: Uint8Array;
|
|
55
35
|
/** The client's public key (used for restoring the service) */
|
|
56
36
|
client?: string;
|
|
37
|
+
/** A method for getting the general wallet information (Can be overwritten if get_info is set in handlers) */
|
|
38
|
+
getInfo?: () => Promise<WalletServiceInfo>;
|
|
57
39
|
/** Map of method handlers */
|
|
58
|
-
handlers: WalletServiceHandlers
|
|
40
|
+
handlers: WalletServiceHandlers<Methods>;
|
|
59
41
|
/** An array of notifications this wallet supports */
|
|
60
42
|
notifications?: NotificationType[];
|
|
61
43
|
}
|
|
62
44
|
/** NIP-47 Wallet Service implementation */
|
|
63
|
-
export declare class WalletService {
|
|
45
|
+
export declare class WalletService<Methods extends TWalletMethod = CommonWalletMethods> {
|
|
64
46
|
/** A fallback method to use for subscriptionMethod if none is passed in when creating the client */
|
|
65
47
|
static subscriptionMethod: NostrSubscriptionMethod | undefined;
|
|
66
48
|
/** A fallback method to use for publishMethod if none is passed in when creating the client */
|
|
@@ -72,27 +54,29 @@ export declare class WalletService {
|
|
|
72
54
|
/** A method for publishing events */
|
|
73
55
|
protected readonly publishMethod: NostrPublishMethod;
|
|
74
56
|
protected log: import("debug").Debugger;
|
|
57
|
+
/** A special method for getting the generic wallet information */
|
|
58
|
+
getInfo?: () => Promise<WalletServiceInfo>;
|
|
75
59
|
/** The relays to use for the service */
|
|
76
60
|
readonly relays: string[];
|
|
77
61
|
/** The signer used for creating and unlocking events */
|
|
78
62
|
protected readonly signer: EventSigner;
|
|
79
63
|
/** Map of method handlers */
|
|
80
|
-
protected readonly handlers: WalletServiceHandlers
|
|
64
|
+
protected readonly handlers: WalletServiceHandlers<Methods>;
|
|
81
65
|
/** Wallet support information */
|
|
82
|
-
protected readonly support: WalletSupport
|
|
66
|
+
protected readonly support: WalletSupport<Methods>;
|
|
83
67
|
/** The service's public key */
|
|
84
68
|
pubkey: string | null;
|
|
85
69
|
/** The client's secret key */
|
|
86
|
-
protected secret
|
|
70
|
+
protected secret?: Uint8Array;
|
|
87
71
|
/** The client's public key */
|
|
88
72
|
client: string;
|
|
89
73
|
/** Shared observable for all wallet request events */
|
|
90
|
-
protected events$: Observable<
|
|
74
|
+
protected events$: Observable<WalletRequestEvent> | null;
|
|
91
75
|
/** Subscription to the events observable */
|
|
92
76
|
protected subscription: Subscription | null;
|
|
93
77
|
/** Whether the service is currently running */
|
|
94
78
|
running: boolean;
|
|
95
|
-
constructor(options: WalletServiceOptions);
|
|
79
|
+
constructor(options: WalletServiceOptions<Methods>);
|
|
96
80
|
/** Start the wallet service */
|
|
97
81
|
start(): Promise<void>;
|
|
98
82
|
/** Stop the wallet service */
|
|
@@ -106,15 +90,18 @@ export declare class WalletService {
|
|
|
106
90
|
/** Publish the wallet support event */
|
|
107
91
|
protected publishSupportEvent(): Promise<void>;
|
|
108
92
|
/** Handle a wallet request event */
|
|
109
|
-
protected handleRequestEvent(requestEvent:
|
|
93
|
+
protected handleRequestEvent(requestEvent: WalletRequestEvent): Promise<void>;
|
|
110
94
|
/** Process a decrypted wallet request */
|
|
111
|
-
protected processRequest(requestEvent:
|
|
95
|
+
protected processRequest<Method extends Methods>(requestEvent: WalletRequestEvent, request: Method["request"]): Promise<void>;
|
|
112
96
|
/** Send a success response */
|
|
113
|
-
protected sendSuccessResponse<
|
|
97
|
+
protected sendSuccessResponse<Method extends Methods>(requestEvent: WalletRequestEvent, method: Method["response"]["result_type"], result: Method["response"]["result"]): Promise<void>;
|
|
114
98
|
/** Send an error response */
|
|
115
|
-
protected sendErrorResponse(requestEvent:
|
|
99
|
+
protected sendErrorResponse<Method extends Methods>(requestEvent: WalletRequestEvent, errorType: WalletErrorCode, errorMessage: string): Promise<void>;
|
|
116
100
|
/** Send a response event */
|
|
117
|
-
protected sendResponse(requestEvent:
|
|
101
|
+
protected sendResponse<Method extends Methods>(requestEvent: WalletRequestEvent, response: Method["response"] | Method["error"]): Promise<void>;
|
|
118
102
|
/** Creates a service for a nostr+walletauth URI */
|
|
119
|
-
static fromAuthURI(uri: string | WalletAuthURI, options: Omit<WalletServiceOptions
|
|
103
|
+
static fromAuthURI<Methods extends TWalletMethod = CommonWalletMethods>(uri: string | WalletAuthURI, options: Omit<WalletServiceOptions<Methods>, "relays"> & {
|
|
104
|
+
/** A relay or method to select a single relay for the client and service to communicate over */
|
|
105
|
+
overrideRelay?: string | ((relays: string[]) => string);
|
|
106
|
+
}): WalletService<Methods>;
|
|
120
107
|
}
|
package/dist/wallet-service.js
CHANGED
|
@@ -7,8 +7,8 @@ import { WalletLegacyNotificationBlueprint, WalletNotificationBlueprint } from "
|
|
|
7
7
|
import { WalletResponseBlueprint } from "./blueprints/response.js";
|
|
8
8
|
import { WalletSupportBlueprint } from "./blueprints/support.js";
|
|
9
9
|
import { parseWalletAuthURI } from "./helpers/auth-uri.js";
|
|
10
|
-
import { WalletBaseError } from "./helpers/error.js";
|
|
11
|
-
import { getWalletRequest,
|
|
10
|
+
import { NotImplementedError, WalletBaseError } from "./helpers/error.js";
|
|
11
|
+
import { getWalletRequest, isValidWalletRequest, isWalletRequestExpired, unlockWalletRequest, WALLET_REQUEST_KIND, } from "./helpers/request.js";
|
|
12
12
|
import { getConnectionMethods, } from "./interop.js";
|
|
13
13
|
/** NIP-47 Wallet Service implementation */
|
|
14
14
|
export class WalletService {
|
|
@@ -23,6 +23,8 @@ export class WalletService {
|
|
|
23
23
|
/** A method for publishing events */
|
|
24
24
|
publishMethod;
|
|
25
25
|
log = logger.extend("WalletService");
|
|
26
|
+
/** A special method for getting the generic wallet information */
|
|
27
|
+
getInfo;
|
|
26
28
|
/** The relays to use for the service */
|
|
27
29
|
relays;
|
|
28
30
|
/** The signer used for creating and unlocking events */
|
|
@@ -49,10 +51,16 @@ export class WalletService {
|
|
|
49
51
|
this.handlers = options.handlers;
|
|
50
52
|
// Set the client's secret and public key
|
|
51
53
|
if (options.secret) {
|
|
54
|
+
// Service was created with a custom secret
|
|
52
55
|
this.secret = options.secret;
|
|
53
56
|
this.client = getPublicKey(this.secret);
|
|
54
57
|
}
|
|
58
|
+
else if (options.client) {
|
|
59
|
+
// Service was restored with only the clients pubkey
|
|
60
|
+
this.client = options.client;
|
|
61
|
+
}
|
|
55
62
|
else {
|
|
63
|
+
// Generate secret and client pubkey
|
|
56
64
|
this.secret = generateSecretKey();
|
|
57
65
|
this.client = getPublicKey(this.secret);
|
|
58
66
|
}
|
|
@@ -98,7 +106,9 @@ export class WalletService {
|
|
|
98
106
|
// Ignore strings (support for applesauce-relay)
|
|
99
107
|
filter((event) => typeof event !== "string"),
|
|
100
108
|
// Only include valid wallet request events
|
|
101
|
-
filter(
|
|
109
|
+
filter(isValidWalletRequest),
|
|
110
|
+
// Ensure they are to our pubkey
|
|
111
|
+
filter((event) => event.pubkey === this.client),
|
|
102
112
|
// Verify event signature
|
|
103
113
|
filter((event) => verifyEvent(event)),
|
|
104
114
|
// Only create a single subscription to the relays
|
|
@@ -129,6 +139,8 @@ export class WalletService {
|
|
|
129
139
|
}
|
|
130
140
|
/** Get the connection URI for the service */
|
|
131
141
|
getConnectURI() {
|
|
142
|
+
if (!this.secret)
|
|
143
|
+
throw new Error("Service was not created with a secret");
|
|
132
144
|
if (!this.pubkey)
|
|
133
145
|
throw new Error("Service is not running");
|
|
134
146
|
if (!this.relays.length)
|
|
@@ -152,7 +164,9 @@ export class WalletService {
|
|
|
152
164
|
/** Publish the wallet support event */
|
|
153
165
|
async publishSupportEvent() {
|
|
154
166
|
try {
|
|
155
|
-
|
|
167
|
+
// Tell the client which relay to use if there is only one (for nostr+walletauth URI connections)
|
|
168
|
+
const overrideRelay = this.relays.length === 1 ? this.relays[0] : undefined;
|
|
169
|
+
const draft = await create({ signer: this.signer }, (WalletSupportBlueprint), this.support, this.client, overrideRelay);
|
|
156
170
|
const event = await this.signer.signEvent(draft);
|
|
157
171
|
await this.publishMethod(this.relays, event);
|
|
158
172
|
}
|
|
@@ -168,13 +182,7 @@ export class WalletService {
|
|
|
168
182
|
if (isWalletRequestExpired(requestEvent))
|
|
169
183
|
return await this.sendErrorResponse(requestEvent, "OTHER", "Request has expired");
|
|
170
184
|
// Unlock the request if needed
|
|
171
|
-
|
|
172
|
-
if (isWalletRequestLocked(requestEvent)) {
|
|
173
|
-
request = await unlockWalletRequest(requestEvent, this.signer);
|
|
174
|
-
}
|
|
175
|
-
else {
|
|
176
|
-
request = getWalletRequest(requestEvent);
|
|
177
|
-
}
|
|
185
|
+
const request = await unlockWalletRequest(requestEvent, this.signer);
|
|
178
186
|
if (!request)
|
|
179
187
|
return await this.sendErrorResponse(requestEvent, "OTHER", "Failed to decrypt or parse request");
|
|
180
188
|
// Handle the request based on its method
|
|
@@ -188,44 +196,27 @@ export class WalletService {
|
|
|
188
196
|
/** Process a decrypted wallet request */
|
|
189
197
|
async processRequest(requestEvent, request) {
|
|
190
198
|
const handler = this.handlers[request.method];
|
|
191
|
-
if (!handler) {
|
|
192
|
-
await this.sendErrorResponse(requestEvent, "NOT_IMPLEMENTED", `Method ${request.method} not supported`);
|
|
193
|
-
return;
|
|
194
|
-
}
|
|
195
199
|
try {
|
|
196
|
-
let result;
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
break;
|
|
208
|
-
case "multi_pay_keysend":
|
|
209
|
-
result = await handler(request.params);
|
|
210
|
-
break;
|
|
211
|
-
case "make_invoice":
|
|
212
|
-
result = await handler(request.params);
|
|
213
|
-
break;
|
|
214
|
-
case "lookup_invoice":
|
|
215
|
-
result = await handler(request.params);
|
|
216
|
-
break;
|
|
217
|
-
case "list_transactions":
|
|
218
|
-
result = await handler(request.params);
|
|
219
|
-
break;
|
|
220
|
-
case "get_balance":
|
|
221
|
-
result = await handler(request.params);
|
|
222
|
-
break;
|
|
223
|
-
case "get_info":
|
|
224
|
-
result = await handler(request.params);
|
|
225
|
-
break;
|
|
200
|
+
let result = undefined;
|
|
201
|
+
// If the user has not implemented the method
|
|
202
|
+
if (!handler) {
|
|
203
|
+
// If its the get_info try to use the builtin getInfo method
|
|
204
|
+
if (request.method === "get_info") {
|
|
205
|
+
result = { ...(this.getInfo?.() ?? {}), ...this.support };
|
|
206
|
+
}
|
|
207
|
+
else {
|
|
208
|
+
// Else throw not supported error
|
|
209
|
+
throw new NotImplementedError(`Method ${request.method} not supported`);
|
|
210
|
+
}
|
|
226
211
|
}
|
|
212
|
+
// Otherwise use the user provided handler
|
|
213
|
+
if (!result && handler)
|
|
214
|
+
result = await handler(request.params);
|
|
215
|
+
// Throw if failed to get result
|
|
216
|
+
if (!result)
|
|
217
|
+
throw new NotImplementedError(`Method ${request.method} not supported`);
|
|
227
218
|
// Send success response
|
|
228
|
-
await this.sendSuccessResponse(requestEvent, method, result);
|
|
219
|
+
await this.sendSuccessResponse(requestEvent, request.method, result);
|
|
229
220
|
}
|
|
230
221
|
catch (error) {
|
|
231
222
|
this.log(`Error executing ${request.method}:`, error);
|
|
@@ -269,7 +260,7 @@ export class WalletService {
|
|
|
269
260
|
/** Send a response event */
|
|
270
261
|
async sendResponse(requestEvent, response) {
|
|
271
262
|
try {
|
|
272
|
-
const draft = await create({ signer: this.signer }, WalletResponseBlueprint
|
|
263
|
+
const draft = await create({ signer: this.signer }, WalletResponseBlueprint(requestEvent, response));
|
|
273
264
|
const event = await this.signer.signEvent(draft);
|
|
274
265
|
await this.publishMethod(this.relays, event);
|
|
275
266
|
}
|
|
@@ -280,10 +271,13 @@ export class WalletService {
|
|
|
280
271
|
}
|
|
281
272
|
/** Creates a service for a nostr+walletauth URI */
|
|
282
273
|
static fromAuthURI(uri, options) {
|
|
283
|
-
const
|
|
274
|
+
const authURI = typeof uri === "string" ? parseWalletAuthURI(uri) : uri;
|
|
275
|
+
const relays = options.overrideRelay
|
|
276
|
+
? [typeof options.overrideRelay === "function" ? options.overrideRelay(authURI.relays) : options.overrideRelay]
|
|
277
|
+
: authURI.relays;
|
|
284
278
|
return new WalletService({
|
|
285
279
|
...options,
|
|
286
|
-
client,
|
|
280
|
+
client: authURI.client,
|
|
287
281
|
relays,
|
|
288
282
|
});
|
|
289
283
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "applesauce-wallet-connect",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.1.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -58,23 +58,25 @@
|
|
|
58
58
|
}
|
|
59
59
|
},
|
|
60
60
|
"dependencies": {
|
|
61
|
-
"applesauce-core": "^3.1.0",
|
|
62
|
-
"applesauce-factory": "^3.1.0",
|
|
63
|
-
"nostr-tools": "~2.15",
|
|
64
61
|
"@noble/hashes": "^1.7.1",
|
|
62
|
+
"applesauce-core": "^4.1.0",
|
|
63
|
+
"applesauce-factory": "^4.0.0",
|
|
64
|
+
"nostr-tools": "~2.17",
|
|
65
65
|
"rxjs": "^7.8.1"
|
|
66
66
|
},
|
|
67
67
|
"devDependencies": {
|
|
68
68
|
"@hirez_io/observer-spy": "^2.2.0",
|
|
69
69
|
"@types/debug": "^4.1.12",
|
|
70
|
+
"rimraf": "^6.0.1",
|
|
70
71
|
"typescript": "^5.8.3",
|
|
71
|
-
"vitest": "^3.2.
|
|
72
|
+
"vitest": "^3.2.4"
|
|
72
73
|
},
|
|
73
74
|
"funding": {
|
|
74
75
|
"type": "lightning",
|
|
75
76
|
"url": "lightning:nostrudel@geyser.fund"
|
|
76
77
|
},
|
|
77
78
|
"scripts": {
|
|
79
|
+
"prebuild": "rimraf dist",
|
|
78
80
|
"build": "tsc",
|
|
79
81
|
"watch:build": "tsc --watch > /dev/null",
|
|
80
82
|
"test": "vitest run --passWithNoTests",
|