@unicitylabs/sphere-sdk 0.13.2 → 0.13.3-dev.1
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 +7 -65
- package/dist/connect/index.cjs +1 -1
- package/dist/connect/index.cjs.map +1 -1
- package/dist/connect/index.js +1 -1
- package/dist/connect/index.js.map +1 -1
- package/dist/core/index.cjs +490 -1279
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.d.cts +58 -134
- package/dist/core/index.d.ts +58 -134
- package/dist/core/index.js +490 -1272
- package/dist/core/index.js.map +1 -1
- package/dist/impl/browser/connect/index.cjs +1 -1
- package/dist/impl/browser/connect/index.cjs.map +1 -1
- package/dist/impl/browser/connect/index.js +1 -1
- package/dist/impl/browser/connect/index.js.map +1 -1
- package/dist/impl/nodejs/index.d.cts +0 -22
- package/dist/impl/nodejs/index.d.ts +0 -22
- package/dist/impl/shared/wallet-api/index.d.cts +0 -22
- package/dist/impl/shared/wallet-api/index.d.ts +0 -22
- package/dist/index.cjs +499 -1391
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +79 -357
- package/dist/index.d.ts +79 -357
- package/dist/index.js +499 -1364
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
package/dist/core/index.js
CHANGED
|
@@ -8833,17 +8833,9 @@ function decryptDeliveryBundle(key, envelope) {
|
|
|
8833
8833
|
}
|
|
8834
8834
|
|
|
8835
8835
|
// types/txf.ts
|
|
8836
|
-
var ARCHIVED_PREFIX = "archived-";
|
|
8837
|
-
var FORKED_PREFIX = "_forked_";
|
|
8838
8836
|
var RESERVED_KEYS = ["_meta", "_nametag", "_nametags", "_tombstones", "_invalidatedNametags", "_outbox", "_mintOutbox", "_sent", "_invalid", "_integrity", "_history"];
|
|
8839
8837
|
function isTokenKey(key) {
|
|
8840
|
-
return key.startsWith("_") && !
|
|
8841
|
-
}
|
|
8842
|
-
function isArchivedKey(key) {
|
|
8843
|
-
return key.startsWith(ARCHIVED_PREFIX);
|
|
8844
|
-
}
|
|
8845
|
-
function isForkedKey(key) {
|
|
8846
|
-
return key.startsWith(FORKED_PREFIX);
|
|
8838
|
+
return key.startsWith("_") && !RESERVED_KEYS.includes(key);
|
|
8847
8839
|
}
|
|
8848
8840
|
function tokenIdFromKey(key) {
|
|
8849
8841
|
return key.startsWith("_") ? key.substring(1) : key;
|
|
@@ -8851,24 +8843,109 @@ function tokenIdFromKey(key) {
|
|
|
8851
8843
|
function keyFromTokenId(tokenId) {
|
|
8852
8844
|
return `_${tokenId}`;
|
|
8853
8845
|
}
|
|
8854
|
-
|
|
8855
|
-
|
|
8846
|
+
|
|
8847
|
+
// serialization/txf-serializer.ts
|
|
8848
|
+
function isV2TokenBlob(sdkData) {
|
|
8849
|
+
return typeof sdkData === "string" && sdkData.length >= 2 && sdkData.length % 2 === 0 && sdkData[0] !== "{" && /^[0-9a-f]+$/i.test(sdkData);
|
|
8856
8850
|
}
|
|
8857
|
-
function
|
|
8858
|
-
return
|
|
8851
|
+
function v2TokenId(token) {
|
|
8852
|
+
return token.id.startsWith("v2_") ? token.id.slice(3) : token.id;
|
|
8859
8853
|
}
|
|
8860
|
-
function
|
|
8861
|
-
return
|
|
8854
|
+
function isV2TokenEntry(entry) {
|
|
8855
|
+
return typeof entry === "object" && entry !== null && !("genesis" in entry) && isV2TokenBlob(entry.sdkData);
|
|
8862
8856
|
}
|
|
8863
|
-
function
|
|
8864
|
-
|
|
8865
|
-
|
|
8866
|
-
|
|
8867
|
-
|
|
8868
|
-
|
|
8869
|
-
|
|
8870
|
-
|
|
8857
|
+
async function buildTxfStorageData(tokens, meta, options) {
|
|
8858
|
+
const storageData = {
|
|
8859
|
+
_meta: {
|
|
8860
|
+
...meta,
|
|
8861
|
+
formatVersion: "2.0"
|
|
8862
|
+
}
|
|
8863
|
+
};
|
|
8864
|
+
if (options?.nametags && options.nametags.length > 0) {
|
|
8865
|
+
storageData._nametags = options.nametags;
|
|
8866
|
+
}
|
|
8867
|
+
if (options?.tombstones && options.tombstones.length > 0) {
|
|
8868
|
+
storageData._tombstones = options.tombstones;
|
|
8869
|
+
}
|
|
8870
|
+
if (options?.historyEntries && options.historyEntries.length > 0) {
|
|
8871
|
+
storageData._history = options.historyEntries;
|
|
8872
|
+
}
|
|
8873
|
+
for (const token of tokens) {
|
|
8874
|
+
if (isV2TokenBlob(token.sdkData)) {
|
|
8875
|
+
storageData[keyFromTokenId(v2TokenId(token))] = token;
|
|
8876
|
+
} else {
|
|
8877
|
+
logger.warn(
|
|
8878
|
+
"TXF",
|
|
8879
|
+
`Refusing to persist token ${token.id.slice(0, 16)}...: sdkData is not a v2 token blob \u2014 entry skipped`
|
|
8880
|
+
);
|
|
8881
|
+
}
|
|
8882
|
+
}
|
|
8883
|
+
return storageData;
|
|
8884
|
+
}
|
|
8885
|
+
function parseTxfStorageData(data) {
|
|
8886
|
+
const result = {
|
|
8887
|
+
tokens: [],
|
|
8888
|
+
meta: null,
|
|
8889
|
+
nametags: [],
|
|
8890
|
+
tombstones: [],
|
|
8891
|
+
historyEntries: [],
|
|
8892
|
+
validationErrors: []
|
|
8871
8893
|
};
|
|
8894
|
+
if (!data || typeof data !== "object") {
|
|
8895
|
+
result.validationErrors.push("Storage data is not an object");
|
|
8896
|
+
return result;
|
|
8897
|
+
}
|
|
8898
|
+
const storageData = data;
|
|
8899
|
+
if (storageData._meta && typeof storageData._meta === "object") {
|
|
8900
|
+
result.meta = storageData._meta;
|
|
8901
|
+
}
|
|
8902
|
+
const seenNames = /* @__PURE__ */ new Set();
|
|
8903
|
+
if (Array.isArray(storageData._nametags)) {
|
|
8904
|
+
for (const entry of storageData._nametags) {
|
|
8905
|
+
if (entry && typeof entry === "object" && typeof entry.name === "string") {
|
|
8906
|
+
result.nametags.push(entry);
|
|
8907
|
+
seenNames.add(entry.name);
|
|
8908
|
+
}
|
|
8909
|
+
}
|
|
8910
|
+
}
|
|
8911
|
+
if (storageData._nametag && typeof storageData._nametag === "object") {
|
|
8912
|
+
const legacy = storageData._nametag;
|
|
8913
|
+
if (typeof legacy.name === "string" && !seenNames.has(legacy.name)) {
|
|
8914
|
+
result.nametags.push(legacy);
|
|
8915
|
+
}
|
|
8916
|
+
}
|
|
8917
|
+
if (storageData._tombstones && Array.isArray(storageData._tombstones)) {
|
|
8918
|
+
for (const entry of storageData._tombstones) {
|
|
8919
|
+
if (typeof entry === "object" && entry !== null && typeof entry.tokenId === "string" && typeof entry.stateHash === "string" && typeof entry.timestamp === "number") {
|
|
8920
|
+
result.tombstones.push(entry);
|
|
8921
|
+
}
|
|
8922
|
+
}
|
|
8923
|
+
}
|
|
8924
|
+
if (Array.isArray(storageData._history)) {
|
|
8925
|
+
for (const entry of storageData._history) {
|
|
8926
|
+
if (typeof entry === "object" && entry !== null && typeof entry.dedupKey === "string" && typeof entry.type === "string") {
|
|
8927
|
+
result.historyEntries.push(entry);
|
|
8928
|
+
}
|
|
8929
|
+
}
|
|
8930
|
+
}
|
|
8931
|
+
for (const key of Object.keys(storageData)) {
|
|
8932
|
+
if (isTokenKey(key)) {
|
|
8933
|
+
const tokenId = tokenIdFromKey(key);
|
|
8934
|
+
const entry = storageData[key];
|
|
8935
|
+
if (isV2TokenEntry(entry)) {
|
|
8936
|
+
result.tokens.push(entry);
|
|
8937
|
+
} else {
|
|
8938
|
+
const msg = `Token ${tokenId}: unrecognized storage entry (not a v2 token blob) \u2014 entry skipped`;
|
|
8939
|
+
logger.warn("TXF", msg);
|
|
8940
|
+
result.validationErrors.push(msg);
|
|
8941
|
+
}
|
|
8942
|
+
} else if (key.startsWith("token-")) {
|
|
8943
|
+
const msg = `Token ${key}: sdkData is not a v2 token blob \u2014 entry skipped`;
|
|
8944
|
+
logger.warn("TXF", msg);
|
|
8945
|
+
result.validationErrors.push(msg);
|
|
8946
|
+
}
|
|
8947
|
+
}
|
|
8948
|
+
return result;
|
|
8872
8949
|
}
|
|
8873
8950
|
|
|
8874
8951
|
// registry/TokenRegistry.ts
|
|
@@ -9298,334 +9375,6 @@ function coinIdsMatch(a, b) {
|
|
|
9298
9375
|
return normalizeCoinId(a) === normalizeCoinId(b);
|
|
9299
9376
|
}
|
|
9300
9377
|
|
|
9301
|
-
// serialization/txf-serializer.ts
|
|
9302
|
-
function bytesToHex5(bytes) {
|
|
9303
|
-
const arr = Array.isArray(bytes) ? bytes : Array.from(bytes);
|
|
9304
|
-
return arr.map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
9305
|
-
}
|
|
9306
|
-
function normalizeToHex(value) {
|
|
9307
|
-
if (typeof value === "string") {
|
|
9308
|
-
return value;
|
|
9309
|
-
}
|
|
9310
|
-
if (value && typeof value === "object") {
|
|
9311
|
-
const obj = value;
|
|
9312
|
-
if ("bytes" in obj && (Array.isArray(obj.bytes) || obj.bytes instanceof Uint8Array)) {
|
|
9313
|
-
return bytesToHex5(obj.bytes);
|
|
9314
|
-
}
|
|
9315
|
-
if (obj.type === "Buffer" && Array.isArray(obj.data)) {
|
|
9316
|
-
return bytesToHex5(obj.data);
|
|
9317
|
-
}
|
|
9318
|
-
}
|
|
9319
|
-
return String(value);
|
|
9320
|
-
}
|
|
9321
|
-
function normalizeSdkTokenToStorage(sdkTokenJson) {
|
|
9322
|
-
const txf = structuredClone(sdkTokenJson);
|
|
9323
|
-
if (txf.genesis?.data) {
|
|
9324
|
-
const data = txf.genesis.data;
|
|
9325
|
-
if (data.tokenId !== void 0) {
|
|
9326
|
-
data.tokenId = normalizeToHex(data.tokenId);
|
|
9327
|
-
}
|
|
9328
|
-
if (data.tokenType !== void 0) {
|
|
9329
|
-
data.tokenType = normalizeToHex(data.tokenType);
|
|
9330
|
-
}
|
|
9331
|
-
if (data.salt !== void 0) {
|
|
9332
|
-
data.salt = normalizeToHex(data.salt);
|
|
9333
|
-
}
|
|
9334
|
-
}
|
|
9335
|
-
if (txf.genesis?.inclusionProof?.authenticator) {
|
|
9336
|
-
const auth = txf.genesis.inclusionProof.authenticator;
|
|
9337
|
-
if (auth.publicKey !== void 0) {
|
|
9338
|
-
auth.publicKey = normalizeToHex(auth.publicKey);
|
|
9339
|
-
}
|
|
9340
|
-
if (auth.signature !== void 0) {
|
|
9341
|
-
auth.signature = normalizeToHex(auth.signature);
|
|
9342
|
-
}
|
|
9343
|
-
}
|
|
9344
|
-
if (Array.isArray(txf.transactions)) {
|
|
9345
|
-
for (const tx of txf.transactions) {
|
|
9346
|
-
if (tx.inclusionProof?.authenticator) {
|
|
9347
|
-
const auth = tx.inclusionProof.authenticator;
|
|
9348
|
-
if (auth.publicKey !== void 0) {
|
|
9349
|
-
auth.publicKey = normalizeToHex(auth.publicKey);
|
|
9350
|
-
}
|
|
9351
|
-
if (auth.signature !== void 0) {
|
|
9352
|
-
auth.signature = normalizeToHex(auth.signature);
|
|
9353
|
-
}
|
|
9354
|
-
}
|
|
9355
|
-
}
|
|
9356
|
-
}
|
|
9357
|
-
return txf;
|
|
9358
|
-
}
|
|
9359
|
-
function tokenToTxf(token) {
|
|
9360
|
-
const jsonData = token.sdkData;
|
|
9361
|
-
if (!jsonData) {
|
|
9362
|
-
return null;
|
|
9363
|
-
}
|
|
9364
|
-
try {
|
|
9365
|
-
const txfData = normalizeSdkTokenToStorage(JSON.parse(jsonData));
|
|
9366
|
-
if (!txfData.genesis || !txfData.state) {
|
|
9367
|
-
return null;
|
|
9368
|
-
}
|
|
9369
|
-
if (!txfData.version) {
|
|
9370
|
-
txfData.version = "2.0";
|
|
9371
|
-
}
|
|
9372
|
-
if (!txfData.transactions) {
|
|
9373
|
-
txfData.transactions = [];
|
|
9374
|
-
}
|
|
9375
|
-
if (!txfData.nametags) {
|
|
9376
|
-
txfData.nametags = [];
|
|
9377
|
-
}
|
|
9378
|
-
if (!txfData._integrity) {
|
|
9379
|
-
txfData._integrity = {
|
|
9380
|
-
genesisDataJSONHash: "0000" + "0".repeat(60)
|
|
9381
|
-
};
|
|
9382
|
-
}
|
|
9383
|
-
return txfData;
|
|
9384
|
-
} catch {
|
|
9385
|
-
return null;
|
|
9386
|
-
}
|
|
9387
|
-
}
|
|
9388
|
-
function determineTokenStatus(txf) {
|
|
9389
|
-
if (txf.transactions.length > 0) {
|
|
9390
|
-
const lastTx = txf.transactions[txf.transactions.length - 1];
|
|
9391
|
-
if (lastTx.inclusionProof === null) {
|
|
9392
|
-
return "pending";
|
|
9393
|
-
}
|
|
9394
|
-
}
|
|
9395
|
-
return "confirmed";
|
|
9396
|
-
}
|
|
9397
|
-
function txfToToken(tokenId, txf) {
|
|
9398
|
-
const coinData = txf.genesis.data.coinData;
|
|
9399
|
-
const totalAmount = coinData.reduce((sum, [, amt]) => {
|
|
9400
|
-
return sum + BigInt(amt || "0");
|
|
9401
|
-
}, BigInt(0));
|
|
9402
|
-
let coinId = coinData[0]?.[0] || "";
|
|
9403
|
-
for (const [cid, amt] of coinData) {
|
|
9404
|
-
if (BigInt(amt || "0") > 0) {
|
|
9405
|
-
coinId = cid;
|
|
9406
|
-
break;
|
|
9407
|
-
}
|
|
9408
|
-
}
|
|
9409
|
-
const tokenType = txf.genesis.data.tokenType;
|
|
9410
|
-
const isNft = tokenType === "455ad8720656b08e8dbd5bac1f3c73eeea5431565f6c1c3af742b1aa12d41d89";
|
|
9411
|
-
const now = Date.now();
|
|
9412
|
-
const registry = TokenRegistry.getInstance();
|
|
9413
|
-
const def = registry.getDefinition(coinId);
|
|
9414
|
-
return {
|
|
9415
|
-
id: tokenId,
|
|
9416
|
-
coinId,
|
|
9417
|
-
symbol: isNft ? "NFT" : def?.symbol || coinId.slice(0, 8),
|
|
9418
|
-
name: isNft ? "NFT" : def?.name ? def.name.charAt(0).toUpperCase() + def.name.slice(1) : "Token",
|
|
9419
|
-
decimals: isNft ? 0 : def?.decimals ?? 8,
|
|
9420
|
-
amount: totalAmount.toString(),
|
|
9421
|
-
status: determineTokenStatus(txf),
|
|
9422
|
-
createdAt: now,
|
|
9423
|
-
updatedAt: now,
|
|
9424
|
-
sdkData: JSON.stringify(txf)
|
|
9425
|
-
};
|
|
9426
|
-
}
|
|
9427
|
-
function isV2TokenBlob(sdkData) {
|
|
9428
|
-
return typeof sdkData === "string" && sdkData.length >= 2 && sdkData.length % 2 === 0 && sdkData[0] !== "{" && /^[0-9a-f]+$/i.test(sdkData);
|
|
9429
|
-
}
|
|
9430
|
-
function v2TokenId(token) {
|
|
9431
|
-
return token.id.startsWith("v2_") ? token.id.slice(3) : token.id;
|
|
9432
|
-
}
|
|
9433
|
-
function isV2TokenEntry(entry) {
|
|
9434
|
-
return typeof entry === "object" && entry !== null && !("genesis" in entry) && isV2TokenBlob(entry.sdkData);
|
|
9435
|
-
}
|
|
9436
|
-
async function buildTxfStorageData(tokens, meta, options) {
|
|
9437
|
-
const storageData = {
|
|
9438
|
-
_meta: {
|
|
9439
|
-
...meta,
|
|
9440
|
-
formatVersion: "2.0"
|
|
9441
|
-
}
|
|
9442
|
-
};
|
|
9443
|
-
if (options?.nametags && options.nametags.length > 0) {
|
|
9444
|
-
storageData._nametags = options.nametags;
|
|
9445
|
-
}
|
|
9446
|
-
if (options?.tombstones && options.tombstones.length > 0) {
|
|
9447
|
-
storageData._tombstones = options.tombstones;
|
|
9448
|
-
}
|
|
9449
|
-
if (options?.outboxEntries && options.outboxEntries.length > 0) {
|
|
9450
|
-
storageData._outbox = options.outboxEntries;
|
|
9451
|
-
}
|
|
9452
|
-
if (options?.mintOutboxEntries && options.mintOutboxEntries.length > 0) {
|
|
9453
|
-
storageData._mintOutbox = options.mintOutboxEntries;
|
|
9454
|
-
}
|
|
9455
|
-
if (options?.invalidatedNametags && options.invalidatedNametags.length > 0) {
|
|
9456
|
-
storageData._invalidatedNametags = options.invalidatedNametags;
|
|
9457
|
-
}
|
|
9458
|
-
if (options?.historyEntries && options.historyEntries.length > 0) {
|
|
9459
|
-
storageData._history = options.historyEntries;
|
|
9460
|
-
}
|
|
9461
|
-
for (const token of tokens) {
|
|
9462
|
-
const txf = tokenToTxf(token);
|
|
9463
|
-
if (txf) {
|
|
9464
|
-
const actualTokenId = txf.genesis.data.tokenId;
|
|
9465
|
-
storageData[keyFromTokenId(actualTokenId)] = txf;
|
|
9466
|
-
} else if (isV2TokenBlob(token.sdkData)) {
|
|
9467
|
-
storageData[keyFromTokenId(v2TokenId(token))] = token;
|
|
9468
|
-
}
|
|
9469
|
-
}
|
|
9470
|
-
if (options?.archivedTokens && options.archivedTokens.size > 0) {
|
|
9471
|
-
for (const [tokenId, txf] of options.archivedTokens) {
|
|
9472
|
-
storageData[archivedKeyFromTokenId(tokenId)] = txf;
|
|
9473
|
-
}
|
|
9474
|
-
}
|
|
9475
|
-
if (options?.forkedTokens && options.forkedTokens.size > 0) {
|
|
9476
|
-
for (const [key, txf] of options.forkedTokens) {
|
|
9477
|
-
const [tokenId, stateHash] = key.split("_");
|
|
9478
|
-
if (tokenId && stateHash) {
|
|
9479
|
-
storageData[forkedKeyFromTokenIdAndState(tokenId, stateHash)] = txf;
|
|
9480
|
-
}
|
|
9481
|
-
}
|
|
9482
|
-
}
|
|
9483
|
-
return storageData;
|
|
9484
|
-
}
|
|
9485
|
-
function parseTxfStorageData(data) {
|
|
9486
|
-
const result = {
|
|
9487
|
-
tokens: [],
|
|
9488
|
-
meta: null,
|
|
9489
|
-
nametags: [],
|
|
9490
|
-
tombstones: [],
|
|
9491
|
-
archivedTokens: /* @__PURE__ */ new Map(),
|
|
9492
|
-
forkedTokens: /* @__PURE__ */ new Map(),
|
|
9493
|
-
outboxEntries: [],
|
|
9494
|
-
mintOutboxEntries: [],
|
|
9495
|
-
invalidatedNametags: [],
|
|
9496
|
-
historyEntries: [],
|
|
9497
|
-
validationErrors: []
|
|
9498
|
-
};
|
|
9499
|
-
if (!data || typeof data !== "object") {
|
|
9500
|
-
result.validationErrors.push("Storage data is not an object");
|
|
9501
|
-
return result;
|
|
9502
|
-
}
|
|
9503
|
-
const storageData = data;
|
|
9504
|
-
if (storageData._meta && typeof storageData._meta === "object") {
|
|
9505
|
-
result.meta = storageData._meta;
|
|
9506
|
-
}
|
|
9507
|
-
const seenNames = /* @__PURE__ */ new Set();
|
|
9508
|
-
if (Array.isArray(storageData._nametags)) {
|
|
9509
|
-
for (const entry of storageData._nametags) {
|
|
9510
|
-
if (entry && typeof entry === "object" && typeof entry.name === "string") {
|
|
9511
|
-
result.nametags.push(entry);
|
|
9512
|
-
seenNames.add(entry.name);
|
|
9513
|
-
}
|
|
9514
|
-
}
|
|
9515
|
-
}
|
|
9516
|
-
if (storageData._nametag && typeof storageData._nametag === "object") {
|
|
9517
|
-
const legacy = storageData._nametag;
|
|
9518
|
-
if (typeof legacy.name === "string" && !seenNames.has(legacy.name)) {
|
|
9519
|
-
result.nametags.push(legacy);
|
|
9520
|
-
}
|
|
9521
|
-
}
|
|
9522
|
-
if (storageData._tombstones && Array.isArray(storageData._tombstones)) {
|
|
9523
|
-
for (const entry of storageData._tombstones) {
|
|
9524
|
-
if (typeof entry === "object" && entry !== null && typeof entry.tokenId === "string" && typeof entry.stateHash === "string" && typeof entry.timestamp === "number") {
|
|
9525
|
-
result.tombstones.push(entry);
|
|
9526
|
-
}
|
|
9527
|
-
}
|
|
9528
|
-
}
|
|
9529
|
-
if (storageData._outbox && Array.isArray(storageData._outbox)) {
|
|
9530
|
-
for (const entry of storageData._outbox) {
|
|
9531
|
-
if (typeof entry === "object" && entry !== null && typeof entry.id === "string" && typeof entry.status === "string") {
|
|
9532
|
-
result.outboxEntries.push(entry);
|
|
9533
|
-
}
|
|
9534
|
-
}
|
|
9535
|
-
}
|
|
9536
|
-
if (storageData._mintOutbox && Array.isArray(storageData._mintOutbox)) {
|
|
9537
|
-
for (const entry of storageData._mintOutbox) {
|
|
9538
|
-
if (typeof entry === "object" && entry !== null && typeof entry.id === "string" && typeof entry.status === "string") {
|
|
9539
|
-
result.mintOutboxEntries.push(entry);
|
|
9540
|
-
}
|
|
9541
|
-
}
|
|
9542
|
-
}
|
|
9543
|
-
if (storageData._invalidatedNametags && Array.isArray(storageData._invalidatedNametags)) {
|
|
9544
|
-
for (const entry of storageData._invalidatedNametags) {
|
|
9545
|
-
if (typeof entry === "object" && entry !== null && typeof entry.name === "string") {
|
|
9546
|
-
result.invalidatedNametags.push(entry);
|
|
9547
|
-
}
|
|
9548
|
-
}
|
|
9549
|
-
}
|
|
9550
|
-
if (Array.isArray(storageData._history)) {
|
|
9551
|
-
for (const entry of storageData._history) {
|
|
9552
|
-
if (typeof entry === "object" && entry !== null && typeof entry.dedupKey === "string" && typeof entry.type === "string") {
|
|
9553
|
-
result.historyEntries.push(entry);
|
|
9554
|
-
}
|
|
9555
|
-
}
|
|
9556
|
-
}
|
|
9557
|
-
for (const key of Object.keys(storageData)) {
|
|
9558
|
-
if (isTokenKey(key)) {
|
|
9559
|
-
const tokenId = tokenIdFromKey(key);
|
|
9560
|
-
try {
|
|
9561
|
-
const txfToken = storageData[key];
|
|
9562
|
-
if (txfToken?.genesis?.data?.tokenId) {
|
|
9563
|
-
const token = txfToToken(tokenId, txfToken);
|
|
9564
|
-
result.tokens.push(token);
|
|
9565
|
-
} else if (isV2TokenEntry(storageData[key])) {
|
|
9566
|
-
result.tokens.push(storageData[key]);
|
|
9567
|
-
}
|
|
9568
|
-
} catch (err) {
|
|
9569
|
-
result.validationErrors.push(`Token ${tokenId}: ${err}`);
|
|
9570
|
-
}
|
|
9571
|
-
} else if (isArchivedKey(key)) {
|
|
9572
|
-
const tokenId = tokenIdFromArchivedKey(key);
|
|
9573
|
-
try {
|
|
9574
|
-
const txfToken = storageData[key];
|
|
9575
|
-
if (txfToken?.genesis?.data?.tokenId) {
|
|
9576
|
-
result.archivedTokens.set(tokenId, txfToken);
|
|
9577
|
-
}
|
|
9578
|
-
} catch {
|
|
9579
|
-
result.validationErrors.push(`Archived token ${tokenId}: invalid structure`);
|
|
9580
|
-
}
|
|
9581
|
-
} else if (isForkedKey(key)) {
|
|
9582
|
-
const parsed = parseForkedKey(key);
|
|
9583
|
-
if (parsed) {
|
|
9584
|
-
try {
|
|
9585
|
-
const txfToken = storageData[key];
|
|
9586
|
-
if (txfToken?.genesis?.data?.tokenId) {
|
|
9587
|
-
const mapKey = `${parsed.tokenId}_${parsed.stateHash}`;
|
|
9588
|
-
result.forkedTokens.set(mapKey, txfToken);
|
|
9589
|
-
}
|
|
9590
|
-
} catch {
|
|
9591
|
-
result.validationErrors.push(`Forked token ${parsed.tokenId}: invalid structure`);
|
|
9592
|
-
}
|
|
9593
|
-
}
|
|
9594
|
-
} else if (key.startsWith("token-")) {
|
|
9595
|
-
try {
|
|
9596
|
-
const entry = storageData[key];
|
|
9597
|
-
const txfToken = entry?.token;
|
|
9598
|
-
if (txfToken?.genesis?.data?.tokenId) {
|
|
9599
|
-
const tokenId = txfToken.genesis.data.tokenId;
|
|
9600
|
-
const token = txfToToken(tokenId, txfToken);
|
|
9601
|
-
result.tokens.push(token);
|
|
9602
|
-
}
|
|
9603
|
-
} catch (err) {
|
|
9604
|
-
result.validationErrors.push(`Token ${key}: ${err}`);
|
|
9605
|
-
}
|
|
9606
|
-
}
|
|
9607
|
-
}
|
|
9608
|
-
return result;
|
|
9609
|
-
}
|
|
9610
|
-
function getCurrentStateHash(txf) {
|
|
9611
|
-
if (txf.transactions && txf.transactions.length > 0) {
|
|
9612
|
-
const lastTx = txf.transactions[txf.transactions.length - 1];
|
|
9613
|
-
if (lastTx?.newStateHash) {
|
|
9614
|
-
return lastTx.newStateHash;
|
|
9615
|
-
}
|
|
9616
|
-
if (lastTx?.inclusionProof?.authenticator?.stateHash) {
|
|
9617
|
-
return lastTx.inclusionProof.authenticator.stateHash;
|
|
9618
|
-
}
|
|
9619
|
-
}
|
|
9620
|
-
if (txf._integrity?.currentStateHash) {
|
|
9621
|
-
return txf._integrity.currentStateHash;
|
|
9622
|
-
}
|
|
9623
|
-
if (txf.genesis?.inclusionProof?.authenticator?.stateHash) {
|
|
9624
|
-
return txf.genesis.inclusionProof.authenticator.stateHash;
|
|
9625
|
-
}
|
|
9626
|
-
return void 0;
|
|
9627
|
-
}
|
|
9628
|
-
|
|
9629
9378
|
// core/utils.ts
|
|
9630
9379
|
var SECP256K1_ORDER = BigInt("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141");
|
|
9631
9380
|
var BASE58_ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
|
|
@@ -9791,6 +9540,10 @@ function surfacedSendFailure(summary) {
|
|
|
9791
9540
|
}
|
|
9792
9541
|
return primaryError;
|
|
9793
9542
|
}
|
|
9543
|
+
function currentStateHashOfTxf(txf) {
|
|
9544
|
+
const last = txf.transactions?.[txf.transactions.length - 1];
|
|
9545
|
+
return last?.newStateHash ?? last?.inclusionProof?.authenticator?.stateHash ?? txf._integrity?.currentStateHash ?? txf.genesis?.inclusionProof?.authenticator?.stateHash ?? "";
|
|
9546
|
+
}
|
|
9794
9547
|
function genesisIdOf(token) {
|
|
9795
9548
|
return extractTokenIdFromSdkData(token.sdkData) ?? token.id.replace(/^v2_/, "");
|
|
9796
9549
|
}
|
|
@@ -9902,7 +9655,7 @@ function parseSdkDataCached(sdkData) {
|
|
|
9902
9655
|
try {
|
|
9903
9656
|
const txf = JSON.parse(sdkData);
|
|
9904
9657
|
tokenId = txf.genesis?.data?.tokenId || null;
|
|
9905
|
-
stateHash =
|
|
9658
|
+
stateHash = currentStateHashOfTxf(txf);
|
|
9906
9659
|
if (!stateHash) {
|
|
9907
9660
|
if (txf.state?.hash) {
|
|
9908
9661
|
stateHash = txf.state.hash;
|
|
@@ -10648,7 +10401,6 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
10648
10401
|
if (op.kind === "direct") {
|
|
10649
10402
|
finished = await ctx.engine.transfer({ token: op.sdkToken, recipientPubkey: ctx.recipientChainPubkey }, opts);
|
|
10650
10403
|
} else {
|
|
10651
|
-
const checkpointStore = this.getCheckpointStore();
|
|
10652
10404
|
const { outputs } = await ctx.engine.split(
|
|
10653
10405
|
{
|
|
10654
10406
|
token: op.sdkToken,
|
|
@@ -10657,7 +10409,7 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
10657
10409
|
{ recipientPubkey: ctx.selfChainPubkey, coinId: ctx.coinId, amount: op.remainderAmount }
|
|
10658
10410
|
]
|
|
10659
10411
|
},
|
|
10660
|
-
{ ...opts, ...checkpointStore ? { checkpointStore } : {} }
|
|
10412
|
+
{ ...opts, ...ctx.checkpointStore ? { checkpointStore: ctx.checkpointStore } : {} }
|
|
10661
10413
|
);
|
|
10662
10414
|
finished = outputs[0];
|
|
10663
10415
|
changeOutput = outputs[1];
|
|
@@ -10844,7 +10596,8 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
10844
10596
|
recipientChainPubkeyHex,
|
|
10845
10597
|
selfChainPubkey: hexToBytes4(this.deps.identity.chainPubkey),
|
|
10846
10598
|
coinId: request.coinId,
|
|
10847
|
-
...request.memo !== void 0 ? { memo: request.memo } : {}
|
|
10599
|
+
...request.memo !== void 0 ? { memo: request.memo } : {},
|
|
10600
|
+
...this.getCheckpointStore() ? { checkpointStore: this.getCheckpointStore() } : {}
|
|
10848
10601
|
})
|
|
10849
10602
|
);
|
|
10850
10603
|
attempt.summary = summary;
|
|
@@ -13361,8 +13114,11 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
13361
13114
|
try {
|
|
13362
13115
|
const plain = decryptField(this.getFieldEncryptionKey(), intent.payload);
|
|
13363
13116
|
payload = JSON.parse(plain);
|
|
13364
|
-
if (payload.v !==
|
|
13365
|
-
throw new SphereError(
|
|
13117
|
+
if (payload.v !== 2 || !Array.isArray(payload.direct)) {
|
|
13118
|
+
throw new SphereError(
|
|
13119
|
+
`Unsupported intent payload shape (v=${String(payload.v)}) \u2014 not resumable by this wallet version`,
|
|
13120
|
+
"VALIDATION_ERROR"
|
|
13121
|
+
);
|
|
13366
13122
|
}
|
|
13367
13123
|
} catch (err) {
|
|
13368
13124
|
logger.warn("Payments", `Intent ${intent.transferId.slice(0, 8)}\u2026 payload undecodable \u2014 skipped:`, err);
|
|
@@ -13501,31 +13257,22 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
13501
13257
|
async resumeIntent(transferId, payload) {
|
|
13502
13258
|
const engine = this.deps.tokenEngine;
|
|
13503
13259
|
const walletApi = this.deps.walletApi;
|
|
13504
|
-
const delivery = this.delivery;
|
|
13505
13260
|
const provider = this.getActiveTokenStorageProvider();
|
|
13506
13261
|
if (!provider) throw new SphereError("No token storage provider for intent resume", "STORAGE_ERROR");
|
|
13507
|
-
const serverApply = delivery.custody === "inventory";
|
|
13508
|
-
const
|
|
13509
|
-
const
|
|
13510
|
-
|
|
13511
|
-
|
|
13512
|
-
|
|
13513
|
-
|
|
13514
|
-
|
|
13515
|
-
|
|
13516
|
-
|
|
13517
|
-
}
|
|
13518
|
-
const nm = this.currentNametagName();
|
|
13519
|
-
await this.tryDeliver(
|
|
13520
|
-
() => delivery.deliver(payload.recipient, hexToBytes4(tokenBlobHex), {
|
|
13521
|
-
transferId,
|
|
13522
|
-
memo: payload.memo,
|
|
13523
|
-
...nm !== void 0 ? { senderNametag: nm } : {}
|
|
13524
|
-
}),
|
|
13525
|
-
tokenBlobHex
|
|
13526
|
-
);
|
|
13262
|
+
const serverApply = this.delivery.custody === "inventory";
|
|
13263
|
+
const checkpointStore = this.getCheckpointStore();
|
|
13264
|
+
const ctx = {
|
|
13265
|
+
engine,
|
|
13266
|
+
transferId,
|
|
13267
|
+
recipientChainPubkey: hexToBytes4(payload.recipient),
|
|
13268
|
+
recipientChainPubkeyHex: payload.recipient,
|
|
13269
|
+
selfChainPubkey: hexToBytes4(this.deps.identity.chainPubkey),
|
|
13270
|
+
coinId: payload.coinId,
|
|
13271
|
+
...payload.memo !== void 0 ? { memo: payload.memo } : {},
|
|
13272
|
+
...checkpointStore ? { checkpointStore } : {}
|
|
13527
13273
|
};
|
|
13528
13274
|
const journaled = await this.journaledByOp(transferId);
|
|
13275
|
+
const blobs = [];
|
|
13529
13276
|
const spent = [];
|
|
13530
13277
|
let conflicted = false;
|
|
13531
13278
|
let firstConflict;
|
|
@@ -13533,91 +13280,71 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
13533
13280
|
for (const [opIndex, genesisId] of payload.direct.entries()) {
|
|
13534
13281
|
const existing = journaled.get(opIndex);
|
|
13535
13282
|
if (existing) {
|
|
13536
|
-
|
|
13283
|
+
blobs.push(existing.tokenBlob);
|
|
13537
13284
|
spent.push(genesisId);
|
|
13538
|
-
|
|
13539
|
-
|
|
13540
|
-
|
|
13541
|
-
|
|
13542
|
-
|
|
13543
|
-
|
|
13544
|
-
|
|
13545
|
-
|
|
13546
|
-
|
|
13547
|
-
|
|
13548
|
-
|
|
13549
|
-
|
|
13550
|
-
|
|
13551
|
-
|
|
13552
|
-
|
|
13553
|
-
|
|
13554
|
-
|
|
13285
|
+
continue;
|
|
13286
|
+
}
|
|
13287
|
+
const source = await engine.decodeToken(await provider.getToken(genesisId));
|
|
13288
|
+
const outcome = await this.certifyOperation(
|
|
13289
|
+
{
|
|
13290
|
+
kind: "direct",
|
|
13291
|
+
opIndex,
|
|
13292
|
+
uiTokenId: `v2_${genesisId}`,
|
|
13293
|
+
genesisId,
|
|
13294
|
+
sourceSdkData: "",
|
|
13295
|
+
sdkToken: source,
|
|
13296
|
+
deliveredAmount: engine.balanceOf(source, payload.coinId)
|
|
13297
|
+
},
|
|
13298
|
+
ctx
|
|
13299
|
+
);
|
|
13300
|
+
if (outcome.error !== void 0) {
|
|
13301
|
+
if (!(outcome.error instanceof TransferConflictError)) throw outcome.error;
|
|
13302
|
+
conflicted = true;
|
|
13303
|
+
firstConflict ??= outcome.error;
|
|
13304
|
+
undeliveredAmount += engine.balanceOf(source, payload.coinId);
|
|
13305
|
+
logger.warn("Payments", `Resume op ${opIndex} lost its source to a concurrent transfer \u2014 not delivered (\xA78.1/#4):`, outcome.error);
|
|
13306
|
+
continue;
|
|
13555
13307
|
}
|
|
13308
|
+
blobs.push(outcome.tokenBlob);
|
|
13309
|
+
spent.push(genesisId);
|
|
13556
13310
|
}
|
|
13557
13311
|
let changeOutput = null;
|
|
13558
13312
|
if (payload.split) {
|
|
13559
13313
|
const splitOpIndex = payload.direct.length;
|
|
13560
|
-
const existingSplit = journaled.get(splitOpIndex);
|
|
13561
|
-
const checkpointStore = payload.v === 2 ? this.getCheckpointStore() : void 0;
|
|
13562
13314
|
let splitDelivered = false;
|
|
13563
|
-
|
|
13564
|
-
await
|
|
13565
|
-
|
|
13566
|
-
|
|
13567
|
-
|
|
13568
|
-
|
|
13569
|
-
|
|
13570
|
-
|
|
13571
|
-
|
|
13572
|
-
|
|
13573
|
-
|
|
13574
|
-
|
|
13575
|
-
|
|
13576
|
-
|
|
13577
|
-
|
|
13578
|
-
|
|
13579
|
-
|
|
13580
|
-
|
|
13581
|
-
|
|
13582
|
-
coinId: payload.coinId,
|
|
13583
|
-
amount: BigInt(payload.split.remainderAmount)
|
|
13584
|
-
}
|
|
13585
|
-
]
|
|
13586
|
-
},
|
|
13587
|
-
{
|
|
13588
|
-
signal: timeoutSignal(SEND_ENGINE_OP_TIMEOUT_MS),
|
|
13589
|
-
transferId,
|
|
13590
|
-
opIndex: splitOpIndex,
|
|
13591
|
-
...checkpointStore ? { checkpointStore } : {}
|
|
13592
|
-
}
|
|
13593
|
-
);
|
|
13594
|
-
changeOutput = outputs[1];
|
|
13595
|
-
if (!serverApply) await this.storeEngineToken(engine, changeOutput, { criticalSave: true });
|
|
13596
|
-
await deliverBlob(bytesToHex4(encodeTokenBlob(engine.encodeToken(outputs[0]))), splitOpIndex);
|
|
13597
|
-
splitDelivered = true;
|
|
13598
|
-
} catch (err) {
|
|
13599
|
-
if (err instanceof ProofUnconfirmedError) throw err;
|
|
13600
|
-
if (checkpointStore !== void 0 && (err instanceof CheckpointPersistFailedError || err instanceof SplitCheckpointLostError || err instanceof CheckpointTrustbaseMismatchError)) {
|
|
13601
|
-
throw err;
|
|
13315
|
+
{
|
|
13316
|
+
const source = await engine.decodeToken(await provider.getToken(payload.split.tokenId));
|
|
13317
|
+
const outcome = await this.certifyOperation(
|
|
13318
|
+
{
|
|
13319
|
+
kind: "split",
|
|
13320
|
+
opIndex: splitOpIndex,
|
|
13321
|
+
uiTokenId: `v2_${payload.split.tokenId}`,
|
|
13322
|
+
genesisId: payload.split.tokenId,
|
|
13323
|
+
sourceSdkData: "",
|
|
13324
|
+
sdkToken: source,
|
|
13325
|
+
deliveredAmount: BigInt(payload.split.splitAmount),
|
|
13326
|
+
remainderAmount: BigInt(payload.split.remainderAmount)
|
|
13327
|
+
},
|
|
13328
|
+
ctx
|
|
13329
|
+
);
|
|
13330
|
+
if (outcome.error === void 0) {
|
|
13331
|
+
changeOutput = outcome.changeOutput ?? null;
|
|
13332
|
+
if (!serverApply && changeOutput) {
|
|
13333
|
+
await this.storeEngineToken(engine, changeOutput, { criticalSave: true });
|
|
13602
13334
|
}
|
|
13603
|
-
|
|
13335
|
+
blobs.push(outcome.tokenBlob);
|
|
13336
|
+
splitDelivered = true;
|
|
13337
|
+
} else {
|
|
13338
|
+
splitDelivered = this.classifyResumeSplitFailure(outcome.error, (err) => {
|
|
13604
13339
|
conflicted = true;
|
|
13605
13340
|
firstConflict ??= err;
|
|
13606
13341
|
undeliveredAmount += BigInt(payload.split.splitAmount);
|
|
13607
|
-
|
|
13608
|
-
} else if (err instanceof SplitCheckpointLostError) {
|
|
13609
|
-
logger.warn("Payments", `Resume split (legacy no-checkpoint): recording the spend; change recovers on the next inventory resync (\xA73.1):`, err);
|
|
13610
|
-
this.deps.emitEvent("inventory:conflict", { transferId, coinId: payload.coinId, error: err.message });
|
|
13611
|
-
splitDelivered = true;
|
|
13612
|
-
} else {
|
|
13613
|
-
throw err;
|
|
13614
|
-
}
|
|
13342
|
+
});
|
|
13615
13343
|
}
|
|
13616
13344
|
}
|
|
13617
|
-
if (splitDelivered)
|
|
13618
|
-
spent.push(payload.split.tokenId);
|
|
13619
|
-
}
|
|
13345
|
+
if (splitDelivered) spent.push(payload.split.tokenId);
|
|
13620
13346
|
}
|
|
13347
|
+
await this.deliverCommittedBlobs(blobs, payload.recipient, transferId, payload.memo);
|
|
13621
13348
|
if (conflicted && spent.length === 0) throw firstConflict;
|
|
13622
13349
|
if (serverApply) {
|
|
13623
13350
|
await this.applyInventoryDelta(
|
|
@@ -13629,6 +13356,55 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
13629
13356
|
// the one the sources were read from, captured for the whole resume
|
|
13630
13357
|
);
|
|
13631
13358
|
}
|
|
13359
|
+
await this.dropResumedSources(spent, transferId, payload);
|
|
13360
|
+
if (conflicted) {
|
|
13361
|
+
const remaining = undeliveredAmount > 0n ? undeliveredAmount : 0n;
|
|
13362
|
+
const delivered = BigInt(payload.amount) - remaining;
|
|
13363
|
+
await walletApi.completeIntent(transferId);
|
|
13364
|
+
await this.recordResumedSentHistory(payload, transferId, delivered > 0n ? delivered.toString() : "0", spent[0]);
|
|
13365
|
+
this.deps.emitEvent("send:partial-remainder", {
|
|
13366
|
+
transferId,
|
|
13367
|
+
remainingAmount: remaining.toString(),
|
|
13368
|
+
coinId: payload.coinId,
|
|
13369
|
+
recipientPubkey: payload.recipient
|
|
13370
|
+
});
|
|
13371
|
+
throw new PartialSendConflictError(
|
|
13372
|
+
"Resume: part of the payment was delivered before a source was consumed by a concurrent transfer \u2014 the delivered legs are final; re-plan ONLY the remainder.",
|
|
13373
|
+
transferId,
|
|
13374
|
+
spent,
|
|
13375
|
+
// the DELIVERED genesisIds — never the conflicted leg
|
|
13376
|
+
remaining.toString(),
|
|
13377
|
+
firstConflict
|
|
13378
|
+
);
|
|
13379
|
+
}
|
|
13380
|
+
await walletApi.completeIntent(transferId);
|
|
13381
|
+
await this.recordResumedSentHistory(payload, transferId, payload.amount, payload.direct[0]);
|
|
13382
|
+
}
|
|
13383
|
+
/**
|
|
13384
|
+
* A resumed split that produced no output: a lost source means the leg delivered
|
|
13385
|
+
* nothing (returns false), and every checkpoint failure keeps the intent open by
|
|
13386
|
+
* throwing. There is no path here that records the spend.
|
|
13387
|
+
*/
|
|
13388
|
+
classifyResumeSplitFailure(error, onConflict) {
|
|
13389
|
+
if (error instanceof ProofUnconfirmedError) throw error;
|
|
13390
|
+
if (error instanceof CheckpointPersistFailedError || error instanceof SplitCheckpointLostError || error instanceof CheckpointTrustbaseMismatchError) {
|
|
13391
|
+
throw error;
|
|
13392
|
+
}
|
|
13393
|
+
if (error instanceof TransferConflictError) {
|
|
13394
|
+
onConflict(error);
|
|
13395
|
+
logger.warn("Payments", "Resume split: source lost to a concurrent transfer \u2014 not delivered (\xA78.1/#4):", error);
|
|
13396
|
+
return false;
|
|
13397
|
+
}
|
|
13398
|
+
throw error;
|
|
13399
|
+
}
|
|
13400
|
+
/**
|
|
13401
|
+
* Drop the local records of the sources a resume consumed (present when
|
|
13402
|
+
* resuming on the originating device). M7: each removal carries the LOCAL state
|
|
13403
|
+
* we spent, so a source a concurrent claim reactivated to a NEW state (a
|
|
13404
|
+
* self-send round-trip) is KEPT, not destroyed.
|
|
13405
|
+
*/
|
|
13406
|
+
async dropResumedSources(spent, transferId, payload) {
|
|
13407
|
+
const engine = this.deps.tokenEngine;
|
|
13632
13408
|
for (const genesisId of spent) {
|
|
13633
13409
|
const local = this.tokens.get(`v2_${genesisId}`);
|
|
13634
13410
|
if (!local) continue;
|
|
@@ -13640,8 +13416,7 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
13640
13416
|
let spentOnChain = false;
|
|
13641
13417
|
try {
|
|
13642
13418
|
if (local.sdkData) {
|
|
13643
|
-
|
|
13644
|
-
spentOnChain = await engine.isSpent(decoded);
|
|
13419
|
+
spentOnChain = await engine.isSpent(await engine.decodeToken(decodeTokenBlob(hexToBytes4(local.sdkData))));
|
|
13645
13420
|
}
|
|
13646
13421
|
} catch {
|
|
13647
13422
|
spentOnChain = false;
|
|
@@ -13655,47 +13430,19 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
13655
13430
|
);
|
|
13656
13431
|
}
|
|
13657
13432
|
}
|
|
13658
|
-
|
|
13659
|
-
|
|
13660
|
-
|
|
13661
|
-
await walletApi.completeIntent(transferId);
|
|
13662
|
-
await this.addToHistory({
|
|
13663
|
-
type: "SENT",
|
|
13664
|
-
amount: (delivered > 0n ? delivered : 0n).toString(),
|
|
13665
|
-
coinId: payload.coinId,
|
|
13666
|
-
symbol: this.getCoinSymbol(payload.coinId),
|
|
13667
|
-
timestamp: Date.now(),
|
|
13668
|
-
recipientPubkey: payload.recipient,
|
|
13669
|
-
memo: payload.memo,
|
|
13670
|
-
transferId,
|
|
13671
|
-
tokenId: spent[0] ? `v2_${spent[0]}` : void 0
|
|
13672
|
-
});
|
|
13673
|
-
this.deps.emitEvent("send:partial-remainder", {
|
|
13674
|
-
transferId,
|
|
13675
|
-
remainingAmount: (remaining > 0n ? remaining : 0n).toString(),
|
|
13676
|
-
coinId: payload.coinId,
|
|
13677
|
-
recipientPubkey: payload.recipient
|
|
13678
|
-
});
|
|
13679
|
-
throw new PartialSendConflictError(
|
|
13680
|
-
"Resume: part of the payment was delivered before a source was consumed by a concurrent transfer \u2014 the delivered legs are final; re-plan ONLY the remainder.",
|
|
13681
|
-
transferId,
|
|
13682
|
-
spent,
|
|
13683
|
-
// the DELIVERED genesisIds — never the conflicted leg
|
|
13684
|
-
(remaining > 0n ? remaining : 0n).toString(),
|
|
13685
|
-
firstConflict
|
|
13686
|
-
);
|
|
13687
|
-
}
|
|
13688
|
-
await walletApi.completeIntent(transferId);
|
|
13433
|
+
}
|
|
13434
|
+
/** §10: the SENT record — same dedupKey as the original attempt, so a resume after the history POST is a server-side no-op. */
|
|
13435
|
+
async recordResumedSentHistory(payload, transferId, amount, tokenGenesisId) {
|
|
13689
13436
|
await this.addToHistory({
|
|
13690
13437
|
type: "SENT",
|
|
13691
|
-
amount
|
|
13438
|
+
amount,
|
|
13692
13439
|
coinId: payload.coinId,
|
|
13693
13440
|
symbol: this.getCoinSymbol(payload.coinId),
|
|
13694
13441
|
timestamp: Date.now(),
|
|
13695
13442
|
recipientPubkey: payload.recipient,
|
|
13696
13443
|
memo: payload.memo,
|
|
13697
13444
|
transferId,
|
|
13698
|
-
tokenId:
|
|
13445
|
+
tokenId: tokenGenesisId ? `v2_${tokenGenesisId}` : void 0
|
|
13699
13446
|
});
|
|
13700
13447
|
}
|
|
13701
13448
|
// ===========================================================================
|
|
@@ -13717,11 +13464,7 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
13717
13464
|
*/
|
|
13718
13465
|
async save(opts = {}) {
|
|
13719
13466
|
const providers = this.getTokenStorageProviders();
|
|
13720
|
-
|
|
13721
|
-
const txf = tokenToTxf(t);
|
|
13722
|
-
return `${t.id.slice(0, 12)}(${t.status},txf=${!!txf})`;
|
|
13723
|
-
});
|
|
13724
|
-
logger.debug("Payments", `save(): providers=${providers.size}, tokens=[${tokenStats.join(", ")}]`);
|
|
13467
|
+
logger.debug("Payments", `save(): providers=${providers.size}, tokens=${this.tokens.size}`);
|
|
13725
13468
|
if (providers.size === 0) {
|
|
13726
13469
|
logger.debug("Payments", "save(): No token storage providers - TXF not persisted");
|
|
13727
13470
|
return;
|
|
@@ -22395,7 +22138,6 @@ import canonicalize from "canonicalize";
|
|
|
22395
22138
|
|
|
22396
22139
|
// core/address.ts
|
|
22397
22140
|
var DIRECT_PREFIX = "DIRECT://";
|
|
22398
|
-
var PROXY_PREFIX = "PROXY://";
|
|
22399
22141
|
var NAMETAG_PREFIX = "@";
|
|
22400
22142
|
function parseAddress(address) {
|
|
22401
22143
|
if (!address || typeof address !== "string") return null;
|
|
@@ -22407,9 +22149,6 @@ function parseAddress(address) {
|
|
|
22407
22149
|
}
|
|
22408
22150
|
return null;
|
|
22409
22151
|
}
|
|
22410
|
-
if (trimmed.startsWith(PROXY_PREFIX) && trimmed.length > PROXY_PREFIX.length) {
|
|
22411
|
-
return { type: "PROXY", raw: trimmed, value: trimmed.slice(PROXY_PREFIX.length) };
|
|
22412
|
-
}
|
|
22413
22152
|
if (trimmed.startsWith(NAMETAG_PREFIX)) {
|
|
22414
22153
|
const value = trimmed.slice(NAMETAG_PREFIX.length);
|
|
22415
22154
|
if (value.length > 0) {
|
|
@@ -25656,447 +25395,97 @@ function parseWalletText(content) {
|
|
|
25656
25395
|
/ENCRYPTED MASTER KEY \(password protected\):\s*([^\n]+)/
|
|
25657
25396
|
);
|
|
25658
25397
|
if (!encryptedKey) {
|
|
25659
|
-
return {
|
|
25660
|
-
success: false,
|
|
25661
|
-
error: "Could not find encrypted master key in backup file"
|
|
25662
|
-
};
|
|
25663
|
-
}
|
|
25664
|
-
return {
|
|
25665
|
-
success: false,
|
|
25666
|
-
needsPassword: true,
|
|
25667
|
-
error: "Password required for encrypted wallet"
|
|
25668
|
-
};
|
|
25669
|
-
}
|
|
25670
|
-
const masterKey = extractFromText(
|
|
25671
|
-
content,
|
|
25672
|
-
/MASTER PRIVATE KEY \(keep secret!\):\s*([^\n]+)/
|
|
25673
|
-
);
|
|
25674
|
-
if (!masterKey) {
|
|
25675
|
-
return {
|
|
25676
|
-
success: false,
|
|
25677
|
-
error: "Could not find master private key in backup file"
|
|
25678
|
-
};
|
|
25679
|
-
}
|
|
25680
|
-
const chainCode = extractFromText(
|
|
25681
|
-
content,
|
|
25682
|
-
/MASTER CHAIN CODE \(for (?:BIP32 HD|Alpha) wallet compatibility\):\s*([^\n]+)/
|
|
25683
|
-
);
|
|
25684
|
-
const descriptorPath = extractFromText(content, /DESCRIPTOR PATH:\s*([^\n]+)/);
|
|
25685
|
-
const isBIP32 = content.includes("WALLET TYPE: BIP32 hierarchical deterministic wallet") || content.includes("WALLET TYPE: Alpha descriptor wallet") || !!chainCode;
|
|
25686
|
-
const effectiveDescriptorPath = descriptorPath ?? (isBIP32 ? "84'/1'/0'" : void 0);
|
|
25687
|
-
const data = {
|
|
25688
|
-
masterKey,
|
|
25689
|
-
chainCode: chainCode ?? void 0,
|
|
25690
|
-
descriptorPath: effectiveDescriptorPath,
|
|
25691
|
-
derivationMode: isBIP32 ? "bip32" : "wif_hmac"
|
|
25692
|
-
};
|
|
25693
|
-
return {
|
|
25694
|
-
success: true,
|
|
25695
|
-
data
|
|
25696
|
-
};
|
|
25697
|
-
} catch (e) {
|
|
25698
|
-
return {
|
|
25699
|
-
success: false,
|
|
25700
|
-
error: `Failed to parse wallet text: ${e instanceof Error ? e.message : String(e)}`
|
|
25701
|
-
};
|
|
25702
|
-
}
|
|
25703
|
-
}
|
|
25704
|
-
function parseAndDecryptWalletText(content, password) {
|
|
25705
|
-
try {
|
|
25706
|
-
const isEncrypted = isTextWalletEncrypted(content);
|
|
25707
|
-
if (!isEncrypted) {
|
|
25708
|
-
return parseWalletText(content);
|
|
25709
|
-
}
|
|
25710
|
-
const encryptedKey = extractFromText(
|
|
25711
|
-
content,
|
|
25712
|
-
/ENCRYPTED MASTER KEY \(password protected\):\s*([^\n]+)/
|
|
25713
|
-
);
|
|
25714
|
-
if (!encryptedKey) {
|
|
25715
|
-
return {
|
|
25716
|
-
success: false,
|
|
25717
|
-
error: "Could not find encrypted master key in backup file"
|
|
25718
|
-
};
|
|
25719
|
-
}
|
|
25720
|
-
const masterKey = decryptTextFormatKey(encryptedKey, password);
|
|
25721
|
-
if (!masterKey) {
|
|
25722
|
-
return {
|
|
25723
|
-
success: false,
|
|
25724
|
-
error: "Failed to decrypt - incorrect password?"
|
|
25725
|
-
};
|
|
25726
|
-
}
|
|
25727
|
-
const chainCode = extractFromText(
|
|
25728
|
-
content,
|
|
25729
|
-
/MASTER CHAIN CODE \(for (?:BIP32 HD|Alpha) wallet compatibility\):\s*([^\n]+)/
|
|
25730
|
-
);
|
|
25731
|
-
const descriptorPath = extractFromText(content, /DESCRIPTOR PATH:\s*([^\n]+)/);
|
|
25732
|
-
const isBIP32 = content.includes("WALLET TYPE: BIP32 hierarchical deterministic wallet") || content.includes("WALLET TYPE: Alpha descriptor wallet") || !!chainCode;
|
|
25733
|
-
const effectiveDescriptorPath = descriptorPath ?? (isBIP32 ? "84'/1'/0'" : void 0);
|
|
25734
|
-
const data = {
|
|
25735
|
-
masterKey,
|
|
25736
|
-
chainCode: chainCode ?? void 0,
|
|
25737
|
-
descriptorPath: effectiveDescriptorPath,
|
|
25738
|
-
derivationMode: isBIP32 ? "bip32" : "wif_hmac"
|
|
25739
|
-
};
|
|
25740
|
-
return {
|
|
25741
|
-
success: true,
|
|
25742
|
-
data
|
|
25743
|
-
};
|
|
25744
|
-
} catch (e) {
|
|
25745
|
-
return {
|
|
25746
|
-
success: false,
|
|
25747
|
-
error: `Failed to parse/decrypt wallet text: ${e instanceof Error ? e.message : String(e)}`
|
|
25748
|
-
};
|
|
25749
|
-
}
|
|
25750
|
-
}
|
|
25751
|
-
|
|
25752
|
-
// serialization/wallet-dat.ts
|
|
25753
|
-
import CryptoJS4 from "crypto-js";
|
|
25754
|
-
function uint8ArrayToWordArray(u8arr) {
|
|
25755
|
-
const hex = bytesToHex4(u8arr);
|
|
25756
|
-
return CryptoJS4.enc.Hex.parse(hex);
|
|
25757
|
-
}
|
|
25758
|
-
function isSQLiteDatabase(data) {
|
|
25759
|
-
if (data.length < 16) return false;
|
|
25760
|
-
const header = new TextDecoder().decode(data.slice(0, 16));
|
|
25761
|
-
return header.startsWith("SQLite format 3");
|
|
25762
|
-
}
|
|
25763
|
-
function isWalletDatEncrypted(data) {
|
|
25764
|
-
const mkeyPattern = new TextEncoder().encode("mkey");
|
|
25765
|
-
return findPattern(data, mkeyPattern, 0) !== -1;
|
|
25766
|
-
}
|
|
25767
|
-
function findAllCMasterKeys(data) {
|
|
25768
|
-
const results = [];
|
|
25769
|
-
for (let pos = 0; pos < data.length - 70; pos++) {
|
|
25770
|
-
if (data[pos] === 48) {
|
|
25771
|
-
const saltLenPos = pos + 1 + 48;
|
|
25772
|
-
if (saltLenPos < data.length && data[saltLenPos] === 8) {
|
|
25773
|
-
const iterPos = saltLenPos + 1 + 8 + 4;
|
|
25774
|
-
if (iterPos + 4 <= data.length) {
|
|
25775
|
-
const iterations = data[iterPos] | data[iterPos + 1] << 8 | data[iterPos + 2] << 16 | data[iterPos + 3] << 24;
|
|
25776
|
-
if (iterations >= 1e3 && iterations <= 1e7) {
|
|
25777
|
-
const encryptedKey = data.slice(pos + 1, pos + 1 + 48);
|
|
25778
|
-
const salt = data.slice(saltLenPos + 1, saltLenPos + 1 + 8);
|
|
25779
|
-
const derivationMethod = data[saltLenPos + 1 + 8] | data[saltLenPos + 1 + 8 + 1] << 8 | data[saltLenPos + 1 + 8 + 2] << 16 | data[saltLenPos + 1 + 8 + 3] << 24;
|
|
25780
|
-
results.push({ encryptedKey, salt, derivationMethod, iterations, position: pos });
|
|
25781
|
-
}
|
|
25782
|
-
}
|
|
25783
|
-
}
|
|
25784
|
-
}
|
|
25785
|
-
}
|
|
25786
|
-
return results;
|
|
25787
|
-
}
|
|
25788
|
-
function findWpkhDescriptor(data) {
|
|
25789
|
-
const descriptorPattern = new TextEncoder().encode("walletdescriptor");
|
|
25790
|
-
let descriptorIndex = 0;
|
|
25791
|
-
while ((descriptorIndex = findPattern(data, descriptorPattern, descriptorIndex)) !== -1) {
|
|
25792
|
-
let scanPos = descriptorIndex + descriptorPattern.length + 32;
|
|
25793
|
-
const descLen = data[scanPos];
|
|
25794
|
-
scanPos++;
|
|
25795
|
-
const descBytes = data.slice(scanPos, scanPos + Math.min(descLen, 200));
|
|
25796
|
-
let descStr = "";
|
|
25797
|
-
for (let i = 0; i < descBytes.length && descBytes[i] >= 32 && descBytes[i] <= 126; i++) {
|
|
25798
|
-
descStr += String.fromCharCode(descBytes[i]);
|
|
25799
|
-
}
|
|
25800
|
-
if (descStr.startsWith("wpkh(xpub") && descStr.includes("/0/*)")) {
|
|
25801
|
-
const xpubMatch = descStr.match(/xpub[1-9A-HJ-NP-Za-km-z]{100,}/);
|
|
25802
|
-
if (xpubMatch) {
|
|
25803
|
-
const descIdStart = descriptorIndex + descriptorPattern.length;
|
|
25804
|
-
const descriptorId = data.slice(descIdStart, descIdStart + 32);
|
|
25805
|
-
const pathMatch = descStr.match(/\[[\da-f]+\/(\d+'\/\d+'\/\d+')\]/);
|
|
25806
|
-
const descriptorPath = pathMatch ? pathMatch[1] : "84'/1'/0'";
|
|
25807
|
-
return {
|
|
25808
|
-
descriptorId,
|
|
25809
|
-
xpubString: xpubMatch[0],
|
|
25810
|
-
descriptorPath
|
|
25811
|
-
};
|
|
25812
|
-
}
|
|
25813
|
-
}
|
|
25814
|
-
descriptorIndex++;
|
|
25815
|
-
}
|
|
25816
|
-
return { descriptorId: null, xpubString: null, descriptorPath: null };
|
|
25817
|
-
}
|
|
25818
|
-
function extractChainCodeFromXpub(xpubString) {
|
|
25819
|
-
const decoded = base58Decode(xpubString);
|
|
25820
|
-
return bytesToHex4(decoded.slice(13, 45));
|
|
25821
|
-
}
|
|
25822
|
-
function findMasterChainCode(data) {
|
|
25823
|
-
const xpubPattern = new TextEncoder().encode("xpub");
|
|
25824
|
-
const base58Chars = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
|
|
25825
|
-
let searchPos = 0;
|
|
25826
|
-
while (searchPos < data.length) {
|
|
25827
|
-
const xpubIndex = findPattern(data, xpubPattern, searchPos);
|
|
25828
|
-
if (xpubIndex === -1) break;
|
|
25829
|
-
let xpubStr = "xpub";
|
|
25830
|
-
let pos = xpubIndex + 4;
|
|
25831
|
-
while (pos < data.length && xpubStr.length < 120) {
|
|
25832
|
-
const char = String.fromCharCode(data[pos]);
|
|
25833
|
-
if (base58Chars.includes(char)) {
|
|
25834
|
-
xpubStr += char;
|
|
25835
|
-
pos++;
|
|
25836
|
-
} else {
|
|
25837
|
-
break;
|
|
25838
|
-
}
|
|
25839
|
-
}
|
|
25840
|
-
if (xpubStr.length > 100) {
|
|
25841
|
-
try {
|
|
25842
|
-
const decoded = base58Decode(xpubStr);
|
|
25843
|
-
const depth = decoded[4];
|
|
25844
|
-
if (depth === 0) {
|
|
25845
|
-
return bytesToHex4(decoded.slice(13, 45));
|
|
25846
|
-
}
|
|
25847
|
-
} catch {
|
|
25848
|
-
}
|
|
25849
|
-
}
|
|
25850
|
-
searchPos = xpubIndex + 4;
|
|
25851
|
-
}
|
|
25852
|
-
return null;
|
|
25853
|
-
}
|
|
25854
|
-
function extractDescriptorKeys(data) {
|
|
25855
|
-
const keys = [];
|
|
25856
|
-
const descriptorKeyPattern = new TextEncoder().encode("walletdescriptorkey");
|
|
25857
|
-
let index = 0;
|
|
25858
|
-
while ((index = findPattern(data, descriptorKeyPattern, index)) !== -1) {
|
|
25859
|
-
for (let checkPos = index + descriptorKeyPattern.length; checkPos < Math.min(index + descriptorKeyPattern.length + 200, data.length - 40); checkPos++) {
|
|
25860
|
-
if (data[checkPos] === 211 && data[checkPos + 1] === 2 && data[checkPos + 2] === 1 && data[checkPos + 3] === 1 && data[checkPos + 4] === 4 && data[checkPos + 5] === 32) {
|
|
25861
|
-
const privKey = data.slice(checkPos + 6, checkPos + 38);
|
|
25862
|
-
const privKeyHex = bytesToHex4(privKey);
|
|
25863
|
-
if (isValidPrivateKey(privKeyHex)) {
|
|
25864
|
-
keys.push(privKeyHex);
|
|
25865
|
-
break;
|
|
25866
|
-
}
|
|
25867
|
-
}
|
|
25868
|
-
}
|
|
25869
|
-
index++;
|
|
25870
|
-
}
|
|
25871
|
-
return keys;
|
|
25872
|
-
}
|
|
25873
|
-
function extractLegacyKeys(data) {
|
|
25874
|
-
const keys = [];
|
|
25875
|
-
const keyPattern = new TextEncoder().encode("key");
|
|
25876
|
-
let index = 0;
|
|
25877
|
-
while ((index = findPattern(data, keyPattern, index)) !== -1) {
|
|
25878
|
-
const searchPattern = new Uint8Array([4, 32]);
|
|
25879
|
-
for (let i = index; i < Math.min(index + 200, data.length - 34); i++) {
|
|
25880
|
-
if (data[i] === searchPattern[0] && data[i + 1] === searchPattern[1]) {
|
|
25881
|
-
const privKey = data.slice(i + 2, i + 34);
|
|
25882
|
-
const privKeyHex = bytesToHex4(privKey);
|
|
25883
|
-
if (isValidPrivateKey(privKeyHex)) {
|
|
25884
|
-
keys.push(privKeyHex);
|
|
25885
|
-
break;
|
|
25886
|
-
}
|
|
25887
|
-
}
|
|
25888
|
-
}
|
|
25889
|
-
index++;
|
|
25890
|
-
}
|
|
25891
|
-
return keys;
|
|
25892
|
-
}
|
|
25893
|
-
function findEncryptedKeyForDescriptor(data, descriptorId) {
|
|
25894
|
-
const ckeyPattern = new TextEncoder().encode("walletdescriptorckey");
|
|
25895
|
-
let ckeyIndex = findPattern(data, ckeyPattern, 0);
|
|
25896
|
-
while (ckeyIndex !== -1) {
|
|
25897
|
-
const recordDescId = data.slice(ckeyIndex + ckeyPattern.length, ckeyIndex + ckeyPattern.length + 32);
|
|
25898
|
-
if (Array.from(recordDescId).every((b, i) => b === descriptorId[i])) {
|
|
25899
|
-
let keyPos = ckeyIndex + ckeyPattern.length + 32;
|
|
25900
|
-
const pubkeyLen = data[keyPos];
|
|
25901
|
-
keyPos++;
|
|
25902
|
-
const pubkey = data.slice(keyPos, keyPos + pubkeyLen);
|
|
25903
|
-
for (let searchPos = keyPos + pubkeyLen; searchPos < Math.min(keyPos + pubkeyLen + 100, data.length - 50); searchPos++) {
|
|
25904
|
-
const valueLen = data[searchPos];
|
|
25905
|
-
if (valueLen >= 32 && valueLen <= 64) {
|
|
25906
|
-
const encryptedKey = data.slice(searchPos + 1, searchPos + 1 + valueLen);
|
|
25907
|
-
return { pubkey, encryptedKey };
|
|
25908
|
-
}
|
|
25909
|
-
}
|
|
25910
|
-
}
|
|
25911
|
-
ckeyIndex = findPattern(data, ckeyPattern, ckeyIndex + 1);
|
|
25912
|
-
}
|
|
25913
|
-
return null;
|
|
25914
|
-
}
|
|
25915
|
-
async function decryptCMasterKey(cmk, password, onProgress) {
|
|
25916
|
-
const { encryptedKey, salt, iterations } = cmk;
|
|
25917
|
-
const passwordHex = bytesToHex4(new TextEncoder().encode(password));
|
|
25918
|
-
const saltHex = bytesToHex4(salt);
|
|
25919
|
-
const inputHex = passwordHex + saltHex;
|
|
25920
|
-
let hash = CryptoJS4.SHA512(CryptoJS4.enc.Hex.parse(inputHex));
|
|
25921
|
-
const BATCH_SIZE = 1e3;
|
|
25922
|
-
for (let i = 0; i < iterations - 1; i++) {
|
|
25923
|
-
hash = CryptoJS4.SHA512(hash);
|
|
25924
|
-
if (onProgress && i % BATCH_SIZE === 0) {
|
|
25925
|
-
await onProgress(i, iterations);
|
|
25926
|
-
}
|
|
25927
|
-
}
|
|
25928
|
-
const derivedKey = CryptoJS4.lib.WordArray.create(hash.words.slice(0, 8));
|
|
25929
|
-
const derivedIv = CryptoJS4.lib.WordArray.create(hash.words.slice(8, 12));
|
|
25930
|
-
const encryptedWords = uint8ArrayToWordArray(encryptedKey);
|
|
25931
|
-
const decrypted = CryptoJS4.AES.decrypt(
|
|
25932
|
-
{ ciphertext: encryptedWords },
|
|
25933
|
-
derivedKey,
|
|
25934
|
-
{ iv: derivedIv, padding: CryptoJS4.pad.Pkcs7, mode: CryptoJS4.mode.CBC }
|
|
25935
|
-
);
|
|
25936
|
-
const result = CryptoJS4.enc.Hex.stringify(decrypted);
|
|
25937
|
-
if (!result || result.length !== 64) {
|
|
25938
|
-
throw new SphereError("Master key decryption failed - incorrect password", "DECRYPTION_ERROR");
|
|
25939
|
-
}
|
|
25940
|
-
return result;
|
|
25941
|
-
}
|
|
25942
|
-
function decryptPrivateKey(encryptedKey, pubkey, masterKeyHex) {
|
|
25943
|
-
const pubkeyWords = uint8ArrayToWordArray(pubkey);
|
|
25944
|
-
const pubkeyHashWords = CryptoJS4.SHA256(CryptoJS4.SHA256(pubkeyWords));
|
|
25945
|
-
const ivWords = CryptoJS4.lib.WordArray.create(pubkeyHashWords.words.slice(0, 4));
|
|
25946
|
-
const masterKeyWords = CryptoJS4.enc.Hex.parse(masterKeyHex);
|
|
25947
|
-
const encryptedWords = uint8ArrayToWordArray(encryptedKey);
|
|
25948
|
-
const decrypted = CryptoJS4.AES.decrypt(
|
|
25949
|
-
{ ciphertext: encryptedWords },
|
|
25950
|
-
masterKeyWords,
|
|
25951
|
-
{ iv: ivWords, padding: CryptoJS4.pad.Pkcs7, mode: CryptoJS4.mode.CBC }
|
|
25952
|
-
);
|
|
25953
|
-
return CryptoJS4.enc.Hex.stringify(decrypted);
|
|
25954
|
-
}
|
|
25955
|
-
function parseWalletDat(data) {
|
|
25956
|
-
if (!isSQLiteDatabase(data)) {
|
|
25957
|
-
return {
|
|
25958
|
-
success: false,
|
|
25959
|
-
error: "Invalid wallet.dat file - not an SQLite database"
|
|
25960
|
-
};
|
|
25961
|
-
}
|
|
25962
|
-
const isEncrypted = isWalletDatEncrypted(data);
|
|
25963
|
-
const cmasterKeys = isEncrypted ? findAllCMasterKeys(data) : [];
|
|
25964
|
-
const descriptorKeys = isEncrypted ? [] : extractDescriptorKeys(data);
|
|
25965
|
-
const legacyKeys = isEncrypted ? [] : extractLegacyKeys(data);
|
|
25966
|
-
const { descriptorId: _descriptorId, xpubString, descriptorPath } = findWpkhDescriptor(data);
|
|
25967
|
-
void _descriptorId;
|
|
25968
|
-
let chainCode = null;
|
|
25969
|
-
if (xpubString) {
|
|
25970
|
-
try {
|
|
25971
|
-
chainCode = extractChainCodeFromXpub(xpubString);
|
|
25972
|
-
} catch {
|
|
25973
|
-
chainCode = findMasterChainCode(data);
|
|
25974
|
-
}
|
|
25975
|
-
} else {
|
|
25976
|
-
chainCode = findMasterChainCode(data);
|
|
25977
|
-
}
|
|
25978
|
-
if (!isEncrypted) {
|
|
25979
|
-
let masterKey = null;
|
|
25980
|
-
if (descriptorKeys.length > 0) {
|
|
25981
|
-
masterKey = descriptorKeys[0];
|
|
25982
|
-
} else if (legacyKeys.length > 0) {
|
|
25983
|
-
masterKey = legacyKeys[0];
|
|
25984
|
-
}
|
|
25985
|
-
if (masterKey) {
|
|
25986
|
-
const parsedData = {
|
|
25987
|
-
masterKey,
|
|
25988
|
-
chainCode: chainCode ?? void 0,
|
|
25989
|
-
descriptorPath: descriptorPath ?? "84'/1'/0'",
|
|
25990
|
-
derivationMode: chainCode ? "bip32" : "wif_hmac"
|
|
25398
|
+
return {
|
|
25399
|
+
success: false,
|
|
25400
|
+
error: "Could not find encrypted master key in backup file"
|
|
25401
|
+
};
|
|
25402
|
+
}
|
|
25403
|
+
return {
|
|
25404
|
+
success: false,
|
|
25405
|
+
needsPassword: true,
|
|
25406
|
+
error: "Password required for encrypted wallet"
|
|
25991
25407
|
};
|
|
25408
|
+
}
|
|
25409
|
+
const masterKey = extractFromText(
|
|
25410
|
+
content,
|
|
25411
|
+
/MASTER PRIVATE KEY \(keep secret!\):\s*([^\n]+)/
|
|
25412
|
+
);
|
|
25413
|
+
if (!masterKey) {
|
|
25992
25414
|
return {
|
|
25993
|
-
success:
|
|
25994
|
-
|
|
25415
|
+
success: false,
|
|
25416
|
+
error: "Could not find master private key in backup file"
|
|
25995
25417
|
};
|
|
25996
25418
|
}
|
|
25997
|
-
|
|
25998
|
-
|
|
25999
|
-
|
|
26000
|
-
|
|
26001
|
-
|
|
26002
|
-
|
|
26003
|
-
|
|
26004
|
-
|
|
26005
|
-
|
|
25419
|
+
const chainCode = extractFromText(
|
|
25420
|
+
content,
|
|
25421
|
+
/MASTER CHAIN CODE \(for (?:BIP32 HD|Alpha) wallet compatibility\):\s*([^\n]+)/
|
|
25422
|
+
);
|
|
25423
|
+
const descriptorPath = extractFromText(content, /DESCRIPTOR PATH:\s*([^\n]+)/);
|
|
25424
|
+
const isBIP32 = content.includes("WALLET TYPE: BIP32 hierarchical deterministic wallet") || content.includes("WALLET TYPE: Alpha descriptor wallet") || !!chainCode;
|
|
25425
|
+
const effectiveDescriptorPath = descriptorPath ?? (isBIP32 ? "84'/1'/0'" : void 0);
|
|
25426
|
+
const data = {
|
|
25427
|
+
masterKey,
|
|
25428
|
+
chainCode: chainCode ?? void 0,
|
|
25429
|
+
descriptorPath: effectiveDescriptorPath,
|
|
25430
|
+
derivationMode: isBIP32 ? "bip32" : "wif_hmac"
|
|
26006
25431
|
};
|
|
26007
|
-
}
|
|
26008
|
-
const firstCmk = cmasterKeys[0];
|
|
26009
|
-
return {
|
|
26010
|
-
success: false,
|
|
26011
|
-
needsPassword: true,
|
|
26012
|
-
encryptionInfo: {
|
|
26013
|
-
iterations: firstCmk.iterations,
|
|
26014
|
-
salt: firstCmk.salt,
|
|
26015
|
-
encryptedKey: firstCmk.encryptedKey
|
|
26016
|
-
},
|
|
26017
|
-
error: "Password required for encrypted wallet"
|
|
26018
|
-
};
|
|
26019
|
-
}
|
|
26020
|
-
async function parseAndDecryptWalletDat(data, password, onProgress) {
|
|
26021
|
-
if (!isSQLiteDatabase(data)) {
|
|
26022
25432
|
return {
|
|
26023
|
-
success:
|
|
26024
|
-
|
|
25433
|
+
success: true,
|
|
25434
|
+
data
|
|
26025
25435
|
};
|
|
26026
|
-
}
|
|
26027
|
-
const isEncrypted = isWalletDatEncrypted(data);
|
|
26028
|
-
if (!isEncrypted) {
|
|
26029
|
-
return parseWalletDat(data);
|
|
26030
|
-
}
|
|
26031
|
-
const cmasterKeys = findAllCMasterKeys(data);
|
|
26032
|
-
if (cmasterKeys.length === 0) {
|
|
25436
|
+
} catch (e) {
|
|
26033
25437
|
return {
|
|
26034
25438
|
success: false,
|
|
26035
|
-
error:
|
|
25439
|
+
error: `Failed to parse wallet text: ${e instanceof Error ? e.message : String(e)}`
|
|
26036
25440
|
};
|
|
26037
25441
|
}
|
|
26038
|
-
|
|
26039
|
-
|
|
26040
|
-
|
|
26041
|
-
|
|
26042
|
-
|
|
26043
|
-
|
|
26044
|
-
}
|
|
26045
|
-
} catch {
|
|
26046
|
-
continue;
|
|
25442
|
+
}
|
|
25443
|
+
function parseAndDecryptWalletText(content, password) {
|
|
25444
|
+
try {
|
|
25445
|
+
const isEncrypted = isTextWalletEncrypted(content);
|
|
25446
|
+
if (!isEncrypted) {
|
|
25447
|
+
return parseWalletText(content);
|
|
26047
25448
|
}
|
|
26048
|
-
|
|
26049
|
-
|
|
26050
|
-
|
|
26051
|
-
|
|
26052
|
-
|
|
26053
|
-
|
|
26054
|
-
|
|
26055
|
-
|
|
26056
|
-
|
|
26057
|
-
if (xpubString) {
|
|
26058
|
-
try {
|
|
26059
|
-
chainCode = extractChainCodeFromXpub(xpubString);
|
|
26060
|
-
} catch {
|
|
26061
|
-
chainCode = findMasterChainCode(data);
|
|
25449
|
+
const encryptedKey = extractFromText(
|
|
25450
|
+
content,
|
|
25451
|
+
/ENCRYPTED MASTER KEY \(password protected\):\s*([^\n]+)/
|
|
25452
|
+
);
|
|
25453
|
+
if (!encryptedKey) {
|
|
25454
|
+
return {
|
|
25455
|
+
success: false,
|
|
25456
|
+
error: "Could not find encrypted master key in backup file"
|
|
25457
|
+
};
|
|
26062
25458
|
}
|
|
26063
|
-
|
|
26064
|
-
|
|
26065
|
-
|
|
26066
|
-
|
|
26067
|
-
|
|
26068
|
-
|
|
26069
|
-
|
|
25459
|
+
const masterKey = decryptTextFormatKey(encryptedKey, password);
|
|
25460
|
+
if (!masterKey) {
|
|
25461
|
+
return {
|
|
25462
|
+
success: false,
|
|
25463
|
+
error: "Failed to decrypt - incorrect password?"
|
|
25464
|
+
};
|
|
25465
|
+
}
|
|
25466
|
+
const chainCode = extractFromText(
|
|
25467
|
+
content,
|
|
25468
|
+
/MASTER CHAIN CODE \(for (?:BIP32 HD|Alpha) wallet compatibility\):\s*([^\n]+)/
|
|
25469
|
+
);
|
|
25470
|
+
const descriptorPath = extractFromText(content, /DESCRIPTOR PATH:\s*([^\n]+)/);
|
|
25471
|
+
const isBIP32 = content.includes("WALLET TYPE: BIP32 hierarchical deterministic wallet") || content.includes("WALLET TYPE: Alpha descriptor wallet") || !!chainCode;
|
|
25472
|
+
const effectiveDescriptorPath = descriptorPath ?? (isBIP32 ? "84'/1'/0'" : void 0);
|
|
25473
|
+
const data = {
|
|
25474
|
+
masterKey,
|
|
25475
|
+
chainCode: chainCode ?? void 0,
|
|
25476
|
+
descriptorPath: effectiveDescriptorPath,
|
|
25477
|
+
derivationMode: isBIP32 ? "bip32" : "wif_hmac"
|
|
26070
25478
|
};
|
|
26071
|
-
}
|
|
26072
|
-
const encryptedKeyData = findEncryptedKeyForDescriptor(data, descriptorId);
|
|
26073
|
-
if (!encryptedKeyData) {
|
|
26074
25479
|
return {
|
|
26075
|
-
success:
|
|
26076
|
-
|
|
25480
|
+
success: true,
|
|
25481
|
+
data
|
|
26077
25482
|
};
|
|
26078
|
-
}
|
|
26079
|
-
const bip32MasterKey = decryptPrivateKey(
|
|
26080
|
-
encryptedKeyData.encryptedKey,
|
|
26081
|
-
encryptedKeyData.pubkey,
|
|
26082
|
-
masterKeyHex
|
|
26083
|
-
);
|
|
26084
|
-
if (!bip32MasterKey || bip32MasterKey.length !== 64) {
|
|
25483
|
+
} catch (e) {
|
|
26085
25484
|
return {
|
|
26086
25485
|
success: false,
|
|
26087
|
-
error:
|
|
25486
|
+
error: `Failed to parse/decrypt wallet text: ${e instanceof Error ? e.message : String(e)}`
|
|
26088
25487
|
};
|
|
26089
25488
|
}
|
|
26090
|
-
const parsedData = {
|
|
26091
|
-
masterKey: bip32MasterKey,
|
|
26092
|
-
chainCode: chainCode ?? void 0,
|
|
26093
|
-
descriptorPath: descriptorPath ?? "84'/1'/0'",
|
|
26094
|
-
derivationMode: "bip32"
|
|
26095
|
-
};
|
|
26096
|
-
return {
|
|
26097
|
-
success: true,
|
|
26098
|
-
data: parsedData
|
|
26099
|
-
};
|
|
26100
25489
|
}
|
|
26101
25490
|
|
|
26102
25491
|
// core/Sphere.ts
|
|
@@ -26929,32 +26318,196 @@ var Sphere = class _Sphere {
|
|
|
26929
26318
|
if (this._transport.isConnected() && this._transport.fetchPendingEvents) {
|
|
26930
26319
|
await this._transport.fetchPendingEvents();
|
|
26931
26320
|
}
|
|
26932
|
-
}
|
|
26933
|
-
getAggregator() {
|
|
26934
|
-
return this._oracle;
|
|
26321
|
+
}
|
|
26322
|
+
getAggregator() {
|
|
26323
|
+
return this._oracle;
|
|
26324
|
+
}
|
|
26325
|
+
/**
|
|
26326
|
+
* Apply a new gateway API key to the LIVE oracle + token engine WITHOUT a full
|
|
26327
|
+
* Sphere rebuild (transport / realtime socket / discovery stay up). Use this to
|
|
26328
|
+
* inject a per-wallet subscription key provisioned AFTER init, or a per-address
|
|
26329
|
+
* key on an address switch, so the next money operation authenticates with it.
|
|
26330
|
+
*
|
|
26331
|
+
* Money-safety: the running token engine is snapshotted per-operation (the
|
|
26332
|
+
* payments module reads `deps.tokenEngine` into a local at op start), so an
|
|
26333
|
+
* in-flight send/mint completes on the OLD key (submit + proof stay paired on
|
|
26334
|
+
* one client) and only operations started AFTER this call use the new key.
|
|
26335
|
+
* Only the ACTIVE address's engine is rebuilt — other tracked addresses re-key
|
|
26336
|
+
* on their next switch/build. No-op-safe if the engine can't be built (the
|
|
26337
|
+
* modules fall back to their legacy path, same as init).
|
|
26338
|
+
*/
|
|
26339
|
+
async setOracleApiKey(apiKey) {
|
|
26340
|
+
this._oracle.setApiKey?.(apiKey);
|
|
26341
|
+
const active = this._addressModules.get(this._currentAddressIndex);
|
|
26342
|
+
const replaced = active?.tokenEngine ?? this._tokenEngine;
|
|
26343
|
+
this._tokenEngine = await this.buildTokenEngine();
|
|
26344
|
+
if (active) active.tokenEngine = this._tokenEngine;
|
|
26345
|
+
this._payments.setTokenEngine(this._tokenEngine);
|
|
26346
|
+
if (replaced && replaced !== this._tokenEngine) replaced.dispose?.();
|
|
26347
|
+
}
|
|
26348
|
+
/**
|
|
26349
|
+
* Import a wallet from a backup file: a `UNICITY WALLET DETAILS` text backup
|
|
26350
|
+
* (the format `exportToTxt` writes, optionally password-encrypted), a legacy
|
|
26351
|
+
* flat-JSON webwallet export, or a bare mnemonic in a text file.
|
|
26352
|
+
*
|
|
26353
|
+
* @example
|
|
26354
|
+
* const result = await Sphere.importFromLegacyFile({
|
|
26355
|
+
* fileContent: await file.text(),
|
|
26356
|
+
* fileName: file.name,
|
|
26357
|
+
* password, // when the backup is encrypted
|
|
26358
|
+
* ...providers,
|
|
26359
|
+
* });
|
|
26360
|
+
*/
|
|
26361
|
+
static async importFromLegacyFile(options) {
|
|
26362
|
+
const { fileContent, fileName, password, onDecryptProgress, ...baseOptions } = options;
|
|
26363
|
+
const fileType = _Sphere.detectLegacyFileType(fileName, fileContent);
|
|
26364
|
+
if (fileType === "unknown") {
|
|
26365
|
+
return { success: false, error: "Unknown file format" };
|
|
26366
|
+
}
|
|
26367
|
+
if (fileType === "mnemonic") {
|
|
26368
|
+
const mnemonic = fileContent.trim().toLowerCase().split(/\s+/).join(" ");
|
|
26369
|
+
if (!_Sphere.validateMnemonic(mnemonic)) {
|
|
26370
|
+
return { success: false, error: "Invalid mnemonic phrase" };
|
|
26371
|
+
}
|
|
26372
|
+
const sphere = await _Sphere.import({ ...baseOptions, mnemonic });
|
|
26373
|
+
return { success: true, sphere, mnemonic };
|
|
26374
|
+
}
|
|
26375
|
+
if (fileType === "txt") {
|
|
26376
|
+
const content = fileContent;
|
|
26377
|
+
let parseResult;
|
|
26378
|
+
if (password) {
|
|
26379
|
+
parseResult = parseAndDecryptWalletText(content, password);
|
|
26380
|
+
} else if (isTextWalletEncrypted(content)) {
|
|
26381
|
+
return { success: false, needsPassword: true, error: "Password required for encrypted wallet" };
|
|
26382
|
+
} else {
|
|
26383
|
+
parseResult = parseWalletText(content);
|
|
26384
|
+
}
|
|
26385
|
+
if (parseResult.needsPassword && !password) {
|
|
26386
|
+
return { success: false, needsPassword: true, error: "Password required for encrypted wallet" };
|
|
26387
|
+
}
|
|
26388
|
+
if (!parseResult.success || !parseResult.data) {
|
|
26389
|
+
return { success: false, error: parseResult.error };
|
|
26390
|
+
}
|
|
26391
|
+
const { masterKey, chainCode, descriptorPath, derivationMode } = parseResult.data;
|
|
26392
|
+
const basePath = descriptorPath ? `m/${descriptorPath}` : DEFAULT_BASE_PATH;
|
|
26393
|
+
const sphere = await _Sphere.import({
|
|
26394
|
+
...baseOptions,
|
|
26395
|
+
masterKey,
|
|
26396
|
+
chainCode,
|
|
26397
|
+
basePath,
|
|
26398
|
+
derivationMode: derivationMode || (chainCode ? "bip32" : "wif_hmac")
|
|
26399
|
+
});
|
|
26400
|
+
return { success: true, sphere };
|
|
26401
|
+
}
|
|
26402
|
+
if (fileType === "json") {
|
|
26403
|
+
const content = fileContent;
|
|
26404
|
+
let parsed;
|
|
26405
|
+
try {
|
|
26406
|
+
parsed = JSON.parse(content);
|
|
26407
|
+
} catch {
|
|
26408
|
+
return { success: false, error: "Invalid JSON file" };
|
|
26409
|
+
}
|
|
26410
|
+
if (parsed.type === "sphere-wallet") {
|
|
26411
|
+
const result = await _Sphere.importFromJSON({
|
|
26412
|
+
...baseOptions,
|
|
26413
|
+
jsonContent: content,
|
|
26414
|
+
password
|
|
26415
|
+
});
|
|
26416
|
+
if (result.success) {
|
|
26417
|
+
const sphere2 = _Sphere.getInstance();
|
|
26418
|
+
return { success: true, sphere: sphere2, mnemonic: result.mnemonic };
|
|
26419
|
+
}
|
|
26420
|
+
if (!password && result.error?.includes("Password required")) {
|
|
26421
|
+
return { success: false, needsPassword: true, error: result.error };
|
|
26422
|
+
}
|
|
26423
|
+
return { success: false, error: result.error };
|
|
26424
|
+
}
|
|
26425
|
+
let masterKey;
|
|
26426
|
+
let mnemonic;
|
|
26427
|
+
if (parsed.encrypted && typeof parsed.encrypted === "object") {
|
|
26428
|
+
if (!password) {
|
|
26429
|
+
return { success: false, needsPassword: true, error: "Password required for encrypted wallet" };
|
|
26430
|
+
}
|
|
26431
|
+
const enc = parsed.encrypted;
|
|
26432
|
+
if (!enc.salt || !enc.masterPrivateKey) {
|
|
26433
|
+
return { success: false, error: "Invalid encrypted wallet format" };
|
|
26434
|
+
}
|
|
26435
|
+
const decryptedKey = decryptWithSalt(enc.masterPrivateKey, password, enc.salt);
|
|
26436
|
+
if (!decryptedKey) {
|
|
26437
|
+
return { success: false, error: "Failed to decrypt - incorrect password?" };
|
|
26438
|
+
}
|
|
26439
|
+
masterKey = decryptedKey;
|
|
26440
|
+
if (enc.mnemonic) {
|
|
26441
|
+
mnemonic = decryptWithSalt(enc.mnemonic, password, enc.salt) ?? void 0;
|
|
26442
|
+
}
|
|
26443
|
+
} else {
|
|
26444
|
+
masterKey = parsed.masterPrivateKey;
|
|
26445
|
+
mnemonic = parsed.mnemonic;
|
|
26446
|
+
}
|
|
26447
|
+
if (!masterKey) {
|
|
26448
|
+
return { success: false, error: "No master key found in wallet JSON" };
|
|
26449
|
+
}
|
|
26450
|
+
const chainCode = parsed.chainCode;
|
|
26451
|
+
const descriptorPath = parsed.descriptorPath;
|
|
26452
|
+
const derivationMode = parsed.derivationMode;
|
|
26453
|
+
const isBIP32 = derivationMode === "bip32" || !!chainCode;
|
|
26454
|
+
const basePath = descriptorPath ? `m/${descriptorPath}` : isBIP32 ? "m/84'/1'/0'" : DEFAULT_BASE_PATH;
|
|
26455
|
+
if (mnemonic) {
|
|
26456
|
+
const sphere2 = await _Sphere.import({ ...baseOptions, mnemonic, basePath });
|
|
26457
|
+
return { success: true, sphere: sphere2, mnemonic };
|
|
26458
|
+
}
|
|
26459
|
+
const sphere = await _Sphere.import({
|
|
26460
|
+
...baseOptions,
|
|
26461
|
+
masterKey,
|
|
26462
|
+
chainCode,
|
|
26463
|
+
basePath,
|
|
26464
|
+
derivationMode: derivationMode || (chainCode ? "bip32" : "wif_hmac")
|
|
26465
|
+
});
|
|
26466
|
+
return { success: true, sphere };
|
|
26467
|
+
}
|
|
26468
|
+
return { success: false, error: "Unsupported file type" };
|
|
26469
|
+
}
|
|
26470
|
+
/**
|
|
26471
|
+
* Detect legacy file type from filename and content
|
|
26472
|
+
*/
|
|
26473
|
+
static detectLegacyFileType(fileName, content) {
|
|
26474
|
+
if (fileName.endsWith(".json")) {
|
|
26475
|
+
return "json";
|
|
26476
|
+
}
|
|
26477
|
+
try {
|
|
26478
|
+
const trimmed = content.trim();
|
|
26479
|
+
if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
|
|
26480
|
+
JSON.parse(trimmed);
|
|
26481
|
+
return "json";
|
|
26482
|
+
}
|
|
26483
|
+
} catch {
|
|
26484
|
+
}
|
|
26485
|
+
const words = content.trim().split(/\s+/);
|
|
26486
|
+
if ((words.length === 12 || words.length === 24) && words.every((w) => /^[a-z]+$/.test(w.toLowerCase()))) {
|
|
26487
|
+
return "mnemonic";
|
|
26488
|
+
}
|
|
26489
|
+
if (isWalletTextFormat(content)) {
|
|
26490
|
+
return "txt";
|
|
26491
|
+
}
|
|
26492
|
+
return "unknown";
|
|
26935
26493
|
}
|
|
26936
26494
|
/**
|
|
26937
|
-
*
|
|
26938
|
-
* Sphere rebuild (transport / realtime socket / discovery stay up). Use this to
|
|
26939
|
-
* inject a per-wallet subscription key provisioned AFTER init, or a per-address
|
|
26940
|
-
* key on an address switch, so the next money operation authenticates with it.
|
|
26941
|
-
*
|
|
26942
|
-
* Money-safety: the running token engine is snapshotted per-operation (the
|
|
26943
|
-
* payments module reads `deps.tokenEngine` into a local at op start), so an
|
|
26944
|
-
* in-flight send/mint completes on the OLD key (submit + proof stay paired on
|
|
26945
|
-
* one client) and only operations started AFTER this call use the new key.
|
|
26946
|
-
* Only the ACTIVE address's engine is rebuilt — other tracked addresses re-key
|
|
26947
|
-
* on their next switch/build. No-op-safe if the engine can't be built (the
|
|
26948
|
-
* modules fall back to their legacy path, same as init).
|
|
26495
|
+
* Check if a legacy file is encrypted
|
|
26949
26496
|
*/
|
|
26950
|
-
|
|
26951
|
-
|
|
26952
|
-
|
|
26953
|
-
|
|
26954
|
-
|
|
26955
|
-
if (
|
|
26956
|
-
|
|
26957
|
-
|
|
26497
|
+
static isLegacyFileEncrypted(fileName, content) {
|
|
26498
|
+
const fileType = _Sphere.detectLegacyFileType(fileName, content);
|
|
26499
|
+
if (fileType === "txt") {
|
|
26500
|
+
return isTextWalletEncrypted(content);
|
|
26501
|
+
}
|
|
26502
|
+
if (fileType === "json") {
|
|
26503
|
+
try {
|
|
26504
|
+
const data = JSON.parse(content);
|
|
26505
|
+
return !!data.encrypted;
|
|
26506
|
+
} catch {
|
|
26507
|
+
return false;
|
|
26508
|
+
}
|
|
26509
|
+
}
|
|
26510
|
+
return false;
|
|
26958
26511
|
}
|
|
26959
26512
|
/**
|
|
26960
26513
|
* Check if wallet has BIP32 master key for HD derivation
|
|
@@ -27226,224 +26779,6 @@ var Sphere = class _Sphere {
|
|
|
27226
26779
|
};
|
|
27227
26780
|
}
|
|
27228
26781
|
}
|
|
27229
|
-
/**
|
|
27230
|
-
* Import wallet from legacy file (.dat, .txt, or mnemonic text)
|
|
27231
|
-
*
|
|
27232
|
-
* Supports:
|
|
27233
|
-
* - Bitcoin Core wallet.dat files (SQLite format, encrypted or unencrypted)
|
|
27234
|
-
* - Text backup files (UNICITY WALLET DETAILS format)
|
|
27235
|
-
* - Plain mnemonic text (12 or 24 words)
|
|
27236
|
-
*
|
|
27237
|
-
* @returns Object with success status, created Sphere instance, and optionally recovered mnemonic
|
|
27238
|
-
*
|
|
27239
|
-
* @example
|
|
27240
|
-
* ```ts
|
|
27241
|
-
* // Import from .dat file
|
|
27242
|
-
* const fileBuffer = await file.arrayBuffer();
|
|
27243
|
-
* const result = await Sphere.importFromLegacyFile({
|
|
27244
|
-
* fileContent: new Uint8Array(fileBuffer),
|
|
27245
|
-
* fileName: 'wallet.dat',
|
|
27246
|
-
* password: 'wallet-password', // if encrypted
|
|
27247
|
-
* storage, transport, oracle,
|
|
27248
|
-
* });
|
|
27249
|
-
*
|
|
27250
|
-
* // Import from .txt file
|
|
27251
|
-
* const textContent = await file.text();
|
|
27252
|
-
* const result = await Sphere.importFromLegacyFile({
|
|
27253
|
-
* fileContent: textContent,
|
|
27254
|
-
* fileName: 'backup.txt',
|
|
27255
|
-
* storage, transport, oracle,
|
|
27256
|
-
* });
|
|
27257
|
-
* ```
|
|
27258
|
-
*/
|
|
27259
|
-
static async importFromLegacyFile(options) {
|
|
27260
|
-
const { fileContent, fileName, password, onDecryptProgress, ...baseOptions } = options;
|
|
27261
|
-
const fileType = _Sphere.detectLegacyFileType(fileName, fileContent);
|
|
27262
|
-
if (fileType === "unknown") {
|
|
27263
|
-
return { success: false, error: "Unknown file format" };
|
|
27264
|
-
}
|
|
27265
|
-
if (fileType === "mnemonic") {
|
|
27266
|
-
const mnemonic = fileContent.trim().toLowerCase().split(/\s+/).join(" ");
|
|
27267
|
-
if (!_Sphere.validateMnemonic(mnemonic)) {
|
|
27268
|
-
return { success: false, error: "Invalid mnemonic phrase" };
|
|
27269
|
-
}
|
|
27270
|
-
const sphere = await _Sphere.import({ ...baseOptions, mnemonic });
|
|
27271
|
-
return { success: true, sphere, mnemonic };
|
|
27272
|
-
}
|
|
27273
|
-
if (fileType === "dat") {
|
|
27274
|
-
const data = fileContent instanceof Uint8Array ? fileContent : new TextEncoder().encode(fileContent);
|
|
27275
|
-
let parseResult;
|
|
27276
|
-
if (password) {
|
|
27277
|
-
parseResult = await parseAndDecryptWalletDat(data, password, onDecryptProgress);
|
|
27278
|
-
} else {
|
|
27279
|
-
parseResult = parseWalletDat(data);
|
|
27280
|
-
}
|
|
27281
|
-
if (parseResult.needsPassword && !password) {
|
|
27282
|
-
return { success: false, needsPassword: true, error: "Password required for encrypted wallet" };
|
|
27283
|
-
}
|
|
27284
|
-
if (!parseResult.success || !parseResult.data) {
|
|
27285
|
-
return { success: false, error: parseResult.error };
|
|
27286
|
-
}
|
|
27287
|
-
const { masterKey, chainCode, descriptorPath, derivationMode } = parseResult.data;
|
|
27288
|
-
const basePath = descriptorPath ? `m/${descriptorPath}` : DEFAULT_BASE_PATH;
|
|
27289
|
-
const sphere = await _Sphere.import({
|
|
27290
|
-
...baseOptions,
|
|
27291
|
-
masterKey,
|
|
27292
|
-
chainCode,
|
|
27293
|
-
basePath,
|
|
27294
|
-
derivationMode: derivationMode || (chainCode ? "bip32" : "wif_hmac")
|
|
27295
|
-
});
|
|
27296
|
-
return { success: true, sphere };
|
|
27297
|
-
}
|
|
27298
|
-
if (fileType === "txt") {
|
|
27299
|
-
const content = typeof fileContent === "string" ? fileContent : new TextDecoder().decode(fileContent);
|
|
27300
|
-
let parseResult;
|
|
27301
|
-
if (password) {
|
|
27302
|
-
parseResult = parseAndDecryptWalletText(content, password);
|
|
27303
|
-
} else if (isTextWalletEncrypted(content)) {
|
|
27304
|
-
return { success: false, needsPassword: true, error: "Password required for encrypted wallet" };
|
|
27305
|
-
} else {
|
|
27306
|
-
parseResult = parseWalletText(content);
|
|
27307
|
-
}
|
|
27308
|
-
if (parseResult.needsPassword && !password) {
|
|
27309
|
-
return { success: false, needsPassword: true, error: "Password required for encrypted wallet" };
|
|
27310
|
-
}
|
|
27311
|
-
if (!parseResult.success || !parseResult.data) {
|
|
27312
|
-
return { success: false, error: parseResult.error };
|
|
27313
|
-
}
|
|
27314
|
-
const { masterKey, chainCode, descriptorPath, derivationMode } = parseResult.data;
|
|
27315
|
-
const basePath = descriptorPath ? `m/${descriptorPath}` : DEFAULT_BASE_PATH;
|
|
27316
|
-
const sphere = await _Sphere.import({
|
|
27317
|
-
...baseOptions,
|
|
27318
|
-
masterKey,
|
|
27319
|
-
chainCode,
|
|
27320
|
-
basePath,
|
|
27321
|
-
derivationMode: derivationMode || (chainCode ? "bip32" : "wif_hmac")
|
|
27322
|
-
});
|
|
27323
|
-
return { success: true, sphere };
|
|
27324
|
-
}
|
|
27325
|
-
if (fileType === "json") {
|
|
27326
|
-
const content = typeof fileContent === "string" ? fileContent : new TextDecoder().decode(fileContent);
|
|
27327
|
-
let parsed;
|
|
27328
|
-
try {
|
|
27329
|
-
parsed = JSON.parse(content);
|
|
27330
|
-
} catch {
|
|
27331
|
-
return { success: false, error: "Invalid JSON file" };
|
|
27332
|
-
}
|
|
27333
|
-
if (parsed.type === "sphere-wallet") {
|
|
27334
|
-
const result = await _Sphere.importFromJSON({
|
|
27335
|
-
...baseOptions,
|
|
27336
|
-
jsonContent: content,
|
|
27337
|
-
password
|
|
27338
|
-
});
|
|
27339
|
-
if (result.success) {
|
|
27340
|
-
const sphere2 = _Sphere.getInstance();
|
|
27341
|
-
return { success: true, sphere: sphere2, mnemonic: result.mnemonic };
|
|
27342
|
-
}
|
|
27343
|
-
if (!password && result.error?.includes("Password required")) {
|
|
27344
|
-
return { success: false, needsPassword: true, error: result.error };
|
|
27345
|
-
}
|
|
27346
|
-
return { success: false, error: result.error };
|
|
27347
|
-
}
|
|
27348
|
-
let masterKey;
|
|
27349
|
-
let mnemonic;
|
|
27350
|
-
if (parsed.encrypted && typeof parsed.encrypted === "object") {
|
|
27351
|
-
if (!password) {
|
|
27352
|
-
return { success: false, needsPassword: true, error: "Password required for encrypted wallet" };
|
|
27353
|
-
}
|
|
27354
|
-
const enc = parsed.encrypted;
|
|
27355
|
-
if (!enc.salt || !enc.masterPrivateKey) {
|
|
27356
|
-
return { success: false, error: "Invalid encrypted wallet format" };
|
|
27357
|
-
}
|
|
27358
|
-
const decryptedKey = decryptWithSalt(enc.masterPrivateKey, password, enc.salt);
|
|
27359
|
-
if (!decryptedKey) {
|
|
27360
|
-
return { success: false, error: "Failed to decrypt - incorrect password?" };
|
|
27361
|
-
}
|
|
27362
|
-
masterKey = decryptedKey;
|
|
27363
|
-
if (enc.mnemonic) {
|
|
27364
|
-
mnemonic = decryptWithSalt(enc.mnemonic, password, enc.salt) ?? void 0;
|
|
27365
|
-
}
|
|
27366
|
-
} else {
|
|
27367
|
-
masterKey = parsed.masterPrivateKey;
|
|
27368
|
-
mnemonic = parsed.mnemonic;
|
|
27369
|
-
}
|
|
27370
|
-
if (!masterKey) {
|
|
27371
|
-
return { success: false, error: "No master key found in wallet JSON" };
|
|
27372
|
-
}
|
|
27373
|
-
const chainCode = parsed.chainCode;
|
|
27374
|
-
const descriptorPath = parsed.descriptorPath;
|
|
27375
|
-
const derivationMode = parsed.derivationMode;
|
|
27376
|
-
const isBIP32 = derivationMode === "bip32" || !!chainCode;
|
|
27377
|
-
const basePath = descriptorPath ? `m/${descriptorPath}` : isBIP32 ? "m/84'/1'/0'" : DEFAULT_BASE_PATH;
|
|
27378
|
-
if (mnemonic) {
|
|
27379
|
-
const sphere2 = await _Sphere.import({ ...baseOptions, mnemonic, basePath });
|
|
27380
|
-
return { success: true, sphere: sphere2, mnemonic };
|
|
27381
|
-
}
|
|
27382
|
-
const sphere = await _Sphere.import({
|
|
27383
|
-
...baseOptions,
|
|
27384
|
-
masterKey,
|
|
27385
|
-
chainCode,
|
|
27386
|
-
basePath,
|
|
27387
|
-
derivationMode: derivationMode || (chainCode ? "bip32" : "wif_hmac")
|
|
27388
|
-
});
|
|
27389
|
-
return { success: true, sphere };
|
|
27390
|
-
}
|
|
27391
|
-
return { success: false, error: "Unsupported file type" };
|
|
27392
|
-
}
|
|
27393
|
-
/**
|
|
27394
|
-
* Detect legacy file type from filename and content
|
|
27395
|
-
*/
|
|
27396
|
-
static detectLegacyFileType(fileName, content) {
|
|
27397
|
-
if (fileName.endsWith(".dat")) {
|
|
27398
|
-
return "dat";
|
|
27399
|
-
}
|
|
27400
|
-
const textContent = typeof content === "string" ? content : content.length < 1e3 ? new TextDecoder().decode(content) : "";
|
|
27401
|
-
if (fileName.endsWith(".json")) {
|
|
27402
|
-
return "json";
|
|
27403
|
-
}
|
|
27404
|
-
try {
|
|
27405
|
-
const trimmed = textContent.trim();
|
|
27406
|
-
if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
|
|
27407
|
-
JSON.parse(trimmed);
|
|
27408
|
-
return "json";
|
|
27409
|
-
}
|
|
27410
|
-
} catch {
|
|
27411
|
-
}
|
|
27412
|
-
const words = textContent.trim().split(/\s+/);
|
|
27413
|
-
if ((words.length === 12 || words.length === 24) && words.every((w) => /^[a-z]+$/.test(w.toLowerCase()))) {
|
|
27414
|
-
return "mnemonic";
|
|
27415
|
-
}
|
|
27416
|
-
if (isWalletTextFormat(textContent)) {
|
|
27417
|
-
return "txt";
|
|
27418
|
-
}
|
|
27419
|
-
if (content instanceof Uint8Array && isSQLiteDatabase(content)) {
|
|
27420
|
-
return "dat";
|
|
27421
|
-
}
|
|
27422
|
-
return "unknown";
|
|
27423
|
-
}
|
|
27424
|
-
/**
|
|
27425
|
-
* Check if a legacy file is encrypted
|
|
27426
|
-
*/
|
|
27427
|
-
static isLegacyFileEncrypted(fileName, content) {
|
|
27428
|
-
const fileType = _Sphere.detectLegacyFileType(fileName, content);
|
|
27429
|
-
if (fileType === "dat" && content instanceof Uint8Array) {
|
|
27430
|
-
return isWalletDatEncrypted(content);
|
|
27431
|
-
}
|
|
27432
|
-
if (fileType === "txt") {
|
|
27433
|
-
const textContent = typeof content === "string" ? content : new TextDecoder().decode(content);
|
|
27434
|
-
return isTextWalletEncrypted(textContent);
|
|
27435
|
-
}
|
|
27436
|
-
if (fileType === "json") {
|
|
27437
|
-
try {
|
|
27438
|
-
const textContent = typeof content === "string" ? content : new TextDecoder().decode(content);
|
|
27439
|
-
const data = JSON.parse(textContent);
|
|
27440
|
-
return !!data.encrypted;
|
|
27441
|
-
} catch {
|
|
27442
|
-
return false;
|
|
27443
|
-
}
|
|
27444
|
-
}
|
|
27445
|
-
return false;
|
|
27446
|
-
}
|
|
27447
26782
|
/**
|
|
27448
26783
|
* Get the current active address index
|
|
27449
26784
|
*
|
|
@@ -29354,116 +28689,6 @@ var CurrencyUtils = {
|
|
|
29354
28689
|
format: formatAmount
|
|
29355
28690
|
};
|
|
29356
28691
|
|
|
29357
|
-
// core/bech32.ts
|
|
29358
|
-
var CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l";
|
|
29359
|
-
var GENERATOR = [996825010, 642813549, 513874426, 1027748829, 705979059];
|
|
29360
|
-
function convertBits(data, fromBits, toBits, pad) {
|
|
29361
|
-
let acc = 0;
|
|
29362
|
-
let bits = 0;
|
|
29363
|
-
const ret = [];
|
|
29364
|
-
const maxv = (1 << toBits) - 1;
|
|
29365
|
-
for (let i = 0; i < data.length; i++) {
|
|
29366
|
-
const value = data[i];
|
|
29367
|
-
if (value < 0 || value >> fromBits !== 0) return null;
|
|
29368
|
-
acc = acc << fromBits | value;
|
|
29369
|
-
bits += fromBits;
|
|
29370
|
-
while (bits >= toBits) {
|
|
29371
|
-
bits -= toBits;
|
|
29372
|
-
ret.push(acc >> bits & maxv);
|
|
29373
|
-
}
|
|
29374
|
-
}
|
|
29375
|
-
if (pad) {
|
|
29376
|
-
if (bits > 0) {
|
|
29377
|
-
ret.push(acc << toBits - bits & maxv);
|
|
29378
|
-
}
|
|
29379
|
-
} else if (bits >= fromBits || acc << toBits - bits & maxv) {
|
|
29380
|
-
return null;
|
|
29381
|
-
}
|
|
29382
|
-
return ret;
|
|
29383
|
-
}
|
|
29384
|
-
function hrpExpand(hrp) {
|
|
29385
|
-
const ret = [];
|
|
29386
|
-
for (let i = 0; i < hrp.length; i++) ret.push(hrp.charCodeAt(i) >> 5);
|
|
29387
|
-
ret.push(0);
|
|
29388
|
-
for (let i = 0; i < hrp.length; i++) ret.push(hrp.charCodeAt(i) & 31);
|
|
29389
|
-
return ret;
|
|
29390
|
-
}
|
|
29391
|
-
function bech32Polymod(values) {
|
|
29392
|
-
let chk = 1;
|
|
29393
|
-
for (let p = 0; p < values.length; p++) {
|
|
29394
|
-
const top = chk >> 25;
|
|
29395
|
-
chk = (chk & 33554431) << 5 ^ values[p];
|
|
29396
|
-
for (let i = 0; i < 5; i++) {
|
|
29397
|
-
if (top >> i & 1) chk ^= GENERATOR[i];
|
|
29398
|
-
}
|
|
29399
|
-
}
|
|
29400
|
-
return chk;
|
|
29401
|
-
}
|
|
29402
|
-
function bech32Checksum(hrp, data) {
|
|
29403
|
-
const values = hrpExpand(hrp).concat(data).concat([0, 0, 0, 0, 0, 0]);
|
|
29404
|
-
const mod2 = bech32Polymod(values) ^ 1;
|
|
29405
|
-
const ret = [];
|
|
29406
|
-
for (let p = 0; p < 6; p++) {
|
|
29407
|
-
ret.push(mod2 >> 5 * (5 - p) & 31);
|
|
29408
|
-
}
|
|
29409
|
-
return ret;
|
|
29410
|
-
}
|
|
29411
|
-
function encodeBech32(hrp, version, program) {
|
|
29412
|
-
if (version < 0 || version > 16) {
|
|
29413
|
-
throw new SphereError("Invalid witness version", "VALIDATION_ERROR");
|
|
29414
|
-
}
|
|
29415
|
-
const converted = convertBits(Array.from(program), 8, 5, true);
|
|
29416
|
-
if (!converted) {
|
|
29417
|
-
throw new SphereError("Failed to convert bits", "VALIDATION_ERROR");
|
|
29418
|
-
}
|
|
29419
|
-
const data = [version].concat(converted);
|
|
29420
|
-
const checksum = bech32Checksum(hrp, data);
|
|
29421
|
-
const combined = data.concat(checksum);
|
|
29422
|
-
let out = hrp + "1";
|
|
29423
|
-
for (let i = 0; i < combined.length; i++) {
|
|
29424
|
-
out += CHARSET[combined[i]];
|
|
29425
|
-
}
|
|
29426
|
-
return out;
|
|
29427
|
-
}
|
|
29428
|
-
function decodeBech32(addr) {
|
|
29429
|
-
addr = addr.toLowerCase();
|
|
29430
|
-
const pos = addr.lastIndexOf("1");
|
|
29431
|
-
if (pos < 1) return null;
|
|
29432
|
-
const hrp = addr.substring(0, pos);
|
|
29433
|
-
const dataStr = addr.substring(pos + 1);
|
|
29434
|
-
const data = [];
|
|
29435
|
-
for (let i = 0; i < dataStr.length; i++) {
|
|
29436
|
-
const val = CHARSET.indexOf(dataStr[i]);
|
|
29437
|
-
if (val === -1) return null;
|
|
29438
|
-
data.push(val);
|
|
29439
|
-
}
|
|
29440
|
-
const checksum = bech32Checksum(hrp, data.slice(0, -6));
|
|
29441
|
-
for (let i = 0; i < 6; i++) {
|
|
29442
|
-
if (checksum[i] !== data[data.length - 6 + i]) {
|
|
29443
|
-
return null;
|
|
29444
|
-
}
|
|
29445
|
-
}
|
|
29446
|
-
const version = data[0];
|
|
29447
|
-
const program = convertBits(data.slice(1, -6), 5, 8, false);
|
|
29448
|
-
if (!program) return null;
|
|
29449
|
-
return {
|
|
29450
|
-
hrp,
|
|
29451
|
-
witnessVersion: version,
|
|
29452
|
-
data: Uint8Array.from(program)
|
|
29453
|
-
};
|
|
29454
|
-
}
|
|
29455
|
-
function createAddress(hrp, pubkeyHash) {
|
|
29456
|
-
const hashBytes = typeof pubkeyHash === "string" ? Uint8Array.from(Buffer.from(pubkeyHash, "hex")) : pubkeyHash;
|
|
29457
|
-
return encodeBech32(hrp, 1, hashBytes);
|
|
29458
|
-
}
|
|
29459
|
-
function isValidBech32(addr) {
|
|
29460
|
-
return decodeBech32(addr) !== null;
|
|
29461
|
-
}
|
|
29462
|
-
function getAddressHrp(addr) {
|
|
29463
|
-
const result = decodeBech32(addr);
|
|
29464
|
-
return result?.hrp ?? null;
|
|
29465
|
-
}
|
|
29466
|
-
|
|
29467
28692
|
// core/network-health.ts
|
|
29468
28693
|
var DEFAULT_TIMEOUT_MS = 5e3;
|
|
29469
28694
|
async function checkNetworkHealth(network = "testnet", options) {
|
|
@@ -29646,7 +28871,6 @@ async function runCustomCheck(name, checkFn, timeoutMs) {
|
|
|
29646
28871
|
}
|
|
29647
28872
|
}
|
|
29648
28873
|
export {
|
|
29649
|
-
CHARSET,
|
|
29650
28874
|
CurrencyUtils,
|
|
29651
28875
|
DEFAULT_DERIVATION_PATH2 as DEFAULT_DERIVATION_PATH,
|
|
29652
28876
|
DEFAULT_TOKEN_DECIMALS,
|
|
@@ -29664,11 +28888,8 @@ export {
|
|
|
29664
28888
|
base58Encode,
|
|
29665
28889
|
bytesToHex4 as bytesToHex,
|
|
29666
28890
|
checkNetworkHealth,
|
|
29667
|
-
convertBits,
|
|
29668
|
-
createAddress,
|
|
29669
28891
|
createKeyPair,
|
|
29670
28892
|
createSphere,
|
|
29671
|
-
decodeBech32,
|
|
29672
28893
|
decrypt,
|
|
29673
28894
|
decryptDeliveryBundle,
|
|
29674
28895
|
decryptField,
|
|
@@ -29685,7 +28906,6 @@ export {
|
|
|
29685
28906
|
deserializeEncrypted,
|
|
29686
28907
|
discoverAddressesImpl,
|
|
29687
28908
|
doubleSha256,
|
|
29688
|
-
encodeBech32,
|
|
29689
28909
|
encrypt,
|
|
29690
28910
|
encryptDeliveryBundle,
|
|
29691
28911
|
encryptField,
|
|
@@ -29701,7 +28921,6 @@ export {
|
|
|
29701
28921
|
generateMasterKey,
|
|
29702
28922
|
generateMnemonic2 as generateMnemonic,
|
|
29703
28923
|
generateRandomKey,
|
|
29704
|
-
getAddressHrp,
|
|
29705
28924
|
getPublicKey,
|
|
29706
28925
|
getSphere,
|
|
29707
28926
|
hash160,
|
|
@@ -29714,7 +28933,6 @@ export {
|
|
|
29714
28933
|
isEncryptedData,
|
|
29715
28934
|
isPossiblyCommittedSendOutcome,
|
|
29716
28935
|
isSphereError,
|
|
29717
|
-
isValidBech32,
|
|
29718
28936
|
isValidNametag2 as isValidNametag,
|
|
29719
28937
|
isValidPrivateKey,
|
|
29720
28938
|
loadSphere,
|