@ledgerhq/coin-sui 0.10.0 → 0.10.1-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 (72) hide show
  1. package/.eslintrc.js +1 -0
  2. package/.turbo/turbo-build.log +1 -1
  3. package/CHANGELOG.md +27 -0
  4. package/index.d.ts +1 -0
  5. package/jest.config.js +1 -1
  6. package/lib/api/index.integration.test.js +2 -2
  7. package/lib/api/index.integration.test.js.map +1 -1
  8. package/lib/bridge/buildTransaction.d.ts +1 -1
  9. package/lib/bridge/buildTransaction.d.ts.map +1 -1
  10. package/lib/bridge/buildTransaction.integration.test.js +12 -0
  11. package/lib/bridge/buildTransaction.integration.test.js.map +1 -1
  12. package/lib/bridge/buildTransaction.js +8 -4
  13. package/lib/bridge/buildTransaction.js.map +1 -1
  14. package/lib/bridge/buildTransaction.test.js +14 -10
  15. package/lib/bridge/buildTransaction.test.js.map +1 -1
  16. package/lib/bridge/estimateMaxSpendable.js +1 -1
  17. package/lib/bridge/estimateMaxSpendable.js.map +1 -1
  18. package/lib/bridge/getFeesForTransaction.d.ts.map +1 -1
  19. package/lib/bridge/getFeesForTransaction.js +2 -3
  20. package/lib/bridge/getFeesForTransaction.js.map +1 -1
  21. package/lib/bridge/signOperation.integration.test.js +2 -2
  22. package/lib/bridge/signOperation.integration.test.js.map +1 -1
  23. package/lib/logic/craftTransaction.js +1 -1
  24. package/lib/logic/craftTransaction.js.map +1 -1
  25. package/lib/network/sdk.d.ts +18 -3
  26. package/lib/network/sdk.d.ts.map +1 -1
  27. package/lib/network/sdk.integration.test.js +2 -2
  28. package/lib/network/sdk.integration.test.js.map +1 -1
  29. package/lib/network/sdk.js +68 -55
  30. package/lib/network/sdk.js.map +1 -1
  31. package/lib/network/sdk.test.js +148 -65
  32. package/lib/network/sdk.test.js.map +1 -1
  33. package/lib-es/api/index.integration.test.js +2 -2
  34. package/lib-es/api/index.integration.test.js.map +1 -1
  35. package/lib-es/bridge/buildTransaction.d.ts +1 -1
  36. package/lib-es/bridge/buildTransaction.d.ts.map +1 -1
  37. package/lib-es/bridge/buildTransaction.integration.test.js +12 -0
  38. package/lib-es/bridge/buildTransaction.integration.test.js.map +1 -1
  39. package/lib-es/bridge/buildTransaction.js +9 -5
  40. package/lib-es/bridge/buildTransaction.js.map +1 -1
  41. package/lib-es/bridge/buildTransaction.test.js +14 -10
  42. package/lib-es/bridge/buildTransaction.test.js.map +1 -1
  43. package/lib-es/bridge/estimateMaxSpendable.js +1 -1
  44. package/lib-es/bridge/estimateMaxSpendable.js.map +1 -1
  45. package/lib-es/bridge/getFeesForTransaction.d.ts.map +1 -1
  46. package/lib-es/bridge/getFeesForTransaction.js +2 -3
  47. package/lib-es/bridge/getFeesForTransaction.js.map +1 -1
  48. package/lib-es/bridge/signOperation.integration.test.js +2 -2
  49. package/lib-es/bridge/signOperation.integration.test.js.map +1 -1
  50. package/lib-es/logic/craftTransaction.js +1 -1
  51. package/lib-es/logic/craftTransaction.js.map +1 -1
  52. package/lib-es/network/sdk.d.ts +18 -3
  53. package/lib-es/network/sdk.d.ts.map +1 -1
  54. package/lib-es/network/sdk.integration.test.js +2 -2
  55. package/lib-es/network/sdk.integration.test.js.map +1 -1
  56. package/lib-es/network/sdk.js +65 -52
  57. package/lib-es/network/sdk.js.map +1 -1
  58. package/lib-es/network/sdk.test.js +148 -65
  59. package/lib-es/network/sdk.test.js.map +1 -1
  60. package/package.json +10 -10
  61. package/src/api/index.integration.test.ts +2 -2
  62. package/src/bridge/buildTransaction.integration.test.ts +13 -0
  63. package/src/bridge/buildTransaction.test.ts +25 -25
  64. package/src/bridge/buildTransaction.ts +10 -5
  65. package/src/bridge/estimateMaxSpendable.ts +1 -1
  66. package/src/bridge/getFeesForTransaction.ts +2 -3
  67. package/src/bridge/signOperation.integration.test.ts +2 -2
  68. package/src/logic/craftTransaction.ts +1 -1
  69. package/src/network/sdk.integration.test.ts +2 -2
  70. package/src/network/sdk.test.ts +186 -77
  71. package/src/network/sdk.ts +86 -66
  72. package/tsconfig.json +4 -3
@@ -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
+ });
@@ -1,18 +1,18 @@
1
1
  import {
2
+ BalanceChange,
2
3
  Checkpoint,
3
4
  ExecuteTransactionBlockParams,
4
5
  PaginatedTransactionResponse,
6
+ QueryTransactionBlocksParams,
5
7
  SuiCallArg,
6
8
  SuiClient,
7
- SuiTransactionBlockResponse,
8
- TransactionBlockData,
9
9
  SuiHTTPTransport,
10
- TransactionEffects,
11
- QueryTransactionBlocksParams,
12
- BalanceChange,
10
+ SuiTransactionBlockResponse,
13
11
  SuiTransactionBlockResponseOptions,
14
12
  DelegatedStake,
15
13
  StakeObject,
14
+ TransactionBlockData,
15
+ TransactionEffects,
16
16
  } from "@mysten/sui/client";
17
17
  import { Transaction } from "@mysten/sui/transactions";
18
18
  import { BigNumber } from "bignumber.js";
@@ -232,7 +232,7 @@ export function transactionToOperation(
232
232
  coinType,
233
233
  },
234
234
  fee: getOperationFee(transaction),
235
- hasFailed: transaction.effects?.status.status != "success",
235
+ hasFailed: transaction.effects?.status.status !== "success",
236
236
  hash,
237
237
  recipients: getOperationRecipients(transaction.transaction?.data),
238
238
  senders: getOperationSenders(transaction.transaction?.data),
@@ -278,7 +278,7 @@ export function toBlockInfo(checkpoint: Checkpoint): BlockInfo {
278
278
  time: new Date(parseInt(checkpoint.timestampMs)),
279
279
  };
280
280
 
281
- if (typeof checkpoint.previousDigest == "string")
281
+ if (typeof checkpoint.previousDigest === "string")
282
282
  return {
283
283
  ...info,
284
284
  parent: {
@@ -303,7 +303,7 @@ export function toBlockInfo(checkpoint: Checkpoint): BlockInfo {
303
303
  export function toBlockTransaction(transaction: SuiTransactionBlockResponse): BlockTransaction {
304
304
  return {
305
305
  hash: transaction.digest,
306
- failed: transaction.effects?.status.status != "success",
306
+ failed: transaction.effects?.status.status !== "success",
307
307
  operations: transaction.balanceChanges?.flatMap(toBlockOperation) || [],
308
308
  fees: BigInt(getOperationFee(transaction).toString()),
309
309
  feesPayer: transaction.transaction?.data.sender || "",
@@ -316,7 +316,7 @@ export function toBlockTransaction(transaction: SuiTransactionBlockResponse): Bl
316
316
  * @param change balance change
317
317
  */
318
318
  export function toBlockOperation(change: BalanceChange): BlockOperation[] {
319
- if (typeof change.owner == "string" || !("AddressOwner" in change.owner)) return [];
319
+ if (typeof change.owner === "string" || !("AddressOwner" in change.owner)) return [];
320
320
  return [
321
321
  {
322
322
  type: "transfer",
@@ -481,75 +481,79 @@ const getTotalGasUsed = (effects?: TransactionEffects | null): bigint => {
481
481
  );
482
482
  };
483
483
 
484
- const FALLBACK_GAS_BUDGET = {
485
- SUI_TRANSFER: "3976000",
486
- TOKEN_TRANSFER: "4461792",
487
- };
484
+ /**
485
+ * Get coins for a given address and coin type, stopping when we have enough to cover the amount.
486
+ * Returns the minimum coins needed to cover the required amount.
487
+ */
488
+ export const getCoinsForAmount = async (
489
+ api: SuiClient,
490
+ address: string,
491
+ coinType: string,
492
+ requiredAmount: number,
493
+ ) => {
494
+ const coins = [];
495
+ let cursor = null;
496
+ let hasNextPage = true;
497
+ let totalBalance = 0;
498
+
499
+ while (hasNextPage && totalBalance < requiredAmount) {
500
+ const response = await api.getCoins({
501
+ owner: address,
502
+ coinType,
503
+ cursor,
504
+ });
488
505
 
489
- export const paymentInfo = async (sender: string, fakeTransaction: TransactionType) =>
490
- withApi(async api => {
491
- const tx = new Transaction();
492
- tx.setSender(ensureAddressFormat(sender));
493
- const coinObjects = await getCoinObjectIds(sender, fakeTransaction);
494
-
495
- const [coin] = tx.splitCoins(Array.isArray(coinObjects) ? coinObjects[0] : tx.gas, [
496
- fakeTransaction.amount.toNumber(),
497
- ]);
498
- tx.transferObjects([coin], fakeTransaction.recipient);
499
-
500
- try {
501
- const txb = await tx.build({ client: api });
502
- const dryRunTxResponse = await api.dryRunTransactionBlock({ transactionBlock: txb });
503
- const fees = getTotalGasUsed(dryRunTxResponse.effects);
504
-
505
- return {
506
- gasBudget: dryRunTxResponse.input.gasData.budget,
507
- totalGasUsed: fees,
508
- fees,
509
- };
510
- } catch (error) {
511
- console.warn("Fee estimation failed:", error);
512
- // If dry run fails return a reasonable default gas budget as fallback
513
- return {
514
- gasBudget: Array.isArray(coinObjects)
515
- ? FALLBACK_GAS_BUDGET.TOKEN_TRANSFER
516
- : FALLBACK_GAS_BUDGET.SUI_TRANSFER,
517
- totalGasUsed: BigInt(1000000),
518
- fees: BigInt(1000000),
519
- };
506
+ // Filter out zero-balance coins and sort by balance (largest first)
507
+ const validCoins = response.data
508
+ .filter(coin => parseInt(coin.balance) > 0)
509
+ .sort((a, b) => parseInt(b.balance) - parseInt(a.balance));
510
+
511
+ let currentBalance = totalBalance;
512
+ let i = 0;
513
+ while (i < validCoins.length && currentBalance < requiredAmount) {
514
+ const coin = validCoins[i];
515
+ coins.push(coin);
516
+ currentBalance += parseInt(coin.balance);
517
+ i++;
520
518
  }
521
- });
519
+ totalBalance = currentBalance;
522
520
 
523
- export const getCoinObjectIds = async (
524
- address: string,
525
- transaction: CreateExtrinsicArg | TransactionType,
526
- ) =>
527
- withApi(async api => {
528
- const coinObjectId = null;
521
+ cursor = response.nextCursor;
522
+ hasNextPage = response.hasNextPage && totalBalance < requiredAmount;
523
+ }
529
524
 
530
- if (transaction.coinType !== DEFAULT_COIN_TYPE) {
531
- const tokenInfo = await api.getCoins({
532
- owner: address,
533
- coinType: transaction.coinType,
534
- });
535
- return tokenInfo.data.map(coin => coin.coinObjectId);
536
- }
537
- return coinObjectId;
538
- });
525
+ return coins;
526
+ };
539
527
 
528
+ /**
529
+ * Creates a Sui transaction block for transferring coins.
530
+ *
531
+ * @param address - The sender's address
532
+ * @param transaction - The transaction details including recipient, amount, and coin type
533
+ * @returns Promise<TransactionBlock> - A built transaction block ready for execution
534
+ *
535
+ */
540
536
  export const createTransaction = async (address: string, transaction: CreateExtrinsicArg) =>
541
537
  withApi(async api => {
542
538
  const tx = new Transaction();
543
539
  tx.setSender(ensureAddressFormat(address));
544
540
 
545
- const coinObjects = await getCoinObjectIds(address, transaction);
541
+ if (transaction.coinType !== DEFAULT_COIN_TYPE) {
542
+ const requiredAmount = transaction.amount.toNumber();
543
+
544
+ const coins = await getCoinsForAmount(api, address, transaction.coinType, requiredAmount);
546
545
 
547
- if (Array.isArray(coinObjects) && transaction.coinType !== DEFAULT_COIN_TYPE) {
548
- const coins = coinObjects.map(coinId => tx.object(coinId));
549
- if (coins.length > 1) {
550
- tx.mergeCoins(coins[0], coins.slice(1));
546
+ if (coins.length === 0) {
547
+ throw new Error(`No coins found for type ${transaction.coinType}`);
551
548
  }
552
- const [coin] = tx.splitCoins(coins[0], [transaction.amount.toNumber()]);
549
+
550
+ const coinObjects = coins.map(coin => tx.object(coin.coinObjectId));
551
+
552
+ if (coinObjects.length > 1) {
553
+ tx.mergeCoins(coinObjects[0], coinObjects.slice(1));
554
+ }
555
+
556
+ const [coin] = tx.splitCoins(coinObjects[0], [transaction.amount.toNumber()]);
553
557
  tx.transferObjects([coin], transaction.recipient);
554
558
  } else {
555
559
  const [coin] = tx.splitCoins(tx.gas, [transaction.amount.toNumber()]);
@@ -559,6 +563,22 @@ export const createTransaction = async (address: string, transaction: CreateExtr
559
563
  return tx.build({ client: api });
560
564
  });
561
565
 
566
+ /**
567
+ * Performs a dry run of a transaction to estimate gas costs and fees
568
+ */
569
+ export const paymentInfo = async (sender: string, fakeTransaction: TransactionType) =>
570
+ withApi(async api => {
571
+ const txb = await createTransaction(sender, fakeTransaction);
572
+ const dryRunTxResponse = await api.dryRunTransactionBlock({ transactionBlock: txb });
573
+ const fees = getTotalGasUsed(dryRunTxResponse.effects);
574
+
575
+ return {
576
+ gasBudget: dryRunTxResponse.input.gasData.budget,
577
+ totalGasUsed: fees,
578
+ fees,
579
+ };
580
+ });
581
+
562
582
  export const executeTransactionBlock = async (params: ExecuteTransactionBlockParams) =>
563
583
  withApi(async api => {
564
584
  return api.executeTransactionBlock(params);
package/tsconfig.json CHANGED
@@ -8,7 +8,8 @@
8
8
  "rootDir": "./src",
9
9
  "exactOptionalPropertyTypes": true,
10
10
  "module": "ESNext",
11
- "moduleResolution": "bundler"
11
+ "moduleResolution": "bundler",
12
+ "typeRoots": ["./node_modules/@types", "index.d.ts"]
12
13
  },
13
- "include": ["src/**/*"]
14
- }
14
+ "include": ["src/**/*", "index.d.ts"]
15
+ }