@ledgerhq/coin-sui 0.8.1-nightly.1 → 0.9.0-nightly.3

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 (154) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/.unimportedrc.json +1 -1
  3. package/CHANGELOG.md +20 -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 +5 -35
  14. package/lib/bridge/buildTransaction.integration.test.js.map +1 -1
  15. package/lib/bridge/buildTransaction.js +5 -20
  16. package/lib/bridge/buildTransaction.js.map +1 -1
  17. package/lib/bridge/buildTransaction.test.js +28 -129
  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 +6 -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 +1 -1
  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 +10 -17
  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 +50 -18
  64. package/lib/network/sdk.js.map +1 -1
  65. package/lib/test/testUtils.d.ts +2 -0
  66. package/lib/test/testUtils.d.ts.map +1 -0
  67. package/lib/test/testUtils.js +35 -0
  68. package/lib/test/testUtils.js.map +1 -0
  69. package/lib-es/api/index.d.ts.map +1 -1
  70. package/lib-es/api/index.integration.test.js +21 -2
  71. package/lib-es/api/index.integration.test.js.map +1 -1
  72. package/lib-es/api/index.js +3 -1
  73. package/lib-es/api/index.js.map +1 -1
  74. package/lib-es/api/index.test.js +37 -0
  75. package/lib-es/api/index.test.js.map +1 -1
  76. package/lib-es/bridge/buildTransaction.d.ts +1 -3
  77. package/lib-es/bridge/buildTransaction.d.ts.map +1 -1
  78. package/lib-es/bridge/buildTransaction.integration.test.js +1 -31
  79. package/lib-es/bridge/buildTransaction.integration.test.js.map +1 -1
  80. package/lib-es/bridge/buildTransaction.js +4 -15
  81. package/lib-es/bridge/buildTransaction.js.map +1 -1
  82. package/lib-es/bridge/buildTransaction.test.js +29 -130
  83. package/lib-es/bridge/buildTransaction.test.js.map +1 -1
  84. package/lib-es/bridge/getFeesForTransaction.d.ts.map +1 -1
  85. package/lib-es/bridge/getFeesForTransaction.js +6 -1
  86. package/lib-es/bridge/getFeesForTransaction.js.map +1 -1
  87. package/lib-es/logic/craftTransaction.d.ts +2 -2
  88. package/lib-es/logic/craftTransaction.d.ts.map +1 -1
  89. package/lib-es/logic/craftTransaction.integration.test.d.ts +2 -0
  90. package/lib-es/logic/craftTransaction.integration.test.d.ts.map +1 -0
  91. package/lib-es/logic/craftTransaction.integration.test.js +51 -0
  92. package/lib-es/logic/craftTransaction.integration.test.js.map +1 -0
  93. package/lib-es/logic/craftTransaction.js +1 -1
  94. package/lib-es/logic/craftTransaction.js.map +1 -1
  95. package/lib-es/logic/estimateFees.d.ts +1 -1
  96. package/lib-es/logic/estimateFees.d.ts.map +1 -1
  97. package/lib-es/logic/estimateFees.integration.test.d.ts +2 -0
  98. package/lib-es/logic/estimateFees.integration.test.d.ts.map +1 -0
  99. package/lib-es/logic/estimateFees.integration.test.js +74 -0
  100. package/lib-es/logic/estimateFees.integration.test.js.map +1 -0
  101. package/lib-es/logic/estimateFees.js +7 -2
  102. package/lib-es/logic/estimateFees.js.map +1 -1
  103. package/lib-es/logic/getBalance.d.ts.map +1 -1
  104. package/lib-es/logic/getBalance.integration.test.d.ts +2 -0
  105. package/lib-es/logic/getBalance.integration.test.d.ts.map +1 -0
  106. package/lib-es/logic/getBalance.integration.test.js +51 -0
  107. package/lib-es/logic/getBalance.integration.test.js.map +1 -0
  108. package/lib-es/logic/getBalance.js +19 -8
  109. package/lib-es/logic/getBalance.js.map +1 -1
  110. package/lib-es/logic/getBalance.test.js +50 -8
  111. package/lib-es/logic/getBalance.test.js.map +1 -1
  112. package/lib-es/logic/index.d.ts +1 -0
  113. package/lib-es/logic/index.d.ts.map +1 -1
  114. package/lib-es/logic/index.js +1 -0
  115. package/lib-es/logic/index.js.map +1 -1
  116. package/lib-es/logic/staking.d.ts +4 -0
  117. package/lib-es/logic/staking.d.ts.map +1 -0
  118. package/lib-es/logic/staking.js +8 -0
  119. package/lib-es/logic/staking.js.map +1 -0
  120. package/lib-es/network/index.d.ts +6 -6
  121. package/lib-es/network/index.d.ts.map +1 -1
  122. package/lib-es/network/index.js +6 -3
  123. package/lib-es/network/index.js.map +1 -1
  124. package/lib-es/network/sdk.d.ts +10 -17
  125. package/lib-es/network/sdk.d.ts.map +1 -1
  126. package/lib-es/network/sdk.integration.test.js +22 -23
  127. package/lib-es/network/sdk.integration.test.js.map +1 -1
  128. package/lib-es/network/sdk.js +45 -16
  129. package/lib-es/network/sdk.js.map +1 -1
  130. package/lib-es/test/testUtils.d.ts +2 -0
  131. package/lib-es/test/testUtils.d.ts.map +1 -0
  132. package/lib-es/test/testUtils.js +31 -0
  133. package/lib-es/test/testUtils.js.map +1 -0
  134. package/package.json +4 -4
  135. package/src/api/index.integration.test.ts +24 -2
  136. package/src/api/index.test.ts +40 -0
  137. package/src/api/index.ts +4 -0
  138. package/src/bridge/buildTransaction.integration.test.ts +1 -39
  139. package/src/bridge/buildTransaction.test.ts +36 -159
  140. package/src/bridge/buildTransaction.ts +5 -19
  141. package/src/bridge/getFeesForTransaction.ts +7 -1
  142. package/src/logic/craftTransaction.integration.test.ts +63 -0
  143. package/src/logic/craftTransaction.ts +3 -3
  144. package/src/logic/estimateFees.integration.test.ts +89 -0
  145. package/src/logic/estimateFees.ts +7 -1
  146. package/src/logic/getBalance.integration.test.ts +66 -0
  147. package/src/logic/getBalance.test.ts +58 -8
  148. package/src/logic/getBalance.ts +24 -8
  149. package/src/logic/index.ts +1 -0
  150. package/src/logic/staking.ts +10 -0
  151. package/src/network/index.ts +12 -3
  152. package/src/network/sdk.integration.test.ts +25 -22
  153. package/src/network/sdk.ts +68 -32
  154. package/src/test/testUtils.ts +38 -0
@@ -5,18 +5,18 @@ import suiAPI from "../network";
5
5
  import { DEFAULT_COIN_TYPE } from "../network/sdk";
6
6
 
7
7
  export type CreateExtrinsicArg = {
8
- mode: SuiTransactionMode;
9
8
  amount: BigNumber;
10
9
  coinType: string;
10
+ mode: SuiTransactionMode;
11
11
  recipient: string;
12
12
  useAllAmount?: boolean | undefined;
13
13
  };
14
14
 
15
15
  export async function craftTransaction({
16
- sender,
17
16
  amount,
18
- recipient,
19
17
  asset,
18
+ recipient,
19
+ sender,
20
20
  type,
21
21
  }: TransactionIntent): Promise<CoreTransaction> {
22
22
  let coinType = DEFAULT_COIN_TYPE;
@@ -0,0 +1,89 @@
1
+ import type { TransactionIntent } from "@ledgerhq/coin-framework/api/index";
2
+ import { getFullnodeUrl } from "@mysten/sui/client";
3
+ import coinConfig from "../config";
4
+ import { estimateFees } from "./estimateFees";
5
+
6
+ const SENDER = "0x6e143fe0a8ca010a86580dafac44298e5b1b7d73efc345356a59a15f0d7824f0";
7
+ const RECIPIENT = "0x33444cf803c690db96527cec67e3c9ab512596f4ba2d4eace43f0b4f716e0164";
8
+
9
+ describe("estimateFees", () => {
10
+ beforeAll(() => {
11
+ coinConfig.setCoinConfig(() => ({
12
+ status: {
13
+ type: "active",
14
+ },
15
+ node: {
16
+ url: getFullnodeUrl("mainnet"),
17
+ },
18
+ }));
19
+ });
20
+
21
+ it("should estimate fees for native SUI transaction", async () => {
22
+ const transactionIntent: TransactionIntent = {
23
+ sender: SENDER,
24
+ recipient: RECIPIENT,
25
+ amount: BigInt(1000),
26
+ type: "send",
27
+ asset: { type: "native" },
28
+ };
29
+
30
+ const estimatedFees = await estimateFees(transactionIntent);
31
+
32
+ expect(typeof estimatedFees).toBe("bigint");
33
+ expect(estimatedFees).toBeGreaterThan(1000n);
34
+ expect(estimatedFees).toBeLessThan(10000000n);
35
+ }, 25000);
36
+
37
+ it("should estimate fees for token transaction", async () => {
38
+ const coinType =
39
+ "0x375f70cf2ae4c00bf37117d0c85a2c71545e6ee05c4a5c7d282cd66a4504b068::usdt::USDT";
40
+
41
+ const transactionIntent: TransactionIntent = {
42
+ sender: SENDER,
43
+ recipient: RECIPIENT,
44
+ amount: BigInt(1000),
45
+ type: "send",
46
+ asset: {
47
+ type: "token",
48
+ assetReference: coinType,
49
+ },
50
+ };
51
+
52
+ const estimatedFees = await estimateFees(transactionIntent);
53
+
54
+ expect(typeof estimatedFees).toBe("bigint");
55
+ expect(estimatedFees).toBeGreaterThan(1000n);
56
+ expect(estimatedFees).toBeLessThan(10000000n);
57
+ }, 25000);
58
+
59
+ it("should handle concurrent fee estimations", async () => {
60
+ const transactionIntent: TransactionIntent = {
61
+ sender: SENDER,
62
+ recipient: RECIPIENT,
63
+ amount: BigInt(1000),
64
+ type: "send",
65
+ asset: { type: "native" },
66
+ };
67
+
68
+ // Run multiple concurrent estimations
69
+ const promises = Array(5)
70
+ .fill(0)
71
+ .map(() => estimateFees(transactionIntent));
72
+ const results = await Promise.all(promises);
73
+
74
+ // All results should be valid
75
+ results.forEach(fees => {
76
+ expect(typeof fees).toBe("bigint");
77
+ expect(fees).toBeGreaterThan(1000n);
78
+ expect(fees).toBeLessThan(10000000n);
79
+ });
80
+
81
+ // Results should be similar (may have slight variations)
82
+ const values = results.map(r => Number(r));
83
+ const minValue = Math.min(...values);
84
+ const maxValue = Math.max(...values);
85
+
86
+ // Should not vary by more than 50% under concurrent load
87
+ expect((maxValue - minValue) / minValue).toBeLessThan(0.5);
88
+ }, 25000);
89
+ });
@@ -1,19 +1,25 @@
1
1
  import suiAPI from "../network";
2
2
  import { BigNumber } from "bignumber.js";
3
3
  import type { TransactionIntent } from "@ledgerhq/coin-framework/api/index";
4
+ import { DEFAULT_COIN_TYPE } from "../network/sdk";
4
5
 
5
6
  export async function estimateFees({
6
7
  recipient,
7
8
  amount,
8
9
  sender,
10
+ asset,
9
11
  }: TransactionIntent): Promise<bigint> {
12
+ let coinType = DEFAULT_COIN_TYPE;
13
+ if (asset.type === "token" && asset.assetReference) {
14
+ coinType = asset.assetReference;
15
+ }
10
16
  const { gasBudget } = await suiAPI.paymentInfo(sender, {
11
17
  mode: "send",
12
18
  family: "sui",
13
19
  recipient,
14
20
  amount: BigNumber(amount.toString()),
15
21
  errors: {},
16
- coinType: "0x2::sui::SUI",
22
+ coinType,
17
23
  });
18
24
  return BigInt(gasBudget);
19
25
  }
@@ -0,0 +1,66 @@
1
+ import { getFullnodeUrl } from "@mysten/sui/client";
2
+ import coinConfig from "../config";
3
+ import { getBalance } from "./getBalance";
4
+
5
+ const SENDER = "0x33444cf803c690db96527cec67e3c9ab512596f4ba2d4eace43f0b4f716e0164";
6
+
7
+ describe("getBalance", () => {
8
+ beforeAll(() => {
9
+ coinConfig.setCoinConfig(() => ({
10
+ status: {
11
+ type: "active",
12
+ },
13
+ node: {
14
+ url: getFullnodeUrl("testnet"),
15
+ },
16
+ }));
17
+ });
18
+
19
+ it("should fetch native SUI balance", async () => {
20
+ const balances = await getBalance(SENDER);
21
+
22
+ expect(balances.length).toBeGreaterThanOrEqual(1);
23
+ expect(balances[0]).toMatchObject({
24
+ asset: { type: "native" },
25
+ });
26
+
27
+ expect(typeof balances[0].value).toBe("bigint");
28
+ expect(balances[0].value).toBeGreaterThanOrEqual(0n);
29
+ }, 10000);
30
+
31
+ it("should fetch token balances", async () => {
32
+ const balances = await getBalance(SENDER);
33
+
34
+ expect(balances.length).toBeGreaterThanOrEqual(1);
35
+
36
+ const tokenBalances = balances.filter(balance => balance.asset.type === "token");
37
+ tokenBalances.forEach(balance => {
38
+ expect(balance.asset.type).toBe("token");
39
+ if (balance.asset.type === "token") {
40
+ expect(balance.asset.assetReference).toMatch(
41
+ /^0x[a-fA-F0-9]+::[a-zA-Z0-9_]+::[a-zA-Z0-9_]+$/,
42
+ );
43
+ }
44
+ expect(typeof balance.value).toBe("bigint");
45
+ expect(balance.value).toBeGreaterThanOrEqual(0n);
46
+ });
47
+ }, 15000);
48
+
49
+ it("should properly parse token asset reference", async () => {
50
+ const balances = await getBalance(SENDER);
51
+
52
+ const usdTokens = balances.filter(
53
+ balance =>
54
+ balance.asset.type === "token" &&
55
+ balance.asset.assetReference?.toLowerCase().includes("usd"),
56
+ );
57
+
58
+ usdTokens.forEach(balance => {
59
+ expect(balance.asset.type).toBe("token");
60
+ if (balance.asset.type === "token") {
61
+ expect(balance.asset.assetReference).toBeTruthy();
62
+ }
63
+ expect(typeof balance.value).toBe("bigint");
64
+ });
65
+ }, 15000);
66
+ });
@@ -1,24 +1,74 @@
1
1
  import { getBalance } from "./getBalance";
2
- import { getAccount } from "../network";
2
+ import { getAllBalancesCached, getStakes } from "../network";
3
+ import type { Stake } from "@ledgerhq/coin-framework/api/types";
3
4
 
4
- // Mock the getAccount function
5
5
  jest.mock("../network", () => ({
6
- getAccount: jest.fn().mockResolvedValue({ balance: 1000000000 }),
6
+ getAllBalancesCached: jest.fn().mockResolvedValue([
7
+ { coinType: "0x2::sui::SUI", totalBalance: 1000000000 },
8
+ { coinType: "0x3::usdt::USDT", totalBalance: 500000000 },
9
+ ]),
10
+ getStakes: jest.fn().mockResolvedValue([]),
7
11
  }));
8
12
 
13
+ const mockedGetStakes = jest.mocked(getStakes);
14
+
9
15
  describe("getBalance", () => {
10
16
  beforeEach(() => {
11
17
  jest.clearAllMocks();
12
18
  });
13
19
 
14
- it("should return the correct balance as bigint", async () => {
15
- // Mock the getAccount response
16
- const mockBalance = { balance: 1000000000 };
20
+ it("should return the correct native SUI balance", async () => {
21
+ const address = "0x123";
22
+ const result = await getBalance(address);
17
23
 
24
+ expect(result[0]).toMatchObject({
25
+ value: BigInt(1000000000),
26
+ asset: { type: "native" },
27
+ });
28
+ });
29
+
30
+ it("should return the correct USDT token balance", async () => {
18
31
  const address = "0x123";
19
32
  const result = await getBalance(address);
20
33
 
21
- expect(getAccount).toHaveBeenCalledWith(address);
22
- expect(result[0].value).toBe(BigInt(mockBalance.balance.toString()));
34
+ expect(result[1]).toMatchObject({
35
+ value: BigInt(500000000),
36
+ asset: { type: "token", assetReference: "0x3::usdt::USDT" },
37
+ });
38
+ });
39
+
40
+ it("should return staking balances when stakes are available", async () => {
41
+ const mockStakes: Stake[] = [
42
+ {
43
+ uid: "stake_1",
44
+ address: "0x123",
45
+ delegate: "0xvalidator1",
46
+ state: "active",
47
+ asset: { type: "native" },
48
+ amount: BigInt(2000000000),
49
+ },
50
+ ];
51
+
52
+ mockedGetStakes.mockResolvedValueOnce(mockStakes);
53
+
54
+ const address = "0x123";
55
+ const result = await getBalance(address);
56
+
57
+ expect(getAllBalancesCached).toHaveBeenCalledWith(address);
58
+ expect(getStakes).toHaveBeenCalledWith(address);
59
+ expect(result).toHaveLength(3);
60
+
61
+ expect(result[2]).toMatchObject({
62
+ value: BigInt(2000000000),
63
+ asset: { type: "native" },
64
+ stake: {
65
+ uid: "stake_1",
66
+ address: "0x123",
67
+ delegate: "0xvalidator1",
68
+ state: "active",
69
+ asset: { type: "native" },
70
+ amount: BigInt(2000000000),
71
+ },
72
+ });
23
73
  });
24
74
  });
@@ -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
  });
@@ -11,6 +11,8 @@ import {
11
11
  QueryTransactionBlocksParams,
12
12
  BalanceChange,
13
13
  SuiTransactionBlockResponseOptions,
14
+ DelegatedStake,
15
+ StakeObject,
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
@@ -670,3 +658,51 @@ export const queryTransactionsByDigest = async (params: {
670
658
 
671
659
  return responses;
672
660
  };
661
+
662
+ export const getStakes = (address: string): Promise<Stake[]> =>
663
+ withApi(async api =>
664
+ api
665
+ .getStakes({ owner: address })
666
+ .then(delegations => delegations.flatMap(delegation => toStakes(address, delegation))),
667
+ );
668
+
669
+ export const toStakes = (address: string, delegation: DelegatedStake): Stake[] =>
670
+ delegation.stakes.map(stake => {
671
+ const { deposited, rewarded } = toStakeAmounts(stake);
672
+ return {
673
+ uid: stake.stakedSuiId,
674
+ address: address,
675
+ delegate: delegation.validatorAddress,
676
+ state: toStakeState(stake.status),
677
+ asset: { type: "native" },
678
+ amount: deposited + rewarded,
679
+ amountDeposited: deposited,
680
+ amountRewarded: rewarded,
681
+ details: {
682
+ activeEpoch: Number(stake.stakeActiveEpoch),
683
+ requestEpoch: Number(stake.stakeRequestEpoch),
684
+ },
685
+ };
686
+ });
687
+
688
+ export const toStakeState = (status: "Pending" | "Active" | "Unstaked"): StakeState => {
689
+ switch (status) {
690
+ case "Pending":
691
+ return "activating";
692
+ case "Active":
693
+ return "active";
694
+ case "Unstaked":
695
+ return "inactive";
696
+ }
697
+ };
698
+
699
+ export const toStakeAmounts = (stake: StakeObject): { deposited: bigint; rewarded: bigint } => {
700
+ switch (stake.status) {
701
+ case "Pending":
702
+ return { deposited: BigInt(stake.principal), rewarded: 0n };
703
+ case "Active":
704
+ return { deposited: BigInt(stake.principal), rewarded: BigInt(stake.estimatedReward) };
705
+ case "Unstaked":
706
+ return { deposited: BigInt(stake.principal), rewarded: 0n }; // note: we lose reward information in unstaked state here
707
+ }
708
+ };
@@ -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
+ }