@buildonspark/spark-sdk 0.3.9 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/android/build.gradle +1 -1
  3. package/android/src/main/java/uniffi/uniffi/spark_frost/spark_frost.kt +1361 -1367
  4. package/android/src/main/jniLibs/arm64-v8a/libuniffi_spark_frost.so +0 -0
  5. package/android/src/main/jniLibs/armeabi-v7a/libuniffi_spark_frost.so +0 -0
  6. package/android/src/main/jniLibs/x86/libuniffi_spark_frost.so +0 -0
  7. package/android/src/main/jniLibs/x86_64/libuniffi_spark_frost.so +0 -0
  8. package/dist/bare/index.cjs +322 -142
  9. package/dist/bare/index.d.cts +13 -3
  10. package/dist/bare/index.d.ts +13 -3
  11. package/dist/bare/index.js +321 -142
  12. package/dist/{chunk-S55NZT4P.js → chunk-27ILUWDJ.js} +1 -1
  13. package/dist/{chunk-O4C4HGQL.js → chunk-G3LHXHF3.js} +313 -133
  14. package/dist/{chunk-WRE2T22S.js → chunk-LOXWCMZL.js} +1 -1
  15. package/dist/{chunk-MFCM6GUD.js → chunk-WICAF6BS.js} +1 -1
  16. package/dist/debug.cjs +322 -143
  17. package/dist/debug.d.cts +5 -4
  18. package/dist/debug.d.ts +5 -4
  19. package/dist/debug.js +2 -2
  20. package/dist/index.cjs +323 -143
  21. package/dist/index.d.cts +4 -4
  22. package/dist/index.d.ts +4 -4
  23. package/dist/index.js +5 -3
  24. package/dist/index.node.cjs +323 -143
  25. package/dist/index.node.d.cts +4 -4
  26. package/dist/index.node.d.ts +4 -4
  27. package/dist/index.node.js +4 -2
  28. package/dist/{logging-DDeMLsVN.d.ts → logging-BNGm6dBp.d.ts} +3 -2
  29. package/dist/{logging-CXhvuqJJ.d.cts → logging-D3IfXfHG.d.cts} +3 -2
  30. package/dist/native/index.react-native.cjs +466 -145
  31. package/dist/native/index.react-native.d.cts +21 -3
  32. package/dist/native/index.react-native.d.ts +21 -3
  33. package/dist/native/index.react-native.js +462 -144
  34. package/dist/{spark-wallet.browser-Cz8c4kOW.d.ts → spark-wallet.browser-B2rGwjuM.d.ts} +1 -1
  35. package/dist/{spark-wallet.browser-CbYo8A_U.d.cts → spark-wallet.browser-Ck9No4Ks.d.cts} +1 -1
  36. package/dist/{spark-wallet.node-CmIvxtcC.d.ts → spark-wallet.node-BqmKsGPs.d.ts} +1 -1
  37. package/dist/{spark-wallet.node-4WQgWwB2.d.cts → spark-wallet.node-C2TIkyt4.d.cts} +1 -1
  38. package/dist/tests/test-utils.cjs +321 -143
  39. package/dist/tests/test-utils.d.cts +16 -2
  40. package/dist/tests/test-utils.d.ts +16 -2
  41. package/dist/tests/test-utils.js +4 -4
  42. package/dist/{token-transactions-CV8QD3I7.d.cts → token-transactions-Db8mkjnU.d.cts} +1 -1
  43. package/dist/{token-transactions-Bu023ztN.d.ts → token-transactions-DoMcrxXQ.d.ts} +1 -1
  44. package/dist/{wallet-config-Bmk2eAn8.d.ts → wallet-config-Bg3kWltL.d.ts} +11 -2
  45. package/dist/{wallet-config-DQw5llqA.d.cts → wallet-config-CuZKNo9S.d.cts} +11 -2
  46. package/ios/spark_frostFFI.xcframework/ios-arm64/SparkFrost +0 -0
  47. package/ios/spark_frostFFI.xcframework/ios-arm64/spark_frostFFI.framework/spark_frostFFI +0 -0
  48. package/ios/spark_frostFFI.xcframework/ios-arm64_x86_64-simulator/SparkFrost +0 -0
  49. package/ios/spark_frostFFI.xcframework/ios-arm64_x86_64-simulator/spark_frostFFI.framework/spark_frostFFI +0 -0
  50. package/ios/spark_frostFFI.xcframework/macos-arm64_x86_64/spark_frostFFI.framework/spark_frostFFI +0 -0
  51. package/package.json +1 -1
  52. package/src/index.react-native.ts +8 -2
  53. package/src/services/config.ts +5 -0
  54. package/src/services/wallet-config.ts +10 -0
  55. package/src/services/xhr-transport.ts +13 -3
  56. package/src/signer/signer.react-native.ts +73 -1
  57. package/src/spark-wallet/spark-wallet.ts +98 -76
  58. package/src/tests/integration/lightning.test.ts +0 -28
  59. package/src/tests/integration/static_deposit.test.ts +4 -8
  60. package/src/tests/integration/unilateral-exit.test.ts +117 -0
  61. package/src/tests/optimize.test.ts +31 -1
  62. package/src/tests/utils/signing.ts +33 -0
  63. package/src/tests/utils/test-faucet.ts +61 -0
  64. package/src/utils/optimize.ts +42 -0
  65. package/src/utils/unilateral-exit.ts +1 -40
@@ -1315,6 +1315,7 @@ __export(index_exports, {
1315
1315
  getTxFromRawTxHex: () => getTxFromRawTxHex,
1316
1316
  getTxId: () => getTxId,
1317
1317
  getTxIdNoReverse: () => getTxIdNoReverse,
1318
+ hash160: () => hash160,
1318
1319
  isEphemeralAnchorOutput: () => isEphemeralAnchorOutput,
1319
1320
  isLegacySparkAddress: () => isLegacySparkAddress,
1320
1321
  isSafeForNumber: () => isSafeForNumber,
@@ -19555,7 +19556,11 @@ var BASE_CONFIG = {
19555
19556
  console: {
19556
19557
  otel: false
19557
19558
  },
19558
- events: {}
19559
+ events: {},
19560
+ optimizationOptions: {
19561
+ auto: true,
19562
+ multiplicity: 0
19563
+ }
19559
19564
  };
19560
19565
  var LOCAL_WALLET_CONFIG = {
19561
19566
  ...BASE_CONFIG,
@@ -19766,6 +19771,9 @@ var WalletConfigService = class {
19766
19771
  getEvents() {
19767
19772
  return this.config.events;
19768
19773
  }
19774
+ getOptimizationOptions() {
19775
+ return this.config.optimizationOptions;
19776
+ }
19769
19777
  };
19770
19778
 
19771
19779
  // src/services/coop-exit.ts
@@ -28454,7 +28462,7 @@ var isWebExtension = (
28454
28462
  "chrome" in globalThis && globalThis.chrome.runtime?.id
28455
28463
  );
28456
28464
  var userAgent = "navigator" in globalThis ? globalThis.navigator.userAgent || "unknown-user-agent" : void 0;
28457
- var packageVersion = true ? "0.3.9" : "unknown";
28465
+ var packageVersion = true ? "0.4.0" : "unknown";
28458
28466
  var baseEnvStr = "unknown";
28459
28467
  if (isBun) {
28460
28468
  const bunVersion = "version" in globalThis.Bun ? globalThis.Bun.version : "unknown-version";
@@ -29915,6 +29923,14 @@ var BitcoinFaucet = class _BitcoinFaucet {
29915
29923
  async mineBlocks(numBlocks) {
29916
29924
  return await this.generateToAddress(numBlocks, this.miningAddress);
29917
29925
  }
29926
+ async mineBlocksAndWaitForMiningToComplete(numBlocks) {
29927
+ const startBlock = await this.getBlockCount();
29928
+ await this.mineBlocks(numBlocks);
29929
+ await this.waitForBlocksMined({
29930
+ startBlock,
29931
+ expectedIncrease: numBlocks
29932
+ });
29933
+ }
29918
29934
  async call(method, params) {
29919
29935
  try {
29920
29936
  const { fetch, Headers: Headers3 } = getFetch();
@@ -29967,15 +29983,50 @@ var BitcoinFaucet = class _BitcoinFaucet {
29967
29983
  async getBlock(blockHash) {
29968
29984
  return await this.call("getblock", [blockHash, 2]);
29969
29985
  }
29986
+ async getBlockCount() {
29987
+ return await this.call("getblockcount", []);
29988
+ }
29989
+ async waitForBlocksMined({
29990
+ startBlock,
29991
+ expectedIncrease,
29992
+ timeoutMs = 3e4,
29993
+ intervalMs = 5e3
29994
+ }) {
29995
+ const deadline = Date.now() + timeoutMs;
29996
+ await new Promise((r) => setTimeout(r, intervalMs));
29997
+ const start = startBlock;
29998
+ const target = start + expectedIncrease;
29999
+ while (Date.now() < deadline) {
30000
+ const currentBlock = await this.getBlockCount();
30001
+ if (currentBlock >= target) return currentBlock;
30002
+ await new Promise((r) => setTimeout(r, intervalMs));
30003
+ }
30004
+ throw new Error(
30005
+ `Timed out waiting for ${expectedIncrease} blocks (target height ${target})`
30006
+ );
30007
+ }
29970
30008
  async broadcastTx(txHex) {
29971
30009
  let response = await this.call("sendrawtransaction", [txHex, 0]);
29972
30010
  return response;
29973
30011
  }
30012
+ async submitPackage(txHexs) {
30013
+ let response = await this.call("submitpackage", [txHexs]);
30014
+ return response;
30015
+ }
29974
30016
  async getNewAddress() {
29975
30017
  const key = import_secp256k113.secp256k1.utils.randomPrivateKey();
29976
30018
  const pubKey = import_secp256k113.secp256k1.getPublicKey(key);
29977
30019
  return getP2TRAddressFromPublicKey(pubKey, 4 /* LOCAL */);
29978
30020
  }
30021
+ async getNewExternalWallet() {
30022
+ const key = import_secp256k113.secp256k1.utils.randomPrivateKey();
30023
+ const pubKey = import_secp256k113.secp256k1.getPublicKey(key);
30024
+ return {
30025
+ address: getP2TRAddressFromPublicKey(pubKey, 4 /* LOCAL */),
30026
+ key,
30027
+ pubKey
30028
+ };
30029
+ }
29979
30030
  async sendToAddress(address, amount, blocksToGenerate = 1) {
29980
30031
  const coin = await this.fund();
29981
30032
  if (!coin) {
@@ -30076,75 +30127,6 @@ function chunkArray(arr, size) {
30076
30127
  return chunks;
30077
30128
  }
30078
30129
 
30079
- // src/utils/optimize.ts
30080
- var DENOMINATIONS = Array.from({ length: 28 }, (_, i) => 2 ** i);
30081
- function assert(condition, message) {
30082
- if (!condition) {
30083
- throw new InternalValidationError(message || "Assertion failed");
30084
- }
30085
- }
30086
- function sum(arr) {
30087
- return arr.reduce((a, b) => a + b, 0);
30088
- }
30089
- function sorted(arr) {
30090
- return [...arr].sort((a, b) => a - b);
30091
- }
30092
- function equals(a, b) {
30093
- return a.length === b.length && a.every((val, index) => val === b[index]);
30094
- }
30095
- function greedyLeaves(amount) {
30096
- const leaves = [];
30097
- let remaining = amount;
30098
- for (let i = DENOMINATIONS.length - 1; i >= 0; i--) {
30099
- const leaf = DENOMINATIONS[i];
30100
- if (typeof leaf === "number" && leaf > 0) {
30101
- while (remaining >= leaf) {
30102
- remaining -= leaf;
30103
- leaves.push(leaf);
30104
- }
30105
- }
30106
- }
30107
- assert(sum(leaves) === amount, "greedy_leaves: sum mismatch");
30108
- return sorted(leaves);
30109
- }
30110
- var Swap = class {
30111
- inLeaves;
30112
- outLeaves;
30113
- constructor(inLeaves, outLeaves) {
30114
- this.inLeaves = [...inLeaves];
30115
- this.outLeaves = [...outLeaves];
30116
- assert(
30117
- sum(this.inLeaves) === sum(this.outLeaves),
30118
- "Swap in/out leaves must sum to same value for swap: " + this.toString()
30119
- );
30120
- }
30121
- toString() {
30122
- return `Swap(in=${JSON.stringify(this.inLeaves)}, out=${JSON.stringify(this.outLeaves)})`;
30123
- }
30124
- };
30125
- function maximizeUnilateralExit(inputLeaves, maxLeavesPerSwap = 64) {
30126
- const swaps = [];
30127
- let batch = [];
30128
- let leaves = sorted(inputLeaves);
30129
- while (leaves.length > 0) {
30130
- batch.push(leaves.shift());
30131
- const target = greedyLeaves(sum(batch));
30132
- if (batch.length >= maxLeavesPerSwap || target.length >= maxLeavesPerSwap) {
30133
- if (!equals(target, batch)) {
30134
- swaps.push(new Swap([...batch], target));
30135
- }
30136
- batch = [];
30137
- }
30138
- }
30139
- if (batch.length > 0) {
30140
- const target = greedyLeaves(sum(batch));
30141
- if (!equals(target, batch)) {
30142
- swaps.push(new Swap([...batch], target));
30143
- }
30144
- }
30145
- return swaps;
30146
- }
30147
-
30148
30130
  // src/utils/retry.ts
30149
30131
  var DEFAULT_RETRY_CONFIG = {
30150
30132
  maxAttempts: 5,
@@ -30221,6 +30203,199 @@ var SparkWalletEvent = {
30221
30203
  StreamReconnecting: "stream:reconnecting"
30222
30204
  };
30223
30205
 
30206
+ // src/utils/optimize.ts
30207
+ var DENOMINATIONS = Array.from({ length: 28 }, (_, i) => 2 ** i);
30208
+ function assert(condition, message) {
30209
+ if (!condition) {
30210
+ throw new InternalValidationError(message || "Assertion failed");
30211
+ }
30212
+ }
30213
+ function sum(arr) {
30214
+ return arr.reduce((a, b) => a + b, 0);
30215
+ }
30216
+ function sorted(arr) {
30217
+ return [...arr].sort((a, b) => a - b);
30218
+ }
30219
+ function equals(a, b) {
30220
+ return a.length === b.length && a.every((val, index) => val === b[index]);
30221
+ }
30222
+ function countOccurrences(arr) {
30223
+ const map = /* @__PURE__ */ new Map();
30224
+ for (const x of arr) {
30225
+ map.set(x, (map.get(x) ?? 0) + 1);
30226
+ }
30227
+ return map;
30228
+ }
30229
+ function subtractCounters(a, b) {
30230
+ const result = /* @__PURE__ */ new Map();
30231
+ for (const [key, value] of a.entries()) {
30232
+ const diff = value - (b.get(key) ?? 0);
30233
+ if (diff > 0) {
30234
+ result.set(key, diff);
30235
+ }
30236
+ }
30237
+ return result;
30238
+ }
30239
+ function counterToFlatArray(counter) {
30240
+ const arr = [];
30241
+ for (const [k, v] of Array.from(counter.entries()).sort(
30242
+ (a, b) => a[0] - b[0]
30243
+ )) {
30244
+ for (let i = 0; i < v; i++) {
30245
+ arr.push(k);
30246
+ }
30247
+ }
30248
+ return arr;
30249
+ }
30250
+ function greedyLeaves(amount) {
30251
+ const leaves = [];
30252
+ let remaining = amount;
30253
+ for (let i = DENOMINATIONS.length - 1; i >= 0; i--) {
30254
+ const leaf = DENOMINATIONS[i];
30255
+ if (typeof leaf === "number" && leaf > 0) {
30256
+ while (remaining >= leaf) {
30257
+ remaining -= leaf;
30258
+ leaves.push(leaf);
30259
+ }
30260
+ }
30261
+ }
30262
+ assert(sum(leaves) === amount, "greedy_leaves: sum mismatch");
30263
+ return sorted(leaves);
30264
+ }
30265
+ function swapMinimizingLeaves(amount, multiplicity = 1) {
30266
+ const leaves = [];
30267
+ let remaining = amount;
30268
+ assert(multiplicity > 0, "multiplicity must be > 0");
30269
+ for (const leaf of DENOMINATIONS) {
30270
+ if (typeof leaf === "number" && leaf > 0) {
30271
+ for (let i = 0; i < multiplicity; i++) {
30272
+ if (remaining >= leaf) {
30273
+ remaining -= leaf;
30274
+ leaves.push(leaf);
30275
+ }
30276
+ }
30277
+ }
30278
+ }
30279
+ leaves.push(...greedyLeaves(remaining));
30280
+ assert(sum(leaves) === amount, "swap_minimizing_leaves: sum mismatch");
30281
+ return sorted(leaves);
30282
+ }
30283
+ var Swap = class {
30284
+ inLeaves;
30285
+ outLeaves;
30286
+ constructor(inLeaves, outLeaves) {
30287
+ this.inLeaves = [...inLeaves];
30288
+ this.outLeaves = [...outLeaves];
30289
+ assert(
30290
+ sum(this.inLeaves) === sum(this.outLeaves),
30291
+ "Swap in/out leaves must sum to same value for swap: " + this.toString()
30292
+ );
30293
+ }
30294
+ toString() {
30295
+ return `Swap(in=${JSON.stringify(this.inLeaves)}, out=${JSON.stringify(this.outLeaves)})`;
30296
+ }
30297
+ };
30298
+ function maximizeUnilateralExit(inputLeaves, maxLeavesPerSwap = 64) {
30299
+ const swaps = [];
30300
+ let batch = [];
30301
+ let leaves = sorted(inputLeaves);
30302
+ while (leaves.length > 0) {
30303
+ batch.push(leaves.shift());
30304
+ const target = greedyLeaves(sum(batch));
30305
+ if (batch.length >= maxLeavesPerSwap || target.length >= maxLeavesPerSwap) {
30306
+ if (!equals(target, batch)) {
30307
+ swaps.push(new Swap([...batch], target));
30308
+ }
30309
+ batch = [];
30310
+ }
30311
+ }
30312
+ if (batch.length > 0) {
30313
+ const target = greedyLeaves(sum(batch));
30314
+ if (!equals(target, batch)) {
30315
+ swaps.push(new Swap([...batch], target));
30316
+ }
30317
+ }
30318
+ return swaps;
30319
+ }
30320
+ function minimizeTransferSwap(inputLeaves, multiplicity = 1, maxLeavesPerSwap = 64) {
30321
+ const balance = sum(inputLeaves);
30322
+ const optimalLeaves = swapMinimizingLeaves(balance, multiplicity);
30323
+ const walletCounter = countOccurrences(inputLeaves);
30324
+ const optimalCounter = countOccurrences(optimalLeaves);
30325
+ const leavesToGive = subtractCounters(walletCounter, optimalCounter);
30326
+ const leavesToReceive = subtractCounters(optimalCounter, walletCounter);
30327
+ const leavesToGiveFlat = counterToFlatArray(leavesToGive);
30328
+ const leavesToReceiveFlat = counterToFlatArray(leavesToReceive);
30329
+ const swaps = [];
30330
+ let toGiveBatch = [];
30331
+ let toReceiveBatch = [];
30332
+ let give = [...leavesToGiveFlat];
30333
+ let receive = [...leavesToReceiveFlat];
30334
+ while (give.length > 0 || receive.length > 0) {
30335
+ if (sum(toGiveBatch) > sum(toReceiveBatch)) {
30336
+ if (receive.length === 0) break;
30337
+ toReceiveBatch.push(receive.shift());
30338
+ } else {
30339
+ if (give.length === 0) break;
30340
+ toGiveBatch.push(give.shift());
30341
+ }
30342
+ if (toGiveBatch.length > 0 && toReceiveBatch.length > 0 && sum(toGiveBatch) === sum(toReceiveBatch)) {
30343
+ if (toGiveBatch.length > maxLeavesPerSwap) {
30344
+ for (let i = 0; i < toGiveBatch.length; i += maxLeavesPerSwap) {
30345
+ const subset = toGiveBatch.slice(i, i + maxLeavesPerSwap);
30346
+ swaps.push(new Swap(subset, greedyLeaves(sum(subset))));
30347
+ }
30348
+ } else if (toReceiveBatch.length > maxLeavesPerSwap) {
30349
+ for (let cutoff = maxLeavesPerSwap; cutoff > 0; cutoff--) {
30350
+ const sumCut = sum(toReceiveBatch.slice(0, cutoff));
30351
+ const remainder = sum(toGiveBatch) - sumCut;
30352
+ const alternateBatch = [
30353
+ ...toReceiveBatch.slice(0, cutoff),
30354
+ ...greedyLeaves(remainder)
30355
+ ];
30356
+ if (alternateBatch.length <= maxLeavesPerSwap) {
30357
+ swaps.push(new Swap([...toGiveBatch], alternateBatch));
30358
+ break;
30359
+ }
30360
+ }
30361
+ } else {
30362
+ swaps.push(new Swap([...toGiveBatch], [...toReceiveBatch]));
30363
+ }
30364
+ toGiveBatch = [];
30365
+ toReceiveBatch = [];
30366
+ }
30367
+ }
30368
+ return swaps;
30369
+ }
30370
+ function shouldOptimize(inputLeaves, multiplicity = 1, maxLeavesPerSwap = 64) {
30371
+ if (multiplicity == 0) {
30372
+ const swaps = maximizeUnilateralExit(inputLeaves, maxLeavesPerSwap);
30373
+ const numInputs = sum(swaps.map((swap) => swap.inLeaves.length));
30374
+ const numOutputs = sum(swaps.map((swap) => swap.outLeaves.length));
30375
+ return numOutputs * 5 < numInputs;
30376
+ } else {
30377
+ const swaps = minimizeTransferSwap(
30378
+ inputLeaves,
30379
+ multiplicity,
30380
+ maxLeavesPerSwap
30381
+ );
30382
+ const inputCounter = countOccurrences(
30383
+ swaps.flatMap((swap) => swap.inLeaves)
30384
+ );
30385
+ const outputCounter = countOccurrences(
30386
+ swaps.flatMap((swap) => swap.outLeaves)
30387
+ );
30388
+ return Math.abs(inputCounter.size - outputCounter.size) > 1;
30389
+ }
30390
+ }
30391
+ function optimize(inputLeaves, multiplicity = 1, maxLeavesPerSwap = 64) {
30392
+ if (multiplicity == 0) {
30393
+ return maximizeUnilateralExit(inputLeaves, maxLeavesPerSwap);
30394
+ } else {
30395
+ return minimizeTransferSwap(inputLeaves, multiplicity, maxLeavesPerSwap);
30396
+ }
30397
+ }
30398
+
30224
30399
  // src/spark-wallet/spark-wallet.ts
30225
30400
  var SparkWallet = class extends import_eventemitter3.EventEmitter {
30226
30401
  config;
@@ -30325,8 +30500,7 @@ var SparkWallet = class extends import_eventemitter3.EventEmitter {
30325
30500
  if (event.transfer.transfer && !(0, import_utils24.equalBytes)(senderIdentityPublicKey, receiverIdentityPublicKey)) {
30326
30501
  await this.claimTransfer({
30327
30502
  transfer: event.transfer.transfer,
30328
- emit: true,
30329
- optimize: true
30503
+ emit: true
30330
30504
  });
30331
30505
  }
30332
30506
  } else if (isDepositStreamEvent(event)) {
@@ -30617,73 +30791,84 @@ var SparkWallet = class extends import_eventemitter3.EventEmitter {
30617
30791
  }
30618
30792
  return nodes;
30619
30793
  }
30620
- areLeavesInefficient() {
30621
- const totalAmount = this.getInternalBalance();
30622
- if (this.leaves.length <= 1) {
30623
- return false;
30794
+ async *optimizeLeaves(multiplicity = void 0) {
30795
+ const multiplicityValue = multiplicity ?? this.config.getOptimizationOptions().multiplicity ?? 0;
30796
+ if (multiplicityValue < 0) {
30797
+ throw new ValidationError("Multiplicity cannot be negative");
30798
+ } else if (multiplicityValue > 5) {
30799
+ throw new ValidationError("Multiplicity cannot be greater than 5");
30624
30800
  }
30625
- const nextLowerPowerOfTwo = 31 - Math.clz32(totalAmount);
30626
- let remainingAmount = totalAmount;
30627
- let optimalLeavesLength = 0;
30628
- for (let i = nextLowerPowerOfTwo; i >= 0; i--) {
30629
- const denomination = 2 ** i;
30630
- while (remainingAmount >= denomination) {
30631
- remainingAmount -= denomination;
30632
- optimalLeavesLength++;
30633
- }
30634
- }
30635
- return this.leaves.length > optimalLeavesLength * 5;
30636
- }
30637
- async optimizeLeaves() {
30638
- if (this.optimizationInProgress || !this.areLeavesInefficient()) {
30801
+ if (this.optimizationInProgress || !shouldOptimize(
30802
+ this.leaves.map((leaf) => leaf.value),
30803
+ multiplicityValue
30804
+ )) {
30639
30805
  return;
30640
30806
  }
30641
- await this.withLeaves(async () => {
30807
+ const controller = new AbortController();
30808
+ const release = await this.leavesMutex.acquire();
30809
+ try {
30642
30810
  this.optimizationInProgress = true;
30643
- try {
30644
- this.leaves = await this.getLeaves();
30645
- const swaps = maximizeUnilateralExit(
30646
- this.leaves.map((leaf) => leaf.value)
30647
- );
30648
- const valueToNodes = /* @__PURE__ */ new Map();
30649
- this.leaves.forEach((leaf) => {
30650
- if (!valueToNodes.has(leaf.value)) {
30651
- valueToNodes.set(leaf.value, []);
30652
- }
30653
- valueToNodes.get(leaf.value).push(leaf);
30654
- });
30655
- for (const swap of swaps) {
30656
- const leavesToSend = [];
30657
- for (const leafValue of swap.inLeaves) {
30658
- const nodes = valueToNodes.get(leafValue);
30659
- if (nodes && nodes.length > 0) {
30660
- const node = nodes.shift();
30661
- leavesToSend.push(node);
30662
- } else {
30663
- throw new InternalValidationError(
30664
- `No unused leaf with value ${leafValue} found in leaves`
30665
- );
30666
- }
30811
+ this.leaves = await this.getLeaves();
30812
+ const swaps = optimize(
30813
+ this.leaves.map((leaf) => leaf.value),
30814
+ multiplicityValue
30815
+ );
30816
+ if (swaps.length === 0) {
30817
+ return;
30818
+ }
30819
+ yield {
30820
+ step: 0,
30821
+ total: swaps.length,
30822
+ controller
30823
+ };
30824
+ const valueToNodes = /* @__PURE__ */ new Map();
30825
+ this.leaves.forEach((leaf) => {
30826
+ if (!valueToNodes.has(leaf.value)) {
30827
+ valueToNodes.set(leaf.value, []);
30828
+ }
30829
+ valueToNodes.get(leaf.value).push(leaf);
30830
+ });
30831
+ for (const swap of swaps) {
30832
+ if (controller.signal.aborted) {
30833
+ break;
30834
+ }
30835
+ const leavesToSend = [];
30836
+ for (const leafValue of swap.inLeaves) {
30837
+ const nodes = valueToNodes.get(leafValue);
30838
+ if (nodes && nodes.length > 0) {
30839
+ const node = nodes.shift();
30840
+ leavesToSend.push(node);
30841
+ } else {
30842
+ throw new InternalValidationError(
30843
+ `No unused leaf with value ${leafValue} found in leaves`
30844
+ );
30667
30845
  }
30668
- await this.requestLeavesSwap({
30669
- leaves: leavesToSend,
30670
- targetAmounts: swap.outLeaves
30671
- });
30672
30846
  }
30673
- this.leaves = await this.getLeaves();
30674
- } finally {
30675
- this.optimizationInProgress = false;
30847
+ await this.requestLeavesSwap({
30848
+ leaves: leavesToSend,
30849
+ targetAmounts: swap.outLeaves
30850
+ });
30851
+ yield {
30852
+ step: swaps.indexOf(swap) + 1,
30853
+ total: swaps.length,
30854
+ controller
30855
+ };
30676
30856
  }
30677
- });
30857
+ this.leaves = await this.getLeaves();
30858
+ } finally {
30859
+ this.optimizationInProgress = false;
30860
+ release();
30861
+ }
30678
30862
  }
30679
30863
  async syncWallet() {
30680
30864
  await this.syncTokenOutputs();
30681
30865
  let leaves = await this.getLeaves();
30682
30866
  leaves = await this.checkRenewLeaves(leaves);
30683
30867
  this.leaves = leaves;
30684
- this.optimizeLeaves().catch((e) => {
30685
- console.error("Failed to optimize leaves", e);
30686
- });
30868
+ if (this.config.getOptimizationOptions().auto) {
30869
+ for await (const _ of this.optimizeLeaves()) {
30870
+ }
30871
+ }
30687
30872
  }
30688
30873
  async withLeaves(operation) {
30689
30874
  const release = await this.leavesMutex.acquire();
@@ -31266,8 +31451,7 @@ var SparkWallet = class extends import_eventemitter3.EventEmitter {
31266
31451
  }
31267
31452
  return await this.claimTransfer({
31268
31453
  transfer: incomingTransfer,
31269
- emit: false,
31270
- optimize: false
31454
+ emit: false
31271
31455
  });
31272
31456
  } catch (e) {
31273
31457
  console.error("[processSwapBatch] Error details:", {
@@ -32296,7 +32480,7 @@ var SparkWallet = class extends import_eventemitter3.EventEmitter {
32296
32480
  transfer.id
32297
32481
  );
32298
32482
  if (pending) {
32299
- await this.claimTransfer({ transfer: pending, optimize: true });
32483
+ await this.claimTransfer({ transfer: pending });
32300
32484
  }
32301
32485
  }
32302
32486
  return {
@@ -32469,13 +32653,14 @@ var SparkWallet = class extends import_eventemitter3.EventEmitter {
32469
32653
  return response.nodes;
32470
32654
  });
32471
32655
  }
32472
- async processClaimedTransferResults(result, transfer, emit, optimize) {
32656
+ async processClaimedTransferResults(result, transfer, emit) {
32473
32657
  result = await this.checkRenewLeaves(result);
32474
32658
  const existingIds = new Set(this.leaves.map((leaf) => leaf.id));
32475
32659
  const uniqueResults = result.filter((node) => !existingIds.has(node.id));
32476
32660
  this.leaves.push(...uniqueResults);
32477
- if (optimize && transfer.type !== 40 /* COUNTER_SWAP */) {
32478
- await this.optimizeLeaves();
32661
+ if (this.config.getOptimizationOptions().auto && transfer.type !== 40 /* COUNTER_SWAP */) {
32662
+ for await (const _ of this.optimizeLeaves()) {
32663
+ }
32479
32664
  }
32480
32665
  if (emit) {
32481
32666
  this.emit(
@@ -32494,8 +32679,7 @@ var SparkWallet = class extends import_eventemitter3.EventEmitter {
32494
32679
  */
32495
32680
  async claimTransfer({
32496
32681
  transfer,
32497
- emit,
32498
- optimize
32682
+ emit
32499
32683
  }) {
32500
32684
  const onError = async (context) => {
32501
32685
  const error = context.error;
@@ -32540,12 +32724,7 @@ var SparkWallet = class extends import_eventemitter3.EventEmitter {
32540
32724
  if (result.length === 0) {
32541
32725
  return [];
32542
32726
  }
32543
- return await this.processClaimedTransferResults(
32544
- result,
32545
- transfer,
32546
- emit,
32547
- optimize
32548
- );
32727
+ return await this.processClaimedTransferResults(result, transfer, emit);
32549
32728
  } catch (error) {
32550
32729
  console.warn(
32551
32730
  `Failed to claim transfer after all retries. Please try reinitializing your wallet in a few minutes. Transfer ID: ${transfer.id}`,
@@ -32578,7 +32757,7 @@ var SparkWallet = class extends import_eventemitter3.EventEmitter {
32578
32757
  continue;
32579
32758
  }
32580
32759
  promises.push(
32581
- this.claimTransfer({ transfer, emit, optimize: true }).then(() => transfer.id).catch((error) => {
32760
+ this.claimTransfer({ transfer, emit }).then(() => transfer.id).catch((error) => {
32582
32761
  console.warn(`Failed to claim transfer ${transfer.id}:`, error);
32583
32762
  return null;
32584
32763
  })
@@ -35618,6 +35797,7 @@ setFetch(sparkBareFetch, Headers2);
35618
35797
  getTxFromRawTxHex,
35619
35798
  getTxId,
35620
35799
  getTxIdNoReverse,
35800
+ hash160,
35621
35801
  isEphemeralAnchorOutput,
35622
35802
  isLegacySparkAddress,
35623
35803
  isSafeForNumber,
@@ -3703,6 +3703,10 @@ type SigningOperator = {
3703
3703
  type ConsoleOptions = {
3704
3704
  otel?: boolean;
3705
3705
  };
3706
+ type OptimizationOptions = {
3707
+ readonly auto?: boolean;
3708
+ readonly multiplicity?: number;
3709
+ };
3706
3710
  type ConfigOptions = MayHaveSspClientOptions & {
3707
3711
  readonly network?: NetworkType;
3708
3712
  readonly signingOperators?: Readonly<Record<string, SigningOperator>>;
@@ -3718,6 +3722,7 @@ type ConfigOptions = MayHaveSspClientOptions & {
3718
3722
  readonly signerWithPreExistingKeys?: boolean;
3719
3723
  readonly console?: ConsoleOptions;
3720
3724
  readonly events?: Partial<SparkWalletEvents>;
3725
+ readonly optimizationOptions?: OptimizationOptions;
3721
3726
  };
3722
3727
 
3723
3728
  declare class WalletConfigService implements HasSspClientOptions {
@@ -3743,6 +3748,7 @@ declare class WalletConfigService implements HasSspClientOptions {
3743
3748
  getSspIdentityPublicKey(): string;
3744
3749
  getConsoleOptions(): ConsoleOptions;
3745
3750
  getEvents(): Partial<SparkWalletEvents>;
3751
+ getOptimizationOptions(): OptimizationOptions;
3746
3752
  }
3747
3753
 
3748
3754
  /** Challenge represents the core challenge data */
@@ -4044,8 +4050,11 @@ declare abstract class SparkWallet extends EventEmitter<SparkWalletEvents> {
4044
4050
  private popOrThrow;
4045
4051
  private selectLeaves;
4046
4052
  private selectLeavesForSwap;
4047
- private areLeavesInefficient;
4048
- private optimizeLeaves;
4053
+ optimizeLeaves(multiplicity?: number | undefined): AsyncGenerator<{
4054
+ step: number;
4055
+ total: number;
4056
+ controller: AbortController;
4057
+ }, void, void>;
4049
4058
  private syncWallet;
4050
4059
  private withLeaves;
4051
4060
  /**
@@ -4802,6 +4811,7 @@ interface BroadcastResult {
4802
4811
  declare function isEphemeralAnchorOutput(script?: Uint8Array, amount?: bigint): boolean;
4803
4812
  declare function constructUnilateralExitTxs(nodeHexStrings: string[], sparkClient?: SparkServiceClient, network?: any): Promise<TxChain[]>;
4804
4813
  declare function constructUnilateralExitFeeBumpPackages(nodeHexStrings: string[], utxos: Utxo[], feeRate: FeeRate, electrsUrl: string, sparkClient?: SparkServiceClient, network?: any): Promise<FeeBumpTxChain[]>;
4814
+ declare function hash160(data: Uint8Array): Uint8Array;
4805
4815
  declare function constructFeeBumpTx(txHex: string, utxos: Utxo[], feeRate: FeeRate, previousFeeBumpTx?: string): {
4806
4816
  feeBumpPsbt: string;
4807
4817
  usedUtxos: Utxo[];
@@ -4823,4 +4833,4 @@ declare class SparkSdkLogger {
4823
4833
  static setAllEnabled(enabled: boolean): void;
4824
4834
  }
4825
4835
 
4826
- export { type AggregateFrostParams, AuthenticationError, type Bech32mTokenIdentifier, type Bech32mTokenIdentifierData, type BroadcastConfig, type BroadcastResult, ConfigurationError, DEFAULT_FEE_SATS, DIRECT_HTLC_TIMELOCK_OFFSET, DIRECT_TIMELOCK_OFFSET, type DecodedSparkAddressData, DefaultSparkSigner, type FeeBumpTxChain, type FeeBumpTxPackage, type FeeRate, HTLC_TIMELOCK_OFFSET, type IKeyPackage, INITIAL_SEQUENCE, InternalValidationError, LOGGER_NAMES, type LeafInfo, type LegacySparkAddressFormat, type LoggerName, Network, NetworkError, NetworkToProto, type NetworkType, NotImplementedError, RPCError, type SignFrostParams, type SparkAddressData, type SparkAddressFormat, SparkSDKError, SparkSdkLogger, type SparkSigner, SparkWalletBare as SparkWallet, TEST_UNILATERAL_DIRECT_SEQUENCE, TEST_UNILATERAL_SEQUENCE, TaprootOutputKeysGenerator, TaprootSparkSigner, type TxChain, UnsafeStatelessSparkSigner, type Utxo, ValidationError, type VerifiableSecretShare, addPrivateKeys, addPublicKeys, applyAdaptorToSignature, applyAdditiveTweakToPublicKey, assertBech32, bech32mDecode, bigIntToPrivateKey, checkIfSelectedOutputsAreAvailable, checkIfValidSequence, collectResponses, computeTaprootKeyNoScript, computerLagrangeCoefficients, constructFeeBumpTx, constructUnilateralExitFeeBumpPackages, constructUnilateralExitTxs, createConnectorRefundTxs, createCurrentTimelockRefundTxs, createDecrementedTimelockNodeTx, createDecrementedTimelockRefundTxs, createInitialTimelockNodeTx, createInitialTimelockRefundTxs, createRootNodeTx, createSigningCommitment, createSigningNonce, createTestUnilateralRefundTxs, createTestUnilateralTimelockNodeTx, createZeroTimelockNodeTx, decodeBech32mTokenIdentifier, decodeBytesToSigningCommitment, decodeBytesToSigningNonce, decodeSparkAddress, doesLeafNeedRefresh, doesTxnNeedRenewed, encodeBech32mTokenIdentifier, encodeSigningCommitmentToBytes, encodeSigningNonceToBytes, encodeSparkAddress, encodeSparkAddressWithSignature, evaluatePolynomial, fieldDiv, filterTokenBalanceForTokenIdentifier, generateAdaptorFromSignature, generatePolynomialForSecretSharing, generateSignatureFromExistingAdaptor, getCurrentTimelock, getEphemeralAnchorOutput, getLatestDepositTxId, getNetwork, getNetworkFromAddress, getNetworkFromBech32mTokenIdentifier, getNetworkFromSparkAddress, getNetworkFromString, getNextHTLCTransactionSequence, getNextTransactionSequence, getP2TRAddressFromPkScript, getP2TRAddressFromPublicKey, getP2TRScriptFromPublicKey, getP2WPKHAddressFromPublicKey, getRandomBigInt, getRandomSigningNonce, getSigHashFromTx, getSigningCommitmentFromNonce, getSparkAddressFromTaproot, getTransactionSequence, getTransferPackageSigningPayload, getTxEstimatedVbytesSizeByNumberOfInputsOutputs, getTxFromRawTxBytes, getTxFromRawTxHex, getTxId, getTxIdNoReverse, isEphemeralAnchorOutput, isLegacySparkAddress, isSafeForNumber, isTxBroadcast, isValidPublicKey, isValidSparkAddress, isZeroTimelock, lastKeyWithTarget, maybeApplyFee, modInverse, proofOfPossessionMessageHashForDepositAddress, protoToNetwork, recoverSecret, splitSecret, splitSecretWithProofs, subtractPrivateKeys, subtractPublicKeys, sumAvailableTokens, sumOfPrivateKeys, toProtoTimestamp, validateOutboundAdaptorSignature, validateShare, validateSparkInvoiceFields, validateSparkInvoiceSignature };
4836
+ export { type AggregateFrostParams, AuthenticationError, type Bech32mTokenIdentifier, type Bech32mTokenIdentifierData, type BroadcastConfig, type BroadcastResult, ConfigurationError, DEFAULT_FEE_SATS, DIRECT_HTLC_TIMELOCK_OFFSET, DIRECT_TIMELOCK_OFFSET, type DecodedSparkAddressData, DefaultSparkSigner, type FeeBumpTxChain, type FeeBumpTxPackage, type FeeRate, HTLC_TIMELOCK_OFFSET, type IKeyPackage, INITIAL_SEQUENCE, InternalValidationError, LOGGER_NAMES, type LeafInfo, type LegacySparkAddressFormat, type LoggerName, Network, NetworkError, NetworkToProto, type NetworkType, NotImplementedError, RPCError, type SignFrostParams, type SparkAddressData, type SparkAddressFormat, SparkSDKError, SparkSdkLogger, type SparkSigner, SparkWalletBare as SparkWallet, TEST_UNILATERAL_DIRECT_SEQUENCE, TEST_UNILATERAL_SEQUENCE, TaprootOutputKeysGenerator, TaprootSparkSigner, type TxChain, UnsafeStatelessSparkSigner, type Utxo, ValidationError, type VerifiableSecretShare, addPrivateKeys, addPublicKeys, applyAdaptorToSignature, applyAdditiveTweakToPublicKey, assertBech32, bech32mDecode, bigIntToPrivateKey, checkIfSelectedOutputsAreAvailable, checkIfValidSequence, collectResponses, computeTaprootKeyNoScript, computerLagrangeCoefficients, constructFeeBumpTx, constructUnilateralExitFeeBumpPackages, constructUnilateralExitTxs, createConnectorRefundTxs, createCurrentTimelockRefundTxs, createDecrementedTimelockNodeTx, createDecrementedTimelockRefundTxs, createInitialTimelockNodeTx, createInitialTimelockRefundTxs, createRootNodeTx, createSigningCommitment, createSigningNonce, createTestUnilateralRefundTxs, createTestUnilateralTimelockNodeTx, createZeroTimelockNodeTx, decodeBech32mTokenIdentifier, decodeBytesToSigningCommitment, decodeBytesToSigningNonce, decodeSparkAddress, doesLeafNeedRefresh, doesTxnNeedRenewed, encodeBech32mTokenIdentifier, encodeSigningCommitmentToBytes, encodeSigningNonceToBytes, encodeSparkAddress, encodeSparkAddressWithSignature, evaluatePolynomial, fieldDiv, filterTokenBalanceForTokenIdentifier, generateAdaptorFromSignature, generatePolynomialForSecretSharing, generateSignatureFromExistingAdaptor, getCurrentTimelock, getEphemeralAnchorOutput, getLatestDepositTxId, getNetwork, getNetworkFromAddress, getNetworkFromBech32mTokenIdentifier, getNetworkFromSparkAddress, getNetworkFromString, getNextHTLCTransactionSequence, getNextTransactionSequence, getP2TRAddressFromPkScript, getP2TRAddressFromPublicKey, getP2TRScriptFromPublicKey, getP2WPKHAddressFromPublicKey, getRandomBigInt, getRandomSigningNonce, getSigHashFromTx, getSigningCommitmentFromNonce, getSparkAddressFromTaproot, getTransactionSequence, getTransferPackageSigningPayload, getTxEstimatedVbytesSizeByNumberOfInputsOutputs, getTxFromRawTxBytes, getTxFromRawTxHex, getTxId, getTxIdNoReverse, hash160, isEphemeralAnchorOutput, isLegacySparkAddress, isSafeForNumber, isTxBroadcast, isValidPublicKey, isValidSparkAddress, isZeroTimelock, lastKeyWithTarget, maybeApplyFee, modInverse, proofOfPossessionMessageHashForDepositAddress, protoToNetwork, recoverSecret, splitSecret, splitSecretWithProofs, subtractPrivateKeys, subtractPublicKeys, sumAvailableTokens, sumOfPrivateKeys, toProtoTimestamp, validateOutboundAdaptorSignature, validateShare, validateSparkInvoiceFields, validateSparkInvoiceSignature };