@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
@@ -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 };
@@ -18230,7 +18230,11 @@ var BASE_CONFIG = {
18230
18230
  console: {
18231
18231
  otel: false
18232
18232
  },
18233
- events: {}
18233
+ events: {},
18234
+ optimizationOptions: {
18235
+ auto: true,
18236
+ multiplicity: 0
18237
+ }
18234
18238
  };
18235
18239
  var LOCAL_WALLET_CONFIG = {
18236
18240
  ...BASE_CONFIG,
@@ -18441,6 +18445,9 @@ var WalletConfigService = class {
18441
18445
  getEvents() {
18442
18446
  return this.config.events;
18443
18447
  }
18448
+ getOptimizationOptions() {
18449
+ return this.config.optimizationOptions;
18450
+ }
18444
18451
  };
18445
18452
 
18446
18453
  // src/services/coop-exit.ts
@@ -27140,7 +27147,7 @@ var isWebExtension = (
27140
27147
  "chrome" in globalThis && globalThis.chrome.runtime?.id
27141
27148
  );
27142
27149
  var userAgent = "navigator" in globalThis ? globalThis.navigator.userAgent || "unknown-user-agent" : void 0;
27143
- var packageVersion = true ? "0.3.9" : "unknown";
27150
+ var packageVersion = true ? "0.4.0" : "unknown";
27144
27151
  var baseEnvStr = "unknown";
27145
27152
  if (isBun) {
27146
27153
  const bunVersion = "version" in globalThis.Bun ? globalThis.Bun.version : "unknown-version";
@@ -28612,6 +28619,14 @@ var BitcoinFaucet = class _BitcoinFaucet {
28612
28619
  async mineBlocks(numBlocks) {
28613
28620
  return await this.generateToAddress(numBlocks, this.miningAddress);
28614
28621
  }
28622
+ async mineBlocksAndWaitForMiningToComplete(numBlocks) {
28623
+ const startBlock = await this.getBlockCount();
28624
+ await this.mineBlocks(numBlocks);
28625
+ await this.waitForBlocksMined({
28626
+ startBlock,
28627
+ expectedIncrease: numBlocks
28628
+ });
28629
+ }
28615
28630
  async call(method, params) {
28616
28631
  try {
28617
28632
  const { fetch, Headers: Headers3 } = getFetch();
@@ -28664,15 +28679,50 @@ var BitcoinFaucet = class _BitcoinFaucet {
28664
28679
  async getBlock(blockHash) {
28665
28680
  return await this.call("getblock", [blockHash, 2]);
28666
28681
  }
28682
+ async getBlockCount() {
28683
+ return await this.call("getblockcount", []);
28684
+ }
28685
+ async waitForBlocksMined({
28686
+ startBlock,
28687
+ expectedIncrease,
28688
+ timeoutMs = 3e4,
28689
+ intervalMs = 5e3
28690
+ }) {
28691
+ const deadline = Date.now() + timeoutMs;
28692
+ await new Promise((r) => setTimeout(r, intervalMs));
28693
+ const start = startBlock;
28694
+ const target = start + expectedIncrease;
28695
+ while (Date.now() < deadline) {
28696
+ const currentBlock = await this.getBlockCount();
28697
+ if (currentBlock >= target) return currentBlock;
28698
+ await new Promise((r) => setTimeout(r, intervalMs));
28699
+ }
28700
+ throw new Error(
28701
+ `Timed out waiting for ${expectedIncrease} blocks (target height ${target})`
28702
+ );
28703
+ }
28667
28704
  async broadcastTx(txHex) {
28668
28705
  let response = await this.call("sendrawtransaction", [txHex, 0]);
28669
28706
  return response;
28670
28707
  }
28708
+ async submitPackage(txHexs) {
28709
+ let response = await this.call("submitpackage", [txHexs]);
28710
+ return response;
28711
+ }
28671
28712
  async getNewAddress() {
28672
28713
  const key = secp256k112.utils.randomPrivateKey();
28673
28714
  const pubKey = secp256k112.getPublicKey(key);
28674
28715
  return getP2TRAddressFromPublicKey(pubKey, 4 /* LOCAL */);
28675
28716
  }
28717
+ async getNewExternalWallet() {
28718
+ const key = secp256k112.utils.randomPrivateKey();
28719
+ const pubKey = secp256k112.getPublicKey(key);
28720
+ return {
28721
+ address: getP2TRAddressFromPublicKey(pubKey, 4 /* LOCAL */),
28722
+ key,
28723
+ pubKey
28724
+ };
28725
+ }
28676
28726
  async sendToAddress(address, amount, blocksToGenerate = 1) {
28677
28727
  const coin = await this.fund();
28678
28728
  if (!coin) {
@@ -28773,75 +28823,6 @@ function chunkArray(arr, size) {
28773
28823
  return chunks;
28774
28824
  }
28775
28825
 
28776
- // src/utils/optimize.ts
28777
- var DENOMINATIONS = Array.from({ length: 28 }, (_, i) => 2 ** i);
28778
- function assert(condition, message) {
28779
- if (!condition) {
28780
- throw new InternalValidationError(message || "Assertion failed");
28781
- }
28782
- }
28783
- function sum(arr) {
28784
- return arr.reduce((a, b) => a + b, 0);
28785
- }
28786
- function sorted(arr) {
28787
- return [...arr].sort((a, b) => a - b);
28788
- }
28789
- function equals(a, b) {
28790
- return a.length === b.length && a.every((val, index) => val === b[index]);
28791
- }
28792
- function greedyLeaves(amount) {
28793
- const leaves = [];
28794
- let remaining = amount;
28795
- for (let i = DENOMINATIONS.length - 1; i >= 0; i--) {
28796
- const leaf = DENOMINATIONS[i];
28797
- if (typeof leaf === "number" && leaf > 0) {
28798
- while (remaining >= leaf) {
28799
- remaining -= leaf;
28800
- leaves.push(leaf);
28801
- }
28802
- }
28803
- }
28804
- assert(sum(leaves) === amount, "greedy_leaves: sum mismatch");
28805
- return sorted(leaves);
28806
- }
28807
- var Swap = class {
28808
- inLeaves;
28809
- outLeaves;
28810
- constructor(inLeaves, outLeaves) {
28811
- this.inLeaves = [...inLeaves];
28812
- this.outLeaves = [...outLeaves];
28813
- assert(
28814
- sum(this.inLeaves) === sum(this.outLeaves),
28815
- "Swap in/out leaves must sum to same value for swap: " + this.toString()
28816
- );
28817
- }
28818
- toString() {
28819
- return `Swap(in=${JSON.stringify(this.inLeaves)}, out=${JSON.stringify(this.outLeaves)})`;
28820
- }
28821
- };
28822
- function maximizeUnilateralExit(inputLeaves, maxLeavesPerSwap = 64) {
28823
- const swaps = [];
28824
- let batch = [];
28825
- let leaves = sorted(inputLeaves);
28826
- while (leaves.length > 0) {
28827
- batch.push(leaves.shift());
28828
- const target = greedyLeaves(sum(batch));
28829
- if (batch.length >= maxLeavesPerSwap || target.length >= maxLeavesPerSwap) {
28830
- if (!equals(target, batch)) {
28831
- swaps.push(new Swap([...batch], target));
28832
- }
28833
- batch = [];
28834
- }
28835
- }
28836
- if (batch.length > 0) {
28837
- const target = greedyLeaves(sum(batch));
28838
- if (!equals(target, batch)) {
28839
- swaps.push(new Swap([...batch], target));
28840
- }
28841
- }
28842
- return swaps;
28843
- }
28844
-
28845
28826
  // src/utils/retry.ts
28846
28827
  var DEFAULT_RETRY_CONFIG = {
28847
28828
  maxAttempts: 5,
@@ -28918,6 +28899,199 @@ var SparkWalletEvent = {
28918
28899
  StreamReconnecting: "stream:reconnecting"
28919
28900
  };
28920
28901
 
28902
+ // src/utils/optimize.ts
28903
+ var DENOMINATIONS = Array.from({ length: 28 }, (_, i) => 2 ** i);
28904
+ function assert(condition, message) {
28905
+ if (!condition) {
28906
+ throw new InternalValidationError(message || "Assertion failed");
28907
+ }
28908
+ }
28909
+ function sum(arr) {
28910
+ return arr.reduce((a, b) => a + b, 0);
28911
+ }
28912
+ function sorted(arr) {
28913
+ return [...arr].sort((a, b) => a - b);
28914
+ }
28915
+ function equals(a, b) {
28916
+ return a.length === b.length && a.every((val, index) => val === b[index]);
28917
+ }
28918
+ function countOccurrences(arr) {
28919
+ const map = /* @__PURE__ */ new Map();
28920
+ for (const x of arr) {
28921
+ map.set(x, (map.get(x) ?? 0) + 1);
28922
+ }
28923
+ return map;
28924
+ }
28925
+ function subtractCounters(a, b) {
28926
+ const result = /* @__PURE__ */ new Map();
28927
+ for (const [key, value] of a.entries()) {
28928
+ const diff = value - (b.get(key) ?? 0);
28929
+ if (diff > 0) {
28930
+ result.set(key, diff);
28931
+ }
28932
+ }
28933
+ return result;
28934
+ }
28935
+ function counterToFlatArray(counter) {
28936
+ const arr = [];
28937
+ for (const [k, v] of Array.from(counter.entries()).sort(
28938
+ (a, b) => a[0] - b[0]
28939
+ )) {
28940
+ for (let i = 0; i < v; i++) {
28941
+ arr.push(k);
28942
+ }
28943
+ }
28944
+ return arr;
28945
+ }
28946
+ function greedyLeaves(amount) {
28947
+ const leaves = [];
28948
+ let remaining = amount;
28949
+ for (let i = DENOMINATIONS.length - 1; i >= 0; i--) {
28950
+ const leaf = DENOMINATIONS[i];
28951
+ if (typeof leaf === "number" && leaf > 0) {
28952
+ while (remaining >= leaf) {
28953
+ remaining -= leaf;
28954
+ leaves.push(leaf);
28955
+ }
28956
+ }
28957
+ }
28958
+ assert(sum(leaves) === amount, "greedy_leaves: sum mismatch");
28959
+ return sorted(leaves);
28960
+ }
28961
+ function swapMinimizingLeaves(amount, multiplicity = 1) {
28962
+ const leaves = [];
28963
+ let remaining = amount;
28964
+ assert(multiplicity > 0, "multiplicity must be > 0");
28965
+ for (const leaf of DENOMINATIONS) {
28966
+ if (typeof leaf === "number" && leaf > 0) {
28967
+ for (let i = 0; i < multiplicity; i++) {
28968
+ if (remaining >= leaf) {
28969
+ remaining -= leaf;
28970
+ leaves.push(leaf);
28971
+ }
28972
+ }
28973
+ }
28974
+ }
28975
+ leaves.push(...greedyLeaves(remaining));
28976
+ assert(sum(leaves) === amount, "swap_minimizing_leaves: sum mismatch");
28977
+ return sorted(leaves);
28978
+ }
28979
+ var Swap = class {
28980
+ inLeaves;
28981
+ outLeaves;
28982
+ constructor(inLeaves, outLeaves) {
28983
+ this.inLeaves = [...inLeaves];
28984
+ this.outLeaves = [...outLeaves];
28985
+ assert(
28986
+ sum(this.inLeaves) === sum(this.outLeaves),
28987
+ "Swap in/out leaves must sum to same value for swap: " + this.toString()
28988
+ );
28989
+ }
28990
+ toString() {
28991
+ return `Swap(in=${JSON.stringify(this.inLeaves)}, out=${JSON.stringify(this.outLeaves)})`;
28992
+ }
28993
+ };
28994
+ function maximizeUnilateralExit(inputLeaves, maxLeavesPerSwap = 64) {
28995
+ const swaps = [];
28996
+ let batch = [];
28997
+ let leaves = sorted(inputLeaves);
28998
+ while (leaves.length > 0) {
28999
+ batch.push(leaves.shift());
29000
+ const target = greedyLeaves(sum(batch));
29001
+ if (batch.length >= maxLeavesPerSwap || target.length >= maxLeavesPerSwap) {
29002
+ if (!equals(target, batch)) {
29003
+ swaps.push(new Swap([...batch], target));
29004
+ }
29005
+ batch = [];
29006
+ }
29007
+ }
29008
+ if (batch.length > 0) {
29009
+ const target = greedyLeaves(sum(batch));
29010
+ if (!equals(target, batch)) {
29011
+ swaps.push(new Swap([...batch], target));
29012
+ }
29013
+ }
29014
+ return swaps;
29015
+ }
29016
+ function minimizeTransferSwap(inputLeaves, multiplicity = 1, maxLeavesPerSwap = 64) {
29017
+ const balance = sum(inputLeaves);
29018
+ const optimalLeaves = swapMinimizingLeaves(balance, multiplicity);
29019
+ const walletCounter = countOccurrences(inputLeaves);
29020
+ const optimalCounter = countOccurrences(optimalLeaves);
29021
+ const leavesToGive = subtractCounters(walletCounter, optimalCounter);
29022
+ const leavesToReceive = subtractCounters(optimalCounter, walletCounter);
29023
+ const leavesToGiveFlat = counterToFlatArray(leavesToGive);
29024
+ const leavesToReceiveFlat = counterToFlatArray(leavesToReceive);
29025
+ const swaps = [];
29026
+ let toGiveBatch = [];
29027
+ let toReceiveBatch = [];
29028
+ let give = [...leavesToGiveFlat];
29029
+ let receive = [...leavesToReceiveFlat];
29030
+ while (give.length > 0 || receive.length > 0) {
29031
+ if (sum(toGiveBatch) > sum(toReceiveBatch)) {
29032
+ if (receive.length === 0) break;
29033
+ toReceiveBatch.push(receive.shift());
29034
+ } else {
29035
+ if (give.length === 0) break;
29036
+ toGiveBatch.push(give.shift());
29037
+ }
29038
+ if (toGiveBatch.length > 0 && toReceiveBatch.length > 0 && sum(toGiveBatch) === sum(toReceiveBatch)) {
29039
+ if (toGiveBatch.length > maxLeavesPerSwap) {
29040
+ for (let i = 0; i < toGiveBatch.length; i += maxLeavesPerSwap) {
29041
+ const subset = toGiveBatch.slice(i, i + maxLeavesPerSwap);
29042
+ swaps.push(new Swap(subset, greedyLeaves(sum(subset))));
29043
+ }
29044
+ } else if (toReceiveBatch.length > maxLeavesPerSwap) {
29045
+ for (let cutoff = maxLeavesPerSwap; cutoff > 0; cutoff--) {
29046
+ const sumCut = sum(toReceiveBatch.slice(0, cutoff));
29047
+ const remainder = sum(toGiveBatch) - sumCut;
29048
+ const alternateBatch = [
29049
+ ...toReceiveBatch.slice(0, cutoff),
29050
+ ...greedyLeaves(remainder)
29051
+ ];
29052
+ if (alternateBatch.length <= maxLeavesPerSwap) {
29053
+ swaps.push(new Swap([...toGiveBatch], alternateBatch));
29054
+ break;
29055
+ }
29056
+ }
29057
+ } else {
29058
+ swaps.push(new Swap([...toGiveBatch], [...toReceiveBatch]));
29059
+ }
29060
+ toGiveBatch = [];
29061
+ toReceiveBatch = [];
29062
+ }
29063
+ }
29064
+ return swaps;
29065
+ }
29066
+ function shouldOptimize(inputLeaves, multiplicity = 1, maxLeavesPerSwap = 64) {
29067
+ if (multiplicity == 0) {
29068
+ const swaps = maximizeUnilateralExit(inputLeaves, maxLeavesPerSwap);
29069
+ const numInputs = sum(swaps.map((swap) => swap.inLeaves.length));
29070
+ const numOutputs = sum(swaps.map((swap) => swap.outLeaves.length));
29071
+ return numOutputs * 5 < numInputs;
29072
+ } else {
29073
+ const swaps = minimizeTransferSwap(
29074
+ inputLeaves,
29075
+ multiplicity,
29076
+ maxLeavesPerSwap
29077
+ );
29078
+ const inputCounter = countOccurrences(
29079
+ swaps.flatMap((swap) => swap.inLeaves)
29080
+ );
29081
+ const outputCounter = countOccurrences(
29082
+ swaps.flatMap((swap) => swap.outLeaves)
29083
+ );
29084
+ return Math.abs(inputCounter.size - outputCounter.size) > 1;
29085
+ }
29086
+ }
29087
+ function optimize(inputLeaves, multiplicity = 1, maxLeavesPerSwap = 64) {
29088
+ if (multiplicity == 0) {
29089
+ return maximizeUnilateralExit(inputLeaves, maxLeavesPerSwap);
29090
+ } else {
29091
+ return minimizeTransferSwap(inputLeaves, multiplicity, maxLeavesPerSwap);
29092
+ }
29093
+ }
29094
+
28921
29095
  // src/spark-wallet/spark-wallet.ts
28922
29096
  var SparkWallet = class extends EventEmitter {
28923
29097
  config;
@@ -29022,8 +29196,7 @@ var SparkWallet = class extends EventEmitter {
29022
29196
  if (event.transfer.transfer && !equalBytes6(senderIdentityPublicKey, receiverIdentityPublicKey)) {
29023
29197
  await this.claimTransfer({
29024
29198
  transfer: event.transfer.transfer,
29025
- emit: true,
29026
- optimize: true
29199
+ emit: true
29027
29200
  });
29028
29201
  }
29029
29202
  } else if (isDepositStreamEvent(event)) {
@@ -29314,73 +29487,84 @@ var SparkWallet = class extends EventEmitter {
29314
29487
  }
29315
29488
  return nodes;
29316
29489
  }
29317
- areLeavesInefficient() {
29318
- const totalAmount = this.getInternalBalance();
29319
- if (this.leaves.length <= 1) {
29320
- return false;
29490
+ async *optimizeLeaves(multiplicity = void 0) {
29491
+ const multiplicityValue = multiplicity ?? this.config.getOptimizationOptions().multiplicity ?? 0;
29492
+ if (multiplicityValue < 0) {
29493
+ throw new ValidationError("Multiplicity cannot be negative");
29494
+ } else if (multiplicityValue > 5) {
29495
+ throw new ValidationError("Multiplicity cannot be greater than 5");
29321
29496
  }
29322
- const nextLowerPowerOfTwo = 31 - Math.clz32(totalAmount);
29323
- let remainingAmount = totalAmount;
29324
- let optimalLeavesLength = 0;
29325
- for (let i = nextLowerPowerOfTwo; i >= 0; i--) {
29326
- const denomination = 2 ** i;
29327
- while (remainingAmount >= denomination) {
29328
- remainingAmount -= denomination;
29329
- optimalLeavesLength++;
29330
- }
29331
- }
29332
- return this.leaves.length > optimalLeavesLength * 5;
29333
- }
29334
- async optimizeLeaves() {
29335
- if (this.optimizationInProgress || !this.areLeavesInefficient()) {
29497
+ if (this.optimizationInProgress || !shouldOptimize(
29498
+ this.leaves.map((leaf) => leaf.value),
29499
+ multiplicityValue
29500
+ )) {
29336
29501
  return;
29337
29502
  }
29338
- await this.withLeaves(async () => {
29503
+ const controller = new AbortController();
29504
+ const release = await this.leavesMutex.acquire();
29505
+ try {
29339
29506
  this.optimizationInProgress = true;
29340
- try {
29341
- this.leaves = await this.getLeaves();
29342
- const swaps = maximizeUnilateralExit(
29343
- this.leaves.map((leaf) => leaf.value)
29344
- );
29345
- const valueToNodes = /* @__PURE__ */ new Map();
29346
- this.leaves.forEach((leaf) => {
29347
- if (!valueToNodes.has(leaf.value)) {
29348
- valueToNodes.set(leaf.value, []);
29349
- }
29350
- valueToNodes.get(leaf.value).push(leaf);
29351
- });
29352
- for (const swap of swaps) {
29353
- const leavesToSend = [];
29354
- for (const leafValue of swap.inLeaves) {
29355
- const nodes = valueToNodes.get(leafValue);
29356
- if (nodes && nodes.length > 0) {
29357
- const node = nodes.shift();
29358
- leavesToSend.push(node);
29359
- } else {
29360
- throw new InternalValidationError(
29361
- `No unused leaf with value ${leafValue} found in leaves`
29362
- );
29363
- }
29507
+ this.leaves = await this.getLeaves();
29508
+ const swaps = optimize(
29509
+ this.leaves.map((leaf) => leaf.value),
29510
+ multiplicityValue
29511
+ );
29512
+ if (swaps.length === 0) {
29513
+ return;
29514
+ }
29515
+ yield {
29516
+ step: 0,
29517
+ total: swaps.length,
29518
+ controller
29519
+ };
29520
+ const valueToNodes = /* @__PURE__ */ new Map();
29521
+ this.leaves.forEach((leaf) => {
29522
+ if (!valueToNodes.has(leaf.value)) {
29523
+ valueToNodes.set(leaf.value, []);
29524
+ }
29525
+ valueToNodes.get(leaf.value).push(leaf);
29526
+ });
29527
+ for (const swap of swaps) {
29528
+ if (controller.signal.aborted) {
29529
+ break;
29530
+ }
29531
+ const leavesToSend = [];
29532
+ for (const leafValue of swap.inLeaves) {
29533
+ const nodes = valueToNodes.get(leafValue);
29534
+ if (nodes && nodes.length > 0) {
29535
+ const node = nodes.shift();
29536
+ leavesToSend.push(node);
29537
+ } else {
29538
+ throw new InternalValidationError(
29539
+ `No unused leaf with value ${leafValue} found in leaves`
29540
+ );
29364
29541
  }
29365
- await this.requestLeavesSwap({
29366
- leaves: leavesToSend,
29367
- targetAmounts: swap.outLeaves
29368
- });
29369
29542
  }
29370
- this.leaves = await this.getLeaves();
29371
- } finally {
29372
- this.optimizationInProgress = false;
29543
+ await this.requestLeavesSwap({
29544
+ leaves: leavesToSend,
29545
+ targetAmounts: swap.outLeaves
29546
+ });
29547
+ yield {
29548
+ step: swaps.indexOf(swap) + 1,
29549
+ total: swaps.length,
29550
+ controller
29551
+ };
29373
29552
  }
29374
- });
29553
+ this.leaves = await this.getLeaves();
29554
+ } finally {
29555
+ this.optimizationInProgress = false;
29556
+ release();
29557
+ }
29375
29558
  }
29376
29559
  async syncWallet() {
29377
29560
  await this.syncTokenOutputs();
29378
29561
  let leaves = await this.getLeaves();
29379
29562
  leaves = await this.checkRenewLeaves(leaves);
29380
29563
  this.leaves = leaves;
29381
- this.optimizeLeaves().catch((e) => {
29382
- console.error("Failed to optimize leaves", e);
29383
- });
29564
+ if (this.config.getOptimizationOptions().auto) {
29565
+ for await (const _ of this.optimizeLeaves()) {
29566
+ }
29567
+ }
29384
29568
  }
29385
29569
  async withLeaves(operation) {
29386
29570
  const release = await this.leavesMutex.acquire();
@@ -29963,8 +30147,7 @@ var SparkWallet = class extends EventEmitter {
29963
30147
  }
29964
30148
  return await this.claimTransfer({
29965
30149
  transfer: incomingTransfer,
29966
- emit: false,
29967
- optimize: false
30150
+ emit: false
29968
30151
  });
29969
30152
  } catch (e) {
29970
30153
  console.error("[processSwapBatch] Error details:", {
@@ -30993,7 +31176,7 @@ var SparkWallet = class extends EventEmitter {
30993
31176
  transfer.id
30994
31177
  );
30995
31178
  if (pending) {
30996
- await this.claimTransfer({ transfer: pending, optimize: true });
31179
+ await this.claimTransfer({ transfer: pending });
30997
31180
  }
30998
31181
  }
30999
31182
  return {
@@ -31166,13 +31349,14 @@ var SparkWallet = class extends EventEmitter {
31166
31349
  return response.nodes;
31167
31350
  });
31168
31351
  }
31169
- async processClaimedTransferResults(result, transfer, emit, optimize) {
31352
+ async processClaimedTransferResults(result, transfer, emit) {
31170
31353
  result = await this.checkRenewLeaves(result);
31171
31354
  const existingIds = new Set(this.leaves.map((leaf) => leaf.id));
31172
31355
  const uniqueResults = result.filter((node) => !existingIds.has(node.id));
31173
31356
  this.leaves.push(...uniqueResults);
31174
- if (optimize && transfer.type !== 40 /* COUNTER_SWAP */) {
31175
- await this.optimizeLeaves();
31357
+ if (this.config.getOptimizationOptions().auto && transfer.type !== 40 /* COUNTER_SWAP */) {
31358
+ for await (const _ of this.optimizeLeaves()) {
31359
+ }
31176
31360
  }
31177
31361
  if (emit) {
31178
31362
  this.emit(
@@ -31191,8 +31375,7 @@ var SparkWallet = class extends EventEmitter {
31191
31375
  */
31192
31376
  async claimTransfer({
31193
31377
  transfer,
31194
- emit,
31195
- optimize
31378
+ emit
31196
31379
  }) {
31197
31380
  const onError = async (context) => {
31198
31381
  const error = context.error;
@@ -31237,12 +31420,7 @@ var SparkWallet = class extends EventEmitter {
31237
31420
  if (result.length === 0) {
31238
31421
  return [];
31239
31422
  }
31240
- return await this.processClaimedTransferResults(
31241
- result,
31242
- transfer,
31243
- emit,
31244
- optimize
31245
- );
31423
+ return await this.processClaimedTransferResults(result, transfer, emit);
31246
31424
  } catch (error) {
31247
31425
  console.warn(
31248
31426
  `Failed to claim transfer after all retries. Please try reinitializing your wallet in a few minutes. Transfer ID: ${transfer.id}`,
@@ -31275,7 +31453,7 @@ var SparkWallet = class extends EventEmitter {
31275
31453
  continue;
31276
31454
  }
31277
31455
  promises.push(
31278
- this.claimTransfer({ transfer, emit, optimize: true }).then(() => transfer.id).catch((error) => {
31456
+ this.claimTransfer({ transfer, emit }).then(() => transfer.id).catch((error) => {
31279
31457
  console.warn(`Failed to claim transfer ${transfer.id}:`, error);
31280
31458
  return null;
31281
31459
  })
@@ -34315,6 +34493,7 @@ export {
34315
34493
  getTxFromRawTxHex,
34316
34494
  getTxId,
34317
34495
  getTxIdNoReverse,
34496
+ hash160,
34318
34497
  isEphemeralAnchorOutput,
34319
34498
  isLegacySparkAddress,
34320
34499
  isSafeForNumber,
@@ -3,7 +3,7 @@ import {
3
3
  NetworkError,
4
4
  SparkWallet,
5
5
  clientEnv
6
- } from "./chunk-O4C4HGQL.js";
6
+ } from "./chunk-G3LHXHF3.js";
7
7
 
8
8
  // src/services/connection/connection.browser.ts
9
9
  import {