@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
@@ -17108,7 +17108,11 @@ var BASE_CONFIG = {
17108
17108
  console: {
17109
17109
  otel: false
17110
17110
  },
17111
- events: {}
17111
+ events: {},
17112
+ optimizationOptions: {
17113
+ auto: true,
17114
+ multiplicity: 0
17115
+ }
17112
17116
  };
17113
17117
  var LOCAL_WALLET_CONFIG = {
17114
17118
  ...BASE_CONFIG,
@@ -19714,7 +19718,7 @@ var isWebExtension = (
19714
19718
  "chrome" in globalThis && globalThis.chrome.runtime?.id
19715
19719
  );
19716
19720
  var userAgent = "navigator" in globalThis ? globalThis.navigator.userAgent || "unknown-user-agent" : void 0;
19717
- var packageVersion = true ? "0.3.9" : "unknown";
19721
+ var packageVersion = true ? "0.4.0" : "unknown";
19718
19722
  var baseEnvStr = "unknown";
19719
19723
  if (isBun) {
19720
19724
  const bunVersion = "version" in globalThis.Bun ? globalThis.Bun.version : "unknown-version";
@@ -19790,6 +19794,63 @@ var DefaultSparkKeysGenerator = class {
19790
19794
  };
19791
19795
  }
19792
19796
  };
19797
+ var TaprootOutputKeysGenerator = class {
19798
+ constructor(useAddressIndex = false) {
19799
+ this.useAddressIndex = useAddressIndex;
19800
+ }
19801
+ async deriveKeysFromSeed(seed, accountNumber) {
19802
+ const hdkey = HDKey.fromMasterSeed(seed);
19803
+ if (!hdkey.privateKey || !hdkey.publicKey) {
19804
+ throw new ValidationError("Failed to derive keys from seed", {
19805
+ field: "hdkey",
19806
+ value: seed
19807
+ });
19808
+ }
19809
+ const derivationPath = this.useAddressIndex ? `m/86'/0'/0'/0/${accountNumber}` : `m/86'/0'/${accountNumber}'/0/0`;
19810
+ const taprootInternalKey = hdkey.derive(derivationPath);
19811
+ let tweakedPrivateKey = taprootTweakPrivKey(taprootInternalKey.privateKey);
19812
+ let tweakedPublicKey = secp256k17.getPublicKey(tweakedPrivateKey);
19813
+ if (tweakedPublicKey[0] === 3) {
19814
+ tweakedPrivateKey = privateNegate(tweakedPrivateKey);
19815
+ tweakedPublicKey = secp256k17.getPublicKey(tweakedPrivateKey);
19816
+ }
19817
+ const identityKey = {
19818
+ publicKey: tweakedPublicKey,
19819
+ privateKey: tweakedPrivateKey
19820
+ };
19821
+ const signingKey = hdkey.derive(`${derivationPath}/1'`);
19822
+ const depositKey = hdkey.derive(`${derivationPath}/2'`);
19823
+ const staticDepositKey = hdkey.derive(`${derivationPath}/3'`);
19824
+ if (!signingKey.privateKey || !signingKey.publicKey || !depositKey.privateKey || !depositKey.publicKey || !staticDepositKey.privateKey || !staticDepositKey.publicKey) {
19825
+ throw new ValidationError(
19826
+ "Failed to derive all required keys from seed",
19827
+ {
19828
+ field: "derivedKeys"
19829
+ }
19830
+ );
19831
+ }
19832
+ return {
19833
+ identityKey: {
19834
+ privateKey: identityKey.privateKey,
19835
+ publicKey: identityKey.publicKey
19836
+ },
19837
+ signingHDKey: {
19838
+ hdKey: signingKey,
19839
+ privateKey: signingKey.privateKey,
19840
+ publicKey: signingKey.publicKey
19841
+ },
19842
+ depositKey: {
19843
+ privateKey: depositKey.privateKey,
19844
+ publicKey: depositKey.publicKey
19845
+ },
19846
+ staticDepositHDKey: {
19847
+ hdKey: staticDepositKey,
19848
+ privateKey: staticDepositKey.privateKey,
19849
+ publicKey: staticDepositKey.publicKey
19850
+ }
19851
+ };
19852
+ }
19853
+ };
19793
19854
  var DefaultSparkSigner = class {
19794
19855
  constructor({
19795
19856
  sparkKeysGenerator
@@ -20108,6 +20169,13 @@ var DefaultSparkSigner = class {
20108
20169
  tx.signIdx(privateKey, index);
20109
20170
  }
20110
20171
  };
20172
+ var TaprootSparkSigner = class extends DefaultSparkSigner {
20173
+ constructor(useAddressIndex = false) {
20174
+ super({
20175
+ sparkKeysGenerator: new TaprootOutputKeysGenerator(useAddressIndex)
20176
+ });
20177
+ }
20178
+ };
20111
20179
 
20112
20180
  // src/signer/signer.react-native.ts
20113
20181
  var ReactNativeSparkSigner = class extends DefaultSparkSigner {
@@ -20171,6 +20239,70 @@ var ReactNativeSparkSigner = class extends DefaultSparkSigner {
20171
20239
  });
20172
20240
  }
20173
20241
  };
20242
+ var ReactNativeTaprootSparkSigner = class extends TaprootSparkSigner {
20243
+ constructor(useAddressIndex = false) {
20244
+ super(useAddressIndex);
20245
+ }
20246
+ async signFrost({
20247
+ message,
20248
+ keyDerivation,
20249
+ publicKey,
20250
+ verifyingKey,
20251
+ selfCommitment,
20252
+ statechainCommitments,
20253
+ adaptorPubKey
20254
+ }) {
20255
+ const signingPrivateKey = await this.getSigningPrivateKeyFromDerivation(keyDerivation);
20256
+ if (!signingPrivateKey) {
20257
+ throw new ValidationError("Private key not found for public key", {
20258
+ field: "privateKey"
20259
+ });
20260
+ }
20261
+ const commitment = selfCommitment.commitment;
20262
+ const nonce = this.commitmentToNonceMap.get(commitment);
20263
+ if (!nonce) {
20264
+ throw new ValidationError("Nonce not found for commitment", {
20265
+ field: "nonce"
20266
+ });
20267
+ }
20268
+ const keyPackage = {
20269
+ secretKey: signingPrivateKey,
20270
+ publicKey,
20271
+ verifyingKey
20272
+ };
20273
+ return NativeSparkFrost.signFrost({
20274
+ message,
20275
+ keyPackage,
20276
+ nonce,
20277
+ selfCommitment: commitment,
20278
+ statechainCommitments,
20279
+ adaptorPubKey
20280
+ });
20281
+ }
20282
+ async aggregateFrost({
20283
+ message,
20284
+ publicKey,
20285
+ verifyingKey,
20286
+ selfCommitment,
20287
+ statechainCommitments,
20288
+ adaptorPubKey,
20289
+ selfSignature,
20290
+ statechainSignatures,
20291
+ statechainPublicKeys
20292
+ }) {
20293
+ return NativeSparkFrost.aggregateFrost({
20294
+ message,
20295
+ statechainSignatures,
20296
+ statechainPublicKeys,
20297
+ verifyingKey,
20298
+ statechainCommitments,
20299
+ selfCommitment: selfCommitment.commitment,
20300
+ selfPublicKey: publicKey,
20301
+ selfSignature,
20302
+ adaptorPubKey
20303
+ });
20304
+ }
20305
+ };
20174
20306
 
20175
20307
  // src/services/xhr-transport.ts
20176
20308
  import { throwIfAborted } from "abort-controller-x";
@@ -20308,7 +20440,16 @@ function headersToMetadata(headers) {
20308
20440
  const parts = line.split(": ");
20309
20441
  const header = parts.shift() ?? "";
20310
20442
  const value = parts.join(": ");
20311
- metadata.set(header, value);
20443
+ if (header.endsWith("-bin")) {
20444
+ try {
20445
+ metadata.set(header, Base64.toUint8Array(value));
20446
+ } catch (e) {
20447
+ console.warn(`Failed to decode binary metadata ${header}:`, e);
20448
+ metadata.set(header, value);
20449
+ }
20450
+ } else {
20451
+ metadata.set(header, value);
20452
+ }
20312
20453
  });
20313
20454
  return metadata;
20314
20455
  }
@@ -20334,7 +20475,7 @@ function getStatusFromHttpCode(statusCode) {
20334
20475
  }
20335
20476
  }
20336
20477
  function getErrorDetailsFromHttpResponse(statusCode, responseText) {
20337
- return `Received HTTP ${statusCode} response: ` + (responseText.length > 1e3 ? responseText.slice(0, 1e3) + "... (truncated)" : responseText);
20478
+ return `Received HTTP ${statusCode} response: ` + (responseText?.length > 1e3 ? responseText?.slice(0, 1e3) + "... (truncated)" : responseText);
20338
20479
  }
20339
20480
 
20340
20481
  // src/spark-wallet/spark-wallet.ts
@@ -21566,6 +21707,9 @@ var WalletConfigService = class {
21566
21707
  getEvents() {
21567
21708
  return this.config.events;
21568
21709
  }
21710
+ getOptimizationOptions() {
21711
+ return this.config.optimizationOptions;
21712
+ }
21569
21713
  };
21570
21714
 
21571
21715
  // src/services/coop-exit.ts
@@ -29438,6 +29582,14 @@ var _BitcoinFaucet = class _BitcoinFaucet {
29438
29582
  async mineBlocks(numBlocks) {
29439
29583
  return await this.generateToAddress(numBlocks, this.miningAddress);
29440
29584
  }
29585
+ async mineBlocksAndWaitForMiningToComplete(numBlocks) {
29586
+ const startBlock = await this.getBlockCount();
29587
+ await this.mineBlocks(numBlocks);
29588
+ await this.waitForBlocksMined({
29589
+ startBlock,
29590
+ expectedIncrease: numBlocks
29591
+ });
29592
+ }
29441
29593
  async call(method, params) {
29442
29594
  try {
29443
29595
  const { fetch, Headers: Headers2 } = getFetch();
@@ -29490,15 +29642,50 @@ var _BitcoinFaucet = class _BitcoinFaucet {
29490
29642
  async getBlock(blockHash) {
29491
29643
  return await this.call("getblock", [blockHash, 2]);
29492
29644
  }
29645
+ async getBlockCount() {
29646
+ return await this.call("getblockcount", []);
29647
+ }
29648
+ async waitForBlocksMined({
29649
+ startBlock,
29650
+ expectedIncrease,
29651
+ timeoutMs = 3e4,
29652
+ intervalMs = 5e3
29653
+ }) {
29654
+ const deadline = Date.now() + timeoutMs;
29655
+ await new Promise((r) => setTimeout(r, intervalMs));
29656
+ const start = startBlock;
29657
+ const target = start + expectedIncrease;
29658
+ while (Date.now() < deadline) {
29659
+ const currentBlock = await this.getBlockCount();
29660
+ if (currentBlock >= target) return currentBlock;
29661
+ await new Promise((r) => setTimeout(r, intervalMs));
29662
+ }
29663
+ throw new Error(
29664
+ `Timed out waiting for ${expectedIncrease} blocks (target height ${target})`
29665
+ );
29666
+ }
29493
29667
  async broadcastTx(txHex) {
29494
29668
  let response = await this.call("sendrawtransaction", [txHex, 0]);
29495
29669
  return response;
29496
29670
  }
29671
+ async submitPackage(txHexs) {
29672
+ let response = await this.call("submitpackage", [txHexs]);
29673
+ return response;
29674
+ }
29497
29675
  async getNewAddress() {
29498
29676
  const key = secp256k112.utils.randomPrivateKey();
29499
29677
  const pubKey = secp256k112.getPublicKey(key);
29500
29678
  return getP2TRAddressFromPublicKey(pubKey, 4 /* LOCAL */);
29501
29679
  }
29680
+ async getNewExternalWallet() {
29681
+ const key = secp256k112.utils.randomPrivateKey();
29682
+ const pubKey = secp256k112.getPublicKey(key);
29683
+ return {
29684
+ address: getP2TRAddressFromPublicKey(pubKey, 4 /* LOCAL */),
29685
+ key,
29686
+ pubKey
29687
+ };
29688
+ }
29502
29689
  async sendToAddress(address, amount, blocksToGenerate = 1) {
29503
29690
  const coin = await this.fund();
29504
29691
  if (!coin) {
@@ -29556,75 +29743,6 @@ function chunkArray(arr, size) {
29556
29743
  return chunks;
29557
29744
  }
29558
29745
 
29559
- // src/utils/optimize.ts
29560
- var DENOMINATIONS = Array.from({ length: 28 }, (_, i) => 2 ** i);
29561
- function assert(condition, message) {
29562
- if (!condition) {
29563
- throw new InternalValidationError(message || "Assertion failed");
29564
- }
29565
- }
29566
- function sum(arr) {
29567
- return arr.reduce((a, b) => a + b, 0);
29568
- }
29569
- function sorted(arr) {
29570
- return [...arr].sort((a, b) => a - b);
29571
- }
29572
- function equals(a, b) {
29573
- return a.length === b.length && a.every((val, index) => val === b[index]);
29574
- }
29575
- function greedyLeaves(amount) {
29576
- const leaves = [];
29577
- let remaining = amount;
29578
- for (let i = DENOMINATIONS.length - 1; i >= 0; i--) {
29579
- const leaf = DENOMINATIONS[i];
29580
- if (typeof leaf === "number" && leaf > 0) {
29581
- while (remaining >= leaf) {
29582
- remaining -= leaf;
29583
- leaves.push(leaf);
29584
- }
29585
- }
29586
- }
29587
- assert(sum(leaves) === amount, "greedy_leaves: sum mismatch");
29588
- return sorted(leaves);
29589
- }
29590
- var Swap = class {
29591
- constructor(inLeaves, outLeaves) {
29592
- __publicField(this, "inLeaves");
29593
- __publicField(this, "outLeaves");
29594
- this.inLeaves = [...inLeaves];
29595
- this.outLeaves = [...outLeaves];
29596
- assert(
29597
- sum(this.inLeaves) === sum(this.outLeaves),
29598
- "Swap in/out leaves must sum to same value for swap: " + this.toString()
29599
- );
29600
- }
29601
- toString() {
29602
- return `Swap(in=${JSON.stringify(this.inLeaves)}, out=${JSON.stringify(this.outLeaves)})`;
29603
- }
29604
- };
29605
- function maximizeUnilateralExit(inputLeaves, maxLeavesPerSwap = 64) {
29606
- const swaps = [];
29607
- let batch = [];
29608
- let leaves = sorted(inputLeaves);
29609
- while (leaves.length > 0) {
29610
- batch.push(leaves.shift());
29611
- const target = greedyLeaves(sum(batch));
29612
- if (batch.length >= maxLeavesPerSwap || target.length >= maxLeavesPerSwap) {
29613
- if (!equals(target, batch)) {
29614
- swaps.push(new Swap([...batch], target));
29615
- }
29616
- batch = [];
29617
- }
29618
- }
29619
- if (batch.length > 0) {
29620
- const target = greedyLeaves(sum(batch));
29621
- if (!equals(target, batch)) {
29622
- swaps.push(new Swap([...batch], target));
29623
- }
29624
- }
29625
- return swaps;
29626
- }
29627
-
29628
29746
  // src/utils/retry.ts
29629
29747
  var DEFAULT_RETRY_CONFIG = {
29630
29748
  maxAttempts: 5,
@@ -29701,6 +29819,199 @@ var SparkWalletEvent = {
29701
29819
  StreamReconnecting: "stream:reconnecting"
29702
29820
  };
29703
29821
 
29822
+ // src/utils/optimize.ts
29823
+ var DENOMINATIONS = Array.from({ length: 28 }, (_, i) => 2 ** i);
29824
+ function assert(condition, message) {
29825
+ if (!condition) {
29826
+ throw new InternalValidationError(message || "Assertion failed");
29827
+ }
29828
+ }
29829
+ function sum(arr) {
29830
+ return arr.reduce((a, b) => a + b, 0);
29831
+ }
29832
+ function sorted(arr) {
29833
+ return [...arr].sort((a, b) => a - b);
29834
+ }
29835
+ function equals(a, b) {
29836
+ return a.length === b.length && a.every((val, index) => val === b[index]);
29837
+ }
29838
+ function countOccurrences(arr) {
29839
+ const map = /* @__PURE__ */ new Map();
29840
+ for (const x of arr) {
29841
+ map.set(x, (map.get(x) ?? 0) + 1);
29842
+ }
29843
+ return map;
29844
+ }
29845
+ function subtractCounters(a, b) {
29846
+ const result = /* @__PURE__ */ new Map();
29847
+ for (const [key, value] of a.entries()) {
29848
+ const diff = value - (b.get(key) ?? 0);
29849
+ if (diff > 0) {
29850
+ result.set(key, diff);
29851
+ }
29852
+ }
29853
+ return result;
29854
+ }
29855
+ function counterToFlatArray(counter) {
29856
+ const arr = [];
29857
+ for (const [k, v] of Array.from(counter.entries()).sort(
29858
+ (a, b) => a[0] - b[0]
29859
+ )) {
29860
+ for (let i = 0; i < v; i++) {
29861
+ arr.push(k);
29862
+ }
29863
+ }
29864
+ return arr;
29865
+ }
29866
+ function greedyLeaves(amount) {
29867
+ const leaves = [];
29868
+ let remaining = amount;
29869
+ for (let i = DENOMINATIONS.length - 1; i >= 0; i--) {
29870
+ const leaf = DENOMINATIONS[i];
29871
+ if (typeof leaf === "number" && leaf > 0) {
29872
+ while (remaining >= leaf) {
29873
+ remaining -= leaf;
29874
+ leaves.push(leaf);
29875
+ }
29876
+ }
29877
+ }
29878
+ assert(sum(leaves) === amount, "greedy_leaves: sum mismatch");
29879
+ return sorted(leaves);
29880
+ }
29881
+ function swapMinimizingLeaves(amount, multiplicity = 1) {
29882
+ const leaves = [];
29883
+ let remaining = amount;
29884
+ assert(multiplicity > 0, "multiplicity must be > 0");
29885
+ for (const leaf of DENOMINATIONS) {
29886
+ if (typeof leaf === "number" && leaf > 0) {
29887
+ for (let i = 0; i < multiplicity; i++) {
29888
+ if (remaining >= leaf) {
29889
+ remaining -= leaf;
29890
+ leaves.push(leaf);
29891
+ }
29892
+ }
29893
+ }
29894
+ }
29895
+ leaves.push(...greedyLeaves(remaining));
29896
+ assert(sum(leaves) === amount, "swap_minimizing_leaves: sum mismatch");
29897
+ return sorted(leaves);
29898
+ }
29899
+ var Swap = class {
29900
+ constructor(inLeaves, outLeaves) {
29901
+ __publicField(this, "inLeaves");
29902
+ __publicField(this, "outLeaves");
29903
+ this.inLeaves = [...inLeaves];
29904
+ this.outLeaves = [...outLeaves];
29905
+ assert(
29906
+ sum(this.inLeaves) === sum(this.outLeaves),
29907
+ "Swap in/out leaves must sum to same value for swap: " + this.toString()
29908
+ );
29909
+ }
29910
+ toString() {
29911
+ return `Swap(in=${JSON.stringify(this.inLeaves)}, out=${JSON.stringify(this.outLeaves)})`;
29912
+ }
29913
+ };
29914
+ function maximizeUnilateralExit(inputLeaves, maxLeavesPerSwap = 64) {
29915
+ const swaps = [];
29916
+ let batch = [];
29917
+ let leaves = sorted(inputLeaves);
29918
+ while (leaves.length > 0) {
29919
+ batch.push(leaves.shift());
29920
+ const target = greedyLeaves(sum(batch));
29921
+ if (batch.length >= maxLeavesPerSwap || target.length >= maxLeavesPerSwap) {
29922
+ if (!equals(target, batch)) {
29923
+ swaps.push(new Swap([...batch], target));
29924
+ }
29925
+ batch = [];
29926
+ }
29927
+ }
29928
+ if (batch.length > 0) {
29929
+ const target = greedyLeaves(sum(batch));
29930
+ if (!equals(target, batch)) {
29931
+ swaps.push(new Swap([...batch], target));
29932
+ }
29933
+ }
29934
+ return swaps;
29935
+ }
29936
+ function minimizeTransferSwap(inputLeaves, multiplicity = 1, maxLeavesPerSwap = 64) {
29937
+ const balance = sum(inputLeaves);
29938
+ const optimalLeaves = swapMinimizingLeaves(balance, multiplicity);
29939
+ const walletCounter = countOccurrences(inputLeaves);
29940
+ const optimalCounter = countOccurrences(optimalLeaves);
29941
+ const leavesToGive = subtractCounters(walletCounter, optimalCounter);
29942
+ const leavesToReceive = subtractCounters(optimalCounter, walletCounter);
29943
+ const leavesToGiveFlat = counterToFlatArray(leavesToGive);
29944
+ const leavesToReceiveFlat = counterToFlatArray(leavesToReceive);
29945
+ const swaps = [];
29946
+ let toGiveBatch = [];
29947
+ let toReceiveBatch = [];
29948
+ let give = [...leavesToGiveFlat];
29949
+ let receive = [...leavesToReceiveFlat];
29950
+ while (give.length > 0 || receive.length > 0) {
29951
+ if (sum(toGiveBatch) > sum(toReceiveBatch)) {
29952
+ if (receive.length === 0) break;
29953
+ toReceiveBatch.push(receive.shift());
29954
+ } else {
29955
+ if (give.length === 0) break;
29956
+ toGiveBatch.push(give.shift());
29957
+ }
29958
+ if (toGiveBatch.length > 0 && toReceiveBatch.length > 0 && sum(toGiveBatch) === sum(toReceiveBatch)) {
29959
+ if (toGiveBatch.length > maxLeavesPerSwap) {
29960
+ for (let i = 0; i < toGiveBatch.length; i += maxLeavesPerSwap) {
29961
+ const subset = toGiveBatch.slice(i, i + maxLeavesPerSwap);
29962
+ swaps.push(new Swap(subset, greedyLeaves(sum(subset))));
29963
+ }
29964
+ } else if (toReceiveBatch.length > maxLeavesPerSwap) {
29965
+ for (let cutoff = maxLeavesPerSwap; cutoff > 0; cutoff--) {
29966
+ const sumCut = sum(toReceiveBatch.slice(0, cutoff));
29967
+ const remainder = sum(toGiveBatch) - sumCut;
29968
+ const alternateBatch = [
29969
+ ...toReceiveBatch.slice(0, cutoff),
29970
+ ...greedyLeaves(remainder)
29971
+ ];
29972
+ if (alternateBatch.length <= maxLeavesPerSwap) {
29973
+ swaps.push(new Swap([...toGiveBatch], alternateBatch));
29974
+ break;
29975
+ }
29976
+ }
29977
+ } else {
29978
+ swaps.push(new Swap([...toGiveBatch], [...toReceiveBatch]));
29979
+ }
29980
+ toGiveBatch = [];
29981
+ toReceiveBatch = [];
29982
+ }
29983
+ }
29984
+ return swaps;
29985
+ }
29986
+ function shouldOptimize(inputLeaves, multiplicity = 1, maxLeavesPerSwap = 64) {
29987
+ if (multiplicity == 0) {
29988
+ const swaps = maximizeUnilateralExit(inputLeaves, maxLeavesPerSwap);
29989
+ const numInputs = sum(swaps.map((swap) => swap.inLeaves.length));
29990
+ const numOutputs = sum(swaps.map((swap) => swap.outLeaves.length));
29991
+ return numOutputs * 5 < numInputs;
29992
+ } else {
29993
+ const swaps = minimizeTransferSwap(
29994
+ inputLeaves,
29995
+ multiplicity,
29996
+ maxLeavesPerSwap
29997
+ );
29998
+ const inputCounter = countOccurrences(
29999
+ swaps.flatMap((swap) => swap.inLeaves)
30000
+ );
30001
+ const outputCounter = countOccurrences(
30002
+ swaps.flatMap((swap) => swap.outLeaves)
30003
+ );
30004
+ return Math.abs(inputCounter.size - outputCounter.size) > 1;
30005
+ }
30006
+ }
30007
+ function optimize(inputLeaves, multiplicity = 1, maxLeavesPerSwap = 64) {
30008
+ if (multiplicity == 0) {
30009
+ return maximizeUnilateralExit(inputLeaves, maxLeavesPerSwap);
30010
+ } else {
30011
+ return minimizeTransferSwap(inputLeaves, multiplicity, maxLeavesPerSwap);
30012
+ }
30013
+ }
30014
+
29704
30015
  // src/spark-wallet/spark-wallet.ts
29705
30016
  var SparkWallet = class extends EventEmitter {
29706
30017
  constructor(options, signerArg) {
@@ -29805,8 +30116,7 @@ var SparkWallet = class extends EventEmitter {
29805
30116
  if (event.transfer.transfer && !equalBytes6(senderIdentityPublicKey, receiverIdentityPublicKey)) {
29806
30117
  await this.claimTransfer({
29807
30118
  transfer: event.transfer.transfer,
29808
- emit: true,
29809
- optimize: true
30119
+ emit: true
29810
30120
  });
29811
30121
  }
29812
30122
  } else if (isDepositStreamEvent(event)) {
@@ -30097,73 +30407,84 @@ var SparkWallet = class extends EventEmitter {
30097
30407
  }
30098
30408
  return nodes;
30099
30409
  }
30100
- areLeavesInefficient() {
30101
- const totalAmount = this.getInternalBalance();
30102
- if (this.leaves.length <= 1) {
30103
- return false;
30410
+ async *optimizeLeaves(multiplicity = void 0) {
30411
+ const multiplicityValue = multiplicity ?? this.config.getOptimizationOptions().multiplicity ?? 0;
30412
+ if (multiplicityValue < 0) {
30413
+ throw new ValidationError("Multiplicity cannot be negative");
30414
+ } else if (multiplicityValue > 5) {
30415
+ throw new ValidationError("Multiplicity cannot be greater than 5");
30104
30416
  }
30105
- const nextLowerPowerOfTwo = 31 - Math.clz32(totalAmount);
30106
- let remainingAmount = totalAmount;
30107
- let optimalLeavesLength = 0;
30108
- for (let i = nextLowerPowerOfTwo; i >= 0; i--) {
30109
- const denomination = 2 ** i;
30110
- while (remainingAmount >= denomination) {
30111
- remainingAmount -= denomination;
30112
- optimalLeavesLength++;
30113
- }
30114
- }
30115
- return this.leaves.length > optimalLeavesLength * 5;
30116
- }
30117
- async optimizeLeaves() {
30118
- if (this.optimizationInProgress || !this.areLeavesInefficient()) {
30417
+ if (this.optimizationInProgress || !shouldOptimize(
30418
+ this.leaves.map((leaf) => leaf.value),
30419
+ multiplicityValue
30420
+ )) {
30119
30421
  return;
30120
30422
  }
30121
- await this.withLeaves(async () => {
30423
+ const controller = new AbortController();
30424
+ const release = await this.leavesMutex.acquire();
30425
+ try {
30122
30426
  this.optimizationInProgress = true;
30123
- try {
30124
- this.leaves = await this.getLeaves();
30125
- const swaps = maximizeUnilateralExit(
30126
- this.leaves.map((leaf) => leaf.value)
30127
- );
30128
- const valueToNodes = /* @__PURE__ */ new Map();
30129
- this.leaves.forEach((leaf) => {
30130
- if (!valueToNodes.has(leaf.value)) {
30131
- valueToNodes.set(leaf.value, []);
30132
- }
30133
- valueToNodes.get(leaf.value).push(leaf);
30134
- });
30135
- for (const swap of swaps) {
30136
- const leavesToSend = [];
30137
- for (const leafValue of swap.inLeaves) {
30138
- const nodes = valueToNodes.get(leafValue);
30139
- if (nodes && nodes.length > 0) {
30140
- const node = nodes.shift();
30141
- leavesToSend.push(node);
30142
- } else {
30143
- throw new InternalValidationError(
30144
- `No unused leaf with value ${leafValue} found in leaves`
30145
- );
30146
- }
30427
+ this.leaves = await this.getLeaves();
30428
+ const swaps = optimize(
30429
+ this.leaves.map((leaf) => leaf.value),
30430
+ multiplicityValue
30431
+ );
30432
+ if (swaps.length === 0) {
30433
+ return;
30434
+ }
30435
+ yield {
30436
+ step: 0,
30437
+ total: swaps.length,
30438
+ controller
30439
+ };
30440
+ const valueToNodes = /* @__PURE__ */ new Map();
30441
+ this.leaves.forEach((leaf) => {
30442
+ if (!valueToNodes.has(leaf.value)) {
30443
+ valueToNodes.set(leaf.value, []);
30444
+ }
30445
+ valueToNodes.get(leaf.value).push(leaf);
30446
+ });
30447
+ for (const swap of swaps) {
30448
+ if (controller.signal.aborted) {
30449
+ break;
30450
+ }
30451
+ const leavesToSend = [];
30452
+ for (const leafValue of swap.inLeaves) {
30453
+ const nodes = valueToNodes.get(leafValue);
30454
+ if (nodes && nodes.length > 0) {
30455
+ const node = nodes.shift();
30456
+ leavesToSend.push(node);
30457
+ } else {
30458
+ throw new InternalValidationError(
30459
+ `No unused leaf with value ${leafValue} found in leaves`
30460
+ );
30147
30461
  }
30148
- await this.requestLeavesSwap({
30149
- leaves: leavesToSend,
30150
- targetAmounts: swap.outLeaves
30151
- });
30152
30462
  }
30153
- this.leaves = await this.getLeaves();
30154
- } finally {
30155
- this.optimizationInProgress = false;
30463
+ await this.requestLeavesSwap({
30464
+ leaves: leavesToSend,
30465
+ targetAmounts: swap.outLeaves
30466
+ });
30467
+ yield {
30468
+ step: swaps.indexOf(swap) + 1,
30469
+ total: swaps.length,
30470
+ controller
30471
+ };
30156
30472
  }
30157
- });
30473
+ this.leaves = await this.getLeaves();
30474
+ } finally {
30475
+ this.optimizationInProgress = false;
30476
+ release();
30477
+ }
30158
30478
  }
30159
30479
  async syncWallet() {
30160
30480
  await this.syncTokenOutputs();
30161
30481
  let leaves = await this.getLeaves();
30162
30482
  leaves = await this.checkRenewLeaves(leaves);
30163
30483
  this.leaves = leaves;
30164
- this.optimizeLeaves().catch((e) => {
30165
- console.error("Failed to optimize leaves", e);
30166
- });
30484
+ if (this.config.getOptimizationOptions().auto) {
30485
+ for await (const _ of this.optimizeLeaves()) {
30486
+ }
30487
+ }
30167
30488
  }
30168
30489
  async withLeaves(operation) {
30169
30490
  const release = await this.leavesMutex.acquire();
@@ -30746,8 +31067,7 @@ var SparkWallet = class extends EventEmitter {
30746
31067
  }
30747
31068
  return await this.claimTransfer({
30748
31069
  transfer: incomingTransfer,
30749
- emit: false,
30750
- optimize: false
31070
+ emit: false
30751
31071
  });
30752
31072
  } catch (e) {
30753
31073
  console.error("[processSwapBatch] Error details:", {
@@ -31776,7 +32096,7 @@ var SparkWallet = class extends EventEmitter {
31776
32096
  transfer.id
31777
32097
  );
31778
32098
  if (pending) {
31779
- await this.claimTransfer({ transfer: pending, optimize: true });
32099
+ await this.claimTransfer({ transfer: pending });
31780
32100
  }
31781
32101
  }
31782
32102
  return {
@@ -31949,13 +32269,14 @@ var SparkWallet = class extends EventEmitter {
31949
32269
  return response.nodes;
31950
32270
  });
31951
32271
  }
31952
- async processClaimedTransferResults(result, transfer, emit, optimize) {
32272
+ async processClaimedTransferResults(result, transfer, emit) {
31953
32273
  result = await this.checkRenewLeaves(result);
31954
32274
  const existingIds = new Set(this.leaves.map((leaf) => leaf.id));
31955
32275
  const uniqueResults = result.filter((node) => !existingIds.has(node.id));
31956
32276
  this.leaves.push(...uniqueResults);
31957
- if (optimize && transfer.type !== 40 /* COUNTER_SWAP */) {
31958
- await this.optimizeLeaves();
32277
+ if (this.config.getOptimizationOptions().auto && transfer.type !== 40 /* COUNTER_SWAP */) {
32278
+ for await (const _ of this.optimizeLeaves()) {
32279
+ }
31959
32280
  }
31960
32281
  if (emit) {
31961
32282
  this.emit(
@@ -31974,8 +32295,7 @@ var SparkWallet = class extends EventEmitter {
31974
32295
  */
31975
32296
  async claimTransfer({
31976
32297
  transfer,
31977
- emit,
31978
- optimize
32298
+ emit
31979
32299
  }) {
31980
32300
  const onError = async (context) => {
31981
32301
  const error = context.error;
@@ -32020,12 +32340,7 @@ var SparkWallet = class extends EventEmitter {
32020
32340
  if (result.length === 0) {
32021
32341
  return [];
32022
32342
  }
32023
- return await this.processClaimedTransferResults(
32024
- result,
32025
- transfer,
32026
- emit,
32027
- optimize
32028
- );
32343
+ return await this.processClaimedTransferResults(result, transfer, emit);
32029
32344
  } catch (error) {
32030
32345
  console.warn(
32031
32346
  `Failed to claim transfer after all retries. Please try reinitializing your wallet in a few minutes. Transfer ID: ${transfer.id}`,
@@ -32058,7 +32373,7 @@ var SparkWallet = class extends EventEmitter {
32058
32373
  continue;
32059
32374
  }
32060
32375
  promises.push(
32061
- this.claimTransfer({ transfer, emit, optimize: true }).then(() => transfer.id).catch((error) => {
32376
+ this.claimTransfer({ transfer, emit }).then(() => transfer.id).catch((error) => {
32062
32377
  console.warn(`Failed to claim transfer ${transfer.id}:`, error);
32063
32378
  return null;
32064
32379
  })
@@ -34406,12 +34721,14 @@ export {
34406
34721
  NotImplementedError,
34407
34722
  RPCError,
34408
34723
  ReactNativeSparkSigner,
34724
+ ReactNativeTaprootSparkSigner,
34409
34725
  SparkSDKError,
34410
34726
  SparkSdkLogger,
34411
34727
  SparkWalletReactNative as SparkWallet,
34412
34728
  SparkWalletEvent,
34413
34729
  TEST_UNILATERAL_DIRECT_SEQUENCE,
34414
34730
  TEST_UNILATERAL_SEQUENCE,
34731
+ ReactNativeTaprootSparkSigner as TaprootSparkSigner,
34415
34732
  TokenTransactionService,
34416
34733
  ValidationError,
34417
34734
  WalletConfig,
@@ -34485,6 +34802,7 @@ export {
34485
34802
  getTxFromRawTxHex,
34486
34803
  getTxId,
34487
34804
  getTxIdNoReverse,
34805
+ hash160,
34488
34806
  isEphemeralAnchorOutput,
34489
34807
  isLegacySparkAddress,
34490
34808
  isSafeForNumber,