@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.
- package/.turbo/turbo-build.log +1 -1
- package/.unimportedrc.json +1 -1
- package/CHANGELOG.md +20 -0
- package/lib/api/index.d.ts.map +1 -1
- package/lib/api/index.integration.test.js +21 -2
- package/lib/api/index.integration.test.js.map +1 -1
- package/lib/api/index.js +2 -0
- package/lib/api/index.js.map +1 -1
- package/lib/api/index.test.js +37 -0
- package/lib/api/index.test.js.map +1 -1
- package/lib/bridge/buildTransaction.d.ts +1 -3
- package/lib/bridge/buildTransaction.d.ts.map +1 -1
- package/lib/bridge/buildTransaction.integration.test.js +5 -35
- package/lib/bridge/buildTransaction.integration.test.js.map +1 -1
- package/lib/bridge/buildTransaction.js +5 -20
- package/lib/bridge/buildTransaction.js.map +1 -1
- package/lib/bridge/buildTransaction.test.js +28 -129
- package/lib/bridge/buildTransaction.test.js.map +1 -1
- package/lib/bridge/getFeesForTransaction.d.ts.map +1 -1
- package/lib/bridge/getFeesForTransaction.js +6 -1
- package/lib/bridge/getFeesForTransaction.js.map +1 -1
- package/lib/logic/craftTransaction.d.ts +2 -2
- package/lib/logic/craftTransaction.d.ts.map +1 -1
- package/lib/logic/craftTransaction.integration.test.d.ts +2 -0
- package/lib/logic/craftTransaction.integration.test.d.ts.map +1 -0
- package/lib/logic/craftTransaction.integration.test.js +56 -0
- package/lib/logic/craftTransaction.integration.test.js.map +1 -0
- package/lib/logic/craftTransaction.js +1 -1
- package/lib/logic/craftTransaction.js.map +1 -1
- package/lib/logic/estimateFees.d.ts +1 -1
- package/lib/logic/estimateFees.d.ts.map +1 -1
- package/lib/logic/estimateFees.integration.test.d.ts +2 -0
- package/lib/logic/estimateFees.integration.test.d.ts.map +1 -0
- package/lib/logic/estimateFees.integration.test.js +79 -0
- package/lib/logic/estimateFees.integration.test.js.map +1 -0
- package/lib/logic/estimateFees.js +7 -2
- package/lib/logic/estimateFees.js.map +1 -1
- package/lib/logic/getBalance.d.ts.map +1 -1
- package/lib/logic/getBalance.integration.test.d.ts +2 -0
- package/lib/logic/getBalance.integration.test.d.ts.map +1 -0
- package/lib/logic/getBalance.integration.test.js +56 -0
- package/lib/logic/getBalance.integration.test.js.map +1 -0
- package/lib/logic/getBalance.js +18 -7
- package/lib/logic/getBalance.js.map +1 -1
- package/lib/logic/getBalance.test.js +49 -7
- package/lib/logic/getBalance.test.js.map +1 -1
- package/lib/logic/index.d.ts +1 -0
- package/lib/logic/index.d.ts.map +1 -1
- package/lib/logic/index.js +4 -1
- package/lib/logic/index.js.map +1 -1
- package/lib/logic/staking.d.ts +4 -0
- package/lib/logic/staking.d.ts.map +1 -0
- package/lib/logic/staking.js +36 -0
- package/lib/logic/staking.js.map +1 -0
- package/lib/network/index.d.ts +6 -6
- package/lib/network/index.d.ts.map +1 -1
- package/lib/network/index.js +9 -3
- package/lib/network/index.js.map +1 -1
- package/lib/network/sdk.d.ts +10 -17
- package/lib/network/sdk.d.ts.map +1 -1
- package/lib/network/sdk.integration.test.js +21 -22
- package/lib/network/sdk.integration.test.js.map +1 -1
- package/lib/network/sdk.js +50 -18
- package/lib/network/sdk.js.map +1 -1
- package/lib/test/testUtils.d.ts +2 -0
- package/lib/test/testUtils.d.ts.map +1 -0
- package/lib/test/testUtils.js +35 -0
- package/lib/test/testUtils.js.map +1 -0
- package/lib-es/api/index.d.ts.map +1 -1
- package/lib-es/api/index.integration.test.js +21 -2
- package/lib-es/api/index.integration.test.js.map +1 -1
- package/lib-es/api/index.js +3 -1
- package/lib-es/api/index.js.map +1 -1
- package/lib-es/api/index.test.js +37 -0
- package/lib-es/api/index.test.js.map +1 -1
- package/lib-es/bridge/buildTransaction.d.ts +1 -3
- package/lib-es/bridge/buildTransaction.d.ts.map +1 -1
- package/lib-es/bridge/buildTransaction.integration.test.js +1 -31
- package/lib-es/bridge/buildTransaction.integration.test.js.map +1 -1
- package/lib-es/bridge/buildTransaction.js +4 -15
- package/lib-es/bridge/buildTransaction.js.map +1 -1
- package/lib-es/bridge/buildTransaction.test.js +29 -130
- package/lib-es/bridge/buildTransaction.test.js.map +1 -1
- package/lib-es/bridge/getFeesForTransaction.d.ts.map +1 -1
- package/lib-es/bridge/getFeesForTransaction.js +6 -1
- package/lib-es/bridge/getFeesForTransaction.js.map +1 -1
- package/lib-es/logic/craftTransaction.d.ts +2 -2
- package/lib-es/logic/craftTransaction.d.ts.map +1 -1
- package/lib-es/logic/craftTransaction.integration.test.d.ts +2 -0
- package/lib-es/logic/craftTransaction.integration.test.d.ts.map +1 -0
- package/lib-es/logic/craftTransaction.integration.test.js +51 -0
- package/lib-es/logic/craftTransaction.integration.test.js.map +1 -0
- package/lib-es/logic/craftTransaction.js +1 -1
- package/lib-es/logic/craftTransaction.js.map +1 -1
- package/lib-es/logic/estimateFees.d.ts +1 -1
- package/lib-es/logic/estimateFees.d.ts.map +1 -1
- package/lib-es/logic/estimateFees.integration.test.d.ts +2 -0
- package/lib-es/logic/estimateFees.integration.test.d.ts.map +1 -0
- package/lib-es/logic/estimateFees.integration.test.js +74 -0
- package/lib-es/logic/estimateFees.integration.test.js.map +1 -0
- package/lib-es/logic/estimateFees.js +7 -2
- package/lib-es/logic/estimateFees.js.map +1 -1
- package/lib-es/logic/getBalance.d.ts.map +1 -1
- package/lib-es/logic/getBalance.integration.test.d.ts +2 -0
- package/lib-es/logic/getBalance.integration.test.d.ts.map +1 -0
- package/lib-es/logic/getBalance.integration.test.js +51 -0
- package/lib-es/logic/getBalance.integration.test.js.map +1 -0
- package/lib-es/logic/getBalance.js +19 -8
- package/lib-es/logic/getBalance.js.map +1 -1
- package/lib-es/logic/getBalance.test.js +50 -8
- package/lib-es/logic/getBalance.test.js.map +1 -1
- package/lib-es/logic/index.d.ts +1 -0
- package/lib-es/logic/index.d.ts.map +1 -1
- package/lib-es/logic/index.js +1 -0
- package/lib-es/logic/index.js.map +1 -1
- package/lib-es/logic/staking.d.ts +4 -0
- package/lib-es/logic/staking.d.ts.map +1 -0
- package/lib-es/logic/staking.js +8 -0
- package/lib-es/logic/staking.js.map +1 -0
- package/lib-es/network/index.d.ts +6 -6
- package/lib-es/network/index.d.ts.map +1 -1
- package/lib-es/network/index.js +6 -3
- package/lib-es/network/index.js.map +1 -1
- package/lib-es/network/sdk.d.ts +10 -17
- package/lib-es/network/sdk.d.ts.map +1 -1
- package/lib-es/network/sdk.integration.test.js +22 -23
- package/lib-es/network/sdk.integration.test.js.map +1 -1
- package/lib-es/network/sdk.js +45 -16
- package/lib-es/network/sdk.js.map +1 -1
- package/lib-es/test/testUtils.d.ts +2 -0
- package/lib-es/test/testUtils.d.ts.map +1 -0
- package/lib-es/test/testUtils.js +31 -0
- package/lib-es/test/testUtils.js.map +1 -0
- package/package.json +4 -4
- package/src/api/index.integration.test.ts +24 -2
- package/src/api/index.test.ts +40 -0
- package/src/api/index.ts +4 -0
- package/src/bridge/buildTransaction.integration.test.ts +1 -39
- package/src/bridge/buildTransaction.test.ts +36 -159
- package/src/bridge/buildTransaction.ts +5 -19
- package/src/bridge/getFeesForTransaction.ts +7 -1
- package/src/logic/craftTransaction.integration.test.ts +63 -0
- package/src/logic/craftTransaction.ts +3 -3
- package/src/logic/estimateFees.integration.test.ts +89 -0
- package/src/logic/estimateFees.ts +7 -1
- package/src/logic/getBalance.integration.test.ts +66 -0
- package/src/logic/getBalance.test.ts +58 -8
- package/src/logic/getBalance.ts +24 -8
- package/src/logic/index.ts +1 -0
- package/src/logic/staking.ts +10 -0
- package/src/network/index.ts +12 -3
- package/src/network/sdk.integration.test.ts +25 -22
- package/src/network/sdk.ts +68 -32
- 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
|
|
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 {
|
|
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
|
-
|
|
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
|
|
15
|
-
|
|
16
|
-
const
|
|
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(
|
|
22
|
-
|
|
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
|
});
|
package/src/logic/getBalance.ts
CHANGED
|
@@ -1,12 +1,28 @@
|
|
|
1
|
-
import {
|
|
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
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
+
);
|
package/src/logic/index.ts
CHANGED
|
@@ -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
|
+
};
|
package/src/network/index.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
});
|
package/src/network/sdk.ts
CHANGED
|
@@ -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
|
-
(
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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
|
+
}
|