@unicitylabs/sphere-sdk 0.2.1 → 0.2.3

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.
@@ -5,6 +5,9 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
6
  var __getProtoOf = Object.getPrototypeOf;
7
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __esm = (fn, res) => function __init() {
9
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
10
+ };
8
11
  var __export = (target, all) => {
9
12
  for (var name in all)
10
13
  __defProp(target, name, { get: all[name], enumerable: true });
@@ -27,82 +30,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
27
30
  ));
28
31
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
32
 
30
- // core/index.ts
31
- var core_exports = {};
32
- __export(core_exports, {
33
- CHARSET: () => CHARSET,
34
- CurrencyUtils: () => CurrencyUtils,
35
- DEFAULT_DERIVATION_PATH: () => DEFAULT_DERIVATION_PATH,
36
- DEFAULT_TOKEN_DECIMALS: () => DEFAULT_TOKEN_DECIMALS,
37
- Sphere: () => Sphere,
38
- base58Decode: () => base58Decode,
39
- base58Encode: () => base58Encode,
40
- bytesToHex: () => bytesToHex2,
41
- computeHash160: () => computeHash160,
42
- convertBits: () => convertBits,
43
- createAddress: () => createAddress,
44
- createBech32: () => createBech32,
45
- createKeyPair: () => createKeyPair,
46
- createSphere: () => createSphere,
47
- decodeBech32: () => decodeBech32,
48
- decrypt: () => decrypt2,
49
- decryptJson: () => decryptJson,
50
- decryptMnemonic: () => decryptMnemonic,
51
- decryptSimple: () => decryptSimple,
52
- deriveAddressInfo: () => deriveAddressInfo,
53
- deriveChildKey: () => deriveChildKey,
54
- deriveKeyAtPath: () => deriveKeyAtPath,
55
- deserializeEncrypted: () => deserializeEncrypted,
56
- doubleSha256: () => doubleSha256,
57
- ec: () => ec,
58
- encodeBech32: () => encodeBech32,
59
- encrypt: () => encrypt2,
60
- encryptMnemonic: () => encryptMnemonic,
61
- encryptSimple: () => encryptSimple,
62
- entropyToMnemonic: () => entropyToMnemonic2,
63
- extractFromText: () => extractFromText,
64
- findPattern: () => findPattern,
65
- formatAmount: () => formatAmount,
66
- generateAddressInfo: () => generateAddressInfo,
67
- generateMasterKey: () => generateMasterKey,
68
- generateMnemonic: () => generateMnemonic2,
69
- generateRandomKey: () => generateRandomKey,
70
- getAddressHrp: () => getAddressHrp,
71
- getPublicKey: () => getPublicKey,
72
- getSphere: () => getSphere,
73
- hash160: () => hash160,
74
- hash160ToBytes: () => hash160ToBytes,
75
- hexToBytes: () => hexToBytes,
76
- identityFromMnemonic: () => identityFromMnemonic,
77
- identityFromMnemonicSync: () => identityFromMnemonicSync,
78
- importSphere: () => importSphere,
79
- initSphere: () => initSphere,
80
- isEncryptedData: () => isEncryptedData,
81
- isValidBech32: () => isValidBech32,
82
- isValidPrivateKey: () => isValidPrivateKey,
83
- loadSphere: () => loadSphere,
84
- mnemonicToEntropy: () => mnemonicToEntropy2,
85
- mnemonicToSeed: () => mnemonicToSeed2,
86
- mnemonicToSeedSync: () => mnemonicToSeedSync2,
87
- privateKeyToAddressInfo: () => privateKeyToAddressInfo,
88
- publicKeyToAddress: () => publicKeyToAddress,
89
- randomBytes: () => randomBytes,
90
- randomHex: () => randomHex,
91
- randomUUID: () => randomUUID,
92
- ripemd160: () => ripemd160,
93
- serializeEncrypted: () => serializeEncrypted,
94
- sha256: () => sha256,
95
- sleep: () => sleep,
96
- sphereExists: () => sphereExists,
97
- toHumanReadable: () => toHumanReadable,
98
- toSmallestUnit: () => toSmallestUnit,
99
- validateMnemonic: () => validateMnemonic2
100
- });
101
- module.exports = __toCommonJS(core_exports);
102
-
103
33
  // core/bech32.ts
104
- var CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l";
105
- var GENERATOR = [996825010, 642813549, 513874426, 1027748829, 705979059];
106
34
  function convertBits(data, fromBits, toBits, pad) {
107
35
  let acc = 0;
108
36
  let bits = 0;
@@ -209,10 +137,17 @@ function getAddressHrp(addr) {
209
137
  const result = decodeBech32(addr);
210
138
  return result?.hrp ?? null;
211
139
  }
212
- var createBech32 = encodeBech32;
140
+ var CHARSET, GENERATOR, createBech32;
141
+ var init_bech32 = __esm({
142
+ "core/bech32.ts"() {
143
+ "use strict";
144
+ CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l";
145
+ GENERATOR = [996825010, 642813549, 513874426, 1027748829, 705979059];
146
+ createBech32 = encodeBech32;
147
+ }
148
+ });
213
149
 
214
150
  // l1/addressToScriptHash.ts
215
- var import_crypto_js = __toESM(require("crypto-js"), 1);
216
151
  function bytesToHex(buf) {
217
152
  return Array.from(buf).map((b) => b.toString(16).padStart(2, "0")).join("");
218
153
  }
@@ -223,267 +158,75 @@ function addressToScriptHash(address) {
223
158
  const sha = import_crypto_js.default.SHA256(import_crypto_js.default.enc.Hex.parse(scriptHex)).toString();
224
159
  return sha.match(/../g).reverse().join("");
225
160
  }
161
+ var import_crypto_js;
162
+ var init_addressToScriptHash = __esm({
163
+ "l1/addressToScriptHash.ts"() {
164
+ "use strict";
165
+ init_bech32();
166
+ import_crypto_js = __toESM(require("crypto-js"), 1);
167
+ }
168
+ });
226
169
 
227
- // core/crypto.ts
228
- var bip39 = __toESM(require("bip39"), 1);
229
- var import_crypto_js2 = __toESM(require("crypto-js"), 1);
230
- var import_elliptic = __toESM(require("elliptic"), 1);
231
- var ec = new import_elliptic.default.ec("secp256k1");
232
- var CURVE_ORDER = BigInt(
233
- "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141"
234
- );
235
- var DEFAULT_DERIVATION_PATH = "m/44'/0'/0'";
236
- function generateMnemonic2(strength = 128) {
237
- return bip39.generateMnemonic(strength);
238
- }
239
- function validateMnemonic2(mnemonic) {
240
- return bip39.validateMnemonic(mnemonic);
241
- }
242
- async function mnemonicToSeed2(mnemonic, passphrase = "") {
243
- const seedBuffer = await bip39.mnemonicToSeed(mnemonic, passphrase);
244
- return Buffer.from(seedBuffer).toString("hex");
245
- }
246
- function mnemonicToSeedSync2(mnemonic, passphrase = "") {
247
- const seedBuffer = bip39.mnemonicToSeedSync(mnemonic, passphrase);
248
- return Buffer.from(seedBuffer).toString("hex");
249
- }
250
- function mnemonicToEntropy2(mnemonic) {
251
- return bip39.mnemonicToEntropy(mnemonic);
252
- }
253
- function entropyToMnemonic2(entropy) {
254
- return bip39.entropyToMnemonic(entropy);
170
+ // l1/network.ts
171
+ var network_exports = {};
172
+ __export(network_exports, {
173
+ broadcast: () => broadcast,
174
+ connect: () => connect,
175
+ disconnect: () => disconnect,
176
+ getBalance: () => getBalance,
177
+ getBlockHeader: () => getBlockHeader,
178
+ getCurrentBlockHeight: () => getCurrentBlockHeight,
179
+ getTransaction: () => getTransaction,
180
+ getTransactionHistory: () => getTransactionHistory,
181
+ getUtxo: () => getUtxo,
182
+ isWebSocketConnected: () => isWebSocketConnected,
183
+ rpc: () => rpc,
184
+ subscribeBlocks: () => subscribeBlocks,
185
+ waitForConnection: () => waitForConnection
186
+ });
187
+ function isWebSocketConnected() {
188
+ return isConnected && ws !== null && ws.readyState === WebSocket.OPEN;
255
189
  }
256
- function generateMasterKey(seedHex) {
257
- const I = import_crypto_js2.default.HmacSHA512(
258
- import_crypto_js2.default.enc.Hex.parse(seedHex),
259
- import_crypto_js2.default.enc.Utf8.parse("Bitcoin seed")
260
- ).toString();
261
- const IL = I.substring(0, 64);
262
- const IR = I.substring(64);
263
- const masterKeyBigInt = BigInt("0x" + IL);
264
- if (masterKeyBigInt === 0n || masterKeyBigInt >= CURVE_ORDER) {
265
- throw new Error("Invalid master key generated");
190
+ function waitForConnection() {
191
+ if (isWebSocketConnected()) {
192
+ return Promise.resolve();
266
193
  }
267
- return {
268
- privateKey: IL,
269
- chainCode: IR
270
- };
194
+ return new Promise((resolve, reject) => {
195
+ const callback = {
196
+ resolve: () => {
197
+ if (callback.timeoutId) clearTimeout(callback.timeoutId);
198
+ resolve();
199
+ },
200
+ reject: (err) => {
201
+ if (callback.timeoutId) clearTimeout(callback.timeoutId);
202
+ reject(err);
203
+ }
204
+ };
205
+ callback.timeoutId = setTimeout(() => {
206
+ const idx = connectionCallbacks.indexOf(callback);
207
+ if (idx > -1) connectionCallbacks.splice(idx, 1);
208
+ reject(new Error("Connection timeout"));
209
+ }, CONNECTION_TIMEOUT);
210
+ connectionCallbacks.push(callback);
211
+ });
271
212
  }
272
- function deriveChildKey(parentPrivKey, parentChainCode, index) {
273
- const isHardened = index >= 2147483648;
274
- let data;
275
- if (isHardened) {
276
- const indexHex = index.toString(16).padStart(8, "0");
277
- data = "00" + parentPrivKey + indexHex;
278
- } else {
279
- const keyPair = ec.keyFromPrivate(parentPrivKey, "hex");
280
- const compressedPubKey = keyPair.getPublic(true, "hex");
281
- const indexHex = index.toString(16).padStart(8, "0");
282
- data = compressedPubKey + indexHex;
283
- }
284
- const I = import_crypto_js2.default.HmacSHA512(
285
- import_crypto_js2.default.enc.Hex.parse(data),
286
- import_crypto_js2.default.enc.Hex.parse(parentChainCode)
287
- ).toString();
288
- const IL = I.substring(0, 64);
289
- const IR = I.substring(64);
290
- const ilBigInt = BigInt("0x" + IL);
291
- const parentKeyBigInt = BigInt("0x" + parentPrivKey);
292
- if (ilBigInt >= CURVE_ORDER) {
293
- throw new Error("Invalid key: IL >= curve order");
213
+ function connect(endpoint = DEFAULT_ENDPOINT) {
214
+ if (isConnected) {
215
+ return Promise.resolve();
294
216
  }
295
- const childKeyBigInt = (ilBigInt + parentKeyBigInt) % CURVE_ORDER;
296
- if (childKeyBigInt === 0n) {
297
- throw new Error("Invalid key: child key is zero");
217
+ if (isConnecting) {
218
+ return waitForConnection();
298
219
  }
299
- const childPrivKey = childKeyBigInt.toString(16).padStart(64, "0");
300
- return {
301
- privateKey: childPrivKey,
302
- chainCode: IR
303
- };
304
- }
305
- function deriveKeyAtPath(masterPrivKey, masterChainCode, path) {
306
- const pathParts = path.replace("m/", "").split("/");
307
- let currentKey = masterPrivKey;
308
- let currentChainCode = masterChainCode;
309
- for (const part of pathParts) {
310
- const isHardened = part.endsWith("'") || part.endsWith("h");
311
- const indexStr = part.replace(/['h]$/, "");
312
- let index = parseInt(indexStr, 10);
313
- if (isHardened) {
314
- index += 2147483648;
315
- }
316
- const derived = deriveChildKey(currentKey, currentChainCode, index);
317
- currentKey = derived.privateKey;
318
- currentChainCode = derived.chainCode;
319
- }
320
- return {
321
- privateKey: currentKey,
322
- chainCode: currentChainCode
323
- };
324
- }
325
- function getPublicKey(privateKey, compressed = true) {
326
- const keyPair = ec.keyFromPrivate(privateKey, "hex");
327
- return keyPair.getPublic(compressed, "hex");
328
- }
329
- function createKeyPair(privateKey) {
330
- return {
331
- privateKey,
332
- publicKey: getPublicKey(privateKey)
333
- };
334
- }
335
- function sha256(data, inputEncoding = "hex") {
336
- const parsed = inputEncoding === "hex" ? import_crypto_js2.default.enc.Hex.parse(data) : import_crypto_js2.default.enc.Utf8.parse(data);
337
- return import_crypto_js2.default.SHA256(parsed).toString();
338
- }
339
- function ripemd160(data, inputEncoding = "hex") {
340
- const parsed = inputEncoding === "hex" ? import_crypto_js2.default.enc.Hex.parse(data) : import_crypto_js2.default.enc.Utf8.parse(data);
341
- return import_crypto_js2.default.RIPEMD160(parsed).toString();
342
- }
343
- function hash160(data) {
344
- const sha = sha256(data, "hex");
345
- return ripemd160(sha, "hex");
346
- }
347
- function doubleSha256(data, inputEncoding = "hex") {
348
- const first = sha256(data, inputEncoding);
349
- return sha256(first, "hex");
350
- }
351
- var computeHash160 = hash160;
352
- function hash160ToBytes(hash160Hex) {
353
- const matches = hash160Hex.match(/../g);
354
- if (!matches) return new Uint8Array(0);
355
- return Uint8Array.from(matches.map((x) => parseInt(x, 16)));
356
- }
357
- function publicKeyToAddress(publicKey, prefix = "alpha", witnessVersion = 0) {
358
- const pubKeyHash = hash160(publicKey);
359
- const programBytes = hash160ToBytes(pubKeyHash);
360
- return encodeBech32(prefix, witnessVersion, programBytes);
361
- }
362
- function privateKeyToAddressInfo(privateKey, prefix = "alpha") {
363
- const publicKey = getPublicKey(privateKey);
364
- const address = publicKeyToAddress(publicKey, prefix);
365
- return { address, publicKey };
366
- }
367
- function hexToBytes(hex) {
368
- const matches = hex.match(/../g);
369
- if (!matches) {
370
- return new Uint8Array(0);
371
- }
372
- return Uint8Array.from(matches.map((x) => parseInt(x, 16)));
373
- }
374
- function bytesToHex2(bytes) {
375
- return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
376
- }
377
- function randomBytes(length) {
378
- const words = import_crypto_js2.default.lib.WordArray.random(length);
379
- return words.toString(import_crypto_js2.default.enc.Hex);
380
- }
381
- async function identityFromMnemonic(mnemonic, passphrase = "") {
382
- if (!validateMnemonic2(mnemonic)) {
383
- throw new Error("Invalid mnemonic phrase");
384
- }
385
- const seedHex = await mnemonicToSeed2(mnemonic, passphrase);
386
- return generateMasterKey(seedHex);
387
- }
388
- function identityFromMnemonicSync(mnemonic, passphrase = "") {
389
- if (!validateMnemonic2(mnemonic)) {
390
- throw new Error("Invalid mnemonic phrase");
391
- }
392
- const seedHex = mnemonicToSeedSync2(mnemonic, passphrase);
393
- return generateMasterKey(seedHex);
394
- }
395
- function deriveAddressInfo(masterKey, basePath, index, isChange = false, prefix = "alpha") {
396
- const chain = isChange ? 1 : 0;
397
- const fullPath = `${basePath}/${chain}/${index}`;
398
- const derived = deriveKeyAtPath(masterKey.privateKey, masterKey.chainCode, fullPath);
399
- const publicKey = getPublicKey(derived.privateKey);
400
- const address = publicKeyToAddress(publicKey, prefix);
401
- return {
402
- privateKey: derived.privateKey,
403
- publicKey,
404
- address,
405
- path: fullPath,
406
- index
407
- };
408
- }
409
- function generateAddressInfo(privateKey, index, path, prefix = "alpha") {
410
- const { address, publicKey } = privateKeyToAddressInfo(privateKey, prefix);
411
- return {
412
- privateKey,
413
- publicKey,
414
- address,
415
- path,
416
- index
417
- };
418
- }
419
-
420
- // l1/crypto.ts
421
- var import_crypto_js3 = __toESM(require("crypto-js"), 1);
422
-
423
- // l1/address.ts
424
- var import_crypto_js4 = __toESM(require("crypto-js"), 1);
425
-
426
- // l1/network.ts
427
- var DEFAULT_ENDPOINT = "wss://fulcrum.unicity.network:50004";
428
- var ws = null;
429
- var isConnected = false;
430
- var isConnecting = false;
431
- var requestId = 0;
432
- var intentionalClose = false;
433
- var reconnectAttempts = 0;
434
- var isBlockSubscribed = false;
435
- var lastBlockHeader = null;
436
- var pending = {};
437
- var blockSubscribers = [];
438
- var connectionCallbacks = [];
439
- var MAX_RECONNECT_ATTEMPTS = 10;
440
- var BASE_DELAY = 2e3;
441
- var MAX_DELAY = 6e4;
442
- var RPC_TIMEOUT = 3e4;
443
- var CONNECTION_TIMEOUT = 3e4;
444
- function isWebSocketConnected() {
445
- return isConnected && ws !== null && ws.readyState === WebSocket.OPEN;
446
- }
447
- function waitForConnection() {
448
- if (isWebSocketConnected()) {
449
- return Promise.resolve();
450
- }
451
- return new Promise((resolve, reject) => {
452
- const callback = {
453
- resolve: () => {
454
- if (callback.timeoutId) clearTimeout(callback.timeoutId);
455
- resolve();
456
- },
457
- reject: (err) => {
458
- if (callback.timeoutId) clearTimeout(callback.timeoutId);
459
- reject(err);
460
- }
461
- };
462
- callback.timeoutId = setTimeout(() => {
463
- const idx = connectionCallbacks.indexOf(callback);
464
- if (idx > -1) connectionCallbacks.splice(idx, 1);
465
- reject(new Error("Connection timeout"));
466
- }, CONNECTION_TIMEOUT);
467
- connectionCallbacks.push(callback);
468
- });
469
- }
470
- function connect(endpoint = DEFAULT_ENDPOINT) {
471
- if (isConnected) {
472
- return Promise.resolve();
473
- }
474
- if (isConnecting) {
475
- return waitForConnection();
476
- }
477
- isConnecting = true;
478
- return new Promise((resolve, reject) => {
479
- let hasResolved = false;
480
- try {
481
- ws = new WebSocket(endpoint);
482
- } catch (err) {
483
- console.error("[L1] WebSocket constructor threw exception:", err);
484
- isConnecting = false;
485
- reject(err);
486
- return;
220
+ isConnecting = true;
221
+ return new Promise((resolve, reject) => {
222
+ let hasResolved = false;
223
+ try {
224
+ ws = new WebSocket(endpoint);
225
+ } catch (err) {
226
+ console.error("[L1] WebSocket constructor threw exception:", err);
227
+ isConnecting = false;
228
+ reject(err);
229
+ return;
487
230
  }
488
231
  ws.onopen = () => {
489
232
  isConnected = true;
@@ -631,6 +374,31 @@ async function getBalance(address) {
631
374
  async function broadcast(rawHex) {
632
375
  return await rpc("blockchain.transaction.broadcast", [rawHex]);
633
376
  }
377
+ async function subscribeBlocks(cb) {
378
+ if (!isConnected && !isConnecting) {
379
+ await connect();
380
+ }
381
+ if (!isWebSocketConnected()) {
382
+ await waitForConnection();
383
+ }
384
+ blockSubscribers.push(cb);
385
+ if (!isBlockSubscribed) {
386
+ isBlockSubscribed = true;
387
+ const header = await rpc("blockchain.headers.subscribe", []);
388
+ if (header) {
389
+ lastBlockHeader = header;
390
+ blockSubscribers.forEach((subscriber) => subscriber(header));
391
+ }
392
+ } else if (lastBlockHeader) {
393
+ cb(lastBlockHeader);
394
+ }
395
+ return () => {
396
+ const index = blockSubscribers.indexOf(cb);
397
+ if (index > -1) {
398
+ blockSubscribers.splice(index, 1);
399
+ }
400
+ };
401
+ }
634
402
  async function getTransactionHistory(address) {
635
403
  const scriptHash = addressToScriptHash(address);
636
404
  const result = await rpc("blockchain.scripthash.get_history", [scriptHash]);
@@ -638,45 +406,365 @@ async function getTransactionHistory(address) {
638
406
  console.warn("get_history returned non-array:", result);
639
407
  return [];
640
408
  }
641
- return result;
409
+ return result;
410
+ }
411
+ async function getTransaction(txid) {
412
+ return await rpc("blockchain.transaction.get", [txid, true]);
413
+ }
414
+ async function getBlockHeader(height) {
415
+ return await rpc("blockchain.block.header", [height, height]);
416
+ }
417
+ async function getCurrentBlockHeight() {
418
+ try {
419
+ const header = await rpc("blockchain.headers.subscribe", []);
420
+ return header?.height || 0;
421
+ } catch (err) {
422
+ console.error("Error getting current block height:", err);
423
+ return 0;
424
+ }
425
+ }
426
+ function disconnect() {
427
+ if (ws) {
428
+ intentionalClose = true;
429
+ ws.close();
430
+ ws = null;
431
+ }
432
+ isConnected = false;
433
+ isConnecting = false;
434
+ reconnectAttempts = 0;
435
+ isBlockSubscribed = false;
436
+ Object.values(pending).forEach((req) => {
437
+ if (req.timeoutId) clearTimeout(req.timeoutId);
438
+ });
439
+ Object.keys(pending).forEach((key) => delete pending[Number(key)]);
440
+ connectionCallbacks.forEach((cb) => {
441
+ if (cb.timeoutId) clearTimeout(cb.timeoutId);
442
+ });
443
+ connectionCallbacks.length = 0;
444
+ }
445
+ var DEFAULT_ENDPOINT, ws, isConnected, isConnecting, requestId, intentionalClose, reconnectAttempts, isBlockSubscribed, lastBlockHeader, pending, blockSubscribers, connectionCallbacks, MAX_RECONNECT_ATTEMPTS, BASE_DELAY, MAX_DELAY, RPC_TIMEOUT, CONNECTION_TIMEOUT;
446
+ var init_network = __esm({
447
+ "l1/network.ts"() {
448
+ "use strict";
449
+ init_addressToScriptHash();
450
+ DEFAULT_ENDPOINT = "wss://fulcrum.unicity.network:50004";
451
+ ws = null;
452
+ isConnected = false;
453
+ isConnecting = false;
454
+ requestId = 0;
455
+ intentionalClose = false;
456
+ reconnectAttempts = 0;
457
+ isBlockSubscribed = false;
458
+ lastBlockHeader = null;
459
+ pending = {};
460
+ blockSubscribers = [];
461
+ connectionCallbacks = [];
462
+ MAX_RECONNECT_ATTEMPTS = 10;
463
+ BASE_DELAY = 2e3;
464
+ MAX_DELAY = 6e4;
465
+ RPC_TIMEOUT = 3e4;
466
+ CONNECTION_TIMEOUT = 3e4;
467
+ }
468
+ });
469
+
470
+ // core/index.ts
471
+ var core_exports = {};
472
+ __export(core_exports, {
473
+ CHARSET: () => CHARSET,
474
+ CurrencyUtils: () => CurrencyUtils,
475
+ DEFAULT_DERIVATION_PATH: () => DEFAULT_DERIVATION_PATH,
476
+ DEFAULT_TOKEN_DECIMALS: () => DEFAULT_TOKEN_DECIMALS,
477
+ Sphere: () => Sphere,
478
+ base58Decode: () => base58Decode,
479
+ base58Encode: () => base58Encode,
480
+ bytesToHex: () => bytesToHex2,
481
+ computeHash160: () => computeHash160,
482
+ convertBits: () => convertBits,
483
+ createAddress: () => createAddress,
484
+ createBech32: () => createBech32,
485
+ createKeyPair: () => createKeyPair,
486
+ createSphere: () => createSphere,
487
+ decodeBech32: () => decodeBech32,
488
+ decrypt: () => decrypt2,
489
+ decryptJson: () => decryptJson,
490
+ decryptMnemonic: () => decryptMnemonic,
491
+ decryptSimple: () => decryptSimple,
492
+ decryptWithSalt: () => decryptWithSalt,
493
+ deriveAddressInfo: () => deriveAddressInfo,
494
+ deriveChildKey: () => deriveChildKey,
495
+ deriveKeyAtPath: () => deriveKeyAtPath,
496
+ deserializeEncrypted: () => deserializeEncrypted,
497
+ doubleSha256: () => doubleSha256,
498
+ ec: () => ec,
499
+ encodeBech32: () => encodeBech32,
500
+ encrypt: () => encrypt2,
501
+ encryptMnemonic: () => encryptMnemonic,
502
+ encryptSimple: () => encryptSimple,
503
+ entropyToMnemonic: () => entropyToMnemonic2,
504
+ extractFromText: () => extractFromText,
505
+ findPattern: () => findPattern,
506
+ formatAmount: () => formatAmount,
507
+ generateAddressInfo: () => generateAddressInfo,
508
+ generateMasterKey: () => generateMasterKey,
509
+ generateMnemonic: () => generateMnemonic2,
510
+ generateRandomKey: () => generateRandomKey,
511
+ getAddressHrp: () => getAddressHrp,
512
+ getPublicKey: () => getPublicKey,
513
+ getSphere: () => getSphere,
514
+ hash160: () => hash160,
515
+ hash160ToBytes: () => hash160ToBytes,
516
+ hexToBytes: () => hexToBytes,
517
+ identityFromMnemonic: () => identityFromMnemonic,
518
+ identityFromMnemonicSync: () => identityFromMnemonicSync,
519
+ importSphere: () => importSphere,
520
+ initSphere: () => initSphere,
521
+ isEncryptedData: () => isEncryptedData,
522
+ isValidBech32: () => isValidBech32,
523
+ isValidPrivateKey: () => isValidPrivateKey,
524
+ loadSphere: () => loadSphere,
525
+ mnemonicToEntropy: () => mnemonicToEntropy2,
526
+ mnemonicToSeed: () => mnemonicToSeed2,
527
+ mnemonicToSeedSync: () => mnemonicToSeedSync2,
528
+ privateKeyToAddressInfo: () => privateKeyToAddressInfo,
529
+ publicKeyToAddress: () => publicKeyToAddress,
530
+ randomBytes: () => randomBytes,
531
+ randomHex: () => randomHex,
532
+ randomUUID: () => randomUUID,
533
+ ripemd160: () => ripemd160,
534
+ scanAddressesImpl: () => scanAddressesImpl,
535
+ serializeEncrypted: () => serializeEncrypted,
536
+ sha256: () => sha256,
537
+ sleep: () => sleep,
538
+ sphereExists: () => sphereExists,
539
+ toHumanReadable: () => toHumanReadable,
540
+ toSmallestUnit: () => toSmallestUnit,
541
+ validateMnemonic: () => validateMnemonic2
542
+ });
543
+ module.exports = __toCommonJS(core_exports);
544
+
545
+ // l1/index.ts
546
+ init_bech32();
547
+ init_addressToScriptHash();
548
+
549
+ // core/crypto.ts
550
+ var bip39 = __toESM(require("bip39"), 1);
551
+ var import_crypto_js2 = __toESM(require("crypto-js"), 1);
552
+ var import_elliptic = __toESM(require("elliptic"), 1);
553
+ init_bech32();
554
+ var ec = new import_elliptic.default.ec("secp256k1");
555
+ var CURVE_ORDER = BigInt(
556
+ "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141"
557
+ );
558
+ var DEFAULT_DERIVATION_PATH = "m/44'/0'/0'";
559
+ function generateMnemonic2(strength = 128) {
560
+ return bip39.generateMnemonic(strength);
561
+ }
562
+ function validateMnemonic2(mnemonic) {
563
+ return bip39.validateMnemonic(mnemonic);
564
+ }
565
+ async function mnemonicToSeed2(mnemonic, passphrase = "") {
566
+ const seedBuffer = await bip39.mnemonicToSeed(mnemonic, passphrase);
567
+ return Buffer.from(seedBuffer).toString("hex");
568
+ }
569
+ function mnemonicToSeedSync2(mnemonic, passphrase = "") {
570
+ const seedBuffer = bip39.mnemonicToSeedSync(mnemonic, passphrase);
571
+ return Buffer.from(seedBuffer).toString("hex");
572
+ }
573
+ function mnemonicToEntropy2(mnemonic) {
574
+ return bip39.mnemonicToEntropy(mnemonic);
575
+ }
576
+ function entropyToMnemonic2(entropy) {
577
+ return bip39.entropyToMnemonic(entropy);
578
+ }
579
+ function generateMasterKey(seedHex) {
580
+ const I = import_crypto_js2.default.HmacSHA512(
581
+ import_crypto_js2.default.enc.Hex.parse(seedHex),
582
+ import_crypto_js2.default.enc.Utf8.parse("Bitcoin seed")
583
+ ).toString();
584
+ const IL = I.substring(0, 64);
585
+ const IR = I.substring(64);
586
+ const masterKeyBigInt = BigInt("0x" + IL);
587
+ if (masterKeyBigInt === 0n || masterKeyBigInt >= CURVE_ORDER) {
588
+ throw new Error("Invalid master key generated");
589
+ }
590
+ return {
591
+ privateKey: IL,
592
+ chainCode: IR
593
+ };
594
+ }
595
+ function deriveChildKey(parentPrivKey, parentChainCode, index) {
596
+ const isHardened = index >= 2147483648;
597
+ let data;
598
+ if (isHardened) {
599
+ const indexHex = index.toString(16).padStart(8, "0");
600
+ data = "00" + parentPrivKey + indexHex;
601
+ } else {
602
+ const keyPair = ec.keyFromPrivate(parentPrivKey, "hex");
603
+ const compressedPubKey = keyPair.getPublic(true, "hex");
604
+ const indexHex = index.toString(16).padStart(8, "0");
605
+ data = compressedPubKey + indexHex;
606
+ }
607
+ const I = import_crypto_js2.default.HmacSHA512(
608
+ import_crypto_js2.default.enc.Hex.parse(data),
609
+ import_crypto_js2.default.enc.Hex.parse(parentChainCode)
610
+ ).toString();
611
+ const IL = I.substring(0, 64);
612
+ const IR = I.substring(64);
613
+ const ilBigInt = BigInt("0x" + IL);
614
+ const parentKeyBigInt = BigInt("0x" + parentPrivKey);
615
+ if (ilBigInt >= CURVE_ORDER) {
616
+ throw new Error("Invalid key: IL >= curve order");
617
+ }
618
+ const childKeyBigInt = (ilBigInt + parentKeyBigInt) % CURVE_ORDER;
619
+ if (childKeyBigInt === 0n) {
620
+ throw new Error("Invalid key: child key is zero");
621
+ }
622
+ const childPrivKey = childKeyBigInt.toString(16).padStart(64, "0");
623
+ return {
624
+ privateKey: childPrivKey,
625
+ chainCode: IR
626
+ };
627
+ }
628
+ function deriveKeyAtPath(masterPrivKey, masterChainCode, path) {
629
+ const pathParts = path.replace("m/", "").split("/");
630
+ let currentKey = masterPrivKey;
631
+ let currentChainCode = masterChainCode;
632
+ for (const part of pathParts) {
633
+ const isHardened = part.endsWith("'") || part.endsWith("h");
634
+ const indexStr = part.replace(/['h]$/, "");
635
+ let index = parseInt(indexStr, 10);
636
+ if (isHardened) {
637
+ index += 2147483648;
638
+ }
639
+ const derived = deriveChildKey(currentKey, currentChainCode, index);
640
+ currentKey = derived.privateKey;
641
+ currentChainCode = derived.chainCode;
642
+ }
643
+ return {
644
+ privateKey: currentKey,
645
+ chainCode: currentChainCode
646
+ };
647
+ }
648
+ function getPublicKey(privateKey, compressed = true) {
649
+ const keyPair = ec.keyFromPrivate(privateKey, "hex");
650
+ return keyPair.getPublic(compressed, "hex");
651
+ }
652
+ function createKeyPair(privateKey) {
653
+ return {
654
+ privateKey,
655
+ publicKey: getPublicKey(privateKey)
656
+ };
657
+ }
658
+ function sha256(data, inputEncoding = "hex") {
659
+ const parsed = inputEncoding === "hex" ? import_crypto_js2.default.enc.Hex.parse(data) : import_crypto_js2.default.enc.Utf8.parse(data);
660
+ return import_crypto_js2.default.SHA256(parsed).toString();
661
+ }
662
+ function ripemd160(data, inputEncoding = "hex") {
663
+ const parsed = inputEncoding === "hex" ? import_crypto_js2.default.enc.Hex.parse(data) : import_crypto_js2.default.enc.Utf8.parse(data);
664
+ return import_crypto_js2.default.RIPEMD160(parsed).toString();
665
+ }
666
+ function hash160(data) {
667
+ const sha = sha256(data, "hex");
668
+ return ripemd160(sha, "hex");
669
+ }
670
+ function doubleSha256(data, inputEncoding = "hex") {
671
+ const first = sha256(data, inputEncoding);
672
+ return sha256(first, "hex");
673
+ }
674
+ var computeHash160 = hash160;
675
+ function hash160ToBytes(hash160Hex) {
676
+ const matches = hash160Hex.match(/../g);
677
+ if (!matches) return new Uint8Array(0);
678
+ return Uint8Array.from(matches.map((x) => parseInt(x, 16)));
679
+ }
680
+ function publicKeyToAddress(publicKey, prefix = "alpha", witnessVersion = 0) {
681
+ const pubKeyHash = hash160(publicKey);
682
+ const programBytes = hash160ToBytes(pubKeyHash);
683
+ return encodeBech32(prefix, witnessVersion, programBytes);
684
+ }
685
+ function privateKeyToAddressInfo(privateKey, prefix = "alpha") {
686
+ const publicKey = getPublicKey(privateKey);
687
+ const address = publicKeyToAddress(publicKey, prefix);
688
+ return { address, publicKey };
689
+ }
690
+ function hexToBytes(hex) {
691
+ const matches = hex.match(/../g);
692
+ if (!matches) {
693
+ return new Uint8Array(0);
694
+ }
695
+ return Uint8Array.from(matches.map((x) => parseInt(x, 16)));
642
696
  }
643
- async function getTransaction(txid) {
644
- return await rpc("blockchain.transaction.get", [txid, true]);
697
+ function bytesToHex2(bytes) {
698
+ return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
645
699
  }
646
- async function getCurrentBlockHeight() {
647
- try {
648
- const header = await rpc("blockchain.headers.subscribe", []);
649
- return header?.height || 0;
650
- } catch (err) {
651
- console.error("Error getting current block height:", err);
652
- return 0;
700
+ function randomBytes(length) {
701
+ const words = import_crypto_js2.default.lib.WordArray.random(length);
702
+ return words.toString(import_crypto_js2.default.enc.Hex);
703
+ }
704
+ async function identityFromMnemonic(mnemonic, passphrase = "") {
705
+ if (!validateMnemonic2(mnemonic)) {
706
+ throw new Error("Invalid mnemonic phrase");
653
707
  }
708
+ const seedHex = await mnemonicToSeed2(mnemonic, passphrase);
709
+ return generateMasterKey(seedHex);
654
710
  }
655
- function disconnect() {
656
- if (ws) {
657
- intentionalClose = true;
658
- ws.close();
659
- ws = null;
711
+ function identityFromMnemonicSync(mnemonic, passphrase = "") {
712
+ if (!validateMnemonic2(mnemonic)) {
713
+ throw new Error("Invalid mnemonic phrase");
660
714
  }
661
- isConnected = false;
662
- isConnecting = false;
663
- reconnectAttempts = 0;
664
- isBlockSubscribed = false;
665
- Object.values(pending).forEach((req) => {
666
- if (req.timeoutId) clearTimeout(req.timeoutId);
667
- });
668
- Object.keys(pending).forEach((key) => delete pending[Number(key)]);
669
- connectionCallbacks.forEach((cb) => {
670
- if (cb.timeoutId) clearTimeout(cb.timeoutId);
671
- });
672
- connectionCallbacks.length = 0;
715
+ const seedHex = mnemonicToSeedSync2(mnemonic, passphrase);
716
+ return generateMasterKey(seedHex);
717
+ }
718
+ function deriveAddressInfo(masterKey, basePath, index, isChange = false, prefix = "alpha") {
719
+ const chain = isChange ? 1 : 0;
720
+ const fullPath = `${basePath}/${chain}/${index}`;
721
+ const derived = deriveKeyAtPath(masterKey.privateKey, masterKey.chainCode, fullPath);
722
+ const publicKey = getPublicKey(derived.privateKey);
723
+ const address = publicKeyToAddress(publicKey, prefix);
724
+ return {
725
+ privateKey: derived.privateKey,
726
+ publicKey,
727
+ address,
728
+ path: fullPath,
729
+ index
730
+ };
731
+ }
732
+ function generateAddressInfo(privateKey, index, path, prefix = "alpha") {
733
+ const { address, publicKey } = privateKeyToAddressInfo(privateKey, prefix);
734
+ return {
735
+ privateKey,
736
+ publicKey,
737
+ address,
738
+ path,
739
+ index
740
+ };
741
+ }
742
+
743
+ // l1/crypto.ts
744
+ var import_crypto_js3 = __toESM(require("crypto-js"), 1);
745
+
746
+ // l1/address.ts
747
+ var import_crypto_js4 = __toESM(require("crypto-js"), 1);
748
+ function generateAddressFromMasterKey(masterPrivateKey, index) {
749
+ const derivationPath = `m/44'/0'/${index}'`;
750
+ const hmacInput = import_crypto_js4.default.enc.Hex.parse(masterPrivateKey);
751
+ const hmacKey = import_crypto_js4.default.enc.Utf8.parse(derivationPath);
752
+ const hmacOutput = import_crypto_js4.default.HmacSHA512(hmacInput, hmacKey).toString();
753
+ const childPrivateKey = hmacOutput.substring(0, 64);
754
+ return generateAddressInfo(childPrivateKey, index, derivationPath);
673
755
  }
674
756
 
757
+ // l1/index.ts
758
+ init_network();
759
+
675
760
  // l1/tx.ts
761
+ init_network();
762
+ init_bech32();
676
763
  var import_crypto_js5 = __toESM(require("crypto-js"), 1);
677
764
  var import_elliptic2 = __toESM(require("elliptic"), 1);
678
765
 
679
766
  // l1/vesting.ts
767
+ init_network();
680
768
  var VESTING_THRESHOLD = 28e4;
681
769
  var currentBlockHeight = null;
682
770
  var VestingClassifier = class {
@@ -895,6 +983,24 @@ var VestingClassifier = class {
895
983
  tx.objectStore(this.storeName).clear();
896
984
  }
897
985
  }
986
+ /**
987
+ * Destroy caches and delete the IndexedDB database entirely.
988
+ */
989
+ async destroy() {
990
+ this.memoryCache.clear();
991
+ if (this.db) {
992
+ this.db.close();
993
+ this.db = null;
994
+ }
995
+ if (typeof indexedDB !== "undefined") {
996
+ await new Promise((resolve) => {
997
+ const req = indexedDB.deleteDatabase(this.dbName);
998
+ req.onsuccess = () => resolve();
999
+ req.onerror = () => resolve();
1000
+ req.onblocked = () => resolve();
1001
+ });
1002
+ }
1003
+ }
898
1004
  };
899
1005
  var vestingClassifier = new VestingClassifier();
900
1006
 
@@ -1447,7 +1553,7 @@ var L1PaymentsModule = class {
1447
1553
  _transport;
1448
1554
  constructor(config) {
1449
1555
  this._config = {
1450
- electrumUrl: config?.electrumUrl ?? "wss://fulcrum.alpha.unicity.network:50004",
1556
+ electrumUrl: config?.electrumUrl ?? "wss://fulcrum.unicity.network:50004",
1451
1557
  network: config?.network ?? "mainnet",
1452
1558
  defaultFeeRate: config?.defaultFeeRate ?? 10,
1453
1559
  enableVesting: config?.enableVesting ?? true
@@ -1479,10 +1585,17 @@ var L1PaymentsModule = class {
1479
1585
  });
1480
1586
  }
1481
1587
  }
1482
- if (this._config.electrumUrl) {
1588
+ this._initialized = true;
1589
+ }
1590
+ /**
1591
+ * Ensure the Fulcrum WebSocket is connected. Called lazily before any
1592
+ * operation that needs the network. If the singleton is already connected
1593
+ * (e.g. by the address scanner), this is a no-op.
1594
+ */
1595
+ async ensureConnected() {
1596
+ if (!isWebSocketConnected() && this._config.electrumUrl) {
1483
1597
  await connect(this._config.electrumUrl);
1484
1598
  }
1485
- this._initialized = true;
1486
1599
  }
1487
1600
  destroy() {
1488
1601
  if (isWebSocketConnected()) {
@@ -1540,6 +1653,7 @@ var L1PaymentsModule = class {
1540
1653
  }
1541
1654
  async send(request) {
1542
1655
  this.ensureInitialized();
1656
+ await this.ensureConnected();
1543
1657
  if (!this._wallet || !this._identity) {
1544
1658
  return { success: false, error: "No wallet available" };
1545
1659
  }
@@ -1574,6 +1688,7 @@ var L1PaymentsModule = class {
1574
1688
  }
1575
1689
  async getBalance() {
1576
1690
  this.ensureInitialized();
1691
+ await this.ensureConnected();
1577
1692
  const addresses = this._getWatchedAddresses();
1578
1693
  let totalAlpha = 0;
1579
1694
  let vestedSats = BigInt(0);
@@ -1605,6 +1720,7 @@ var L1PaymentsModule = class {
1605
1720
  }
1606
1721
  async getUtxos() {
1607
1722
  this.ensureInitialized();
1723
+ await this.ensureConnected();
1608
1724
  const result = [];
1609
1725
  const currentHeight = await getCurrentBlockHeight();
1610
1726
  const allUtxos = await this._getAllUtxos();
@@ -1640,42 +1756,73 @@ var L1PaymentsModule = class {
1640
1756
  return result;
1641
1757
  }
1642
1758
  async getHistory(limit) {
1759
+ await this.ensureConnected();
1643
1760
  this.ensureInitialized();
1644
1761
  const addresses = this._getWatchedAddresses();
1645
1762
  const transactions = [];
1646
1763
  const seenTxids = /* @__PURE__ */ new Set();
1647
1764
  const currentHeight = await getCurrentBlockHeight();
1765
+ const txCache = /* @__PURE__ */ new Map();
1766
+ const fetchTx = async (txid) => {
1767
+ if (txCache.has(txid)) return txCache.get(txid);
1768
+ const detail = await getTransaction(txid);
1769
+ txCache.set(txid, detail);
1770
+ return detail;
1771
+ };
1772
+ const addressSet = new Set(addresses.map((a) => a.toLowerCase()));
1648
1773
  for (const address of addresses) {
1649
1774
  const history = await getTransactionHistory(address);
1650
1775
  for (const item of history) {
1651
1776
  if (seenTxids.has(item.tx_hash)) continue;
1652
1777
  seenTxids.add(item.tx_hash);
1653
- const tx = await getTransaction(item.tx_hash);
1778
+ const tx = await fetchTx(item.tx_hash);
1654
1779
  if (!tx) continue;
1655
- const isSend = tx.vin?.some(
1656
- (vin) => addresses.includes(vin.txid ?? "")
1657
- );
1658
- let amount = "0";
1780
+ let isSend = false;
1781
+ for (const vin of tx.vin ?? []) {
1782
+ if (!vin.txid) continue;
1783
+ const prevTx = await fetchTx(vin.txid);
1784
+ if (prevTx?.vout?.[vin.vout]) {
1785
+ const prevOut = prevTx.vout[vin.vout];
1786
+ const prevAddrs = [
1787
+ ...prevOut.scriptPubKey?.addresses ?? [],
1788
+ ...prevOut.scriptPubKey?.address ? [prevOut.scriptPubKey.address] : []
1789
+ ];
1790
+ if (prevAddrs.some((a) => addressSet.has(a.toLowerCase()))) {
1791
+ isSend = true;
1792
+ break;
1793
+ }
1794
+ }
1795
+ }
1796
+ let amountToUs = 0;
1797
+ let amountToOthers = 0;
1659
1798
  let txAddress = address;
1799
+ let externalAddress = "";
1660
1800
  if (tx.vout) {
1661
1801
  for (const vout of tx.vout) {
1662
- const voutAddresses = vout.scriptPubKey?.addresses ?? [];
1663
- if (vout.scriptPubKey?.address) {
1664
- voutAddresses.push(vout.scriptPubKey.address);
1665
- }
1666
- const matchedAddr = voutAddresses.find((a) => addresses.includes(a));
1667
- if (matchedAddr) {
1668
- amount = Math.floor((vout.value ?? 0) * 1e8).toString();
1669
- txAddress = matchedAddr;
1670
- break;
1802
+ const voutAddresses = [
1803
+ ...vout.scriptPubKey?.addresses ?? [],
1804
+ ...vout.scriptPubKey?.address ? [vout.scriptPubKey.address] : []
1805
+ ];
1806
+ const isOurs = voutAddresses.some((a) => addressSet.has(a.toLowerCase()));
1807
+ const valueSats = Math.floor((vout.value ?? 0) * 1e8);
1808
+ if (isOurs) {
1809
+ amountToUs += valueSats;
1810
+ if (!txAddress) txAddress = voutAddresses[0];
1811
+ } else {
1812
+ amountToOthers += valueSats;
1813
+ if (!externalAddress && voutAddresses.length > 0) {
1814
+ externalAddress = voutAddresses[0];
1815
+ }
1671
1816
  }
1672
1817
  }
1673
1818
  }
1819
+ const amount = isSend ? amountToOthers.toString() : amountToUs.toString();
1820
+ const displayAddress = isSend ? externalAddress || txAddress : txAddress;
1674
1821
  transactions.push({
1675
1822
  txid: item.tx_hash,
1676
1823
  type: isSend ? "send" : "receive",
1677
1824
  amount,
1678
- address: txAddress,
1825
+ address: displayAddress,
1679
1826
  confirmations: item.height > 0 ? currentHeight - item.height : 0,
1680
1827
  timestamp: tx.time ? tx.time * 1e3 : Date.now(),
1681
1828
  blockHeight: item.height > 0 ? item.height : void 0
@@ -1687,6 +1834,7 @@ var L1PaymentsModule = class {
1687
1834
  }
1688
1835
  async getTransaction(txid) {
1689
1836
  this.ensureInitialized();
1837
+ await this.ensureConnected();
1690
1838
  const tx = await getTransaction(txid);
1691
1839
  if (!tx) return null;
1692
1840
  const addresses = this._getWatchedAddresses();
@@ -1722,6 +1870,7 @@ var L1PaymentsModule = class {
1722
1870
  }
1723
1871
  async estimateFee(to, amount) {
1724
1872
  this.ensureInitialized();
1873
+ await this.ensureConnected();
1725
1874
  if (!this._wallet) {
1726
1875
  return { fee: "0", feeRate: this._config.defaultFeeRate ?? 10 };
1727
1876
  }
@@ -2241,7 +2390,9 @@ var STORAGE_KEYS_GLOBAL = {
2241
2390
  /** Nametag cache per address (separate from tracked addresses registry) */
2242
2391
  ADDRESS_NAMETAGS: "address_nametags",
2243
2392
  /** Active addresses registry (JSON: TrackedAddressesStorage) */
2244
- TRACKED_ADDRESSES: "tracked_addresses"
2393
+ TRACKED_ADDRESSES: "tracked_addresses",
2394
+ /** Last processed Nostr wallet event timestamp (unix seconds), keyed per pubkey */
2395
+ LAST_WALLET_EVENT_TS: "last_wallet_event_ts"
2245
2396
  };
2246
2397
  var STORAGE_KEYS_ADDRESS = {
2247
2398
  /** Pending transfers for this address */
@@ -3645,7 +3796,7 @@ async function parseTokenInfo(tokenData) {
3645
3796
  try {
3646
3797
  const sdkToken = await import_Token6.Token.fromJSON(data);
3647
3798
  if (sdkToken.id) {
3648
- defaultInfo.tokenId = sdkToken.id.toString();
3799
+ defaultInfo.tokenId = sdkToken.id.toJSON();
3649
3800
  }
3650
3801
  if (sdkToken.coins && sdkToken.coins.coins) {
3651
3802
  const rawCoins = sdkToken.coins.coins;
@@ -3936,8 +4087,7 @@ var PaymentsModule = class _PaymentsModule {
3936
4087
  maxRetries: config?.maxRetries ?? 3,
3937
4088
  debug: config?.debug ?? false
3938
4089
  };
3939
- const l1Enabled = config?.l1?.electrumUrl && config.l1.electrumUrl.length > 0;
3940
- this.l1 = l1Enabled ? new L1PaymentsModule(config?.l1) : null;
4090
+ this.l1 = config?.l1 === null ? null : new L1PaymentsModule(config?.l1);
3941
4091
  }
3942
4092
  /** Get module configuration */
3943
4093
  getConfig() {
@@ -4139,7 +4289,7 @@ var PaymentsModule = class _PaymentsModule {
4139
4289
  memo: request.memo
4140
4290
  });
4141
4291
  console.log(`[Payments] Split token sent successfully`);
4142
- await this.removeToken(splitPlan.tokenToSplit.uiToken.id, recipientNametag);
4292
+ await this.removeToken(splitPlan.tokenToSplit.uiToken.id, recipientNametag, true);
4143
4293
  result.txHash = "split-" + Date.now().toString(16);
4144
4294
  this.log(`Split transfer completed`);
4145
4295
  }
@@ -4165,7 +4315,7 @@ var PaymentsModule = class _PaymentsModule {
4165
4315
  });
4166
4316
  console.log(`[Payments] Direct token sent successfully`);
4167
4317
  this.log(`Token ${token.id} transferred, txHash: ${result.txHash}`);
4168
- await this.removeToken(token.id, recipientNametag);
4318
+ await this.removeToken(token.id, recipientNametag, true);
4169
4319
  }
4170
4320
  result.status = "delivered";
4171
4321
  await this.save();
@@ -4314,7 +4464,7 @@ var PaymentsModule = class _PaymentsModule {
4314
4464
  );
4315
4465
  if (result.success) {
4316
4466
  const recipientNametag = request.recipient.startsWith("@") ? request.recipient.slice(1) : void 0;
4317
- await this.removeToken(tokenToSplit.id, recipientNametag);
4467
+ await this.removeToken(tokenToSplit.id, recipientNametag, true);
4318
4468
  await this.addToHistory({
4319
4469
  type: "SENT",
4320
4470
  amount: request.amount,
@@ -4599,13 +4749,16 @@ var PaymentsModule = class _PaymentsModule {
4599
4749
  if (this.paymentRequests.find((r) => r.id === transportRequest.id)) {
4600
4750
  return;
4601
4751
  }
4752
+ const coinId = transportRequest.request.coinId;
4753
+ const registry = TokenRegistry.getInstance();
4754
+ const coinDef = registry.getDefinition(coinId);
4602
4755
  const request = {
4603
4756
  id: transportRequest.id,
4604
4757
  senderPubkey: transportRequest.senderTransportPubkey,
4758
+ senderNametag: transportRequest.senderNametag,
4605
4759
  amount: transportRequest.request.amount,
4606
- coinId: transportRequest.request.coinId,
4607
- symbol: transportRequest.request.coinId,
4608
- // Use coinId as symbol for now
4760
+ coinId,
4761
+ symbol: coinDef?.symbol || coinId.slice(0, 8),
4609
4762
  message: transportRequest.request.message,
4610
4763
  recipientNametag: transportRequest.request.recipientNametag,
4611
4764
  requestId: transportRequest.request.requestId,
@@ -4820,35 +4973,39 @@ var PaymentsModule = class _PaymentsModule {
4820
4973
  const rawAssets = Array.from(assetsMap.values());
4821
4974
  let priceMap = null;
4822
4975
  if (this.priceProvider && rawAssets.length > 0) {
4823
- const registry = TokenRegistry.getInstance();
4824
- const nameToCoins = /* @__PURE__ */ new Map();
4825
- for (const asset of rawAssets) {
4826
- const def = registry.getDefinition(asset.coinId);
4827
- if (def?.name) {
4828
- const existing = nameToCoins.get(def.name);
4829
- if (existing) {
4830
- existing.push(asset.coinId);
4831
- } else {
4832
- nameToCoins.set(def.name, [asset.coinId]);
4976
+ try {
4977
+ const registry = TokenRegistry.getInstance();
4978
+ const nameToCoins = /* @__PURE__ */ new Map();
4979
+ for (const asset of rawAssets) {
4980
+ const def = registry.getDefinition(asset.coinId);
4981
+ if (def?.name) {
4982
+ const existing = nameToCoins.get(def.name);
4983
+ if (existing) {
4984
+ existing.push(asset.coinId);
4985
+ } else {
4986
+ nameToCoins.set(def.name, [asset.coinId]);
4987
+ }
4833
4988
  }
4834
4989
  }
4835
- }
4836
- if (nameToCoins.size > 0) {
4837
- const tokenNames = Array.from(nameToCoins.keys());
4838
- const prices = await this.priceProvider.getPrices(tokenNames);
4839
- priceMap = /* @__PURE__ */ new Map();
4840
- for (const [name, coinIds] of nameToCoins) {
4841
- const price = prices.get(name);
4842
- if (price) {
4843
- for (const cid of coinIds) {
4844
- priceMap.set(cid, {
4845
- priceUsd: price.priceUsd,
4846
- priceEur: price.priceEur,
4847
- change24h: price.change24h
4848
- });
4990
+ if (nameToCoins.size > 0) {
4991
+ const tokenNames = Array.from(nameToCoins.keys());
4992
+ const prices = await this.priceProvider.getPrices(tokenNames);
4993
+ priceMap = /* @__PURE__ */ new Map();
4994
+ for (const [name, coinIds] of nameToCoins) {
4995
+ const price = prices.get(name);
4996
+ if (price) {
4997
+ for (const cid of coinIds) {
4998
+ priceMap.set(cid, {
4999
+ priceUsd: price.priceUsd,
5000
+ priceEur: price.priceEur,
5001
+ change24h: price.change24h
5002
+ });
5003
+ }
4849
5004
  }
4850
5005
  }
4851
5006
  }
5007
+ } catch (error) {
5008
+ console.warn("[Payments] Failed to fetch prices, returning assets without price data:", error);
4852
5009
  }
4853
5010
  }
4854
5011
  return rawAssets.map((raw) => {
@@ -5051,6 +5208,12 @@ var PaymentsModule = class _PaymentsModule {
5051
5208
  updatedAt: Date.now(),
5052
5209
  sdkData: typeof tokenJson === "string" ? tokenJson : JSON.stringify(tokenJson)
5053
5210
  };
5211
+ const loadedTokenId = extractTokenIdFromSdkData(token.sdkData);
5212
+ const loadedStateHash = extractStateHashFromSdkData(token.sdkData);
5213
+ if (loadedTokenId && loadedStateHash && this.isStateTombstoned(loadedTokenId, loadedStateHash)) {
5214
+ this.log(`Skipping tombstoned token file ${tokenId} (${loadedTokenId.slice(0, 8)}...)`);
5215
+ continue;
5216
+ }
5054
5217
  this.tokens.set(token.id, token);
5055
5218
  this.log(`Loaded token from file: ${tokenId}`);
5056
5219
  } catch (tokenError) {
@@ -5108,6 +5271,7 @@ var PaymentsModule = class _PaymentsModule {
5108
5271
  this.log(`Warning: Could not create tombstone for token ${tokenId.slice(0, 8)}... (missing tokenId or stateHash)`);
5109
5272
  }
5110
5273
  this.tokens.delete(tokenId);
5274
+ await this.deleteTokenFiles(token);
5111
5275
  if (!skipHistory && token.coinId && token.amount) {
5112
5276
  await this.addToHistory({
5113
5277
  type: "SENT",
@@ -5120,6 +5284,31 @@ var PaymentsModule = class _PaymentsModule {
5120
5284
  }
5121
5285
  await this.save();
5122
5286
  }
5287
+ /**
5288
+ * Delete physical token file(s) from all storage providers.
5289
+ * Finds files by matching the SDK token ID prefix in the filename.
5290
+ */
5291
+ async deleteTokenFiles(token) {
5292
+ const sdkTokenId = extractTokenIdFromSdkData(token.sdkData);
5293
+ if (!sdkTokenId) return;
5294
+ const tokenIdPrefix = sdkTokenId.slice(0, 16);
5295
+ const providers = this.getTokenStorageProviders();
5296
+ for (const [providerId, provider] of providers) {
5297
+ if (!provider.listTokenIds || !provider.deleteToken) continue;
5298
+ try {
5299
+ const allIds = await provider.listTokenIds();
5300
+ const matchingFiles = allIds.filter(
5301
+ (id) => id.startsWith(`token-${tokenIdPrefix}`)
5302
+ );
5303
+ for (const fileId of matchingFiles) {
5304
+ await provider.deleteToken(fileId);
5305
+ this.log(`Deleted token file ${fileId} from ${providerId}`);
5306
+ }
5307
+ } catch (error) {
5308
+ console.warn(`[Payments] Failed to delete token files from ${providerId}:`, error);
5309
+ }
5310
+ }
5311
+ }
5123
5312
  // ===========================================================================
5124
5313
  // Public API - Tombstones
5125
5314
  // ===========================================================================
@@ -6624,6 +6813,20 @@ function decryptSimple(ciphertext, password) {
6624
6813
  }
6625
6814
  return result;
6626
6815
  }
6816
+ function decryptWithSalt(ciphertext, password, salt) {
6817
+ try {
6818
+ const key = import_crypto_js6.default.PBKDF2(password, salt, {
6819
+ keySize: 256 / 32,
6820
+ iterations: 1e5,
6821
+ hasher: import_crypto_js6.default.algo.SHA256
6822
+ }).toString();
6823
+ const decrypted = import_crypto_js6.default.AES.decrypt(ciphertext, key);
6824
+ const result = decrypted.toString(import_crypto_js6.default.enc.Utf8);
6825
+ return result || null;
6826
+ } catch {
6827
+ return null;
6828
+ }
6829
+ }
6627
6830
  function encryptMnemonic(mnemonic, password) {
6628
6831
  return encryptSimple(mnemonic, password);
6629
6832
  }
@@ -6651,6 +6854,81 @@ function generateRandomKey(bytes = 32) {
6651
6854
  return import_crypto_js6.default.lib.WordArray.random(bytes).toString(import_crypto_js6.default.enc.Hex);
6652
6855
  }
6653
6856
 
6857
+ // core/scan.ts
6858
+ async function scanAddressesImpl(deriveAddress, options = {}) {
6859
+ const maxAddresses = options.maxAddresses ?? 50;
6860
+ const gapLimit = options.gapLimit ?? 20;
6861
+ const includeChange = options.includeChange ?? true;
6862
+ const { onProgress, signal, resolveNametag } = options;
6863
+ const { connect: connect2, getBalance: getBalance2 } = await Promise.resolve().then(() => (init_network(), network_exports));
6864
+ await connect2();
6865
+ const foundAddresses = [];
6866
+ let totalBalance = 0;
6867
+ let totalScanned = 0;
6868
+ let nametagsFoundCount = 0;
6869
+ const chains = includeChange ? [false, true] : [false];
6870
+ const totalToScan = maxAddresses * chains.length;
6871
+ for (const isChange of chains) {
6872
+ let consecutiveEmpty = 0;
6873
+ for (let index = 0; index < maxAddresses; index++) {
6874
+ if (signal?.aborted) break;
6875
+ const addrInfo = deriveAddress(index, isChange);
6876
+ totalScanned++;
6877
+ onProgress?.({
6878
+ scanned: totalScanned,
6879
+ total: totalToScan,
6880
+ currentAddress: addrInfo.address,
6881
+ foundCount: foundAddresses.length,
6882
+ currentGap: consecutiveEmpty,
6883
+ nametagsFoundCount
6884
+ });
6885
+ try {
6886
+ const balance = await getBalance2(addrInfo.address);
6887
+ if (balance > 0) {
6888
+ let nametag;
6889
+ if (resolveNametag) {
6890
+ try {
6891
+ const tag = await resolveNametag(addrInfo.address);
6892
+ if (tag) {
6893
+ nametag = tag;
6894
+ nametagsFoundCount++;
6895
+ }
6896
+ } catch {
6897
+ }
6898
+ }
6899
+ foundAddresses.push({
6900
+ index,
6901
+ address: addrInfo.address,
6902
+ path: addrInfo.path,
6903
+ balance,
6904
+ isChange,
6905
+ nametag
6906
+ });
6907
+ totalBalance += balance;
6908
+ consecutiveEmpty = 0;
6909
+ } else {
6910
+ consecutiveEmpty++;
6911
+ }
6912
+ } catch (err) {
6913
+ console.warn(`[scanAddresses] Error checking ${addrInfo.address}:`, err);
6914
+ consecutiveEmpty++;
6915
+ }
6916
+ if (consecutiveEmpty >= gapLimit) {
6917
+ break;
6918
+ }
6919
+ if (totalScanned % 5 === 0) {
6920
+ await new Promise((resolve) => setTimeout(resolve, 0));
6921
+ }
6922
+ }
6923
+ if (signal?.aborted) break;
6924
+ }
6925
+ return {
6926
+ addresses: foundAddresses,
6927
+ totalBalance,
6928
+ scannedCount: totalScanned
6929
+ };
6930
+ }
6931
+
6654
6932
  // serialization/wallet-text.ts
6655
6933
  var import_crypto_js7 = __toESM(require("crypto-js"), 1);
6656
6934
 
@@ -6907,10 +7185,11 @@ function parseWalletText(content) {
6907
7185
  );
6908
7186
  const descriptorPath = extractFromText(content, /DESCRIPTOR PATH:\s*([^\n]+)/);
6909
7187
  const isBIP32 = content.includes("WALLET TYPE: BIP32 hierarchical deterministic wallet") || content.includes("WALLET TYPE: Alpha descriptor wallet") || !!chainCode;
7188
+ const effectiveDescriptorPath = descriptorPath ?? (isBIP32 ? "84'/1'/0'" : void 0);
6910
7189
  const data = {
6911
7190
  masterKey,
6912
7191
  chainCode: chainCode ?? void 0,
6913
- descriptorPath: descriptorPath ?? void 0,
7192
+ descriptorPath: effectiveDescriptorPath,
6914
7193
  derivationMode: isBIP32 ? "bip32" : "wif_hmac"
6915
7194
  };
6916
7195
  return {
@@ -6953,10 +7232,11 @@ function parseAndDecryptWalletText(content, password) {
6953
7232
  );
6954
7233
  const descriptorPath = extractFromText(content, /DESCRIPTOR PATH:\s*([^\n]+)/);
6955
7234
  const isBIP32 = content.includes("WALLET TYPE: BIP32 hierarchical deterministic wallet") || content.includes("WALLET TYPE: Alpha descriptor wallet") || !!chainCode;
7235
+ const effectiveDescriptorPath = descriptorPath ?? (isBIP32 ? "84'/1'/0'" : void 0);
6956
7236
  const data = {
6957
7237
  masterKey,
6958
7238
  chainCode: chainCode ?? void 0,
6959
- descriptorPath: descriptorPath ?? void 0,
7239
+ descriptorPath: effectiveDescriptorPath,
6960
7240
  derivationMode: isBIP32 ? "bip32" : "wif_hmac"
6961
7241
  };
6962
7242
  return {
@@ -7497,8 +7777,8 @@ var Sphere = class _Sphere {
7497
7777
  if (options.nametag) {
7498
7778
  await sphere.registerNametag(options.nametag);
7499
7779
  } else {
7500
- await sphere.syncIdentityWithTransport();
7501
7780
  await sphere.recoverNametagFromTransport();
7781
+ await sphere.syncIdentityWithTransport();
7502
7782
  }
7503
7783
  return sphere;
7504
7784
  }
@@ -7545,7 +7825,15 @@ var Sphere = class _Sphere {
7545
7825
  if (!options.mnemonic && !options.masterKey) {
7546
7826
  throw new Error("Either mnemonic or masterKey is required");
7547
7827
  }
7828
+ console.log("[Sphere.import] Starting import...");
7829
+ console.log("[Sphere.import] Clearing existing wallet data...");
7548
7830
  await _Sphere.clear({ storage: options.storage, tokenStorage: options.tokenStorage });
7831
+ console.log("[Sphere.import] Clear done");
7832
+ if (!options.storage.isConnected()) {
7833
+ console.log("[Sphere.import] Reconnecting storage...");
7834
+ await options.storage.connect();
7835
+ console.log("[Sphere.import] Storage reconnected");
7836
+ }
7549
7837
  const sphere = new _Sphere(
7550
7838
  options.storage,
7551
7839
  options.transport,
@@ -7558,9 +7846,12 @@ var Sphere = class _Sphere {
7558
7846
  if (!_Sphere.validateMnemonic(options.mnemonic)) {
7559
7847
  throw new Error("Invalid mnemonic");
7560
7848
  }
7849
+ console.log("[Sphere.import] Storing mnemonic...");
7561
7850
  await sphere.storeMnemonic(options.mnemonic, options.derivationPath, options.basePath);
7851
+ console.log("[Sphere.import] Initializing identity from mnemonic...");
7562
7852
  await sphere.initializeIdentityFromMnemonic(options.mnemonic, options.derivationPath);
7563
7853
  } else if (options.masterKey) {
7854
+ console.log("[Sphere.import] Storing master key...");
7564
7855
  await sphere.storeMasterKey(
7565
7856
  options.masterKey,
7566
7857
  options.chainCode,
@@ -7568,24 +7859,35 @@ var Sphere = class _Sphere {
7568
7859
  options.basePath,
7569
7860
  options.derivationMode
7570
7861
  );
7862
+ console.log("[Sphere.import] Initializing identity from master key...");
7571
7863
  await sphere.initializeIdentityFromMasterKey(
7572
7864
  options.masterKey,
7573
7865
  options.chainCode,
7574
7866
  options.derivationPath
7575
7867
  );
7576
7868
  }
7869
+ console.log("[Sphere.import] Initializing providers...");
7577
7870
  await sphere.initializeProviders();
7871
+ console.log("[Sphere.import] Providers initialized. Initializing modules...");
7578
7872
  await sphere.initializeModules();
7873
+ console.log("[Sphere.import] Modules initialized");
7579
7874
  if (!options.nametag) {
7875
+ console.log("[Sphere.import] Recovering nametag from transport...");
7580
7876
  await sphere.recoverNametagFromTransport();
7877
+ console.log("[Sphere.import] Nametag recovery done");
7878
+ await sphere.syncIdentityWithTransport();
7581
7879
  }
7880
+ console.log("[Sphere.import] Finalizing wallet creation...");
7582
7881
  await sphere.finalizeWalletCreation();
7583
7882
  sphere._initialized = true;
7584
7883
  _Sphere.instance = sphere;
7884
+ console.log("[Sphere.import] Tracking address 0...");
7585
7885
  await sphere.ensureAddressTracked(0);
7586
7886
  if (options.nametag) {
7887
+ console.log("[Sphere.import] Registering nametag...");
7587
7888
  await sphere.registerNametag(options.nametag);
7588
7889
  }
7890
+ console.log("[Sphere.import] Import complete");
7589
7891
  return sphere;
7590
7892
  }
7591
7893
  /**
@@ -7610,6 +7912,10 @@ var Sphere = class _Sphere {
7610
7912
  static async clear(storageOrOptions) {
7611
7913
  const storage = "get" in storageOrOptions ? storageOrOptions : storageOrOptions.storage;
7612
7914
  const tokenStorage = "get" in storageOrOptions ? void 0 : storageOrOptions.tokenStorage;
7915
+ if (!storage.isConnected()) {
7916
+ await storage.connect();
7917
+ }
7918
+ console.log("[Sphere.clear] Removing storage keys...");
7613
7919
  await storage.remove(STORAGE_KEYS_GLOBAL.MNEMONIC);
7614
7920
  await storage.remove(STORAGE_KEYS_GLOBAL.MASTER_KEY);
7615
7921
  await storage.remove(STORAGE_KEYS_GLOBAL.CHAIN_CODE);
@@ -7622,11 +7928,30 @@ var Sphere = class _Sphere {
7622
7928
  await storage.remove(STORAGE_KEYS_GLOBAL.ADDRESS_NAMETAGS);
7623
7929
  await storage.remove(STORAGE_KEYS_ADDRESS.PENDING_TRANSFERS);
7624
7930
  await storage.remove(STORAGE_KEYS_ADDRESS.OUTBOX);
7931
+ console.log("[Sphere.clear] Storage keys removed");
7625
7932
  if (tokenStorage?.clear) {
7626
- await tokenStorage.clear();
7933
+ console.log("[Sphere.clear] Clearing token storage...");
7934
+ try {
7935
+ await Promise.race([
7936
+ tokenStorage.clear(),
7937
+ new Promise(
7938
+ (_, reject) => setTimeout(() => reject(new Error("tokenStorage.clear() timed out after 2s")), 2e3)
7939
+ )
7940
+ ]);
7941
+ console.log("[Sphere.clear] Token storage cleared");
7942
+ } catch (err) {
7943
+ console.warn("[Sphere.clear] Token storage clear failed/timed out:", err);
7944
+ }
7627
7945
  }
7946
+ console.log("[Sphere.clear] Destroying vesting classifier...");
7947
+ await vestingClassifier.destroy();
7948
+ console.log("[Sphere.clear] Vesting classifier destroyed");
7628
7949
  if (_Sphere.instance) {
7950
+ console.log("[Sphere.clear] Destroying Sphere instance...");
7629
7951
  await _Sphere.instance.destroy();
7952
+ console.log("[Sphere.clear] Sphere instance destroyed");
7953
+ } else {
7954
+ console.log("[Sphere.clear] No Sphere instance to destroy");
7630
7955
  }
7631
7956
  }
7632
7957
  /**
@@ -7807,7 +8132,7 @@ var Sphere = class _Sphere {
7807
8132
  return {
7808
8133
  source: this._source,
7809
8134
  hasMnemonic: this._mnemonic !== null,
7810
- hasChainCode: this._masterKey?.chainCode !== void 0,
8135
+ hasChainCode: !!this._masterKey?.chainCode,
7811
8136
  derivationMode: this._derivationMode,
7812
8137
  basePath: this._basePath,
7813
8138
  address0
@@ -7860,7 +8185,7 @@ var Sphere = class _Sphere {
7860
8185
  let chainCode;
7861
8186
  if (this._masterKey) {
7862
8187
  masterPrivateKey = this._masterKey.privateKey;
7863
- chainCode = this._masterKey.chainCode;
8188
+ chainCode = this._masterKey.chainCode || void 0;
7864
8189
  }
7865
8190
  let mnemonic;
7866
8191
  let encrypted = false;
@@ -7937,7 +8262,7 @@ var Sphere = class _Sphere {
7937
8262
  }
7938
8263
  }
7939
8264
  const masterPrivateKey = this._masterKey?.privateKey || "";
7940
- const chainCode = this._masterKey?.chainCode;
8265
+ const chainCode = this._masterKey?.chainCode || void 0;
7941
8266
  const isBIP32 = this._derivationMode === "bip32";
7942
8267
  const descriptorPath = this._basePath.replace(/^m\//, "");
7943
8268
  if (options.password) {
@@ -8007,7 +8332,8 @@ var Sphere = class _Sphere {
8007
8332
  storage: options.storage,
8008
8333
  transport: options.transport,
8009
8334
  oracle: options.oracle,
8010
- tokenStorage: options.tokenStorage
8335
+ tokenStorage: options.tokenStorage,
8336
+ l1: options.l1
8011
8337
  });
8012
8338
  return { success: true, mnemonic };
8013
8339
  }
@@ -8020,7 +8346,8 @@ var Sphere = class _Sphere {
8020
8346
  storage: options.storage,
8021
8347
  transport: options.transport,
8022
8348
  oracle: options.oracle,
8023
- tokenStorage: options.tokenStorage
8349
+ tokenStorage: options.tokenStorage,
8350
+ l1: options.l1
8024
8351
  });
8025
8352
  return { success: true };
8026
8353
  }
@@ -8079,7 +8406,8 @@ var Sphere = class _Sphere {
8079
8406
  transport: options.transport,
8080
8407
  oracle: options.oracle,
8081
8408
  tokenStorage: options.tokenStorage,
8082
- nametag: options.nametag
8409
+ nametag: options.nametag,
8410
+ l1: options.l1
8083
8411
  });
8084
8412
  return { success: true, sphere, mnemonic };
8085
8413
  }
@@ -8108,7 +8436,8 @@ var Sphere = class _Sphere {
8108
8436
  transport: options.transport,
8109
8437
  oracle: options.oracle,
8110
8438
  tokenStorage: options.tokenStorage,
8111
- nametag: options.nametag
8439
+ nametag: options.nametag,
8440
+ l1: options.l1
8112
8441
  });
8113
8442
  return { success: true, sphere };
8114
8443
  }
@@ -8139,25 +8468,94 @@ var Sphere = class _Sphere {
8139
8468
  transport: options.transport,
8140
8469
  oracle: options.oracle,
8141
8470
  tokenStorage: options.tokenStorage,
8142
- nametag: options.nametag
8471
+ nametag: options.nametag,
8472
+ l1: options.l1
8143
8473
  });
8144
8474
  return { success: true, sphere };
8145
8475
  }
8146
8476
  if (fileType === "json") {
8147
8477
  const content = typeof fileContent === "string" ? fileContent : new TextDecoder().decode(fileContent);
8148
- const result = await _Sphere.importFromJSON({
8149
- jsonContent: content,
8150
- password,
8478
+ let parsed;
8479
+ try {
8480
+ parsed = JSON.parse(content);
8481
+ } catch {
8482
+ return { success: false, error: "Invalid JSON file" };
8483
+ }
8484
+ if (parsed.type === "sphere-wallet") {
8485
+ const result = await _Sphere.importFromJSON({
8486
+ jsonContent: content,
8487
+ password,
8488
+ storage: options.storage,
8489
+ transport: options.transport,
8490
+ oracle: options.oracle,
8491
+ tokenStorage: options.tokenStorage,
8492
+ l1: options.l1
8493
+ });
8494
+ if (result.success) {
8495
+ const sphere2 = _Sphere.getInstance();
8496
+ return { success: true, sphere: sphere2, mnemonic: result.mnemonic };
8497
+ }
8498
+ if (!password && result.error?.includes("Password required")) {
8499
+ return { success: false, needsPassword: true, error: result.error };
8500
+ }
8501
+ return { success: false, error: result.error };
8502
+ }
8503
+ let masterKey;
8504
+ let mnemonic;
8505
+ if (parsed.encrypted && typeof parsed.encrypted === "object") {
8506
+ if (!password) {
8507
+ return { success: false, needsPassword: true, error: "Password required for encrypted wallet" };
8508
+ }
8509
+ const enc = parsed.encrypted;
8510
+ if (!enc.salt || !enc.masterPrivateKey) {
8511
+ return { success: false, error: "Invalid encrypted wallet format" };
8512
+ }
8513
+ const decryptedKey = decryptWithSalt(enc.masterPrivateKey, password, enc.salt);
8514
+ if (!decryptedKey) {
8515
+ return { success: false, error: "Failed to decrypt - incorrect password?" };
8516
+ }
8517
+ masterKey = decryptedKey;
8518
+ if (enc.mnemonic) {
8519
+ mnemonic = decryptWithSalt(enc.mnemonic, password, enc.salt) ?? void 0;
8520
+ }
8521
+ } else {
8522
+ masterKey = parsed.masterPrivateKey;
8523
+ mnemonic = parsed.mnemonic;
8524
+ }
8525
+ if (!masterKey) {
8526
+ return { success: false, error: "No master key found in wallet JSON" };
8527
+ }
8528
+ const chainCode = parsed.chainCode;
8529
+ const descriptorPath = parsed.descriptorPath;
8530
+ const derivationMode = parsed.derivationMode;
8531
+ const isBIP32 = derivationMode === "bip32" || !!chainCode;
8532
+ const basePath = descriptorPath ? `m/${descriptorPath}` : isBIP32 ? "m/84'/1'/0'" : DEFAULT_BASE_PATH;
8533
+ if (mnemonic) {
8534
+ const sphere2 = await _Sphere.import({
8535
+ mnemonic,
8536
+ basePath,
8537
+ storage: options.storage,
8538
+ transport: options.transport,
8539
+ oracle: options.oracle,
8540
+ tokenStorage: options.tokenStorage,
8541
+ nametag: options.nametag,
8542
+ l1: options.l1
8543
+ });
8544
+ return { success: true, sphere: sphere2, mnemonic };
8545
+ }
8546
+ const sphere = await _Sphere.import({
8547
+ masterKey,
8548
+ chainCode,
8549
+ basePath,
8550
+ derivationMode: derivationMode || (chainCode ? "bip32" : "wif_hmac"),
8151
8551
  storage: options.storage,
8152
8552
  transport: options.transport,
8153
8553
  oracle: options.oracle,
8154
- tokenStorage: options.tokenStorage
8554
+ tokenStorage: options.tokenStorage,
8555
+ nametag: options.nametag,
8556
+ l1: options.l1
8155
8557
  });
8156
- if (result.success) {
8157
- const sphere = _Sphere.getInstance();
8158
- return { success: true, sphere, mnemonic: result.mnemonic };
8159
- }
8160
- return result;
8558
+ return { success: true, sphere };
8161
8559
  }
8162
8560
  return { success: false, error: "Unsupported file type" };
8163
8561
  }
@@ -8455,7 +8853,7 @@ var Sphere = class _Sphere {
8455
8853
  transport: this._transport,
8456
8854
  oracle: this._oracle,
8457
8855
  emitEvent,
8458
- chainCode: this._masterKey?.chainCode,
8856
+ chainCode: this._masterKey?.chainCode || void 0,
8459
8857
  price: this._priceProvider ?? void 0
8460
8858
  });
8461
8859
  this._communications.initialize({
@@ -8500,6 +8898,9 @@ var Sphere = class _Sphere {
8500
8898
  if (!this._masterKey) {
8501
8899
  throw new Error("HD derivation requires master key with chain code");
8502
8900
  }
8901
+ if (this._derivationMode === "wif_hmac") {
8902
+ return generateAddressFromMasterKey(this._masterKey.privateKey, index);
8903
+ }
8503
8904
  const info = deriveAddressInfo(
8504
8905
  this._masterKey,
8505
8906
  this._basePath,
@@ -8571,6 +8972,66 @@ var Sphere = class _Sphere {
8571
8972
  }
8572
8973
  return addresses;
8573
8974
  }
8975
+ /**
8976
+ * Scan blockchain addresses to discover used addresses with balances.
8977
+ * Derives addresses sequentially and checks L1 balance via Fulcrum.
8978
+ * Uses gap limit to stop after N consecutive empty addresses.
8979
+ *
8980
+ * @param options - Scanning options
8981
+ * @returns Scan results with found addresses and total balance
8982
+ *
8983
+ * @example
8984
+ * ```ts
8985
+ * const result = await sphere.scanAddresses({
8986
+ * maxAddresses: 100,
8987
+ * gapLimit: 20,
8988
+ * onProgress: (p) => console.log(`Scanned ${p.scanned}/${p.total}, found ${p.foundCount}`),
8989
+ * });
8990
+ * console.log(`Found ${result.addresses.length} addresses, total: ${result.totalBalance} ALPHA`);
8991
+ * ```
8992
+ */
8993
+ async scanAddresses(options = {}) {
8994
+ this.ensureReady();
8995
+ if (!this._masterKey) {
8996
+ throw new Error("Address scanning requires HD master key");
8997
+ }
8998
+ const resolveNametag = options.resolveNametag ?? (this._transport.resolveAddressInfo ? async (l1Address) => {
8999
+ try {
9000
+ const info = await this._transport.resolveAddressInfo(l1Address);
9001
+ return info?.nametag ?? null;
9002
+ } catch {
9003
+ return null;
9004
+ }
9005
+ } : void 0);
9006
+ return scanAddressesImpl(
9007
+ (index, isChange) => this._deriveAddressInternal(index, isChange),
9008
+ { ...options, resolveNametag }
9009
+ );
9010
+ }
9011
+ /**
9012
+ * Bulk-track scanned addresses with visibility and nametag data.
9013
+ * Selected addresses get `hidden: false`, unselected get `hidden: true`.
9014
+ * Performs only 2 storage writes total (tracked addresses + nametags).
9015
+ */
9016
+ async trackScannedAddresses(entries) {
9017
+ this.ensureReady();
9018
+ for (const { index, hidden, nametag } of entries) {
9019
+ const tracked = await this.ensureAddressTracked(index);
9020
+ if (nametag) {
9021
+ let nametags = this._addressNametags.get(tracked.addressId);
9022
+ if (!nametags) {
9023
+ nametags = /* @__PURE__ */ new Map();
9024
+ this._addressNametags.set(tracked.addressId, nametags);
9025
+ }
9026
+ if (!nametags.has(0)) nametags.set(0, nametag);
9027
+ }
9028
+ if (tracked.hidden !== hidden) {
9029
+ tracked.hidden = hidden;
9030
+ }
9031
+ }
9032
+ await this.persistTrackedAddresses();
9033
+ await this.persistAddressNametags();
9034
+ }
8574
9035
  // ===========================================================================
8575
9036
  // Public Methods - Status
8576
9037
  // ===========================================================================
@@ -8955,35 +9416,40 @@ var Sphere = class _Sphere {
8955
9416
  if (this._identity?.nametag) {
8956
9417
  return;
8957
9418
  }
8958
- if (!this._transport.recoverNametag) {
9419
+ let recoveredNametag = null;
9420
+ if (this._transport.recoverNametag) {
9421
+ try {
9422
+ recoveredNametag = await this._transport.recoverNametag();
9423
+ } catch {
9424
+ }
9425
+ }
9426
+ if (!recoveredNametag && this._transport.resolveAddressInfo && this._identity?.l1Address) {
9427
+ try {
9428
+ const info = await this._transport.resolveAddressInfo(this._identity.l1Address);
9429
+ if (info?.nametag) {
9430
+ recoveredNametag = info.nametag;
9431
+ }
9432
+ } catch {
9433
+ }
9434
+ }
9435
+ if (!recoveredNametag) {
8959
9436
  return;
8960
9437
  }
8961
9438
  try {
8962
- const recoveredNametag = await this._transport.recoverNametag();
8963
- if (recoveredNametag) {
8964
- if (this._identity) {
8965
- this._identity.nametag = recoveredNametag;
8966
- await this._updateCachedProxyAddress();
8967
- }
8968
- const entry = await this.ensureAddressTracked(this._currentAddressIndex);
8969
- let nametags = this._addressNametags.get(entry.addressId);
8970
- if (!nametags) {
8971
- nametags = /* @__PURE__ */ new Map();
8972
- this._addressNametags.set(entry.addressId, nametags);
8973
- }
8974
- const nextIndex = nametags.size;
8975
- nametags.set(nextIndex, recoveredNametag);
8976
- await this.persistAddressNametags();
8977
- if (this._transport.publishIdentityBinding) {
8978
- await this._transport.publishIdentityBinding(
8979
- this._identity.chainPubkey,
8980
- this._identity.l1Address,
8981
- this._identity.directAddress || "",
8982
- recoveredNametag
8983
- );
8984
- }
8985
- this.emitEvent("nametag:recovered", { nametag: recoveredNametag });
9439
+ if (this._identity) {
9440
+ this._identity.nametag = recoveredNametag;
9441
+ await this._updateCachedProxyAddress();
9442
+ }
9443
+ const entry = await this.ensureAddressTracked(this._currentAddressIndex);
9444
+ let nametags = this._addressNametags.get(entry.addressId);
9445
+ if (!nametags) {
9446
+ nametags = /* @__PURE__ */ new Map();
9447
+ this._addressNametags.set(entry.addressId, nametags);
8986
9448
  }
9449
+ const nextIndex = nametags.size;
9450
+ nametags.set(nextIndex, recoveredNametag);
9451
+ await this.persistAddressNametags();
9452
+ this.emitEvent("nametag:recovered", { nametag: recoveredNametag });
8987
9453
  } catch {
8988
9454
  }
8989
9455
  }
@@ -9152,8 +9618,8 @@ var Sphere = class _Sphere {
9152
9618
  };
9153
9619
  this._masterKey = masterKey;
9154
9620
  }
9155
- async initializeIdentityFromMasterKey(masterKey, chainCode, derivationPath) {
9156
- const basePath = derivationPath ?? DEFAULT_BASE_PATH;
9621
+ async initializeIdentityFromMasterKey(masterKey, chainCode, _derivationPath) {
9622
+ const basePath = this._basePath;
9157
9623
  const fullPath = `${basePath}/0/0`;
9158
9624
  let privateKey;
9159
9625
  if (chainCode) {
@@ -9164,8 +9630,12 @@ var Sphere = class _Sphere {
9164
9630
  chainCode
9165
9631
  };
9166
9632
  } else {
9167
- privateKey = masterKey;
9168
- this._masterKey = null;
9633
+ const addr0 = generateAddressFromMasterKey(masterKey, 0);
9634
+ privateKey = addr0.privateKey;
9635
+ this._masterKey = {
9636
+ privateKey: masterKey,
9637
+ chainCode: ""
9638
+ };
9169
9639
  }
9170
9640
  const publicKey = getPublicKey(privateKey);
9171
9641
  const address = publicKeyToAddress(publicKey, "alpha");
@@ -9188,8 +9658,12 @@ var Sphere = class _Sphere {
9188
9658
  for (const provider of this._tokenStorageProviders.values()) {
9189
9659
  provider.setIdentity(this._identity);
9190
9660
  }
9191
- await this._storage.connect();
9192
- await this._transport.connect();
9661
+ if (!this._storage.isConnected()) {
9662
+ await this._storage.connect();
9663
+ }
9664
+ if (!this._transport.isConnected()) {
9665
+ await this._transport.connect();
9666
+ }
9193
9667
  await this._oracle.initialize();
9194
9668
  for (const provider of this._tokenStorageProviders.values()) {
9195
9669
  await provider.initialize();
@@ -9205,7 +9679,7 @@ var Sphere = class _Sphere {
9205
9679
  oracle: this._oracle,
9206
9680
  emitEvent,
9207
9681
  // Pass chain code for L1 HD derivation
9208
- chainCode: this._masterKey?.chainCode,
9682
+ chainCode: this._masterKey?.chainCode || void 0,
9209
9683
  price: this._priceProvider ?? void 0
9210
9684
  });
9211
9685
  this._communications.initialize({
@@ -9292,6 +9766,9 @@ var CurrencyUtils = {
9292
9766
  toHumanReadable,
9293
9767
  format: formatAmount
9294
9768
  };
9769
+
9770
+ // core/index.ts
9771
+ init_bech32();
9295
9772
  // Annotate the CommonJS export names for ESM import in node:
9296
9773
  0 && (module.exports = {
9297
9774
  CHARSET,
@@ -9313,6 +9790,7 @@ var CurrencyUtils = {
9313
9790
  decryptJson,
9314
9791
  decryptMnemonic,
9315
9792
  decryptSimple,
9793
+ decryptWithSalt,
9316
9794
  deriveAddressInfo,
9317
9795
  deriveChildKey,
9318
9796
  deriveKeyAtPath,
@@ -9354,6 +9832,7 @@ var CurrencyUtils = {
9354
9832
  randomHex,
9355
9833
  randomUUID,
9356
9834
  ripemd160,
9835
+ scanAddressesImpl,
9357
9836
  serializeEncrypted,
9358
9837
  sha256,
9359
9838
  sleep,