@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,16 +1,18 @@
1
1
  import {
2
+ BalanceChange,
2
3
  Checkpoint,
3
4
  ExecuteTransactionBlockParams,
4
5
  PaginatedTransactionResponse,
6
+ QueryTransactionBlocksParams,
5
7
  SuiCallArg,
6
8
  SuiClient,
9
+ SuiHTTPTransport,
7
10
  SuiTransactionBlockResponse,
11
+ SuiTransactionBlockResponseOptions,
12
+ DelegatedStake,
13
+ StakeObject,
8
14
  TransactionBlockData,
9
- SuiHTTPTransport,
10
15
  TransactionEffects,
11
- QueryTransactionBlocksParams,
12
- BalanceChange,
13
- SuiTransactionBlockResponseOptions,
14
16
  } from "@mysten/sui/client";
15
17
  import { Transaction } from "@mysten/sui/transactions";
16
18
  import { BigNumber } from "bignumber.js";
@@ -20,6 +22,8 @@ import type {
20
22
  BlockTransaction,
21
23
  BlockOperation,
22
24
  Operation as Op,
25
+ Stake,
26
+ StakeState,
23
27
  AssetInfo,
24
28
  } from "@ledgerhq/coin-framework/api/index";
25
29
  import type { Operation, OperationType } from "@ledgerhq/types-live";
@@ -81,45 +85,29 @@ export async function withApi<T>(execute: AsyncApiFunction<T>) {
81
85
  return result;
82
86
  }
83
87
 
84
- export const getBalanceCached = makeLRUCache(
85
- ({ api, owner }: { api: SuiClient; owner: string }) => api.getBalance({ owner }),
86
- (params: { api: SuiClient; owner: string }) => params.owner,
87
- minutes(1),
88
- );
89
-
90
88
  export const getAllBalancesCached = makeLRUCache(
91
- ({ api, owner }: { api: SuiClient; owner: string }) =>
92
- api.getAllBalances({
93
- owner,
94
- }),
95
- (params: { api: SuiClient; owner: string }) => params.owner,
89
+ async (owner: string) =>
90
+ withApi(
91
+ async api =>
92
+ await api.getAllBalances({
93
+ owner,
94
+ }),
95
+ ),
96
+ (owner: string) => owner,
96
97
  minutes(1),
97
98
  );
98
99
 
99
- /**
100
- * Get account balance
101
- */
102
- export const getAccount = async (addr: string) =>
103
- withApi(async api => {
104
- const balance = await getBalanceCached({ api, owner: addr });
105
- return {
106
- blockHeight: BLOCK_HEIGHT * 2,
107
- balance: BigNumber(balance.totalBalance),
108
- };
109
- });
110
-
111
100
  /**
112
101
  * Get account balance (native and tokens)
113
102
  */
114
- export const getAccountBalances = async (addr: string) =>
115
- withApi(async api => {
116
- const balances = await getAllBalancesCached({ api, owner: addr });
117
- return balances.map(({ coinType, totalBalance }) => ({
118
- coinType,
119
- blockHeight: BLOCK_HEIGHT * 2,
120
- balance: BigNumber(totalBalance),
121
- }));
122
- });
103
+ export const getAccountBalances = async (addr: string) => {
104
+ const balances = await getAllBalancesCached(addr);
105
+ return balances.map(({ coinType, totalBalance }) => ({
106
+ coinType,
107
+ blockHeight: BLOCK_HEIGHT * 2,
108
+ balance: BigNumber(totalBalance),
109
+ }));
110
+ };
123
111
 
124
112
  /**
125
113
  * Returns true if account is the signer
@@ -493,75 +481,79 @@ const getTotalGasUsed = (effects?: TransactionEffects | null): bigint => {
493
481
  );
494
482
  };
495
483
 
496
- const FALLBACK_GAS_BUDGET = {
497
- SUI_TRANSFER: "3976000",
498
- TOKEN_TRANSFER: "4461792",
499
- };
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
+ });
500
505
 
501
- export const paymentInfo = async (sender: string, fakeTransaction: TransactionType) =>
502
- withApi(async api => {
503
- const tx = new Transaction();
504
- tx.setSender(ensureAddressFormat(sender));
505
- const coinObjects = await getCoinObjectIds(sender, fakeTransaction);
506
-
507
- const [coin] = tx.splitCoins(Array.isArray(coinObjects) ? coinObjects[0] : tx.gas, [
508
- fakeTransaction.amount.toNumber(),
509
- ]);
510
- tx.transferObjects([coin], fakeTransaction.recipient);
511
-
512
- try {
513
- const txb = await tx.build({ client: api });
514
- const dryRunTxResponse = await api.dryRunTransactionBlock({ transactionBlock: txb });
515
- const fees = getTotalGasUsed(dryRunTxResponse.effects);
516
-
517
- return {
518
- gasBudget: dryRunTxResponse.input.gasData.budget,
519
- totalGasUsed: fees,
520
- fees,
521
- };
522
- } catch (error) {
523
- console.warn("Fee estimation failed:", error);
524
- // If dry run fails return a reasonable default gas budget as fallback
525
- return {
526
- gasBudget: Array.isArray(coinObjects)
527
- ? FALLBACK_GAS_BUDGET.TOKEN_TRANSFER
528
- : FALLBACK_GAS_BUDGET.SUI_TRANSFER,
529
- totalGasUsed: BigInt(1000000),
530
- fees: BigInt(1000000),
531
- };
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++;
532
518
  }
533
- });
519
+ totalBalance = currentBalance;
534
520
 
535
- export const getCoinObjectIds = async (
536
- address: string,
537
- transaction: CreateExtrinsicArg | TransactionType,
538
- ) =>
539
- withApi(async api => {
540
- const coinObjectId = null;
521
+ cursor = response.nextCursor;
522
+ hasNextPage = response.hasNextPage && totalBalance < requiredAmount;
523
+ }
541
524
 
542
- if (transaction.coinType !== DEFAULT_COIN_TYPE) {
543
- const tokenInfo = await api.getCoins({
544
- owner: address,
545
- coinType: transaction.coinType,
546
- });
547
- return tokenInfo.data.map(coin => coin.coinObjectId);
548
- }
549
- return coinObjectId;
550
- });
525
+ return coins;
526
+ };
551
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
+ */
552
536
  export const createTransaction = async (address: string, transaction: CreateExtrinsicArg) =>
553
537
  withApi(async api => {
554
538
  const tx = new Transaction();
555
539
  tx.setSender(ensureAddressFormat(address));
556
540
 
557
- const coinObjects = await getCoinObjectIds(address, transaction);
541
+ if (transaction.coinType !== DEFAULT_COIN_TYPE) {
542
+ const requiredAmount = transaction.amount.toNumber();
558
543
 
559
- if (Array.isArray(coinObjects) && transaction.coinType !== DEFAULT_COIN_TYPE) {
560
- const coins = coinObjects.map(coinId => tx.object(coinId));
561
- if (coins.length > 1) {
562
- tx.mergeCoins(coins[0], coins.slice(1));
544
+ const coins = await getCoinsForAmount(api, address, transaction.coinType, requiredAmount);
545
+
546
+ if (coins.length === 0) {
547
+ throw new Error(`No coins found for type ${transaction.coinType}`);
563
548
  }
564
- 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()]);
565
557
  tx.transferObjects([coin], transaction.recipient);
566
558
  } else {
567
559
  const [coin] = tx.splitCoins(tx.gas, [transaction.amount.toNumber()]);
@@ -571,6 +563,22 @@ export const createTransaction = async (address: string, transaction: CreateExtr
571
563
  return tx.build({ client: api });
572
564
  });
573
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
+
574
582
  export const executeTransactionBlock = async (params: ExecuteTransactionBlockParams) =>
575
583
  withApi(async api => {
576
584
  return api.executeTransactionBlock(params);
@@ -670,3 +678,51 @@ export const queryTransactionsByDigest = async (params: {
670
678
 
671
679
  return responses;
672
680
  };
681
+
682
+ export const getStakes = (address: string): Promise<Stake[]> =>
683
+ withApi(async api =>
684
+ api
685
+ .getStakes({ owner: address })
686
+ .then(delegations => delegations.flatMap(delegation => toStakes(address, delegation))),
687
+ );
688
+
689
+ export const toStakes = (address: string, delegation: DelegatedStake): Stake[] =>
690
+ delegation.stakes.map(stake => {
691
+ const { deposited, rewarded } = toStakeAmounts(stake);
692
+ return {
693
+ uid: stake.stakedSuiId,
694
+ address: address,
695
+ delegate: delegation.validatorAddress,
696
+ state: toStakeState(stake.status),
697
+ asset: { type: "native" },
698
+ amount: deposited + rewarded,
699
+ amountDeposited: deposited,
700
+ amountRewarded: rewarded,
701
+ details: {
702
+ activeEpoch: Number(stake.stakeActiveEpoch),
703
+ requestEpoch: Number(stake.stakeRequestEpoch),
704
+ },
705
+ };
706
+ });
707
+
708
+ export const toStakeState = (status: "Pending" | "Active" | "Unstaked"): StakeState => {
709
+ switch (status) {
710
+ case "Pending":
711
+ return "activating";
712
+ case "Active":
713
+ return "active";
714
+ case "Unstaked":
715
+ return "inactive";
716
+ }
717
+ };
718
+
719
+ export const toStakeAmounts = (stake: StakeObject): { deposited: bigint; rewarded: bigint } => {
720
+ switch (stake.status) {
721
+ case "Pending":
722
+ return { deposited: BigInt(stake.principal), rewarded: 0n };
723
+ case "Active":
724
+ return { deposited: BigInt(stake.principal), rewarded: BigInt(stake.estimatedReward) };
725
+ case "Unstaked":
726
+ return { deposited: BigInt(stake.principal), rewarded: 0n }; // note: we lose reward information in unstaked state here
727
+ }
728
+ };
@@ -0,0 +1,38 @@
1
+ import { Transaction } from "@mysten/sui/transactions";
2
+ import { SuiClient, getFullnodeUrl } from "@mysten/sui/client";
3
+
4
+ export async function extractCoinTypeFromUnsignedTx(
5
+ unsignedTxBytes: Uint8Array,
6
+ ): Promise<string[] | null> {
7
+ const tx = Transaction.from(unsignedTxBytes);
8
+ const data = tx.getData();
9
+
10
+ const gasObjectIds = data.gasData.payment?.map(object => object.objectId) ?? [];
11
+ const inputObjectIds = data.inputs
12
+ .map(input => {
13
+ return input.$kind === "Object" && input.Object.$kind === "ImmOrOwnedObject"
14
+ ? input.Object.ImmOrOwnedObject.objectId
15
+ : null;
16
+ })
17
+ .filter((objectId): objectId is string => !!objectId);
18
+
19
+ const suiClient = new SuiClient({ url: getFullnodeUrl("mainnet") });
20
+ const objects = await suiClient.multiGetObjects({
21
+ ids: [...gasObjectIds, ...inputObjectIds],
22
+ options: {
23
+ showBcs: true,
24
+ showPreviousTransaction: true,
25
+ showStorageRebate: true,
26
+ showOwner: true,
27
+ },
28
+ });
29
+
30
+ const coinObjects = objects.filter(obj => {
31
+ const bcsData = obj.data?.bcs as any;
32
+ return bcsData.type.includes("coin");
33
+ });
34
+
35
+ const coinTypes: string[] = coinObjects.map(obj => (obj.data?.bcs as any).type);
36
+
37
+ return coinTypes;
38
+ }