@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.
- package/README.md +2 -0
- package/dist/connect/index.cjs +145 -23
- package/dist/connect/index.cjs.map +1 -1
- package/dist/connect/index.d.cts +15 -2
- package/dist/connect/index.d.ts +15 -2
- package/dist/connect/index.js +145 -23
- package/dist/connect/index.js.map +1 -1
- package/dist/core/index.cjs +670 -473
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.d.cts +123 -2
- package/dist/core/index.d.ts +123 -2
- package/dist/core/index.js +667 -473
- package/dist/core/index.js.map +1 -1
- package/dist/impl/browser/connect/index.cjs +119 -1
- package/dist/impl/browser/connect/index.cjs.map +1 -1
- package/dist/impl/browser/connect/index.d.cts +53 -1
- package/dist/impl/browser/connect/index.d.ts +53 -1
- package/dist/impl/browser/connect/index.js +119 -1
- package/dist/impl/browser/connect/index.js.map +1 -1
- package/dist/impl/browser/index.cjs +306 -193
- package/dist/impl/browser/index.cjs.map +1 -1
- package/dist/impl/browser/index.js +306 -193
- package/dist/impl/browser/index.js.map +1 -1
- package/dist/impl/browser/ipfs.cjs +134 -19
- package/dist/impl/browser/ipfs.cjs.map +1 -1
- package/dist/impl/browser/ipfs.js +134 -19
- package/dist/impl/browser/ipfs.js.map +1 -1
- package/dist/impl/nodejs/connect/index.cjs +101 -6
- package/dist/impl/nodejs/connect/index.cjs.map +1 -1
- package/dist/impl/nodejs/connect/index.d.cts +2 -0
- package/dist/impl/nodejs/connect/index.d.ts +2 -0
- package/dist/impl/nodejs/connect/index.js +101 -6
- package/dist/impl/nodejs/connect/index.js.map +1 -1
- package/dist/impl/nodejs/index.cjs +267 -152
- package/dist/impl/nodejs/index.cjs.map +1 -1
- package/dist/impl/nodejs/index.d.cts +2 -1
- package/dist/impl/nodejs/index.d.ts +2 -1
- package/dist/impl/nodejs/index.js +267 -152
- package/dist/impl/nodejs/index.js.map +1 -1
- package/dist/index.cjs +682 -493
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +124 -8
- package/dist/index.d.ts +124 -8
- package/dist/index.js +680 -493
- package/dist/index.js.map +1 -1
- package/dist/l1/index.cjs +139 -32
- package/dist/l1/index.cjs.map +1 -1
- package/dist/l1/index.js +139 -32
- package/dist/l1/index.js.map +1 -1
- package/package.json +1 -16
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
539
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
1591
|
+
logger.debug("L1", `Using ${utxos.length} ${currentMode} UTXOs`);
|
|
1485
1592
|
} else {
|
|
1486
1593
|
utxos = await getUtxo(senderAddress);
|
|
1487
|
-
|
|
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
|
|
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
|
|
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) {
|