@ledgerhq/coin-hedera 1.15.0 → 1.16.0-nightly.20251210114107

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 (186) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/lib/bridge/buildOptimisticOperation.d.ts.map +1 -1
  3. package/lib/bridge/buildOptimisticOperation.js +33 -0
  4. package/lib/bridge/buildOptimisticOperation.js.map +1 -1
  5. package/lib/bridge/getTransactionStatus.d.ts.map +1 -1
  6. package/lib/bridge/getTransactionStatus.js +54 -0
  7. package/lib/bridge/getTransactionStatus.js.map +1 -1
  8. package/lib/bridge/index.d.ts.map +1 -1
  9. package/lib/bridge/index.js +4 -2
  10. package/lib/bridge/index.js.map +1 -1
  11. package/lib/bridge/prepareTransaction.d.ts.map +1 -1
  12. package/lib/bridge/prepareTransaction.js +16 -0
  13. package/lib/bridge/prepareTransaction.js.map +1 -1
  14. package/lib/bridge/serialization.d.ts.map +1 -1
  15. package/lib/bridge/serialization.js +20 -0
  16. package/lib/bridge/serialization.js.map +1 -1
  17. package/lib/bridge/signOperation.d.ts +4 -4
  18. package/lib/bridge/signOperation.d.ts.map +1 -1
  19. package/lib/bridge/signOperation.js +10 -0
  20. package/lib/bridge/signOperation.js.map +1 -1
  21. package/lib/bridge/synchronisation.d.ts.map +1 -1
  22. package/lib/bridge/synchronisation.js +8 -0
  23. package/lib/bridge/synchronisation.js.map +1 -1
  24. package/lib/constants.d.ts +21 -1
  25. package/lib/constants.d.ts.map +1 -1
  26. package/lib/constants.js +22 -1
  27. package/lib/constants.js.map +1 -1
  28. package/lib/deviceTransactionConfig.d.ts.map +1 -1
  29. package/lib/deviceTransactionConfig.js +30 -0
  30. package/lib/deviceTransactionConfig.js.map +1 -1
  31. package/lib/errors.d.ts +9 -0
  32. package/lib/errors.d.ts.map +1 -1
  33. package/lib/errors.js +4 -1
  34. package/lib/errors.js.map +1 -1
  35. package/lib/logic/craftTransaction.d.ts +2 -2
  36. package/lib/logic/craftTransaction.d.ts.map +1 -1
  37. package/lib/logic/craftTransaction.js +42 -8
  38. package/lib/logic/craftTransaction.js.map +1 -1
  39. package/lib/logic/getBlock.d.ts.map +1 -1
  40. package/lib/logic/getBlock.js +1 -0
  41. package/lib/logic/getBlock.js.map +1 -1
  42. package/lib/logic/listOperations.d.ts.map +1 -1
  43. package/lib/logic/listOperations.js +39 -7
  44. package/lib/logic/listOperations.js.map +1 -1
  45. package/lib/logic/utils.d.ts +61 -3
  46. package/lib/logic/utils.d.ts.map +1 -1
  47. package/lib/logic/utils.js +117 -4
  48. package/lib/logic/utils.js.map +1 -1
  49. package/lib/network/api.d.ts +3 -1
  50. package/lib/network/api.d.ts.map +1 -1
  51. package/lib/network/api.js +19 -0
  52. package/lib/network/api.js.map +1 -1
  53. package/lib/preload-data.d.ts +7 -0
  54. package/lib/preload-data.d.ts.map +1 -0
  55. package/lib/preload-data.js +37 -0
  56. package/lib/preload-data.js.map +1 -0
  57. package/lib/preload.d.ts +8 -0
  58. package/lib/preload.d.ts.map +1 -0
  59. package/lib/preload.js +76 -0
  60. package/lib/preload.js.map +1 -0
  61. package/lib/test/fixtures/account.fixture.d.ts +1 -1
  62. package/lib/test/fixtures/account.fixture.d.ts.map +1 -1
  63. package/lib/test/fixtures/account.fixture.js +2 -0
  64. package/lib/test/fixtures/account.fixture.js.map +1 -1
  65. package/lib/transaction.d.ts.map +1 -1
  66. package/lib/transaction.js +34 -0
  67. package/lib/transaction.js.map +1 -1
  68. package/lib/types/alpaca.d.ts +3 -0
  69. package/lib/types/alpaca.d.ts.map +1 -1
  70. package/lib/types/bridge.d.ts +87 -3
  71. package/lib/types/bridge.d.ts.map +1 -1
  72. package/lib/types/logic.d.ts +5 -1
  73. package/lib/types/logic.d.ts.map +1 -1
  74. package/lib/types/mirror.d.ts +19 -0
  75. package/lib/types/mirror.d.ts.map +1 -1
  76. package/lib-es/bridge/buildOptimisticOperation.d.ts.map +1 -1
  77. package/lib-es/bridge/buildOptimisticOperation.js +34 -1
  78. package/lib-es/bridge/buildOptimisticOperation.js.map +1 -1
  79. package/lib-es/bridge/getTransactionStatus.d.ts.map +1 -1
  80. package/lib-es/bridge/getTransactionStatus.js +57 -3
  81. package/lib-es/bridge/getTransactionStatus.js.map +1 -1
  82. package/lib-es/bridge/index.d.ts.map +1 -1
  83. package/lib-es/bridge/index.js +4 -2
  84. package/lib-es/bridge/index.js.map +1 -1
  85. package/lib-es/bridge/prepareTransaction.d.ts.map +1 -1
  86. package/lib-es/bridge/prepareTransaction.js +15 -2
  87. package/lib-es/bridge/prepareTransaction.js.map +1 -1
  88. package/lib-es/bridge/serialization.d.ts.map +1 -1
  89. package/lib-es/bridge/serialization.js +17 -0
  90. package/lib-es/bridge/serialization.js.map +1 -1
  91. package/lib-es/bridge/signOperation.d.ts +4 -4
  92. package/lib-es/bridge/signOperation.d.ts.map +1 -1
  93. package/lib-es/bridge/signOperation.js +11 -1
  94. package/lib-es/bridge/signOperation.js.map +1 -1
  95. package/lib-es/bridge/synchronisation.d.ts.map +1 -1
  96. package/lib-es/bridge/synchronisation.js +8 -0
  97. package/lib-es/bridge/synchronisation.js.map +1 -1
  98. package/lib-es/constants.d.ts +21 -1
  99. package/lib-es/constants.d.ts.map +1 -1
  100. package/lib-es/constants.js +21 -0
  101. package/lib-es/constants.js.map +1 -1
  102. package/lib-es/deviceTransactionConfig.d.ts.map +1 -1
  103. package/lib-es/deviceTransactionConfig.js +31 -1
  104. package/lib-es/deviceTransactionConfig.js.map +1 -1
  105. package/lib-es/errors.d.ts +9 -0
  106. package/lib-es/errors.d.ts.map +1 -1
  107. package/lib-es/errors.js +3 -0
  108. package/lib-es/errors.js.map +1 -1
  109. package/lib-es/logic/craftTransaction.d.ts +2 -2
  110. package/lib-es/logic/craftTransaction.d.ts.map +1 -1
  111. package/lib-es/logic/craftTransaction.js +44 -10
  112. package/lib-es/logic/craftTransaction.js.map +1 -1
  113. package/lib-es/logic/getBlock.d.ts.map +1 -1
  114. package/lib-es/logic/getBlock.js +2 -1
  115. package/lib-es/logic/getBlock.js.map +1 -1
  116. package/lib-es/logic/listOperations.d.ts.map +1 -1
  117. package/lib-es/logic/listOperations.js +39 -7
  118. package/lib-es/logic/listOperations.js.map +1 -1
  119. package/lib-es/logic/utils.d.ts +61 -3
  120. package/lib-es/logic/utils.d.ts.map +1 -1
  121. package/lib-es/logic/utils.js +107 -4
  122. package/lib-es/logic/utils.js.map +1 -1
  123. package/lib-es/network/api.d.ts +3 -1
  124. package/lib-es/network/api.d.ts.map +1 -1
  125. package/lib-es/network/api.js +19 -0
  126. package/lib-es/network/api.js.map +1 -1
  127. package/lib-es/preload-data.d.ts +7 -0
  128. package/lib-es/preload-data.d.ts.map +1 -0
  129. package/lib-es/preload-data.js +31 -0
  130. package/lib-es/preload-data.js.map +1 -0
  131. package/lib-es/preload.d.ts +8 -0
  132. package/lib-es/preload.d.ts.map +1 -0
  133. package/lib-es/preload.js +67 -0
  134. package/lib-es/preload.js.map +1 -0
  135. package/lib-es/test/fixtures/account.fixture.d.ts +1 -1
  136. package/lib-es/test/fixtures/account.fixture.d.ts.map +1 -1
  137. package/lib-es/test/fixtures/account.fixture.js +2 -0
  138. package/lib-es/test/fixtures/account.fixture.js.map +1 -1
  139. package/lib-es/transaction.d.ts.map +1 -1
  140. package/lib-es/transaction.js +34 -0
  141. package/lib-es/transaction.js.map +1 -1
  142. package/lib-es/types/alpaca.d.ts +3 -0
  143. package/lib-es/types/alpaca.d.ts.map +1 -1
  144. package/lib-es/types/bridge.d.ts +87 -3
  145. package/lib-es/types/bridge.d.ts.map +1 -1
  146. package/lib-es/types/logic.d.ts +5 -1
  147. package/lib-es/types/logic.d.ts.map +1 -1
  148. package/lib-es/types/mirror.d.ts +19 -0
  149. package/lib-es/types/mirror.d.ts.map +1 -1
  150. package/package.json +13 -12
  151. package/src/api/index.integ.test.ts +11 -1
  152. package/src/bridge/buildOptimisticOperation.integration.test.ts +159 -4
  153. package/src/bridge/buildOptimisticOperation.ts +50 -2
  154. package/src/bridge/getTransactionStatus.test.ts +191 -21
  155. package/src/bridge/getTransactionStatus.ts +75 -1
  156. package/src/bridge/index.ts +4 -2
  157. package/src/bridge/prepareTransaction.test.ts +112 -8
  158. package/src/bridge/prepareTransaction.ts +20 -2
  159. package/src/bridge/serialization.ts +17 -0
  160. package/src/bridge/signOperation.ts +15 -5
  161. package/src/bridge/synchronisation.ts +9 -0
  162. package/src/bridge/utils.integration.test.ts +3 -10
  163. package/src/constants.ts +22 -0
  164. package/src/deviceTransactionConfig.test.ts +315 -0
  165. package/src/deviceTransactionConfig.ts +37 -1
  166. package/src/errors.ts +7 -0
  167. package/src/logic/craftTransaction.ts +70 -13
  168. package/src/logic/getBalance.test.ts +15 -16
  169. package/src/logic/getBlock.ts +2 -1
  170. package/src/logic/listOperations.test.ts +86 -29
  171. package/src/logic/listOperations.ts +46 -6
  172. package/src/logic/utils.test.ts +362 -8
  173. package/src/logic/utils.ts +158 -4
  174. package/src/network/api.test.ts +58 -6
  175. package/src/network/api.ts +25 -0
  176. package/src/network/thirdweb.test.ts +2 -2
  177. package/src/network/utils.test.ts +4 -6
  178. package/src/preload-data.ts +38 -0
  179. package/src/preload.test.ts +64 -0
  180. package/src/preload.ts +80 -0
  181. package/src/test/fixtures/account.fixture.ts +3 -1
  182. package/src/transaction.ts +42 -0
  183. package/src/types/alpaca.ts +4 -0
  184. package/src/types/bridge.ts +108 -3
  185. package/src/types/logic.ts +6 -1
  186. package/src/types/mirror.ts +21 -0
@@ -72,7 +72,6 @@ describe("getAccountTransactions", () => {
72
72
  fetchAllPages: true,
73
73
  });
74
74
 
75
- expect(result.transactions).toHaveLength(3);
76
75
  expect(result.transactions.map(tx => tx.consensus_timestamp)).toEqual(["1", "3", "4"]);
77
76
  expect(result.nextCursor).toBeNull();
78
77
  expect(mockedNetwork).toHaveBeenCalledTimes(5);
@@ -118,7 +117,6 @@ describe("getAccountTransactions", () => {
118
117
  fetchAllPages: false,
119
118
  });
120
119
 
121
- expect(result.transactions).toHaveLength(2);
122
120
  expect(result.transactions.map(tx => tx.consensus_timestamp)).toEqual(["1", "3"]);
123
121
  expect(result.nextCursor).toBe("3");
124
122
  expect(mockedNetwork).toHaveBeenCalledTimes(3);
@@ -267,6 +265,7 @@ describe("findTransactionByContractCall", () => {
267
265
  const mockedResults: HederaMirrorTransaction = {
268
266
  transfers: [],
269
267
  token_transfers: [],
268
+ staking_reward_transfers: [],
270
269
  charged_tx_fee: 100,
271
270
  transaction_id: "xxxxxxxxxxxxxx",
272
271
  transaction_hash: "xxxxxxxxxxxxx",
@@ -300,6 +299,7 @@ describe("findTransactionByContractCall", () => {
300
299
  {
301
300
  transfers: [],
302
301
  token_transfers: [],
302
+ staking_reward_transfers: [],
303
303
  charged_tx_fee: 100,
304
304
  transaction_hash: "xxxxxxxxxxxxx",
305
305
  consensus_timestamp: "xxxxxxxxxxxxx",
@@ -310,6 +310,7 @@ describe("findTransactionByContractCall", () => {
310
310
  {
311
311
  transfers: [],
312
312
  token_transfers: [],
313
+ staking_reward_transfers: [],
313
314
  charged_tx_fee: 100,
314
315
  transaction_hash: "xxxxxxxxxxxxx",
315
316
  consensus_timestamp: "xxxxxxxxxxxxx",
@@ -317,7 +318,7 @@ describe("findTransactionByContractCall", () => {
317
318
  entity_id: "0.0.1111",
318
319
  name: "CONTRACTCALL",
319
320
  },
320
- ],
321
+ ] satisfies Partial<HederaMirrorTransaction>[],
321
322
  }),
322
323
  );
323
324
 
@@ -450,7 +451,6 @@ describe("getTransactionsByTimestampRange", () => {
450
451
  "2000.000000000",
451
452
  );
452
453
 
453
- expect(result).toHaveLength(2);
454
454
  expect(result.map(tx => tx.consensus_timestamp)).toEqual(["1500.123456789", "1750.987654321"]);
455
455
  expect(mockedNetwork).toHaveBeenCalledTimes(1);
456
456
  });
@@ -481,7 +481,6 @@ describe("getTransactionsByTimestampRange", () => {
481
481
  "2000.000000000",
482
482
  );
483
483
 
484
- expect(result).toHaveLength(3);
485
484
  expect(result.map(tx => tx.consensus_timestamp)).toEqual([
486
485
  "1100.000000000",
487
486
  "1200.000000000",
@@ -516,8 +515,61 @@ describe("getTransactionsByTimestampRange", () => {
516
515
  "2000.000000000",
517
516
  );
518
517
 
519
- expect(result).toHaveLength(2);
520
518
  expect(result.map(tx => tx.consensus_timestamp)).toEqual(["1100.000000000", "1300.000000000"]);
521
519
  expect(mockedNetwork).toHaveBeenCalledTimes(3);
522
520
  });
523
521
  });
522
+
523
+ describe("getNodes", () => {
524
+ beforeEach(() => {
525
+ jest.resetAllMocks();
526
+ });
527
+
528
+ it("should return all nodes if only one page is needed", async () => {
529
+ mockedNetwork.mockResolvedValueOnce(
530
+ getMockResponse({
531
+ nodes: [
532
+ { node_id: 0, node_account_id: "0.0.3" },
533
+ { node_id: 1, node_account_id: "0.0.4" },
534
+ ],
535
+ links: { next: null },
536
+ }),
537
+ );
538
+
539
+ const result = await apiClient.getNodes();
540
+ const requestUrl = mockedNetwork.mock.calls[0][0].url;
541
+
542
+ expect(result.map(n => n.node_id)).toEqual([0, 1]);
543
+ expect(requestUrl).toContain("/api/v1/network/nodes");
544
+ expect(requestUrl).toContain("limit=100");
545
+ expect(requestUrl).toContain("order=desc");
546
+ expect(mockedNetwork).toHaveBeenCalledTimes(1);
547
+ });
548
+
549
+ it("should keep fetching if links.next is present and new nodes are returned", async () => {
550
+ mockedNetwork
551
+ .mockResolvedValueOnce(
552
+ getMockResponse({
553
+ nodes: [{ node_id: 0, node_account_id: "0.0.3" }],
554
+ links: { next: "/next-1" },
555
+ }),
556
+ )
557
+ .mockResolvedValueOnce(
558
+ getMockResponse({
559
+ nodes: [{ node_id: 1, node_account_id: "0.0.4" }],
560
+ links: { next: "/next-2" },
561
+ }),
562
+ )
563
+ .mockResolvedValueOnce(
564
+ getMockResponse({
565
+ nodes: [{ node_id: 2, node_account_id: "0.0.5" }],
566
+ links: { next: null },
567
+ }),
568
+ );
569
+
570
+ const result = await apiClient.getNodes();
571
+
572
+ expect(result.map(n => n.node_id)).toEqual([0, 1, 2]);
573
+ expect(mockedNetwork).toHaveBeenCalledTimes(3);
574
+ });
575
+ });
@@ -16,6 +16,8 @@ import type {
16
16
  HederaMirrorContractCallResult,
17
17
  HederaMirrorContractCallBalance,
18
18
  HederaMirrorContractCallEstimate,
19
+ HederaMirrorNode,
20
+ HederaMirrorNodesResponse,
19
21
  } from "../types";
20
22
 
21
23
  const API_URL = getEnv("API_HEDERA_MIRROR");
@@ -267,6 +269,28 @@ async function getTransactionsByTimestampRange(
267
269
  return transactions;
268
270
  }
269
271
 
272
+ async function getNodes(): Promise<HederaMirrorNode[]> {
273
+ const nodes: HederaMirrorNode[] = [];
274
+ const params = new URLSearchParams({
275
+ order: "desc",
276
+ limit: "100",
277
+ });
278
+
279
+ let nextPath: string | null = `/api/v1/network/nodes?${params.toString()}`;
280
+
281
+ while (nextPath) {
282
+ const res: LiveNetworkResponse<HederaMirrorNodesResponse> = await network({
283
+ method: "GET",
284
+ url: `${API_URL}${nextPath}`,
285
+ });
286
+ const newNodes = res.data.nodes;
287
+ nodes.push(...newNodes);
288
+ nextPath = res.data.links.next;
289
+ }
290
+
291
+ return nodes;
292
+ }
293
+
270
294
  export const apiClient = {
271
295
  getAccountsForPublicKey,
272
296
  getAccount,
@@ -279,4 +303,5 @@ export const apiClient = {
279
303
  getERC20Balance,
280
304
  estimateContractCallGas,
281
305
  getTransactionsByTimestampRange,
306
+ getNodes,
282
307
  };
@@ -84,7 +84,7 @@ describe("fetchERC20Transactions", () => {
84
84
  };
85
85
  const result = await thirdwebClient.fetchERC20Transactions(mockedERC20TokenAddress1, params);
86
86
 
87
- expect(result).toHaveLength(0);
87
+ expect(result).toEqual([]);
88
88
  expect(mockedNetwork).toHaveBeenCalledTimes(1);
89
89
  });
90
90
 
@@ -139,7 +139,7 @@ describe("getERC20TransactionsForAccount", () => {
139
139
  since: null,
140
140
  });
141
141
 
142
- expect(result).toHaveLength(0);
142
+ expect(result).toEqual([]);
143
143
  });
144
144
 
145
145
  it("should return exactly 2 transactions (out & in)", async () => {
@@ -207,8 +207,7 @@ describe("network utils", () => {
207
207
  erc20Token.contractAddress,
208
208
  );
209
209
 
210
- expect(res).toHaveLength(mockedSupportedTokenIds.length);
211
- expect(res).toMatchObject(mockedResponse);
210
+ expect(res).toEqual(mockedResponse);
212
211
  });
213
212
 
214
213
  it("returns empty array when there are no supported ERC20 tokens", async () => {
@@ -265,8 +264,7 @@ describe("network utils", () => {
265
264
 
266
265
  const result = await getERC20Operations([mockThirdwebTransaction]);
267
266
 
268
- expect(result).toHaveLength(1);
269
- expect(result).toMatchObject([
267
+ expect(result).toEqual([
270
268
  {
271
269
  thirdwebTransaction: mockThirdwebTransaction,
272
270
  mirrorTransaction: mockMirrorTransaction,
@@ -299,7 +297,7 @@ describe("network utils", () => {
299
297
 
300
298
  const result = await getERC20Operations(mockThirdwebTransactions);
301
299
 
302
- expect(result).toHaveLength(0);
300
+ expect(result).toEqual([]);
303
301
  expect(apiClient.getContractCallResult).not.toHaveBeenCalled();
304
302
  expect(apiClient.findTransactionByContractCall).not.toHaveBeenCalled();
305
303
  });
@@ -328,7 +326,7 @@ describe("network utils", () => {
328
326
 
329
327
  const result = await getERC20Operations([mockThirdwebTransactions]);
330
328
 
331
- expect(result).toHaveLength(0);
329
+ expect(result).toEqual([]);
332
330
  });
333
331
  });
334
332
 
@@ -0,0 +1,38 @@
1
+ import type { CryptoCurrency } from "@ledgerhq/types-cryptoassets";
2
+ import { BehaviorSubject, Observable } from "rxjs";
3
+ import type { HederaPreloadData } from "./types";
4
+
5
+ const initialData: HederaPreloadData = {
6
+ validators: [],
7
+ };
8
+
9
+ const dataByCurrency = new Map<string, HederaPreloadData>([["hedera", initialData]]);
10
+
11
+ const dataUpdatesByCurrency = new Map([
12
+ ["hedera", new BehaviorSubject<HederaPreloadData>(initialData)],
13
+ ]);
14
+
15
+ export function setHederaPreloadData(data: HederaPreloadData, currency: CryptoCurrency): void {
16
+ dataByCurrency.set(currency.id, data ?? initialData);
17
+ const subject = dataUpdatesByCurrency.get(currency.id);
18
+ if (subject === undefined) {
19
+ throw new Error(`unsupported currency ${currency.id}`);
20
+ }
21
+ subject.next(data);
22
+ }
23
+
24
+ export function getHederaPreloadData(currency: CryptoCurrency): Observable<HederaPreloadData> {
25
+ const subject = dataUpdatesByCurrency.get(currency.id);
26
+ if (subject === undefined) {
27
+ throw new Error(`unsupported currency ${currency.id}`);
28
+ }
29
+ return subject.asObservable();
30
+ }
31
+
32
+ export function getCurrentHederaPreloadData(currency: CryptoCurrency): HederaPreloadData {
33
+ const data = dataByCurrency.get(currency.id);
34
+ if (data === undefined) {
35
+ throw new Error(`unsupported currency ${currency.id}`);
36
+ }
37
+ return data;
38
+ }
@@ -0,0 +1,64 @@
1
+ import BigNumber from "bignumber.js";
2
+ import { getCryptoCurrencyById } from "@ledgerhq/cryptoassets";
3
+ import { hydrate } from "./preload";
4
+ import { setHederaPreloadData } from "./preload-data";
5
+
6
+ jest.mock("./preload-data", () => ({
7
+ setHederaPreloadData: jest.fn(),
8
+ }));
9
+
10
+ describe("hydrate", () => {
11
+ const currency = getCryptoCurrencyById("hedera");
12
+
13
+ beforeEach(() => {
14
+ jest.clearAllMocks();
15
+ });
16
+
17
+ test.each([undefined, null, {}, []])(
18
+ "should hydrate empty validators list if data is corrupted (%p value)",
19
+ value => {
20
+ hydrate(value, currency);
21
+ expect(setHederaPreloadData).toHaveBeenCalledWith({ validators: [] }, currency);
22
+ },
23
+ );
24
+
25
+ it("should hydrate valid validators list", () => {
26
+ hydrate(
27
+ {
28
+ validators: [
29
+ {
30
+ nodeId: 1,
31
+ address: "0.0.1",
32
+ addressChecksum: "abcde",
33
+ name: "Ledger",
34
+ minStake: "1",
35
+ maxStake: "10",
36
+ activeStake: "5",
37
+ activeStakePercentage: "50",
38
+ overstaked: false,
39
+ },
40
+ ],
41
+ },
42
+ currency,
43
+ );
44
+
45
+ expect(setHederaPreloadData).toHaveBeenCalledWith(
46
+ {
47
+ validators: [
48
+ {
49
+ nodeId: 1,
50
+ address: "0.0.1",
51
+ addressChecksum: "abcde",
52
+ name: "Ledger",
53
+ minStake: new BigNumber(1),
54
+ maxStake: new BigNumber(10),
55
+ activeStake: new BigNumber(5),
56
+ activeStakePercentage: new BigNumber(50),
57
+ overstaked: false,
58
+ },
59
+ ],
60
+ },
61
+ currency,
62
+ );
63
+ });
64
+ });
package/src/preload.ts ADDED
@@ -0,0 +1,80 @@
1
+ import BigNumber from "bignumber.js";
2
+ import { log } from "@ledgerhq/logs";
3
+ import type { CryptoCurrency } from "@ledgerhq/types-cryptoassets";
4
+ import { extractCompanyFromNodeDescription, getChecksum, sortValidators } from "./logic/utils";
5
+ import { apiClient } from "./network/api";
6
+ import { setHederaPreloadData } from "./preload-data";
7
+ import type { HederaPreloadData, HederaValidator, HederaValidatorRaw } from "./types";
8
+
9
+ export const getPreloadStrategy = () => ({
10
+ preloadMaxAge: 15 * 60 * 1000, // 15 minutes
11
+ });
12
+
13
+ export async function preload(currency: CryptoCurrency): Promise<HederaPreloadData> {
14
+ log("hedera/preload", "preloading hedera data...");
15
+ const nodes = await apiClient.getNodes();
16
+
17
+ const validators: HederaValidator[] = nodes.map(mirrorNode => {
18
+ const minStake = new BigNumber(mirrorNode.min_stake);
19
+ const maxStake = new BigNumber(mirrorNode.max_stake);
20
+ const activeStake = new BigNumber(mirrorNode.stake_rewarded);
21
+ const activeStakePercentage = maxStake.gt(0)
22
+ ? activeStake.dividedBy(maxStake).multipliedBy(100).dp(0, BigNumber.ROUND_CEIL)
23
+ : new BigNumber(0);
24
+
25
+ return {
26
+ nodeId: mirrorNode.node_id,
27
+ address: mirrorNode.node_account_id,
28
+ addressChecksum: getChecksum(mirrorNode.node_account_id),
29
+ name: extractCompanyFromNodeDescription(mirrorNode.description),
30
+ minStake,
31
+ maxStake,
32
+ activeStake,
33
+ activeStakePercentage,
34
+ overstaked: activeStake.gte(maxStake),
35
+ };
36
+ });
37
+
38
+ const sortedValidators = sortValidators(validators);
39
+ const data: HederaPreloadData = {
40
+ validators: sortedValidators,
41
+ };
42
+
43
+ setHederaPreloadData(data, currency);
44
+
45
+ return data;
46
+ }
47
+
48
+ function mapRawValidatorToValidator(validatorRaw: HederaValidatorRaw): HederaValidator {
49
+ return {
50
+ nodeId: validatorRaw.nodeId,
51
+ address: validatorRaw.address,
52
+ addressChecksum: validatorRaw.addressChecksum,
53
+ name: validatorRaw.name,
54
+ minStake: new BigNumber(validatorRaw.minStake),
55
+ maxStake: new BigNumber(validatorRaw.maxStake),
56
+ activeStake: new BigNumber(validatorRaw.activeStake),
57
+ activeStakePercentage: new BigNumber(validatorRaw.activeStakePercentage),
58
+ overstaked: validatorRaw.overstaked,
59
+ };
60
+ }
61
+
62
+ function fromHydratePreloadData(data: unknown): HederaPreloadData {
63
+ let validators: HederaValidator[] = [];
64
+
65
+ if (data && typeof data === "object" && "validators" in data) {
66
+ if (Array.isArray(data.validators)) {
67
+ validators = data.validators.map(mapRawValidatorToValidator);
68
+ }
69
+ }
70
+
71
+ return {
72
+ validators,
73
+ };
74
+ }
75
+
76
+ export function hydrate(data: unknown, currency: CryptoCurrency): void {
77
+ const hydrated = fromHydratePreloadData(data);
78
+ log("hedera/preload", `hydrated ${hydrated.validators.length} hedera validators`);
79
+ setHederaPreloadData(hydrated, currency);
80
+ }
@@ -5,9 +5,9 @@ import type {
5
5
  HederaResources,
6
6
  HederaResourcesRaw,
7
7
  } from "../../types";
8
+ import type { TokenCurrency } from "@ledgerhq/types-cryptoassets";
8
9
  import type { TokenAccount } from "@ledgerhq/types-live";
9
10
  import { getMockedCurrency, getMockedHTSTokenCurrency } from "./currency.fixture";
10
- import { TokenCurrency } from "@ledgerhq/types-cryptoassets";
11
11
 
12
12
  const defaultMockedCurrency = getMockedCurrency();
13
13
  const defaultMockedTokenCurrency = getMockedHTSTokenCurrency();
@@ -19,11 +19,13 @@ const defaultTokenBalance = new BigNumber(10);
19
19
  export const mockHederaResources: HederaResources = {
20
20
  maxAutomaticTokenAssociations: 0,
21
21
  isAutoTokenAssociationEnabled: false,
22
+ delegation: null,
22
23
  };
23
24
 
24
25
  export const mockHederaResourcesRaw: HederaResourcesRaw = {
25
26
  maxAutomaticTokenAssociations: 0,
26
27
  isAutoTokenAssociationEnabled: false,
28
+ delegation: null,
27
29
  };
28
30
 
29
31
  /**
@@ -40,6 +40,27 @@ export function fromTransactionRaw(tr: TransactionRaw): Transaction {
40
40
  };
41
41
  }
42
42
 
43
+ if (
44
+ tr.mode === HEDERA_TRANSACTION_MODES.Delegate ||
45
+ tr.mode === HEDERA_TRANSACTION_MODES.Undelegate ||
46
+ tr.mode === HEDERA_TRANSACTION_MODES.Redelegate
47
+ ) {
48
+ return {
49
+ ...commonGeneric,
50
+ ...commonHedera,
51
+ mode: tr.mode,
52
+ properties: tr.properties,
53
+ };
54
+ }
55
+
56
+ if (tr.mode === HEDERA_TRANSACTION_MODES.ClaimRewards) {
57
+ return {
58
+ ...commonGeneric,
59
+ ...commonHedera,
60
+ mode: tr.mode,
61
+ };
62
+ }
63
+
43
64
  return {
44
65
  ...commonGeneric,
45
66
  ...commonHedera,
@@ -67,6 +88,27 @@ export function toTransactionRaw(t: Transaction): TransactionRaw {
67
88
  };
68
89
  }
69
90
 
91
+ if (
92
+ t.mode === HEDERA_TRANSACTION_MODES.Delegate ||
93
+ t.mode === HEDERA_TRANSACTION_MODES.Undelegate ||
94
+ t.mode === HEDERA_TRANSACTION_MODES.Redelegate
95
+ ) {
96
+ return {
97
+ ...commonGeneric,
98
+ ...commonHedera,
99
+ mode: t.mode,
100
+ properties: t.properties,
101
+ };
102
+ }
103
+
104
+ if (t.mode === HEDERA_TRANSACTION_MODES.ClaimRewards) {
105
+ return {
106
+ ...commonGeneric,
107
+ ...commonHedera,
108
+ mode: t.mode,
109
+ };
110
+ }
111
+
70
112
  return {
71
113
  ...commonGeneric,
72
114
  ...commonHedera,
@@ -7,4 +7,8 @@ export type HederaTxData =
7
7
  | {
8
8
  type: "erc20";
9
9
  gasLimit: bigint;
10
+ }
11
+ | {
12
+ type: "staking";
13
+ stakingNodeId: number | null | undefined;
10
14
  };
@@ -9,7 +9,7 @@ import type {
9
9
  TransactionStatusCommon,
10
10
  TransactionStatusCommonRaw,
11
11
  } from "@ledgerhq/types-live";
12
- import { HEDERA_TRANSACTION_MODES } from "../constants";
12
+ import { HEDERA_DELEGATION_STATUS, HEDERA_TRANSACTION_MODES } from "../constants";
13
13
 
14
14
  export type NetworkInfo = {
15
15
  family: "hedera";
@@ -37,6 +37,28 @@ export type Transaction = TransactionCommon & {
37
37
  token: TokenCurrency;
38
38
  };
39
39
  }
40
+ | {
41
+ mode: HEDERA_TRANSACTION_MODES.Delegate;
42
+ properties: {
43
+ stakingNodeId: number | null;
44
+ };
45
+ }
46
+ | {
47
+ mode: HEDERA_TRANSACTION_MODES.Undelegate;
48
+ properties: {
49
+ stakingNodeId: number | null;
50
+ };
51
+ }
52
+ | {
53
+ mode: HEDERA_TRANSACTION_MODES.Redelegate;
54
+ properties: {
55
+ stakingNodeId: number | null;
56
+ };
57
+ }
58
+ | {
59
+ mode: HEDERA_TRANSACTION_MODES.ClaimRewards;
60
+ properties?: never;
61
+ }
40
62
  );
41
63
 
42
64
  export type TransactionRaw = TransactionCommonRaw & {
@@ -57,25 +79,77 @@ export type TransactionRaw = TransactionCommonRaw & {
57
79
  token: TokenCurrency;
58
80
  };
59
81
  }
82
+ | {
83
+ mode: HEDERA_TRANSACTION_MODES.Delegate;
84
+ properties: {
85
+ stakingNodeId: number | null;
86
+ };
87
+ }
88
+ | {
89
+ mode: HEDERA_TRANSACTION_MODES.Undelegate;
90
+ properties: {
91
+ stakingNodeId: number | null;
92
+ };
93
+ }
94
+ | {
95
+ mode: HEDERA_TRANSACTION_MODES.Redelegate;
96
+ properties: {
97
+ stakingNodeId: number | null;
98
+ };
99
+ }
100
+ | {
101
+ mode: HEDERA_TRANSACTION_MODES.ClaimRewards;
102
+ properties?: never;
103
+ }
60
104
  );
61
105
 
106
+ export type TransactionStatus = TransactionStatusCommon;
107
+
108
+ export type TransactionStatusRaw = TransactionStatusCommonRaw;
109
+
62
110
  export type TransactionTokenAssociate = Extract<
63
111
  Transaction,
64
112
  { mode: HEDERA_TRANSACTION_MODES.TokenAssociate }
65
113
  >;
66
114
 
67
- export type TransactionStatus = TransactionStatusCommon;
115
+ export type TransactionStaking = Extract<
116
+ Transaction,
117
+ {
118
+ mode:
119
+ | HEDERA_TRANSACTION_MODES.Delegate
120
+ | HEDERA_TRANSACTION_MODES.Undelegate
121
+ | HEDERA_TRANSACTION_MODES.Redelegate
122
+ | HEDERA_TRANSACTION_MODES.ClaimRewards;
123
+ }
124
+ >;
68
125
 
69
- export type TransactionStatusRaw = TransactionStatusCommonRaw;
126
+ export interface HederaDelegation {
127
+ nodeId: number;
128
+ delegated: BigNumber;
129
+ pendingReward: BigNumber;
130
+ }
131
+
132
+ export interface HederaEnrichedDelegation extends HederaDelegation {
133
+ status: HEDERA_DELEGATION_STATUS;
134
+ validator: HederaValidator;
135
+ }
136
+
137
+ interface HederaDelegationRaw {
138
+ nodeId: number;
139
+ delegated: string;
140
+ pendingReward: string;
141
+ }
70
142
 
71
143
  export interface HederaResources {
72
144
  maxAutomaticTokenAssociations: number;
73
145
  isAutoTokenAssociationEnabled: boolean;
146
+ delegation: HederaDelegation | null;
74
147
  }
75
148
 
76
149
  export interface HederaResourcesRaw {
77
150
  maxAutomaticTokenAssociations: number;
78
151
  isAutoTokenAssociationEnabled: boolean;
152
+ delegation: HederaDelegationRaw | null;
79
153
  }
80
154
 
81
155
  export type HederaAccount = Account & {
@@ -95,6 +169,37 @@ export type HederaOperationExtra = {
95
169
  gasLimit?: number;
96
170
  gasUsed?: number;
97
171
  memo?: string | null;
172
+ targetStakingNodeId?: number | null;
173
+ previousStakingNodeId?: number | null;
174
+ };
175
+
176
+ export type HederaValidator = {
177
+ nodeId: number;
178
+ minStake: BigNumber;
179
+ maxStake: BigNumber;
180
+ activeStake: BigNumber;
181
+ activeStakePercentage: BigNumber;
182
+ address: string;
183
+ addressChecksum: string | null;
184
+ name: string;
185
+ overstaked: boolean;
186
+ };
187
+
188
+ export type HederaValidatorRaw = {
189
+ nodeId: number;
190
+ minStake: string;
191
+ maxStake: string;
192
+ activeStake: string;
193
+ activeStakePercentage: string;
194
+ address: string;
195
+ addressChecksum: string | null;
196
+ name: string;
197
+ overstaked: boolean;
198
+ };
199
+
200
+ export type HederaPreloadData = {
201
+ validators: HederaValidator[];
202
+ associatedTokenId?: string;
98
203
  };
99
204
 
100
205
  export type HederaOperation = Operation<HederaOperationExtra>;
@@ -13,7 +13,7 @@ export type EstimateFeesParams =
13
13
  operationType: Exclude<HEDERA_OPERATION_TYPES, HEDERA_OPERATION_TYPES.ContractCall>;
14
14
  }
15
15
  | {
16
- operationType: typeof HEDERA_OPERATION_TYPES.ContractCall;
16
+ operationType: HEDERA_OPERATION_TYPES.ContractCall;
17
17
  txIntent: TransactionIntent;
18
18
  };
19
19
 
@@ -42,3 +42,8 @@ export interface ERC20OperationFields {
42
42
  standard: "erc20";
43
43
  hasFailed: false;
44
44
  }
45
+
46
+ export interface OperationDetailsExtraField {
47
+ key: string;
48
+ value: string | number;
49
+ }