@ledgerhq/coin-hedera 1.15.0-nightly.20251126023856 → 1.15.0-nightly.20251127023715

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 (280) hide show
  1. package/CHANGELOG.md +10 -8
  2. package/lib/api/index.d.ts.map +1 -1
  3. package/lib/api/index.js +6 -2
  4. package/lib/api/index.js.map +1 -1
  5. package/lib/bridge/broadcast.js +1 -1
  6. package/lib/bridge/broadcast.js.map +1 -1
  7. package/lib/bridge/buildOptimisticOperation.d.ts.map +1 -1
  8. package/lib/bridge/buildOptimisticOperation.js +80 -15
  9. package/lib/bridge/buildOptimisticOperation.js.map +1 -1
  10. package/lib/bridge/estimateMaxSpendable.js +5 -2
  11. package/lib/bridge/estimateMaxSpendable.js.map +1 -1
  12. package/lib/bridge/getTransactionStatus.d.ts.map +1 -1
  13. package/lib/bridge/getTransactionStatus.js +70 -13
  14. package/lib/bridge/getTransactionStatus.js.map +1 -1
  15. package/lib/bridge/index.js +1 -1
  16. package/lib/bridge/index.js.map +1 -1
  17. package/lib/bridge/prepareTransaction.d.ts.map +1 -1
  18. package/lib/bridge/prepareTransaction.js +40 -7
  19. package/lib/bridge/prepareTransaction.js.map +1 -1
  20. package/lib/bridge/signOperation.d.ts.map +1 -1
  21. package/lib/bridge/signOperation.js +19 -2
  22. package/lib/bridge/signOperation.js.map +1 -1
  23. package/lib/bridge/synchronisation.d.ts +2 -0
  24. package/lib/bridge/synchronisation.d.ts.map +1 -1
  25. package/lib/bridge/synchronisation.js +101 -30
  26. package/lib/bridge/synchronisation.js.map +1 -1
  27. package/lib/bridge/utils.d.ts +35 -2
  28. package/lib/bridge/utils.d.ts.map +1 -1
  29. package/lib/bridge/utils.js +215 -16
  30. package/lib/bridge/utils.js.map +1 -1
  31. package/lib/constants.d.ts +22 -2
  32. package/lib/constants.d.ts.map +1 -1
  33. package/lib/constants.js +42 -2
  34. package/lib/constants.js.map +1 -1
  35. package/lib/deviceTransactionConfig.d.ts +1 -1
  36. package/lib/deviceTransactionConfig.d.ts.map +1 -1
  37. package/lib/deviceTransactionConfig.js +8 -0
  38. package/lib/deviceTransactionConfig.js.map +1 -1
  39. package/lib/errors.d.ts +3 -0
  40. package/lib/errors.d.ts.map +1 -1
  41. package/lib/errors.js +2 -1
  42. package/lib/errors.js.map +1 -1
  43. package/lib/logic/craftTransaction.d.ts +4 -4
  44. package/lib/logic/craftTransaction.d.ts.map +1 -1
  45. package/lib/logic/craftTransaction.js +46 -5
  46. package/lib/logic/craftTransaction.js.map +1 -1
  47. package/lib/logic/estimateFees.d.ts +2 -4
  48. package/lib/logic/estimateFees.d.ts.map +1 -1
  49. package/lib/logic/estimateFees.js +45 -5
  50. package/lib/logic/estimateFees.js.map +1 -1
  51. package/lib/logic/listOperations.d.ts.map +1 -1
  52. package/lib/logic/listOperations.js +7 -3
  53. package/lib/logic/listOperations.js.map +1 -1
  54. package/lib/logic/utils.d.ts +24 -2
  55. package/lib/logic/utils.d.ts.map +1 -1
  56. package/lib/logic/utils.js +66 -13
  57. package/lib/logic/utils.js.map +1 -1
  58. package/lib/network/api.d.ts +12 -1
  59. package/lib/network/api.d.ts.map +1 -1
  60. package/lib/network/api.js +91 -19
  61. package/lib/network/api.js.map +1 -1
  62. package/lib/network/rpc.d.ts.map +1 -1
  63. package/lib/network/rpc.js +1 -0
  64. package/lib/network/rpc.js.map +1 -1
  65. package/lib/network/thirdweb.d.ts +21 -0
  66. package/lib/network/thirdweb.d.ts.map +1 -0
  67. package/lib/network/thirdweb.js +72 -0
  68. package/lib/network/thirdweb.js.map +1 -0
  69. package/lib/network/utils.d.ts +4 -1
  70. package/lib/network/utils.d.ts.map +1 -1
  71. package/lib/network/utils.js +53 -1
  72. package/lib/network/utils.js.map +1 -1
  73. package/lib/test/bridgeDatasetTest.d.ts.map +1 -1
  74. package/lib/test/bridgeDatasetTest.js +4 -4
  75. package/lib/test/bridgeDatasetTest.js.map +1 -1
  76. package/lib/test/fixtures/account.fixture.js +1 -1
  77. package/lib/test/fixtures/account.fixture.js.map +1 -1
  78. package/lib/test/fixtures/common.fixture.d.ts +12 -0
  79. package/lib/test/fixtures/common.fixture.d.ts.map +1 -0
  80. package/lib/test/fixtures/common.fixture.js +66 -0
  81. package/lib/test/fixtures/common.fixture.js.map +1 -0
  82. package/lib/test/fixtures/currency.fixture.d.ts +3 -1
  83. package/lib/test/fixtures/currency.fixture.d.ts.map +1 -1
  84. package/lib/test/fixtures/currency.fixture.js +63 -16
  85. package/lib/test/fixtures/currency.fixture.js.map +1 -1
  86. package/lib/test/fixtures/mirror.fixture.d.ts +3 -1
  87. package/lib/test/fixtures/mirror.fixture.d.ts.map +1 -1
  88. package/lib/test/fixtures/mirror.fixture.js +12 -1
  89. package/lib/test/fixtures/mirror.fixture.js.map +1 -1
  90. package/lib/test/fixtures/thirdweb.fixture.d.ts +3 -0
  91. package/lib/test/fixtures/thirdweb.fixture.d.ts.map +1 -0
  92. package/lib/test/fixtures/thirdweb.fixture.js +34 -0
  93. package/lib/test/fixtures/thirdweb.fixture.js.map +1 -0
  94. package/lib/transaction.d.ts.map +1 -1
  95. package/lib/transaction.js +2 -0
  96. package/lib/transaction.js.map +1 -1
  97. package/lib/types/alpaca.d.ts +5 -1
  98. package/lib/types/alpaca.d.ts.map +1 -1
  99. package/lib/types/bridge.d.ts +6 -1
  100. package/lib/types/bridge.d.ts.map +1 -1
  101. package/lib/types/index.d.ts +2 -0
  102. package/lib/types/index.d.ts.map +1 -1
  103. package/lib/types/index.js +2 -0
  104. package/lib/types/index.js.map +1 -1
  105. package/lib/types/logic.d.ts +39 -0
  106. package/lib/types/logic.d.ts.map +1 -0
  107. package/lib/types/logic.js +3 -0
  108. package/lib/types/logic.js.map +1 -0
  109. package/lib/types/mirror.d.ts +29 -0
  110. package/lib/types/mirror.d.ts.map +1 -1
  111. package/lib/types/thirdweb.d.ts +34 -0
  112. package/lib/types/thirdweb.d.ts.map +1 -0
  113. package/lib/types/thirdweb.js +3 -0
  114. package/lib/types/thirdweb.js.map +1 -0
  115. package/lib-es/api/index.d.ts.map +1 -1
  116. package/lib-es/api/index.js +7 -3
  117. package/lib-es/api/index.js.map +1 -1
  118. package/lib-es/bridge/broadcast.js +2 -2
  119. package/lib-es/bridge/broadcast.js.map +1 -1
  120. package/lib-es/bridge/buildOptimisticOperation.d.ts.map +1 -1
  121. package/lib-es/bridge/buildOptimisticOperation.js +83 -18
  122. package/lib-es/bridge/buildOptimisticOperation.js.map +1 -1
  123. package/lib-es/bridge/estimateMaxSpendable.js +5 -2
  124. package/lib-es/bridge/estimateMaxSpendable.js.map +1 -1
  125. package/lib-es/bridge/getTransactionStatus.d.ts.map +1 -1
  126. package/lib-es/bridge/getTransactionStatus.js +73 -16
  127. package/lib-es/bridge/getTransactionStatus.js.map +1 -1
  128. package/lib-es/bridge/index.js +2 -2
  129. package/lib-es/bridge/index.js.map +1 -1
  130. package/lib-es/bridge/prepareTransaction.d.ts.map +1 -1
  131. package/lib-es/bridge/prepareTransaction.js +42 -9
  132. package/lib-es/bridge/prepareTransaction.js.map +1 -1
  133. package/lib-es/bridge/signOperation.d.ts.map +1 -1
  134. package/lib-es/bridge/signOperation.js +21 -4
  135. package/lib-es/bridge/signOperation.js.map +1 -1
  136. package/lib-es/bridge/synchronisation.d.ts +2 -0
  137. package/lib-es/bridge/synchronisation.d.ts.map +1 -1
  138. package/lib-es/bridge/synchronisation.js +97 -27
  139. package/lib-es/bridge/synchronisation.js.map +1 -1
  140. package/lib-es/bridge/utils.d.ts +35 -2
  141. package/lib-es/bridge/utils.d.ts.map +1 -1
  142. package/lib-es/bridge/utils.js +211 -16
  143. package/lib-es/bridge/utils.js.map +1 -1
  144. package/lib-es/constants.d.ts +22 -2
  145. package/lib-es/constants.d.ts.map +1 -1
  146. package/lib-es/constants.js +38 -1
  147. package/lib-es/constants.js.map +1 -1
  148. package/lib-es/deviceTransactionConfig.d.ts +1 -1
  149. package/lib-es/deviceTransactionConfig.d.ts.map +1 -1
  150. package/lib-es/deviceTransactionConfig.js +8 -0
  151. package/lib-es/deviceTransactionConfig.js.map +1 -1
  152. package/lib-es/errors.d.ts +3 -0
  153. package/lib-es/errors.d.ts.map +1 -1
  154. package/lib-es/errors.js +1 -0
  155. package/lib-es/errors.js.map +1 -1
  156. package/lib-es/logic/craftTransaction.d.ts +4 -4
  157. package/lib-es/logic/craftTransaction.d.ts.map +1 -1
  158. package/lib-es/logic/craftTransaction.js +48 -7
  159. package/lib-es/logic/craftTransaction.js.map +1 -1
  160. package/lib-es/logic/estimateFees.d.ts +2 -4
  161. package/lib-es/logic/estimateFees.d.ts.map +1 -1
  162. package/lib-es/logic/estimateFees.js +47 -7
  163. package/lib-es/logic/estimateFees.js.map +1 -1
  164. package/lib-es/logic/listOperations.d.ts.map +1 -1
  165. package/lib-es/logic/listOperations.js +7 -3
  166. package/lib-es/logic/listOperations.js.map +1 -1
  167. package/lib-es/logic/utils.d.ts +24 -2
  168. package/lib-es/logic/utils.d.ts.map +1 -1
  169. package/lib-es/logic/utils.js +63 -13
  170. package/lib-es/logic/utils.js.map +1 -1
  171. package/lib-es/network/api.d.ts +12 -1
  172. package/lib-es/network/api.d.ts.map +1 -1
  173. package/lib-es/network/api.js +91 -19
  174. package/lib-es/network/api.js.map +1 -1
  175. package/lib-es/network/rpc.d.ts.map +1 -1
  176. package/lib-es/network/rpc.js +1 -0
  177. package/lib-es/network/rpc.js.map +1 -1
  178. package/lib-es/network/thirdweb.d.ts +21 -0
  179. package/lib-es/network/thirdweb.d.ts.map +1 -0
  180. package/lib-es/network/thirdweb.js +66 -0
  181. package/lib-es/network/thirdweb.js.map +1 -0
  182. package/lib-es/network/utils.d.ts +4 -1
  183. package/lib-es/network/utils.d.ts.map +1 -1
  184. package/lib-es/network/utils.js +49 -0
  185. package/lib-es/network/utils.js.map +1 -1
  186. package/lib-es/test/bridgeDatasetTest.d.ts.map +1 -1
  187. package/lib-es/test/bridgeDatasetTest.js +4 -4
  188. package/lib-es/test/bridgeDatasetTest.js.map +1 -1
  189. package/lib-es/test/fixtures/account.fixture.js +2 -2
  190. package/lib-es/test/fixtures/account.fixture.js.map +1 -1
  191. package/lib-es/test/fixtures/common.fixture.d.ts +12 -0
  192. package/lib-es/test/fixtures/common.fixture.d.ts.map +1 -0
  193. package/lib-es/test/fixtures/common.fixture.js +57 -0
  194. package/lib-es/test/fixtures/common.fixture.js.map +1 -0
  195. package/lib-es/test/fixtures/currency.fixture.d.ts +3 -1
  196. package/lib-es/test/fixtures/currency.fixture.d.ts.map +1 -1
  197. package/lib-es/test/fixtures/currency.fixture.js +59 -14
  198. package/lib-es/test/fixtures/currency.fixture.js.map +1 -1
  199. package/lib-es/test/fixtures/mirror.fixture.d.ts +3 -1
  200. package/lib-es/test/fixtures/mirror.fixture.d.ts.map +1 -1
  201. package/lib-es/test/fixtures/mirror.fixture.js +9 -0
  202. package/lib-es/test/fixtures/mirror.fixture.js.map +1 -1
  203. package/lib-es/test/fixtures/thirdweb.fixture.d.ts +3 -0
  204. package/lib-es/test/fixtures/thirdweb.fixture.d.ts.map +1 -0
  205. package/lib-es/test/fixtures/thirdweb.fixture.js +30 -0
  206. package/lib-es/test/fixtures/thirdweb.fixture.js.map +1 -0
  207. package/lib-es/transaction.d.ts.map +1 -1
  208. package/lib-es/transaction.js +2 -0
  209. package/lib-es/transaction.js.map +1 -1
  210. package/lib-es/types/alpaca.d.ts +5 -1
  211. package/lib-es/types/alpaca.d.ts.map +1 -1
  212. package/lib-es/types/bridge.d.ts +6 -1
  213. package/lib-es/types/bridge.d.ts.map +1 -1
  214. package/lib-es/types/index.d.ts +2 -0
  215. package/lib-es/types/index.d.ts.map +1 -1
  216. package/lib-es/types/index.js +2 -0
  217. package/lib-es/types/index.js.map +1 -1
  218. package/lib-es/types/logic.d.ts +39 -0
  219. package/lib-es/types/logic.d.ts.map +1 -0
  220. package/lib-es/types/logic.js +2 -0
  221. package/lib-es/types/logic.js.map +1 -0
  222. package/lib-es/types/mirror.d.ts +29 -0
  223. package/lib-es/types/mirror.d.ts.map +1 -1
  224. package/lib-es/types/thirdweb.d.ts +34 -0
  225. package/lib-es/types/thirdweb.d.ts.map +1 -0
  226. package/lib-es/types/thirdweb.js +2 -0
  227. package/lib-es/types/thirdweb.js.map +1 -0
  228. package/package.json +9 -8
  229. package/src/api/index.integ.test.ts +11 -8
  230. package/src/api/index.ts +10 -3
  231. package/src/bridge/broadcast.ts +2 -2
  232. package/src/bridge/buildOptimisticOperation.integration.test.ts +70 -19
  233. package/src/bridge/buildOptimisticOperation.ts +98 -20
  234. package/src/bridge/estimateMaxSpendable.ts +5 -5
  235. package/src/bridge/getTransactionStatus.test.ts +57 -12
  236. package/src/bridge/getTransactionStatus.ts +88 -15
  237. package/src/bridge/index.ts +2 -2
  238. package/src/bridge/js-estimateMaxSpendable.integration.test.ts +12 -9
  239. package/src/bridge/prepareTransaction.test.ts +3 -1
  240. package/src/bridge/prepareTransaction.ts +45 -10
  241. package/src/bridge/signOperation.ts +23 -5
  242. package/src/bridge/synchronisation.test.ts +67 -0
  243. package/src/bridge/synchronisation.ts +114 -34
  244. package/src/bridge/utils.integration.test.ts +486 -180
  245. package/src/bridge/utils.test.ts +404 -0
  246. package/src/bridge/utils.ts +330 -27
  247. package/src/constants.ts +47 -2
  248. package/src/deviceTransactionConfig.ts +10 -1
  249. package/src/errors.ts +3 -0
  250. package/src/logic/craftTransaction.test.ts +49 -9
  251. package/src/logic/craftTransaction.ts +76 -11
  252. package/src/logic/estimateFees.test.ts +180 -31
  253. package/src/logic/estimateFees.ts +68 -7
  254. package/src/logic/getAssetFromToken.test.ts +2 -2
  255. package/src/logic/getBalance.test.ts +18 -57
  256. package/src/logic/getTokenFromAsset.test.ts +2 -2
  257. package/src/logic/listOperations.ts +9 -5
  258. package/src/logic/utils.test.ts +157 -69
  259. package/src/logic/utils.ts +75 -13
  260. package/src/network/api.test.ts +211 -3
  261. package/src/network/api.ts +118 -24
  262. package/src/network/rpc.test.ts +1 -0
  263. package/src/network/rpc.ts +1 -0
  264. package/src/network/thirdweb.test.ts +188 -0
  265. package/src/network/thirdweb.ts +101 -0
  266. package/src/network/utils.test.ts +364 -164
  267. package/src/network/utils.ts +83 -1
  268. package/src/test/bridgeDatasetTest.ts +4 -5
  269. package/src/test/fixtures/account.fixture.ts +2 -2
  270. package/src/test/fixtures/common.fixture.ts +74 -0
  271. package/src/test/fixtures/currency.fixture.ts +66 -14
  272. package/src/test/fixtures/mirror.fixture.ts +23 -1
  273. package/src/test/fixtures/thirdweb.fixture.ts +33 -0
  274. package/src/transaction.ts +2 -0
  275. package/src/types/alpaca.ts +8 -1
  276. package/src/types/bridge.ts +6 -1
  277. package/src/types/index.ts +2 -0
  278. package/src/types/logic.ts +44 -0
  279. package/src/types/mirror.ts +35 -0
  280. package/src/types/thirdweb.ts +36 -0
@@ -1,12 +1,12 @@
1
1
  import { getTokenFromAsset } from "./getTokenFromAsset";
2
- import { getMockedCurrency, getMockedTokenCurrency } from "../test/fixtures/currency.fixture";
2
+ import { getMockedCurrency, getMockedHTSTokenCurrency } from "../test/fixtures/currency.fixture";
3
3
  import { setupMockCryptoAssetsStore } from "@ledgerhq/cryptoassets/cal-client/test-helpers";
4
4
 
5
5
  setupMockCryptoAssetsStore();
6
6
 
7
7
  describe("getTokenFromAsset", () => {
8
8
  const mockCurrency = getMockedCurrency();
9
- const mockToken = getMockedTokenCurrency();
9
+ const mockToken = getMockedHTSTokenCurrency();
10
10
 
11
11
  beforeEach(() => {
12
12
  jest.clearAllMocks();
@@ -1,5 +1,5 @@
1
1
  import BigNumber from "bignumber.js";
2
- import type { Operation } from "@ledgerhq/types-live";
2
+ import type { Operation, OperationType } from "@ledgerhq/types-live";
3
3
  import type { CryptoCurrency } from "@ledgerhq/types-cryptoassets";
4
4
  import type { Pagination } from "@ledgerhq/coin-framework/api/types";
5
5
  import { getCryptoAssetsStore } from "@ledgerhq/cryptoassets/state";
@@ -10,6 +10,11 @@ import { parseTransfers } from "../network/utils";
10
10
  import type { HederaMirrorToken, HederaMirrorTransaction, HederaOperationExtra } from "../types";
11
11
  import { base64ToUrlSafeBase64, getMemoFromBase64, getSyntheticBlock } from "./utils";
12
12
 
13
+ const txNameToCustomOperationType: Record<string, OperationType> = {
14
+ TOKENASSOCIATE: "ASSOCIATE_TOKEN",
15
+ CONTRACTCALL: "CONTRACT_CALL",
16
+ };
17
+
13
18
  function getCommonOperationData(
14
19
  rawTx: HederaMirrorTransaction,
15
20
  useEncodedHash: boolean,
@@ -26,6 +31,7 @@ function getCommonOperationData(
26
31
  const extra: HederaOperationExtra = {
27
32
  pagingToken: rawTx.consensus_timestamp,
28
33
  consensusTimestamp: rawTx.consensus_timestamp,
34
+ transactionId: rawTx.transaction_id,
29
35
  ...(memo && { memo }),
30
36
  };
31
37
 
@@ -134,14 +140,12 @@ function processTransfers({
134
140
  const { type, value, senders, recipients } = parseTransfers(transfers, address);
135
141
  const { hash, fee, timestamp, blockHeight, blockHash, hasFailed } = commonData;
136
142
  const extra = { ...commonData.extra };
137
- let operationType = type;
143
+ const operationType = txNameToCustomOperationType[rawTx.name] ?? type;
138
144
 
139
145
  // try to enrich ASSOCIATE_TOKEN operation with extra.associatedTokenId
140
146
  // this value is used by custom OperationDetails components in Hedera family
141
147
  // accounts or contracts must first associate with an HTS token before they can receive or send that token; without association, token transfers fail
142
- if (rawTx.name === "TOKENASSOCIATE") {
143
- operationType = "ASSOCIATE_TOKEN";
144
-
148
+ if (operationType === "ASSOCIATE_TOKEN") {
145
149
  const relatedMirrorToken = mirrorTokens.find(t => {
146
150
  return t.created_timestamp === rawTx.consensus_timestamp;
147
151
  });
@@ -1,13 +1,18 @@
1
1
  import BigNumber from "bignumber.js";
2
2
  import { createHash } from "crypto";
3
- import { Transaction } from "@hashgraph/sdk";
3
+ import { Transaction, TransactionId } from "@hashgraph/sdk";
4
4
  import type { AssetInfo } from "@ledgerhq/coin-framework/api/types";
5
5
  import { getCryptoCurrencyById } from "@ledgerhq/cryptoassets/currencies";
6
6
  import { InvalidAddress } from "@ledgerhq/errors";
7
7
  import { HEDERA_TRANSACTION_MODES, SYNTHETIC_BLOCK_WINDOW_SECONDS } from "../constants";
8
+ import { HederaRecipientInvalidChecksum } from "../errors";
8
9
  import { apiClient } from "../network/api";
10
+ import { rpcClient } from "../network/rpc";
9
11
  import { getMockedOperation } from "../test/fixtures/operation.fixture";
10
- import { getMockedTokenCurrency } from "../test/fixtures/currency.fixture";
12
+ import {
13
+ getMockedERC20TokenCurrency,
14
+ getMockedHTSTokenCurrency,
15
+ } from "../test/fixtures/currency.fixture";
11
16
  import { getMockedAccount, getMockedTokenAccount } from "../test/fixtures/account.fixture";
12
17
  import {
13
18
  serializeSignature,
@@ -25,16 +30,22 @@ import {
25
30
  checkAccountTokenAssociationStatus,
26
31
  safeParseAccountId,
27
32
  getSyntheticBlock,
33
+ fromEVMAddress,
34
+ toEVMAddress,
35
+ formatTransactionId,
28
36
  } from "./utils";
29
- import { HederaRecipientInvalidChecksum } from "../errors";
30
37
 
31
38
  jest.mock("../network/api");
32
39
 
33
- describe("utils", () => {
40
+ describe("logic utils", () => {
34
41
  beforeEach(() => {
35
42
  jest.clearAllMocks();
36
43
  });
37
44
 
45
+ afterAll(() => {
46
+ rpcClient._resetInstance();
47
+ });
48
+
38
49
  describe("signature serialization", () => {
39
50
  it("should serialize a signature to base64", () => {
40
51
  const signature = new Uint8Array([1, 2, 3, 4, 5]);
@@ -84,49 +95,49 @@ describe("utils", () => {
84
95
  expect(deserialized).toBe(mockTransaction);
85
96
  });
86
97
  });
87
- });
88
98
 
89
- describe("getOperationValue", () => {
90
- const nativeAsset: AssetInfo = { type: "native" };
91
- const tokenAsset: AssetInfo = { type: "hts", assetReference: "0.0.1234" };
99
+ describe("getOperationValue", () => {
100
+ const nativeAsset: AssetInfo = { type: "native" };
101
+ const tokenAsset: AssetInfo = { type: "hts", assetReference: "0.0.1234" };
92
102
 
93
- it("should return 0 for FEES operations", () => {
94
- const operation = getMockedOperation({
95
- type: "FEES",
96
- value: BigNumber(0),
97
- fee: BigNumber(100),
103
+ it("should return 0 for FEES operations", () => {
104
+ const operation = getMockedOperation({
105
+ type: "FEES",
106
+ value: BigNumber(0),
107
+ fee: BigNumber(100),
108
+ });
109
+
110
+ expect(getOperationValue({ asset: nativeAsset, operation })).toBe(BigInt(0));
111
+ expect(getOperationValue({ asset: tokenAsset, operation })).toBe(BigInt(0));
98
112
  });
99
113
 
100
- expect(getOperationValue({ asset: nativeAsset, operation })).toBe(BigInt(0));
101
- expect(getOperationValue({ asset: tokenAsset, operation })).toBe(BigInt(0));
102
- });
114
+ it("should substract fee from value for native OUT operations", () => {
115
+ const operation = getMockedOperation({
116
+ type: "OUT",
117
+ value: BigNumber(1000),
118
+ fee: BigNumber(100),
119
+ });
103
120
 
104
- it("should substract fee from value for native OUT operations", () => {
105
- const operation = getMockedOperation({
106
- type: "OUT",
107
- value: BigNumber(1000),
108
- fee: BigNumber(100),
121
+ expect(getOperationValue({ asset: nativeAsset, operation })).toBe(BigInt(900));
109
122
  });
110
123
 
111
- expect(getOperationValue({ asset: nativeAsset, operation })).toBe(BigInt(900));
112
- });
124
+ it("should return value for other operations", () => {
125
+ const operationOut = getMockedOperation({
126
+ type: "OUT",
127
+ value: BigNumber(500),
128
+ fee: BigNumber(20),
129
+ });
113
130
 
114
- it("should return value for other operations", () => {
115
- const operationOut = getMockedOperation({
116
- type: "OUT",
117
- value: BigNumber(500),
118
- fee: BigNumber(20),
119
- });
131
+ const operationIn = getMockedOperation({
132
+ type: "IN",
133
+ value: BigNumber(800),
134
+ fee: BigNumber(30),
135
+ });
120
136
 
121
- const operationIn = getMockedOperation({
122
- type: "IN",
123
- value: BigNumber(800),
124
- fee: BigNumber(30),
137
+ expect(getOperationValue({ asset: tokenAsset, operation: operationOut })).toBe(BigInt(500));
138
+ expect(getOperationValue({ asset: tokenAsset, operation: operationIn })).toBe(BigInt(800));
139
+ expect(getOperationValue({ asset: nativeAsset, operation: operationIn })).toBe(BigInt(800));
125
140
  });
126
-
127
- expect(getOperationValue({ asset: tokenAsset, operation: operationOut })).toBe(BigInt(500));
128
- expect(getOperationValue({ asset: tokenAsset, operation: operationIn })).toBe(BigInt(800));
129
- expect(getOperationValue({ asset: nativeAsset, operation: operationIn })).toBe(BigInt(800));
130
141
  });
131
142
 
132
143
  describe("getMemoFromBase64", () => {
@@ -157,7 +168,7 @@ describe("getOperationValue", () => {
157
168
  });
158
169
 
159
170
  describe("getTransactionExplorer", () => {
160
- test("Tx explorer URL is converted from hash to consensus timestamp", async () => {
171
+ it("Tx explorer URL is converted from hash to consensus timestamp", async () => {
161
172
  const explorerView = getCryptoCurrencyById("hedera").explorerViews[0];
162
173
  expect(explorerView).toEqual({
163
174
  tx: expect.any(String),
@@ -172,7 +183,7 @@ describe("getOperationValue", () => {
172
183
  expect(newUrl).toBe("https://hashscan.io/mainnet/transaction/1.2.3.4");
173
184
  });
174
185
 
175
- test("Tx explorer URL is based on transaction id if consensus timestamp is not available", async () => {
186
+ it("Tx explorer URL is based on transaction id if consensus timestamp is not available", async () => {
176
187
  const explorerView = getCryptoCurrencyById("hedera").explorerViews[0];
177
188
  expect(explorerView).toEqual({
178
189
  tx: expect.any(String),
@@ -189,7 +200,7 @@ describe("getOperationValue", () => {
189
200
  });
190
201
 
191
202
  describe("isTokenAssociateTransaction", () => {
192
- test("returns correct value based on tx.properties", () => {
203
+ it("returns correct value based on tx.properties", () => {
193
204
  expect(
194
205
  isTokenAssociateTransaction({ mode: HEDERA_TRANSACTION_MODES.TokenAssociate } as any),
195
206
  ).toBe(true);
@@ -201,7 +212,7 @@ describe("getOperationValue", () => {
201
212
  });
202
213
 
203
214
  describe("isAutoTokenAssociationEnabled", () => {
204
- test("returns value based on isAutoTokenAssociationEnabled flag", () => {
215
+ it("returns value based on isAutoTokenAssociationEnabled flag", () => {
205
216
  expect(
206
217
  isAutoTokenAssociationEnabled({
207
218
  hederaResources: { isAutoTokenAssociationEnabled: true },
@@ -219,16 +230,16 @@ describe("getOperationValue", () => {
219
230
  });
220
231
 
221
232
  describe("isTokenAssociationRequired", () => {
222
- test("should return false if token is already associated (token account exists)", () => {
223
- const mockedTokenCurrency = getMockedTokenCurrency();
233
+ it("should return false if token is already associated (token account exists)", () => {
234
+ const mockedTokenCurrency = getMockedHTSTokenCurrency();
224
235
  const mockedTokenAccount = getMockedTokenAccount(mockedTokenCurrency);
225
236
  const mockedAccount = getMockedAccount({ subAccounts: [mockedTokenAccount] });
226
237
 
227
238
  expect(isTokenAssociationRequired(mockedAccount, mockedTokenCurrency)).toBe(false);
228
239
  });
229
240
 
230
- test("should return false if auto token associations are enabled", () => {
231
- const mockedTokenCurrency = getMockedTokenCurrency();
241
+ it("should return false if auto token associations are enabled", () => {
242
+ const mockedTokenCurrency = getMockedHTSTokenCurrency();
232
243
  const mockedAccount = getMockedAccount({
233
244
  subAccounts: [],
234
245
  hederaResources: {
@@ -240,21 +251,28 @@ describe("getOperationValue", () => {
240
251
  expect(isTokenAssociationRequired(mockedAccount, mockedTokenCurrency)).toBe(false);
241
252
  });
242
253
 
243
- test("should return true if token is not associated and auto associations are disabled", () => {
244
- const mockedTokenCurrency = getMockedTokenCurrency();
254
+ it("should return true if token is not associated and auto associations are disabled", () => {
255
+ const mockedTokenCurrency = getMockedHTSTokenCurrency();
245
256
  const mockedAccount = getMockedAccount({ subAccounts: [] });
246
257
 
247
258
  expect(isTokenAssociationRequired(mockedAccount, mockedTokenCurrency)).toBe(true);
248
259
  });
249
260
 
250
- test("should return false if token is undefined", () => {
261
+ it("should return false for erc20 token", () => {
262
+ const mockedTokenCurrency = getMockedERC20TokenCurrency();
263
+ const mockedAccount = getMockedAccount({ subAccounts: [] });
264
+
265
+ expect(isTokenAssociationRequired(mockedAccount, mockedTokenCurrency)).toBe(false);
266
+ });
267
+
268
+ it("should return false if token is undefined", () => {
251
269
  const mockedAccount = getMockedAccount({ subAccounts: [] });
252
270
 
253
271
  expect(isTokenAssociationRequired(mockedAccount, undefined)).toBe(false);
254
272
  });
255
273
 
256
- test("should return false for legacy accounts without subAccounts or hederaResources", () => {
257
- const mockedTokenCurrency = getMockedTokenCurrency();
274
+ it("should return false for legacy accounts without subAccounts or hederaResources", () => {
275
+ const mockedTokenCurrency = getMockedHTSTokenCurrency();
258
276
  const mockedAccount = getMockedAccount();
259
277
 
260
278
  delete mockedAccount.subAccounts;
@@ -265,7 +283,7 @@ describe("getOperationValue", () => {
265
283
  });
266
284
 
267
285
  describe("isValidExtra", () => {
268
- test("returns true for object and false for invalid types", () => {
286
+ it("returns true for object and false for invalid types", () => {
269
287
  expect(isValidExtra({ some: "value" })).toBe(true);
270
288
  expect(isValidExtra(null)).toBe(false);
271
289
  expect(isValidExtra(undefined)).toBe(false);
@@ -276,7 +294,7 @@ describe("getOperationValue", () => {
276
294
  });
277
295
 
278
296
  describe("sendRecipientCanNext", () => {
279
- test("handles association warnings", () => {
297
+ it("handles association warnings", () => {
280
298
  expect(sendRecipientCanNext({ warnings: {} } as any)).toBe(true);
281
299
  expect(sendRecipientCanNext({ warnings: { missingAssociation: new Error() } } as any)).toBe(
282
300
  false,
@@ -289,15 +307,19 @@ describe("getOperationValue", () => {
289
307
 
290
308
  describe("checkAccountTokenAssociationStatus", () => {
291
309
  const accountId = "0.0.1234";
292
- const tokenId = "0.0.5678";
310
+ const htsToken = getMockedHTSTokenCurrency({ contractAddress: "0.0.1234", tokenType: "hts" });
311
+ const erc20Token = getMockedHTSTokenCurrency({
312
+ contractAddress: "0.0.4321",
313
+ tokenType: "erc20",
314
+ });
293
315
 
294
316
  beforeEach(() => {
295
317
  jest.clearAllMocks();
296
318
  // reset LRU cache to make sure all tests receive correct mocks from mockedGetAccount
297
- checkAccountTokenAssociationStatus.clear(`${accountId}-${tokenId}`);
319
+ checkAccountTokenAssociationStatus.clear(`${accountId}-${htsToken.contractAddress}`);
298
320
  });
299
321
 
300
- test("returns true if max_automatic_token_associations === -1", async () => {
322
+ it("returns true if max_automatic_token_associations === -1", async () => {
301
323
  (apiClient.getAccount as jest.Mock).mockResolvedValueOnce({
302
324
  account: accountId,
303
325
  max_automatic_token_associations: -1,
@@ -308,41 +330,47 @@ describe("getOperationValue", () => {
308
330
  },
309
331
  });
310
332
 
311
- const result = await checkAccountTokenAssociationStatus(accountId, tokenId);
333
+ const result = await checkAccountTokenAssociationStatus(accountId, htsToken);
312
334
  expect(result).toBe(true);
313
335
  });
314
336
 
315
- test("returns true if token is already associated", async () => {
337
+ it("returns true if token is already associated", async () => {
316
338
  (apiClient.getAccount as jest.Mock).mockResolvedValueOnce({
317
339
  account: accountId,
318
340
  max_automatic_token_associations: 0,
319
341
  balance: {
320
342
  balance: 1,
321
343
  timestamp: "",
322
- tokens: [{ token_id: tokenId, balance: 1 }],
344
+ tokens: [{ token_id: htsToken.contractAddress, balance: 1 }],
323
345
  },
324
346
  });
325
347
 
326
- const result = await checkAccountTokenAssociationStatus(accountId, tokenId);
348
+ const result = await checkAccountTokenAssociationStatus(accountId, htsToken);
327
349
  expect(result).toBe(true);
328
350
  });
329
351
 
330
- test("returns false if token is not associated", async () => {
352
+ it("returns false if token is not associated", async () => {
331
353
  (apiClient.getAccount as jest.Mock).mockResolvedValueOnce({
332
354
  account: accountId,
333
355
  max_automatic_token_associations: 0,
334
356
  balance: {
335
357
  balance: 1,
336
358
  timestamp: "",
337
- tokens: [{ token_id: "0.0.9999", balance: 1 }],
359
+ tokens: [{ token_id: "0.1234", balance: 1 }],
338
360
  },
339
361
  });
340
362
 
341
- const result = await checkAccountTokenAssociationStatus(accountId, tokenId);
363
+ const result = await checkAccountTokenAssociationStatus(accountId, htsToken);
342
364
  expect(result).toBe(false);
343
365
  });
344
366
 
345
- test("supports addresses with checksum", async () => {
367
+ it("returns true for erc20 tokens", async () => {
368
+ const result = await checkAccountTokenAssociationStatus(accountId, erc20Token);
369
+ expect(apiClient.getAccount as jest.Mock).not.toHaveBeenCalled();
370
+ expect(result).toBe(true);
371
+ });
372
+
373
+ it("supports addresses with checksum", async () => {
346
374
  const addressWithChecksum = "0.0.9124531-xrxlv";
347
375
 
348
376
  (apiClient.getAccount as jest.Mock).mockResolvedValueOnce({
@@ -351,18 +379,18 @@ describe("getOperationValue", () => {
351
379
  balance: {
352
380
  balance: 1,
353
381
  timestamp: "",
354
- tokens: [{ token_id: "0.0.9999", balance: 1 }],
382
+ tokens: [{ token_id: htsToken.contractAddress, balance: 1 }],
355
383
  },
356
384
  });
357
385
 
358
- await checkAccountTokenAssociationStatus(addressWithChecksum, tokenId);
386
+ await checkAccountTokenAssociationStatus(addressWithChecksum, htsToken);
359
387
  expect(apiClient.getAccount).toHaveBeenCalledTimes(1);
360
388
  expect(apiClient.getAccount).toHaveBeenCalledWith("0.0.9124531");
361
389
  });
362
390
  });
363
391
 
364
392
  describe("safeParseAccountId", () => {
365
- test("returns account id and no checksum for valid address without checksum", () => {
393
+ it("returns account id and no checksum for valid address without checksum", () => {
366
394
  const [error, result] = safeParseAccountId("0.0.9124531");
367
395
 
368
396
  expect(error).toBeNull();
@@ -370,7 +398,7 @@ describe("getOperationValue", () => {
370
398
  expect(result?.checksum).toBeNull();
371
399
  });
372
400
 
373
- test("returns account id and checksum for valid address with correct checksum", () => {
401
+ it("returns account id and checksum for valid address with correct checksum", () => {
374
402
  const [error, result] = safeParseAccountId("0.0.9124531-xrxlv");
375
403
 
376
404
  expect(error).toBeNull();
@@ -378,14 +406,14 @@ describe("getOperationValue", () => {
378
406
  expect(result?.checksum).toBe("xrxlv");
379
407
  });
380
408
 
381
- test("returns error for valid address with incorrect checksum", () => {
409
+ it("returns error for valid address with incorrect checksum", () => {
382
410
  const [error, accountId] = safeParseAccountId("0.0.9124531-invld");
383
411
 
384
412
  expect(error).toBeInstanceOf(HederaRecipientInvalidChecksum);
385
413
  expect(accountId).toBeNull();
386
414
  });
387
415
 
388
- test("returns error for invalid address format", () => {
416
+ it("returns error for invalid address format", () => {
389
417
  const [error, accountId] = safeParseAccountId("not-a-valid-address");
390
418
 
391
419
  expect(error).toBeInstanceOf(InvalidAddress);
@@ -429,4 +457,64 @@ describe("getOperationValue", () => {
429
457
  expect(() => getSyntheticBlock("")).toThrow();
430
458
  });
431
459
  });
460
+
461
+ describe("formatTransactionId", () => {
462
+ it("converts SDK TransactionId format to mirror node format", () => {
463
+ const mockTransactionId = {
464
+ toString: () => "0.0.8835924@1759825731.231952875",
465
+ } as TransactionId;
466
+
467
+ const result = formatTransactionId(mockTransactionId);
468
+ expect(result).toBe("0.0.8835924-1759825731-231952875");
469
+ });
470
+
471
+ it("handles different account ID formats", () => {
472
+ const mockTransactionId = {
473
+ toString: () => "0.0.1@1234567890.987654321",
474
+ } as TransactionId;
475
+
476
+ const result = formatTransactionId(mockTransactionId);
477
+ expect(result).toBe("0.0.1-1234567890-987654321");
478
+ });
479
+ });
480
+
481
+ describe("toEVMAddress", () => {
482
+ it("returns correct EVM address for valid Hedera account ID", () => {
483
+ const evmAddress = toEVMAddress("0.0.12345");
484
+ expect(evmAddress).toBe("0x0000000000000000000000000000000000003039");
485
+ });
486
+
487
+ it("returns null for invalid Hedera account ID", () => {
488
+ const evmAddress = toEVMAddress("invalid_account_id");
489
+ expect(evmAddress).toBeNull();
490
+ });
491
+ });
492
+
493
+ describe("fromEVMAddress", () => {
494
+ it("should convert a long-zero EVM address to Hedera account ID", () => {
495
+ const evmAddress = "0x00000000000000000000000000000000008b3ab3";
496
+ const result = fromEVMAddress(evmAddress);
497
+ expect(result).toBe("0.0.9124531");
498
+ });
499
+
500
+ it("should return null for non-long-zero EVM address", () => {
501
+ const evmAddress = "0xae2e616828973ec543bbce40cf640c012c5a3805";
502
+ const result = fromEVMAddress(evmAddress, 0, 0);
503
+ expect(result).toBeNull();
504
+ });
505
+
506
+ it("should handle custom shard and realm values", () => {
507
+ const evmAddress = "0x0000000000000000000000000000000000000064";
508
+ const result = fromEVMAddress(evmAddress, 1, 2);
509
+ expect(result).toBe("1.2.100");
510
+ });
511
+
512
+ it("should return null for invalid EVM addresses", () => {
513
+ expect(fromEVMAddress("not-an-address")).toBeNull();
514
+ expect(fromEVMAddress("0xInvalid")).toBeNull();
515
+ expect(fromEVMAddress("")).toBeNull();
516
+ expect(fromEVMAddress("1234567890")).toBeNull();
517
+ expect(fromEVMAddress(undefined as unknown as string)).toBeNull();
518
+ });
519
+ });
432
520
  });
@@ -1,7 +1,12 @@
1
1
  import BigNumber from "bignumber.js";
2
2
  import { createHash } from "crypto";
3
3
  import invariant from "invariant";
4
- import { AccountId, Transaction as HederaSDKTransaction } from "@hashgraph/sdk";
4
+ import {
5
+ AccountId,
6
+ EntityIdHelper,
7
+ Transaction as HederaSDKTransaction,
8
+ TransactionId,
9
+ } from "@hashgraph/sdk";
5
10
  import { AssetInfo, TransactionIntent } from "@ledgerhq/coin-framework/api/types";
6
11
  import { findCryptoCurrencyById } from "@ledgerhq/cryptoassets/currencies";
7
12
  import { getFiatCurrencyByTicker } from "@ledgerhq/cryptoassets/fiats";
@@ -75,10 +80,14 @@ export const mapIntentToSDKOperation = (txIntent: TransactionIntent) => {
75
80
  return HEDERA_OPERATION_TYPES.TokenAssociate;
76
81
  }
77
82
 
78
- if (txIntent.type === HEDERA_TRANSACTION_MODES.Send && txIntent.asset.type !== "native") {
83
+ if (txIntent.type === HEDERA_TRANSACTION_MODES.Send && txIntent.asset.type === "hts") {
79
84
  return HEDERA_OPERATION_TYPES.TokenTransfer;
80
85
  }
81
86
 
87
+ if (txIntent.type === HEDERA_TRANSACTION_MODES.Send && txIntent.asset.type === "erc20") {
88
+ return HEDERA_OPERATION_TYPES.ContractCall;
89
+ }
90
+
82
91
  return HEDERA_OPERATION_TYPES.CryptoTransfer;
83
92
  };
84
93
 
@@ -127,6 +136,10 @@ export const isTokenAssociationRequired = (
127
136
  account: AccountLike,
128
137
  token: TokenCurrency | null | undefined,
129
138
  ) => {
139
+ if (token?.tokenType !== "hts") {
140
+ return false;
141
+ }
142
+
130
143
  const subAccounts = !!account && "subAccounts" in account ? account.subAccounts ?? [] : [];
131
144
  const isTokenAssociated = subAccounts.some(item => item.token.id === token?.id);
132
145
 
@@ -170,7 +183,11 @@ export const getCurrencyToUSDRate = makeLRUCache(
170
183
  );
171
184
 
172
185
  export const checkAccountTokenAssociationStatus = makeLRUCache(
173
- async (address: string, tokenId: string) => {
186
+ async (address: string, token: TokenCurrency) => {
187
+ if (token.tokenType !== "hts") {
188
+ return true;
189
+ }
190
+
174
191
  const [parsingError, parsingResult] = safeParseAccountId(address);
175
192
 
176
193
  if (parsingError) {
@@ -185,13 +202,13 @@ export const checkAccountTokenAssociationStatus = makeLRUCache(
185
202
  return true;
186
203
  }
187
204
 
188
- const isTokenAssociated = mirrorAccount.balance.tokens.some(token => {
189
- return token.token_id === tokenId;
205
+ const isTokenAssociated = mirrorAccount.balance.tokens.some(t => {
206
+ return t.token_id === token.contractAddress;
190
207
  });
191
208
 
192
209
  return isTokenAssociated;
193
210
  },
194
- (accountId, tokenId) => `${accountId}-${tokenId}`,
211
+ (accountId, token) => `${accountId}-${token.contractAddress}`,
195
212
  seconds(30),
196
213
  );
197
214
 
@@ -203,16 +220,11 @@ export const safeParseAccountId = (
203
220
 
204
221
  try {
205
222
  const accountId = AccountId.fromString(address);
223
+ const checksum = EntityIdHelper.fromString(address).checksum ?? null;
206
224
 
207
- // verify checksum if present
208
- // FIXME: migrate to EntityIdHelper methods once SDK is upgraded:
209
- // https://github.com/hiero-ledger/hiero-sdk-js/blob/main/src/EntityIdHelper.js#L197
210
- // https://github.com/hiero-ledger/hiero-sdk-js/blob/main/src/EntityIdHelper.js#L446
211
- const checksum: string | null = address.split("-")[1] ?? null;
212
225
  if (checksum) {
213
226
  const client = rpcClient.getInstance();
214
- const recipientWithChecksum = accountId.toStringWithChecksum(client);
215
- const expectedChecksum = recipientWithChecksum.split("-")[1];
227
+ const expectedChecksum = accountId.toStringWithChecksum(client).split("-")[1];
216
228
 
217
229
  if (checksum !== expectedChecksum) {
218
230
  return [new HederaRecipientInvalidChecksum(), null];
@@ -253,3 +265,53 @@ export function getSyntheticBlock(
253
265
 
254
266
  return { blockHeight, blockHash, blockTime };
255
267
  }
268
+
269
+ export const formatTransactionId = (transactionId: TransactionId): string => {
270
+ const [accountId, timestamp] = transactionId.toString().split("@");
271
+ const [secs, nanos] = timestamp.split(".");
272
+
273
+ return `${accountId}-${secs}-${nanos}`;
274
+ };
275
+
276
+ /**
277
+ * Converts a Hedera account ID (e.g. "0.0.1234") into its corresponding EVM address in hexadecimal format.
278
+ * If the conversion fails, it returns null.
279
+ *
280
+ * @param address - Hedera account ID in the format `shard.realm.num`
281
+ * @returns the long-zero EVM address (`0x...`) or null if conversion fails
282
+ */
283
+ export const toEVMAddress = (accountId: string) => {
284
+ try {
285
+ const evmAddress = "0x" + AccountId.fromString(accountId).toEvmAddress();
286
+ return evmAddress;
287
+ } catch {
288
+ return null;
289
+ }
290
+ };
291
+
292
+ /**
293
+ * Converts EVM address in hexadecimal format to its corresponding Hedera account ID.
294
+ * Only long-zero addresses can be mathematically converted back to account IDs.
295
+ * Non-long-zero addresses support would require mirror node call and is not needed for now
296
+ * Uses shard 0 and realm 0 by default for the conversion.
297
+ * If the conversion fails, it returns null.
298
+ *
299
+ * @param evmAddress - EVM address in hexadecimal format (should start with '0x')
300
+ * @param shard - Optional shard ID (defaults to 0)
301
+ * @param realm - Optional realm ID (defaults to 0)
302
+ * @returns Hedera account ID in the format `shard.realm.num` or null if conversion fails
303
+ */
304
+ export const fromEVMAddress = (evmAddress: string, shard = 0, realm = 0): string | null => {
305
+ try {
306
+ const isLongZeroAddress = evmAddress.includes("0".repeat(20));
307
+
308
+ if (!isLongZeroAddress) {
309
+ return null;
310
+ }
311
+
312
+ const accountId = AccountId.fromEvmAddress(shard, realm, evmAddress).toString();
313
+ return accountId;
314
+ } catch {
315
+ return null;
316
+ }
317
+ };