@solana-mobile/mobile-wallet-adapter-protocol 2.2.8 → 2.3.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/android/build.gradle +2 -2
- package/android/gradle/wrapper/gradle-wrapper.jar +0 -0
- package/android/gradle/wrapper/gradle-wrapper.properties +3 -1
- package/android/gradlew +1 -1
- package/android/gradlew.bat +10 -21
- package/lib/cjs/chunks/base58Utils.js +63 -0
- package/lib/cjs/chunks/base58Utils.js.map +1 -0
- package/lib/cjs/chunks/encoding.js +145 -0
- package/lib/cjs/chunks/encoding.js.map +1 -0
- package/lib/cjs/encoding.browser.js +12 -0
- package/lib/cjs/encoding.js +12 -0
- package/lib/cjs/encoding.native.js +49 -0
- package/lib/cjs/encoding.native.js.map +1 -0
- package/lib/cjs/index.browser.js +373 -74
- package/lib/cjs/index.browser.js.map +1 -1
- package/lib/cjs/index.js +373 -74
- package/lib/cjs/index.js.map +1 -1
- package/lib/cjs/index.native.js +71 -23
- package/lib/cjs/index.native.js.map +1 -1
- package/lib/esm/chunks/encoding.js +62 -0
- package/lib/esm/chunks/encoding.js.map +1 -0
- package/lib/esm/encoding.browser.js +2 -0
- package/lib/esm/encoding.js +2 -0
- package/lib/esm/index.browser.js +366 -73
- package/lib/esm/index.browser.js.map +1 -1
- package/lib/esm/index.js +366 -73
- package/lib/esm/index.js.map +1 -1
- package/lib/types/encoding.d.ts +16 -0
- package/lib/types/encoding.d.ts.map +1 -0
- package/lib/types/index.d.ts +28 -6
- package/lib/types/index.d.ts.map +1 -1
- package/package.json +46 -21
package/lib/cjs/index.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
|
|
2
|
+
const require_encoding = require("./chunks/encoding.js");
|
|
3
|
+
let _noble_curves_secp256k1_js = require("@noble/curves/secp256k1.js");
|
|
4
|
+
let _noble_hashes_sha2_js = require("@noble/hashes/sha2.js");
|
|
5
|
+
let _noble_hashes_utils_js = require("@noble/hashes/utils.js");
|
|
3
6
|
let _solana_wallet_standard_util = require("@solana/wallet-standard-util");
|
|
4
7
|
//#region src/errors.ts
|
|
5
8
|
const SolanaMobileWalletAdapterErrorCode = {
|
|
@@ -13,7 +16,8 @@ const SolanaMobileWalletAdapterErrorCode = {
|
|
|
13
16
|
ERROR_INVALID_PROTOCOL_VERSION: "ERROR_INVALID_PROTOCOL_VERSION",
|
|
14
17
|
ERROR_BROWSER_NOT_SUPPORTED: "ERROR_BROWSER_NOT_SUPPORTED",
|
|
15
18
|
ERROR_LOOPBACK_ACCESS_BLOCKED: "ERROR_LOOPBACK_ACCESS_BLOCKED",
|
|
16
|
-
ERROR_ASSOCIATION_CANCELLED: "ERROR_ASSOCIATION_CANCELLED"
|
|
19
|
+
ERROR_ASSOCIATION_CANCELLED: "ERROR_ASSOCIATION_CANCELLED",
|
|
20
|
+
ERROR_ILLEGAL_TRANSPORT_STATE: "ERROR_ILLEGAL_TRANSPORT_STATE"
|
|
17
21
|
};
|
|
18
22
|
var SolanaMobileWalletAdapterError = class extends Error {
|
|
19
23
|
data;
|
|
@@ -48,17 +52,55 @@ var SolanaMobileWalletAdapterProtocolError = class extends Error {
|
|
|
48
52
|
}
|
|
49
53
|
};
|
|
50
54
|
//#endregion
|
|
51
|
-
//#region src/
|
|
52
|
-
|
|
53
|
-
|
|
55
|
+
//#region src/nostr.ts
|
|
56
|
+
const NOSTR_EVENT_KIND_MWA = 20012;
|
|
57
|
+
function generateNostrKeypair() {
|
|
58
|
+
const privateKey = _noble_curves_secp256k1_js.schnorr.utils.randomSecretKey();
|
|
59
|
+
return {
|
|
60
|
+
privateKey,
|
|
61
|
+
publicKey: (0, _noble_hashes_utils_js.bytesToHex)(_noble_curves_secp256k1_js.schnorr.getPublicKey(privateKey))
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
async function deriveSessionIdentifier(associationPublicKey) {
|
|
65
|
+
const rawKey = await crypto.subtle.exportKey("raw", associationPublicKey);
|
|
66
|
+
const hash = (0, _noble_hashes_sha2_js.sha256)(new Uint8Array(rawKey));
|
|
67
|
+
return (0, _noble_hashes_utils_js.bytesToHex)(hash);
|
|
68
|
+
}
|
|
69
|
+
function serializeEvent(pubkey, createdAt, kind, tags, content) {
|
|
70
|
+
return JSON.stringify([
|
|
71
|
+
0,
|
|
72
|
+
pubkey,
|
|
73
|
+
createdAt,
|
|
74
|
+
kind,
|
|
75
|
+
tags,
|
|
76
|
+
content
|
|
77
|
+
]);
|
|
54
78
|
}
|
|
55
|
-
function
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
else return base64;
|
|
79
|
+
function computeEventId(pubkey, createdAt, kind, tags, content) {
|
|
80
|
+
const serialized = serializeEvent(pubkey, createdAt, kind, tags, content);
|
|
81
|
+
return (0, _noble_hashes_utils_js.bytesToHex)((0, _noble_hashes_sha2_js.sha256)(new TextEncoder().encode(serialized)));
|
|
59
82
|
}
|
|
60
|
-
function
|
|
61
|
-
|
|
83
|
+
function createNostrEvent(kind, content, tags, privateKey) {
|
|
84
|
+
const pubkey = (0, _noble_hashes_utils_js.bytesToHex)(_noble_curves_secp256k1_js.schnorr.getPublicKey(privateKey));
|
|
85
|
+
const createdAt = Math.floor(Date.now() / 1e3);
|
|
86
|
+
const id = computeEventId(pubkey, createdAt, kind, tags, content);
|
|
87
|
+
return {
|
|
88
|
+
id,
|
|
89
|
+
pubkey,
|
|
90
|
+
created_at: createdAt,
|
|
91
|
+
kind,
|
|
92
|
+
tags,
|
|
93
|
+
content,
|
|
94
|
+
sig: (0, _noble_hashes_utils_js.bytesToHex)(_noble_curves_secp256k1_js.schnorr.sign((0, _noble_hashes_utils_js.hexToBytes)(id), privateKey))
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
function verifyNostrEvent(event) {
|
|
98
|
+
if (computeEventId(event.pubkey, event.created_at, event.kind, event.tags, event.content) !== event.id) return false;
|
|
99
|
+
try {
|
|
100
|
+
return _noble_curves_secp256k1_js.schnorr.verify((0, _noble_hashes_utils_js.hexToBytes)(event.sig), (0, _noble_hashes_utils_js.hexToBytes)(event.id), (0, _noble_hashes_utils_js.hexToBytes)(event.pubkey));
|
|
101
|
+
} catch {
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
62
104
|
}
|
|
63
105
|
//#endregion
|
|
64
106
|
//#region src/createHelloReq.ts
|
|
@@ -74,20 +116,12 @@ async function createHelloReq(ecdhPublicKey, associationKeypairPrivateKey) {
|
|
|
74
116
|
return response;
|
|
75
117
|
}
|
|
76
118
|
//#endregion
|
|
77
|
-
//#region src/base58Utils.ts
|
|
78
|
-
function fromUint8Array(byteArray) {
|
|
79
|
-
return (0, _solana_codecs_strings.getBase58Decoder)().decode(byteArray);
|
|
80
|
-
}
|
|
81
|
-
function base64ToBase58(base64EncodedString) {
|
|
82
|
-
return fromUint8Array(toUint8Array(base64EncodedString));
|
|
83
|
-
}
|
|
84
|
-
//#endregion
|
|
85
119
|
//#region src/createSIWSMessage.ts
|
|
86
120
|
function createSIWSMessage(payload) {
|
|
87
121
|
return (0, _solana_wallet_standard_util.createSignInMessageText)(payload);
|
|
88
122
|
}
|
|
89
123
|
function createSIWSMessageBase64Url(payload) {
|
|
90
|
-
return encode(createSIWSMessage(payload)).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
124
|
+
return require_encoding.encode(createSIWSMessage(payload)).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
91
125
|
}
|
|
92
126
|
//#endregion
|
|
93
127
|
//#region src/types.ts
|
|
@@ -160,9 +194,7 @@ function handleMobileWalletRequest(methodName, methodParams, protocolVersion) {
|
|
|
160
194
|
case "devnet":
|
|
161
195
|
chain = `solana:${chain}`;
|
|
162
196
|
break;
|
|
163
|
-
case "mainnet-beta":
|
|
164
|
-
chain = "solana:mainnet";
|
|
165
|
-
break;
|
|
197
|
+
case "mainnet-beta": chain = "solana:mainnet";
|
|
166
198
|
}
|
|
167
199
|
authorizeParams.chain = chain;
|
|
168
200
|
params = authorizeParams;
|
|
@@ -178,9 +210,7 @@ function handleMobileWalletRequest(methodName, methodParams, protocolVersion) {
|
|
|
178
210
|
identity
|
|
179
211
|
};
|
|
180
212
|
break;
|
|
181
|
-
default:
|
|
182
|
-
method = "authorize";
|
|
183
|
-
break;
|
|
213
|
+
default: method = "authorize";
|
|
184
214
|
}
|
|
185
215
|
break;
|
|
186
216
|
}
|
|
@@ -227,14 +257,15 @@ async function signInFallback(signInPayload, authorizationResult, protocolReques
|
|
|
227
257
|
const siwsMessage = createSIWSMessageBase64Url({
|
|
228
258
|
...signInPayload,
|
|
229
259
|
domain,
|
|
230
|
-
address: base64ToBase58(address)
|
|
260
|
+
address: require_encoding.base64ToBase58$1(address)
|
|
231
261
|
});
|
|
232
|
-
const
|
|
262
|
+
const signMessageResult = await protocolRequestHandler("sign_messages", {
|
|
233
263
|
addresses: [address],
|
|
234
264
|
payloads: [siwsMessage]
|
|
235
|
-
})
|
|
236
|
-
const
|
|
237
|
-
const
|
|
265
|
+
});
|
|
266
|
+
const signedPayload = require_encoding.toUint8Array(signMessageResult.signed_payloads[0]);
|
|
267
|
+
const signedMessage = require_encoding.fromUint8Array(signedPayload.slice(0, signedPayload.length - 64));
|
|
268
|
+
const signature = require_encoding.fromUint8Array(signedPayload.slice(signedPayload.length - 64));
|
|
238
269
|
return {
|
|
239
270
|
address,
|
|
240
271
|
signed_message: signedMessage.length == 0 ? siwsMessage : signedMessage,
|
|
@@ -254,7 +285,7 @@ async function encryptMessage(plaintext, sequenceNumber, sharedSecret) {
|
|
|
254
285
|
const sequenceNumberVector = createSequenceNumberVector(sequenceNumber);
|
|
255
286
|
const initializationVector = new Uint8Array(INITIALIZATION_VECTOR_BYTES);
|
|
256
287
|
crypto.getRandomValues(initializationVector);
|
|
257
|
-
const ciphertext = await crypto.subtle.encrypt(getAlgorithmParams(sequenceNumberVector, initializationVector), sharedSecret,
|
|
288
|
+
const ciphertext = await crypto.subtle.encrypt(getAlgorithmParams(sequenceNumberVector, initializationVector), sharedSecret, require_encoding.utf8ToUint8Array(plaintext));
|
|
258
289
|
const response = new Uint8Array(sequenceNumberVector.byteLength + initializationVector.byteLength + ciphertext.byteLength);
|
|
259
290
|
response.set(new Uint8Array(sequenceNumberVector), 0);
|
|
260
291
|
response.set(new Uint8Array(initializationVector), sequenceNumberVector.byteLength);
|
|
@@ -263,10 +294,10 @@ async function encryptMessage(plaintext, sequenceNumber, sharedSecret) {
|
|
|
263
294
|
}
|
|
264
295
|
async function decryptMessage(message, sharedSecret) {
|
|
265
296
|
const sequenceNumberVector = message.slice(0, 4);
|
|
266
|
-
const initializationVector = message.slice(4,
|
|
267
|
-
const ciphertext = message.slice(
|
|
297
|
+
const initializationVector = message.slice(4, 16);
|
|
298
|
+
const ciphertext = message.slice(16);
|
|
268
299
|
const plaintextBuffer = await crypto.subtle.decrypt(getAlgorithmParams(sequenceNumberVector, initializationVector), sharedSecret, ciphertext);
|
|
269
|
-
return
|
|
300
|
+
return require_encoding.utf8FromUint8Array(new Uint8Array(plaintextBuffer));
|
|
270
301
|
}
|
|
271
302
|
function getAlgorithmParams(sequenceNumber, initializationVector) {
|
|
272
303
|
return {
|
|
@@ -276,11 +307,6 @@ function getAlgorithmParams(sequenceNumber, initializationVector) {
|
|
|
276
307
|
tagLength: 128
|
|
277
308
|
};
|
|
278
309
|
}
|
|
279
|
-
let _utf8Decoder;
|
|
280
|
-
function getUtf8Decoder() {
|
|
281
|
-
if (_utf8Decoder === void 0) _utf8Decoder = new TextDecoder("utf-8");
|
|
282
|
-
return _utf8Decoder;
|
|
283
|
-
}
|
|
284
310
|
//#endregion
|
|
285
311
|
//#region src/generateAssociationKeypair.ts
|
|
286
312
|
async function generateAssociationKeypair() {
|
|
@@ -298,15 +324,6 @@ async function generateECDHKeypair() {
|
|
|
298
324
|
}, false, ["deriveKey", "deriveBits"]);
|
|
299
325
|
}
|
|
300
326
|
//#endregion
|
|
301
|
-
//#region src/arrayBufferToBase64String.ts
|
|
302
|
-
function arrayBufferToBase64String(buffer) {
|
|
303
|
-
let binary = "";
|
|
304
|
-
const bytes = new Uint8Array(buffer);
|
|
305
|
-
const len = bytes.byteLength;
|
|
306
|
-
for (let ii = 0; ii < len; ii++) binary += String.fromCharCode(bytes[ii]);
|
|
307
|
-
return window.btoa(binary);
|
|
308
|
-
}
|
|
309
|
-
//#endregion
|
|
310
327
|
//#region src/associationPort.ts
|
|
311
328
|
function getRandomAssociationPort() {
|
|
312
329
|
return assertAssociationPort(49152 + Math.floor(Math.random() * 16384));
|
|
@@ -317,7 +334,7 @@ function assertAssociationPort(port) {
|
|
|
317
334
|
}
|
|
318
335
|
//#endregion
|
|
319
336
|
//#region src/getStringWithURLUnsafeBase64CharactersReplaced.ts
|
|
320
|
-
function
|
|
337
|
+
function getStringWithURLUnsafeBase64CharactersReplaced(unsafeBase64EncodedString) {
|
|
321
338
|
return unsafeBase64EncodedString.replace(/[/+=]/g, (m) => ({
|
|
322
339
|
"/": "_",
|
|
323
340
|
"+": "-",
|
|
@@ -344,9 +361,10 @@ function getIntentURL(methodPathname, intentUrlBase) {
|
|
|
344
361
|
}
|
|
345
362
|
async function getAssociateAndroidIntentURL(associationPublicKey, putativePort, associationURLBase, protocolVersions = ["v1"]) {
|
|
346
363
|
const associationPort = assertAssociationPort(putativePort);
|
|
347
|
-
const
|
|
364
|
+
const exportedKey = await crypto.subtle.exportKey("raw", associationPublicKey);
|
|
365
|
+
const encodedKey = require_encoding.arrayBufferToBase64String(exportedKey);
|
|
348
366
|
const url = getIntentURL("v1/associate/local", associationURLBase);
|
|
349
|
-
url.searchParams.set("association",
|
|
367
|
+
url.searchParams.set("association", getStringWithURLUnsafeBase64CharactersReplaced(encodedKey));
|
|
350
368
|
url.searchParams.set("port", `${associationPort}`);
|
|
351
369
|
protocolVersions.forEach((version) => {
|
|
352
370
|
url.searchParams.set("v", version);
|
|
@@ -354,11 +372,24 @@ async function getAssociateAndroidIntentURL(associationPublicKey, putativePort,
|
|
|
354
372
|
return url;
|
|
355
373
|
}
|
|
356
374
|
async function getRemoteAssociateAndroidIntentURL(associationPublicKey, hostAuthority, reflectorId, associationURLBase, protocolVersions = ["v1"]) {
|
|
357
|
-
const
|
|
375
|
+
const exportedKey = await crypto.subtle.exportKey("raw", associationPublicKey);
|
|
376
|
+
const encodedKey = require_encoding.arrayBufferToBase64String(exportedKey);
|
|
358
377
|
const url = getIntentURL("v1/associate/remote", associationURLBase);
|
|
359
|
-
url.searchParams.set("association",
|
|
378
|
+
url.searchParams.set("association", getStringWithURLUnsafeBase64CharactersReplaced(encodedKey));
|
|
360
379
|
url.searchParams.set("reflector", `${hostAuthority}`);
|
|
361
|
-
url.searchParams.set("id", `${fromUint8Array
|
|
380
|
+
url.searchParams.set("id", `${require_encoding.fromUint8Array(reflectorId, true)}`);
|
|
381
|
+
protocolVersions.forEach((version) => {
|
|
382
|
+
url.searchParams.set("v", version);
|
|
383
|
+
});
|
|
384
|
+
return url;
|
|
385
|
+
}
|
|
386
|
+
async function getNostrAssociateAndroidIntentURL(associationPublicKey, connectionType, relayDomain, dappNostrPubkey, associationURLBase, protocolVersions = ["v1"]) {
|
|
387
|
+
const exportedKey = await crypto.subtle.exportKey("raw", associationPublicKey);
|
|
388
|
+
const encodedKey = require_encoding.arrayBufferToBase64String(exportedKey);
|
|
389
|
+
const url = getIntentURL(`v1/associate/${connectionType}/nostr`, associationURLBase);
|
|
390
|
+
url.searchParams.set("association", getStringWithURLUnsafeBase64CharactersReplaced(encodedKey));
|
|
391
|
+
url.searchParams.set("relay", relayDomain);
|
|
392
|
+
url.searchParams.set("pubkey", dappNostrPubkey);
|
|
362
393
|
protocolVersions.forEach((version) => {
|
|
363
394
|
url.searchParams.set("v", version);
|
|
364
395
|
});
|
|
@@ -393,7 +424,7 @@ async function parseHelloRsp(payloadBuffer, associationPublicKey, ecdhPrivateKey
|
|
|
393
424
|
name: "HKDF",
|
|
394
425
|
hash: "SHA-256",
|
|
395
426
|
salt: new Uint8Array(associationPublicKeyBuffer),
|
|
396
|
-
info: new Uint8Array()
|
|
427
|
+
info: /* @__PURE__ */ new Uint8Array()
|
|
397
428
|
}, ecdhSecretKey, {
|
|
398
429
|
name: "AES-GCM",
|
|
399
430
|
length: 128
|
|
@@ -424,9 +455,6 @@ const Browser = {
|
|
|
424
455
|
Firefox: 0,
|
|
425
456
|
Other: 1
|
|
426
457
|
};
|
|
427
|
-
function assertUnreachable(x) {
|
|
428
|
-
return x;
|
|
429
|
-
}
|
|
430
458
|
function getBrowser() {
|
|
431
459
|
return navigator.userAgent.indexOf("Firefox/") !== -1 ? Browser.Firefox : Browser.Other;
|
|
432
460
|
}
|
|
@@ -449,7 +477,7 @@ function getDetectionPromise() {
|
|
|
449
477
|
}
|
|
450
478
|
let _frame = null;
|
|
451
479
|
function launchUrlThroughHiddenFrame(url) {
|
|
452
|
-
if (_frame == null) {
|
|
480
|
+
if (_frame == null || !_frame.isConnected) {
|
|
453
481
|
_frame = document.createElement("iframe");
|
|
454
482
|
_frame.style.display = "none";
|
|
455
483
|
document.body.appendChild(_frame);
|
|
@@ -459,8 +487,7 @@ function launchUrlThroughHiddenFrame(url) {
|
|
|
459
487
|
async function launchAssociation(associationUrl) {
|
|
460
488
|
if (associationUrl.protocol === "https:") window.location.assign(associationUrl);
|
|
461
489
|
else try {
|
|
462
|
-
|
|
463
|
-
switch (browser) {
|
|
490
|
+
switch (getBrowser()) {
|
|
464
491
|
case Browser.Firefox:
|
|
465
492
|
launchUrlThroughHiddenFrame(associationUrl);
|
|
466
493
|
break;
|
|
@@ -470,7 +497,6 @@ async function launchAssociation(associationUrl) {
|
|
|
470
497
|
await detectionPromise;
|
|
471
498
|
break;
|
|
472
499
|
}
|
|
473
|
-
default: assertUnreachable(browser);
|
|
474
500
|
}
|
|
475
501
|
} catch {
|
|
476
502
|
throw new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_WALLET_NOT_FOUND, "Found no installed wallet that supports the mobile wallet protocol.");
|
|
@@ -481,9 +507,24 @@ async function startSession(associationPublicKey, associationURLBase) {
|
|
|
481
507
|
await launchAssociation(await getAssociateAndroidIntentURL(associationPublicKey, randomAssociationPort, associationURLBase));
|
|
482
508
|
return randomAssociationPort;
|
|
483
509
|
}
|
|
510
|
+
async function startNostrSession(associationPublicKey, connectionType, relayDomain, dappNostrPubkey, associationURLBase) {
|
|
511
|
+
const associationUrl = await getNostrAssociateAndroidIntentURL(associationPublicKey, connectionType, relayDomain, dappNostrPubkey, associationURLBase);
|
|
512
|
+
if (connectionType == "local") await launchAssociation(associationUrl);
|
|
513
|
+
return associationUrl;
|
|
514
|
+
}
|
|
484
515
|
//#endregion
|
|
485
516
|
//#region src/transact.ts
|
|
486
517
|
const WEBSOCKET_CONNECTION_CONFIG = {
|
|
518
|
+
/**
|
|
519
|
+
* 300 milliseconds is a generally accepted threshold for what someone
|
|
520
|
+
* would consider an acceptable response time for a user interface
|
|
521
|
+
* after having performed a low-attention tapping task. We set the initial
|
|
522
|
+
* interval at which we wait for the wallet to set up the websocket at
|
|
523
|
+
* half this, as per the Nyquist frequency, with a progressive backoff
|
|
524
|
+
* sequence from there. The total wait time is 30s, which allows for the
|
|
525
|
+
* user to be presented with a disambiguation dialog, select a wallet, and
|
|
526
|
+
* for the wallet app to subsequently start.
|
|
527
|
+
*/
|
|
487
528
|
retryDelayScheduleMs: [
|
|
488
529
|
150,
|
|
489
530
|
150,
|
|
@@ -597,7 +638,10 @@ async function startScenario(config) {
|
|
|
597
638
|
const responseBuffer = await evt.data.arrayBuffer();
|
|
598
639
|
switch (state.__type) {
|
|
599
640
|
case "connecting": {
|
|
600
|
-
if (responseBuffer.byteLength !== 0)
|
|
641
|
+
if (responseBuffer.byteLength !== 0) {
|
|
642
|
+
reject(new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_ILLEGAL_TRANSPORT_STATE, "Encountered unexpected message while connecting"));
|
|
643
|
+
return;
|
|
644
|
+
}
|
|
601
645
|
const ecdhKeypair = await generateECDHKeypair();
|
|
602
646
|
socket.send(await createHelloReq(ecdhKeypair.publicKey, associationKeypair.privateKey));
|
|
603
647
|
state = {
|
|
@@ -610,7 +654,7 @@ async function startScenario(config) {
|
|
|
610
654
|
case "connected":
|
|
611
655
|
try {
|
|
612
656
|
const sequenceNumber = getSequenceNumberFromByteArray(responseBuffer.slice(0, 4));
|
|
613
|
-
if (sequenceNumber !== lastKnownInboundSequenceNumber + 1) throw new
|
|
657
|
+
if (sequenceNumber !== lastKnownInboundSequenceNumber + 1) throw new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_ILLEGAL_TRANSPORT_STATE, "Encrypted message has invalid sequence number");
|
|
614
658
|
lastKnownInboundSequenceNumber = sequenceNumber;
|
|
615
659
|
const jsonRpcMessage = await decryptJsonRpcMessage(responseBuffer, state.sharedSecret);
|
|
616
660
|
const responsePromise = jsonRpcResponsePromises[jsonRpcMessage.id];
|
|
@@ -639,7 +683,11 @@ async function startScenario(config) {
|
|
|
639
683
|
const sessionPropertiesBuffer = responseBuffer.slice(65);
|
|
640
684
|
const sessionProperties = sessionPropertiesBuffer.byteLength !== 0 ? await (async () => {
|
|
641
685
|
const sequenceNumber = getSequenceNumberFromByteArray(sessionPropertiesBuffer.slice(0, 4));
|
|
642
|
-
if (sequenceNumber !== lastKnownInboundSequenceNumber + 1)
|
|
686
|
+
if (sequenceNumber !== lastKnownInboundSequenceNumber + 1) {
|
|
687
|
+
reject(new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_ILLEGAL_TRANSPORT_STATE, "Encrypted message has invalid sequence number"));
|
|
688
|
+
socket.close();
|
|
689
|
+
return { protocol_version: "v1" };
|
|
690
|
+
}
|
|
643
691
|
lastKnownInboundSequenceNumber = sequenceNumber;
|
|
644
692
|
return parseSessionProps(sessionPropertiesBuffer, sharedSecret);
|
|
645
693
|
})() : { protocol_version: "legacy" };
|
|
@@ -735,8 +783,10 @@ async function startRemoteScenario(config) {
|
|
|
735
783
|
let socket;
|
|
736
784
|
let disposeSocket;
|
|
737
785
|
const decodeBytes = async (evt) => {
|
|
738
|
-
if (encoding == "base64")
|
|
739
|
-
|
|
786
|
+
if (encoding == "base64") {
|
|
787
|
+
const message = await evt.data;
|
|
788
|
+
return require_encoding.toUint8Array(message).buffer;
|
|
789
|
+
} else return await evt.data.arrayBuffer();
|
|
740
790
|
};
|
|
741
791
|
const associationUrl = await new Promise((resolve, reject) => {
|
|
742
792
|
const handleOpen = async () => {
|
|
@@ -767,7 +817,11 @@ async function startRemoteScenario(config) {
|
|
|
767
817
|
const handleReflectorIdMessage = async (evt) => {
|
|
768
818
|
const responseBuffer = await decodeBytes(evt);
|
|
769
819
|
if (state.__type === "connecting") {
|
|
770
|
-
if (responseBuffer.byteLength == 0)
|
|
820
|
+
if (responseBuffer.byteLength == 0) {
|
|
821
|
+
reject(new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_ILLEGAL_TRANSPORT_STATE, "Encountered unexpected message while connecting"));
|
|
822
|
+
socket.close();
|
|
823
|
+
return;
|
|
824
|
+
}
|
|
771
825
|
const reflectorId = getReflectorIdFromByteArray(responseBuffer);
|
|
772
826
|
state = {
|
|
773
827
|
__type: "reflector_id_received",
|
|
@@ -815,10 +869,14 @@ async function startRemoteScenario(config) {
|
|
|
815
869
|
const responseBuffer = await decodeBytes(evt);
|
|
816
870
|
switch (state.__type) {
|
|
817
871
|
case "reflector_id_received": {
|
|
818
|
-
if (responseBuffer.byteLength !== 0)
|
|
872
|
+
if (responseBuffer.byteLength !== 0) {
|
|
873
|
+
reject(new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_ILLEGAL_TRANSPORT_STATE, "Encountered unexpected message while awaiting reflection"));
|
|
874
|
+
socket.close();
|
|
875
|
+
return;
|
|
876
|
+
}
|
|
819
877
|
const ecdhKeypair = await generateECDHKeypair();
|
|
820
878
|
const binaryMsg = await createHelloReq(ecdhKeypair.publicKey, associationKeypair.privateKey);
|
|
821
|
-
if (encoding == "base64") socket.send(fromUint8Array
|
|
879
|
+
if (encoding == "base64") socket.send(require_encoding.fromUint8Array(binaryMsg));
|
|
822
880
|
else socket.send(binaryMsg);
|
|
823
881
|
state = {
|
|
824
882
|
__type: "hello_req_sent",
|
|
@@ -830,7 +888,7 @@ async function startRemoteScenario(config) {
|
|
|
830
888
|
case "connected":
|
|
831
889
|
try {
|
|
832
890
|
const sequenceNumber = getSequenceNumberFromByteArray(responseBuffer.slice(0, 4));
|
|
833
|
-
if (sequenceNumber !== lastKnownInboundSequenceNumber + 1) throw new
|
|
891
|
+
if (sequenceNumber !== lastKnownInboundSequenceNumber + 1) throw new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_ILLEGAL_TRANSPORT_STATE, "Encrypted message has invalid sequence number");
|
|
834
892
|
lastKnownInboundSequenceNumber = sequenceNumber;
|
|
835
893
|
const jsonRpcMessage = await decryptJsonRpcMessage(responseBuffer, state.sharedSecret);
|
|
836
894
|
const responsePromise = jsonRpcResponsePromises[jsonRpcMessage.id];
|
|
@@ -849,7 +907,11 @@ async function startRemoteScenario(config) {
|
|
|
849
907
|
const sessionPropertiesBuffer = responseBuffer.slice(65);
|
|
850
908
|
const sessionProperties = sessionPropertiesBuffer.byteLength !== 0 ? await (async () => {
|
|
851
909
|
const sequenceNumber = getSequenceNumberFromByteArray(sessionPropertiesBuffer.slice(0, 4));
|
|
852
|
-
if (sequenceNumber !== lastKnownInboundSequenceNumber + 1)
|
|
910
|
+
if (sequenceNumber !== lastKnownInboundSequenceNumber + 1) {
|
|
911
|
+
reject(new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_ILLEGAL_TRANSPORT_STATE, "Encrypted message has invalid sequence number"));
|
|
912
|
+
socket.close();
|
|
913
|
+
return { protocol_version: "v1" };
|
|
914
|
+
}
|
|
853
915
|
lastKnownInboundSequenceNumber = sequenceNumber;
|
|
854
916
|
return parseSessionProps(sessionPropertiesBuffer, sharedSecret);
|
|
855
917
|
})() : { protocol_version: "legacy" };
|
|
@@ -866,7 +928,7 @@ async function startRemoteScenario(config) {
|
|
|
866
928
|
method,
|
|
867
929
|
params: params ?? {}
|
|
868
930
|
}, sharedSecret);
|
|
869
|
-
if (encoding == "base64") socket.send(fromUint8Array
|
|
931
|
+
if (encoding == "base64") socket.send(require_encoding.fromUint8Array(binaryMsg));
|
|
870
932
|
else socket.send(binaryMsg);
|
|
871
933
|
return new Promise((resolve, reject) => {
|
|
872
934
|
jsonRpcResponsePromises[id] = {
|
|
@@ -909,7 +971,239 @@ async function startRemoteScenario(config) {
|
|
|
909
971
|
})
|
|
910
972
|
};
|
|
911
973
|
}
|
|
974
|
+
async function startNostrScenario(config) {
|
|
975
|
+
assertSecureContext();
|
|
976
|
+
const associationKeypair = await generateAssociationKeypair();
|
|
977
|
+
const { privateKey: dappNostrPrivateKey, publicKey: dappNostrPubkey } = generateNostrKeypair();
|
|
978
|
+
const associationUrl = await startNostrSession(associationKeypair.publicKey, config.connectionType, config.relayDomain, dappNostrPubkey, config.baseUri);
|
|
979
|
+
const sessionIdentifier = await deriveSessionIdentifier(associationKeypair.publicKey);
|
|
980
|
+
const subscriptionId = crypto.randomUUID();
|
|
981
|
+
const webSocketURL = `wss://${config.relayDomain}`;
|
|
982
|
+
const connectionStartTime = Date.now();
|
|
983
|
+
const getNextRetryDelayMs = (() => {
|
|
984
|
+
const schedule = [...WEBSOCKET_CONNECTION_CONFIG.retryDelayScheduleMs];
|
|
985
|
+
return () => schedule.length > 1 ? schedule.shift() : schedule[0];
|
|
986
|
+
})();
|
|
987
|
+
let nextJsonRpcMessageId = 1;
|
|
988
|
+
let lastKnownInboundSequenceNumber = 0;
|
|
989
|
+
let state = { __type: "disconnected" };
|
|
990
|
+
let socket;
|
|
991
|
+
let sessionEstablished = false;
|
|
992
|
+
let handleForceClose;
|
|
993
|
+
const sendNostrEvent = (content, walletPubkey) => {
|
|
994
|
+
const event = createNostrEvent(NOSTR_EVENT_KIND_MWA, content, [["d", sessionIdentifier], ["p", walletPubkey]], dappNostrPrivateKey);
|
|
995
|
+
socket.send(JSON.stringify(["EVENT", event]));
|
|
996
|
+
};
|
|
997
|
+
const doClose = () => {
|
|
998
|
+
if (state.__type == "connected" || state.__type == "hello_req_sent") {
|
|
999
|
+
const event = createNostrEvent(NOSTR_EVENT_KIND_MWA, "", [
|
|
1000
|
+
["d", sessionIdentifier],
|
|
1001
|
+
["p", state.walletNostrPubkey],
|
|
1002
|
+
["msg", "SESSION_END"]
|
|
1003
|
+
], dappNostrPrivateKey);
|
|
1004
|
+
socket.send(JSON.stringify(["EVENT", event]));
|
|
1005
|
+
}
|
|
1006
|
+
socket.close();
|
|
1007
|
+
};
|
|
1008
|
+
const scenario = {
|
|
1009
|
+
close: () => {
|
|
1010
|
+
doClose();
|
|
1011
|
+
handleForceClose();
|
|
1012
|
+
},
|
|
1013
|
+
wallet: new Promise((resolve, reject) => {
|
|
1014
|
+
const jsonRpcResponsePromises = {};
|
|
1015
|
+
const handleOpen = async () => {
|
|
1016
|
+
if (state.__type !== "connecting") {
|
|
1017
|
+
console.warn(`Expected adapter state to be \`connecting\` at the moment the websocket opens. Got \`${state.__type}\`.`);
|
|
1018
|
+
return;
|
|
1019
|
+
}
|
|
1020
|
+
socket.removeEventListener("open", handleOpen);
|
|
1021
|
+
socket.send(JSON.stringify([
|
|
1022
|
+
"REQ",
|
|
1023
|
+
subscriptionId,
|
|
1024
|
+
{
|
|
1025
|
+
kinds: [NOSTR_EVENT_KIND_MWA],
|
|
1026
|
+
"#d": [sessionIdentifier]
|
|
1027
|
+
}
|
|
1028
|
+
]));
|
|
1029
|
+
state = {
|
|
1030
|
+
__type: "subscribed",
|
|
1031
|
+
dappNostrPrivateKey,
|
|
1032
|
+
dappNostrPubkey,
|
|
1033
|
+
sessionIdentifier
|
|
1034
|
+
};
|
|
1035
|
+
};
|
|
1036
|
+
const handleClose = (evt) => {
|
|
1037
|
+
if (evt.wasClean) state = { __type: "disconnected" };
|
|
1038
|
+
else reject(new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_SESSION_CLOSED, `The Nostr subscription dropped unexpectedly (${evt.code}: ${evt.reason}).`, { closeEvent: evt }));
|
|
1039
|
+
disposeSocket();
|
|
1040
|
+
};
|
|
1041
|
+
const handleError = async () => {
|
|
1042
|
+
disposeSocket();
|
|
1043
|
+
if (Date.now() - connectionStartTime >= WEBSOCKET_CONNECTION_CONFIG.timeoutMs) reject(new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_SESSION_TIMEOUT, `Failed to connect to the Nostr relay at ${config.relayDomain}.`));
|
|
1044
|
+
else {
|
|
1045
|
+
await new Promise((resolve) => {
|
|
1046
|
+
const retryDelayMs = getNextRetryDelayMs();
|
|
1047
|
+
retryWaitTimeoutId = window.setTimeout(resolve, retryDelayMs);
|
|
1048
|
+
});
|
|
1049
|
+
attemptSocketConnection();
|
|
1050
|
+
}
|
|
1051
|
+
};
|
|
1052
|
+
const handleMessage = async (evt) => {
|
|
1053
|
+
let msg;
|
|
1054
|
+
try {
|
|
1055
|
+
msg = JSON.parse(evt.data);
|
|
1056
|
+
if (!Array.isArray(msg)) throw new Error();
|
|
1057
|
+
} catch {
|
|
1058
|
+
reject(new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_ILLEGAL_TRANSPORT_STATE, "Invalid Nostr message received: " + evt.data));
|
|
1059
|
+
doClose();
|
|
1060
|
+
return;
|
|
1061
|
+
}
|
|
1062
|
+
const type = msg[0];
|
|
1063
|
+
if (type === "CLOSED") disposeSocket();
|
|
1064
|
+
else if (type === "EVENT") {
|
|
1065
|
+
const event = msg[2];
|
|
1066
|
+
if (!event || !verifyNostrEvent(event)) return;
|
|
1067
|
+
switch (state.__type) {
|
|
1068
|
+
case "subscribed": {
|
|
1069
|
+
if (event.content.length !== 0) {
|
|
1070
|
+
reject(new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_ILLEGAL_TRANSPORT_STATE, "Encountered unexpected message while awaiting reflection"));
|
|
1071
|
+
doClose();
|
|
1072
|
+
return;
|
|
1073
|
+
}
|
|
1074
|
+
const walletNostrPubkey = event.pubkey;
|
|
1075
|
+
const ecdhKeypair = await generateECDHKeypair();
|
|
1076
|
+
const helloReq = await createHelloReq(ecdhKeypair.publicKey, associationKeypair.privateKey);
|
|
1077
|
+
const base64Content = require_encoding.fromUint8Array(helloReq);
|
|
1078
|
+
sendNostrEvent(base64Content, walletNostrPubkey);
|
|
1079
|
+
state = {
|
|
1080
|
+
__type: "hello_req_sent",
|
|
1081
|
+
associationPublicKey: associationKeypair.publicKey,
|
|
1082
|
+
ecdhPrivateKey: ecdhKeypair.privateKey,
|
|
1083
|
+
walletNostrPubkey
|
|
1084
|
+
};
|
|
1085
|
+
break;
|
|
1086
|
+
}
|
|
1087
|
+
case "hello_req_sent": {
|
|
1088
|
+
if (event.pubkey !== state.walletNostrPubkey) return;
|
|
1089
|
+
if (event.content === "") return;
|
|
1090
|
+
const responseBuffer = require_encoding.toUint8Array(event.content).buffer;
|
|
1091
|
+
const sharedSecret = await parseHelloRsp(responseBuffer, state.associationPublicKey, state.ecdhPrivateKey);
|
|
1092
|
+
const sessionPropertiesBuffer = responseBuffer.slice(65);
|
|
1093
|
+
const sessionProperties = sessionPropertiesBuffer.byteLength !== 0 ? await (async () => {
|
|
1094
|
+
const sequenceNumber = getSequenceNumberFromByteArray(sessionPropertiesBuffer.slice(0, 4));
|
|
1095
|
+
if (sequenceNumber !== lastKnownInboundSequenceNumber + 1) {
|
|
1096
|
+
reject(new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_ILLEGAL_TRANSPORT_STATE, "Encrypted message has invalid sequence number"));
|
|
1097
|
+
doClose();
|
|
1098
|
+
return { protocol_version: "v1" };
|
|
1099
|
+
}
|
|
1100
|
+
lastKnownInboundSequenceNumber = sequenceNumber;
|
|
1101
|
+
return parseSessionProps(sessionPropertiesBuffer, sharedSecret);
|
|
1102
|
+
})() : { protocol_version: "legacy" };
|
|
1103
|
+
state = {
|
|
1104
|
+
__type: "connected",
|
|
1105
|
+
sharedSecret,
|
|
1106
|
+
sessionProperties,
|
|
1107
|
+
walletNostrPubkey: state.walletNostrPubkey
|
|
1108
|
+
};
|
|
1109
|
+
const wallet = createMobileWalletProxy(sessionProperties.protocol_version, async (method, params) => {
|
|
1110
|
+
const id = nextJsonRpcMessageId++;
|
|
1111
|
+
const binaryMsg = await encryptJsonRpcMessage({
|
|
1112
|
+
id,
|
|
1113
|
+
jsonrpc: "2.0",
|
|
1114
|
+
method,
|
|
1115
|
+
params: params ?? {}
|
|
1116
|
+
}, sharedSecret);
|
|
1117
|
+
sendNostrEvent(require_encoding.fromUint8Array(binaryMsg), state.walletNostrPubkey);
|
|
1118
|
+
return new Promise((resolve, reject) => {
|
|
1119
|
+
jsonRpcResponsePromises[id] = {
|
|
1120
|
+
resolve(result) {
|
|
1121
|
+
switch (method) {
|
|
1122
|
+
case "authorize":
|
|
1123
|
+
case "reauthorize": {
|
|
1124
|
+
const { wallet_uri_base } = result;
|
|
1125
|
+
if (wallet_uri_base != null) try {
|
|
1126
|
+
assertSecureEndpointSpecificURI(wallet_uri_base);
|
|
1127
|
+
} catch (e) {
|
|
1128
|
+
reject(e);
|
|
1129
|
+
return;
|
|
1130
|
+
}
|
|
1131
|
+
break;
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
resolve(result);
|
|
1135
|
+
},
|
|
1136
|
+
reject
|
|
1137
|
+
};
|
|
1138
|
+
});
|
|
1139
|
+
});
|
|
1140
|
+
sessionEstablished = true;
|
|
1141
|
+
try {
|
|
1142
|
+
resolve(wallet);
|
|
1143
|
+
} catch (e) {
|
|
1144
|
+
reject(e);
|
|
1145
|
+
}
|
|
1146
|
+
break;
|
|
1147
|
+
}
|
|
1148
|
+
case "connected":
|
|
1149
|
+
if (event.pubkey !== state.walletNostrPubkey) return;
|
|
1150
|
+
if (event.content === "") return;
|
|
1151
|
+
try {
|
|
1152
|
+
const responseBuffer = require_encoding.toUint8Array(event.content).buffer;
|
|
1153
|
+
const sequenceNumber = getSequenceNumberFromByteArray(responseBuffer.slice(0, 4));
|
|
1154
|
+
if (sequenceNumber !== lastKnownInboundSequenceNumber + 1) throw new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_ILLEGAL_TRANSPORT_STATE, "Encrypted message has invalid sequence number");
|
|
1155
|
+
lastKnownInboundSequenceNumber = sequenceNumber;
|
|
1156
|
+
const jsonRpcMessage = await decryptJsonRpcMessage(responseBuffer, state.sharedSecret);
|
|
1157
|
+
const responsePromise = jsonRpcResponsePromises[jsonRpcMessage.id];
|
|
1158
|
+
delete jsonRpcResponsePromises[jsonRpcMessage.id];
|
|
1159
|
+
responsePromise.resolve(jsonRpcMessage.result);
|
|
1160
|
+
} catch (e) {
|
|
1161
|
+
if (e instanceof SolanaMobileWalletAdapterProtocolError) {
|
|
1162
|
+
const responsePromise = jsonRpcResponsePromises[e.jsonRpcMessageId];
|
|
1163
|
+
delete jsonRpcResponsePromises[e.jsonRpcMessageId];
|
|
1164
|
+
responsePromise.reject(e);
|
|
1165
|
+
} else throw e;
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
};
|
|
1170
|
+
handleForceClose = () => {
|
|
1171
|
+
socket.removeEventListener("message", handleMessage);
|
|
1172
|
+
disposeSocket();
|
|
1173
|
+
if (!sessionEstablished) reject(new SolanaMobileWalletAdapterError(SolanaMobileWalletAdapterErrorCode.ERROR_SESSION_CLOSED, `The wallet session was closed before connection.`, { closeEvent: new CloseEvent("socket was closed before connection") }));
|
|
1174
|
+
};
|
|
1175
|
+
let disposeSocket;
|
|
1176
|
+
let retryWaitTimeoutId;
|
|
1177
|
+
const attemptSocketConnection = () => {
|
|
1178
|
+
if (disposeSocket) disposeSocket();
|
|
1179
|
+
state = {
|
|
1180
|
+
__type: "connecting",
|
|
1181
|
+
associationKeypair
|
|
1182
|
+
};
|
|
1183
|
+
socket = new WebSocket(webSocketURL);
|
|
1184
|
+
socket.addEventListener("open", handleOpen);
|
|
1185
|
+
socket.addEventListener("close", handleClose);
|
|
1186
|
+
socket.addEventListener("error", handleError);
|
|
1187
|
+
socket.addEventListener("message", handleMessage);
|
|
1188
|
+
disposeSocket = () => {
|
|
1189
|
+
window.clearTimeout(retryWaitTimeoutId);
|
|
1190
|
+
socket.removeEventListener("open", handleOpen);
|
|
1191
|
+
socket.removeEventListener("close", handleClose);
|
|
1192
|
+
socket.removeEventListener("error", handleError);
|
|
1193
|
+
socket.removeEventListener("message", handleMessage);
|
|
1194
|
+
};
|
|
1195
|
+
};
|
|
1196
|
+
attemptSocketConnection();
|
|
1197
|
+
})
|
|
1198
|
+
};
|
|
1199
|
+
if (config.connectionType == "local") return scenario;
|
|
1200
|
+
else return {
|
|
1201
|
+
...scenario,
|
|
1202
|
+
associationUrl
|
|
1203
|
+
};
|
|
1204
|
+
}
|
|
912
1205
|
//#endregion
|
|
1206
|
+
exports.NOSTR_EVENT_KIND_MWA = NOSTR_EVENT_KIND_MWA;
|
|
913
1207
|
exports.SolanaCloneAuthorization = SolanaCloneAuthorization;
|
|
914
1208
|
exports.SolanaMobileWalletAdapterError = SolanaMobileWalletAdapterError;
|
|
915
1209
|
exports.SolanaMobileWalletAdapterErrorCode = SolanaMobileWalletAdapterErrorCode;
|
|
@@ -917,8 +1211,13 @@ exports.SolanaMobileWalletAdapterProtocolError = SolanaMobileWalletAdapterProtoc
|
|
|
917
1211
|
exports.SolanaMobileWalletAdapterProtocolErrorCode = SolanaMobileWalletAdapterProtocolErrorCode;
|
|
918
1212
|
exports.SolanaSignInWithSolana = SolanaSignInWithSolana;
|
|
919
1213
|
exports.SolanaSignTransactions = SolanaSignTransactions;
|
|
1214
|
+
exports.createNostrEvent = createNostrEvent;
|
|
1215
|
+
exports.deriveSessionIdentifier = deriveSessionIdentifier;
|
|
1216
|
+
exports.generateNostrKeypair = generateNostrKeypair;
|
|
1217
|
+
exports.startNostrScenario = startNostrScenario;
|
|
920
1218
|
exports.startRemoteScenario = startRemoteScenario;
|
|
921
1219
|
exports.startScenario = startScenario;
|
|
922
1220
|
exports.transact = transact;
|
|
1221
|
+
exports.verifyNostrEvent = verifyNostrEvent;
|
|
923
1222
|
|
|
924
1223
|
//# sourceMappingURL=index.js.map
|