@unicitylabs/sphere-sdk 0.5.4 → 0.5.5

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.
Files changed (39) hide show
  1. package/dist/connect/index.cjs +128 -22
  2. package/dist/connect/index.cjs.map +1 -1
  3. package/dist/connect/index.js +128 -22
  4. package/dist/connect/index.js.map +1 -1
  5. package/dist/core/index.cjs +670 -473
  6. package/dist/core/index.cjs.map +1 -1
  7. package/dist/core/index.d.cts +123 -2
  8. package/dist/core/index.d.ts +123 -2
  9. package/dist/core/index.js +667 -473
  10. package/dist/core/index.js.map +1 -1
  11. package/dist/impl/browser/index.cjs +306 -193
  12. package/dist/impl/browser/index.cjs.map +1 -1
  13. package/dist/impl/browser/index.js +306 -193
  14. package/dist/impl/browser/index.js.map +1 -1
  15. package/dist/impl/browser/ipfs.cjs +134 -19
  16. package/dist/impl/browser/ipfs.cjs.map +1 -1
  17. package/dist/impl/browser/ipfs.js +134 -19
  18. package/dist/impl/browser/ipfs.js.map +1 -1
  19. package/dist/impl/nodejs/connect/index.cjs +101 -6
  20. package/dist/impl/nodejs/connect/index.cjs.map +1 -1
  21. package/dist/impl/nodejs/connect/index.js +101 -6
  22. package/dist/impl/nodejs/connect/index.js.map +1 -1
  23. package/dist/impl/nodejs/index.cjs +267 -152
  24. package/dist/impl/nodejs/index.cjs.map +1 -1
  25. package/dist/impl/nodejs/index.d.cts +2 -1
  26. package/dist/impl/nodejs/index.d.ts +2 -1
  27. package/dist/impl/nodejs/index.js +267 -152
  28. package/dist/impl/nodejs/index.js.map +1 -1
  29. package/dist/index.cjs +682 -493
  30. package/dist/index.cjs.map +1 -1
  31. package/dist/index.d.cts +124 -8
  32. package/dist/index.d.ts +124 -8
  33. package/dist/index.js +680 -493
  34. package/dist/index.js.map +1 -1
  35. package/dist/l1/index.cjs +139 -32
  36. package/dist/l1/index.cjs.map +1 -1
  37. package/dist/l1/index.js +139 -32
  38. package/dist/l1/index.js.map +1 -1
  39. package/package.json +1 -1
package/dist/l1/index.js CHANGED
@@ -23,6 +23,18 @@ function domIdToPath(encoded) {
23
23
  }).join("/");
24
24
  }
25
25
 
26
+ // core/errors.ts
27
+ var SphereError = class extends Error {
28
+ code;
29
+ cause;
30
+ constructor(message, code, cause) {
31
+ super(message);
32
+ this.name = "SphereError";
33
+ this.code = code;
34
+ this.cause = cause;
35
+ }
36
+ };
37
+
26
38
  // core/bech32.ts
27
39
  var CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l";
28
40
  var GENERATOR = [996825010, 642813549, 513874426, 1027748829, 705979059];
@@ -79,11 +91,11 @@ function bech32Checksum(hrp, data) {
79
91
  }
80
92
  function encodeBech32(hrp, version, program) {
81
93
  if (version < 0 || version > 16) {
82
- throw new Error("Invalid witness version");
94
+ throw new SphereError("Invalid witness version", "VALIDATION_ERROR");
83
95
  }
84
96
  const converted = convertBits(Array.from(program), 8, 5, true);
85
97
  if (!converted) {
86
- throw new Error("Failed to convert bits");
98
+ throw new SphereError("Failed to convert bits", "VALIDATION_ERROR");
87
99
  }
88
100
  const data = [version].concat(converted);
89
101
  const checksum = bech32Checksum(hrp, data);
@@ -130,7 +142,7 @@ function bytesToHex(buf) {
130
142
  }
131
143
  function addressToScriptHash(address) {
132
144
  const decoded = decodeBech32(address);
133
- if (!decoded) throw new Error("Invalid bech32 address: " + address);
145
+ if (!decoded) throw new SphereError("Invalid bech32 address: " + address, "VALIDATION_ERROR");
134
146
  const scriptHex = "0014" + bytesToHex(decoded.data);
135
147
  const sha = CryptoJS.SHA256(CryptoJS.enc.Hex.parse(scriptHex)).toString();
136
148
  return sha.match(/../g).reverse().join("");
@@ -153,7 +165,7 @@ function generateMasterKey(seedHex) {
153
165
  const IR = I.substring(64);
154
166
  const masterKeyBigInt = BigInt("0x" + IL);
155
167
  if (masterKeyBigInt === 0n || masterKeyBigInt >= CURVE_ORDER) {
156
- throw new Error("Invalid master key generated");
168
+ throw new SphereError("Invalid master key generated", "VALIDATION_ERROR");
157
169
  }
158
170
  return {
159
171
  privateKey: IL,
@@ -181,11 +193,11 @@ function deriveChildKey(parentPrivKey, parentChainCode, index) {
181
193
  const ilBigInt = BigInt("0x" + IL);
182
194
  const parentKeyBigInt = BigInt("0x" + parentPrivKey);
183
195
  if (ilBigInt >= CURVE_ORDER) {
184
- throw new Error("Invalid key: IL >= curve order");
196
+ throw new SphereError("Invalid key: IL >= curve order", "VALIDATION_ERROR");
185
197
  }
186
198
  const childKeyBigInt = (ilBigInt + parentKeyBigInt) % CURVE_ORDER;
187
199
  if (childKeyBigInt === 0n) {
188
- throw new Error("Invalid key: child key is zero");
200
+ throw new SphereError("Invalid key: child key is zero", "VALIDATION_ERROR");
189
201
  }
190
202
  const childPrivKey = childKeyBigInt.toString(16).padStart(64, "0");
191
203
  return {
@@ -355,6 +367,98 @@ function generateHDAddress(masterPriv, chainCode, index) {
355
367
  return generateAddressInfo(child.privateKey, index, path);
356
368
  }
357
369
 
370
+ // core/logger.ts
371
+ var LOGGER_KEY = "__sphere_sdk_logger__";
372
+ function getState() {
373
+ const g = globalThis;
374
+ if (!g[LOGGER_KEY]) {
375
+ g[LOGGER_KEY] = { debug: false, tags: {}, handler: null };
376
+ }
377
+ return g[LOGGER_KEY];
378
+ }
379
+ function isEnabled(tag) {
380
+ const state = getState();
381
+ if (tag in state.tags) return state.tags[tag];
382
+ return state.debug;
383
+ }
384
+ var logger = {
385
+ /**
386
+ * Configure the logger. Can be called multiple times (last write wins).
387
+ * Typically called by createBrowserProviders(), createNodeProviders(), or Sphere.init().
388
+ */
389
+ configure(config) {
390
+ const state = getState();
391
+ if (config.debug !== void 0) state.debug = config.debug;
392
+ if (config.handler !== void 0) state.handler = config.handler;
393
+ },
394
+ /**
395
+ * Enable/disable debug logging for a specific tag.
396
+ * Per-tag setting overrides the global debug flag.
397
+ *
398
+ * @example
399
+ * ```ts
400
+ * logger.setTagDebug('Nostr', true); // enable only Nostr logs
401
+ * logger.setTagDebug('Nostr', false); // disable Nostr logs even if global debug=true
402
+ * ```
403
+ */
404
+ setTagDebug(tag, enabled) {
405
+ getState().tags[tag] = enabled;
406
+ },
407
+ /**
408
+ * Clear per-tag override, falling back to global debug flag.
409
+ */
410
+ clearTagDebug(tag) {
411
+ delete getState().tags[tag];
412
+ },
413
+ /** Returns true if debug mode is enabled for the given tag (or globally). */
414
+ isDebugEnabled(tag) {
415
+ if (tag) return isEnabled(tag);
416
+ return getState().debug;
417
+ },
418
+ /**
419
+ * Debug-level log. Only shown when debug is enabled (globally or for this tag).
420
+ * Use for detailed operational information.
421
+ */
422
+ debug(tag, message, ...args) {
423
+ if (!isEnabled(tag)) return;
424
+ const state = getState();
425
+ if (state.handler) {
426
+ state.handler("debug", tag, message, ...args);
427
+ } else {
428
+ console.log(`[${tag}]`, message, ...args);
429
+ }
430
+ },
431
+ /**
432
+ * Warning-level log. ALWAYS shown regardless of debug flag.
433
+ * Use for important but non-critical issues (timeouts, retries, degraded state).
434
+ */
435
+ warn(tag, message, ...args) {
436
+ const state = getState();
437
+ if (state.handler) {
438
+ state.handler("warn", tag, message, ...args);
439
+ } else {
440
+ console.warn(`[${tag}]`, message, ...args);
441
+ }
442
+ },
443
+ /**
444
+ * Error-level log. ALWAYS shown regardless of debug flag.
445
+ * Use for critical failures that should never be silenced.
446
+ */
447
+ error(tag, message, ...args) {
448
+ const state = getState();
449
+ if (state.handler) {
450
+ state.handler("error", tag, message, ...args);
451
+ } else {
452
+ console.error(`[${tag}]`, message, ...args);
453
+ }
454
+ },
455
+ /** Reset all logger state (debug flag, tags, handler). Primarily for tests. */
456
+ reset() {
457
+ const g = globalThis;
458
+ delete g[LOGGER_KEY];
459
+ }
460
+ };
461
+
358
462
  // constants.ts
359
463
  var STORAGE_KEYS_GLOBAL = {
360
464
  /** Encrypted BIP39 mnemonic */
@@ -483,7 +587,7 @@ function connect(endpoint = DEFAULT_ENDPOINT) {
483
587
  try {
484
588
  ws = new WebSocket(endpoint);
485
589
  } catch (err) {
486
- console.error("[L1] WebSocket constructor threw exception:", err);
590
+ logger.error("L1", "WebSocket constructor threw exception:", err);
487
591
  isConnecting = false;
488
592
  reject(err);
489
593
  return;
@@ -519,7 +623,7 @@ function connect(endpoint = DEFAULT_ENDPOINT) {
519
623
  return;
520
624
  }
521
625
  if (reconnectAttempts >= MAX_RECONNECT_ATTEMPTS) {
522
- console.error("[L1] Max reconnect attempts reached. Giving up.");
626
+ logger.error("L1", "Max reconnect attempts reached. Giving up.");
523
627
  isConnecting = false;
524
628
  const error = new Error("Max reconnect attempts reached");
525
629
  connectionCallbacks.forEach((cb) => {
@@ -535,8 +639,9 @@ function connect(endpoint = DEFAULT_ENDPOINT) {
535
639
  }
536
640
  const delay = Math.min(BASE_DELAY * Math.pow(2, reconnectAttempts), MAX_DELAY);
537
641
  reconnectAttempts++;
538
- console.warn(
539
- `[L1] WebSocket closed unexpectedly. Reconnecting in ${delay}ms (attempt ${reconnectAttempts}/${MAX_RECONNECT_ATTEMPTS})...`
642
+ logger.warn(
643
+ "L1",
644
+ `WebSocket closed unexpectedly. Reconnecting in ${delay}ms (attempt ${reconnectAttempts}/${MAX_RECONNECT_ATTEMPTS})...`
540
645
  );
541
646
  setTimeout(() => {
542
647
  connect(endpoint).then(() => {
@@ -553,7 +658,7 @@ function connect(endpoint = DEFAULT_ENDPOINT) {
553
658
  }, delay);
554
659
  };
555
660
  ws.onerror = (err) => {
556
- console.error("[L1] WebSocket error:", err);
661
+ logger.error("L1", "WebSocket error:", err);
557
662
  };
558
663
  ws.onmessage = (msg) => handleMessage(msg);
559
664
  });
@@ -611,7 +716,7 @@ async function getUtxo(address) {
611
716
  const scripthash = addressToScriptHash(address);
612
717
  const result = await rpc("blockchain.scripthash.listunspent", [scripthash]);
613
718
  if (!Array.isArray(result)) {
614
- console.warn("listunspent returned non-array:", result);
719
+ logger.warn("L1", "listunspent returned non-array:", result);
615
720
  return [];
616
721
  }
617
722
  return result.map((u) => ({
@@ -663,7 +768,7 @@ async function getTransactionHistory(address) {
663
768
  const scriptHash = addressToScriptHash(address);
664
769
  const result = await rpc("blockchain.scripthash.get_history", [scriptHash]);
665
770
  if (!Array.isArray(result)) {
666
- console.warn("get_history returned non-array:", result);
771
+ logger.warn("L1", "get_history returned non-array:", result);
667
772
  return [];
668
773
  }
669
774
  return result;
@@ -679,7 +784,7 @@ async function getCurrentBlockHeight() {
679
784
  const header = await rpc("blockchain.headers.subscribe", []);
680
785
  return header?.height || 0;
681
786
  } catch (err) {
682
- console.error("Error getting current block height:", err);
787
+ logger.error("L1", "Error getting current block height:", err);
683
788
  return 0;
684
789
  }
685
790
  }
@@ -951,7 +1056,7 @@ var VestingClassifier = class {
951
1056
  await new Promise((resolve) => {
952
1057
  const req = indexedDB.deleteDatabase(this.dbName);
953
1058
  const timer = setTimeout(() => {
954
- console.warn(`[VestingClassifier] destroy: deleteDatabase timed out for ${this.dbName}`);
1059
+ logger.warn("L1", ` destroy: deleteDatabase timed out for ${this.dbName}`);
955
1060
  resolve();
956
1061
  }, 3e3);
957
1062
  req.onsuccess = () => {
@@ -963,7 +1068,7 @@ var VestingClassifier = class {
963
1068
  resolve();
964
1069
  };
965
1070
  req.onblocked = () => {
966
- console.warn(`[VestingClassifier] destroy: deleteDatabase blocked for ${this.dbName}, waiting...`);
1071
+ logger.warn("L1", ` destroy: deleteDatabase blocked for ${this.dbName}, waiting...`);
967
1072
  };
968
1073
  });
969
1074
  }
@@ -981,7 +1086,7 @@ var VestingStateManager = class {
981
1086
  */
982
1087
  setMode(mode) {
983
1088
  if (!["all", "vested", "unvested"].includes(mode)) {
984
- throw new Error(`Invalid vesting mode: ${mode}`);
1089
+ throw new SphereError(`Invalid vesting mode: ${mode}`, "VALIDATION_ERROR");
985
1090
  }
986
1091
  this.currentMode = mode;
987
1092
  }
@@ -1018,10 +1123,10 @@ var VestingStateManager = class {
1018
1123
  }
1019
1124
  });
1020
1125
  if (result.errors.length > 0) {
1021
- console.warn(`Vesting classification errors: ${result.errors.length}`);
1126
+ logger.warn("L1", `Vesting classification errors: ${result.errors.length}`);
1022
1127
  result.errors.slice(0, 5).forEach((err) => {
1023
1128
  const txHash = err.utxo.tx_hash || err.utxo.txid;
1024
- console.warn(` ${txHash}: ${err.error}`);
1129
+ logger.warn("L1", ` ${txHash}: ${err.error}`);
1025
1130
  });
1026
1131
  }
1027
1132
  } finally {
@@ -1143,16 +1248,17 @@ var WalletAddressHelper = class {
1143
1248
  */
1144
1249
  static add(wallet, newAddress) {
1145
1250
  if (!newAddress.path) {
1146
- throw new Error("Cannot add address without a path");
1251
+ throw new SphereError("Cannot add address without a path", "INVALID_CONFIG");
1147
1252
  }
1148
1253
  const existing = this.findByPath(wallet, newAddress.path);
1149
1254
  if (existing) {
1150
1255
  if (existing.address !== newAddress.address) {
1151
- throw new Error(
1256
+ throw new SphereError(
1152
1257
  `CRITICAL: Attempted to overwrite address for path ${newAddress.path}
1153
1258
  Existing: ${existing.address}
1154
1259
  New: ${newAddress.address}
1155
- This indicates master key corruption or derivation logic error.`
1260
+ This indicates master key corruption or derivation logic error.`,
1261
+ "INVALID_CONFIG"
1156
1262
  );
1157
1263
  }
1158
1264
  return wallet;
@@ -1211,9 +1317,10 @@ This indicates master key corruption or derivation logic error.`
1211
1317
  const uniquePaths = new Set(paths);
1212
1318
  if (paths.length !== uniquePaths.size) {
1213
1319
  const duplicates = paths.filter((p, i) => paths.indexOf(p) !== i);
1214
- throw new Error(
1320
+ throw new SphereError(
1215
1321
  `CRITICAL: Wallet has duplicate paths: ${duplicates.join(", ")}
1216
- This indicates data corruption. Please restore from backup.`
1322
+ This indicates data corruption. Please restore from backup.`,
1323
+ "INVALID_CONFIG"
1217
1324
  );
1218
1325
  }
1219
1326
  }
@@ -1245,11 +1352,11 @@ var DUST = 546;
1245
1352
  var SAT = 1e8;
1246
1353
  function createScriptPubKey(address) {
1247
1354
  if (!address || typeof address !== "string") {
1248
- throw new Error("Invalid address: must be a string");
1355
+ throw new SphereError("Invalid address: must be a string", "VALIDATION_ERROR");
1249
1356
  }
1250
1357
  const decoded = decodeBech32(address);
1251
1358
  if (!decoded) {
1252
- throw new Error("Invalid bech32 address: " + address);
1359
+ throw new SphereError("Invalid bech32 address: " + address, "VALIDATION_ERROR");
1253
1360
  }
1254
1361
  const dataHex = Array.from(decoded.data).map((byte) => byte.toString(16).padStart(2, "0")).join("");
1255
1362
  return "0014" + dataHex;
@@ -1373,7 +1480,7 @@ function createAndSignTransaction(wallet, txPlan) {
1373
1480
  privateKeyHex = wallet.masterPrivateKey;
1374
1481
  }
1375
1482
  if (!privateKeyHex) {
1376
- throw new Error("No private key available for address: " + fromAddress);
1483
+ throw new SphereError("No private key available for address: " + fromAddress, "INVALID_CONFIG");
1377
1484
  }
1378
1485
  const keyPair = ec2.keyFromPrivate(privateKeyHex, "hex");
1379
1486
  const publicKey = keyPair.getPublic(true, "hex");
@@ -1472,7 +1579,7 @@ function collectUtxosForAmount(utxoList, amountSats, recipientAddress, senderAdd
1472
1579
  }
1473
1580
  async function createTransactionPlan(wallet, toAddress, amountAlpha, fromAddress) {
1474
1581
  if (!decodeBech32(toAddress)) {
1475
- throw new Error("Invalid recipient address");
1582
+ throw new SphereError("Invalid recipient address", "INVALID_RECIPIENT");
1476
1583
  }
1477
1584
  const defaultAddr = WalletAddressHelper.getDefault(wallet);
1478
1585
  const senderAddress = fromAddress || defaultAddr.address;
@@ -1481,21 +1588,21 @@ async function createTransactionPlan(wallet, toAddress, amountAlpha, fromAddress
1481
1588
  const currentMode = vestingState.getMode();
1482
1589
  if (vestingState.hasClassifiedData(senderAddress)) {
1483
1590
  utxos = vestingState.getFilteredUtxos(senderAddress);
1484
- console.log(`Using ${utxos.length} ${currentMode} UTXOs`);
1591
+ logger.debug("L1", `Using ${utxos.length} ${currentMode} UTXOs`);
1485
1592
  } else {
1486
1593
  utxos = await getUtxo(senderAddress);
1487
- console.log(`Using ${utxos.length} UTXOs (vesting not classified yet)`);
1594
+ logger.debug("L1", `Using ${utxos.length} UTXOs (vesting not classified yet)`);
1488
1595
  }
1489
1596
  if (!Array.isArray(utxos) || utxos.length === 0) {
1490
1597
  const modeText = currentMode !== "all" ? ` (${currentMode} coins)` : "";
1491
- throw new Error(`No UTXOs available${modeText} for address: ` + senderAddress);
1598
+ throw new SphereError(`No UTXOs available${modeText} for address: ` + senderAddress, "INSUFFICIENT_BALANCE");
1492
1599
  }
1493
1600
  return collectUtxosForAmount(utxos, amountSats, toAddress, senderAddress);
1494
1601
  }
1495
1602
  async function sendAlpha(wallet, toAddress, amountAlpha, fromAddress) {
1496
1603
  const plan = await createTransactionPlan(wallet, toAddress, amountAlpha, fromAddress);
1497
1604
  if (!plan.success) {
1498
- throw new Error(plan.error || "Transaction planning failed");
1605
+ throw new SphereError(plan.error || "Transaction planning failed", "TRANSFER_FAILED");
1499
1606
  }
1500
1607
  const results = [];
1501
1608
  for (const tx of plan.transactions) {