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

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
@@ -1,3 +1,4 @@
1
+ import BigNumber from "bignumber.js";
1
2
  import type { AccountRaw, Account } from "@ledgerhq/types-live";
2
3
  import type {
3
4
  HederaAccount,
@@ -8,19 +9,35 @@ import type {
8
9
 
9
10
  export function toHederaResourcesRaw(resources: HederaResources): HederaResourcesRaw {
10
11
  const { maxAutomaticTokenAssociations, isAutoTokenAssociationEnabled } = resources;
12
+ const delegation = resources.delegation
13
+ ? {
14
+ nodeId: resources.delegation.nodeId,
15
+ delegated: resources.delegation.delegated.toString(),
16
+ pendingReward: resources.delegation.pendingReward.toString(),
17
+ }
18
+ : null;
11
19
 
12
20
  return {
13
21
  maxAutomaticTokenAssociations,
14
22
  isAutoTokenAssociationEnabled,
23
+ delegation,
15
24
  };
16
25
  }
17
26
 
18
27
  export function fromHederaResourcesRaw(rawResources: HederaResourcesRaw): HederaResources {
19
28
  const { maxAutomaticTokenAssociations, isAutoTokenAssociationEnabled } = rawResources;
29
+ const delegation = rawResources.delegation
30
+ ? {
31
+ nodeId: rawResources.delegation.nodeId,
32
+ delegated: new BigNumber(rawResources.delegation.delegated),
33
+ pendingReward: new BigNumber(rawResources.delegation.pendingReward),
34
+ }
35
+ : null;
20
36
 
21
37
  return {
22
38
  maxAutomaticTokenAssociations,
23
39
  isAutoTokenAssociationEnabled,
40
+ delegation,
24
41
  };
25
42
  }
26
43
 
@@ -1,7 +1,7 @@
1
1
  import { Observable } from "rxjs";
2
- import { Account, AccountBridge } from "@ledgerhq/types-live";
3
- import { AssetInfo, FeeEstimation } from "@ledgerhq/coin-framework/api/types";
4
- import { SignerContext } from "@ledgerhq/coin-framework/signer";
2
+ import type { AccountBridge } from "@ledgerhq/types-live";
3
+ import type { AssetInfo, FeeEstimation } from "@ledgerhq/coin-framework/api/types";
4
+ import type { SignerContext } from "@ledgerhq/coin-framework/signer";
5
5
  import { findSubAccountById } from "@ledgerhq/coin-framework/account/helpers";
6
6
  import { buildOptimisticOperation } from "./buildOptimisticOperation";
7
7
  import { DEFAULT_GAS_LIMIT, HEDERA_TRANSACTION_MODES } from "../constants";
@@ -12,13 +12,14 @@ import {
12
12
  serializeTransaction,
13
13
  getHederaTransactionBodyBytes,
14
14
  isTokenAssociateTransaction,
15
+ isStakingTransaction,
15
16
  } from "../logic/utils";
16
- import type { Transaction, HederaSigner, HederaTxData } from "../types";
17
+ import type { Transaction, HederaSigner, HederaTxData, HederaAccount } from "../types";
17
18
 
18
19
  export const buildSignOperation =
19
20
  (
20
21
  signerContext: SignerContext<HederaSigner>,
21
- ): AccountBridge<Transaction, Account>["signOperation"] =>
22
+ ): AccountBridge<Transaction, HederaAccount>["signOperation"] =>
22
23
  ({ account, transaction, deviceId }) =>
23
24
  new Observable(o => {
24
25
  void (async function () {
@@ -64,6 +65,15 @@ export const buildSignOperation =
64
65
  type: "erc20",
65
66
  gasLimit: BigInt((transaction.gasLimit ?? DEFAULT_GAS_LIMIT).toString()),
66
67
  };
68
+ } else if (isStakingTransaction(transaction)) {
69
+ type = transaction.mode;
70
+ asset = {
71
+ type: "native",
72
+ };
73
+ data = {
74
+ type: "staking",
75
+ stakingNodeId: transaction.properties?.stakingNodeId,
76
+ };
67
77
  } else {
68
78
  type = HEDERA_TRANSACTION_MODES.Send;
69
79
  asset = {
@@ -107,6 +107,14 @@ export const getAccountShape: GetAccountShape<HederaAccount> = async (
107
107
  const operations = shouldSyncFromScratch
108
108
  ? enrichedNewOperations
109
109
  : mergeOps(oldOperations, enrichedNewOperations);
110
+ const delegation =
111
+ typeof mirrorAccount.staked_node_id === "number"
112
+ ? {
113
+ nodeId: mirrorAccount.staked_node_id,
114
+ delegated: accountBalance,
115
+ pendingReward: new BigNumber(mirrorAccount.pending_reward),
116
+ }
117
+ : null;
110
118
 
111
119
  // how ERC20 operations are handled:
112
120
  // - mirror node doesn't include "IN" erc20 token transactions
@@ -159,6 +167,7 @@ export const getAccountShape: GetAccountShape<HederaAccount> = async (
159
167
  hederaResources: {
160
168
  maxAutomaticTokenAssociations: mirrorAccount.max_automatic_token_associations,
161
169
  isAutoTokenAssociationEnabled: mirrorAccount.max_automatic_token_associations === -1,
170
+ delegation,
162
171
  },
163
172
  };
164
173
  };
@@ -218,7 +218,7 @@ describe("utils", () => {
218
218
  erc20Tokens: [],
219
219
  });
220
220
 
221
- expect(result).toHaveLength(0);
221
+ expect(result).toEqual([]);
222
222
  });
223
223
 
224
224
  it("returns sub account for mirror token with no operations yet (e.g. right after association)", async () => {
@@ -237,7 +237,6 @@ describe("utils", () => {
237
237
  erc20Tokens: [],
238
238
  });
239
239
 
240
- expect(result).toHaveLength(1);
241
240
  expect(result).toMatchObject([
242
241
  {
243
242
  token: tokenCurrencyFromCAL,
@@ -259,7 +258,6 @@ describe("utils", () => {
259
258
  erc20Tokens: [{ balance: new BigNumber(42), token: tokenCurrencyFromCAL }],
260
259
  });
261
260
 
262
- expect(result).toHaveLength(1);
263
261
  expect(result).toMatchObject([
264
262
  {
265
263
  token: tokenCurrencyFromCAL,
@@ -341,7 +339,6 @@ describe("utils", () => {
341
339
  hash: incomingTxHash,
342
340
  blockHash: incomingERC20Transaction.blockHash,
343
341
  });
344
- expect(incomingOp?.subOperations).toHaveLength(1);
345
342
  expect(incomingOp?.subOperations).toMatchObject([
346
343
  {
347
344
  type: "IN",
@@ -353,7 +350,6 @@ describe("utils", () => {
353
350
  recipients: [address],
354
351
  },
355
352
  ]);
356
- expect(newERC20TokenOperations).toHaveLength(1);
357
353
  expect(newERC20TokenOperations).toMatchObject([incomingOp?.subOperations?.[0]]);
358
354
  expect(updatedOperations).toHaveLength(oldMirrorOperations.length + 1);
359
355
  });
@@ -422,7 +418,6 @@ describe("utils", () => {
422
418
  blockHash: allowanceERC20Transaction.blockHash,
423
419
  standard: "erc20",
424
420
  });
425
- expect(allowanceOp?.subOperations).toHaveLength(1);
426
421
  expect(allowanceOp?.subOperations).toMatchObject([
427
422
  {
428
423
  type: "OUT",
@@ -434,7 +429,6 @@ describe("utils", () => {
434
429
  recipients: [allowanceTxTo],
435
430
  },
436
431
  ]);
437
- expect(newERC20TokenOperations).toHaveLength(1);
438
432
  expect(newERC20TokenOperations).toMatchObject([allowanceOp?.subOperations?.[0]]);
439
433
  expect(updatedOperations).toHaveLength(oldMirrorOperations.length + 1);
440
434
  });
@@ -519,7 +513,7 @@ describe("utils", () => {
519
513
  );
520
514
 
521
515
  expect(updatedOperations).toHaveLength(2);
522
- expect(duplicatedContractCalls).toHaveLength(0);
516
+ expect(duplicatedContractCalls).toEqual([]);
523
517
  expect(feesOps).toHaveLength(1);
524
518
  expect(feesOps).toMatchObject([{ blockHash: "0xBLOCK" }]);
525
519
  });
@@ -553,7 +547,7 @@ describe("utils", () => {
553
547
 
554
548
  expect(pendingOp).toBeUndefined();
555
549
  expect(updatedOperations).toHaveLength(1);
556
- expect(updatedOperations[0].hash).toBe("confirmed_tx");
550
+ expect(updatedOperations).toMatchObject([{ hash: "confirmed_tx" }]);
557
551
  });
558
552
 
559
553
  /**
@@ -672,7 +666,6 @@ describe("utils", () => {
672
666
  },
673
667
  ],
674
668
  });
675
- expect(newERC20TokenOperations).toHaveLength(1);
676
669
  expect(newERC20TokenOperations).toMatchObject([
677
670
  {
678
671
  type: "OUT",
package/src/constants.ts CHANGED
@@ -7,12 +7,17 @@ import BigNumber from "bignumber.js";
7
7
  export enum HEDERA_TRANSACTION_MODES {
8
8
  Send = "send",
9
9
  TokenAssociate = "token-associate",
10
+ Delegate = "delegate",
11
+ Undelegate = "undelegate",
12
+ Redelegate = "redelegate",
13
+ ClaimRewards = "claim-rewards",
10
14
  }
11
15
 
12
16
  /**
13
17
  * Enum representing the supported Hedera operation types for fee estimation
14
18
  */
15
19
  export enum HEDERA_OPERATION_TYPES {
20
+ CryptoUpdate = "CryptoUpdate",
16
21
  CryptoTransfer = "CryptoTransfer",
17
22
  TokenTransfer = "TokenTransfer",
18
23
  TokenAssociate = "TokenAssociate",
@@ -39,6 +44,15 @@ export const DEFAULT_GAS_PRICE_TINYBARS = new BigNumber(100);
39
44
 
40
45
  export const HEDERA_MAINNET_CHAIN_ID = 295;
41
46
 
47
+ /**
48
+ * Enum representing the delegation status of a Hedera account
49
+ */
50
+ export enum HEDERA_DELEGATION_STATUS {
51
+ Inactive = "inactive",
52
+ Overstaked = "overstaked",
53
+ Active = "active",
54
+ }
55
+
42
56
  /**
43
57
  * https://docs.hedera.com/hedera/networks/mainnet/fees
44
58
  *
@@ -49,6 +63,7 @@ export const HEDERA_MAINNET_CHAIN_ID = 295;
49
63
  * has sufficient balance to cover the cost of a transaction (e.g. token association).
50
64
  */
51
65
  export const BASE_USD_FEE_BY_OPERATION_TYPE = {
66
+ [HEDERA_OPERATION_TYPES.CryptoUpdate]: 0.00022 * 10 ** TINYBAR_SCALE,
52
67
  [HEDERA_OPERATION_TYPES.CryptoTransfer]: 0.0001 * 10 ** TINYBAR_SCALE,
53
68
  [HEDERA_OPERATION_TYPES.TokenTransfer]: 0.001 * 10 ** TINYBAR_SCALE,
54
69
  [HEDERA_OPERATION_TYPES.TokenAssociate]: 0.05 * 10 ** TINYBAR_SCALE,
@@ -84,3 +99,10 @@ export const SUPPORTED_ERC20_TOKENS = [
84
99
  tokenId: "0.0.10047837",
85
100
  },
86
101
  ];
102
+
103
+ export const MAP_STAKING_MODE_TO_MEMO = {
104
+ [HEDERA_TRANSACTION_MODES.ClaimRewards]: "Collect Staking Rewards",
105
+ [HEDERA_TRANSACTION_MODES.Delegate]: "Stake",
106
+ [HEDERA_TRANSACTION_MODES.Undelegate]: "Unstake",
107
+ [HEDERA_TRANSACTION_MODES.Redelegate]: "Restake",
108
+ } as const;
@@ -0,0 +1,315 @@
1
+ import BigNumber from "bignumber.js";
2
+ import type { Account } from "@ledgerhq/types-live";
3
+ import { HEDERA_TRANSACTION_MODES } from "./constants";
4
+ import getDeviceTransactionConfig from "./deviceTransactionConfig";
5
+ import type { Transaction, TransactionStatus } from "./types";
6
+
7
+ describe("getDeviceTransactionConfig", () => {
8
+ const mockAccount = {
9
+ id: "mock-account-id",
10
+ currency: { id: "hedera" },
11
+ } as Account;
12
+
13
+ const createMockStatus = (estimatedFees: BigNumber): TransactionStatus => ({
14
+ errors: {},
15
+ warnings: {},
16
+ estimatedFees,
17
+ amount: new BigNumber(0),
18
+ totalSpent: new BigNumber(0),
19
+ });
20
+
21
+ describe("staking transactions", () => {
22
+ it("should return correct fields for ClaimRewards transaction", async () => {
23
+ const transaction = {
24
+ family: "hedera",
25
+ mode: HEDERA_TRANSACTION_MODES.ClaimRewards,
26
+ amount: new BigNumber(0),
27
+ recipient: "",
28
+ memo: "Claiming rewards",
29
+ } as Transaction;
30
+
31
+ const status = createMockStatus(new BigNumber(100000));
32
+
33
+ const fields = await getDeviceTransactionConfig({
34
+ account: mockAccount,
35
+ transaction,
36
+ status,
37
+ });
38
+
39
+ expect(fields).toEqual([
40
+ {
41
+ type: "text",
42
+ label: "Method",
43
+ value: "Claim Rewards",
44
+ },
45
+ {
46
+ label: "Fees",
47
+ type: "fees",
48
+ },
49
+ {
50
+ type: "text",
51
+ label: "Memo",
52
+ value: "Claiming rewards",
53
+ },
54
+ ]);
55
+ });
56
+
57
+ it("should return correct fields for Delegate transaction", async () => {
58
+ const transaction = {
59
+ family: "hedera",
60
+ mode: HEDERA_TRANSACTION_MODES.Delegate,
61
+ amount: new BigNumber(0),
62
+ recipient: "",
63
+ properties: {
64
+ stakingNodeId: 10,
65
+ },
66
+ } as Transaction;
67
+
68
+ const status = createMockStatus(new BigNumber(100000));
69
+
70
+ const fields = await getDeviceTransactionConfig({
71
+ account: mockAccount,
72
+ transaction,
73
+ status,
74
+ });
75
+
76
+ expect(fields).toEqual([
77
+ {
78
+ type: "text",
79
+ label: "Method",
80
+ value: "Update Account",
81
+ },
82
+ {
83
+ label: "Fees",
84
+ type: "fees",
85
+ },
86
+ {
87
+ type: "text",
88
+ label: "Staked Node ID",
89
+ value: "10",
90
+ },
91
+ ]);
92
+ });
93
+
94
+ it("should not include staking node ID if not provided", async () => {
95
+ const transaction = {
96
+ family: "hedera",
97
+ mode: HEDERA_TRANSACTION_MODES.Undelegate,
98
+ amount: new BigNumber(0),
99
+ recipient: "",
100
+ properties: {},
101
+ } as Transaction;
102
+
103
+ const status = createMockStatus(new BigNumber(100000));
104
+
105
+ const fields = await getDeviceTransactionConfig({
106
+ account: mockAccount,
107
+ transaction,
108
+ status,
109
+ });
110
+
111
+ expect(fields).toEqual([
112
+ {
113
+ type: "text",
114
+ label: "Method",
115
+ value: "Update Account",
116
+ },
117
+ {
118
+ label: "Fees",
119
+ type: "fees",
120
+ },
121
+ ]);
122
+ });
123
+ });
124
+
125
+ describe("token associate transactions", () => {
126
+ it("should return correct fields for TokenAssociate transaction", async () => {
127
+ const transaction = {
128
+ family: "hedera",
129
+ mode: HEDERA_TRANSACTION_MODES.TokenAssociate,
130
+ amount: new BigNumber(0),
131
+ recipient: "",
132
+ subAccountId: "token-account-id",
133
+ memo: "Associating token",
134
+ } as Transaction;
135
+
136
+ const status = createMockStatus(new BigNumber(50000));
137
+
138
+ const fields = await getDeviceTransactionConfig({
139
+ account: mockAccount,
140
+ transaction,
141
+ status,
142
+ });
143
+
144
+ expect(fields).toEqual([
145
+ {
146
+ type: "text",
147
+ label: "Method",
148
+ value: "Associate Token",
149
+ },
150
+ {
151
+ type: "fees",
152
+ label: "Fees",
153
+ },
154
+ {
155
+ type: "text",
156
+ label: "Memo",
157
+ value: "Associating token",
158
+ },
159
+ ]);
160
+ });
161
+
162
+ it("should not include fees if they are zero", async () => {
163
+ const transaction = {
164
+ family: "hedera",
165
+ mode: HEDERA_TRANSACTION_MODES.TokenAssociate,
166
+ amount: new BigNumber(0),
167
+ recipient: "",
168
+ subAccountId: "token-account-id",
169
+ } as Transaction;
170
+
171
+ const status = createMockStatus(new BigNumber(0));
172
+
173
+ const fields = await getDeviceTransactionConfig({
174
+ account: mockAccount,
175
+ transaction,
176
+ status,
177
+ });
178
+
179
+ expect(fields).toEqual([
180
+ {
181
+ type: "text",
182
+ label: "Method",
183
+ value: "Associate Token",
184
+ },
185
+ ]);
186
+ });
187
+ });
188
+
189
+ describe("regular transfer transactions", () => {
190
+ it("should return correct fields for regular Send transaction", async () => {
191
+ const transaction = {
192
+ family: "hedera",
193
+ mode: HEDERA_TRANSACTION_MODES.Send,
194
+ amount: new BigNumber(1000000),
195
+ recipient: "0.0.12345",
196
+ useAllAmount: false,
197
+ memo: "Payment",
198
+ } as Transaction;
199
+
200
+ const status = createMockStatus(new BigNumber(100000));
201
+
202
+ const fields = await getDeviceTransactionConfig({
203
+ account: mockAccount,
204
+ transaction,
205
+ status,
206
+ });
207
+
208
+ expect(fields).toEqual([
209
+ {
210
+ type: "text",
211
+ label: "Method",
212
+ value: "Transfer",
213
+ },
214
+ {
215
+ type: "amount",
216
+ label: "Amount",
217
+ },
218
+ {
219
+ type: "fees",
220
+ label: "Fees",
221
+ },
222
+ {
223
+ type: "text",
224
+ label: "Memo",
225
+ value: "Payment",
226
+ },
227
+ ]);
228
+ });
229
+
230
+ it("should show 'Transfer All' method when useAllAmount is true", async () => {
231
+ const transaction = {
232
+ family: "hedera",
233
+ mode: HEDERA_TRANSACTION_MODES.Send,
234
+ amount: new BigNumber(0),
235
+ recipient: "0.0.12345",
236
+ useAllAmount: true,
237
+ } as Transaction;
238
+
239
+ const status = createMockStatus(new BigNumber(100000));
240
+
241
+ const fields = await getDeviceTransactionConfig({
242
+ account: mockAccount,
243
+ transaction,
244
+ status,
245
+ });
246
+
247
+ expect(fields[0]).toEqual({
248
+ type: "text",
249
+ label: "Method",
250
+ value: "Transfer All",
251
+ });
252
+ });
253
+
254
+ it("should include gas limit for Send transactions with gasLimit", async () => {
255
+ const transaction = {
256
+ family: "hedera",
257
+ mode: HEDERA_TRANSACTION_MODES.Send,
258
+ amount: new BigNumber(1000000),
259
+ recipient: "0.0.12345",
260
+ useAllAmount: false,
261
+ gasLimit: new BigNumber(300000),
262
+ } as Transaction;
263
+
264
+ const status = createMockStatus(new BigNumber(100000));
265
+
266
+ const fields = await getDeviceTransactionConfig({
267
+ account: mockAccount,
268
+ transaction,
269
+ status,
270
+ });
271
+
272
+ expect(fields).toEqual([
273
+ {
274
+ type: "text",
275
+ label: "Method",
276
+ value: "Transfer",
277
+ },
278
+ {
279
+ type: "amount",
280
+ label: "Amount",
281
+ },
282
+ {
283
+ type: "fees",
284
+ label: "Fees",
285
+ },
286
+ {
287
+ type: "text",
288
+ label: "Gas Limit",
289
+ value: "300000",
290
+ },
291
+ ]);
292
+ });
293
+
294
+ it("should not include memo if not provided", async () => {
295
+ const transaction = {
296
+ family: "hedera",
297
+ mode: HEDERA_TRANSACTION_MODES.Send,
298
+ amount: new BigNumber(1000000),
299
+ recipient: "0.0.12345",
300
+ useAllAmount: false,
301
+ } as Transaction;
302
+
303
+ const status = createMockStatus(new BigNumber(100000));
304
+
305
+ const fields = await getDeviceTransactionConfig({
306
+ account: mockAccount,
307
+ transaction,
308
+ status,
309
+ });
310
+
311
+ expect(fields).toHaveLength(3);
312
+ expect(fields.some(f => f.label === "Memo")).toBe(false);
313
+ });
314
+ });
315
+ });
@@ -1,7 +1,7 @@
1
1
  import type { CommonDeviceTransactionField as DeviceTransactionField } from "@ledgerhq/coin-framework/transaction/common";
2
2
  import type { AccountLike, Account } from "@ledgerhq/types-live";
3
3
  import { HEDERA_TRANSACTION_MODES } from "./constants";
4
- import { isTokenAssociateTransaction } from "./logic/utils";
4
+ import { isTokenAssociateTransaction, isStakingTransaction } from "./logic/utils";
5
5
  import type { Transaction, TransactionStatus } from "./types";
6
6
 
7
7
  async function getDeviceTransactionConfig({
@@ -15,6 +15,42 @@ async function getDeviceTransactionConfig({
15
15
  }): Promise<Array<DeviceTransactionField>> {
16
16
  const fields: Array<DeviceTransactionField> = [];
17
17
 
18
+ if (isStakingTransaction(transaction)) {
19
+ fields.push({
20
+ type: "text",
21
+ label: "Method",
22
+ value:
23
+ transaction.mode === HEDERA_TRANSACTION_MODES.ClaimRewards
24
+ ? "Claim Rewards"
25
+ : "Update Account",
26
+ });
27
+
28
+ if (!estimatedFees.isZero()) {
29
+ fields.push({
30
+ type: "fees",
31
+ label: "Fees",
32
+ });
33
+ }
34
+
35
+ if (typeof transaction.properties?.stakingNodeId === "number") {
36
+ fields.push({
37
+ type: "text",
38
+ label: "Staked Node ID",
39
+ value: transaction.properties.stakingNodeId.toString(),
40
+ });
41
+ }
42
+
43
+ if (transaction.memo) {
44
+ fields.push({
45
+ type: "text",
46
+ label: "Memo",
47
+ value: transaction.memo,
48
+ });
49
+ }
50
+
51
+ return fields;
52
+ }
53
+
18
54
  const method = (() => {
19
55
  if (isTokenAssociateTransaction(transaction)) return "Associate Token";
20
56
  else if (transaction.useAllAmount) return "Transfer All";
package/src/errors.ts CHANGED
@@ -16,3 +16,10 @@ export const HederaRecipientTokenAssociationUnverified = createCustomErrorClass(
16
16
  export const HederaRecipientEvmAddressVerificationRequired = createCustomErrorClass(
17
17
  "HederaRecipientEvmAddressVerificationRequired",
18
18
  );
19
+ export const HederaRedundantStakingNodeIdError = createCustomErrorClass(
20
+ "HederaRedundantStakingNodeIdError",
21
+ );
22
+ export const HederaInvalidStakingNodeIdError = createCustomErrorClass(
23
+ "HederaInvalidStakingNodeIdError",
24
+ );
25
+ export const HederaNoStakingRewardsError = createCustomErrorClass("HederaNoStakingRewardsError");