From 381944150c7cea995d3ae7fd3e65fee4640f9080 Mon Sep 17 00:00:00 2001 From: Jonathan Tzeng Date: Thu, 25 Jun 2026 16:01:53 -0700 Subject: [PATCH] Add DFX fiat ramp provider Integrate DFX (app.dfx.swiss) as a SEPA-based buy/sell fiat ramp using the external ramp webview pattern. Registers the dfx ramp plugin and adds its GUI plugin entry. Like every ramp plugin it is enabled through the rampPlugins map (config.json plus keys.json). Supports EUR/CHF across the SEPA region plus Switzerland and Liechtenstein for BTC, ETH and major EVM chains, Solana and Monero. --- CHANGELOG.md | 1 + src/constants/plugins/GuiPlugins.ts | 7 ++ src/docs/ramps-deeplink-provider-priority.md | 2 +- src/plugins/ramps/allRampPlugins.ts | 2 + src/plugins/ramps/dfx/dfxRampPlugin.ts | 89 ++++++++++++++++++++ src/plugins/ramps/dfx/dfxRampTypes.ts | 13 +++ 6 files changed, 113 insertions(+), 1 deletion(-) create mode 100644 src/plugins/ramps/dfx/dfxRampPlugin.ts create mode 100644 src/plugins/ramps/dfx/dfxRampTypes.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 597e7c137fb..eff25fb8bb6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ - added: Logbox disable option to env.json - added: Reverse-resolve recipient addresses to ENS / Unstoppable Domains / ZNS names in the send flow, address modal, and transaction history. - added: Warning confirmation when sending a zero amount on EVM chains, since the transaction still spends gas. +- added: DFX (app.dfx.swiss) as a SEPA fiat buy/sell ramp provider. - added: Remote enable/disable of gift card providers via the info server's giftCardInfo config, supporting whole-provider disabling for Phaze and Bitrefill and per-brand disabling for Phaze. - added: Exchange deep links (`edge://exchange/buy|sell|swap`) that open the flow with the asset pre-selected - added: Promo attribution for buy, sell and swap flows opened from deep links and promo cards diff --git a/src/constants/plugins/GuiPlugins.ts b/src/constants/plugins/GuiPlugins.ts index 7ec784ae1c4..1eb75445301 100644 --- a/src/constants/plugins/GuiPlugins.ts +++ b/src/constants/plugins/GuiPlugins.ts @@ -94,6 +94,13 @@ export const guiPlugins: Record = { displayName: 'Bits of Gold', permissions: ['camera'] }, + dfx: { + pluginId: 'dfx', + storeId: 'dfx', + baseUri: 'https://app.dfx.swiss', + displayName: 'DFX', + permissions: ['camera'] + }, creditcard: { pluginId: 'amountquote', storeId: '', diff --git a/src/docs/ramps-deeplink-provider-priority.md b/src/docs/ramps-deeplink-provider-priority.md index 0317ac94321..4a403297cef 100644 --- a/src/docs/ramps-deeplink-provider-priority.md +++ b/src/docs/ramps-deeplink-provider-priority.md @@ -373,7 +373,7 @@ The ordering and the badge are each simple rules, but their product across the i Both blocks below are cards inside the `promoCards2` array of the [info server](#info-server) rollup document in the `info_data` CouchDB database. `promoCards2` is a healing array, so a card that fails the cleaner is dropped silently and the rest still load. `background` is required on every card, including the silent one. -[Ramp plugin](#ramp-plugin) ids usable in `preferProviders`: `banxa`, `bitsofgold`, `infinite`, `libertyx`, `moonpay`, `paybis`, `revolut`, `simplex`. +[Ramp plugin](#ramp-plugin) ids usable in `preferProviders`: `banxa`, `bitsofgold`, `dfx`, `infinite`, `libertyx`, `moonpay`, `paybis`, `revolut`, `simplex`. ### 6.1 Visible card with a deep link call-to-action diff --git a/src/plugins/ramps/allRampPlugins.ts b/src/plugins/ramps/allRampPlugins.ts index 0555d3a1793..514700cf5e7 100644 --- a/src/plugins/ramps/allRampPlugins.ts +++ b/src/plugins/ramps/allRampPlugins.ts @@ -1,5 +1,6 @@ import { banxaRampPlugin } from './banxa/banxaRampPlugin' import { bitsofgoldRampPlugin } from './bitsofgold/bitsofgoldRampPlugin' +import { dfxRampPlugin } from './dfx/dfxRampPlugin' import { infiniteRampPlugin } from './infinite/infiniteRampPlugin' import { libertyxRampPlugin } from './libertyx/libertyxRampPlugin' import { moonpayRampPlugin } from './moonpay/moonpayRampPlugin' @@ -11,6 +12,7 @@ import { simplexRampPlugin } from './simplex/simplexRampPlugin' export const pluginFactories: Record = { banxa: banxaRampPlugin, bitsofgold: bitsofgoldRampPlugin, + dfx: dfxRampPlugin, infinite: infiniteRampPlugin, libertyx: libertyxRampPlugin, moonpay: moonpayRampPlugin, diff --git a/src/plugins/ramps/dfx/dfxRampPlugin.ts b/src/plugins/ramps/dfx/dfxRampPlugin.ts new file mode 100644 index 00000000000..fa4e1d75628 --- /dev/null +++ b/src/plugins/ramps/dfx/dfxRampPlugin.ts @@ -0,0 +1,89 @@ +import { guiPlugins } from '../../../constants/plugins/GuiPlugins' +import type { RampPluginFactory, SettlementRange } from '../rampPluginTypes' +import { + createExternalRampPlugin, + type DirectionSupportData +} from '../utils/createExternalRampPlugin' +import { asInitOptions } from './dfxRampTypes' + +// DFX settles SEPA bank transfers within a few business days. +const SETTLEMENT_ONE_TO_THREE_DAYS: SettlementRange = { + min: { value: 1, unit: 'days' }, + max: { value: 3, unit: 'days' } +} + +// EEA countries plus Switzerland where DFX accepts bank transfers. Bulgaria +// is left out: DFX's country list (api.dfx.swiss/v1/country) disallows IBAN, +// bank and KYC there. +const DFX_COUNTRIES = [ + 'AT', + 'BE', + 'CH', + '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' +] + +const DFX_FIAT_CURRENCY_CODES = ['EUR', 'CHF'] + +const DFX_CRYPTO_ASSETS = [ + { pluginId: 'bitcoin', tokenId: null }, + { pluginId: 'ethereum', tokenId: null }, + { pluginId: 'arbitrum', tokenId: null }, + { pluginId: 'optimism', tokenId: null }, + { pluginId: 'polygon', tokenId: null }, + { pluginId: 'base', tokenId: null }, + { pluginId: 'binancesmartchain', tokenId: null }, + { pluginId: 'solana', tokenId: null }, + { pluginId: 'monero', tokenId: null } +] + +export const dfxRampPlugin: RampPluginFactory = config => { + const initOptions = asInitOptions(config.initOptions) + const support: Omit = { + paymentTypes: ['sepa'], + countries: DFX_COUNTRIES, + fiatCurrencyCodes: DFX_FIAT_CURRENCY_CODES, + cryptoAssets: DFX_CRYPTO_ASSETS, + settlementRange: SETTLEMENT_ONE_TO_THREE_DAYS, + deepQuery: { + wallet: initOptions.wallet, + lang: 'en' + } + } + + return createExternalRampPlugin( + 'dfx', + { + guiPlugin: guiPlugins.dfx, + partnerIcon: initOptions.partnerIcon, + buy: { ...support, deepPath: '/buy' }, + sell: { ...support, deepPath: '/sell' } + }, + config + ) +} diff --git a/src/plugins/ramps/dfx/dfxRampTypes.ts b/src/plugins/ramps/dfx/dfxRampTypes.ts new file mode 100644 index 00000000000..c592d1ace06 --- /dev/null +++ b/src/plugins/ramps/dfx/dfxRampTypes.ts @@ -0,0 +1,13 @@ +import { asObject, asOptional, asString } from 'cleaners' + +export interface InitOptions { + readonly partnerIcon: string + // Partner/wallet identifier sent to the DFX widget. Override per build so + // white-label apps do not identify as Edge. + readonly wallet: string +} + +export const asInitOptions = asObject({ + partnerIcon: asOptional(asString, `https://content.edge.app/dfx-logo.png`), + wallet: asOptional(asString, 'Edge') +}).withRest