@ledgerhq/live-common 24.0.0 → 24.1.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 (91) hide show
  1. package/lib/__tests__/range.js +8 -0
  2. package/lib/__tests__/range.js.map +1 -1
  3. package/lib/api/Ethereum.d.ts +7 -0
  4. package/lib/api/Ethereum.d.ts.map +1 -1
  5. package/lib/api/Ethereum.js +29 -1
  6. package/lib/api/Ethereum.js.map +1 -1
  7. package/lib/bridge/jsHelpers.d.ts +2 -1
  8. package/lib/bridge/jsHelpers.d.ts.map +1 -1
  9. package/lib/bridge/jsHelpers.js +4 -2
  10. package/lib/bridge/jsHelpers.js.map +1 -1
  11. package/lib/families/bitcoin/js-synchronisation.d.ts.map +1 -1
  12. package/lib/families/bitcoin/js-synchronisation.js +2 -173
  13. package/lib/families/bitcoin/js-synchronisation.js.map +1 -1
  14. package/lib/families/bitcoin/logic.d.ts +6 -1
  15. package/lib/families/bitcoin/logic.d.ts.map +1 -1
  16. package/lib/families/bitcoin/logic.js +186 -1
  17. package/lib/families/bitcoin/logic.js.map +1 -1
  18. package/lib/families/bitcoin/xpub.txs.txNoOutput.unit.test.d.ts +2 -0
  19. package/lib/families/bitcoin/xpub.txs.txNoOutput.unit.test.d.ts.map +1 -0
  20. package/lib/families/bitcoin/xpub.txs.txNoOutput.unit.test.js +72 -0
  21. package/lib/families/bitcoin/xpub.txs.txNoOutput.unit.test.js.map +1 -0
  22. package/lib/families/cosmos/api/Cosmos.d.ts.map +1 -1
  23. package/lib/families/cosmos/api/Cosmos.js +1 -0
  24. package/lib/families/cosmos/api/Cosmos.js.map +1 -1
  25. package/lib/families/cosmos/react.test.js +5 -4
  26. package/lib/families/cosmos/react.test.js.map +1 -1
  27. package/lib/families/ethereum/bridge/js.d.ts.map +1 -1
  28. package/lib/families/ethereum/bridge/js.js +5 -1
  29. package/lib/families/ethereum/bridge/js.js.map +1 -1
  30. package/lib/families/ethereum/datasets/ethereum2.d.ts +3 -0
  31. package/lib/families/ethereum/datasets/ethereum2.d.ts.map +1 -0
  32. package/lib/families/ethereum/datasets/ethereum2.js +22 -0
  33. package/lib/families/ethereum/datasets/ethereum2.js.map +1 -0
  34. package/lib/families/ethereum/synchronisation.d.ts.map +1 -1
  35. package/lib/families/ethereum/synchronisation.integration.test.js +68 -0
  36. package/lib/families/ethereum/synchronisation.integration.test.js.map +1 -1
  37. package/lib/families/ethereum/synchronisation.js +17 -11
  38. package/lib/families/ethereum/synchronisation.js.map +1 -1
  39. package/lib/hw/extractOnboardingState.d.ts +26 -0
  40. package/lib/hw/extractOnboardingState.d.ts.map +1 -0
  41. package/lib/hw/extractOnboardingState.js +69 -0
  42. package/lib/hw/extractOnboardingState.js.map +1 -0
  43. package/lib/hw/extractOnboardingState.test.d.ts +2 -0
  44. package/lib/hw/extractOnboardingState.test.d.ts.map +1 -0
  45. package/lib/hw/extractOnboardingState.test.js +182 -0
  46. package/lib/hw/extractOnboardingState.test.js.map +1 -0
  47. package/lib/hw/getOnboardingStatePolling.d.ts +20 -0
  48. package/lib/hw/getOnboardingStatePolling.d.ts.map +1 -0
  49. package/lib/hw/getOnboardingStatePolling.js +117 -0
  50. package/lib/hw/getOnboardingStatePolling.js.map +1 -0
  51. package/lib/hw/getOnboardingStatePolling.test.d.ts +2 -0
  52. package/lib/hw/getOnboardingStatePolling.test.d.ts.map +1 -0
  53. package/lib/hw/getOnboardingStatePolling.test.js +224 -0
  54. package/lib/hw/getOnboardingStatePolling.test.js.map +1 -0
  55. package/lib/onboarding/hooks/useOnboardingStatePolling.d.ts +22 -0
  56. package/lib/onboarding/hooks/useOnboardingStatePolling.d.ts.map +1 -0
  57. package/lib/onboarding/hooks/useOnboardingStatePolling.js +81 -0
  58. package/lib/onboarding/hooks/useOnboardingStatePolling.js.map +1 -0
  59. package/lib/onboarding/hooks/useOnboardingStatePolling.test.d.ts +2 -0
  60. package/lib/onboarding/hooks/useOnboardingStatePolling.test.d.ts.map +1 -0
  61. package/lib/onboarding/hooks/useOnboardingStatePolling.test.js +374 -0
  62. package/lib/onboarding/hooks/useOnboardingStatePolling.test.js.map +1 -0
  63. package/lib/operation.d.ts +7 -0
  64. package/lib/operation.d.ts.map +1 -1
  65. package/lib/operation.js +15 -1
  66. package/lib/operation.js.map +1 -1
  67. package/lib/types/manager.d.ts +5 -0
  68. package/lib/types/manager.d.ts.map +1 -1
  69. package/lib/types/manager.js +7 -1
  70. package/lib/types/manager.js.map +1 -1
  71. package/package.json +2 -2
  72. package/src/__tests__/range.ts +13 -0
  73. package/src/api/Ethereum.ts +31 -0
  74. package/src/bridge/jsHelpers.ts +7 -1
  75. package/src/families/bitcoin/js-synchronisation.ts +3 -172
  76. package/src/families/bitcoin/logic.ts +169 -0
  77. package/src/families/bitcoin/xpub.txs.txNoOutput.unit.test.ts +37 -0
  78. package/src/families/cosmos/api/Cosmos.ts +4 -0
  79. package/src/families/cosmos/react.test.ts +3 -0
  80. package/src/families/ethereum/bridge/js.ts +5 -1
  81. package/src/families/ethereum/datasets/ethereum2.ts +20 -0
  82. package/src/families/ethereum/synchronisation.integration.test.ts +63 -1
  83. package/src/families/ethereum/synchronisation.ts +15 -7
  84. package/src/hw/extractOnboardingState.test.ts +251 -0
  85. package/src/hw/extractOnboardingState.ts +97 -0
  86. package/src/hw/getOnboardingStatePolling.test.ts +247 -0
  87. package/src/hw/getOnboardingStatePolling.ts +153 -0
  88. package/src/onboarding/hooks/useOnboardingStatePolling.test.ts +304 -0
  89. package/src/onboarding/hooks/useOnboardingStatePolling.ts +86 -0
  90. package/src/operation.ts +24 -0
  91. package/src/types/manager.ts +5 -0
@@ -72,26 +72,38 @@ export type Tx = {
72
72
  time: string;
73
73
  };
74
74
  };
75
+
75
76
  export type ERC20BalancesInput = Array<{
76
77
  address: string;
77
78
  contract: string;
78
79
  }>;
80
+
79
81
  export type ERC20BalanceOutput = Array<{
80
82
  address: string;
81
83
  contract: string;
82
84
  balance: BigNumber;
83
85
  }>;
86
+
84
87
  export type NFTMetadataInput = Readonly<
85
88
  Array<{
86
89
  contract: string;
87
90
  tokenId: string;
88
91
  }>
89
92
  >;
93
+
90
94
  export type NFTCollectionMetadataInput = Readonly<
91
95
  Array<{
92
96
  contract: string;
93
97
  }>
94
98
  >;
99
+
100
+ export type BlockByHashOutput = {
101
+ hash: string;
102
+ height: number;
103
+ time: string;
104
+ txs: string[];
105
+ };
106
+
95
107
  export type API = {
96
108
  getTransactions: (
97
109
  address: string,
@@ -133,6 +145,7 @@ export type API = {
133
145
  medium: BigNumber;
134
146
  high: BigNumber;
135
147
  }>;
148
+ getBlockByHash: (blockHash: string) => Promise<BlockByHashOutput | undefined>;
136
149
  };
137
150
  export const apiForCurrency = (currency: CryptoCurrency): API => {
138
151
  const baseURL = blockchainBaseURL(currency);
@@ -328,5 +341,23 @@ export const apiForCurrency = (currency: CryptoCurrency): API => {
328
341
  maxAge: 30 * 1000,
329
342
  }
330
343
  ),
344
+
345
+ async getBlockByHash(blockHash) {
346
+ if (!blockHash) {
347
+ return undefined;
348
+ }
349
+
350
+ try {
351
+ const { data }: { data: BlockByHashOutput[] } = await network({
352
+ method: "GET",
353
+ url: `${baseURL}/blocks/${blockHash}`,
354
+ transformResponse: JSONBigNumber.parse,
355
+ });
356
+
357
+ return data[0];
358
+ } catch (e) {
359
+ return undefined;
360
+ }
361
+ },
331
362
  };
332
363
  };
@@ -172,9 +172,11 @@ export const makeSync =
172
172
  ({
173
173
  getAccountShape,
174
174
  postSync = (_, a) => a,
175
+ shouldMergeOps = true,
175
176
  }: {
176
177
  getAccountShape: GetAccountShape;
177
178
  postSync?: (initial: Account, synced: Account) => Account;
179
+ shouldMergeOps?: boolean;
178
180
  }): AccountBridge<any>["sync"] =>
179
181
  (initial, syncConfig): Observable<AccountUpdater> =>
180
182
  Observable.create((o) => {
@@ -205,10 +207,14 @@ export const makeSync =
205
207
  },
206
208
  syncConfig
207
209
  );
210
+
208
211
  o.next((acc) => {
209
212
  const a = needClear ? clearAccount(acc) : acc;
210
213
  // FIXME reconsider doing mergeOps here. work is redundant for impl like eth
211
- const operations = mergeOps(a.operations, shape.operations || []);
214
+ const operations = shouldMergeOps
215
+ ? mergeOps(a.operations, shape.operations || [])
216
+ : shape.operations || [];
217
+
212
218
  return recalculateAccountBalanceHistories(
213
219
  postSync(a, {
214
220
  ...a,
@@ -1,26 +1,14 @@
1
- import { $Shape } from "utility-types";
2
- import type {
3
- TX,
4
- Currency,
5
- Input as WalletInput,
6
- Output as WalletOutput,
7
- } from "./wallet-btc";
1
+ import type { Currency, Output as WalletOutput } from "./wallet-btc";
8
2
  import { DerivationModes as WalletDerivationModes } from "./wallet-btc";
9
3
  import { BigNumber } from "bignumber.js";
10
4
  import Btc from "@ledgerhq/hw-app-btc";
11
5
  import { log } from "@ledgerhq/logs";
12
6
  import { getCryptoCurrencyById } from "@ledgerhq/cryptoassets";
13
- import type {
14
- Account,
15
- Operation,
16
- OperationType,
17
- DerivationMode,
18
- } from "../../types";
7
+ import type { Account, Operation, DerivationMode } from "../../types";
19
8
  import type { GetAccountShape } from "../../bridge/jsHelpers";
20
9
  import { makeSync, makeScanAccounts, mergeOps } from "../../bridge/jsHelpers";
21
10
  import { findCurrencyExplorer } from "../../api/Ledger";
22
11
  import { encodeAccountId } from "../../account";
23
- import { encodeOperationId } from "../../operation";
24
12
  import {
25
13
  isSegwitDerivationMode,
26
14
  isNativeSegwitDerivationMode,
@@ -30,6 +18,7 @@ import { BitcoinOutput } from "./types";
30
18
  import { perCoinLogic } from "./logic";
31
19
  import wallet from "./wallet-btc";
32
20
  import { getAddressWithBtcInstance } from "./hw-getAddress";
21
+ import { mapTxToOperations } from "./logic";
33
22
 
34
23
  // Map LL's DerivationMode to wallet-btc's
35
24
  const toWalletDerivationMode = (
@@ -89,164 +78,6 @@ const deduplicateOperations = (
89
78
  return out;
90
79
  };
91
80
 
92
- const mapTxToOperations = (
93
- tx: TX,
94
- currencyId: string,
95
- accountId: string,
96
- accountAddresses: Set<string>,
97
- changeAddresses: Set<string>
98
- ): $Shape<Operation[]> => {
99
- const operations: Operation[] = [];
100
- const txId = tx.id;
101
- const fee = new BigNumber(tx.fees);
102
- const blockHeight = tx.block?.height;
103
- const blockHash = tx.block?.hash;
104
- const date = new Date(tx.block?.time || tx.received_at);
105
- const senders = new Set<string>();
106
- const recipients: string[] = [];
107
- let type: OperationType = "OUT";
108
- let value = new BigNumber(0);
109
- const hasFailed = false;
110
- const accountInputs: WalletInput[] = [];
111
- const accountOutputs: WalletOutput[] = [];
112
- const syncReplaceAddress = perCoinLogic[currencyId]?.syncReplaceAddress;
113
-
114
- for (const input of tx.inputs) {
115
- if (input.address) {
116
- senders.add(
117
- syncReplaceAddress ? syncReplaceAddress(input.address) : input.address
118
- );
119
-
120
- if (input.value) {
121
- if (accountAddresses.has(input.address)) {
122
- // This address is part of the account
123
- value = value.plus(input.value);
124
- accountInputs.push(input);
125
- }
126
- }
127
- }
128
- }
129
-
130
- // All inputs of a same transaction have the same sequence
131
- const transactionSequenceNumber =
132
- (accountInputs.length > 0 && accountInputs[0].sequence) || undefined;
133
-
134
- const hasSpentNothing = value.eq(0);
135
- // Change output is always the last one
136
- const changeOutputIndex = tx.outputs
137
- .map((o) => o.output_index)
138
- .reduce((p, c) => (p > c ? p : c));
139
-
140
- for (const output of tx.outputs) {
141
- if (output.address) {
142
- if (!accountAddresses.has(output.address)) {
143
- // The output doesn't belong to this account
144
- if (
145
- accountInputs.length > 0 && // It's a SEND operation
146
- (tx.outputs.length === 1 || // The transaction has only 1 output
147
- output.output_index < changeOutputIndex) // The output isn't the change output
148
- ) {
149
- recipients.push(
150
- syncReplaceAddress
151
- ? syncReplaceAddress(output.address)
152
- : output.address
153
- );
154
- }
155
- } else {
156
- // The output belongs to this account
157
- accountOutputs.push(output);
158
-
159
- if (!changeAddresses.has(output.address)) {
160
- // The output isn't a change output of this account
161
- recipients.push(
162
- syncReplaceAddress
163
- ? syncReplaceAddress(output.address)
164
- : output.address
165
- );
166
- } else {
167
- // The output is a change output of this account,
168
- // we count it as a recipient only in some special cases
169
- if (
170
- (recipients.length === 0 &&
171
- output.output_index >= changeOutputIndex) ||
172
- hasSpentNothing
173
- ) {
174
- recipients.push(
175
- syncReplaceAddress
176
- ? syncReplaceAddress(output.address)
177
- : output.address
178
- );
179
- }
180
- }
181
- }
182
- }
183
- }
184
-
185
- if (accountInputs.length > 0) {
186
- // It's a SEND operation
187
- for (const output of accountOutputs) {
188
- if (changeAddresses.has(output.address)) {
189
- value = value.minus(output.value);
190
- }
191
- }
192
-
193
- type = "OUT";
194
- operations.push({
195
- id: encodeOperationId(accountId, txId, type),
196
- hash: txId,
197
- type,
198
- value,
199
- fee,
200
- senders: Array.from(senders),
201
- recipients,
202
- blockHeight,
203
- blockHash,
204
- transactionSequenceNumber,
205
- accountId,
206
- date,
207
- hasFailed,
208
- extra: {},
209
- });
210
- }
211
-
212
- if (accountOutputs.length > 0) {
213
- // It's a RECEIVE operation
214
- const filterChangeAddresses = !!accountInputs.length;
215
- let accountOutputCount = 0;
216
- let finalAmount = new BigNumber(0);
217
-
218
- for (const output of accountOutputs) {
219
- if (!filterChangeAddresses || !changeAddresses.has(output.address)) {
220
- finalAmount = finalAmount.plus(output.value);
221
- accountOutputCount += 1;
222
- }
223
- }
224
-
225
- if (accountOutputCount > 0) {
226
- value = finalAmount;
227
- type = "IN";
228
- operations.push({
229
- id: encodeOperationId(accountId, txId, type),
230
- hash: txId,
231
- type,
232
- value,
233
- fee,
234
- senders: Array.from(senders),
235
- recipients,
236
- blockHeight,
237
- blockHash,
238
- transactionSequenceNumber,
239
- accountId,
240
- date,
241
- hasFailed,
242
- extra: {},
243
- });
244
- }
245
- }
246
-
247
- return operations;
248
- };
249
-
250
81
  const getAccountShape: GetAccountShape = async (info) => {
251
82
  const {
252
83
  transport,
@@ -9,6 +9,15 @@ import type {
9
9
  NetworkInfo,
10
10
  UtxoStrategy,
11
11
  } from "./types";
12
+ import { $Shape } from "utility-types";
13
+ import type {
14
+ TX,
15
+ Input as WalletInput,
16
+ Output as WalletOutput,
17
+ } from "./wallet-btc";
18
+ import { BigNumber } from "bignumber.js";
19
+ import { encodeOperationId } from "../../operation";
20
+ import type { Operation, OperationType } from "../../types";
12
21
 
13
22
  // correspond ~ to min relay fees but determined empirically for a tx to be accepted by network
14
23
  const minFees = {
@@ -176,3 +185,163 @@ export const perCoinLogic: Record<
176
185
  }),
177
186
  },
178
187
  };
188
+
189
+ export const mapTxToOperations = (
190
+ tx: TX,
191
+ currencyId: string,
192
+ accountId: string,
193
+ accountAddresses: Set<string>,
194
+ changeAddresses: Set<string>
195
+ ): $Shape<Operation[]> => {
196
+ const operations: Operation[] = [];
197
+ const txId = tx.id;
198
+ const fee = new BigNumber(tx.fees);
199
+ const blockHeight = tx.block?.height;
200
+ const blockHash = tx.block?.hash;
201
+ const date = new Date(tx.block?.time || tx.received_at);
202
+ const senders = new Set<string>();
203
+ const recipients: string[] = [];
204
+ let type: OperationType = "OUT";
205
+ let value = new BigNumber(0);
206
+ const hasFailed = false;
207
+ const accountInputs: WalletInput[] = [];
208
+ const accountOutputs: WalletOutput[] = [];
209
+ const syncReplaceAddress = perCoinLogic[currencyId]?.syncReplaceAddress;
210
+
211
+ for (const input of tx.inputs) {
212
+ if (input.address) {
213
+ senders.add(
214
+ syncReplaceAddress ? syncReplaceAddress(input.address) : input.address
215
+ );
216
+
217
+ if (input.value) {
218
+ if (accountAddresses.has(input.address)) {
219
+ // This address is part of the account
220
+ value = value.plus(input.value);
221
+ accountInputs.push(input);
222
+ }
223
+ }
224
+ }
225
+ }
226
+
227
+ // All inputs of a same transaction have the same sequence
228
+ const transactionSequenceNumber =
229
+ (accountInputs.length > 0 && accountInputs[0].sequence) || undefined;
230
+
231
+ const hasSpentNothing = value.eq(0);
232
+
233
+ // Change output is always the last one
234
+ const changeOutputIndex =
235
+ tx.outputs.length === 0
236
+ ? 0
237
+ : tx.outputs.map((o) => o.output_index).reduce((p, c) => (p > c ? p : c));
238
+
239
+ for (const output of tx.outputs) {
240
+ if (output.address) {
241
+ if (!accountAddresses.has(output.address)) {
242
+ // The output doesn't belong to this account
243
+ if (
244
+ accountInputs.length > 0 && // It's a SEND operation
245
+ (tx.outputs.length === 1 || // The transaction has only 1 output
246
+ output.output_index < changeOutputIndex) // The output isn't the change output
247
+ ) {
248
+ recipients.push(
249
+ syncReplaceAddress
250
+ ? syncReplaceAddress(output.address)
251
+ : output.address
252
+ );
253
+ }
254
+ } else {
255
+ // The output belongs to this account
256
+ accountOutputs.push(output);
257
+
258
+ if (!changeAddresses.has(output.address)) {
259
+ // The output isn't a change output of this account
260
+ recipients.push(
261
+ syncReplaceAddress
262
+ ? syncReplaceAddress(output.address)
263
+ : output.address
264
+ );
265
+ } else {
266
+ // The output is a change output of this account,
267
+ // we count it as a recipient only in some special cases
268
+ if (
269
+ (recipients.length === 0 &&
270
+ output.output_index >= changeOutputIndex) ||
271
+ hasSpentNothing
272
+ ) {
273
+ recipients.push(
274
+ syncReplaceAddress
275
+ ? syncReplaceAddress(output.address)
276
+ : output.address
277
+ );
278
+ }
279
+ }
280
+ }
281
+ }
282
+ }
283
+
284
+ if (accountInputs.length > 0) {
285
+ // It's a SEND operation
286
+ for (const output of accountOutputs) {
287
+ if (changeAddresses.has(output.address)) {
288
+ value = value.minus(output.value);
289
+ }
290
+ }
291
+
292
+ type = "OUT";
293
+ operations.push({
294
+ id: encodeOperationId(accountId, txId, type),
295
+ hash: txId,
296
+ type,
297
+ value,
298
+ fee,
299
+ senders: Array.from(senders),
300
+ recipients,
301
+ blockHeight,
302
+ blockHash,
303
+ transactionSequenceNumber,
304
+ accountId,
305
+ date,
306
+ hasFailed,
307
+ extra: {},
308
+ });
309
+ }
310
+
311
+ if (accountOutputs.length > 0) {
312
+ // It's a RECEIVE operation
313
+ const filterChangeAddresses = !!accountInputs.length;
314
+ let accountOutputCount = 0;
315
+ let finalAmount = new BigNumber(0);
316
+
317
+ for (const output of accountOutputs) {
318
+ if (!filterChangeAddresses || !changeAddresses.has(output.address)) {
319
+ finalAmount = finalAmount.plus(output.value);
320
+ accountOutputCount += 1;
321
+ }
322
+ }
323
+
324
+ if (accountOutputCount > 0) {
325
+ value = finalAmount;
326
+ type = "IN";
327
+ operations.push({
328
+ id: encodeOperationId(accountId, txId, type),
329
+ hash: txId,
330
+ type,
331
+ value,
332
+ fee,
333
+ senders: Array.from(senders),
334
+ recipients,
335
+ blockHeight,
336
+ blockHash,
337
+ transactionSequenceNumber,
338
+ accountId,
339
+ date,
340
+ hasFailed,
341
+ extra: {},
342
+ });
343
+ }
344
+ }
345
+
346
+ return operations;
347
+ };
@@ -0,0 +1,37 @@
1
+ import { mapTxToOperations } from "./logic";
2
+
3
+ describe("testing transaction without output", () => {
4
+ it("Parse zcash transaction without output", async () => {
5
+ const tx = {
6
+ id: "0b9f98d07eb418fa20573112d3cba6b871d429a06c724a7888ff0886be5213d1",
7
+ inputs: [
8
+ {
9
+ output_hash:
10
+ "2772f3963856f3eb38cb706ec8c2b62fcdeb2ce10f32cf7160afb3873be6f60d",
11
+ output_index: 0,
12
+ value: "5000000000",
13
+ address: "2NCDBM9DAuMrD1T8XDHMxvbTmLutP7at4AB",
14
+ sequence: 4294967294,
15
+ },
16
+ ],
17
+ outputs: [],
18
+ block: {
19
+ hash: "305d4b8d4a6d6ecca0a3dd0216f8ecd090978ed346d1845883c8aa4529d72fc8",
20
+ height: 120,
21
+ time: "2021-07-28T13:34:38Z",
22
+ },
23
+ account: 0,
24
+ index: 0,
25
+ address: "mwXTtHo8Yy3aNKUUZLkBDrTcKT9qG9TqLb",
26
+ received_at: "2021-07-28T13:34:38Z",
27
+ };
28
+ const out = mapTxToOperations(
29
+ tx,
30
+ "zcash",
31
+ "zcash 1",
32
+ new Set<string>(),
33
+ new Set<string>()
34
+ );
35
+ expect(out.length).toEqual(0);
36
+ }, 30000);
37
+ });
@@ -128,6 +128,10 @@ const getDelegations = async (
128
128
  url: `${defaultEndpoint}/cosmos/staking/v1beta1/delegations/${address}`,
129
129
  });
130
130
 
131
+ data1.delegation_responses = data1.delegation_responses.filter(
132
+ (d) => d.balance.amount !== "0"
133
+ );
134
+
131
135
  let status = "unbonded";
132
136
  const statusMap = {
133
137
  BOND_STATUS_UNBONDED: "unbonded",
@@ -49,6 +49,9 @@ describe("cosmos/react", () => {
49
49
  );
50
50
  const delegations = account.cosmosResources?.delegations;
51
51
  invariant(delegations, "cosmos: delegations is required");
52
+ expect(
53
+ account.cosmosResources?.delegations?.some((d) => d.amount[0] === 0)
54
+ ).toBe(false);
52
55
  expect(Array.isArray(result.current)).toBe(true);
53
56
  expect(result.current.length).toBe(
54
57
  (delegations as CosmosDelegation[]).length
@@ -47,7 +47,11 @@ const broadcast = async ({
47
47
  };
48
48
 
49
49
  const scanAccounts = makeScanAccounts({ getAccountShape });
50
- const sync = makeSync({ getAccountShape, postSync: postSyncPatch });
50
+ const sync = makeSync({
51
+ getAccountShape,
52
+ postSync: postSyncPatch,
53
+ shouldMergeOps: false,
54
+ });
51
55
 
52
56
  const createTransaction = (): Transaction => ({
53
57
  family: "ethereum",
@@ -0,0 +1,20 @@
1
+ import { AccountRaw } from "../../../types";
2
+
3
+ export const ethereum2: AccountRaw = {
4
+ id: "js:2:ethereum:0x789d2f10826BF8f3a56Ec524359bBA4e738Af5bF:",
5
+ seedIdentifier: "0x789d2f10826BF8f3a56Ec524359bBA4e738Af5bF",
6
+ name: "Ethereum legacy xpub6Bem...JyAdpYZy",
7
+ derivationMode: "",
8
+ index: 0,
9
+ freshAddress: "0x789d2f10826BF8f3a56Ec524359bBA4e738Af5bF",
10
+ freshAddressPath: "44'/60'/0'/0/0",
11
+ freshAddresses: [],
12
+ pendingOperations: [],
13
+ operations: [],
14
+ currencyId: "ethereum",
15
+ unitMagnitude: 18,
16
+ balance: "",
17
+ blockHeight: 0,
18
+ lastSyncDate: "",
19
+ xpub: "",
20
+ };
@@ -2,10 +2,16 @@ import "../../__tests__/test-helpers/setup";
2
2
  import { reduce } from "rxjs/operators";
3
3
  import { fromAccountRaw } from "../../account";
4
4
  import { getAccountCurrency } from "../../account/helpers";
5
- import type { Account, SubAccount } from "../../types";
5
+ import type { Account, OperationRaw, SubAccount } from "../../types";
6
6
  import { getAccountBridge } from "../../bridge";
7
7
  import { makeBridgeCacheSystem } from "../../bridge/cache";
8
8
  import { ethereum1 } from "./datasets/ethereum1";
9
+ import { ethereum2 } from "./datasets/ethereum2";
10
+ import { encodeOperationId } from "../../operation";
11
+ import {
12
+ getCryptoCurrencyById,
13
+ listTokensForCryptoCurrency,
14
+ } from "@ledgerhq/cryptoassets";
9
15
 
10
16
  describe("blacklistedTokenIds functionality", () => {
11
17
  const account = fromAccountRaw(ethereum1);
@@ -83,3 +89,59 @@ describe("blacklistedTokenIds functionality", () => {
83
89
  ).toBeTruthy();
84
90
  });
85
91
  });
92
+
93
+ describe("sync on reorg", () => {
94
+ test("should sync from scratch if stableoperations latest blockhash is not on chain", async () => {
95
+ const opHash = "NotImportant";
96
+ const accountId = ethereum2.id;
97
+ const opType = "OUT";
98
+ const opId = encodeOperationId(accountId, opHash, opType);
99
+ const blockHeight = 10042069;
100
+ const currency = getCryptoCurrencyById(ethereum2.currencyId);
101
+ const syncHash =
102
+ JSON.stringify([]) +
103
+ "_" +
104
+ listTokensForCryptoCurrency(currency, {
105
+ withDelisted: true,
106
+ }).length;
107
+
108
+ const operation: OperationRaw = {
109
+ id: opId,
110
+ hash: opHash,
111
+ type: opType,
112
+ value: "1",
113
+ fee: "string",
114
+ senders: ["0x789d2f10826BF8f3a56Ec524359bBA4e738Af5bF"],
115
+ recipients: ["0xdA9EDcC3CF66bc18050dB55D376407Cf85e0617B"],
116
+ blockHeight: blockHeight - 81, // 80 is defined today as the threshold number of confirmation to be safe for a reorg. @see SAFE_REORG_THRESHOLD
117
+ blockHash:
118
+ "0x00000000000000000000000000000000000Th1sH4shSh0u1dN0t3x1sTOnCh4iN",
119
+ transactionSequenceNumber: 1,
120
+ date: "May-11-2020 01:50:10 UTC",
121
+ extra: {},
122
+ accountId,
123
+ };
124
+
125
+ const account = fromAccountRaw({
126
+ ...ethereum2,
127
+ blockHeight,
128
+ syncHash,
129
+ operations: [operation],
130
+ operationsCount: 1,
131
+ });
132
+ const bridge = getAccountBridge(account);
133
+ const syncPromise = await bridge
134
+ .sync(account, {
135
+ paginationConfig: {},
136
+ blacklistedTokenIds: [],
137
+ })
138
+ .pipe(reduce((a, f: (arg0: Account) => Account) => f(a), account))
139
+ .toPromise();
140
+ // expect not to throw ok if you reach this point
141
+ // + expect operations to be from full sync instead of incremental
142
+ // so the fake op should be removed
143
+ expect(syncPromise.operations.map((o) => o.id)).not.toContain(opId);
144
+ // To this date (June 20th 2022) this account has 160 ops, we should at least get them all
145
+ expect(syncPromise.operations.length >= 160).toBe(true);
146
+ });
147
+ });