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