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,270 @@
|
|
|
1
|
+
import { logger } from "applesauce-core";
|
|
2
|
+
import { create } from "applesauce-factory";
|
|
3
|
+
import { generateSecretKey, getPublicKey, verifyEvent } from "nostr-tools";
|
|
4
|
+
import { filter, mergeMap, share } from "rxjs";
|
|
5
|
+
import { WalletLegacyNotificationBlueprint, WalletNotificationBlueprint } from "./blueprints/notification.js";
|
|
6
|
+
import { WalletResponseBlueprint } from "./blueprints/response.js";
|
|
7
|
+
import { WalletSupportBlueprint } from "./blueprints/support.js";
|
|
8
|
+
import { WalletBaseError } from "./helpers/error.js";
|
|
9
|
+
import { getWalletRequest, isWalletRequestExpired, isWalletRequestLocked, unlockWalletRequest, WALLET_REQUEST_KIND, } from "./helpers/request.js";
|
|
10
|
+
import { bytesToHex } from "@noble/hashes/utils";
|
|
11
|
+
/** NIP-47 Wallet Service implementation */
|
|
12
|
+
export class WalletService {
|
|
13
|
+
/** A fallback method to use for subscriptionMethod if none is passed in when creating the client */
|
|
14
|
+
static subscriptionMethod = undefined;
|
|
15
|
+
/** A fallback method to use for publishMethod if none is passed in when creating the client */
|
|
16
|
+
static publishMethod = undefined;
|
|
17
|
+
/** A method for subscribing to relays */
|
|
18
|
+
subscriptionMethod;
|
|
19
|
+
/** A method for publishing events */
|
|
20
|
+
publishMethod;
|
|
21
|
+
log = logger.extend("WalletService");
|
|
22
|
+
/** The relays to use for the service */
|
|
23
|
+
relays;
|
|
24
|
+
/** The signer used for creating and unlocking events */
|
|
25
|
+
signer;
|
|
26
|
+
/** Map of method handlers */
|
|
27
|
+
handlers;
|
|
28
|
+
/** Wallet support information */
|
|
29
|
+
support;
|
|
30
|
+
/** The service's public key */
|
|
31
|
+
pubkey = null;
|
|
32
|
+
/** The client's secret key */
|
|
33
|
+
secret;
|
|
34
|
+
/** Shared observable for all wallet request events */
|
|
35
|
+
events$ = null;
|
|
36
|
+
/** Subscription to the events observable */
|
|
37
|
+
subscription = null;
|
|
38
|
+
/** Whether the service is currently running */
|
|
39
|
+
running = false;
|
|
40
|
+
/** Get the clients public key */
|
|
41
|
+
get client() {
|
|
42
|
+
return getPublicKey(this.secret);
|
|
43
|
+
}
|
|
44
|
+
constructor(options) {
|
|
45
|
+
this.relays = options.relays;
|
|
46
|
+
this.signer = options.signer;
|
|
47
|
+
this.handlers = options.handlers;
|
|
48
|
+
this.secret = options.secret ?? generateSecretKey();
|
|
49
|
+
const subscriptionMethod = options.subscriptionMethod || WalletService.subscriptionMethod;
|
|
50
|
+
if (!subscriptionMethod)
|
|
51
|
+
throw new Error("Missing subscriptionMethod, either pass a method or set WalletService.subscriptionMethod");
|
|
52
|
+
const publishMethod = options.publishMethod || WalletService.publishMethod;
|
|
53
|
+
if (!publishMethod)
|
|
54
|
+
throw new Error("Missing publishMethod, either pass a method or set WalletService.publishMethod");
|
|
55
|
+
this.subscriptionMethod = subscriptionMethod;
|
|
56
|
+
this.publishMethod = publishMethod;
|
|
57
|
+
const encryption = [];
|
|
58
|
+
if (options.signer.nip04)
|
|
59
|
+
encryption.push("nip04");
|
|
60
|
+
if (options.signer.nip44)
|
|
61
|
+
encryption.push("nip44_v2");
|
|
62
|
+
// Ensure there is at least one encryption method
|
|
63
|
+
if (!encryption.length)
|
|
64
|
+
throw new Error("No encryption methods supported by signer");
|
|
65
|
+
// Build the support infomation based on options
|
|
66
|
+
this.support = {
|
|
67
|
+
methods: Object.keys(options.handlers),
|
|
68
|
+
notifications: options.notifications,
|
|
69
|
+
encryption,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
/** Start the wallet service */
|
|
73
|
+
async start() {
|
|
74
|
+
if (this.running)
|
|
75
|
+
return;
|
|
76
|
+
this.running = true;
|
|
77
|
+
// Get our public key
|
|
78
|
+
this.pubkey = await this.signer.getPublicKey();
|
|
79
|
+
// Create shared request observable with ref counting and timer
|
|
80
|
+
this.events$ = this.subscriptionMethod(this.relays, [
|
|
81
|
+
{
|
|
82
|
+
kinds: [WALLET_REQUEST_KIND],
|
|
83
|
+
"#p": [this.pubkey], // Only requests directed to us
|
|
84
|
+
authors: [this.client], // Only requests from the client
|
|
85
|
+
},
|
|
86
|
+
]).pipe(
|
|
87
|
+
// Ignore strings (support for applesauce-relay)
|
|
88
|
+
filter((event) => typeof event !== "string"),
|
|
89
|
+
// Only include valid wallet request events
|
|
90
|
+
filter((event) => event.kind === WALLET_REQUEST_KIND && event.pubkey === this.client),
|
|
91
|
+
// Verify event signature
|
|
92
|
+
filter((event) => verifyEvent(event)),
|
|
93
|
+
// Only create a single subscription to the relays
|
|
94
|
+
share());
|
|
95
|
+
// Subscribe to request events and handle them
|
|
96
|
+
this.subscription = this.events$.pipe(mergeMap((requestEvent) => this.handleRequestEvent(requestEvent))).subscribe({
|
|
97
|
+
error: (error) => {
|
|
98
|
+
this.log("Error handling wallet request:", error);
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
// Publish wallet support event
|
|
102
|
+
await this.publishSupportEvent();
|
|
103
|
+
}
|
|
104
|
+
/** Stop the wallet service */
|
|
105
|
+
stop() {
|
|
106
|
+
if (!this.running)
|
|
107
|
+
return;
|
|
108
|
+
this.running = false;
|
|
109
|
+
if (this.subscription) {
|
|
110
|
+
this.subscription.unsubscribe();
|
|
111
|
+
this.subscription = null;
|
|
112
|
+
}
|
|
113
|
+
this.events$ = null;
|
|
114
|
+
}
|
|
115
|
+
/** Check if the service is running */
|
|
116
|
+
isRunning() {
|
|
117
|
+
return this.running;
|
|
118
|
+
}
|
|
119
|
+
/** Get the connection string for the service */
|
|
120
|
+
getConnectionString() {
|
|
121
|
+
if (!this.pubkey)
|
|
122
|
+
throw new Error("Service is not running");
|
|
123
|
+
if (!this.relays.length)
|
|
124
|
+
throw new Error("No relays configured");
|
|
125
|
+
const url = new URL(`nostr+walletconnect://${this.pubkey}`);
|
|
126
|
+
for (const relay of this.relays) {
|
|
127
|
+
url.searchParams.append("relay", relay);
|
|
128
|
+
}
|
|
129
|
+
url.searchParams.set("secret", bytesToHex(this.secret));
|
|
130
|
+
return url.toString();
|
|
131
|
+
}
|
|
132
|
+
/** Send a notification to the client */
|
|
133
|
+
async notify(type, notification, legacy = false) {
|
|
134
|
+
const draft = await create({ signer: this.signer }, legacy ? WalletLegacyNotificationBlueprint : WalletNotificationBlueprint, this.client, {
|
|
135
|
+
notification_type: type,
|
|
136
|
+
notification,
|
|
137
|
+
});
|
|
138
|
+
const event = await this.signer.signEvent(draft);
|
|
139
|
+
await this.publishMethod(this.relays, event);
|
|
140
|
+
}
|
|
141
|
+
/** Publish the wallet support event */
|
|
142
|
+
async publishSupportEvent() {
|
|
143
|
+
try {
|
|
144
|
+
const draft = await create({ signer: this.signer }, WalletSupportBlueprint, this.support);
|
|
145
|
+
const event = await this.signer.signEvent(draft);
|
|
146
|
+
await this.publishMethod(this.relays, event);
|
|
147
|
+
}
|
|
148
|
+
catch (error) {
|
|
149
|
+
this.log("Failed to publish wallet support event:", error);
|
|
150
|
+
throw error;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
/** Handle a wallet request event */
|
|
154
|
+
async handleRequestEvent(requestEvent) {
|
|
155
|
+
try {
|
|
156
|
+
// Check if the request has expired
|
|
157
|
+
if (isWalletRequestExpired(requestEvent))
|
|
158
|
+
return await this.sendErrorResponse(requestEvent, "OTHER", "Request has expired");
|
|
159
|
+
// Unlock the request if needed
|
|
160
|
+
let request;
|
|
161
|
+
if (isWalletRequestLocked(requestEvent)) {
|
|
162
|
+
request = await unlockWalletRequest(requestEvent, this.signer);
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
request = getWalletRequest(requestEvent);
|
|
166
|
+
}
|
|
167
|
+
if (!request)
|
|
168
|
+
return await this.sendErrorResponse(requestEvent, "OTHER", "Failed to decrypt or parse request");
|
|
169
|
+
// Handle the request based on its method
|
|
170
|
+
await this.processRequest(requestEvent, request);
|
|
171
|
+
}
|
|
172
|
+
catch (error) {
|
|
173
|
+
this.log("Error processing wallet request:", error);
|
|
174
|
+
await this.sendErrorResponse(requestEvent, "INTERNAL", "Internal server error");
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
/** Process a decrypted wallet request */
|
|
178
|
+
async processRequest(requestEvent, request) {
|
|
179
|
+
const handler = this.handlers[request.method];
|
|
180
|
+
if (!handler) {
|
|
181
|
+
await this.sendErrorResponse(requestEvent, "NOT_IMPLEMENTED", `Method ${request.method} not supported`);
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
try {
|
|
185
|
+
let result;
|
|
186
|
+
const method = request.method; // Store method for use in catch block
|
|
187
|
+
switch (method) {
|
|
188
|
+
case "pay_invoice":
|
|
189
|
+
result = await handler(request.params);
|
|
190
|
+
break;
|
|
191
|
+
case "multi_pay_invoice":
|
|
192
|
+
result = await handler(request.params);
|
|
193
|
+
break;
|
|
194
|
+
case "pay_keysend":
|
|
195
|
+
result = await handler(request.params);
|
|
196
|
+
break;
|
|
197
|
+
case "multi_pay_keysend":
|
|
198
|
+
result = await handler(request.params);
|
|
199
|
+
break;
|
|
200
|
+
case "make_invoice":
|
|
201
|
+
result = await handler(request.params);
|
|
202
|
+
break;
|
|
203
|
+
case "lookup_invoice":
|
|
204
|
+
result = await handler(request.params);
|
|
205
|
+
break;
|
|
206
|
+
case "list_transactions":
|
|
207
|
+
result = await handler(request.params);
|
|
208
|
+
break;
|
|
209
|
+
case "get_balance":
|
|
210
|
+
result = await handler(request.params);
|
|
211
|
+
break;
|
|
212
|
+
case "get_info":
|
|
213
|
+
result = await handler(request.params);
|
|
214
|
+
break;
|
|
215
|
+
}
|
|
216
|
+
// Send success response
|
|
217
|
+
await this.sendSuccessResponse(requestEvent, method, result);
|
|
218
|
+
}
|
|
219
|
+
catch (error) {
|
|
220
|
+
this.log(`Error executing ${request.method}:`, error);
|
|
221
|
+
// Determine error type and message
|
|
222
|
+
let errorCode = "OTHER";
|
|
223
|
+
let errorMessage = "Unknown error";
|
|
224
|
+
if (error instanceof WalletBaseError) {
|
|
225
|
+
errorCode = error.code;
|
|
226
|
+
errorMessage = error.message;
|
|
227
|
+
}
|
|
228
|
+
else if (error instanceof Error) {
|
|
229
|
+
errorMessage = error.message;
|
|
230
|
+
}
|
|
231
|
+
await this.sendErrorResponse(requestEvent, errorCode, errorMessage);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
/** Send a success response */
|
|
235
|
+
async sendSuccessResponse(requestEvent, method, result) {
|
|
236
|
+
const response = {
|
|
237
|
+
result_type: method,
|
|
238
|
+
error: null,
|
|
239
|
+
result,
|
|
240
|
+
};
|
|
241
|
+
await this.sendResponse(requestEvent, response);
|
|
242
|
+
}
|
|
243
|
+
/** Send an error response */
|
|
244
|
+
async sendErrorResponse(requestEvent, errorType, errorMessage) {
|
|
245
|
+
const request = getWalletRequest(requestEvent);
|
|
246
|
+
if (!request)
|
|
247
|
+
throw new Error("Cant respond to a locked request");
|
|
248
|
+
const response = {
|
|
249
|
+
result_type: request.method,
|
|
250
|
+
error: {
|
|
251
|
+
type: errorType,
|
|
252
|
+
message: errorMessage,
|
|
253
|
+
},
|
|
254
|
+
result: null,
|
|
255
|
+
};
|
|
256
|
+
await this.sendResponse(requestEvent, response);
|
|
257
|
+
}
|
|
258
|
+
/** Send a response event */
|
|
259
|
+
async sendResponse(requestEvent, response) {
|
|
260
|
+
try {
|
|
261
|
+
const draft = await create({ signer: this.signer }, WalletResponseBlueprint, requestEvent, response);
|
|
262
|
+
const event = await this.signer.signEvent(draft);
|
|
263
|
+
await this.publishMethod(this.relays, event);
|
|
264
|
+
}
|
|
265
|
+
catch (error) {
|
|
266
|
+
this.log("Failed to send response:", error);
|
|
267
|
+
throw error;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "applesauce-wallet-connect",
|
|
3
|
+
"version": "0.0.0-next-20250808173123",
|
|
4
|
+
"description": "",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"keywords": [
|
|
9
|
+
"nostr",
|
|
10
|
+
"applesauce",
|
|
11
|
+
"nwc"
|
|
12
|
+
],
|
|
13
|
+
"author": "hzrd149",
|
|
14
|
+
"license": "MIT",
|
|
15
|
+
"files": [
|
|
16
|
+
"dist"
|
|
17
|
+
],
|
|
18
|
+
"exports": {
|
|
19
|
+
".": {
|
|
20
|
+
"import": "./dist/index.js",
|
|
21
|
+
"require": "./dist/index.js",
|
|
22
|
+
"types": "./dist/index.d.ts"
|
|
23
|
+
},
|
|
24
|
+
"./wallet-connect": {
|
|
25
|
+
"import": "./dist/wallet-connect.js",
|
|
26
|
+
"require": "./dist/wallet-connect.js",
|
|
27
|
+
"types": "./dist/wallet-connect.d.ts"
|
|
28
|
+
},
|
|
29
|
+
"./wallet-service": {
|
|
30
|
+
"import": "./dist/wallet-service.js",
|
|
31
|
+
"require": "./dist/wallet-service.js",
|
|
32
|
+
"types": "./dist/wallet-service.d.ts"
|
|
33
|
+
},
|
|
34
|
+
"./types": {
|
|
35
|
+
"import": "./dist/types.js",
|
|
36
|
+
"require": "./dist/types.js",
|
|
37
|
+
"types": "./dist/types.d.ts"
|
|
38
|
+
},
|
|
39
|
+
"./helpers": {
|
|
40
|
+
"import": "./dist/helpers/index.js",
|
|
41
|
+
"require": "./dist/helpers/index.js",
|
|
42
|
+
"types": "./dist/helpers/index.d.ts"
|
|
43
|
+
},
|
|
44
|
+
"./helpers/*": {
|
|
45
|
+
"import": "./dist/helpers/*.js",
|
|
46
|
+
"require": "./dist/helpers/*.js",
|
|
47
|
+
"types": "./dist/helpers/*.d.ts"
|
|
48
|
+
},
|
|
49
|
+
"./blueprints": {
|
|
50
|
+
"import": "./dist/blueprints/index.js",
|
|
51
|
+
"require": "./dist/blueprints/index.js",
|
|
52
|
+
"types": "./dist/blueprints/index.d.ts"
|
|
53
|
+
},
|
|
54
|
+
"./blueprints/*": {
|
|
55
|
+
"import": "./dist/blueprints/*.js",
|
|
56
|
+
"require": "./dist/blueprints/*.js",
|
|
57
|
+
"types": "./dist/blueprints/*.d.ts"
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
"dependencies": {
|
|
61
|
+
"applesauce-core": "0.0.0-next-20250808173123",
|
|
62
|
+
"applesauce-factory": "0.0.0-next-20250808173123",
|
|
63
|
+
"nostr-tools": "^2.13",
|
|
64
|
+
"@noble/hashes": "^1.7.1",
|
|
65
|
+
"rxjs": "^7.8.1"
|
|
66
|
+
},
|
|
67
|
+
"devDependencies": {
|
|
68
|
+
"@hirez_io/observer-spy": "^2.2.0",
|
|
69
|
+
"@types/debug": "^4.1.12",
|
|
70
|
+
"typescript": "^5.8.3",
|
|
71
|
+
"vitest": "^3.2.3"
|
|
72
|
+
},
|
|
73
|
+
"funding": {
|
|
74
|
+
"type": "lightning",
|
|
75
|
+
"url": "lightning:nostrudel@geyser.fund"
|
|
76
|
+
},
|
|
77
|
+
"scripts": {
|
|
78
|
+
"build": "tsc",
|
|
79
|
+
"watch:build": "tsc --watch > /dev/null",
|
|
80
|
+
"test": "vitest run --passWithNoTests",
|
|
81
|
+
"watch:test": "vitest"
|
|
82
|
+
}
|
|
83
|
+
}
|