@ledgerhq/coin-hedera 1.10.1 → 1.10.2-nightly.0

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 (248) hide show
  1. package/.eslintrc.js +1 -0
  2. package/CHANGELOG.md +10 -0
  3. package/lib/api/mirror.d.ts +3 -20
  4. package/lib/api/mirror.d.ts.map +1 -1
  5. package/lib/api/mirror.js +32 -90
  6. package/lib/api/mirror.js.map +1 -1
  7. package/lib/api/mirror.test.js +59 -4
  8. package/lib/api/mirror.test.js.map +1 -1
  9. package/lib/api/network.d.ts +3 -3
  10. package/lib/api/network.d.ts.map +1 -1
  11. package/lib/api/network.js +46 -3
  12. package/lib/api/network.js.map +1 -1
  13. package/lib/api/types.d.ts +44 -0
  14. package/lib/api/types.d.ts.map +1 -0
  15. package/lib/api/types.js +3 -0
  16. package/lib/api/types.js.map +1 -0
  17. package/lib/api/utils.d.ts +8 -0
  18. package/lib/api/utils.d.ts.map +1 -0
  19. package/lib/api/utils.js +132 -0
  20. package/lib/api/utils.js.map +1 -0
  21. package/lib/bridge/broadcast.d.ts.map +1 -1
  22. package/lib/bridge/broadcast.js +2 -0
  23. package/lib/bridge/broadcast.js.map +1 -1
  24. package/lib/bridge/buildOptimisticOperation.d.ts +2 -2
  25. package/lib/bridge/buildOptimisticOperation.d.ts.map +1 -1
  26. package/lib/bridge/buildOptimisticOperation.integration.test.d.ts +2 -0
  27. package/lib/bridge/buildOptimisticOperation.integration.test.d.ts.map +1 -0
  28. package/lib/bridge/buildOptimisticOperation.integration.test.js +82 -0
  29. package/lib/bridge/buildOptimisticOperation.integration.test.js.map +1 -0
  30. package/lib/bridge/buildOptimisticOperation.js +87 -5
  31. package/lib/bridge/buildOptimisticOperation.js.map +1 -1
  32. package/lib/bridge/estimateMaxSpendable.d.ts.map +1 -1
  33. package/lib/bridge/estimateMaxSpendable.js +8 -2
  34. package/lib/bridge/estimateMaxSpendable.js.map +1 -1
  35. package/lib/bridge/getTransactionStatus.d.ts +3 -3
  36. package/lib/bridge/getTransactionStatus.d.ts.map +1 -1
  37. package/lib/bridge/getTransactionStatus.js +116 -23
  38. package/lib/bridge/getTransactionStatus.js.map +1 -1
  39. package/lib/bridge/getTransactionStatus.test.d.ts +2 -0
  40. package/lib/bridge/getTransactionStatus.test.d.ts.map +1 -0
  41. package/lib/bridge/getTransactionStatus.test.js +176 -0
  42. package/lib/bridge/getTransactionStatus.test.js.map +1 -0
  43. package/lib/bridge/index.d.ts +4 -4
  44. package/lib/bridge/index.d.ts.map +1 -1
  45. package/lib/bridge/index.js +9 -6
  46. package/lib/bridge/index.js.map +1 -1
  47. package/lib/bridge/js-estimateMaxSpendable.integration.test.js +28 -44
  48. package/lib/bridge/js-estimateMaxSpendable.integration.test.js.map +1 -1
  49. package/lib/bridge/js-transaction.test.js +10 -49
  50. package/lib/bridge/js-transaction.test.js.map +1 -1
  51. package/lib/bridge/prepareTransaction.d.ts +0 -1
  52. package/lib/bridge/prepareTransaction.d.ts.map +1 -1
  53. package/lib/bridge/prepareTransaction.js +0 -1
  54. package/lib/bridge/prepareTransaction.js.map +1 -1
  55. package/lib/bridge/serialization.d.ts +7 -0
  56. package/lib/bridge/serialization.d.ts.map +1 -0
  57. package/lib/bridge/serialization.js +36 -0
  58. package/lib/bridge/serialization.js.map +1 -0
  59. package/lib/bridge/serialization.test.d.ts +2 -0
  60. package/lib/bridge/serialization.test.d.ts.map +1 -0
  61. package/lib/bridge/serialization.test.js +27 -0
  62. package/lib/bridge/serialization.test.js.map +1 -0
  63. package/lib/bridge/synchronisation.d.ts +3 -3
  64. package/lib/bridge/synchronisation.d.ts.map +1 -1
  65. package/lib/bridge/synchronisation.js +37 -15
  66. package/lib/bridge/synchronisation.js.map +1 -1
  67. package/lib/bridge/transaction.test.js +18 -59
  68. package/lib/bridge/transaction.test.js.map +1 -1
  69. package/lib/bridge/utils.d.ts +22 -8
  70. package/lib/bridge/utils.d.ts.map +1 -1
  71. package/lib/bridge/utils.integration.test.js +415 -73
  72. package/lib/bridge/utils.integration.test.js.map +1 -1
  73. package/lib/bridge/utils.js +300 -15
  74. package/lib/bridge/utils.js.map +1 -1
  75. package/lib/constants.d.ts +32 -0
  76. package/lib/constants.d.ts.map +1 -0
  77. package/lib/constants.js +37 -0
  78. package/lib/constants.js.map +1 -0
  79. package/lib/deviceTransactionConfig.d.ts.map +1 -1
  80. package/lib/deviceTransactionConfig.js +17 -15
  81. package/lib/deviceTransactionConfig.js.map +1 -1
  82. package/lib/logic.d.ts +9 -3
  83. package/lib/logic.d.ts.map +1 -1
  84. package/lib/logic.js +31 -3
  85. package/lib/logic.js.map +1 -1
  86. package/lib/logic.test.js +103 -50
  87. package/lib/logic.test.js.map +1 -1
  88. package/lib/test/fixtures/account.fixture.d.ts +19 -0
  89. package/lib/test/fixtures/account.fixture.d.ts.map +1 -0
  90. package/lib/test/fixtures/account.fixture.js +116 -0
  91. package/lib/test/fixtures/account.fixture.js.map +1 -0
  92. package/lib/test/fixtures/currency.fixture.d.ts +5 -0
  93. package/lib/test/fixtures/currency.fixture.d.ts.map +1 -0
  94. package/lib/test/fixtures/currency.fixture.js +67 -0
  95. package/lib/test/fixtures/currency.fixture.js.map +1 -0
  96. package/lib/test/fixtures/mirror.fixture.d.ts +3 -0
  97. package/lib/test/fixtures/mirror.fixture.d.ts.map +1 -0
  98. package/lib/test/fixtures/mirror.fixture.js +17 -0
  99. package/lib/test/fixtures/mirror.fixture.js.map +1 -0
  100. package/lib/test/fixtures/operation.fixture.d.ts +3 -0
  101. package/lib/test/fixtures/operation.fixture.d.ts.map +1 -0
  102. package/lib/test/fixtures/operation.fixture.js +26 -0
  103. package/lib/test/fixtures/operation.fixture.js.map +1 -0
  104. package/lib/test/fixtures/transaction.fixture.d.ts +4 -0
  105. package/lib/test/fixtures/transaction.fixture.d.ts.map +1 -0
  106. package/lib/test/fixtures/transaction.fixture.js +28 -0
  107. package/lib/test/fixtures/transaction.fixture.js.map +1 -0
  108. package/lib/types/bridge.d.ts +25 -1
  109. package/lib/types/bridge.d.ts.map +1 -1
  110. package/lib-es/api/mirror.d.ts +3 -20
  111. package/lib-es/api/mirror.d.ts.map +1 -1
  112. package/lib-es/api/mirror.js +29 -88
  113. package/lib-es/api/mirror.js.map +1 -1
  114. package/lib-es/api/mirror.test.js +60 -5
  115. package/lib-es/api/mirror.test.js.map +1 -1
  116. package/lib-es/api/network.d.ts +3 -3
  117. package/lib-es/api/network.d.ts.map +1 -1
  118. package/lib-es/api/network.js +44 -4
  119. package/lib-es/api/network.js.map +1 -1
  120. package/lib-es/api/types.d.ts +44 -0
  121. package/lib-es/api/types.d.ts.map +1 -0
  122. package/lib-es/api/types.js +2 -0
  123. package/lib-es/api/types.js.map +1 -0
  124. package/lib-es/api/utils.d.ts +8 -0
  125. package/lib-es/api/utils.d.ts.map +1 -0
  126. package/lib-es/api/utils.js +124 -0
  127. package/lib-es/api/utils.js.map +1 -0
  128. package/lib-es/bridge/broadcast.d.ts.map +1 -1
  129. package/lib-es/bridge/broadcast.js +2 -0
  130. package/lib-es/bridge/broadcast.js.map +1 -1
  131. package/lib-es/bridge/buildOptimisticOperation.d.ts +2 -2
  132. package/lib-es/bridge/buildOptimisticOperation.d.ts.map +1 -1
  133. package/lib-es/bridge/buildOptimisticOperation.integration.test.d.ts +2 -0
  134. package/lib-es/bridge/buildOptimisticOperation.integration.test.d.ts.map +1 -0
  135. package/lib-es/bridge/buildOptimisticOperation.integration.test.js +77 -0
  136. package/lib-es/bridge/buildOptimisticOperation.integration.test.js.map +1 -0
  137. package/lib-es/bridge/buildOptimisticOperation.js +84 -5
  138. package/lib-es/bridge/buildOptimisticOperation.js.map +1 -1
  139. package/lib-es/bridge/estimateMaxSpendable.d.ts.map +1 -1
  140. package/lib-es/bridge/estimateMaxSpendable.js +8 -2
  141. package/lib-es/bridge/estimateMaxSpendable.js.map +1 -1
  142. package/lib-es/bridge/getTransactionStatus.d.ts +3 -3
  143. package/lib-es/bridge/getTransactionStatus.d.ts.map +1 -1
  144. package/lib-es/bridge/getTransactionStatus.js +114 -24
  145. package/lib-es/bridge/getTransactionStatus.js.map +1 -1
  146. package/lib-es/bridge/getTransactionStatus.test.d.ts +2 -0
  147. package/lib-es/bridge/getTransactionStatus.test.d.ts.map +1 -0
  148. package/lib-es/bridge/getTransactionStatus.test.js +148 -0
  149. package/lib-es/bridge/getTransactionStatus.test.js.map +1 -0
  150. package/lib-es/bridge/index.d.ts +4 -4
  151. package/lib-es/bridge/index.d.ts.map +1 -1
  152. package/lib-es/bridge/index.js +9 -6
  153. package/lib-es/bridge/index.js.map +1 -1
  154. package/lib-es/bridge/js-estimateMaxSpendable.integration.test.js +28 -44
  155. package/lib-es/bridge/js-estimateMaxSpendable.integration.test.js.map +1 -1
  156. package/lib-es/bridge/js-transaction.test.js +10 -49
  157. package/lib-es/bridge/js-transaction.test.js.map +1 -1
  158. package/lib-es/bridge/prepareTransaction.d.ts +0 -1
  159. package/lib-es/bridge/prepareTransaction.d.ts.map +1 -1
  160. package/lib-es/bridge/prepareTransaction.js +0 -1
  161. package/lib-es/bridge/prepareTransaction.js.map +1 -1
  162. package/lib-es/bridge/serialization.d.ts +7 -0
  163. package/lib-es/bridge/serialization.d.ts.map +1 -0
  164. package/lib-es/bridge/serialization.js +29 -0
  165. package/lib-es/bridge/serialization.js.map +1 -0
  166. package/lib-es/bridge/serialization.test.d.ts +2 -0
  167. package/lib-es/bridge/serialization.test.d.ts.map +1 -0
  168. package/lib-es/bridge/serialization.test.js +25 -0
  169. package/lib-es/bridge/serialization.test.js.map +1 -0
  170. package/lib-es/bridge/synchronisation.d.ts +3 -3
  171. package/lib-es/bridge/synchronisation.d.ts.map +1 -1
  172. package/lib-es/bridge/synchronisation.js +39 -17
  173. package/lib-es/bridge/synchronisation.js.map +1 -1
  174. package/lib-es/bridge/transaction.test.js +18 -59
  175. package/lib-es/bridge/transaction.test.js.map +1 -1
  176. package/lib-es/bridge/utils.d.ts +22 -8
  177. package/lib-es/bridge/utils.d.ts.map +1 -1
  178. package/lib-es/bridge/utils.integration.test.js +416 -74
  179. package/lib-es/bridge/utils.integration.test.js.map +1 -1
  180. package/lib-es/bridge/utils.js +295 -15
  181. package/lib-es/bridge/utils.js.map +1 -1
  182. package/lib-es/constants.d.ts +32 -0
  183. package/lib-es/constants.d.ts.map +1 -0
  184. package/lib-es/constants.js +34 -0
  185. package/lib-es/constants.js.map +1 -0
  186. package/lib-es/deviceTransactionConfig.d.ts.map +1 -1
  187. package/lib-es/deviceTransactionConfig.js +17 -15
  188. package/lib-es/deviceTransactionConfig.js.map +1 -1
  189. package/lib-es/logic.d.ts +9 -3
  190. package/lib-es/logic.d.ts.map +1 -1
  191. package/lib-es/logic.js +26 -3
  192. package/lib-es/logic.js.map +1 -1
  193. package/lib-es/logic.test.js +104 -51
  194. package/lib-es/logic.test.js.map +1 -1
  195. package/lib-es/test/fixtures/account.fixture.d.ts +19 -0
  196. package/lib-es/test/fixtures/account.fixture.d.ts.map +1 -0
  197. package/lib-es/test/fixtures/account.fixture.js +107 -0
  198. package/lib-es/test/fixtures/account.fixture.js.map +1 -0
  199. package/lib-es/test/fixtures/currency.fixture.d.ts +5 -0
  200. package/lib-es/test/fixtures/currency.fixture.d.ts.map +1 -0
  201. package/lib-es/test/fixtures/currency.fixture.js +58 -0
  202. package/lib-es/test/fixtures/currency.fixture.js.map +1 -0
  203. package/lib-es/test/fixtures/mirror.fixture.d.ts +3 -0
  204. package/lib-es/test/fixtures/mirror.fixture.d.ts.map +1 -0
  205. package/lib-es/test/fixtures/mirror.fixture.js +13 -0
  206. package/lib-es/test/fixtures/mirror.fixture.js.map +1 -0
  207. package/lib-es/test/fixtures/operation.fixture.d.ts +3 -0
  208. package/lib-es/test/fixtures/operation.fixture.d.ts.map +1 -0
  209. package/lib-es/test/fixtures/operation.fixture.js +19 -0
  210. package/lib-es/test/fixtures/operation.fixture.js.map +1 -0
  211. package/lib-es/test/fixtures/transaction.fixture.d.ts +4 -0
  212. package/lib-es/test/fixtures/transaction.fixture.d.ts.map +1 -0
  213. package/lib-es/test/fixtures/transaction.fixture.js +20 -0
  214. package/lib-es/test/fixtures/transaction.fixture.js.map +1 -0
  215. package/lib-es/types/bridge.d.ts +25 -1
  216. package/lib-es/types/bridge.d.ts.map +1 -1
  217. package/package.json +11 -9
  218. package/src/api/mirror.test.ts +79 -5
  219. package/src/api/mirror.ts +30 -111
  220. package/src/api/network.ts +71 -4
  221. package/src/api/types.ts +48 -0
  222. package/src/api/utils.ts +150 -0
  223. package/src/bridge/broadcast.ts +2 -0
  224. package/src/bridge/buildOptimisticOperation.integration.test.ts +88 -0
  225. package/src/bridge/buildOptimisticOperation.ts +118 -7
  226. package/src/bridge/estimateMaxSpendable.ts +8 -2
  227. package/src/bridge/getTransactionStatus.test.ts +200 -0
  228. package/src/bridge/getTransactionStatus.ts +166 -32
  229. package/src/bridge/index.ts +13 -10
  230. package/src/bridge/js-estimateMaxSpendable.integration.test.ts +37 -46
  231. package/src/bridge/js-transaction.test.ts +13 -54
  232. package/src/bridge/prepareTransaction.ts +1 -2
  233. package/src/bridge/serialization.test.ts +39 -0
  234. package/src/bridge/serialization.ts +43 -0
  235. package/src/bridge/synchronisation.ts +65 -27
  236. package/src/bridge/transaction.test.ts +22 -64
  237. package/src/bridge/utils.integration.test.ts +525 -76
  238. package/src/bridge/utils.ts +423 -24
  239. package/src/constants.ts +35 -0
  240. package/src/deviceTransactionConfig.ts +16 -15
  241. package/src/logic.test.ts +147 -57
  242. package/src/logic.ts +58 -7
  243. package/src/test/fixtures/account.fixture.ts +123 -0
  244. package/src/test/fixtures/currency.fixture.ts +66 -0
  245. package/src/test/fixtures/mirror.fixture.ts +14 -0
  246. package/src/test/fixtures/operation.fixture.ts +20 -0
  247. package/src/test/fixtures/transaction.fixture.ts +22 -0
  248. package/src/types/bridge.ts +33 -0
package/src/logic.test.ts CHANGED
@@ -1,62 +1,152 @@
1
- import { BigNumber } from "bignumber.js";
2
- import { Operation } from "@ledgerhq/types-live";
3
1
  import { getCryptoCurrencyById } from "@ledgerhq/cryptoassets";
4
- import { getTransactionExplorer } from "./logic";
5
-
6
- describe("getTransactionExplorer", () => {
7
- test("Tx explorer URL is converted from hash to consensus timestamp", async () => {
8
- const explorerView = getCryptoCurrencyById("hedera").explorerViews[0];
9
- expect(explorerView).toEqual({
10
- tx: expect.any(String),
11
- address: expect.any(String),
12
- });
13
-
14
- const mockOperation: Operation = {
15
- extra: {
16
- consensusTimestamp: "1.2.3.4",
17
- },
18
- id: "",
19
- hash: "",
20
- type: "IN",
21
- value: new BigNumber(0),
22
- fee: new BigNumber(0),
23
- senders: [],
24
- recipients: [],
25
- blockHeight: undefined,
26
- blockHash: undefined,
27
- accountId: "",
28
- date: new Date(),
29
- };
30
-
31
- const newUrl = getTransactionExplorer(explorerView, mockOperation);
32
- expect(newUrl).toBe("https://hashscan.io/mainnet/transaction/1.2.3.4");
2
+ import {
3
+ getTransactionExplorer,
4
+ isAutoTokenAssociationEnabled,
5
+ isTokenAssociateTransaction,
6
+ isTokenAssociationRequired,
7
+ isValidExtra,
8
+ sendRecipientCanNext,
9
+ } from "./logic";
10
+ import { getMockedAccount, getMockedTokenAccount } from "./test/fixtures/account.fixture";
11
+ import { getMockedOperation } from "./test/fixtures/operation.fixture";
12
+ import { getMockedTokenCurrency } from "./test/fixtures/currency.fixture";
13
+ import { HEDERA_TRANSACTION_KINDS } from "./constants";
14
+
15
+ describe("logic", () => {
16
+ describe("getTransactionExplorer", () => {
17
+ test("Tx explorer URL is converted from hash to consensus timestamp", async () => {
18
+ const explorerView = getCryptoCurrencyById("hedera").explorerViews[0];
19
+ expect(explorerView).toEqual({
20
+ tx: expect.any(String),
21
+ address: expect.any(String),
22
+ });
23
+
24
+ const mockedOperation = getMockedOperation({
25
+ extra: { consensusTimestamp: "1.2.3.4" },
26
+ });
27
+
28
+ const newUrl = getTransactionExplorer(explorerView, mockedOperation);
29
+ expect(newUrl).toBe("https://hashscan.io/mainnet/transaction/1.2.3.4");
30
+ });
31
+
32
+ test("Tx explorer URL is based on transaction id if consensus timestamp is not available", async () => {
33
+ const explorerView = getCryptoCurrencyById("hedera").explorerViews[0];
34
+ expect(explorerView).toEqual({
35
+ tx: expect.any(String),
36
+ address: expect.any(String),
37
+ });
38
+
39
+ const mockedOperation = getMockedOperation({
40
+ extra: { transactionId: "0.0.1234567-123-123" },
41
+ });
42
+
43
+ const newUrl = getTransactionExplorer(explorerView, mockedOperation);
44
+ expect(newUrl).toBe("https://hashscan.io/mainnet/transaction/0.0.1234567-123-123");
45
+ });
46
+ });
47
+
48
+ describe("isTokenAssociateTransaction", () => {
49
+ test("returns correct value based on tx.properties", () => {
50
+ expect(
51
+ isTokenAssociateTransaction({
52
+ properties: { name: HEDERA_TRANSACTION_KINDS.TokenAssociate.name },
53
+ } as any),
54
+ ).toBe(true);
55
+
56
+ expect(
57
+ isTokenAssociateTransaction({
58
+ properties: { name: "transfer" },
59
+ } as any),
60
+ ).toBe(false);
61
+
62
+ expect(isTokenAssociateTransaction({} as any)).toBe(false);
63
+ });
64
+ });
65
+
66
+ describe("isAutoTokenAssociationEnabled", () => {
67
+ test("returns value based on isAutoTokenAssociationEnabled flag", () => {
68
+ expect(
69
+ isAutoTokenAssociationEnabled({
70
+ hederaResources: { isAutoTokenAssociationEnabled: true },
71
+ } as any),
72
+ ).toBe(true);
73
+
74
+ expect(
75
+ isAutoTokenAssociationEnabled({
76
+ hederaResources: { isAutoTokenAssociationEnabled: false },
77
+ } as any),
78
+ ).toBe(false);
79
+
80
+ expect(isAutoTokenAssociationEnabled({} as any)).toBe(false);
81
+ });
33
82
  });
34
83
 
35
- test("Tx explorer URL is based on transaction id if consensus timestamp is not available", async () => {
36
- const explorerView = getCryptoCurrencyById("hedera").explorerViews[0];
37
- expect(explorerView).toEqual({
38
- tx: expect.any(String),
39
- address: expect.any(String),
40
- });
41
-
42
- const mockOperation: Operation = {
43
- extra: {
44
- transactionId: "0.0.1234567-123-123",
45
- },
46
- id: "",
47
- hash: "",
48
- type: "IN",
49
- value: new BigNumber(0),
50
- fee: new BigNumber(0),
51
- senders: [],
52
- recipients: [],
53
- blockHeight: undefined,
54
- blockHash: undefined,
55
- accountId: "",
56
- date: new Date(),
57
- };
58
-
59
- const newUrl = getTransactionExplorer(explorerView, mockOperation);
60
- expect(newUrl).toBe("https://hashscan.io/mainnet/transaction/0.0.1234567-123-123");
84
+ describe("isTokenAssociationRequired", () => {
85
+ test("should return false if token is already associated (token account exists)", () => {
86
+ const mockedTokenCurrency = getMockedTokenCurrency();
87
+ const mockedTokenAccount = getMockedTokenAccount(mockedTokenCurrency);
88
+ const mockedAccount = getMockedAccount({ subAccounts: [mockedTokenAccount] });
89
+
90
+ expect(isTokenAssociationRequired(mockedAccount, mockedTokenCurrency)).toBe(false);
91
+ });
92
+
93
+ test("should return false if auto token associations are enabled", () => {
94
+ const mockedTokenCurrency = getMockedTokenCurrency();
95
+ const mockedAccount = getMockedAccount({
96
+ subAccounts: [],
97
+ hederaResources: {
98
+ maxAutomaticTokenAssociations: -1,
99
+ isAutoTokenAssociationEnabled: true,
100
+ },
101
+ });
102
+
103
+ expect(isTokenAssociationRequired(mockedAccount, mockedTokenCurrency)).toBe(false);
104
+ });
105
+
106
+ test("should return true if token is not associated and auto associations are disabled", () => {
107
+ const mockedTokenCurrency = getMockedTokenCurrency();
108
+ const mockedAccount = getMockedAccount({ subAccounts: [] });
109
+
110
+ expect(isTokenAssociationRequired(mockedAccount, mockedTokenCurrency)).toBe(true);
111
+ });
112
+
113
+ test("should return false if token is undefined", () => {
114
+ const mockedAccount = getMockedAccount({ subAccounts: [] });
115
+
116
+ expect(isTokenAssociationRequired(mockedAccount, undefined)).toBe(false);
117
+ });
118
+
119
+ test("should return false for legacy accounts without subAccounts or hederaResources", () => {
120
+ const mockedTokenCurrency = getMockedTokenCurrency();
121
+ const mockedAccount = getMockedAccount();
122
+
123
+ delete mockedAccount.subAccounts;
124
+ delete mockedAccount.hederaResources;
125
+
126
+ expect(isTokenAssociationRequired(mockedAccount, mockedTokenCurrency)).toBe(true);
127
+ });
128
+ });
129
+
130
+ describe("isValidExtra", () => {
131
+ test("returns true for object and false for invalid types", () => {
132
+ expect(isValidExtra({ some: "value" })).toBe(true);
133
+ expect(isValidExtra(null)).toBe(false);
134
+ expect(isValidExtra(undefined)).toBe(false);
135
+ expect(isValidExtra("string")).toBe(false);
136
+ expect(isValidExtra(123)).toBe(false);
137
+ expect(isValidExtra([])).toBe(false);
138
+ });
139
+ });
140
+
141
+ describe("sendRecipientCanNext", () => {
142
+ test("handles association warnings", () => {
143
+ expect(sendRecipientCanNext({ warnings: {} } as any)).toBe(true);
144
+ expect(sendRecipientCanNext({ warnings: { missingAssociation: new Error() } } as any)).toBe(
145
+ false,
146
+ );
147
+ expect(
148
+ sendRecipientCanNext({ warnings: { unverifiedAssociation: new Error() } } as any),
149
+ ).toBe(false);
150
+ });
61
151
  });
62
152
  });
package/src/logic.ts CHANGED
@@ -1,15 +1,66 @@
1
- import { ExplorerView } from "@ledgerhq/types-cryptoassets";
2
- import { Operation } from "@ledgerhq/types-live";
3
-
4
- import { HederaOperationExtra } from "./types";
1
+ import type { ExplorerView, TokenCurrency } from "@ledgerhq/types-cryptoassets";
2
+ import type { AccountLike, Operation } from "@ledgerhq/types-live";
3
+ import { HEDERA_TRANSACTION_KINDS } from "./constants";
4
+ import type {
5
+ HederaAccount,
6
+ HederaOperationExtra,
7
+ TokenAssociateProperties,
8
+ Transaction,
9
+ TransactionStatus,
10
+ } from "./types";
5
11
 
6
12
  const getTransactionExplorer = (
7
13
  explorerView: ExplorerView | null | undefined,
8
14
  operation: Operation,
9
15
  ): string | undefined => {
10
- const extra = operation.extra as HederaOperationExtra;
16
+ const extra = isValidExtra(operation.extra) ? operation.extra : null;
17
+
18
+ return explorerView?.tx?.replace(
19
+ "$hash",
20
+ extra?.consensusTimestamp ?? extra?.transactionId ?? "0",
21
+ );
22
+ };
23
+
24
+ const isTokenAssociateTransaction = (
25
+ tx: Transaction,
26
+ ): tx is Extract<Required<Transaction>, { properties: TokenAssociateProperties }> => {
27
+ return tx.properties?.name === HEDERA_TRANSACTION_KINDS.TokenAssociate.name;
28
+ };
29
+
30
+ const isAutoTokenAssociationEnabled = (account: AccountLike) => {
31
+ const hederaAccount = "hederaResources" in account ? (account as HederaAccount) : null;
11
32
 
12
- return explorerView?.tx?.replace("$hash", extra.consensusTimestamp ?? extra.transactionId ?? "0");
33
+ return hederaAccount?.hederaResources?.isAutoTokenAssociationEnabled ?? false;
13
34
  };
14
35
 
15
- export { getTransactionExplorer };
36
+ const isTokenAssociationRequired = (
37
+ account: AccountLike,
38
+ token: TokenCurrency | null | undefined,
39
+ ) => {
40
+ const subAccounts = !!account && "subAccounts" in account ? account.subAccounts ?? [] : [];
41
+ const isTokenAssociated = subAccounts.some(item => item.token.id === token?.id);
42
+
43
+ return !!token && !isTokenAssociated && !isAutoTokenAssociationEnabled(account);
44
+ };
45
+
46
+ const isValidExtra = (extra: unknown): extra is HederaOperationExtra => {
47
+ return !!extra && typeof extra === "object" && !Array.isArray(extra);
48
+ };
49
+
50
+ // disables the "Continue" button in the Send modal's Recipient step during token transfers if:
51
+ // - the recipient is not associated with the token
52
+ // - the association status can't be verified
53
+ const sendRecipientCanNext = (status: TransactionStatus) => {
54
+ const { missingAssociation, unverifiedAssociation } = status.warnings;
55
+
56
+ return !missingAssociation && !unverifiedAssociation;
57
+ };
58
+
59
+ export {
60
+ sendRecipientCanNext,
61
+ getTransactionExplorer,
62
+ isValidExtra,
63
+ isTokenAssociateTransaction,
64
+ isTokenAssociationRequired,
65
+ isAutoTokenAssociationEnabled,
66
+ };
@@ -0,0 +1,123 @@
1
+ import BigNumber from "bignumber.js";
2
+ import type {
3
+ HederaAccount,
4
+ HederaAccountRaw,
5
+ HederaResources,
6
+ HederaResourcesRaw,
7
+ } from "../../types";
8
+ import type { TokenAccount } from "@ledgerhq/types-live";
9
+ import { getMockedCurrency, getMockedTokenCurrency } from "./currency.fixture";
10
+ import { TokenCurrency } from "@ledgerhq/types-cryptoassets";
11
+
12
+ const defaultMockedCurrency = getMockedCurrency();
13
+ const defaultMockedTokenCurrency = getMockedTokenCurrency();
14
+ const defaultMockAccountId = "js:2:hedera:0.0.1234567:hederaBip44";
15
+ const defaultMockTokenAccountId = `${defaultMockAccountId}+${defaultMockedTokenCurrency.id}`;
16
+ const defaultBalance = new BigNumber(100000000);
17
+ const defaultTokenBalance = new BigNumber(10);
18
+
19
+ export const mockHederaResources: HederaResources = {
20
+ maxAutomaticTokenAssociations: 0,
21
+ isAutoTokenAssociationEnabled: false,
22
+ };
23
+
24
+ export const mockHederaResourcesRaw: HederaResourcesRaw = {
25
+ maxAutomaticTokenAssociations: 0,
26
+ isAutoTokenAssociationEnabled: false,
27
+ };
28
+
29
+ /**
30
+ * default settings:
31
+ * - account balance is 1 HBAR
32
+ * - auto token association is disabled
33
+ * - subAccounts array is empty (no tokens account are used)
34
+ */
35
+ export const getMockedAccount = (overrides?: Partial<HederaAccount>): HederaAccount => {
36
+ return {
37
+ type: "Account",
38
+ id: defaultMockAccountId,
39
+ seedIdentifier: "",
40
+ derivationMode: "",
41
+ index: 0,
42
+ freshAddress: "0.0.12345",
43
+ freshAddressPath: "44/3030",
44
+ used: false,
45
+ balance: defaultBalance,
46
+ spendableBalance: defaultBalance,
47
+ creationDate: new Date(),
48
+ blockHeight: 0,
49
+ currency: defaultMockedCurrency,
50
+ operationsCount: 0,
51
+ operations: [],
52
+ pendingOperations: [],
53
+ lastSyncDate: new Date(),
54
+ balanceHistoryCache: {
55
+ HOUR: { latestDate: null, balances: [] },
56
+ DAY: { latestDate: null, balances: [] },
57
+ WEEK: { latestDate: null, balances: [] },
58
+ },
59
+ swapHistory: [],
60
+ subAccounts: [],
61
+ hederaResources: mockHederaResources,
62
+ ...overrides,
63
+ };
64
+ };
65
+
66
+ export const getMockedAccountRaw = (overrides?: Partial<HederaAccountRaw>): HederaAccountRaw => {
67
+ return {
68
+ id: defaultMockAccountId,
69
+ seedIdentifier: "",
70
+ derivationMode: "",
71
+ index: 0,
72
+ freshAddress: "0.0.12345",
73
+ freshAddressPath: "44/3030",
74
+ used: false,
75
+ balance: defaultBalance.toString(),
76
+ spendableBalance: defaultBalance.toString(),
77
+ creationDate: new Date().toISOString(),
78
+ blockHeight: 0,
79
+ currencyId: defaultMockedCurrency.id,
80
+ operationsCount: 0,
81
+ operations: [],
82
+ pendingOperations: [],
83
+ lastSyncDate: new Date().toISOString(),
84
+ balanceHistoryCache: {
85
+ HOUR: { latestDate: null, balances: [] },
86
+ DAY: { latestDate: null, balances: [] },
87
+ WEEK: { latestDate: null, balances: [] },
88
+ },
89
+ swapHistory: [],
90
+ subAccounts: [],
91
+ hederaResources: mockHederaResourcesRaw,
92
+ ...overrides,
93
+ };
94
+ };
95
+
96
+ /**
97
+ * default settings:
98
+ * - balance is 10
99
+ */
100
+ export const getMockedTokenAccount = (
101
+ token: TokenCurrency,
102
+ overrides?: Partial<TokenAccount>,
103
+ ): TokenAccount => {
104
+ return {
105
+ type: "TokenAccount",
106
+ id: defaultMockTokenAccountId,
107
+ parentId: defaultMockAccountId,
108
+ token,
109
+ balance: defaultTokenBalance,
110
+ spendableBalance: defaultTokenBalance,
111
+ creationDate: new Date(),
112
+ operations: [],
113
+ operationsCount: 0,
114
+ pendingOperations: [],
115
+ swapHistory: [],
116
+ balanceHistoryCache: {
117
+ HOUR: { latestDate: null, balances: [] },
118
+ DAY: { latestDate: null, balances: [] },
119
+ WEEK: { latestDate: null, balances: [] },
120
+ },
121
+ ...overrides,
122
+ };
123
+ };
@@ -0,0 +1,66 @@
1
+ import { getCryptoCurrencyById, listTokensForCryptoCurrency } from "@ledgerhq/cryptoassets";
2
+ import type { CryptoCurrency, TokenCurrency } from "@ledgerhq/types-cryptoassets";
3
+ import invariant from "invariant";
4
+
5
+ export const getMockedCurrency = (overrides?: Partial<CryptoCurrency>): CryptoCurrency => {
6
+ return {
7
+ type: "CryptoCurrency",
8
+ id: "hedera",
9
+ managerAppName: "Hedera",
10
+ coinType: 3030,
11
+ scheme: "hedera",
12
+ color: "#000",
13
+ family: "hedera",
14
+ explorerViews: [
15
+ {
16
+ tx: "https://hashscan.io/mainnet/transaction/$hash",
17
+ address: "https://hashscan.io/mainnet/account/$address",
18
+ },
19
+ ],
20
+ name: "Hedera",
21
+ ticker: "HBAR",
22
+ units: [
23
+ {
24
+ name: "HBAR",
25
+ code: "HBAR",
26
+ magnitude: 8,
27
+ },
28
+ ],
29
+ ...overrides,
30
+ };
31
+ };
32
+
33
+ export const getTokenCurrencyFromCAL = (
34
+ index: number,
35
+ overrides?: Partial<TokenCurrency>,
36
+ ): TokenCurrency => {
37
+ const hedera = getCryptoCurrencyById("hedera");
38
+ const token = listTokensForCryptoCurrency(hedera)[index];
39
+
40
+ invariant(token, `token not found in CAL list on ${index} position`);
41
+
42
+ return {
43
+ ...token,
44
+ ...overrides,
45
+ };
46
+ };
47
+
48
+ export const getMockedTokenCurrency = (overrides?: Partial<TokenCurrency>): TokenCurrency => {
49
+ return {
50
+ id: "hedera/hts/test_0.0.1234567",
51
+ contractAddress: "0.0.1001",
52
+ parentCurrency: getMockedCurrency(),
53
+ tokenType: "hts",
54
+ name: "Test token",
55
+ ticker: "TEST",
56
+ type: "TokenCurrency",
57
+ units: [
58
+ {
59
+ name: "Test",
60
+ code: "TEST",
61
+ magnitude: 8,
62
+ },
63
+ ],
64
+ ...overrides,
65
+ };
66
+ };
@@ -0,0 +1,14 @@
1
+ import { HederaMirrorToken } from "../../api/types";
2
+
3
+ export const getMockedMirrorToken = (overrides?: Partial<HederaMirrorToken>): HederaMirrorToken => {
4
+ return {
5
+ token_id: "",
6
+ created_timestamp: "123",
7
+ automatic_association: false,
8
+ balance: 0,
9
+ decimals: 0,
10
+ freeze_status: "NOT_APPLICABLE",
11
+ kyc_status: "NOT_APPLICABLE",
12
+ ...overrides,
13
+ };
14
+ };
@@ -0,0 +1,20 @@
1
+ import BigNumber from "bignumber.js";
2
+ import type { HederaOperation } from "../../types";
3
+
4
+ export const getMockedOperation = (overrides?: Partial<HederaOperation>): HederaOperation => {
5
+ return {
6
+ id: "",
7
+ hash: "",
8
+ type: "IN",
9
+ value: new BigNumber(0),
10
+ fee: new BigNumber(0),
11
+ senders: [],
12
+ recipients: [],
13
+ blockHeight: undefined,
14
+ blockHash: undefined,
15
+ accountId: "",
16
+ date: new Date(),
17
+ extra: {},
18
+ ...overrides,
19
+ };
20
+ };
@@ -0,0 +1,22 @@
1
+ import BigNumber from "bignumber.js";
2
+ import type { Transaction, TransactionRaw } from "../../types";
3
+
4
+ export const getMockedTransaction = (overrides?: Partial<Transaction>): Transaction => {
5
+ return {
6
+ family: "hedera",
7
+ amount: new BigNumber(0),
8
+ recipient: "",
9
+ useAllAmount: false,
10
+ ...overrides,
11
+ };
12
+ };
13
+
14
+ export const getMockedTransactionRaw = (overrides?: Partial<TransactionRaw>): TransactionRaw => {
15
+ return {
16
+ family: "hedera",
17
+ amount: "0",
18
+ recipient: "",
19
+ useAllAmount: false,
20
+ ...overrides,
21
+ };
22
+ };
@@ -1,9 +1,14 @@
1
+ import { TokenCurrency } from "@ledgerhq/types-cryptoassets";
1
2
  import type {
3
+ Account,
4
+ AccountRaw,
5
+ Operation,
2
6
  TransactionCommon,
3
7
  TransactionCommonRaw,
4
8
  TransactionStatusCommon,
5
9
  TransactionStatusCommonRaw,
6
10
  } from "@ledgerhq/types-live";
11
+ import { HEDERA_TRANSACTION_KINDS } from "../constants";
7
12
 
8
13
  export type NetworkInfo = {
9
14
  family: "hedera";
@@ -13,21 +18,49 @@ export type NetworkInfoRaw = {
13
18
  family: "hedera";
14
19
  };
15
20
 
21
+ export type TokenAssociateProperties = {
22
+ name: typeof HEDERA_TRANSACTION_KINDS.TokenAssociate.name;
23
+ token: TokenCurrency;
24
+ };
25
+
16
26
  export type Transaction = TransactionCommon & {
17
27
  family: "hedera";
18
28
  memo?: string | undefined;
29
+ properties?: TokenAssociateProperties;
19
30
  };
20
31
 
21
32
  export type TransactionRaw = TransactionCommonRaw & {
22
33
  family: "hedera";
23
34
  memo?: string | undefined;
35
+ properties?: TokenAssociateProperties;
24
36
  };
25
37
 
26
38
  export type TransactionStatus = TransactionStatusCommon;
27
39
 
28
40
  export type TransactionStatusRaw = TransactionStatusCommonRaw;
29
41
 
42
+ export interface HederaResources {
43
+ maxAutomaticTokenAssociations: number;
44
+ isAutoTokenAssociationEnabled: boolean;
45
+ }
46
+
47
+ export interface HederaResourcesRaw {
48
+ maxAutomaticTokenAssociations: number;
49
+ isAutoTokenAssociationEnabled: boolean;
50
+ }
51
+
52
+ export type HederaAccount = Account & {
53
+ hederaResources?: HederaResources;
54
+ };
55
+
56
+ export type HederaAccountRaw = AccountRaw & {
57
+ hederaResources?: HederaResourcesRaw;
58
+ };
59
+
30
60
  export type HederaOperationExtra = {
31
61
  consensusTimestamp?: string;
32
62
  transactionId?: string;
63
+ associatedTokenId?: string;
33
64
  };
65
+
66
+ export type HederaOperation = Operation<HederaOperationExtra>;