@ledgerhq/live-common 21.19.7 → 21.20.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 (89) hide show
  1. package/lib/__tests__/svg.d.ts +9 -0
  2. package/lib/__tests__/svg.d.ts.map +1 -0
  3. package/lib/__tests__/svg.js +105 -0
  4. package/lib/__tests__/svg.js.map +1 -0
  5. package/lib/bridge/jsHelpers.d.ts +1 -1
  6. package/lib/bridge/jsHelpers.d.ts.map +1 -1
  7. package/lib/bridge/jsHelpers.js +7 -8
  8. package/lib/bridge/jsHelpers.js.map +1 -1
  9. package/lib/data/icons/react/index.d.ts +0 -2
  10. package/lib/data/icons/react/index.d.ts.map +1 -1
  11. package/lib/data/icons/react/index.js +8 -12
  12. package/lib/data/icons/react/index.js.map +1 -1
  13. package/lib/data/icons/react/matic.js +2 -2
  14. package/lib/data/icons/react/matic.js.map +1 -1
  15. package/lib/data/icons/reactNative/index.d.ts +0 -2
  16. package/lib/data/icons/reactNative/index.d.ts.map +1 -1
  17. package/lib/data/icons/reactNative/index.js +8 -12
  18. package/lib/data/icons/reactNative/index.js.map +1 -1
  19. package/lib/data/icons/reactNative/matic.js +2 -2
  20. package/lib/data/icons/reactNative/matic.js.map +1 -1
  21. package/lib/families/ethereum/modules/compound.resilience.test.js +2 -18
  22. package/lib/families/ethereum/modules/compound.resilience.test.js.map +1 -1
  23. package/lib/families/ethereum/modules/erc20.d.ts.map +1 -1
  24. package/lib/families/ethereum/modules/erc20.js +0 -12
  25. package/lib/families/ethereum/modules/erc20.js.map +1 -1
  26. package/lib/families/ethereum/modules/erc721.d.ts.map +1 -1
  27. package/lib/families/ethereum/modules/erc721.js +0 -1
  28. package/lib/families/ethereum/modules/erc721.js.map +1 -1
  29. package/lib/families/ethereum/nft.integration.test.d.ts +2 -0
  30. package/lib/families/ethereum/nft.integration.test.d.ts.map +1 -0
  31. package/lib/families/ethereum/nft.integration.test.js +264 -0
  32. package/lib/families/ethereum/nft.integration.test.js.map +1 -0
  33. package/lib/families/ethereum/nft.merging.unit.test.d.ts +2 -0
  34. package/lib/families/ethereum/nft.merging.unit.test.d.ts.map +1 -0
  35. package/lib/families/ethereum/nft.merging.unit.test.js +72 -0
  36. package/lib/families/ethereum/nft.merging.unit.test.js.map +1 -0
  37. package/lib/families/ethereum/synchronisation.js +5 -5
  38. package/lib/families/ethereum/synchronisation.js.map +1 -1
  39. package/lib/nft/NftMetadataProvider/index.d.ts +3 -2
  40. package/lib/nft/NftMetadataProvider/index.d.ts.map +1 -1
  41. package/lib/nft/NftMetadataProvider/index.js +9 -2
  42. package/lib/nft/NftMetadataProvider/index.js.map +1 -1
  43. package/lib/operation.d.ts.map +1 -1
  44. package/lib/operation.js +0 -2
  45. package/lib/operation.js.map +1 -1
  46. package/package.json +1 -1
  47. package/src/__tests__/svg.ts +78 -0
  48. package/src/bridge/jsHelpers.ts +9 -15
  49. package/src/data/icons/react/index.tsx +0 -2
  50. package/src/data/icons/react/matic.tsx +1 -1
  51. package/src/data/icons/reactNative/index.tsx +0 -2
  52. package/src/data/icons/reactNative/matic.tsx +1 -1
  53. package/src/data/icons/svg/MATIC.svg +2 -2
  54. package/src/families/ethereum/modules/compound.resilience.test.ts +2 -18
  55. package/src/families/ethereum/modules/erc20.ts +0 -4
  56. package/src/families/ethereum/modules/erc721.ts +0 -4
  57. package/src/families/ethereum/nft.integration.test.ts +168 -0
  58. package/src/families/ethereum/nft.merging.unit.test.ts +78 -0
  59. package/src/families/ethereum/synchronisation.ts +5 -5
  60. package/src/nft/NftMetadataProvider/index.tsx +12 -3
  61. package/src/operation.ts +0 -2
  62. package/lib/data/icons/react/cro.d.ts +0 -8
  63. package/lib/data/icons/react/cro.d.ts.map +0 -1
  64. package/lib/data/icons/react/cro.js +0 -30
  65. package/lib/data/icons/react/cro.js.map +0 -1
  66. package/lib/data/icons/react/grs.d.ts +0 -8
  67. package/lib/data/icons/react/grs.d.ts.map +0 -1
  68. package/lib/data/icons/react/grs.js +0 -34
  69. package/lib/data/icons/react/grs.js.map +0 -1
  70. package/lib/data/icons/reactNative/cro.d.ts +0 -8
  71. package/lib/data/icons/reactNative/cro.d.ts.map +0 -1
  72. package/lib/data/icons/reactNative/cro.js +0 -31
  73. package/lib/data/icons/reactNative/cro.js.map +0 -1
  74. package/lib/data/icons/reactNative/grs.d.ts +0 -8
  75. package/lib/data/icons/reactNative/grs.d.ts.map +0 -1
  76. package/lib/data/icons/reactNative/grs.js +0 -35
  77. package/lib/data/icons/reactNative/grs.js.map +0 -1
  78. package/lib/families/ethereum/nft.test.d.ts +0 -2
  79. package/lib/families/ethereum/nft.test.d.ts.map +0 -1
  80. package/lib/families/ethereum/nft.test.js +0 -360
  81. package/lib/families/ethereum/nft.test.js.map +0 -1
  82. package/src/data/icons/react/cro.tsx +0 -12
  83. package/src/data/icons/react/grs.tsx +0 -12
  84. package/src/data/icons/reactNative/cro.tsx +0 -13
  85. package/src/data/icons/reactNative/grs.tsx +0 -13
  86. package/src/data/icons/svg/CRO.svg +0 -3
  87. package/src/data/icons/svg/GRS.svg +0 -10
  88. package/src/families/ethereum/modules/__snapshots__/compound.resilience.test.ts.snap +0 -15991
  89. package/src/families/ethereum/nft.test.ts +0 -263
package/package.json CHANGED
@@ -8,7 +8,7 @@
8
8
  "type": "git",
9
9
  "url": "https://github.com/LedgerHQ/ledger-live-common"
10
10
  },
11
- "version": "21.19.7",
11
+ "version": "21.20.0",
12
12
  "main": "lib/index.js",
13
13
  "types": "lib/index.d.ts",
14
14
  "license": "Apache-2.0",
@@ -0,0 +1,78 @@
1
+ import fs from "fs";
2
+ import glob from "glob";
3
+ import path from "path";
4
+
5
+ const rootDir = path.join(__dirname, "../data");
6
+
7
+ declare global {
8
+ namespace jest {
9
+ interface Matchers<R> {
10
+ toBeValidSvg(a: string, b: string): R;
11
+ }
12
+ }
13
+ }
14
+
15
+ expect.extend({
16
+ toBeValidSvg(received, fileName, errorMessage) {
17
+ if (received) {
18
+ return {
19
+ message: () => `Svg: ${fileName} valid`,
20
+ pass: true,
21
+ };
22
+ } else {
23
+ return {
24
+ message: () => `Svg: ${fileName} invalid
25
+ ${errorMessage}
26
+ `,
27
+ pass: false,
28
+ };
29
+ }
30
+ },
31
+ });
32
+
33
+ test("svg icons valid", async () => {
34
+ glob.sync(`${rootDir}/icons/svg/*.svg`).map((svgFileName) => {
35
+ const buffer = fs.readFileSync(svgFileName);
36
+ const fileContent = buffer.toString();
37
+
38
+ const tags = fileContent.match(/<[^>]*?>/g) || [];
39
+
40
+ expect(tags.length > 1).toBeTruthy();
41
+
42
+ tags.forEach((element, i) => {
43
+ if (i === 0) {
44
+ const m = /width="([^"]+)"/.exec(element);
45
+ if (!m) {
46
+ throw new Error("no width found in " + svgFileName);
47
+ }
48
+ const [, size] = m;
49
+ expect(element.includes('height="' + size + '"')).toBeValidSvg(
50
+ svgFileName,
51
+ "Must contain height of same width. " + element
52
+ );
53
+ expect(
54
+ element.includes('viewBox="0 0 ' + size + " " + size + '"')
55
+ ).toBeValidSvg(svgFileName, "Must contain correct viewport");
56
+ expect(!/style=/.test(element)).toBeValidSvg(
57
+ svgFileName,
58
+ "Must not contain style attrs. " + element
59
+ );
60
+ } else {
61
+ expect(
62
+ /svg|path|line|rect|ellipse|polyline|polygon|circle|g/.test(element)
63
+ ).toBeValidSvg(
64
+ svgFileName,
65
+ "must only contain svg|path|line|rect|ellipse|polyline|polygon|circle|g"
66
+ );
67
+ expect(
68
+ !/style=|clipPath|mask|id=|class=|url\(|clip-path|data|defs/.test(
69
+ element
70
+ )
71
+ ).toBeValidSvg(
72
+ svgFileName,
73
+ "must not contain style=|clipPath|mask|id|class|url|clip-path|data|defs"
74
+ );
75
+ }
76
+ });
77
+ });
78
+ });
@@ -1,5 +1,4 @@
1
1
  import isEqual from "lodash/isEqual";
2
- import setWith from "lodash/setWith";
3
2
  import { BigNumber } from "bignumber.js";
4
3
  import { Observable, from } from "rxjs";
5
4
  import { log } from "@ledgerhq/logs";
@@ -64,7 +63,7 @@ type AccountUpdater = (arg0: Account) => Account;
64
63
  const sameDate = (a, b) => Math.abs(a - b) < 1000 * 60 * 30;
65
64
 
66
65
  // an operation is relatively immutable, however we saw that sometimes it can temporarily change due to reorg,..
67
- export const sameOp = (a: Operation, b: Operation) =>
66
+ export const sameOp = (a: Operation, b: Operation): boolean =>
68
67
  a === b ||
69
68
  (a.id === b.id && // hash, accountId, type are in id
70
69
  (a.fee ? a.fee.isEqualTo(b.fee) : a.fee === b.fee) &&
@@ -119,20 +118,15 @@ Operation[] {
119
118
  return all;
120
119
  }
121
120
 
122
- export const mergeNfts = (
123
- oldNfts: NFT[] | undefined,
124
- newNfts: NFT[] | undefined
125
- ): NFT[] => {
126
- if (!newNfts?.length) return oldNfts ?? [];
127
-
121
+ export const mergeNfts = (oldNfts: NFT[], newNfts: NFT[]): NFT[] => {
128
122
  // Getting a map of id => NFT
129
- const newNftsPerId: Record<string, NFT> = (newNfts as NFT[]).reduce(
130
- (acc, curr) => setWith(acc, curr.id, curr, Object),
131
- {}
132
- );
123
+ const newNftsPerId: Record<string, NFT> = {};
124
+ newNfts.forEach((n) => {
125
+ newNftsPerId[n.id] = n;
126
+ });
133
127
 
134
128
  // copying the argument to avoid mutating it
135
- const nfts = oldNfts?.slice() ?? [];
129
+ const nfts = oldNfts.slice();
136
130
  for (let i = 0; i < nfts.length; i++) {
137
131
  const nft = nfts[i];
138
132
 
@@ -141,8 +135,8 @@ export const mergeNfts = (
141
135
  nfts.splice(i, 1);
142
136
  i--;
143
137
  } else if (!isEqual(nft, newNftsPerId[nft.id])) {
144
- // Use the new NFT instead (as a copy cause we're deleting the reference just after)
145
- nfts[i] = Object.assign({}, newNftsPerId[nft.id]);
138
+ // Use the new NFT instead
139
+ nfts[i] = newNftsPerId[nft.id];
146
140
  }
147
141
 
148
142
  // Delete it from the newNfts to keep only the un-added ones at the end
@@ -95,7 +95,6 @@ export { default as colx } from "./colx";
95
95
  export { default as comp } from "./comp";
96
96
  export { default as coqui } from "./coqui";
97
97
  export { default as cred } from "./cred";
98
- export { default as cro } from "./cro";
99
98
  export { default as crpt } from "./crpt";
100
99
  export { default as crw } from "./crw";
101
100
  export { default as cs } from "./cs";
@@ -179,7 +178,6 @@ export { default as gnt } from "./gnt";
179
178
  export { default as gold } from "./gold";
180
179
  export { default as grc } from "./grc";
181
180
  export { default as grin } from "./grin";
182
- export { default as grs } from "./grs";
183
181
  export { default as gsc } from "./gsc";
184
182
  export { default as gto } from "./gto";
185
183
  export { default as gup } from "./gup";
@@ -6,7 +6,7 @@ interface Props {
6
6
  };
7
7
 
8
8
  function matic({size, color = "currentColor"}: Props) {
9
- return <svg width={size} height={size} viewBox="0 0 24 24" fill="none"><path fillRule="evenodd" clipRule="evenodd" d="M17.0004 6.08237L14.5034 7.52212H14.5009V10.4019L14.4921 10.4031L7.00013 6.08237L4.50113 7.52237L4.49988 7.52187V16.4749L6.99863 17.9156L7.00013 17.9041V17.9149L9.49988 16.4749V13.2849L11.9944 14.7224L11.9959 14.7254L11.9979 14.7241L14.5014 13.2911V16.4761L16.9896 17.9101V17.9176L16.9959 17.9139L17.0004 17.9164L17.0039 17.9094L19.4889 16.4776V7.52987L19.5001 7.52337L17.0004 6.08237Z" fill={color} /></svg>;
9
+ return <svg width={size} height={size} viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M17.0922 8.69345C16.7232 8.47781 16.2435 8.47781 15.8377 8.69345L12.9594 10.3467L11.0037 11.425L8.12546 13.0782C7.75645 13.2939 7.27675 13.2939 6.87085 13.0782L4.58303 11.7843C4.21402 11.5687 3.95572 11.1734 3.95572 10.7421V8.19028C3.95572 7.75899 4.17712 7.36364 4.58303 7.14799L6.83394 5.89007C7.20295 5.67442 7.68265 5.67442 8.08855 5.89007L10.3395 7.14799C10.7085 7.36364 10.9668 7.75899 10.9668 8.19028V9.84356L12.9225 8.72939V7.07612C12.9225 6.64482 12.7011 6.24947 12.2952 6.03383L8.12546 3.66173C7.75645 3.44609 7.27675 3.44609 6.87085 3.66173L2.62731 6.03383C2.2214 6.24947 2 6.64482 2 7.07612V11.8563C2 12.2875 2.2214 12.6829 2.62731 12.8986L6.87085 15.2706C7.23984 15.4863 7.71956 15.4863 8.12546 15.2706L11.0037 13.6533L12.9594 12.5391L15.8377 10.9218C16.2066 10.7061 16.6863 10.7061 17.0922 10.9218L19.3432 12.1797C19.7121 12.3954 19.9704 12.7907 19.9704 13.222V15.7738C19.9704 16.2051 19.7491 16.6004 19.3432 16.8161L17.0922 18.11C16.7232 18.3256 16.2435 18.3256 15.8377 18.11L13.5867 16.852C13.2177 16.6364 12.9594 16.241 12.9594 15.8097V14.1565L11.0037 15.2706V16.9239C11.0037 17.3552 11.2251 17.7505 11.631 17.9662L15.8745 20.3383C16.2435 20.5539 16.7232 20.5539 17.1291 20.3383L21.3727 17.9662C21.7417 17.7505 22 17.3552 22 16.9239V12.1437C22 11.7125 21.7786 11.3172 21.3727 11.1015L17.0922 8.69345Z" fill={color} /></svg>;
10
10
  }
11
11
 
12
12
  export default matic;
@@ -95,7 +95,6 @@ export { default as colx } from "./colx";
95
95
  export { default as comp } from "./comp";
96
96
  export { default as coqui } from "./coqui";
97
97
  export { default as cred } from "./cred";
98
- export { default as cro } from "./cro";
99
98
  export { default as crpt } from "./crpt";
100
99
  export { default as crw } from "./crw";
101
100
  export { default as cs } from "./cs";
@@ -179,7 +178,6 @@ export { default as gnt } from "./gnt";
179
178
  export { default as gold } from "./gold";
180
179
  export { default as grc } from "./grc";
181
180
  export { default as grin } from "./grin";
182
- export { default as grs } from "./grs";
183
181
  export { default as gsc } from "./gsc";
184
182
  export { default as gto } from "./gto";
185
183
  export { default as gup } from "./gup";
@@ -7,7 +7,7 @@ interface Props {
7
7
  };
8
8
 
9
9
  function matic({ size, color }: Props) {
10
- return <Svg width={size} height={size} viewBox="0 0 24 24" fill="none"><Path fillRule="evenodd" clipRule="evenodd" d="M17.0004 6.08237L14.5034 7.52212H14.5009V10.4019L14.4921 10.4031L7.00013 6.08237L4.50113 7.52237L4.49988 7.52187V16.4749L6.99863 17.9156L7.00013 17.9041V17.9149L9.49988 16.4749V13.2849L11.9944 14.7224L11.9959 14.7254L11.9979 14.7241L14.5014 13.2911V16.4761L16.9896 17.9101V17.9176L16.9959 17.9139L17.0004 17.9164L17.0039 17.9094L19.4889 16.4776V7.52987L19.5001 7.52337L17.0004 6.08237Z" fill={color} /></Svg>;
10
+ return <Svg width={size} height={size} viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><Path d="M17.0922 8.69345C16.7232 8.47781 16.2435 8.47781 15.8377 8.69345L12.9594 10.3467L11.0037 11.425L8.12546 13.0782C7.75645 13.2939 7.27675 13.2939 6.87085 13.0782L4.58303 11.7843C4.21402 11.5687 3.95572 11.1734 3.95572 10.7421V8.19028C3.95572 7.75899 4.17712 7.36364 4.58303 7.14799L6.83394 5.89007C7.20295 5.67442 7.68265 5.67442 8.08855 5.89007L10.3395 7.14799C10.7085 7.36364 10.9668 7.75899 10.9668 8.19028V9.84356L12.9225 8.72939V7.07612C12.9225 6.64482 12.7011 6.24947 12.2952 6.03383L8.12546 3.66173C7.75645 3.44609 7.27675 3.44609 6.87085 3.66173L2.62731 6.03383C2.2214 6.24947 2 6.64482 2 7.07612V11.8563C2 12.2875 2.2214 12.6829 2.62731 12.8986L6.87085 15.2706C7.23984 15.4863 7.71956 15.4863 8.12546 15.2706L11.0037 13.6533L12.9594 12.5391L15.8377 10.9218C16.2066 10.7061 16.6863 10.7061 17.0922 10.9218L19.3432 12.1797C19.7121 12.3954 19.9704 12.7907 19.9704 13.222V15.7738C19.9704 16.2051 19.7491 16.6004 19.3432 16.8161L17.0922 18.11C16.7232 18.3256 16.2435 18.3256 15.8377 18.11L13.5867 16.852C13.2177 16.6364 12.9594 16.241 12.9594 15.8097V14.1565L11.0037 15.2706V16.9239C11.0037 17.3552 11.2251 17.7505 11.631 17.9662L15.8745 20.3383C16.2435 20.5539 16.7232 20.5539 17.1291 20.3383L21.3727 17.9662C21.7417 17.7505 22 17.3552 22 16.9239V12.1437C22 11.7125 21.7786 11.3172 21.3727 11.1015L17.0922 8.69345Z" fill={color} /></Svg>;
11
11
  }
12
12
 
13
13
  export default matic;
@@ -1,3 +1,3 @@
1
- <svg width="24" height="24" viewBox="0 0 24 24" fill="none">
2
- <path fill-rule="evenodd" clip-rule="evenodd" d="M17.0004 6.08237L14.5034 7.52212H14.5009V10.4019L14.4921 10.4031L7.00013 6.08237L4.50113 7.52237L4.49988 7.52187V16.4749L6.99863 17.9156L7.00013 17.9041V17.9149L9.49988 16.4749V13.2849L11.9944 14.7224L11.9959 14.7254L11.9979 14.7241L14.5014 13.2911V16.4761L16.9896 17.9101V17.9176L16.9959 17.9139L17.0004 17.9164L17.0039 17.9094L19.4889 16.4776V7.52987L19.5001 7.52337L17.0004 6.08237Z" fill="#2B6DEF"/>
1
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <path d="M17.0922 8.69345C16.7232 8.47781 16.2435 8.47781 15.8377 8.69345L12.9594 10.3467L11.0037 11.425L8.12546 13.0782C7.75645 13.2939 7.27675 13.2939 6.87085 13.0782L4.58303 11.7843C4.21402 11.5687 3.95572 11.1734 3.95572 10.7421V8.19028C3.95572 7.75899 4.17712 7.36364 4.58303 7.14799L6.83394 5.89007C7.20295 5.67442 7.68265 5.67442 8.08855 5.89007L10.3395 7.14799C10.7085 7.36364 10.9668 7.75899 10.9668 8.19028V9.84356L12.9225 8.72939V7.07612C12.9225 6.64482 12.7011 6.24947 12.2952 6.03383L8.12546 3.66173C7.75645 3.44609 7.27675 3.44609 6.87085 3.66173L2.62731 6.03383C2.2214 6.24947 2 6.64482 2 7.07612V11.8563C2 12.2875 2.2214 12.6829 2.62731 12.8986L6.87085 15.2706C7.23984 15.4863 7.71956 15.4863 8.12546 15.2706L11.0037 13.6533L12.9594 12.5391L15.8377 10.9218C16.2066 10.7061 16.6863 10.7061 17.0922 10.9218L19.3432 12.1797C19.7121 12.3954 19.9704 12.7907 19.9704 13.222V15.7738C19.9704 16.2051 19.7491 16.6004 19.3432 16.8161L17.0922 18.11C16.7232 18.3256 16.2435 18.3256 15.8377 18.11L13.5867 16.852C13.2177 16.6364 12.9594 16.241 12.9594 15.8097V14.1565L11.0037 15.2706V16.9239C11.0037 17.3552 11.2251 17.7505 11.631 17.9662L15.8745 20.3383C16.2435 20.5539 16.7232 20.5539 17.1291 20.3383L21.3727 17.9662C21.7417 17.7505 22 17.3552 22 16.9239V12.1437C22 11.7125 21.7786 11.3172 21.3727 11.1015L17.0922 8.69345Z" fill="#8247E5"/>
3
3
  </svg>
@@ -1,6 +1,6 @@
1
1
  import "../../../__tests__/test-helpers/setup";
2
2
  import { reduce } from "rxjs/operators";
3
- import { fromAccountRaw, toAccountRaw } from "../../../account";
3
+ import { fromAccountRaw } from "../../../account";
4
4
  import type { Account } from "../../../types";
5
5
  import { getAccountBridge } from "../../../bridge";
6
6
  import { makeBridgeCacheSystem } from "../../../bridge/cache";
@@ -33,21 +33,5 @@ test("if API is down, an account still sync fine", async () => {
33
33
  })
34
34
  .pipe(reduce((a, f: (arg0: Account) => Account) => f(a), account))
35
35
  .toPromise();
36
- const raw = toAccountRaw(synced);
37
- // empty unstable fields
38
- raw.blockHeight = 0;
39
- raw.lastSyncDate = "";
40
- raw.creationDate = "";
41
- delete raw.balanceHistoryCache;
42
- delete raw.syncHash;
43
- raw.subAccounts = (raw.subAccounts || []).map((a) => {
44
- delete a.balanceHistoryCache;
45
- return a;
46
- });
47
- raw.operations = raw.operations.map((op) => {
48
- op.blockHeight = 0;
49
- op.date = "";
50
- return op;
51
- });
52
- expect(raw).toMatchSnapshot();
36
+ expect(synced).not.toBeFalsy();
53
37
  });
@@ -137,10 +137,6 @@ const erc20approve: ModeModule = {
137
137
 
138
138
  fillOptimisticOperation(_account, _transaction, operation) {
139
139
  operation.type = "FEES";
140
- operation.extra = {
141
- ...operation.extra,
142
- approving: true, // workaround to track the status ENABLING
143
- };
144
140
  },
145
141
  };
146
142
  export const modes: Record<Modes, ModeModule> = {
@@ -118,10 +118,6 @@ const erc721Transfer: ModeModule = {
118
118
  */
119
119
  fillOptimisticOperation(a, t, op) {
120
120
  op.type = "FEES";
121
- op.extra = {
122
- ...op.extra,
123
- approving: true, // workaround to track the status ENABLING
124
- };
125
121
  },
126
122
  };
127
123
 
@@ -0,0 +1,168 @@
1
+ import "../../__tests__/test-helpers/setup";
2
+ import { reduce } from "rxjs/operators";
3
+ import { fromAccountRaw, toAccountRaw } from "../../account";
4
+ import type { Account, AccountRaw } from "../../types";
5
+ import { getAccountBridge } from "../../bridge";
6
+ import { makeBridgeCacheSystem } from "../../bridge/cache";
7
+ import { patchAccount } from "../../reconciliation";
8
+ import { setEnv } from "../../env";
9
+
10
+ jest.setTimeout(200000);
11
+
12
+ const ethTestingAccounts: AccountRaw[] = [
13
+ {
14
+ id: "js:1:ethereum:0xb98d10d9f6d07ba283bfd21b2dfec050f9ae282a:",
15
+ seedIdentifier: "0xb98d10d9f6d07ba283bfd21b2dfec050f9ae282a",
16
+ name: "G4sp4rd",
17
+ derivationMode: "",
18
+ index: 0,
19
+ freshAddress: "0xb98d10d9f6d07ba283bfd21b2dfec050f9ae282a",
20
+ freshAddressPath: "44'/60'/0'/0/0",
21
+ freshAddresses: [],
22
+ pendingOperations: [],
23
+ operations: [],
24
+ currencyId: "ethereum",
25
+ unitMagnitude: 18,
26
+ balance: "",
27
+ blockHeight: 0,
28
+ lastSyncDate: "",
29
+ xpub: "",
30
+ },
31
+ ];
32
+
33
+ const gaspardPolygonAccount: AccountRaw = {
34
+ id: "js:1:polygon:0xb98d10d9f6d07ba283bfd21b2dfec050f9ae282a:",
35
+ seedIdentifier: "0xb98d10d9f6d07ba283bfd21b2dfec050f9ae282a",
36
+ name: "G4sp4rd",
37
+ derivationMode: "",
38
+ index: 0,
39
+ freshAddress: "0xb98d10d9f6d07ba283bfd21b2dfec050f9ae282a",
40
+ freshAddressPath: "44'/60'/0'/0/0",
41
+ freshAddresses: [],
42
+ pendingOperations: [],
43
+ operations: [],
44
+ currencyId: "polygon",
45
+ unitMagnitude: 18,
46
+ balance: "",
47
+ blockHeight: 0,
48
+ lastSyncDate: "",
49
+ xpub: "",
50
+ };
51
+
52
+ const gaspardBscAccount: AccountRaw = {
53
+ id: "js:1:bsc:0xb98d10d9f6d07ba283bfd21b2dfec050f9ae282a:",
54
+ seedIdentifier: "0xb98d10d9f6d07ba283bfd21b2dfec050f9ae282a",
55
+ name: "G4sp4rd",
56
+ derivationMode: "",
57
+ index: 0,
58
+ freshAddress: "0xb98d10d9f6d07ba283bfd21b2dfec050f9ae282a",
59
+ freshAddressPath: "44'/60'/0'/0/0",
60
+ freshAddresses: [],
61
+ pendingOperations: [],
62
+ operations: [],
63
+ currencyId: "bsc",
64
+ unitMagnitude: 18,
65
+ balance: "",
66
+ blockHeight: 0,
67
+ lastSyncDate: "",
68
+ xpub: "",
69
+ };
70
+
71
+ async function sync(account, withNFT = true) {
72
+ const bridge = getAccountBridge(account);
73
+ const blacklistedTokenIds = [];
74
+ setEnv("NFT", withNFT);
75
+ const r = await bridge
76
+ .sync(account, {
77
+ paginationConfig: {},
78
+ blacklistedTokenIds,
79
+ })
80
+ .pipe(reduce((a, f: (arg0: Account) => Account) => f(a), account))
81
+ .toPromise();
82
+ setEnv("NFT", false);
83
+ return r;
84
+ }
85
+
86
+ const localCache = {};
87
+ const cache = makeBridgeCacheSystem({
88
+ saveData(c, d) {
89
+ localCache[c.id] = d;
90
+ return Promise.resolve();
91
+ },
92
+ getData(c) {
93
+ return Promise.resolve(localCache[c.id]);
94
+ },
95
+ });
96
+
97
+ ethTestingAccounts.forEach((a) => {
98
+ describe(a.name + " NFT on ethereum", () => {
99
+ let account = fromAccountRaw(a);
100
+
101
+ test("first sync & have nfts", async () => {
102
+ await cache.prepareCurrency(account.currency);
103
+ account = await sync(account);
104
+ expect(account.nfts?.length || 0).not.toBe(0);
105
+ });
106
+
107
+ test("no nft should be on quantity 0 or negative", () => {
108
+ expect(account.nfts?.find((n) => !n.amount.gt(0))).toEqual(undefined);
109
+ });
110
+
111
+ test("patchAccount restore new NFTs correctly", () => {
112
+ const copy = {
113
+ ...account,
114
+ operations: [],
115
+ nfts: account.nfts?.slice(Math.ceil((account.nfts?.length || 0) / 2)),
116
+ };
117
+ const newAccount = patchAccount(copy, toAccountRaw(account));
118
+ expect(newAccount.nfts).toEqual(account.nfts);
119
+ });
120
+
121
+ test("remove half NFTs will restore them with half operations", async () => {
122
+ const halfOps = Math.ceil(account.operations.length / 2);
123
+ const copy = {
124
+ ...account,
125
+ operations: account.operations.slice(halfOps),
126
+ nfts: account.nfts?.slice(Math.ceil((account.nfts?.length || 0) / 2)),
127
+ };
128
+ const resync = await sync(copy);
129
+ expect(resync.nfts).toEqual(account.nfts);
130
+ });
131
+
132
+ test("start account with .nfts and disable the NFT flag should make it disappear", async () => {
133
+ expect(account.nfts).not.toBeFalsy();
134
+ const resync = await sync(account, false);
135
+ expect(resync.nfts).toBeFalsy();
136
+ });
137
+
138
+ test("start account without .nfts and enable the NFT flag should make it appear", async () => {
139
+ const first = await sync(account, false);
140
+ expect(first.nfts).toBeFalsy();
141
+ const second = await sync(first, true);
142
+ expect(second.nfts).not.toBeFalsy();
143
+ expect(second.nfts).toEqual(account.nfts);
144
+ });
145
+ });
146
+ });
147
+
148
+ describe.skip("gaspard NFT on polygon", () => {
149
+ let account = fromAccountRaw(gaspardPolygonAccount);
150
+ test("first sync", async () => {
151
+ await cache.prepareCurrency(account.currency);
152
+ account = await sync(account);
153
+ });
154
+ test(".nfts shouldn't be visible", () => {
155
+ expect(account.nfts).toBeFalsy();
156
+ });
157
+ });
158
+
159
+ describe("gaspard NFT on bsc", () => {
160
+ let account = fromAccountRaw(gaspardBscAccount);
161
+ test("first sync", async () => {
162
+ await cache.prepareCurrency(account.currency);
163
+ account = await sync(account);
164
+ });
165
+ test(".nfts shouldn't be visible", () => {
166
+ expect(account.nfts).toBeFalsy();
167
+ });
168
+ });
@@ -0,0 +1,78 @@
1
+ import "../../__tests__/test-helpers/setup";
2
+ import BigNumber from "bignumber.js";
3
+ import { toNFTRaw } from "../../account";
4
+ import type { NFT } from "../../types";
5
+ import { mergeNfts } from "../../bridge/jsHelpers";
6
+ import { encodeNftId } from "../../nft";
7
+
8
+ describe("nft merging", () => {
9
+ const makeNFT = (tokenId: string, contract: string, amount: number): NFT => ({
10
+ id: encodeNftId("test", contract, tokenId),
11
+ tokenId,
12
+ amount: new BigNumber(amount),
13
+ collection: {
14
+ contract,
15
+ standard: "erc721",
16
+ },
17
+ });
18
+ const oldNfts = [
19
+ makeNFT("1", "contract1", 10),
20
+ makeNFT("2", "contract1", 1),
21
+ makeNFT("3", "contract2", 6),
22
+ ];
23
+
24
+ test("should remove first NFT and return new array with same refs", () => {
25
+ const nfts = [makeNFT("2", "contract1", 1), makeNFT("3", "contract2", 6)];
26
+ const newNfts = mergeNfts(oldNfts, nfts);
27
+
28
+ expect(newNfts.map(toNFTRaw)).toEqual(nfts.map(toNFTRaw));
29
+ expect(oldNfts[1]).toBe(newNfts[0]);
30
+ expect(oldNfts[2]).toBe(newNfts[1]);
31
+ });
32
+
33
+ test("should remove any NFT and return new array with same refs", () => {
34
+ const nfts = [makeNFT("1", "contract1", 10), makeNFT("3", "contract2", 6)];
35
+ const newNfts = mergeNfts(oldNfts, nfts);
36
+
37
+ expect(newNfts.map(toNFTRaw)).toEqual(nfts.map(toNFTRaw));
38
+ expect(oldNfts[0]).toBe(newNfts[0]);
39
+ expect(oldNfts[2]).toBe(newNfts[1]);
40
+ });
41
+
42
+ test("should change NFT amount and return new array with new ref", () => {
43
+ const nfts = [
44
+ makeNFT("1", "contract1", 10),
45
+ makeNFT("2", "contract1", 5),
46
+ makeNFT("3", "contract2", 6),
47
+ ];
48
+ const addToNft1 = mergeNfts(oldNfts, nfts);
49
+
50
+ expect(addToNft1.map(toNFTRaw)).toEqual(nfts.map(toNFTRaw));
51
+ expect(oldNfts[0]).toBe(addToNft1[0]);
52
+ expect(oldNfts[1]).not.toBe(addToNft1[1]);
53
+ expect(oldNfts[2]).toBe(addToNft1[2]);
54
+ });
55
+
56
+ test("should add NFT and return new array with new ref", () => {
57
+ const nfts = [
58
+ makeNFT("1", "contract1", 10),
59
+ makeNFT("2", "contract1", 1),
60
+ makeNFT("3", "contract2", 6),
61
+ makeNFT("4", "contract2", 4),
62
+ ];
63
+ const addToNft1 = mergeNfts(oldNfts, nfts);
64
+
65
+ expect(addToNft1.map(toNFTRaw)).toEqual(
66
+ [
67
+ makeNFT("4", "contract2", 4),
68
+ makeNFT("1", "contract1", 10),
69
+ makeNFT("2", "contract1", 1),
70
+ makeNFT("3", "contract2", 6),
71
+ ].map(toNFTRaw)
72
+ );
73
+ expect(oldNfts[0]).toBe(addToNft1[1]);
74
+ expect(oldNfts[1]).toBe(addToNft1[2]);
75
+ expect(oldNfts[2]).toBe(addToNft1[3]);
76
+ expect(addToNft1[0]).toBe(nfts[3]);
77
+ });
78
+ });
@@ -167,7 +167,7 @@ export const getAccountShape: GetAccountShape = async (
167
167
  const operations = mergeOps(initialStableOperations, newOps);
168
168
 
169
169
  const nfts = isNFTActive(currency)
170
- ? mergeNfts(initialAccount?.nfts, nftsFromOperations(operations))
170
+ ? mergeNfts(initialAccount?.nfts || [], nftsFromOperations(operations))
171
171
  : undefined;
172
172
 
173
173
  const accountShape: Partial<Account> = {
@@ -396,7 +396,7 @@ const txToOps =
396
396
  const erc1155Operations =
397
397
  !erc1155_transfer_events || !isNFTActive(currency)
398
398
  ? []
399
- : flatMap(erc1155_transfer_events, (event) => {
399
+ : flatMap(erc1155_transfer_events, (event, i) => {
400
400
  const sender = safeEncodeEIP55(event.sender);
401
401
  const receiver = safeEncodeEIP55(event.receiver);
402
402
  const contract = safeEncodeEIP55(event.contract);
@@ -410,7 +410,7 @@ const txToOps =
410
410
 
411
411
  const all: Operation[] = [];
412
412
 
413
- event.transfers.forEach((transfer) => {
413
+ event.transfers.forEach((transfer, j) => {
414
414
  const tokenId = transfer.id;
415
415
  const value = new BigNumber(transfer.value);
416
416
  const nftId = encodeNftId(id, event.contract, tokenId);
@@ -418,7 +418,7 @@ const txToOps =
418
418
  if (sending) {
419
419
  const type = "NFT_OUT";
420
420
  all.push({
421
- id: `${nftId}-${hash}-${type}`,
421
+ id: `${nftId}-${hash}-${type}-i${i}_${j}`,
422
422
  senders: [sender],
423
423
  recipients: [receiver],
424
424
  contract,
@@ -441,7 +441,7 @@ const txToOps =
441
441
  if (receiving) {
442
442
  const type = "NFT_IN";
443
443
  all.push({
444
- id: `${nftId}-${hash}-${type}`,
444
+ id: `${nftId}-${hash}-${type}-i${i}`,
445
445
  senders: [sender],
446
446
  recipients: [receiver],
447
447
  contract,
@@ -7,7 +7,7 @@ import React, {
7
7
  } from "react";
8
8
  import { Currency, findCryptoCurrencyById } from "@ledgerhq/cryptoassets";
9
9
  import { API, apiForCurrency } from "../../api/Ethereum";
10
- import { NFTMetadataResponse } from "../../types";
10
+ import { NFT, NFTMetadataResponse } from "../../types";
11
11
  import { getNftKey } from "../helpers";
12
12
  import {
13
13
  Batch,
@@ -77,14 +77,19 @@ export const metadataCallBatcher = (() => {
77
77
  };
78
78
  })();
79
79
 
80
- export function useNftMetadata(contract: string, tokenId: string): NFTResource {
80
+ // DEPRECATED, use useNftResource
81
+ export function useNftMetadata(
82
+ contract: string | undefined,
83
+ tokenId: string | undefined
84
+ ): NFTResource {
81
85
  const { cache, loadNFTMetadata } = useContext(NftMetadataContext);
82
86
 
83
- const key = getNftKey(contract, tokenId);
87
+ const key = contract && tokenId ? getNftKey(contract, tokenId) : "";
84
88
 
85
89
  const cachedData = cache[key];
86
90
 
87
91
  useEffect(() => {
92
+ if (!contract || !tokenId) return;
88
93
  if (!cachedData || isOutdated(cachedData)) {
89
94
  loadNFTMetadata(contract, tokenId);
90
95
  }
@@ -99,6 +104,10 @@ export function useNftMetadata(contract: string, tokenId: string): NFTResource {
99
104
  }
100
105
  }
101
106
 
107
+ export function useNftResource(nft: NFT | undefined): NFTResource {
108
+ return useNftMetadata(nft?.collection.contract, nft?.tokenId);
109
+ }
110
+
102
111
  export function useNftAPI(): NFTMetadataContextAPI {
103
112
  const { clearCache, loadNFTMetadata } = useContext(NftMetadataContext);
104
113
 
package/src/operation.ts CHANGED
@@ -105,7 +105,6 @@ export function getOperationAmountNumber(op: Operation): BigNumber {
105
105
  case "REWARD":
106
106
  case "REWARD_PAYOUT":
107
107
  case "SUPPLY":
108
- case "NFT_IN":
109
108
  return op.value;
110
109
 
111
110
  case "OUT":
@@ -119,7 +118,6 @@ export function getOperationAmountNumber(op: Operation): BigNumber {
119
118
  case "OPT_OUT":
120
119
  case "REDEEM":
121
120
  case "SLASH":
122
- case "NFT_OUT":
123
121
  return op.value.negated();
124
122
 
125
123
  case "FREEZE":
@@ -1,8 +0,0 @@
1
- /// <reference types="react" />
2
- interface Props {
3
- size: number;
4
- color?: string;
5
- }
6
- declare function cro({ size, color }: Props): JSX.Element;
7
- export default cro;
8
- //# sourceMappingURL=cro.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"cro.d.ts","sourceRoot":"","sources":["../../../../src/data/icons/react/cro.tsx"],"names":[],"mappings":";AAEA,UAAU,KAAK;IACH,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAEX,iBAAS,GAAG,CAAC,EAAC,IAAI,EAAE,KAAsB,EAAC,EAAE,KAAK,eAEjD;AAED,eAAe,GAAG,CAAC"}
@@ -1,30 +0,0 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
5
- }) : (function(o, m, k, k2) {
6
- if (k2 === undefined) k2 = k;
7
- o[k2] = m[k];
8
- }));
9
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
10
- Object.defineProperty(o, "default", { enumerable: true, value: v });
11
- }) : function(o, v) {
12
- o["default"] = v;
13
- });
14
- var __importStar = (this && this.__importStar) || function (mod) {
15
- if (mod && mod.__esModule) return mod;
16
- var result = {};
17
- if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
18
- __setModuleDefault(result, mod);
19
- return result;
20
- };
21
- exports.__esModule = true;
22
- var React = __importStar(require("react"));
23
- ;
24
- function cro(_a) {
25
- var size = _a.size, _b = _a.color, color = _b === void 0 ? "currentColor" : _b;
26
- return React.createElement("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", xmlns: "http://www.w3.org/2000/svg" },
27
- React.createElement("path", { fillRule: "evenodd", clipRule: "evenodd", d: "M0 5.99919L10.3937 0L20.7808 5.99919V18.0008L10.3937 24L0 18.0008V5.99919ZM10.3937 20.4289L17.7038 16.2314V7.77829L10.3937 3.55819L3.08032 7.77829V16.2314L10.3937 20.4289ZM10.3906 17.6037L15.2436 14.801V9.19898L10.3906 6.39634L5.53767 9.19898V14.801L10.3906 17.6037Z", fill: color }));
28
- }
29
- exports["default"] = cro;
30
- //# sourceMappingURL=cro.js.map