@ledgerhq/live-common 34.55.0-nightly.20251215100948 → 34.55.0-nightly.20251216024112

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 (40) hide show
  1. package/lib/hw/getOnboardingStatePolling.d.ts.map +1 -1
  2. package/lib/hw/getOnboardingStatePolling.js +11 -1
  3. package/lib/hw/getOnboardingStatePolling.js.map +1 -1
  4. package/lib/hw/installApp.d.ts.map +1 -1
  5. package/lib/hw/installApp.js +4 -2
  6. package/lib/hw/installApp.js.map +1 -1
  7. package/lib/wallet-api/Exchange/server.d.ts.map +1 -1
  8. package/lib/wallet-api/Exchange/server.js +18 -0
  9. package/lib/wallet-api/Exchange/server.js.map +1 -1
  10. package/lib/wallet-api/Exchange/tracking.d.ts +18 -0
  11. package/lib/wallet-api/Exchange/tracking.d.ts.map +1 -1
  12. package/lib/wallet-api/Exchange/tracking.js +22 -0
  13. package/lib/wallet-api/Exchange/tracking.js.map +1 -1
  14. package/lib-es/hw/getOnboardingStatePolling.d.ts.map +1 -1
  15. package/lib-es/hw/getOnboardingStatePolling.js +12 -2
  16. package/lib-es/hw/getOnboardingStatePolling.js.map +1 -1
  17. package/lib-es/hw/installApp.d.ts.map +1 -1
  18. package/lib-es/hw/installApp.js +5 -3
  19. package/lib-es/hw/installApp.js.map +1 -1
  20. package/lib-es/wallet-api/Exchange/server.d.ts.map +1 -1
  21. package/lib-es/wallet-api/Exchange/server.js +18 -0
  22. package/lib-es/wallet-api/Exchange/server.js.map +1 -1
  23. package/lib-es/wallet-api/Exchange/tracking.d.ts +18 -0
  24. package/lib-es/wallet-api/Exchange/tracking.d.ts.map +1 -1
  25. package/lib-es/wallet-api/Exchange/tracking.js +22 -0
  26. package/lib-es/wallet-api/Exchange/tracking.js.map +1 -1
  27. package/package.json +73 -73
  28. package/src/families/cosmos/datasets/__snapshots__/babylon.integration.test.ts.snap +2 -2
  29. package/src/families/cosmos/datasets/__snapshots__/cosmos.integration.test.ts.snap +667 -10
  30. package/src/families/cosmos/datasets/__snapshots__/cryptoOrg.integration.test.ts.snap +2 -2
  31. package/src/families/cosmos/datasets/__snapshots__/mantra.integration.test.ts.snap +2 -2
  32. package/src/families/cosmos/datasets/__snapshots__/osmosis.integration.test.ts.snap +2 -2
  33. package/src/families/cosmos/datasets/__snapshots__/persistence.integration.test.ts.snap +4 -4
  34. package/src/hw/getOnboardingStatePolling.test.ts +64 -1
  35. package/src/hw/getOnboardingStatePolling.ts +15 -3
  36. package/src/hw/installApp.test.ts +82 -1
  37. package/src/hw/installApp.ts +58 -52
  38. package/src/wallet-api/Exchange/server.test.ts +2 -0
  39. package/src/wallet-api/Exchange/server.ts +20 -0
  40. package/src/wallet-api/Exchange/tracking.ts +56 -0
@@ -3,7 +3,7 @@
3
3
  exports[`crypto_org currency bridge scanAccounts crypto_org seed 1 1`] = `
4
4
  [
5
5
  {
6
- "balance": "10000009",
6
+ "balance": "10000010",
7
7
  "cosmosResources": {
8
8
  "delegatedBalance": "0",
9
9
  "delegations": [],
@@ -23,7 +23,7 @@ exports[`crypto_org currency bridge scanAccounts crypto_org seed 1 1`] = `
23
23
  "operationsCount": 0,
24
24
  "pendingOperations": [],
25
25
  "seedIdentifier": "0346289788c2f518a158232e7028e8e86b30cff0670326d3a2507183cb0d81cecb",
26
- "spendableBalance": "10000009",
26
+ "spendableBalance": "10000010",
27
27
  "swapHistory": [],
28
28
  "syncHash": undefined,
29
29
  "used": true,
@@ -3,7 +3,7 @@
3
3
  exports[`mantra currency bridge scanAccounts mantra seed 1 1`] = `
4
4
  [
5
5
  {
6
- "balance": "714929",
6
+ "balance": "714930",
7
7
  "currencyId": "mantra",
8
8
  "derivationMode": "",
9
9
  "freshAddress": "mantra1gyauvl44q2apn3u3aujm36q8zrj74vry7n5nvn",
@@ -12,7 +12,7 @@ exports[`mantra currency bridge scanAccounts mantra seed 1 1`] = `
12
12
  "index": 0,
13
13
  "pendingOperations": [],
14
14
  "seedIdentifier": "03d5e0ebb3f1ae2afe87e5d5a24b5029a59cc12f8fd1056840091b2f0b97e54e83",
15
- "spendableBalance": "204929",
15
+ "spendableBalance": "204930",
16
16
  "swapHistory": [],
17
17
  "syncHash": undefined,
18
18
  "used": true,
@@ -3,7 +3,7 @@
3
3
  exports[`osmo currency bridge scanAccounts osmo seed 1 1`] = `
4
4
  [
5
5
  {
6
- "balance": "124347",
6
+ "balance": "124348",
7
7
  "currencyId": "osmo",
8
8
  "derivationMode": "",
9
9
  "freshAddress": "osmo17gmcxyc5ccd5kwqqatpgfdgh380w2hc77zm0zw",
@@ -12,7 +12,7 @@ exports[`osmo currency bridge scanAccounts osmo seed 1 1`] = `
12
12
  "index": 0,
13
13
  "pendingOperations": [],
14
14
  "seedIdentifier": "02dc75cfe8137450ae2259dc7f29fd8767951956cc943adb4387e91c37a058a9f0",
15
- "spendableBalance": "124347",
15
+ "spendableBalance": "124348",
16
16
  "swapHistory": [],
17
17
  "syncHash": undefined,
18
18
  "used": true,
@@ -3,7 +3,7 @@
3
3
  exports[`persistence currency bridge scanAccounts persistence seed 1 1`] = `
4
4
  [
5
5
  {
6
- "balance": "2636576",
6
+ "balance": "2636577",
7
7
  "currencyId": "persistence",
8
8
  "derivationMode": "",
9
9
  "freshAddress": "persistence1gyauvl44q2apn3u3aujm36q8zrj74vrym5cypd",
@@ -12,14 +12,14 @@ exports[`persistence currency bridge scanAccounts persistence seed 1 1`] = `
12
12
  "index": 0,
13
13
  "pendingOperations": [],
14
14
  "seedIdentifier": "03d5e0ebb3f1ae2afe87e5d5a24b5029a59cc12f8fd1056840091b2f0b97e54e83",
15
- "spendableBalance": "1202572",
15
+ "spendableBalance": "1202573",
16
16
  "swapHistory": [],
17
17
  "syncHash": undefined,
18
18
  "used": true,
19
19
  "xpub": "persistence1gyauvl44q2apn3u3aujm36q8zrj74vrym5cypd",
20
20
  },
21
21
  {
22
- "balance": "230007",
22
+ "balance": "230008",
23
23
  "currencyId": "persistence",
24
24
  "derivationMode": "",
25
25
  "freshAddress": "persistence1v2mp0m7k96dm9qv60fkspcqlzpkzrwnevf47z2",
@@ -28,7 +28,7 @@ exports[`persistence currency bridge scanAccounts persistence seed 1 1`] = `
28
28
  "index": 1,
29
29
  "pendingOperations": [],
30
30
  "seedIdentifier": "03d5e0ebb3f1ae2afe87e5d5a24b5029a59cc12f8fd1056840091b2f0b97e54e83",
31
- "spendableBalance": "230007",
31
+ "spendableBalance": "230008",
32
32
  "swapHistory": [],
33
33
  "syncHash": undefined,
34
34
  "used": true,
@@ -1,5 +1,5 @@
1
1
  import { getOnboardingStatePolling } from "./getOnboardingStatePolling";
2
- import { from, Subscription, TimeoutError } from "rxjs";
2
+ import { from, of, Subscription, TimeoutError } from "rxjs";
3
3
  import * as rxjsOperators from "rxjs/operators";
4
4
  import { DeviceModelId } from "@ledgerhq/devices";
5
5
  import Transport from "@ledgerhq/hw-transport";
@@ -14,7 +14,13 @@ import { getVersion } from "../device/use-cases/getVersionUseCase";
14
14
  import { extractOnboardingState, OnboardingState, OnboardingStep } from "./extractOnboardingState";
15
15
  import { SeedPhraseType } from "@ledgerhq/types-live";
16
16
  import { DeviceDisconnectedWhileSendingError } from "@ledgerhq/device-management-kit";
17
+ import { quitApp } from "../deviceSDK/commands/quitApp";
17
18
 
19
+ jest.mock("../deviceSDK/commands/quitApp", () => {
20
+ return {
21
+ quitApp: jest.fn(() => of(undefined)), // immediately-completing observable
22
+ };
23
+ });
18
24
  jest.mock("./deviceAccess");
19
25
  jest.mock("../device/use-cases/getVersionUseCase");
20
26
  jest.mock("./extractOnboardingState");
@@ -42,6 +48,7 @@ const pollingPeriodMs = 1000;
42
48
 
43
49
  const mockedGetVersion = jest.mocked(getVersion);
44
50
  const mockedWithDevice = jest.mocked(withDevice);
51
+ const mockedQuitApp = jest.mocked(quitApp);
45
52
  mockedWithDevice.mockReturnValue(job => from(job(new Transport())));
46
53
 
47
54
  const mockedExtractOnboardingState = jest.mocked(extractOnboardingState);
@@ -65,6 +72,7 @@ describe("getOnboardingStatePolling", () => {
65
72
  afterEach(() => {
66
73
  mockedGetVersion.mockClear();
67
74
  mockedExtractOnboardingState.mockClear();
75
+ mockedQuitApp.mockClear();
68
76
  jest.clearAllTimers();
69
77
  onboardingStatePollingSubscription?.unsubscribe();
70
78
  });
@@ -308,6 +316,61 @@ describe("getOnboardingStatePolling", () => {
308
316
  },
309
317
  });
310
318
  });
319
+ it("should call quitApp before fetching the device version", done => {
320
+ mockedGetVersion.mockResolvedValue(aFirmwareInfo);
321
+ mockedExtractOnboardingState.mockReturnValue(anOnboardingState);
322
+
323
+ const device = aDevice;
324
+
325
+ getOnboardingStatePolling({
326
+ deviceId: device.deviceId,
327
+ deviceName: null,
328
+ pollingPeriodMs,
329
+ }).subscribe({
330
+ next: value => {
331
+ try {
332
+ expect(value.onboardingState).toEqual(anOnboardingState);
333
+
334
+ expect(mockedQuitApp).toHaveBeenCalledTimes(1);
335
+
336
+ const firstCallArgs = (mockedQuitApp as jest.Mock).mock.calls[0];
337
+ expect(firstCallArgs[0]).toBeInstanceOf(Transport);
338
+
339
+ done();
340
+ } catch (err) {
341
+ done(err);
342
+ }
343
+ },
344
+ error: err => done(err),
345
+ });
346
+
347
+ jest.advanceTimersByTime(pollingPeriodMs - 1);
348
+ });
349
+
350
+ it("should call quitApp only once when polling", () => {
351
+ mockedGetVersion.mockResolvedValue(aFirmwareInfo);
352
+ mockedExtractOnboardingState.mockReturnValue(anOnboardingState);
353
+
354
+ const device = aDevice;
355
+
356
+ onboardingStatePollingSubscription = getOnboardingStatePolling({
357
+ deviceId: device.deviceId,
358
+ deviceName: null,
359
+ pollingPeriodMs,
360
+ }).subscribe();
361
+
362
+ jest.advanceTimersByTime(pollingPeriodMs - 1);
363
+
364
+ expect(mockedQuitApp).toHaveBeenCalledTimes(1);
365
+ const firstCallArgs = (mockedQuitApp as jest.Mock).mock.calls[0];
366
+ expect(firstCallArgs[0]).toBeInstanceOf(Transport);
367
+
368
+ jest.advanceTimersByTime(pollingPeriodMs * 5);
369
+
370
+ expect(mockedQuitApp).toHaveBeenCalledTimes(1);
371
+
372
+ expect(mockedGetVersion.mock.calls.length).toBeGreaterThanOrEqual(1);
373
+ });
311
374
  });
312
375
 
313
376
  describe("When the device is in bootloader mode", () => {
@@ -1,5 +1,5 @@
1
1
  import { from, of, throwError, Observable, TimeoutError, timer } from "rxjs";
2
- import { map, catchError, first, timeout, repeat } from "rxjs/operators";
2
+ import { map, catchError, first, timeout, repeat, switchMap } from "rxjs/operators";
3
3
  import { getVersion } from "../device/use-cases/getVersionUseCase";
4
4
  import { withDevice } from "./deviceAccess";
5
5
  import {
@@ -17,6 +17,7 @@ import {
17
17
  import { FirmwareInfo } from "@ledgerhq/types-live";
18
18
  import { extractOnboardingState, OnboardingState } from "./extractOnboardingState";
19
19
  import { DeviceDisconnectedWhileSendingError } from "@ledgerhq/device-management-kit";
20
+ import { quitApp } from "../deviceSDK/commands/quitApp";
20
21
 
21
22
  export type OnboardingStatePollingResult = {
22
23
  onboardingState: OnboardingState | null;
@@ -58,11 +59,23 @@ export const getOnboardingStatePolling = ({
58
59
  safeGuardTimeoutMs = pollingPeriodMs * 10, // Nb Empirical value
59
60
  allowedErrorChecks = [],
60
61
  }: GetOnboardingStatePollingArgs): GetOnboardingStatePollingResult => {
62
+ let hasQuitAppAlreadyRun = false;
63
+
61
64
  const getOnboardingStateOnce = (): Observable<OnboardingStatePollingResult> => {
62
65
  return withDevice(deviceId, {
63
66
  openTimeoutMs: transportAbortTimeoutMs,
64
67
  matchDeviceByName: deviceName ?? undefined,
65
- })(t => from(getVersion(t, { abortTimeoutMs: transportAbortTimeoutMs }))).pipe(
68
+ })(t => {
69
+ const getVersionObs = from(getVersion(t, { abortTimeoutMs: transportAbortTimeoutMs }));
70
+
71
+ // only run quitApp the first time
72
+ if (hasQuitAppAlreadyRun) {
73
+ return getVersionObs;
74
+ }
75
+
76
+ hasQuitAppAlreadyRun = true;
77
+ return quitApp(t).pipe(switchMap(() => getVersionObs));
78
+ }).pipe(
66
79
  timeout(safeGuardTimeoutMs), // Throws a TimeoutError
67
80
  first(),
68
81
  catchError((error: unknown) => {
@@ -73,7 +86,6 @@ export const getOnboardingStatePolling = ({
73
86
  // Pushes the error to the next step to be processed (no retry from the beginning)
74
87
  return of(error as Error);
75
88
  }
76
-
77
89
  return throwError(() => error);
78
90
  }),
79
91
  map((event: FirmwareInfo | Error) => {
@@ -1,7 +1,7 @@
1
1
  import { TestScheduler } from "rxjs/testing";
2
2
  import installApp from "./installApp";
3
3
  import ManagerAPI from "../manager/api";
4
- import { defer } from "rxjs";
4
+ import { defer, of, throwError } from "rxjs";
5
5
  import { anAppBuilder } from "../mock/fixtures/anApp";
6
6
  import { aTransportBuilder } from "@ledgerhq/hw-transport-mocker";
7
7
  import {
@@ -9,6 +9,7 @@ import {
9
9
  ManagerDeviceLockedError,
10
10
  UnresponsiveDeviceError,
11
11
  } from "@ledgerhq/errors";
12
+ import { quitApp } from "../deviceSDK/commands/quitApp";
12
13
 
13
14
  // Mocking ManagerAPI
14
15
  jest.mock("../manager/api", () => {
@@ -19,7 +20,13 @@ jest.mock("../manager/api", () => {
19
20
  install: jest.fn(),
20
21
  };
21
22
  });
23
+
24
+ jest.mock("../deviceSDK/commands/quitApp", () => ({
25
+ quitApp: jest.fn(),
26
+ }));
27
+
22
28
  const mockManagerApiInstall = jest.mocked(ManagerAPI.install);
29
+ const mockQuitApp = jest.mocked(quitApp);
23
30
 
24
31
  describe("installApp", () => {
25
32
  let testScheduler: TestScheduler;
@@ -29,6 +36,11 @@ describe("installApp", () => {
29
36
  // Makes TestScheduler assertions works with our test framework Jest
30
37
  expect(actual).toEqual(expected);
31
38
  });
39
+
40
+ mockManagerApiInstall.mockReset();
41
+ mockQuitApp.mockReset();
42
+
43
+ mockQuitApp.mockReturnValue(of<void>(undefined));
32
44
  });
33
45
 
34
46
  test("On one error followed by a successfully emitted event, it should retry after the given delay", () => {
@@ -149,4 +161,73 @@ describe("installApp", () => {
149
161
  });
150
162
  }
151
163
  });
164
+
165
+ test("it should run quit app at the beginning then keep going with manager", () => {
166
+ const transport = aTransportBuilder();
167
+ const app = anAppBuilder();
168
+
169
+ const fakeEvent = {
170
+ type: "bulk-progress",
171
+ progress: 0.5,
172
+ };
173
+
174
+ mockManagerApiInstall.mockReturnValue(of(fakeEvent));
175
+
176
+ const results: Array<{ progress: number }> = [];
177
+
178
+ installApp(transport, "target-test", app).subscribe({
179
+ next: v => results.push(v),
180
+ error: e => {
181
+ throw e;
182
+ },
183
+ });
184
+
185
+ // quitApp called once, with the same transport
186
+ expect(mockQuitApp).toHaveBeenCalledTimes(1);
187
+ expect(mockQuitApp).toHaveBeenCalledWith(transport);
188
+
189
+ // ManagerAPI.install called once, with correct params
190
+ expect(mockManagerApiInstall).toHaveBeenCalledTimes(1);
191
+ expect(mockManagerApiInstall).toHaveBeenCalledWith(
192
+ transport,
193
+ "install-app",
194
+ expect.objectContaining({
195
+ targetId: "target-test",
196
+ perso: app.perso,
197
+ deleteKey: app.delete_key,
198
+ firmware: app.firmware,
199
+ firmwareKey: app.firmware_key,
200
+ hash: app.hash,
201
+ }),
202
+ );
203
+
204
+ // quitApp must be invoked before ManagerAPI.install
205
+ expect(mockQuitApp.mock.invocationCallOrder[0]).toBeLessThan(
206
+ mockManagerApiInstall.mock.invocationCallOrder[0],
207
+ );
208
+
209
+ expect(results).toEqual([{ progress: 0.5 }]);
210
+ });
211
+
212
+ test("it should return error if quit app fail", done => {
213
+ const transport = aTransportBuilder();
214
+ const app = anAppBuilder();
215
+ const error = new Error("quit failed");
216
+
217
+ mockQuitApp.mockReturnValueOnce(throwError(() => error));
218
+
219
+ const observable = installApp(transport, "target-test", app);
220
+
221
+ observable.subscribe({
222
+ next: () => {
223
+ done(new Error("Expected an error, but got a next notification"));
224
+ },
225
+ error: err => {
226
+ expect(err).toBe(error);
227
+ // ManagerAPI.install should never be called if quitApp fails
228
+ expect(mockManagerApiInstall).not.toHaveBeenCalled();
229
+ done();
230
+ },
231
+ });
232
+ });
152
233
  });
@@ -1,5 +1,5 @@
1
1
  import { Observable, throwError, timer } from "rxjs";
2
- import { throttleTime, filter, map, catchError, retry } from "rxjs/operators";
2
+ import { throttleTime, filter, map, catchError, retry, switchMap } from "rxjs/operators";
3
3
  import {
4
4
  LockedDeviceError,
5
5
  ManagerAppDepInstallRequired,
@@ -12,6 +12,7 @@ import ManagerAPI from "../manager/api";
12
12
  import { getDependencies } from "../apps/polyfill";
13
13
  import { LocalTracer } from "@ledgerhq/logs";
14
14
  import { LOG_TYPE } from ".";
15
+ import { quitApp } from "../deviceSDK/commands/quitApp";
15
16
 
16
17
  const APP_INSTALL_RETRY_DELAY = 500;
17
18
  const APP_INSTALL_RETRY_LIMIT = 5;
@@ -53,60 +54,65 @@ export default function installApp(
53
54
  retryDelayMs,
54
55
  });
55
56
 
56
- return ManagerAPI.install(transport, "install-app", {
57
- targetId,
58
- perso: app.perso,
59
- deleteKey: app.delete_key,
60
- firmware: app.firmware,
61
- firmwareKey: app.firmware_key,
62
- hash: app.hash,
63
- }).pipe(
64
- retry({
65
- count: retryLimit,
66
- delay: (error: unknown, retryCount: number) => {
67
- // Not retrying on locked device errors
68
- if (
69
- error instanceof LockedDeviceError ||
70
- error instanceof ManagerDeviceLockedError ||
71
- error instanceof UnresponsiveDeviceError
72
- ) {
73
- tracer.trace(`Not retrying on error: ${error}`, {
74
- error,
75
- });
76
- return throwError(() => error);
77
- }
57
+ // Run quitApp just before ManagerAPI.install
58
+ return quitApp(transport).pipe(
59
+ switchMap(() =>
60
+ ManagerAPI.install(transport, "install-app", {
61
+ targetId,
62
+ perso: app.perso,
63
+ deleteKey: app.delete_key,
64
+ firmware: app.firmware,
65
+ firmwareKey: app.firmware_key,
66
+ hash: app.hash,
67
+ }).pipe(
68
+ retry({
69
+ count: retryLimit,
70
+ delay: (error: unknown, retryCount: number) => {
71
+ // Not retrying on locked device errors
72
+ if (
73
+ error instanceof LockedDeviceError ||
74
+ error instanceof ManagerDeviceLockedError ||
75
+ error instanceof UnresponsiveDeviceError
76
+ ) {
77
+ tracer.trace(`Not retrying on error: ${error}`, {
78
+ error,
79
+ });
80
+ return throwError(() => error);
81
+ }
78
82
 
79
- tracer.trace(`Retrying (${retryCount}/${retryLimit}) on error: ${error}`, {
80
- error,
81
- retryLimit,
82
- retryDelayMs,
83
- });
84
- return timer(retryDelayMs);
85
- },
86
- }),
87
- filter((e: any) => e.type === "bulk-progress"), // only bulk progress interests the UI
88
- throttleTime(100), // throttle to only emit 10 event/s max, to not spam the UI
89
- map((e: any) => ({
90
- progress: e.progress,
91
- })), // extract a stream of progress percentage
92
- catchError((e: Error) => {
93
- tracer.trace(`Error: ${e}`, { error: e });
83
+ tracer.trace(`Retrying (${retryCount}/${retryLimit}) on error: ${error}`, {
84
+ error,
85
+ retryLimit,
86
+ retryDelayMs,
87
+ });
88
+ return timer(retryDelayMs);
89
+ },
90
+ }),
91
+ filter((e: any) => e.type === "bulk-progress"), // only bulk progress interests the UI
92
+ throttleTime(100), // throttle to only emit 10 event/s max, to not spam the UI
93
+ map((e: any) => ({
94
+ progress: e.progress,
95
+ })), // extract a stream of progress percentage
96
+ catchError((e: Error) => {
97
+ tracer.trace(`Error: ${e}`, { error: e });
94
98
 
95
- if (!e || !e.message) return throwError(() => e);
99
+ if (!e || !e.message) return throwError(() => e);
96
100
 
97
- const status = e.message.slice(e.message.length - 4);
98
- if (status === "6a83" || status === "6811") {
99
- const dependencies = getDependencies(app.name);
100
- return throwError(
101
- () =>
102
- new ManagerAppDepInstallRequired("", {
103
- appName: app.name,
104
- dependency: dependencies.join(", "),
105
- }),
106
- );
107
- }
101
+ const status = e.message.slice(e.message.length - 4);
102
+ if (status === "6a83" || status === "6811") {
103
+ const dependencies = getDependencies(app.name);
104
+ return throwError(
105
+ () =>
106
+ new ManagerAppDepInstallRequired("", {
107
+ appName: app.name,
108
+ dependency: dependencies.join(", "),
109
+ }),
110
+ );
111
+ }
108
112
 
109
- return throwError(() => e);
110
- }),
113
+ return throwError(() => e);
114
+ }),
115
+ ),
116
+ ),
111
117
  );
112
118
  }
@@ -19,6 +19,8 @@ const mockTracking = {
19
19
  completeExchangeSuccess: jest.fn(),
20
20
  completeExchangeFail: jest.fn(),
21
21
  completeExchangeNoParams: jest.fn(),
22
+ swapPayloadRequested: jest.fn(),
23
+ swapResponseRetrieved: jest.fn(),
22
24
  };
23
25
  const testAppManifest = {
24
26
  id: "12",
@@ -467,6 +467,17 @@ export const handlers = ({
467
467
  throw wrappedError;
468
468
  }
469
469
 
470
+ tracking.swapPayloadRequested({
471
+ provider,
472
+ transactionId,
473
+ fromAccountAddress,
474
+ toAccountAddress,
475
+ fromCurrencyId: fromCurrency!.id,
476
+ toCurrencyId: toCurrency?.id,
477
+ fromAmount,
478
+ quoteId,
479
+ });
480
+
470
481
  const {
471
482
  binaryPayload,
472
483
  signature,
@@ -494,6 +505,15 @@ export const handlers = ({
494
505
  throw wrappedError;
495
506
  });
496
507
 
508
+ tracking.swapResponseRetrieved({
509
+ binaryPayload,
510
+ signature,
511
+ payinAddress,
512
+ swapId,
513
+ payinExtraId,
514
+ extraTransactionParameters,
515
+ });
516
+
497
517
  // Complete Swap
498
518
  const trackingCompleteParams = {
499
519
  provider: params.provider,
@@ -119,6 +119,62 @@ export default function trackingWrapper(trackCall: TrackExchange) {
119
119
  completeExchangeNoParams: (manifest: AppManifest) => {
120
120
  track("Completes Exchange no params", getEventData(manifest));
121
121
  },
122
+
123
+ swapPayloadRequested: ({
124
+ provider,
125
+ transactionId,
126
+ fromAccountAddress,
127
+ toAccountAddress,
128
+ fromCurrencyId,
129
+ toCurrencyId,
130
+ fromAmount,
131
+ quoteId,
132
+ }: {
133
+ provider: string;
134
+ transactionId: string;
135
+ fromAccountAddress: string;
136
+ toAccountAddress: string;
137
+ fromCurrencyId: string;
138
+ toCurrencyId?: string;
139
+ fromAmount: string | number;
140
+ quoteId?: string;
141
+ }) => {
142
+ track("Swap payload requested", {
143
+ provider,
144
+ transactionId,
145
+ fromAccountAddress,
146
+ toAccountAddress,
147
+ fromCurrencyId,
148
+ toCurrencyId,
149
+ fromAmount: String(fromAmount),
150
+ quoteId,
151
+ });
152
+ },
153
+
154
+ swapResponseRetrieved: ({
155
+ binaryPayload,
156
+ signature,
157
+ payinAddress,
158
+ swapId,
159
+ payinExtraId,
160
+ extraTransactionParameters,
161
+ }: {
162
+ binaryPayload: string;
163
+ signature: string;
164
+ payinAddress: string;
165
+ swapId: string;
166
+ payinExtraId?: string;
167
+ extraTransactionParameters?: string;
168
+ }) => {
169
+ track("Swap response retrieved", {
170
+ binaryPayload,
171
+ signature,
172
+ payinAddress,
173
+ swapId,
174
+ payinExtraId,
175
+ extraTransactionParameters,
176
+ });
177
+ },
122
178
  } as const;
123
179
  }
124
180