@ledgerhq/live-common 34.54.0-nightly.20251206023719 → 34.54.0-nightly.20251209140356

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 (212) hide show
  1. package/lib/__tests__/test-helpers/environment.js +2 -0
  2. package/lib/__tests__/test-helpers/environment.js.map +1 -1
  3. package/lib/account/serialization.js +1 -1
  4. package/lib/account/serialization.js.map +1 -1
  5. package/lib/account/support.js +1 -1
  6. package/lib/account/support.js.map +1 -1
  7. package/lib/bridge/generic-alpaca/getAccountShape.d.ts.map +1 -1
  8. package/lib/bridge/generic-alpaca/getAccountShape.js +3 -2
  9. package/lib/bridge/generic-alpaca/getAccountShape.js.map +1 -1
  10. package/lib/bridge/generic-alpaca/prepareTransaction.d.ts.map +1 -1
  11. package/lib/bridge/generic-alpaca/prepareTransaction.js +7 -0
  12. package/lib/bridge/generic-alpaca/prepareTransaction.js.map +1 -1
  13. package/lib/domain/getTokensWithFunds.d.ts +7 -1
  14. package/lib/domain/getTokensWithFunds.d.ts.map +1 -1
  15. package/lib/domain/getTokensWithFunds.js +15 -4
  16. package/lib/domain/getTokensWithFunds.js.map +1 -1
  17. package/lib/domain/getTotalStakeableAssets.d.ts +10 -0
  18. package/lib/domain/getTotalStakeableAssets.d.ts.map +1 -0
  19. package/lib/domain/getTotalStakeableAssets.js +35 -0
  20. package/lib/domain/getTotalStakeableAssets.js.map +1 -0
  21. package/lib/e2e/index.d.ts +9 -0
  22. package/lib/e2e/index.d.ts.map +1 -1
  23. package/lib/exchange/swap/api/v5/fetchCurrencyFrom.js +1 -1
  24. package/lib/exchange/swap/api/v5/fetchCurrencyFrom.js.map +1 -1
  25. package/lib/exchange/swap/getIncompatibleCurrencyKeys.d.ts.map +1 -1
  26. package/lib/exchange/swap/getIncompatibleCurrencyKeys.js +4 -0
  27. package/lib/exchange/swap/getIncompatibleCurrencyKeys.js.map +1 -1
  28. package/lib/exchange/swap/transactionStrategies.d.ts +2 -1
  29. package/lib/exchange/swap/transactionStrategies.d.ts.map +1 -1
  30. package/lib/exchange/swap/transactionStrategies.js +7 -6
  31. package/lib/exchange/swap/transactionStrategies.js.map +1 -1
  32. package/lib/featureFlags/defaultFeatures.d.ts +2 -0
  33. package/lib/featureFlags/defaultFeatures.d.ts.map +1 -1
  34. package/lib/featureFlags/defaultFeatures.js +3 -0
  35. package/lib/featureFlags/defaultFeatures.js.map +1 -1
  36. package/lib/featureFlags/firebaseFeatureFlags.js +1 -1
  37. package/lib/featureFlags/firebaseFeatureFlags.js.map +1 -1
  38. package/lib/featureFlags/useFeature.d.ts +1 -1
  39. package/lib/featureFlags/useFeature.d.ts.map +1 -1
  40. package/lib/featureFlags/useHasOverriddenFeatureFlags.js +1 -1
  41. package/lib/featureFlags/useHasOverriddenFeatureFlags.js.map +1 -1
  42. package/lib/hw/getBitcoinLikeInfo.js +1 -1
  43. package/lib/hw/getBitcoinLikeInfo.js.map +1 -1
  44. package/lib/mock/account.js +1 -1
  45. package/lib/mock/account.js.map +1 -1
  46. package/lib/modularDrawer/hooks/useCurrenciesUnderFeatureFlag.d.ts.map +1 -1
  47. package/lib/modularDrawer/hooks/useCurrenciesUnderFeatureFlag.js +6 -0
  48. package/lib/modularDrawer/hooks/useCurrenciesUnderFeatureFlag.js.map +1 -1
  49. package/lib/wallet-api/Exchange/SwapError.d.ts +93 -0
  50. package/lib/wallet-api/Exchange/SwapError.d.ts.map +1 -0
  51. package/lib/wallet-api/Exchange/SwapError.js +142 -0
  52. package/lib/wallet-api/Exchange/SwapError.js.map +1 -0
  53. package/lib/wallet-api/Exchange/handleSwapErrors.d.ts +40 -0
  54. package/lib/wallet-api/Exchange/handleSwapErrors.d.ts.map +1 -0
  55. package/lib/wallet-api/Exchange/handleSwapErrors.js +112 -0
  56. package/lib/wallet-api/Exchange/handleSwapErrors.js.map +1 -0
  57. package/lib/wallet-api/Exchange/index.d.ts +4 -0
  58. package/lib/wallet-api/Exchange/index.d.ts.map +1 -0
  59. package/lib/wallet-api/Exchange/index.js +27 -0
  60. package/lib/wallet-api/Exchange/index.js.map +1 -0
  61. package/lib/wallet-api/Exchange/parser.d.ts +46 -0
  62. package/lib/wallet-api/Exchange/parser.d.ts.map +1 -0
  63. package/lib/wallet-api/Exchange/parser.js +97 -0
  64. package/lib/wallet-api/Exchange/parser.js.map +1 -0
  65. package/lib/wallet-api/Exchange/server.d.ts.map +1 -1
  66. package/lib/wallet-api/Exchange/server.js +226 -177
  67. package/lib/wallet-api/Exchange/server.js.map +1 -1
  68. package/lib/wallet-api/Exchange/tracking.d.ts +7 -6
  69. package/lib/wallet-api/Exchange/tracking.d.ts.map +1 -1
  70. package/lib/wallet-api/Exchange/tracking.js +52 -13
  71. package/lib/wallet-api/Exchange/tracking.js.map +1 -1
  72. package/lib/wallet-api/logic.d.ts +1 -1
  73. package/lib/wallet-api/logic.d.ts.map +1 -1
  74. package/lib/wallet-api/logic.js +5 -5
  75. package/lib/wallet-api/logic.js.map +1 -1
  76. package/lib/wallet-api/react.d.ts.map +1 -1
  77. package/lib/wallet-api/react.js +9 -6
  78. package/lib/wallet-api/react.js.map +1 -1
  79. package/lib/wallet-api/tracking.d.ts +5 -5
  80. package/lib/wallet-api/tracking.d.ts.map +1 -1
  81. package/lib/wallet-api/tracking.js +30 -10
  82. package/lib/wallet-api/tracking.js.map +1 -1
  83. package/lib/wallet-api/useDappLogic.d.ts.map +1 -1
  84. package/lib/wallet-api/useDappLogic.js +31 -20
  85. package/lib/wallet-api/useDappLogic.js.map +1 -1
  86. package/lib/wallet-api/utils/extractDappURLFromManifest.js +3 -3
  87. package/lib/wallet-api/utils/extractDappURLFromManifest.js.map +1 -1
  88. package/lib/wallet-api/utils/extractURLFromManifest.js +1 -1
  89. package/lib/wallet-api/utils/extractURLFromManifest.js.map +1 -1
  90. package/lib-es/__tests__/test-helpers/environment.js +2 -0
  91. package/lib-es/__tests__/test-helpers/environment.js.map +1 -1
  92. package/lib-es/account/serialization.js +1 -1
  93. package/lib-es/account/serialization.js.map +1 -1
  94. package/lib-es/account/support.js +1 -1
  95. package/lib-es/account/support.js.map +1 -1
  96. package/lib-es/bridge/generic-alpaca/getAccountShape.d.ts.map +1 -1
  97. package/lib-es/bridge/generic-alpaca/getAccountShape.js +3 -2
  98. package/lib-es/bridge/generic-alpaca/getAccountShape.js.map +1 -1
  99. package/lib-es/bridge/generic-alpaca/prepareTransaction.d.ts.map +1 -1
  100. package/lib-es/bridge/generic-alpaca/prepareTransaction.js +7 -0
  101. package/lib-es/bridge/generic-alpaca/prepareTransaction.js.map +1 -1
  102. package/lib-es/domain/getTokensWithFunds.d.ts +7 -1
  103. package/lib-es/domain/getTokensWithFunds.d.ts.map +1 -1
  104. package/lib-es/domain/getTokensWithFunds.js +13 -3
  105. package/lib-es/domain/getTokensWithFunds.js.map +1 -1
  106. package/lib-es/domain/getTotalStakeableAssets.d.ts +10 -0
  107. package/lib-es/domain/getTotalStakeableAssets.d.ts.map +1 -0
  108. package/lib-es/domain/getTotalStakeableAssets.js +31 -0
  109. package/lib-es/domain/getTotalStakeableAssets.js.map +1 -0
  110. package/lib-es/e2e/index.d.ts +9 -0
  111. package/lib-es/e2e/index.d.ts.map +1 -1
  112. package/lib-es/exchange/swap/api/v5/fetchCurrencyFrom.js +1 -1
  113. package/lib-es/exchange/swap/api/v5/fetchCurrencyFrom.js.map +1 -1
  114. package/lib-es/exchange/swap/getIncompatibleCurrencyKeys.d.ts.map +1 -1
  115. package/lib-es/exchange/swap/getIncompatibleCurrencyKeys.js +4 -0
  116. package/lib-es/exchange/swap/getIncompatibleCurrencyKeys.js.map +1 -1
  117. package/lib-es/exchange/swap/transactionStrategies.d.ts +2 -1
  118. package/lib-es/exchange/swap/transactionStrategies.d.ts.map +1 -1
  119. package/lib-es/exchange/swap/transactionStrategies.js +7 -6
  120. package/lib-es/exchange/swap/transactionStrategies.js.map +1 -1
  121. package/lib-es/featureFlags/defaultFeatures.d.ts +2 -0
  122. package/lib-es/featureFlags/defaultFeatures.d.ts.map +1 -1
  123. package/lib-es/featureFlags/defaultFeatures.js +3 -0
  124. package/lib-es/featureFlags/defaultFeatures.js.map +1 -1
  125. package/lib-es/featureFlags/firebaseFeatureFlags.js +1 -1
  126. package/lib-es/featureFlags/firebaseFeatureFlags.js.map +1 -1
  127. package/lib-es/featureFlags/useFeature.d.ts +1 -1
  128. package/lib-es/featureFlags/useFeature.d.ts.map +1 -1
  129. package/lib-es/featureFlags/useHasOverriddenFeatureFlags.js +1 -1
  130. package/lib-es/featureFlags/useHasOverriddenFeatureFlags.js.map +1 -1
  131. package/lib-es/hw/getBitcoinLikeInfo.js +1 -1
  132. package/lib-es/hw/getBitcoinLikeInfo.js.map +1 -1
  133. package/lib-es/mock/account.js +1 -1
  134. package/lib-es/mock/account.js.map +1 -1
  135. package/lib-es/modularDrawer/hooks/useCurrenciesUnderFeatureFlag.d.ts.map +1 -1
  136. package/lib-es/modularDrawer/hooks/useCurrenciesUnderFeatureFlag.js +6 -0
  137. package/lib-es/modularDrawer/hooks/useCurrenciesUnderFeatureFlag.js.map +1 -1
  138. package/lib-es/wallet-api/Exchange/SwapError.d.ts +93 -0
  139. package/lib-es/wallet-api/Exchange/SwapError.d.ts.map +1 -0
  140. package/lib-es/wallet-api/Exchange/SwapError.js +128 -0
  141. package/lib-es/wallet-api/Exchange/SwapError.js.map +1 -0
  142. package/lib-es/wallet-api/Exchange/handleSwapErrors.d.ts +40 -0
  143. package/lib-es/wallet-api/Exchange/handleSwapErrors.d.ts.map +1 -0
  144. package/lib-es/wallet-api/Exchange/handleSwapErrors.js +106 -0
  145. package/lib-es/wallet-api/Exchange/handleSwapErrors.js.map +1 -0
  146. package/lib-es/wallet-api/Exchange/index.d.ts +4 -0
  147. package/lib-es/wallet-api/Exchange/index.d.ts.map +1 -0
  148. package/lib-es/wallet-api/Exchange/index.js +7 -0
  149. package/lib-es/wallet-api/Exchange/index.js.map +1 -0
  150. package/lib-es/wallet-api/Exchange/parser.d.ts +46 -0
  151. package/lib-es/wallet-api/Exchange/parser.d.ts.map +1 -0
  152. package/lib-es/wallet-api/Exchange/parser.js +90 -0
  153. package/lib-es/wallet-api/Exchange/parser.js.map +1 -0
  154. package/lib-es/wallet-api/Exchange/server.d.ts.map +1 -1
  155. package/lib-es/wallet-api/Exchange/server.js +223 -177
  156. package/lib-es/wallet-api/Exchange/server.js.map +1 -1
  157. package/lib-es/wallet-api/Exchange/tracking.d.ts +7 -6
  158. package/lib-es/wallet-api/Exchange/tracking.d.ts.map +1 -1
  159. package/lib-es/wallet-api/Exchange/tracking.js +52 -13
  160. package/lib-es/wallet-api/Exchange/tracking.js.map +1 -1
  161. package/lib-es/wallet-api/logic.d.ts +1 -1
  162. package/lib-es/wallet-api/logic.d.ts.map +1 -1
  163. package/lib-es/wallet-api/logic.js +5 -5
  164. package/lib-es/wallet-api/logic.js.map +1 -1
  165. package/lib-es/wallet-api/react.d.ts.map +1 -1
  166. package/lib-es/wallet-api/react.js +9 -6
  167. package/lib-es/wallet-api/react.js.map +1 -1
  168. package/lib-es/wallet-api/tracking.d.ts +5 -5
  169. package/lib-es/wallet-api/tracking.d.ts.map +1 -1
  170. package/lib-es/wallet-api/tracking.js +30 -10
  171. package/lib-es/wallet-api/tracking.js.map +1 -1
  172. package/lib-es/wallet-api/useDappLogic.d.ts.map +1 -1
  173. package/lib-es/wallet-api/useDappLogic.js +31 -20
  174. package/lib-es/wallet-api/useDappLogic.js.map +1 -1
  175. package/lib-es/wallet-api/utils/extractDappURLFromManifest.js +3 -3
  176. package/lib-es/wallet-api/utils/extractDappURLFromManifest.js.map +1 -1
  177. package/lib-es/wallet-api/utils/extractURLFromManifest.js +1 -1
  178. package/lib-es/wallet-api/utils/extractURLFromManifest.js.map +1 -1
  179. package/package.json +73 -73
  180. package/src/__tests__/test-helpers/environment.ts +2 -0
  181. package/src/account/serialization.ts +1 -1
  182. package/src/account/support.ts +1 -1
  183. package/src/bridge/generic-alpaca/getAccountShape.ts +4 -2
  184. package/src/bridge/generic-alpaca/prepareTransaction.ts +7 -0
  185. package/src/bridge/generic-alpaca/tests/prepareTransaction.test.ts +42 -0
  186. package/src/domain/getTokensWithFunds.ts +18 -5
  187. package/src/domain/getTotalStakeableAssets.test.ts +267 -0
  188. package/src/domain/getTotalStakeableAssets.ts +47 -0
  189. package/src/exchange/swap/api/v5/fetchCurrencyFrom.ts +1 -1
  190. package/src/exchange/swap/getIncompatibleCurrencyKeys.ts +4 -0
  191. package/src/exchange/swap/transactionStrategies.ts +8 -7
  192. package/src/featureFlags/defaultFeatures.ts +3 -0
  193. package/src/featureFlags/firebaseFeatureFlags.ts +1 -1
  194. package/src/featureFlags/useHasOverriddenFeatureFlags.ts +1 -1
  195. package/src/hw/getBitcoinLikeInfo.ts +1 -1
  196. package/src/mock/account.ts +1 -1
  197. package/src/modularDrawer/hooks/useCurrenciesUnderFeatureFlag.ts +6 -0
  198. package/src/wallet-api/Exchange/SwapError.test.ts +126 -0
  199. package/src/wallet-api/Exchange/SwapError.ts +159 -0
  200. package/src/wallet-api/Exchange/handleSwapErrors.test.ts +46 -0
  201. package/src/wallet-api/Exchange/handleSwapErrors.ts +161 -0
  202. package/src/wallet-api/Exchange/index.ts +26 -0
  203. package/src/wallet-api/Exchange/parser.test.ts +86 -0
  204. package/src/wallet-api/Exchange/parser.ts +119 -0
  205. package/src/wallet-api/Exchange/server.ts +287 -235
  206. package/src/wallet-api/Exchange/tracking.ts +56 -13
  207. package/src/wallet-api/logic.ts +5 -4
  208. package/src/wallet-api/react.ts +10 -5
  209. package/src/wallet-api/tracking.ts +30 -10
  210. package/src/wallet-api/useDappLogic.ts +32 -20
  211. package/src/wallet-api/utils/extractDappURLFromManifest.ts +3 -3
  212. package/src/wallet-api/utils/extractURLFromManifest.ts +1 -1
@@ -55,6 +55,10 @@ import { DeviceModelId } from "@ledgerhq/types-devices";
55
55
  import { setBroadcastTransaction } from "../../exchange/swap/setBroadcastTransaction";
56
56
  import { Transaction as EvmTransaction } from "@ledgerhq/coin-evm/types/index";
57
57
  import { padHexString } from "@ledgerhq/hw-app-eth";
58
+ import { createStepError, StepError, toError } from "./parser";
59
+ import { handleErrors } from "./handleSwapErrors";
60
+ import get from "lodash/get";
61
+ import { SwapError } from "./SwapError";
58
62
 
59
63
  export { ExchangeType };
60
64
 
@@ -376,240 +380,288 @@ export const handlers = ({
376
380
  );
377
381
  }),
378
382
  "custom.exchange.swap": customWrapper<ExchangeSwapParams, SwapResult>(async params => {
379
- if (!params) {
380
- tracking.startExchangeNoParams(manifest);
381
- throw new ServerError(createUnknownError({ message: "params is undefined" }));
382
- }
383
+ try {
384
+ if (!params) {
385
+ tracking.startExchangeNoParams(manifest);
386
+ throw new ServerError(createUnknownError({ message: "params is undefined" }));
387
+ }
383
388
 
384
- const {
385
- provider,
386
- fromAmount,
387
- fromAmountAtomic,
388
- quoteId,
389
- toNewTokenId,
390
- customFeeConfig,
391
- swapAppVersion,
392
- sponsored,
393
- } = params;
394
-
395
- const trackingParams = {
396
- provider: params.provider,
397
- exchangeType: params.exchangeType,
398
- };
399
-
400
- tracking.startExchangeRequested(trackingParams);
401
-
402
- const exchangeStartParams: ExchangeStartParamsUiRequest = (await extractSwapStartParam(
403
- params,
404
- accounts,
405
- )) as SwapStartParamsUiRequest;
406
-
407
- const {
408
- fromCurrency,
409
- fromAccount,
410
- fromParentAccount,
411
- toCurrency,
412
- toAccount,
413
- toParentAccount,
414
- } = exchangeStartParams.exchange;
415
-
416
- if (!fromAccount || !fromCurrency) {
417
- throw new ServerError(createAccountNotFound(params.fromAccountId));
418
- }
389
+ const {
390
+ provider,
391
+ fromAmount,
392
+ fromAmountAtomic,
393
+ quoteId,
394
+ toNewTokenId,
395
+ customFeeConfig,
396
+ swapAppVersion,
397
+ sponsored,
398
+ isEmbedded,
399
+ } = params;
419
400
 
420
- const fromAccountAddress = fromParentAccount
421
- ? fromParentAccount.freshAddress
422
- : (fromAccount as Account).freshAddress;
423
-
424
- const toAccountAddress = toParentAccount
425
- ? toParentAccount.freshAddress
426
- : (toAccount as Account).freshAddress;
427
-
428
- // Step 1: Open the drawer and open exchange app
429
- const startExchange = async () => {
430
- return new Promise<{ transactionId: string; device?: ExchangeStartResult["device"] }>(
431
- (resolve, reject) => {
432
- uiExchangeStart({
433
- exchangeParams: exchangeStartParams,
434
- onSuccess: (nonce, device) => {
435
- tracking.startExchangeSuccess(trackingParams);
436
- resolve({ transactionId: nonce, device });
437
- },
438
- onCancel: error => {
439
- tracking.startExchangeFail(trackingParams);
440
- reject(error);
441
- },
442
- });
443
- },
444
- );
445
- };
446
-
447
- const { transactionId, device: deviceInfo } = await startExchange();
448
-
449
- const {
450
- binaryPayload,
451
- signature,
452
- payinAddress,
453
- swapId,
454
- payinExtraId,
455
- extraTransactionParameters,
456
- } = await retrieveSwapPayload({
457
- provider,
458
- deviceTransactionId: transactionId,
459
- fromAccountAddress,
460
- toAccountAddress,
461
- fromAccountCurrency: fromCurrency!.id,
462
- toAccountCurrency: toCurrency!.id,
463
- amount: fromAmount,
464
- amountInAtomicUnit: fromAmountAtomic,
465
- quoteId,
466
- toNewTokenId,
467
- }).catch((error: Error) => {
468
- throw error;
469
- });
470
-
471
- // Complete Swap
472
- const trackingCompleteParams = {
473
- provider: params.provider,
474
- exchangeType: params.exchangeType,
475
- };
476
- tracking.completeExchangeRequested(trackingCompleteParams);
477
-
478
- const strategyData = {
479
- recipient: payinAddress,
480
- amount: fromAmountAtomic,
481
- currency: fromCurrency as CryptoOrTokenCurrency,
482
- customFeeConfig: customFeeConfig ?? {},
483
- payinExtraId,
484
- extraTransactionParameters,
485
- sponsored,
486
- };
487
-
488
- const transaction: Transaction = await getStrategy(strategyData, "swap").catch(
489
- async error => {
490
- throw error;
491
- },
492
- );
401
+ const trackingParams = {
402
+ provider: params.provider,
403
+ exchangeType: params.exchangeType,
404
+ isEmbeddedSwap: isEmbedded,
405
+ };
493
406
 
494
- const mainFromAccount = getMainAccount(fromAccount, fromParentAccount);
407
+ tracking.startExchangeRequested(trackingParams);
495
408
 
496
- if (transaction.family !== mainFromAccount.currency.family) {
497
- return Promise.reject(
498
- new Error(
499
- `Account and transaction must be from the same family. Account family: ${mainFromAccount.currency.family}, Transaction family: ${transaction.family}`,
500
- ),
501
- );
502
- }
409
+ const exchangeStartParams: ExchangeStartParamsUiRequest = (await extractSwapStartParam(
410
+ params,
411
+ accounts,
412
+ )) as SwapStartParamsUiRequest;
413
+
414
+ const {
415
+ fromCurrency,
416
+ fromAccount,
417
+ fromParentAccount,
418
+ toCurrency,
419
+ toAccount,
420
+ toParentAccount,
421
+ } = exchangeStartParams.exchange;
422
+
423
+ if (!fromAccount || !fromCurrency) {
424
+ throw new ServerError(createAccountNotFound(params.fromAccountId));
425
+ }
503
426
 
504
- const accountBridge = getAccountBridge(fromAccount, fromParentAccount);
505
-
506
- /**
507
- * 'subAccountId' is used for ETH and it's ERC-20 tokens.
508
- * This field is ignored for BTC
509
- */
510
- const subAccountId =
511
- fromParentAccount && fromParentAccount.id !== fromAccount.id ? fromAccount.id : undefined;
512
-
513
- const bridgeTx = accountBridge.createTransaction(fromAccount);
514
- /**
515
- * We append the `recipient` to the tx created from `createTransaction`
516
- * to avoid having userGasLimit reset to null for ETH txs
517
- * cf. libs/ledger-live-common/src/families/ethereum/updateTransaction.ts
518
- */
519
- const tx = accountBridge.updateTransaction(
520
- {
521
- ...bridgeTx,
522
- recipient: transaction.recipient,
523
- },
524
- {
525
- ...transaction,
526
- feesStrategy: params.feeStrategy.toLowerCase(),
527
- subAccountId,
528
- },
529
- );
427
+ const fromAccountAddress = fromParentAccount
428
+ ? fromParentAccount.freshAddress
429
+ : (fromAccount as Account).freshAddress;
430
+
431
+ const toAccountAddress = toParentAccount
432
+ ? toParentAccount.freshAddress
433
+ : (toAccount as Account).freshAddress;
434
+
435
+ // Step 1: Open the drawer and open exchange app
436
+ const startExchange = async () => {
437
+ return new Promise<{ transactionId: string; device?: ExchangeStartResult["device"] }>(
438
+ (resolve, reject) => {
439
+ uiExchangeStart({
440
+ exchangeParams: exchangeStartParams,
441
+ onSuccess: (nonce, device) => {
442
+ tracking.startExchangeSuccess(trackingParams);
443
+ resolve({ transactionId: nonce, device });
444
+ },
445
+ onCancel: error => {
446
+ tracking.startExchangeFail(trackingParams);
447
+ reject(error);
448
+ },
449
+ });
450
+ },
451
+ );
452
+ };
530
453
 
531
- // Get amountExpectedTo and magnitudeAwareRate from binary payload
532
- const decodePayload = await decodeSwapPayload(binaryPayload);
533
- const amountExpectedTo = new BigNumber(decodePayload.amountToWallet.toString());
534
- const magnitudeAwareRate = tx.amount && amountExpectedTo.dividedBy(tx.amount);
535
- const refundAddress = decodePayload.refundAddress;
536
- const payoutAddress = decodePayload.payoutAddress;
454
+ let transactionId: string;
455
+ let deviceInfo: ExchangeStartResult["device"];
456
+
457
+ try {
458
+ const result = await startExchange();
459
+ transactionId = result.transactionId;
460
+ deviceInfo = result.device;
461
+ } catch (error) {
462
+ const rawError = get(error, "response.data.error", error);
463
+ const wrappedError = createStepError({
464
+ error: toError(rawError),
465
+ step: StepError.NONCE,
466
+ });
467
+ throw wrappedError;
468
+ }
537
469
 
538
- // tx.amount should be BigNumber
539
- tx.amount = new BigNumber(tx.amount);
470
+ const {
471
+ binaryPayload,
472
+ signature,
473
+ payinAddress,
474
+ swapId,
475
+ payinExtraId,
476
+ extraTransactionParameters,
477
+ } = await retrieveSwapPayload({
478
+ provider,
479
+ deviceTransactionId: transactionId,
480
+ fromAccountAddress,
481
+ toAccountAddress,
482
+ fromAccountCurrency: fromCurrency!.id,
483
+ toAccountCurrency: toCurrency!.id,
484
+ amount: fromAmount,
485
+ amountInAtomicUnit: fromAmountAtomic,
486
+ quoteId,
487
+ toNewTokenId,
488
+ }).catch((error: Error) => {
489
+ const wrappedError = createStepError({
490
+ error: get(error, "response.data.error", error),
491
+ step: StepError.PAYLOAD,
492
+ });
493
+
494
+ throw wrappedError;
495
+ });
540
496
 
541
- return new Promise((resolve, reject) =>
542
- uiSwap({
543
- exchangeParams: {
544
- exchangeType: ExchangeType.SWAP,
545
- provider: params.provider,
546
- transaction: tx,
547
- signature: signature,
548
- binaryPayload: binaryPayload,
549
- exchange: {
550
- fromAccount,
551
- fromParentAccount,
552
- toAccount,
553
- toParentAccount,
554
- fromCurrency: fromCurrency!,
555
- toCurrency: toCurrency!,
556
- },
557
- feesStrategy: params.feeStrategy,
558
- swapId: swapId,
559
- amountExpectedTo: amountExpectedTo.toNumber(),
560
- magnitudeAwareRate,
561
- refundAddress,
562
- payoutAddress,
563
- sponsored,
564
- },
565
- onSuccess: ({ operationHash, swapId }: { operationHash: string; swapId: string }) => {
566
- tracking.completeExchangeSuccess({
567
- ...trackingParams,
568
- currency: transaction.family,
569
- });
497
+ // Complete Swap
498
+ const trackingCompleteParams = {
499
+ provider: params.provider,
500
+ exchangeType: params.exchangeType,
501
+ isEmbeddedSwap: isEmbedded,
502
+ };
503
+ tracking.completeExchangeRequested(trackingCompleteParams);
504
+
505
+ const strategyData = {
506
+ recipient: payinAddress,
507
+ amount: fromAmountAtomic,
508
+ currency: fromCurrency as CryptoOrTokenCurrency,
509
+ customFeeConfig: customFeeConfig ?? {},
510
+ payinExtraId,
511
+ extraTransactionParameters,
512
+ sponsored,
513
+ };
570
514
 
571
- setBroadcastTransaction({
572
- provider,
573
- result: { operation: operationHash, swapId },
574
- sourceCurrencyId: fromCurrency.id,
575
- targetCurrencyId: toCurrency?.id,
576
- hardwareWalletType: deviceInfo?.modelId as DeviceModelId,
577
- swapAppVersion,
578
- fromAccountAddress,
579
- toAccountAddress,
580
- fromAmount,
581
- });
515
+ const transaction: Transaction = await getStrategy(strategyData, "swap");
516
+
517
+ const mainFromAccount = getMainAccount(fromAccount, fromParentAccount);
518
+
519
+ if (transaction.family !== mainFromAccount.currency.family) {
520
+ return Promise.reject(
521
+ new Error(
522
+ `Account and transaction must be from the same family. Account family: ${mainFromAccount.currency.family}, Transaction family: ${transaction.family}`,
523
+ ),
524
+ );
525
+ }
526
+
527
+ const accountBridge = getAccountBridge(fromAccount, fromParentAccount);
528
+
529
+ /**
530
+ * 'subAccountId' is used for ETH and it's ERC-20 tokens.
531
+ * This field is ignored for BTC
532
+ */
533
+ const subAccountId =
534
+ fromParentAccount && fromParentAccount.id !== fromAccount.id ? fromAccount.id : undefined;
582
535
 
583
- resolve({ operationHash, swapId });
536
+ const bridgeTx = accountBridge.createTransaction(fromAccount);
537
+ /**
538
+ * We append the `recipient` to the tx created from `createTransaction`
539
+ * to avoid having userGasLimit reset to null for ETH txs
540
+ * cf. libs/ledger-live-common/src/families/ethereum/updateTransaction.ts
541
+ */
542
+ const tx = accountBridge.updateTransaction(
543
+ {
544
+ ...bridgeTx,
545
+ recipient: transaction.recipient,
584
546
  },
585
- onCancel: error => {
586
- postSwapCancelled({
587
- provider: provider,
547
+ {
548
+ ...transaction,
549
+ feesStrategy: params.feeStrategy.toLowerCase(),
550
+ subAccountId,
551
+ },
552
+ );
553
+
554
+ // Get amountExpectedTo and magnitudeAwareRate from binary payload
555
+ const decodePayload = await decodeSwapPayload(binaryPayload);
556
+ const amountExpectedTo = new BigNumber(decodePayload.amountToWallet.toString());
557
+ const magnitudeAwareRate = tx.amount && amountExpectedTo.dividedBy(tx.amount);
558
+ const refundAddress = decodePayload.refundAddress;
559
+ const payoutAddress = decodePayload.payoutAddress;
560
+
561
+ // tx.amount should be BigNumber
562
+ tx.amount = new BigNumber(tx.amount);
563
+
564
+ return new Promise((resolve, reject) =>
565
+ uiSwap({
566
+ exchangeParams: {
567
+ exchangeType: ExchangeType.SWAP,
568
+ provider: params.provider,
569
+ transaction: tx,
570
+ signature: signature,
571
+ binaryPayload: binaryPayload,
572
+ exchange: {
573
+ fromAccount,
574
+ fromParentAccount,
575
+ toAccount,
576
+ toParentAccount,
577
+ fromCurrency: fromCurrency!,
578
+ toCurrency: toCurrency!,
579
+ },
580
+ feesStrategy: params.feeStrategy,
588
581
  swapId: swapId,
589
- swapStep: getSwapStepFromError(error),
590
- statusCode: error.name,
591
- errorMessage: error.message,
592
- sourceCurrencyId: fromCurrency.id,
593
- targetCurrencyId: toCurrency?.id,
594
- hardwareWalletType: deviceInfo?.modelId as DeviceModelId,
595
- swapType: quoteId ? "fixed" : "float",
596
- swapAppVersion,
597
- fromAccountAddress,
598
- toAccountAddress,
582
+ amountExpectedTo: amountExpectedTo.toNumber(),
583
+ magnitudeAwareRate,
599
584
  refundAddress,
600
585
  payoutAddress,
601
- fromAmount,
602
- seedIdFrom: mainFromAccount.seedIdentifier,
603
- seedIdTo: toParentAccount?.seedIdentifier || (toAccount as Account)?.seedIdentifier,
604
- data: (transaction as EvmTransaction).data
605
- ? `0x${padHexString((transaction as EvmTransaction).data?.toString("hex") || "")}`
606
- : "0x",
586
+ sponsored,
587
+ },
588
+ onSuccess: ({ operationHash, swapId }: { operationHash: string; swapId: string }) => {
589
+ tracking.completeExchangeSuccess({
590
+ ...trackingParams,
591
+ currency: transaction.family,
592
+ });
593
+
594
+ setBroadcastTransaction({
595
+ provider,
596
+ result: { operation: operationHash, swapId },
597
+ sourceCurrencyId: fromCurrency.id,
598
+ targetCurrencyId: toCurrency?.id,
599
+ hardwareWalletType: deviceInfo?.modelId as DeviceModelId,
600
+ swapAppVersion,
601
+ fromAccountAddress,
602
+ toAccountAddress,
603
+ fromAmount,
604
+ });
605
+
606
+ resolve({ operationHash, swapId });
607
+ },
608
+ onCancel: error => {
609
+ postSwapCancelled({
610
+ provider: provider,
611
+ swapId: swapId,
612
+ swapStep: getSwapStepFromError(error),
613
+ statusCode: error.name,
614
+ errorMessage: error.message,
615
+ sourceCurrencyId: fromCurrency.id,
616
+ targetCurrencyId: toCurrency?.id,
617
+ hardwareWalletType: deviceInfo?.modelId as DeviceModelId,
618
+ swapType: quoteId ? "fixed" : "float",
619
+ swapAppVersion,
620
+ fromAccountAddress,
621
+ toAccountAddress,
622
+ refundAddress,
623
+ payoutAddress,
624
+ fromAmount,
625
+ seedIdFrom: mainFromAccount.seedIdentifier,
626
+ seedIdTo: toParentAccount?.seedIdentifier || (toAccount as Account)?.seedIdentifier,
627
+ data: (transaction as EvmTransaction).data
628
+ ? `0x${padHexString((transaction as EvmTransaction).data?.toString("hex") || "")}`
629
+ : "0x",
630
+ });
631
+
632
+ reject(createStepError({ error, step: StepError.SIGNATURE }));
633
+ },
634
+ }),
635
+ );
636
+ } catch (error) {
637
+ // Skip DrawerClosedError
638
+ // do not redirect to the error screen
639
+ if (isDrawerClosedError(error)) {
640
+ throw error;
641
+ }
642
+
643
+ // Global catch for any errors during the swap process
644
+ // moved out as sonarcloud suggested to avoid 4 level nested functions
645
+ const createErrorRejector = (error: SwapError, reject: (error: SwapError) => void) => {
646
+ return () => reject(error);
647
+ };
648
+
649
+ const displayError = (error: SwapError): Promise<void> =>
650
+ new Promise((resolve, reject) => {
651
+ const rejectWithError = createErrorRejector(error, reject);
652
+ uiError({
653
+ error,
654
+ onSuccess: rejectWithError,
655
+ onCancel: rejectWithError,
607
656
  });
657
+ });
608
658
 
609
- reject(error);
610
- },
611
- }),
612
- );
659
+ await handleErrors(error, {
660
+ onDisplayError: displayError,
661
+ });
662
+
663
+ throw error;
664
+ }
613
665
  }),
614
666
 
615
667
  "custom.isReady": customWrapper<void, void>(async () => {
@@ -831,21 +883,21 @@ async function getStrategy(
831
883
  }
832
884
  }
833
885
 
834
- try {
835
- return await strategy({
836
- family,
837
- amount: new BigNumber(amount),
838
- recipient,
839
- customFeeConfig: convertedCustomFeeConfig,
840
- payinExtraId,
841
- extraTransactionParameters,
842
- customErrorType,
843
- sponsored,
844
- });
845
- } catch (error) {
846
- const errorMessage = error instanceof Error ? error.message : String(error);
847
- throw new Error(
848
- `Failed to execute transaction strategy for family: ${family}. Reason: ${errorMessage}`,
849
- );
850
- }
886
+ return strategy({
887
+ family,
888
+ amount: new BigNumber(amount),
889
+ recipient,
890
+ customFeeConfig: convertedCustomFeeConfig,
891
+ payinExtraId,
892
+ extraTransactionParameters,
893
+ customErrorType,
894
+ sponsored,
895
+ });
896
+ }
897
+
898
+ function isDrawerClosedError(error: unknown) {
899
+ if (!error || typeof error !== "object") return false;
900
+ return (
901
+ get(error, "name") === "DrawerClosedError" || get(error, "cause.name") === "DrawerClosedError"
902
+ );
851
903
  }
@@ -14,8 +14,15 @@ type TrackExchange = (
14
14
  interface TrackEventPayload {
15
15
  exchangeType: "SELL" | "FUND" | "SWAP";
16
16
  provider: string;
17
+ isEmbeddedSwap?: boolean;
17
18
  }
18
19
 
20
+ /**
21
+ * Converts isEmbeddedSwap boolean to string for analytics consistency
22
+ */
23
+ const formatIsEmbeddedSwap = (isEmbeddedSwap?: boolean): string | undefined =>
24
+ isEmbeddedSwap !== undefined ? String(isEmbeddedSwap) : undefined;
25
+
19
26
  function getEventData(manifest: AppManifest) {
20
27
  return { walletAPI: manifest.name };
21
28
  }
@@ -29,24 +36,46 @@ function getEventData(manifest: AppManifest) {
29
36
  // in order to get the exact type matching the tracking wrapper API
30
37
  // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
31
38
  export default function trackingWrapper(trackCall: TrackExchange) {
32
- const track = (event: string, properties: Record<string, string> | null) => {
33
- return trackCall(event, properties, null);
39
+ const track = (event: string, properties: Record<string, string | undefined> | null) => {
40
+ // Filter out undefined values before passing to trackCall
41
+ if (!properties) {
42
+ return trackCall(event, null, null);
43
+ }
44
+ const filteredProperties: Record<string, string> = {};
45
+ for (const [key, value] of Object.entries(properties)) {
46
+ if (value !== undefined) {
47
+ filteredProperties[key] = value;
48
+ }
49
+ }
50
+ return trackCall(event, filteredProperties, null);
34
51
  };
35
52
 
36
53
  return {
37
54
  // Generate Exchange nonce modal open
38
- startExchangeRequested: ({ provider, exchangeType }: TrackEventPayload) => {
39
- track(`Starts Exchange ${exchangeType} Nonce request`, { provider, exchangeType });
55
+ startExchangeRequested: ({ provider, exchangeType, isEmbeddedSwap }: TrackEventPayload) => {
56
+ track(`Starts Exchange ${exchangeType} Nonce request`, {
57
+ provider,
58
+ exchangeType,
59
+ isEmbeddedSwap: formatIsEmbeddedSwap(isEmbeddedSwap),
60
+ });
40
61
  },
41
62
 
42
63
  // Successfully generated an Exchange app nonce
43
- startExchangeSuccess: ({ provider, exchangeType }: TrackEventPayload) => {
44
- track(`Starts Exchange ${exchangeType} Nonce success`, { provider, exchangeType });
64
+ startExchangeSuccess: ({ provider, exchangeType, isEmbeddedSwap }: TrackEventPayload) => {
65
+ track(`Starts Exchange ${exchangeType} Nonce success`, {
66
+ provider,
67
+ exchangeType,
68
+ isEmbeddedSwap: formatIsEmbeddedSwap(isEmbeddedSwap),
69
+ });
45
70
  },
46
71
 
47
72
  // Failed to generate an Exchange app nonce
48
- startExchangeFail: ({ provider, exchangeType }: TrackEventPayload) => {
49
- track(`Starts Exchange ${exchangeType} Nonce fail`, { provider, exchangeType });
73
+ startExchangeFail: ({ provider, exchangeType, isEmbeddedSwap }: TrackEventPayload) => {
74
+ track(`Starts Exchange ${exchangeType} Nonce fail`, {
75
+ provider,
76
+ exchangeType,
77
+ isEmbeddedSwap: formatIsEmbeddedSwap(isEmbeddedSwap),
78
+ });
50
79
  },
51
80
 
52
81
  // No Params to generate an Exchange app nonce
@@ -54,8 +83,12 @@ export default function trackingWrapper(trackCall: TrackExchange) {
54
83
  track("Starts Exchange no params", getEventData(manifest));
55
84
  },
56
85
 
57
- completeExchangeRequested: ({ provider, exchangeType }: TrackEventPayload) => {
58
- track(`Completes Exchange ${exchangeType} requested`, { provider, exchangeType });
86
+ completeExchangeRequested: ({ provider, exchangeType, isEmbeddedSwap }: TrackEventPayload) => {
87
+ track(`Completes Exchange ${exchangeType} requested`, {
88
+ provider,
89
+ exchangeType,
90
+ isEmbeddedSwap: formatIsEmbeddedSwap(isEmbeddedSwap),
91
+ });
59
92
  },
60
93
 
61
94
  // Successfully completed an Exchange
@@ -63,13 +96,23 @@ export default function trackingWrapper(trackCall: TrackExchange) {
63
96
  provider,
64
97
  exchangeType,
65
98
  currency,
99
+ isEmbeddedSwap,
66
100
  }: TrackEventPayload & { currency: string }) => {
67
- track(`Completes Exchange ${exchangeType} success`, { provider, exchangeType, currency });
101
+ track(`Completes Exchange ${exchangeType} success`, {
102
+ provider,
103
+ exchangeType,
104
+ currency,
105
+ isEmbeddedSwap: formatIsEmbeddedSwap(isEmbeddedSwap),
106
+ });
68
107
  },
69
108
 
70
109
  // Failed to complete an Exchange
71
- completeExchangeFail: ({ provider, exchangeType }: TrackEventPayload) => {
72
- track(`Completes Exchange ${exchangeType} Nonce fail`, { provider, exchangeType });
110
+ completeExchangeFail: ({ provider, exchangeType, isEmbeddedSwap }: TrackEventPayload) => {
111
+ track(`Completes Exchange ${exchangeType} Nonce fail`, {
112
+ provider,
113
+ exchangeType,
114
+ isEmbeddedSwap: formatIsEmbeddedSwap(isEmbeddedSwap),
115
+ });
73
116
  },
74
117
 
75
118
  // No Params to complete an Exchange