@ledgerhq/live-common 21.14.1 → 21.16.2

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 (265) hide show
  1. package/lib/__tests__/families/bitcoin/wallet-btc/utils.test.js +85 -0
  2. package/lib/__tests__/families/bitcoin/wallet-btc/utils.test.js.map +1 -1
  3. package/lib/__tests__/families/bitcoin/wallet-btc/wallet.estimateMaxSpendable.test.js +1 -1
  4. package/lib/__tests__/families/bitcoin/wallet-btc/wallet.estimateMaxSpendable.test.js.map +1 -1
  5. package/lib/__tests__/families/bitcoin/wallet-btc/wallet.integration.test.js +2 -2
  6. package/lib/__tests__/families/bitcoin/wallet-btc/wallet.integration.test.js.map +1 -1
  7. package/lib/__tests__/families/bitcoin/wallet-btc/xpub.pickingStrategies.integration.test.js +20 -10
  8. package/lib/__tests__/families/bitcoin/wallet-btc/xpub.pickingStrategies.integration.test.js.map +1 -1
  9. package/lib/__tests__/families/bitcoin/wallet-btc/xpub.txs.NP2WPKH.integration.test.js +1 -1
  10. package/lib/__tests__/families/bitcoin/wallet-btc/xpub.txs.NP2WPKH.integration.test.js.map +1 -1
  11. package/lib/__tests__/families/bitcoin/wallet-btc/xpub.txs.P2WPKH.integration.test.js +1 -1
  12. package/lib/__tests__/families/bitcoin/wallet-btc/xpub.txs.P2WPKH.integration.test.js.map +1 -1
  13. package/lib/__tests__/families/bitcoin/wallet-btc/xpub.txs.integration.test.js +2 -2
  14. package/lib/__tests__/families/bitcoin/wallet-btc/xpub.txs.integration.test.js.map +1 -1
  15. package/lib/account/formatters.d.ts.map +1 -1
  16. package/lib/account/formatters.js +33 -1
  17. package/lib/account/formatters.js.map +1 -1
  18. package/lib/account/groupOperations.js +1 -1
  19. package/lib/account/groupOperations.js.map +1 -1
  20. package/lib/account/pending.d.ts +1 -0
  21. package/lib/account/pending.d.ts.map +1 -1
  22. package/lib/account/serialization.d.ts +6 -4
  23. package/lib/account/serialization.d.ts.map +1 -1
  24. package/lib/account/serialization.js +47 -9
  25. package/lib/account/serialization.js.map +1 -1
  26. package/lib/api/Ethereum.d.ts +22 -1
  27. package/lib/api/Ethereum.d.ts.map +1 -1
  28. package/lib/api/Ethereum.js +21 -1
  29. package/lib/api/Ethereum.js.map +1 -1
  30. package/lib/apps/polyfill.d.ts.map +1 -1
  31. package/lib/apps/polyfill.js +3 -0
  32. package/lib/apps/polyfill.js.map +1 -1
  33. package/lib/bridge/jsHelpers.d.ts +2 -1
  34. package/lib/bridge/jsHelpers.d.ts.map +1 -1
  35. package/lib/bridge/jsHelpers.js +27 -1
  36. package/lib/bridge/jsHelpers.js.map +1 -1
  37. package/lib/csvExport.d.ts.map +1 -1
  38. package/lib/csvExport.js +3 -1
  39. package/lib/csvExport.js.map +1 -1
  40. package/lib/currencies/sortByMarketcap.d.ts.map +1 -1
  41. package/lib/currencies/sortByMarketcap.js +8 -1
  42. package/lib/currencies/sortByMarketcap.js.map +1 -1
  43. package/lib/currencies/support.d.ts.map +1 -1
  44. package/lib/currencies/support.js.map +1 -1
  45. package/lib/env.d.ts +15 -5
  46. package/lib/env.d.ts.map +1 -1
  47. package/lib/env.js +11 -0
  48. package/lib/env.js.map +1 -1
  49. package/lib/env.react.d.ts +1 -1
  50. package/lib/env.react.d.ts.map +1 -1
  51. package/lib/families/bitcoin/datasets/bitcoin.d.ts.map +1 -1
  52. package/lib/families/bitcoin/datasets/bitcoin.js +4 -0
  53. package/lib/families/bitcoin/datasets/bitcoin.js.map +1 -1
  54. package/lib/families/bitcoin/datasets/bitcoin_cash.d.ts.map +1 -1
  55. package/lib/families/bitcoin/datasets/bitcoin_cash.js +4 -0
  56. package/lib/families/bitcoin/datasets/bitcoin_cash.js.map +1 -1
  57. package/lib/families/bitcoin/datasets/bitcoin_gold.d.ts.map +1 -1
  58. package/lib/families/bitcoin/datasets/bitcoin_gold.js +4 -0
  59. package/lib/families/bitcoin/datasets/bitcoin_gold.js.map +1 -1
  60. package/lib/families/bitcoin/datasets/dash.d.ts.map +1 -1
  61. package/lib/families/bitcoin/datasets/dash.js +4 -0
  62. package/lib/families/bitcoin/datasets/dash.js.map +1 -1
  63. package/lib/families/bitcoin/datasets/decred.d.ts.map +1 -1
  64. package/lib/families/bitcoin/datasets/decred.js +4 -0
  65. package/lib/families/bitcoin/datasets/decred.js.map +1 -1
  66. package/lib/families/bitcoin/datasets/digibyte.d.ts.map +1 -1
  67. package/lib/families/bitcoin/datasets/digibyte.js +4 -0
  68. package/lib/families/bitcoin/datasets/digibyte.js.map +1 -1
  69. package/lib/families/bitcoin/datasets/dogecoin.d.ts.map +1 -1
  70. package/lib/families/bitcoin/datasets/dogecoin.js +4 -0
  71. package/lib/families/bitcoin/datasets/dogecoin.js.map +1 -1
  72. package/lib/families/bitcoin/datasets/komodo.d.ts.map +1 -1
  73. package/lib/families/bitcoin/datasets/komodo.js +4 -0
  74. package/lib/families/bitcoin/datasets/komodo.js.map +1 -1
  75. package/lib/families/bitcoin/datasets/litecoin.d.ts.map +1 -1
  76. package/lib/families/bitcoin/datasets/litecoin.js +4 -0
  77. package/lib/families/bitcoin/datasets/litecoin.js.map +1 -1
  78. package/lib/families/bitcoin/datasets/peercoin.d.ts.map +1 -1
  79. package/lib/families/bitcoin/datasets/peercoin.js +4 -0
  80. package/lib/families/bitcoin/datasets/peercoin.js.map +1 -1
  81. package/lib/families/bitcoin/datasets/pivx.d.ts.map +1 -1
  82. package/lib/families/bitcoin/datasets/pivx.js +4 -0
  83. package/lib/families/bitcoin/datasets/pivx.js.map +1 -1
  84. package/lib/families/bitcoin/datasets/qtum.d.ts.map +1 -1
  85. package/lib/families/bitcoin/datasets/qtum.js +4 -0
  86. package/lib/families/bitcoin/datasets/qtum.js.map +1 -1
  87. package/lib/families/bitcoin/datasets/stakenet.d.ts.map +1 -1
  88. package/lib/families/bitcoin/datasets/stakenet.js +4 -0
  89. package/lib/families/bitcoin/datasets/stakenet.js.map +1 -1
  90. package/lib/families/bitcoin/datasets/vertcoin.d.ts.map +1 -1
  91. package/lib/families/bitcoin/datasets/vertcoin.js +4 -0
  92. package/lib/families/bitcoin/datasets/vertcoin.js.map +1 -1
  93. package/lib/families/bitcoin/datasets/viacoin.d.ts.map +1 -1
  94. package/lib/families/bitcoin/datasets/viacoin.js +4 -0
  95. package/lib/families/bitcoin/datasets/viacoin.js.map +1 -1
  96. package/lib/families/bitcoin/datasets/zcash.d.ts.map +1 -1
  97. package/lib/families/bitcoin/datasets/zcash.js +4 -0
  98. package/lib/families/bitcoin/datasets/zcash.js.map +1 -1
  99. package/lib/families/bitcoin/datasets/zencash.d.ts.map +1 -1
  100. package/lib/families/bitcoin/datasets/zencash.js +4 -0
  101. package/lib/families/bitcoin/datasets/zencash.js.map +1 -1
  102. package/lib/families/bitcoin/js-buildTransaction.d.ts.map +1 -1
  103. package/lib/families/bitcoin/js-buildTransaction.js +1 -1
  104. package/lib/families/bitcoin/js-buildTransaction.js.map +1 -1
  105. package/lib/families/bitcoin/js-estimateMaxSpendable.d.ts.map +1 -1
  106. package/lib/families/bitcoin/js-estimateMaxSpendable.js +1 -1
  107. package/lib/families/bitcoin/js-estimateMaxSpendable.js.map +1 -1
  108. package/lib/families/bitcoin/js-synchronisation.d.ts.map +1 -1
  109. package/lib/families/bitcoin/js-synchronisation.js +26 -14
  110. package/lib/families/bitcoin/js-synchronisation.js.map +1 -1
  111. package/lib/families/bitcoin/logic.d.ts +1 -1
  112. package/lib/families/bitcoin/logic.d.ts.map +1 -1
  113. package/lib/families/bitcoin/logic.js +6 -0
  114. package/lib/families/bitcoin/logic.js.map +1 -1
  115. package/lib/families/bitcoin/specs.d.ts.map +1 -1
  116. package/lib/families/bitcoin/specs.js +2 -1
  117. package/lib/families/bitcoin/specs.js.map +1 -1
  118. package/lib/families/bitcoin/test-dataset.js +2 -2
  119. package/lib/families/bitcoin/test-dataset.js.map +1 -1
  120. package/lib/families/bitcoin/wallet-btc/pickingstrategies/CoinSelect.d.ts +2 -1
  121. package/lib/families/bitcoin/wallet-btc/pickingstrategies/CoinSelect.d.ts.map +1 -1
  122. package/lib/families/bitcoin/wallet-btc/pickingstrategies/CoinSelect.js +16 -12
  123. package/lib/families/bitcoin/wallet-btc/pickingstrategies/CoinSelect.js.map +1 -1
  124. package/lib/families/bitcoin/wallet-btc/pickingstrategies/DeepFirst.d.ts +2 -1
  125. package/lib/families/bitcoin/wallet-btc/pickingstrategies/DeepFirst.d.ts.map +1 -1
  126. package/lib/families/bitcoin/wallet-btc/pickingstrategies/DeepFirst.js +12 -7
  127. package/lib/families/bitcoin/wallet-btc/pickingstrategies/DeepFirst.js.map +1 -1
  128. package/lib/families/bitcoin/wallet-btc/pickingstrategies/Merge.d.ts +2 -1
  129. package/lib/families/bitcoin/wallet-btc/pickingstrategies/Merge.d.ts.map +1 -1
  130. package/lib/families/bitcoin/wallet-btc/pickingstrategies/Merge.js +12 -7
  131. package/lib/families/bitcoin/wallet-btc/pickingstrategies/Merge.js.map +1 -1
  132. package/lib/families/bitcoin/wallet-btc/pickingstrategies/types.d.ts +2 -1
  133. package/lib/families/bitcoin/wallet-btc/pickingstrategies/types.d.ts.map +1 -1
  134. package/lib/families/bitcoin/wallet-btc/pickingstrategies/types.js.map +1 -1
  135. package/lib/families/bitcoin/wallet-btc/utils.d.ts +38 -1
  136. package/lib/families/bitcoin/wallet-btc/utils.d.ts.map +1 -1
  137. package/lib/families/bitcoin/wallet-btc/utils.js +104 -32
  138. package/lib/families/bitcoin/wallet-btc/utils.js.map +1 -1
  139. package/lib/families/bitcoin/wallet-btc/wallet.d.ts +1 -1
  140. package/lib/families/bitcoin/wallet-btc/wallet.d.ts.map +1 -1
  141. package/lib/families/bitcoin/wallet-btc/wallet.js +6 -3
  142. package/lib/families/bitcoin/wallet-btc/wallet.js.map +1 -1
  143. package/lib/families/bitcoin/wallet-btc/xpub.js +2 -2
  144. package/lib/families/bitcoin/wallet-btc/xpub.js.map +1 -1
  145. package/lib/families/ethereum/signOperation.d.ts.map +1 -1
  146. package/lib/families/ethereum/signOperation.js +7 -0
  147. package/lib/families/ethereum/signOperation.js.map +1 -1
  148. package/lib/families/ethereum/synchronisation.d.ts.map +1 -1
  149. package/lib/families/ethereum/synchronisation.js +175 -15
  150. package/lib/families/ethereum/synchronisation.js.map +1 -1
  151. package/lib/families/ethereum/versions.d.ts +2 -0
  152. package/lib/families/ethereum/versions.d.ts.map +1 -0
  153. package/lib/families/ethereum/versions.js +5 -0
  154. package/lib/families/ethereum/versions.js.map +1 -0
  155. package/lib/nft/NftMetadataProvider/index.d.ts +17 -0
  156. package/lib/nft/NftMetadataProvider/index.d.ts.map +1 -0
  157. package/lib/nft/NftMetadataProvider/index.js +244 -0
  158. package/lib/nft/NftMetadataProvider/index.js.map +1 -0
  159. package/lib/nft/NftMetadataProvider/logic.d.ts +3 -0
  160. package/lib/nft/NftMetadataProvider/logic.d.ts.map +1 -0
  161. package/lib/nft/NftMetadataProvider/logic.js +22 -0
  162. package/lib/nft/NftMetadataProvider/logic.js.map +1 -0
  163. package/lib/nft/NftMetadataProvider/types.d.ts +44 -0
  164. package/lib/nft/NftMetadataProvider/types.d.ts.map +1 -0
  165. package/lib/nft/NftMetadataProvider/types.js +3 -0
  166. package/lib/nft/NftMetadataProvider/types.js.map +1 -0
  167. package/lib/nft/helpers.d.ts +10 -0
  168. package/lib/nft/helpers.d.ts.map +1 -0
  169. package/lib/nft/helpers.js +85 -0
  170. package/lib/nft/helpers.js.map +1 -0
  171. package/lib/nft/index.d.ts +4 -0
  172. package/lib/nft/index.d.ts.map +1 -0
  173. package/lib/nft/index.js +16 -0
  174. package/lib/nft/index.js.map +1 -0
  175. package/lib/nft/nftId.d.ts +3 -0
  176. package/lib/nft/nftId.d.ts.map +1 -0
  177. package/lib/nft/nftId.js +33 -0
  178. package/lib/nft/nftId.js.map +1 -0
  179. package/lib/operation.d.ts +1 -1
  180. package/lib/operation.d.ts.map +1 -1
  181. package/lib/operation.js +19 -5
  182. package/lib/operation.js.map +1 -1
  183. package/lib/reconciliation.d.ts.map +1 -1
  184. package/lib/reconciliation.js +6 -0
  185. package/lib/reconciliation.js.map +1 -1
  186. package/lib/types/account.d.ts +3 -0
  187. package/lib/types/account.d.ts.map +1 -1
  188. package/lib/types/index.d.ts +1 -0
  189. package/lib/types/index.d.ts.map +1 -1
  190. package/lib/types/index.js +1 -0
  191. package/lib/types/index.js.map +1 -1
  192. package/lib/types/nft.d.ts +29 -0
  193. package/lib/types/nft.d.ts.map +1 -0
  194. package/lib/types/nft.js +3 -0
  195. package/lib/types/nft.js.map +1 -0
  196. package/lib/types/operation.d.ts +12 -1
  197. package/lib/types/operation.d.ts.map +1 -1
  198. package/package.json +13 -13
  199. package/src/.DS_Store +0 -0
  200. package/src/__tests__/__snapshots__/all.libcore.ts.snap +11643 -127265
  201. package/src/__tests__/families/bitcoin/wallet-btc/utils.test.ts +105 -0
  202. package/src/__tests__/families/bitcoin/wallet-btc/wallet.estimateMaxSpendable.test.ts +3 -2
  203. package/src/__tests__/families/bitcoin/wallet-btc/wallet.integration.test.ts +2 -2
  204. package/src/__tests__/families/bitcoin/wallet-btc/xpub.pickingStrategies.integration.test.ts +35 -31
  205. package/src/__tests__/families/bitcoin/wallet-btc/xpub.txs.NP2WPKH.integration.test.ts +3 -2
  206. package/src/__tests__/families/bitcoin/wallet-btc/xpub.txs.P2WPKH.integration.test.ts +3 -2
  207. package/src/__tests__/families/bitcoin/wallet-btc/xpub.txs.integration.test.ts +6 -4
  208. package/src/account/formatters.ts +43 -0
  209. package/src/account/groupOperations.ts +2 -2
  210. package/src/account/serialization.ts +52 -0
  211. package/src/api/Ethereum.ts +40 -2
  212. package/src/apps/polyfill.ts +3 -0
  213. package/src/bridge/jsHelpers.ts +35 -0
  214. package/src/csvExport.ts +3 -2
  215. package/src/currencies/.DS_Store +0 -0
  216. package/src/currencies/__snapshots__/sortByMarketcap.test.ts.snap +1354 -1
  217. package/src/currencies/sortByMarketcap.ts +9 -1
  218. package/src/currencies/support.ts +1 -0
  219. package/src/env.ts +11 -0
  220. package/src/families/bitcoin/datasets/bitcoin.ts +4 -0
  221. package/src/families/bitcoin/datasets/bitcoin_cash.ts +4 -0
  222. package/src/families/bitcoin/datasets/bitcoin_gold.ts +4 -0
  223. package/src/families/bitcoin/datasets/dash.ts +4 -0
  224. package/src/families/bitcoin/datasets/decred.ts +4 -0
  225. package/src/families/bitcoin/datasets/digibyte.ts +4 -0
  226. package/src/families/bitcoin/datasets/dogecoin.ts +4 -0
  227. package/src/families/bitcoin/datasets/komodo.ts +4 -0
  228. package/src/families/bitcoin/datasets/litecoin.ts +4 -0
  229. package/src/families/bitcoin/datasets/peercoin.ts +4 -0
  230. package/src/families/bitcoin/datasets/pivx.ts +4 -0
  231. package/src/families/bitcoin/datasets/qtum.ts +4 -0
  232. package/src/families/bitcoin/datasets/stakenet.ts +4 -0
  233. package/src/families/bitcoin/datasets/vertcoin.ts +4 -0
  234. package/src/families/bitcoin/datasets/viacoin.ts +4 -0
  235. package/src/families/bitcoin/datasets/zcash.ts +4 -0
  236. package/src/families/bitcoin/datasets/zencash.ts +4 -0
  237. package/src/families/bitcoin/js-buildTransaction.ts +2 -1
  238. package/src/families/bitcoin/js-estimateMaxSpendable.ts +2 -1
  239. package/src/families/bitcoin/js-synchronisation.ts +30 -16
  240. package/src/families/bitcoin/logic.ts +7 -2
  241. package/src/families/bitcoin/specs.ts +6 -1
  242. package/src/families/bitcoin/test-dataset.ts +2 -2
  243. package/src/families/bitcoin/wallet-btc/pickingstrategies/CoinSelect.ts +34 -22
  244. package/src/families/bitcoin/wallet-btc/pickingstrategies/DeepFirst.ts +30 -9
  245. package/src/families/bitcoin/wallet-btc/pickingstrategies/Merge.ts +30 -9
  246. package/src/families/bitcoin/wallet-btc/pickingstrategies/types.ts +3 -3
  247. package/src/families/bitcoin/wallet-btc/utils.ts +118 -35
  248. package/src/families/bitcoin/wallet-btc/wallet.ts +9 -4
  249. package/src/families/bitcoin/wallet-btc/xpub.ts +5 -5
  250. package/src/families/ethereum/modules/__snapshots__/compound.resilience.test.ts.snap +2876 -0
  251. package/src/families/ethereum/signOperation.ts +8 -0
  252. package/src/families/ethereum/synchronisation.ts +185 -6
  253. package/src/families/ethereum/versions.ts +1 -0
  254. package/src/nft/NftMetadataProvider/index.tsx +205 -0
  255. package/src/nft/NftMetadataProvider/logic.ts +20 -0
  256. package/src/nft/NftMetadataProvider/types.ts +60 -0
  257. package/src/nft/helpers.ts +78 -0
  258. package/src/nft/index.ts +3 -0
  259. package/src/nft/nftId.ts +17 -0
  260. package/src/operation.ts +22 -3
  261. package/src/reconciliation.ts +7 -0
  262. package/src/types/account.ts +4 -0
  263. package/src/types/index.ts +1 -0
  264. package/src/types/nft.ts +38 -0
  265. package/src/types/operation.ts +23 -1
@@ -13,6 +13,7 @@ import { getGasLimit, buildEthereumTx } from "./transaction";
13
13
  import { apiForCurrency } from "../../api/Ethereum";
14
14
  import { withDevice } from "../../hw/deviceAccess";
15
15
  import { modes } from "./modules";
16
+ import { getEnv } from "../../env";
16
17
  export const signOperation = ({
17
18
  account,
18
19
  deviceId,
@@ -61,6 +62,13 @@ export const signOperation = ({
61
62
  "0x" + (tx.value.toString("hex") || "0")
62
63
  );
63
64
  const eth = new Eth(transport);
65
+ if (getEnv("NFT")) {
66
+ eth.setLoadConfig({
67
+ // FIXME drop this after LL-8001
68
+ nftExplorerBaseURL:
69
+ "https://nft.staging.aws.ledger.fr/v1/ethereum",
70
+ });
71
+ }
64
72
  // FIXME this part is still required for compound to correctly display info on the device
65
73
  const addrs =
66
74
  (fillTransactionDataResult &&
@@ -4,7 +4,7 @@ import throttle from "lodash/throttle";
4
4
  import flatMap from "lodash/flatMap";
5
5
  import eip55 from "eip55";
6
6
  import { log } from "@ledgerhq/logs";
7
- import { mergeOps } from "../../bridge/jsHelpers";
7
+ import { mergeNfts, mergeOps } from "../../bridge/jsHelpers";
8
8
  import type { GetAccountShape } from "../../bridge/jsHelpers";
9
9
  import {
10
10
  encodeTokenAccountId,
@@ -15,10 +15,14 @@ import {
15
15
  } from "../../account";
16
16
  import { listTokensForCryptoCurrency } from "../../currencies";
17
17
  import { encodeAccountId } from "../../account";
18
- import type { Operation, TokenAccount, Account } from "../../types";
18
+ import type { Operation, TokenAccount, Account, NFT } from "../../types";
19
19
  import { API, apiForCurrency, Tx } from "../../api/Ethereum";
20
20
  import { digestTokenAccounts, prepareTokenAccounts } from "./modules";
21
21
  import { findTokenByAddressInCurrency } from "@ledgerhq/cryptoassets";
22
+ import { encodeNftId, nftsFromOperations } from "../../nft";
23
+ import { NFT_VERSION } from "./versions";
24
+ import { getEnv } from "../../env";
25
+
22
26
  export const getAccountShape: GetAccountShape = async (
23
27
  infoInput,
24
28
  { blacklistedTokenIds }
@@ -47,11 +51,15 @@ export const getAccountShape: GetAccountShape = async (
47
51
  withDelisted: true,
48
52
  }).length;
49
53
  const outdatedBlacklist = initialAccount?.syncHash !== syncHash;
54
+ const firstNftSync =
55
+ getEnv("NFT") && typeof initialAccount?.nfts === "undefined";
50
56
  const pullFromBlockHash =
51
57
  initialAccount &&
52
58
  areAllOperationsLoaded(initialAccount) &&
53
59
  mostRecentStableOperation &&
54
- !outdatedBlacklist
60
+ !outdatedBlacklist &&
61
+ !firstNftSync &&
62
+ NFT_VERSION
55
63
  ? mostRecentStableOperation.blockHash
56
64
  : undefined;
57
65
  const txsP = fetchAllTransactions(api, address, pullFromBlockHash);
@@ -75,8 +83,10 @@ export const getAccountShape: GetAccountShape = async (
75
83
  let newOps = flatMap(txs, txToOps({ address, id: accountId, currency }));
76
84
  // extracting out the sub operations by token account
77
85
  const perTokenAccountIdOperations = {};
86
+ // extracting and concat all nft operations for an account
87
+ const flatNftOps: Operation[] = [];
78
88
  newOps.forEach((op) => {
79
- const { subOperations } = op;
89
+ const { subOperations, nftOperations } = op;
80
90
 
81
91
  if (subOperations?.length) {
82
92
  subOperations.forEach((sop) => {
@@ -87,6 +97,10 @@ export const getAccountShape: GetAccountShape = async (
87
97
  perTokenAccountIdOperations[sop.accountId].push(sop);
88
98
  });
89
99
  }
100
+
101
+ if (nftOperations?.length) {
102
+ nftOperations.forEach((nop) => flatNftOps.push(nop));
103
+ }
90
104
  });
91
105
  const subAccountsExisting = {};
92
106
  initialAccount?.subAccounts?.forEach((a) => {
@@ -158,6 +172,11 @@ export const getAccountShape: GetAccountShape = async (
158
172
  subOperations: inferSubOperations(o.hash, subAccounts),
159
173
  }));
160
174
  const operations = mergeOps(initialStableOperations, newOps);
175
+
176
+ const nfts = getEnv("NFT")
177
+ ? mergeNfts(initialAccount?.nfts, await getNfts(flatNftOps))
178
+ : undefined;
179
+
161
180
  const accountShape: Partial<Account> = {
162
181
  id: accountId,
163
182
  operations,
@@ -168,6 +187,7 @@ export const getAccountShape: GetAccountShape = async (
168
187
  lastSyncDate: new Date(),
169
188
  balanceHistory: undefined,
170
189
  syncHash,
190
+ nfts,
171
191
  };
172
192
  return accountShape;
173
193
  };
@@ -188,7 +208,14 @@ const txToOps =
188
208
  (tx: Tx): Operation[] => {
189
209
  // workaround bugs in our explorer that don't treat partial/optimistic operation really well
190
210
  if (!tx.gas_used) return [];
191
- const { hash, block, actions, transfer_events } = tx;
211
+ const {
212
+ hash,
213
+ block,
214
+ actions,
215
+ transfer_events,
216
+ erc721_transfer_events,
217
+ erc1155_transfer_events,
218
+ } = tx;
192
219
  const addr = address;
193
220
  const from = safeEncodeEIP55(tx.from);
194
221
  const to = safeEncodeEIP55(tx.to);
@@ -305,6 +332,148 @@ const txToOps =
305
332
 
306
333
  return all;
307
334
  });
335
+
336
+ // Creating NFTOps from transfer events related to ERC721 only
337
+ const erc721Operations = !erc721_transfer_events
338
+ ? []
339
+ : flatMap(erc721_transfer_events, (event) => {
340
+ const sender = safeEncodeEIP55(event.sender);
341
+ const receiver = safeEncodeEIP55(event.receiver);
342
+ const contract = safeEncodeEIP55(event.contract);
343
+ const tokenId = event.token_id;
344
+ const nftId = encodeNftId(id, event.contract, tokenId);
345
+ const sending = addr === sender;
346
+ const receiving = addr === receiver;
347
+
348
+ if (!sending && !receiving) {
349
+ return [];
350
+ }
351
+
352
+ const all: Operation[] = [];
353
+
354
+ if (sending) {
355
+ const type = "NFT_OUT";
356
+ all.push({
357
+ id: `${nftId}-${hash}-${type}`,
358
+ senders: [sender],
359
+ recipients: [receiver],
360
+ contract,
361
+ fee,
362
+ standard: "ERC721",
363
+ tokenId,
364
+ value: new BigNumber(1),
365
+ hash,
366
+ type,
367
+ blockHeight,
368
+ blockHash,
369
+ date,
370
+ transactionSequenceNumber,
371
+ accountId: id,
372
+ extra: {},
373
+ });
374
+ }
375
+
376
+ if (receiving) {
377
+ const type = "NFT_IN";
378
+ all.push({
379
+ id: `${nftId}-${hash}-${type}`,
380
+ senders: [sender],
381
+ recipients: [receiver],
382
+ contract,
383
+ fee,
384
+ standard: "ERC721",
385
+ tokenId,
386
+ value: new BigNumber(1),
387
+ hash,
388
+ type,
389
+ blockHeight,
390
+ blockHash,
391
+ date,
392
+ transactionSequenceNumber,
393
+ accountId: id,
394
+ extra: {},
395
+ });
396
+ }
397
+
398
+ return all;
399
+ });
400
+
401
+ // Creating NFTOps from transfer events related to ERC1155 only
402
+ const erc1155Operations = !erc1155_transfer_events
403
+ ? []
404
+ : flatMap(erc1155_transfer_events, (event) => {
405
+ const sender = safeEncodeEIP55(event.sender);
406
+ const receiver = safeEncodeEIP55(event.receiver);
407
+ const contract = safeEncodeEIP55(event.contract);
408
+ const operator = safeEncodeEIP55(event.operator);
409
+ const sending = addr === sender;
410
+ const receiving = addr === receiver;
411
+
412
+ if (!sending && !receiving) {
413
+ return [];
414
+ }
415
+
416
+ const all: Operation[] = [];
417
+
418
+ event.transfers.forEach((transfer) => {
419
+ const tokenId = transfer.id;
420
+ const value = new BigNumber(transfer.value);
421
+ const nftId = encodeNftId(id, event.contract, tokenId);
422
+
423
+ if (sending) {
424
+ const type = "NFT_OUT";
425
+ all.push({
426
+ id: `${nftId}-${hash}-${type}`,
427
+ senders: [sender],
428
+ recipients: [receiver],
429
+ contract,
430
+ fee,
431
+ operator,
432
+ standard: "ERC1155",
433
+ tokenId,
434
+ value,
435
+ hash,
436
+ type,
437
+ blockHeight,
438
+ blockHash,
439
+ date,
440
+ transactionSequenceNumber,
441
+ accountId: id,
442
+ extra: {},
443
+ });
444
+ }
445
+
446
+ if (receiving) {
447
+ const type = "NFT_IN";
448
+ all.push({
449
+ id: `${nftId}-${hash}-${type}`,
450
+ senders: [sender],
451
+ recipients: [receiver],
452
+ contract,
453
+ fee,
454
+ operator,
455
+ standard: "ERC1155",
456
+ tokenId,
457
+ value,
458
+ hash,
459
+ type,
460
+ blockHeight,
461
+ blockHash,
462
+ date,
463
+ transactionSequenceNumber,
464
+ accountId: id,
465
+ extra: {},
466
+ });
467
+ }
468
+ });
469
+
470
+ return all;
471
+ });
472
+
473
+ const nftOperations = erc721Operations
474
+ .concat(erc1155Operations)
475
+ /** @warning is this necessary ? Do we need the operations to be chronologically organised for LLD/LLM ? */
476
+ .sort((a, b) => b.date.getTime() - a.date.getTime());
308
477
  const ops: Operation[] = [];
309
478
 
310
479
  if (sending) {
@@ -325,6 +494,7 @@ const txToOps =
325
494
  hasFailed,
326
495
  internalOperations: internalOperations,
327
496
  subOperations,
497
+ nftOperations,
328
498
  transactionSequenceNumber,
329
499
  });
330
500
  }
@@ -347,6 +517,7 @@ const txToOps =
347
517
  internalOperations: sending ? [] : internalOperations,
348
518
  // if it was already in sending, we don't add twice
349
519
  subOperations: sending ? [] : subOperations,
520
+ nftOperations,
350
521
  transactionSequenceNumber,
351
522
  });
352
523
  }
@@ -354,7 +525,9 @@ const txToOps =
354
525
  if (
355
526
  !sending &&
356
527
  !receiving &&
357
- (internalOperations.length || subOperations.length)
528
+ (internalOperations.length ||
529
+ subOperations.length ||
530
+ nftOperations.length)
358
531
  ) {
359
532
  ops.push({
360
533
  id: `${id}-${hash}-NONE`,
@@ -371,6 +544,7 @@ const txToOps =
371
544
  extra: {},
372
545
  internalOperations,
373
546
  subOperations,
547
+ nftOperations,
374
548
  transactionSequenceNumber,
375
549
  });
376
550
  }
@@ -445,6 +619,11 @@ async function loadERC20Balances(tokenAccounts, address, api) {
445
619
  .filter(Boolean);
446
620
  }
447
621
 
622
+ function getNfts(nftOperations: Operation[]): NFT[] {
623
+ const nfts: NFT[] = nftsFromOperations(nftOperations);
624
+ return nfts.filter((nft) => nft.amount.gt(0));
625
+ }
626
+
448
627
  const SAFE_REORG_THRESHOLD = 80;
449
628
 
450
629
  function stableOperations(a) {
@@ -0,0 +1 @@
1
+ export const NFT_VERSION = 1;
@@ -0,0 +1,205 @@
1
+ import React, {
2
+ createContext,
3
+ useContext,
4
+ useMemo,
5
+ useState,
6
+ useEffect,
7
+ } from "react";
8
+ import { Currency, findCryptoCurrencyById } from "@ledgerhq/cryptoassets";
9
+ import { API, apiForCurrency } from "../../api/Ethereum";
10
+ import { NFTMetadataResponse } from "../../types";
11
+ import { getNftKey } from "../helpers";
12
+ import {
13
+ Batch,
14
+ BatchElement,
15
+ NFTMetadataContextAPI,
16
+ NFTMetadataContextState,
17
+ NFTMetadataContextType,
18
+ NFTResource,
19
+ } from "./types";
20
+ import { isOutdated } from "./logic";
21
+
22
+ const currency: Currency = findCryptoCurrencyById("ethereum")!;
23
+ const ethApi: API = apiForCurrency(currency);
24
+
25
+ const NftMetadataContext = createContext<NFTMetadataContextType>({
26
+ cache: {},
27
+ loadNFTMetadata: () => Promise.resolve(),
28
+ clearCache: () => {},
29
+ });
30
+
31
+ export const metadataCallBatcher = (() => {
32
+ const batch: BatchElement[] = [];
33
+
34
+ let debounce;
35
+ const timeoutBatchCall = () => {
36
+ // Clear the previous scheduled call if it was existing
37
+ clearTimeout(debounce);
38
+
39
+ // Schedule a new call with the whole batch
40
+ debounce = setTimeout(() => {
41
+ // Seperate each batch element properties into arrays by type and index
42
+ const { couples, resolvers, rejecters } = batch.reduce(
43
+ (acc, { couple, resolve, reject }) => {
44
+ acc.couples.push(couple);
45
+ acc.resolvers.push(resolve);
46
+ acc.rejecters.push(reject);
47
+
48
+ return acc;
49
+ },
50
+ { couples: [], resolvers: [], rejecters: [] } as Batch
51
+ );
52
+ // Empty the batch
53
+ batch.length = 0;
54
+
55
+ // Make the call with all the couples of contract and tokenId at once
56
+ ethApi
57
+ .getNFTMetadata(couples)
58
+ .then((res) => {
59
+ // Resolve each batch element with its own resolver and only its response
60
+ res.forEach((metadata, index) => resolvers[index](metadata));
61
+ })
62
+ .catch((err) => {
63
+ // Reject all batch element with the error
64
+ rejecters.forEach((reject) => reject(err));
65
+ });
66
+ });
67
+ };
68
+
69
+ return {
70
+ // Load the metadata for a given couple contract + tokenId
71
+ load({ contract, tokenId }): Promise<NFTMetadataResponse> {
72
+ return new Promise((resolve, reject) => {
73
+ batch.push({ couple: { contract, tokenId }, resolve, reject });
74
+ timeoutBatchCall();
75
+ });
76
+ },
77
+ };
78
+ })();
79
+
80
+ export function useNftMetadata(contract: string, tokenId: string): NFTResource {
81
+ const { cache, loadNFTMetadata } = useContext(NftMetadataContext);
82
+
83
+ const key = getNftKey(contract, tokenId);
84
+
85
+ const cachedData = cache[key];
86
+
87
+ useEffect(() => {
88
+ if (!cachedData || isOutdated(cachedData)) {
89
+ loadNFTMetadata(contract, tokenId);
90
+ }
91
+ }, [contract, tokenId, cachedData, loadNFTMetadata]);
92
+
93
+ if (cachedData) {
94
+ return cachedData;
95
+ } else {
96
+ return {
97
+ status: "queued",
98
+ };
99
+ }
100
+ }
101
+
102
+ export function useNftAPI(): NFTMetadataContextAPI {
103
+ const { clearCache, loadNFTMetadata } = useContext(NftMetadataContext);
104
+
105
+ return {
106
+ clearCache,
107
+ loadNFTMetadata,
108
+ };
109
+ }
110
+
111
+ type NFTMetadataProviderProps = {
112
+ children: React.ReactNode;
113
+ };
114
+
115
+ export function NftMetadataProvider({
116
+ children,
117
+ }: NFTMetadataProviderProps): React.ReactElement {
118
+ const [state, setState] = useState<NFTMetadataContextState>({
119
+ cache: {},
120
+ });
121
+
122
+ const api = useMemo(
123
+ () => ({
124
+ loadNFTMetadata: async (contract: string, tokenId: string) => {
125
+ const key = getNftKey(contract, tokenId);
126
+
127
+ setState((oldState) => ({
128
+ ...oldState,
129
+ cache: {
130
+ ...oldState.cache,
131
+ [key]: {
132
+ status: "loading",
133
+ },
134
+ },
135
+ }));
136
+
137
+ try {
138
+ const { status, result } = await metadataCallBatcher.load({
139
+ contract,
140
+ tokenId,
141
+ });
142
+
143
+ switch (status) {
144
+ case 500:
145
+ throw new Error("NFT Metadata Provider failed");
146
+ case 404:
147
+ setState((oldState) => ({
148
+ ...oldState,
149
+ cache: {
150
+ ...oldState.cache,
151
+ [key]: {
152
+ status: "nodata",
153
+ metadata: null,
154
+ updatedAt: Date.now(),
155
+ },
156
+ },
157
+ }));
158
+ break;
159
+ case 200:
160
+ setState((oldState) => ({
161
+ ...oldState,
162
+ cache: {
163
+ ...oldState.cache,
164
+ [key]: {
165
+ status: "loaded",
166
+ metadata: result || {},
167
+ updatedAt: Date.now(),
168
+ },
169
+ },
170
+ }));
171
+ break;
172
+ default:
173
+ break;
174
+ }
175
+ } catch (error) {
176
+ setState((oldState) => ({
177
+ ...oldState,
178
+ cache: {
179
+ ...oldState.cache,
180
+ [key]: {
181
+ status: "error",
182
+ error,
183
+ updatedAt: Date.now(),
184
+ },
185
+ },
186
+ }));
187
+ }
188
+ },
189
+ clearCache: () => {
190
+ setState((oldState) => ({
191
+ ...oldState,
192
+ cache: {},
193
+ }));
194
+ },
195
+ }),
196
+ []
197
+ );
198
+
199
+ const value = { ...state, ...api };
200
+ return (
201
+ <NftMetadataContext.Provider value={value}>
202
+ {children}
203
+ </NftMetadataContext.Provider>
204
+ );
205
+ }
@@ -0,0 +1,20 @@
1
+ import { NFTResource } from "./types";
2
+
3
+ // Handle lifecycle of cached data.
4
+ // Expiration date depend on the resource's status.
5
+ export function isOutdated(resource: NFTResource): boolean {
6
+ const now = Date.now();
7
+
8
+ switch (resource.status) {
9
+ case "loaded": {
10
+ return now - resource.updatedAt > 14 * 24 * 60 * 60 * 1 * 1000; // 14 days
11
+ }
12
+ case "error": {
13
+ return now - resource.updatedAt > 1 * 1000; // 1 second
14
+ }
15
+ case "nodata": {
16
+ return now - resource.updatedAt > 24 * 60 * 60 * 1 * 1000; // 1 day
17
+ }
18
+ }
19
+ return false;
20
+ }
@@ -0,0 +1,60 @@
1
+ import { NFTMetadataResponse } from "../../types";
2
+
3
+ export type NFTResourceQueued = {
4
+ status: "queued";
5
+ };
6
+
7
+ export type NFTResourceLoading = {
8
+ status: "loading";
9
+ };
10
+
11
+ export type NFTResourceLoaded = {
12
+ status: "loaded";
13
+ metadata: Pick<NFTMetadataResponse, "result">;
14
+ updatedAt: number;
15
+ };
16
+
17
+ export type NFTResourceError = {
18
+ status: "error";
19
+ error: any;
20
+ updatedAt: number;
21
+ };
22
+
23
+ export type NFTResourceNoData = {
24
+ status: "nodata";
25
+ updatedAt: number;
26
+ };
27
+
28
+ export type NFTResource =
29
+ | NFTResourceQueued
30
+ | NFTResourceLoading
31
+ | NFTResourceLoaded
32
+ | NFTResourceError
33
+ | NFTResourceNoData;
34
+
35
+ export type NFTMetadataContextState = {
36
+ cache: Record<string, NFTResource>;
37
+ };
38
+
39
+ export type NFTMetadataContextAPI = {
40
+ loadNFTMetadata: (contract: string, tokenId: string) => Promise<void>;
41
+ clearCache: () => void;
42
+ };
43
+
44
+ export type NFTMetadataContextType = NFTMetadataContextState &
45
+ NFTMetadataContextAPI;
46
+
47
+ export type BatchElement = {
48
+ couple: {
49
+ contract: string;
50
+ tokenId: string;
51
+ };
52
+ resolve: (value: NFTMetadataResponse) => void;
53
+ reject: (reason?: Error) => void;
54
+ };
55
+
56
+ export type Batch = {
57
+ couples: Array<BatchElement["couple"]>;
58
+ resolvers: Array<BatchElement["resolve"]>;
59
+ rejecters: Array<BatchElement["reject"]>;
60
+ };
@@ -0,0 +1,78 @@
1
+ import eip55 from "eip55";
2
+ import BigNumber from "bignumber.js";
3
+ import { NFT, Operation } from "../types";
4
+
5
+ type Collection = NFT["collection"];
6
+
7
+ type CollectionMap<C> = Record<string, C>;
8
+
9
+ export type CollectionWithNFT = Collection & {
10
+ nfts: Array<Omit<NFT, "collection">>;
11
+ };
12
+
13
+ export const nftsFromOperations = (ops: Operation[]): NFT[] => {
14
+ const nftsMap = ops
15
+ // if ops are Operations get the prop nftOperations, else ops are considered nftOperations already
16
+ .flatMap((op) => (op?.nftOperations?.length ? op.nftOperations : op))
17
+ .reduce((acc: Record<string, NFT>, nftOp: Operation) => {
18
+ if (!nftOp?.contract) {
19
+ return acc;
20
+ }
21
+
22
+ // Creating a "token for a contract" unique key
23
+ const contract = eip55.encode(nftOp.contract!);
24
+ const nftKey = contract + nftOp.tokenId!;
25
+ const { tokenId, standard, id } = nftOp;
26
+
27
+ const nft = (acc[nftKey] ?? {
28
+ id,
29
+ tokenId: tokenId!,
30
+ amount: new BigNumber(0),
31
+ collection: {
32
+ contract,
33
+ standard: standard!,
34
+ },
35
+ }) as NFT;
36
+
37
+ if (nftOp.type === "NFT_IN") {
38
+ nft.amount = nft.amount.plus(nftOp.value);
39
+ } else if (nftOp.type === "NFT_OUT") {
40
+ nft.amount = nft.amount.minus(nftOp.value);
41
+ }
42
+
43
+ acc[nftKey] = nft;
44
+
45
+ return acc;
46
+ }, {});
47
+
48
+ return Object.values(nftsMap);
49
+ };
50
+
51
+ export const nftsByCollections = (
52
+ nfts: NFT[] = [],
53
+ collectionAddress?: string
54
+ ): CollectionWithNFT[] => {
55
+ const filteredNfts = collectionAddress
56
+ ? nfts.filter((n) => n.collection.contract === collectionAddress)
57
+ : nfts;
58
+
59
+ const collectionMap = filteredNfts.reduce(
60
+ (acc: CollectionMap<CollectionWithNFT>, nft: NFT) => {
61
+ const { collection, ...nftWithoutCollection } = nft;
62
+
63
+ if (!acc[collection.contract]) {
64
+ acc[collection.contract] = { ...collection, nfts: [] };
65
+ }
66
+ acc[collection.contract].nfts.push(nftWithoutCollection);
67
+
68
+ return acc;
69
+ },
70
+ {} as CollectionMap<CollectionWithNFT>
71
+ );
72
+
73
+ return Object.values(collectionMap);
74
+ };
75
+
76
+ export const getNftKey = (contract: string, tokenId: string): string => {
77
+ return `${contract}-${tokenId}`;
78
+ };
@@ -0,0 +1,3 @@
1
+ export * from "./nftId";
2
+ export * from "./helpers";
3
+ export * from "./NftMetadataProvider";
@@ -0,0 +1,17 @@
1
+ export const encodeNftId = (
2
+ accountId: string,
3
+ contract: string,
4
+ tokenId: string
5
+ ): string => {
6
+ return `${accountId}+${contract}+${tokenId.toString()}`;
7
+ };
8
+
9
+ export const decodeNftId = (id: string): unknown => {
10
+ const [accountId, contract, tokenId] = id.split("+");
11
+
12
+ return {
13
+ accountId,
14
+ contract,
15
+ tokenId,
16
+ };
17
+ };