@ledgerhq/coin-sui 0.11.0 → 0.12.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.
- package/.eslintrc.js +1 -0
- package/.turbo/turbo-build.log +1 -1
- package/.unimportedrc.json +1 -0
- package/CHANGELOG.md +21 -0
- package/index.d.ts +0 -1
- package/jest.config.js +1 -1
- package/lib/bridge/broadcast.d.ts.map +1 -1
- package/lib/bridge/broadcast.js +3 -0
- package/lib/bridge/broadcast.js.map +1 -1
- package/lib/bridge/buildOptimisticOperation.js +31 -0
- package/lib/bridge/buildOptimisticOperation.js.map +1 -1
- package/lib/bridge/buildTransaction.d.ts +1 -1
- package/lib/bridge/buildTransaction.d.ts.map +1 -1
- package/lib/bridge/buildTransaction.js +3 -1
- package/lib/bridge/buildTransaction.js.map +1 -1
- package/lib/bridge/buildTransaction.test.js +4 -0
- package/lib/bridge/buildTransaction.test.js.map +1 -1
- package/lib/bridge/estimateMaxSpendable.d.ts.map +1 -1
- package/lib/bridge/estimateMaxSpendable.js +15 -3
- package/lib/bridge/estimateMaxSpendable.js.map +1 -1
- package/lib/bridge/estimateMaxSpendable.test.js +27 -0
- package/lib/bridge/estimateMaxSpendable.test.js.map +1 -1
- package/lib/bridge/getFeesForTransaction.d.ts.map +1 -1
- package/lib/bridge/getFeesForTransaction.js +13 -1
- package/lib/bridge/getFeesForTransaction.js.map +1 -1
- package/lib/bridge/getOperationExtra.d.ts +2 -0
- package/lib/bridge/getOperationExtra.d.ts.map +1 -0
- package/lib/bridge/getOperationExtra.js +6 -0
- package/lib/bridge/getOperationExtra.js.map +1 -0
- package/lib/bridge/getTransactionStatus.d.ts.map +1 -1
- package/lib/bridge/getTransactionStatus.js +45 -20
- package/lib/bridge/getTransactionStatus.js.map +1 -1
- package/lib/bridge/preload.d.ts.map +1 -1
- package/lib/bridge/preload.js +5 -3
- package/lib/bridge/preload.js.map +1 -1
- package/lib/bridge/preload.test.js +13 -217
- package/lib/bridge/preload.test.js.map +1 -1
- package/lib/bridge/prepareTransaction.js +1 -1
- package/lib/bridge/prepareTransaction.js.map +1 -1
- package/lib/bridge/synchronisation.d.ts.map +1 -1
- package/lib/bridge/synchronisation.js +5 -2
- package/lib/bridge/synchronisation.js.map +1 -1
- package/lib/bridge/synchronisation.test.js +355 -7
- package/lib/bridge/synchronisation.test.js.map +1 -1
- package/lib/bridge/utils.d.ts +1 -1
- package/lib/bridge/utils.d.ts.map +1 -1
- package/lib/bridge/utils.js +22 -0
- package/lib/bridge/utils.js.map +1 -1
- package/lib/constants.d.ts +4 -0
- package/lib/constants.d.ts.map +1 -0
- package/lib/constants.js +7 -0
- package/lib/constants.js.map +1 -0
- package/lib/errors.d.ts +13 -0
- package/lib/errors.d.ts.map +1 -0
- package/lib/errors.js +21 -0
- package/lib/errors.js.map +1 -0
- package/lib/logic/craftTransaction.d.ts +5 -10
- package/lib/logic/craftTransaction.d.ts.map +1 -1
- package/lib/logic/craftTransaction.js +2 -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.js +14 -2
- package/lib/logic/estimateFees.js.map +1 -1
- package/lib/logic/index.d.ts +2 -1
- package/lib/logic/index.d.ts.map +1 -1
- package/lib/logic/index.js +3 -1
- package/lib/logic/index.js.map +1 -1
- package/lib/logic/stake.d.ts +3 -0
- package/lib/logic/stake.d.ts.map +1 -0
- package/lib/logic/stake.js +12 -0
- package/lib/logic/stake.js.map +1 -0
- package/lib/network/index.d.ts +5 -4
- package/lib/network/index.d.ts.map +1 -1
- package/lib/network/index.js +5 -3
- package/lib/network/index.js.map +1 -1
- package/lib/network/sdk.d.ts +5 -3
- package/lib/network/sdk.d.ts.map +1 -1
- package/lib/network/sdk.js +148 -26
- package/lib/network/sdk.js.map +1 -1
- package/lib/network/sdk.test.js +491 -6
- package/lib/network/sdk.test.js.map +1 -1
- package/lib/types/bridge.d.ts +33 -6
- package/lib/types/bridge.d.ts.map +1 -1
- package/lib-es/bridge/broadcast.d.ts.map +1 -1
- package/lib-es/bridge/broadcast.js +3 -0
- package/lib-es/bridge/broadcast.js.map +1 -1
- package/lib-es/bridge/buildOptimisticOperation.js +31 -0
- package/lib-es/bridge/buildOptimisticOperation.js.map +1 -1
- package/lib-es/bridge/buildTransaction.d.ts +1 -1
- package/lib-es/bridge/buildTransaction.d.ts.map +1 -1
- package/lib-es/bridge/buildTransaction.js +3 -1
- package/lib-es/bridge/buildTransaction.js.map +1 -1
- package/lib-es/bridge/buildTransaction.test.js +4 -0
- package/lib-es/bridge/buildTransaction.test.js.map +1 -1
- package/lib-es/bridge/estimateMaxSpendable.d.ts.map +1 -1
- package/lib-es/bridge/estimateMaxSpendable.js +15 -3
- package/lib-es/bridge/estimateMaxSpendable.js.map +1 -1
- package/lib-es/bridge/estimateMaxSpendable.test.js +27 -0
- package/lib-es/bridge/estimateMaxSpendable.test.js.map +1 -1
- package/lib-es/bridge/getFeesForTransaction.d.ts.map +1 -1
- package/lib-es/bridge/getFeesForTransaction.js +13 -1
- package/lib-es/bridge/getFeesForTransaction.js.map +1 -1
- package/lib-es/bridge/getOperationExtra.d.ts +2 -0
- package/lib-es/bridge/getOperationExtra.d.ts.map +1 -0
- package/lib-es/bridge/getOperationExtra.js +2 -0
- package/lib-es/bridge/getOperationExtra.js.map +1 -0
- package/lib-es/bridge/getTransactionStatus.d.ts.map +1 -1
- package/lib-es/bridge/getTransactionStatus.js +46 -21
- package/lib-es/bridge/getTransactionStatus.js.map +1 -1
- package/lib-es/bridge/preload.d.ts.map +1 -1
- package/lib-es/bridge/preload.js +5 -3
- package/lib-es/bridge/preload.js.map +1 -1
- package/lib-es/bridge/preload.test.js +14 -218
- package/lib-es/bridge/preload.test.js.map +1 -1
- package/lib-es/bridge/prepareTransaction.js +1 -1
- package/lib-es/bridge/prepareTransaction.js.map +1 -1
- package/lib-es/bridge/synchronisation.d.ts.map +1 -1
- package/lib-es/bridge/synchronisation.js +6 -3
- package/lib-es/bridge/synchronisation.js.map +1 -1
- package/lib-es/bridge/synchronisation.test.js +332 -7
- package/lib-es/bridge/synchronisation.test.js.map +1 -1
- package/lib-es/bridge/utils.d.ts +1 -1
- package/lib-es/bridge/utils.d.ts.map +1 -1
- package/lib-es/bridge/utils.js +22 -0
- package/lib-es/bridge/utils.js.map +1 -1
- package/lib-es/constants.d.ts +4 -0
- package/lib-es/constants.d.ts.map +1 -0
- package/lib-es/constants.js +4 -0
- package/lib-es/constants.js.map +1 -0
- package/lib-es/errors.d.ts +13 -0
- package/lib-es/errors.d.ts.map +1 -0
- package/lib-es/errors.js +18 -0
- package/lib-es/errors.js.map +1 -0
- package/lib-es/logic/craftTransaction.d.ts +5 -10
- package/lib-es/logic/craftTransaction.d.ts.map +1 -1
- package/lib-es/logic/craftTransaction.js +2 -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.js +14 -2
- package/lib-es/logic/estimateFees.js.map +1 -1
- package/lib-es/logic/index.d.ts +2 -1
- 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/stake.d.ts +3 -0
- package/lib-es/logic/stake.d.ts.map +1 -0
- package/lib-es/logic/stake.js +8 -0
- package/lib-es/logic/stake.js.map +1 -0
- package/lib-es/network/index.d.ts +5 -4
- package/lib-es/network/index.d.ts.map +1 -1
- package/lib-es/network/index.js +4 -3
- package/lib-es/network/index.js.map +1 -1
- package/lib-es/network/sdk.d.ts +5 -3
- package/lib-es/network/sdk.d.ts.map +1 -1
- package/lib-es/network/sdk.js +144 -25
- package/lib-es/network/sdk.js.map +1 -1
- package/lib-es/network/sdk.test.js +491 -6
- package/lib-es/network/sdk.test.js.map +1 -1
- package/lib-es/types/bridge.d.ts +33 -6
- package/lib-es/types/bridge.d.ts.map +1 -1
- package/package.json +20 -12
- package/src/bridge/broadcast.ts +3 -0
- package/src/bridge/buildOptimisticOperation.ts +47 -4
- package/src/bridge/buildTransaction.test.ts +4 -0
- package/src/bridge/buildTransaction.ts +3 -1
- package/src/bridge/estimateMaxSpendable.test.ts +33 -0
- package/src/bridge/estimateMaxSpendable.ts +17 -3
- package/src/bridge/getFeesForTransaction.ts +14 -1
- package/src/bridge/getOperationExtra.ts +1 -0
- package/src/bridge/getTransactionStatus.ts +53 -21
- package/src/bridge/preload.test.ts +13 -279
- package/src/bridge/preload.ts +5 -3
- package/src/bridge/prepareTransaction.ts +1 -1
- package/src/bridge/synchronisation.test.ts +389 -7
- package/src/bridge/synchronisation.ts +6 -3
- package/src/bridge/utils.ts +25 -1
- package/src/constants.ts +4 -0
- package/src/errors.ts +21 -0
- package/src/logic/craftTransaction.ts +6 -9
- package/src/logic/estimateFees.ts +16 -1
- package/src/logic/index.ts +2 -1
- package/src/logic/stake.ts +9 -0
- package/src/network/index.ts +6 -3
- package/src/network/sdk.test.ts +538 -10
- package/src/network/sdk.ts +179 -31
- package/src/types/bridge.ts +32 -6
|
@@ -4,6 +4,9 @@ import { faker } from "@faker-js/faker";
|
|
|
4
4
|
import { createFixtureAccount, createFixtureOperation } from "../types/bridge.fixture";
|
|
5
5
|
import { DEFAULT_COIN_TYPE } from "../network/sdk";
|
|
6
6
|
import { getAccountShape } from "./synchronisation";
|
|
7
|
+
import coinConfig from "../config";
|
|
8
|
+
import { getFullnodeUrl } from "@mysten/sui/client";
|
|
9
|
+
import * as networkModule from "../network";
|
|
7
10
|
|
|
8
11
|
// Mock getTokenById and listTokensForCryptoCurrency
|
|
9
12
|
jest.mock("@ledgerhq/cryptoassets/tokens", () => ({
|
|
@@ -20,17 +23,34 @@ jest.mock("@ledgerhq/cryptoassets/tokens", () => ({
|
|
|
20
23
|
listTokensForCryptoCurrency: () => [{ id: "0x123::sui::TEST" }],
|
|
21
24
|
}));
|
|
22
25
|
|
|
23
|
-
|
|
24
|
-
const
|
|
25
|
-
jest.
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
jest.mock("../network", () => {
|
|
27
|
+
const mockGetAccountBalances = jest.fn();
|
|
28
|
+
const mockGetOperations = jest.fn();
|
|
29
|
+
const mockGetStakesRaw = jest.fn();
|
|
30
|
+
return {
|
|
31
|
+
getAccountBalances: mockGetAccountBalances,
|
|
32
|
+
getOperations: mockGetOperations,
|
|
33
|
+
getStakesRaw: mockGetStakesRaw,
|
|
34
|
+
createTransaction: jest.fn(),
|
|
35
|
+
};
|
|
36
|
+
});
|
|
29
37
|
|
|
30
38
|
describe("getAccountShape", () => {
|
|
39
|
+
const mockGetAccountBalances = networkModule.getAccountBalances as jest.Mock;
|
|
40
|
+
const mockGetOperations = networkModule.getOperations as jest.Mock;
|
|
41
|
+
const mockGetStakesRaw = networkModule.getStakesRaw as jest.Mock;
|
|
42
|
+
|
|
31
43
|
beforeEach(() => {
|
|
32
44
|
mockGetAccountBalances.mockClear();
|
|
33
45
|
mockGetOperations.mockClear();
|
|
46
|
+
mockGetStakesRaw.mockClear();
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
beforeAll(() => {
|
|
50
|
+
coinConfig.setCoinConfig(() => ({
|
|
51
|
+
status: { type: "active" },
|
|
52
|
+
node: { url: getFullnodeUrl("mainnet") },
|
|
53
|
+
}));
|
|
34
54
|
});
|
|
35
55
|
|
|
36
56
|
it("calls getAccountBalances and getOperations", async () => {
|
|
@@ -38,6 +58,7 @@ describe("getAccountShape", () => {
|
|
|
38
58
|
const initialAccount = undefined;
|
|
39
59
|
mockGetAccountBalances.mockResolvedValue([createAccountBalance()]);
|
|
40
60
|
mockGetOperations.mockResolvedValue([]);
|
|
61
|
+
mockGetStakesRaw.mockResolvedValue([]);
|
|
41
62
|
|
|
42
63
|
// WHEN
|
|
43
64
|
await getAccountShape(
|
|
@@ -63,6 +84,7 @@ describe("getAccountShape", () => {
|
|
|
63
84
|
const accountBalance = createAccountBalance();
|
|
64
85
|
mockGetAccountBalances.mockResolvedValue([accountBalance]);
|
|
65
86
|
mockGetOperations.mockResolvedValue([]);
|
|
87
|
+
mockGetStakesRaw.mockResolvedValue([]);
|
|
66
88
|
|
|
67
89
|
// WHEN
|
|
68
90
|
const shape = await getAccountShape(
|
|
@@ -85,8 +107,11 @@ describe("getAccountShape", () => {
|
|
|
85
107
|
blockHeight: 5,
|
|
86
108
|
operations: [],
|
|
87
109
|
operationsCount: 0,
|
|
88
|
-
suiResources: {
|
|
110
|
+
suiResources: {
|
|
111
|
+
stakes: [],
|
|
112
|
+
},
|
|
89
113
|
subAccounts: [],
|
|
114
|
+
syncHash: undefined,
|
|
90
115
|
});
|
|
91
116
|
});
|
|
92
117
|
|
|
@@ -98,6 +123,7 @@ describe("getAccountShape", () => {
|
|
|
98
123
|
const accountBalance = createAccountBalance();
|
|
99
124
|
mockGetAccountBalances.mockResolvedValue([accountBalance]);
|
|
100
125
|
mockGetOperations.mockResolvedValue([]);
|
|
126
|
+
mockGetStakesRaw.mockResolvedValue([]);
|
|
101
127
|
|
|
102
128
|
// WHEN
|
|
103
129
|
const shape = await getAccountShape(
|
|
@@ -128,6 +154,7 @@ describe("getAccountShape", () => {
|
|
|
128
154
|
createFixtureOperation({ id: faker.string.uuid(), extra }),
|
|
129
155
|
];
|
|
130
156
|
mockGetOperations.mockResolvedValue(apiOperations);
|
|
157
|
+
mockGetStakesRaw.mockResolvedValue([]);
|
|
131
158
|
|
|
132
159
|
// WHEN
|
|
133
160
|
const shape = await getAccountShape(
|
|
@@ -154,6 +181,7 @@ describe("getAccountShape", () => {
|
|
|
154
181
|
const tokenBalance = createAccountBalance({ coinType: "0x123::sui::TEST" });
|
|
155
182
|
mockGetAccountBalances.mockResolvedValue([mainBalance, tokenBalance]);
|
|
156
183
|
mockGetOperations.mockResolvedValue([]);
|
|
184
|
+
mockGetStakesRaw.mockResolvedValue([]);
|
|
157
185
|
|
|
158
186
|
// WHEN
|
|
159
187
|
const shape = await getAccountShape(
|
|
@@ -173,6 +201,360 @@ describe("getAccountShape", () => {
|
|
|
173
201
|
expect(shape.subAccounts).toBeDefined();
|
|
174
202
|
expect(Array.isArray(shape.subAccounts)).toBe(true);
|
|
175
203
|
});
|
|
204
|
+
|
|
205
|
+
describe("stakes functionality", () => {
|
|
206
|
+
it("calls getStakesRaw with the correct address", async () => {
|
|
207
|
+
// GIVEN
|
|
208
|
+
const address = "0x6e143fe0a8ca010a86580dafac44298e5b1b7d73efc345356a59a15f0d7824f0";
|
|
209
|
+
const initialAccount = undefined;
|
|
210
|
+
mockGetAccountBalances.mockResolvedValue([createAccountBalance()]);
|
|
211
|
+
mockGetOperations.mockResolvedValue([]);
|
|
212
|
+
mockGetStakesRaw.mockResolvedValue([]);
|
|
213
|
+
|
|
214
|
+
// WHEN
|
|
215
|
+
await getAccountShape(
|
|
216
|
+
{
|
|
217
|
+
index: 0,
|
|
218
|
+
derivationPath: "44'/784'/0'/0'/0'",
|
|
219
|
+
currency: getCryptoCurrencyById("sui"),
|
|
220
|
+
address,
|
|
221
|
+
initialAccount,
|
|
222
|
+
derivationMode: "sui",
|
|
223
|
+
},
|
|
224
|
+
{ blacklistedTokenIds: [], paginationConfig: {} },
|
|
225
|
+
);
|
|
226
|
+
|
|
227
|
+
// THEN
|
|
228
|
+
expect(mockGetStakesRaw).toHaveBeenCalledTimes(1);
|
|
229
|
+
expect(mockGetStakesRaw).toHaveBeenCalledWith(address);
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
it("includes empty stakes in suiResources when no stakes are returned", async () => {
|
|
233
|
+
// GIVEN
|
|
234
|
+
const initialAccount = undefined;
|
|
235
|
+
mockGetAccountBalances.mockResolvedValue([createAccountBalance()]);
|
|
236
|
+
mockGetOperations.mockResolvedValue([]);
|
|
237
|
+
mockGetStakesRaw.mockResolvedValue([]);
|
|
238
|
+
|
|
239
|
+
// WHEN
|
|
240
|
+
const shape = await getAccountShape(
|
|
241
|
+
{
|
|
242
|
+
index: 0,
|
|
243
|
+
derivationPath: "44'/784'/0'/0'/0'",
|
|
244
|
+
currency: getCryptoCurrencyById("sui"),
|
|
245
|
+
address: "0x6e143fe0a8ca010a86580dafac44298e5b1b7d73efc345356a59a15f0d7824f0",
|
|
246
|
+
initialAccount,
|
|
247
|
+
derivationMode: "sui",
|
|
248
|
+
},
|
|
249
|
+
{ blacklistedTokenIds: [], paginationConfig: {} },
|
|
250
|
+
);
|
|
251
|
+
|
|
252
|
+
// THEN
|
|
253
|
+
expect(shape.suiResources).toBeDefined();
|
|
254
|
+
expect(shape.suiResources?.stakes).toEqual([]);
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
it("includes stakes in suiResources when stakes are returned", async () => {
|
|
258
|
+
// GIVEN
|
|
259
|
+
const initialAccount = undefined;
|
|
260
|
+
const mockStakes = [
|
|
261
|
+
{
|
|
262
|
+
validatorAddress: "0xvalidator1",
|
|
263
|
+
stakes: [
|
|
264
|
+
{
|
|
265
|
+
stakedSuiId: "0xstake1",
|
|
266
|
+
status: "Active" as const,
|
|
267
|
+
principal: "1000000000",
|
|
268
|
+
stakeActiveEpoch: "100",
|
|
269
|
+
stakeRequestEpoch: "95",
|
|
270
|
+
},
|
|
271
|
+
],
|
|
272
|
+
},
|
|
273
|
+
{
|
|
274
|
+
validatorAddress: "0xvalidator2",
|
|
275
|
+
stakes: [
|
|
276
|
+
{
|
|
277
|
+
stakedSuiId: "0xstake2",
|
|
278
|
+
status: "Pending" as const,
|
|
279
|
+
principal: "2000000000",
|
|
280
|
+
stakeActiveEpoch: "0",
|
|
281
|
+
stakeRequestEpoch: "100",
|
|
282
|
+
},
|
|
283
|
+
],
|
|
284
|
+
},
|
|
285
|
+
];
|
|
286
|
+
mockGetAccountBalances.mockResolvedValue([createAccountBalance()]);
|
|
287
|
+
mockGetOperations.mockResolvedValue([]);
|
|
288
|
+
mockGetStakesRaw.mockResolvedValue(mockStakes);
|
|
289
|
+
|
|
290
|
+
// WHEN
|
|
291
|
+
const shape = await getAccountShape(
|
|
292
|
+
{
|
|
293
|
+
index: 0,
|
|
294
|
+
derivationPath: "44'/784'/0'/0'/0'",
|
|
295
|
+
currency: getCryptoCurrencyById("sui"),
|
|
296
|
+
address: "0x6e143fe0a8ca010a86580dafac44298e5b1b7d73efc345356a59a15f0d7824f0",
|
|
297
|
+
initialAccount,
|
|
298
|
+
derivationMode: "sui",
|
|
299
|
+
},
|
|
300
|
+
{ blacklistedTokenIds: [], paginationConfig: {} },
|
|
301
|
+
);
|
|
302
|
+
|
|
303
|
+
// THEN
|
|
304
|
+
expect(shape.suiResources).toBeDefined();
|
|
305
|
+
expect(shape.suiResources?.stakes).toEqual(mockStakes);
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
it("handles multiple stakes per validator", async () => {
|
|
309
|
+
// GIVEN
|
|
310
|
+
const initialAccount = undefined;
|
|
311
|
+
const mockStakes = [
|
|
312
|
+
{
|
|
313
|
+
validatorAddress: "0xvalidator1",
|
|
314
|
+
stakes: [
|
|
315
|
+
{
|
|
316
|
+
stakedSuiId: "0xstake1",
|
|
317
|
+
status: "Active" as const,
|
|
318
|
+
principal: "1000000000",
|
|
319
|
+
stakeActiveEpoch: "100",
|
|
320
|
+
stakeRequestEpoch: "95",
|
|
321
|
+
},
|
|
322
|
+
{
|
|
323
|
+
stakedSuiId: "0xstake2",
|
|
324
|
+
status: "Active" as const,
|
|
325
|
+
principal: "1500000000",
|
|
326
|
+
stakeActiveEpoch: "100",
|
|
327
|
+
stakeRequestEpoch: "95",
|
|
328
|
+
},
|
|
329
|
+
],
|
|
330
|
+
},
|
|
331
|
+
];
|
|
332
|
+
mockGetAccountBalances.mockResolvedValue([createAccountBalance()]);
|
|
333
|
+
mockGetOperations.mockResolvedValue([]);
|
|
334
|
+
mockGetStakesRaw.mockResolvedValue(mockStakes);
|
|
335
|
+
|
|
336
|
+
// WHEN
|
|
337
|
+
const shape = await getAccountShape(
|
|
338
|
+
{
|
|
339
|
+
index: 0,
|
|
340
|
+
derivationPath: "44'/784'/0'/0'/0'",
|
|
341
|
+
currency: getCryptoCurrencyById("sui"),
|
|
342
|
+
address: "0x6e143fe0a8ca010a86580dafac44298e5b1b7d73efc345356a59a15f0d7824f0",
|
|
343
|
+
initialAccount,
|
|
344
|
+
derivationMode: "sui",
|
|
345
|
+
},
|
|
346
|
+
{ blacklistedTokenIds: [], paginationConfig: {} },
|
|
347
|
+
);
|
|
348
|
+
|
|
349
|
+
// THEN
|
|
350
|
+
expect(shape.suiResources?.stakes).toEqual(mockStakes);
|
|
351
|
+
expect(shape.suiResources?.stakes?.[0].stakes).toHaveLength(2);
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
it("handles different stake statuses correctly", async () => {
|
|
355
|
+
// GIVEN
|
|
356
|
+
const initialAccount = undefined;
|
|
357
|
+
const mockStakes = [
|
|
358
|
+
{
|
|
359
|
+
validatorAddress: "0xvalidator1",
|
|
360
|
+
stakes: [
|
|
361
|
+
{
|
|
362
|
+
stakedSuiId: "0xactive",
|
|
363
|
+
status: "Active" as const,
|
|
364
|
+
principal: "1000000000",
|
|
365
|
+
stakeActiveEpoch: "100",
|
|
366
|
+
stakeRequestEpoch: "95",
|
|
367
|
+
},
|
|
368
|
+
{
|
|
369
|
+
stakedSuiId: "0xpending",
|
|
370
|
+
status: "Pending" as const,
|
|
371
|
+
principal: "2000000000",
|
|
372
|
+
stakeActiveEpoch: "0",
|
|
373
|
+
stakeRequestEpoch: "100",
|
|
374
|
+
},
|
|
375
|
+
{
|
|
376
|
+
stakedSuiId: "0xunstaked",
|
|
377
|
+
status: "Unstaked" as const,
|
|
378
|
+
principal: "3000000000",
|
|
379
|
+
stakeActiveEpoch: "0",
|
|
380
|
+
stakeRequestEpoch: "0",
|
|
381
|
+
},
|
|
382
|
+
],
|
|
383
|
+
},
|
|
384
|
+
];
|
|
385
|
+
mockGetAccountBalances.mockResolvedValue([createAccountBalance()]);
|
|
386
|
+
mockGetOperations.mockResolvedValue([]);
|
|
387
|
+
mockGetStakesRaw.mockResolvedValue(mockStakes);
|
|
388
|
+
|
|
389
|
+
// WHEN
|
|
390
|
+
const shape = await getAccountShape(
|
|
391
|
+
{
|
|
392
|
+
index: 0,
|
|
393
|
+
derivationPath: "44'/784'/0'/0'/0'",
|
|
394
|
+
currency: getCryptoCurrencyById("sui"),
|
|
395
|
+
address: "0x6e143fe0a8ca010a86580dafac44298e5b1b7d73efc345356a59a15f0d7824f0",
|
|
396
|
+
initialAccount,
|
|
397
|
+
derivationMode: "sui",
|
|
398
|
+
},
|
|
399
|
+
{ blacklistedTokenIds: [], paginationConfig: {} },
|
|
400
|
+
);
|
|
401
|
+
|
|
402
|
+
// THEN
|
|
403
|
+
expect(shape.suiResources?.stakes).toEqual(mockStakes);
|
|
404
|
+
const stakes = shape.suiResources?.stakes?.[0].stakes || [];
|
|
405
|
+
expect(stakes).toHaveLength(3);
|
|
406
|
+
expect(stakes.find(s => s.stakedSuiId === "0xactive")?.status).toBe("Active");
|
|
407
|
+
expect(stakes.find(s => s.stakedSuiId === "0xpending")?.status).toBe("Pending");
|
|
408
|
+
expect(stakes.find(s => s.stakedSuiId === "0xunstaked")?.status).toBe("Unstaked");
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
it("handles getStakesRaw throwing an error gracefully", async () => {
|
|
412
|
+
// GIVEN
|
|
413
|
+
const initialAccount = undefined;
|
|
414
|
+
const error = new Error("Network error");
|
|
415
|
+
mockGetAccountBalances.mockResolvedValue([createAccountBalance()]);
|
|
416
|
+
mockGetOperations.mockResolvedValue([]);
|
|
417
|
+
mockGetStakesRaw.mockRejectedValue(error);
|
|
418
|
+
|
|
419
|
+
// WHEN & THEN
|
|
420
|
+
await expect(
|
|
421
|
+
getAccountShape(
|
|
422
|
+
{
|
|
423
|
+
index: 0,
|
|
424
|
+
derivationPath: "44'/784'/0'/0'/0'",
|
|
425
|
+
currency: getCryptoCurrencyById("sui"),
|
|
426
|
+
address: "0x6e143fe0a8ca010a86580dafac44298e5b1b7d73efc345356a59a15f0d7824f0",
|
|
427
|
+
initialAccount,
|
|
428
|
+
derivationMode: "sui",
|
|
429
|
+
},
|
|
430
|
+
{ blacklistedTokenIds: [], paginationConfig: {} },
|
|
431
|
+
),
|
|
432
|
+
).rejects.toThrow("Network error");
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
it("preserves existing suiResources when initialAccount has stakes", async () => {
|
|
436
|
+
// GIVEN
|
|
437
|
+
const existingStakes = [
|
|
438
|
+
{
|
|
439
|
+
validatorAddress: "0xexistingValidator",
|
|
440
|
+
stakes: [
|
|
441
|
+
{
|
|
442
|
+
stakedSuiId: "0xexistingStake",
|
|
443
|
+
status: "Active" as const,
|
|
444
|
+
principal: "500000000",
|
|
445
|
+
stakeActiveEpoch: "90",
|
|
446
|
+
stakeRequestEpoch: "85",
|
|
447
|
+
},
|
|
448
|
+
],
|
|
449
|
+
},
|
|
450
|
+
];
|
|
451
|
+
const initialAccount = createFixtureAccount({
|
|
452
|
+
suiResources: { stakes: existingStakes },
|
|
453
|
+
});
|
|
454
|
+
const newStakes = [
|
|
455
|
+
{
|
|
456
|
+
validatorAddress: "0xnewValidator",
|
|
457
|
+
stakes: [
|
|
458
|
+
{
|
|
459
|
+
stakedSuiId: "0xnewStake",
|
|
460
|
+
status: "Active" as const,
|
|
461
|
+
principal: "1000000000",
|
|
462
|
+
stakeActiveEpoch: "100",
|
|
463
|
+
stakeRequestEpoch: "95",
|
|
464
|
+
},
|
|
465
|
+
],
|
|
466
|
+
},
|
|
467
|
+
];
|
|
468
|
+
mockGetAccountBalances.mockResolvedValue([createAccountBalance()]);
|
|
469
|
+
mockGetOperations.mockResolvedValue([]);
|
|
470
|
+
mockGetStakesRaw.mockResolvedValue(newStakes);
|
|
471
|
+
|
|
472
|
+
// WHEN
|
|
473
|
+
const shape = await getAccountShape(
|
|
474
|
+
{
|
|
475
|
+
index: 0,
|
|
476
|
+
derivationPath: "44'/784'/0'/0'/0'",
|
|
477
|
+
currency: getCryptoCurrencyById("sui"),
|
|
478
|
+
address: "0x6e143fe0a8ca010a86580dafac44298e5b1b7d73efc345356a59a15f0d7824f0",
|
|
479
|
+
initialAccount,
|
|
480
|
+
derivationMode: "sui",
|
|
481
|
+
},
|
|
482
|
+
{ blacklistedTokenIds: [], paginationConfig: {} },
|
|
483
|
+
);
|
|
484
|
+
|
|
485
|
+
// THEN
|
|
486
|
+
expect(shape.suiResources?.stakes).toEqual(newStakes);
|
|
487
|
+
// The new stakes should replace the old ones, not merge
|
|
488
|
+
expect(shape.suiResources?.stakes).not.toEqual(existingStakes);
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
it("handles large stake amounts correctly", async () => {
|
|
492
|
+
// GIVEN
|
|
493
|
+
const initialAccount = undefined;
|
|
494
|
+
const mockStakes = [
|
|
495
|
+
{
|
|
496
|
+
validatorAddress: "0xvalidator1",
|
|
497
|
+
stakes: [
|
|
498
|
+
{
|
|
499
|
+
stakedSuiId: "0xlargeStake",
|
|
500
|
+
status: "Active" as const,
|
|
501
|
+
principal: "999999999999999999999999999999",
|
|
502
|
+
stakeActiveEpoch: "1000",
|
|
503
|
+
stakeRequestEpoch: "995",
|
|
504
|
+
},
|
|
505
|
+
],
|
|
506
|
+
},
|
|
507
|
+
];
|
|
508
|
+
mockGetAccountBalances.mockResolvedValue([createAccountBalance()]);
|
|
509
|
+
mockGetOperations.mockResolvedValue([]);
|
|
510
|
+
mockGetStakesRaw.mockResolvedValue(mockStakes);
|
|
511
|
+
|
|
512
|
+
// WHEN
|
|
513
|
+
const shape = await getAccountShape(
|
|
514
|
+
{
|
|
515
|
+
index: 0,
|
|
516
|
+
derivationPath: "44'/784'/0'/0'/0'",
|
|
517
|
+
currency: getCryptoCurrencyById("sui"),
|
|
518
|
+
address: "0x6e143fe0a8ca010a86580dafac44298e5b1b7d73efc345356a59a15f0d7824f0",
|
|
519
|
+
initialAccount,
|
|
520
|
+
derivationMode: "sui",
|
|
521
|
+
},
|
|
522
|
+
{ blacklistedTokenIds: [], paginationConfig: {} },
|
|
523
|
+
);
|
|
524
|
+
|
|
525
|
+
// THEN
|
|
526
|
+
expect(shape.suiResources?.stakes).toEqual(mockStakes);
|
|
527
|
+
const stake = shape.suiResources?.stakes?.[0].stakes?.[0];
|
|
528
|
+
expect(stake?.principal).toBe("999999999999999999999999999999");
|
|
529
|
+
expect(stake?.stakeActiveEpoch).toBe("1000");
|
|
530
|
+
expect(stake?.stakeRequestEpoch).toBe("995");
|
|
531
|
+
});
|
|
532
|
+
|
|
533
|
+
it("handles getStakesRaw returning null or undefined", async () => {
|
|
534
|
+
// GIVEN
|
|
535
|
+
const initialAccount = undefined;
|
|
536
|
+
mockGetAccountBalances.mockResolvedValue([createAccountBalance()]);
|
|
537
|
+
mockGetOperations.mockResolvedValue([]);
|
|
538
|
+
mockGetStakesRaw.mockResolvedValue(null);
|
|
539
|
+
|
|
540
|
+
// WHEN
|
|
541
|
+
const shape = await getAccountShape(
|
|
542
|
+
{
|
|
543
|
+
index: 0,
|
|
544
|
+
derivationPath: "44'/784'/0'/0'/0'",
|
|
545
|
+
currency: getCryptoCurrencyById("sui"),
|
|
546
|
+
address: "0x6e143fe0a8ca010a86580dafac44298e5b1b7d73efc345356a59a15f0d7824f0",
|
|
547
|
+
initialAccount,
|
|
548
|
+
derivationMode: "sui",
|
|
549
|
+
},
|
|
550
|
+
{ blacklistedTokenIds: [], paginationConfig: {} },
|
|
551
|
+
);
|
|
552
|
+
|
|
553
|
+
// THEN
|
|
554
|
+
expect(shape.suiResources).toBeDefined();
|
|
555
|
+
expect(shape.suiResources?.stakes).toBeNull();
|
|
556
|
+
});
|
|
557
|
+
});
|
|
176
558
|
});
|
|
177
559
|
|
|
178
560
|
function createAccountBalance(overrides = {}) {
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
} from "@ledgerhq/coin-framework/bridge/jsHelpers";
|
|
12
12
|
import { type Operation } from "@ledgerhq/types-live";
|
|
13
13
|
import { listTokensForCryptoCurrency } from "@ledgerhq/cryptoassets/tokens";
|
|
14
|
-
import { getAccountBalances, getOperations } from "../network";
|
|
14
|
+
import { getAccountBalances, getOperations, getStakesRaw } from "../network";
|
|
15
15
|
import { DEFAULT_COIN_TYPE } from "../network/sdk";
|
|
16
16
|
import { SuiOperationExtra, SuiAccount } from "../types";
|
|
17
17
|
import type { SyncConfig, TokenAccount } from "@ledgerhq/types-live";
|
|
@@ -41,6 +41,7 @@ export const getAccountShape: GetAccountShape<SuiAccount> = async (info, syncCon
|
|
|
41
41
|
});
|
|
42
42
|
|
|
43
43
|
let operations: Operation[] = [];
|
|
44
|
+
const stakes = await getStakesRaw(address);
|
|
44
45
|
|
|
45
46
|
let syncHash = initialAccount?.syncHash ?? latestHash(oldOperations);
|
|
46
47
|
const newOperations = await getOperations(accountId, address, syncHash);
|
|
@@ -84,7 +85,9 @@ export const getAccountShape: GetAccountShape<SuiAccount> = async (info, syncCon
|
|
|
84
85
|
operationsCount: mainAccountOperations.length,
|
|
85
86
|
blockHeight: 5,
|
|
86
87
|
subAccounts,
|
|
87
|
-
suiResources: {
|
|
88
|
+
suiResources: {
|
|
89
|
+
stakes,
|
|
90
|
+
},
|
|
88
91
|
operations: mainAccountOperations,
|
|
89
92
|
};
|
|
90
93
|
};
|
|
@@ -118,7 +121,7 @@ async function buildSubAccounts({
|
|
|
118
121
|
const existingAccountByTicker: { [ticker: string]: TokenAccount } = {}; // used for fast lookup
|
|
119
122
|
const existingAccountTickers: string[] = []; // used to keep track of ordering
|
|
120
123
|
|
|
121
|
-
if (initialAccount
|
|
124
|
+
if (initialAccount?.subAccounts) {
|
|
122
125
|
for (const existingSubAccount of initialAccount.subAccounts) {
|
|
123
126
|
if (existingSubAccount.type === "TokenAccount") {
|
|
124
127
|
const { ticker, id } = existingSubAccount.token;
|
package/src/bridge/utils.ts
CHANGED
|
@@ -35,6 +35,22 @@ const calculateMaxSend = (account: SuiAccount, transaction: Transaction): BigNum
|
|
|
35
35
|
return amount.lt(0) ? new BigNumber(0) : amount;
|
|
36
36
|
};
|
|
37
37
|
|
|
38
|
+
/**
|
|
39
|
+
* Calculate the maximum amount that can be delegated after deducting fees and reserving gas.
|
|
40
|
+
*
|
|
41
|
+
* @param {SuiAccount} account - The account from which the amount is being delegated.
|
|
42
|
+
* @param {Transaction} transaction - The transaction details including fees.
|
|
43
|
+
* @returns {BigNumber} - The maximum amount that can be delegated, or 0 if insufficient balance.
|
|
44
|
+
*/
|
|
45
|
+
const calculateMaxDelegate = (account: SuiAccount, transaction: Transaction): BigNumber => {
|
|
46
|
+
// Reserve 0.1 SUI for future gas fees as recommended for delegation
|
|
47
|
+
const ONE_SUI = new BigNumber("1000000000"); // 1 SUI in MIST
|
|
48
|
+
const gasReserve = ONE_SUI.div(10); // 0.1 SUI
|
|
49
|
+
|
|
50
|
+
const amount = account.spendableBalance.minus(transaction.fees || 0).minus(gasReserve);
|
|
51
|
+
return amount.lt(0) ? new BigNumber(0) : amount;
|
|
52
|
+
};
|
|
53
|
+
|
|
38
54
|
/**
|
|
39
55
|
* Calculates the amount to be sent in a transaction based on the account's balance and transaction details.
|
|
40
56
|
*
|
|
@@ -61,6 +77,14 @@ export const calculateAmount = ({
|
|
|
61
77
|
amount =
|
|
62
78
|
findSubAccountById(account, transaction.subAccountId!)?.spendableBalance ??
|
|
63
79
|
new BigNumber(0);
|
|
80
|
+
break;
|
|
81
|
+
case "delegate":
|
|
82
|
+
amount = calculateMaxDelegate(account, transaction);
|
|
83
|
+
break;
|
|
84
|
+
case "undelegate":
|
|
85
|
+
// For undelegate, use the full staked amount (handled elsewhere)
|
|
86
|
+
amount = transaction.amount;
|
|
87
|
+
break;
|
|
64
88
|
}
|
|
65
89
|
} else if (transaction.amount.gt(MAX_AMOUNT_INPUT)) {
|
|
66
90
|
return new BigNumber(MAX_AMOUNT_INPUT);
|
|
@@ -69,6 +93,6 @@ export const calculateAmount = ({
|
|
|
69
93
|
return amount.lt(0) ? new BigNumber(0) : amount;
|
|
70
94
|
};
|
|
71
95
|
|
|
72
|
-
export const assertUnreachable = (_:
|
|
96
|
+
export const assertUnreachable = (_: unknown): never => {
|
|
73
97
|
throw new Error("unreachable assertion failed");
|
|
74
98
|
};
|
package/src/constants.ts
ADDED
package/src/errors.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { createCustomErrorClass } from "@ledgerhq/errors";
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
* One SUI is minimal to stake.
|
|
5
|
+
*/
|
|
6
|
+
export const OneSuiMinForStake = createCustomErrorClass("OneSuiMinForStake");
|
|
7
|
+
|
|
8
|
+
/*
|
|
9
|
+
* One SUI is minimal for partial unstake.
|
|
10
|
+
*/
|
|
11
|
+
export const OneSuiMinForUnstake = createCustomErrorClass("OneSuiMinForUnstake");
|
|
12
|
+
|
|
13
|
+
/*
|
|
14
|
+
* One SUI is minimal to be left when partial unstake.
|
|
15
|
+
*/
|
|
16
|
+
export const OneSuiMinForUnstakeToBeLeft = createCustomErrorClass("OneSuiMinForUnstakeToBeLeft");
|
|
17
|
+
|
|
18
|
+
/*
|
|
19
|
+
* At least 0.1 SUI to unstake
|
|
20
|
+
*/
|
|
21
|
+
export const SomeSuiForUnstake = createCustomErrorClass("SomeSuiForUnstake");
|
|
@@ -4,21 +4,17 @@ import type { SuiTransactionMode, CoreTransaction } from "../types";
|
|
|
4
4
|
import suiAPI from "../network";
|
|
5
5
|
import { DEFAULT_COIN_TYPE } from "../network/sdk";
|
|
6
6
|
|
|
7
|
-
export type CreateExtrinsicArg = {
|
|
8
|
-
amount: BigNumber;
|
|
9
|
-
coinType: string;
|
|
10
|
-
mode: SuiTransactionMode;
|
|
11
|
-
recipient: string;
|
|
12
|
-
useAllAmount?: boolean | undefined;
|
|
13
|
-
};
|
|
14
|
-
|
|
15
7
|
export async function craftTransaction({
|
|
16
8
|
amount,
|
|
17
9
|
asset,
|
|
18
10
|
recipient,
|
|
19
11
|
sender,
|
|
20
12
|
type,
|
|
21
|
-
|
|
13
|
+
...extra
|
|
14
|
+
}: TransactionIntent & {
|
|
15
|
+
useAllAmount?: boolean;
|
|
16
|
+
stakedSuiId?: string;
|
|
17
|
+
}): Promise<CoreTransaction> {
|
|
22
18
|
let coinType = DEFAULT_COIN_TYPE;
|
|
23
19
|
if (asset.type === "token" && asset.assetReference) {
|
|
24
20
|
coinType = asset.assetReference;
|
|
@@ -28,6 +24,7 @@ export async function craftTransaction({
|
|
|
28
24
|
coinType,
|
|
29
25
|
mode: type as SuiTransactionMode,
|
|
30
26
|
recipient,
|
|
27
|
+
...extra,
|
|
31
28
|
});
|
|
32
29
|
|
|
33
30
|
return { unsigned };
|
|
@@ -8,13 +8,28 @@ export async function estimateFees({
|
|
|
8
8
|
amount,
|
|
9
9
|
sender,
|
|
10
10
|
asset,
|
|
11
|
+
type,
|
|
11
12
|
}: TransactionIntent): Promise<bigint> {
|
|
12
13
|
let coinType = DEFAULT_COIN_TYPE;
|
|
13
14
|
if (asset.type === "token" && asset.assetReference) {
|
|
14
15
|
coinType = asset.assetReference;
|
|
15
16
|
}
|
|
17
|
+
|
|
18
|
+
let mode: "send" | "delegate" | "undelegate";
|
|
19
|
+
switch (type) {
|
|
20
|
+
case "delegate":
|
|
21
|
+
mode = "delegate";
|
|
22
|
+
break;
|
|
23
|
+
case "undelegate":
|
|
24
|
+
mode = "undelegate";
|
|
25
|
+
break;
|
|
26
|
+
default:
|
|
27
|
+
mode = "send";
|
|
28
|
+
break;
|
|
29
|
+
}
|
|
30
|
+
|
|
16
31
|
const { gasBudget } = await suiAPI.paymentInfo(sender, {
|
|
17
|
-
mode
|
|
32
|
+
mode,
|
|
18
33
|
family: "sui",
|
|
19
34
|
recipient,
|
|
20
35
|
amount: BigNumber(amount.toString()),
|
package/src/logic/index.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { craftTransaction
|
|
1
|
+
export { craftTransaction } from "./craftTransaction";
|
|
2
2
|
export { estimateFees } from "./estimateFees";
|
|
3
3
|
export { broadcast } from "./broadcast";
|
|
4
4
|
export { combine } from "./combine";
|
|
@@ -7,3 +7,4 @@ export { lastBlock } from "./lastBlock";
|
|
|
7
7
|
export { getBlock, getBlockInfo } from "./getBlock";
|
|
8
8
|
export { listOperations } from "./listOperations";
|
|
9
9
|
export { getStakes, getRewards } from "./staking";
|
|
10
|
+
export { canStake } from "./stake";
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { SuiAccount } from "../types";
|
|
2
|
+
import { ONE_SUI } from "../constants";
|
|
3
|
+
|
|
4
|
+
/*
|
|
5
|
+
* Make sure that an account has enough funds to stake, unstake, AND withdraw before staking.
|
|
6
|
+
*/
|
|
7
|
+
export const canStake = (account: SuiAccount): boolean => {
|
|
8
|
+
return account.balance.gte(ONE_SUI);
|
|
9
|
+
};
|
package/src/network/index.ts
CHANGED
|
@@ -4,10 +4,11 @@ import {
|
|
|
4
4
|
getOperations,
|
|
5
5
|
getBlock,
|
|
6
6
|
getBlockInfo,
|
|
7
|
-
|
|
7
|
+
getStakesRaw,
|
|
8
8
|
paymentInfo,
|
|
9
9
|
createTransaction,
|
|
10
10
|
executeTransactionBlock,
|
|
11
|
+
getStakes,
|
|
11
12
|
} from "./sdk";
|
|
12
13
|
|
|
13
14
|
export {
|
|
@@ -16,10 +17,11 @@ export {
|
|
|
16
17
|
getOperations,
|
|
17
18
|
getBlock,
|
|
18
19
|
getBlockInfo,
|
|
19
|
-
|
|
20
|
+
getStakesRaw,
|
|
20
21
|
paymentInfo,
|
|
21
22
|
createTransaction,
|
|
22
23
|
executeTransactionBlock,
|
|
24
|
+
getStakes,
|
|
23
25
|
};
|
|
24
26
|
|
|
25
27
|
export default {
|
|
@@ -28,8 +30,9 @@ export default {
|
|
|
28
30
|
getOperations,
|
|
29
31
|
getBlock,
|
|
30
32
|
getBlockInfo,
|
|
31
|
-
|
|
33
|
+
getStakesRaw,
|
|
32
34
|
paymentInfo,
|
|
33
35
|
createTransaction,
|
|
34
36
|
executeTransactionBlock,
|
|
37
|
+
getStakes,
|
|
35
38
|
};
|