@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
@@ -0,0 +1,404 @@
1
+ import BigNumber from "bignumber.js";
2
+ import { encodeTokenAccountId } from "@ledgerhq/coin-framework/account";
3
+ import { setupMockCryptoAssetsStore } from "@ledgerhq/cryptoassets/cal-client/test-helpers";
4
+ import { encodeOperationId } from "@ledgerhq/coin-framework/operation";
5
+ import { getMockedAccount, getMockedTokenAccount } from "../test/fixtures/account.fixture";
6
+ import { getMockERC20Fields, getMockERC20Operation } from "../test/fixtures/common.fixture";
7
+ import {
8
+ getMockedHTSTokenCurrency,
9
+ getTokenCurrencyFromCALByType,
10
+ } from "../test/fixtures/currency.fixture";
11
+ import { getMockedOperation } from "../test/fixtures/operation.fixture";
12
+ import type { HederaOperationExtra } from "../types";
13
+ import {
14
+ applyPendingExtras,
15
+ classifyERC20Operations,
16
+ mergeSubAccounts,
17
+ patchContractCallOperation,
18
+ patchOperationWithExtra,
19
+ prepareOperations,
20
+ removeDuplicatedContractCallOperations,
21
+ } from "./utils";
22
+
23
+ describe("bridge utils", () => {
24
+ describe("prepareOperations", () => {
25
+ const tokenCurrencyFromCAL = getTokenCurrencyFromCALByType("hts");
26
+
27
+ beforeAll(() => {
28
+ setupMockCryptoAssetsStore({
29
+ findTokenByAddressInCurrency: jest
30
+ .fn()
31
+ .mockImplementation(async () => tokenCurrencyFromCAL),
32
+ });
33
+ });
34
+
35
+ it("links token operation to existing coin operation with matching hash", async () => {
36
+ const mockedTokenAccount = getMockedTokenAccount(tokenCurrencyFromCAL);
37
+ const mockedCoinOperation = getMockedOperation({ hash: "shared" });
38
+ const mockedTokenOperation = getMockedOperation({
39
+ hash: "shared",
40
+ accountId: encodeTokenAccountId(mockedTokenAccount.parentId, tokenCurrencyFromCAL),
41
+ });
42
+
43
+ const result = await prepareOperations([mockedCoinOperation], [mockedTokenOperation]);
44
+
45
+ expect(result).toHaveLength(1);
46
+ expect(result[0].subOperations).toEqual([mockedTokenOperation]);
47
+ });
48
+
49
+ it("creates NONE coin operation as parent if no coin op with matching hash exists", async () => {
50
+ const mockedTokenAccount = getMockedTokenAccount(tokenCurrencyFromCAL);
51
+ const mockedOrphanTokenOperation = getMockedOperation({
52
+ hash: "unknown-hash",
53
+ accountId: encodeTokenAccountId(mockedTokenAccount.parentId, tokenCurrencyFromCAL),
54
+ });
55
+
56
+ const result = await prepareOperations([], [mockedOrphanTokenOperation]);
57
+ const noneOp = result.find(op => op.type === "NONE");
58
+
59
+ expect(typeof noneOp).toBe("object");
60
+ expect(noneOp).not.toBeNull();
61
+ expect(noneOp?.subOperations?.[0]).toEqual(mockedOrphanTokenOperation);
62
+ expect(noneOp?.hash).toBe("unknown-hash");
63
+ });
64
+ });
65
+
66
+ describe("mergeSubAccounts", () => {
67
+ it("returns newSubAccounts if no initial account exists", () => {
68
+ const mockedTokenCurrency1 = getMockedHTSTokenCurrency({ id: "token1" });
69
+ const mockedTokenCurrency2 = getMockedHTSTokenCurrency({ id: "token2" });
70
+ const mockedTokenAccount1 = getMockedTokenAccount(mockedTokenCurrency1, { id: "ta1" });
71
+ const mockedTokenAccount2 = getMockedTokenAccount(mockedTokenCurrency2, { id: "ta2" });
72
+ const initialAccount = undefined;
73
+ const newSubAccounts = [mockedTokenAccount1, mockedTokenAccount2];
74
+
75
+ const result = mergeSubAccounts(initialAccount, newSubAccounts);
76
+
77
+ expect(result).toEqual(newSubAccounts);
78
+ });
79
+
80
+ it("merges operations and updates only changed fields", () => {
81
+ const mockedTokenCurrency = getMockedHTSTokenCurrency();
82
+ const existingOperation = getMockedOperation({ id: "op1" });
83
+ const newOperation = getMockedOperation({ id: "op2" });
84
+ const newPendingOperation = getMockedOperation({ id: "op3" });
85
+ const existingTokenAccount = getMockedTokenAccount(mockedTokenCurrency, {
86
+ id: "tokenaccount",
87
+ balance: new BigNumber(1000),
88
+ creationDate: new Date(),
89
+ operations: [existingOperation],
90
+ pendingOperations: [],
91
+ });
92
+ const updatedTokenAccount = getMockedTokenAccount(mockedTokenCurrency, {
93
+ id: "tokenaccount",
94
+ balance: new BigNumber(2000),
95
+ creationDate: new Date(Date.now() - 24 * 60 * 60 * 1000),
96
+ operations: [newOperation],
97
+ pendingOperations: [newPendingOperation],
98
+ });
99
+ const mockedAccount = getMockedAccount({ subAccounts: [existingTokenAccount] });
100
+
101
+ const result = mergeSubAccounts(mockedAccount, [updatedTokenAccount]);
102
+ const merged = result[0];
103
+
104
+ expect(result).toHaveLength(1);
105
+ expect(merged.creationDate).toEqual(existingTokenAccount.creationDate);
106
+ expect(merged.balance).toEqual(new BigNumber(2000));
107
+ expect(merged.pendingOperations.map(op => op.id)).toEqual(["op3"]);
108
+ expect(merged.operations.map(op => op.id)).toEqual(["op2", "op1"]);
109
+ expect(merged.operationsCount).toEqual(2);
110
+ });
111
+
112
+ it("adds new sub accounts that are not present in initial account", () => {
113
+ const existingToken = getMockedHTSTokenCurrency({ id: "token1" });
114
+ const newToken = getMockedHTSTokenCurrency({ id: "token2" });
115
+ const existingTokenAccount = getMockedTokenAccount(existingToken, { id: "ta1" });
116
+ const newTokenAccount = getMockedTokenAccount(newToken, { id: "ta2" });
117
+ const mockedAccount = getMockedAccount({ subAccounts: [existingTokenAccount] });
118
+
119
+ const result = mergeSubAccounts(mockedAccount, [existingTokenAccount, newTokenAccount]);
120
+
121
+ expect(result.map(sa => sa.id)).toEqual(["ta1", "ta2"]);
122
+ });
123
+ });
124
+
125
+ describe("applyPendingExtras", () => {
126
+ it("merges valid extras from pending operations", () => {
127
+ const opExtra1: HederaOperationExtra = { consensusTimestamp: "1.2.3.4" };
128
+ const pendingExtra1: HederaOperationExtra = { associatedTokenId: "0.0.1234" };
129
+
130
+ const mockedOperation1 = getMockedOperation({ hash: "op1", extra: opExtra1 });
131
+ const mockedPendingOperation1 = getMockedOperation({ hash: "op1", extra: pendingExtra1 });
132
+
133
+ const result = applyPendingExtras([mockedOperation1], [mockedPendingOperation1]);
134
+
135
+ expect(result).toHaveLength(1);
136
+ expect(result[0].extra).toEqual({
137
+ ...mockedOperation1.extra,
138
+ ...mockedPendingOperation1.extra,
139
+ });
140
+ });
141
+
142
+ it("returns original operation if no matching pending is found", () => {
143
+ const opExtra: HederaOperationExtra = { consensusTimestamp: "1.2.3.4" };
144
+ const pendingExtra: HederaOperationExtra = { associatedTokenId: "0.0.1234" };
145
+
146
+ const mockedOperation = getMockedOperation({ hash: "unknown", extra: opExtra });
147
+ const mockedPendingOperation = getMockedOperation({ hash: "op1", extra: pendingExtra });
148
+
149
+ const result = applyPendingExtras([mockedOperation], [mockedPendingOperation]);
150
+ expect(result).toHaveLength(1);
151
+ expect(result[0].extra).toEqual(mockedOperation.extra);
152
+ });
153
+ });
154
+
155
+ describe("patchOperationWithExtra", () => {
156
+ it("adds extra to operation and nested sub operations", () => {
157
+ const mockedOperation = getMockedOperation({
158
+ hash: "parent",
159
+ extra: {},
160
+ subOperations: [getMockedOperation({ hash: "sub1", extra: {} })],
161
+ });
162
+
163
+ const extra: HederaOperationExtra = {
164
+ consensusTimestamp: "12345",
165
+ associatedTokenId: "0.0.1001",
166
+ };
167
+
168
+ const patched = patchOperationWithExtra(mockedOperation, extra);
169
+
170
+ expect(patched.extra).toEqual(extra);
171
+ expect(patched.subOperations).toHaveLength(1);
172
+ expect(patched.subOperations?.[0].extra).toEqual(extra);
173
+ });
174
+ });
175
+
176
+ describe("removeDuplicatedContractCallOperations", () => {
177
+ it("keeps non-CONTRACT_CALL operations", () => {
178
+ const operations = [
179
+ getMockedOperation({ type: "OUT", hash: "hash1" }),
180
+ getMockedOperation({ type: "IN", hash: "hash2" }),
181
+ getMockedOperation({ type: "FEES", hash: "hash3" }),
182
+ ];
183
+ const pendingOperationHashes = new Set<string>();
184
+ const erc20OperationHashes = new Set<string>();
185
+
186
+ const result = removeDuplicatedContractCallOperations(
187
+ operations,
188
+ pendingOperationHashes,
189
+ erc20OperationHashes,
190
+ );
191
+
192
+ expect(result).toHaveLength(3);
193
+ expect(result.map(op => op.hash)).toEqual(["hash1", "hash2", "hash3"]);
194
+ });
195
+
196
+ it("removes CONTRACT_CALL if hash exists in ERC20 operations", () => {
197
+ const operations = [
198
+ getMockedOperation({ type: "CONTRACT_CALL", hash: "duplicate_erc20" }),
199
+ getMockedOperation({ type: "OUT", hash: "unique" }),
200
+ ];
201
+ const pendingOperationHashes = new Set<string>();
202
+ const erc20OperationHashes = new Set(["duplicate_erc20"]);
203
+
204
+ const result = removeDuplicatedContractCallOperations(
205
+ operations,
206
+ pendingOperationHashes,
207
+ erc20OperationHashes,
208
+ );
209
+
210
+ expect(result).toHaveLength(1);
211
+ expect(result).toMatchObject([
212
+ {
213
+ hash: "unique",
214
+ type: "OUT",
215
+ },
216
+ ]);
217
+ });
218
+
219
+ it("removes CONTRACT_CALL if hash exists in pending operations", () => {
220
+ const operations = [
221
+ getMockedOperation({ type: "CONTRACT_CALL", hash: "duplicate_pending" }),
222
+ getMockedOperation({ type: "OUT", hash: "confirmed" }),
223
+ ];
224
+ const pendingOperationHashes = new Set(["duplicate_pending"]);
225
+ const erc20OperationHashes = new Set<string>();
226
+
227
+ const result = removeDuplicatedContractCallOperations(
228
+ operations,
229
+ pendingOperationHashes,
230
+ erc20OperationHashes,
231
+ );
232
+
233
+ expect(result).toHaveLength(1);
234
+ expect(result[0].hash).toBe("confirmed");
235
+ });
236
+
237
+ it("keeps CONTRACT_CALL if hash is unique", () => {
238
+ const operations = [
239
+ getMockedOperation({ type: "CONTRACT_CALL", hash: "unique_contract_call" }),
240
+ getMockedOperation({ type: "OUT", hash: "regular_out" }),
241
+ ];
242
+ const pendingOperationHashes = new Set<string>();
243
+ const erc20OperationHashes = new Set<string>();
244
+
245
+ const result = removeDuplicatedContractCallOperations(
246
+ operations,
247
+ pendingOperationHashes,
248
+ erc20OperationHashes,
249
+ );
250
+
251
+ expect(result).toHaveLength(2);
252
+ expect(result.find(op => op.type === "CONTRACT_CALL")?.hash).toBe("unique_contract_call");
253
+ });
254
+ });
255
+
256
+ describe("classifyERC20Operations", () => {
257
+ const evmAddress = "0x1234567890abcdef";
258
+ const tokenCurrency = getTokenCurrencyFromCALByType("erc20");
259
+
260
+ it("classifies to 'add' when no matching operation exists", () => {
261
+ const mockERC20Operations = [
262
+ getMockERC20Operation({
263
+ hash: "newTxHash",
264
+ from: "0xOTHER",
265
+ to: evmAddress,
266
+ token: tokenCurrency,
267
+ }),
268
+ ];
269
+ const operationsByHash = new Map();
270
+
271
+ const { erc20OperationsToPatch, erc20OperationsToAdd } = classifyERC20Operations({
272
+ latestERC20Operations: mockERC20Operations,
273
+ operationsByHash,
274
+ evmAccountAddress: evmAddress,
275
+ });
276
+
277
+ expect(erc20OperationsToPatch.size).toBe(0);
278
+ expect(erc20OperationsToAdd.size).toBe(1);
279
+ expect(erc20OperationsToAdd.has("newTxHash")).toBe(true);
280
+ });
281
+
282
+ it("classifies to 'patch' when CONTRACT_CALL exists without blockHash", () => {
283
+ const hash = "patchableHash";
284
+ const erc20Ops = [
285
+ getMockERC20Operation({
286
+ hash,
287
+ from: evmAddress,
288
+ to: "0xRECIPIENT",
289
+ token: tokenCurrency,
290
+ }),
291
+ ];
292
+ const existingOp = getMockedOperation({
293
+ type: "CONTRACT_CALL",
294
+ hash,
295
+ blockHash: undefined,
296
+ });
297
+ const operationsByHash = new Map([[hash, existingOp]]);
298
+
299
+ const { erc20OperationsToPatch, erc20OperationsToAdd } = classifyERC20Operations({
300
+ latestERC20Operations: erc20Ops,
301
+ operationsByHash,
302
+ evmAccountAddress: evmAddress,
303
+ });
304
+
305
+ expect(erc20OperationsToPatch.size).toBe(1);
306
+ expect(erc20OperationsToPatch.has(hash)).toBe(true);
307
+ expect(erc20OperationsToAdd.size).toBe(0);
308
+ });
309
+
310
+ it("does NOT patch if operation already has blockHash", () => {
311
+ const hash = "alreadyEnriched";
312
+ const erc20Ops = [
313
+ getMockERC20Operation({
314
+ hash,
315
+ from: evmAddress,
316
+ to: "0xRECIPIENT",
317
+ token: tokenCurrency,
318
+ }),
319
+ ];
320
+ const existingOp = getMockedOperation({
321
+ type: "CONTRACT_CALL",
322
+ hash,
323
+ blockHash: "0xEXISTING_BLOCK",
324
+ });
325
+ const operationsByHash = new Map([[hash, existingOp]]);
326
+
327
+ const { erc20OperationsToPatch, erc20OperationsToAdd } = classifyERC20Operations({
328
+ latestERC20Operations: erc20Ops,
329
+ operationsByHash,
330
+ evmAccountAddress: evmAddress,
331
+ });
332
+
333
+ expect(erc20OperationsToPatch.size).toBe(0);
334
+ expect(erc20OperationsToAdd.size).toBe(0);
335
+ });
336
+
337
+ it("does NOT patch IN operations (only OUT)", () => {
338
+ const hash = "incomingTx";
339
+ const erc20Ops = [
340
+ getMockERC20Operation({
341
+ hash,
342
+ from: "0xSENDER",
343
+ to: evmAddress,
344
+ token: tokenCurrency,
345
+ }),
346
+ ];
347
+ const existingOp = getMockedOperation({
348
+ type: "CONTRACT_CALL",
349
+ hash,
350
+ blockHash: undefined,
351
+ });
352
+ const operationsByHash = new Map([[hash, existingOp]]);
353
+
354
+ const { erc20OperationsToPatch, erc20OperationsToAdd } = classifyERC20Operations({
355
+ latestERC20Operations: erc20Ops,
356
+ operationsByHash,
357
+ evmAccountAddress: evmAddress,
358
+ });
359
+
360
+ expect(erc20OperationsToPatch.size).toBe(0);
361
+ expect(erc20OperationsToAdd.size).toBe(0);
362
+ });
363
+ });
364
+
365
+ describe("patchContractCallOperation", () => {
366
+ it("patches all required fields on existing operation", () => {
367
+ const ledgerAccountId = "js:2:hedera:0.0.12345:";
368
+ const hash = "txHash123";
369
+ const erc20Fields = getMockERC20Fields();
370
+ const tokenOperation = getMockedOperation({ type: "OUT", hash, standard: "erc20" });
371
+ const existingOp = getMockedOperation({
372
+ type: "CONTRACT_CALL",
373
+ hash,
374
+ blockHash: undefined,
375
+ extra: { memo: "original memo" },
376
+ });
377
+
378
+ patchContractCallOperation({
379
+ relatedExistingOperation: existingOp,
380
+ ledgerAccountId,
381
+ hash,
382
+ erc20Fields,
383
+ tokenOperation,
384
+ });
385
+
386
+ expect(existingOp).toMatchObject({
387
+ type: "FEES",
388
+ id: encodeOperationId(ledgerAccountId, hash, "FEES"),
389
+ hash,
390
+ value: erc20Fields.fee,
391
+ fee: erc20Fields.fee,
392
+ recipients: erc20Fields.recipients,
393
+ senders: erc20Fields.senders,
394
+ blockHash: erc20Fields.blockHash,
395
+ blockHeight: erc20Fields.blockHeight,
396
+ date: erc20Fields.date,
397
+ extra: erc20Fields.extra,
398
+ standard: "erc20",
399
+ hasFailed: false,
400
+ subOperations: [tokenOperation],
401
+ });
402
+ });
403
+ });
404
+ });