@ledgerhq/coin-hedera 1.16.0-nightly.20251211024123 → 1.16.0-nightly.20251213023821

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 (149) hide show
  1. package/CHANGELOG.md +14 -10
  2. package/lib/api/index.d.ts.map +1 -1
  3. package/lib/api/index.js +4 -3
  4. package/lib/api/index.js.map +1 -1
  5. package/lib/bridge/buildOptimisticOperation.d.ts.map +1 -1
  6. package/lib/bridge/buildOptimisticOperation.js +14 -43
  7. package/lib/bridge/buildOptimisticOperation.js.map +1 -1
  8. package/lib/bridge/index.d.ts.map +1 -1
  9. package/lib/bridge/index.js +2 -0
  10. package/lib/bridge/index.js.map +1 -1
  11. package/lib/bridge/validateAddress.d.ts +3 -0
  12. package/lib/bridge/validateAddress.d.ts.map +1 -0
  13. package/lib/bridge/validateAddress.js +10 -0
  14. package/lib/bridge/validateAddress.js.map +1 -0
  15. package/lib/constants.d.ts +11 -6
  16. package/lib/constants.d.ts.map +1 -1
  17. package/lib/constants.js +20 -1
  18. package/lib/constants.js.map +1 -1
  19. package/lib/deviceTransactionConfig.d.ts.map +1 -1
  20. package/lib/deviceTransactionConfig.js +1 -3
  21. package/lib/deviceTransactionConfig.js.map +1 -1
  22. package/lib/logic/getBalance.d.ts.map +1 -1
  23. package/lib/logic/getBalance.js +21 -4
  24. package/lib/logic/getBalance.js.map +1 -1
  25. package/lib/logic/getBlock.d.ts.map +1 -1
  26. package/lib/logic/getBlock.js +39 -2
  27. package/lib/logic/getBlock.js.map +1 -1
  28. package/lib/logic/getValidators.d.ts +3 -0
  29. package/lib/logic/getValidators.d.ts.map +1 -0
  30. package/lib/logic/getValidators.js +24 -0
  31. package/lib/logic/getValidators.js.map +1 -0
  32. package/lib/logic/index.d.ts +1 -0
  33. package/lib/logic/index.d.ts.map +1 -1
  34. package/lib/logic/index.js +3 -1
  35. package/lib/logic/index.js.map +1 -1
  36. package/lib/logic/listOperations.d.ts.map +1 -1
  37. package/lib/logic/listOperations.js +16 -2
  38. package/lib/logic/listOperations.js.map +1 -1
  39. package/lib/logic/utils.d.ts +17 -1
  40. package/lib/logic/utils.d.ts.map +1 -1
  41. package/lib/logic/utils.js +54 -1
  42. package/lib/logic/utils.js.map +1 -1
  43. package/lib/network/api.d.ts +22 -2
  44. package/lib/network/api.d.ts.map +1 -1
  45. package/lib/network/api.js +49 -14
  46. package/lib/network/api.js.map +1 -1
  47. package/lib/preload.js +2 -2
  48. package/lib/preload.js.map +1 -1
  49. package/lib/test/fixtures/account.fixture.d.ts +8 -0
  50. package/lib/test/fixtures/account.fixture.d.ts.map +1 -1
  51. package/lib/test/fixtures/account.fixture.js +8 -0
  52. package/lib/test/fixtures/account.fixture.js.map +1 -1
  53. package/lib/test/fixtures/mirror.fixture.d.ts +2 -1
  54. package/lib/test/fixtures/mirror.fixture.d.ts.map +1 -1
  55. package/lib/test/fixtures/mirror.fixture.js +16 -1
  56. package/lib/test/fixtures/mirror.fixture.js.map +1 -1
  57. package/lib/types/bridge.d.ts +1 -0
  58. package/lib/types/bridge.d.ts.map +1 -1
  59. package/lib/types/logic.d.ts +6 -0
  60. package/lib/types/logic.d.ts.map +1 -1
  61. package/lib-es/api/index.d.ts.map +1 -1
  62. package/lib-es/api/index.js +5 -4
  63. package/lib-es/api/index.js.map +1 -1
  64. package/lib-es/bridge/buildOptimisticOperation.d.ts.map +1 -1
  65. package/lib-es/bridge/buildOptimisticOperation.js +15 -44
  66. package/lib-es/bridge/buildOptimisticOperation.js.map +1 -1
  67. package/lib-es/bridge/index.d.ts.map +1 -1
  68. package/lib-es/bridge/index.js +2 -0
  69. package/lib-es/bridge/index.js.map +1 -1
  70. package/lib-es/bridge/validateAddress.d.ts +3 -0
  71. package/lib-es/bridge/validateAddress.d.ts.map +1 -0
  72. package/lib-es/bridge/validateAddress.js +6 -0
  73. package/lib-es/bridge/validateAddress.js.map +1 -0
  74. package/lib-es/constants.d.ts +11 -6
  75. package/lib-es/constants.d.ts.map +1 -1
  76. package/lib-es/constants.js +19 -0
  77. package/lib-es/constants.js.map +1 -1
  78. package/lib-es/deviceTransactionConfig.d.ts.map +1 -1
  79. package/lib-es/deviceTransactionConfig.js +2 -4
  80. package/lib-es/deviceTransactionConfig.js.map +1 -1
  81. package/lib-es/logic/getBalance.d.ts.map +1 -1
  82. package/lib-es/logic/getBalance.js +21 -4
  83. package/lib-es/logic/getBalance.js.map +1 -1
  84. package/lib-es/logic/getBlock.d.ts.map +1 -1
  85. package/lib-es/logic/getBlock.js +40 -3
  86. package/lib-es/logic/getBlock.js.map +1 -1
  87. package/lib-es/logic/getValidators.d.ts +3 -0
  88. package/lib-es/logic/getValidators.d.ts.map +1 -0
  89. package/lib-es/logic/getValidators.js +20 -0
  90. package/lib-es/logic/getValidators.js.map +1 -0
  91. package/lib-es/logic/index.d.ts +1 -0
  92. package/lib-es/logic/index.d.ts.map +1 -1
  93. package/lib-es/logic/index.js +1 -0
  94. package/lib-es/logic/index.js.map +1 -1
  95. package/lib-es/logic/listOperations.d.ts.map +1 -1
  96. package/lib-es/logic/listOperations.js +17 -3
  97. package/lib-es/logic/listOperations.js.map +1 -1
  98. package/lib-es/logic/utils.d.ts +17 -1
  99. package/lib-es/logic/utils.d.ts.map +1 -1
  100. package/lib-es/logic/utils.js +52 -1
  101. package/lib-es/logic/utils.js.map +1 -1
  102. package/lib-es/network/api.d.ts +22 -2
  103. package/lib-es/network/api.d.ts.map +1 -1
  104. package/lib-es/network/api.js +49 -14
  105. package/lib-es/network/api.js.map +1 -1
  106. package/lib-es/preload.js +2 -2
  107. package/lib-es/preload.js.map +1 -1
  108. package/lib-es/test/fixtures/account.fixture.d.ts +8 -0
  109. package/lib-es/test/fixtures/account.fixture.d.ts.map +1 -1
  110. package/lib-es/test/fixtures/account.fixture.js +8 -0
  111. package/lib-es/test/fixtures/account.fixture.js.map +1 -1
  112. package/lib-es/test/fixtures/mirror.fixture.d.ts +2 -1
  113. package/lib-es/test/fixtures/mirror.fixture.d.ts.map +1 -1
  114. package/lib-es/test/fixtures/mirror.fixture.js +14 -0
  115. package/lib-es/test/fixtures/mirror.fixture.js.map +1 -1
  116. package/lib-es/types/bridge.d.ts +1 -0
  117. package/lib-es/types/bridge.d.ts.map +1 -1
  118. package/lib-es/types/logic.d.ts +6 -0
  119. package/lib-es/types/logic.d.ts.map +1 -1
  120. package/package.json +10 -10
  121. package/src/api/index.integ.test.ts +226 -1
  122. package/src/api/index.test.ts +5 -2
  123. package/src/api/index.ts +5 -5
  124. package/src/bridge/{buildOptimisticOperation.integration.test.ts → buildOptimisticOperation.test.ts} +23 -68
  125. package/src/bridge/buildOptimisticOperation.ts +16 -45
  126. package/src/bridge/index.ts +2 -0
  127. package/src/bridge/validateAddress.test.ts +31 -0
  128. package/src/bridge/validateAddress.ts +10 -0
  129. package/src/constants.ts +23 -1
  130. package/src/deviceTransactionConfig.test.ts +59 -43
  131. package/src/deviceTransactionConfig.ts +2 -5
  132. package/src/logic/getBalance.test.ts +50 -0
  133. package/src/logic/getBalance.ts +21 -4
  134. package/src/logic/getBlock.test.ts +283 -1
  135. package/src/logic/getBlock.ts +57 -6
  136. package/src/logic/getValidators.test.ts +50 -0
  137. package/src/logic/getValidators.ts +22 -0
  138. package/src/logic/index.ts +1 -0
  139. package/src/logic/listOperations.ts +33 -3
  140. package/src/logic/utils.test.ts +113 -0
  141. package/src/logic/utils.ts +67 -1
  142. package/src/network/api.test.ts +55 -9
  143. package/src/network/api.ts +66 -14
  144. package/src/preload.ts +2 -2
  145. package/src/test/fixtures/account.fixture.ts +8 -0
  146. package/src/test/fixtures/mirror.fixture.ts +18 -0
  147. package/src/types/bridge.ts +1 -0
  148. package/src/types/logic.ts +7 -0
  149. package/tsconfig.json +17 -12
@@ -1,14 +1,12 @@
1
1
  import BigNumber from "bignumber.js";
2
- import type { Account } from "@ledgerhq/types-live";
3
2
  import { HEDERA_TRANSACTION_MODES } from "./constants";
4
3
  import getDeviceTransactionConfig from "./deviceTransactionConfig";
5
- import type { Transaction, TransactionStatus } from "./types";
4
+ import type { TransactionStatus } from "./types";
5
+ import { getMockedAccount } from "./test/fixtures/account.fixture";
6
+ import { getMockedTransaction } from "./test/fixtures/transaction.fixture";
6
7
 
7
8
  describe("getDeviceTransactionConfig", () => {
8
- const mockAccount = {
9
- id: "mock-account-id",
10
- currency: { id: "hedera" },
11
- } as Account;
9
+ const mockAccount = getMockedAccount({ id: "mock-account-id" });
12
10
 
13
11
  const createMockStatus = (estimatedFees: BigNumber): TransactionStatus => ({
14
12
  errors: {},
@@ -20,13 +18,11 @@ describe("getDeviceTransactionConfig", () => {
20
18
 
21
19
  describe("staking transactions", () => {
22
20
  it("should return correct fields for ClaimRewards transaction", async () => {
23
- const transaction = {
24
- family: "hedera",
21
+ const transaction = getMockedTransaction({
25
22
  mode: HEDERA_TRANSACTION_MODES.ClaimRewards,
26
23
  amount: new BigNumber(0),
27
- recipient: "",
28
24
  memo: "Claiming rewards",
29
- } as Transaction;
25
+ });
30
26
 
31
27
  const status = createMockStatus(new BigNumber(100000));
32
28
 
@@ -55,15 +51,48 @@ describe("getDeviceTransactionConfig", () => {
55
51
  });
56
52
 
57
53
  it("should return correct fields for Delegate transaction", async () => {
58
- const transaction = {
59
- family: "hedera",
54
+ const transaction = getMockedTransaction({
60
55
  mode: HEDERA_TRANSACTION_MODES.Delegate,
61
56
  amount: new BigNumber(0),
62
- recipient: "",
63
57
  properties: {
64
58
  stakingNodeId: 10,
65
59
  },
66
- } as Transaction;
60
+ });
61
+
62
+ const status = createMockStatus(new BigNumber(100000));
63
+
64
+ const fields = await getDeviceTransactionConfig({
65
+ account: mockAccount,
66
+ transaction,
67
+ status,
68
+ });
69
+
70
+ expect(fields).toEqual([
71
+ {
72
+ type: "text",
73
+ label: "Method",
74
+ value: "Delegate",
75
+ },
76
+ {
77
+ label: "Fees",
78
+ type: "fees",
79
+ },
80
+ {
81
+ type: "text",
82
+ label: "Staked Node ID",
83
+ value: "10",
84
+ },
85
+ ]);
86
+ });
87
+
88
+ it("should return correct fields for Redelegate transaction", async () => {
89
+ const transaction = getMockedTransaction({
90
+ mode: HEDERA_TRANSACTION_MODES.Redelegate,
91
+ amount: new BigNumber(0),
92
+ properties: {
93
+ stakingNodeId: 10,
94
+ },
95
+ });
67
96
 
68
97
  const status = createMockStatus(new BigNumber(100000));
69
98
 
@@ -77,7 +106,7 @@ describe("getDeviceTransactionConfig", () => {
77
106
  {
78
107
  type: "text",
79
108
  label: "Method",
80
- value: "Update Account",
109
+ value: "Redelegate",
81
110
  },
82
111
  {
83
112
  label: "Fees",
@@ -92,13 +121,10 @@ describe("getDeviceTransactionConfig", () => {
92
121
  });
93
122
 
94
123
  it("should not include staking node ID if not provided", async () => {
95
- const transaction = {
96
- family: "hedera",
124
+ const transaction = getMockedTransaction({
97
125
  mode: HEDERA_TRANSACTION_MODES.Undelegate,
98
126
  amount: new BigNumber(0),
99
- recipient: "",
100
- properties: {},
101
- } as Transaction;
127
+ });
102
128
 
103
129
  const status = createMockStatus(new BigNumber(100000));
104
130
 
@@ -112,7 +138,7 @@ describe("getDeviceTransactionConfig", () => {
112
138
  {
113
139
  type: "text",
114
140
  label: "Method",
115
- value: "Update Account",
141
+ value: "Undelegate",
116
142
  },
117
143
  {
118
144
  label: "Fees",
@@ -124,14 +150,11 @@ describe("getDeviceTransactionConfig", () => {
124
150
 
125
151
  describe("token associate transactions", () => {
126
152
  it("should return correct fields for TokenAssociate transaction", async () => {
127
- const transaction = {
128
- family: "hedera",
153
+ const transaction = getMockedTransaction({
129
154
  mode: HEDERA_TRANSACTION_MODES.TokenAssociate,
130
- amount: new BigNumber(0),
131
- recipient: "",
132
155
  subAccountId: "token-account-id",
133
156
  memo: "Associating token",
134
- } as Transaction;
157
+ });
135
158
 
136
159
  const status = createMockStatus(new BigNumber(50000));
137
160
 
@@ -160,13 +183,10 @@ describe("getDeviceTransactionConfig", () => {
160
183
  });
161
184
 
162
185
  it("should not include fees if they are zero", async () => {
163
- const transaction = {
164
- family: "hedera",
186
+ const transaction = getMockedTransaction({
165
187
  mode: HEDERA_TRANSACTION_MODES.TokenAssociate,
166
- amount: new BigNumber(0),
167
- recipient: "",
168
188
  subAccountId: "token-account-id",
169
- } as Transaction;
189
+ });
170
190
 
171
191
  const status = createMockStatus(new BigNumber(0));
172
192
 
@@ -188,14 +208,13 @@ describe("getDeviceTransactionConfig", () => {
188
208
 
189
209
  describe("regular transfer transactions", () => {
190
210
  it("should return correct fields for regular Send transaction", async () => {
191
- const transaction = {
192
- family: "hedera",
211
+ const transaction = getMockedTransaction({
193
212
  mode: HEDERA_TRANSACTION_MODES.Send,
194
213
  amount: new BigNumber(1000000),
195
214
  recipient: "0.0.12345",
196
215
  useAllAmount: false,
197
216
  memo: "Payment",
198
- } as Transaction;
217
+ });
199
218
 
200
219
  const status = createMockStatus(new BigNumber(100000));
201
220
 
@@ -228,13 +247,12 @@ describe("getDeviceTransactionConfig", () => {
228
247
  });
229
248
 
230
249
  it("should show 'Transfer All' method when useAllAmount is true", async () => {
231
- const transaction = {
232
- family: "hedera",
250
+ const transaction = getMockedTransaction({
233
251
  mode: HEDERA_TRANSACTION_MODES.Send,
234
252
  amount: new BigNumber(0),
235
253
  recipient: "0.0.12345",
236
254
  useAllAmount: true,
237
- } as Transaction;
255
+ });
238
256
 
239
257
  const status = createMockStatus(new BigNumber(100000));
240
258
 
@@ -252,14 +270,13 @@ describe("getDeviceTransactionConfig", () => {
252
270
  });
253
271
 
254
272
  it("should include gas limit for Send transactions with gasLimit", async () => {
255
- const transaction = {
256
- family: "hedera",
273
+ const transaction = getMockedTransaction({
257
274
  mode: HEDERA_TRANSACTION_MODES.Send,
258
275
  amount: new BigNumber(1000000),
259
276
  recipient: "0.0.12345",
260
277
  useAllAmount: false,
261
278
  gasLimit: new BigNumber(300000),
262
- } as Transaction;
279
+ });
263
280
 
264
281
  const status = createMockStatus(new BigNumber(100000));
265
282
 
@@ -292,13 +309,12 @@ describe("getDeviceTransactionConfig", () => {
292
309
  });
293
310
 
294
311
  it("should not include memo if not provided", async () => {
295
- const transaction = {
296
- family: "hedera",
312
+ const transaction = getMockedTransaction({
297
313
  mode: HEDERA_TRANSACTION_MODES.Send,
298
314
  amount: new BigNumber(1000000),
299
315
  recipient: "0.0.12345",
300
316
  useAllAmount: false,
301
- } as Transaction;
317
+ });
302
318
 
303
319
  const status = createMockStatus(new BigNumber(100000));
304
320
 
@@ -1,6 +1,6 @@
1
1
  import type { CommonDeviceTransactionField as DeviceTransactionField } from "@ledgerhq/coin-framework/transaction/common";
2
2
  import type { AccountLike, Account } from "@ledgerhq/types-live";
3
- import { HEDERA_TRANSACTION_MODES } from "./constants";
3
+ import { HEDERA_TRANSACTION_MODES, MAP_STAKING_MODE_TO_METHOD } from "./constants";
4
4
  import { isTokenAssociateTransaction, isStakingTransaction } from "./logic/utils";
5
5
  import type { Transaction, TransactionStatus } from "./types";
6
6
 
@@ -19,10 +19,7 @@ async function getDeviceTransactionConfig({
19
19
  fields.push({
20
20
  type: "text",
21
21
  label: "Method",
22
- value:
23
- transaction.mode === HEDERA_TRANSACTION_MODES.ClaimRewards
24
- ? "Claim Rewards"
25
- : "Update Account",
22
+ value: MAP_STAKING_MODE_TO_METHOD[transaction.mode],
26
23
  });
27
24
 
28
25
  if (!estimatedFees.isZero()) {
@@ -22,6 +22,7 @@ describe("getBalance", () => {
22
22
 
23
23
  (apiClient.getAccount as jest.Mock).mockResolvedValue(mockMirrorAccount);
24
24
  (apiClient.getAccountTokens as jest.Mock).mockResolvedValue([]);
25
+ (apiClient.getNodes as jest.Mock).mockResolvedValue({ nodes: [] });
25
26
 
26
27
  const result = await getBalance(mockCurrency, address);
27
28
 
@@ -70,6 +71,7 @@ describe("getBalance", () => {
70
71
 
71
72
  (apiClient.getAccount as jest.Mock).mockResolvedValue(mockMirrorAccount);
72
73
  (apiClient.getAccountTokens as jest.Mock).mockResolvedValue(mockMirrorTokens);
74
+ (apiClient.getNodes as jest.Mock).mockResolvedValue({ nodes: [] });
73
75
 
74
76
  const result = await getBalance(mockCurrency, address);
75
77
 
@@ -99,6 +101,51 @@ describe("getBalance", () => {
99
101
  );
100
102
  });
101
103
 
104
+ it("should return stake", async () => {
105
+ const address = "0.0.12345";
106
+ const mockCurrency = getMockedCurrency();
107
+ const mockMirrorAccount = {
108
+ account: address,
109
+ staked_node_id: 5,
110
+ balance: {
111
+ balance: 100,
112
+ },
113
+ pending_reward: 100,
114
+ };
115
+ const mockMirrorNode = {
116
+ node_id: 5,
117
+ node_account_id: "0.0.5",
118
+ description: "Hosted for Wipro | Amsterdam, Netherlands",
119
+ max_stake: 45000000000000000,
120
+ stake: 45000000000000000,
121
+ };
122
+
123
+ (apiClient.getAccount as jest.Mock).mockResolvedValue(mockMirrorAccount);
124
+ (apiClient.getAccountTokens as jest.Mock).mockResolvedValue([]);
125
+ (apiClient.getNodes as jest.Mock).mockResolvedValue({ nodes: [mockMirrorNode] });
126
+
127
+ const result = await getBalance(mockCurrency, address);
128
+
129
+ expect(apiClient.getAccount).toHaveBeenCalledTimes(1);
130
+ expect(apiClient.getAccount).toHaveBeenCalledWith(address);
131
+ expect(apiClient.getNodes).toHaveBeenCalledTimes(1);
132
+ expect(result).toHaveLength(1);
133
+ expect(result[0]).toMatchObject({
134
+ asset: { type: "native" },
135
+ value: BigInt(mockMirrorAccount.balance.balance),
136
+ stake: {
137
+ uid: address,
138
+ address,
139
+ asset: { type: "native" },
140
+ state: "active",
141
+ amount: BigInt(mockMirrorAccount.balance.balance + mockMirrorAccount.pending_reward),
142
+ amountDeposited: BigInt(mockMirrorAccount.balance.balance),
143
+ amountRewarded: BigInt(mockMirrorAccount.pending_reward),
144
+ delegate: mockMirrorNode.node_account_id,
145
+ },
146
+ });
147
+ });
148
+
102
149
  it("should skip tokens not found in CAL", async () => {
103
150
  const address = "0.0.12345";
104
151
  const mockCurrency = getMockedCurrency();
@@ -145,6 +192,7 @@ describe("getBalance", () => {
145
192
 
146
193
  (apiClient.getAccount as jest.Mock).mockResolvedValue(mockMirrorAccount);
147
194
  (apiClient.getAccountTokens as jest.Mock).mockResolvedValue(mockMirrorTokens);
195
+ (apiClient.getNodes as jest.Mock).mockResolvedValue({ nodes: [] });
148
196
 
149
197
  const result = await getBalance(mockCurrency, address);
150
198
 
@@ -173,6 +221,7 @@ describe("getBalance", () => {
173
221
 
174
222
  (apiClient.getAccount as jest.Mock).mockRejectedValue(error);
175
223
  (apiClient.getAccountTokens as jest.Mock).mockResolvedValue([]);
224
+ (apiClient.getNodes as jest.Mock).mockResolvedValue({ nodes: [] });
176
225
 
177
226
  await expect(getBalance(mockCurrency, address)).rejects.toThrow(error);
178
227
  });
@@ -189,6 +238,7 @@ describe("getBalance", () => {
189
238
 
190
239
  (apiClient.getAccount as jest.Mock).mockResolvedValue(mockMirrorAccount);
191
240
  (apiClient.getAccountTokens as jest.Mock).mockRejectedValue(error);
241
+ (apiClient.getNodes as jest.Mock).mockResolvedValue({ nodes: [] });
192
242
 
193
243
  await expect(getBalance(mockCurrency, address)).rejects.toThrow(error);
194
244
  });
@@ -4,15 +4,32 @@ import { getCryptoAssetsStore } from "@ledgerhq/cryptoassets/state";
4
4
  import { apiClient } from "../network/api";
5
5
 
6
6
  export async function getBalance(currency: CryptoCurrency, address: string): Promise<Balance[]> {
7
- const [mirrorAccount, mirrorTokens] = await Promise.all([
7
+ const [mirrorAccount, mirrorTokens, mirrorNodes] = await Promise.all([
8
8
  apiClient.getAccount(address),
9
9
  apiClient.getAccountTokens(address),
10
+ apiClient.getNodes({ fetchAllPages: true }),
10
11
  ]);
11
12
 
12
- const balance: Balance[] = [
13
+ const validator = mirrorNodes.nodes.find(v => v.node_id === mirrorAccount.staked_node_id);
14
+ const balances: Balance[] = [
13
15
  {
14
16
  asset: { type: "native" },
15
17
  value: BigInt(mirrorAccount.balance.balance),
18
+ ...(validator && {
19
+ stake: {
20
+ uid: address,
21
+ address,
22
+ asset: { type: "native" },
23
+ state: "active",
24
+ amount: BigInt(mirrorAccount.balance.balance) + BigInt(mirrorAccount.pending_reward),
25
+ amountDeposited: BigInt(mirrorAccount.balance.balance),
26
+ amountRewarded: BigInt(mirrorAccount.pending_reward),
27
+ delegate: validator.node_account_id,
28
+ details: {
29
+ overstaked: BigInt(validator.stake) >= BigInt(validator.max_stake),
30
+ },
31
+ },
32
+ }),
16
33
  },
17
34
  ];
18
35
 
@@ -26,7 +43,7 @@ export async function getBalance(currency: CryptoCurrency, address: string): Pro
26
43
  continue;
27
44
  }
28
45
 
29
- balance.push({
46
+ balances.push({
30
47
  value: BigInt(mirrorToken.balance),
31
48
  asset: {
32
49
  type: calToken.tokenType,
@@ -38,5 +55,5 @@ export async function getBalance(currency: CryptoCurrency, address: string): Pro
38
55
  });
39
56
  }
40
57
 
41
- return balance;
58
+ return balances;
42
59
  }
@@ -1,7 +1,9 @@
1
+ import { HEDERA_TRANSACTION_NAMES } from "../constants";
1
2
  import { getBlock } from "./getBlock";
2
3
  import { getBlockInfo } from "./getBlockInfo";
3
4
  import { apiClient } from "../network/api";
4
- import { getTimestampRangeFromBlockHeight } from "./utils";
5
+ import type { StakingAnalysis } from "../types";
6
+ import { analyzeStakingOperation, getTimestampRangeFromBlockHeight } from "./utils";
5
7
 
6
8
  jest.mock("./getBlockInfo");
7
9
  jest.mock("../network/api");
@@ -23,6 +25,7 @@ describe("getBlock", () => {
23
25
  jest.clearAllMocks();
24
26
  (getBlockInfo as jest.Mock).mockResolvedValue(mockBlockInfo);
25
27
  (getTimestampRangeFromBlockHeight as jest.Mock).mockReturnValue(mockTimestampRange);
28
+ (analyzeStakingOperation as jest.Mock).mockResolvedValue(null);
26
29
  });
27
30
 
28
31
  it("should return empty block when no transactions exist", async () => {
@@ -57,6 +60,7 @@ describe("getBlock", () => {
57
60
  name: "CRYPTOTRANSFER",
58
61
  result: "SUCCESS",
59
62
  charged_tx_fee: 100000,
63
+ staking_reward_transfers: [],
60
64
  transfers: [],
61
65
  token_transfers: [],
62
66
  };
@@ -75,6 +79,7 @@ describe("getBlock", () => {
75
79
  name: "CRYPTOTRANSFER",
76
80
  result: "SUCCESS",
77
81
  charged_tx_fee: 67179,
82
+ staking_reward_transfers: [],
78
83
  transfers: [
79
84
  {
80
85
  account: "0.0.999",
@@ -98,4 +103,281 @@ describe("getBlock", () => {
98
103
  amount: BigInt(-567179 + 67179),
99
104
  });
100
105
  });
106
+
107
+ it("should handle token transfers", async () => {
108
+ const mockTx = {
109
+ transaction_id: "0.0.999-1234567890-000000000",
110
+ transaction_hash: "hash",
111
+ name: "CRYPTOTRANSFER",
112
+ result: "SUCCESS",
113
+ charged_tx_fee: 100000,
114
+ staking_reward_transfers: [],
115
+ transfers: [],
116
+ token_transfers: [
117
+ {
118
+ token_id: "0.0.12345",
119
+ account: "0.0.999",
120
+ amount: -1000,
121
+ },
122
+ {
123
+ token_id: "0.0.12345",
124
+ account: "0.0.1001",
125
+ amount: 1000,
126
+ },
127
+ ],
128
+ };
129
+
130
+ (apiClient.getTransactionsByTimestampRange as jest.Mock).mockResolvedValue([mockTx]);
131
+
132
+ const result = await getBlock(100);
133
+
134
+ expect(result.transactions[0].operations).toEqual([
135
+ {
136
+ type: "transfer",
137
+ address: "0.0.999",
138
+ asset: {
139
+ type: "hts",
140
+ assetReference: "0.0.12345",
141
+ },
142
+ amount: BigInt(-1000),
143
+ },
144
+ {
145
+ type: "transfer",
146
+ address: "0.0.1001",
147
+ asset: {
148
+ type: "hts",
149
+ assetReference: "0.0.12345",
150
+ },
151
+ amount: BigInt(1000),
152
+ },
153
+ ]);
154
+ });
155
+
156
+ it("should mark failed transactions", async () => {
157
+ const mockTx = {
158
+ transaction_id: "0.0.999-1234567890-000000000",
159
+ transaction_hash: "hash",
160
+ name: "CRYPTOTRANSFER",
161
+ result: "INSUFFICIENT_ACCOUNT_BALANCE",
162
+ charged_tx_fee: 100000,
163
+ staking_reward_transfers: [],
164
+ transfers: [],
165
+ token_transfers: [],
166
+ };
167
+
168
+ (apiClient.getTransactionsByTimestampRange as jest.Mock).mockResolvedValue([mockTx]);
169
+
170
+ const result = await getBlock(100);
171
+
172
+ expect(result.transactions[0].failed).toBe(true);
173
+ });
174
+
175
+ it("should analyze CRYPTOUPDATEACCOUNT transactions for staking", async () => {
176
+ const mockTx = {
177
+ transaction_id: "0.0.999-1234567890-000000000",
178
+ transaction_hash: "hash_update",
179
+ name: HEDERA_TRANSACTION_NAMES.UpdateAccount,
180
+ result: "SUCCESS",
181
+ charged_tx_fee: 22000,
182
+ consensus_timestamp: "1704067210.123456789",
183
+ staking_reward_transfers: [],
184
+ transfers: [],
185
+ token_transfers: [],
186
+ };
187
+ const mockStakingAnalysis: StakingAnalysis = {
188
+ operationType: "DELEGATE",
189
+ targetStakingNodeId: 5,
190
+ previousStakingNodeId: null,
191
+ stakedAmount: BigInt(100),
192
+ };
193
+
194
+ (apiClient.getTransactionsByTimestampRange as jest.Mock).mockResolvedValue([mockTx]);
195
+ (analyzeStakingOperation as jest.Mock).mockResolvedValue(mockStakingAnalysis);
196
+
197
+ const result = await getBlock(100);
198
+
199
+ expect(analyzeStakingOperation).toHaveBeenCalledTimes(1);
200
+ expect(analyzeStakingOperation).toHaveBeenCalledWith("0.0.999", mockTx);
201
+ expect(result.transactions[0].operations).toHaveLength(1);
202
+ expect(result.transactions[0].operations[0]).toEqual({
203
+ type: "other",
204
+ operationType: mockStakingAnalysis.operationType,
205
+ stakedNodeId: mockStakingAnalysis.targetStakingNodeId,
206
+ previousStakedNodeId: mockStakingAnalysis.previousStakingNodeId,
207
+ stakedAmount: mockStakingAnalysis.stakedAmount,
208
+ });
209
+ });
210
+
211
+ it("should handle UNDELEGATE staking operation", async () => {
212
+ const mockTx = {
213
+ transaction_id: "0.0.999-1234567890-000000000",
214
+ transaction_hash: "hash_undelegate",
215
+ name: HEDERA_TRANSACTION_NAMES.UpdateAccount,
216
+ result: "SUCCESS",
217
+ charged_tx_fee: 22000,
218
+ consensus_timestamp: "1704067210.123456789",
219
+ staking_reward_transfers: [],
220
+ transfers: [],
221
+ token_transfers: [],
222
+ };
223
+ const mockStakingAnalysis: StakingAnalysis = {
224
+ operationType: "UNDELEGATE",
225
+ targetStakingNodeId: null,
226
+ previousStakingNodeId: 3,
227
+ stakedAmount: BigInt(100),
228
+ };
229
+
230
+ (apiClient.getTransactionsByTimestampRange as jest.Mock).mockResolvedValue([mockTx]);
231
+ (analyzeStakingOperation as jest.Mock).mockResolvedValue(mockStakingAnalysis);
232
+
233
+ const result = await getBlock(100);
234
+
235
+ expect(result.transactions[0].operations[0]).toEqual({
236
+ type: "other",
237
+ operationType: mockStakingAnalysis.operationType,
238
+ stakedNodeId: mockStakingAnalysis.targetStakingNodeId,
239
+ previousStakedNodeId: mockStakingAnalysis.previousStakingNodeId,
240
+ stakedAmount: mockStakingAnalysis.stakedAmount,
241
+ });
242
+ });
243
+
244
+ it("should handle REDELEGATE staking operation", async () => {
245
+ const mockTx = {
246
+ transaction_id: "0.0.999-1234567890-000000000",
247
+ transaction_hash: "hash_redelegate",
248
+ name: HEDERA_TRANSACTION_NAMES.UpdateAccount,
249
+ result: "SUCCESS",
250
+ charged_tx_fee: 22000,
251
+ consensus_timestamp: "1704067210.123456789",
252
+ staking_reward_transfers: [],
253
+ transfers: [],
254
+ token_transfers: [],
255
+ };
256
+ const mockStakingAnalysis: StakingAnalysis = {
257
+ operationType: "REDELEGATE",
258
+ targetStakingNodeId: 10,
259
+ previousStakingNodeId: 5,
260
+ stakedAmount: BigInt(100),
261
+ };
262
+
263
+ (apiClient.getTransactionsByTimestampRange as jest.Mock).mockResolvedValue([mockTx]);
264
+ (analyzeStakingOperation as jest.Mock).mockResolvedValue(mockStakingAnalysis);
265
+
266
+ const result = await getBlock(100);
267
+
268
+ expect(result.transactions[0].operations).toEqual([
269
+ {
270
+ type: "other",
271
+ operationType: mockStakingAnalysis.operationType,
272
+ stakedNodeId: mockStakingAnalysis.targetStakingNodeId,
273
+ previousStakedNodeId: mockStakingAnalysis.previousStakingNodeId,
274
+ stakedAmount: mockStakingAnalysis.stakedAmount,
275
+ },
276
+ ]);
277
+ });
278
+
279
+ it("should create CLAIM_REWARDS operations for staking reward transfers", async () => {
280
+ const mockTx = {
281
+ transaction_id: "0.0.999-1234567890-000000000",
282
+ transaction_hash: "hash",
283
+ name: "CRYPTOTRANSFER",
284
+ result: "SUCCESS",
285
+ charged_tx_fee: 100000,
286
+ staking_reward_transfers: [
287
+ {
288
+ account: "0.0.999",
289
+ amount: 100000,
290
+ },
291
+ {
292
+ account: "0.0.1001",
293
+ amount: 200000,
294
+ },
295
+ ],
296
+ transfers: [
297
+ {
298
+ account: "0.0.999",
299
+ amount: -600000,
300
+ },
301
+ {
302
+ account: "0.0.1001",
303
+ amount: 500000,
304
+ },
305
+ ],
306
+ token_transfers: [],
307
+ };
308
+
309
+ (apiClient.getTransactionsByTimestampRange as jest.Mock).mockResolvedValue([mockTx]);
310
+
311
+ const result = await getBlock(100);
312
+
313
+ expect(result.transactions[0].operations).toEqual([
314
+ {
315
+ type: "transfer",
316
+ address: "0.0.999",
317
+ asset: { type: "native" },
318
+ amount: BigInt(-500000),
319
+ },
320
+ {
321
+ type: "transfer",
322
+ address: "0.0.1001",
323
+ asset: { type: "native" },
324
+ amount: BigInt(500000),
325
+ },
326
+ {
327
+ type: "transfer",
328
+ address: "0.0.999",
329
+ asset: { type: "native" },
330
+ amount: BigInt(100000),
331
+ },
332
+ {
333
+ type: "transfer",
334
+ address: "0.0.1001",
335
+ asset: { type: "native" },
336
+ amount: BigInt(200000),
337
+ },
338
+ ]);
339
+ });
340
+
341
+ it("should handle CRYPTOUPDATEACCOUNT if it's not related to staking", async () => {
342
+ const mockTx = {
343
+ transaction_id: "0.0.999-1234567890-000000000",
344
+ transaction_hash: "hash_regular_update",
345
+ name: HEDERA_TRANSACTION_NAMES.UpdateAccount,
346
+ result: "SUCCESS",
347
+ charged_tx_fee: 22000,
348
+ consensus_timestamp: "1704067210.123456789",
349
+ staking_reward_transfers: [],
350
+ transfers: [
351
+ {
352
+ account: "0.0.999",
353
+ amount: -23000,
354
+ },
355
+ {
356
+ account: "0.0.1000",
357
+ amount: 1000,
358
+ },
359
+ ],
360
+ token_transfers: [],
361
+ };
362
+
363
+ (apiClient.getTransactionsByTimestampRange as jest.Mock).mockResolvedValue([mockTx]);
364
+ (analyzeStakingOperation as jest.Mock).mockResolvedValue(null);
365
+
366
+ const result = await getBlock(100);
367
+
368
+ expect(result.transactions[0].operations).toEqual([
369
+ {
370
+ type: "transfer",
371
+ address: "0.0.999",
372
+ asset: { type: "native" },
373
+ amount: BigInt(-1000),
374
+ },
375
+ {
376
+ type: "transfer",
377
+ address: "0.0.1000",
378
+ asset: { type: "native" },
379
+ amount: BigInt(1000),
380
+ },
381
+ ]);
382
+ });
101
383
  });