@ledgerhq/live-common 34.53.0-nightly.20251125074637 → 34.54.0-nightly.20251126023856

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 (57) hide show
  1. package/lib/e2e/data/assetsDrawer.d.ts +9 -0
  2. package/lib/e2e/data/assetsDrawer.d.ts.map +1 -0
  3. package/lib/e2e/data/assetsDrawer.js +12 -0
  4. package/lib/e2e/data/assetsDrawer.js.map +1 -0
  5. package/lib/e2e/index.d.ts +3 -0
  6. package/lib/e2e/index.d.ts.map +1 -1
  7. package/lib/families/solana/bridge/mock.d.ts.map +1 -1
  8. package/lib/families/solana/bridge/mock.js +10 -8
  9. package/lib/families/solana/bridge/mock.js.map +1 -1
  10. package/lib/families/solana/config.d.ts.map +1 -1
  11. package/lib/families/solana/config.js +0 -1
  12. package/lib/families/solana/config.js.map +1 -1
  13. package/lib/featureFlags/defaultFeatures.d.ts.map +1 -1
  14. package/lib/featureFlags/defaultFeatures.js +3 -0
  15. package/lib/featureFlags/defaultFeatures.js.map +1 -1
  16. package/lib/featureFlags/stakePrograms/index.d.ts +1 -0
  17. package/lib/featureFlags/stakePrograms/index.d.ts.map +1 -1
  18. package/lib/featureFlags/stakePrograms/index.js +16 -1
  19. package/lib/featureFlags/stakePrograms/index.js.map +1 -1
  20. package/lib/featureFlags/useFeature.d.ts +1 -1
  21. package/lib/featureFlags/useFeature.d.ts.map +1 -1
  22. package/lib/wallet-api/Exchange/server.d.ts.map +1 -1
  23. package/lib/wallet-api/Exchange/server.js +158 -225
  24. package/lib/wallet-api/Exchange/server.js.map +1 -1
  25. package/lib-es/e2e/data/assetsDrawer.d.ts +9 -0
  26. package/lib-es/e2e/data/assetsDrawer.d.ts.map +1 -0
  27. package/lib-es/e2e/data/assetsDrawer.js +9 -0
  28. package/lib-es/e2e/data/assetsDrawer.js.map +1 -0
  29. package/lib-es/e2e/index.d.ts +3 -0
  30. package/lib-es/e2e/index.d.ts.map +1 -1
  31. package/lib-es/families/solana/bridge/mock.d.ts.map +1 -1
  32. package/lib-es/families/solana/bridge/mock.js +11 -9
  33. package/lib-es/families/solana/bridge/mock.js.map +1 -1
  34. package/lib-es/families/solana/config.d.ts.map +1 -1
  35. package/lib-es/families/solana/config.js +0 -1
  36. package/lib-es/families/solana/config.js.map +1 -1
  37. package/lib-es/featureFlags/defaultFeatures.d.ts.map +1 -1
  38. package/lib-es/featureFlags/defaultFeatures.js +3 -0
  39. package/lib-es/featureFlags/defaultFeatures.js.map +1 -1
  40. package/lib-es/featureFlags/stakePrograms/index.d.ts +1 -0
  41. package/lib-es/featureFlags/stakePrograms/index.d.ts.map +1 -1
  42. package/lib-es/featureFlags/stakePrograms/index.js +14 -0
  43. package/lib-es/featureFlags/stakePrograms/index.js.map +1 -1
  44. package/lib-es/featureFlags/useFeature.d.ts +1 -1
  45. package/lib-es/featureFlags/useFeature.d.ts.map +1 -1
  46. package/lib-es/wallet-api/Exchange/server.d.ts.map +1 -1
  47. package/lib-es/wallet-api/Exchange/server.js +158 -225
  48. package/lib-es/wallet-api/Exchange/server.js.map +1 -1
  49. package/package.json +52 -52
  50. package/src/__tests__/currencies.ts +5 -0
  51. package/src/e2e/data/assetsDrawer.ts +8 -0
  52. package/src/families/solana/__snapshots__/bridge.integration.test.ts.snap +44 -14
  53. package/src/families/solana/bridge/mock.ts +13 -23
  54. package/src/families/solana/config.ts +0 -1
  55. package/src/featureFlags/defaultFeatures.ts +4 -0
  56. package/src/featureFlags/stakePrograms/index.ts +18 -0
  57. package/src/wallet-api/Exchange/server.ts +215 -289
@@ -374,259 +374,237 @@ export const handlers = ({
374
374
  );
375
375
  }),
376
376
  "custom.exchange.swap": customWrapper<ExchangeSwapParams, SwapResult>(async params => {
377
- try {
378
- if (!params) {
379
- tracking.startExchangeNoParams(manifest);
380
- throw new ServerError(createUnknownError({ message: "params is undefined" }));
381
- }
382
-
383
- const {
384
- provider,
385
- fromAmount,
386
- fromAmountAtomic,
387
- quoteId,
388
- toNewTokenId,
389
- customFeeConfig,
390
- swapAppVersion,
391
- sponsored,
392
- } = params;
393
-
394
- const trackingParams = {
395
- provider: params.provider,
396
- exchangeType: params.exchangeType,
397
- };
398
-
399
- tracking.startExchangeRequested(trackingParams);
400
-
401
- const exchangeStartParams: ExchangeStartParamsUiRequest = (await extractSwapStartParam(
402
- params,
403
- accounts,
404
- )) as SwapStartParamsUiRequest;
405
-
406
- const {
407
- fromCurrency,
408
- fromAccount,
409
- fromParentAccount,
410
- toCurrency,
411
- toAccount,
412
- toParentAccount,
413
- } = exchangeStartParams.exchange;
414
-
415
- if (!fromAccount || !fromCurrency) {
416
- throw new ServerError(createAccountNotFound(params.fromAccountId));
417
- }
377
+ if (!params) {
378
+ tracking.startExchangeNoParams(manifest);
379
+ throw new ServerError(createUnknownError({ message: "params is undefined" }));
380
+ }
418
381
 
419
- const fromAccountAddress = fromParentAccount
420
- ? fromParentAccount.freshAddress
421
- : (fromAccount as Account).freshAddress;
422
-
423
- const toAccountAddress = toParentAccount
424
- ? toParentAccount.freshAddress
425
- : (toAccount as Account).freshAddress;
426
-
427
- // Step 1: Open the drawer and open exchange app
428
- const startExchange = async () => {
429
- return new Promise<{ transactionId: string; device?: ExchangeStartResult["device"] }>(
430
- (resolve, reject) => {
431
- uiExchangeStart({
432
- exchangeParams: exchangeStartParams,
433
- onSuccess: (nonce, device) => {
434
- tracking.startExchangeSuccess(trackingParams);
435
- resolve({ transactionId: nonce, device });
436
- },
437
- onCancel: error => {
438
- tracking.startExchangeFail(trackingParams);
439
- reject(error);
440
- },
441
- });
442
- },
443
- );
444
- };
382
+ const {
383
+ provider,
384
+ fromAmount,
385
+ fromAmountAtomic,
386
+ quoteId,
387
+ toNewTokenId,
388
+ customFeeConfig,
389
+ swapAppVersion,
390
+ sponsored,
391
+ } = params;
392
+
393
+ const trackingParams = {
394
+ provider: params.provider,
395
+ exchangeType: params.exchangeType,
396
+ };
397
+
398
+ tracking.startExchangeRequested(trackingParams);
399
+
400
+ const exchangeStartParams: ExchangeStartParamsUiRequest = (await extractSwapStartParam(
401
+ params,
402
+ accounts,
403
+ )) as SwapStartParamsUiRequest;
404
+
405
+ const {
406
+ fromCurrency,
407
+ fromAccount,
408
+ fromParentAccount,
409
+ toCurrency,
410
+ toAccount,
411
+ toParentAccount,
412
+ } = exchangeStartParams.exchange;
413
+
414
+ if (!fromAccount || !fromCurrency) {
415
+ throw new ServerError(createAccountNotFound(params.fromAccountId));
416
+ }
445
417
 
446
- const { transactionId, device: deviceInfo } = await startExchange();
447
-
448
- const {
449
- binaryPayload,
450
- signature,
451
- payinAddress,
452
- swapId,
453
- payinExtraId,
454
- extraTransactionParameters,
455
- } = await retrieveSwapPayload({
456
- provider,
457
- deviceTransactionId: transactionId,
458
- fromAccountAddress,
459
- toAccountAddress,
460
- fromAccountCurrency: fromCurrency!.id,
461
- toAccountCurrency: toCurrency!.id,
462
- amount: fromAmount,
463
- amountInAtomicUnit: fromAmountAtomic,
464
- quoteId,
465
- toNewTokenId,
466
- }).catch((error: Error) => {
418
+ const fromAccountAddress = fromParentAccount
419
+ ? fromParentAccount.freshAddress
420
+ : (fromAccount as Account).freshAddress;
421
+
422
+ const toAccountAddress = toParentAccount
423
+ ? toParentAccount.freshAddress
424
+ : (toAccount as Account).freshAddress;
425
+
426
+ // Step 1: Open the drawer and open exchange app
427
+ const startExchange = async () => {
428
+ return new Promise<{ transactionId: string; device?: ExchangeStartResult["device"] }>(
429
+ (resolve, reject) => {
430
+ uiExchangeStart({
431
+ exchangeParams: exchangeStartParams,
432
+ onSuccess: (nonce, device) => {
433
+ tracking.startExchangeSuccess(trackingParams);
434
+ resolve({ transactionId: nonce, device });
435
+ },
436
+ onCancel: error => {
437
+ tracking.startExchangeFail(trackingParams);
438
+ reject(error);
439
+ },
440
+ });
441
+ },
442
+ );
443
+ };
444
+
445
+ const { transactionId, device: deviceInfo } = await startExchange();
446
+
447
+ const {
448
+ binaryPayload,
449
+ signature,
450
+ payinAddress,
451
+ swapId,
452
+ payinExtraId,
453
+ extraTransactionParameters,
454
+ } = await retrieveSwapPayload({
455
+ provider,
456
+ deviceTransactionId: transactionId,
457
+ fromAccountAddress,
458
+ toAccountAddress,
459
+ fromAccountCurrency: fromCurrency!.id,
460
+ toAccountCurrency: toCurrency!.id,
461
+ amount: fromAmount,
462
+ amountInAtomicUnit: fromAmountAtomic,
463
+ quoteId,
464
+ toNewTokenId,
465
+ }).catch((error: Error) => {
466
+ throw error;
467
+ });
468
+
469
+ // Complete Swap
470
+ const trackingCompleteParams = {
471
+ provider: params.provider,
472
+ exchangeType: params.exchangeType,
473
+ };
474
+ tracking.completeExchangeRequested(trackingCompleteParams);
475
+
476
+ const strategyData = {
477
+ recipient: payinAddress,
478
+ amount: fromAmountAtomic,
479
+ currency: fromCurrency as CryptoOrTokenCurrency,
480
+ customFeeConfig: customFeeConfig ?? {},
481
+ payinExtraId,
482
+ extraTransactionParameters,
483
+ sponsored,
484
+ };
485
+
486
+ const transaction: Transaction = await getStrategy(strategyData, "swap").catch(
487
+ async error => {
467
488
  throw error;
468
- });
489
+ },
490
+ );
469
491
 
470
- // Complete Swap
471
- const trackingCompleteParams = {
472
- provider: params.provider,
473
- exchangeType: params.exchangeType,
474
- };
475
- tracking.completeExchangeRequested(trackingCompleteParams);
476
-
477
- const strategyData = {
478
- recipient: payinAddress,
479
- amount: fromAmountAtomic,
480
- currency: fromCurrency as CryptoOrTokenCurrency,
481
- customFeeConfig: customFeeConfig ?? {},
482
- payinExtraId,
483
- extraTransactionParameters,
484
- sponsored,
485
- };
492
+ const mainFromAccount = getMainAccount(fromAccount, fromParentAccount);
486
493
 
487
- const transaction: Transaction = await getStrategy(strategyData, "swap").catch(
488
- async error => {
489
- throw error;
490
- },
494
+ if (transaction.family !== mainFromAccount.currency.family) {
495
+ return Promise.reject(
496
+ new Error(
497
+ `Account and transaction must be from the same family. Account family: ${mainFromAccount.currency.family}, Transaction family: ${transaction.family}`,
498
+ ),
491
499
  );
500
+ }
492
501
 
493
- const mainFromAccount = getMainAccount(fromAccount, fromParentAccount);
494
-
495
- if (transaction.family !== mainFromAccount.currency.family) {
496
- return Promise.reject(
497
- new Error(
498
- `Account and transaction must be from the same family. Account family: ${mainFromAccount.currency.family}, Transaction family: ${transaction.family}`,
499
- ),
500
- );
501
- }
502
+ const accountBridge = getAccountBridge(fromAccount, fromParentAccount);
503
+
504
+ /**
505
+ * 'subAccountId' is used for ETH and it's ERC-20 tokens.
506
+ * This field is ignored for BTC
507
+ */
508
+ const subAccountId =
509
+ fromParentAccount && fromParentAccount.id !== fromAccount.id ? fromAccount.id : undefined;
510
+
511
+ const bridgeTx = accountBridge.createTransaction(fromAccount);
512
+ /**
513
+ * We append the `recipient` to the tx created from `createTransaction`
514
+ * to avoid having userGasLimit reset to null for ETH txs
515
+ * cf. libs/ledger-live-common/src/families/ethereum/updateTransaction.ts
516
+ */
517
+ const tx = accountBridge.updateTransaction(
518
+ {
519
+ ...bridgeTx,
520
+ recipient: transaction.recipient,
521
+ },
522
+ {
523
+ ...transaction,
524
+ feesStrategy: params.feeStrategy.toLowerCase(),
525
+ subAccountId,
526
+ },
527
+ );
502
528
 
503
- const accountBridge = getAccountBridge(fromAccount, fromParentAccount);
529
+ // Get amountExpectedTo and magnitudeAwareRate from binary payload
530
+ const decodePayload = await decodeSwapPayload(binaryPayload);
531
+ const amountExpectedTo = new BigNumber(decodePayload.amountToWallet.toString());
532
+ const magnitudeAwareRate = tx.amount && amountExpectedTo.dividedBy(tx.amount);
533
+ const refundAddress = decodePayload.refundAddress;
534
+ const payoutAddress = decodePayload.payoutAddress;
504
535
 
505
- /**
506
- * 'subAccountId' is used for ETH and it's ERC-20 tokens.
507
- * This field is ignored for BTC
508
- */
509
- const subAccountId =
510
- fromParentAccount && fromParentAccount.id !== fromAccount.id ? fromAccount.id : undefined;
536
+ // tx.amount should be BigNumber
537
+ tx.amount = new BigNumber(tx.amount);
511
538
 
512
- const bridgeTx = accountBridge.createTransaction(fromAccount);
513
- /**
514
- * We append the `recipient` to the tx created from `createTransaction`
515
- * to avoid having userGasLimit reset to null for ETH txs
516
- * cf. libs/ledger-live-common/src/families/ethereum/updateTransaction.ts
517
- */
518
- const tx = accountBridge.updateTransaction(
519
- {
520
- ...bridgeTx,
521
- recipient: transaction.recipient,
539
+ return new Promise((resolve, reject) =>
540
+ uiSwap({
541
+ exchangeParams: {
542
+ exchangeType: ExchangeType.SWAP,
543
+ provider: params.provider,
544
+ transaction: tx,
545
+ signature: signature,
546
+ binaryPayload: binaryPayload,
547
+ exchange: {
548
+ fromAccount,
549
+ fromParentAccount,
550
+ toAccount,
551
+ toParentAccount,
552
+ fromCurrency: fromCurrency!,
553
+ toCurrency: toCurrency!,
554
+ },
555
+ feesStrategy: params.feeStrategy,
556
+ swapId: swapId,
557
+ amountExpectedTo: amountExpectedTo.toNumber(),
558
+ magnitudeAwareRate,
559
+ refundAddress,
560
+ payoutAddress,
561
+ sponsored,
522
562
  },
523
- {
524
- ...transaction,
525
- feesStrategy: params.feeStrategy.toLowerCase(),
526
- subAccountId,
563
+ onSuccess: ({ operationHash, swapId }: { operationHash: string; swapId: string }) => {
564
+ tracking.completeExchangeSuccess({
565
+ ...trackingParams,
566
+ currency: transaction.family,
567
+ });
568
+
569
+ setBroadcastTransaction({
570
+ provider,
571
+ result: { operation: operationHash, swapId },
572
+ sourceCurrencyId: fromCurrency.id,
573
+ targetCurrencyId: toCurrency?.id,
574
+ hardwareWalletType: deviceInfo?.modelId as DeviceModelId,
575
+ swapAppVersion,
576
+ fromAccountAddress,
577
+ toAccountAddress,
578
+ fromAmount,
579
+ });
580
+
581
+ resolve({ operationHash, swapId });
527
582
  },
528
- );
529
-
530
- // Get amountExpectedTo and magnitudeAwareRate from binary payload
531
- const decodePayload = await decodeSwapPayload(binaryPayload);
532
- const amountExpectedTo = new BigNumber(decodePayload.amountToWallet.toString());
533
- const magnitudeAwareRate = tx.amount && amountExpectedTo.dividedBy(tx.amount);
534
- const refundAddress = decodePayload.refundAddress;
535
- const payoutAddress = decodePayload.payoutAddress;
536
-
537
- // tx.amount should be BigNumber
538
- tx.amount = new BigNumber(tx.amount);
539
-
540
- return new Promise((resolve, reject) =>
541
- uiSwap({
542
- exchangeParams: {
543
- exchangeType: ExchangeType.SWAP,
544
- provider: params.provider,
545
- transaction: tx,
546
- signature: signature,
547
- binaryPayload: binaryPayload,
548
- exchange: {
549
- fromAccount,
550
- fromParentAccount,
551
- toAccount,
552
- toParentAccount,
553
- fromCurrency: fromCurrency!,
554
- toCurrency: toCurrency!,
555
- },
556
- feesStrategy: params.feeStrategy,
583
+ onCancel: error => {
584
+ postSwapCancelled({
585
+ provider: provider,
557
586
  swapId: swapId,
558
- amountExpectedTo: amountExpectedTo.toNumber(),
559
- magnitudeAwareRate,
587
+ swapStep: getSwapStepFromError(error),
588
+ statusCode: error.name,
589
+ errorMessage: error.message,
590
+ sourceCurrencyId: fromCurrency.id,
591
+ targetCurrencyId: toCurrency?.id,
592
+ hardwareWalletType: deviceInfo?.modelId as DeviceModelId,
593
+ swapType: quoteId ? "fixed" : "float",
594
+ swapAppVersion,
595
+ fromAccountAddress,
596
+ toAccountAddress,
560
597
  refundAddress,
561
598
  payoutAddress,
562
- sponsored,
563
- },
564
- onSuccess: ({ operationHash, swapId }: { operationHash: string; swapId: string }) => {
565
- tracking.completeExchangeSuccess({
566
- ...trackingParams,
567
- currency: transaction.family,
568
- });
599
+ fromAmount,
600
+ seedIdFrom: mainFromAccount.seedIdentifier,
601
+ seedIdTo: toParentAccount?.seedIdentifier || (toAccount as Account)?.seedIdentifier,
602
+ });
569
603
 
570
- setBroadcastTransaction({
571
- provider,
572
- result: { operation: operationHash, swapId },
573
- sourceCurrencyId: fromCurrency.id,
574
- targetCurrencyId: toCurrency?.id,
575
- hardwareWalletType: deviceInfo?.modelId as DeviceModelId,
576
- swapAppVersion,
577
- fromAccountAddress,
578
- toAccountAddress,
579
- fromAmount,
580
- });
581
-
582
- resolve({ operationHash, swapId });
583
- },
584
- onCancel: error => {
585
- postSwapCancelled({
586
- provider: provider,
587
- swapId: swapId,
588
- swapStep: getSwapStepFromError(error),
589
- statusCode: error.name,
590
- errorMessage: error.message,
591
- sourceCurrencyId: fromCurrency.id,
592
- targetCurrencyId: toCurrency?.id,
593
- hardwareWalletType: deviceInfo?.modelId as DeviceModelId,
594
- swapType: quoteId ? "fixed" : "float",
595
- swapAppVersion,
596
- fromAccountAddress,
597
- toAccountAddress,
598
- refundAddress,
599
- payoutAddress,
600
- fromAmount,
601
- seedIdFrom: mainFromAccount.seedIdentifier,
602
- seedIdTo: toParentAccount?.seedIdentifier || (toAccount as Account)?.seedIdentifier,
603
- });
604
-
605
- reject(error);
606
- },
607
- }),
608
- );
609
- } catch (error) {
610
- // Skip DrawerClosedError
611
- if (isDrawerClosedError(error)) {
612
- throw error;
613
- }
614
- // Global catch for any errors during the swap process
615
- // Convert the error to SwapLiveError format for WebviewErrorDrawer
616
- const swapError = getSwapError(error);
617
-
618
- return new Promise((resolve, reject) => {
619
- uiError({
620
- error: swapError,
621
- onSuccess: () => {
622
- reject(error);
623
- },
624
- onCancel: () => {
625
- reject(error);
626
- },
627
- });
628
- });
629
- }
604
+ reject(error);
605
+ },
606
+ }),
607
+ );
630
608
  }),
631
609
 
632
610
  "custom.isReady": customWrapper<void, void>(async () => {
@@ -866,55 +844,3 @@ async function getStrategy(
866
844
  );
867
845
  }
868
846
  }
869
-
870
- function isDrawerClosedError(error: unknown) {
871
- return (
872
- error && typeof error === "object" && "name" in error && error.name === "DrawerClosedError"
873
- );
874
- }
875
-
876
- function getSwapError(error: unknown) {
877
- let swapError: SwapLiveError;
878
- if (typeof error === "object" && error !== null) {
879
- const err = error as any;
880
-
881
- // Handle Axios error format: error.response.data.error
882
- const apiError = err.response?.data?.error || err.error;
883
- const errorMessage =
884
- apiError?.message || err.message || (err instanceof Error ? err.message : String(error));
885
- const messageKey = apiError?.messageKey || err.messageKey;
886
-
887
- // Create a plain object with only serializable properties to avoid analytics issues
888
- // Preserve the original error structure for WebviewErrorDrawer to parse
889
- // It checks error.cause.response.data.error.messageKey (line 96)
890
- swapError = Object.assign(Object.create(null), {
891
- message: err.message,
892
- name: err.name,
893
- code: err.code,
894
- status: err.status,
895
- swap: err.swap,
896
- cause: {
897
- message: errorMessage,
898
- swapCode: apiError?.code,
899
- response: {
900
- data: {
901
- error: {
902
- messageKey: messageKey,
903
- message: errorMessage,
904
- },
905
- },
906
- },
907
- },
908
- }) as SwapLiveError;
909
-
910
- // Add hasOwnProperty method to the plain object
911
- Object.setPrototypeOf(swapError, Object.prototype);
912
- } else {
913
- swapError = {
914
- cause: {
915
- message: error instanceof Error ? error.message : String(error),
916
- },
917
- };
918
- }
919
- return swapError;
920
- }