@ledgerhq/coin-sui 0.9.0 → 0.10.0-nightly.1

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 (159) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/.unimportedrc.json +1 -1
  3. package/CHANGELOG.md +26 -0
  4. package/lib/api/index.d.ts.map +1 -1
  5. package/lib/api/index.integration.test.js +21 -2
  6. package/lib/api/index.integration.test.js.map +1 -1
  7. package/lib/api/index.js +2 -0
  8. package/lib/api/index.js.map +1 -1
  9. package/lib/api/index.test.js +37 -0
  10. package/lib/api/index.test.js.map +1 -1
  11. package/lib/bridge/buildTransaction.d.ts +1 -3
  12. package/lib/bridge/buildTransaction.d.ts.map +1 -1
  13. package/lib/bridge/buildTransaction.integration.test.js +17 -35
  14. package/lib/bridge/buildTransaction.integration.test.js.map +1 -1
  15. package/lib/bridge/buildTransaction.js +10 -21
  16. package/lib/bridge/buildTransaction.js.map +1 -1
  17. package/lib/bridge/buildTransaction.test.js +33 -130
  18. package/lib/bridge/buildTransaction.test.js.map +1 -1
  19. package/lib/bridge/getFeesForTransaction.d.ts.map +1 -1
  20. package/lib/bridge/getFeesForTransaction.js +5 -1
  21. package/lib/bridge/getFeesForTransaction.js.map +1 -1
  22. package/lib/logic/craftTransaction.d.ts +2 -2
  23. package/lib/logic/craftTransaction.d.ts.map +1 -1
  24. package/lib/logic/craftTransaction.integration.test.d.ts +2 -0
  25. package/lib/logic/craftTransaction.integration.test.d.ts.map +1 -0
  26. package/lib/logic/craftTransaction.integration.test.js +56 -0
  27. package/lib/logic/craftTransaction.integration.test.js.map +1 -0
  28. package/lib/logic/craftTransaction.js +2 -2
  29. package/lib/logic/craftTransaction.js.map +1 -1
  30. package/lib/logic/estimateFees.d.ts +1 -1
  31. package/lib/logic/estimateFees.d.ts.map +1 -1
  32. package/lib/logic/estimateFees.integration.test.d.ts +2 -0
  33. package/lib/logic/estimateFees.integration.test.d.ts.map +1 -0
  34. package/lib/logic/estimateFees.integration.test.js +79 -0
  35. package/lib/logic/estimateFees.integration.test.js.map +1 -0
  36. package/lib/logic/estimateFees.js +7 -2
  37. package/lib/logic/estimateFees.js.map +1 -1
  38. package/lib/logic/getBalance.d.ts.map +1 -1
  39. package/lib/logic/getBalance.integration.test.d.ts +2 -0
  40. package/lib/logic/getBalance.integration.test.d.ts.map +1 -0
  41. package/lib/logic/getBalance.integration.test.js +56 -0
  42. package/lib/logic/getBalance.integration.test.js.map +1 -0
  43. package/lib/logic/getBalance.js +18 -7
  44. package/lib/logic/getBalance.js.map +1 -1
  45. package/lib/logic/getBalance.test.js +49 -7
  46. package/lib/logic/getBalance.test.js.map +1 -1
  47. package/lib/logic/index.d.ts +1 -0
  48. package/lib/logic/index.d.ts.map +1 -1
  49. package/lib/logic/index.js +4 -1
  50. package/lib/logic/index.js.map +1 -1
  51. package/lib/logic/staking.d.ts +4 -0
  52. package/lib/logic/staking.d.ts.map +1 -0
  53. package/lib/logic/staking.js +36 -0
  54. package/lib/logic/staking.js.map +1 -0
  55. package/lib/network/index.d.ts +6 -6
  56. package/lib/network/index.d.ts.map +1 -1
  57. package/lib/network/index.js +9 -3
  58. package/lib/network/index.js.map +1 -1
  59. package/lib/network/sdk.d.ts +27 -19
  60. package/lib/network/sdk.d.ts.map +1 -1
  61. package/lib/network/sdk.integration.test.js +21 -22
  62. package/lib/network/sdk.integration.test.js.map +1 -1
  63. package/lib/network/sdk.js +113 -68
  64. package/lib/network/sdk.js.map +1 -1
  65. package/lib/network/sdk.test.js +148 -65
  66. package/lib/network/sdk.test.js.map +1 -1
  67. package/lib/test/testUtils.d.ts +2 -0
  68. package/lib/test/testUtils.d.ts.map +1 -0
  69. package/lib/test/testUtils.js +35 -0
  70. package/lib/test/testUtils.js.map +1 -0
  71. package/lib-es/api/index.d.ts.map +1 -1
  72. package/lib-es/api/index.integration.test.js +21 -2
  73. package/lib-es/api/index.integration.test.js.map +1 -1
  74. package/lib-es/api/index.js +3 -1
  75. package/lib-es/api/index.js.map +1 -1
  76. package/lib-es/api/index.test.js +37 -0
  77. package/lib-es/api/index.test.js.map +1 -1
  78. package/lib-es/bridge/buildTransaction.d.ts +1 -3
  79. package/lib-es/bridge/buildTransaction.d.ts.map +1 -1
  80. package/lib-es/bridge/buildTransaction.integration.test.js +13 -31
  81. package/lib-es/bridge/buildTransaction.integration.test.js.map +1 -1
  82. package/lib-es/bridge/buildTransaction.js +9 -16
  83. package/lib-es/bridge/buildTransaction.js.map +1 -1
  84. package/lib-es/bridge/buildTransaction.test.js +34 -131
  85. package/lib-es/bridge/buildTransaction.test.js.map +1 -1
  86. package/lib-es/bridge/getFeesForTransaction.d.ts.map +1 -1
  87. package/lib-es/bridge/getFeesForTransaction.js +5 -1
  88. package/lib-es/bridge/getFeesForTransaction.js.map +1 -1
  89. package/lib-es/logic/craftTransaction.d.ts +2 -2
  90. package/lib-es/logic/craftTransaction.d.ts.map +1 -1
  91. package/lib-es/logic/craftTransaction.integration.test.d.ts +2 -0
  92. package/lib-es/logic/craftTransaction.integration.test.d.ts.map +1 -0
  93. package/lib-es/logic/craftTransaction.integration.test.js +51 -0
  94. package/lib-es/logic/craftTransaction.integration.test.js.map +1 -0
  95. package/lib-es/logic/craftTransaction.js +2 -2
  96. package/lib-es/logic/craftTransaction.js.map +1 -1
  97. package/lib-es/logic/estimateFees.d.ts +1 -1
  98. package/lib-es/logic/estimateFees.d.ts.map +1 -1
  99. package/lib-es/logic/estimateFees.integration.test.d.ts +2 -0
  100. package/lib-es/logic/estimateFees.integration.test.d.ts.map +1 -0
  101. package/lib-es/logic/estimateFees.integration.test.js +74 -0
  102. package/lib-es/logic/estimateFees.integration.test.js.map +1 -0
  103. package/lib-es/logic/estimateFees.js +7 -2
  104. package/lib-es/logic/estimateFees.js.map +1 -1
  105. package/lib-es/logic/getBalance.d.ts.map +1 -1
  106. package/lib-es/logic/getBalance.integration.test.d.ts +2 -0
  107. package/lib-es/logic/getBalance.integration.test.d.ts.map +1 -0
  108. package/lib-es/logic/getBalance.integration.test.js +51 -0
  109. package/lib-es/logic/getBalance.integration.test.js.map +1 -0
  110. package/lib-es/logic/getBalance.js +19 -8
  111. package/lib-es/logic/getBalance.js.map +1 -1
  112. package/lib-es/logic/getBalance.test.js +50 -8
  113. package/lib-es/logic/getBalance.test.js.map +1 -1
  114. package/lib-es/logic/index.d.ts +1 -0
  115. package/lib-es/logic/index.d.ts.map +1 -1
  116. package/lib-es/logic/index.js +1 -0
  117. package/lib-es/logic/index.js.map +1 -1
  118. package/lib-es/logic/staking.d.ts +4 -0
  119. package/lib-es/logic/staking.d.ts.map +1 -0
  120. package/lib-es/logic/staking.js +8 -0
  121. package/lib-es/logic/staking.js.map +1 -0
  122. package/lib-es/network/index.d.ts +6 -6
  123. package/lib-es/network/index.d.ts.map +1 -1
  124. package/lib-es/network/index.js +6 -3
  125. package/lib-es/network/index.js.map +1 -1
  126. package/lib-es/network/sdk.d.ts +27 -19
  127. package/lib-es/network/sdk.d.ts.map +1 -1
  128. package/lib-es/network/sdk.integration.test.js +22 -23
  129. package/lib-es/network/sdk.integration.test.js.map +1 -1
  130. package/lib-es/network/sdk.js +106 -64
  131. package/lib-es/network/sdk.js.map +1 -1
  132. package/lib-es/network/sdk.test.js +148 -65
  133. package/lib-es/network/sdk.test.js.map +1 -1
  134. package/lib-es/test/testUtils.d.ts +2 -0
  135. package/lib-es/test/testUtils.d.ts.map +1 -0
  136. package/lib-es/test/testUtils.js +31 -0
  137. package/lib-es/test/testUtils.js.map +1 -0
  138. package/package.json +7 -7
  139. package/src/api/index.integration.test.ts +24 -2
  140. package/src/api/index.test.ts +40 -0
  141. package/src/api/index.ts +4 -0
  142. package/src/bridge/buildTransaction.integration.test.ts +14 -39
  143. package/src/bridge/buildTransaction.test.ts +37 -160
  144. package/src/bridge/buildTransaction.ts +12 -21
  145. package/src/bridge/getFeesForTransaction.ts +6 -1
  146. package/src/logic/craftTransaction.integration.test.ts +63 -0
  147. package/src/logic/craftTransaction.ts +4 -4
  148. package/src/logic/estimateFees.integration.test.ts +89 -0
  149. package/src/logic/estimateFees.ts +7 -1
  150. package/src/logic/getBalance.integration.test.ts +66 -0
  151. package/src/logic/getBalance.test.ts +58 -8
  152. package/src/logic/getBalance.ts +24 -8
  153. package/src/logic/index.ts +1 -0
  154. package/src/logic/staking.ts +10 -0
  155. package/src/network/index.ts +12 -3
  156. package/src/network/sdk.integration.test.ts +25 -22
  157. package/src/network/sdk.test.ts +186 -77
  158. package/src/network/sdk.ts +149 -93
  159. package/src/test/testUtils.ts +38 -0
@@ -1,12 +1,28 @@
1
- import { getAccount } from "../network";
1
+ import { getStakes, getAllBalancesCached } from "../network";
2
2
  import { Balance } from "@ledgerhq/coin-framework/api/types";
3
+ import { toSuiAsset } from "../network/sdk";
3
4
 
4
5
  export async function getBalance(address: string): Promise<Balance[]> {
5
- const { balance } = await getAccount(address);
6
- return [
7
- {
8
- value: BigInt(balance.toString()),
9
- asset: { type: "native" },
10
- },
11
- ];
6
+ const [native, staking] = await Promise.all([
7
+ getNativeBalance(address),
8
+ getStakingBalances(address),
9
+ ]);
10
+ return [...native, ...staking];
12
11
  }
12
+
13
+ const getNativeBalance = async (address: string): Promise<Balance[]> => {
14
+ const balances = await getAllBalancesCached(address);
15
+ return balances.map(({ coinType, totalBalance }) => ({
16
+ value: BigInt(totalBalance),
17
+ asset: toSuiAsset(coinType),
18
+ }));
19
+ };
20
+
21
+ const getStakingBalances = (address: string): Promise<Balance[]> =>
22
+ getStakes(address).then(stakes =>
23
+ stakes.map(stake => ({
24
+ value: stake.amount,
25
+ asset: stake.asset,
26
+ stake: stake,
27
+ })),
28
+ );
@@ -6,3 +6,4 @@ export { getBalance } from "./getBalance";
6
6
  export { lastBlock } from "./lastBlock";
7
7
  export { getBlock, getBlockInfo } from "./getBlock";
8
8
  export { listOperations } from "./listOperations";
9
+ export { getStakes, getRewards } from "./staking";
@@ -0,0 +1,10 @@
1
+ import { Cursor, Page, Stake, Reward } from "@ledgerhq/coin-framework/api/types";
2
+ import * as sdk from "../network";
3
+
4
+ export const getStakes = (address: string, _cursor?: Cursor): Promise<Page<Stake>> => {
5
+ return sdk.getStakes(address).then(stakes => ({ items: stakes }));
6
+ };
7
+
8
+ export const getRewards = (_address: string, _cursor?: Cursor): Promise<Page<Reward>> => {
9
+ throw new Error("getRewards is not supported");
10
+ };
@@ -1,25 +1,34 @@
1
1
  import {
2
- getAccount,
3
2
  getAccountBalances,
3
+ getAllBalancesCached,
4
4
  getOperations,
5
+ getBlock,
6
+ getBlockInfo,
7
+ getStakes,
5
8
  paymentInfo,
6
9
  createTransaction,
7
10
  executeTransactionBlock,
8
11
  } from "./sdk";
9
12
 
10
13
  export {
11
- getAccount,
12
14
  getAccountBalances,
15
+ getAllBalancesCached,
13
16
  getOperations,
17
+ getBlock,
18
+ getBlockInfo,
19
+ getStakes,
14
20
  paymentInfo,
15
21
  createTransaction,
16
22
  executeTransactionBlock,
17
23
  };
18
24
 
19
25
  export default {
20
- getAccount,
21
26
  getAccountBalances,
27
+ getAllBalancesCached,
22
28
  getOperations,
29
+ getBlock,
30
+ getBlockInfo,
31
+ getStakes,
23
32
  paymentInfo,
24
33
  createTransaction,
25
34
  executeTransactionBlock,
@@ -10,6 +10,7 @@ import {
10
10
  paymentInfo,
11
11
  getBlock,
12
12
  getBlockInfo,
13
+ getStakes,
13
14
  } from "./sdk";
14
15
  import { getFullnodeUrl } from "@mysten/sui/client";
15
16
 
@@ -189,26 +190,6 @@ describe("SUI SDK Integration tests", () => {
189
190
  expect(checkpointById.transactions.length).toEqual(19);
190
191
  expect(checkpointById.digest).toEqual(checkpointBySequenceNumber.digest);
191
192
  });
192
- /*
193
- test("getCheckpointWithTransactions", async () => {
194
- const { checkpoint: checkpointById, transactions: checkpointByIdTransactions } =
195
- await getCheckpointWithTransactions("3Q4zW4ieWnNgKLEq6kvVfP35PX2tBDUJERTWYyyz4eyS");
196
- const {
197
- checkpoint: checkpointBySequenceNumber,
198
- transactions: checkpointBySequenceNumberTransactions,
199
- } = await getCheckpointWithTransactions("164167623");
200
- expect(checkpointById.epoch).toEqual("814");
201
- expect(checkpointById.sequenceNumber).toEqual("164167623");
202
- expect(checkpointById.timestampMs).toEqual("1751696298663");
203
- expect(checkpointById.digest).toEqual("3Q4zW4ieWnNgKLEq6kvVfP35PX2tBDUJERTWYyyz4eyS");
204
- expect(checkpointById.previousDigest).toEqual("6VKtVnpxstb968SzSrgYJ7zy5LXgFB6PnNHSJsT8Wr4E");
205
- expect(checkpointById.transactions.length).toEqual(19);
206
- expect(checkpointById).toEqual(checkpointBySequenceNumber);
207
- expect(checkpointByIdTransactions.length).toEqual(19);
208
- expect(checkpointBySequenceNumberTransactions.length).toEqual(19);
209
- expect(checkpointByIdTransactions).toEqual(checkpointBySequenceNumberTransactions);
210
- });
211
- */
212
193
  });
213
194
 
214
195
  describe("getBlockInfo", () => {
@@ -219,7 +200,7 @@ describe("SUI SDK Integration tests", () => {
219
200
  expect(blockById.hash).toEqual("3Q4zW4ieWnNgKLEq6kvVfP35PX2tBDUJERTWYyyz4eyS");
220
201
  expect(blockById.time).toEqual(new Date(1751696298663));
221
202
  expect(blockById.parent?.height).toEqual(164167622);
222
- // expect(blockById.parent?.hash).toEqual("TODO");
203
+ expect(blockById.parent?.hash).toEqual("6VKtVnpxstb968SzSrgYJ7zy5LXgFB6PnNHSJsT8Wr4E");
223
204
  expect(blockById).toEqual(blockBySequenceNumber);
224
205
  });
225
206
  });
@@ -232,9 +213,31 @@ describe("SUI SDK Integration tests", () => {
232
213
  expect(blockById.info.hash).toEqual("3Q4zW4ieWnNgKLEq6kvVfP35PX2tBDUJERTWYyyz4eyS");
233
214
  expect(blockById.info.time).toEqual(new Date(1751696298663));
234
215
  expect(blockById.info.parent?.height).toEqual(164167622);
235
- // expect(blockById.info.parent?.hash).toEqual("TODO");
216
+ expect(blockById.info.parent?.hash).toEqual("6VKtVnpxstb968SzSrgYJ7zy5LXgFB6PnNHSJsT8Wr4E");
236
217
  expect(blockById.transactions.length).toEqual(19);
237
218
  expect(blockById).toEqual(blockBySequenceNumber);
238
219
  });
239
220
  });
221
+
222
+ describe("getStakes", () => {
223
+ test("Account 0xea438b6ce07762ea61e04af4d405dfcf197d5f77d30765f365f75460380f3cce", async () => {
224
+ const stakes = await getStakes(
225
+ "0xea438b6ce07762ea61e04af4d405dfcf197d5f77d30765f365f75460380f3cce",
226
+ );
227
+ expect(stakes.length).toBeGreaterThan(0);
228
+ stakes.forEach(stake => {
229
+ expect(stake.uid).toMatch(/0x[0-9a-z]+/);
230
+ expect(stake.address).toMatch(/0x[0-9a-z]+/);
231
+ expect(stake.delegate).toMatch(/0x[0-9a-z]+/);
232
+ expect(stake.state).toMatch(/(activating|active|inactive)/);
233
+ expect(stake.asset).toEqual({ type: "native" });
234
+ expect(stake.amount).toBeGreaterThan(0);
235
+ expect(stake.amountDeposited).toBeGreaterThan(0);
236
+ expect(stake.amountRewarded).toBeGreaterThanOrEqual(0);
237
+ // @ts-expect-error properties are defined
238
+ expect(stake.amount).toEqual(stake.amountDeposited + stake.amountRewarded);
239
+ expect(stake.details).toBeDefined();
240
+ });
241
+ });
242
+ });
240
243
  });
@@ -1,50 +1,3 @@
1
- // Move all jest.mock calls to the very top
2
- jest.mock("../config", () => ({
3
- __esModule: true,
4
- default: {
5
- getCoinConfig: jest.fn(() => ({ node: { url: "http://test.com" } })),
6
- setCoinConfig: jest.fn(),
7
- },
8
- }));
9
-
10
- jest.mock("../utils", () => ({
11
- ensureAddressFormat: jest.fn((addr: string) => addr),
12
- }));
13
-
14
- jest.mock("@ledgerhq/live-network/cache", () => ({
15
- makeLRUCache: jest.fn(() => jest.fn()),
16
- minutes: jest.fn(() => 60000),
17
- }));
18
-
19
- jest.mock("@ledgerhq/logs", () => ({
20
- log: jest.fn(),
21
- }));
22
-
23
- jest.mock("@mysten/sui/client", () => {
24
- const mockClient = {
25
- queryTransactionBlocks: jest.fn(),
26
- getBalance: jest.fn(),
27
- getLatestCheckpointSequenceNumber: jest.fn(),
28
- getCheckpoint: jest.fn(),
29
- dryRunTransactionBlock: jest.fn(),
30
- executeTransactionBlock: jest.fn(),
31
- };
32
- return {
33
- SuiClient: jest.fn().mockImplementation(() => mockClient),
34
- };
35
- });
36
-
37
- jest.mock("@mysten/sui/transactions", () => ({
38
- Transaction: jest.fn().mockImplementation(() => ({
39
- setSender: jest.fn().mockReturnThis(),
40
- splitCoins: jest.fn().mockReturnValue([{ id: "coin1" }]),
41
- transferObjects: jest.fn().mockReturnThis(),
42
- gas: { id: "gas" },
43
- build: jest.fn().mockResolvedValue(new Uint8Array([1, 2, 3])),
44
- })),
45
- }));
46
-
47
- // Now import after mocks
48
1
  import * as sdk from "./sdk";
49
2
  import coinConfig from "../config";
50
3
 
@@ -215,6 +168,16 @@ const mockTransaction = {
215
168
 
216
169
  const mockApi = new SuiClient({ url: "mock" }) as jest.Mocked<SuiClient>;
217
170
 
171
+ // Helper function to generate mock coins from an array of balances
172
+ const createMockCoins = (balances: string[]): any[] => {
173
+ return balances.map((balance, index) => ({
174
+ coinObjectId: `0xcoin${index + 1}`,
175
+ balance,
176
+ digest: `0xdigest${index + 1}`,
177
+ version: "1",
178
+ }));
179
+ };
180
+
218
181
  beforeAll(() => {
219
182
  coinConfig.setCoinConfig(() => ({
220
183
  status: {
@@ -320,9 +283,9 @@ describe("SDK Functions", () => {
320
283
  });
321
284
 
322
285
  test("getOperationDate should return correct date", () => {
323
- expect(sdk.getOperationDate(mockTransaction as SuiTransactionBlockResponse)).toEqual(
324
- new Date("2025-03-18T10:40:54.878Z"),
325
- );
286
+ const date = sdk.getOperationDate(mockTransaction as SuiTransactionBlockResponse);
287
+ expect(date).toBeDefined();
288
+ expect(date).toBeInstanceOf(Date);
326
289
  });
327
290
 
328
291
  test("getOperationCoinType should extract token coin type", () => {
@@ -534,33 +497,6 @@ describe("SDK Functions", () => {
534
497
  expect(info).toHaveProperty("fees");
535
498
  });
536
499
 
537
- test("getCoinObjectIds should return array of object IDs for token transactions", async () => {
538
- const address = "0x6e143fe0a8ca010a86580dafac44298e5b1b7d73efc345356a59a15f0d7824f0";
539
- const transaction = {
540
- mode: "token.send" as const,
541
- coinType: "0x123::test::TOKEN",
542
- amount: new BigNumber(100),
543
- recipient: "0x33444cf803c690db96527cec67e3c9ab512596f4ba2d4eace43f0b4f716e0164",
544
- };
545
-
546
- const coinObjectIds = await sdk.getCoinObjectIds(address, transaction);
547
- expect(Array.isArray(coinObjectIds)).toBe(true);
548
- expect(coinObjectIds).toContain("0xtest_coin_object_id");
549
- });
550
-
551
- test("getCoinObjectIds should return null for SUI transactions", async () => {
552
- const address = "0x6e143fe0a8ca010a86580dafac44298e5b1b7d73efc345356a59a15f0d7824f0";
553
- const transaction = {
554
- mode: "send" as const,
555
- coinType: sdk.DEFAULT_COIN_TYPE,
556
- amount: new BigNumber(100),
557
- recipient: "0x33444cf803c690db96527cec67e3c9ab512596f4ba2d4eace43f0b4f716e0164",
558
- };
559
-
560
- const coinObjectIds = await sdk.getCoinObjectIds(address, transaction);
561
- expect(coinObjectIds).toBeNull();
562
- });
563
-
564
500
  test("createTransaction should build a transaction", async () => {
565
501
  const address = "0x6e143fe0a8ca010a86580dafac44298e5b1b7d73efc345356a59a15f0d7824f0";
566
502
  const transaction = {
@@ -1531,3 +1467,176 @@ describe("filterOperations", () => {
1531
1467
  });
1532
1468
  });
1533
1469
  });
1470
+
1471
+ describe("getCoinsForAmount", () => {
1472
+ const mockAddress = "0x33444cf803c690db96527cec67e3c9ab512596f4ba2d4eace43f0b4f716e0164";
1473
+ const mockCoinType = "0x2::sui::SUI";
1474
+
1475
+ beforeEach(() => {
1476
+ mockApi.getCoins.mockReset();
1477
+ });
1478
+
1479
+ describe("basic functionality", () => {
1480
+ test("handles single coin scenarios", async () => {
1481
+ const sufficientCoins = createMockCoins(["1000"]);
1482
+ mockApi.getCoins.mockResolvedValueOnce({ data: sufficientCoins, hasNextPage: false });
1483
+
1484
+ let result = await sdk.getCoinsForAmount(mockApi, mockAddress, mockCoinType, 1000);
1485
+ expect(result).toHaveLength(1);
1486
+ expect(result[0].balance).toBe("1000");
1487
+
1488
+ const insufficientCoins = createMockCoins(["500"]);
1489
+ mockApi.getCoins.mockResolvedValueOnce({ data: insufficientCoins, hasNextPage: false });
1490
+
1491
+ result = await sdk.getCoinsForAmount(mockApi, mockAddress, mockCoinType, 1000);
1492
+ expect(result).toHaveLength(1);
1493
+ expect(result[0].balance).toBe("500");
1494
+ });
1495
+
1496
+ test("selects minimum coins needed", async () => {
1497
+ const exactMatchCoins = createMockCoins(["600", "400", "300"]);
1498
+ mockApi.getCoins.mockResolvedValueOnce({ data: exactMatchCoins, hasNextPage: false });
1499
+
1500
+ let result = await sdk.getCoinsForAmount(mockApi, mockAddress, mockCoinType, 1000);
1501
+ expect(result).toHaveLength(2);
1502
+ expect(result[0].balance).toBe("600");
1503
+ expect(result[1].balance).toBe("400");
1504
+
1505
+ const exceedCoins = createMockCoins(["800", "400", "200"]);
1506
+ mockApi.getCoins.mockResolvedValueOnce({ data: exceedCoins, hasNextPage: false });
1507
+
1508
+ result = await sdk.getCoinsForAmount(mockApi, mockAddress, mockCoinType, 1000);
1509
+ expect(result).toHaveLength(2);
1510
+ expect(result[0].balance).toBe("800");
1511
+ expect(result[1].balance).toBe("400");
1512
+ });
1513
+
1514
+ test("handles edge cases", async () => {
1515
+ mockApi.getCoins.mockResolvedValueOnce({ data: [], hasNextPage: false });
1516
+
1517
+ let result = await sdk.getCoinsForAmount(mockApi, mockAddress, mockCoinType, 1000);
1518
+ expect(result).toHaveLength(0);
1519
+
1520
+ const coins = createMockCoins(["1000"]);
1521
+ mockApi.getCoins.mockResolvedValueOnce({ data: coins, hasNextPage: false });
1522
+
1523
+ result = await sdk.getCoinsForAmount(mockApi, mockAddress, mockCoinType, 0);
1524
+ expect(result).toHaveLength(0);
1525
+ });
1526
+ });
1527
+
1528
+ describe("sorting and filtering", () => {
1529
+ test("filters zero balance coins", async () => {
1530
+ const mockCoins = createMockCoins(["1000", "500"]);
1531
+ mockCoins.splice(1, 0, createMockCoins(["0"])[0]);
1532
+ mockCoins.push({ coinObjectId: "0xcoin4", balance: "0", digest: "0xdigest4", version: "1" });
1533
+
1534
+ mockApi.getCoins.mockResolvedValueOnce({ data: mockCoins, hasNextPage: false });
1535
+
1536
+ const result = await sdk.getCoinsForAmount(mockApi, mockAddress, mockCoinType, 1000);
1537
+
1538
+ expect(result).toHaveLength(1);
1539
+ expect(result[0].balance).toBe("1000");
1540
+ expect(result.every(coin => parseInt(coin.balance) > 0)).toBe(true);
1541
+ });
1542
+
1543
+ test("sorts and optimizes coin selection", async () => {
1544
+ const unsortedCoins = createMockCoins(["100", "800", "300", "500"]);
1545
+ mockApi.getCoins.mockResolvedValueOnce({ data: unsortedCoins, hasNextPage: false });
1546
+
1547
+ let result = await sdk.getCoinsForAmount(mockApi, mockAddress, mockCoinType, 1000);
1548
+ expect(result).toHaveLength(2);
1549
+ expect(result[0].balance).toBe("800");
1550
+ expect(result[1].balance).toBe("500");
1551
+
1552
+ const mixedCoins = createMockCoins(["200", "800", "400"]);
1553
+ mixedCoins.unshift(createMockCoins(["0"])[0]);
1554
+ mixedCoins.splice(2, 0, createMockCoins(["0"])[0]);
1555
+
1556
+ mockApi.getCoins.mockResolvedValueOnce({ data: mixedCoins, hasNextPage: false });
1557
+
1558
+ result = await sdk.getCoinsForAmount(mockApi, mockAddress, mockCoinType, 1000);
1559
+ expect(result).toHaveLength(2);
1560
+ expect(result[0].balance).toBe("800");
1561
+ expect(result[1].balance).toBe("400");
1562
+ expect(result.every(coin => parseInt(coin.balance) > 0)).toBe(true);
1563
+ });
1564
+
1565
+ test("handles all zero balance coins", async () => {
1566
+ const mockCoins = createMockCoins(["0", "0", "0"]);
1567
+ mockApi.getCoins.mockResolvedValueOnce({ data: mockCoins, hasNextPage: false });
1568
+
1569
+ const result = await sdk.getCoinsForAmount(mockApi, mockAddress, mockCoinType, 1000);
1570
+
1571
+ expect(result).toHaveLength(0);
1572
+ expect(result).toEqual([]);
1573
+ });
1574
+ });
1575
+
1576
+ describe("pagination", () => {
1577
+ test("handles single page scenarios", async () => {
1578
+ const mockCoins = createMockCoins(["800", "400", "300"]);
1579
+ mockApi.getCoins.mockResolvedValueOnce({
1580
+ data: mockCoins,
1581
+ hasNextPage: true,
1582
+ nextCursor: "cursor1",
1583
+ });
1584
+
1585
+ const result = await sdk.getCoinsForAmount(mockApi, mockAddress, mockCoinType, 1000);
1586
+
1587
+ expect(result).toHaveLength(2);
1588
+ expect(result[0].balance).toBe("800");
1589
+ expect(result[1].balance).toBe("400");
1590
+ expect(mockApi.getCoins).toHaveBeenCalledTimes(1);
1591
+ });
1592
+
1593
+ test("handles multi-page scenarios", async () => {
1594
+ const firstPageCoins = createMockCoins(["300", "200"]);
1595
+ const secondPageCoins = createMockCoins(["600", "400", "100"]);
1596
+
1597
+ mockApi.getCoins
1598
+ .mockResolvedValueOnce({
1599
+ data: firstPageCoins,
1600
+ hasNextPage: true,
1601
+ nextCursor: "cursor1",
1602
+ })
1603
+ .mockResolvedValueOnce({
1604
+ data: secondPageCoins,
1605
+ hasNextPage: false,
1606
+ });
1607
+
1608
+ const result = await sdk.getCoinsForAmount(mockApi, mockAddress, mockCoinType, 1000);
1609
+
1610
+ expect(result).toHaveLength(3);
1611
+ expect(result[0].balance).toBe("300");
1612
+ expect(result[1].balance).toBe("200");
1613
+ expect(result[2].balance).toBe("600");
1614
+ expect(mockApi.getCoins).toHaveBeenCalledTimes(2);
1615
+ });
1616
+
1617
+ test("handles insufficient funds across pages", async () => {
1618
+ const firstPageCoins = createMockCoins(["300", "200"]);
1619
+ const secondPageCoins = createMockCoins(["200", "100"]);
1620
+
1621
+ mockApi.getCoins
1622
+ .mockResolvedValueOnce({
1623
+ data: firstPageCoins,
1624
+ hasNextPage: true,
1625
+ nextCursor: "cursor1",
1626
+ })
1627
+ .mockResolvedValueOnce({
1628
+ data: secondPageCoins,
1629
+ hasNextPage: false,
1630
+ });
1631
+
1632
+ const result = await sdk.getCoinsForAmount(mockApi, mockAddress, mockCoinType, 1000);
1633
+
1634
+ expect(result).toHaveLength(4);
1635
+ expect(result[0].balance).toBe("300");
1636
+ expect(result[1].balance).toBe("200");
1637
+ expect(result[2].balance).toBe("200");
1638
+ expect(result[3].balance).toBe("100");
1639
+ expect(mockApi.getCoins).toHaveBeenCalledTimes(2);
1640
+ });
1641
+ });
1642
+ });