@buildonspark/spark-sdk 0.2.7 → 0.2.9

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 (88) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/dist/bare/index.cjs +1683 -1901
  3. package/dist/bare/index.d.cts +242 -292
  4. package/dist/bare/index.d.ts +242 -292
  5. package/dist/bare/index.js +1604 -1829
  6. package/dist/{chunk-7LY7PJQL.js → chunk-23BBEC25.js} +14 -5
  7. package/dist/{chunk-R5VUHUJR.js → chunk-5Y7YILMA.js} +4153 -3728
  8. package/dist/{chunk-GIDAHHDB.js → chunk-6CMNEDBK.js} +217 -9
  9. package/dist/{chunk-J24LM4RO.js → chunk-76SYPHOC.js} +1 -1
  10. package/dist/{chunk-2HD3USKS.js → chunk-A5M55UR3.js} +0 -24
  11. package/dist/{client-BmnZ1xDg.d.cts → client-B9CAWKWz.d.cts} +1 -1
  12. package/dist/{client-DmjOifnt.d.ts → client-Dd3QnxQu.d.ts} +1 -1
  13. package/dist/debug.cjs +1680 -1957
  14. package/dist/debug.d.cts +13 -8
  15. package/dist/debug.d.ts +13 -8
  16. package/dist/debug.js +6 -8
  17. package/dist/graphql/objects/index.d.cts +3 -3
  18. package/dist/graphql/objects/index.d.ts +3 -3
  19. package/dist/index.cjs +1729 -1948
  20. package/dist/index.d.cts +18 -6
  21. package/dist/index.d.ts +18 -6
  22. package/dist/index.js +17 -8
  23. package/dist/index.node.cjs +1729 -1948
  24. package/dist/index.node.d.cts +7 -6
  25. package/dist/index.node.d.ts +7 -6
  26. package/dist/index.node.js +22 -6
  27. package/dist/native/index.cjs +1723 -1949
  28. package/dist/native/index.d.cts +80 -125
  29. package/dist/native/index.d.ts +80 -125
  30. package/dist/native/index.js +1652 -1884
  31. package/dist/proto/spark.cjs +0 -24
  32. package/dist/proto/spark.d.cts +1 -1
  33. package/dist/proto/spark.d.ts +1 -1
  34. package/dist/proto/spark.js +1 -1
  35. package/dist/proto/spark_token.cjs +221 -8
  36. package/dist/proto/spark_token.d.cts +25 -2
  37. package/dist/proto/spark_token.d.ts +25 -2
  38. package/dist/proto/spark_token.js +12 -2
  39. package/dist/{spark-B305mDNB.d.cts → spark-CtGJPkx4.d.cts} +3 -31
  40. package/dist/{spark-B305mDNB.d.ts → spark-CtGJPkx4.d.ts} +3 -31
  41. package/dist/{spark-wallet-BdwARy70.d.cts → spark-wallet-Cp3yv6cK.d.ts} +40 -31
  42. package/dist/{spark-wallet-enp968Uc.d.ts → spark-wallet-yc2KhsVY.d.cts} +40 -31
  43. package/dist/{spark-wallet.node-CtpJlYBs.d.cts → spark-wallet.node-D0Qw5Wb4.d.cts} +1 -1
  44. package/dist/{spark-wallet.node-DqWcsNb6.d.ts → spark-wallet.node-D4IovOHu.d.ts} +1 -1
  45. package/dist/tests/test-utils.cjs +483 -1120
  46. package/dist/tests/test-utils.d.cts +9 -5
  47. package/dist/tests/test-utils.d.ts +9 -5
  48. package/dist/tests/test-utils.js +5 -6
  49. package/dist/{token-transactions-3-pVToE0.d.cts → token-transactions-0nmR9mQO.d.ts} +17 -12
  50. package/dist/{token-transactions-84Hp0hGz.d.ts → token-transactions-CwhlOgIP.d.cts} +17 -12
  51. package/dist/types/index.cjs +0 -24
  52. package/dist/types/index.d.cts +2 -2
  53. package/dist/types/index.d.ts +2 -2
  54. package/dist/types/index.js +2 -2
  55. package/dist/{xchain-address-BtuJEbzG.d.cts → xchain-address-BPwpnmuY.d.ts} +9 -3
  56. package/dist/{xchain-address-Q1BrcwID.d.ts → xchain-address-CNQEwLjR.d.cts} +9 -3
  57. package/package.json +1 -1
  58. package/src/constants.ts +7 -1
  59. package/src/debug.ts +1 -1
  60. package/src/proto/spark.ts +2 -48
  61. package/src/proto/spark_token.ts +255 -7
  62. package/src/services/token-transactions.ts +92 -44
  63. package/src/services/transfer.ts +20 -17
  64. package/src/services/wallet-config.ts +2 -0
  65. package/src/signer/signer.react-native.ts +0 -2
  66. package/src/spark-wallet/spark-wallet.browser.ts +9 -8
  67. package/src/spark-wallet/spark-wallet.node.ts +8 -4
  68. package/src/spark-wallet/spark-wallet.ts +427 -229
  69. package/src/tests/address.test.ts +87 -1
  70. package/src/tests/integration/retry.test.ts +78 -0
  71. package/src/tests/integration/ssp/static-deposit-validation.test.ts +1 -1
  72. package/src/tests/integration/transfer.test.ts +285 -1
  73. package/src/tests/integration/wallet.test.ts +160 -0
  74. package/src/tests/{tokens.test.ts → token-hashing.test.ts} +150 -162
  75. package/src/tests/token-outputs.test.ts +194 -0
  76. package/src/tests/utils/spark-testing-wallet.ts +16 -8
  77. package/src/utils/address.ts +152 -11
  78. package/src/utils/invoice-hashing.test.ts +235 -0
  79. package/src/utils/invoice-hashing.ts +227 -0
  80. package/src/utils/mempool.ts +6 -0
  81. package/src/utils/retry.ts +116 -0
  82. package/src/utils/token-hashing.ts +566 -0
  83. package/src/utils/token-transactions.ts +9 -5
  84. package/dist/chunk-7N6R7G3E.js +0 -7
  85. package/dist/spark-wallet.browser-BYlprQpX.d.ts +0 -12
  86. package/dist/spark-wallet.browser-CVI2Ss3u.d.cts +0 -12
  87. package/src/services/tree-creation.ts +0 -893
  88. package/src/tests/integration/tree-creation.test.ts +0 -46
@@ -2,6 +2,7 @@ import { numberToBytesBE } from "@noble/curves/abstract/utils";
2
2
  import {
3
3
  hashTokenTransactionV0,
4
4
  hashTokenTransactionV1,
5
+ hashTokenTransactionV2,
5
6
  } from "../utils/token-hashing.js";
6
7
  import { Network, OutputWithPreviousTransactionData } from "../proto/spark.js";
7
8
  import { TokenTransactionService } from "../services/token-transactions.js";
@@ -30,6 +31,17 @@ const TEST_OPERATOR_PUB_KEY = new Uint8Array([
30
31
  63, 99, 54, 137, 226, 7, 224, 163, 122, 93, 248, 42, 159, 173, 46,
31
32
  ]);
32
33
 
34
+ const TEST_INVOICE_ATTACHMENTS = [
35
+ {
36
+ sparkInvoice:
37
+ "sprt1pgssypkrjhrpzt2hw0ggrmndanmm035ley75nxu3gejaju4wx9nq86lwzfjqsqgjzqqe3zul2fm8a24y576t0ne2ehup5fg2yz4r6hxlhatyu9kpw09s2fk36ta5j0k85qascf6snpuy4sp0rp4ezyspvs4qgmt9d4hnyggzqmpet3s394th85ypaek7eaahc60uj02fnwg5vewew2hrzesra0hqflc0vn",
38
+ },
39
+ {
40
+ sparkInvoice:
41
+ "sprt1pgssypkrjhrpzt2hw0ggrmndanmm035ley75nxu3gejaju4wx9nq86lwzf5ssqgjzqqe3zulcs6h42v0kqkdsv9utxyp5fs2yz4r6hxlhatyu9kpw09s2fk36ta5j0k85qascf6snpuy4sp0rp4ezyszq86z5zryd9nxvmt9d4hnyggzqmpet3s394th85ypaek7eaahc60uj02fnwg5vewew2hrzesra0hql7r5ne",
42
+ },
43
+ ];
44
+
33
45
  const TEST_LEAF_ID = "db1a4e48-0fc5-4f6c-8a80-d9d6c561a436";
34
46
  const TEST_BOND_SATS = 10000;
35
47
  const TEST_LOCKTIME = 100;
@@ -217,6 +229,7 @@ describe("hash token transaction", () => {
217
229
  network: Network.REGTEST,
218
230
  expiryTime: new Date(TEST_EXPIRY_TIME),
219
231
  clientCreatedTimestamp: new Date(TEST_CLIENT_TIMESTAMP),
232
+ invoiceAttachments: [],
220
233
  };
221
234
 
222
235
  const hash = hashTokenTransactionV1(tokenTransaction, false);
@@ -246,6 +259,7 @@ describe("hash token transaction", () => {
246
259
  network: Network.REGTEST,
247
260
  expiryTime: new Date(TEST_EXPIRY_TIME),
248
261
  clientCreatedTimestamp: new Date(TEST_CLIENT_TIMESTAMP),
262
+ invoiceAttachments: [],
249
263
  };
250
264
 
251
265
  const hash = hashTokenTransactionV1(tokenTransaction, false);
@@ -285,6 +299,7 @@ describe("hash token transaction", () => {
285
299
  network: Network.REGTEST,
286
300
  expiryTime: new Date(TEST_EXPIRY_TIME),
287
301
  clientCreatedTimestamp: new Date(TEST_CLIENT_TIMESTAMP),
302
+ invoiceAttachments: [],
288
303
  };
289
304
 
290
305
  const hash = hashTokenTransactionV1(tokenTransaction, false);
@@ -295,177 +310,150 @@ describe("hash token transaction", () => {
295
310
  35,
296
311
  ]);
297
312
  });
298
- });
299
313
 
300
- describe("select token outputs", () => {
301
- let tokenTransactionService: TokenTransactionService;
314
+ it("should produce the exact same hash for mint v2", () => {
315
+ const tokenTransaction = {
316
+ version: 2,
317
+ tokenInputs: {
318
+ $case: "mintInput" as const,
319
+ mintInput: {
320
+ issuerPublicKey: TEST_TOKEN_PUBLIC_KEY,
321
+ tokenIdentifier: TEST_TOKEN_IDENTIFIER,
322
+ },
323
+ },
324
+ tokenOutputs: [
325
+ {
326
+ id: TEST_LEAF_ID,
327
+ ownerPublicKey: TEST_IDENTITY_PUB_KEY,
328
+ withdrawBondSats: TEST_WITHDRAW_BOND_SATS,
329
+ withdrawRelativeBlockLocktime: TEST_WITHDRAW_RELATIVE_BLOCK_LOCKTIME,
330
+ tokenPublicKey: TEST_TOKEN_PUBLIC_KEY,
331
+ tokenAmount: numberToBytesBE(TEST_TOKEN_AMOUNT, 16),
332
+ revocationCommitment: TEST_REVOCATION_PUB_KEY,
333
+ },
334
+ ],
335
+ sparkOperatorIdentityPublicKeys: [TEST_OPERATOR_PUB_KEY],
336
+ network: Network.REGTEST,
337
+ expiryTime: new Date(TEST_EXPIRY_TIME),
338
+ clientCreatedTimestamp: new Date(TEST_CLIENT_TIMESTAMP),
339
+ invoiceAttachments: [],
340
+ };
302
341
 
303
- beforeEach(() => {
304
- const mockConfig = {} as WalletConfigService;
305
- const mockConnectionManager = {} as ConnectionManager;
306
- tokenTransactionService = new TokenTransactionService(
307
- mockConfig,
308
- mockConnectionManager,
309
- );
310
- });
342
+ const hash = hashTokenTransactionV1(tokenTransaction, false);
311
343
 
312
- const createMockTokenOutput = (
313
- id: string,
314
- tokenAmount: bigint,
315
- tokenPublicKey: Uint8Array = new Uint8Array(32).fill(1),
316
- ownerPublicKey: Uint8Array = new Uint8Array(32).fill(2),
317
- ): OutputWithPreviousTransactionData => ({
318
- output: {
319
- id,
320
- ownerPublicKey,
321
- tokenPublicKey,
322
- tokenAmount: numberToBytesBE(tokenAmount, 16),
323
- revocationCommitment: new Uint8Array(32).fill(3),
324
- },
325
- previousTransactionHash: new Uint8Array(32).fill(4),
326
- previousTransactionVout: 0,
344
+ expect(Array.from(hash)).toEqual([
345
+ 2, 4, 36, 141, 246, 170, 160, 204, 181, 102, 122, 220, 56, 182, 138, 153,
346
+ 199, 216, 80, 3, 35, 2, 146, 139, 209, 31, 195, 129, 121, 120, 236, 126,
347
+ ]);
327
348
  });
328
349
 
329
- describe("exact match scenarios", () => {
330
- it("should return exact match when available", () => {
331
- const tokenOutputs = [
332
- createMockTokenOutput("output1", 100n),
333
- createMockTokenOutput("output2", 500n),
334
- createMockTokenOutput("output3", 1000n),
335
- ];
336
-
337
- const result = tokenTransactionService.selectTokenOutputs(
338
- tokenOutputs,
339
- 500n,
340
- "SMALL_FIRST",
341
- );
342
-
343
- expect(result).toHaveLength(1);
344
- expect(result[0]!.output!.id).toBe("output2");
345
- });
346
- });
350
+ it("should produce the exact same hash for create v2", () => {
351
+ const tokenTransaction = {
352
+ version: 2,
353
+ tokenInputs: {
354
+ $case: "createInput" as const,
355
+ createInput: {
356
+ issuerPublicKey: TEST_TOKEN_PUBLIC_KEY,
357
+ tokenName: TEST_TOKEN_NAME,
358
+ tokenTicker: TEST_TOKEN_TICKER,
359
+ decimals: TEST_DECIMALS,
360
+ maxSupply: TEST_MAX_SUPPLY,
361
+ isFreezable: false,
362
+ },
363
+ },
364
+ tokenOutputs: [],
365
+ sparkOperatorIdentityPublicKeys: [TEST_OPERATOR_PUB_KEY],
366
+ network: Network.REGTEST,
367
+ expiryTime: new Date(TEST_EXPIRY_TIME),
368
+ clientCreatedTimestamp: new Date(TEST_CLIENT_TIMESTAMP),
369
+ invoiceAttachments: [],
370
+ };
347
371
 
348
- describe("SMALL_FIRST strategy", () => {
349
- it("should select smallest outputs first when no exact match", () => {
350
- const tokenOutputs = [
351
- createMockTokenOutput("output1", 1000n),
352
- createMockTokenOutput("output2", 100n),
353
- createMockTokenOutput("output3", 300n),
354
- ];
355
-
356
- const result = tokenTransactionService.selectTokenOutputs(
357
- tokenOutputs,
358
- 350n,
359
- "SMALL_FIRST",
360
- );
361
-
362
- expect(result).toHaveLength(2);
363
- expect(result[0]!.output!.id).toBe("output2"); // 100n
364
- expect(result[1]!.output!.id).toBe("output3"); // 300n
365
- // Total: 400n >= 350n
366
- });
367
-
368
- it("should select minimum number of outputs needed", () => {
369
- const tokenOutputs = [
370
- createMockTokenOutput("output1", 50n),
371
- createMockTokenOutput("output2", 100n),
372
- createMockTokenOutput("output3", 200n),
373
- createMockTokenOutput("output4", 1000n),
374
- ];
375
-
376
- const result = tokenTransactionService.selectTokenOutputs(
377
- tokenOutputs,
378
- 300n,
379
- "SMALL_FIRST",
380
- );
381
-
382
- expect(result).toHaveLength(3);
383
- expect(result[0]!.output!.id).toBe("output1"); // 50n
384
- expect(result[1]!.output!.id).toBe("output2"); // 100n
385
- expect(result[2]!.output!.id).toBe("output3"); // 200n
386
- // Total: 350n >= 300n
387
- });
372
+ const hash = hashTokenTransactionV1(tokenTransaction, false);
373
+
374
+ expect(Array.from(hash)).toEqual([
375
+ 92, 161, 134, 55, 164, 211, 69, 97, 149, 43, 29, 110, 94, 225, 55, 59,
376
+ 178, 51, 203, 51, 189, 197, 203, 56, 6, 105, 55, 156, 106, 147, 155, 185,
377
+ ]);
388
378
  });
389
379
 
390
- describe("LARGE_FIRST strategy", () => {
391
- it("should select largest outputs first when no exact match", () => {
392
- const tokenOutputs = [
393
- createMockTokenOutput("output1", 100n),
394
- createMockTokenOutput("output2", 1000n),
395
- createMockTokenOutput("output3", 300n),
396
- ];
397
-
398
- const result = tokenTransactionService.selectTokenOutputs(
399
- tokenOutputs,
400
- 350n,
401
- "LARGE_FIRST",
402
- );
403
-
404
- expect(result).toHaveLength(1);
405
- expect(result[0]!.output!.id).toBe("output2"); // 1000n >= 350n
406
- });
407
-
408
- it("should select multiple outputs if largest is insufficient", () => {
409
- const tokenOutputs = [
410
- createMockTokenOutput("output1", 100n),
411
- createMockTokenOutput("output2", 200n),
412
- createMockTokenOutput("output3", 150n),
413
- ];
414
-
415
- const result = tokenTransactionService.selectTokenOutputs(
416
- tokenOutputs,
417
- 350n,
418
- "LARGE_FIRST",
419
- );
420
-
421
- expect(result).toHaveLength(2);
422
- expect(result[0]!.output!.id).toBe("output2"); // 200n
423
- expect(result[1]!.output!.id).toBe("output3"); // 150n
424
- // Total: 350n >= 350n
425
- });
380
+ it("should produce the exact same hash for transfer v2", () => {
381
+ const tokenTransaction = {
382
+ version: 2,
383
+ tokenInputs: {
384
+ $case: "transferInput" as const,
385
+ transferInput: {
386
+ outputsToSpend: [
387
+ {
388
+ prevTokenTransactionHash: PREV_TX_HASH,
389
+ prevTokenTransactionVout: 0,
390
+ },
391
+ ],
392
+ },
393
+ },
394
+ tokenOutputs: [
395
+ {
396
+ id: TEST_LEAF_ID,
397
+ ownerPublicKey: TEST_IDENTITY_PUB_KEY,
398
+ tokenPublicKey: TEST_TOKEN_PUBLIC_KEY,
399
+ tokenAmount: numberToBytesBE(TEST_TOKEN_AMOUNT, 16),
400
+ revocationCommitment: TEST_REVOCATION_PUB_KEY,
401
+ withdrawBondSats: TEST_BOND_SATS,
402
+ withdrawRelativeBlockLocktime: TEST_LOCKTIME,
403
+ },
404
+ ],
405
+ sparkOperatorIdentityPublicKeys: [TEST_OPERATOR_PUB_KEY],
406
+ network: Network.REGTEST,
407
+ expiryTime: new Date(TEST_EXPIRY_TIME),
408
+ clientCreatedTimestamp: new Date(TEST_CLIENT_TIMESTAMP),
409
+ invoiceAttachments: [],
410
+ };
411
+
412
+ const hash = hashTokenTransactionV2(tokenTransaction, false);
413
+
414
+ expect(Array.from(hash)).toEqual([
415
+ 21, 226, 190, 223, 0, 62, 121, 223, 94, 193, 34, 62, 186, 68, 52, 197, 6,
416
+ 189, 107, 37, 65, 141, 222, 109, 212, 128, 5, 40, 81, 247, 15, 249,
417
+ ]);
426
418
  });
427
419
 
428
- describe("edge cases", () => {
429
- it("should handle single output that exactly matches", () => {
430
- const tokenOutputs = [createMockTokenOutput("output1", 500n)];
431
-
432
- const result = tokenTransactionService.selectTokenOutputs(
433
- tokenOutputs,
434
- 500n,
435
- "SMALL_FIRST",
436
- );
437
-
438
- expect(result).toHaveLength(1);
439
- expect(result[0]!.output!.id).toBe("output1");
440
- });
441
-
442
- it("should handle zero amount request", () => {
443
- const tokenOutputs = [createMockTokenOutput("output1", 100n)];
444
-
445
- const result = tokenTransactionService.selectTokenOutputs(
446
- tokenOutputs,
447
- 0n,
448
- "SMALL_FIRST",
449
- );
450
-
451
- expect(result).toHaveLength(0);
452
- });
453
-
454
- it("should select all outputs if needed", () => {
455
- const tokenOutputs = [
456
- createMockTokenOutput("output1", 100n),
457
- createMockTokenOutput("output2", 200n),
458
- createMockTokenOutput("output3", 300n),
459
- ];
460
-
461
- const result = tokenTransactionService.selectTokenOutputs(
462
- tokenOutputs,
463
- 600n,
464
- "SMALL_FIRST",
465
- );
466
-
467
- expect(result).toHaveLength(3);
468
- // Total: 600n >= 600n
469
- });
420
+ it("should produce the exact same hash for transfer v2 with invoice attachments", () => {
421
+ const tokenTransaction = {
422
+ version: 2,
423
+ tokenInputs: {
424
+ $case: "transferInput" as const,
425
+ transferInput: {
426
+ outputsToSpend: [
427
+ {
428
+ prevTokenTransactionHash: PREV_TX_HASH,
429
+ prevTokenTransactionVout: 0,
430
+ },
431
+ ],
432
+ },
433
+ },
434
+ tokenOutputs: [
435
+ {
436
+ id: TEST_LEAF_ID,
437
+ ownerPublicKey: TEST_IDENTITY_PUB_KEY,
438
+ tokenPublicKey: TEST_TOKEN_PUBLIC_KEY,
439
+ tokenAmount: numberToBytesBE(TEST_TOKEN_AMOUNT, 16),
440
+ revocationCommitment: TEST_REVOCATION_PUB_KEY,
441
+ withdrawBondSats: TEST_BOND_SATS,
442
+ withdrawRelativeBlockLocktime: TEST_LOCKTIME,
443
+ },
444
+ ],
445
+ sparkOperatorIdentityPublicKeys: [TEST_OPERATOR_PUB_KEY],
446
+ network: Network.REGTEST,
447
+ expiryTime: new Date(TEST_EXPIRY_TIME),
448
+ clientCreatedTimestamp: new Date(TEST_CLIENT_TIMESTAMP),
449
+ invoiceAttachments: TEST_INVOICE_ATTACHMENTS,
450
+ };
451
+
452
+ const hash = hashTokenTransactionV2(tokenTransaction, false);
453
+
454
+ expect(Array.from(hash)).toEqual([
455
+ 139, 4, 220, 112, 69, 32, 149, 81, 90, 67, 151, 101, 240, 182, 13, 123,
456
+ 70, 4, 153, 159, 172, 225, 15, 120, 71, 219, 154, 27, 72, 167, 2, 149,
457
+ ]);
470
458
  });
471
459
  });
@@ -0,0 +1,194 @@
1
+ import { numberToBytesBE } from "@noble/curves/abstract/utils";
2
+ import { OutputWithPreviousTransactionData } from "../proto/spark.js";
3
+ import { TokenTransactionService } from "../services/token-transactions.js";
4
+ import { WalletConfigService } from "../services/config.js";
5
+ import { ConnectionManager } from "../services/connection.js";
6
+ import { ValidationError } from "../errors/types.js";
7
+
8
+ describe("select token outputs", () => {
9
+ let tokenTransactionService: TokenTransactionService;
10
+
11
+ beforeEach(() => {
12
+ const mockConfig = {} as WalletConfigService;
13
+ const mockConnectionManager = {} as ConnectionManager;
14
+ tokenTransactionService = new TokenTransactionService(
15
+ mockConfig,
16
+ mockConnectionManager,
17
+ );
18
+ });
19
+
20
+ const createMockTokenOutput = (
21
+ id: string,
22
+ tokenAmount: bigint,
23
+ tokenPublicKey: Uint8Array = new Uint8Array(32).fill(1),
24
+ ownerPublicKey: Uint8Array = new Uint8Array(32).fill(2),
25
+ ): OutputWithPreviousTransactionData => ({
26
+ output: {
27
+ id,
28
+ ownerPublicKey,
29
+ tokenPublicKey,
30
+ tokenAmount: numberToBytesBE(tokenAmount, 16),
31
+ revocationCommitment: new Uint8Array(32).fill(3),
32
+ },
33
+ previousTransactionHash: new Uint8Array(32).fill(4),
34
+ previousTransactionVout: 0,
35
+ });
36
+
37
+ describe("exact match scenarios", () => {
38
+ it("should return exact match when available", () => {
39
+ const tokenOutputs = [
40
+ createMockTokenOutput("output1", 100n),
41
+ createMockTokenOutput("output2", 500n),
42
+ createMockTokenOutput("output3", 1000n),
43
+ ];
44
+
45
+ const result = tokenTransactionService.selectTokenOutputs(
46
+ tokenOutputs,
47
+ 500n,
48
+ "SMALL_FIRST",
49
+ );
50
+
51
+ expect(result).toHaveLength(1);
52
+ expect(result[0]!.output!.id).toBe("output2");
53
+ });
54
+ });
55
+
56
+ describe("SMALL_FIRST strategy", () => {
57
+ it("should select smallest outputs first when no exact match", () => {
58
+ const tokenOutputs = [
59
+ createMockTokenOutput("output1", 1000n),
60
+ createMockTokenOutput("output2", 100n),
61
+ createMockTokenOutput("output3", 300n),
62
+ ];
63
+
64
+ const result = tokenTransactionService.selectTokenOutputs(
65
+ tokenOutputs,
66
+ 350n,
67
+ "SMALL_FIRST",
68
+ );
69
+
70
+ expect(result).toHaveLength(2);
71
+ expect(result[0]!.output!.id).toBe("output2"); // 100n
72
+ expect(result[1]!.output!.id).toBe("output3"); // 300n
73
+ // Total: 400n >= 350n
74
+ });
75
+
76
+ it("should select minimum number of outputs needed", () => {
77
+ const tokenOutputs = [
78
+ createMockTokenOutput("output1", 50n),
79
+ createMockTokenOutput("output2", 100n),
80
+ createMockTokenOutput("output3", 200n),
81
+ createMockTokenOutput("output4", 1000n),
82
+ ];
83
+
84
+ const result = tokenTransactionService.selectTokenOutputs(
85
+ tokenOutputs,
86
+ 300n,
87
+ "SMALL_FIRST",
88
+ );
89
+
90
+ expect(result).toHaveLength(3);
91
+ expect(result[0]!.output!.id).toBe("output1"); // 50n
92
+ expect(result[1]!.output!.id).toBe("output2"); // 100n
93
+ expect(result[2]!.output!.id).toBe("output3"); // 200n
94
+ // Total: 350n >= 300n
95
+ });
96
+ });
97
+
98
+ describe("LARGE_FIRST strategy", () => {
99
+ it("should select largest outputs first when no exact match", () => {
100
+ const tokenOutputs = [
101
+ createMockTokenOutput("output1", 100n),
102
+ createMockTokenOutput("output2", 1000n),
103
+ createMockTokenOutput("output3", 300n),
104
+ ];
105
+
106
+ const result = tokenTransactionService.selectTokenOutputs(
107
+ tokenOutputs,
108
+ 350n,
109
+ "LARGE_FIRST",
110
+ );
111
+
112
+ expect(result).toHaveLength(1);
113
+ expect(result[0]!.output!.id).toBe("output2"); // 1000n >= 350n
114
+ });
115
+
116
+ it("should select multiple outputs if largest is insufficient", () => {
117
+ const tokenOutputs = [
118
+ createMockTokenOutput("output1", 100n),
119
+ createMockTokenOutput("output2", 200n),
120
+ createMockTokenOutput("output3", 150n),
121
+ ];
122
+
123
+ const result = tokenTransactionService.selectTokenOutputs(
124
+ tokenOutputs,
125
+ 350n,
126
+ "LARGE_FIRST",
127
+ );
128
+
129
+ expect(result).toHaveLength(2);
130
+ expect(result[0]!.output!.id).toBe("output2"); // 200n
131
+ expect(result[1]!.output!.id).toBe("output3"); // 150n
132
+ // Total: 350n >= 350n
133
+ });
134
+ });
135
+
136
+ describe("edge cases", () => {
137
+ it("should handle single output that exactly matches", () => {
138
+ const tokenOutputs = [createMockTokenOutput("output1", 500n)];
139
+
140
+ const result = tokenTransactionService.selectTokenOutputs(
141
+ tokenOutputs,
142
+ 500n,
143
+ "SMALL_FIRST",
144
+ );
145
+
146
+ expect(result).toHaveLength(1);
147
+ expect(result[0]!.output!.id).toBe("output1");
148
+ });
149
+
150
+ it("should throw ValidationError when tokenAmount is 0", () => {
151
+ const tokenOutputs = [createMockTokenOutput("output1", 100n)];
152
+
153
+ expect(() =>
154
+ tokenTransactionService.selectTokenOutputs(
155
+ tokenOutputs,
156
+ 0n,
157
+ "SMALL_FIRST",
158
+ ),
159
+ ).toThrow(ValidationError);
160
+ });
161
+
162
+ it("should throw ValidationError when available token amount is less than needed", () => {
163
+ const tokenOutputs = [
164
+ createMockTokenOutput("output1", 100n),
165
+ createMockTokenOutput("output2", 50n),
166
+ ];
167
+
168
+ expect(() =>
169
+ tokenTransactionService.selectTokenOutputs(
170
+ tokenOutputs,
171
+ 500n,
172
+ "SMALL_FIRST",
173
+ ),
174
+ ).toThrow(ValidationError);
175
+ });
176
+
177
+ it("should select all outputs if needed", () => {
178
+ const tokenOutputs = [
179
+ createMockTokenOutput("output1", 100n),
180
+ createMockTokenOutput("output2", 200n),
181
+ createMockTokenOutput("output3", 300n),
182
+ ];
183
+
184
+ const result = tokenTransactionService.selectTokenOutputs(
185
+ tokenOutputs,
186
+ 600n,
187
+ "SMALL_FIRST",
188
+ );
189
+
190
+ expect(result).toHaveLength(3);
191
+ // Total: 600n >= 600n
192
+ });
193
+ });
194
+ });
@@ -35,14 +35,22 @@ export class SparkWalletTesting
35
35
  disableEvents,
36
36
  );
37
37
 
38
- const initResponse = await wallet.initWallet(
39
- props.mnemonicOrSeed,
40
- props.accountNumber,
41
- );
42
- return {
43
- wallet,
44
- mnemonic: initResponse?.mnemonic,
45
- };
38
+ if (props.options && props.options.signerWithPreExistingKeys) {
39
+ await wallet.initWalletWithoutSeed();
40
+
41
+ return {
42
+ wallet,
43
+ };
44
+ } else {
45
+ const initResponse = await wallet.initWallet(
46
+ props.mnemonicOrSeed,
47
+ props.accountNumber,
48
+ );
49
+ return {
50
+ wallet,
51
+ mnemonic: initResponse?.mnemonic,
52
+ };
53
+ }
46
54
  }
47
55
 
48
56
  protected override async setupBackgroundStream() {