@ledgerhq/live-common 21.19.0 → 21.19.1

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 (87) hide show
  1. package/lib/__tests__/test-helpers/setup.js +1 -0
  2. package/lib/__tests__/test-helpers/setup.js.map +1 -1
  3. package/lib/account/helpers.d.ts.map +1 -1
  4. package/lib/account/helpers.js +1 -0
  5. package/lib/account/helpers.js.map +1 -1
  6. package/lib/api/Ethereum.d.ts.map +1 -1
  7. package/lib/api/Ethereum.js +2 -1
  8. package/lib/api/Ethereum.js.map +1 -1
  9. package/lib/api/explorerConfig/index.d.ts.map +1 -1
  10. package/lib/api/explorerConfig/index.js +0 -4
  11. package/lib/api/explorerConfig/index.js.map +1 -1
  12. package/lib/bridge/jsHelpers.d.ts.map +1 -1
  13. package/lib/bridge/jsHelpers.js +7 -6
  14. package/lib/bridge/jsHelpers.js.map +1 -1
  15. package/lib/env.d.ts +10 -5
  16. package/lib/env.d.ts.map +1 -1
  17. package/lib/env.js +5 -0
  18. package/lib/env.js.map +1 -1
  19. package/lib/env.react.d.ts +1 -1
  20. package/lib/env.react.d.ts.map +1 -1
  21. package/lib/families/bitcoin/test-dataset.js +2 -2
  22. package/lib/families/bitcoin/test-dataset.js.map +1 -1
  23. package/lib/families/ethereum/bridge/js.d.ts.map +1 -1
  24. package/lib/families/ethereum/bridge/js.js +3 -0
  25. package/lib/families/ethereum/bridge/js.js.map +1 -1
  26. package/lib/families/ethereum/modules/erc1155.d.ts.map +1 -1
  27. package/lib/families/ethereum/modules/erc1155.js +21 -9
  28. package/lib/families/ethereum/modules/erc1155.js.map +1 -1
  29. package/lib/families/ethereum/modules/erc721.d.ts +3 -1
  30. package/lib/families/ethereum/modules/erc721.d.ts.map +1 -1
  31. package/lib/families/ethereum/modules/erc721.js +101 -7
  32. package/lib/families/ethereum/modules/erc721.js.map +1 -1
  33. package/lib/families/ethereum/modules/index.d.ts +5 -0
  34. package/lib/families/ethereum/modules/index.d.ts.map +1 -1
  35. package/lib/families/ethereum/modules/index.js +9 -1
  36. package/lib/families/ethereum/modules/index.js.map +1 -1
  37. package/lib/families/ethereum/nft.test.js +220 -34
  38. package/lib/families/ethereum/nft.test.js.map +1 -1
  39. package/lib/families/ethereum/signOperation.d.ts.map +1 -1
  40. package/lib/families/ethereum/signOperation.js +3 -4
  41. package/lib/families/ethereum/signOperation.js.map +1 -1
  42. package/lib/families/ethereum/synchronisation.d.ts.map +1 -1
  43. package/lib/families/ethereum/synchronisation.js +21 -38
  44. package/lib/families/ethereum/synchronisation.js.map +1 -1
  45. package/lib/families/ethereum/transaction.d.ts.map +1 -1
  46. package/lib/families/ethereum/transaction.js +4 -4
  47. package/lib/families/ethereum/transaction.js.map +1 -1
  48. package/lib/families/ethereum/types.d.ts +2 -0
  49. package/lib/families/ethereum/types.d.ts.map +1 -1
  50. package/lib/families/ethereum/types.js.map +1 -1
  51. package/lib/nft/helpers.d.ts +1 -2
  52. package/lib/nft/helpers.d.ts.map +1 -1
  53. package/lib/nft/helpers.js +2 -10
  54. package/lib/nft/helpers.js.map +1 -1
  55. package/lib/nft/index.d.ts +1 -0
  56. package/lib/nft/index.d.ts.map +1 -1
  57. package/lib/nft/index.js +1 -0
  58. package/lib/nft/index.js.map +1 -1
  59. package/lib/nft/support.d.ts +4 -0
  60. package/lib/nft/support.d.ts.map +1 -0
  61. package/lib/nft/support.js +16 -0
  62. package/lib/nft/support.js.map +1 -0
  63. package/package.json +1 -1
  64. package/src/.DS_Store +0 -0
  65. package/src/__tests__/.DS_Store +0 -0
  66. package/src/__tests__/__snapshots__/all.libcore.ts.snap +10 -49
  67. package/src/__tests__/test-helpers/setup.ts +1 -0
  68. package/src/account/helpers.ts +1 -0
  69. package/src/api/Ethereum.ts +4 -4
  70. package/src/api/explorerConfig/index.ts +0 -4
  71. package/src/bridge/jsHelpers.ts +7 -6
  72. package/src/currencies/__snapshots__/.DS_Store +0 -0
  73. package/src/env.ts +5 -0
  74. package/src/families/bitcoin/test-dataset.ts +2 -2
  75. package/src/families/ethereum/bridge/js.ts +8 -1
  76. package/src/families/ethereum/modules/erc1155.ts +22 -7
  77. package/src/families/ethereum/modules/erc721.ts +42 -4
  78. package/src/families/ethereum/modules/index.ts +20 -0
  79. package/src/families/ethereum/nft.test.ts +195 -26
  80. package/src/families/ethereum/signOperation.ts +2 -3
  81. package/src/families/ethereum/synchronisation.ts +104 -113
  82. package/src/families/ethereum/transaction.ts +8 -4
  83. package/src/families/ethereum/types.ts +2 -0
  84. package/src/nft/helpers.ts +2 -10
  85. package/src/nft/index.ts +1 -0
  86. package/src/nft/support.ts +16 -0
  87. package/src/currencies/__snapshots__/sortByMarketcap.test.ts.snap +0 -6152
@@ -9,11 +9,35 @@ import {
9
9
  import { validateRecipient } from "../transaction";
10
10
  import type { ModeModule, Transaction } from "../types";
11
11
  import type { Account } from "../../../types";
12
+ import { apiForCurrency } from "../../../api/Ethereum";
12
13
 
13
14
  const notOwnedNft = createCustomErrorClass("NotOwnedNft");
14
15
 
15
16
  export type Modes = "erc721.transfer";
16
17
 
18
+ export async function prepareTransaction(
19
+ account: Account,
20
+ transaction: Transaction
21
+ ): Promise<Transaction> {
22
+ let t = transaction;
23
+ const { collection, collectionName, tokenIds } = transaction;
24
+ if (collection && tokenIds && typeof collectionName === "undefined") {
25
+ const api = apiForCurrency(account.currency);
26
+ const [{ status, result }] = await api.getNFTMetadata([
27
+ {
28
+ contract: collection,
29
+ tokenId: tokenIds[0],
30
+ },
31
+ ]);
32
+ let collectionName = ""; // default value fallback if issue
33
+ if (status === 200) {
34
+ collectionName = result?.tokenName || "";
35
+ }
36
+ t = { ...t, collectionName };
37
+ }
38
+ return Promise.resolve(t);
39
+ }
40
+
17
41
  const erc721Transfer: ModeModule = {
18
42
  /**
19
43
  * Tx data is filled during the buildEthereumTx
@@ -52,6 +76,8 @@ const erc721Transfer: ModeModule = {
52
76
  }
53
77
  },
54
78
 
79
+ prepareTransaction,
80
+
55
81
  /**
56
82
  * This will only be used by LLM & LLD, not the HW.
57
83
  */
@@ -59,18 +85,30 @@ const erc721Transfer: ModeModule = {
59
85
  fields.push({
60
86
  type: "text",
61
87
  label: "Type",
62
- value: `ERC721.transfer`,
88
+ value: `NFT Transfer`,
63
89
  });
64
90
 
65
91
  fields.push({
66
92
  type: "text",
67
- label: "Collection",
68
- value: input.transaction.collection ?? "",
93
+ label: "To",
94
+ value: input.transaction.recipient ?? "",
95
+ });
96
+
97
+ fields.push({
98
+ type: "text",
99
+ label: "Collection Name",
100
+ value: input.transaction.collectionName || "",
101
+ });
102
+
103
+ fields.push({
104
+ type: "address",
105
+ label: "NFT Address",
106
+ address: input.transaction.collection ?? "",
69
107
  });
70
108
 
71
109
  fields.push({
72
110
  type: "text",
73
- label: "Token ID",
111
+ label: "NFT ID",
74
112
  value: input.transaction.tokenIds?.[0] ?? "",
75
113
  });
76
114
  },
@@ -90,6 +90,14 @@ export type ModeModule = {
90
90
  arg1: Transaction,
91
91
  arg2: Operation
92
92
  ) => void;
93
+
94
+ /**
95
+ * hook to resolve a transaction like the prepareTransaction of the bridge
96
+ */
97
+ prepareTransaction?: (
98
+ account: Account,
99
+ transaction: Transaction
100
+ ) => Promise<Transaction>;
93
101
  };
94
102
  export const modes: Record<TransactionMode, ModeModule> = {} as Record<
95
103
  TransactionMode,
@@ -137,6 +145,18 @@ export function hydrate(value: unknown, currency: CryptoCurrency): void {
137
145
  }
138
146
  }
139
147
  }
148
+ export const prepareTransaction = (
149
+ account: Account,
150
+ transaction: Transaction
151
+ ): Promise<Transaction> =>
152
+ values(modules)
153
+ // @ts-expect-error some module implement it
154
+ .map((m) => m.prepareTransaction)
155
+ .filter(Boolean)
156
+ .reduce(
157
+ (p, fn) => p.then((t) => fn(account, t)),
158
+ Promise.resolve(transaction)
159
+ );
140
160
  export const prepareTokenAccounts = (
141
161
  currency: CryptoCurrency,
142
162
  subAccounts: TokenAccount[],
@@ -1,11 +1,16 @@
1
1
  import "../../__tests__/test-helpers/setup";
2
+ import BigNumber from "bignumber.js";
2
3
  import { reduce } from "rxjs/operators";
3
- import { fromAccountRaw, toAccountRaw } from "../../account";
4
- import type { Account, AccountRaw } from "../../types";
4
+ import { fromAccountRaw, toAccountRaw, toNFTRaw } from "../../account";
5
+ import type { Account, AccountRaw, NFT } from "../../types";
5
6
  import { getAccountBridge } from "../../bridge";
6
7
  import { makeBridgeCacheSystem } from "../../bridge/cache";
7
8
  import { patchAccount } from "../../reconciliation";
8
9
  import { setEnv } from "../../env";
10
+ import { mergeNfts } from "../../bridge/jsHelpers";
11
+ import { encodeNftId } from "../../nft";
12
+
13
+ jest.setTimeout(120000);
9
14
 
10
15
  const gaspardAccount: AccountRaw = {
11
16
  id: "js:1:ethereum:0xb98d10d9f6d07ba283bfd21b2dfec050f9ae282a:",
@@ -26,32 +31,146 @@ const gaspardAccount: AccountRaw = {
26
31
  xpub: "",
27
32
  };
28
33
 
29
- describe("nft reconciliation", () => {
30
- let account = fromAccountRaw(gaspardAccount);
31
- const localCache = {};
32
- const cache = makeBridgeCacheSystem({
33
- saveData(c, d) {
34
- localCache[c.id] = d;
35
- return Promise.resolve();
36
- },
37
- getData(c) {
38
- return Promise.resolve(localCache[c.id]);
34
+ /*
35
+ const gaspardPolygonAccount: AccountRaw = {
36
+ id: "js:1:polygon:0xb98d10d9f6d07ba283bfd21b2dfec050f9ae282a:",
37
+ seedIdentifier: "0xb98d10d9f6d07ba283bfd21b2dfec050f9ae282a",
38
+ name: "G4sp4rd",
39
+ derivationMode: "",
40
+ index: 0,
41
+ freshAddress: "0xb98d10d9f6d07ba283bfd21b2dfec050f9ae282a",
42
+ freshAddressPath: "44'/60'/0'/0/0",
43
+ freshAddresses: [],
44
+ pendingOperations: [],
45
+ operations: [],
46
+ currencyId: "polygon",
47
+ unitMagnitude: 18,
48
+ balance: "",
49
+ blockHeight: 0,
50
+ lastSyncDate: "",
51
+ xpub: "",
52
+ };
53
+ */
54
+
55
+ const gaspardBscAccount: AccountRaw = {
56
+ id: "js:1:bsc:0xb98d10d9f6d07ba283bfd21b2dfec050f9ae282a:",
57
+ seedIdentifier: "0xb98d10d9f6d07ba283bfd21b2dfec050f9ae282a",
58
+ name: "G4sp4rd",
59
+ derivationMode: "",
60
+ index: 0,
61
+ freshAddress: "0xb98d10d9f6d07ba283bfd21b2dfec050f9ae282a",
62
+ freshAddressPath: "44'/60'/0'/0/0",
63
+ freshAddresses: [],
64
+ pendingOperations: [],
65
+ operations: [],
66
+ currencyId: "bsc",
67
+ unitMagnitude: 18,
68
+ balance: "",
69
+ blockHeight: 0,
70
+ lastSyncDate: "",
71
+ xpub: "",
72
+ };
73
+
74
+ async function sync(account, withNFT = true) {
75
+ const bridge = getAccountBridge(account);
76
+ const blacklistedTokenIds = [];
77
+ setEnv("NFT", withNFT);
78
+ const r = await bridge
79
+ .sync(account, {
80
+ paginationConfig: {},
81
+ blacklistedTokenIds,
82
+ })
83
+ .pipe(reduce((a, f: (arg0: Account) => Account) => f(a), account))
84
+ .toPromise();
85
+ setEnv("NFT", false);
86
+ return r;
87
+ }
88
+
89
+ const localCache = {};
90
+ const cache = makeBridgeCacheSystem({
91
+ saveData(c, d) {
92
+ localCache[c.id] = d;
93
+ return Promise.resolve();
94
+ },
95
+ getData(c) {
96
+ return Promise.resolve(localCache[c.id]);
97
+ },
98
+ });
99
+
100
+ describe("nft merging", () => {
101
+ const makeNFT = (tokenId: string, contract: string, amount: number): NFT => ({
102
+ id: encodeNftId("test", contract, tokenId),
103
+ tokenId,
104
+ amount: new BigNumber(amount),
105
+ collection: {
106
+ contract,
107
+ standard: "erc721",
39
108
  },
40
109
  });
41
- const bridge = getAccountBridge(account);
42
- async function sync(account) {
43
- const blacklistedTokenIds = [];
44
- setEnv("NFT", true);
45
- const r = await bridge
46
- .sync(account, {
47
- paginationConfig: {},
48
- blacklistedTokenIds,
49
- })
50
- .pipe(reduce((a, f: (arg0: Account) => Account) => f(a), account))
51
- .toPromise();
52
- setEnv("NFT", false);
53
- return r;
54
- }
110
+ const oldNfts = [
111
+ makeNFT("1", "contract1", 10),
112
+ makeNFT("2", "contract1", 1),
113
+ makeNFT("3", "contract2", 6),
114
+ ];
115
+
116
+ test("should remove first NFT and return new array with same refs", () => {
117
+ const nfts = [makeNFT("2", "contract1", 1), makeNFT("3", "contract2", 6)];
118
+ const newNfts = mergeNfts(oldNfts, nfts);
119
+
120
+ expect(newNfts.map(toNFTRaw)).toEqual(nfts.map(toNFTRaw));
121
+ expect(oldNfts[1]).toBe(newNfts[0]);
122
+ expect(oldNfts[2]).toBe(newNfts[1]);
123
+ });
124
+
125
+ test("should remove any NFT and return new array with same refs", () => {
126
+ const nfts = [makeNFT("1", "contract1", 10), makeNFT("3", "contract2", 6)];
127
+ const newNfts = mergeNfts(oldNfts, nfts);
128
+
129
+ expect(newNfts.map(toNFTRaw)).toEqual(nfts.map(toNFTRaw));
130
+ expect(oldNfts[0]).toBe(newNfts[0]);
131
+ expect(oldNfts[2]).toBe(newNfts[1]);
132
+ });
133
+
134
+ test("should change NFT amount and return new array with new ref", () => {
135
+ const nfts = [
136
+ makeNFT("1", "contract1", 10),
137
+ makeNFT("2", "contract1", 5),
138
+ makeNFT("3", "contract2", 6),
139
+ ];
140
+ const addToNft1 = mergeNfts(oldNfts, nfts);
141
+
142
+ expect(addToNft1.map(toNFTRaw)).toEqual(nfts.map(toNFTRaw));
143
+ expect(oldNfts[0]).toBe(addToNft1[0]);
144
+ expect(oldNfts[1]).not.toBe(addToNft1[1]);
145
+ expect(oldNfts[2]).toBe(addToNft1[2]);
146
+ });
147
+
148
+ test("should add NFT and return new array with new ref", () => {
149
+ const nfts = [
150
+ makeNFT("1", "contract1", 10),
151
+ makeNFT("2", "contract1", 1),
152
+ makeNFT("3", "contract2", 6),
153
+ makeNFT("4", "contract2", 4),
154
+ ];
155
+ const addToNft1 = mergeNfts(oldNfts, nfts);
156
+
157
+ expect(addToNft1.map(toNFTRaw)).toEqual(
158
+ [
159
+ makeNFT("4", "contract2", 4),
160
+ makeNFT("1", "contract1", 10),
161
+ makeNFT("2", "contract1", 1),
162
+ makeNFT("3", "contract2", 6),
163
+ ].map(toNFTRaw)
164
+ );
165
+ expect(oldNfts[0]).toBe(addToNft1[1]);
166
+ expect(oldNfts[1]).toBe(addToNft1[2]);
167
+ expect(oldNfts[2]).toBe(addToNft1[3]);
168
+ expect(addToNft1[0]).toBe(nfts[3]);
169
+ });
170
+ });
171
+
172
+ describe("gaspard NFT on ethereum", () => {
173
+ let account = fromAccountRaw(gaspardAccount);
55
174
 
56
175
  test("first sync & have nfts", async () => {
57
176
  await cache.prepareCurrency(account.currency);
@@ -78,6 +197,17 @@ describe("nft reconciliation", () => {
78
197
  expect(resync.nfts).toEqual(account.nfts);
79
198
  });
80
199
 
200
+ test("remove half NFTs will restore them with half operations", async () => {
201
+ const halfOps = Math.ceil(account.operations.length / 2);
202
+ const copy = {
203
+ ...account,
204
+ operations: account.operations.slice(halfOps),
205
+ nfts: account.nfts?.slice(Math.ceil((account.nfts?.length || 0) / 2)),
206
+ };
207
+ const resync = await sync(copy);
208
+ expect(resync.nfts).toEqual(account.nfts);
209
+ });
210
+
81
211
  test("patchAccount restore new NFTs correctly", async () => {
82
212
  const copy = {
83
213
  ...account,
@@ -87,4 +217,43 @@ describe("nft reconciliation", () => {
87
217
  const newAccount = patchAccount(copy, toAccountRaw(account));
88
218
  expect(newAccount.nfts).toEqual(account.nfts);
89
219
  });
220
+
221
+ test("start account with .nfts and disable the NFT flag should make it disappear", async () => {
222
+ expect(account.nfts).not.toBeFalsy();
223
+ const resync = await sync(account, false);
224
+ expect(resync.nfts).toBeFalsy();
225
+ });
226
+
227
+ test("start account without .nfts and enable the NFT flag should make it appear", async () => {
228
+ const first = await sync(account, false);
229
+ expect(first.nfts).toBeFalsy();
230
+ const second = await sync(first, true);
231
+ expect(second.nfts).not.toBeFalsy();
232
+ expect(second.nfts).toEqual(account.nfts);
233
+ });
234
+ });
235
+
236
+ /*
237
+ // this is never ending here... have to disable this test... (backend issue)
238
+ describe("gaspard NFT on polygon", () => {
239
+ let account = fromAccountRaw(gaspardPolygonAccount);
240
+ test("first sync", async () => {
241
+ await cache.prepareCurrency(account.currency);
242
+ account = await sync(account);
243
+ });
244
+ test(".nfts shouldn't be visible", () => {
245
+ expect(account.nfts).toBeFalsy();
246
+ });
247
+ });
248
+ */
249
+
250
+ describe("gaspard NFT on bsc", () => {
251
+ let account = fromAccountRaw(gaspardBscAccount);
252
+ test("first sync", async () => {
253
+ await cache.prepareCurrency(account.currency);
254
+ account = await sync(account);
255
+ });
256
+ test(".nfts shouldn't be visible", () => {
257
+ expect(account.nfts).toBeFalsy();
258
+ });
90
259
  });
@@ -13,7 +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
+ import { isNFTActive } from "../../nft";
17
17
  export const signOperation = ({
18
18
  account,
19
19
  deviceId,
@@ -57,12 +57,11 @@ export const signOperation = ({
57
57
  nonce
58
58
  );
59
59
  const to = eip55.encode("0x" + tx.to.toString("hex"));
60
- const chainId = tx.getChainId();
61
60
  const value = new BigNumber(
62
61
  "0x" + (tx.value.toString("hex") || "0")
63
62
  );
64
63
  const eth = new Eth(transport);
65
- if (getEnv("NFT")) {
64
+ if (isNFTActive(account.currency)) {
66
65
  eth.setLoadConfig({
67
66
  // FIXME drop this after LL-8001
68
67
  nftExplorerBaseURL: