@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.
@@ -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("_") && !key.startsWith(ARCHIVED_PREFIX) && !key.startsWith(FORKED_PREFIX) && !RESERVED_KEYS.includes(key);
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
- function tokenIdFromArchivedKey(key) {
8855
- return key.startsWith(ARCHIVED_PREFIX) ? key.substring(ARCHIVED_PREFIX.length) : key;
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 archivedKeyFromTokenId(tokenId) {
8858
- return `${ARCHIVED_PREFIX}${tokenId}`;
8851
+ function v2TokenId(token) {
8852
+ return token.id.startsWith("v2_") ? token.id.slice(3) : token.id;
8859
8853
  }
8860
- function forkedKeyFromTokenIdAndState(tokenId, stateHash) {
8861
- return `${FORKED_PREFIX}${tokenId}_${stateHash}`;
8854
+ function isV2TokenEntry(entry) {
8855
+ return typeof entry === "object" && entry !== null && !("genesis" in entry) && isV2TokenBlob(entry.sdkData);
8862
8856
  }
8863
- function parseForkedKey(key) {
8864
- if (!key.startsWith(FORKED_PREFIX)) return null;
8865
- const remainder = key.substring(FORKED_PREFIX.length);
8866
- const underscoreIndex = remainder.indexOf("_");
8867
- if (underscoreIndex === -1 || underscoreIndex < 64) return null;
8868
- return {
8869
- tokenId: remainder.substring(0, underscoreIndex),
8870
- stateHash: remainder.substring(underscoreIndex + 1)
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 = getCurrentStateHash(txf) || "";
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 !== 1 && payload.v !== 2 || !Array.isArray(payload.direct)) {
13365
- throw new SphereError("Unknown intent payload shape", "VALIDATION_ERROR");
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 recipientChainPubkey = hexToBytes4(payload.recipient);
13509
- const deliverBlob = async (tokenBlobHex, opIndex) => {
13510
- await this.savePendingV2Delivery({
13511
- transferId,
13512
- recipientPubkey: payload.recipient,
13513
- tokenBlob: tokenBlobHex,
13514
- memo: payload.memo,
13515
- opIndex,
13516
- createdAt: Date.now()
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
- await deliverBlob(existing.tokenBlob, opIndex);
13283
+ blobs.push(existing.tokenBlob);
13537
13284
  spent.push(genesisId);
13538
- } else {
13539
- const source = await engine.decodeToken(await provider.getToken(genesisId));
13540
- try {
13541
- const finished = await engine.transfer(
13542
- { token: source, recipientPubkey: recipientChainPubkey },
13543
- // (transferId, opIndex) pairing replayed from the intent's persisted order (§8.1)
13544
- { signal: timeoutSignal(SEND_ENGINE_OP_TIMEOUT_MS), transferId, opIndex }
13545
- );
13546
- await deliverBlob(bytesToHex4(encodeTokenBlob(engine.encodeToken(finished))), opIndex);
13547
- spent.push(genesisId);
13548
- } catch (err) {
13549
- if (!(err instanceof TransferConflictError)) throw err;
13550
- conflicted = true;
13551
- firstConflict ??= err;
13552
- undeliveredAmount += engine.balanceOf(source, payload.coinId);
13553
- logger.warn("Payments", `Resume op ${opIndex} lost its source to a concurrent transfer \u2014 not delivered (\xA78.1/#4):`, err);
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
- if (checkpointStore === void 0 && existingSplit) {
13564
- await deliverBlob(existingSplit.tokenBlob, splitOpIndex);
13565
- splitDelivered = true;
13566
- } else {
13567
- try {
13568
- const blob = await provider.getToken(payload.split.tokenId);
13569
- const source = await engine.decodeToken(blob);
13570
- const selfChainPubkey = hexToBytes4(this.deps.identity.chainPubkey);
13571
- const { outputs } = await engine.split(
13572
- {
13573
- token: source,
13574
- outputs: [
13575
- {
13576
- recipientPubkey: recipientChainPubkey,
13577
- coinId: payload.coinId,
13578
- amount: BigInt(payload.split.splitAmount)
13579
- },
13580
- {
13581
- recipientPubkey: selfChainPubkey,
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
- if (err instanceof TransferConflictError) {
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
- logger.warn("Payments", `Resume split: source lost to a concurrent transfer \u2014 not delivered (\xA78.1/#4):`, err);
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
- const decoded = await engine.decodeToken(decodeTokenBlob(hexToBytes4(local.sdkData)));
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
- if (conflicted) {
13659
- const remaining = undeliveredAmount;
13660
- const delivered = BigInt(payload.amount) - remaining;
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: payload.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: payload.direct[0] ? `v2_${payload.direct[0]}` : void 0
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
- const tokenStats = Array.from(this.tokens.values()).map((t) => {
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: true,
25994
- data: parsedData
25415
+ success: false,
25416
+ error: "Could not find master private key in backup file"
25995
25417
  };
25996
25418
  }
25997
- return {
25998
- success: false,
25999
- error: "No valid private keys found in wallet.dat file"
26000
- };
26001
- }
26002
- if (cmasterKeys.length === 0) {
26003
- return {
26004
- success: false,
26005
- error: "Encrypted wallet but no CMasterKey structures found"
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: false,
26024
- error: "Invalid wallet.dat file - not an SQLite database"
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: "Encrypted wallet but no CMasterKey structures found"
25439
+ error: `Failed to parse wallet text: ${e instanceof Error ? e.message : String(e)}`
26036
25440
  };
26037
25441
  }
26038
- let masterKeyHex = null;
26039
- for (const cmk of cmasterKeys) {
26040
- try {
26041
- masterKeyHex = await decryptCMasterKey(cmk, password, onProgress);
26042
- if (masterKeyHex && masterKeyHex.length === 64) {
26043
- break;
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
- if (!masterKeyHex || masterKeyHex.length !== 64) {
26050
- return {
26051
- success: false,
26052
- error: "Master key decryption failed - incorrect password"
26053
- };
26054
- }
26055
- const { descriptorId, xpubString, descriptorPath } = findWpkhDescriptor(data);
26056
- let chainCode = null;
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
- } else {
26064
- chainCode = findMasterChainCode(data);
26065
- }
26066
- if (!descriptorId) {
26067
- return {
26068
- success: false,
26069
- error: "Could not find native SegWit receive descriptor"
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: false,
26076
- error: "Could not find encrypted private key for descriptor"
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: "Could not decrypt BIP32 master private key"
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
- * Apply a new gateway API key to the LIVE oracle + token engine WITHOUT a full
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
- async setOracleApiKey(apiKey) {
26951
- this._oracle.setApiKey?.(apiKey);
26952
- const active = this._addressModules.get(this._currentAddressIndex);
26953
- const replaced = active?.tokenEngine ?? this._tokenEngine;
26954
- this._tokenEngine = await this.buildTokenEngine();
26955
- if (active) active.tokenEngine = this._tokenEngine;
26956
- this._payments.setTokenEngine(this._tokenEngine);
26957
- if (replaced && replaced !== this._tokenEngine) replaced.dispose?.();
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,