@unicitylabs/sphere-sdk 0.5.3 → 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 (50) hide show
  1. package/README.md +2 -0
  2. package/dist/connect/index.cjs +145 -23
  3. package/dist/connect/index.cjs.map +1 -1
  4. package/dist/connect/index.d.cts +15 -2
  5. package/dist/connect/index.d.ts +15 -2
  6. package/dist/connect/index.js +145 -23
  7. package/dist/connect/index.js.map +1 -1
  8. package/dist/core/index.cjs +670 -473
  9. package/dist/core/index.cjs.map +1 -1
  10. package/dist/core/index.d.cts +123 -2
  11. package/dist/core/index.d.ts +123 -2
  12. package/dist/core/index.js +667 -473
  13. package/dist/core/index.js.map +1 -1
  14. package/dist/impl/browser/connect/index.cjs +119 -1
  15. package/dist/impl/browser/connect/index.cjs.map +1 -1
  16. package/dist/impl/browser/connect/index.d.cts +53 -1
  17. package/dist/impl/browser/connect/index.d.ts +53 -1
  18. package/dist/impl/browser/connect/index.js +119 -1
  19. package/dist/impl/browser/connect/index.js.map +1 -1
  20. package/dist/impl/browser/index.cjs +306 -193
  21. package/dist/impl/browser/index.cjs.map +1 -1
  22. package/dist/impl/browser/index.js +306 -193
  23. package/dist/impl/browser/index.js.map +1 -1
  24. package/dist/impl/browser/ipfs.cjs +134 -19
  25. package/dist/impl/browser/ipfs.cjs.map +1 -1
  26. package/dist/impl/browser/ipfs.js +134 -19
  27. package/dist/impl/browser/ipfs.js.map +1 -1
  28. package/dist/impl/nodejs/connect/index.cjs +101 -6
  29. package/dist/impl/nodejs/connect/index.cjs.map +1 -1
  30. package/dist/impl/nodejs/connect/index.d.cts +2 -0
  31. package/dist/impl/nodejs/connect/index.d.ts +2 -0
  32. package/dist/impl/nodejs/connect/index.js +101 -6
  33. package/dist/impl/nodejs/connect/index.js.map +1 -1
  34. package/dist/impl/nodejs/index.cjs +267 -152
  35. package/dist/impl/nodejs/index.cjs.map +1 -1
  36. package/dist/impl/nodejs/index.d.cts +2 -1
  37. package/dist/impl/nodejs/index.d.ts +2 -1
  38. package/dist/impl/nodejs/index.js +267 -152
  39. package/dist/impl/nodejs/index.js.map +1 -1
  40. package/dist/index.cjs +682 -493
  41. package/dist/index.cjs.map +1 -1
  42. package/dist/index.d.cts +124 -8
  43. package/dist/index.d.ts +124 -8
  44. package/dist/index.js +680 -493
  45. package/dist/index.js.map +1 -1
  46. package/dist/l1/index.cjs +139 -32
  47. package/dist/l1/index.cjs.map +1 -1
  48. package/dist/l1/index.js +139 -32
  49. package/dist/l1/index.js.map +1 -1
  50. package/package.json +1 -16
package/dist/l1/index.cjs CHANGED
@@ -112,6 +112,18 @@ function domIdToPath(encoded) {
112
112
  }).join("/");
113
113
  }
114
114
 
115
+ // core/errors.ts
116
+ var SphereError = class extends Error {
117
+ code;
118
+ cause;
119
+ constructor(message, code, cause) {
120
+ super(message);
121
+ this.name = "SphereError";
122
+ this.code = code;
123
+ this.cause = cause;
124
+ }
125
+ };
126
+
115
127
  // core/bech32.ts
116
128
  var CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l";
117
129
  var GENERATOR = [996825010, 642813549, 513874426, 1027748829, 705979059];
@@ -168,11 +180,11 @@ function bech32Checksum(hrp, data) {
168
180
  }
169
181
  function encodeBech32(hrp, version, program) {
170
182
  if (version < 0 || version > 16) {
171
- throw new Error("Invalid witness version");
183
+ throw new SphereError("Invalid witness version", "VALIDATION_ERROR");
172
184
  }
173
185
  const converted = convertBits(Array.from(program), 8, 5, true);
174
186
  if (!converted) {
175
- throw new Error("Failed to convert bits");
187
+ throw new SphereError("Failed to convert bits", "VALIDATION_ERROR");
176
188
  }
177
189
  const data = [version].concat(converted);
178
190
  const checksum = bech32Checksum(hrp, data);
@@ -219,7 +231,7 @@ function bytesToHex(buf) {
219
231
  }
220
232
  function addressToScriptHash(address) {
221
233
  const decoded = decodeBech32(address);
222
- if (!decoded) throw new Error("Invalid bech32 address: " + address);
234
+ if (!decoded) throw new SphereError("Invalid bech32 address: " + address, "VALIDATION_ERROR");
223
235
  const scriptHex = "0014" + bytesToHex(decoded.data);
224
236
  const sha = import_crypto_js.default.SHA256(import_crypto_js.default.enc.Hex.parse(scriptHex)).toString();
225
237
  return sha.match(/../g).reverse().join("");
@@ -242,7 +254,7 @@ function generateMasterKey(seedHex) {
242
254
  const IR = I.substring(64);
243
255
  const masterKeyBigInt = BigInt("0x" + IL);
244
256
  if (masterKeyBigInt === 0n || masterKeyBigInt >= CURVE_ORDER) {
245
- throw new Error("Invalid master key generated");
257
+ throw new SphereError("Invalid master key generated", "VALIDATION_ERROR");
246
258
  }
247
259
  return {
248
260
  privateKey: IL,
@@ -270,11 +282,11 @@ function deriveChildKey(parentPrivKey, parentChainCode, index) {
270
282
  const ilBigInt = BigInt("0x" + IL);
271
283
  const parentKeyBigInt = BigInt("0x" + parentPrivKey);
272
284
  if (ilBigInt >= CURVE_ORDER) {
273
- throw new Error("Invalid key: IL >= curve order");
285
+ throw new SphereError("Invalid key: IL >= curve order", "VALIDATION_ERROR");
274
286
  }
275
287
  const childKeyBigInt = (ilBigInt + parentKeyBigInt) % CURVE_ORDER;
276
288
  if (childKeyBigInt === 0n) {
277
- throw new Error("Invalid key: child key is zero");
289
+ throw new SphereError("Invalid key: child key is zero", "VALIDATION_ERROR");
278
290
  }
279
291
  const childPrivKey = childKeyBigInt.toString(16).padStart(64, "0");
280
292
  return {
@@ -444,6 +456,98 @@ function generateHDAddress(masterPriv, chainCode, index) {
444
456
  return generateAddressInfo(child.privateKey, index, path);
445
457
  }
446
458
 
459
+ // core/logger.ts
460
+ var LOGGER_KEY = "__sphere_sdk_logger__";
461
+ function getState() {
462
+ const g = globalThis;
463
+ if (!g[LOGGER_KEY]) {
464
+ g[LOGGER_KEY] = { debug: false, tags: {}, handler: null };
465
+ }
466
+ return g[LOGGER_KEY];
467
+ }
468
+ function isEnabled(tag) {
469
+ const state = getState();
470
+ if (tag in state.tags) return state.tags[tag];
471
+ return state.debug;
472
+ }
473
+ var logger = {
474
+ /**
475
+ * Configure the logger. Can be called multiple times (last write wins).
476
+ * Typically called by createBrowserProviders(), createNodeProviders(), or Sphere.init().
477
+ */
478
+ configure(config) {
479
+ const state = getState();
480
+ if (config.debug !== void 0) state.debug = config.debug;
481
+ if (config.handler !== void 0) state.handler = config.handler;
482
+ },
483
+ /**
484
+ * Enable/disable debug logging for a specific tag.
485
+ * Per-tag setting overrides the global debug flag.
486
+ *
487
+ * @example
488
+ * ```ts
489
+ * logger.setTagDebug('Nostr', true); // enable only Nostr logs
490
+ * logger.setTagDebug('Nostr', false); // disable Nostr logs even if global debug=true
491
+ * ```
492
+ */
493
+ setTagDebug(tag, enabled) {
494
+ getState().tags[tag] = enabled;
495
+ },
496
+ /**
497
+ * Clear per-tag override, falling back to global debug flag.
498
+ */
499
+ clearTagDebug(tag) {
500
+ delete getState().tags[tag];
501
+ },
502
+ /** Returns true if debug mode is enabled for the given tag (or globally). */
503
+ isDebugEnabled(tag) {
504
+ if (tag) return isEnabled(tag);
505
+ return getState().debug;
506
+ },
507
+ /**
508
+ * Debug-level log. Only shown when debug is enabled (globally or for this tag).
509
+ * Use for detailed operational information.
510
+ */
511
+ debug(tag, message, ...args) {
512
+ if (!isEnabled(tag)) return;
513
+ const state = getState();
514
+ if (state.handler) {
515
+ state.handler("debug", tag, message, ...args);
516
+ } else {
517
+ console.log(`[${tag}]`, message, ...args);
518
+ }
519
+ },
520
+ /**
521
+ * Warning-level log. ALWAYS shown regardless of debug flag.
522
+ * Use for important but non-critical issues (timeouts, retries, degraded state).
523
+ */
524
+ warn(tag, message, ...args) {
525
+ const state = getState();
526
+ if (state.handler) {
527
+ state.handler("warn", tag, message, ...args);
528
+ } else {
529
+ console.warn(`[${tag}]`, message, ...args);
530
+ }
531
+ },
532
+ /**
533
+ * Error-level log. ALWAYS shown regardless of debug flag.
534
+ * Use for critical failures that should never be silenced.
535
+ */
536
+ error(tag, message, ...args) {
537
+ const state = getState();
538
+ if (state.handler) {
539
+ state.handler("error", tag, message, ...args);
540
+ } else {
541
+ console.error(`[${tag}]`, message, ...args);
542
+ }
543
+ },
544
+ /** Reset all logger state (debug flag, tags, handler). Primarily for tests. */
545
+ reset() {
546
+ const g = globalThis;
547
+ delete g[LOGGER_KEY];
548
+ }
549
+ };
550
+
447
551
  // constants.ts
448
552
  var STORAGE_KEYS_GLOBAL = {
449
553
  /** Encrypted BIP39 mnemonic */
@@ -572,7 +676,7 @@ function connect(endpoint = DEFAULT_ENDPOINT) {
572
676
  try {
573
677
  ws = new WebSocket(endpoint);
574
678
  } catch (err) {
575
- console.error("[L1] WebSocket constructor threw exception:", err);
679
+ logger.error("L1", "WebSocket constructor threw exception:", err);
576
680
  isConnecting = false;
577
681
  reject(err);
578
682
  return;
@@ -608,7 +712,7 @@ function connect(endpoint = DEFAULT_ENDPOINT) {
608
712
  return;
609
713
  }
610
714
  if (reconnectAttempts >= MAX_RECONNECT_ATTEMPTS) {
611
- console.error("[L1] Max reconnect attempts reached. Giving up.");
715
+ logger.error("L1", "Max reconnect attempts reached. Giving up.");
612
716
  isConnecting = false;
613
717
  const error = new Error("Max reconnect attempts reached");
614
718
  connectionCallbacks.forEach((cb) => {
@@ -624,8 +728,9 @@ function connect(endpoint = DEFAULT_ENDPOINT) {
624
728
  }
625
729
  const delay = Math.min(BASE_DELAY * Math.pow(2, reconnectAttempts), MAX_DELAY);
626
730
  reconnectAttempts++;
627
- console.warn(
628
- `[L1] WebSocket closed unexpectedly. Reconnecting in ${delay}ms (attempt ${reconnectAttempts}/${MAX_RECONNECT_ATTEMPTS})...`
731
+ logger.warn(
732
+ "L1",
733
+ `WebSocket closed unexpectedly. Reconnecting in ${delay}ms (attempt ${reconnectAttempts}/${MAX_RECONNECT_ATTEMPTS})...`
629
734
  );
630
735
  setTimeout(() => {
631
736
  connect(endpoint).then(() => {
@@ -642,7 +747,7 @@ function connect(endpoint = DEFAULT_ENDPOINT) {
642
747
  }, delay);
643
748
  };
644
749
  ws.onerror = (err) => {
645
- console.error("[L1] WebSocket error:", err);
750
+ logger.error("L1", "WebSocket error:", err);
646
751
  };
647
752
  ws.onmessage = (msg) => handleMessage(msg);
648
753
  });
@@ -700,7 +805,7 @@ async function getUtxo(address) {
700
805
  const scripthash = addressToScriptHash(address);
701
806
  const result = await rpc("blockchain.scripthash.listunspent", [scripthash]);
702
807
  if (!Array.isArray(result)) {
703
- console.warn("listunspent returned non-array:", result);
808
+ logger.warn("L1", "listunspent returned non-array:", result);
704
809
  return [];
705
810
  }
706
811
  return result.map((u) => ({
@@ -752,7 +857,7 @@ async function getTransactionHistory(address) {
752
857
  const scriptHash = addressToScriptHash(address);
753
858
  const result = await rpc("blockchain.scripthash.get_history", [scriptHash]);
754
859
  if (!Array.isArray(result)) {
755
- console.warn("get_history returned non-array:", result);
860
+ logger.warn("L1", "get_history returned non-array:", result);
756
861
  return [];
757
862
  }
758
863
  return result;
@@ -768,7 +873,7 @@ async function getCurrentBlockHeight() {
768
873
  const header = await rpc("blockchain.headers.subscribe", []);
769
874
  return header?.height || 0;
770
875
  } catch (err) {
771
- console.error("Error getting current block height:", err);
876
+ logger.error("L1", "Error getting current block height:", err);
772
877
  return 0;
773
878
  }
774
879
  }
@@ -1040,7 +1145,7 @@ var VestingClassifier = class {
1040
1145
  await new Promise((resolve) => {
1041
1146
  const req = indexedDB.deleteDatabase(this.dbName);
1042
1147
  const timer = setTimeout(() => {
1043
- console.warn(`[VestingClassifier] destroy: deleteDatabase timed out for ${this.dbName}`);
1148
+ logger.warn("L1", ` destroy: deleteDatabase timed out for ${this.dbName}`);
1044
1149
  resolve();
1045
1150
  }, 3e3);
1046
1151
  req.onsuccess = () => {
@@ -1052,7 +1157,7 @@ var VestingClassifier = class {
1052
1157
  resolve();
1053
1158
  };
1054
1159
  req.onblocked = () => {
1055
- console.warn(`[VestingClassifier] destroy: deleteDatabase blocked for ${this.dbName}, waiting...`);
1160
+ logger.warn("L1", ` destroy: deleteDatabase blocked for ${this.dbName}, waiting...`);
1056
1161
  };
1057
1162
  });
1058
1163
  }
@@ -1070,7 +1175,7 @@ var VestingStateManager = class {
1070
1175
  */
1071
1176
  setMode(mode) {
1072
1177
  if (!["all", "vested", "unvested"].includes(mode)) {
1073
- throw new Error(`Invalid vesting mode: ${mode}`);
1178
+ throw new SphereError(`Invalid vesting mode: ${mode}`, "VALIDATION_ERROR");
1074
1179
  }
1075
1180
  this.currentMode = mode;
1076
1181
  }
@@ -1107,10 +1212,10 @@ var VestingStateManager = class {
1107
1212
  }
1108
1213
  });
1109
1214
  if (result.errors.length > 0) {
1110
- console.warn(`Vesting classification errors: ${result.errors.length}`);
1215
+ logger.warn("L1", `Vesting classification errors: ${result.errors.length}`);
1111
1216
  result.errors.slice(0, 5).forEach((err) => {
1112
1217
  const txHash = err.utxo.tx_hash || err.utxo.txid;
1113
- console.warn(` ${txHash}: ${err.error}`);
1218
+ logger.warn("L1", ` ${txHash}: ${err.error}`);
1114
1219
  });
1115
1220
  }
1116
1221
  } finally {
@@ -1232,16 +1337,17 @@ var WalletAddressHelper = class {
1232
1337
  */
1233
1338
  static add(wallet, newAddress) {
1234
1339
  if (!newAddress.path) {
1235
- throw new Error("Cannot add address without a path");
1340
+ throw new SphereError("Cannot add address without a path", "INVALID_CONFIG");
1236
1341
  }
1237
1342
  const existing = this.findByPath(wallet, newAddress.path);
1238
1343
  if (existing) {
1239
1344
  if (existing.address !== newAddress.address) {
1240
- throw new Error(
1345
+ throw new SphereError(
1241
1346
  `CRITICAL: Attempted to overwrite address for path ${newAddress.path}
1242
1347
  Existing: ${existing.address}
1243
1348
  New: ${newAddress.address}
1244
- This indicates master key corruption or derivation logic error.`
1349
+ This indicates master key corruption or derivation logic error.`,
1350
+ "INVALID_CONFIG"
1245
1351
  );
1246
1352
  }
1247
1353
  return wallet;
@@ -1300,9 +1406,10 @@ This indicates master key corruption or derivation logic error.`
1300
1406
  const uniquePaths = new Set(paths);
1301
1407
  if (paths.length !== uniquePaths.size) {
1302
1408
  const duplicates = paths.filter((p, i) => paths.indexOf(p) !== i);
1303
- throw new Error(
1409
+ throw new SphereError(
1304
1410
  `CRITICAL: Wallet has duplicate paths: ${duplicates.join(", ")}
1305
- This indicates data corruption. Please restore from backup.`
1411
+ This indicates data corruption. Please restore from backup.`,
1412
+ "INVALID_CONFIG"
1306
1413
  );
1307
1414
  }
1308
1415
  }
@@ -1334,11 +1441,11 @@ var DUST = 546;
1334
1441
  var SAT = 1e8;
1335
1442
  function createScriptPubKey(address) {
1336
1443
  if (!address || typeof address !== "string") {
1337
- throw new Error("Invalid address: must be a string");
1444
+ throw new SphereError("Invalid address: must be a string", "VALIDATION_ERROR");
1338
1445
  }
1339
1446
  const decoded = decodeBech32(address);
1340
1447
  if (!decoded) {
1341
- throw new Error("Invalid bech32 address: " + address);
1448
+ throw new SphereError("Invalid bech32 address: " + address, "VALIDATION_ERROR");
1342
1449
  }
1343
1450
  const dataHex = Array.from(decoded.data).map((byte) => byte.toString(16).padStart(2, "0")).join("");
1344
1451
  return "0014" + dataHex;
@@ -1462,7 +1569,7 @@ function createAndSignTransaction(wallet, txPlan) {
1462
1569
  privateKeyHex = wallet.masterPrivateKey;
1463
1570
  }
1464
1571
  if (!privateKeyHex) {
1465
- throw new Error("No private key available for address: " + fromAddress);
1572
+ throw new SphereError("No private key available for address: " + fromAddress, "INVALID_CONFIG");
1466
1573
  }
1467
1574
  const keyPair = ec2.keyFromPrivate(privateKeyHex, "hex");
1468
1575
  const publicKey = keyPair.getPublic(true, "hex");
@@ -1561,7 +1668,7 @@ function collectUtxosForAmount(utxoList, amountSats, recipientAddress, senderAdd
1561
1668
  }
1562
1669
  async function createTransactionPlan(wallet, toAddress, amountAlpha, fromAddress) {
1563
1670
  if (!decodeBech32(toAddress)) {
1564
- throw new Error("Invalid recipient address");
1671
+ throw new SphereError("Invalid recipient address", "INVALID_RECIPIENT");
1565
1672
  }
1566
1673
  const defaultAddr = WalletAddressHelper.getDefault(wallet);
1567
1674
  const senderAddress = fromAddress || defaultAddr.address;
@@ -1570,21 +1677,21 @@ async function createTransactionPlan(wallet, toAddress, amountAlpha, fromAddress
1570
1677
  const currentMode = vestingState.getMode();
1571
1678
  if (vestingState.hasClassifiedData(senderAddress)) {
1572
1679
  utxos = vestingState.getFilteredUtxos(senderAddress);
1573
- console.log(`Using ${utxos.length} ${currentMode} UTXOs`);
1680
+ logger.debug("L1", `Using ${utxos.length} ${currentMode} UTXOs`);
1574
1681
  } else {
1575
1682
  utxos = await getUtxo(senderAddress);
1576
- console.log(`Using ${utxos.length} UTXOs (vesting not classified yet)`);
1683
+ logger.debug("L1", `Using ${utxos.length} UTXOs (vesting not classified yet)`);
1577
1684
  }
1578
1685
  if (!Array.isArray(utxos) || utxos.length === 0) {
1579
1686
  const modeText = currentMode !== "all" ? ` (${currentMode} coins)` : "";
1580
- throw new Error(`No UTXOs available${modeText} for address: ` + senderAddress);
1687
+ throw new SphereError(`No UTXOs available${modeText} for address: ` + senderAddress, "INSUFFICIENT_BALANCE");
1581
1688
  }
1582
1689
  return collectUtxosForAmount(utxos, amountSats, toAddress, senderAddress);
1583
1690
  }
1584
1691
  async function sendAlpha(wallet, toAddress, amountAlpha, fromAddress) {
1585
1692
  const plan = await createTransactionPlan(wallet, toAddress, amountAlpha, fromAddress);
1586
1693
  if (!plan.success) {
1587
- throw new Error(plan.error || "Transaction planning failed");
1694
+ throw new SphereError(plan.error || "Transaction planning failed", "TRANSFER_FAILED");
1588
1695
  }
1589
1696
  const results = [];
1590
1697
  for (const tx of plan.transactions) {