@ledgerhq/live-common 24.0.0 → 24.1.0-next.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/lib/__tests__/range.js +8 -0
  2. package/lib/__tests__/range.js.map +1 -1
  3. package/lib/families/bitcoin/js-synchronisation.d.ts.map +1 -1
  4. package/lib/families/bitcoin/js-synchronisation.js +2 -173
  5. package/lib/families/bitcoin/js-synchronisation.js.map +1 -1
  6. package/lib/families/bitcoin/logic.d.ts +6 -1
  7. package/lib/families/bitcoin/logic.d.ts.map +1 -1
  8. package/lib/families/bitcoin/logic.js +186 -1
  9. package/lib/families/bitcoin/logic.js.map +1 -1
  10. package/lib/families/bitcoin/xpub.txs.txNoOutput.unit.test.d.ts +2 -0
  11. package/lib/families/bitcoin/xpub.txs.txNoOutput.unit.test.d.ts.map +1 -0
  12. package/lib/families/bitcoin/xpub.txs.txNoOutput.unit.test.js +72 -0
  13. package/lib/families/bitcoin/xpub.txs.txNoOutput.unit.test.js.map +1 -0
  14. package/lib/families/cosmos/api/Cosmos.d.ts.map +1 -1
  15. package/lib/families/cosmos/api/Cosmos.js +1 -0
  16. package/lib/families/cosmos/api/Cosmos.js.map +1 -1
  17. package/lib/families/cosmos/react.test.js +5 -4
  18. package/lib/families/cosmos/react.test.js.map +1 -1
  19. package/lib/hw/extractOnboardingState.d.ts +26 -0
  20. package/lib/hw/extractOnboardingState.d.ts.map +1 -0
  21. package/lib/hw/extractOnboardingState.js +69 -0
  22. package/lib/hw/extractOnboardingState.js.map +1 -0
  23. package/lib/hw/extractOnboardingState.test.d.ts +2 -0
  24. package/lib/hw/extractOnboardingState.test.d.ts.map +1 -0
  25. package/lib/hw/extractOnboardingState.test.js +182 -0
  26. package/lib/hw/extractOnboardingState.test.js.map +1 -0
  27. package/lib/hw/getOnboardingStatePolling.d.ts +20 -0
  28. package/lib/hw/getOnboardingStatePolling.d.ts.map +1 -0
  29. package/lib/hw/getOnboardingStatePolling.js +117 -0
  30. package/lib/hw/getOnboardingStatePolling.js.map +1 -0
  31. package/lib/hw/getOnboardingStatePolling.test.d.ts +2 -0
  32. package/lib/hw/getOnboardingStatePolling.test.d.ts.map +1 -0
  33. package/lib/hw/getOnboardingStatePolling.test.js +224 -0
  34. package/lib/hw/getOnboardingStatePolling.test.js.map +1 -0
  35. package/lib/onboarding/hooks/useOnboardingStatePolling.d.ts +22 -0
  36. package/lib/onboarding/hooks/useOnboardingStatePolling.d.ts.map +1 -0
  37. package/lib/onboarding/hooks/useOnboardingStatePolling.js +81 -0
  38. package/lib/onboarding/hooks/useOnboardingStatePolling.js.map +1 -0
  39. package/lib/onboarding/hooks/useOnboardingStatePolling.test.d.ts +2 -0
  40. package/lib/onboarding/hooks/useOnboardingStatePolling.test.d.ts.map +1 -0
  41. package/lib/onboarding/hooks/useOnboardingStatePolling.test.js +374 -0
  42. package/lib/onboarding/hooks/useOnboardingStatePolling.test.js.map +1 -0
  43. package/lib/types/manager.d.ts +5 -0
  44. package/lib/types/manager.d.ts.map +1 -1
  45. package/lib/types/manager.js +7 -1
  46. package/lib/types/manager.js.map +1 -1
  47. package/package.json +2 -2
  48. package/src/__tests__/range.ts +13 -0
  49. package/src/families/bitcoin/js-synchronisation.ts +3 -172
  50. package/src/families/bitcoin/logic.ts +169 -0
  51. package/src/families/bitcoin/xpub.txs.txNoOutput.unit.test.ts +37 -0
  52. package/src/families/cosmos/api/Cosmos.ts +4 -0
  53. package/src/families/cosmos/react.test.ts +3 -0
  54. package/src/hw/extractOnboardingState.test.ts +251 -0
  55. package/src/hw/extractOnboardingState.ts +97 -0
  56. package/src/hw/getOnboardingStatePolling.test.ts +247 -0
  57. package/src/hw/getOnboardingStatePolling.ts +153 -0
  58. package/src/onboarding/hooks/useOnboardingStatePolling.test.ts +304 -0
  59. package/src/onboarding/hooks/useOnboardingStatePolling.ts +86 -0
  60. package/src/types/manager.ts +5 -0
@@ -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
@@ -0,0 +1,251 @@
1
+ import {
2
+ extractOnboardingState,
3
+ OnboardingStep,
4
+ } from "./extractOnboardingState";
5
+
6
+ describe("@hw/extractOnboardingState", () => {
7
+ describe("extractOnboardingState", () => {
8
+ describe("When the flag bytes are incorrect", () => {
9
+ it("should throw an error", () => {
10
+ const incompleteFlagsBytes = Buffer.from([0, 0]);
11
+ // DeviceExtractOnboardingStateError is not of type Error,
12
+ // so cannot check in toThrow(DeviceExtractOnboardingStateError)
13
+ expect(() => extractOnboardingState(incompleteFlagsBytes)).toThrow();
14
+ });
15
+ });
16
+
17
+ describe("When the device is onboarded", () => {
18
+ it("should return a device state that is onboarded", () => {
19
+ const flagsBytes = Buffer.from([1 << 2, 0, 0, 0]);
20
+
21
+ const onboardingState = extractOnboardingState(flagsBytes);
22
+
23
+ expect(onboardingState).not.toBeNull();
24
+ expect(onboardingState?.isOnboarded).toBe(true);
25
+ });
26
+ });
27
+
28
+ describe("When the device is in recovery mode", () => {
29
+ it("should return a device state that is in recovery mode", () => {
30
+ const flagsBytes = Buffer.from([1, 0, 0, 0]);
31
+
32
+ const onboardingState = extractOnboardingState(flagsBytes);
33
+
34
+ expect(onboardingState).not.toBeNull();
35
+ expect(onboardingState?.isInRecoveryMode).toBe(true);
36
+ });
37
+ });
38
+
39
+ describe("When the device is not onboarded and in normal mode", () => {
40
+ let flagsBytes: Buffer;
41
+
42
+ beforeEach(() => {
43
+ flagsBytes = Buffer.from([0, 0, 0, 0]);
44
+ });
45
+
46
+ describe("and the user is on the welcome screen", () => {
47
+ beforeEach(() => {
48
+ flagsBytes[3] = 0;
49
+ });
50
+
51
+ it("should return an onboarding step that is set at the welcome screen", () => {
52
+ const onboardingState = extractOnboardingState(flagsBytes);
53
+
54
+ expect(onboardingState).not.toBeNull();
55
+ expect(onboardingState?.currentOnboardingStep).toBe(
56
+ OnboardingStep.WelcomeScreen
57
+ );
58
+ });
59
+ });
60
+
61
+ describe("and the user is choosing what kind of setup they want", () => {
62
+ beforeEach(() => {
63
+ flagsBytes[3] = 1;
64
+ });
65
+
66
+ it("should return an onboarding step that is set at the setup choice", () => {
67
+ const onboardingState = extractOnboardingState(flagsBytes);
68
+
69
+ expect(onboardingState).not.toBeNull();
70
+ expect(onboardingState?.currentOnboardingStep).toBe(
71
+ OnboardingStep.SetupChoice
72
+ );
73
+ });
74
+ });
75
+
76
+ describe("and the user is setting their pin", () => {
77
+ beforeEach(() => {
78
+ flagsBytes[3] = 2;
79
+ });
80
+
81
+ it("should return an onboarding step that is set at setting the pin", () => {
82
+ const onboardingState = extractOnboardingState(flagsBytes);
83
+
84
+ expect(onboardingState).not.toBeNull();
85
+ expect(onboardingState?.currentOnboardingStep).toBe(
86
+ OnboardingStep.Pin
87
+ );
88
+ });
89
+ });
90
+
91
+ describe("and the user is generating a new seed", () => {
92
+ describe("and the seed phrase type is set to 24 words", () => {
93
+ beforeEach(() => {
94
+ // 24-words seed
95
+ flagsBytes[2] |= 0 << 5;
96
+ });
97
+
98
+ it("should return a device state with the correct seed phrase type", () => {
99
+ const onboardingState = extractOnboardingState(flagsBytes);
100
+
101
+ expect(onboardingState).not.toBeNull();
102
+ expect(onboardingState?.seedPhraseType).toBe("24-words");
103
+ });
104
+
105
+ describe("and the user is writing the seed word i", () => {
106
+ beforeEach(() => {
107
+ flagsBytes[3] = 3;
108
+ });
109
+
110
+ it("should return an onboarding step that is set at writting the seed phrase", () => {
111
+ const onboardingState = extractOnboardingState(flagsBytes);
112
+
113
+ expect(onboardingState).not.toBeNull();
114
+ expect(onboardingState?.currentOnboardingStep).toBe(
115
+ OnboardingStep.NewDevice
116
+ );
117
+ });
118
+
119
+ it("should return a device state with the index of the current seed word being written", () => {
120
+ const byte3 = flagsBytes[2];
121
+ for (let wordIndex = 0; wordIndex < 24; wordIndex++) {
122
+ flagsBytes[2] = byte3 | wordIndex;
123
+
124
+ const onboardingState = extractOnboardingState(flagsBytes);
125
+
126
+ expect(onboardingState).not.toBeNull();
127
+ expect(onboardingState?.currentSeedWordIndex).toBe(wordIndex);
128
+ }
129
+ });
130
+ });
131
+
132
+ describe("and the user is confirming the seed word i", () => {
133
+ beforeEach(() => {
134
+ flagsBytes[3] = 4;
135
+ });
136
+
137
+ it("should return an onboarding step that is set at confirming the seed phrase", () => {
138
+ const onboardingState = extractOnboardingState(flagsBytes);
139
+
140
+ expect(onboardingState).not.toBeNull();
141
+ expect(onboardingState?.currentOnboardingStep).toBe(
142
+ OnboardingStep.NewDeviceConfirming
143
+ );
144
+ });
145
+
146
+ it("should return a device state with the index of the current seed word being confirmed", () => {
147
+ const byte3 = flagsBytes[2];
148
+ for (let wordIndex = 0; wordIndex < 24; wordIndex++) {
149
+ flagsBytes[2] = byte3 | wordIndex;
150
+
151
+ const onboardingState = extractOnboardingState(flagsBytes);
152
+
153
+ expect(onboardingState).not.toBeNull();
154
+ expect(onboardingState?.currentSeedWordIndex).toBe(wordIndex);
155
+ }
156
+ });
157
+ });
158
+ });
159
+ });
160
+
161
+ describe("and the user is recovering a seed", () => {
162
+ describe("and the seed phrase type is set to X words", () => {
163
+ it("should return a device state with the correct seed phrase type", () => {
164
+ const byte3 = flagsBytes[2];
165
+
166
+ // 24-words
167
+ flagsBytes[2] = byte3 | (0 << 5);
168
+ let onboardingState = extractOnboardingState(flagsBytes);
169
+
170
+ expect(onboardingState).not.toBeNull();
171
+ expect(onboardingState?.seedPhraseType).toBe("24-words");
172
+
173
+ // 18-words
174
+ flagsBytes[2] = byte3 | (1 << 5);
175
+ onboardingState = extractOnboardingState(flagsBytes);
176
+
177
+ expect(onboardingState).not.toBeNull();
178
+ expect(onboardingState?.seedPhraseType).toBe("18-words");
179
+
180
+ // 12-words
181
+ flagsBytes[2] = byte3 | (2 << 5);
182
+ onboardingState = extractOnboardingState(flagsBytes);
183
+
184
+ expect(onboardingState).not.toBeNull();
185
+ expect(onboardingState?.seedPhraseType).toBe("12-words");
186
+ });
187
+
188
+ describe("and the user is confirming (seed recovery) the seed word i", () => {
189
+ beforeEach(() => {
190
+ // 24-words seed
191
+ flagsBytes[2] |= 0 << 5;
192
+
193
+ flagsBytes[3] = 5;
194
+ });
195
+
196
+ it("should return an onboarding step that is set at confirming the restored seed phrase", () => {
197
+ const onboardingState = extractOnboardingState(flagsBytes);
198
+
199
+ expect(onboardingState).not.toBeNull();
200
+ expect(onboardingState?.currentOnboardingStep).toBe(
201
+ OnboardingStep.RestoreSeed
202
+ );
203
+ });
204
+
205
+ it("should return a device state with the index of the current seed word being confirmed", () => {
206
+ const byte3 = flagsBytes[2];
207
+ for (let wordIndex = 0; wordIndex < 24; wordIndex++) {
208
+ flagsBytes[2] = byte3 | wordIndex;
209
+
210
+ const onboardingState = extractOnboardingState(flagsBytes);
211
+
212
+ expect(onboardingState).not.toBeNull();
213
+ expect(onboardingState?.currentSeedWordIndex).toBe(wordIndex);
214
+ }
215
+ });
216
+ });
217
+ });
218
+ });
219
+
220
+ describe("and the user is on the safety warning screen", () => {
221
+ beforeEach(() => {
222
+ flagsBytes[3] = 6;
223
+ });
224
+
225
+ it("should return an onboarding step that is set at the safety warning screen", () => {
226
+ const onboardingState = extractOnboardingState(flagsBytes);
227
+
228
+ expect(onboardingState).not.toBeNull();
229
+ expect(onboardingState?.currentOnboardingStep).toBe(
230
+ OnboardingStep.SafetyWarning
231
+ );
232
+ });
233
+ });
234
+
235
+ describe("and the user finished the onboarding process", () => {
236
+ beforeEach(() => {
237
+ flagsBytes[3] = 7;
238
+ });
239
+
240
+ it("should return an onboarding step that is set at ready", () => {
241
+ const onboardingState = extractOnboardingState(flagsBytes);
242
+
243
+ expect(onboardingState).not.toBeNull();
244
+ expect(onboardingState?.currentOnboardingStep).toBe(
245
+ OnboardingStep.Ready
246
+ );
247
+ });
248
+ });
249
+ });
250
+ });
251
+ });