@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
@@ -1,7 +1,7 @@
1
1
  import { Q as QueryTransfersResponse, T as Transfer, c as TreeNode } from '../spark-DOpheE8_.js';
2
- import { b as ConfigOptions } from '../wallet-config-Bmk2eAn8.js';
2
+ import { b as ConfigOptions } from '../wallet-config-Bg3kWltL.js';
3
3
  import { S as SparkSigner, D as DefaultSparkSigner } from '../client-BaQf-5gD.js';
4
- import { S as SparkWalletNodeJS } from '../spark-wallet.node-CmIvxtcC.js';
4
+ import { S as SparkWalletNodeJS } from '../spark-wallet.node-BqmKsGPs.js';
5
5
  import { Transaction } from '@scure/btc-signer';
6
6
  import { TransactionInput, TransactionOutput } from '@scure/btc-signer/psbt';
7
7
  import '@bufbuild/protobuf/wire';
@@ -38,12 +38,26 @@ declare class BitcoinFaucet {
38
38
  sendFaucetCoinToP2WPKHAddress(pubKey: Uint8Array): Promise<void>;
39
39
  signFaucetCoin(unsignedTx: Transaction, fundingTxOut: TransactionOutput, key: Uint8Array): Promise<Transaction>;
40
40
  mineBlocks(numBlocks: number): Promise<any>;
41
+ mineBlocksAndWaitForMiningToComplete(numBlocks: number): Promise<void>;
41
42
  private call;
42
43
  generateToAddress(numBlocks: number, address: string): Promise<any>;
43
44
  sendToAddressInternal(address: string, amountSats: number): Promise<any>;
44
45
  getBlock(blockHash: string): Promise<any>;
46
+ getBlockCount(): Promise<number>;
47
+ waitForBlocksMined({ startBlock, expectedIncrease, timeoutMs, intervalMs, }: {
48
+ startBlock: number;
49
+ expectedIncrease: number;
50
+ timeoutMs?: number;
51
+ intervalMs?: number;
52
+ }): Promise<number>;
45
53
  broadcastTx(txHex: string): Promise<any>;
54
+ submitPackage(txHexs: string[]): Promise<any>;
46
55
  getNewAddress(): Promise<string>;
56
+ getNewExternalWallet(): Promise<{
57
+ address: string;
58
+ key: Uint8Array;
59
+ pubKey: Uint8Array;
60
+ }>;
47
61
  sendToAddress(address: string, amount: bigint, blocksToGenerate?: number): Promise<Transaction>;
48
62
  getRawTransaction(txid: string): Promise<any>;
49
63
  }
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  ConnectionManagerNodeJS,
3
3
  SparkWalletNodeJS
4
- } from "../chunk-MFCM6GUD.js";
5
- import "../chunk-WRE2T22S.js";
6
- import "../chunk-S55NZT4P.js";
4
+ } from "../chunk-WICAF6BS.js";
5
+ import "../chunk-LOXWCMZL.js";
6
+ import "../chunk-27ILUWDJ.js";
7
7
  import {
8
8
  BitcoinFaucet,
9
9
  DefaultSparkSigner,
@@ -14,7 +14,7 @@ import {
14
14
  WalletConfigService,
15
15
  getNetwork,
16
16
  getP2TRAddressFromPublicKey
17
- } from "../chunk-O4C4HGQL.js";
17
+ } from "../chunk-G3LHXHF3.js";
18
18
  import "../chunk-NX5KPN5F.js";
19
19
  import "../chunk-JLF6WJ7K.js";
20
20
  import "../chunk-4YFT7DAE.js";
@@ -1,6 +1,6 @@
1
1
  import { O as OutputWithPreviousTransactionData } from './spark-DOpheE8_.cjs';
2
2
  import { TokenTransaction, QueryTokenTransactionsResponse } from './proto/spark_token.cjs';
3
- import { W as WalletConfigService, C as ConnectionManager, O as TokenOutputsMap, B as Bech32mTokenIdentifier, S as SparkAddressFormat } from './wallet-config-DQw5llqA.cjs';
3
+ import { W as WalletConfigService, C as ConnectionManager, O as TokenOutputsMap, B as Bech32mTokenIdentifier, S as SparkAddressFormat } from './wallet-config-CuZKNo9S.cjs';
4
4
 
5
5
  declare class SparkSDKError extends Error {
6
6
  readonly context: Record<string, unknown>;
@@ -1,6 +1,6 @@
1
1
  import { O as OutputWithPreviousTransactionData } from './spark-DOpheE8_.js';
2
2
  import { TokenTransaction, QueryTokenTransactionsResponse } from './proto/spark_token.js';
3
- import { W as WalletConfigService, C as ConnectionManager, O as TokenOutputsMap, B as Bech32mTokenIdentifier, S as SparkAddressFormat } from './wallet-config-Bmk2eAn8.js';
3
+ import { W as WalletConfigService, C as ConnectionManager, O as TokenOutputsMap, B as Bech32mTokenIdentifier, S as SparkAddressFormat } from './wallet-config-Bg3kWltL.js';
4
4
 
5
5
  declare class SparkSDKError extends Error {
6
6
  readonly context: Record<string, unknown>;
@@ -118,6 +118,7 @@ declare class WalletConfigService implements HasSspClientOptions {
118
118
  getSspIdentityPublicKey(): string;
119
119
  getConsoleOptions(): ConsoleOptions;
120
120
  getEvents(): Partial<SparkWalletEvents>;
121
+ getOptimizationOptions(): OptimizationOptions;
121
122
  }
122
123
 
123
124
  /** Challenge represents the core challenge data */
@@ -419,8 +420,11 @@ declare abstract class SparkWallet extends EventEmitter<SparkWalletEvents> {
419
420
  private popOrThrow;
420
421
  private selectLeaves;
421
422
  private selectLeavesForSwap;
422
- private areLeavesInefficient;
423
- private optimizeLeaves;
423
+ optimizeLeaves(multiplicity?: number | undefined): AsyncGenerator<{
424
+ step: number;
425
+ total: number;
426
+ controller: AbortController;
427
+ }, void, void>;
424
428
  private syncWallet;
425
429
  private withLeaves;
426
430
  /**
@@ -1141,6 +1145,10 @@ type SigningOperator = {
1141
1145
  type ConsoleOptions = {
1142
1146
  otel?: boolean;
1143
1147
  };
1148
+ type OptimizationOptions = {
1149
+ readonly auto?: boolean;
1150
+ readonly multiplicity?: number;
1151
+ };
1144
1152
  type ConfigOptions = MayHaveSspClientOptions & {
1145
1153
  readonly network?: NetworkType;
1146
1154
  readonly signingOperators?: Readonly<Record<string, SigningOperator>>;
@@ -1156,6 +1164,7 @@ type ConfigOptions = MayHaveSspClientOptions & {
1156
1164
  readonly signerWithPreExistingKeys?: boolean;
1157
1165
  readonly console?: ConsoleOptions;
1158
1166
  readonly events?: Partial<SparkWalletEvents>;
1167
+ readonly optimizationOptions?: OptimizationOptions;
1159
1168
  };
1160
1169
  declare const WalletConfig: {
1161
1170
  LOCAL: Required<ConfigOptions>;
@@ -118,6 +118,7 @@ declare class WalletConfigService implements HasSspClientOptions {
118
118
  getSspIdentityPublicKey(): string;
119
119
  getConsoleOptions(): ConsoleOptions;
120
120
  getEvents(): Partial<SparkWalletEvents>;
121
+ getOptimizationOptions(): OptimizationOptions;
121
122
  }
122
123
 
123
124
  /** Challenge represents the core challenge data */
@@ -419,8 +420,11 @@ declare abstract class SparkWallet extends EventEmitter<SparkWalletEvents> {
419
420
  private popOrThrow;
420
421
  private selectLeaves;
421
422
  private selectLeavesForSwap;
422
- private areLeavesInefficient;
423
- private optimizeLeaves;
423
+ optimizeLeaves(multiplicity?: number | undefined): AsyncGenerator<{
424
+ step: number;
425
+ total: number;
426
+ controller: AbortController;
427
+ }, void, void>;
424
428
  private syncWallet;
425
429
  private withLeaves;
426
430
  /**
@@ -1141,6 +1145,10 @@ type SigningOperator = {
1141
1145
  type ConsoleOptions = {
1142
1146
  otel?: boolean;
1143
1147
  };
1148
+ type OptimizationOptions = {
1149
+ readonly auto?: boolean;
1150
+ readonly multiplicity?: number;
1151
+ };
1144
1152
  type ConfigOptions = MayHaveSspClientOptions & {
1145
1153
  readonly network?: NetworkType;
1146
1154
  readonly signingOperators?: Readonly<Record<string, SigningOperator>>;
@@ -1156,6 +1164,7 @@ type ConfigOptions = MayHaveSspClientOptions & {
1156
1164
  readonly signerWithPreExistingKeys?: boolean;
1157
1165
  readonly console?: ConsoleOptions;
1158
1166
  readonly events?: Partial<SparkWalletEvents>;
1167
+ readonly optimizationOptions?: OptimizationOptions;
1159
1168
  };
1160
1169
  declare const WalletConfig: {
1161
1170
  LOCAL: Required<ConfigOptions>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@buildonspark/spark-sdk",
3
- "version": "0.3.9",
3
+ "version": "0.4.0",
4
4
  "author": "",
5
5
  "license": "Apache-2.0",
6
6
  "main": "./dist/index.js",
@@ -7,9 +7,15 @@ setCrypto(globalThis.crypto);
7
7
  export * from "./errors/index.js";
8
8
  export * from "./utils/index.js";
9
9
 
10
- export { ReactNativeSparkSigner } from "./signer/signer.react-native.js";
10
+ export {
11
+ ReactNativeSparkSigner,
12
+ ReactNativeTaprootSparkSigner,
13
+ } from "./signer/signer.react-native.js";
11
14
  /* Enable some consumers to use named import DefaultSparkSigner regardless of module, see LIG-7662 */
12
- export { ReactNativeSparkSigner as DefaultSparkSigner } from "./signer/signer.react-native.js";
15
+ export {
16
+ ReactNativeSparkSigner as DefaultSparkSigner,
17
+ ReactNativeTaprootSparkSigner as TaprootSparkSigner,
18
+ } from "./signer/signer.react-native.js";
13
19
 
14
20
  export { SparkWallet } from "./spark-wallet/spark-wallet.react-native.js";
15
21
  export * from "./spark-wallet/types.js";
@@ -7,6 +7,7 @@ import {
7
7
  WalletConfig,
8
8
  SigningOperator,
9
9
  ConsoleOptions,
10
+ OptimizationOptions,
10
11
  } from "./wallet-config.js";
11
12
  import { ConfigurationError } from "../errors/types.js";
12
13
  import { SparkWalletEvents } from "../spark-wallet/types.js";
@@ -127,4 +128,8 @@ export class WalletConfigService implements HasSspClientOptions {
127
128
  public getEvents(): Partial<SparkWalletEvents> {
128
129
  return this.config.events;
129
130
  }
131
+
132
+ public getOptimizationOptions(): OptimizationOptions {
133
+ return this.config.optimizationOptions;
134
+ }
130
135
  }
@@ -104,6 +104,11 @@ export type ConsoleOptions = {
104
104
  otel?: boolean;
105
105
  };
106
106
 
107
+ export type OptimizationOptions = {
108
+ readonly auto?: boolean;
109
+ readonly multiplicity?: number;
110
+ };
111
+
107
112
  export type ConfigOptions = MayHaveSspClientOptions & {
108
113
  readonly network?: NetworkType;
109
114
  readonly signingOperators?: Readonly<Record<string, SigningOperator>>;
@@ -119,6 +124,7 @@ export type ConfigOptions = MayHaveSspClientOptions & {
119
124
  readonly signerWithPreExistingKeys?: boolean;
120
125
  readonly console?: ConsoleOptions;
121
126
  readonly events?: Partial<SparkWalletEvents>;
127
+ readonly optimizationOptions?: OptimizationOptions;
122
128
  };
123
129
 
124
130
  const PROD_PUBKEYS = [
@@ -153,6 +159,10 @@ const BASE_CONFIG: Required<ConfigOptions> = {
153
159
  otel: false,
154
160
  },
155
161
  events: {},
162
+ optimizationOptions: {
163
+ auto: true,
164
+ multiplicity: 0,
165
+ },
156
166
  };
157
167
 
158
168
  const LOCAL_WALLET_CONFIG: Required<ConfigOptions> = {
@@ -169,7 +169,17 @@ function headersToMetadata(headers: string): Metadata {
169
169
  const parts = line.split(": ");
170
170
  const header = parts.shift() ?? "";
171
171
  const value = parts.join(": ");
172
- metadata.set(header, value);
172
+
173
+ if (header.endsWith("-bin")) {
174
+ try {
175
+ metadata.set(header, Base64.toUint8Array(value));
176
+ } catch (e) {
177
+ console.warn(`Failed to decode binary metadata ${header}:`, e);
178
+ metadata.set(header, value);
179
+ }
180
+ } else {
181
+ metadata.set(header, value);
182
+ }
173
183
  });
174
184
  return metadata;
175
185
  }
@@ -202,8 +212,8 @@ function getErrorDetailsFromHttpResponse(
202
212
  ): string {
203
213
  return (
204
214
  `Received HTTP ${statusCode} response: ` +
205
- (responseText.length > 1000
206
- ? responseText.slice(0, 1000) + "... (truncated)"
215
+ (responseText?.length > 1000
216
+ ? responseText?.slice(0, 1000) + "... (truncated)"
207
217
  : responseText)
208
218
  );
209
219
  }
@@ -1,7 +1,7 @@
1
1
  import { ValidationError } from "../errors/index.js";
2
2
  import { NativeSparkFrost } from "../spark_bindings/native/index.js";
3
3
  import { IKeyPackage } from "../spark_bindings/types.js";
4
- import { DefaultSparkSigner } from "./signer.js";
4
+ import { DefaultSparkSigner, TaprootSparkSigner } from "./signer.js";
5
5
  import type { AggregateFrostParams, SignFrostParams } from "./types.js";
6
6
 
7
7
  export class ReactNativeSparkSigner extends DefaultSparkSigner {
@@ -71,3 +71,75 @@ export class ReactNativeSparkSigner extends DefaultSparkSigner {
71
71
  });
72
72
  }
73
73
  }
74
+
75
+ export class ReactNativeTaprootSparkSigner extends TaprootSparkSigner {
76
+ constructor(useAddressIndex = false) {
77
+ super(useAddressIndex);
78
+ }
79
+
80
+ async signFrost({
81
+ message,
82
+ keyDerivation,
83
+ publicKey,
84
+ verifyingKey,
85
+ selfCommitment,
86
+ statechainCommitments,
87
+ adaptorPubKey,
88
+ }: SignFrostParams): Promise<Uint8Array> {
89
+ const signingPrivateKey =
90
+ await this.getSigningPrivateKeyFromDerivation(keyDerivation);
91
+
92
+ if (!signingPrivateKey) {
93
+ throw new ValidationError("Private key not found for public key", {
94
+ field: "privateKey",
95
+ });
96
+ }
97
+
98
+ const commitment = selfCommitment.commitment;
99
+ const nonce = this.commitmentToNonceMap.get(commitment);
100
+ if (!nonce) {
101
+ throw new ValidationError("Nonce not found for commitment", {
102
+ field: "nonce",
103
+ });
104
+ }
105
+
106
+ const keyPackage: IKeyPackage = {
107
+ secretKey: signingPrivateKey,
108
+ publicKey: publicKey,
109
+ verifyingKey: verifyingKey,
110
+ };
111
+
112
+ return NativeSparkFrost.signFrost({
113
+ message,
114
+ keyPackage,
115
+ nonce,
116
+ selfCommitment: commitment,
117
+ statechainCommitments,
118
+ adaptorPubKey,
119
+ });
120
+ }
121
+
122
+ async aggregateFrost({
123
+ message,
124
+ publicKey,
125
+ verifyingKey,
126
+ selfCommitment,
127
+ statechainCommitments,
128
+ adaptorPubKey,
129
+ selfSignature,
130
+ statechainSignatures,
131
+ statechainPublicKeys,
132
+ }: AggregateFrostParams): Promise<Uint8Array> {
133
+ return NativeSparkFrost.aggregateFrost({
134
+ message,
135
+ statechainSignatures,
136
+ statechainPublicKeys,
137
+ verifyingKey,
138
+ statechainCommitments,
139
+ selfCommitment: selfCommitment.commitment,
140
+ selfPublicKey: publicKey,
141
+ selfSignature,
142
+ adaptorPubKey,
143
+ });
144
+ }
145
+ }
@@ -124,7 +124,6 @@ import {
124
124
  import { chunkArray } from "../utils/chunkArray.js";
125
125
  import { getFetch } from "../utils/fetch.js";
126
126
  import { addPublicKeys } from "../utils/keys.js";
127
- import { maximizeUnilateralExit } from "../utils/optimize.js";
128
127
  import { RetryContext, withRetry } from "../utils/retry.js";
129
128
  import {
130
129
  Bech32mTokenIdentifier,
@@ -152,6 +151,7 @@ import type {
152
151
  UserTokenMetadata,
153
152
  } from "./types.js";
154
153
  import { SparkWalletEvent } from "./types.js";
154
+ import { optimize, shouldOptimize } from "../utils/optimize.js";
155
155
 
156
156
  /**
157
157
  * The SparkWallet class is the primary interface for interacting with the Spark network.
@@ -299,7 +299,6 @@ export abstract class SparkWallet extends EventEmitter<SparkWalletEvents> {
299
299
  await this.claimTransfer({
300
300
  transfer: event.transfer.transfer,
301
301
  emit: true,
302
- optimize: true,
303
302
  });
304
303
  }
305
304
  } else if (isDepositStreamEvent(event)) {
@@ -669,76 +668,101 @@ export abstract class SparkWallet extends EventEmitter<SparkWalletEvents> {
669
668
  return nodes;
670
669
  }
671
670
 
672
- private areLeavesInefficient() {
673
- const totalAmount = this.getInternalBalance();
674
-
675
- if (this.leaves.length <= 1) {
676
- return false;
671
+ public async *optimizeLeaves(
672
+ multiplicity: number | undefined = undefined,
673
+ ): AsyncGenerator<
674
+ {
675
+ step: number;
676
+ total: number;
677
+ controller: AbortController;
678
+ },
679
+ void,
680
+ void
681
+ > {
682
+ const multiplicityValue =
683
+ multiplicity ?? this.config.getOptimizationOptions().multiplicity ?? 0;
684
+ if (multiplicityValue < 0) {
685
+ throw new ValidationError("Multiplicity cannot be negative");
686
+ } else if (multiplicityValue > 5) {
687
+ throw new ValidationError("Multiplicity cannot be greater than 5");
677
688
  }
678
689
 
679
- const nextLowerPowerOfTwo = 31 - Math.clz32(totalAmount);
690
+ if (
691
+ this.optimizationInProgress ||
692
+ !shouldOptimize(
693
+ this.leaves.map((leaf) => leaf.value),
694
+ multiplicityValue,
695
+ )
696
+ ) {
697
+ return;
698
+ }
680
699
 
681
- let remainingAmount = totalAmount;
682
- let optimalLeavesLength = 0;
700
+ const controller = new AbortController();
701
+ const release = await this.leavesMutex.acquire();
702
+ try {
703
+ this.optimizationInProgress = true;
683
704
 
684
- for (let i = nextLowerPowerOfTwo; i >= 0; i--) {
685
- const denomination = 2 ** i;
686
- while (remainingAmount >= denomination) {
687
- remainingAmount -= denomination;
688
- optimalLeavesLength++;
705
+ this.leaves = await this.getLeaves();
706
+ const swaps = optimize(
707
+ this.leaves.map((leaf) => leaf.value),
708
+ multiplicityValue,
709
+ );
710
+ if (swaps.length === 0) {
711
+ return;
689
712
  }
690
- }
691
-
692
- return this.leaves.length > optimalLeavesLength * 5;
693
- }
694
713
 
695
- private async optimizeLeaves() {
696
- if (this.optimizationInProgress || !this.areLeavesInefficient()) {
697
- return;
698
- }
714
+ yield {
715
+ step: 0,
716
+ total: swaps.length,
717
+ controller,
718
+ };
699
719
 
700
- await this.withLeaves(async () => {
701
- this.optimizationInProgress = true;
702
- try {
703
- this.leaves = await this.getLeaves();
704
- const swaps = maximizeUnilateralExit(
705
- this.leaves.map((leaf) => leaf.value),
706
- );
720
+ // Build a map from the denomination to the indices
721
+ const valueToNodes = new Map<number, TreeNode[]>();
722
+ this.leaves.forEach((leaf) => {
723
+ if (!valueToNodes.has(leaf.value)) {
724
+ valueToNodes.set(leaf.value, []);
725
+ }
726
+ valueToNodes.get(leaf.value)!.push(leaf);
727
+ });
707
728
 
708
- // Build a map from the denomination to the nodes
709
- const valueToNodes = new Map<number, TreeNode[]>();
710
- this.leaves.forEach((leaf) => {
711
- if (!valueToNodes.has(leaf.value)) {
712
- valueToNodes.set(leaf.value, []);
713
- }
714
- valueToNodes.get(leaf.value)!.push(leaf);
715
- });
729
+ // Select the leaves to send for each swap.
730
+ for (const swap of swaps) {
731
+ if (controller.signal.aborted) {
732
+ break;
733
+ }
716
734
 
717
- // Select the leaves to send for each swap.
718
- for (const swap of swaps) {
719
- const leavesToSend: TreeNode[] = [];
720
- for (const leafValue of swap.inLeaves) {
721
- const nodes = valueToNodes.get(leafValue);
722
- if (nodes && nodes.length > 0) {
723
- const node = nodes.shift()!;
724
- leavesToSend.push(node);
725
- } else {
726
- throw new InternalValidationError(
727
- `No unused leaf with value ${leafValue} found in leaves`,
728
- );
729
- }
735
+ const leavesToSend: TreeNode[] = [];
736
+ for (const leafValue of swap.inLeaves) {
737
+ const nodes = valueToNodes.get(leafValue);
738
+ if (nodes && nodes.length > 0) {
739
+ const node = nodes.shift()!;
740
+ leavesToSend.push(node);
741
+ } else {
742
+ throw new InternalValidationError(
743
+ `No unused leaf with value ${leafValue} found in leaves`,
744
+ );
730
745
  }
731
- await this.requestLeavesSwap({
732
- leaves: leavesToSend,
733
- targetAmounts: swap.outLeaves,
734
- });
735
746
  }
736
747
 
737
- this.leaves = await this.getLeaves();
738
- } finally {
739
- this.optimizationInProgress = false;
748
+ // TODO: Parallelize this.
749
+ await this.requestLeavesSwap({
750
+ leaves: leavesToSend,
751
+ targetAmounts: swap.outLeaves,
752
+ });
753
+
754
+ yield {
755
+ step: swaps.indexOf(swap) + 1,
756
+ total: swaps.length,
757
+ controller,
758
+ };
740
759
  }
741
- });
760
+
761
+ this.leaves = await this.getLeaves();
762
+ } finally {
763
+ this.optimizationInProgress = false;
764
+ release();
765
+ }
742
766
  }
743
767
 
744
768
  private async syncWallet() {
@@ -750,9 +774,11 @@ export abstract class SparkWallet extends EventEmitter<SparkWalletEvents> {
750
774
 
751
775
  this.leaves = leaves;
752
776
 
753
- this.optimizeLeaves().catch((e) => {
754
- console.error("Failed to optimize leaves", e);
755
- });
777
+ if (this.config.getOptimizationOptions().auto) {
778
+ for await (const _ of this.optimizeLeaves()) {
779
+ // run all optimizer steps, do nothing with them
780
+ }
781
+ }
756
782
  }
757
783
 
758
784
  private async withLeaves<T>(operation: () => Promise<T>): Promise<T> {
@@ -1453,7 +1479,6 @@ export abstract class SparkWallet extends EventEmitter<SparkWalletEvents> {
1453
1479
  return await this.claimTransfer({
1454
1480
  transfer: incomingTransfer,
1455
1481
  emit: false,
1456
- optimize: false,
1457
1482
  });
1458
1483
  } catch (e) {
1459
1484
  console.error("[processSwapBatch] Error details:", {
@@ -2749,7 +2774,7 @@ export abstract class SparkWallet extends EventEmitter<SparkWalletEvents> {
2749
2774
  transfer.id,
2750
2775
  );
2751
2776
  if (pending) {
2752
- await this.claimTransfer({ transfer: pending, optimize: true });
2777
+ await this.claimTransfer({ transfer: pending });
2753
2778
  }
2754
2779
  }
2755
2780
  return {
@@ -2959,7 +2984,6 @@ export abstract class SparkWallet extends EventEmitter<SparkWalletEvents> {
2959
2984
  result: TreeNode[],
2960
2985
  transfer: Transfer,
2961
2986
  emit?: boolean,
2962
- optimize?: boolean,
2963
2987
  ): Promise<TreeNode[]> {
2964
2988
  result = await this.checkRenewLeaves(result);
2965
2989
 
@@ -2967,8 +2991,13 @@ export abstract class SparkWallet extends EventEmitter<SparkWalletEvents> {
2967
2991
  const uniqueResults = result.filter((node) => !existingIds.has(node.id));
2968
2992
  this.leaves.push(...uniqueResults);
2969
2993
 
2970
- if (optimize && transfer.type !== TransferType.COUNTER_SWAP) {
2971
- await this.optimizeLeaves();
2994
+ if (
2995
+ this.config.getOptimizationOptions().auto &&
2996
+ transfer.type !== TransferType.COUNTER_SWAP
2997
+ ) {
2998
+ for await (const _ of this.optimizeLeaves()) {
2999
+ // run all optimizer steps, do nothing with them
3000
+ }
2972
3001
  }
2973
3002
 
2974
3003
  if (emit) {
@@ -2991,11 +3020,9 @@ export abstract class SparkWallet extends EventEmitter<SparkWalletEvents> {
2991
3020
  private async claimTransfer({
2992
3021
  transfer,
2993
3022
  emit,
2994
- optimize,
2995
3023
  }: {
2996
3024
  transfer: Transfer;
2997
3025
  emit?: boolean;
2998
- optimize?: boolean;
2999
3026
  }) {
3000
3027
  const onError = async (
3001
3028
  context: RetryContext<TreeNode[], Transfer>,
@@ -3053,12 +3080,7 @@ export abstract class SparkWallet extends EventEmitter<SparkWalletEvents> {
3053
3080
  return [];
3054
3081
  }
3055
3082
 
3056
- return await this.processClaimedTransferResults(
3057
- result,
3058
- transfer,
3059
- emit,
3060
- optimize,
3061
- );
3083
+ return await this.processClaimedTransferResults(result, transfer, emit);
3062
3084
  } catch (error) {
3063
3085
  console.warn(
3064
3086
  `Failed to claim transfer after all retries. Please try reinitializing your wallet in a few minutes. Transfer ID: ${transfer.id}`,
@@ -3107,7 +3129,7 @@ export abstract class SparkWallet extends EventEmitter<SparkWalletEvents> {
3107
3129
  continue;
3108
3130
  }
3109
3131
  promises.push(
3110
- this.claimTransfer({ transfer, emit, optimize: true })
3132
+ this.claimTransfer({ transfer, emit })
3111
3133
  .then(() => transfer.id)
3112
3134
  .catch((error) => {
3113
3135
  console.warn(`Failed to claim transfer ${transfer.id}:`, error);