From 7cfe6739bfc96c5ab7ffd5ea4f9f626ccfc0b200 Mon Sep 17 00:00:00 2001 From: Sam Holmes Date: Fri, 10 Oct 2025 15:24:48 -0700 Subject: [PATCH 01/10] Remove unnecessary `pluginId` from `RampQuoteRequest` Require `wallet` and use `currencyInfo` to get the `pluginId`. --- src/components/scenes/RampSelectOptionScene.tsx | 4 ++-- src/plugins/ramps/banxa/banxaRampPlugin.ts | 2 +- src/plugins/ramps/bity/bityRampPlugin.ts | 2 +- src/plugins/ramps/moonpay/moonpayRampPlugin.ts | 6 +++--- src/plugins/ramps/paybis/paybisRampPlugin.ts | 2 +- src/plugins/ramps/rampPluginTypes.ts | 3 +-- src/plugins/ramps/revolut/revolutRampPlugin.ts | 2 +- src/plugins/ramps/simplex/simplexRampPlugin.ts | 2 +- 8 files changed, 11 insertions(+), 12 deletions(-) diff --git a/src/components/scenes/RampSelectOptionScene.tsx b/src/components/scenes/RampSelectOptionScene.tsx index c5218ebf0ef..e6e304bdeff 100644 --- a/src/components/scenes/RampSelectOptionScene.tsx +++ b/src/components/scenes/RampSelectOptionScene.tsx @@ -65,7 +65,7 @@ export const RampSelectOptionScene: React.FC = (props: Props) => { selectedCrypto: rampQuoteRequest.wallet != null ? { - pluginId: rampQuoteRequest.pluginId, + pluginId: rampQuoteRequest.wallet.currencyInfo.pluginId, tokenId: rampQuoteRequest.tokenId } : undefined, @@ -100,7 +100,7 @@ export const RampSelectOptionScene: React.FC = (props: Props) => { setIsApprovingQuote(true) try { await quote.approveQuote({ - coreWallet: rampQuoteRequest.wallet! + coreWallet: rampQuoteRequest.wallet }) } finally { setIsApprovingQuote(false) diff --git a/src/plugins/ramps/banxa/banxaRampPlugin.ts b/src/plugins/ramps/banxa/banxaRampPlugin.ts index 64b2d023f68..25161ccd095 100644 --- a/src/plugins/ramps/banxa/banxaRampPlugin.ts +++ b/src/plugins/ramps/banxa/banxaRampPlugin.ts @@ -864,11 +864,11 @@ export const banxaRampPlugin: RampPluginFactory = ( direction, regionCode, amountType, - pluginId: currencyPluginId, fiatCurrencyCode, displayCurrencyCode, tokenId } = request + const currencyPluginId = request.wallet.currencyInfo.pluginId const isMaxAmount = typeof request.exchangeAmount === 'object' && request.exchangeAmount.max diff --git a/src/plugins/ramps/bity/bityRampPlugin.ts b/src/plugins/ramps/bity/bityRampPlugin.ts index 072c79f9e71..cd829e0aa5c 100644 --- a/src/plugins/ramps/bity/bityRampPlugin.ts +++ b/src/plugins/ramps/bity/bityRampPlugin.ts @@ -679,10 +679,10 @@ export const bityRampPlugin = (pluginConfig: RampPluginConfig): RampPlugin => { direction, fiatCurrencyCode, regionCode, - pluginId: currencyPluginId, tokenId, displayCurrencyCode } = request + const currencyPluginId = request.wallet.currencyInfo.pluginId const isBuy = direction === 'buy' const isMaxAmount = diff --git a/src/plugins/ramps/moonpay/moonpayRampPlugin.ts b/src/plugins/ramps/moonpay/moonpayRampPlugin.ts index 34ea2234755..67e7b4ca483 100644 --- a/src/plugins/ramps/moonpay/moonpayRampPlugin.ts +++ b/src/plugins/ramps/moonpay/moonpayRampPlugin.ts @@ -544,7 +544,7 @@ export const moonpayRampPlugin: RampPluginFactory = ( for (const method of supportedPaymentMethods) { const cryptoSupported = isCryptoSupported( - request.pluginId, + request.wallet.currencyInfo.pluginId, request.tokenId, method.assetMap, regionCode @@ -581,8 +581,8 @@ export const moonpayRampPlugin: RampPluginFactory = ( const displayFiatCurrencyCode = removeIsoPrefix(fiatCurrencyCode) const fiatCode = removeIsoPrefix(fiatCurrencyCode).toLowerCase() const walletAddress = ( - await request.wallet?.getAddresses({ tokenId: null }) - )?.[0]?.publicAddress + await request.wallet.getAddresses({ tokenId: null }) + )[0].publicAddress const walletAddressParam = walletAddress == null ? '' : `&walletAddress=${walletAddress}` diff --git a/src/plugins/ramps/paybis/paybisRampPlugin.ts b/src/plugins/ramps/paybis/paybisRampPlugin.ts index 25e4902b27a..13899bc698c 100644 --- a/src/plugins/ramps/paybis/paybisRampPlugin.ts +++ b/src/plugins/ramps/paybis/paybisRampPlugin.ts @@ -721,13 +721,13 @@ export const paybisRampPlugin: RampPluginFactory = ( const { amountType, regionCode, - pluginId: currencyPluginId, promoCode: maybePromoCode, fiatCurrencyCode, displayCurrencyCode, direction, tokenId } = request + const currencyPluginId = request.wallet.currencyInfo.pluginId const isMaxAmount = typeof request.exchangeAmount === 'object' && request.exchangeAmount.max diff --git a/src/plugins/ramps/rampPluginTypes.ts b/src/plugins/ramps/rampPluginTypes.ts index e37acbd0973..d18a4e91138 100644 --- a/src/plugins/ramps/rampPluginTypes.ts +++ b/src/plugins/ramps/rampPluginTypes.ts @@ -36,8 +36,7 @@ export interface RampSupportResult { } export interface RampQuoteRequest { - wallet?: EdgeCurrencyWallet - pluginId: string + wallet: EdgeCurrencyWallet tokenId: EdgeTokenId displayCurrencyCode: string exchangeAmount: string | { max: true } diff --git a/src/plugins/ramps/revolut/revolutRampPlugin.ts b/src/plugins/ramps/revolut/revolutRampPlugin.ts index f99d0db65e1..6186282c700 100644 --- a/src/plugins/ramps/revolut/revolutRampPlugin.ts +++ b/src/plugins/ramps/revolut/revolutRampPlugin.ts @@ -173,11 +173,11 @@ export const revolutRampPlugin: RampPluginFactory = ( const { fiatCurrencyCode, regionCode, - pluginId: currencyPluginId, tokenId, displayCurrencyCode, direction } = request + const currencyPluginId = request.wallet.currencyInfo.pluginId const isMaxAmount = typeof request.exchangeAmount === 'object' && request.exchangeAmount.max diff --git a/src/plugins/ramps/simplex/simplexRampPlugin.ts b/src/plugins/ramps/simplex/simplexRampPlugin.ts index 1ad420e2377..0e7d87fb3c1 100644 --- a/src/plugins/ramps/simplex/simplexRampPlugin.ts +++ b/src/plugins/ramps/simplex/simplexRampPlugin.ts @@ -451,11 +451,11 @@ export const simplexRampPlugin: RampPluginFactory = ( const { amountType, regionCode, - pluginId: currencyPluginId, fiatCurrencyCode, displayCurrencyCode, direction } = request + const currencyPluginId = request.wallet.currencyInfo.pluginId const isMaxAmount = typeof request.exchangeAmount === 'object' && request.exchangeAmount.max From ca6ad546237d3cbf7edb86a44f83193709ec254b Mon Sep 17 00:00:00 2001 From: Sam Holmes Date: Tue, 14 Oct 2025 12:57:35 -0700 Subject: [PATCH 02/10] Remove and deprecate `iach` We're removing `iach` from ramp plugin implementation and deprecating the value for documentation purposes. We'll remove the value variant completely when it's no longer a dependency for legacy code. --- src/plugins/gui/fiatPluginTypes.ts | 2 ++ src/plugins/ramps/paybis/paybisRampPlugin.ts | 8 ++++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/plugins/gui/fiatPluginTypes.ts b/src/plugins/gui/fiatPluginTypes.ts index 29390a37b96..45f59ced386 100644 --- a/src/plugins/gui/fiatPluginTypes.ts +++ b/src/plugins/gui/fiatPluginTypes.ts @@ -49,6 +49,8 @@ export const asFiatPaymentType = asValue( 'directtobank', 'fasterpayments', 'googlepay', + // TODO: Remove `iach` from the variants when old fiat plugin code has been gutted. + /** @deprecated because Instant ACH isn't different from regular ACH. Use `ach` instead. */ 'iach', 'ideal', 'interac', diff --git a/src/plugins/ramps/paybis/paybisRampPlugin.ts b/src/plugins/ramps/paybis/paybisRampPlugin.ts index 13899bc698c..2d2a98926c8 100644 --- a/src/plugins/ramps/paybis/paybisRampPlugin.ts +++ b/src/plugins/ramps/paybis/paybisRampPlugin.ts @@ -95,7 +95,7 @@ type AllowedPaymentTypes = Record< const allowedPaymentTypes: AllowedPaymentTypes = { buy: { - iach: true, + ach: true, applepay: true, credit: true, googlepay: true, @@ -105,7 +105,7 @@ const allowedPaymentTypes: AllowedPaymentTypes = { spei: true }, sell: { - iach: true, + ach: true, colombiabank: true, credit: true, mexicobank: true, @@ -334,7 +334,7 @@ const EDGE_TO_PAYBIS_CURRENCY_MAP: StringMap = Object.entries( }, {}) const PAYMENT_METHOD_MAP: Record = { - 'method-id-trustly': 'iach', + 'method-id-trustly': 'ach', 'method-id-credit-card': 'credit', 'method-id-credit-card-out': 'credit', 'method-id_bridgerpay_revolutpay': 'revolut', @@ -351,7 +351,7 @@ const PAYMENT_METHOD_MAP: Record = { const REVERSE_PAYMENT_METHOD_MAP: Partial< Record > = { - iach: 'method-id-trustly', + ach: 'method-id-trustly', applepay: 'method-id-credit-card', credit: 'method-id-credit-card', googlepay: 'method-id-credit-card', From af775abcff8b6ad9eaf10d7b91387006d8d7babc Mon Sep 17 00:00:00 2001 From: Sam Holmes Date: Tue, 14 Oct 2025 14:48:27 -0700 Subject: [PATCH 03/10] Add applepay and googlepay payment types to simplex --- .../ramps/simplex/simplexRampPlugin.ts | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/src/plugins/ramps/simplex/simplexRampPlugin.ts b/src/plugins/ramps/simplex/simplexRampPlugin.ts index 0e7d87fb3c1..db4bd8a7f63 100644 --- a/src/plugins/ramps/simplex/simplexRampPlugin.ts +++ b/src/plugins/ramps/simplex/simplexRampPlugin.ts @@ -1,4 +1,5 @@ import { gt, lt } from 'biggystring' +import { Platform } from 'react-native' import { showToast } from '../../../components/services/AirshipInstance' import { EDGE_CONTENT_SERVER_URI } from '../../../constants/CdnConstants' @@ -149,6 +150,16 @@ const SIMPLEX_ID_MAP: Record> = { wax: { WAX: 'WAXP' } } +// Build quotes for supported payment methods (credit always, plus platform wallets) +const basePaymentTypes = ['credit'] as const +const platformPaymentTypes = + Platform.OS === 'ios' + ? (['applepay'] as const) + : Platform.OS === 'android' + ? (['googlepay'] as const) + : ([] as const) +const paymentTypes = [...basePaymentTypes, ...platformPaymentTypes] + interface SimplexPluginState { partner: string jwtTokenProvider: string @@ -584,8 +595,7 @@ export const simplexRampPlugin: RampPluginFactory = ( const quoteFiatAmount = goodQuote.fiat_money.amount.toString() const quoteCryptoAmount = goodQuote.digital_money.amount.toString() - // Return quote for credit card payment type - const rampQuote: RampQuote = { + const quotes: RampQuote[] = paymentTypes.map(paymentType => ({ pluginId, partnerIcon, pluginDisplayName, @@ -597,8 +607,8 @@ export const simplexRampPlugin: RampPluginFactory = ( direction, expirationDate: new Date(Date.now() + 8000), regionCode, - paymentType: 'credit', // Simplex supports 'applepay', 'credit', and 'googlepay' but we always return credit for now - settlementRange: getSettlementRange('credit', direction), + paymentType, + settlementRange: getSettlementRange(paymentType, direction), approveQuote: async (params: RampApproveQuoteParams): Promise => { if (state == null) throw new Error('Plugin state not initialized') const { coreWallet } = params @@ -672,9 +682,9 @@ export const simplexRampPlugin: RampPluginFactory = ( }) }, closeQuote: async (): Promise => {} - } + })) - return [rampQuote] + return quotes } } From 1cdaa780a49d0e76e1f2b551d09a1feb5ad8fec0 Mon Sep 17 00:00:00 2001 From: Sam Holmes Date: Thu, 16 Oct 2025 11:09:55 -0700 Subject: [PATCH 04/10] Constrain ach payment type to US only We need generators to not early exit on constraints so they're all applied. This is nice so we don't need to couple our constraints to other constrains when adding/removing. The contract is that all the constraints will get equal chance of being applied. --- src/plugins/ramps/banxa/banxaRampPlugin.ts | 28 +++ .../ramps/moonpay/moonpayRampPlugin.ts | 31 ++- src/plugins/ramps/paybis/paybisRampPlugin.ts | 27 +++ src/plugins/ramps/rampConstraints.ts | 34 ++++ src/plugins/ramps/rampPluginTypes.ts | 10 +- .../ramps/revolut/revolutRampPlugin.ts | 32 ++- .../ramps/simplex/simplexRampPlugin.ts | 191 ++++++++++-------- src/plugins/ramps/utils/constraintUtils.ts | 50 +++++ 8 files changed, 305 insertions(+), 98 deletions(-) create mode 100644 src/plugins/ramps/rampConstraints.ts create mode 100644 src/plugins/ramps/utils/constraintUtils.ts diff --git a/src/plugins/ramps/banxa/banxaRampPlugin.ts b/src/plugins/ramps/banxa/banxaRampPlugin.ts index 25161ccd095..9928a02f5b8 100644 --- a/src/plugins/ramps/banxa/banxaRampPlugin.ts +++ b/src/plugins/ramps/banxa/banxaRampPlugin.ts @@ -63,6 +63,10 @@ import type { RampQuoteRequest, RampSupportResult } from '../rampPluginTypes' +import { + validateRampCheckSupportRequest, + validateRampQuoteRequest +} from '../utils/constraintUtils' import { getSettlementRange } from '../utils/getSettlementRange' import { openExternalWebView } from '../utils/webViewUtils' import { asInitOptions } from './banxaRampTypes' @@ -819,6 +823,22 @@ export const banxaRampPlugin: RampPluginFactory = ( const config = await fetchProviderConfig() const { allowedCountryCodes, allowedCurrencyCodes } = config + // Get supported payment types for this direction + const supportedPaymentTypes = Object.keys( + allowedPaymentTypes[request.direction] + ).filter( + pt => + allowedPaymentTypes[request.direction][pt as FiatPaymentType] === true + ) as FiatPaymentType[] + + // Global constraints pre-check + const constraintOk = validateRampCheckSupportRequest( + pluginId, + request, + supportedPaymentTypes + ) + if (!constraintOk) return { supported: false } + // Check region support if (!isRegionSupported(request.regionCode, allowedCountryCodes)) { return { supported: false } @@ -936,6 +956,14 @@ export const banxaRampPlugin: RampPluginFactory = ( const quotes: RampQuote[] = [] for (const paymentType of supportedPaymentTypes) { + // Constraints per request + const constraintOk = validateRampQuoteRequest( + pluginId, + request, + paymentType + ) + if (!constraintOk) continue + try { // Find payment method for this type let paymentObj: BanxaPaymentIdLimit | undefined diff --git a/src/plugins/ramps/moonpay/moonpayRampPlugin.ts b/src/plugins/ramps/moonpay/moonpayRampPlugin.ts index 67e7b4ca483..0abdabf0d9f 100644 --- a/src/plugins/ramps/moonpay/moonpayRampPlugin.ts +++ b/src/plugins/ramps/moonpay/moonpayRampPlugin.ts @@ -59,6 +59,10 @@ import type { RampQuoteRequest, RampSupportResult } from '../rampPluginTypes' +import { + validateRampCheckSupportRequest, + validateRampQuoteRequest +} from '../utils/constraintUtils' import { getSettlementRange } from '../utils/getSettlementRange' import { openExternalWebView } from '../utils/webViewUtils' import { @@ -455,11 +459,6 @@ export const moonpayRampPlugin: RampPluginFactory = ( const config = await fetchProviderConfig() const { allowedCountryCodes, allowedCurrencyCodes } = config - // Check region support - if (!isRegionSupported(regionCode, direction, allowedCountryCodes)) { - return { supported: false } - } - // Get supported payment methods const supportedMethods = getSupportedPaymentMethods( direction, @@ -469,6 +468,21 @@ export const moonpayRampPlugin: RampPluginFactory = ( return { supported: false } } + // Get supported payment methods + const paymentTypes = supportedMethods.map(method => method.paymentType) + // Global constraints pre-check + const constraintOk = validateRampCheckSupportRequest( + pluginId, + request, + paymentTypes + ) + if (!constraintOk) return { supported: false } + + // Check region support + if (!isRegionSupported(regionCode, direction, allowedCountryCodes)) { + return { supported: false } + } + // Check support across all payment methods for (const { assetMap } of supportedMethods) { // Check crypto support @@ -554,6 +568,13 @@ export const moonpayRampPlugin: RampPluginFactory = ( const fiatSupported = isFiatSupported(fiatCurrencyCode, method.assetMap) if (fiatSupported == null) continue + const constraintOk = validateRampQuoteRequest( + pluginId, + request, + method.paymentType + ) + if (!constraintOk) continue + methodCandidates.push({ paymentType: method.paymentType, paymentMethod: method.paymentMethod, diff --git a/src/plugins/ramps/paybis/paybisRampPlugin.ts b/src/plugins/ramps/paybis/paybisRampPlugin.ts index 2d2a98926c8..5b9a99a0181 100644 --- a/src/plugins/ramps/paybis/paybisRampPlugin.ts +++ b/src/plugins/ramps/paybis/paybisRampPlugin.ts @@ -79,6 +79,10 @@ import type { RampQuoteRequest, RampSupportResult } from '../rampPluginTypes' +import { + validateRampCheckSupportRequest, + validateRampQuoteRequest +} from '../utils/constraintUtils' import { getSettlementRange } from '../utils/getSettlementRange' import { asInitOptions } from './paybisRampTypes' @@ -685,6 +689,20 @@ export const paybisRampPlugin: RampPluginFactory = ( ): Promise => { const { direction, regionCode, fiatAsset, cryptoAsset } = request + const allPaymentTypes = Object.keys( + allowedPaymentTypes[direction] + ).filter( + key => allowedPaymentTypes[direction][key as FiatPaymentType] === true + ) as FiatPaymentType[] + + // Global constraints pre-check + const constraintOk = validateRampCheckSupportRequest( + pluginId, + request, + allPaymentTypes + ) + if (!constraintOk) return { supported: false } + // Ensure assets are initialized for the direction await ensureAssetsInitialized(direction) @@ -747,6 +765,7 @@ export const paybisRampPlugin: RampPluginFactory = ( key => allowedPaymentTypes[direction][key as FiatPaymentType] === true ) as FiatPaymentType[] + // TODO: Remove this and use the constraints instead // Filter out credit for sell in US if (direction === 'sell' && regionCode.countryCode === 'US') { allPaymentTypes = allPaymentTypes.filter(pt => pt !== 'credit') @@ -798,6 +817,14 @@ export const paybisRampPlugin: RampPluginFactory = ( // Get quote for each supported payment type for (const paymentType of allPaymentTypes) { + // Constraints per request + const constraintOk = validateRampQuoteRequest( + pluginId, + request, + paymentType + ) + if (!constraintOk) continue + try { const paymentMethod = direction === 'buy' diff --git a/src/plugins/ramps/rampConstraints.ts b/src/plugins/ramps/rampConstraints.ts new file mode 100644 index 00000000000..2e395ed1bb1 --- /dev/null +++ b/src/plugins/ramps/rampConstraints.ts @@ -0,0 +1,34 @@ +import type { + FiatPaymentType, + FiatPluginRegionCode +} from '../gui/fiatPluginTypes' +import type { CryptoAsset } from './rampPluginTypes' + +export interface RampConstraintParams { + rampPluginId: string + cryptoAsset: CryptoAsset + fiatCurrencyCode: string + direction: 'buy' | 'sell' + regionCode: FiatPluginRegionCode + paymentType: FiatPaymentType +} + +export function validateRampConstraintParams( + params: RampConstraintParams +): boolean { + for (const constraint of constraintGenerator(params)) { + if (!constraint) { + return false + } + } + return true +} + +export function* constraintGenerator( + params: RampConstraintParams +): Generator { + // Restrict ACH to only US + if (params.paymentType === 'ach') { + yield params.regionCode.countryCode === 'US' + } +} diff --git a/src/plugins/ramps/rampPluginTypes.ts b/src/plugins/ramps/rampPluginTypes.ts index d18a4e91138..138da33e9d4 100644 --- a/src/plugins/ramps/rampPluginTypes.ts +++ b/src/plugins/ramps/rampPluginTypes.ts @@ -15,6 +15,11 @@ export interface ProviderToken { otherInfo?: unknown } +export interface CryptoAsset { + pluginId: string + tokenId: EdgeTokenId +} + // Support checking types export interface RampCheckSupportRequest { direction: 'buy' | 'sell' @@ -23,10 +28,7 @@ export interface RampCheckSupportRequest { // ISO currency code (without 'iso:' prefix) currencyCode: string } - cryptoAsset: { - pluginId: string - tokenId: EdgeTokenId - } + cryptoAsset: CryptoAsset } export interface RampSupportResult { diff --git a/src/plugins/ramps/revolut/revolutRampPlugin.ts b/src/plugins/ramps/revolut/revolutRampPlugin.ts index 6186282c700..999573c9a21 100644 --- a/src/plugins/ramps/revolut/revolutRampPlugin.ts +++ b/src/plugins/ramps/revolut/revolutRampPlugin.ts @@ -22,6 +22,10 @@ import type { RampQuoteRequest, RampSupportResult } from '../rampPluginTypes' +import { + validateRampCheckSupportRequest, + validateRampQuoteRequest +} from '../utils/constraintUtils' import { getSettlementRange } from '../utils/getSettlementRange' import { openExternalWebView } from '../utils/webViewUtils' import { asInitOptions } from './revolutRampTypes' @@ -39,6 +43,8 @@ import { const pluginId = 'revolut' const partnerIcon = `${EDGE_CONTENT_SERVER_URI}/revolut.png` const pluginDisplayName = 'Revolut' +// Only 'revolut' is supported for this plugin +const paymentType = 'revolut' interface ProviderConfigCache { data: RevolutConfig | null @@ -126,6 +132,12 @@ export const revolutRampPlugin: RampPluginFactory = ( ): Promise => { const { direction, regionCode, fiatAsset, cryptoAsset } = request + // Global constraints pre-check + const constraintOk = validateRampCheckSupportRequest(pluginId, request, [ + paymentType + ]) + if (!constraintOk) return { supported: false } + // Check direction support if (!validateDirection(direction)) { return { supported: false } @@ -152,7 +164,7 @@ export const revolutRampPlugin: RampPluginFactory = ( const assetMap = store.getFiatProviderAssetMap({ direction: 'buy', region, - payment: 'revolut' + payment: paymentType }) // Check asset support @@ -184,6 +196,14 @@ export const revolutRampPlugin: RampPluginFactory = ( const exchangeAmount = typeof request.exchangeAmount === 'object' ? '' : request.exchangeAmount + // Constraints per request + const constraintOk = validateRampQuoteRequest( + pluginId, + request, + paymentType + ) + if (!constraintOk) return [] + // Check direction support if (!validateDirection(direction)) { return [] @@ -212,7 +232,7 @@ export const revolutRampPlugin: RampPluginFactory = ( const assetMap = store.getFiatProviderAssetMap({ direction: 'buy', region, - payment: 'revolut' + payment: paymentType }) // Check asset support @@ -272,7 +292,7 @@ export const revolutRampPlugin: RampPluginFactory = ( fiat: revolutFiat.currency, amount, crypto: revolutCrypto.id, - payment: 'revolut', // Only revolut is supported at the moment + payment: paymentType, region: regionCode.countryCode }, { apiKey, baseUrl: apiUrl } @@ -296,8 +316,8 @@ export const revolutRampPlugin: RampPluginFactory = ( direction, expirationDate, regionCode, - paymentType: 'revolut', - settlementRange: getSettlementRange('revolut', direction), + paymentType, + settlementRange: getSettlementRange(paymentType, direction), approveQuote: async ( approveParams: RampApproveQuoteParams @@ -324,7 +344,7 @@ export const revolutRampPlugin: RampPluginFactory = ( fiat: revolutFiat.currency, amount: parseFloat(fiatAmount), crypto: quoteData.crypto.currencyId, - payment: 'revolut', + payment: paymentType, region: regionCode.countryCode, // API only needs country code wallet: walletAddress, partnerRedirectUrl: successReturnURL, diff --git a/src/plugins/ramps/simplex/simplexRampPlugin.ts b/src/plugins/ramps/simplex/simplexRampPlugin.ts index db4bd8a7f63..075e9bda577 100644 --- a/src/plugins/ramps/simplex/simplexRampPlugin.ts +++ b/src/plugins/ramps/simplex/simplexRampPlugin.ts @@ -22,6 +22,10 @@ import type { RampQuoteRequest, RampSupportResult } from '../rampPluginTypes' +import { + validateRampCheckSupportRequest, + validateRampQuoteRequest +} from '../utils/constraintUtils' import { getSettlementRange } from '../utils/getSettlementRange' import { openExternalWebView } from '../utils/webViewUtils' import { @@ -367,6 +371,14 @@ export const simplexRampPlugin: RampPluginFactory = ( ): Promise => { const { direction, regionCode, fiatAsset, cryptoAsset } = request + // Global constraints pre-check + const constraintOk = validateRampCheckSupportRequest( + pluginId, + request, + paymentTypes + ) + if (!constraintOk) return { supported: false } + // Validate direction if (!validateDirection(direction)) { return { supported: false } @@ -595,94 +607,107 @@ export const simplexRampPlugin: RampPluginFactory = ( const quoteFiatAmount = goodQuote.fiat_money.amount.toString() const quoteCryptoAmount = goodQuote.digital_money.amount.toString() - const quotes: RampQuote[] = paymentTypes.map(paymentType => ({ - pluginId, - partnerIcon, - pluginDisplayName, - displayCurrencyCode, - cryptoAmount: quoteCryptoAmount, - isEstimate: false, - fiatCurrencyCode, - fiatAmount: quoteFiatAmount, - direction, - expirationDate: new Date(Date.now() + 8000), - regionCode, - paymentType, - settlementRange: getSettlementRange(paymentType, direction), - approveQuote: async (params: RampApproveQuoteParams): Promise => { - if (state == null) throw new Error('Plugin state not initialized') - const { coreWallet } = params - - const receiveAddress = await coreWallet.getReceiveAddress({ - tokenId: null - }) + const quotes: RampQuote[] = [] + for (const paymentType of paymentTypes) { + // Constraints per request + const constraintOk = validateRampQuoteRequest( + pluginId, + request, + paymentType + ) + if (!constraintOk) continue + + quotes.push({ + pluginId, + partnerIcon, + pluginDisplayName, + displayCurrencyCode, + cryptoAmount: quoteCryptoAmount, + isEstimate: false, + fiatCurrencyCode, + fiatAmount: quoteFiatAmount, + direction, + expirationDate: new Date(Date.now() + 8000), + regionCode, + paymentType, + settlementRange: getSettlementRange(paymentType, direction), + approveQuote: async ( + params: RampApproveQuoteParams + ): Promise => { + if (state == null) throw new Error('Plugin state not initialized') + const { coreWallet } = params + + const receiveAddress = await coreWallet.getReceiveAddress({ + tokenId: null + }) + + const data: SimplexJwtData = { + ts: Math.floor(Date.now() / 1000), + euid: state.simplexUserId, + crad: receiveAddress.publicAddress, + crcn: simplexCryptoCode, + ficn: simplexFiatCode, + fiam: goodQuote.fiat_money.amount + } - const data: SimplexJwtData = { - ts: Math.floor(Date.now() / 1000), - euid: state.simplexUserId, - crad: receiveAddress.publicAddress, - crcn: simplexCryptoCode, - ficn: simplexFiatCode, - fiam: goodQuote.fiat_money.amount - } + const token = await fetchJwtToken(state.jwtTokenProvider, data) + const url = `${widgetUrl}/?partner=${state.partner}&t=${token}` - const token = await fetchJwtToken(state.jwtTokenProvider, data) - const url = `${widgetUrl}/?partner=${state.partner}&t=${token}` - - await openExternalWebView({ - url, - deeplink: { - direction: 'buy', - providerId: pluginId, - handler: async link => { - if (link.direction !== 'buy') return - - const orderId = link.query.orderId ?? 'unknown' - const status = link.query.status?.replace('?', '') - - switch (status) { - case 'success': { - onLogEvent('Buy_Success', { - conversionValues: { - conversionType: 'buy', - sourceFiatCurrencyCode: simplexFiatCode, - sourceFiatAmount: - goodQuote.fiat_money.amount.toString(), - destAmount: new CryptoAmount({ - currencyConfig: coreWallet.currencyConfig, - currencyCode: coreWallet.currencyInfo.currencyCode, - exchangeAmount: - goodQuote.digital_money.amount.toString() - }), - fiatProviderId: pluginId, - orderId - } - }) - navigation.pop() - break - } - case 'failure': { - showToast( - lstrings.fiat_plugin_buy_failed_try_again, - NOT_SUCCESS_TOAST_HIDE_MS - ) - navigation.pop() - break - } - default: { - showToast( - lstrings.fiat_plugin_buy_unknown_status, - NOT_SUCCESS_TOAST_HIDE_MS - ) - navigation.pop() + await openExternalWebView({ + url, + deeplink: { + direction: 'buy', + providerId: pluginId, + handler: async link => { + if (link.direction !== 'buy') return + + const orderId = link.query.orderId ?? 'unknown' + const status = link.query.status?.replace('?', '') + + switch (status) { + case 'success': { + onLogEvent('Buy_Success', { + conversionValues: { + conversionType: 'buy', + sourceFiatCurrencyCode: simplexFiatCode, + sourceFiatAmount: + goodQuote.fiat_money.amount.toString(), + destAmount: new CryptoAmount({ + currencyConfig: coreWallet.currencyConfig, + currencyCode: coreWallet.currencyInfo.currencyCode, + exchangeAmount: + goodQuote.digital_money.amount.toString() + }), + fiatProviderId: pluginId, + orderId + } + }) + navigation.pop() + break + } + case 'failure': { + showToast( + lstrings.fiat_plugin_buy_failed_try_again, + NOT_SUCCESS_TOAST_HIDE_MS + ) + navigation.pop() + break + } + default: { + showToast( + lstrings.fiat_plugin_buy_unknown_status, + NOT_SUCCESS_TOAST_HIDE_MS + ) + navigation.pop() + } } } } - } - }) - }, - closeQuote: async (): Promise => {} - })) + }) + }, + closeQuote: async (): Promise => {} + }) + } return quotes } diff --git a/src/plugins/ramps/utils/constraintUtils.ts b/src/plugins/ramps/utils/constraintUtils.ts new file mode 100644 index 00000000000..3d33dd476f5 --- /dev/null +++ b/src/plugins/ramps/utils/constraintUtils.ts @@ -0,0 +1,50 @@ +import type { FiatPaymentType } from '../../gui/fiatPluginTypes' +import { + type RampConstraintParams, + validateRampConstraintParams +} from '../rampConstraints' +import type { + RampCheckSupportRequest, + RampQuoteRequest +} from '../rampPluginTypes' + +export const validateRampCheckSupportRequest = ( + rampPluginId: string, + request: RampCheckSupportRequest, + paymentType: FiatPaymentType[] +): boolean => { + return paymentType.some(paymentType => { + const params: RampConstraintParams = { + rampPluginId, + cryptoAsset: { + pluginId: request.cryptoAsset.pluginId, + tokenId: request.cryptoAsset.tokenId + }, + fiatCurrencyCode: request.fiatAsset.currencyCode, + direction: request.direction, + regionCode: request.regionCode, + paymentType + } + return validateRampConstraintParams(params) + }) +} + +export const validateRampQuoteRequest = ( + rampPluginId: string, + request: RampQuoteRequest, + /** It is required to check the payment type when checking the RampQuoteRequest */ + paymentType: FiatPaymentType +): boolean => { + const params: RampConstraintParams = { + rampPluginId, + cryptoAsset: { + pluginId: request.wallet.currencyInfo.pluginId, + tokenId: request.tokenId + }, + fiatCurrencyCode: request.fiatCurrencyCode, + direction: request.direction, + regionCode: request.regionCode, + paymentType + } + return validateRampConstraintParams(params) +} From c1fd6661e3155b5c55dae90c171944a7376cf127 Mon Sep 17 00:00:00 2001 From: Sam Holmes Date: Wed, 15 Oct 2025 07:42:07 -0700 Subject: [PATCH 05/10] Move US-only venmo constraint to rampConstraints.ts --- src/plugins/ramps/moonpay/moonpayRampPlugin.ts | 9 +-------- src/plugins/ramps/rampConstraints.ts | 5 +++++ 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/plugins/ramps/moonpay/moonpayRampPlugin.ts b/src/plugins/ramps/moonpay/moonpayRampPlugin.ts index 0abdabf0d9f..00ccfa9aae5 100644 --- a/src/plugins/ramps/moonpay/moonpayRampPlugin.ts +++ b/src/plugins/ramps/moonpay/moonpayRampPlugin.ts @@ -548,7 +548,7 @@ export const moonpayRampPlugin: RampPluginFactory = ( } // Build list of payment methods that support both fiat and crypto - let methodCandidates: Array<{ + const methodCandidates: Array<{ paymentType: FiatPaymentType paymentMethod: MoonpayPaymentMethod assetMap: AssetMap @@ -584,13 +584,6 @@ export const moonpayRampPlugin: RampPluginFactory = ( }) } - // Venmo payment method is only supported in the USA - if (regionCode.countryCode !== 'US') { - methodCandidates = methodCandidates.filter( - method => method.paymentType !== 'venmo' - ) - } - // If no payment method supports both crypto and fiat, throw error if (methodCandidates.length === 0) { throw new FiatProviderError({ diff --git a/src/plugins/ramps/rampConstraints.ts b/src/plugins/ramps/rampConstraints.ts index 2e395ed1bb1..877e18ce4c6 100644 --- a/src/plugins/ramps/rampConstraints.ts +++ b/src/plugins/ramps/rampConstraints.ts @@ -31,4 +31,9 @@ export function* constraintGenerator( if (params.paymentType === 'ach') { yield params.regionCode.countryCode === 'US' } + + // Venmo payment method is only supported in the USA + if (params.paymentType === 'venmo') { + yield params.regionCode.countryCode === 'US' + } } From eb42c429a58af5dd1c8e2c352d772473affaca38 Mon Sep 17 00:00:00 2001 From: Sam Holmes Date: Wed, 15 Oct 2025 07:45:03 -0700 Subject: [PATCH 06/10] Restrict 'revolut' paymentType to only select countries The list of supported countries is copied from the `buyPluginList.json` file. Revolut is not supported for sell direction, so no need to get the country codes from `sellPluginList.json`. --- src/plugins/ramps/rampConstraints.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/plugins/ramps/rampConstraints.ts b/src/plugins/ramps/rampConstraints.ts index 877e18ce4c6..6c030388726 100644 --- a/src/plugins/ramps/rampConstraints.ts +++ b/src/plugins/ramps/rampConstraints.ts @@ -36,4 +36,19 @@ export function* constraintGenerator( if (params.paymentType === 'venmo') { yield params.regionCode.countryCode === 'US' } + + // Constrain Revolut to the supported regions + if (params.paymentType === 'revolut') { + const forCountries = ` + AU, BR, AT, BE, BG, CY, CZ, DE, DK, EE, ES, FI, + FR, GR, HR, HU, IE, IS, IT, LI, LT, LU, LV, + MT, NL, NO, PL, PT, RO, SE, SI, SK, JP, NZ, + SG, CH, GB, IO, IM, JE, GG, GF, GP, YT, + MQ, RE, MF + ` + .replace(/\s/g, '') + .split(',') + + yield forCountries.includes(params.regionCode.countryCode) + } } From b4dd77015d90bf3cb7629896aca3c9e406538c63 Mon Sep 17 00:00:00 2001 From: Sam Holmes Date: Thu, 16 Oct 2025 13:46:33 -0700 Subject: [PATCH 07/10] Move paybis US-only constraint to rampConstraints.ts --- src/plugins/ramps/paybis/paybisRampPlugin.ts | 10 +++------- src/plugins/ramps/rampConstraints.ts | 9 +++++++++ 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/plugins/ramps/paybis/paybisRampPlugin.ts b/src/plugins/ramps/paybis/paybisRampPlugin.ts index 5b9a99a0181..e24fcd8cd23 100644 --- a/src/plugins/ramps/paybis/paybisRampPlugin.ts +++ b/src/plugins/ramps/paybis/paybisRampPlugin.ts @@ -761,16 +761,12 @@ export const paybisRampPlugin: RampPluginFactory = ( await ensureAssetsInitialized(direction) // Get all supported payment types for the direction - let allPaymentTypes = Object.keys(allowedPaymentTypes[direction]).filter( + const allPaymentTypes = Object.keys( + allowedPaymentTypes[direction] + ).filter( key => allowedPaymentTypes[direction][key as FiatPaymentType] === true ) as FiatPaymentType[] - // TODO: Remove this and use the constraints instead - // Filter out credit for sell in US - if (direction === 'sell' && regionCode.countryCode === 'US') { - allPaymentTypes = allPaymentTypes.filter(pt => pt !== 'credit') - } - if (allPaymentTypes.length === 0) { // Return empty array if no payment types supported return [] diff --git a/src/plugins/ramps/rampConstraints.ts b/src/plugins/ramps/rampConstraints.ts index 6c030388726..a89c91a2fd5 100644 --- a/src/plugins/ramps/rampConstraints.ts +++ b/src/plugins/ramps/rampConstraints.ts @@ -37,6 +37,15 @@ export function* constraintGenerator( yield params.regionCode.countryCode === 'US' } + // Filter out credit for sell in US for Paybis + if (params.rampPluginId === 'paybis') { + yield !( + params.direction === 'sell' && + params.regionCode.countryCode === 'US' && + params.paymentType === 'credit' + ) + } + // Constrain Revolut to the supported regions if (params.paymentType === 'revolut') { const forCountries = ` From 59acd593a69e4cbc734935381fd616f1f23aa64a Mon Sep 17 00:00:00 2001 From: Sam Holmes Date: Tue, 14 Oct 2025 16:56:01 -0700 Subject: [PATCH 08/10] Restrict Paybis from the US This was missing from the paybisProvider implementation. --- src/plugins/ramps/rampConstraints.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/plugins/ramps/rampConstraints.ts b/src/plugins/ramps/rampConstraints.ts index a89c91a2fd5..214161246a2 100644 --- a/src/plugins/ramps/rampConstraints.ts +++ b/src/plugins/ramps/rampConstraints.ts @@ -60,4 +60,9 @@ export function* constraintGenerator( yield forCountries.includes(params.regionCode.countryCode) } + + // Paybis is not supported in the UK (Great Britain) + if (params.rampPluginId === 'paybis') { + yield params.regionCode.countryCode !== 'GB' + } } From a167d6782020a8b34a5d85023af5d0e24f2329d5 Mon Sep 17 00:00:00 2001 From: Sam Holmes Date: Tue, 14 Oct 2025 17:00:18 -0700 Subject: [PATCH 09/10] Restrict paypal to a set of countries The list of supported countries is copied from the `buyPluginList.json` and `sellPluginList.json`. --- src/plugins/ramps/rampConstraints.ts | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/plugins/ramps/rampConstraints.ts b/src/plugins/ramps/rampConstraints.ts index 214161246a2..250a0c90bcf 100644 --- a/src/plugins/ramps/rampConstraints.ts +++ b/src/plugins/ramps/rampConstraints.ts @@ -53,8 +53,20 @@ export function* constraintGenerator( FR, GR, HR, HU, IE, IS, IT, LI, LT, LU, LV, MT, NL, NO, PL, PT, RO, SE, SI, SK, JP, NZ, SG, CH, GB, IO, IM, JE, GG, GF, GP, YT, - MQ, RE, MF - ` + MQ, RE, MF ` + .replace(/\s/g, '') + .split(',') + + yield forCountries.includes(params.regionCode.countryCode) + } + + if (params.paymentType === 'paypal') { + const forCountries = ` + AE, AR, AT, AU, BE, BR, CA, CH, CL, CO, + CZ, DE, DK, EG, ES, FI, FR, GH, GR, HK, + HU, IE, IN, IT, JP, KE, KR, MX, MY, + NG, NL, NO, NZ, PE, PH, PL, PT, RU, SA, + SE, SG, TH, TR, TZ, US, VN, ZA` .replace(/\s/g, '') .split(',') From 3c951df77d9c57a36bf3fb1e5cbac58778070d2f Mon Sep 17 00:00:00 2001 From: Sam Holmes Date: Thu, 16 Oct 2025 11:08:06 -0700 Subject: [PATCH 10/10] Remove sell support for paybis This may be a temporary constraint, but Paybis does not support sell now. --- src/plugins/ramps/rampConstraints.ts | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/src/plugins/ramps/rampConstraints.ts b/src/plugins/ramps/rampConstraints.ts index 250a0c90bcf..ee35224d7cd 100644 --- a/src/plugins/ramps/rampConstraints.ts +++ b/src/plugins/ramps/rampConstraints.ts @@ -37,15 +37,6 @@ export function* constraintGenerator( yield params.regionCode.countryCode === 'US' } - // Filter out credit for sell in US for Paybis - if (params.rampPluginId === 'paybis') { - yield !( - params.direction === 'sell' && - params.regionCode.countryCode === 'US' && - params.paymentType === 'credit' - ) - } - // Constrain Revolut to the supported regions if (params.paymentType === 'revolut') { const forCountries = ` @@ -73,8 +64,25 @@ export function* constraintGenerator( yield forCountries.includes(params.regionCode.countryCode) } + // + // Paybis + // + // Paybis is not supported in the UK (Great Britain) if (params.rampPluginId === 'paybis') { yield params.regionCode.countryCode !== 'GB' } + // Disable sell for Paybis because they no longer support sell + if (params.rampPluginId === 'paybis') { + yield params.direction !== 'sell' + } + // Filter out credit for sell in US for Paybis (kept in-case they support sell + // again in the future) + if (params.rampPluginId === 'paybis') { + yield !( + params.direction === 'sell' && + params.regionCode.countryCode === 'US' && + params.paymentType === 'credit' + ) + } }