@ledgerhq/live-common 34.53.0-nightly.20251122023607 → 34.53.0-nightly.20251125074637
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.
- package/lib/e2e/index.d.ts +1 -0
- package/lib/e2e/index.d.ts.map +1 -1
- package/lib/env.react.d.ts +1 -1
- package/lib/env.react.d.ts.map +1 -1
- package/lib/wallet-api/Exchange/server.d.ts.map +1 -1
- package/lib/wallet-api/Exchange/server.js +225 -158
- package/lib/wallet-api/Exchange/server.js.map +1 -1
- package/lib-es/e2e/index.d.ts +1 -0
- package/lib-es/e2e/index.d.ts.map +1 -1
- package/lib-es/env.react.d.ts +1 -1
- package/lib-es/env.react.d.ts.map +1 -1
- package/lib-es/wallet-api/Exchange/server.d.ts.map +1 -1
- package/lib-es/wallet-api/Exchange/server.js +225 -158
- package/lib-es/wallet-api/Exchange/server.js.map +1 -1
- package/package.json +52 -52
- package/src/families/algorand/__snapshots__/bridge.integration.test.ts.snap +11 -11
- package/src/wallet-api/Exchange/server.ts +289 -215
|
@@ -374,237 +374,259 @@ export const handlers = ({
|
|
|
374
374
|
);
|
|
375
375
|
}),
|
|
376
376
|
"custom.exchange.swap": customWrapper<ExchangeSwapParams, SwapResult>(async params => {
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
377
|
+
try {
|
|
378
|
+
if (!params) {
|
|
379
|
+
tracking.startExchangeNoParams(manifest);
|
|
380
|
+
throw new ServerError(createUnknownError({ message: "params is undefined" }));
|
|
381
|
+
}
|
|
381
382
|
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
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
|
-
}
|
|
383
|
+
const {
|
|
384
|
+
provider,
|
|
385
|
+
fromAmount,
|
|
386
|
+
fromAmountAtomic,
|
|
387
|
+
quoteId,
|
|
388
|
+
toNewTokenId,
|
|
389
|
+
customFeeConfig,
|
|
390
|
+
swapAppVersion,
|
|
391
|
+
sponsored,
|
|
392
|
+
} = params;
|
|
417
393
|
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
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
|
+
}
|
|
418
|
+
|
|
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
|
+
};
|
|
445
|
+
|
|
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) => {
|
|
488
467
|
throw error;
|
|
489
|
-
}
|
|
490
|
-
);
|
|
468
|
+
});
|
|
491
469
|
|
|
492
|
-
|
|
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
|
+
};
|
|
493
486
|
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
),
|
|
487
|
+
const transaction: Transaction = await getStrategy(strategyData, "swap").catch(
|
|
488
|
+
async error => {
|
|
489
|
+
throw error;
|
|
490
|
+
},
|
|
499
491
|
);
|
|
500
|
-
}
|
|
501
492
|
|
|
502
|
-
|
|
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
|
-
);
|
|
493
|
+
const mainFromAccount = getMainAccount(fromAccount, fromParentAccount);
|
|
528
494
|
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
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
|
+
}
|
|
535
502
|
|
|
536
|
-
|
|
537
|
-
tx.amount = new BigNumber(tx.amount);
|
|
503
|
+
const accountBridge = getAccountBridge(fromAccount, fromParentAccount);
|
|
538
504
|
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
swapId: swapId,
|
|
557
|
-
amountExpectedTo: amountExpectedTo.toNumber(),
|
|
558
|
-
magnitudeAwareRate,
|
|
559
|
-
refundAddress,
|
|
560
|
-
payoutAddress,
|
|
561
|
-
sponsored,
|
|
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;
|
|
511
|
+
|
|
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,
|
|
562
522
|
},
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
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 });
|
|
523
|
+
{
|
|
524
|
+
...transaction,
|
|
525
|
+
feesStrategy: params.feeStrategy.toLowerCase(),
|
|
526
|
+
subAccountId,
|
|
582
527
|
},
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
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,
|
|
586
557
|
swapId: swapId,
|
|
587
|
-
|
|
588
|
-
|
|
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,
|
|
558
|
+
amountExpectedTo: amountExpectedTo.toNumber(),
|
|
559
|
+
magnitudeAwareRate,
|
|
597
560
|
refundAddress,
|
|
598
561
|
payoutAddress,
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
562
|
+
sponsored,
|
|
563
|
+
},
|
|
564
|
+
onSuccess: ({ operationHash, swapId }: { operationHash: string; swapId: string }) => {
|
|
565
|
+
tracking.completeExchangeSuccess({
|
|
566
|
+
...trackingParams,
|
|
567
|
+
currency: transaction.family,
|
|
568
|
+
});
|
|
603
569
|
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
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
|
+
}
|
|
608
630
|
}),
|
|
609
631
|
|
|
610
632
|
"custom.isReady": customWrapper<void, void>(async () => {
|
|
@@ -844,3 +866,55 @@ async function getStrategy(
|
|
|
844
866
|
);
|
|
845
867
|
}
|
|
846
868
|
}
|
|
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
|
+
}
|