@kasflow/passkey-wallet 0.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/dist/index.js ADDED
@@ -0,0 +1,1845 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ Address: () => import_kaspa_wasm2.Address,
34
+ DEFAULT_NETWORK: () => DEFAULT_NETWORK,
35
+ DEFAULT_PRIORITY_FEE_SOMPI: () => DEFAULT_PRIORITY_FEE_SOMPI,
36
+ ERROR_MESSAGES: () => ERROR_MESSAGES,
37
+ Generator: () => import_kaspa_wasm6.Generator,
38
+ KaspaRpc: () => KaspaRpc,
39
+ MIN_FEE_SOMPI: () => MIN_FEE_SOMPI,
40
+ NETWORK_ID: () => NETWORK_ID,
41
+ NetworkType: () => import_kaspa_wasm2.NetworkType,
42
+ PasskeyWallet: () => PasskeyWallet,
43
+ PendingTransaction: () => import_kaspa_wasm6.PendingTransaction,
44
+ PrivateKey: () => import_kaspa_wasm2.PrivateKey,
45
+ PublicKey: () => import_kaspa_wasm2.PublicKey,
46
+ Resolver: () => import_kaspa_wasm6.Resolver,
47
+ RpcClient: () => import_kaspa_wasm6.RpcClient,
48
+ SOMPI_PER_KAS: () => SOMPI_PER_KAS,
49
+ Transaction: () => import_kaspa_wasm6.Transaction,
50
+ UtxoContext: () => import_kaspa_wasm6.UtxoContext,
51
+ UtxoEntryReference: () => import_kaspa_wasm6.UtxoEntryReference,
52
+ UtxoProcessor: () => import_kaspa_wasm6.UtxoProcessor,
53
+ authenticateWithChallenge: () => authenticateWithChallenge,
54
+ buildTransactions: () => buildTransactions,
55
+ computeTransactionHash: () => computeTransactionHash,
56
+ createGenerator: () => createGenerator,
57
+ createLogger: () => createLogger,
58
+ createPrivateKey: () => createPrivateKey,
59
+ deriveKaspaKeysFromPasskey: () => deriveKaspaKeysFromPasskey,
60
+ estimateFee: () => estimateFee,
61
+ extractPublicKeyFromAttestation: () => extractPublicKeyFromAttestation,
62
+ formatKas: () => formatKas,
63
+ generatePrivateKey: () => generatePrivateKey,
64
+ getAddressFromPrivateKey: () => getAddressFromPrivateKey,
65
+ getDefaultRpc: () => getDefaultRpc,
66
+ getNetworkFromAddress: () => getNetworkFromAddress,
67
+ getNetworkIdString: () => getNetworkIdString,
68
+ getNetworkType: () => getNetworkType,
69
+ getPublicKeyHex: () => getPublicKeyHex,
70
+ hexToUint8Array: () => hexToUint8Array,
71
+ initDebugMode: () => initDebugMode,
72
+ isValidAddress: () => isValidAddress,
73
+ isValidPrivateKey: () => isValidPrivateKey,
74
+ isWebAuthnSupported: () => isWebAuthnSupported,
75
+ kasStringToSompi: () => kasStringToSompi,
76
+ kasToSompi: () => kasToSompi,
77
+ parseAddress: () => parseAddress,
78
+ parseKas: () => parseKas,
79
+ publicKeyToAddress: () => publicKeyToAddress,
80
+ resetDefaultRpc: () => resetDefaultRpc,
81
+ sendTransaction: () => sendTransaction,
82
+ signMessageWithKey: () => signMessageWithKey,
83
+ signTransaction: () => signTransaction,
84
+ signTransactions: () => signTransactions,
85
+ sompiToKas: () => sompiToKas,
86
+ sompiToKasString: () => sompiToKasString,
87
+ sompiToKasStringWithSuffix: () => sompiToKasStringWithSuffix,
88
+ submitTransactions: () => submitTransactions,
89
+ uint8ArrayToHex: () => uint8ArrayToHex,
90
+ verifyDeterminism: () => verifyDeterminism
91
+ });
92
+ module.exports = __toCommonJS(index_exports);
93
+
94
+ // src/constants.ts
95
+ var WEBAUTHN_RP_NAME = "KasFlow";
96
+ var WEBAUTHN_RP_ID = typeof window !== "undefined" ? window.location.hostname : "localhost";
97
+ var WEBAUTHN_TIMEOUT_MS = 6e4;
98
+ var WEBAUTHN_PUB_KEY_CRED_PARAMS = [
99
+ { alg: -7, type: "public-key" },
100
+ // ES256 (recommended)
101
+ { alg: -257, type: "public-key" }
102
+ // RS256 (fallback)
103
+ ];
104
+ var WEBAUTHN_USER_VERIFICATION = "required";
105
+ var WEBAUTHN_AUTHENTICATOR_ATTACHMENT = "platform";
106
+ var IDB_DATABASE_NAME = "kasflow-wallet";
107
+ var IDB_STORE_NAME = "keystore";
108
+ var STORAGE_KEY_CREDENTIAL_ID = "credential-id";
109
+ var STORAGE_KEY_METADATA = "wallet-metadata";
110
+ var NETWORK_ID = {
111
+ MAINNET: "mainnet",
112
+ TESTNET_10: "testnet-10",
113
+ TESTNET_11: "testnet-11"
114
+ };
115
+ var DEFAULT_NETWORK = NETWORK_ID.TESTNET_10;
116
+ var RPC_TIMEOUT_MS = 3e4;
117
+ var DEFAULT_PRIORITY_FEE_SOMPI = 100000n;
118
+ var SOMPI_PER_KAS = 100000000n;
119
+ var MIN_FEE_SOMPI = 1000n;
120
+ var ERROR_MESSAGES = {
121
+ WALLET_NOT_FOUND: "No wallet found. Please create a wallet first.",
122
+ WALLET_ALREADY_EXISTS: "A wallet already exists. Delete it before creating a new one.",
123
+ PASSKEY_REGISTRATION_FAILED: "Failed to register passkey. Please try again.",
124
+ PASSKEY_AUTHENTICATION_FAILED: "Failed to authenticate with passkey. Please try again.",
125
+ INVALID_ADDRESS: "Invalid Kaspa address format.",
126
+ INSUFFICIENT_BALANCE: "Insufficient balance for this transaction.",
127
+ WEBAUTHN_NOT_SUPPORTED: "WebAuthn is not supported in this browser.",
128
+ USER_CANCELLED: "Operation was cancelled by the user.",
129
+ WASM_NOT_INITIALIZED: "Kaspa WASM module not initialized. Call initKaspa() first.",
130
+ RPC_NOT_CONNECTED: "Not connected to Kaspa network. Call connect() first.",
131
+ RPC_CONNECTION_FAILED: "Failed to connect to Kaspa network.",
132
+ TRANSACTION_FAILED: "Transaction failed to submit.",
133
+ INVALID_AMOUNT: "Invalid transaction amount."
134
+ };
135
+
136
+ // src/crypto.ts
137
+ var generateRandomBytes = (length) => {
138
+ return crypto.getRandomValues(new Uint8Array(length));
139
+ };
140
+ var uint8ArrayToBase64 = (bytes) => {
141
+ const binary = String.fromCharCode(...bytes);
142
+ return btoa(binary);
143
+ };
144
+ var base64ToUint8Array = (base64) => {
145
+ const binary = atob(base64);
146
+ const bytes = new Uint8Array(binary.length);
147
+ for (let i = 0; i < binary.length; i++) {
148
+ bytes[i] = binary.charCodeAt(i);
149
+ }
150
+ return bytes;
151
+ };
152
+ var base64urlToUint8Array = (base64url) => {
153
+ let base64 = base64url.replace(/-/g, "+").replace(/_/g, "/");
154
+ while (base64.length % 4 !== 0) {
155
+ base64 += "=";
156
+ }
157
+ return base64ToUint8Array(base64);
158
+ };
159
+ var uint8ArrayToHex = (bytes) => {
160
+ return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
161
+ };
162
+ var hexToUint8Array = (hex) => {
163
+ const matches = hex.match(/.{1,2}/g);
164
+ if (!matches) {
165
+ throw new Error("Invalid hex string");
166
+ }
167
+ return new Uint8Array(matches.map((byte) => parseInt(byte, 16)));
168
+ };
169
+
170
+ // src/deterministic-keys.ts
171
+ var import_sha256 = require("@noble/hashes/sha256");
172
+ var import_utils = require("@noble/hashes/utils");
173
+
174
+ // src/logger.ts
175
+ var DEBUG_ENABLED = typeof process !== "undefined" ? process.env.NODE_ENV === "development" : true;
176
+ var Logger = class {
177
+ module;
178
+ constructor(module2) {
179
+ this.module = module2;
180
+ }
181
+ formatMessage(level, ...args) {
182
+ return [`[${this.module}] ${level}:`, ...args];
183
+ }
184
+ /**
185
+ * Log info message
186
+ */
187
+ info(...args) {
188
+ console.log(...this.formatMessage("INFO", ...args));
189
+ }
190
+ /**
191
+ * Log warning message
192
+ */
193
+ warn(...args) {
194
+ console.warn(...this.formatMessage("WARN", ...args));
195
+ }
196
+ /**
197
+ * Log error message
198
+ */
199
+ error(...args) {
200
+ console.error(...this.formatMessage("ERROR", ...args));
201
+ }
202
+ /**
203
+ * Log debug message (only in development)
204
+ */
205
+ debug(...args) {
206
+ if (DEBUG_ENABLED) {
207
+ console.log(...this.formatMessage("DEBUG", ...args));
208
+ }
209
+ }
210
+ };
211
+ function createLogger(module2) {
212
+ return new Logger(module2);
213
+ }
214
+
215
+ // src/deterministic-keys.ts
216
+ var logger = createLogger("DeterministicKeys");
217
+ function deriveKaspaKeysFromPasskey(passkeyPublicKey) {
218
+ logger.info("[DeterministicKeys] Deriving Kaspa keys from passkey public key");
219
+ if (passkeyPublicKey.length !== 65) {
220
+ throw new Error(`Invalid passkey public key length: ${passkeyPublicKey.length} (expected 65)`);
221
+ }
222
+ if (passkeyPublicKey[0] !== 4) {
223
+ throw new Error(`Invalid passkey public key prefix: 0x${passkeyPublicKey[0].toString(16)} (expected 0x04)`);
224
+ }
225
+ const seed = (0, import_sha256.sha256)(passkeyPublicKey);
226
+ logger.info("[DeterministicKeys] Seed derived:", {
227
+ seedLength: seed.length,
228
+ seedHex: (0, import_utils.bytesToHex)(seed).substring(0, 16) + "..."
229
+ // Log first 8 bytes only
230
+ });
231
+ const privateKeyHex = (0, import_utils.bytesToHex)(seed);
232
+ logger.info("[DeterministicKeys] Kaspa private key derived successfully");
233
+ return {
234
+ privateKeyHex,
235
+ seed
236
+ };
237
+ }
238
+ function verifyDeterminism(passkeyPublicKey, expectedPrivateKeyHex) {
239
+ try {
240
+ const { privateKeyHex } = deriveKaspaKeysFromPasskey(passkeyPublicKey);
241
+ return privateKeyHex === expectedPrivateKeyHex;
242
+ } catch {
243
+ return false;
244
+ }
245
+ }
246
+
247
+ // src/kaspa.ts
248
+ var import_kaspa_wasm2 = require("@onekeyfe/kaspa-wasm");
249
+ var import_kaspa_address = require("@kluster/kaspa-address");
250
+
251
+ // src/wasm-init.ts
252
+ var import_kaspa_wasm = __toESM(require("@onekeyfe/kaspa-wasm"));
253
+ var wasmInitialized = false;
254
+ var initPromise = null;
255
+ async function ensureWasmInitialized() {
256
+ if (wasmInitialized) {
257
+ return;
258
+ }
259
+ if (initPromise) {
260
+ return initPromise;
261
+ }
262
+ initPromise = (async () => {
263
+ try {
264
+ if (typeof WebAssembly === "undefined") {
265
+ throw new Error(
266
+ "WebAssembly is not supported in this browser. Please use a modern browser (Chrome 57+, Firefox 52+, Safari 11+, Edge 16+)"
267
+ );
268
+ }
269
+ console.log("[WASM] Initializing Kaspa WASM module...");
270
+ await (0, import_kaspa_wasm.default)();
271
+ if (process.env.NODE_ENV !== "production") {
272
+ try {
273
+ (0, import_kaspa_wasm.initConsolePanicHook)();
274
+ console.log("[WASM] Console panic hook enabled for better error messages");
275
+ } catch (error) {
276
+ console.warn("[WASM] Failed to initialize console panic hook:", error);
277
+ }
278
+ }
279
+ wasmInitialized = true;
280
+ console.log("[WASM] Kaspa WASM module initialized successfully");
281
+ } catch (error) {
282
+ initPromise = null;
283
+ wasmInitialized = false;
284
+ const errorMessage = error instanceof Error ? error.message : String(error);
285
+ console.error("[WASM] Initialization failed:", errorMessage);
286
+ throw new Error(`WASM initialization failed: ${errorMessage}`);
287
+ }
288
+ })();
289
+ return initPromise;
290
+ }
291
+
292
+ // src/kaspa.ts
293
+ var getNetworkType = (network) => {
294
+ switch (network) {
295
+ case NETWORK_ID.MAINNET:
296
+ return "mainnet";
297
+ case NETWORK_ID.TESTNET_10:
298
+ return "testnet-10";
299
+ case NETWORK_ID.TESTNET_11:
300
+ return "testnet-11";
301
+ default:
302
+ return "testnet-11";
303
+ }
304
+ };
305
+ var getNetworkIdString = (network) => getNetworkType(network);
306
+ var generatePrivateKey = () => {
307
+ const randomBytes = generateRandomBytes(32);
308
+ return uint8ArrayToHex(randomBytes);
309
+ };
310
+ var createPrivateKey = async (privateKeyHex) => {
311
+ await ensureWasmInitialized();
312
+ return new import_kaspa_wasm2.PrivateKey(privateKeyHex);
313
+ };
314
+ var getPublicKeyHex = async (privateKeyHex) => {
315
+ await ensureWasmInitialized();
316
+ const privateKey = new import_kaspa_wasm2.PrivateKey(privateKeyHex);
317
+ return privateKey.toPublicKey().toString();
318
+ };
319
+ var getAddressFromPrivateKey = async (privateKeyHex, network) => {
320
+ await ensureWasmInitialized();
321
+ const privateKey = new import_kaspa_wasm2.PrivateKey(privateKeyHex);
322
+ const networkType = getNetworkType(network);
323
+ return privateKey.toAddress(networkType).toString();
324
+ };
325
+ var isValidPrivateKey = async (privateKeyHex) => {
326
+ await ensureWasmInitialized();
327
+ try {
328
+ new import_kaspa_wasm2.PrivateKey(privateKeyHex);
329
+ return true;
330
+ } catch {
331
+ return false;
332
+ }
333
+ };
334
+ var publicKeyToAddress = async (publicKeyHex, network) => {
335
+ await ensureWasmInitialized();
336
+ const networkType = getNetworkType(network);
337
+ const address = (0, import_kaspa_wasm2.createAddress)(publicKeyHex, networkType);
338
+ return address.toString();
339
+ };
340
+ var isValidAddress = (address) => {
341
+ try {
342
+ import_kaspa_address.KaspaAddress.fromString(address);
343
+ return true;
344
+ } catch {
345
+ return false;
346
+ }
347
+ };
348
+ var parseAddress = (address) => {
349
+ const parsed = import_kaspa_address.KaspaAddress.fromString(address);
350
+ return {
351
+ prefix: parsed.prefix,
352
+ version: parsed.version,
353
+ payload: parsed.payload
354
+ };
355
+ };
356
+ var getNetworkFromAddress = (address) => {
357
+ const parsed = import_kaspa_address.KaspaAddress.fromString(address);
358
+ switch (parsed.prefix) {
359
+ case import_kaspa_address.KaspaPrefix.MAINNET:
360
+ return NETWORK_ID.MAINNET;
361
+ case import_kaspa_address.KaspaPrefix.TESTNET:
362
+ return NETWORK_ID.TESTNET_11;
363
+ default:
364
+ return NETWORK_ID.TESTNET_11;
365
+ }
366
+ };
367
+ var signMessageWithKey = async (message, privateKeyHex) => {
368
+ await ensureWasmInitialized();
369
+ const privateKey = new import_kaspa_wasm2.PrivateKey(privateKeyHex);
370
+ return (0, import_kaspa_wasm2.signMessage)({
371
+ message,
372
+ privateKey
373
+ });
374
+ };
375
+ var signTransaction = async (transaction, privateKeys, verifySig = true) => {
376
+ await ensureWasmInitialized();
377
+ const keys = privateKeys.map((k) => typeof k === "string" ? new import_kaspa_wasm2.PrivateKey(k) : k);
378
+ return (0, import_kaspa_wasm2.signTransaction)(transaction, keys, verifySig);
379
+ };
380
+ var kasStringToSompi = (kasString) => {
381
+ const result = (0, import_kaspa_wasm2.kaspaToSompi)(kasString);
382
+ if (result === void 0) {
383
+ throw new Error(ERROR_MESSAGES.INVALID_AMOUNT);
384
+ }
385
+ return result;
386
+ };
387
+ var sompiToKasString = (sompi) => {
388
+ return (0, import_kaspa_wasm2.sompiToKaspaString)(sompi);
389
+ };
390
+ var sompiToKasStringWithSuffix = (sompi, network) => {
391
+ return (0, import_kaspa_wasm2.sompiToKaspaStringWithSuffix)(sompi, getNetworkType(network));
392
+ };
393
+ var sompiToKas = (sompi) => {
394
+ return Number(sompi) / Number(SOMPI_PER_KAS);
395
+ };
396
+ var kasToSompi = (kas) => {
397
+ return BigInt(Math.floor(kas * Number(SOMPI_PER_KAS)));
398
+ };
399
+ var formatKas = (sompi, decimals = 8) => {
400
+ const kas = sompiToKas(sompi);
401
+ return kas.toFixed(decimals).replace(/\.?0+$/, "");
402
+ };
403
+ var parseKas = kasStringToSompi;
404
+ var computeTransactionHash = (tx) => {
405
+ const txId = tx.id;
406
+ if (!txId || typeof txId !== "string") {
407
+ throw new Error("Invalid transaction: missing transaction ID");
408
+ }
409
+ const hashBytes = hexToUint8Array(txId);
410
+ if (hashBytes.length !== 32) {
411
+ throw new Error(`Invalid transaction hash length: expected 32 bytes, got ${hashBytes.length}`);
412
+ }
413
+ return hashBytes;
414
+ };
415
+
416
+ // src/webauthn.ts
417
+ var import_browser = require("@simplewebauthn/browser");
418
+
419
+ // src/cose-parser.ts
420
+ var import_cbor_x = require("cbor-x");
421
+ var logger2 = createLogger("COSEParser");
422
+ function extractPublicKeyFromAttestation(attestationObjectBase64) {
423
+ logger2.info("[COSEParser] Extracting public key from attestation object");
424
+ try {
425
+ const attestationBuffer = base64urlToUint8Array(attestationObjectBase64);
426
+ logger2.info("[COSEParser] Attestation buffer length:", attestationBuffer.length);
427
+ const attestation = (0, import_cbor_x.decode)(attestationBuffer);
428
+ logger2.info("[COSEParser] Attestation decoded:", {
429
+ fmt: attestation.fmt,
430
+ authDataLength: attestation.authData?.length
431
+ });
432
+ if (!attestation.authData) {
433
+ throw new Error("Attestation object missing authData");
434
+ }
435
+ const publicKey = parseAuthenticatorData(attestation.authData);
436
+ logger2.info("[COSEParser] Public key extracted:", {
437
+ length: publicKey.length,
438
+ prefix: publicKey[0]
439
+ });
440
+ return publicKey;
441
+ } catch (error) {
442
+ logger2.error("[COSEParser] Failed to extract public key:", error);
443
+ if (error instanceof Error) {
444
+ throw new Error(`Failed to extract public key: ${error.message}`);
445
+ }
446
+ throw new Error("Failed to extract public key from attestation object");
447
+ }
448
+ }
449
+ function parseAuthenticatorData(authData) {
450
+ logger2.info("[COSEParser] Parsing authenticator data:", {
451
+ length: authData.length
452
+ });
453
+ if (authData.length < 37) {
454
+ throw new Error(`Authenticator data too short: ${authData.length} bytes`);
455
+ }
456
+ const flags = authData[32];
457
+ const attestedCredentialDataIncluded = (flags & 64) !== 0;
458
+ logger2.info("[COSEParser] Flags:", {
459
+ raw: flags.toString(2).padStart(8, "0"),
460
+ attestedCredentialData: attestedCredentialDataIncluded
461
+ });
462
+ if (!attestedCredentialDataIncluded) {
463
+ throw new Error("Attested credential data not included in authenticator data");
464
+ }
465
+ let offset = 37;
466
+ let publicKeyOffset = 0;
467
+ let credIdLength = 0;
468
+ if (authData.length >= 55) {
469
+ offset = 53;
470
+ const view = new DataView(authData.buffer, authData.byteOffset + offset, 2);
471
+ credIdLength = view.getUint16(0, false);
472
+ logger2.info("[COSEParser] Attempting parse WITH AAGUID - Credential ID length:", credIdLength);
473
+ if (credIdLength > 0 && credIdLength <= 1024) {
474
+ publicKeyOffset = 55 + credIdLength;
475
+ if (authData.length > publicKeyOffset) {
476
+ logger2.info("[COSEParser] Successfully parsed with AAGUID");
477
+ } else {
478
+ logger2.info("[COSEParser] AAGUID parse failed, trying without AAGUID");
479
+ offset = 37;
480
+ }
481
+ } else {
482
+ logger2.info("[COSEParser] Unreasonable cred ID length with AAGUID, trying without");
483
+ offset = 37;
484
+ }
485
+ }
486
+ if (offset === 37) {
487
+ if (authData.length < 39) {
488
+ throw new Error("Authenticator data too short for credential ID length");
489
+ }
490
+ const view = new DataView(authData.buffer, authData.byteOffset + offset, 2);
491
+ credIdLength = view.getUint16(0, false);
492
+ logger2.info("[COSEParser] Attempting parse WITHOUT AAGUID - Credential ID length:", credIdLength);
493
+ if (credIdLength > 1024) {
494
+ throw new Error(`Credential ID length too large: ${credIdLength} bytes`);
495
+ }
496
+ publicKeyOffset = 39 + credIdLength;
497
+ }
498
+ logger2.info("[COSEParser] Offset calculation:", {
499
+ authDataLength: authData.length,
500
+ credIdLength,
501
+ publicKeyOffset,
502
+ remainingBytes: authData.length - publicKeyOffset
503
+ });
504
+ if (authData.length <= publicKeyOffset) {
505
+ throw new Error(
506
+ `Authenticator data missing COSE public key (authData length: ${authData.length}, expected offset: ${publicKeyOffset})`
507
+ );
508
+ }
509
+ const coseKey = authData.slice(publicKeyOffset);
510
+ logger2.info("[COSEParser] COSE key length:", coseKey.length);
511
+ return parseCOSEPublicKey(coseKey);
512
+ }
513
+ function parseCOSEPublicKey(coseKey) {
514
+ logger2.info("[COSEParser] Parsing COSE public key");
515
+ try {
516
+ const key = (0, import_cbor_x.decode)(coseKey);
517
+ logger2.info("[COSEParser] COSE key decoded:", {
518
+ kty: key[1],
519
+ alg: key[3],
520
+ crv: key[-1],
521
+ hasX: !!key[-2],
522
+ hasY: !!key[-3]
523
+ });
524
+ const x = key[-2];
525
+ const y = key[-3];
526
+ if (!x || !y) {
527
+ throw new Error("COSE key missing x or y coordinate");
528
+ }
529
+ if (x.length !== 32 || y.length !== 32) {
530
+ throw new Error(`Invalid coordinate length: x=${x.length}, y=${y.length}`);
531
+ }
532
+ const publicKey = new Uint8Array(65);
533
+ publicKey[0] = 4;
534
+ publicKey.set(x, 1);
535
+ publicKey.set(y, 33);
536
+ logger2.info("[COSEParser] Public key constructed successfully");
537
+ return publicKey;
538
+ } catch (error) {
539
+ logger2.error("[COSEParser] Failed to parse COSE key:", error);
540
+ if (error instanceof Error) {
541
+ throw new Error(`Failed to parse COSE public key: ${error.message}`);
542
+ }
543
+ throw new Error("Failed to parse COSE public key");
544
+ }
545
+ }
546
+
547
+ // src/webauthn.ts
548
+ var logger3 = createLogger("WebAuthn");
549
+ var isWebAuthnSupported = () => {
550
+ return (0, import_browser.browserSupportsWebAuthn)();
551
+ };
552
+ var registerPasskey = async (walletName) => {
553
+ logger3.info("[WebAuthn] registerPasskey() called with name:", walletName);
554
+ if (!isWebAuthnSupported()) {
555
+ logger3.error("[WebAuthn] WebAuthn not supported");
556
+ return {
557
+ success: false,
558
+ error: ERROR_MESSAGES.WEBAUTHN_NOT_SUPPORTED
559
+ };
560
+ }
561
+ try {
562
+ logger3.info("[WebAuthn] Generating challenge...");
563
+ const challenge = generateRandomBytes(32);
564
+ const challengeBase64 = uint8ArrayToBase64(challenge);
565
+ const userId = generateRandomBytes(32);
566
+ const userIdBase64 = uint8ArrayToBase64(userId);
567
+ const registrationOptions = {
568
+ challenge: challengeBase64,
569
+ rp: {
570
+ name: WEBAUTHN_RP_NAME,
571
+ id: WEBAUTHN_RP_ID
572
+ },
573
+ user: {
574
+ id: userIdBase64,
575
+ name: walletName,
576
+ displayName: walletName
577
+ },
578
+ pubKeyCredParams: WEBAUTHN_PUB_KEY_CRED_PARAMS,
579
+ timeout: WEBAUTHN_TIMEOUT_MS,
580
+ authenticatorSelection: {
581
+ authenticatorAttachment: WEBAUTHN_AUTHENTICATOR_ATTACHMENT,
582
+ userVerification: WEBAUTHN_USER_VERIFICATION,
583
+ residentKey: "required",
584
+ requireResidentKey: true
585
+ },
586
+ attestation: "none"
587
+ };
588
+ logger3.info("[WebAuthn] Registration options:", {
589
+ rpName: registrationOptions.rp.name,
590
+ rpId: registrationOptions.rp.id,
591
+ userName: registrationOptions.user.name,
592
+ timeout: registrationOptions.timeout
593
+ });
594
+ logger3.info("[WebAuthn] Starting WebAuthn registration ceremony...");
595
+ const credential = await (0, import_browser.startRegistration)({ optionsJSON: registrationOptions });
596
+ logger3.info("[WebAuthn] Registration ceremony completed, credential received:", {
597
+ id: credential.id,
598
+ type: credential.type,
599
+ hasPublicKey: !!credential.response.publicKey,
600
+ hasAttestationObject: !!credential.response.attestationObject,
601
+ rawId: credential.rawId
602
+ });
603
+ const publicKeyBytes = credential.response.publicKey ? credential.response.publicKey : "";
604
+ logger3.info("[WebAuthn] Extracting passkey public key from attestation object...");
605
+ let passkeyPublicKey;
606
+ try {
607
+ passkeyPublicKey = extractPublicKeyFromAttestation(credential.response.attestationObject);
608
+ logger3.info("[WebAuthn] Passkey public key extracted successfully:", {
609
+ length: passkeyPublicKey.length,
610
+ prefix: passkeyPublicKey[0]
611
+ // Should be 0x04 for uncompressed
612
+ });
613
+ } catch (error) {
614
+ logger3.error("[WebAuthn] Failed to extract passkey public key:", error);
615
+ }
616
+ const storedCredential = {
617
+ id: credential.id,
618
+ rawId: base64urlToUint8Array(credential.rawId),
619
+ publicKey: publicKeyBytes,
620
+ counter: 0,
621
+ transports: credential.response.transports
622
+ };
623
+ logger3.info("[WebAuthn] Passkey registered successfully");
624
+ return {
625
+ success: true,
626
+ credential: storedCredential,
627
+ userId,
628
+ // Included for WebAuthn spec compliance (not used for key derivation)
629
+ passkeyPublicKey
630
+ // Used for deterministic key derivation
631
+ };
632
+ } catch (error) {
633
+ logger3.error("[WebAuthn] Registration failed with error:", error);
634
+ if (error instanceof Error && error.name === "NotAllowedError") {
635
+ logger3.warn("[WebAuthn] User cancelled the registration");
636
+ return {
637
+ success: false,
638
+ error: ERROR_MESSAGES.USER_CANCELLED
639
+ };
640
+ }
641
+ if (error instanceof Error) {
642
+ logger3.error("[WebAuthn] Error details:", {
643
+ name: error.name,
644
+ message: error.message,
645
+ stack: error.stack
646
+ });
647
+ }
648
+ return {
649
+ success: false,
650
+ error: ERROR_MESSAGES.PASSKEY_REGISTRATION_FAILED
651
+ };
652
+ }
653
+ };
654
+ var authenticateWithPasskey = async (credentialId) => {
655
+ logger3.info("[WebAuthn] authenticateWithPasskey() called with credentialId:", credentialId);
656
+ if (!isWebAuthnSupported()) {
657
+ logger3.error("[WebAuthn] WebAuthn not supported");
658
+ return {
659
+ success: false,
660
+ error: ERROR_MESSAGES.WEBAUTHN_NOT_SUPPORTED
661
+ };
662
+ }
663
+ try {
664
+ logger3.info("[WebAuthn] Generating challenge for authentication...");
665
+ const challenge = generateRandomBytes(32);
666
+ const challengeBase64 = uint8ArrayToBase64(challenge);
667
+ const allowCredentials = credentialId ? [
668
+ {
669
+ id: credentialId,
670
+ type: "public-key"
671
+ }
672
+ ] : void 0;
673
+ const authenticationOptions = {
674
+ challenge: challengeBase64,
675
+ rpId: WEBAUTHN_RP_ID,
676
+ timeout: WEBAUTHN_TIMEOUT_MS,
677
+ userVerification: WEBAUTHN_USER_VERIFICATION,
678
+ allowCredentials
679
+ };
680
+ logger3.info("[WebAuthn] Authentication options:", {
681
+ rpId: authenticationOptions.rpId,
682
+ timeout: authenticationOptions.timeout,
683
+ hasAllowedCredentials: !!allowCredentials
684
+ });
685
+ logger3.info("[WebAuthn] Starting WebAuthn authentication ceremony...");
686
+ const assertion = await (0, import_browser.startAuthentication)({ optionsJSON: authenticationOptions });
687
+ logger3.info("[WebAuthn] Authentication ceremony completed, assertion received");
688
+ const authenticatorData = base64urlToUint8Array(assertion.response.authenticatorData);
689
+ const clientDataJSON = base64urlToUint8Array(assertion.response.clientDataJSON);
690
+ const signature = base64urlToUint8Array(assertion.response.signature);
691
+ logger3.info("[WebAuthn] Authentication successful");
692
+ return {
693
+ success: true,
694
+ authenticatorData,
695
+ clientDataJSON,
696
+ signature
697
+ };
698
+ } catch (error) {
699
+ logger3.error("[WebAuthn] Authentication failed with error:", error);
700
+ if (error instanceof Error && error.name === "NotAllowedError") {
701
+ logger3.warn("[WebAuthn] User cancelled the authentication");
702
+ return {
703
+ success: false,
704
+ error: ERROR_MESSAGES.USER_CANCELLED
705
+ };
706
+ }
707
+ if (error instanceof Error) {
708
+ logger3.error("[WebAuthn] Error details:", {
709
+ name: error.name,
710
+ message: error.message,
711
+ stack: error.stack
712
+ });
713
+ }
714
+ return {
715
+ success: false,
716
+ error: ERROR_MESSAGES.PASSKEY_AUTHENTICATION_FAILED
717
+ };
718
+ }
719
+ };
720
+ var authenticateWithChallenge = async (credentialId, challenge) => {
721
+ logger3.info("[WebAuthn] authenticateWithChallenge() called");
722
+ logger3.info("[WebAuthn] Challenge length:", challenge.length, "bytes");
723
+ if (!isWebAuthnSupported()) {
724
+ logger3.error("[WebAuthn] WebAuthn not supported");
725
+ return {
726
+ success: false,
727
+ error: ERROR_MESSAGES.WEBAUTHN_NOT_SUPPORTED
728
+ };
729
+ }
730
+ if (!credentialId) {
731
+ logger3.error("[WebAuthn] No credential ID provided");
732
+ return {
733
+ success: false,
734
+ error: "Credential ID is required for authentication"
735
+ };
736
+ }
737
+ try {
738
+ const challengeBase64 = uint8ArrayToBase64(challenge);
739
+ const authenticationOptions = {
740
+ challenge: challengeBase64,
741
+ rpId: WEBAUTHN_RP_ID,
742
+ timeout: WEBAUTHN_TIMEOUT_MS,
743
+ userVerification: WEBAUTHN_USER_VERIFICATION,
744
+ allowCredentials: [
745
+ {
746
+ id: credentialId,
747
+ type: "public-key"
748
+ }
749
+ ]
750
+ };
751
+ logger3.info("[WebAuthn] Authentication options with custom challenge:", {
752
+ rpId: authenticationOptions.rpId,
753
+ timeout: authenticationOptions.timeout,
754
+ challengePreview: challengeBase64.substring(0, 16) + "..."
755
+ });
756
+ logger3.info("[WebAuthn] Starting WebAuthn authentication ceremony with custom challenge...");
757
+ const assertion = await (0, import_browser.startAuthentication)({ optionsJSON: authenticationOptions });
758
+ logger3.info("[WebAuthn] Authentication ceremony completed, assertion received");
759
+ const authenticatorData = base64urlToUint8Array(assertion.response.authenticatorData);
760
+ const clientDataJSON = base64urlToUint8Array(assertion.response.clientDataJSON);
761
+ const signature = base64urlToUint8Array(assertion.response.signature);
762
+ logger3.info("[WebAuthn] Authentication with custom challenge successful");
763
+ return {
764
+ success: true,
765
+ authenticatorData,
766
+ clientDataJSON,
767
+ signature
768
+ };
769
+ } catch (error) {
770
+ logger3.error("[WebAuthn] Authentication with custom challenge failed:", error);
771
+ if (error instanceof Error && error.name === "NotAllowedError") {
772
+ logger3.warn("[WebAuthn] User cancelled the authentication");
773
+ return {
774
+ success: false,
775
+ error: ERROR_MESSAGES.USER_CANCELLED
776
+ };
777
+ }
778
+ if (error instanceof Error) {
779
+ logger3.error("[WebAuthn] Error details:", {
780
+ name: error.name,
781
+ message: error.message,
782
+ stack: error.stack
783
+ });
784
+ }
785
+ return {
786
+ success: false,
787
+ error: ERROR_MESSAGES.PASSKEY_AUTHENTICATION_FAILED
788
+ };
789
+ }
790
+ };
791
+
792
+ // src/keystore.ts
793
+ var import_idb_keyval = require("idb-keyval");
794
+ var customStore = null;
795
+ var getStore = () => {
796
+ if (!customStore) {
797
+ customStore = (0, import_idb_keyval.createStore)(IDB_DATABASE_NAME, IDB_STORE_NAME);
798
+ }
799
+ return customStore;
800
+ };
801
+ var hasStoredWallet = async () => {
802
+ try {
803
+ const metadata = await (0, import_idb_keyval.get)(STORAGE_KEY_METADATA, getStore());
804
+ return metadata !== void 0;
805
+ } catch {
806
+ return false;
807
+ }
808
+ };
809
+ var storeWalletMetadata = async (metadata) => {
810
+ await (0, import_idb_keyval.set)(STORAGE_KEY_METADATA, metadata, getStore());
811
+ };
812
+ var getWalletMetadata = async () => {
813
+ try {
814
+ const data = await (0, import_idb_keyval.get)(STORAGE_KEY_METADATA, getStore());
815
+ return data ?? null;
816
+ } catch {
817
+ return null;
818
+ }
819
+ };
820
+ var deleteWalletMetadata = async () => {
821
+ await (0, import_idb_keyval.del)(STORAGE_KEY_METADATA, getStore());
822
+ };
823
+ var storeCredentialId = async (credentialId) => {
824
+ await (0, import_idb_keyval.set)(STORAGE_KEY_CREDENTIAL_ID, credentialId, getStore());
825
+ };
826
+ var getCredentialId = async () => {
827
+ try {
828
+ const id = await (0, import_idb_keyval.get)(STORAGE_KEY_CREDENTIAL_ID, getStore());
829
+ return id ?? null;
830
+ } catch {
831
+ return null;
832
+ }
833
+ };
834
+ var deleteCredentialId = async () => {
835
+ await (0, import_idb_keyval.del)(STORAGE_KEY_CREDENTIAL_ID, getStore());
836
+ };
837
+ var clearAllData = async () => {
838
+ await Promise.all([
839
+ deleteWalletMetadata(),
840
+ deleteCredentialId()
841
+ ]);
842
+ };
843
+
844
+ // src/rpc.ts
845
+ var import_kaspa_wasm3 = require("@onekeyfe/kaspa-wasm");
846
+ var KaspaRpc = class {
847
+ rpc = null;
848
+ network = null;
849
+ eventHandlers = {};
850
+ /**
851
+ * Check if connected to the network
852
+ */
853
+ get isConnected() {
854
+ return this.rpc !== null;
855
+ }
856
+ /**
857
+ * Get the current network
858
+ */
859
+ get currentNetwork() {
860
+ return this.network;
861
+ }
862
+ /**
863
+ * Get the underlying RpcClient instance
864
+ * Only use for advanced operations
865
+ */
866
+ get client() {
867
+ return this.rpc;
868
+ }
869
+ /**
870
+ * Set event handlers for connection events
871
+ */
872
+ setEventHandlers(handlers) {
873
+ this.eventHandlers = { ...this.eventHandlers, ...handlers };
874
+ }
875
+ /**
876
+ * Connect to the Kaspa network
877
+ *
878
+ * @param options - Connection options
879
+ * @throws Error if connection fails
880
+ */
881
+ async connect(options) {
882
+ await ensureWasmInitialized();
883
+ const { network, url, timeout = RPC_TIMEOUT_MS } = options;
884
+ const networkType = getNetworkType(network);
885
+ if (this.rpc) {
886
+ await this.disconnect();
887
+ }
888
+ try {
889
+ if (url) {
890
+ this.rpc = new import_kaspa_wasm3.RpcClient({
891
+ url,
892
+ networkId: networkType
893
+ });
894
+ } else {
895
+ this.rpc = new import_kaspa_wasm3.RpcClient({
896
+ resolver: new import_kaspa_wasm3.Resolver(),
897
+ networkId: networkType
898
+ });
899
+ }
900
+ this.rpc.addEventListener("connect", (event) => {
901
+ this.eventHandlers.onConnect?.(event?.url || "unknown");
902
+ });
903
+ this.rpc.addEventListener("disconnect", () => {
904
+ this.eventHandlers.onDisconnect?.();
905
+ });
906
+ await Promise.race([
907
+ this.rpc.connect(),
908
+ new Promise(
909
+ (_, reject) => setTimeout(() => reject(new Error("Connection timeout")), timeout)
910
+ )
911
+ ]);
912
+ this.network = network;
913
+ } catch (error) {
914
+ this.rpc = null;
915
+ this.network = null;
916
+ const message = error instanceof Error ? error.message : String(error);
917
+ this.eventHandlers.onError?.(message);
918
+ throw new Error(`${ERROR_MESSAGES.RPC_CONNECTION_FAILED}: ${message}`);
919
+ }
920
+ }
921
+ /**
922
+ * Disconnect from the network
923
+ */
924
+ async disconnect() {
925
+ if (this.rpc) {
926
+ try {
927
+ await this.rpc.disconnect();
928
+ } catch {
929
+ }
930
+ this.rpc = null;
931
+ this.network = null;
932
+ }
933
+ }
934
+ /**
935
+ * Ensure RPC is connected, throw if not
936
+ */
937
+ ensureConnected() {
938
+ if (!this.rpc) {
939
+ throw new Error(ERROR_MESSAGES.RPC_NOT_CONNECTED);
940
+ }
941
+ return this.rpc;
942
+ }
943
+ /**
944
+ * Get balance for an address
945
+ *
946
+ * @param address - Kaspa address
947
+ * @returns Balance info with available, pending, and total amounts
948
+ */
949
+ async getBalance(address) {
950
+ const rpc = this.ensureConnected();
951
+ const response = await rpc.getBalanceByAddress({ address });
952
+ const available = response.balance;
953
+ return {
954
+ available,
955
+ pending: 0n,
956
+ total: available
957
+ };
958
+ }
959
+ /**
960
+ * Get UTXOs for an address
961
+ *
962
+ * @param address - Kaspa address
963
+ * @returns Array of UTXO entries
964
+ */
965
+ async getUtxos(address) {
966
+ const rpc = this.ensureConnected();
967
+ const response = await rpc.getUtxosByAddresses({ addresses: [address] });
968
+ return response.entries;
969
+ }
970
+ /**
971
+ * Get UTXOs for multiple addresses
972
+ *
973
+ * @param addresses - Array of Kaspa addresses
974
+ * @returns Array of UTXO entries
975
+ */
976
+ async getUtxosForAddresses(addresses) {
977
+ const rpc = this.ensureConnected();
978
+ const response = await rpc.getUtxosByAddresses({ addresses });
979
+ return response.entries;
980
+ }
981
+ /**
982
+ * Submit a signed transaction to the network
983
+ *
984
+ * @param transaction - Signed transaction object
985
+ * @param allowOrphan - Allow orphan transactions (default: false)
986
+ * @returns Transaction ID
987
+ */
988
+ async submitTransaction(transaction, allowOrphan = false) {
989
+ const rpc = this.ensureConnected();
990
+ const response = await rpc.submitTransaction({
991
+ transaction,
992
+ allowOrphan
993
+ });
994
+ return response.transactionId;
995
+ }
996
+ /**
997
+ * Get the current block count
998
+ */
999
+ async getBlockCount() {
1000
+ const rpc = this.ensureConnected();
1001
+ const response = await rpc.getBlockCount();
1002
+ return response.blockCount;
1003
+ }
1004
+ /**
1005
+ * Get network info (DAG info)
1006
+ */
1007
+ async getNetworkInfo() {
1008
+ const rpc = this.ensureConnected();
1009
+ const response = await rpc.getBlockDagInfo();
1010
+ return {
1011
+ network: response.network,
1012
+ blockCount: response.blockCount,
1013
+ headerCount: response.headerCount,
1014
+ tipHashes: response.tipHashes,
1015
+ difficulty: response.difficulty,
1016
+ pastMedianTime: response.pastMedianTime,
1017
+ virtualParentHashes: response.virtualParentHashes,
1018
+ pruningPointHash: response.pruningPointHash,
1019
+ virtualDaaScore: response.virtualDaaScore,
1020
+ sink: response.sink
1021
+ };
1022
+ }
1023
+ /**
1024
+ * Get the current server info
1025
+ */
1026
+ async getServerInfo() {
1027
+ const rpc = this.ensureConnected();
1028
+ const response = await rpc.getServerInfo();
1029
+ return {
1030
+ serverVersion: response.serverVersion,
1031
+ rpcApiVersion: response.rpcApiVersion,
1032
+ isSynced: response.isSynced,
1033
+ hasUtxoIndex: response.hasUtxoIndex
1034
+ };
1035
+ }
1036
+ };
1037
+ var defaultRpcInstance = null;
1038
+ var getDefaultRpc = () => {
1039
+ if (!defaultRpcInstance) {
1040
+ defaultRpcInstance = new KaspaRpc();
1041
+ }
1042
+ return defaultRpcInstance;
1043
+ };
1044
+ var resetDefaultRpc = async () => {
1045
+ if (defaultRpcInstance) {
1046
+ await defaultRpcInstance.disconnect();
1047
+ defaultRpcInstance = null;
1048
+ }
1049
+ };
1050
+
1051
+ // src/transaction.ts
1052
+ var import_kaspa_wasm4 = require("@onekeyfe/kaspa-wasm");
1053
+ var import_kaspa_wasm5 = require("@onekeyfe/kaspa-wasm");
1054
+ var buildTransactions = async (utxos, outputs, changeAddress, priorityFee = DEFAULT_PRIORITY_FEE_SOMPI, network) => {
1055
+ await ensureWasmInitialized();
1056
+ const settings = {
1057
+ outputs,
1058
+ changeAddress,
1059
+ priorityFee,
1060
+ entries: utxos,
1061
+ networkId: getNetworkType(network)
1062
+ };
1063
+ const result = await (0, import_kaspa_wasm4.createTransactions)(settings);
1064
+ return {
1065
+ transactions: result.transactions,
1066
+ summary: {
1067
+ transactionCount: result.summary.transactions,
1068
+ fees: result.summary.fees,
1069
+ utxoCount: result.summary.utxos,
1070
+ finalAmount: result.summary.finalAmount
1071
+ }
1072
+ };
1073
+ };
1074
+ var estimateFee = async (utxos, outputs, changeAddress, priorityFee = DEFAULT_PRIORITY_FEE_SOMPI, network) => {
1075
+ await ensureWasmInitialized();
1076
+ const settings = {
1077
+ outputs,
1078
+ changeAddress,
1079
+ priorityFee,
1080
+ entries: utxos,
1081
+ networkId: getNetworkType(network)
1082
+ };
1083
+ const summary = await (0, import_kaspa_wasm4.estimateTransactions)(settings);
1084
+ return {
1085
+ transactionCount: summary.transactions,
1086
+ fees: summary.fees,
1087
+ utxoCount: summary.utxos,
1088
+ finalAmount: summary.finalAmount ?? 0n
1089
+ };
1090
+ };
1091
+ var signTransactions = async (transactions, privateKeyHex) => {
1092
+ await ensureWasmInitialized();
1093
+ const privateKey = await createPrivateKey(privateKeyHex);
1094
+ for (const tx of transactions) {
1095
+ tx.sign([privateKey]);
1096
+ }
1097
+ return transactions;
1098
+ };
1099
+ var submitTransactions = async (transactions, rpc) => {
1100
+ if (!rpc.client) {
1101
+ throw new Error(ERROR_MESSAGES.RPC_NOT_CONNECTED);
1102
+ }
1103
+ const transactionIds = [];
1104
+ for (const tx of transactions) {
1105
+ const txId = await tx.submit(rpc.client);
1106
+ transactionIds.push(txId);
1107
+ }
1108
+ return transactionIds;
1109
+ };
1110
+ var sendTransaction = async (options, utxos, changeAddress, privateKeyHex, rpc, network) => {
1111
+ await ensureWasmInitialized();
1112
+ const { to, amount, priorityFee = DEFAULT_PRIORITY_FEE_SOMPI } = options;
1113
+ if (!rpc.isConnected) {
1114
+ throw new Error(ERROR_MESSAGES.RPC_NOT_CONNECTED);
1115
+ }
1116
+ const outputs = [{ address: to, amount }];
1117
+ const { transactions, summary } = await buildTransactions(
1118
+ utxos,
1119
+ outputs,
1120
+ changeAddress,
1121
+ priorityFee,
1122
+ network
1123
+ );
1124
+ if (transactions.length === 0) {
1125
+ throw new Error(ERROR_MESSAGES.TRANSACTION_FAILED);
1126
+ }
1127
+ const signedTransactions = await signTransactions(transactions, privateKeyHex);
1128
+ const transactionIds = await submitTransactions(signedTransactions, rpc);
1129
+ return {
1130
+ transactionId: transactionIds[0],
1131
+ amount,
1132
+ fee: summary.fees
1133
+ };
1134
+ };
1135
+ var createGenerator = async (settings) => {
1136
+ await ensureWasmInitialized();
1137
+ return new import_kaspa_wasm4.Generator(settings);
1138
+ };
1139
+
1140
+ // src/wallet.ts
1141
+ var logger4 = createLogger("PasskeyWallet");
1142
+ var PasskeyWallet = class _PasskeyWallet {
1143
+ privateKeyHex;
1144
+ publicKeyHex;
1145
+ address;
1146
+ network;
1147
+ eventHandlers = /* @__PURE__ */ new Set();
1148
+ rpc;
1149
+ credentialId = null;
1150
+ passkeyPublicKey = null;
1151
+ constructor(privateKeyHex, publicKeyHex, address, network, credentialId, passkeyPublicKey) {
1152
+ this.privateKeyHex = privateKeyHex;
1153
+ this.publicKeyHex = publicKeyHex;
1154
+ this.address = address;
1155
+ this.network = network;
1156
+ this.credentialId = credentialId ?? null;
1157
+ this.passkeyPublicKey = passkeyPublicKey ?? null;
1158
+ this.rpc = new KaspaRpc();
1159
+ this.rpc.setEventHandlers({
1160
+ onConnect: (url) => {
1161
+ logger4.info(`Connected to Kaspa network: ${url}`);
1162
+ },
1163
+ onDisconnect: () => {
1164
+ logger4.info("Disconnected from Kaspa network");
1165
+ },
1166
+ onError: (error) => {
1167
+ logger4.error("RPC error:", error);
1168
+ }
1169
+ });
1170
+ }
1171
+ // ===========================================================================
1172
+ // Static Factory Methods
1173
+ // ===========================================================================
1174
+ /**
1175
+ * Check if WebAuthn/passkeys are supported in the current browser
1176
+ */
1177
+ static isSupported() {
1178
+ return isWebAuthnSupported();
1179
+ }
1180
+ /**
1181
+ * Check if a wallet already exists in storage
1182
+ */
1183
+ static async exists() {
1184
+ return hasStoredWallet();
1185
+ }
1186
+ /**
1187
+ * Create a new passkey-protected wallet
1188
+ *
1189
+ * @param options - Configuration options
1190
+ * @returns Result containing the wallet instance or error
1191
+ */
1192
+ static async create(options = {}) {
1193
+ logger4.info("create() called with options:", options);
1194
+ const { name = "KasFlow Wallet", network = DEFAULT_NETWORK } = options;
1195
+ logger4.debug("Checking if wallet already exists...");
1196
+ if (await hasStoredWallet()) {
1197
+ logger4.warn("Wallet already exists in storage");
1198
+ return {
1199
+ success: false,
1200
+ error: ERROR_MESSAGES.WALLET_ALREADY_EXISTS
1201
+ };
1202
+ }
1203
+ logger4.debug("Checking WebAuthn support...");
1204
+ if (!isWebAuthnSupported()) {
1205
+ logger4.error("WebAuthn not supported");
1206
+ return {
1207
+ success: false,
1208
+ error: ERROR_MESSAGES.WEBAUTHN_NOT_SUPPORTED
1209
+ };
1210
+ }
1211
+ logger4.debug("WebAuthn is supported");
1212
+ logger4.info("Starting passkey registration...");
1213
+ const registration = await registerPasskey(name);
1214
+ logger4.debug("Registration result:", {
1215
+ success: registration.success,
1216
+ hasCredential: !!registration.credential,
1217
+ hasPasskeyPublicKey: !!registration.passkeyPublicKey,
1218
+ error: registration.error
1219
+ });
1220
+ if (!registration.success || !registration.credential || !registration.passkeyPublicKey) {
1221
+ logger4.error("Passkey registration failed:", registration.error);
1222
+ return {
1223
+ success: false,
1224
+ error: registration.error ?? ERROR_MESSAGES.PASSKEY_REGISTRATION_FAILED
1225
+ };
1226
+ }
1227
+ logger4.info("Deriving Kaspa keys from passkey public key...");
1228
+ const { privateKeyHex } = deriveKaspaKeysFromPasskey(registration.passkeyPublicKey);
1229
+ logger4.debug("Getting public key...");
1230
+ const publicKeyHex = await getPublicKeyHex(privateKeyHex);
1231
+ logger4.info("Generating addresses for all networks...");
1232
+ const addresses = {
1233
+ [NETWORK_ID.MAINNET]: await getAddressFromPrivateKey(privateKeyHex, NETWORK_ID.MAINNET),
1234
+ [NETWORK_ID.TESTNET_10]: await getAddressFromPrivateKey(privateKeyHex, NETWORK_ID.TESTNET_10),
1235
+ [NETWORK_ID.TESTNET_11]: await getAddressFromPrivateKey(privateKeyHex, NETWORK_ID.TESTNET_11)
1236
+ };
1237
+ const address = addresses[network];
1238
+ logger4.info("Wallet created for all networks:", {
1239
+ primaryNetwork: network,
1240
+ mainnetAddress: addresses[NETWORK_ID.MAINNET],
1241
+ testnet10Address: addresses[NETWORK_ID.TESTNET_10],
1242
+ testnet11Address: addresses[NETWORK_ID.TESTNET_11],
1243
+ publicKeyPrefix: publicKeyHex.substring(0, 10) + "..."
1244
+ });
1245
+ const passkeyPublicKeyBase64 = uint8ArrayToBase64(registration.passkeyPublicKey);
1246
+ logger4.debug("Storing wallet metadata...");
1247
+ await storeWalletMetadata({
1248
+ passkeyPublicKey: passkeyPublicKeyBase64,
1249
+ addresses,
1250
+ primaryNetwork: network,
1251
+ createdAt: Date.now(),
1252
+ // Backward compatibility fields
1253
+ address,
1254
+ network
1255
+ });
1256
+ await storeCredentialId(registration.credential.id);
1257
+ logger4.debug("Wallet metadata stored successfully");
1258
+ logger4.debug("Creating wallet instance...");
1259
+ const wallet = new _PasskeyWallet(
1260
+ privateKeyHex,
1261
+ publicKeyHex,
1262
+ address,
1263
+ network,
1264
+ registration.credential.id,
1265
+ registration.passkeyPublicKey
1266
+ );
1267
+ wallet.emit({ type: "connected", address });
1268
+ logger4.info("Wallet created successfully!");
1269
+ return {
1270
+ success: true,
1271
+ data: wallet
1272
+ };
1273
+ }
1274
+ /**
1275
+ * Unlock an existing wallet using passkey authentication
1276
+ *
1277
+ * @param options - Configuration options
1278
+ * @returns Result containing the wallet instance or error
1279
+ */
1280
+ static async unlock(options = {}) {
1281
+ logger4.info("unlock() called with options:", options);
1282
+ logger4.debug("Checking if wallet exists in storage...");
1283
+ if (!await hasStoredWallet()) {
1284
+ logger4.error("No wallet found in storage");
1285
+ return {
1286
+ success: false,
1287
+ error: ERROR_MESSAGES.WALLET_NOT_FOUND
1288
+ };
1289
+ }
1290
+ logger4.debug("Wallet exists in storage");
1291
+ logger4.debug("Getting stored credential ID and wallet metadata...");
1292
+ const credentialId = await getCredentialId();
1293
+ const metadata = await getWalletMetadata();
1294
+ logger4.debug("Stored credential ID:", credentialId);
1295
+ logger4.debug("Wallet metadata:", metadata ? "found" : "not found");
1296
+ if (!metadata || !metadata.passkeyPublicKey) {
1297
+ logger4.error("No passkey public key found in metadata");
1298
+ return {
1299
+ success: false,
1300
+ error: ERROR_MESSAGES.WALLET_NOT_FOUND
1301
+ };
1302
+ }
1303
+ logger4.info("Authenticating with passkey...");
1304
+ const auth = await authenticateWithPasskey(credentialId ?? void 0);
1305
+ logger4.debug("Authentication result:", {
1306
+ success: auth.success,
1307
+ error: auth.error
1308
+ });
1309
+ if (!auth.success) {
1310
+ logger4.error("Passkey authentication failed:", auth.error);
1311
+ return {
1312
+ success: false,
1313
+ error: auth.error ?? ERROR_MESSAGES.PASSKEY_AUTHENTICATION_FAILED
1314
+ };
1315
+ }
1316
+ logger4.info("Deriving keys from passkey public key...");
1317
+ const passkeyPublicKey = base64ToUint8Array(metadata.passkeyPublicKey);
1318
+ const { privateKeyHex } = deriveKaspaKeysFromPasskey(passkeyPublicKey);
1319
+ const publicKeyHex = await getPublicKeyHex(privateKeyHex);
1320
+ let currentMetadata = metadata;
1321
+ if (!metadata.addresses && metadata.address && metadata.network) {
1322
+ logger4.info("Migrating wallet to multi-network format...");
1323
+ const addresses = {
1324
+ [NETWORK_ID.MAINNET]: await getAddressFromPrivateKey(privateKeyHex, NETWORK_ID.MAINNET),
1325
+ [NETWORK_ID.TESTNET_10]: await getAddressFromPrivateKey(privateKeyHex, NETWORK_ID.TESTNET_10),
1326
+ [NETWORK_ID.TESTNET_11]: await getAddressFromPrivateKey(privateKeyHex, NETWORK_ID.TESTNET_11)
1327
+ };
1328
+ if (addresses[metadata.network] !== metadata.address) {
1329
+ logger4.error("Migration validation failed", {
1330
+ expectedAddress: metadata.address,
1331
+ derivedAddress: addresses[metadata.network],
1332
+ network: metadata.network
1333
+ });
1334
+ return {
1335
+ success: false,
1336
+ error: "Failed to migrate wallet to multi-network format"
1337
+ };
1338
+ }
1339
+ currentMetadata = {
1340
+ passkeyPublicKey: metadata.passkeyPublicKey,
1341
+ addresses,
1342
+ primaryNetwork: metadata.network,
1343
+ createdAt: metadata.createdAt,
1344
+ // Keep old fields for rollback safety
1345
+ address: metadata.address,
1346
+ network: metadata.network
1347
+ };
1348
+ await storeWalletMetadata(currentMetadata);
1349
+ logger4.info("Migration complete - wallet now supports all networks");
1350
+ }
1351
+ const network = options.network ?? currentMetadata.primaryNetwork ?? currentMetadata.network ?? NETWORK_ID.TESTNET_10;
1352
+ const address = await getAddressFromPrivateKey(privateKeyHex, network);
1353
+ const expectedAddress = currentMetadata.addresses ? currentMetadata.addresses[network] : currentMetadata.address;
1354
+ if (address !== expectedAddress) {
1355
+ logger4.error("Derived address mismatch for network", {
1356
+ network,
1357
+ expected: expectedAddress,
1358
+ derived: address
1359
+ });
1360
+ return {
1361
+ success: false,
1362
+ error: "Failed to derive correct wallet address for network"
1363
+ };
1364
+ }
1365
+ logger4.debug("Keys derived successfully, address verified for network:", network);
1366
+ const wallet = new _PasskeyWallet(
1367
+ privateKeyHex,
1368
+ publicKeyHex,
1369
+ address,
1370
+ network,
1371
+ credentialId ?? void 0,
1372
+ passkeyPublicKey
1373
+ );
1374
+ wallet.emit({ type: "connected", address });
1375
+ logger4.info("Wallet unlocked successfully!");
1376
+ return {
1377
+ success: true,
1378
+ data: wallet
1379
+ };
1380
+ }
1381
+ /**
1382
+ * Delete the wallet from storage
1383
+ * This is irreversible - make sure user has backed up their keys!
1384
+ */
1385
+ static async delete() {
1386
+ await clearAllData();
1387
+ return { success: true };
1388
+ }
1389
+ // ===========================================================================
1390
+ // Instance Methods - Basic Info
1391
+ // ===========================================================================
1392
+ /**
1393
+ * Get the wallet's Kaspa address
1394
+ */
1395
+ getAddress() {
1396
+ return this.address;
1397
+ }
1398
+ /**
1399
+ * Get the wallet's public key as hex string
1400
+ */
1401
+ getPublicKey() {
1402
+ return this.publicKeyHex;
1403
+ }
1404
+ /**
1405
+ * Get the current network
1406
+ */
1407
+ getNetwork() {
1408
+ return this.network;
1409
+ }
1410
+ /**
1411
+ * Sign a message with the wallet's private key
1412
+ * Uses Kaspa's message signing scheme
1413
+ *
1414
+ * @param message - Message to sign
1415
+ * @returns Signature as hex string
1416
+ */
1417
+ async signMessage(message) {
1418
+ return await signMessageWithKey(message, this.privateKeyHex);
1419
+ }
1420
+ /**
1421
+ * Get the private key hex (for advanced usage like transaction signing)
1422
+ * WARNING: Handle with care - this is sensitive data
1423
+ */
1424
+ getPrivateKeyHex() {
1425
+ return this.privateKeyHex;
1426
+ }
1427
+ // ===========================================================================
1428
+ // Network Connection
1429
+ // ===========================================================================
1430
+ /**
1431
+ * Check if connected to the network
1432
+ */
1433
+ get isConnected() {
1434
+ return this.rpc.isConnected;
1435
+ }
1436
+ /**
1437
+ * Get the underlying RPC client for advanced usage
1438
+ */
1439
+ getRpcClient() {
1440
+ return this.rpc;
1441
+ }
1442
+ /**
1443
+ * Connect to the Kaspa network
1444
+ *
1445
+ * @param options - Optional connection options (defaults to current network with public resolver)
1446
+ */
1447
+ async connect(options) {
1448
+ await this.rpc.connect({
1449
+ network: options?.network ?? this.network,
1450
+ url: options?.url,
1451
+ timeout: options?.timeout
1452
+ });
1453
+ }
1454
+ /**
1455
+ * Disconnect from the Kaspa network
1456
+ */
1457
+ async disconnectNetwork() {
1458
+ await this.rpc.disconnect();
1459
+ }
1460
+ /**
1461
+ * Switch to a different network without re-authentication
1462
+ * Uses pre-computed addresses from wallet creation
1463
+ *
1464
+ * @param network - Target network to switch to
1465
+ * @returns Result with success status
1466
+ */
1467
+ async switchNetwork(network) {
1468
+ logger4.info("switchNetwork() called:", { from: this.network, to: network });
1469
+ if (network === this.network) {
1470
+ logger4.info("Already on target network");
1471
+ return { success: true };
1472
+ }
1473
+ const metadata = await getWalletMetadata();
1474
+ if (!metadata || !metadata.addresses || !metadata.addresses[network]) {
1475
+ logger4.error("Address for target network not found in metadata");
1476
+ return {
1477
+ success: false,
1478
+ error: `Address for network ${network} not found. Wallet may need to be recreated.`
1479
+ };
1480
+ }
1481
+ const newAddress = await getAddressFromPrivateKey(this.privateKeyHex, network);
1482
+ if (newAddress !== metadata.addresses[network]) {
1483
+ logger4.error("Derived address does not match stored address for network", {
1484
+ network,
1485
+ stored: metadata.addresses[network],
1486
+ derived: newAddress
1487
+ });
1488
+ return {
1489
+ success: false,
1490
+ error: "Failed to validate address for network switch"
1491
+ };
1492
+ }
1493
+ if (this.isConnected) {
1494
+ logger4.debug("Disconnecting from current network...");
1495
+ await this.disconnectNetwork();
1496
+ }
1497
+ this.network = network;
1498
+ this.address = newAddress;
1499
+ logger4.info("Network switched successfully:", { network, address: newAddress });
1500
+ this.emit({ type: "connected", address: newAddress });
1501
+ return { success: true };
1502
+ }
1503
+ // ===========================================================================
1504
+ // Balance & UTXOs
1505
+ // ===========================================================================
1506
+ /**
1507
+ * Get the wallet's balance
1508
+ *
1509
+ * @returns Balance info with available, pending, and total amounts
1510
+ * @throws Error if not connected to network
1511
+ */
1512
+ async getBalance() {
1513
+ if (!this.rpc.isConnected) {
1514
+ throw new Error(ERROR_MESSAGES.RPC_NOT_CONNECTED);
1515
+ }
1516
+ const balance = await this.rpc.getBalance(this.address);
1517
+ this.emit({ type: "balance_updated", balance });
1518
+ return balance;
1519
+ }
1520
+ // ===========================================================================
1521
+ // Transactions
1522
+ // ===========================================================================
1523
+ /**
1524
+ * Send KAS to an address
1525
+ *
1526
+ * @param options - Send options (to, amount, priorityFee)
1527
+ * @returns Send result with transaction ID and details
1528
+ * @throws Error if not connected or insufficient balance
1529
+ *
1530
+ * @example
1531
+ * ```typescript
1532
+ * const result = await wallet.send({
1533
+ * to: 'kaspatest:qz...',
1534
+ * amount: 100000000n, // 1 KAS
1535
+ * });
1536
+ * console.log('Transaction ID:', result.transactionId);
1537
+ * ```
1538
+ */
1539
+ async send(options) {
1540
+ if (!this.rpc.isConnected) {
1541
+ throw new Error(ERROR_MESSAGES.RPC_NOT_CONNECTED);
1542
+ }
1543
+ const utxos = await this.rpc.getUtxos(this.address);
1544
+ const totalAvailable = utxos.reduce((sum, utxo) => sum + utxo.amount, 0n);
1545
+ if (totalAvailable < options.amount) {
1546
+ throw new Error(ERROR_MESSAGES.INSUFFICIENT_BALANCE);
1547
+ }
1548
+ const result = await sendTransaction(
1549
+ options,
1550
+ utxos,
1551
+ this.address,
1552
+ // Use same address for change
1553
+ this.privateKeyHex,
1554
+ this.rpc,
1555
+ this.network
1556
+ );
1557
+ this.emit({ type: "transaction_sent", txId: result.transactionId });
1558
+ return result;
1559
+ }
1560
+ /**
1561
+ * Send transaction with per-transaction passkey authentication
1562
+ *
1563
+ * **RECOMMENDED:** Use this method instead of `send()` for better security.
1564
+ *
1565
+ * This method prompts for passkey authentication for EACH transaction, using the
1566
+ * transaction hash as the WebAuthn challenge. This provides cryptographic proof
1567
+ * that the user approved THIS specific transaction.
1568
+ *
1569
+ * Benefits over `send()`:
1570
+ * - Per-transaction authentication (user approves each transaction)
1571
+ * - Transaction hash cryptographically bound to WebAuthn signature
1572
+ * - Keys derived only when needed, not stored in memory
1573
+ * - Matches standard wallet UX (MetaMask, hardware wallets)
1574
+ *
1575
+ * @param options - Send options (to, amount, priorityFee)
1576
+ * @returns Transaction result with ID and fees
1577
+ * @throws Error if authentication fails, insufficient balance, or network error
1578
+ *
1579
+ * @example
1580
+ * ```typescript
1581
+ * const result = await wallet.sendWithAuth({
1582
+ * to: 'kaspatest:qz...',
1583
+ * amount: 100000000n, // 1 KAS
1584
+ * });
1585
+ * console.log('Transaction ID:', result.transactionId);
1586
+ * ```
1587
+ */
1588
+ async sendWithAuth(options) {
1589
+ if (!this.rpc.isConnected) {
1590
+ throw new Error(ERROR_MESSAGES.RPC_NOT_CONNECTED);
1591
+ }
1592
+ if (!this.credentialId) {
1593
+ throw new Error("Credential ID not available. Cannot authenticate transaction.");
1594
+ }
1595
+ if (!this.passkeyPublicKey) {
1596
+ throw new Error("Passkey public key not available. Cannot derive signing key.");
1597
+ }
1598
+ logger4.info("[PasskeyWallet] sendWithAuth() - Starting per-transaction auth flow");
1599
+ const utxos = await this.rpc.getUtxos(this.address);
1600
+ const totalAvailable = utxos.reduce((sum, utxo) => sum + utxo.amount, 0n);
1601
+ if (totalAvailable < options.amount) {
1602
+ throw new Error(ERROR_MESSAGES.INSUFFICIENT_BALANCE);
1603
+ }
1604
+ const outputs = [
1605
+ {
1606
+ address: options.to,
1607
+ amount: options.amount
1608
+ }
1609
+ ];
1610
+ const buildResult = await buildTransactions(
1611
+ utxos,
1612
+ outputs,
1613
+ this.address,
1614
+ options.priorityFee,
1615
+ this.network
1616
+ );
1617
+ const { transactions, summary } = buildResult;
1618
+ logger4.info("[PasskeyWallet] Built unsigned transactions:", {
1619
+ count: transactions.length,
1620
+ totalFee: summary.fees.toString()
1621
+ });
1622
+ const signedTransactions = [];
1623
+ for (let i = 0; i < transactions.length; i++) {
1624
+ const tx = transactions[i];
1625
+ logger4.info(`[PasskeyWallet] Processing transaction ${i + 1}/${transactions.length}`);
1626
+ const txHash = computeTransactionHash(tx);
1627
+ logger4.info("[PasskeyWallet] Transaction hash computed:", {
1628
+ length: txHash.length,
1629
+ previewHex: Array.from(txHash.slice(0, 8)).map((b) => b.toString(16).padStart(2, "0")).join("")
1630
+ });
1631
+ logger4.info(
1632
+ "[PasskeyWallet] Requesting passkey authentication for this transaction..."
1633
+ );
1634
+ const authResult = await authenticateWithChallenge(this.credentialId, txHash);
1635
+ if (!authResult.success) {
1636
+ throw new Error(
1637
+ authResult.error ?? "Passkey authentication failed for transaction"
1638
+ );
1639
+ }
1640
+ logger4.info("[PasskeyWallet] User authenticated - passkey signature received");
1641
+ const verified = this.verifyAuthenticationForTransaction(authResult, txHash);
1642
+ if (!verified) {
1643
+ throw new Error(
1644
+ "Authentication signature does not match transaction. Possible security issue."
1645
+ );
1646
+ }
1647
+ logger4.info("[PasskeyWallet] \u2713 WebAuthn signature verified for this transaction");
1648
+ const { privateKeyHex } = deriveKaspaKeysFromPasskey(this.passkeyPublicKey);
1649
+ logger4.info("[PasskeyWallet] Derived secp256k1 signing key for this transaction");
1650
+ await signTransactions([tx], privateKeyHex);
1651
+ logger4.info("[PasskeyWallet] Transaction signed successfully");
1652
+ signedTransactions.push(tx);
1653
+ }
1654
+ logger4.info("[PasskeyWallet] All transactions signed successfully");
1655
+ const transactionIds = await submitTransactions(signedTransactions, this.rpc);
1656
+ logger4.info("[PasskeyWallet] Transactions submitted:", transactionIds);
1657
+ this.emit({
1658
+ type: "transaction_sent",
1659
+ txId: transactionIds[0]
1660
+ });
1661
+ return {
1662
+ transactionId: transactionIds[0],
1663
+ amount: options.amount,
1664
+ fee: summary.fees
1665
+ };
1666
+ }
1667
+ /**
1668
+ * Estimate the fee for a transaction
1669
+ *
1670
+ * @param options - Send options (to, amount, priorityFee)
1671
+ * @returns Transaction estimate with fees and UTXO count
1672
+ * @throws Error if not connected
1673
+ */
1674
+ async estimateFee(options) {
1675
+ if (!this.rpc.isConnected) {
1676
+ throw new Error(ERROR_MESSAGES.RPC_NOT_CONNECTED);
1677
+ }
1678
+ const utxos = await this.rpc.getUtxos(this.address);
1679
+ return estimateFee(
1680
+ utxos,
1681
+ [{ address: options.to, amount: options.amount }],
1682
+ this.address,
1683
+ options.priorityFee,
1684
+ this.network
1685
+ );
1686
+ }
1687
+ // ===========================================================================
1688
+ // Event Handling
1689
+ // ===========================================================================
1690
+ /**
1691
+ * Subscribe to wallet events
1692
+ *
1693
+ * @param handler - Event handler function
1694
+ * @returns Unsubscribe function
1695
+ */
1696
+ on(handler) {
1697
+ this.eventHandlers.add(handler);
1698
+ return () => this.eventHandlers.delete(handler);
1699
+ }
1700
+ /**
1701
+ * Emit an event to all subscribers
1702
+ */
1703
+ emit(event) {
1704
+ this.eventHandlers.forEach((handler) => {
1705
+ try {
1706
+ handler(event);
1707
+ } catch (error) {
1708
+ logger4.error("Event handler error:", error);
1709
+ }
1710
+ });
1711
+ }
1712
+ // ===========================================================================
1713
+ // Per-Transaction Authentication Helpers
1714
+ // ===========================================================================
1715
+ /**
1716
+ * Verify that WebAuthn authentication result is for specific transaction
1717
+ *
1718
+ * This method ensures the WebAuthn signature was created for THIS specific
1719
+ * transaction by verifying that the challenge in clientDataJSON matches the
1720
+ * transaction hash.
1721
+ *
1722
+ * This prevents replay attacks where an attacker could try to reuse a signature
1723
+ * from a different transaction.
1724
+ *
1725
+ * @param authResult - Authentication result from passkey
1726
+ * @param txHash - Transaction hash that should be in the challenge
1727
+ * @returns true if verified, false otherwise
1728
+ */
1729
+ verifyAuthenticationForTransaction(authResult, txHash) {
1730
+ try {
1731
+ const clientDataText = new TextDecoder().decode(authResult.clientDataJSON);
1732
+ const clientData = JSON.parse(clientDataText);
1733
+ logger4.debug("[PasskeyWallet] Verifying clientDataJSON:", {
1734
+ type: clientData.type,
1735
+ origin: clientData.origin,
1736
+ challengePreview: clientData.challenge?.substring(0, 16)
1737
+ });
1738
+ const challengeBase64url = clientData.challenge;
1739
+ if (!challengeBase64url) {
1740
+ logger4.error("[PasskeyWallet] No challenge found in clientDataJSON");
1741
+ return false;
1742
+ }
1743
+ const challengeBytes = base64urlToUint8Array(challengeBase64url);
1744
+ if (challengeBytes.length !== txHash.length) {
1745
+ logger4.error("[PasskeyWallet] Challenge length mismatch:", {
1746
+ expected: txHash.length,
1747
+ actual: challengeBytes.length
1748
+ });
1749
+ return false;
1750
+ }
1751
+ for (let i = 0; i < txHash.length; i++) {
1752
+ if (challengeBytes[i] !== txHash[i]) {
1753
+ logger4.error("[PasskeyWallet] Challenge byte mismatch at index:", i);
1754
+ return false;
1755
+ }
1756
+ }
1757
+ logger4.info("[PasskeyWallet] \u2713 WebAuthn challenge verified = transaction hash");
1758
+ if (clientData.type !== "webauthn.get") {
1759
+ logger4.error("[PasskeyWallet] Invalid clientData type:", clientData.type);
1760
+ return false;
1761
+ }
1762
+ return true;
1763
+ } catch (error) {
1764
+ logger4.error("[PasskeyWallet] Failed to verify authentication:", error);
1765
+ return false;
1766
+ }
1767
+ }
1768
+ /**
1769
+ * Disconnect the wallet (clears in-memory keys and network connection)
1770
+ */
1771
+ async disconnect() {
1772
+ await this.rpc.disconnect();
1773
+ this.privateKeyHex = "";
1774
+ this.emit({ type: "disconnected" });
1775
+ this.eventHandlers.clear();
1776
+ }
1777
+ };
1778
+
1779
+ // src/wasm.ts
1780
+ var import_kaspa_wasm6 = require("@onekeyfe/kaspa-wasm");
1781
+ var initDebugMode = async () => {
1782
+ const { initConsolePanicHook: initConsolePanicHook3 } = await import("@onekeyfe/kaspa-wasm");
1783
+ initConsolePanicHook3();
1784
+ };
1785
+ // Annotate the CommonJS export names for ESM import in node:
1786
+ 0 && (module.exports = {
1787
+ Address,
1788
+ DEFAULT_NETWORK,
1789
+ DEFAULT_PRIORITY_FEE_SOMPI,
1790
+ ERROR_MESSAGES,
1791
+ Generator,
1792
+ KaspaRpc,
1793
+ MIN_FEE_SOMPI,
1794
+ NETWORK_ID,
1795
+ NetworkType,
1796
+ PasskeyWallet,
1797
+ PendingTransaction,
1798
+ PrivateKey,
1799
+ PublicKey,
1800
+ Resolver,
1801
+ RpcClient,
1802
+ SOMPI_PER_KAS,
1803
+ Transaction,
1804
+ UtxoContext,
1805
+ UtxoEntryReference,
1806
+ UtxoProcessor,
1807
+ authenticateWithChallenge,
1808
+ buildTransactions,
1809
+ computeTransactionHash,
1810
+ createGenerator,
1811
+ createLogger,
1812
+ createPrivateKey,
1813
+ deriveKaspaKeysFromPasskey,
1814
+ estimateFee,
1815
+ extractPublicKeyFromAttestation,
1816
+ formatKas,
1817
+ generatePrivateKey,
1818
+ getAddressFromPrivateKey,
1819
+ getDefaultRpc,
1820
+ getNetworkFromAddress,
1821
+ getNetworkIdString,
1822
+ getNetworkType,
1823
+ getPublicKeyHex,
1824
+ hexToUint8Array,
1825
+ initDebugMode,
1826
+ isValidAddress,
1827
+ isValidPrivateKey,
1828
+ isWebAuthnSupported,
1829
+ kasStringToSompi,
1830
+ kasToSompi,
1831
+ parseAddress,
1832
+ parseKas,
1833
+ publicKeyToAddress,
1834
+ resetDefaultRpc,
1835
+ sendTransaction,
1836
+ signMessageWithKey,
1837
+ signTransaction,
1838
+ signTransactions,
1839
+ sompiToKas,
1840
+ sompiToKasString,
1841
+ sompiToKasStringWithSuffix,
1842
+ submitTransactions,
1843
+ uint8ArrayToHex,
1844
+ verifyDeterminism
1845
+ });