@buildonspark/spark-sdk 0.3.6 → 0.3.7

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 (70) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/dist/bare/index.cjs +9240 -9125
  3. package/dist/bare/index.d.cts +14 -11
  4. package/dist/bare/index.d.ts +14 -11
  5. package/dist/bare/index.js +3917 -3802
  6. package/dist/{chunk-XPHYQ2L6.js → chunk-KDEVNW7C.js} +4895 -4779
  7. package/dist/{chunk-LIZFXQWK.js → chunk-P4HYYSMU.js} +1 -1
  8. package/dist/{chunk-EHKP3Y65.js → chunk-SRPKOCG4.js} +1 -2
  9. package/dist/{chunk-FJ7LTA2O.js → chunk-UYTT3C6H.js} +1 -1
  10. package/dist/{client-AHn11NHe.d.cts → client-Bcb7TUIp.d.cts} +11 -9
  11. package/dist/{client-GOlkXliC.d.ts → client-D9T58OY8.d.ts} +11 -9
  12. package/dist/debug.cjs +1852 -1738
  13. package/dist/debug.d.cts +4 -4
  14. package/dist/debug.d.ts +4 -4
  15. package/dist/debug.js +2 -2
  16. package/dist/graphql/objects/index.d.cts +2 -2
  17. package/dist/graphql/objects/index.d.ts +2 -2
  18. package/dist/index.cjs +174 -58
  19. package/dist/index.d.cts +5 -5
  20. package/dist/index.d.ts +5 -5
  21. package/dist/index.js +3 -3
  22. package/dist/index.node.cjs +174 -57
  23. package/dist/index.node.d.cts +5 -5
  24. package/dist/index.node.d.ts +5 -5
  25. package/dist/index.node.js +2 -2
  26. package/dist/{logging-D7ukPwRA.d.ts → logging-JIaZZIbR.d.ts} +2 -2
  27. package/dist/{logging-CW3kwBaM.d.cts → logging-zkr4UlOi.d.cts} +2 -2
  28. package/dist/native/{index.cjs → index.react-native.cjs} +182 -62
  29. package/dist/native/{index.d.cts → index.react-native.d.cts} +19 -15
  30. package/dist/native/{index.d.ts → index.react-native.d.ts} +19 -15
  31. package/dist/native/{index.js → index.react-native.js} +179 -60
  32. package/dist/{spark-wallet-NxG55m7K.d.cts → spark-wallet-BuFrUWeE.d.cts} +4 -3
  33. package/dist/{spark-wallet-jwNvWvpK.d.ts → spark-wallet-CE5PYiIb.d.ts} +4 -3
  34. package/dist/{spark-wallet.browser-Cg4fB-Nm.d.ts → spark-wallet.browser-BwYkkOBU.d.ts} +1 -1
  35. package/dist/{spark-wallet.browser-Db7Y95Kt.d.cts → spark-wallet.browser-DC3jdQPW.d.cts} +1 -1
  36. package/dist/{spark-wallet.node-DB3ZqtJG.d.ts → spark-wallet.node-C9d2W-Nb.d.ts} +1 -1
  37. package/dist/{spark-wallet.node-HEG2ahNd.d.cts → spark-wallet.node-CR_zNxmy.d.cts} +1 -1
  38. package/dist/tests/test-utils.cjs +168 -51
  39. package/dist/tests/test-utils.d.cts +4 -4
  40. package/dist/tests/test-utils.d.ts +4 -4
  41. package/dist/tests/test-utils.js +4 -4
  42. package/dist/{token-transactions-B2-BO7Oz.d.ts → token-transactions-BZoJuvuE.d.ts} +1 -1
  43. package/dist/{token-transactions-BAN68xwg.d.cts → token-transactions-I_OFIoNH.d.cts} +1 -1
  44. package/dist/types/index.d.cts +1 -1
  45. package/dist/types/index.d.ts +1 -1
  46. package/package.json +14 -4
  47. package/src/graphql/client.ts +6 -9
  48. package/src/graphql/mutations/CompleteCoopExit.ts +1 -1
  49. package/src/graphql/mutations/RequestCoopExit.ts +3 -1
  50. package/src/graphql/mutations/RequestLightningSend.ts +3 -1
  51. package/src/graphql/objects/CompleteCoopExitInput.ts +22 -33
  52. package/src/graphql/objects/RequestCoopExitInput.ts +39 -45
  53. package/src/graphql/objects/RequestLightningSendInput.ts +31 -39
  54. package/src/index.react-native.ts +21 -0
  55. package/src/services/config.ts +2 -2
  56. package/src/services/connection/connection.ts +10 -0
  57. package/src/services/coop-exit.ts +5 -1
  58. package/src/services/token-transactions.ts +8 -8
  59. package/src/spark-wallet/spark-wallet.browser.ts +0 -1
  60. package/src/spark-wallet/spark-wallet.react-native.ts +5 -3
  61. package/src/spark-wallet/spark-wallet.ts +56 -9
  62. package/src/tests/integration/coop-exit.test.ts +2 -0
  63. package/src/tests/integration/lightning.test.ts +5 -1
  64. package/src/tests/integration/ssp/coop-exit-validation.test.ts +5 -6
  65. package/src/tests/integration/ssp/static_deposit.test.ts +45 -35
  66. package/src/tests/optimize.test.ts +45 -0
  67. package/src/tests/token-outputs.test.ts +60 -1
  68. package/src/tests/utils/test-faucet.ts +12 -8
  69. package/src/utils/optimize.ts +226 -0
  70. package/src/native/index.ts +0 -21
@@ -14,6 +14,7 @@ import { Mutex } from "async-mutex";
14
14
  import { uuidv7 } from "uuidv7";
15
15
  import {
16
16
  ConfigurationError,
17
+ InternalValidationError,
17
18
  NetworkError,
18
19
  NotImplementedError,
19
20
  RPCError,
@@ -106,7 +107,7 @@ import {
106
107
  isValidSparkFallback,
107
108
  } from "../services/bolt11-spark.js";
108
109
  import { SigningService } from "../services/signing.js";
109
- import { SparkSigner } from "../signer/signer.js";
110
+ import { DefaultSparkSigner, SparkSigner } from "../signer/signer.js";
110
111
  import { KeyDerivation, KeyDerivationType } from "../signer/types.js";
111
112
  import { BitcoinFaucet } from "../tests/utils/test-faucet.js";
112
113
  import {
@@ -125,6 +126,7 @@ import {
125
126
  import { chunkArray } from "../utils/chunkArray.js";
126
127
  import { getFetch } from "../utils/fetch.js";
127
128
  import { addPublicKeys } from "../utils/keys.js";
129
+ import { maximizeUnilateralExit } from "../utils/optimize.js";
128
130
  import { RetryContext, withRetry } from "../utils/retry.js";
129
131
  import {
130
132
  Bech32mTokenIdentifier,
@@ -193,9 +195,10 @@ export abstract class SparkWallet extends EventEmitter<SparkWalletEvents> {
193
195
 
194
196
  private tracer: Tracer | null = null;
195
197
 
196
- constructor(options?: ConfigOptions, signer?: SparkSigner) {
198
+ constructor(options?: ConfigOptions, signerArg?: SparkSigner) {
197
199
  super();
198
200
 
201
+ const signer = signerArg || this.buildSigner();
199
202
  this.config = new WalletConfigService(options, signer);
200
203
  this.connectionManager = this.buildConnectionManager(this.config);
201
204
  this.signingService = new SigningService(this.config);
@@ -263,6 +266,10 @@ export abstract class SparkWallet extends EventEmitter<SparkWalletEvents> {
263
266
  return this.sspClient;
264
267
  }
265
268
 
269
+ protected buildSigner() {
270
+ return new DefaultSparkSigner();
271
+ }
272
+
266
273
  protected buildConnectionManager(config: WalletConfigService) {
267
274
  return new ConnectionManager(config);
268
275
  }
@@ -744,9 +751,40 @@ export abstract class SparkWallet extends EventEmitter<SparkWalletEvents> {
744
751
  await this.withLeaves(async () => {
745
752
  this.optimizationInProgress = true;
746
753
  try {
747
- if (this.leaves.length > 0) {
748
- await this.requestLeavesSwap({ leaves: this.leaves });
754
+ this.leaves = await this.getLeaves();
755
+ const swaps = maximizeUnilateralExit(
756
+ this.leaves.map((leaf) => leaf.value),
757
+ );
758
+
759
+ // Build a map from the denomination to the nodes
760
+ const valueToNodes = new Map<number, TreeNode[]>();
761
+ this.leaves.forEach((leaf) => {
762
+ if (!valueToNodes.has(leaf.value)) {
763
+ valueToNodes.set(leaf.value, []);
764
+ }
765
+ valueToNodes.get(leaf.value)!.push(leaf);
766
+ });
767
+
768
+ // Select the leaves to send for each swap.
769
+ for (const swap of swaps) {
770
+ const leavesToSend: TreeNode[] = [];
771
+ for (const leafValue of swap.inLeaves) {
772
+ const nodes = valueToNodes.get(leafValue);
773
+ if (nodes && nodes.length > 0) {
774
+ const node = nodes.shift()!;
775
+ leavesToSend.push(node);
776
+ } else {
777
+ throw new InternalValidationError(
778
+ `No unused leaf with value ${leafValue} found in leaves`,
779
+ );
780
+ }
781
+ }
782
+ await this.requestLeavesSwap({
783
+ leaves: leavesToSend,
784
+ targetAmounts: swap.outLeaves,
785
+ });
749
786
  }
787
+
750
788
  this.leaves = await this.getLeaves();
751
789
  } finally {
752
790
  this.optimizationInProgress = false;
@@ -1430,6 +1468,14 @@ export abstract class SparkWallet extends EventEmitter<SparkWalletEvents> {
1430
1468
  directSignatureMap,
1431
1469
  directFromCpfpSignatureMap,
1432
1470
  );
1471
+
1472
+ // At this point the leaves are considered outgoing.
1473
+ // Remove them from internal state so we don't select them again
1474
+ const leavesToRemove = new Set(leavesBatch.map((leaf) => leaf.id));
1475
+ this.leaves = [
1476
+ ...this.leaves.filter((leaf) => !leavesToRemove.has(leaf.id)),
1477
+ ];
1478
+
1433
1479
  const completeResponse = await sspClient.completeLeaveSwap({
1434
1480
  adaptorSecretKey: bytesToHex(cpfpAdaptorPrivateKey),
1435
1481
  directAdaptorSecretKey: bytesToHex(directAdaptorPrivateKey),
@@ -3537,14 +3583,14 @@ export abstract class SparkWallet extends EventEmitter<SparkWalletEvents> {
3537
3583
 
3538
3584
  const sspResponse = await sspClient.requestLightningSend({
3539
3585
  encodedInvoice: invoice,
3540
- idempotencyKey: uuidv7(),
3541
3586
  amountSats: isZeroAmountInvoice ? amountSatsToSend! : undefined,
3587
+ userOutboundTransferExternalId: swapResponse.transfer.id,
3542
3588
  });
3543
3589
 
3544
3590
  if (!sspResponse) {
3545
3591
  throw new Error("Failed to contact SSP");
3546
3592
  }
3547
- // test
3593
+
3548
3594
  const leavesToRemove = new Set(leavesToSend.map((leaf) => leaf.leaf.id));
3549
3595
  this.leaves = this.leaves.filter((leaf) => !leavesToRemove.has(leaf.id));
3550
3596
 
@@ -3912,12 +3958,14 @@ export abstract class SparkWallet extends EventEmitter<SparkWalletEvents> {
3912
3958
  })),
3913
3959
  );
3914
3960
 
3961
+ const transferId = uuidv7();
3962
+
3915
3963
  const requestCoopExitParams: RequestCoopExitInput = {
3916
3964
  leafExternalIds: leavesToSendToSsp.map((leaf) => leaf.id),
3917
3965
  withdrawalAddress: onchainAddress,
3918
- idempotencyKey: uuidv7(),
3919
3966
  exitSpeed,
3920
3967
  withdrawAll: deductFeeFromWithdrawalAmount,
3968
+ userOutboundTransferExternalId: transferId,
3921
3969
  };
3922
3970
 
3923
3971
  if (!deductFeeFromWithdrawalAmount) {
@@ -3962,11 +4010,11 @@ export abstract class SparkWallet extends EventEmitter<SparkWalletEvents> {
3962
4010
  exitTxId: coopExitTxId,
3963
4011
  connectorOutputs,
3964
4012
  receiverPubKey: sspPubIdentityKey,
4013
+ transferId,
3965
4014
  });
3966
4015
 
3967
4016
  const completeResponse = await sspClient.completeCoopExit({
3968
4017
  userOutboundTransferExternalId: transfer.transfer.id,
3969
- coopExitRequestId: coopExitRequest.id,
3970
4018
  });
3971
4019
 
3972
4020
  return completeResponse;
@@ -4887,7 +4935,6 @@ export abstract class SparkWallet extends EventEmitter<SparkWalletEvents> {
4887
4935
  const consoleOptions = wallet.config.getConsoleOptions();
4888
4936
  const spanProcessors: SpanProcessor[] = [];
4889
4937
  if (consoleOptions.otel) {
4890
- console.log("OpenTelemetry client logging enabled.");
4891
4938
  spanProcessors.push(new SimpleSpanProcessor(new ConsoleSpanExporter()));
4892
4939
  }
4893
4940
  const traceUrls = this.getOtelTraceUrls();
@@ -156,11 +156,13 @@ describe.each(walletTypes)("coop exit", ({ name, Signer, createTree }) => {
156
156
  newKeyDerivation: newLeafDerivationPath,
157
157
  };
158
158
 
159
+ const transferId = uuidv7();
159
160
  const senderTransfer = await coopExitService.getConnectorRefundSignatures({
160
161
  leaves: [transferNode],
161
162
  exitTxId: hexToBytes(getTxIdNoReverse(exitTx)),
162
163
  connectorOutputs,
163
164
  receiverPubKey: hexToBytes(sspPubkey),
165
+ transferId,
164
166
  });
165
167
 
166
168
  const receiverTransfer = await sspTransferService.queryTransfer(
@@ -24,6 +24,7 @@ import {
24
24
  import { getTestWalletConfig, walletTypes } from "../test-utils.js";
25
25
  import { SparkWalletTesting } from "../utils/spark-testing-wallet.js";
26
26
  import { BitcoinFaucet } from "../utils/test-faucet.js";
27
+ import { DefaultSparkSigner } from "../../signer/signer.js";
27
28
 
28
29
  async function cleanUp() {
29
30
  const config = getTestWalletConfig();
@@ -33,7 +34,10 @@ async function cleanUp() {
33
34
  );
34
35
  const paymentHash = sha256(preimage);
35
36
 
36
- const configService = new WalletConfigService(config);
37
+ const configService = new WalletConfigService(
38
+ config,
39
+ new DefaultSparkSigner(),
40
+ );
37
41
  const connectionManager = new ConnectionManagerNodeJS(configService);
38
42
  for (const operator of Object.values(config.signingOperators!)) {
39
43
  const client = await connectionManager.createMockClient(operator!.address);
@@ -1,12 +1,11 @@
1
- import { Transaction } from "@scure/btc-signer";
1
+ import { expect } from "@jest/globals";
2
+ import { ValidationError } from "../../../errors/types.js";
3
+ import { ExitSpeed } from "../../../types/index.js";
4
+ import { getNewAddress } from "../../utils/regtest-test-faucet.js";
2
5
  import {
3
6
  initTestingWallet,
4
7
  SparkWalletTesting,
5
8
  } from "../../utils/spark-testing-wallet.js";
6
- import { expect } from "@jest/globals";
7
- import { ExitSpeed } from "../../../types/index.js";
8
- import { ValidationError } from "../../../errors/types.js";
9
- import { getNewAddress } from "../../utils/regtest-test-faucet.js";
10
9
 
11
10
  const DEPOSIT_AMOUNT = 50_000n;
12
11
 
@@ -200,7 +199,7 @@ describe("SSP coop exit basic validation", () => {
200
199
  exitSpeed: ExitSpeed.FAST,
201
200
  deductFeeFromWithdrawalAmount: true,
202
201
  }),
203
- ).rejects.toThrow("Not enough leaves to swap for the target amount");
202
+ ).rejects.toThrow("Total target amount exceeds available balance");
204
203
  }, 600000);
205
204
 
206
205
  // it("should correctly update balance after successful withdrawal", async () => {
@@ -46,18 +46,20 @@ describe("SSP static deposit address integration", () => {
46
46
  vout!,
47
47
  );
48
48
 
49
- await new Promise((resolve) => setTimeout(resolve, 1000));
50
-
51
49
  const quoteAmount = quote!.creditAmountSats;
52
50
  const sspSignature = quote!.signature;
53
51
 
54
- await userWallet.claimStaticDeposit({
55
- transactionId,
56
- creditAmountSats: quoteAmount,
57
- sspSignature,
58
- outputIndex: vout!,
59
- });
60
- await new Promise((resolve) => setTimeout(resolve, 1000));
52
+ await retryUntilSuccess(
53
+ async () =>
54
+ await userWallet.claimStaticDeposit({
55
+ transactionId,
56
+ creditAmountSats: quoteAmount,
57
+ sspSignature,
58
+ outputIndex: vout!,
59
+ }),
60
+ );
61
+
62
+ await waitForClaim({ wallet: userWallet });
61
63
  const { balance } = await userWallet.getBalance();
62
64
  expect(balance).toBe(BigInt(quoteAmount));
63
65
 
@@ -79,7 +81,7 @@ describe("SSP static deposit address integration", () => {
79
81
  creditAmountSats: quoteAmount2,
80
82
  sspSignature: sspSignature2,
81
83
  });
82
- await new Promise((resolve) => setTimeout(resolve, 1000));
84
+ await waitForClaim({ wallet: userWallet });
83
85
  const { balance: balance2 } = await userWallet.getBalance();
84
86
  expect(balance2).toBe(BigInt(quoteAmount + quoteAmount2));
85
87
 
@@ -99,7 +101,7 @@ describe("SSP static deposit address integration", () => {
99
101
  transactionId: transactionId3,
100
102
  maxFee: 1000,
101
103
  });
102
- await new Promise((resolve) => setTimeout(resolve, 1000));
104
+ await waitForClaim({ wallet: userWallet });
103
105
  const { balance: balance3 } = await userWallet.getBalance();
104
106
  expect(balance3).toBe(BigInt(quoteAmount + quoteAmount2 + quoteAmount3));
105
107
  // Get transfers should include static deposit transfers.
@@ -203,18 +205,20 @@ describe("SSP static deposit address integration", () => {
203
205
  vout!,
204
206
  );
205
207
 
206
- await new Promise((resolve) => setTimeout(resolve, 1000));
207
-
208
208
  const quoteAmount = quote!.creditAmountSats;
209
209
  const sspSignature = quote!.signature;
210
210
 
211
- await userWallet.claimStaticDeposit({
212
- transactionId,
213
- creditAmountSats: quoteAmount,
214
- sspSignature,
215
- outputIndex: vout!,
216
- });
217
- await new Promise((resolve) => setTimeout(resolve, 1000));
211
+ await retryUntilSuccess(
212
+ async () =>
213
+ await userWallet.claimStaticDeposit({
214
+ transactionId,
215
+ creditAmountSats: quoteAmount,
216
+ sspSignature,
217
+ outputIndex: vout!,
218
+ }),
219
+ );
220
+
221
+ await waitForClaim({ wallet: userWallet });
218
222
  const { balance } = await userWallet.getBalance();
219
223
  expect(balance).toBe(BigInt(quoteAmount));
220
224
 
@@ -231,12 +235,15 @@ describe("SSP static deposit address integration", () => {
231
235
  await userWallet.getClaimStaticDepositQuote(transactionId2);
232
236
  const quoteAmount2 = quote2!.creditAmountSats;
233
237
  const sspSignature2 = quote2!.signature;
234
- await userWallet.claimStaticDeposit({
235
- transactionId: transactionId2,
236
- creditAmountSats: quoteAmount2,
237
- sspSignature: sspSignature2,
238
- });
239
- await new Promise((resolve) => setTimeout(resolve, 1000));
238
+ await retryUntilSuccess(
239
+ async () =>
240
+ await userWallet.claimStaticDeposit({
241
+ transactionId: transactionId2,
242
+ creditAmountSats: quoteAmount2,
243
+ sspSignature: sspSignature2,
244
+ }),
245
+ );
246
+ await waitForClaim({ wallet: userWallet });
240
247
  const { balance: balance2 } = await userWallet.getBalance();
241
248
  expect(balance2).toBe(BigInt(quoteAmount + quoteAmount2));
242
249
 
@@ -252,11 +259,14 @@ describe("SSP static deposit address integration", () => {
252
259
  const quote3 =
253
260
  await userWallet.getClaimStaticDepositQuote(transactionId3);
254
261
  const quoteAmount3 = quote3!.creditAmountSats;
255
- await userWallet.claimStaticDepositWithMaxFee({
256
- transactionId: transactionId3,
257
- maxFee: 1000,
258
- });
259
- await new Promise((resolve) => setTimeout(resolve, 1000));
262
+ await retryUntilSuccess(
263
+ async () =>
264
+ await userWallet.claimStaticDepositWithMaxFee({
265
+ transactionId: transactionId3,
266
+ maxFee: 1000,
267
+ }),
268
+ );
269
+ await waitForClaim({ wallet: userWallet });
260
270
  const { balance: balance3 } = await userWallet.getBalance();
261
271
  expect(balance3).toBe(BigInt(quoteAmount + quoteAmount2 + quoteAmount3));
262
272
  // Get transfers should include static deposit transfers.
@@ -533,7 +543,7 @@ describe("SSP static deposit address integration", () => {
533
543
  creditAmountSats: quote.creditAmountSats,
534
544
  sspSignature: quote.signature,
535
545
  });
536
- await new Promise((resolve) => setTimeout(resolve, 40000));
546
+ await waitForClaim({ wallet: aliceWallet });
537
547
 
538
548
  const { balance } = await aliceWallet.getBalance();
539
549
 
@@ -619,7 +629,7 @@ describe("SSP static deposit address integration", () => {
619
629
  outputIndex: vout!,
620
630
  });
621
631
 
622
- await new Promise((resolve) => setTimeout(resolve, 30000));
632
+ await waitForClaim({ wallet: userWallet });
623
633
 
624
634
  expect(outputs).toBeDefined();
625
635
 
@@ -669,7 +679,7 @@ describe("SSP static deposit address integration", () => {
669
679
  outputIndex: vout!,
670
680
  });
671
681
 
672
- await new Promise((resolve) => setTimeout(resolve, 30000));
682
+ await waitForClaim({ wallet: userWallet });
673
683
 
674
684
  console.log("Fetching wallet balance after claim...");
675
685
  const { balance } = await userWallet.getBalance();
@@ -686,7 +696,7 @@ describe("SSP static deposit address integration", () => {
686
696
 
687
697
  expect(transfer).toBeDefined();
688
698
 
689
- await new Promise((resolve) => setTimeout(resolve, 1000));
699
+ await waitForClaim({ wallet: userWallet });
690
700
 
691
701
  // Try to refund the deposit after claiming and transfer
692
702
  console.log("Attempting refund of claimed deposit...");
@@ -0,0 +1,45 @@
1
+ import { describe, expect, it } from "@jest/globals";
2
+ import {
3
+ greedyLeaves,
4
+ swapMinimizingLeaves,
5
+ maximizeUnilateralExit,
6
+ minimizeTransferSwap,
7
+ Swap,
8
+ } from "../utils/optimize.js";
9
+
10
+ describe("keys", () => {
11
+ it("test greedyLeaves", () => {
12
+ expect(greedyLeaves(0)).toEqual([]);
13
+ expect(greedyLeaves(1)).toEqual([1]);
14
+ expect(greedyLeaves(100)).toEqual([4, 32, 64]);
15
+ expect(greedyLeaves(255)).toEqual([1, 2, 4, 8, 16, 32, 64, 128]);
16
+ expect(greedyLeaves(256)).toEqual([256]);
17
+ });
18
+
19
+ it("test swapMinimizingLeaves", () => {
20
+ expect(swapMinimizingLeaves(0)).toEqual([]);
21
+ expect(swapMinimizingLeaves(1)).toEqual([1]);
22
+ expect(swapMinimizingLeaves(100)).toEqual([1, 1, 2, 4, 4, 8, 16, 32, 32]);
23
+ expect(swapMinimizingLeaves(255)).toEqual([1, 2, 4, 8, 16, 32, 64, 128]);
24
+ expect(swapMinimizingLeaves(256)).toEqual([1, 1, 2, 4, 8, 16, 32, 64, 128]);
25
+ });
26
+
27
+ it("test maximizeUnilateralExit", () => {
28
+ expect(maximizeUnilateralExit([100, 64, 28, 1, 1])).toEqual([
29
+ new Swap([1, 1, 28, 64, 100], [2, 64, 128]),
30
+ ]);
31
+ expect(maximizeUnilateralExit([1, 1, 1, 1, 1, 1, 1, 1], 2)).toEqual([
32
+ new Swap([1, 1], [2]),
33
+ new Swap([1, 1], [2]),
34
+ new Swap([1, 1], [2]),
35
+ new Swap([1, 1], [2]),
36
+ ]);
37
+ });
38
+
39
+ it("test minimizeTransferSwap", () => {
40
+ expect(minimizeTransferSwap([8])).toEqual([new Swap([8], [1, 1, 2, 4])]);
41
+ expect(minimizeTransferSwap([100])).toEqual([
42
+ new Swap([100], [1, 1, 2, 4, 4, 8, 16, 32, 32]),
43
+ ]);
44
+ });
45
+ });
@@ -1,4 +1,4 @@
1
- import { numberToBytesBE } from "@noble/curves/utils";
1
+ import { numberToBytesBE, bytesToNumberBE } from "@noble/curves/utils";
2
2
  import { ValidationError } from "../errors/types.js";
3
3
  import { OutputWithPreviousTransactionData } from "../proto/spark.js";
4
4
  import { WalletConfigService } from "../services/config.js";
@@ -17,6 +17,18 @@ describe("select token outputs", () => {
17
17
  );
18
18
  });
19
19
 
20
+ // Helper to access the private sorting method
21
+ const sortTokenOutputsByStrategy = (
22
+ tokenOutputs: OutputWithPreviousTransactionData[],
23
+ strategy: "SMALL_FIRST" | "LARGE_FIRST",
24
+ ) => {
25
+ // TypeScript bracket notation to access private method
26
+ (tokenTransactionService as any)["sortTokenOutputsByStrategy"](
27
+ tokenOutputs,
28
+ strategy,
29
+ );
30
+ };
31
+
20
32
  const createMockTokenOutput = (
21
33
  id: string,
22
34
  tokenAmount: bigint,
@@ -191,4 +203,51 @@ describe("select token outputs", () => {
191
203
  // Total: 600n >= 600n
192
204
  });
193
205
  });
206
+
207
+ describe("sorting with large amounts", () => {
208
+ it("should sort correctly when all amounts are above 2^60", () => {
209
+ const base = 2n ** 60n;
210
+ const amounts = [
211
+ base + 5000n,
212
+ base + 100n,
213
+ base + 1n,
214
+ base + 10000n,
215
+ base + 500n,
216
+ ];
217
+
218
+ const tokenOutputs = amounts.map((amount, i) =>
219
+ createMockTokenOutput(`output${i}`, amount),
220
+ );
221
+
222
+ // SMALL_FIRST
223
+ const smallFirstSorted = [...tokenOutputs];
224
+ sortTokenOutputsByStrategy(smallFirstSorted, "SMALL_FIRST");
225
+
226
+ const smallFirstAmounts = smallFirstSorted.map((o) =>
227
+ bytesToNumberBE(o.output!.tokenAmount!),
228
+ );
229
+ expect(smallFirstAmounts).toEqual([
230
+ base + 1n,
231
+ base + 100n,
232
+ base + 500n,
233
+ base + 5000n,
234
+ base + 10000n,
235
+ ]);
236
+
237
+ // LARGE_FIRST
238
+ const largeFirstSorted = [...tokenOutputs];
239
+ sortTokenOutputsByStrategy(largeFirstSorted, "LARGE_FIRST");
240
+
241
+ const largeFirstAmounts = largeFirstSorted.map((o) =>
242
+ bytesToNumberBE(o.output!.tokenAmount!),
243
+ );
244
+ expect(largeFirstAmounts).toEqual([
245
+ base + 10000n,
246
+ base + 5000n,
247
+ base + 500n,
248
+ base + 100n,
249
+ base + 1n,
250
+ ]);
251
+ });
252
+ });
194
253
  });
@@ -42,9 +42,9 @@ export class BitcoinFaucet {
42
42
  private lock: Promise<void> = Promise.resolve();
43
43
 
44
44
  private constructor(
45
- private url: string = "http://127.0.0.1:8332",
46
- private username: string = "testutil",
47
- private password: string = "testutilpassword",
45
+ private url: string,
46
+ private username: string,
47
+ private password: string,
48
48
  ) {
49
49
  this.miningAddress = getP2TRAddressFromPublicKey(
50
50
  secp256k1.getPublicKey(STATIC_MINING_KEY),
@@ -52,12 +52,16 @@ export class BitcoinFaucet {
52
52
  );
53
53
  }
54
54
 
55
- static getInstance(
56
- url: string = "http://127.0.0.1:8332",
57
- username: string = "testutil",
58
- password: string = "testutilpassword",
59
- ): BitcoinFaucet {
55
+ static getInstance(): BitcoinFaucet {
60
56
  if (!BitcoinFaucet.instance) {
57
+ const url =
58
+ process.env.BITCOIN_RPC_URL ||
59
+ (process.env.MINIKUBE_IP
60
+ ? `http://${process.env.MINIKUBE_IP}:8332`
61
+ : "http://127.0.0.1:8332");
62
+ const username = process.env.BITCOIN_RPC_USER || "testutil";
63
+ const password = process.env.BITCOIN_RPC_PASSWORD || "testutilpassword";
64
+
61
65
  BitcoinFaucet.instance = new BitcoinFaucet(url, username, password);
62
66
  }
63
67
  return BitcoinFaucet.instance;