@ledgerhq/coin-icon 0.4.1-next.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 (341) hide show
  1. package/.eslintrc.js +20 -0
  2. package/.turbo/turbo-build.log +4 -0
  3. package/.unimportedrc.json +41 -0
  4. package/CHANGELOG.md +495 -0
  5. package/LICENSE.txt +21 -0
  6. package/jest.config.js +11 -0
  7. package/lib/__test__/api/index.unit.test.d.ts +2 -0
  8. package/lib/__test__/api/index.unit.test.d.ts.map +1 -0
  9. package/lib/__test__/api/index.unit.test.js +173 -0
  10. package/lib/__test__/api/index.unit.test.js.map +1 -0
  11. package/lib/__test__/unit/buildTransaction.test.d.ts +2 -0
  12. package/lib/__test__/unit/buildTransaction.test.d.ts.map +1 -0
  13. package/lib/__test__/unit/buildTransaction.test.js +76 -0
  14. package/lib/__test__/unit/buildTransaction.test.js.map +1 -0
  15. package/lib/__test__/unit/getFeesForTransaction.unit.test.d.ts +2 -0
  16. package/lib/__test__/unit/getFeesForTransaction.unit.test.d.ts.map +1 -0
  17. package/lib/__test__/unit/getFeesForTransaction.unit.test.js +101 -0
  18. package/lib/__test__/unit/getFeesForTransaction.unit.test.js.map +1 -0
  19. package/lib/__test__/unit/getTransactionStatus.unit.test.d.ts +2 -0
  20. package/lib/__test__/unit/getTransactionStatus.unit.test.d.ts.map +1 -0
  21. package/lib/__test__/unit/getTransactionStatus.unit.test.js +178 -0
  22. package/lib/__test__/unit/getTransactionStatus.unit.test.js.map +1 -0
  23. package/lib/__test__/unit/logic.unit.test.d.ts +2 -0
  24. package/lib/__test__/unit/logic.unit.test.d.ts.map +1 -0
  25. package/lib/__test__/unit/logic.unit.test.js +112 -0
  26. package/lib/__test__/unit/logic.unit.test.js.map +1 -0
  27. package/lib/__test__/unit/serializations.unit.test.d.ts +2 -0
  28. package/lib/__test__/unit/serializations.unit.test.d.ts.map +1 -0
  29. package/lib/__test__/unit/serializations.unit.test.js +73 -0
  30. package/lib/__test__/unit/serializations.unit.test.js.map +1 -0
  31. package/lib/__test__/unit/transaction.unit.test.d.ts +2 -0
  32. package/lib/__test__/unit/transaction.unit.test.d.ts.map +1 -0
  33. package/lib/__test__/unit/transaction.unit.test.js +130 -0
  34. package/lib/__test__/unit/transaction.unit.test.js.map +1 -0
  35. package/lib/account.d.ts +7 -0
  36. package/lib/account.d.ts.map +1 -0
  37. package/lib/account.js +26 -0
  38. package/lib/account.js.map +1 -0
  39. package/lib/api/api-type.d.ts +62 -0
  40. package/lib/api/api-type.d.ts.map +1 -0
  41. package/lib/api/api-type.js +3 -0
  42. package/lib/api/api-type.js.map +1 -0
  43. package/lib/api/index.d.ts +13 -0
  44. package/lib/api/index.d.ts.map +1 -0
  45. package/lib/api/index.js +134 -0
  46. package/lib/api/index.js.map +1 -0
  47. package/lib/api/node.d.ts +31 -0
  48. package/lib/api/node.d.ts.map +1 -0
  49. package/lib/api/node.js +112 -0
  50. package/lib/api/node.js.map +1 -0
  51. package/lib/bridge/index.d.ts +13 -0
  52. package/lib/bridge/index.d.ts.map +1 -0
  53. package/lib/bridge/index.js +71 -0
  54. package/lib/bridge/index.js.map +1 -0
  55. package/lib/bridge.integration.test.d.ts +4 -0
  56. package/lib/bridge.integration.test.d.ts.map +1 -0
  57. package/lib/bridge.integration.test.js +105 -0
  58. package/lib/bridge.integration.test.js.map +1 -0
  59. package/lib/broadcast.d.ts +9 -0
  60. package/lib/broadcast.d.ts.map +1 -0
  61. package/lib/broadcast.js +25 -0
  62. package/lib/broadcast.js.map +1 -0
  63. package/lib/buildTransaction.d.ts +12 -0
  64. package/lib/buildTransaction.d.ts.map +1 -0
  65. package/lib/buildTransaction.js +55 -0
  66. package/lib/buildTransaction.js.map +1 -0
  67. package/lib/cli-transaction.d.ts +16 -0
  68. package/lib/cli-transaction.d.ts.map +1 -0
  69. package/lib/cli-transaction.js +35 -0
  70. package/lib/cli-transaction.js.map +1 -0
  71. package/lib/config.d.ts +15 -0
  72. package/lib/config.d.ts.map +1 -0
  73. package/lib/config.js +17 -0
  74. package/lib/config.js.map +1 -0
  75. package/lib/constants.d.ts +14 -0
  76. package/lib/constants.d.ts.map +1 -0
  77. package/lib/constants.js +17 -0
  78. package/lib/constants.js.map +1 -0
  79. package/lib/createTransaction.d.ts +8 -0
  80. package/lib/createTransaction.d.ts.map +1 -0
  81. package/lib/createTransaction.js +19 -0
  82. package/lib/createTransaction.js.map +1 -0
  83. package/lib/deviceTransactionConfig.d.ts +15 -0
  84. package/lib/deviceTransactionConfig.d.ts.map +1 -0
  85. package/lib/deviceTransactionConfig.js +24 -0
  86. package/lib/deviceTransactionConfig.js.map +1 -0
  87. package/lib/errors.d.ts +10 -0
  88. package/lib/errors.d.ts.map +1 -0
  89. package/lib/errors.js +8 -0
  90. package/lib/errors.js.map +1 -0
  91. package/lib/estimateMaxSpendable.d.ts +14 -0
  92. package/lib/estimateMaxSpendable.d.ts.map +1 -0
  93. package/lib/estimateMaxSpendable.js +38 -0
  94. package/lib/estimateMaxSpendable.js.map +1 -0
  95. package/lib/getFeesForTransaction.d.ts +14 -0
  96. package/lib/getFeesForTransaction.d.ts.map +1 -0
  97. package/lib/getFeesForTransaction.js +44 -0
  98. package/lib/getFeesForTransaction.js.map +1 -0
  99. package/lib/getTransactionStatus.d.ts +4 -0
  100. package/lib/getTransactionStatus.d.ts.map +1 -0
  101. package/lib/getTransactionStatus.js +114 -0
  102. package/lib/getTransactionStatus.js.map +1 -0
  103. package/lib/hw-getAddress.d.ts +6 -0
  104. package/lib/hw-getAddress.d.ts.map +1 -0
  105. package/lib/hw-getAddress.js +23 -0
  106. package/lib/hw-getAddress.js.map +1 -0
  107. package/lib/initAccount.d.ts +3 -0
  108. package/lib/initAccount.d.ts.map +1 -0
  109. package/lib/initAccount.js +13 -0
  110. package/lib/initAccount.js.map +1 -0
  111. package/lib/logic.d.ts +55 -0
  112. package/lib/logic.d.ts.map +1 -0
  113. package/lib/logic.js +124 -0
  114. package/lib/logic.js.map +1 -0
  115. package/lib/prepareTransaction.d.ts +9 -0
  116. package/lib/prepareTransaction.d.ts.map +1 -0
  117. package/lib/prepareTransaction.js +33 -0
  118. package/lib/prepareTransaction.js.map +1 -0
  119. package/lib/serialization.d.ts +7 -0
  120. package/lib/serialization.d.ts.map +1 -0
  121. package/lib/serialization.js +36 -0
  122. package/lib/serialization.js.map +1 -0
  123. package/lib/signOperation.d.ts +10 -0
  124. package/lib/signOperation.d.ts.map +1 -0
  125. package/lib/signOperation.js +96 -0
  126. package/lib/signOperation.js.map +1 -0
  127. package/lib/signer.d.ts +14 -0
  128. package/lib/signer.d.ts.map +1 -0
  129. package/lib/signer.js +3 -0
  130. package/lib/signer.js.map +1 -0
  131. package/lib/specs.d.ts +7 -0
  132. package/lib/specs.d.ts.map +1 -0
  133. package/lib/specs.js +100 -0
  134. package/lib/specs.js.map +1 -0
  135. package/lib/speculos-deviceActions.d.ts +4 -0
  136. package/lib/speculos-deviceActions.d.ts.map +1 -0
  137. package/lib/speculos-deviceActions.js +44 -0
  138. package/lib/speculos-deviceActions.js.map +1 -0
  139. package/lib/synchronization.d.ts +3 -0
  140. package/lib/synchronization.d.ts.map +1 -0
  141. package/lib/synchronization.js +76 -0
  142. package/lib/synchronization.js.map +1 -0
  143. package/lib/transaction.d.ts +15 -0
  144. package/lib/transaction.d.ts.map +1 -0
  145. package/lib/transaction.js +39 -0
  146. package/lib/transaction.js.map +1 -0
  147. package/lib/types/bridge.fixture.d.ts +5 -0
  148. package/lib/types/bridge.fixture.d.ts.map +1 -0
  149. package/lib/types/bridge.fixture.js +77 -0
  150. package/lib/types/bridge.fixture.js.map +1 -0
  151. package/lib/types/index.d.ts +46 -0
  152. package/lib/types/index.d.ts.map +1 -0
  153. package/lib/types/index.js +3 -0
  154. package/lib/types/index.js.map +1 -0
  155. package/lib-es/__test__/api/index.unit.test.d.ts +2 -0
  156. package/lib-es/__test__/api/index.unit.test.d.ts.map +1 -0
  157. package/lib-es/__test__/api/index.unit.test.js +145 -0
  158. package/lib-es/__test__/api/index.unit.test.js.map +1 -0
  159. package/lib-es/__test__/unit/buildTransaction.test.d.ts +2 -0
  160. package/lib-es/__test__/unit/buildTransaction.test.d.ts.map +1 -0
  161. package/lib-es/__test__/unit/buildTransaction.test.js +71 -0
  162. package/lib-es/__test__/unit/buildTransaction.test.js.map +1 -0
  163. package/lib-es/__test__/unit/getFeesForTransaction.unit.test.d.ts +2 -0
  164. package/lib-es/__test__/unit/getFeesForTransaction.unit.test.d.ts.map +1 -0
  165. package/lib-es/__test__/unit/getFeesForTransaction.unit.test.js +73 -0
  166. package/lib-es/__test__/unit/getFeesForTransaction.unit.test.js.map +1 -0
  167. package/lib-es/__test__/unit/getTransactionStatus.unit.test.d.ts +2 -0
  168. package/lib-es/__test__/unit/getTransactionStatus.unit.test.d.ts.map +1 -0
  169. package/lib-es/__test__/unit/getTransactionStatus.unit.test.js +153 -0
  170. package/lib-es/__test__/unit/getTransactionStatus.unit.test.js.map +1 -0
  171. package/lib-es/__test__/unit/logic.unit.test.d.ts +2 -0
  172. package/lib-es/__test__/unit/logic.unit.test.d.ts.map +1 -0
  173. package/lib-es/__test__/unit/logic.unit.test.js +107 -0
  174. package/lib-es/__test__/unit/logic.unit.test.js.map +1 -0
  175. package/lib-es/__test__/unit/serializations.unit.test.d.ts +2 -0
  176. package/lib-es/__test__/unit/serializations.unit.test.d.ts.map +1 -0
  177. package/lib-es/__test__/unit/serializations.unit.test.js +71 -0
  178. package/lib-es/__test__/unit/serializations.unit.test.js.map +1 -0
  179. package/lib-es/__test__/unit/transaction.unit.test.d.ts +2 -0
  180. package/lib-es/__test__/unit/transaction.unit.test.d.ts.map +1 -0
  181. package/lib-es/__test__/unit/transaction.unit.test.js +128 -0
  182. package/lib-es/__test__/unit/transaction.unit.test.js.map +1 -0
  183. package/lib-es/account.d.ts +7 -0
  184. package/lib-es/account.d.ts.map +1 -0
  185. package/lib-es/account.js +24 -0
  186. package/lib-es/account.js.map +1 -0
  187. package/lib-es/api/api-type.d.ts +62 -0
  188. package/lib-es/api/api-type.d.ts.map +1 -0
  189. package/lib-es/api/api-type.js +2 -0
  190. package/lib-es/api/api-type.js.map +1 -0
  191. package/lib-es/api/index.d.ts +13 -0
  192. package/lib-es/api/index.d.ts.map +1 -0
  193. package/lib-es/api/index.js +123 -0
  194. package/lib-es/api/index.js.map +1 -0
  195. package/lib-es/api/node.d.ts +31 -0
  196. package/lib-es/api/node.d.ts.map +1 -0
  197. package/lib-es/api/node.js +99 -0
  198. package/lib-es/api/node.js.map +1 -0
  199. package/lib-es/bridge/index.d.ts +13 -0
  200. package/lib-es/bridge/index.d.ts.map +1 -0
  201. package/lib-es/bridge/index.js +62 -0
  202. package/lib-es/bridge/index.js.map +1 -0
  203. package/lib-es/bridge.integration.test.d.ts +4 -0
  204. package/lib-es/bridge.integration.test.d.ts.map +1 -0
  205. package/lib-es/bridge.integration.test.js +99 -0
  206. package/lib-es/bridge.integration.test.js.map +1 -0
  207. package/lib-es/broadcast.d.ts +9 -0
  208. package/lib-es/broadcast.d.ts.map +1 -0
  209. package/lib-es/broadcast.js +21 -0
  210. package/lib-es/broadcast.js.map +1 -0
  211. package/lib-es/buildTransaction.d.ts +12 -0
  212. package/lib-es/buildTransaction.d.ts.map +1 -0
  213. package/lib-es/buildTransaction.js +48 -0
  214. package/lib-es/buildTransaction.js.map +1 -0
  215. package/lib-es/cli-transaction.d.ts +16 -0
  216. package/lib-es/cli-transaction.d.ts.map +1 -0
  217. package/lib-es/cli-transaction.js +29 -0
  218. package/lib-es/cli-transaction.js.map +1 -0
  219. package/lib-es/config.d.ts +15 -0
  220. package/lib-es/config.d.ts.map +1 -0
  221. package/lib-es/config.js +12 -0
  222. package/lib-es/config.js.map +1 -0
  223. package/lib-es/constants.d.ts +14 -0
  224. package/lib-es/constants.d.ts.map +1 -0
  225. package/lib-es/constants.js +14 -0
  226. package/lib-es/constants.js.map +1 -0
  227. package/lib-es/createTransaction.d.ts +8 -0
  228. package/lib-es/createTransaction.d.ts.map +1 -0
  229. package/lib-es/createTransaction.js +15 -0
  230. package/lib-es/createTransaction.js.map +1 -0
  231. package/lib-es/deviceTransactionConfig.d.ts +15 -0
  232. package/lib-es/deviceTransactionConfig.d.ts.map +1 -0
  233. package/lib-es/deviceTransactionConfig.js +22 -0
  234. package/lib-es/deviceTransactionConfig.js.map +1 -0
  235. package/lib-es/errors.d.ts +10 -0
  236. package/lib-es/errors.d.ts.map +1 -0
  237. package/lib-es/errors.js +5 -0
  238. package/lib-es/errors.js.map +1 -0
  239. package/lib-es/estimateMaxSpendable.d.ts +14 -0
  240. package/lib-es/estimateMaxSpendable.d.ts.map +1 -0
  241. package/lib-es/estimateMaxSpendable.js +31 -0
  242. package/lib-es/estimateMaxSpendable.js.map +1 -0
  243. package/lib-es/getFeesForTransaction.d.ts +14 -0
  244. package/lib-es/getFeesForTransaction.d.ts.map +1 -0
  245. package/lib-es/getFeesForTransaction.js +42 -0
  246. package/lib-es/getFeesForTransaction.js.map +1 -0
  247. package/lib-es/getTransactionStatus.d.ts +4 -0
  248. package/lib-es/getTransactionStatus.d.ts.map +1 -0
  249. package/lib-es/getTransactionStatus.js +109 -0
  250. package/lib-es/getTransactionStatus.js.map +1 -0
  251. package/lib-es/hw-getAddress.d.ts +6 -0
  252. package/lib-es/hw-getAddress.d.ts.map +1 -0
  253. package/lib-es/hw-getAddress.js +21 -0
  254. package/lib-es/hw-getAddress.js.map +1 -0
  255. package/lib-es/initAccount.d.ts +3 -0
  256. package/lib-es/initAccount.d.ts.map +1 -0
  257. package/lib-es/initAccount.js +9 -0
  258. package/lib-es/initAccount.js.map +1 -0
  259. package/lib-es/logic.d.ts +55 -0
  260. package/lib-es/logic.d.ts.map +1 -0
  261. package/lib-es/logic.js +109 -0
  262. package/lib-es/logic.js.map +1 -0
  263. package/lib-es/prepareTransaction.d.ts +9 -0
  264. package/lib-es/prepareTransaction.d.ts.map +1 -0
  265. package/lib-es/prepareTransaction.js +26 -0
  266. package/lib-es/prepareTransaction.js.map +1 -0
  267. package/lib-es/serialization.d.ts +7 -0
  268. package/lib-es/serialization.d.ts.map +1 -0
  269. package/lib-es/serialization.js +29 -0
  270. package/lib-es/serialization.js.map +1 -0
  271. package/lib-es/signOperation.d.ts +10 -0
  272. package/lib-es/signOperation.d.ts.map +1 -0
  273. package/lib-es/signOperation.js +89 -0
  274. package/lib-es/signOperation.js.map +1 -0
  275. package/lib-es/signer.d.ts +14 -0
  276. package/lib-es/signer.d.ts.map +1 -0
  277. package/lib-es/signer.js +2 -0
  278. package/lib-es/signer.js.map +1 -0
  279. package/lib-es/specs.d.ts +7 -0
  280. package/lib-es/specs.d.ts.map +1 -0
  281. package/lib-es/specs.js +95 -0
  282. package/lib-es/specs.js.map +1 -0
  283. package/lib-es/speculos-deviceActions.d.ts +4 -0
  284. package/lib-es/speculos-deviceActions.d.ts.map +1 -0
  285. package/lib-es/speculos-deviceActions.js +41 -0
  286. package/lib-es/speculos-deviceActions.js.map +1 -0
  287. package/lib-es/synchronization.d.ts +3 -0
  288. package/lib-es/synchronization.d.ts.map +1 -0
  289. package/lib-es/synchronization.js +69 -0
  290. package/lib-es/synchronization.js.map +1 -0
  291. package/lib-es/transaction.d.ts +15 -0
  292. package/lib-es/transaction.d.ts.map +1 -0
  293. package/lib-es/transaction.js +33 -0
  294. package/lib-es/transaction.js.map +1 -0
  295. package/lib-es/types/bridge.fixture.d.ts +5 -0
  296. package/lib-es/types/bridge.fixture.d.ts.map +1 -0
  297. package/lib-es/types/bridge.fixture.js +68 -0
  298. package/lib-es/types/bridge.fixture.js.map +1 -0
  299. package/lib-es/types/index.d.ts +46 -0
  300. package/lib-es/types/index.d.ts.map +1 -0
  301. package/lib-es/types/index.js +2 -0
  302. package/lib-es/types/index.js.map +1 -0
  303. package/package.json +85 -0
  304. package/src/__test__/api/index.unit.test.ts +151 -0
  305. package/src/__test__/unit/buildTransaction.test.ts +76 -0
  306. package/src/__test__/unit/getFeesForTransaction.unit.test.ts +76 -0
  307. package/src/__test__/unit/getTransactionStatus.unit.test.ts +172 -0
  308. package/src/__test__/unit/logic.unit.test.ts +145 -0
  309. package/src/__test__/unit/serializations.unit.test.ts +92 -0
  310. package/src/__test__/unit/transaction.unit.test.ts +144 -0
  311. package/src/account.ts +30 -0
  312. package/src/api/api-type.ts +65 -0
  313. package/src/api/index.ts +156 -0
  314. package/src/api/node.ts +115 -0
  315. package/src/bridge/index.ts +76 -0
  316. package/src/bridge.integration.test.ts +107 -0
  317. package/src/broadcast.ts +18 -0
  318. package/src/buildTransaction.ts +53 -0
  319. package/src/cli-transaction.ts +49 -0
  320. package/src/config.ts +29 -0
  321. package/src/constants.ts +14 -0
  322. package/src/createTransaction.ts +16 -0
  323. package/src/deviceTransactionConfig.ts +44 -0
  324. package/src/errors.ts +5 -0
  325. package/src/estimateMaxSpendable.ts +34 -0
  326. package/src/getFeesForTransaction.ts +46 -0
  327. package/src/getTransactionStatus.ts +139 -0
  328. package/src/hw-getAddress.ts +19 -0
  329. package/src/initAccount.ts +11 -0
  330. package/src/logic.ts +132 -0
  331. package/src/prepareTransaction.ts +24 -0
  332. package/src/serialization.ts +34 -0
  333. package/src/signOperation.ts +140 -0
  334. package/src/signer.ts +14 -0
  335. package/src/specs.ts +124 -0
  336. package/src/speculos-deviceActions.ts +46 -0
  337. package/src/synchronization.ts +70 -0
  338. package/src/transaction.ts +60 -0
  339. package/src/types/bridge.fixture.ts +75 -0
  340. package/src/types/index.ts +61 -0
  341. package/tsconfig.json +12 -0
@@ -0,0 +1,139 @@
1
+ import { BigNumber } from "bignumber.js";
2
+ import {
3
+ NotEnoughBalance,
4
+ RecipientRequired,
5
+ InvalidAddress,
6
+ FeeNotLoaded,
7
+ InvalidAddressBecauseDestinationIsAlsoSource,
8
+ AmountRequired,
9
+ } from "@ledgerhq/errors";
10
+
11
+ import type { IconAccount, Transaction, TransactionStatus } from "./types";
12
+
13
+ import {
14
+ EXISTENTIAL_DEPOSIT,
15
+ EXISTENTIAL_DEPOSIT_RECOMMENDED_MARGIN,
16
+ FEES_SAFETY_BUFFER,
17
+ calculateAmount,
18
+ getMinimumBalance,
19
+ isSelfTransaction,
20
+ isValidAddress,
21
+ } from "./logic";
22
+ import { formatCurrencyUnit } from "@ledgerhq/coin-framework/currencies/index";
23
+ import { IconAllFundsWarning, IconDoMaxSendInstead } from "./errors";
24
+
25
+ export const getSendTransactionStatus = async (
26
+ account: IconAccount,
27
+ transaction: Transaction,
28
+ ): Promise<TransactionStatus> => {
29
+ const errors: any = {};
30
+ const warnings: any = {};
31
+
32
+ // Check if fees are loaded
33
+ if (!transaction.fees) {
34
+ errors.fees = new FeeNotLoaded();
35
+ }
36
+
37
+ // Validate recipient
38
+ if (!transaction.recipient) {
39
+ errors.recipient = new RecipientRequired();
40
+ } else if (isSelfTransaction(account, transaction)) {
41
+ errors.recipient = new InvalidAddressBecauseDestinationIsAlsoSource();
42
+ } else if (!isValidAddress(transaction.recipient)) {
43
+ errors.recipient = new InvalidAddress("", {
44
+ currencyName: account.currency.name,
45
+ });
46
+ }
47
+
48
+ const estimatedFees = transaction.fees || new BigNumber(0);
49
+ const amount = calculateAmount({ account, transaction });
50
+ const totalSpent = amount.plus(estimatedFees);
51
+
52
+ // Check if amount is valid
53
+ if (amount.lte(0) && !transaction.useAllAmount) {
54
+ errors.amount = new AmountRequired();
55
+ } else {
56
+ const minimumBalanceExistential = getMinimumBalance(account);
57
+ const leftover = account.spendableBalance.minus(totalSpent);
58
+ if (
59
+ minimumBalanceExistential.gt(0) &&
60
+ leftover.lt(minimumBalanceExistential) &&
61
+ leftover.gt(0)
62
+ ) {
63
+ errors.amount = new IconDoMaxSendInstead(
64
+ "Balance cannot be below {{minimumBalance}}. Send max to empty account.",
65
+ {
66
+ minimumBalance: formatCurrencyUnit(account.currency.units[0], EXISTENTIAL_DEPOSIT, {
67
+ showCode: true,
68
+ }),
69
+ },
70
+ );
71
+ } else if (
72
+ !errors.amount &&
73
+ !transaction.useAllAmount &&
74
+ account.spendableBalance.lte(EXISTENTIAL_DEPOSIT.plus(EXISTENTIAL_DEPOSIT_RECOMMENDED_MARGIN))
75
+ ) {
76
+ errors.amount = new NotEnoughBalance();
77
+ } else if (totalSpent.gt(account.spendableBalance)) {
78
+ errors.amount = new NotEnoughBalance();
79
+ }
80
+
81
+ if (
82
+ !errors.amount &&
83
+ new BigNumber(account.iconResources?.totalDelegated)
84
+ .plus(account.iconResources?.votingPower)
85
+ .gt(0) &&
86
+ (transaction.useAllAmount ||
87
+ account.spendableBalance.minus(totalSpent).lt(FEES_SAFETY_BUFFER))
88
+ ) {
89
+ warnings.amount = new IconAllFundsWarning();
90
+ }
91
+
92
+ if (totalSpent.gt(account.spendableBalance)) {
93
+ errors.amount = new NotEnoughBalance();
94
+ }
95
+ }
96
+
97
+ return Promise.resolve({
98
+ errors,
99
+ warnings,
100
+ estimatedFees,
101
+ amount: amount.lt(0) ? new BigNumber(0) : amount,
102
+ totalSpent,
103
+ });
104
+ };
105
+
106
+ export const getTransactionStatus = async (
107
+ account: IconAccount,
108
+ transaction: Transaction,
109
+ ): Promise<TransactionStatus> => {
110
+ switch (transaction.mode) {
111
+ case "send":
112
+ return await getSendTransactionStatus(account, transaction);
113
+ default: {
114
+ const errors: { amount?: Error; recipient?: Error } = {};
115
+ const warnings: { amount?: Error } = {};
116
+
117
+ const amount = calculateAmount({ account, transaction });
118
+ const estimatedFees = transaction.fees || new BigNumber(0);
119
+ const totalSpent = amount.plus(estimatedFees);
120
+
121
+ if (totalSpent.gt(account.spendableBalance)) {
122
+ errors.amount = new NotEnoughBalance();
123
+ }
124
+
125
+ // Validate amount
126
+ if (amount.lte(0) && !transaction.useAllAmount) {
127
+ errors.amount = new AmountRequired();
128
+ }
129
+
130
+ return {
131
+ errors,
132
+ warnings,
133
+ estimatedFees,
134
+ amount: amount.lt(0) ? new BigNumber(0) : amount,
135
+ totalSpent,
136
+ };
137
+ }
138
+ }
139
+ };
@@ -0,0 +1,19 @@
1
+ import { GetAddressFn } from "@ledgerhq/coin-framework/bridge/getAddressWrapper";
2
+ import { SignerContext } from "@ledgerhq/coin-framework/signer";
3
+ import { GetAddressOptions } from "@ledgerhq/coin-framework/derivation";
4
+ import { IconAddress, IconSigner } from "./signer";
5
+
6
+ const resolver = (signerContext: SignerContext<IconSigner>): GetAddressFn => {
7
+ return async (deviceId: string, { path, verify }: GetAddressOptions) => {
8
+ const r = (await signerContext(deviceId, signer =>
9
+ signer.getAddress(path, verify || false),
10
+ )) as IconAddress;
11
+ return {
12
+ address: r.address,
13
+ publicKey: r.publicKey,
14
+ path,
15
+ };
16
+ };
17
+ };
18
+
19
+ export default resolver;
@@ -0,0 +1,11 @@
1
+ import { BigNumber } from "bignumber.js";
2
+ import { IconAccount } from "./types";
3
+ import { Account } from "@ledgerhq/types-live";
4
+
5
+ export function initAccount(account: Account): void {
6
+ (account as IconAccount).iconResources = {
7
+ nonce: 0,
8
+ votingPower: BigNumber(0),
9
+ totalDelegated: BigNumber(0),
10
+ };
11
+ }
package/src/logic.ts ADDED
@@ -0,0 +1,132 @@
1
+ import { getCryptoCurrencyById } from "@ledgerhq/cryptoassets";
2
+ import { BigNumber } from "bignumber.js";
3
+ import IconService from "icon-sdk-js";
4
+ import type { CryptoCurrency } from "@ledgerhq/types-cryptoassets";
5
+ import type { Account } from "@ledgerhq/types-live";
6
+ import type { IconAccount, Transaction } from "./types";
7
+ const { IconAmount } = IconService;
8
+ import { BERLIN_TESTNET_NID, MAINNET_NID } from "./constants";
9
+
10
+ /**
11
+ * @param {string|number|BigNumber} value value as loop
12
+ * @returns {BigNumber} value as ICX
13
+ */
14
+ export const convertLoopToIcx = (value: string | number | BigNumber): BigNumber => {
15
+ return new BigNumber(IconAmount.fromLoop(value, IconAmount.Unit.ICX.toString()));
16
+ };
17
+
18
+ /**
19
+ * @param {string|number|BigNumber} value value as ICX
20
+ * @returns {BigNumber} value as loop
21
+ */
22
+ export const convertICXtoLoop = (value: string | number | BigNumber): BigNumber => {
23
+ return new BigNumber(IconAmount.toLoop(value, IconAmount.Unit.ICX.toString()));
24
+ };
25
+
26
+ export const EXISTENTIAL_DEPOSIT = convertICXtoLoop(0.00125);
27
+ export const EXISTENTIAL_DEPOSIT_RECOMMENDED_MARGIN = convertICXtoLoop(0.00125);
28
+ export const FEES_SAFETY_BUFFER = convertICXtoLoop(0.00125); // Arbitrary buffer for paying fees of next transactions
29
+ export const MAX_AMOUNT_INPUT = convertICXtoLoop(5000);
30
+ /**
31
+ * Returns true if address is a valid md5
32
+ *
33
+ * @param {string} address
34
+ */
35
+ export const isValidAddress = (address: string): boolean => {
36
+ if (!address) return false;
37
+ return !!address.match(/^[a-z0-9]{42}$/);
38
+ };
39
+
40
+ /**
41
+ * Returns true if transaction is a self transaction
42
+ *
43
+ * @param {Account} account
44
+ * @param {Transaction} transaction
45
+ */
46
+ export const isSelfTransaction = (account: Account, transaction: Transaction): boolean => {
47
+ return transaction.recipient === account.freshAddress;
48
+ };
49
+
50
+ /**
51
+ * Returns nonce for an account
52
+ *
53
+ * @param {Account} account
54
+ */
55
+ export const getNonce = (account: IconAccount): number => {
56
+ const lastPendingOp = account.pendingOperations[0];
57
+
58
+ const nonce = Math.max(
59
+ account.iconResources?.nonce || 0,
60
+ lastPendingOp && typeof lastPendingOp.transactionSequenceNumber === "number"
61
+ ? lastPendingOp.transactionSequenceNumber + 1
62
+ : 0,
63
+ );
64
+
65
+ return nonce;
66
+ };
67
+
68
+ /**
69
+ * Returns true if the current currency is testnet
70
+ *
71
+ * @param {currency} CryptoCurrency
72
+ */
73
+ export function isTestnet(currency: CryptoCurrency): boolean {
74
+ return getCryptoCurrencyById(currency.id).isTestnetFor ? true : false;
75
+ }
76
+
77
+ export function getNid(currency: CryptoCurrency): number {
78
+ let nid = MAINNET_NID;
79
+ if (isTestnet(currency)) {
80
+ nid = BERLIN_TESTNET_NID;
81
+ }
82
+ return nid;
83
+ }
84
+
85
+ /**
86
+ * Calculate the real spendable
87
+ *
88
+ * @param {*} account
89
+ * @param {*} transaction
90
+ */
91
+ const calculateMaxSend = (account: Account, transaction: Transaction): BigNumber => {
92
+ const amount = account.spendableBalance.minus(transaction.fees || 0);
93
+ return amount.lt(0) ? new BigNumber(0) : BigNumber(amount.toFixed(5));
94
+ };
95
+
96
+ /**
97
+ * Calculates correct amount if useAllAmount
98
+ *
99
+ * @param {*} param
100
+ */
101
+ export const calculateAmount = ({
102
+ account,
103
+ transaction,
104
+ }: {
105
+ account: IconAccount;
106
+ transaction: Transaction;
107
+ }): BigNumber => {
108
+ let amount = transaction.amount;
109
+
110
+ if (transaction.useAllAmount) {
111
+ switch (transaction.mode) {
112
+ case "send":
113
+ amount = calculateMaxSend(account, transaction);
114
+ break;
115
+
116
+ default:
117
+ amount = account.spendableBalance.minus(transaction.fees || 0);
118
+ break;
119
+ }
120
+ } else if (transaction.amount.gt(MAX_AMOUNT_INPUT)) {
121
+ return new BigNumber(MAX_AMOUNT_INPUT);
122
+ }
123
+
124
+ return amount.lt(0) ? new BigNumber(0) : amount;
125
+ };
126
+
127
+ export const getMinimumBalance = (account: Account): BigNumber => {
128
+ const lockedBalance = account.balance.minus(account.spendableBalance);
129
+ return lockedBalance.lte(EXISTENTIAL_DEPOSIT)
130
+ ? EXISTENTIAL_DEPOSIT.minus(lockedBalance)
131
+ : new BigNumber(0);
132
+ };
@@ -0,0 +1,24 @@
1
+ import type { IconAccount, Transaction } from "./types";
2
+ import getEstimatedFees from "./getFeesForTransaction";
3
+ import BigNumber from "bignumber.js";
4
+
5
+ const sameFees = (a: BigNumber, b?: BigNumber | null) => (!a || !b ? a === b : a.eq(b));
6
+
7
+ /**
8
+ * Prepare transaction before checking status
9
+ *
10
+ * @param {IconAccount} account
11
+ * @param {Transaction} transaction
12
+ */
13
+ export const prepareTransaction = async (
14
+ account: IconAccount,
15
+ transaction: Transaction,
16
+ ): Promise<Transaction> => {
17
+ let fees = transaction.fees;
18
+ fees = await getEstimatedFees({ account, transaction });
19
+
20
+ if (fees && !sameFees(fees, transaction.fees)) {
21
+ return { ...transaction, fees };
22
+ }
23
+ return transaction;
24
+ };
@@ -0,0 +1,34 @@
1
+ import { BigNumber } from "bignumber.js";
2
+ import type { IconResourcesRaw, IconResources, IconAccount, IconAccountRaw } from "./types";
3
+ import { AccountRaw, Account } from "@ledgerhq/types-live";
4
+
5
+ export function toIconResourcesRaw(resources: IconResources): IconResourcesRaw {
6
+ const { nonce, votingPower, totalDelegated } = resources;
7
+ return {
8
+ nonce,
9
+ votingPower: votingPower.toString(),
10
+ totalDelegated: totalDelegated.toString(),
11
+ };
12
+ }
13
+
14
+ export function fromIconResourcesRaw(rawResources: IconResourcesRaw): IconResources {
15
+ const { nonce, votingPower, totalDelegated } = rawResources;
16
+ return {
17
+ nonce,
18
+ votingPower: new BigNumber(votingPower || 0),
19
+ totalDelegated: new BigNumber(totalDelegated || 0),
20
+ };
21
+ }
22
+
23
+ export function assignToAccountRaw(account: Account, accountRaw: AccountRaw): void {
24
+ const iconAccount = account as IconAccount;
25
+ if (iconAccount.iconResources) {
26
+ (accountRaw as IconAccountRaw).iconResources = toIconResourcesRaw(iconAccount.iconResources);
27
+ }
28
+ }
29
+
30
+ export function assignFromAccountRaw(accountRaw: AccountRaw, account: Account) {
31
+ const iconResourcesRaw = (accountRaw as IconAccountRaw).iconResources;
32
+ if (iconResourcesRaw)
33
+ (account as IconAccount).iconResources = fromIconResourcesRaw(iconResourcesRaw);
34
+ }
@@ -0,0 +1,140 @@
1
+ import { BigNumber } from "bignumber.js";
2
+ import { Observable } from "rxjs";
3
+
4
+ import type { IconAccount, Transaction } from "./types";
5
+ import type {
6
+ Account,
7
+ AccountBridge,
8
+ DeviceId,
9
+ Operation,
10
+ SignOperationEvent,
11
+ } from "@ledgerhq/types-live";
12
+
13
+ import { encodeOperationId } from "@ledgerhq/coin-framework/operation";
14
+
15
+ import { buildTransaction } from "./buildTransaction";
16
+ import { calculateAmount, getNonce } from "./logic";
17
+ import { FeeNotLoaded } from "@ledgerhq/errors";
18
+ import IconService, { IcxTransaction } from "icon-sdk-js";
19
+ import { SignerContext } from "@ledgerhq/coin-framework/signer";
20
+ import { IconSignature, IconSigner } from "./signer";
21
+ const { IconUtil, IconConverter } = IconService;
22
+
23
+ const buildOptimisticOperation = (
24
+ account: Account,
25
+ transaction: Transaction,
26
+ fee: BigNumber,
27
+ ): Operation => {
28
+ const type = "OUT";
29
+ const value = new BigNumber(transaction.amount).plus(fee);
30
+ const operation: Operation = {
31
+ id: encodeOperationId(account.id, "", type),
32
+ hash: "",
33
+ type,
34
+ value,
35
+ fee,
36
+ blockHash: null,
37
+ blockHeight: null,
38
+ senders: [account.freshAddress],
39
+ recipients: [transaction.recipient].filter(Boolean),
40
+ accountId: account.id,
41
+ transactionSequenceNumber: getNonce(account as IconAccount),
42
+ date: new Date(),
43
+ extra: {},
44
+ };
45
+
46
+ return operation;
47
+ };
48
+
49
+ /**
50
+ * Adds signature to unsigned transaction. Will likely be a call to Icon SDK
51
+ */
52
+ const addSignature = (rawTransaction: IcxTransaction, signature: string) => {
53
+ return {
54
+ rawTransaction: {
55
+ ...rawTransaction,
56
+ signature: signature,
57
+ },
58
+ signature,
59
+ };
60
+ };
61
+
62
+ /**
63
+ * Sign Transaction with Ledger hardware
64
+ */
65
+ export const buildSignOperation =
66
+ (signerContext: SignerContext<IconSigner>): AccountBridge<Transaction>["signOperation"] =>
67
+ ({
68
+ account,
69
+ transaction,
70
+ deviceId,
71
+ }: {
72
+ account: Account;
73
+ transaction: Transaction;
74
+ deviceId: DeviceId;
75
+ }): Observable<SignOperationEvent> =>
76
+ new Observable(o => {
77
+ let cancelled = false;
78
+ async function main() {
79
+ if (!transaction.fees) {
80
+ throw new FeeNotLoaded();
81
+ }
82
+
83
+ const transactionToSign = {
84
+ ...transaction,
85
+ amount: calculateAmount({
86
+ account: account as IconAccount,
87
+ transaction: transaction,
88
+ }),
89
+ };
90
+ const { unsigned } = await buildTransaction(
91
+ account as IconAccount,
92
+ transactionToSign,
93
+ transactionToSign.stepLimit,
94
+ );
95
+
96
+ o.next({ type: "device-signature-requested" });
97
+
98
+ const res = (await signerContext(deviceId, signer =>
99
+ signer.signTransaction(
100
+ account.freshAddressPath,
101
+ IconUtil.generateHashKey(IconConverter.toRawTransaction(unsigned)),
102
+ ),
103
+ )) as IconSignature;
104
+
105
+ const signed = addSignature(unsigned, res.signedRawTxBase64);
106
+
107
+ if (cancelled) return;
108
+ o.next({ type: "device-signature-granted" });
109
+
110
+ if (!signed.signature) {
111
+ throw new Error("No signature");
112
+ }
113
+
114
+ const operation = buildOptimisticOperation(
115
+ account,
116
+ transactionToSign,
117
+ transactionToSign.fees ?? new BigNumber(0),
118
+ );
119
+
120
+ o.next({
121
+ type: "signed",
122
+ signedOperation: {
123
+ operation,
124
+ signature: signed.signature,
125
+ rawData: signed.rawTransaction,
126
+ },
127
+ });
128
+ }
129
+
130
+ main().then(
131
+ () => o.complete(),
132
+ e => o.error(e),
133
+ );
134
+
135
+ return () => {
136
+ cancelled = true;
137
+ };
138
+ });
139
+
140
+ export default buildSignOperation;
package/src/signer.ts ADDED
@@ -0,0 +1,14 @@
1
+ export type IconAddress = {
2
+ publicKey: string;
3
+ address: string;
4
+ chainCode?: string;
5
+ };
6
+ export type IconSignature = {
7
+ signedRawTxBase64: string;
8
+ hashHex: string;
9
+ };
10
+
11
+ export interface IconSigner {
12
+ getAddress(path: string, shouldDisplay?: boolean): Promise<IconAddress>;
13
+ signTransaction(path: string, rawTxAscii: string): Promise<IconSignature>;
14
+ }
package/src/specs.ts ADDED
@@ -0,0 +1,124 @@
1
+ import invariant from "invariant";
2
+ import { botTest, pickSiblings } from "@ledgerhq/coin-framework/bot/specs";
3
+ import type { AppSpec } from "@ledgerhq/coin-framework/bot/types";
4
+ import { toOperationRaw } from "@ledgerhq/coin-framework/serialization/index";
5
+ import { DeviceModelId } from "@ledgerhq/devices";
6
+ import BigNumber from "bignumber.js";
7
+ import expect from "expect";
8
+ import { acceptTransaction } from "./speculos-deviceActions";
9
+ import {
10
+ EXISTENTIAL_DEPOSIT,
11
+ EXISTENTIAL_DEPOSIT_RECOMMENDED_MARGIN,
12
+ convertICXtoLoop,
13
+ } from "./logic";
14
+ import { Transaction } from "./types";
15
+ import { getCryptoCurrencyById } from "@ledgerhq/cryptoassets/currencies";
16
+
17
+ const maxAccounts = 6;
18
+ const currency = getCryptoCurrencyById("icon");
19
+
20
+ // FIX ME ICON have a bug where the amounts from the API have imprecisions
21
+ const expectedApproximate = (
22
+ value: BigNumber,
23
+ expected: BigNumber,
24
+ delta = convertICXtoLoop(0.000005),
25
+ ) => {
26
+ if (value.minus(expected).abs().gt(delta)) {
27
+ expect(value.toString()).toEqual(value.toString());
28
+ }
29
+ };
30
+
31
+ const icon: AppSpec<Transaction> = {
32
+ name: "Icon",
33
+ currency,
34
+ appQuery: {
35
+ model: DeviceModelId.nanoS,
36
+ appName: "Icon",
37
+ },
38
+ genericDeviceAction: acceptTransaction,
39
+ testTimeout: 2 * 60 * 1000,
40
+ transactionCheck: ({ maxSpendable }) => {
41
+ invariant(maxSpendable.gt(EXISTENTIAL_DEPOSIT_RECOMMENDED_MARGIN), "balance is too low");
42
+ },
43
+ test: ({ operation, optimisticOperation }) => {
44
+ const opExpected: Record<string, any> = toOperationRaw({
45
+ ...optimisticOperation,
46
+ });
47
+ delete opExpected.value;
48
+ delete opExpected.fee;
49
+ delete opExpected.date;
50
+ delete opExpected.blockHash;
51
+ delete opExpected.blockHeight;
52
+ delete opExpected.transactionSequenceNumber;
53
+ botTest("optimistic operation matches", () =>
54
+ expect(toOperationRaw(operation)).toMatchObject(opExpected),
55
+ );
56
+ },
57
+ mutations: [
58
+ {
59
+ name: "send 50%~",
60
+ maxRun: 1,
61
+ transaction: ({ account, siblings, bridge }) => {
62
+ invariant(account.spendableBalance.gt(0), "balance is 0");
63
+ const sibling = pickSiblings(siblings, maxAccounts);
64
+ let amount = account.spendableBalance.div(2).integerValue();
65
+
66
+ if (!sibling.used && amount.lt(EXISTENTIAL_DEPOSIT)) {
67
+ invariant(
68
+ account.spendableBalance.gt(EXISTENTIAL_DEPOSIT),
69
+ "send is too low to activate account",
70
+ );
71
+ amount = EXISTENTIAL_DEPOSIT;
72
+ }
73
+
74
+ return {
75
+ transaction: bridge.createTransaction(account),
76
+ updates: [
77
+ {
78
+ recipient: sibling.freshAddress,
79
+ },
80
+ {
81
+ amount,
82
+ },
83
+ ],
84
+ };
85
+ },
86
+ test: ({ accountBeforeTransaction, operation, account }) => {
87
+ botTest("account spendable balance decreased with operation", () =>
88
+ expectedApproximate(
89
+ account.spendableBalance,
90
+ accountBeforeTransaction.spendableBalance.minus(operation.value),
91
+ ),
92
+ );
93
+ },
94
+ },
95
+ {
96
+ name: "send max",
97
+ maxRun: 1,
98
+ transaction: ({ account, siblings, bridge }) => {
99
+ invariant(account.spendableBalance.gt(0), "balance is 0");
100
+ const sibling = pickSiblings(siblings, maxAccounts);
101
+ return {
102
+ transaction: bridge.createTransaction(account),
103
+ updates: [
104
+ {
105
+ recipient: sibling.freshAddress,
106
+ },
107
+ {
108
+ useAllAmount: true,
109
+ },
110
+ ],
111
+ };
112
+ },
113
+ test: ({ account }) => {
114
+ botTest("account spendable balance is zero", () =>
115
+ expectedApproximate(account.spendableBalance, new BigNumber(0)),
116
+ );
117
+ },
118
+ },
119
+ ],
120
+ };
121
+
122
+ export default {
123
+ icon,
124
+ };
@@ -0,0 +1,46 @@
1
+ import type { DeviceAction } from "@ledgerhq/coin-framework/bot/types";
2
+ import type { Transaction } from "./types";
3
+ import { formatCurrencyUnit } from "@ledgerhq/coin-framework/currencies/index";
4
+ import { deviceActionFlow, SpeculosButton } from "@ledgerhq/coin-framework/bot/specs";
5
+ import { getAccountCurrency } from "@ledgerhq/coin-framework/account/index";
6
+
7
+ const confirmWording: Record<string, string> = {
8
+ send: "transfer",
9
+ };
10
+
11
+ export const acceptTransaction: DeviceAction<Transaction, any> = deviceActionFlow({
12
+ steps: [
13
+ {
14
+ title: "Confirm",
15
+ button: SpeculosButton.RIGHT,
16
+ expectedValue: ({ transaction }) => confirmWording[transaction.mode],
17
+ },
18
+ {
19
+ title: "Amount",
20
+ button: SpeculosButton.RIGHT,
21
+ expectedValue: ({ transaction, account }) =>
22
+ formatCurrencyUnit(getAccountCurrency(account).units[0], transaction.amount, {
23
+ disableRounding: true,
24
+ }),
25
+ },
26
+ {
27
+ title: "Address",
28
+ button: SpeculosButton.RIGHT,
29
+ expectedValue: ({ transaction }) => {
30
+ return transaction.recipient;
31
+ },
32
+ },
33
+ {
34
+ title: "Fees",
35
+ button: SpeculosButton.RIGHT,
36
+ expectedValue: ({ account, status }) =>
37
+ formatCurrencyUnit(getAccountCurrency(account).units[0], status.estimatedFees, {
38
+ disableRounding: true,
39
+ }),
40
+ },
41
+ {
42
+ title: "Accept",
43
+ button: SpeculosButton.BOTH,
44
+ },
45
+ ],
46
+ });