@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
package/dist/index.js CHANGED
@@ -1,9 +1,9 @@
1
- import "./chunk-WRE2T22S.js";
1
+ import "./chunk-LOXWCMZL.js";
2
2
  import {
3
3
  ConnectionManagerBrowser,
4
4
  SparkWalletBrowser,
5
5
  initializeTracerEnvBrowser
6
- } from "./chunk-S55NZT4P.js";
6
+ } from "./chunk-27ILUWDJ.js";
7
7
  import {
8
8
  AuthenticationError,
9
9
  ConfigurationError,
@@ -100,6 +100,7 @@ import {
100
100
  getTxFromRawTxHex,
101
101
  getTxId,
102
102
  getTxIdNoReverse,
103
+ hash160,
103
104
  isEphemeralAnchorOutput,
104
105
  isLegacySparkAddress,
105
106
  isSafeForNumber,
@@ -124,7 +125,7 @@ import {
124
125
  validateShare,
125
126
  validateSparkInvoiceFields,
126
127
  validateSparkInvoiceSignature
127
- } from "./chunk-O4C4HGQL.js";
128
+ } from "./chunk-G3LHXHF3.js";
128
129
  import {
129
130
  LOGGER_NAMES,
130
131
  SparkSdkLogger
@@ -234,6 +235,7 @@ export {
234
235
  getTxFromRawTxHex,
235
236
  getTxId,
236
237
  getTxIdNoReverse,
238
+ hash160,
237
239
  initializeTracerEnvBrowser as initializeTracerEnv,
238
240
  isEphemeralAnchorOutput,
239
241
  isLegacySparkAddress,
@@ -1343,6 +1343,7 @@ __export(index_node_exports, {
1343
1343
  getTxFromRawTxHex: () => getTxFromRawTxHex,
1344
1344
  getTxId: () => getTxId,
1345
1345
  getTxIdNoReverse: () => getTxIdNoReverse,
1346
+ hash160: () => hash160,
1346
1347
  initializeTracerEnv: () => initializeTracerEnvNodeJS,
1347
1348
  isEphemeralAnchorOutput: () => isEphemeralAnchorOutput,
1348
1349
  isLegacySparkAddress: () => isLegacySparkAddress,
@@ -18502,7 +18503,11 @@ var BASE_CONFIG = {
18502
18503
  console: {
18503
18504
  otel: false
18504
18505
  },
18505
- events: {}
18506
+ events: {},
18507
+ optimizationOptions: {
18508
+ auto: true,
18509
+ multiplicity: 0
18510
+ }
18506
18511
  };
18507
18512
  var LOCAL_WALLET_CONFIG = {
18508
18513
  ...BASE_CONFIG,
@@ -21039,7 +21044,7 @@ var isWebExtension = (
21039
21044
  "chrome" in globalThis && globalThis.chrome.runtime?.id
21040
21045
  );
21041
21046
  var userAgent = "navigator" in globalThis ? globalThis.navigator.userAgent || "unknown-user-agent" : void 0;
21042
- var packageVersion = true ? "0.3.9" : "unknown";
21047
+ var packageVersion = true ? "0.4.0" : "unknown";
21043
21048
  var baseEnvStr = "unknown";
21044
21049
  if (isBun) {
21045
21050
  const bunVersion = "version" in globalThis.Bun ? globalThis.Bun.version : "unknown-version";
@@ -22824,6 +22829,9 @@ var WalletConfigService = class {
22824
22829
  getEvents() {
22825
22830
  return this.config.events;
22826
22831
  }
22832
+ getOptimizationOptions() {
22833
+ return this.config.optimizationOptions;
22834
+ }
22827
22835
  };
22828
22836
 
22829
22837
  // src/services/coop-exit.ts
@@ -30694,6 +30702,14 @@ var BitcoinFaucet = class _BitcoinFaucet {
30694
30702
  async mineBlocks(numBlocks) {
30695
30703
  return await this.generateToAddress(numBlocks, this.miningAddress);
30696
30704
  }
30705
+ async mineBlocksAndWaitForMiningToComplete(numBlocks) {
30706
+ const startBlock = await this.getBlockCount();
30707
+ await this.mineBlocks(numBlocks);
30708
+ await this.waitForBlocksMined({
30709
+ startBlock,
30710
+ expectedIncrease: numBlocks
30711
+ });
30712
+ }
30697
30713
  async call(method, params) {
30698
30714
  try {
30699
30715
  const { fetch, Headers: Headers2 } = getFetch();
@@ -30746,15 +30762,50 @@ var BitcoinFaucet = class _BitcoinFaucet {
30746
30762
  async getBlock(blockHash) {
30747
30763
  return await this.call("getblock", [blockHash, 2]);
30748
30764
  }
30765
+ async getBlockCount() {
30766
+ return await this.call("getblockcount", []);
30767
+ }
30768
+ async waitForBlocksMined({
30769
+ startBlock,
30770
+ expectedIncrease,
30771
+ timeoutMs = 3e4,
30772
+ intervalMs = 5e3
30773
+ }) {
30774
+ const deadline = Date.now() + timeoutMs;
30775
+ await new Promise((r) => setTimeout(r, intervalMs));
30776
+ const start = startBlock;
30777
+ const target = start + expectedIncrease;
30778
+ while (Date.now() < deadline) {
30779
+ const currentBlock = await this.getBlockCount();
30780
+ if (currentBlock >= target) return currentBlock;
30781
+ await new Promise((r) => setTimeout(r, intervalMs));
30782
+ }
30783
+ throw new Error(
30784
+ `Timed out waiting for ${expectedIncrease} blocks (target height ${target})`
30785
+ );
30786
+ }
30749
30787
  async broadcastTx(txHex) {
30750
30788
  let response = await this.call("sendrawtransaction", [txHex, 0]);
30751
30789
  return response;
30752
30790
  }
30791
+ async submitPackage(txHexs) {
30792
+ let response = await this.call("submitpackage", [txHexs]);
30793
+ return response;
30794
+ }
30753
30795
  async getNewAddress() {
30754
30796
  const key = import_secp256k113.secp256k1.utils.randomPrivateKey();
30755
30797
  const pubKey = import_secp256k113.secp256k1.getPublicKey(key);
30756
30798
  return getP2TRAddressFromPublicKey(pubKey, 4 /* LOCAL */);
30757
30799
  }
30800
+ async getNewExternalWallet() {
30801
+ const key = import_secp256k113.secp256k1.utils.randomPrivateKey();
30802
+ const pubKey = import_secp256k113.secp256k1.getPublicKey(key);
30803
+ return {
30804
+ address: getP2TRAddressFromPublicKey(pubKey, 4 /* LOCAL */),
30805
+ key,
30806
+ pubKey
30807
+ };
30808
+ }
30758
30809
  async sendToAddress(address, amount, blocksToGenerate = 1) {
30759
30810
  const coin = await this.fund();
30760
30811
  if (!coin) {
@@ -30811,76 +30862,6 @@ function chunkArray(arr, size) {
30811
30862
  return chunks;
30812
30863
  }
30813
30864
 
30814
- // src/utils/optimize.ts
30815
- init_buffer();
30816
- var DENOMINATIONS = Array.from({ length: 28 }, (_, i) => 2 ** i);
30817
- function assert(condition, message) {
30818
- if (!condition) {
30819
- throw new InternalValidationError(message || "Assertion failed");
30820
- }
30821
- }
30822
- function sum(arr) {
30823
- return arr.reduce((a, b) => a + b, 0);
30824
- }
30825
- function sorted(arr) {
30826
- return [...arr].sort((a, b) => a - b);
30827
- }
30828
- function equals(a, b) {
30829
- return a.length === b.length && a.every((val, index) => val === b[index]);
30830
- }
30831
- function greedyLeaves(amount) {
30832
- const leaves = [];
30833
- let remaining = amount;
30834
- for (let i = DENOMINATIONS.length - 1; i >= 0; i--) {
30835
- const leaf = DENOMINATIONS[i];
30836
- if (typeof leaf === "number" && leaf > 0) {
30837
- while (remaining >= leaf) {
30838
- remaining -= leaf;
30839
- leaves.push(leaf);
30840
- }
30841
- }
30842
- }
30843
- assert(sum(leaves) === amount, "greedy_leaves: sum mismatch");
30844
- return sorted(leaves);
30845
- }
30846
- var Swap = class {
30847
- inLeaves;
30848
- outLeaves;
30849
- constructor(inLeaves, outLeaves) {
30850
- this.inLeaves = [...inLeaves];
30851
- this.outLeaves = [...outLeaves];
30852
- assert(
30853
- sum(this.inLeaves) === sum(this.outLeaves),
30854
- "Swap in/out leaves must sum to same value for swap: " + this.toString()
30855
- );
30856
- }
30857
- toString() {
30858
- return `Swap(in=${JSON.stringify(this.inLeaves)}, out=${JSON.stringify(this.outLeaves)})`;
30859
- }
30860
- };
30861
- function maximizeUnilateralExit(inputLeaves, maxLeavesPerSwap = 64) {
30862
- const swaps = [];
30863
- let batch = [];
30864
- let leaves = sorted(inputLeaves);
30865
- while (leaves.length > 0) {
30866
- batch.push(leaves.shift());
30867
- const target = greedyLeaves(sum(batch));
30868
- if (batch.length >= maxLeavesPerSwap || target.length >= maxLeavesPerSwap) {
30869
- if (!equals(target, batch)) {
30870
- swaps.push(new Swap([...batch], target));
30871
- }
30872
- batch = [];
30873
- }
30874
- }
30875
- if (batch.length > 0) {
30876
- const target = greedyLeaves(sum(batch));
30877
- if (!equals(target, batch)) {
30878
- swaps.push(new Swap([...batch], target));
30879
- }
30880
- }
30881
- return swaps;
30882
- }
30883
-
30884
30865
  // src/utils/retry.ts
30885
30866
  init_buffer();
30886
30867
  var DEFAULT_RETRY_CONFIG = {
@@ -30959,6 +30940,200 @@ var SparkWalletEvent = {
30959
30940
  StreamReconnecting: "stream:reconnecting"
30960
30941
  };
30961
30942
 
30943
+ // src/utils/optimize.ts
30944
+ init_buffer();
30945
+ var DENOMINATIONS = Array.from({ length: 28 }, (_, i) => 2 ** i);
30946
+ function assert(condition, message) {
30947
+ if (!condition) {
30948
+ throw new InternalValidationError(message || "Assertion failed");
30949
+ }
30950
+ }
30951
+ function sum(arr) {
30952
+ return arr.reduce((a, b) => a + b, 0);
30953
+ }
30954
+ function sorted(arr) {
30955
+ return [...arr].sort((a, b) => a - b);
30956
+ }
30957
+ function equals(a, b) {
30958
+ return a.length === b.length && a.every((val, index) => val === b[index]);
30959
+ }
30960
+ function countOccurrences(arr) {
30961
+ const map = /* @__PURE__ */ new Map();
30962
+ for (const x of arr) {
30963
+ map.set(x, (map.get(x) ?? 0) + 1);
30964
+ }
30965
+ return map;
30966
+ }
30967
+ function subtractCounters(a, b) {
30968
+ const result = /* @__PURE__ */ new Map();
30969
+ for (const [key, value] of a.entries()) {
30970
+ const diff = value - (b.get(key) ?? 0);
30971
+ if (diff > 0) {
30972
+ result.set(key, diff);
30973
+ }
30974
+ }
30975
+ return result;
30976
+ }
30977
+ function counterToFlatArray(counter) {
30978
+ const arr = [];
30979
+ for (const [k, v] of Array.from(counter.entries()).sort(
30980
+ (a, b) => a[0] - b[0]
30981
+ )) {
30982
+ for (let i = 0; i < v; i++) {
30983
+ arr.push(k);
30984
+ }
30985
+ }
30986
+ return arr;
30987
+ }
30988
+ function greedyLeaves(amount) {
30989
+ const leaves = [];
30990
+ let remaining = amount;
30991
+ for (let i = DENOMINATIONS.length - 1; i >= 0; i--) {
30992
+ const leaf = DENOMINATIONS[i];
30993
+ if (typeof leaf === "number" && leaf > 0) {
30994
+ while (remaining >= leaf) {
30995
+ remaining -= leaf;
30996
+ leaves.push(leaf);
30997
+ }
30998
+ }
30999
+ }
31000
+ assert(sum(leaves) === amount, "greedy_leaves: sum mismatch");
31001
+ return sorted(leaves);
31002
+ }
31003
+ function swapMinimizingLeaves(amount, multiplicity = 1) {
31004
+ const leaves = [];
31005
+ let remaining = amount;
31006
+ assert(multiplicity > 0, "multiplicity must be > 0");
31007
+ for (const leaf of DENOMINATIONS) {
31008
+ if (typeof leaf === "number" && leaf > 0) {
31009
+ for (let i = 0; i < multiplicity; i++) {
31010
+ if (remaining >= leaf) {
31011
+ remaining -= leaf;
31012
+ leaves.push(leaf);
31013
+ }
31014
+ }
31015
+ }
31016
+ }
31017
+ leaves.push(...greedyLeaves(remaining));
31018
+ assert(sum(leaves) === amount, "swap_minimizing_leaves: sum mismatch");
31019
+ return sorted(leaves);
31020
+ }
31021
+ var Swap = class {
31022
+ inLeaves;
31023
+ outLeaves;
31024
+ constructor(inLeaves, outLeaves) {
31025
+ this.inLeaves = [...inLeaves];
31026
+ this.outLeaves = [...outLeaves];
31027
+ assert(
31028
+ sum(this.inLeaves) === sum(this.outLeaves),
31029
+ "Swap in/out leaves must sum to same value for swap: " + this.toString()
31030
+ );
31031
+ }
31032
+ toString() {
31033
+ return `Swap(in=${JSON.stringify(this.inLeaves)}, out=${JSON.stringify(this.outLeaves)})`;
31034
+ }
31035
+ };
31036
+ function maximizeUnilateralExit(inputLeaves, maxLeavesPerSwap = 64) {
31037
+ const swaps = [];
31038
+ let batch = [];
31039
+ let leaves = sorted(inputLeaves);
31040
+ while (leaves.length > 0) {
31041
+ batch.push(leaves.shift());
31042
+ const target = greedyLeaves(sum(batch));
31043
+ if (batch.length >= maxLeavesPerSwap || target.length >= maxLeavesPerSwap) {
31044
+ if (!equals(target, batch)) {
31045
+ swaps.push(new Swap([...batch], target));
31046
+ }
31047
+ batch = [];
31048
+ }
31049
+ }
31050
+ if (batch.length > 0) {
31051
+ const target = greedyLeaves(sum(batch));
31052
+ if (!equals(target, batch)) {
31053
+ swaps.push(new Swap([...batch], target));
31054
+ }
31055
+ }
31056
+ return swaps;
31057
+ }
31058
+ function minimizeTransferSwap(inputLeaves, multiplicity = 1, maxLeavesPerSwap = 64) {
31059
+ const balance = sum(inputLeaves);
31060
+ const optimalLeaves = swapMinimizingLeaves(balance, multiplicity);
31061
+ const walletCounter = countOccurrences(inputLeaves);
31062
+ const optimalCounter = countOccurrences(optimalLeaves);
31063
+ const leavesToGive = subtractCounters(walletCounter, optimalCounter);
31064
+ const leavesToReceive = subtractCounters(optimalCounter, walletCounter);
31065
+ const leavesToGiveFlat = counterToFlatArray(leavesToGive);
31066
+ const leavesToReceiveFlat = counterToFlatArray(leavesToReceive);
31067
+ const swaps = [];
31068
+ let toGiveBatch = [];
31069
+ let toReceiveBatch = [];
31070
+ let give = [...leavesToGiveFlat];
31071
+ let receive = [...leavesToReceiveFlat];
31072
+ while (give.length > 0 || receive.length > 0) {
31073
+ if (sum(toGiveBatch) > sum(toReceiveBatch)) {
31074
+ if (receive.length === 0) break;
31075
+ toReceiveBatch.push(receive.shift());
31076
+ } else {
31077
+ if (give.length === 0) break;
31078
+ toGiveBatch.push(give.shift());
31079
+ }
31080
+ if (toGiveBatch.length > 0 && toReceiveBatch.length > 0 && sum(toGiveBatch) === sum(toReceiveBatch)) {
31081
+ if (toGiveBatch.length > maxLeavesPerSwap) {
31082
+ for (let i = 0; i < toGiveBatch.length; i += maxLeavesPerSwap) {
31083
+ const subset = toGiveBatch.slice(i, i + maxLeavesPerSwap);
31084
+ swaps.push(new Swap(subset, greedyLeaves(sum(subset))));
31085
+ }
31086
+ } else if (toReceiveBatch.length > maxLeavesPerSwap) {
31087
+ for (let cutoff = maxLeavesPerSwap; cutoff > 0; cutoff--) {
31088
+ const sumCut = sum(toReceiveBatch.slice(0, cutoff));
31089
+ const remainder = sum(toGiveBatch) - sumCut;
31090
+ const alternateBatch = [
31091
+ ...toReceiveBatch.slice(0, cutoff),
31092
+ ...greedyLeaves(remainder)
31093
+ ];
31094
+ if (alternateBatch.length <= maxLeavesPerSwap) {
31095
+ swaps.push(new Swap([...toGiveBatch], alternateBatch));
31096
+ break;
31097
+ }
31098
+ }
31099
+ } else {
31100
+ swaps.push(new Swap([...toGiveBatch], [...toReceiveBatch]));
31101
+ }
31102
+ toGiveBatch = [];
31103
+ toReceiveBatch = [];
31104
+ }
31105
+ }
31106
+ return swaps;
31107
+ }
31108
+ function shouldOptimize(inputLeaves, multiplicity = 1, maxLeavesPerSwap = 64) {
31109
+ if (multiplicity == 0) {
31110
+ const swaps = maximizeUnilateralExit(inputLeaves, maxLeavesPerSwap);
31111
+ const numInputs = sum(swaps.map((swap) => swap.inLeaves.length));
31112
+ const numOutputs = sum(swaps.map((swap) => swap.outLeaves.length));
31113
+ return numOutputs * 5 < numInputs;
31114
+ } else {
31115
+ const swaps = minimizeTransferSwap(
31116
+ inputLeaves,
31117
+ multiplicity,
31118
+ maxLeavesPerSwap
31119
+ );
31120
+ const inputCounter = countOccurrences(
31121
+ swaps.flatMap((swap) => swap.inLeaves)
31122
+ );
31123
+ const outputCounter = countOccurrences(
31124
+ swaps.flatMap((swap) => swap.outLeaves)
31125
+ );
31126
+ return Math.abs(inputCounter.size - outputCounter.size) > 1;
31127
+ }
31128
+ }
31129
+ function optimize(inputLeaves, multiplicity = 1, maxLeavesPerSwap = 64) {
31130
+ if (multiplicity == 0) {
31131
+ return maximizeUnilateralExit(inputLeaves, maxLeavesPerSwap);
31132
+ } else {
31133
+ return minimizeTransferSwap(inputLeaves, multiplicity, maxLeavesPerSwap);
31134
+ }
31135
+ }
31136
+
30962
31137
  // src/spark-wallet/spark-wallet.ts
30963
31138
  var SparkWallet = class extends import_eventemitter3.EventEmitter {
30964
31139
  config;
@@ -31063,8 +31238,7 @@ var SparkWallet = class extends import_eventemitter3.EventEmitter {
31063
31238
  if (event.transfer.transfer && !(0, import_utils25.equalBytes)(senderIdentityPublicKey, receiverIdentityPublicKey)) {
31064
31239
  await this.claimTransfer({
31065
31240
  transfer: event.transfer.transfer,
31066
- emit: true,
31067
- optimize: true
31241
+ emit: true
31068
31242
  });
31069
31243
  }
31070
31244
  } else if (isDepositStreamEvent(event)) {
@@ -31355,73 +31529,84 @@ var SparkWallet = class extends import_eventemitter3.EventEmitter {
31355
31529
  }
31356
31530
  return nodes;
31357
31531
  }
31358
- areLeavesInefficient() {
31359
- const totalAmount = this.getInternalBalance();
31360
- if (this.leaves.length <= 1) {
31361
- return false;
31532
+ async *optimizeLeaves(multiplicity = void 0) {
31533
+ const multiplicityValue = multiplicity ?? this.config.getOptimizationOptions().multiplicity ?? 0;
31534
+ if (multiplicityValue < 0) {
31535
+ throw new ValidationError("Multiplicity cannot be negative");
31536
+ } else if (multiplicityValue > 5) {
31537
+ throw new ValidationError("Multiplicity cannot be greater than 5");
31362
31538
  }
31363
- const nextLowerPowerOfTwo = 31 - Math.clz32(totalAmount);
31364
- let remainingAmount = totalAmount;
31365
- let optimalLeavesLength = 0;
31366
- for (let i = nextLowerPowerOfTwo; i >= 0; i--) {
31367
- const denomination = 2 ** i;
31368
- while (remainingAmount >= denomination) {
31369
- remainingAmount -= denomination;
31370
- optimalLeavesLength++;
31371
- }
31372
- }
31373
- return this.leaves.length > optimalLeavesLength * 5;
31374
- }
31375
- async optimizeLeaves() {
31376
- if (this.optimizationInProgress || !this.areLeavesInefficient()) {
31539
+ if (this.optimizationInProgress || !shouldOptimize(
31540
+ this.leaves.map((leaf) => leaf.value),
31541
+ multiplicityValue
31542
+ )) {
31377
31543
  return;
31378
31544
  }
31379
- await this.withLeaves(async () => {
31545
+ const controller = new AbortController();
31546
+ const release = await this.leavesMutex.acquire();
31547
+ try {
31380
31548
  this.optimizationInProgress = true;
31381
- try {
31382
- this.leaves = await this.getLeaves();
31383
- const swaps = maximizeUnilateralExit(
31384
- this.leaves.map((leaf) => leaf.value)
31385
- );
31386
- const valueToNodes = /* @__PURE__ */ new Map();
31387
- this.leaves.forEach((leaf) => {
31388
- if (!valueToNodes.has(leaf.value)) {
31389
- valueToNodes.set(leaf.value, []);
31390
- }
31391
- valueToNodes.get(leaf.value).push(leaf);
31392
- });
31393
- for (const swap of swaps) {
31394
- const leavesToSend = [];
31395
- for (const leafValue of swap.inLeaves) {
31396
- const nodes = valueToNodes.get(leafValue);
31397
- if (nodes && nodes.length > 0) {
31398
- const node = nodes.shift();
31399
- leavesToSend.push(node);
31400
- } else {
31401
- throw new InternalValidationError(
31402
- `No unused leaf with value ${leafValue} found in leaves`
31403
- );
31404
- }
31549
+ this.leaves = await this.getLeaves();
31550
+ const swaps = optimize(
31551
+ this.leaves.map((leaf) => leaf.value),
31552
+ multiplicityValue
31553
+ );
31554
+ if (swaps.length === 0) {
31555
+ return;
31556
+ }
31557
+ yield {
31558
+ step: 0,
31559
+ total: swaps.length,
31560
+ controller
31561
+ };
31562
+ const valueToNodes = /* @__PURE__ */ new Map();
31563
+ this.leaves.forEach((leaf) => {
31564
+ if (!valueToNodes.has(leaf.value)) {
31565
+ valueToNodes.set(leaf.value, []);
31566
+ }
31567
+ valueToNodes.get(leaf.value).push(leaf);
31568
+ });
31569
+ for (const swap of swaps) {
31570
+ if (controller.signal.aborted) {
31571
+ break;
31572
+ }
31573
+ const leavesToSend = [];
31574
+ for (const leafValue of swap.inLeaves) {
31575
+ const nodes = valueToNodes.get(leafValue);
31576
+ if (nodes && nodes.length > 0) {
31577
+ const node = nodes.shift();
31578
+ leavesToSend.push(node);
31579
+ } else {
31580
+ throw new InternalValidationError(
31581
+ `No unused leaf with value ${leafValue} found in leaves`
31582
+ );
31405
31583
  }
31406
- await this.requestLeavesSwap({
31407
- leaves: leavesToSend,
31408
- targetAmounts: swap.outLeaves
31409
- });
31410
31584
  }
31411
- this.leaves = await this.getLeaves();
31412
- } finally {
31413
- this.optimizationInProgress = false;
31585
+ await this.requestLeavesSwap({
31586
+ leaves: leavesToSend,
31587
+ targetAmounts: swap.outLeaves
31588
+ });
31589
+ yield {
31590
+ step: swaps.indexOf(swap) + 1,
31591
+ total: swaps.length,
31592
+ controller
31593
+ };
31414
31594
  }
31415
- });
31595
+ this.leaves = await this.getLeaves();
31596
+ } finally {
31597
+ this.optimizationInProgress = false;
31598
+ release();
31599
+ }
31416
31600
  }
31417
31601
  async syncWallet() {
31418
31602
  await this.syncTokenOutputs();
31419
31603
  let leaves = await this.getLeaves();
31420
31604
  leaves = await this.checkRenewLeaves(leaves);
31421
31605
  this.leaves = leaves;
31422
- this.optimizeLeaves().catch((e) => {
31423
- console.error("Failed to optimize leaves", e);
31424
- });
31606
+ if (this.config.getOptimizationOptions().auto) {
31607
+ for await (const _ of this.optimizeLeaves()) {
31608
+ }
31609
+ }
31425
31610
  }
31426
31611
  async withLeaves(operation) {
31427
31612
  const release = await this.leavesMutex.acquire();
@@ -32004,8 +32189,7 @@ var SparkWallet = class extends import_eventemitter3.EventEmitter {
32004
32189
  }
32005
32190
  return await this.claimTransfer({
32006
32191
  transfer: incomingTransfer,
32007
- emit: false,
32008
- optimize: false
32192
+ emit: false
32009
32193
  });
32010
32194
  } catch (e) {
32011
32195
  console.error("[processSwapBatch] Error details:", {
@@ -33034,7 +33218,7 @@ var SparkWallet = class extends import_eventemitter3.EventEmitter {
33034
33218
  transfer.id
33035
33219
  );
33036
33220
  if (pending) {
33037
- await this.claimTransfer({ transfer: pending, optimize: true });
33221
+ await this.claimTransfer({ transfer: pending });
33038
33222
  }
33039
33223
  }
33040
33224
  return {
@@ -33207,13 +33391,14 @@ var SparkWallet = class extends import_eventemitter3.EventEmitter {
33207
33391
  return response.nodes;
33208
33392
  });
33209
33393
  }
33210
- async processClaimedTransferResults(result, transfer, emit, optimize) {
33394
+ async processClaimedTransferResults(result, transfer, emit) {
33211
33395
  result = await this.checkRenewLeaves(result);
33212
33396
  const existingIds = new Set(this.leaves.map((leaf) => leaf.id));
33213
33397
  const uniqueResults = result.filter((node) => !existingIds.has(node.id));
33214
33398
  this.leaves.push(...uniqueResults);
33215
- if (optimize && transfer.type !== 40 /* COUNTER_SWAP */) {
33216
- await this.optimizeLeaves();
33399
+ if (this.config.getOptimizationOptions().auto && transfer.type !== 40 /* COUNTER_SWAP */) {
33400
+ for await (const _ of this.optimizeLeaves()) {
33401
+ }
33217
33402
  }
33218
33403
  if (emit) {
33219
33404
  this.emit(
@@ -33232,8 +33417,7 @@ var SparkWallet = class extends import_eventemitter3.EventEmitter {
33232
33417
  */
33233
33418
  async claimTransfer({
33234
33419
  transfer,
33235
- emit,
33236
- optimize
33420
+ emit
33237
33421
  }) {
33238
33422
  const onError = async (context) => {
33239
33423
  const error = context.error;
@@ -33278,12 +33462,7 @@ var SparkWallet = class extends import_eventemitter3.EventEmitter {
33278
33462
  if (result.length === 0) {
33279
33463
  return [];
33280
33464
  }
33281
- return await this.processClaimedTransferResults(
33282
- result,
33283
- transfer,
33284
- emit,
33285
- optimize
33286
- );
33465
+ return await this.processClaimedTransferResults(result, transfer, emit);
33287
33466
  } catch (error) {
33288
33467
  console.warn(
33289
33468
  `Failed to claim transfer after all retries. Please try reinitializing your wallet in a few minutes. Transfer ID: ${transfer.id}`,
@@ -33316,7 +33495,7 @@ var SparkWallet = class extends import_eventemitter3.EventEmitter {
33316
33495
  continue;
33317
33496
  }
33318
33497
  promises.push(
33319
- this.claimTransfer({ transfer, emit, optimize: true }).then(() => transfer.id).catch((error) => {
33498
+ this.claimTransfer({ transfer, emit }).then(() => transfer.id).catch((error) => {
33320
33499
  console.warn(`Failed to claim transfer ${transfer.id}:`, error);
33321
33500
  return null;
33322
33501
  })
@@ -36072,6 +36251,7 @@ setCrypto(cryptoImpl2);
36072
36251
  getTxFromRawTxHex,
36073
36252
  getTxId,
36074
36253
  getTxIdNoReverse,
36254
+ hash160,
36075
36255
  initializeTracerEnv,
36076
36256
  isEphemeralAnchorOutput,
36077
36257
  isLegacySparkAddress,
@@ -1,9 +1,9 @@
1
- export { A as AuthenticationError, C as ConfigurationError, I as InternalValidationError, N as NetworkError, a as NotImplementedError, R as RPCError, S as SparkSDKError, T as TokenTransactionService, V as ValidationError } from './token-transactions-CV8QD3I7.cjs';
2
- export { C as BaseConnectionManager, B as Bech32mTokenIdentifier, w as Bech32mTokenIdentifierData, b as ConfigOptions, A as CreateLightningInvoiceParams, D as DecodedSparkAddressData, K as DepositParams, G as FulfillSparkInvoiceResponse, J as GroupSparkInvoicesResult, V as InitWalletResponse, I as InvalidInvoice, L as LegacySparkAddressFormat, N as Network, p as NetworkToProto, o as NetworkType, P as PayLightningInvoiceParams, R as RawTokenIdentifierHex, c as SparkAddressData, S as SparkAddressFormat, Y as SparkWalletEvent, Z as SparkWalletEventType, _ as SparkWalletEvents, X as SparkWalletProps, M as TokenBalanceMap, H as TokenInvoice, Q as TokenMetadataMap, O as TokenOutputsMap, T as TransferParams, F as TransferWithInvoiceOutcome, E as TransferWithInvoiceParams, U as UserTokenMetadata, a as WalletConfig, W as WalletConfigService, l as assertBech32, m as bech32mDecode, y as decodeBech32mTokenIdentifier, f as decodeSparkAddress, x as encodeBech32mTokenIdentifier, e as encodeSparkAddress, d as encodeSparkAddressWithSignature, r as getNetwork, s as getNetworkFromAddress, z as getNetworkFromBech32mTokenIdentifier, g as getNetworkFromSparkAddress, u as getNetworkFromString, i as isLegacySparkAddress, n as isSafeForNumber, j as isValidPublicKey, h as isValidSparkAddress, q as protoToNetwork, t as toProtoTimestamp, v as validateSparkInvoiceFields, k as validateSparkInvoiceSignature } from './wallet-config-DQw5llqA.cjs';
3
- export { ah as BroadcastConfig, ai as BroadcastResult, R as DEFAULT_FEE_SATS, O as DIRECT_HTLC_TIMELOCK_OFFSET, M as DIRECT_TIMELOCK_OFFSET, af as FeeBumpTxChain, ae as FeeBumpTxPackage, ad as FeeRate, N as HTLC_TIMELOCK_OFFSET, P as INITIAL_SEQUENCE, ao as LOGGER_NAMES, ab as LeafInfo, ap as LoggerName, aq as SparkSdkLogger, Q as TEST_UNILATERAL_DIRECT_SEQUENCE, T as TEST_UNILATERAL_SEQUENCE, ag as TxChain, ac as Utxo, q as addPrivateKeys, o as addPublicKeys, b as applyAdaptorToSignature, p as applyAdditiveTweakToPublicKey, J as checkIfSelectedOutputsAreAvailable, a5 as checkIfValidSequence, z as collectResponses, c as computeTaprootKeyNoScript, am as constructFeeBumpTx, al as constructUnilateralExitFeeBumpPackages, ak as constructUnilateralExitTxs, a2 as createConnectorRefundTxs, a0 as createCurrentTimelockRefundTxs, X as createDecrementedTimelockNodeTx, $ as createDecrementedTimelockRefundTxs, W as createInitialTimelockNodeTx, _ as createInitialTimelockRefundTxs, U as createRootNodeTx, F as createSigningCommitment, B as createSigningNonce, a1 as createTestUnilateralRefundTxs, Y as createTestUnilateralTimelockNodeTx, V as createZeroTimelockNodeTx, H as decodeBytesToSigningCommitment, E as decodeBytesToSigningNonce, a8 as doesLeafNeedRefresh, a7 as doesTxnNeedRenewed, G as encodeSigningCommitmentToBytes, D as encodeSigningNonceToBytes, K as filterTokenBalanceForTokenIdentifier, a as generateAdaptorFromSignature, g as generateSignatureFromExistingAdaptor, a3 as getCurrentTimelock, aa as getEphemeralAnchorOutput, w as getLatestDepositTxId, Z as getNextHTLCTransactionSequence, a9 as getNextTransactionSequence, f as getP2TRAddressFromPkScript, e as getP2TRAddressFromPublicKey, d as getP2TRScriptFromPublicKey, h as getP2WPKHAddressFromPublicKey, A as getRandomSigningNonce, k as getSigHashFromTx, C as getSigningCommitmentFromNonce, an as getSparkAddressFromTaproot, a4 as getTransactionSequence, L as getTransferPackageSigningPayload, n as getTxEstimatedVbytesSizeByNumberOfInputsOutputs, j as getTxFromRawTxBytes, i as getTxFromRawTxHex, l as getTxId, m as getTxIdNoReverse, aj as isEphemeralAnchorOutput, x as isTxBroadcast, a6 as isZeroTimelock, u as lastKeyWithTarget, S as maybeApplyFee, y as proofOfPossessionMessageHashForDepositAddress, r as subtractPrivateKeys, s as subtractPublicKeys, I as sumAvailableTokens, t as sumOfPrivateKeys, v as validateOutboundAdaptorSignature } from './logging-CXhvuqJJ.cjs';
1
+ export { A as AuthenticationError, C as ConfigurationError, I as InternalValidationError, N as NetworkError, a as NotImplementedError, R as RPCError, S as SparkSDKError, T as TokenTransactionService, V as ValidationError } from './token-transactions-Db8mkjnU.cjs';
2
+ export { C as BaseConnectionManager, B as Bech32mTokenIdentifier, w as Bech32mTokenIdentifierData, b as ConfigOptions, A as CreateLightningInvoiceParams, D as DecodedSparkAddressData, K as DepositParams, G as FulfillSparkInvoiceResponse, J as GroupSparkInvoicesResult, V as InitWalletResponse, I as InvalidInvoice, L as LegacySparkAddressFormat, N as Network, p as NetworkToProto, o as NetworkType, P as PayLightningInvoiceParams, R as RawTokenIdentifierHex, c as SparkAddressData, S as SparkAddressFormat, Y as SparkWalletEvent, Z as SparkWalletEventType, _ as SparkWalletEvents, X as SparkWalletProps, M as TokenBalanceMap, H as TokenInvoice, Q as TokenMetadataMap, O as TokenOutputsMap, T as TransferParams, F as TransferWithInvoiceOutcome, E as TransferWithInvoiceParams, U as UserTokenMetadata, a as WalletConfig, W as WalletConfigService, l as assertBech32, m as bech32mDecode, y as decodeBech32mTokenIdentifier, f as decodeSparkAddress, x as encodeBech32mTokenIdentifier, e as encodeSparkAddress, d as encodeSparkAddressWithSignature, r as getNetwork, s as getNetworkFromAddress, z as getNetworkFromBech32mTokenIdentifier, g as getNetworkFromSparkAddress, u as getNetworkFromString, i as isLegacySparkAddress, n as isSafeForNumber, j as isValidPublicKey, h as isValidSparkAddress, q as protoToNetwork, t as toProtoTimestamp, v as validateSparkInvoiceFields, k as validateSparkInvoiceSignature } from './wallet-config-CuZKNo9S.cjs';
3
+ export { ah as BroadcastConfig, ai as BroadcastResult, R as DEFAULT_FEE_SATS, O as DIRECT_HTLC_TIMELOCK_OFFSET, M as DIRECT_TIMELOCK_OFFSET, af as FeeBumpTxChain, ae as FeeBumpTxPackage, ad as FeeRate, N as HTLC_TIMELOCK_OFFSET, P as INITIAL_SEQUENCE, ap as LOGGER_NAMES, ab as LeafInfo, aq as LoggerName, ar as SparkSdkLogger, Q as TEST_UNILATERAL_DIRECT_SEQUENCE, T as TEST_UNILATERAL_SEQUENCE, ag as TxChain, ac as Utxo, q as addPrivateKeys, o as addPublicKeys, b as applyAdaptorToSignature, p as applyAdditiveTweakToPublicKey, J as checkIfSelectedOutputsAreAvailable, a5 as checkIfValidSequence, z as collectResponses, c as computeTaprootKeyNoScript, an as constructFeeBumpTx, al as constructUnilateralExitFeeBumpPackages, ak as constructUnilateralExitTxs, a2 as createConnectorRefundTxs, a0 as createCurrentTimelockRefundTxs, X as createDecrementedTimelockNodeTx, $ as createDecrementedTimelockRefundTxs, W as createInitialTimelockNodeTx, _ as createInitialTimelockRefundTxs, U as createRootNodeTx, F as createSigningCommitment, B as createSigningNonce, a1 as createTestUnilateralRefundTxs, Y as createTestUnilateralTimelockNodeTx, V as createZeroTimelockNodeTx, H as decodeBytesToSigningCommitment, E as decodeBytesToSigningNonce, a8 as doesLeafNeedRefresh, a7 as doesTxnNeedRenewed, G as encodeSigningCommitmentToBytes, D as encodeSigningNonceToBytes, K as filterTokenBalanceForTokenIdentifier, a as generateAdaptorFromSignature, g as generateSignatureFromExistingAdaptor, a3 as getCurrentTimelock, aa as getEphemeralAnchorOutput, w as getLatestDepositTxId, Z as getNextHTLCTransactionSequence, a9 as getNextTransactionSequence, f as getP2TRAddressFromPkScript, e as getP2TRAddressFromPublicKey, d as getP2TRScriptFromPublicKey, h as getP2WPKHAddressFromPublicKey, A as getRandomSigningNonce, k as getSigHashFromTx, C as getSigningCommitmentFromNonce, ao as getSparkAddressFromTaproot, a4 as getTransactionSequence, L as getTransferPackageSigningPayload, n as getTxEstimatedVbytesSizeByNumberOfInputsOutputs, j as getTxFromRawTxBytes, i as getTxFromRawTxHex, l as getTxId, m as getTxIdNoReverse, am as hash160, aj as isEphemeralAnchorOutput, x as isTxBroadcast, a6 as isZeroTimelock, u as lastKeyWithTarget, S as maybeApplyFee, y as proofOfPossessionMessageHashForDepositAddress, r as subtractPrivateKeys, s as subtractPublicKeys, I as sumAvailableTokens, t as sumOfPrivateKeys, v as validateOutboundAdaptorSignature } from './logging-D3IfXfHG.cjs';
4
4
  export { A as AggregateFrostParams, D as DefaultSparkSigner, p as DerivedHDKey, l as KeyDerivation, K as KeyDerivationType, q as KeyPair, n as SignFrostParams, k as SigningCommitment, i as SigningCommitmentWithOptionalNonce, j as SigningNonce, S as SparkSigner, o as SplitSecretWithProofsParams, T as TaprootOutputKeysGenerator, a as TaprootSparkSigner, U as UnsafeStatelessSparkSigner, V as VerifiableSecretShare, h as bigIntToPrivateKey, c as computerLagrangeCoefficients, e as evaluatePolynomial, f as fieldDiv, b as generatePolynomialForSecretSharing, g as getRandomBigInt, m as modInverse, r as recoverSecret, s as splitSecret, d as splitSecretWithProofs, v as validateShare } from './client-BIqiUNy4.cjs';
5
5
  export { I as IKeyPackage } from './types-B3hMoTYO.cjs';
6
- export { C as ConnectionManager, S as SparkWallet, i as initializeTracerEnv } from './spark-wallet.node-4WQgWwB2.cjs';
6
+ export { C as ConnectionManager, S as SparkWallet, i as initializeTracerEnv } from './spark-wallet.node-C2TIkyt4.cjs';
7
7
  import './spark-DOpheE8_.cjs';
8
8
  import '@bufbuild/protobuf/wire';
9
9
  import 'nice-grpc-common';