@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
@@ -1263,12 +1263,14 @@ __export(index_react_native_exports, {
1263
1263
  NotImplementedError: () => NotImplementedError,
1264
1264
  RPCError: () => RPCError,
1265
1265
  ReactNativeSparkSigner: () => ReactNativeSparkSigner,
1266
+ ReactNativeTaprootSparkSigner: () => ReactNativeTaprootSparkSigner,
1266
1267
  SparkSDKError: () => SparkSDKError,
1267
1268
  SparkSdkLogger: () => SparkSdkLogger,
1268
1269
  SparkWallet: () => SparkWalletReactNative,
1269
1270
  SparkWalletEvent: () => SparkWalletEvent,
1270
1271
  TEST_UNILATERAL_DIRECT_SEQUENCE: () => TEST_UNILATERAL_DIRECT_SEQUENCE,
1271
1272
  TEST_UNILATERAL_SEQUENCE: () => TEST_UNILATERAL_SEQUENCE,
1273
+ TaprootSparkSigner: () => ReactNativeTaprootSparkSigner,
1272
1274
  TokenTransactionService: () => TokenTransactionService,
1273
1275
  ValidationError: () => ValidationError,
1274
1276
  WalletConfig: () => WalletConfig,
@@ -1342,6 +1344,7 @@ __export(index_react_native_exports, {
1342
1344
  getTxFromRawTxHex: () => getTxFromRawTxHex,
1343
1345
  getTxId: () => getTxId,
1344
1346
  getTxIdNoReverse: () => getTxIdNoReverse,
1347
+ hash160: () => hash160,
1345
1348
  isEphemeralAnchorOutput: () => isEphemeralAnchorOutput,
1346
1349
  isLegacySparkAddress: () => isLegacySparkAddress,
1347
1350
  isSafeForNumber: () => isSafeForNumber,
@@ -18499,7 +18502,11 @@ var BASE_CONFIG = {
18499
18502
  console: {
18500
18503
  otel: false
18501
18504
  },
18502
- events: {}
18505
+ events: {},
18506
+ optimizationOptions: {
18507
+ auto: true,
18508
+ multiplicity: 0
18509
+ }
18503
18510
  };
18504
18511
  var LOCAL_WALLET_CONFIG = {
18505
18512
  ...BASE_CONFIG,
@@ -21165,7 +21172,7 @@ var isWebExtension = (
21165
21172
  "chrome" in globalThis && globalThis.chrome.runtime?.id
21166
21173
  );
21167
21174
  var userAgent = "navigator" in globalThis ? globalThis.navigator.userAgent || "unknown-user-agent" : void 0;
21168
- var packageVersion = true ? "0.3.9" : "unknown";
21175
+ var packageVersion = true ? "0.4.0" : "unknown";
21169
21176
  var baseEnvStr = "unknown";
21170
21177
  if (isBun) {
21171
21178
  const bunVersion = "version" in globalThis.Bun ? globalThis.Bun.version : "unknown-version";
@@ -21244,6 +21251,63 @@ var DefaultSparkKeysGenerator = class {
21244
21251
  };
21245
21252
  }
21246
21253
  };
21254
+ var TaprootOutputKeysGenerator = class {
21255
+ constructor(useAddressIndex = false) {
21256
+ this.useAddressIndex = useAddressIndex;
21257
+ }
21258
+ async deriveKeysFromSeed(seed, accountNumber) {
21259
+ const hdkey = import_bip32.HDKey.fromMasterSeed(seed);
21260
+ if (!hdkey.privateKey || !hdkey.publicKey) {
21261
+ throw new ValidationError("Failed to derive keys from seed", {
21262
+ field: "hdkey",
21263
+ value: seed
21264
+ });
21265
+ }
21266
+ const derivationPath = this.useAddressIndex ? `m/86'/0'/0'/0/${accountNumber}` : `m/86'/0'/${accountNumber}'/0/0`;
21267
+ const taprootInternalKey = hdkey.derive(derivationPath);
21268
+ let tweakedPrivateKey = (0, import_utils15.taprootTweakPrivKey)(taprootInternalKey.privateKey);
21269
+ let tweakedPublicKey = import_secp256k18.secp256k1.getPublicKey(tweakedPrivateKey);
21270
+ if (tweakedPublicKey[0] === 3) {
21271
+ tweakedPrivateKey = (0, import_secp256k17.privateNegate)(tweakedPrivateKey);
21272
+ tweakedPublicKey = import_secp256k18.secp256k1.getPublicKey(tweakedPrivateKey);
21273
+ }
21274
+ const identityKey = {
21275
+ publicKey: tweakedPublicKey,
21276
+ privateKey: tweakedPrivateKey
21277
+ };
21278
+ const signingKey = hdkey.derive(`${derivationPath}/1'`);
21279
+ const depositKey = hdkey.derive(`${derivationPath}/2'`);
21280
+ const staticDepositKey = hdkey.derive(`${derivationPath}/3'`);
21281
+ if (!signingKey.privateKey || !signingKey.publicKey || !depositKey.privateKey || !depositKey.publicKey || !staticDepositKey.privateKey || !staticDepositKey.publicKey) {
21282
+ throw new ValidationError(
21283
+ "Failed to derive all required keys from seed",
21284
+ {
21285
+ field: "derivedKeys"
21286
+ }
21287
+ );
21288
+ }
21289
+ return {
21290
+ identityKey: {
21291
+ privateKey: identityKey.privateKey,
21292
+ publicKey: identityKey.publicKey
21293
+ },
21294
+ signingHDKey: {
21295
+ hdKey: signingKey,
21296
+ privateKey: signingKey.privateKey,
21297
+ publicKey: signingKey.publicKey
21298
+ },
21299
+ depositKey: {
21300
+ privateKey: depositKey.privateKey,
21301
+ publicKey: depositKey.publicKey
21302
+ },
21303
+ staticDepositHDKey: {
21304
+ hdKey: staticDepositKey,
21305
+ privateKey: staticDepositKey.privateKey,
21306
+ publicKey: staticDepositKey.publicKey
21307
+ }
21308
+ };
21309
+ }
21310
+ };
21247
21311
  var DefaultSparkSigner = class {
21248
21312
  constructor({
21249
21313
  sparkKeysGenerator
@@ -21562,6 +21626,13 @@ var DefaultSparkSigner = class {
21562
21626
  tx.signIdx(privateKey, index);
21563
21627
  }
21564
21628
  };
21629
+ var TaprootSparkSigner = class extends DefaultSparkSigner {
21630
+ constructor(useAddressIndex = false) {
21631
+ super({
21632
+ sparkKeysGenerator: new TaprootOutputKeysGenerator(useAddressIndex)
21633
+ });
21634
+ }
21635
+ };
21565
21636
 
21566
21637
  // src/signer/signer.react-native.ts
21567
21638
  var ReactNativeSparkSigner = class extends DefaultSparkSigner {
@@ -21625,6 +21696,70 @@ var ReactNativeSparkSigner = class extends DefaultSparkSigner {
21625
21696
  });
21626
21697
  }
21627
21698
  };
21699
+ var ReactNativeTaprootSparkSigner = class extends TaprootSparkSigner {
21700
+ constructor(useAddressIndex = false) {
21701
+ super(useAddressIndex);
21702
+ }
21703
+ async signFrost({
21704
+ message,
21705
+ keyDerivation,
21706
+ publicKey,
21707
+ verifyingKey,
21708
+ selfCommitment,
21709
+ statechainCommitments,
21710
+ adaptorPubKey
21711
+ }) {
21712
+ const signingPrivateKey = await this.getSigningPrivateKeyFromDerivation(keyDerivation);
21713
+ if (!signingPrivateKey) {
21714
+ throw new ValidationError("Private key not found for public key", {
21715
+ field: "privateKey"
21716
+ });
21717
+ }
21718
+ const commitment = selfCommitment.commitment;
21719
+ const nonce = this.commitmentToNonceMap.get(commitment);
21720
+ if (!nonce) {
21721
+ throw new ValidationError("Nonce not found for commitment", {
21722
+ field: "nonce"
21723
+ });
21724
+ }
21725
+ const keyPackage = {
21726
+ secretKey: signingPrivateKey,
21727
+ publicKey,
21728
+ verifyingKey
21729
+ };
21730
+ return NativeSparkFrost.signFrost({
21731
+ message,
21732
+ keyPackage,
21733
+ nonce,
21734
+ selfCommitment: commitment,
21735
+ statechainCommitments,
21736
+ adaptorPubKey
21737
+ });
21738
+ }
21739
+ async aggregateFrost({
21740
+ message,
21741
+ publicKey,
21742
+ verifyingKey,
21743
+ selfCommitment,
21744
+ statechainCommitments,
21745
+ adaptorPubKey,
21746
+ selfSignature,
21747
+ statechainSignatures,
21748
+ statechainPublicKeys
21749
+ }) {
21750
+ return NativeSparkFrost.aggregateFrost({
21751
+ message,
21752
+ statechainSignatures,
21753
+ statechainPublicKeys,
21754
+ verifyingKey,
21755
+ statechainCommitments,
21756
+ selfCommitment: selfCommitment.commitment,
21757
+ selfPublicKey: publicKey,
21758
+ selfSignature,
21759
+ adaptorPubKey
21760
+ });
21761
+ }
21762
+ };
21628
21763
 
21629
21764
  // src/spark-wallet/spark-wallet.react-native.ts
21630
21765
  init_buffer();
@@ -21766,7 +21901,16 @@ function headersToMetadata(headers) {
21766
21901
  const parts = line.split(": ");
21767
21902
  const header = parts.shift() ?? "";
21768
21903
  const value = parts.join(": ");
21769
- metadata.set(header, value);
21904
+ if (header.endsWith("-bin")) {
21905
+ try {
21906
+ metadata.set(header, import_js_base64.Base64.toUint8Array(value));
21907
+ } catch (e) {
21908
+ console.warn(`Failed to decode binary metadata ${header}:`, e);
21909
+ metadata.set(header, value);
21910
+ }
21911
+ } else {
21912
+ metadata.set(header, value);
21913
+ }
21770
21914
  });
21771
21915
  return metadata;
21772
21916
  }
@@ -21792,7 +21936,7 @@ function getStatusFromHttpCode(statusCode) {
21792
21936
  }
21793
21937
  }
21794
21938
  function getErrorDetailsFromHttpResponse(statusCode, responseText) {
21795
- return `Received HTTP ${statusCode} response: ` + (responseText.length > 1e3 ? responseText.slice(0, 1e3) + "... (truncated)" : responseText);
21939
+ return `Received HTTP ${statusCode} response: ` + (responseText?.length > 1e3 ? responseText?.slice(0, 1e3) + "... (truncated)" : responseText);
21796
21940
  }
21797
21941
 
21798
21942
  // src/spark-wallet/spark-wallet.ts
@@ -23051,6 +23195,9 @@ var WalletConfigService = class {
23051
23195
  getEvents() {
23052
23196
  return this.config.events;
23053
23197
  }
23198
+ getOptimizationOptions() {
23199
+ return this.config.optimizationOptions;
23200
+ }
23054
23201
  };
23055
23202
 
23056
23203
  // src/services/coop-exit.ts
@@ -30920,6 +31067,14 @@ var _BitcoinFaucet = class _BitcoinFaucet {
30920
31067
  async mineBlocks(numBlocks) {
30921
31068
  return await this.generateToAddress(numBlocks, this.miningAddress);
30922
31069
  }
31070
+ async mineBlocksAndWaitForMiningToComplete(numBlocks) {
31071
+ const startBlock = await this.getBlockCount();
31072
+ await this.mineBlocks(numBlocks);
31073
+ await this.waitForBlocksMined({
31074
+ startBlock,
31075
+ expectedIncrease: numBlocks
31076
+ });
31077
+ }
30923
31078
  async call(method, params) {
30924
31079
  try {
30925
31080
  const { fetch, Headers: Headers2 } = getFetch();
@@ -30972,15 +31127,50 @@ var _BitcoinFaucet = class _BitcoinFaucet {
30972
31127
  async getBlock(blockHash) {
30973
31128
  return await this.call("getblock", [blockHash, 2]);
30974
31129
  }
31130
+ async getBlockCount() {
31131
+ return await this.call("getblockcount", []);
31132
+ }
31133
+ async waitForBlocksMined({
31134
+ startBlock,
31135
+ expectedIncrease,
31136
+ timeoutMs = 3e4,
31137
+ intervalMs = 5e3
31138
+ }) {
31139
+ const deadline = Date.now() + timeoutMs;
31140
+ await new Promise((r) => setTimeout(r, intervalMs));
31141
+ const start = startBlock;
31142
+ const target = start + expectedIncrease;
31143
+ while (Date.now() < deadline) {
31144
+ const currentBlock = await this.getBlockCount();
31145
+ if (currentBlock >= target) return currentBlock;
31146
+ await new Promise((r) => setTimeout(r, intervalMs));
31147
+ }
31148
+ throw new Error(
31149
+ `Timed out waiting for ${expectedIncrease} blocks (target height ${target})`
31150
+ );
31151
+ }
30975
31152
  async broadcastTx(txHex) {
30976
31153
  let response = await this.call("sendrawtransaction", [txHex, 0]);
30977
31154
  return response;
30978
31155
  }
31156
+ async submitPackage(txHexs) {
31157
+ let response = await this.call("submitpackage", [txHexs]);
31158
+ return response;
31159
+ }
30979
31160
  async getNewAddress() {
30980
31161
  const key = import_secp256k113.secp256k1.utils.randomPrivateKey();
30981
31162
  const pubKey = import_secp256k113.secp256k1.getPublicKey(key);
30982
31163
  return getP2TRAddressFromPublicKey(pubKey, 4 /* LOCAL */);
30983
31164
  }
31165
+ async getNewExternalWallet() {
31166
+ const key = import_secp256k113.secp256k1.utils.randomPrivateKey();
31167
+ const pubKey = import_secp256k113.secp256k1.getPublicKey(key);
31168
+ return {
31169
+ address: getP2TRAddressFromPublicKey(pubKey, 4 /* LOCAL */),
31170
+ key,
31171
+ pubKey
31172
+ };
31173
+ }
30984
31174
  async sendToAddress(address, amount, blocksToGenerate = 1) {
30985
31175
  const coin = await this.fund();
30986
31176
  if (!coin) {
@@ -31039,76 +31229,6 @@ function chunkArray(arr, size) {
31039
31229
  return chunks;
31040
31230
  }
31041
31231
 
31042
- // src/utils/optimize.ts
31043
- init_buffer();
31044
- var DENOMINATIONS = Array.from({ length: 28 }, (_, i) => 2 ** i);
31045
- function assert(condition, message) {
31046
- if (!condition) {
31047
- throw new InternalValidationError(message || "Assertion failed");
31048
- }
31049
- }
31050
- function sum(arr) {
31051
- return arr.reduce((a, b) => a + b, 0);
31052
- }
31053
- function sorted(arr) {
31054
- return [...arr].sort((a, b) => a - b);
31055
- }
31056
- function equals(a, b) {
31057
- return a.length === b.length && a.every((val, index) => val === b[index]);
31058
- }
31059
- function greedyLeaves(amount) {
31060
- const leaves = [];
31061
- let remaining = amount;
31062
- for (let i = DENOMINATIONS.length - 1; i >= 0; i--) {
31063
- const leaf = DENOMINATIONS[i];
31064
- if (typeof leaf === "number" && leaf > 0) {
31065
- while (remaining >= leaf) {
31066
- remaining -= leaf;
31067
- leaves.push(leaf);
31068
- }
31069
- }
31070
- }
31071
- assert(sum(leaves) === amount, "greedy_leaves: sum mismatch");
31072
- return sorted(leaves);
31073
- }
31074
- var Swap = class {
31075
- constructor(inLeaves, outLeaves) {
31076
- __publicField(this, "inLeaves");
31077
- __publicField(this, "outLeaves");
31078
- this.inLeaves = [...inLeaves];
31079
- this.outLeaves = [...outLeaves];
31080
- assert(
31081
- sum(this.inLeaves) === sum(this.outLeaves),
31082
- "Swap in/out leaves must sum to same value for swap: " + this.toString()
31083
- );
31084
- }
31085
- toString() {
31086
- return `Swap(in=${JSON.stringify(this.inLeaves)}, out=${JSON.stringify(this.outLeaves)})`;
31087
- }
31088
- };
31089
- function maximizeUnilateralExit(inputLeaves, maxLeavesPerSwap = 64) {
31090
- const swaps = [];
31091
- let batch = [];
31092
- let leaves = sorted(inputLeaves);
31093
- while (leaves.length > 0) {
31094
- batch.push(leaves.shift());
31095
- const target = greedyLeaves(sum(batch));
31096
- if (batch.length >= maxLeavesPerSwap || target.length >= maxLeavesPerSwap) {
31097
- if (!equals(target, batch)) {
31098
- swaps.push(new Swap([...batch], target));
31099
- }
31100
- batch = [];
31101
- }
31102
- }
31103
- if (batch.length > 0) {
31104
- const target = greedyLeaves(sum(batch));
31105
- if (!equals(target, batch)) {
31106
- swaps.push(new Swap([...batch], target));
31107
- }
31108
- }
31109
- return swaps;
31110
- }
31111
-
31112
31232
  // src/utils/retry.ts
31113
31233
  init_buffer();
31114
31234
  var DEFAULT_RETRY_CONFIG = {
@@ -31187,6 +31307,200 @@ var SparkWalletEvent = {
31187
31307
  StreamReconnecting: "stream:reconnecting"
31188
31308
  };
31189
31309
 
31310
+ // src/utils/optimize.ts
31311
+ init_buffer();
31312
+ var DENOMINATIONS = Array.from({ length: 28 }, (_, i) => 2 ** i);
31313
+ function assert(condition, message) {
31314
+ if (!condition) {
31315
+ throw new InternalValidationError(message || "Assertion failed");
31316
+ }
31317
+ }
31318
+ function sum(arr) {
31319
+ return arr.reduce((a, b) => a + b, 0);
31320
+ }
31321
+ function sorted(arr) {
31322
+ return [...arr].sort((a, b) => a - b);
31323
+ }
31324
+ function equals(a, b) {
31325
+ return a.length === b.length && a.every((val, index) => val === b[index]);
31326
+ }
31327
+ function countOccurrences(arr) {
31328
+ const map = /* @__PURE__ */ new Map();
31329
+ for (const x of arr) {
31330
+ map.set(x, (map.get(x) ?? 0) + 1);
31331
+ }
31332
+ return map;
31333
+ }
31334
+ function subtractCounters(a, b) {
31335
+ const result = /* @__PURE__ */ new Map();
31336
+ for (const [key, value] of a.entries()) {
31337
+ const diff = value - (b.get(key) ?? 0);
31338
+ if (diff > 0) {
31339
+ result.set(key, diff);
31340
+ }
31341
+ }
31342
+ return result;
31343
+ }
31344
+ function counterToFlatArray(counter) {
31345
+ const arr = [];
31346
+ for (const [k, v] of Array.from(counter.entries()).sort(
31347
+ (a, b) => a[0] - b[0]
31348
+ )) {
31349
+ for (let i = 0; i < v; i++) {
31350
+ arr.push(k);
31351
+ }
31352
+ }
31353
+ return arr;
31354
+ }
31355
+ function greedyLeaves(amount) {
31356
+ const leaves = [];
31357
+ let remaining = amount;
31358
+ for (let i = DENOMINATIONS.length - 1; i >= 0; i--) {
31359
+ const leaf = DENOMINATIONS[i];
31360
+ if (typeof leaf === "number" && leaf > 0) {
31361
+ while (remaining >= leaf) {
31362
+ remaining -= leaf;
31363
+ leaves.push(leaf);
31364
+ }
31365
+ }
31366
+ }
31367
+ assert(sum(leaves) === amount, "greedy_leaves: sum mismatch");
31368
+ return sorted(leaves);
31369
+ }
31370
+ function swapMinimizingLeaves(amount, multiplicity = 1) {
31371
+ const leaves = [];
31372
+ let remaining = amount;
31373
+ assert(multiplicity > 0, "multiplicity must be > 0");
31374
+ for (const leaf of DENOMINATIONS) {
31375
+ if (typeof leaf === "number" && leaf > 0) {
31376
+ for (let i = 0; i < multiplicity; i++) {
31377
+ if (remaining >= leaf) {
31378
+ remaining -= leaf;
31379
+ leaves.push(leaf);
31380
+ }
31381
+ }
31382
+ }
31383
+ }
31384
+ leaves.push(...greedyLeaves(remaining));
31385
+ assert(sum(leaves) === amount, "swap_minimizing_leaves: sum mismatch");
31386
+ return sorted(leaves);
31387
+ }
31388
+ var Swap = class {
31389
+ constructor(inLeaves, outLeaves) {
31390
+ __publicField(this, "inLeaves");
31391
+ __publicField(this, "outLeaves");
31392
+ this.inLeaves = [...inLeaves];
31393
+ this.outLeaves = [...outLeaves];
31394
+ assert(
31395
+ sum(this.inLeaves) === sum(this.outLeaves),
31396
+ "Swap in/out leaves must sum to same value for swap: " + this.toString()
31397
+ );
31398
+ }
31399
+ toString() {
31400
+ return `Swap(in=${JSON.stringify(this.inLeaves)}, out=${JSON.stringify(this.outLeaves)})`;
31401
+ }
31402
+ };
31403
+ function maximizeUnilateralExit(inputLeaves, maxLeavesPerSwap = 64) {
31404
+ const swaps = [];
31405
+ let batch = [];
31406
+ let leaves = sorted(inputLeaves);
31407
+ while (leaves.length > 0) {
31408
+ batch.push(leaves.shift());
31409
+ const target = greedyLeaves(sum(batch));
31410
+ if (batch.length >= maxLeavesPerSwap || target.length >= maxLeavesPerSwap) {
31411
+ if (!equals(target, batch)) {
31412
+ swaps.push(new Swap([...batch], target));
31413
+ }
31414
+ batch = [];
31415
+ }
31416
+ }
31417
+ if (batch.length > 0) {
31418
+ const target = greedyLeaves(sum(batch));
31419
+ if (!equals(target, batch)) {
31420
+ swaps.push(new Swap([...batch], target));
31421
+ }
31422
+ }
31423
+ return swaps;
31424
+ }
31425
+ function minimizeTransferSwap(inputLeaves, multiplicity = 1, maxLeavesPerSwap = 64) {
31426
+ const balance = sum(inputLeaves);
31427
+ const optimalLeaves = swapMinimizingLeaves(balance, multiplicity);
31428
+ const walletCounter = countOccurrences(inputLeaves);
31429
+ const optimalCounter = countOccurrences(optimalLeaves);
31430
+ const leavesToGive = subtractCounters(walletCounter, optimalCounter);
31431
+ const leavesToReceive = subtractCounters(optimalCounter, walletCounter);
31432
+ const leavesToGiveFlat = counterToFlatArray(leavesToGive);
31433
+ const leavesToReceiveFlat = counterToFlatArray(leavesToReceive);
31434
+ const swaps = [];
31435
+ let toGiveBatch = [];
31436
+ let toReceiveBatch = [];
31437
+ let give = [...leavesToGiveFlat];
31438
+ let receive = [...leavesToReceiveFlat];
31439
+ while (give.length > 0 || receive.length > 0) {
31440
+ if (sum(toGiveBatch) > sum(toReceiveBatch)) {
31441
+ if (receive.length === 0) break;
31442
+ toReceiveBatch.push(receive.shift());
31443
+ } else {
31444
+ if (give.length === 0) break;
31445
+ toGiveBatch.push(give.shift());
31446
+ }
31447
+ if (toGiveBatch.length > 0 && toReceiveBatch.length > 0 && sum(toGiveBatch) === sum(toReceiveBatch)) {
31448
+ if (toGiveBatch.length > maxLeavesPerSwap) {
31449
+ for (let i = 0; i < toGiveBatch.length; i += maxLeavesPerSwap) {
31450
+ const subset = toGiveBatch.slice(i, i + maxLeavesPerSwap);
31451
+ swaps.push(new Swap(subset, greedyLeaves(sum(subset))));
31452
+ }
31453
+ } else if (toReceiveBatch.length > maxLeavesPerSwap) {
31454
+ for (let cutoff = maxLeavesPerSwap; cutoff > 0; cutoff--) {
31455
+ const sumCut = sum(toReceiveBatch.slice(0, cutoff));
31456
+ const remainder = sum(toGiveBatch) - sumCut;
31457
+ const alternateBatch = [
31458
+ ...toReceiveBatch.slice(0, cutoff),
31459
+ ...greedyLeaves(remainder)
31460
+ ];
31461
+ if (alternateBatch.length <= maxLeavesPerSwap) {
31462
+ swaps.push(new Swap([...toGiveBatch], alternateBatch));
31463
+ break;
31464
+ }
31465
+ }
31466
+ } else {
31467
+ swaps.push(new Swap([...toGiveBatch], [...toReceiveBatch]));
31468
+ }
31469
+ toGiveBatch = [];
31470
+ toReceiveBatch = [];
31471
+ }
31472
+ }
31473
+ return swaps;
31474
+ }
31475
+ function shouldOptimize(inputLeaves, multiplicity = 1, maxLeavesPerSwap = 64) {
31476
+ if (multiplicity == 0) {
31477
+ const swaps = maximizeUnilateralExit(inputLeaves, maxLeavesPerSwap);
31478
+ const numInputs = sum(swaps.map((swap) => swap.inLeaves.length));
31479
+ const numOutputs = sum(swaps.map((swap) => swap.outLeaves.length));
31480
+ return numOutputs * 5 < numInputs;
31481
+ } else {
31482
+ const swaps = minimizeTransferSwap(
31483
+ inputLeaves,
31484
+ multiplicity,
31485
+ maxLeavesPerSwap
31486
+ );
31487
+ const inputCounter = countOccurrences(
31488
+ swaps.flatMap((swap) => swap.inLeaves)
31489
+ );
31490
+ const outputCounter = countOccurrences(
31491
+ swaps.flatMap((swap) => swap.outLeaves)
31492
+ );
31493
+ return Math.abs(inputCounter.size - outputCounter.size) > 1;
31494
+ }
31495
+ }
31496
+ function optimize(inputLeaves, multiplicity = 1, maxLeavesPerSwap = 64) {
31497
+ if (multiplicity == 0) {
31498
+ return maximizeUnilateralExit(inputLeaves, maxLeavesPerSwap);
31499
+ } else {
31500
+ return minimizeTransferSwap(inputLeaves, multiplicity, maxLeavesPerSwap);
31501
+ }
31502
+ }
31503
+
31190
31504
  // src/spark-wallet/spark-wallet.ts
31191
31505
  var SparkWallet = class extends import_eventemitter3.EventEmitter {
31192
31506
  constructor(options, signerArg) {
@@ -31291,8 +31605,7 @@ var SparkWallet = class extends import_eventemitter3.EventEmitter {
31291
31605
  if (event.transfer.transfer && !(0, import_utils25.equalBytes)(senderIdentityPublicKey, receiverIdentityPublicKey)) {
31292
31606
  await this.claimTransfer({
31293
31607
  transfer: event.transfer.transfer,
31294
- emit: true,
31295
- optimize: true
31608
+ emit: true
31296
31609
  });
31297
31610
  }
31298
31611
  } else if (isDepositStreamEvent(event)) {
@@ -31583,73 +31896,84 @@ var SparkWallet = class extends import_eventemitter3.EventEmitter {
31583
31896
  }
31584
31897
  return nodes;
31585
31898
  }
31586
- areLeavesInefficient() {
31587
- const totalAmount = this.getInternalBalance();
31588
- if (this.leaves.length <= 1) {
31589
- return false;
31899
+ async *optimizeLeaves(multiplicity = void 0) {
31900
+ const multiplicityValue = multiplicity ?? this.config.getOptimizationOptions().multiplicity ?? 0;
31901
+ if (multiplicityValue < 0) {
31902
+ throw new ValidationError("Multiplicity cannot be negative");
31903
+ } else if (multiplicityValue > 5) {
31904
+ throw new ValidationError("Multiplicity cannot be greater than 5");
31590
31905
  }
31591
- const nextLowerPowerOfTwo = 31 - Math.clz32(totalAmount);
31592
- let remainingAmount = totalAmount;
31593
- let optimalLeavesLength = 0;
31594
- for (let i = nextLowerPowerOfTwo; i >= 0; i--) {
31595
- const denomination = 2 ** i;
31596
- while (remainingAmount >= denomination) {
31597
- remainingAmount -= denomination;
31598
- optimalLeavesLength++;
31599
- }
31600
- }
31601
- return this.leaves.length > optimalLeavesLength * 5;
31602
- }
31603
- async optimizeLeaves() {
31604
- if (this.optimizationInProgress || !this.areLeavesInefficient()) {
31906
+ if (this.optimizationInProgress || !shouldOptimize(
31907
+ this.leaves.map((leaf) => leaf.value),
31908
+ multiplicityValue
31909
+ )) {
31605
31910
  return;
31606
31911
  }
31607
- await this.withLeaves(async () => {
31912
+ const controller = new AbortController();
31913
+ const release = await this.leavesMutex.acquire();
31914
+ try {
31608
31915
  this.optimizationInProgress = true;
31609
- try {
31610
- this.leaves = await this.getLeaves();
31611
- const swaps = maximizeUnilateralExit(
31612
- this.leaves.map((leaf) => leaf.value)
31613
- );
31614
- const valueToNodes = /* @__PURE__ */ new Map();
31615
- this.leaves.forEach((leaf) => {
31616
- if (!valueToNodes.has(leaf.value)) {
31617
- valueToNodes.set(leaf.value, []);
31618
- }
31619
- valueToNodes.get(leaf.value).push(leaf);
31620
- });
31621
- for (const swap of swaps) {
31622
- const leavesToSend = [];
31623
- for (const leafValue of swap.inLeaves) {
31624
- const nodes = valueToNodes.get(leafValue);
31625
- if (nodes && nodes.length > 0) {
31626
- const node = nodes.shift();
31627
- leavesToSend.push(node);
31628
- } else {
31629
- throw new InternalValidationError(
31630
- `No unused leaf with value ${leafValue} found in leaves`
31631
- );
31632
- }
31916
+ this.leaves = await this.getLeaves();
31917
+ const swaps = optimize(
31918
+ this.leaves.map((leaf) => leaf.value),
31919
+ multiplicityValue
31920
+ );
31921
+ if (swaps.length === 0) {
31922
+ return;
31923
+ }
31924
+ yield {
31925
+ step: 0,
31926
+ total: swaps.length,
31927
+ controller
31928
+ };
31929
+ const valueToNodes = /* @__PURE__ */ new Map();
31930
+ this.leaves.forEach((leaf) => {
31931
+ if (!valueToNodes.has(leaf.value)) {
31932
+ valueToNodes.set(leaf.value, []);
31933
+ }
31934
+ valueToNodes.get(leaf.value).push(leaf);
31935
+ });
31936
+ for (const swap of swaps) {
31937
+ if (controller.signal.aborted) {
31938
+ break;
31939
+ }
31940
+ const leavesToSend = [];
31941
+ for (const leafValue of swap.inLeaves) {
31942
+ const nodes = valueToNodes.get(leafValue);
31943
+ if (nodes && nodes.length > 0) {
31944
+ const node = nodes.shift();
31945
+ leavesToSend.push(node);
31946
+ } else {
31947
+ throw new InternalValidationError(
31948
+ `No unused leaf with value ${leafValue} found in leaves`
31949
+ );
31633
31950
  }
31634
- await this.requestLeavesSwap({
31635
- leaves: leavesToSend,
31636
- targetAmounts: swap.outLeaves
31637
- });
31638
31951
  }
31639
- this.leaves = await this.getLeaves();
31640
- } finally {
31641
- this.optimizationInProgress = false;
31952
+ await this.requestLeavesSwap({
31953
+ leaves: leavesToSend,
31954
+ targetAmounts: swap.outLeaves
31955
+ });
31956
+ yield {
31957
+ step: swaps.indexOf(swap) + 1,
31958
+ total: swaps.length,
31959
+ controller
31960
+ };
31642
31961
  }
31643
- });
31962
+ this.leaves = await this.getLeaves();
31963
+ } finally {
31964
+ this.optimizationInProgress = false;
31965
+ release();
31966
+ }
31644
31967
  }
31645
31968
  async syncWallet() {
31646
31969
  await this.syncTokenOutputs();
31647
31970
  let leaves = await this.getLeaves();
31648
31971
  leaves = await this.checkRenewLeaves(leaves);
31649
31972
  this.leaves = leaves;
31650
- this.optimizeLeaves().catch((e) => {
31651
- console.error("Failed to optimize leaves", e);
31652
- });
31973
+ if (this.config.getOptimizationOptions().auto) {
31974
+ for await (const _ of this.optimizeLeaves()) {
31975
+ }
31976
+ }
31653
31977
  }
31654
31978
  async withLeaves(operation) {
31655
31979
  const release = await this.leavesMutex.acquire();
@@ -32232,8 +32556,7 @@ var SparkWallet = class extends import_eventemitter3.EventEmitter {
32232
32556
  }
32233
32557
  return await this.claimTransfer({
32234
32558
  transfer: incomingTransfer,
32235
- emit: false,
32236
- optimize: false
32559
+ emit: false
32237
32560
  });
32238
32561
  } catch (e) {
32239
32562
  console.error("[processSwapBatch] Error details:", {
@@ -33262,7 +33585,7 @@ var SparkWallet = class extends import_eventemitter3.EventEmitter {
33262
33585
  transfer.id
33263
33586
  );
33264
33587
  if (pending) {
33265
- await this.claimTransfer({ transfer: pending, optimize: true });
33588
+ await this.claimTransfer({ transfer: pending });
33266
33589
  }
33267
33590
  }
33268
33591
  return {
@@ -33435,13 +33758,14 @@ var SparkWallet = class extends import_eventemitter3.EventEmitter {
33435
33758
  return response.nodes;
33436
33759
  });
33437
33760
  }
33438
- async processClaimedTransferResults(result, transfer, emit, optimize) {
33761
+ async processClaimedTransferResults(result, transfer, emit) {
33439
33762
  result = await this.checkRenewLeaves(result);
33440
33763
  const existingIds = new Set(this.leaves.map((leaf) => leaf.id));
33441
33764
  const uniqueResults = result.filter((node) => !existingIds.has(node.id));
33442
33765
  this.leaves.push(...uniqueResults);
33443
- if (optimize && transfer.type !== 40 /* COUNTER_SWAP */) {
33444
- await this.optimizeLeaves();
33766
+ if (this.config.getOptimizationOptions().auto && transfer.type !== 40 /* COUNTER_SWAP */) {
33767
+ for await (const _ of this.optimizeLeaves()) {
33768
+ }
33445
33769
  }
33446
33770
  if (emit) {
33447
33771
  this.emit(
@@ -33460,8 +33784,7 @@ var SparkWallet = class extends import_eventemitter3.EventEmitter {
33460
33784
  */
33461
33785
  async claimTransfer({
33462
33786
  transfer,
33463
- emit,
33464
- optimize
33787
+ emit
33465
33788
  }) {
33466
33789
  const onError = async (context) => {
33467
33790
  const error = context.error;
@@ -33506,12 +33829,7 @@ var SparkWallet = class extends import_eventemitter3.EventEmitter {
33506
33829
  if (result.length === 0) {
33507
33830
  return [];
33508
33831
  }
33509
- return await this.processClaimedTransferResults(
33510
- result,
33511
- transfer,
33512
- emit,
33513
- optimize
33514
- );
33832
+ return await this.processClaimedTransferResults(result, transfer, emit);
33515
33833
  } catch (error) {
33516
33834
  console.warn(
33517
33835
  `Failed to claim transfer after all retries. Please try reinitializing your wallet in a few minutes. Transfer ID: ${transfer.id}`,
@@ -33544,7 +33862,7 @@ var SparkWallet = class extends import_eventemitter3.EventEmitter {
33544
33862
  continue;
33545
33863
  }
33546
33864
  promises.push(
33547
- this.claimTransfer({ transfer, emit, optimize: true }).then(() => transfer.id).catch((error) => {
33865
+ this.claimTransfer({ transfer, emit }).then(() => transfer.id).catch((error) => {
33548
33866
  console.warn(`Failed to claim transfer ${transfer.id}:`, error);
33549
33867
  return null;
33550
33868
  })
@@ -35894,12 +36212,14 @@ setCrypto(globalThis.crypto);
35894
36212
  NotImplementedError,
35895
36213
  RPCError,
35896
36214
  ReactNativeSparkSigner,
36215
+ ReactNativeTaprootSparkSigner,
35897
36216
  SparkSDKError,
35898
36217
  SparkSdkLogger,
35899
36218
  SparkWallet,
35900
36219
  SparkWalletEvent,
35901
36220
  TEST_UNILATERAL_DIRECT_SEQUENCE,
35902
36221
  TEST_UNILATERAL_SEQUENCE,
36222
+ TaprootSparkSigner,
35903
36223
  TokenTransactionService,
35904
36224
  ValidationError,
35905
36225
  WalletConfig,
@@ -35973,6 +36293,7 @@ setCrypto(globalThis.crypto);
35973
36293
  getTxFromRawTxHex,
35974
36294
  getTxId,
35975
36295
  getTxIdNoReverse,
36296
+ hash160,
35976
36297
  isEphemeralAnchorOutput,
35977
36298
  isLegacySparkAddress,
35978
36299
  isSafeForNumber,