diff --git a/src/__tests__/modals/__snapshots__/HelpModal.test.tsx.snap b/src/__tests__/modals/__snapshots__/HelpModal.test.tsx.snap index f8bf7046fbc..5d081a69e8c 100644 --- a/src/__tests__/modals/__snapshots__/HelpModal.test.tsx.snap +++ b/src/__tests__/modals/__snapshots__/HelpModal.test.tsx.snap @@ -242,7 +242,7 @@ exports[`HelpModal should render with loading props 1`] = ` ] } > - Thanks for using Coinhub Bitcoin Wallet! + Thanks for using Coinhub Wallet! @@ -799,7 +799,7 @@ exports[`HelpModal should render with loading props 1`] = ` ] } > - Learn more about Coinhub Bitcoin Wallet + Learn more about Coinhub Wallet diff --git a/src/__tests__/modals/__snapshots__/LogsModal.test.tsx.snap b/src/__tests__/modals/__snapshots__/LogsModal.test.tsx.snap index c0644cc18bb..5f7a4369fc7 100644 --- a/src/__tests__/modals/__snapshots__/LogsModal.test.tsx.snap +++ b/src/__tests__/modals/__snapshots__/LogsModal.test.tsx.snap @@ -1166,7 +1166,7 @@ exports[`LogsModal should render with a logs modal 1`] = ` ] } > - Send to Coinhub Bitcoin Wallet + Send to Coinhub Wallet diff --git a/src/__tests__/scenes/__snapshots__/RequestScene.test.tsx.snap b/src/__tests__/scenes/__snapshots__/RequestScene.test.tsx.snap index e04ee7cb37c..0f971976e70 100644 --- a/src/__tests__/scenes/__snapshots__/RequestScene.test.tsx.snap +++ b/src/__tests__/scenes/__snapshots__/RequestScene.test.tsx.snap @@ -211,7 +211,7 @@ exports[`Request should render a blank scene with loaded props 1`] = ` ] } > - To buy, sell, and receive funds, please create a full account. Coinhub Bitcoin Wallet full accounts require no personal information and use a familiar username and password method that will safeguard your assets and help prevent loss of funds. + To buy, sell, and receive funds, please create a full account. Coinhub Wallet full accounts require no personal information and use a familiar username and password method that will safeguard your assets and help prevent loss of funds. Never share your username and password, and store your credentials securely! diff --git a/src/__tests__/scenes/__snapshots__/SendScene2.ui.test.tsx.snap b/src/__tests__/scenes/__snapshots__/SendScene2.ui.test.tsx.snap index 6fe60706e52..d71770f0464 100644 --- a/src/__tests__/scenes/__snapshots__/SendScene2.ui.test.tsx.snap +++ b/src/__tests__/scenes/__snapshots__/SendScene2.ui.test.tsx.snap @@ -15725,7 +15725,7 @@ exports[`SendScene2 Render SendScene 1`] = ` ] } > - Coinhub Bitcoin Wallet will not give financial advice. + Coinhub Wallet will not give financial advice. { > { > = { moonpay: 'guiPluginLogoMoonpay' } -type BuyProps = - | BuySellTabSceneProps<'pluginListBuyOld'> - | BuySellTabSceneProps<'pluginListBuy'> -type SellProps = - | BuySellTabSceneProps<'pluginListSellOld'> - | BuySellTabSceneProps<'pluginListSell'> +type BuyProps = BuySellTabSceneProps<'pluginListBuyOld'> +type SellProps = BuySellTabSceneProps<'pluginListSellOld'> type OwnProps = BuyProps | SellProps function isBuyProps(props: OwnProps): props is BuyProps { - return ( - props.route.name === 'pluginListBuyOld' || - props.route.name === 'pluginListBuy' - ) + return props.route.name === 'pluginListBuyOld' } interface StateProps { @@ -294,6 +288,16 @@ class GuiPluginList extends React.PureComponent { const { pluginId, paymentType, deepQuery = {} } = listRow const plugin = guiPlugins[pluginId] + // Some rows point at a partner website rather than an embedded purchase + // flow, so hand them to the device browser and stop here: + if (plugin.externalBrowser === true) { + await openBrowserUri( + makePluginUri(plugin, { deepPath: listRow.deepPath, deepQuery }) + ) + onPluginOpened() + return + } + // Don't allow light accounts to enter buy webview plugins const direction = this.getSceneDirection() if ( @@ -499,13 +503,13 @@ class GuiPluginList extends React.PureComponent { settlementTime={settlementTime} partner={partner} onPress={async () => { - await this.openPlugin(item) + await this.openPlugin(item).catch(this.handleError) }} onLongPress={async () => { await this.openPlugin(item, true).catch(this.handleError) }} onProviderPress={async () => { - await this.openPlugin(item) + await this.openPlugin(item).catch(this.handleError) }} /> @@ -834,19 +838,9 @@ const GuiPluginListSceneComponent = React.memo((props: OwnProps) => { }) // Export separate components for buy and sell routes -export const BuyScene = ( - props: BuySellTabSceneProps<'pluginListBuyOld'> -): React.ReactElement => -export const BuySceneLegacy = ( - props: BuySellTabSceneProps<'pluginListBuy'> -): React.ReactElement => ( - +export const BuyScene: React.FC = props => ( + ) -export const SellScene = ( - props: BuySellTabSceneProps<'pluginListSellOld'> -): React.ReactElement => -export const SellSceneLegacy = ( - props: BuySellTabSceneProps<'pluginListSell'> -): React.ReactElement => ( - +export const SellScene: React.FC = props => ( + ) diff --git a/src/constants/plugins/GuiPlugins.ts b/src/constants/plugins/GuiPlugins.ts index 7ec784ae1c4..081db335d8f 100644 --- a/src/constants/plugins/GuiPlugins.ts +++ b/src/constants/plugins/GuiPlugins.ts @@ -294,6 +294,22 @@ export const guiPlugins: Record = { baseUri: 'https://coinhubbitcoinwallet.app', displayName: 'Coinhub ATMs' }, + coinhubexchange: { + pluginId: 'coinhubexchange', + storeId: 'coinhubexchange', + baseUri: 'https://coinhubexchange.com', + lockUriPath: true, + externalBrowser: true, + displayName: 'Coinhub Exchange' + }, + coinhubfunded: { + pluginId: 'coinhubfunded', + storeId: 'coinhubfunded', + baseUri: 'https://coinhubfunded.com', + lockUriPath: true, + externalBrowser: true, + displayName: 'Coinhub Funded' + }, custom: { pluginId: 'custom', storeId: 'custom', diff --git a/src/constants/plugins/buyPluginListOverride.json b/src/constants/plugins/buyPluginListOverride.json index 88c5c3aea3f..57fe6d3c52c 100644 --- a/src/constants/plugins/buyPluginListOverride.json +++ b/src/constants/plugins/buyPluginListOverride.json @@ -14,18 +14,30 @@ "paymentTypeLogoKey": "cash" }, { - "id": "coinhub-teller", - "pluginId": "coinhub", - "deepPath": "/tellers", + "id": "coinhub-exchange", + "pluginId": "coinhubexchange", "paymentTypes": [ - "cash" + "bank" ], - "description": "Teller Transaction Limit: $500\nTotal Daily Limit: $1500\nTotal Monthly Limit: $5000", - "title": "Cash via Bitcoin Teller", + "description": "Bank wire and OTC desk\nOpens in your browser", + "title": "Coinhub Exchange / OTC", "forCountries": [ "US" ], - "paymentTypeLogoKey": "cash" + "paymentTypeLogoKey": "bank" + }, + { + "id": "coinhub-funded", + "pluginId": "coinhubfunded", + "paymentTypes": [ + "bank" + ], + "description": "Funded prop trading accounts\nOpens in your browser", + "title": "Coinhub Funded", + "forCountries": [ + "US" + ], + "paymentTypeLogoKey": "bank" }, { "id": "creditcard", diff --git a/src/constants/plugins/sellPluginListOverride.json b/src/constants/plugins/sellPluginListOverride.json index 411e2af5dfd..f3b49d18adb 100644 --- a/src/constants/plugins/sellPluginListOverride.json +++ b/src/constants/plugins/sellPluginListOverride.json @@ -12,5 +12,45 @@ "US" ], "paymentTypeLogoKey": "cash" + }, + { + "id": "ach", + "pluginId": "ach", + "paymentType": "ach", + "paymentTypes": [ + "ach" + ], + "title": "ACH Bank Transfer", + "description": "Fee: 1.5%\nSettlement: 2 - 3 days", + "forCountries": [ + "US" + ], + "notStateProvinces": { + "US": [ + "AK", + "AR", + "CT", + "LA", + "NC", + "NY", + "TX", + "FL" + ] + }, + "cryptoCodes": [], + "paymentTypeLogoKey": "bank" + }, + { + "id": "coinhub-exchange", + "pluginId": "coinhubexchange", + "paymentTypes": [ + "bank" + ], + "description": "Bank wire and OTC desk\nOpens in your browser", + "title": "Coinhub Exchange / OTC", + "forCountries": [ + "US" + ], + "paymentTypeLogoKey": "bank" } ] diff --git a/src/hooks/useRampPlugins.ts b/src/hooks/useRampPlugins.ts index 1480266c14b..222faa34401 100644 --- a/src/hooks/useRampPlugins.ts +++ b/src/hooks/useRampPlugins.ts @@ -5,13 +5,17 @@ import { useDispatch } from 'react-redux' import { updateFiatPurchaseCount } from '../actions/RequestReviewActions' import { ENV } from '../env' -import { pluginFactories } from '../plugins/ramps/allRampPlugins' +import { + credentiallessPluginIds, + pluginFactories +} from '../plugins/ramps/allRampPlugins' import type { RampPlugin, RampPluginConfig } from '../plugins/ramps/rampPluginTypes' import { createStore } from '../plugins/ramps/utils/createStore' import { getRampPluginStoreId } from '../plugins/ramps/utils/rampStoreIds' +import { config } from '../theme/appConfig' import type { Dispatch as ReduxDispatch } from '../types/reduxTypes' import type { NavigationBase } from '../types/routerTypes' import { logEvent } from '../util/tracking' @@ -44,6 +48,14 @@ export function useRampPlugins({ account }: UseRampPluginsOptions): { for (const [pluginId, factory] of Object.entries(pluginFactories)) { try { + // A white-label app carries only the providers it sells through: + if ( + config.rampPluginIds != null && + !config.rampPluginIds.includes(pluginId) + ) { + continue + } + // Get the appropriate store ID: // - Legacy plugins (e.g., paybis) use their old store IDs for backward compatibility // - New plugins automatically get 'ramp:${pluginId}' format @@ -53,13 +65,14 @@ export function useRampPlugins({ account }: UseRampPluginsOptions): { // Create a minimal config for the plugin const initOptions = ENV.RAMP_PLUGIN_INITS[pluginId] - // If there is no init option defined for the plugin, simply skip over it - if (initOptions == null) { + // If there is no init option defined for the plugin, simply skip + // over it, unless the plugin needs no credentials at all + if (initOptions == null && !credentiallessPluginIds.has(pluginId)) { continue } - const config: RampPluginConfig = { - initOptions, + const pluginConfig: RampPluginConfig = { + initOptions: initOptions ?? {}, store, account, navigation, @@ -72,7 +85,7 @@ export function useRampPlugins({ account }: UseRampPluginsOptions): { disklet: account.disklet } - const plugin = factory(config) + const plugin = factory(pluginConfig) loadedPlugins.push(plugin) } catch (error) { console.warn(`Failed to load plugin ${pluginId}:`, error) diff --git a/src/plugins/ramps/allRampPlugins.ts b/src/plugins/ramps/allRampPlugins.ts index 0555d3a1793..223ad091981 100644 --- a/src/plugins/ramps/allRampPlugins.ts +++ b/src/plugins/ramps/allRampPlugins.ts @@ -1,5 +1,8 @@ import { banxaRampPlugin } from './banxa/banxaRampPlugin' import { bitsofgoldRampPlugin } from './bitsofgold/bitsofgoldRampPlugin' +import { coinhubAtmRampPlugin } from './coinhub/coinhubAtmRampPlugin' +import { coinhubExchangeRampPlugin } from './coinhub/coinhubExchangeRampPlugin' +import { coinhubFundedRampPlugin } from './coinhub/coinhubFundedRampPlugin' import { infiniteRampPlugin } from './infinite/infiniteRampPlugin' import { libertyxRampPlugin } from './libertyx/libertyxRampPlugin' import { moonpayRampPlugin } from './moonpay/moonpayRampPlugin' @@ -11,6 +14,9 @@ import { simplexRampPlugin } from './simplex/simplexRampPlugin' export const pluginFactories: Record = { banxa: banxaRampPlugin, bitsofgold: bitsofgoldRampPlugin, + coinhubatm: coinhubAtmRampPlugin, + coinhubexchange: coinhubExchangeRampPlugin, + coinhubfunded: coinhubFundedRampPlugin, infinite: infiniteRampPlugin, libertyx: libertyxRampPlugin, moonpay: moonpayRampPlugin, @@ -18,3 +24,14 @@ export const pluginFactories: Record = { revolut: revolutRampPlugin, simplex: simplexRampPlugin } + +/** + * Plugins that hold no credentials and therefore need no `RAMP_PLUGIN_INITS` + * entry to load. Everything else stays off until the build's env.json supplies + * its keys. + */ +export const credentiallessPluginIds = new Set([ + 'coinhubatm', + 'coinhubexchange', + 'coinhubfunded' +]) diff --git a/src/plugins/ramps/coinhub/coinhubAtmRampPlugin.ts b/src/plugins/ramps/coinhub/coinhubAtmRampPlugin.ts new file mode 100644 index 00000000000..464abb41b0f --- /dev/null +++ b/src/plugins/ramps/coinhub/coinhubAtmRampPlugin.ts @@ -0,0 +1,39 @@ +import { guiPlugins } from '../../../constants/plugins/GuiPlugins' +import type { RampPluginFactory, SettlementRange } from '../rampPluginTypes' +import { createExternalRampPlugin } from '../utils/createExternalRampPlugin' +import { asInitOptions } from './coinhubRampTypes' + +const SETTLEMENT_INSTANT: SettlementRange = { + min: { value: 0, unit: 'minutes' }, + max: { value: 0, unit: 'minutes' } +} + +/** + * The Coinhub ATM network, reached through the Coinhub website. The machines + * take whichever asset the customer brings, so neither direction restricts the + * crypto assets. + */ +export const coinhubAtmRampPlugin: RampPluginFactory = config => { + const initOptions = asInitOptions(config.initOptions) + + return createExternalRampPlugin( + 'coinhubatm', + { + partnerIcon: initOptions.partnerIcon, + guiPlugin: guiPlugins.coinhub, + buy: { + paymentTypes: ['cash'], + countries: ['US'], + settlementRange: SETTLEMENT_INSTANT, + deepPath: '/buy-atms' + }, + sell: { + paymentTypes: ['cash'], + countries: ['US'], + settlementRange: SETTLEMENT_INSTANT, + deepPath: '/sell-atms' + } + }, + config + ) +} diff --git a/src/plugins/ramps/coinhub/coinhubExchangeRampPlugin.ts b/src/plugins/ramps/coinhub/coinhubExchangeRampPlugin.ts new file mode 100644 index 00000000000..3119dd50c41 --- /dev/null +++ b/src/plugins/ramps/coinhub/coinhubExchangeRampPlugin.ts @@ -0,0 +1,39 @@ +import { guiPlugins } from '../../../constants/plugins/GuiPlugins' +import type { RampPluginFactory, SettlementRange } from '../rampPluginTypes' +import { createExternalRampPlugin } from '../utils/createExternalRampPlugin' +import { asInitOptions } from './coinhubRampTypes' + +const SETTLEMENT_ONE_TO_FORTY_EIGHT_HOURS: SettlementRange = { + min: { value: 1, unit: 'hours' }, + max: { value: 48, unit: 'hours' } +} + +/** + * The Coinhub bank-wire and OTC desk. It is a full trading site rather than an + * embedded purchase flow, so `guiPlugins.coinhubexchange` sends it to the + * device browser. + */ +export const coinhubExchangeRampPlugin: RampPluginFactory = config => { + const initOptions = asInitOptions(config.initOptions) + + return createExternalRampPlugin( + 'coinhubexchange', + { + partnerIcon: initOptions.partnerIcon, + guiPlugin: guiPlugins.coinhubexchange, + buy: { + paymentTypes: ['wire'], + countries: ['US'], + settlementRange: SETTLEMENT_ONE_TO_FORTY_EIGHT_HOURS, + deepPath: '/' + }, + sell: { + paymentTypes: ['wire'], + countries: ['US'], + settlementRange: SETTLEMENT_ONE_TO_FORTY_EIGHT_HOURS, + deepPath: '/' + } + }, + config + ) +} diff --git a/src/plugins/ramps/coinhub/coinhubFundedRampPlugin.ts b/src/plugins/ramps/coinhub/coinhubFundedRampPlugin.ts new file mode 100644 index 00000000000..2d4f5e65b1d --- /dev/null +++ b/src/plugins/ramps/coinhub/coinhubFundedRampPlugin.ts @@ -0,0 +1,35 @@ +import { guiPlugins } from '../../../constants/plugins/GuiPlugins' +import type { RampPluginFactory, SettlementRange } from '../rampPluginTypes' +import { createExternalRampPlugin } from '../utils/createExternalRampPlugin' +import { asInitOptions } from './coinhubRampTypes' + +const SETTLEMENT_ONE_TO_FORTY_EIGHT_HOURS: SettlementRange = { + min: { value: 1, unit: 'hours' }, + max: { value: 48, unit: 'hours' } +} + +/** + * Coinhub Funded prop-trading accounts. Buy only, and like the exchange it is a + * whole website of its own, so it opens in the device browser. Its payment type + * differs from the exchange's on purpose: the quote scene renders one card per + * payment type, so sharing `wire` would bury one Coinhub destination inside the + * other's provider dropdown. + */ +export const coinhubFundedRampPlugin: RampPluginFactory = config => { + const initOptions = asInitOptions(config.initOptions) + + return createExternalRampPlugin( + 'coinhubfunded', + { + partnerIcon: initOptions.partnerIcon, + guiPlugin: guiPlugins.coinhubfunded, + buy: { + paymentTypes: ['iobank'], + countries: ['US'], + settlementRange: SETTLEMENT_ONE_TO_FORTY_EIGHT_HOURS, + deepPath: '/' + } + }, + config + ) +} diff --git a/src/plugins/ramps/coinhub/coinhubRampTypes.ts b/src/plugins/ramps/coinhub/coinhubRampTypes.ts new file mode 100644 index 00000000000..fe4f80c853b --- /dev/null +++ b/src/plugins/ramps/coinhub/coinhubRampTypes.ts @@ -0,0 +1,20 @@ +import { asObject, asOptional, asString } from 'cleaners' + +/** + * The Coinhub icon mark, inlined so the quote row's partner badge renders + * without a CDN round trip. Edge's content server carries no Coinhub asset, and + * the partner's own logo lives on a WordPress upload path that moves. + */ +const COINHUB_PARTNER_ICON = `data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAPwAAAEUCAYAAAAPy1s8AAAACXBIWXMAACE4AAAhOAFFljFgAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAfTSURBVHgB7d1PchTnGcfxt3smlHfmACCkBQo7cwOUGziLrK0jqIT3KHtbVk4Qskw2sU/g4QZkR1Cq9Mc5AN6lUlJ3tEjPtChjjUTPSMzv81m9jaYGWHzreRCj7urx3063y/9VbbtegJXS1M2kO9cFiCF4CDK+GPjb3UVbVc8KsFLqZjbXTXgIIngIIngIIngIIngIIngIIngIIngIIngIIngIIngIIngIIngIIngIIngIIngIIngIIngIIngIIngIIngIIngIIngIIngIIngIIngIIngIIngIIngIMi4LV/1jdm7eFeCSqlTr3bkt5VFZIBMegggegix8pW+r853u/K8/bEwKcMnmX0/2phdVeVEWyISHIIKHIIKHIIKHIIKHIIKHIIKHIIKHIEv4LP3yHO2s3+/O4/FnTwtcw8Nv3kzKijPhIYjgIchKrfT9Nb5tmh8LXE9VVpwJD0EED0EED0EED0EED0EED0EED0EED0EED0EED0EED0EED0EED0EED0EED0EED0EED0EED0EED0EED0EED0EED0EED0EED0EED0EED0HGF82/7C6qtp2UgY3Oy3EBPqipm0l3rpvhZ/Cl9y9ADMFDkJV6WuZPXz/Z6s6eHst1re2/9fRYYHUIHoIIHoIIHoIIHoKMC3dU9epaLy/ts6tfM897tuu9i0eFlWLCQxDBQxAr/R21tv/Preu8/nR3s73qNfO858nu5l53rkp5UUL9e+fx0+7c1PVBGUDdNDvd+cHB4etyC0x4CCJ4CGKlh1/Qjkf3pxdNM8f/gFzzPW+JCQ9BBA9BBA9BBA9BBA9BBA9BBA9BBA9BBA9BBA9BBA9BBA9BBA9BBA9B/HjsTVXlL9NjNXsC720Z6jFJTTP7u4zHZfLLr2mnd4Opqtm5tOXL3ss+L9w5JjwEETwEsdLfUNuW4+689u2bSVkRGwdvjnuXxx942aRc4XT38fbsqtrrfcm97m+RCQ9BBA9BrPQsxNr+4cvufLSz/n13Ho3uze7x3pavCktlwkMQwUMQK/0NVaXa6s79xzN9SNP8d7rKbhwcvytB3vv7bneH093Np71f/6KwcCY8BBE8BLHS31g7ffxQVcqVjyIa1femnzO/+K71Vncear2f5+mxczqZnqoyfdrp2rdvvy9Dq8re9NyWvxcWzoSHIIKHIFb65Zl+F3o8/qz/3elJuVtmn3Xvrdmnzzd/352HWu/773PxT5LC4pnwEETwEMRKz3zactC7Gv479iyFCQ9BBA9BrPTMa8F3qqlezc7tlR9k4mZMeAgieAhipeeOsMYvgwkPQQQPQaz0zOuHMrDL965nGUx4CCJ4CGKl59f83B3Om3qnDOBoZ/3+7Ko6KCyVCQ9BBA9BrPS8Z/aZ9rppZjexvPxU2Wvpr/Gj+t6k96XPC0tlwkMQwUMQK/2KWNt/W5U76r013iOlbpEJD0EED0Gs9CzceVNPH7M1GjV70y+05avCUpnwEETwEMRKz8JtXP7QznZ3ON19PJn9cvXnwsKZ8BBE8BDESr8iTnY398rAqqq87s5DPTG2b23/8GV3vljv+7+z9X5BTHgIIngIYqVfEVUpL8rQ2tnxdHfzpDvXTTv9IM2Dg8PXZQCX1vvnm1u9P4MP5wzIhIcggocgVnrmNX16bFNXk+58tPPkaXfe+Ii74vSdn9d73XlUN1b6AZnwEETwEMRKz01Mbz45rs7796sf5N71/X8a9P93oPT+WcHNmPAQRPAQxErPR2mr+mlZqOq497tZ6T+SCQ9BBA9BrPR8pPZd4ZNhwkMQwUMQKz0fqR38TjgsjgkPQQQPQaz03MQP3aF/p5qhHO2s359dtc8KgzHhIYjgIYiVfnl+7g5nZ+W4fGKqtv1Td3743eEgPwb7IaP6N18WFsKEhyCChyBW+gG0pfzxqteMmtkHVNYGutljX1XXvysDOzv7z/Se8xsHxwv9zPzl78xXe4WFMOEhiOAhiJV+AI/23+6VW/bwmzeT8onpr/Gj+t6k9yV3tlkQEx6CCB6CWOlZqqOdJ+vdeVQ3/R+t/aKwcCY8BBE8BLHSsxCnzzf7n4efnVtPg71NJjwEETwEsdIP4GR3c68MrGnql915Y47P3s/zZ5jnA0Knu4+3Z1f1drmW3t1p2sIdZMJDEMFDECv9AKpSXpSBjcdl0rs8vur1c/4Z9q56QVuq9dl7uoHkqjHhIYjgIYjgIYjgIYjgIYjgIYjgIYjgIYjgIYjgIYjgIYjgIYjgIYjgIYgfj72jmqZ92p1/+vrJla9vm+bK11y8z9aV79M267OLwoox4SGI4CGIlf6Oqkr7XXdum2F264u1/8dCNBMegggegggegggegggegggegggegggegggegggegggegggegggegggegggegggegggegggegggegggegggegggegggegggegggegggegggegggegggegggegggegggegggegggegggegggegggegggegozLCqnOzt9157auXxXgEhMegggegqzUSv/g4PB173KrAJeY8BBE8BBkpVb6ZTrd/e1kdtU+K6yUtmnKKjLhIYjgIYjgIYjgIYjgIYjgIYjgIYjgIYjgIYjgIYjgIYjgIYjgIYjgIYjgIYjgIYjgIYg73txY87I7taWaFLjC+Vk5LrfMhIcggocg/wNwIkuQbkZPRQAAAABJRU5ErkJggg==` + +export interface InitOptions { + readonly partnerIcon: string +} + +/** + * The Coinhub destinations carry no credentials, so the init options exist only + * to let the build swap the icon without an app release. + */ +export const asInitOptions = asObject({ + partnerIcon: asOptional(asString, COINHUB_PARTNER_ICON) +}).withRest diff --git a/src/plugins/ramps/utils/createExternalRampPlugin.ts b/src/plugins/ramps/utils/createExternalRampPlugin.ts index 27322ba2c4c..95aecf37afd 100644 --- a/src/plugins/ramps/utils/createExternalRampPlugin.ts +++ b/src/plugins/ramps/utils/createExternalRampPlugin.ts @@ -3,6 +3,8 @@ import type { EdgeTokenId } from 'edge-core-js' import type { PluginViewParams } from '../../../components/scenes/GuiPluginViewScene' import { lstrings } from '../../../locales/strings' import type { GuiPlugin } from '../../../types/GuiPluginTypes' +import { makePluginUri } from '../../../util/GuiPluginTools' +import { openBrowserUri } from '../../../util/WebUtils' import type { FiatPaymentType } from '../../gui/fiatPluginTypes' import type { RampCheckSupportRequest, @@ -50,8 +52,11 @@ export interface DirectionSupportData { readonly excludedStates?: Record /** The fiat currency codes supported by the plugin */ readonly fiatCurrencyCodes?: string[] - /** The crypto assets supported by the plugin */ - readonly cryptoAssets: ReadonlyArray<{ + /** + * The crypto assets supported by the plugin. Omit for a provider that takes + * any asset the app carries, such as a cash ATM network. + */ + readonly cryptoAssets?: ReadonlyArray<{ readonly pluginId: string readonly tokenId: EdgeTokenId }> @@ -165,6 +170,19 @@ export function createExternalRampPlugin( deepQuery: support.deepQuery } + // A partner destination that is a whole website of its own never + // enters the EdgeProvider WebView, which would hand a third-party + // site the wallet bridge: + if (externalConfig.guiPlugin.externalBrowser === true) { + await openBrowserUri( + makePluginUri(externalConfig.guiPlugin, { + deepPath: support.deepPath, + deepQuery: support.deepQuery + }) + ) + return + } + if (request.direction === 'buy') { config.navigation.navigate('pluginViewBuy', navigationProps) } else { @@ -213,9 +231,12 @@ function normalizeFiatCode(currencyCode: string): string { } function matchesCryptoAsset( - assets: ReadonlyArray<{ pluginId: string; tokenId: string | null }>, + assets: + | ReadonlyArray<{ pluginId: string; tokenId: string | null }> + | undefined, asset: { pluginId: string; tokenId: string | null } ): boolean { + if (assets == null) return true return assets.some( item => item.pluginId === asset.pluginId && item.tokenId === asset.tokenId ) diff --git a/src/theme/coinhubConfig.ts b/src/theme/coinhubConfig.ts index 4daf1327bb8..c58d1550e28 100644 --- a/src/theme/coinhubConfig.ts +++ b/src/theme/coinhubConfig.ts @@ -4,8 +4,8 @@ import { coinhubLight } from './variables/coinhubLight' export const coinhubConfig: AppConfig = { appId: 'app.coinhubatm.wallet', - appName: 'Coinhub Bitcoin Wallet', - appNameShort: 'Coinhub Bitcoin Wallet', + appName: 'Coinhub Wallet', + appNameShort: 'Coinhub Wallet', appStore: 'https://itunes.apple.com/app/id6444903066', backupAccountSite: 'https://edge.app/light-account-creation/', configName: 'coinhub', @@ -36,7 +36,9 @@ export const coinhubConfig: AppConfig = { website: 'https://coinhubatm.app', disableSwaps: true, disableSurveyModal: true, - useLegacyBuySell: true, + // Coinhub's own destinations, plus MoonPay for the card and ACH quotes its + // customers already had under the legacy buy/sell list: + rampPluginIds: ['coinhubatm', 'coinhubexchange', 'coinhubfunded', 'moonpay'], extraTab: { webviewUrl: 'https://coinhubbitcoinwallet.app/buy-atms', tabTitleKey: 'title_map', diff --git a/src/types/GuiPluginTypes.ts b/src/types/GuiPluginTypes.ts index 831fd943152..ba2068b3cf8 100644 --- a/src/types/GuiPluginTypes.ts +++ b/src/types/GuiPluginTypes.ts @@ -108,6 +108,13 @@ export interface GuiPlugin { * Plugin would show only with BETA_FEATURE env flag */ betaOnly?: boolean + + /** + * Hand the URI to the device browser instead of opening the in-app WebView. + * Use for partner destinations that are whole websites of their own rather + * than an embedded purchase flow. + */ + externalBrowser?: boolean } /** diff --git a/src/types/types.ts b/src/types/types.ts index 62b30e9c0cf..6f5b1cb61ac 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -384,7 +384,12 @@ export interface AppConfig { referralAppShareUrl?: string disableSwaps?: boolean disableSurveyModal?: boolean - useLegacyBuySell?: boolean + /** + * Ramp plugin ids this app sells through. Leave unset to carry every ramp + * plugin the build has credentials for; a white-label app lists exactly the + * providers its partner has signed. + */ + rampPluginIds?: string[] /** * Support article for pending transactions "Learn more" link. */