@metamask/ramps-controller 22.0.0 → 23.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,6 @@
1
1
  import { BaseController } from '@metamask/base-controller';
2
2
  import { BrokenCircuitError } from '@metamask/controller-utils';
3
+ import { BigNumber } from 'bignumber.js';
3
4
  import { applyAutorampRemoteStatus, createAutorampAccount, markAutorampNotified, } from './autorampAccount.js';
4
5
  import { getHeadlessProviderAllowlist, isHeadlessAllProvidersEnabled, normalizeHeadlessProviderId, } from './featureFlags.js';
5
6
  import { areOrdersEqual, deleteOrderInUserStorage, syncOrdersWithUserStorage as syncOrdersWithUserStorageInternal, updateOrderInUserStorage, } from './order-syncing/index.js';
@@ -409,6 +410,7 @@ const MESSENGER_EXPOSED_METHODS = [
409
410
  'getPaymentMethodsForContext',
410
411
  'setSelectedPaymentMethod',
411
412
  'getQuotes',
413
+ 'getQuoteWithFees',
412
414
  'addOrder',
413
415
  'removeOrder',
414
416
  'addAutoramp',
@@ -474,6 +476,27 @@ function contextStillMatches(state, context) {
474
476
  context.assetId &&
475
477
  (state.providers.selected?.id.trim() ?? '') === context.providerId);
476
478
  }
479
+ /**
480
+ * Provider codes that identify Transak's native (non-aggregator) integration,
481
+ * in the bare form produced by {@link normalizeHeadlessProviderId}.
482
+ */
483
+ const NATIVE_TRANSAK_PROVIDER_CODES = [
484
+ 'transak-native',
485
+ 'transak-native-staging',
486
+ ];
487
+ /**
488
+ * Coerces a quote fee value to a non-negative BigNumber, treating a missing or
489
+ * invalid value as zero.
490
+ *
491
+ * @param value - Raw fee value from a quote.
492
+ * @returns The fee as a non-negative BigNumber.
493
+ */
494
+ function getSafeRampsFee(value) {
495
+ const fee = new BigNumber(value ?? 0);
496
+ return fee.isFinite() && fee.isGreaterThanOrEqualTo(0)
497
+ ? fee
498
+ : new BigNumber(0);
499
+ }
477
500
  export class RampsController extends BaseController {
478
501
  /**
479
502
  * Default TTL for cached requests.
@@ -1666,6 +1689,120 @@ export class RampsController extends BaseController {
1666
1689
  ],
1667
1690
  };
1668
1691
  }
1692
+ /**
1693
+ * Fetches the best on-ramp quote for a request and, when the resolved
1694
+ * provider is Transak Native, reconciles its fees to match what Transak
1695
+ * Native actually charges.
1696
+ *
1697
+ * The aggregator `/quotes` estimate of Transak's fee does not match the
1698
+ * native integration. When the resolved provider is Transak Native this
1699
+ * fetches the native buy quote (an unauthenticated, API-key-only lookup, so
1700
+ * it is safe at estimate time) and rewrites the returned quote's fee fields
1701
+ * to its `totalFee`, keeping the aggregator's `networkFee` on the network
1702
+ * line and placing the remainder in the provider fee so the breakdown
1703
+ * survives and `providerFee + networkFee` still equals the native total. A
1704
+ * non-native provider, a failed lookup, or an unusable native fee returns the
1705
+ * aggregator quote unchanged.
1706
+ *
1707
+ * Consumers (e.g. `TransactionPayController`) call this instead of owning the
1708
+ * provider check, asset-id parsing, and second native quote themselves.
1709
+ *
1710
+ * @param options - Quote options; see {@link getQuotes}, plus the fee mode.
1711
+ * @param options.amount - Fiat amount for the quote.
1712
+ * @param options.assetId - CAIP-19 asset id being bought.
1713
+ * @param options.fiat - Optional fiat currency; defaults like {@link getQuotes}.
1714
+ * @param options.paymentMethods - Optional payment method ids.
1715
+ * @param options.walletAddress - Wallet address receiving the on-ramped asset.
1716
+ * @param options.isFeeExcludedFromFiat - Whether Transak adds its fee on top
1717
+ * of the fiat amount (`true`, fee-on-top) or carves it out (`false`). Must
1718
+ * mirror the eventual checkout mode so the estimate equals the charge.
1719
+ * Defaults to `true`.
1720
+ * @param options.providers - See {@link getQuotes}.
1721
+ * @param options.autoSelectProvider - See {@link getQuotes}.
1722
+ * @param options.restrictToKnownOrNativeProviders - See {@link getQuotes}.
1723
+ * @param options.preferredProviderIds - See {@link getQuotes}.
1724
+ * @param options.region - See {@link getQuotes}.
1725
+ * @param options.redirectUrl - See {@link getQuotes}.
1726
+ * @param options.action - See {@link getQuotes}.
1727
+ * @param options.forceRefresh - See {@link getQuotes}.
1728
+ * @param options.ttl - See {@link getQuotes}.
1729
+ * @returns The best quote with native-reconciled fees, or `undefined` when
1730
+ * no quote is available.
1731
+ */
1732
+ async getQuoteWithFees(options) {
1733
+ const { isFeeExcludedFromFiat = true, ...quoteOptions } = options;
1734
+ const response = await this.getQuotes(quoteOptions);
1735
+ const quote = response.success?.[0];
1736
+ if (!quote) {
1737
+ return undefined;
1738
+ }
1739
+ return this.#reconcileNativeTransakFee(quote, {
1740
+ amount: options.amount,
1741
+ assetId: options.assetId,
1742
+ fiat: options.fiat,
1743
+ // Use the resolved quote's own payment method, not the request list: the
1744
+ // aggregator may price a method other than `paymentMethods[0]` (or the
1745
+ // caller may omit the list), and the native lookup must match the quote
1746
+ // being reconciled.
1747
+ paymentMethod: quote.quote.paymentMethod,
1748
+ isFeeExcludedFromFiat,
1749
+ });
1750
+ }
1751
+ /**
1752
+ * Rewrites a quote's fees to Transak Native's own total when the resolved
1753
+ * provider is Transak Native, so an estimate matches the native charge.
1754
+ * Returns the quote unchanged for a non-native provider, a failed native
1755
+ * lookup, or an unusable native fee.
1756
+ *
1757
+ * @param quote - The resolved aggregator quote.
1758
+ * @param context - Native lookup inputs.
1759
+ * @param context.amount - Fiat amount for the native quote.
1760
+ * @param context.assetId - CAIP-19 asset id being bought.
1761
+ * @param context.fiat - Fiat currency for the native quote.
1762
+ * @param context.paymentMethod - Payment method id for the native quote.
1763
+ * @param context.isFeeExcludedFromFiat - Fee mode for the native quote.
1764
+ * @returns The quote with reconciled fees, or the original quote.
1765
+ */
1766
+ async #reconcileNativeTransakFee(quote, { amount, assetId, fiat, paymentMethod, isFeeExcludedFromFiat, }) {
1767
+ // `normalizeHeadlessProviderId` strips the `/providers/` prefix and
1768
+ // lowercases, so `/providers/transak-native` and `transak-native` both match
1769
+ // the native codes below (and the aggregator `transak` does not).
1770
+ const providerCode = normalizeHeadlessProviderId(quote.provider);
1771
+ if (!NATIVE_TRANSAK_PROVIDER_CODES.includes(providerCode)) {
1772
+ return quote;
1773
+ }
1774
+ const fiatCurrency = fiat ?? this.state.userRegion?.country?.currency;
1775
+ if (!fiatCurrency || !paymentMethod) {
1776
+ return quote;
1777
+ }
1778
+ try {
1779
+ const network = assetId.split('/')[0];
1780
+ const nativeQuote = await this.messenger.call('TransakService:getBuyQuote', fiatCurrency, assetId, network, paymentMethod, String(amount), isFeeExcludedFromFiat);
1781
+ const nativeTotalFee = new BigNumber(nativeQuote.totalFee ?? NaN);
1782
+ if (!nativeTotalFee.isFinite() || nativeTotalFee.isLessThan(0)) {
1783
+ return quote;
1784
+ }
1785
+ // Transak Native returns a single total fee, so keep the aggregator's
1786
+ // network fee on the network line (clamped to the native total) and put
1787
+ // the remainder in the provider fee. The breakdown survives and
1788
+ // `providerFee + networkFee` still equals the native total.
1789
+ const aggregatorNetworkFee = getSafeRampsFee(quote.quote.networkFee);
1790
+ const networkFee = BigNumber.min(aggregatorNetworkFee, nativeTotalFee);
1791
+ const providerFee = nativeTotalFee.minus(networkFee);
1792
+ return {
1793
+ ...quote,
1794
+ quote: {
1795
+ ...quote.quote,
1796
+ providerFee: providerFee.toString(10),
1797
+ networkFee: networkFee.toString(10),
1798
+ totalFees: nativeTotalFee.toString(10),
1799
+ },
1800
+ };
1801
+ }
1802
+ catch {
1803
+ return quote;
1804
+ }
1805
+ }
1669
1806
  /**
1670
1807
  * Selects the best quote from a widened multi-provider response.
1671
1808
  *
@@ -2825,16 +2962,18 @@ export class RampsController extends BaseController {
2825
2962
  * @param network - The blockchain network identifier.
2826
2963
  * @param paymentMethod - The payment method identifier.
2827
2964
  * @param fiatAmount - The fiat amount as a string.
2965
+ * @param isFeeExcludedFromFiat - Whether fees are added to the fiat amount.
2966
+ * Defaults to true to preserve Unified Buy's native Transak behavior.
2828
2967
  * @returns The buy quote with pricing and fee details.
2829
2968
  */
2830
- async transakGetBuyQuote(fiatCurrency, cryptoCurrency, network, paymentMethod, fiatAmount) {
2969
+ async transakGetBuyQuote(fiatCurrency, cryptoCurrency, network, paymentMethod, fiatAmount, isFeeExcludedFromFiat = true) {
2831
2970
  this.update((state) => {
2832
2971
  state.nativeProviders.transak.buyQuote.isLoading = true;
2833
2972
  state.nativeProviders.transak.buyQuote.error = null;
2834
2973
  delete state.nativeProviders.transak.buyQuote.errorKey;
2835
2974
  });
2836
2975
  try {
2837
- const quote = await this.messenger.call('TransakService:getBuyQuote', fiatCurrency, cryptoCurrency, network, paymentMethod, fiatAmount);
2976
+ const quote = await this.messenger.call('TransakService:getBuyQuote', fiatCurrency, cryptoCurrency, network, paymentMethod, fiatAmount, isFeeExcludedFromFiat);
2838
2977
  this.update((state) => {
2839
2978
  state.nativeProviders.transak.buyQuote.data = quote;
2840
2979
  state.nativeProviders.transak.buyQuote.isLoading = false;