1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-11-22 01:47:00 +01:00

Ability to buy tokens with Coinbase Pay and Transak (#15551)

Co-authored-by: Ariella Vu <20778143+digiwand@users.noreply.github.com>
Co-authored-by: Brad Decker <git@braddecker.dev>
Co-authored-by: Brad Decker <bhdecker84@gmail.com>
This commit is contained in:
Nicolas Ferro 2022-09-19 17:00:57 +02:00 committed by GitHub
parent 075a6fb86a
commit 1b563188bf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 855 additions and 709 deletions

View File

@ -53,16 +53,16 @@ const createWyrePurchaseUrl = async (walletAddress, chainId) => {
*
* @param {string} walletAddress - Ethereum destination address
* @param {string} chainId - Current chain ID
* @param {string|undefined} symbol - Token symbol to buy
* @returns String
*/
const createTransakUrl = (walletAddress, chainId) => {
const { transakCurrencies, network } = BUYABLE_CHAINS_MAP[chainId];
const createTransakUrl = (walletAddress, chainId, symbol) => {
const { nativeCurrency, network } = BUYABLE_CHAINS_MAP[chainId];
const queryParams = new URLSearchParams({
apiKey: TRANSAK_API_KEY,
hostURL: 'https://metamask.io',
cryptoCurrencyList: transakCurrencies.join(','),
defaultCryptoCurrency: transakCurrencies[0],
defaultCryptoCurrency: symbol || nativeCurrency,
networks: network,
walletAddress,
});
@ -115,17 +115,20 @@ const createMoonPayUrl = async (walletAddress, chainId) => {
*
* @param {string} walletAddress - Ethereum destination address
* @param {string} chainId - Current chain ID
* @param {string|undefined} symbol - Token symbol to buy
* @returns String
*/
const createCoinbasePayUrl = (walletAddress, chainId) => {
const { coinbasePayCurrencies } = BUYABLE_CHAINS_MAP[chainId];
const createCoinbasePayUrl = (walletAddress, chainId, symbol) => {
// since coinbasePayCurrencies is going to be extended to include all tokens supported
// we now default to nativeCurrency instead of the 2 previous tokens + eth that we had before
const { nativeCurrency } = BUYABLE_CHAINS_MAP[chainId];
const queryParams = new URLSearchParams({
appId: COINBASEPAY_API_KEY,
attribution: 'extension',
destinationWallets: JSON.stringify([
{
address: walletAddress,
assets: coinbasePayCurrencies,
assets: symbol ? [symbol] : [nativeCurrency],
},
]),
});
@ -139,10 +142,11 @@ const createCoinbasePayUrl = (walletAddress, chainId) => {
* @param {string} opts.chainId - The chainId for which to return a url
* @param {string} opts.address - The address the bought ETH should be sent to. Only relevant if chainId === '0x1'.
* @param opts.service
* @param {string|undefined} opts.symbol - The symbol of the token to buy. Only relevant if buying a token.
* @returns {string|undefined} The url at which the user can access ETH, while in the given chain. If the passed
* chainId does not match any of the specified cases, or if no chainId is given, returns undefined.
*/
export default async function getBuyUrl({ chainId, address, service }) {
export default async function getBuyUrl({ chainId, address, service, symbol }) {
// default service by network if not specified
if (!service) {
// eslint-disable-next-line no-param-reassign
@ -153,11 +157,11 @@ export default async function getBuyUrl({ chainId, address, service }) {
case 'wyre':
return await createWyrePurchaseUrl(address, chainId);
case 'transak':
return createTransakUrl(address, chainId);
return createTransakUrl(address, chainId, symbol);
case 'moonpay':
return createMoonPayUrl(address, chainId);
case 'coinbase':
return createCoinbasePayUrl(address, chainId);
return createCoinbasePayUrl(address, chainId, symbol);
case 'metamask-faucet':
return 'https://faucet.metamask.io/';
case 'rinkeby-faucet':

View File

@ -62,36 +62,27 @@ describe('buy-url', () => {
it('returns Transak url with an ETH address for Ethereum mainnet', async () => {
const transakUrl = await getBuyUrl({ ...MAINNET, service: 'transak' });
const buyableChain = BUYABLE_CHAINS_MAP[MAINNET.chainId];
const buyableCurrencies = encodeURIComponent(
buyableChain.transakCurrencies.join(','),
);
expect(transakUrl).toStrictEqual(
`https://global.transak.com/?apiKey=${TRANSAK_API_KEY}&hostURL=https%3A%2F%2Fmetamask.io&cryptoCurrencyList=${buyableCurrencies}&defaultCryptoCurrency=${buyableChain.transakCurrencies[0]}&networks=${buyableChain.network}&walletAddress=${ETH_ADDRESS}`,
`https://global.transak.com/?apiKey=${TRANSAK_API_KEY}&hostURL=https%3A%2F%2Fmetamask.io&defaultCryptoCurrency=${buyableChain.transakCurrencies[0]}&networks=${buyableChain.network}&walletAddress=${ETH_ADDRESS}`,
);
});
it('returns Transak url with an BNB address for Binance Smart Chain', async () => {
const transakUrl = await getBuyUrl({ ...BSC, service: 'transak' });
const buyableChain = BUYABLE_CHAINS_MAP[BSC.chainId];
const buyableCurrencies = encodeURIComponent(
buyableChain.transakCurrencies.join(','),
);
expect(transakUrl).toStrictEqual(
`https://global.transak.com/?apiKey=${TRANSAK_API_KEY}&hostURL=https%3A%2F%2Fmetamask.io&cryptoCurrencyList=${buyableCurrencies}&defaultCryptoCurrency=${buyableChain.transakCurrencies[0]}&networks=${buyableChain.network}&walletAddress=${ETH_ADDRESS}`,
`https://global.transak.com/?apiKey=${TRANSAK_API_KEY}&hostURL=https%3A%2F%2Fmetamask.io&defaultCryptoCurrency=${buyableChain.transakCurrencies[0]}&networks=${buyableChain.network}&walletAddress=${ETH_ADDRESS}`,
);
});
it('returns Transak url with an MATIC address for Polygon', async () => {
const transakUrl = await getBuyUrl({ ...POLYGON, service: 'transak' });
const buyableChain = BUYABLE_CHAINS_MAP[POLYGON.chainId];
const buyableCurrencies = encodeURIComponent(
buyableChain.transakCurrencies.join(','),
);
expect(transakUrl).toStrictEqual(
`https://global.transak.com/?apiKey=${TRANSAK_API_KEY}&hostURL=https%3A%2F%2Fmetamask.io&cryptoCurrencyList=${buyableCurrencies}&defaultCryptoCurrency=${buyableChain.transakCurrencies[0]}&networks=${buyableChain.network}&walletAddress=${ETH_ADDRESS}`,
`https://global.transak.com/?apiKey=${TRANSAK_API_KEY}&hostURL=https%3A%2F%2Fmetamask.io&defaultCryptoCurrency=${buyableChain.transakCurrencies[0]}&networks=${buyableChain.network}&walletAddress=${ETH_ADDRESS}`,
);
});

View File

@ -459,6 +459,9 @@
"ui/components/app/currency-input/currency-input.stories.js",
"ui/components/app/currency-input/currency-input.test.js",
"ui/components/app/currency-input/index.js",
"ui/components/app/deposit-popover/on-ramp-item.js",
"ui/components/app/deposit-popover/deposit-popover.js",
"ui/components/app/deposit-popover/index.js",
"ui/components/app/detected-token/detected-token-address/detected-token-address.js",
"ui/components/app/detected-token/detected-token-address/detected-token-address.stories.js",
"ui/components/app/detected-token/detected-token-address/detected-token-address.test.js",
@ -624,9 +627,6 @@
"ui/components/app/modals/convert-token-to-nft-modal/convert-token-to-nft-modal.js",
"ui/components/app/modals/customize-nonce/customize-nonce.component.js",
"ui/components/app/modals/customize-nonce/index.js",
"ui/components/app/modals/deposit-ether-modal/deposit-ether-modal.component.js",
"ui/components/app/modals/deposit-ether-modal/deposit-ether-modal.container.js",
"ui/components/app/modals/deposit-ether-modal/index.js",
"ui/components/app/modals/edit-approval-permission/edit-approval-permission.component.js",
"ui/components/app/modals/edit-approval-permission/edit-approval-permission.container.js",
"ui/components/app/modals/edit-approval-permission/index.js",

View File

@ -18,6 +18,11 @@ export type ChainId = typeof CHAIN_IDS[keyof typeof CHAIN_IDS];
* or dapp may supply their own symbol.
*/
type CurrencySymbol = typeof CURRENCY_SYMBOLS[keyof typeof CURRENCY_SYMBOLS];
/**
* A type that is a union type for the supported symbols on different onramp providers.
*/
type SupportedCurrencySymbol =
typeof SUPPORTED_CURRENCY_SYMBOLS[keyof typeof SUPPORTED_CURRENCY_SYMBOLS];
/**
* For certain specific situations we need the above type, but with all symbols
* in lowercase format.
@ -137,7 +142,7 @@ type BuyableChainSettings = {
/**
* The list of supported currencies for the Transak onramp provider
*/
transakCurrencies?: CurrencySymbol[];
transakCurrencies?: SupportedCurrencySymbol[];
/**
* A configuration object for the MoonPay onramp provider
*/
@ -149,7 +154,7 @@ type BuyableChainSettings = {
/**
* The list of supported currencies for the CoinbasePay onramp provider
*/
coinbasePayCurrencies?: CurrencySymbol[];
coinbasePayCurrencies?: SupportedCurrencySymbol[];
};
/**
@ -293,6 +298,121 @@ export const CURRENCY_SYMBOLS = {
WETH: 'WETH',
} as const;
/**
* An object containing the token symbols for various tokens that are supported
* on different on ramp providers. This object is meant for internal consumption,
* hence why it is not exported.
*/
const SUPPORTED_CURRENCY_SYMBOLS = {
...CURRENCY_SYMBOLS,
'1INCH': '1INCH',
AAVE: 'AAVE',
ABT: 'ABT',
ACH: 'ACH',
AGEUR: 'AGEUR',
AGLD: 'AGLD',
AMP: 'AMP',
ANKR: 'ANKR',
APE: 'APE',
ARPA: 'ARPA',
ASM: 'ASM',
AUCTION: 'AUCTION',
AXS: 'AXS',
BADGER: 'BADGER',
BAL: 'BAL',
BAND: 'BAND',
BAT: 'BAT',
BNT: 'BNT',
BOBA: 'BOBA',
BOND: 'BOND',
BTRST: 'BTRST',
CHAIN: 'CHAIN',
CHZ: 'CHZ',
CLV: 'CLV',
COMP: 'COMP',
COTI: 'COTI',
CRO: 'CRO',
CRV: 'CRV',
CTSI: 'CTSI',
CVC: 'CVC',
DAO: 'DAO',
DDX: 'DDX',
DNT: 'DNT',
ENJ: 'ENJ',
ENS: 'ENS',
EURT: 'EURT',
FARM: 'FARM',
FET: 'FET',
FORTH: 'FORTH',
FX: 'FX',
GNO: 'GNO',
GRT: 'GRT',
GTC: 'GTC',
GTH: 'GTH',
HEX: 'HEX',
IOTX: 'IOTX',
JASMY: 'JASMY',
KEEP: 'KEEP',
KNC: 'KNC',
KRL: 'KRL',
LCX: 'LCX',
LINK: 'LINK',
LPT: 'LPT',
LRC: 'LRC',
MANA: 'MANA',
MASK: 'MASK',
MINDS: 'MINDS',
MIR: 'MIR',
MKR: 'MKR',
MLN: 'MLN',
MTL: 'MTL',
NKN: 'NKN',
NMR: 'NMR',
NU: 'NU',
OGN: 'OGN',
OMG: 'OMG',
OXT: 'OXT',
PAX: 'PAX',
PERP: 'PERP',
PLA: 'PLA',
POLS: 'POLS',
POLY: 'POLY',
QNT: 'QNT',
QUICK: 'QUICK',
RAD: 'RAD',
RAI: 'RAI',
RARI: 'RARI',
REN: 'REN',
REP: 'REP',
REQ: 'REQ',
RLC: 'RLC',
RLY: 'RLY',
SAND: 'SAND',
SHIB: 'SHIB',
SKL: 'SKL',
SNX: 'SNX',
STETH: 'STETH',
STORJ: 'STORJ',
SUKU: 'SUKU',
SUSHI: 'SUSHI',
SWAP: 'SWAP',
SWFTC: 'SWFTC',
TRAC: 'TRAC',
TRB: 'TRB',
TRIBE: 'TRIBE',
TRU: 'TRU',
TXL: 'TXL',
UMA: 'UMA',
UNI: 'UNI',
VRA: 'VRA',
WBTC: 'WBTC',
WCFG: 'WCFG',
XYO: 'XYO',
YFII: 'YFII',
YLD: 'YLD',
ZRX: 'ZRX',
} as const;
export const ETH_TOKEN_IMAGE_URL = './images/eth_logo.svg';
export const TEST_ETH_TOKEN_IMAGE_URL = './images/black-eth-logo.svg';
export const BNB_TOKEN_IMAGE_URL = './images/bnb.png';
@ -529,7 +649,41 @@ export const BUYABLE_CHAINS_MAP: {
[CHAIN_IDS.MAINNET]: {
nativeCurrency: CURRENCY_SYMBOLS.ETH,
network: BUYABLE_CHAIN_ETHEREUM_NETWORK_NAME,
transakCurrencies: [CURRENCY_SYMBOLS.ETH, 'USDT', 'USDC', 'DAI'],
transakCurrencies: [
SUPPORTED_CURRENCY_SYMBOLS.ETH,
SUPPORTED_CURRENCY_SYMBOLS['1INCH'],
SUPPORTED_CURRENCY_SYMBOLS.AAVE,
SUPPORTED_CURRENCY_SYMBOLS.AGEUR,
SUPPORTED_CURRENCY_SYMBOLS.BUSD,
SUPPORTED_CURRENCY_SYMBOLS.CHAIN,
SUPPORTED_CURRENCY_SYMBOLS.CLV,
SUPPORTED_CURRENCY_SYMBOLS.COMP,
SUPPORTED_CURRENCY_SYMBOLS.CTSI,
SUPPORTED_CURRENCY_SYMBOLS.DAI,
SUPPORTED_CURRENCY_SYMBOLS.DAO,
SUPPORTED_CURRENCY_SYMBOLS.ENJ,
SUPPORTED_CURRENCY_SYMBOLS.EURT,
SUPPORTED_CURRENCY_SYMBOLS.GTH,
SUPPORTED_CURRENCY_SYMBOLS.HEX,
SUPPORTED_CURRENCY_SYMBOLS.LINK,
SUPPORTED_CURRENCY_SYMBOLS.MANA,
SUPPORTED_CURRENCY_SYMBOLS.MASK,
SUPPORTED_CURRENCY_SYMBOLS.MINDS,
SUPPORTED_CURRENCY_SYMBOLS.MKR,
SUPPORTED_CURRENCY_SYMBOLS.PLA,
SUPPORTED_CURRENCY_SYMBOLS.POLS,
SUPPORTED_CURRENCY_SYMBOLS.SAND,
SUPPORTED_CURRENCY_SYMBOLS.STETH,
SUPPORTED_CURRENCY_SYMBOLS.SUSHI,
SUPPORTED_CURRENCY_SYMBOLS.SWAP,
SUPPORTED_CURRENCY_SYMBOLS.TXL,
SUPPORTED_CURRENCY_SYMBOLS.UNI,
SUPPORTED_CURRENCY_SYMBOLS.USDC,
SUPPORTED_CURRENCY_SYMBOLS.USDT,
SUPPORTED_CURRENCY_SYMBOLS.VRA,
SUPPORTED_CURRENCY_SYMBOLS.WBTC,
SUPPORTED_CURRENCY_SYMBOLS.YLD,
],
moonPay: {
defaultCurrencyCode: 'eth',
showOnlyCurrencies: 'eth,usdt,usdc,dai',
@ -538,7 +692,106 @@ export const BUYABLE_CHAINS_MAP: {
srn: 'ethereum',
currencyCode: CURRENCY_SYMBOLS.ETH,
},
coinbasePayCurrencies: [CURRENCY_SYMBOLS.ETH, 'USDC', 'DAI'],
coinbasePayCurrencies: [
SUPPORTED_CURRENCY_SYMBOLS.ETH,
SUPPORTED_CURRENCY_SYMBOLS['1INCH'],
SUPPORTED_CURRENCY_SYMBOLS.AAVE,
SUPPORTED_CURRENCY_SYMBOLS.ABT,
SUPPORTED_CURRENCY_SYMBOLS.ACH,
SUPPORTED_CURRENCY_SYMBOLS.AGLD,
SUPPORTED_CURRENCY_SYMBOLS.AMP,
SUPPORTED_CURRENCY_SYMBOLS.ANKR,
SUPPORTED_CURRENCY_SYMBOLS.APE,
SUPPORTED_CURRENCY_SYMBOLS.ARPA,
SUPPORTED_CURRENCY_SYMBOLS.ASM,
SUPPORTED_CURRENCY_SYMBOLS.AUCTION,
SUPPORTED_CURRENCY_SYMBOLS.AXS,
SUPPORTED_CURRENCY_SYMBOLS.BADGER,
SUPPORTED_CURRENCY_SYMBOLS.BAL,
SUPPORTED_CURRENCY_SYMBOLS.BAND,
SUPPORTED_CURRENCY_SYMBOLS.BAT,
SUPPORTED_CURRENCY_SYMBOLS.BNT,
SUPPORTED_CURRENCY_SYMBOLS.BOBA,
SUPPORTED_CURRENCY_SYMBOLS.BOND,
SUPPORTED_CURRENCY_SYMBOLS.BTRST,
SUPPORTED_CURRENCY_SYMBOLS.CHZ,
SUPPORTED_CURRENCY_SYMBOLS.CLV,
SUPPORTED_CURRENCY_SYMBOLS.COMP,
SUPPORTED_CURRENCY_SYMBOLS.COTI,
SUPPORTED_CURRENCY_SYMBOLS.CRO,
SUPPORTED_CURRENCY_SYMBOLS.CRV,
SUPPORTED_CURRENCY_SYMBOLS.CTSI,
SUPPORTED_CURRENCY_SYMBOLS.CVC,
SUPPORTED_CURRENCY_SYMBOLS.DAI,
SUPPORTED_CURRENCY_SYMBOLS.DDX,
SUPPORTED_CURRENCY_SYMBOLS.DNT,
SUPPORTED_CURRENCY_SYMBOLS.ENJ,
SUPPORTED_CURRENCY_SYMBOLS.ENS,
SUPPORTED_CURRENCY_SYMBOLS.FARM,
SUPPORTED_CURRENCY_SYMBOLS.FET,
SUPPORTED_CURRENCY_SYMBOLS.FORTH,
SUPPORTED_CURRENCY_SYMBOLS.FX,
SUPPORTED_CURRENCY_SYMBOLS.GNO,
SUPPORTED_CURRENCY_SYMBOLS.GRT,
SUPPORTED_CURRENCY_SYMBOLS.GTC,
SUPPORTED_CURRENCY_SYMBOLS.IOTX,
SUPPORTED_CURRENCY_SYMBOLS.JASMY,
SUPPORTED_CURRENCY_SYMBOLS.KEEP,
SUPPORTED_CURRENCY_SYMBOLS.KNC,
SUPPORTED_CURRENCY_SYMBOLS.KRL,
SUPPORTED_CURRENCY_SYMBOLS.LCX,
SUPPORTED_CURRENCY_SYMBOLS.LINK,
SUPPORTED_CURRENCY_SYMBOLS.LPT,
SUPPORTED_CURRENCY_SYMBOLS.LRC,
SUPPORTED_CURRENCY_SYMBOLS.MANA,
SUPPORTED_CURRENCY_SYMBOLS.MASK,
SUPPORTED_CURRENCY_SYMBOLS.MATIC,
SUPPORTED_CURRENCY_SYMBOLS.MIR,
SUPPORTED_CURRENCY_SYMBOLS.MKR,
SUPPORTED_CURRENCY_SYMBOLS.MLN,
SUPPORTED_CURRENCY_SYMBOLS.MTL,
SUPPORTED_CURRENCY_SYMBOLS.NKN,
SUPPORTED_CURRENCY_SYMBOLS.NMR,
SUPPORTED_CURRENCY_SYMBOLS.NU,
SUPPORTED_CURRENCY_SYMBOLS.OGN,
SUPPORTED_CURRENCY_SYMBOLS.OMG,
SUPPORTED_CURRENCY_SYMBOLS.OXT,
SUPPORTED_CURRENCY_SYMBOLS.PAX,
SUPPORTED_CURRENCY_SYMBOLS.PERP,
SUPPORTED_CURRENCY_SYMBOLS.PLA,
SUPPORTED_CURRENCY_SYMBOLS.POLY,
SUPPORTED_CURRENCY_SYMBOLS.QNT,
SUPPORTED_CURRENCY_SYMBOLS.QUICK,
SUPPORTED_CURRENCY_SYMBOLS.RAD,
SUPPORTED_CURRENCY_SYMBOLS.RAI,
SUPPORTED_CURRENCY_SYMBOLS.RARI,
SUPPORTED_CURRENCY_SYMBOLS.REN,
SUPPORTED_CURRENCY_SYMBOLS.REP,
SUPPORTED_CURRENCY_SYMBOLS.REQ,
SUPPORTED_CURRENCY_SYMBOLS.RLC,
SUPPORTED_CURRENCY_SYMBOLS.RLY,
SUPPORTED_CURRENCY_SYMBOLS.SAND,
SUPPORTED_CURRENCY_SYMBOLS.SHIB,
SUPPORTED_CURRENCY_SYMBOLS.SKL,
SUPPORTED_CURRENCY_SYMBOLS.SNX,
SUPPORTED_CURRENCY_SYMBOLS.STORJ,
SUPPORTED_CURRENCY_SYMBOLS.SUKU,
SUPPORTED_CURRENCY_SYMBOLS.SUSHI,
SUPPORTED_CURRENCY_SYMBOLS.SWFTC,
SUPPORTED_CURRENCY_SYMBOLS.TRAC,
SUPPORTED_CURRENCY_SYMBOLS.TRB,
SUPPORTED_CURRENCY_SYMBOLS.TRIBE,
SUPPORTED_CURRENCY_SYMBOLS.TRU,
SUPPORTED_CURRENCY_SYMBOLS.UMA,
SUPPORTED_CURRENCY_SYMBOLS.UNI,
SUPPORTED_CURRENCY_SYMBOLS.USDC,
SUPPORTED_CURRENCY_SYMBOLS.USDT,
SUPPORTED_CURRENCY_SYMBOLS.WBTC,
SUPPORTED_CURRENCY_SYMBOLS.WCFG,
SUPPORTED_CURRENCY_SYMBOLS.XYO,
SUPPORTED_CURRENCY_SYMBOLS.YFII,
SUPPORTED_CURRENCY_SYMBOLS.ZRX,
],
},
[CHAIN_IDS.ROPSTEN]: {
nativeCurrency: TEST_NETWORK_TICKER_MAP[NETWORK_TYPES.ROPSTEN],
@ -563,7 +816,10 @@ export const BUYABLE_CHAINS_MAP: {
[CHAIN_IDS.BSC]: {
nativeCurrency: CURRENCY_SYMBOLS.BNB,
network: 'bsc',
transakCurrencies: [CURRENCY_SYMBOLS.BNB, CURRENCY_SYMBOLS.BUSD],
transakCurrencies: [
SUPPORTED_CURRENCY_SYMBOLS.BNB,
SUPPORTED_CURRENCY_SYMBOLS.BUSD,
],
moonPay: {
defaultCurrencyCode: 'bnb_bsc',
showOnlyCurrencies: 'bnb_bsc,busd_bsc',
@ -573,10 +829,10 @@ export const BUYABLE_CHAINS_MAP: {
nativeCurrency: CURRENCY_SYMBOLS.MATIC,
network: 'polygon',
transakCurrencies: [
CURRENCY_SYMBOLS.MATIC,
CURRENCY_SYMBOLS.USDT,
CURRENCY_SYMBOLS.USDC,
CURRENCY_SYMBOLS.DAI,
SUPPORTED_CURRENCY_SYMBOLS.MATIC,
SUPPORTED_CURRENCY_SYMBOLS.USDT,
SUPPORTED_CURRENCY_SYMBOLS.USDC,
SUPPORTED_CURRENCY_SYMBOLS.DAI,
],
moonPay: {
defaultCurrencyCode: 'matic_polygon',
@ -590,7 +846,7 @@ export const BUYABLE_CHAINS_MAP: {
[CHAIN_IDS.AVALANCHE]: {
nativeCurrency: CURRENCY_SYMBOLS.AVALANCHE,
network: 'avaxcchain',
transakCurrencies: [CURRENCY_SYMBOLS.AVALANCHE],
transakCurrencies: [SUPPORTED_CURRENCY_SYMBOLS.AVALANCHE],
moonPay: {
defaultCurrencyCode: 'avax_cchain',
showOnlyCurrencies: 'avax_cchain',
@ -599,17 +855,17 @@ export const BUYABLE_CHAINS_MAP: {
srn: 'avalanche',
currencyCode: CURRENCY_SYMBOLS.AVALANCHE,
},
coinbasePayCurrencies: [CURRENCY_SYMBOLS.AVALANCHE],
coinbasePayCurrencies: [SUPPORTED_CURRENCY_SYMBOLS.AVALANCHE],
},
[CHAIN_IDS.FANTOM]: {
nativeCurrency: CURRENCY_SYMBOLS.FANTOM,
network: 'fantom',
transakCurrencies: [CURRENCY_SYMBOLS.FANTOM],
transakCurrencies: [SUPPORTED_CURRENCY_SYMBOLS.FANTOM],
},
[CHAIN_IDS.CELO]: {
nativeCurrency: CURRENCY_SYMBOLS.CELO,
network: 'celo',
transakCurrencies: [CURRENCY_SYMBOLS.CELO],
transakCurrencies: [SUPPORTED_CURRENCY_SYMBOLS.CELO],
moonPay: {
defaultCurrencyCode: 'celo',
showOnlyCurrencies: 'celo',

View File

@ -0,0 +1,221 @@
import PropTypes from 'prop-types';
import React, { useContext } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { I18nContext } from '../../../contexts/i18n';
import { MetaMetricsContext } from '../../../contexts/metametrics';
import {
NETWORK_TO_NAME_MAP,
BUYABLE_CHAINS_MAP,
} from '../../../../shared/constants/network';
import { EVENT, EVENT_NAMES } from '../../../../shared/constants/metametrics';
import LogoMoonPay from '../../ui/logo/logo-moonpay';
import LogoWyre from '../../ui/logo/logo-wyre';
import LogoTransak from '../../ui/logo/logo-transak';
import LogoCoinbasePay from '../../ui/logo/logo-coinbasepay';
import LogoDepositEth from '../../ui/logo/logo-deposit-eth';
import Popover from '../../ui/popover';
import { buy, showModal, hideWarning } from '../../../store/actions';
import {
getIsTestnet,
getCurrentChainId,
getSelectedAddress,
getIsBuyableTransakChain,
getIsBuyableMoonPayChain,
getIsBuyableWyreChain,
getIsBuyableCoinbasePayChain,
getIsBuyableCoinbasePayToken,
getIsBuyableTransakToken,
} from '../../../selectors/selectors';
import OnRampItem from './on-ramp-item';
const DepositPopover = ({ onClose, token }) => {
const isTokenDeposit = Boolean(token);
const t = useContext(I18nContext);
const trackEvent = useContext(MetaMetricsContext);
const dispatch = useDispatch();
const chainId = useSelector(getCurrentChainId);
const isTestnet = useSelector(getIsTestnet);
const address = useSelector(getSelectedAddress);
const isBuyableTransakChain = useSelector(getIsBuyableTransakChain);
const isBuyableMoonPayChain = useSelector(getIsBuyableMoonPayChain);
const isBuyableWyreChain = useSelector(getIsBuyableWyreChain);
const isBuyableCoinbasePayChain = useSelector(getIsBuyableCoinbasePayChain);
const isTokenBuyableCoinbasePay = useSelector((state) =>
getIsBuyableCoinbasePayToken(state, token?.symbol),
);
const isTokenBuyableTransak = useSelector((state) =>
getIsBuyableTransakToken(state, token?.symbol),
);
const networkName = NETWORK_TO_NAME_MAP[chainId];
const symbol = token
? token.symbol
: BUYABLE_CHAINS_MAP[chainId].nativeCurrency;
const showAccountDetailModal = () => {
dispatch(showModal({ name: 'ACCOUNT_DETAILS' }));
};
const hideWarningMessage = () => {
dispatch(hideWarning());
};
const toCoinbasePay = () => {
dispatch(
buy({ service: 'coinbase', address, chainId, symbol: token?.symbol }),
);
};
const toTransak = () => {
dispatch(
buy({ service: 'transak', address, chainId, symbol: token?.symbol }),
);
};
const toMoonPay = () => {
dispatch(buy({ service: 'moonpay', address, chainId }));
};
const toWyre = () => {
dispatch(buy({ service: 'wyre', address, chainId }));
};
const toFaucet = () => dispatch(buy({ chainId }));
const goToAccountDetailsModal = () => {
hideWarningMessage();
showAccountDetailModal();
onClose();
};
return (
<Popover
title={t('depositCrypto', [symbol])}
subtitle={isTokenDeposit ? '' : t('needCryptoInWallet', [symbol])}
onClose={onClose}
>
<OnRampItem
logo={<LogoCoinbasePay />}
title={t('buyCryptoWithCoinbasePay', [symbol])}
text={t('buyCryptoWithCoinbasePayDescription', [symbol])}
buttonLabel={t('continueToCoinbasePay')}
onButtonClick={() => {
trackEvent({
category: EVENT.CATEGORIES.ACCOUNTS,
event: EVENT_NAMES.ONRAMP_PROVIDER_SELECTED,
properties: {
onramp_provider_type: EVENT.ONRAMP_PROVIDER_TYPES.COINBASE,
},
});
toCoinbasePay();
}}
hide={
isTokenDeposit
? !isBuyableCoinbasePayChain || !isTokenBuyableCoinbasePay
: !isBuyableCoinbasePayChain
}
/>
<OnRampItem
logo={<LogoTransak />}
title={t('buyCryptoWithTransak', [symbol])}
text={t('buyCryptoWithTransakDescription', [symbol])}
buttonLabel={t('continueToTransak')}
onButtonClick={() => {
trackEvent({
category: EVENT.CATEGORIES.ACCOUNTS,
event: EVENT_NAMES.ONRAMP_PROVIDER_SELECTED,
properties: {
onramp_provider_type: EVENT.ONRAMP_PROVIDER_TYPES.TRANSAK,
},
});
toTransak();
}}
hide={
isTokenDeposit
? !isBuyableTransakChain || !isTokenBuyableTransak
: !isBuyableTransakChain
}
/>
<OnRampItem
logo={<LogoMoonPay />}
title={t('buyCryptoWithMoonPay', [symbol])}
text={t('buyCryptoWithMoonPayDescription', [symbol])}
buttonLabel={t('continueToMoonPay')}
onButtonClick={() => {
trackEvent({
category: EVENT.CATEGORIES.ACCOUNTS,
event: EVENT_NAMES.ONRAMP_PROVIDER_SELECTED,
properties: {
onramp_provider_type: EVENT.ONRAMP_PROVIDER_TYPES.MOONPAY,
},
});
toMoonPay();
}}
hide={isTokenDeposit || !isBuyableMoonPayChain}
/>
<OnRampItem
logo={<LogoWyre />}
title={t('buyWithWyre', [symbol])}
text={t('buyWithWyreDescription', [symbol])}
buttonLabel={t('continueToWyre')}
onButtonClick={() => {
trackEvent({
category: EVENT.CATEGORIES.ACCOUNTS,
event: EVENT_NAMES.ONRAMP_PROVIDER_SELECTED,
properties: {
onramp_provider_type: EVENT.ONRAMP_PROVIDER_TYPES.WYRE,
},
});
toWyre();
}}
hide={isTokenDeposit || !isBuyableWyreChain}
/>
<OnRampItem
logo={<LogoDepositEth width="50px" />}
title={t('directDepositCrypto', [symbol])}
text={t('directDepositCryptoExplainer', [symbol])}
buttonLabel={t('viewAccount')}
onButtonClick={() => {
trackEvent({
category: EVENT.CATEGORIES.ACCOUNTS,
event: EVENT_NAMES.ONRAMP_PROVIDER_SELECTED,
properties: {
onramp_provider_type: EVENT.ONRAMP_PROVIDER_TYPES.SELF_DEPOSIT,
},
});
goToAccountDetailsModal();
}}
hide={isTokenDeposit || !isBuyableWyreChain}
/>
{networkName && (
<OnRampItem
logo={<i className="fa fa-tint fa-2x" />}
title={t('testFaucet')}
text={t('getEtherFromFaucet', [networkName])}
buttonLabel={t('getEther')}
onButtonClick={() => toFaucet()}
hide={!isTestnet}
/>
)}
</Popover>
);
};
DepositPopover.propTypes = {
onClose: PropTypes.func.isRequired,
token: PropTypes.shape({
address: PropTypes.string.isRequired,
decimals: PropTypes.number,
symbol: PropTypes.string,
image: PropTypes.string,
aggregators: PropTypes.array,
isERC721: PropTypes.bool,
}),
};
export default DepositPopover;

View File

@ -0,0 +1 @@
export { default } from './deposit-popover';

View File

@ -0,0 +1,67 @@
import React from 'react';
import PropTypes from 'prop-types';
import Button from '../../ui/button';
import Box from '../../ui/box';
import Typography from '../../ui/typography';
import { COLORS, FRACTIONS } from '../../../helpers/constants/design-system';
const OnRampItem = ({
logo,
title,
text,
buttonLabel,
onButtonClick,
hide = false,
}) => {
if (hide) {
return null;
}
return (
<Box paddingRight={6} paddingLeft={6}>
<Box
paddingTop={6}
paddingBottom={6}
style={{
borderBottomSize: '1px',
borderBottomColor: COLORS.BORDER_MUTED,
}}
>
<Box width={FRACTIONS.HALF}>{logo}</Box>
<Typography
variant="h6"
fontWeight="bold"
boxProps={{
paddingTop: 2,
paddingBottom: 2,
}}
>
{title}
</Typography>
<Typography
boxProps={{
paddingTop: 2,
paddingBottom: 2,
}}
>
{text}
</Typography>
<Box marginTop={4}>
<Button type="secondary" onClick={onButtonClick}>
{buttonLabel}
</Button>
</Box>
</Box>
</Box>
);
};
OnRampItem.propTypes = {
logo: PropTypes.node.isRequired,
title: PropTypes.string.isRequired,
text: PropTypes.string.isRequired,
buttonLabel: PropTypes.string.isRequired,
onButtonClick: PropTypes.func.isRequired,
hide: PropTypes.bool,
};
export default OnRampItem;

View File

@ -1,243 +0,0 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import {
NETWORK_TO_NAME_MAP,
BUYABLE_CHAINS_MAP,
} from '../../../../../shared/constants/network';
import {
EVENT,
EVENT_NAMES,
} from '../../../../../shared/constants/metametrics';
import Button from '../../../ui/button';
import LogoMoonPay from '../../../ui/logo/logo-moonpay';
import LogoWyre from '../../../ui/logo/logo-wyre';
import LogoTransak from '../../../ui/logo/logo-transak';
import LogoCoinbasePay from '../../../ui/logo/logo-coinbasepay';
import LogoDepositEth from '../../../ui/logo/logo-deposit-eth';
export default class DepositEtherModal extends Component {
static contextTypes = {
t: PropTypes.func,
trackEvent: PropTypes.func.isRequired,
};
static propTypes = {
chainId: PropTypes.string.isRequired,
isTestnet: PropTypes.bool.isRequired,
isBuyableTransakChain: PropTypes.bool.isRequired,
isBuyableMoonPayChain: PropTypes.bool.isRequired,
isBuyableWyreChain: PropTypes.bool.isRequired,
isBuyableCoinbasePayChain: PropTypes.bool.isRequired,
toWyre: PropTypes.func.isRequired,
toTransak: PropTypes.func.isRequired,
toMoonPay: PropTypes.func.isRequired,
toCoinbasePay: PropTypes.func.isRequired,
address: PropTypes.string.isRequired,
toFaucet: PropTypes.func.isRequired,
hideWarning: PropTypes.func.isRequired,
hideModal: PropTypes.func.isRequired,
showAccountDetailModal: PropTypes.func.isRequired,
};
goToAccountDetailsModal = () => {
this.props.hideWarning();
this.props.hideModal();
this.props.showAccountDetailModal();
};
renderRow({
logo,
title,
text,
buttonLabel,
onButtonClick,
hide,
className,
hideButton,
hideTitle,
onBackClick,
showBackButton,
}) {
if (hide) {
return null;
}
return (
<div className={className || 'deposit-ether-modal__buy-row'}>
{onBackClick && showBackButton && (
<div
className="deposit-ether-modal__buy-row__back"
onClick={onBackClick}
>
<i className="fa fa-arrow-left cursor-pointer" />
</div>
)}
<div className="deposit-ether-modal__buy-row__logo-container">
{logo}
</div>
<div className="deposit-ether-modal__buy-row__description">
{!hideTitle && (
<div className="deposit-ether-modal__buy-row__description__title">
{title}
</div>
)}
<div className="deposit-ether-modal__buy-row__description__text">
{text}
</div>
</div>
{!hideButton && (
<div className="deposit-ether-modal__buy-row__button">
<Button
type="secondary"
className="deposit-ether-modal__deposit-button"
large
onClick={onButtonClick}
>
{buttonLabel}
</Button>
</div>
)}
</div>
);
}
render() {
const {
chainId,
toWyre,
toTransak,
toMoonPay,
toCoinbasePay,
address,
toFaucet,
isTestnet,
isBuyableTransakChain,
isBuyableMoonPayChain,
isBuyableWyreChain,
isBuyableCoinbasePayChain,
} = this.props;
const { t } = this.context;
const networkName = NETWORK_TO_NAME_MAP[chainId];
const symbol = BUYABLE_CHAINS_MAP[chainId].nativeCurrency;
return (
<div className="page-container page-container--full-width page-container--full-height">
<div className="page-container__header">
<div className="page-container__title">
{t('depositCrypto', [symbol])}
</div>
<div className="page-container__subtitle">
{t('needCryptoInWallet', [symbol])}
</div>
<div
className="page-container__header-close"
onClick={() => {
this.props.hideWarning();
this.props.hideModal();
}}
/>
</div>
<div className="page-container__content">
<div className="deposit-ether-modal__buy-rows">
{this.renderRow({
logo: <LogoCoinbasePay className="deposit-ether-modal__logo" />,
title: t('buyCryptoWithCoinbasePay', [symbol]),
text: t('buyCryptoWithCoinbasePayDescription', [symbol]),
buttonLabel: t('continueToCoinbasePay'),
onButtonClick: () => {
this.context.trackEvent({
category: EVENT.CATEGORIES.ACCOUNTS,
event: EVENT_NAMES.ONRAMP_PROVIDER_SELECTED,
properties: {
onramp_provider_type: EVENT.ONRAMP_PROVIDER_TYPES.COINBASE,
},
});
toCoinbasePay(address, chainId);
},
hide: !isBuyableCoinbasePayChain,
})}
{this.renderRow({
logo: <LogoTransak className="deposit-ether-modal__logo" />,
title: t('buyCryptoWithTransak', [symbol]),
text: t('buyCryptoWithTransakDescription', [symbol]),
buttonLabel: t('continueToTransak'),
onButtonClick: () => {
this.context.trackEvent({
category: EVENT.CATEGORIES.ACCOUNTS,
event: EVENT_NAMES.ONRAMP_PROVIDER_SELECTED,
properties: {
onramp_provider_type: EVENT.ONRAMP_PROVIDER_TYPES.TRANSAK,
},
});
toTransak(address, chainId);
},
hide: !isBuyableTransakChain,
})}
{this.renderRow({
logo: <LogoMoonPay className="deposit-ether-modal__logo" />,
title: t('buyCryptoWithMoonPay', [symbol]),
text: t('buyCryptoWithMoonPayDescription', [symbol]),
buttonLabel: t('continueToMoonPay'),
onButtonClick: () => {
this.context.trackEvent({
category: EVENT.CATEGORIES.ACCOUNTS,
event: EVENT_NAMES.ONRAMP_PROVIDER_SELECTED,
properties: {
onramp_provider_type: EVENT.ONRAMP_PROVIDER_TYPES.MOONPAY,
},
});
toMoonPay(address, chainId);
},
hide: !isBuyableMoonPayChain,
})}
{this.renderRow({
logo: <LogoWyre className="deposit-ether-modal__logo" />,
title: t('buyWithWyre', [symbol]),
text: t('buyWithWyreDescription', [symbol]),
buttonLabel: t('continueToWyre'),
onButtonClick: () => {
this.context.trackEvent({
category: EVENT.CATEGORIES.ACCOUNTS,
event: EVENT_NAMES.ONRAMP_PROVIDER_SELECTED,
properties: {
onramp_provider_type: EVENT.ONRAMP_PROVIDER_TYPES.WYRE,
},
});
toWyre(address, chainId);
},
hide: !isBuyableWyreChain,
})}
{this.renderRow({
logo: (
<LogoDepositEth className="deposit-ether-modal__logo--lg" />
),
title: t('directDepositCrypto', [symbol]),
text: t('directDepositCryptoExplainer', [symbol]),
buttonLabel: t('viewAccount'),
onButtonClick: () => {
this.context.trackEvent({
category: EVENT.CATEGORIES.ACCOUNTS,
event: EVENT_NAMES.ONRAMP_PROVIDER_SELECTED,
properties: {
onramp_provider_type:
EVENT.ONRAMP_PROVIDER_TYPES.SELF_DEPOSIT,
},
});
this.goToAccountDetailsModal();
},
})}
{networkName &&
this.renderRow({
logo: <i className="fa fa-tint fa-2x" />,
title: t('testFaucet'),
text: t('getEtherFromFaucet', [networkName]),
buttonLabel: t('getEther'),
onButtonClick: () => toFaucet(chainId),
hide: !isTestnet,
})}
</div>
</div>
</div>
);
}
}

View File

@ -1,60 +0,0 @@
import { connect } from 'react-redux';
import {
buyEth,
hideModal,
showModal,
hideWarning,
} from '../../../../store/actions';
import {
getIsTestnet,
getIsMainnet,
getCurrentChainId,
getSelectedAddress,
getIsBuyableTransakChain,
getIsBuyableMoonPayChain,
getIsBuyableWyreChain,
getIsBuyableCoinbasePayChain,
} from '../../../../selectors/selectors';
import DepositEtherModal from './deposit-ether-modal.component';
function mapStateToProps(state) {
return {
chainId: getCurrentChainId(state),
isTestnet: getIsTestnet(state),
isMainnet: getIsMainnet(state),
address: getSelectedAddress(state),
isBuyableTransakChain: getIsBuyableTransakChain(state),
isBuyableMoonPayChain: getIsBuyableMoonPayChain(state),
isBuyableWyreChain: getIsBuyableWyreChain(state),
isBuyableCoinbasePayChain: getIsBuyableCoinbasePayChain(state),
};
}
function mapDispatchToProps(dispatch) {
return {
toWyre: (address, chainId) => {
dispatch(buyEth({ service: 'wyre', address, chainId }));
},
toTransak: (address, chainId) => {
dispatch(buyEth({ service: 'transak', address, chainId }));
},
toMoonPay: (address, chainId) => {
dispatch(buyEth({ service: 'moonpay', address, chainId }));
},
toCoinbasePay: (address, chainId) => {
dispatch(buyEth({ service: 'coinbase', address, chainId }));
},
hideModal: () => {
dispatch(hideModal());
},
hideWarning: () => {
dispatch(hideWarning());
},
showAccountDetailModal: () => {
dispatch(showModal({ name: 'ACCOUNT_DETAILS' }));
},
toFaucet: (chainId) => dispatch(buyEth({ chainId })),
};
}
export default connect(mapStateToProps, mapDispatchToProps)(DepositEtherModal);

View File

@ -1 +0,0 @@
export { default } from './deposit-ether-modal.container';

View File

@ -1,118 +0,0 @@
.deposit-ether-modal {
border-radius: 8px;
display: flex;
flex-flow: column;
height: 100%;
&__buy-rows {
width: 100%;
padding: 0 30px;
display: flex;
flex-flow: column nowrap;
flex: 1;
align-items: center;
@include screen-sm-max {
height: 0;
}
}
&__logo {
max-height: 40px;
background-repeat: no-repeat;
background-size: contain;
background-position: center;
width: 100%;
display: flex;
justify-content: center;
align-items: center;
&--lg {
max-height: 60px;
}
@include screen-sm-min {
height: 60px;
}
}
&__buy-row {
border-bottom: 1px solid var(--color-border-default);
display: flex;
justify-content: space-between;
align-items: center;
flex: 1 0 auto;
padding: 30px 0 20px;
min-height: 170px;
@include screen-sm-max {
min-height: 270px;
flex-flow: column;
justify-content: flex-start;
}
&__back {
position: absolute;
top: 10px;
left: 0;
}
&__logo-container {
display: flex;
justify-content: center;
flex: 0 0 auto;
padding: 0 20px;
@include screen-sm-min {
width: 12rem;
}
@include screen-sm-max {
width: 100%;
max-height: 6rem;
padding-bottom: 20px;
}
}
&__right {
display: flex;
}
&__description {
color: var(--color-text-alternative);
padding-bottom: 20px;
align-self: flex-start;
@include screen-sm-min {
width: 15rem;
}
&__title {
@include H4;
}
&__text {
@include H6;
margin-top: 7px;
}
}
&__button {
display: flex;
justify-content: flex-end;
@include screen-sm-min {
min-width: 300px;
}
}
}
&__buy-row:last-of-type {
border-bottom: 0;
}
&__deposit-button {
width: 257px !important;
}
}

View File

@ -2,7 +2,6 @@
@import 'account-modal-container/index';
@import 'cancel-transaction/index';
@import 'confirm-remove-account/index';
@import 'deposit-ether-modal/index';
@import 'edit-approval-permission/index';
@import 'export-private-key-modal/index';
@import 'hide-token-confirmation-modal/index';

View File

@ -10,7 +10,6 @@ import { ENVIRONMENT_TYPE_POPUP } from '../../../../shared/constants/app';
// Modal Components
import ConfirmCustomizeGasModal from '../gas-customization/gas-modal-page-container';
import DepositEtherModal from './deposit-ether-modal';
import AccountDetailsModal from './account-details-modal';
import ExportPrivateKeyModal from './export-private-key-modal';
import HideTokenConfirmationModal from './hide-token-confirmation-modal';
@ -79,39 +78,6 @@ const accountModalStyle = {
};
const MODALS = {
DEPOSIT_ETHER: {
contents: <DepositEtherModal />,
onHide: (props) => props.hideWarning(),
mobileModalStyle: {
width: '100%',
height: '100%',
transform: 'none',
left: '0',
right: '0',
margin: '0 auto',
boxShadow: 'var(--shadow-size-sm) var(--color-shadow-default)',
top: '0',
display: 'flex',
},
laptopModalStyle: {
width: 'initial',
maxWidth: '850px',
top: 'calc(10% + 10px)',
left: '0',
right: '0',
margin: '0 auto',
boxShadow: 'var(--shadow-size-sm) var(--color-shadow-default)',
borderRadius: '7px',
transform: 'none',
height: 'calc(80% - 20px)',
overflowY: 'hidden',
},
contentStyle: {
borderRadius: '7px',
height: '100%',
},
},
NEW_ACCOUNT: {
contents: <NewAccountModal />,
mobileModalStyle: {

View File

@ -1,4 +1,4 @@
import React, { useContext } from 'react';
import React, { useContext, useState } from 'react';
import PropTypes from 'prop-types';
import { useDispatch, useSelector } from 'react-redux';
import classnames from 'classnames';
@ -13,7 +13,6 @@ import {
import Tooltip from '../../ui/tooltip';
import UserPreferencedCurrencyDisplay from '../user-preferenced-currency-display';
import { PRIMARY, SECONDARY } from '../../../helpers/constants/common';
import { showModal } from '../../../store/actions';
import {
isBalanceCached,
getShouldShowFiat,
@ -35,6 +34,7 @@ import { EVENT, EVENT_NAMES } from '../../../../shared/constants/metametrics';
import Spinner from '../../ui/spinner';
import { startNewDraftTransaction } from '../../../ducks/send';
import { ASSET_TYPES } from '../../../../shared/constants/transaction';
import DepositPopover from '../deposit-popover';
import WalletOverview from './wallet-overview';
const EthOverview = ({ className }) => {
@ -42,6 +42,7 @@ const EthOverview = ({ className }) => {
const t = useContext(I18nContext);
const trackEvent = useContext(MetaMetricsContext);
const history = useHistory();
const [showDepositPopover, setShowDepositPopover] = useState(false);
const keyring = useSelector(getCurrentKeyring);
const usingHardwareWallet = isHardwareKeyring(keyring?.type);
const balanceIsCached = useSelector(isBalanceCached);
@ -53,136 +54,141 @@ const EthOverview = ({ className }) => {
const defaultSwapsToken = useSelector(getSwapsDefaultToken);
return (
<WalletOverview
loading={!balance}
balance={
<Tooltip
position="top"
title={t('balanceOutdated')}
disabled={!balanceIsCached}
>
<div className="eth-overview__balance">
<div className="eth-overview__primary-container">
{balance ? (
<>
{showDepositPopover && (
<DepositPopover onClose={() => setShowDepositPopover(false)} />
)}
<WalletOverview
loading={!balance}
balance={
<Tooltip
position="top"
title={t('balanceOutdated')}
disabled={!balanceIsCached}
>
<div className="eth-overview__balance">
<div className="eth-overview__primary-container">
{balance ? (
<UserPreferencedCurrencyDisplay
className={classnames('eth-overview__primary-balance', {
'eth-overview__cached-balance': balanceIsCached,
})}
data-testid="eth-overview__primary-currency"
value={balance}
type={PRIMARY}
ethNumberOfDecimals={4}
hideTitle
/>
) : (
<Spinner
color="var(--color-secondary-default)"
className="loading-overlay__spinner"
/>
)}
{balanceIsCached ? (
<span className="eth-overview__cached-star">*</span>
) : null}
</div>
{showFiat && balance && (
<UserPreferencedCurrencyDisplay
className={classnames('eth-overview__primary-balance', {
'eth-overview__cached-balance': balanceIsCached,
className={classnames({
'eth-overview__cached-secondary-balance': balanceIsCached,
'eth-overview__secondary-balance': !balanceIsCached,
})}
data-testid="eth-overview__primary-currency"
data-testid="eth-overview__secondary-currency"
value={balance}
type={PRIMARY}
type={SECONDARY}
ethNumberOfDecimals={4}
hideTitle
/>
) : (
<Spinner
color="var(--color-secondary-default)"
className="loading-overlay__spinner"
/>
)}
{balanceIsCached ? (
<span className="eth-overview__cached-star">*</span>
) : null}
</div>
{showFiat && balance && (
<UserPreferencedCurrencyDisplay
className={classnames({
'eth-overview__cached-secondary-balance': balanceIsCached,
'eth-overview__secondary-balance': !balanceIsCached,
})}
data-testid="eth-overview__secondary-currency"
value={balance}
type={SECONDARY}
ethNumberOfDecimals={4}
hideTitle
/>
)}
</div>
</Tooltip>
}
buttons={
<>
<IconButton
className="eth-overview__button"
Icon={BuyIcon}
disabled={!isBuyableChain}
label={t('buy')}
onClick={() => {
trackEvent({
event: EVENT_NAMES.NAV_BUY_BUTTON_CLICKED,
category: EVENT.CATEGORIES.NAVIGATION,
properties: {
location: 'Home',
text: 'Buy',
},
});
dispatch(showModal({ name: 'DEPOSIT_ETHER' }));
}}
/>
<IconButton
className="eth-overview__button"
data-testid="eth-overview-send"
Icon={SendIcon}
label={t('send')}
onClick={() => {
trackEvent({
event: EVENT_NAMES.NAV_SEND_BUTTON_CLICKED,
category: EVENT.CATEGORIES.NAVIGATION,
properties: {
token_symbol: 'ETH',
location: 'Home',
text: 'Send',
},
});
dispatch(
startNewDraftTransaction({ type: ASSET_TYPES.NATIVE }),
).then(() => {
history.push(SEND_ROUTE);
});
}}
/>
<IconButton
className="eth-overview__button"
disabled={!isSwapsChain}
Icon={SwapIcon}
onClick={() => {
if (isSwapsChain) {
</Tooltip>
}
buttons={
<>
<IconButton
className="eth-overview__button"
Icon={BuyIcon}
disabled={!isBuyableChain}
label={t('buy')}
onClick={() => {
trackEvent({
event: EVENT_NAMES.NAV_SWAP_BUTTON_CLICKED,
category: EVENT.CATEGORIES.SWAPS,
event: EVENT_NAMES.NAV_BUY_BUTTON_CLICKED,
category: EVENT.CATEGORIES.NAVIGATION,
properties: {
token_symbol: 'ETH',
location: EVENT.SOURCE.SWAPS.MAIN_VIEW,
text: 'Swap',
location: 'Home',
text: 'Buy',
},
});
dispatch(setSwapsFromToken(defaultSwapsToken));
if (usingHardwareWallet) {
global.platform.openExtensionInBrowser(BUILD_QUOTE_ROUTE);
} else {
history.push(BUILD_QUOTE_ROUTE);
setShowDepositPopover(true);
}}
/>
<IconButton
className="eth-overview__button"
data-testid="eth-overview-send"
Icon={SendIcon}
label={t('send')}
onClick={() => {
trackEvent({
event: EVENT_NAMES.NAV_SEND_BUTTON_CLICKED,
category: EVENT.CATEGORIES.NAVIGATION,
properties: {
token_symbol: 'ETH',
location: 'Home',
text: 'Send',
},
});
dispatch(
startNewDraftTransaction({ type: ASSET_TYPES.NATIVE }),
).then(() => {
history.push(SEND_ROUTE);
});
}}
/>
<IconButton
className="eth-overview__button"
disabled={!isSwapsChain}
Icon={SwapIcon}
onClick={() => {
if (isSwapsChain) {
trackEvent({
event: EVENT_NAMES.NAV_SWAP_BUTTON_CLICKED,
category: EVENT.CATEGORIES.SWAPS,
properties: {
token_symbol: 'ETH',
location: EVENT.SOURCE.SWAPS.MAIN_VIEW,
text: 'Swap',
},
});
dispatch(setSwapsFromToken(defaultSwapsToken));
if (usingHardwareWallet) {
global.platform.openExtensionInBrowser(BUILD_QUOTE_ROUTE);
} else {
history.push(BUILD_QUOTE_ROUTE);
}
}
}}
label={t('swap')}
tooltipRender={
isSwapsChain
? null
: (contents) => (
<Tooltip
title={t('currentlyUnavailable')}
position="bottom"
>
{contents}
</Tooltip>
)
}
}}
label={t('swap')}
tooltipRender={
isSwapsChain
? null
: (contents) => (
<Tooltip
title={t('currentlyUnavailable')}
position="bottom"
>
{contents}
</Tooltip>
)
}
/>
</>
}
className={className}
icon={<Identicon diameter={32} image={primaryTokenImage} imageBorder />}
/>
/>
</>
}
className={className}
icon={<Identicon diameter={32} image={primaryTokenImage} imageBorder />}
/>
</>
);
};

View File

@ -1,4 +1,4 @@
import React, { useContext, useEffect } from 'react';
import React, { useContext, useEffect, useState } from 'react';
import PropTypes from 'prop-types';
import { useDispatch, useSelector } from 'react-redux';
import { useHistory } from 'react-router-dom';
@ -19,8 +19,11 @@ import { setSwapsFromToken } from '../../../ducks/swaps/swaps';
import {
getCurrentKeyring,
getIsSwapsChain,
getIsBuyableCoinbasePayToken,
getIsBuyableTransakToken,
} from '../../../selectors/selectors';
import BuyIcon from '../../ui/icon/overview-buy-icon.component';
import SwapIcon from '../../ui/icon/swap-icon.component';
import SendIcon from '../../ui/icon/overview-send-icon.component';
@ -30,6 +33,7 @@ import { showModal } from '../../../store/actions';
import { MetaMetricsContext } from '../../../contexts/metametrics';
import { EVENT, EVENT_NAMES } from '../../../../shared/constants/metametrics';
import { ASSET_TYPES } from '../../../../shared/constants/transaction';
import DepositPopover from '../deposit-popover';
import WalletOverview from './wallet-overview';
const TokenOverview = ({ className, token }) => {
@ -37,6 +41,7 @@ const TokenOverview = ({ className, token }) => {
const t = useContext(I18nContext);
const trackEvent = useContext(MetaMetricsContext);
const history = useHistory();
const [showDepositPopover, setShowDepositPopover] = useState(false);
const keyring = useSelector(getCurrentKeyring);
const usingHardwareWallet = isHardwareKeyring(keyring.type);
const { tokensWithBalances } = useTokenTracker([token]);
@ -48,6 +53,13 @@ const TokenOverview = ({ className, token }) => {
token.symbol,
);
const isSwapsChain = useSelector(getIsSwapsChain);
const isTokenBuyableCoinbasePay = useSelector((state) =>
getIsBuyableCoinbasePayToken(state, token.symbol),
);
const isTokenBuyableTransak = useSelector((state) =>
getIsBuyableTransakToken(state, token.symbol),
);
const isBuyable = isTokenBuyableCoinbasePay || isTokenBuyableTransak;
useEffect(() => {
if (token.isERC721 && process.env.COLLECTIBLES_V1) {
@ -61,109 +73,140 @@ const TokenOverview = ({ className, token }) => {
}, [token.isERC721, token.address, dispatch]);
return (
<WalletOverview
balance={
<div className="token-overview__balance">
<CurrencyDisplay
className="token-overview__primary-balance"
displayValue={balanceToRender}
suffix={token.symbol}
/>
{formattedFiatBalance ? (
<>
{showDepositPopover && (
<DepositPopover
onClose={() => setShowDepositPopover(false)}
token={token}
/>
)}
<WalletOverview
balance={
<div className="token-overview__balance">
<CurrencyDisplay
className="token-overview__secondary-balance"
displayValue={formattedFiatBalance}
hideLabel
className="token-overview__primary-balance"
displayValue={balanceToRender}
suffix={token.symbol}
/>
) : null}
</div>
}
buttons={
<>
<IconButton
className="token-overview__button"
onClick={async () => {
trackEvent({
event: EVENT_NAMES.NAV_SEND_BUTTON_CLICKED,
category: EVENT.CATEGORIES.NAVIGATION,
properties: {
token_symbol: token.symbol,
location: EVENT.SOURCE.SWAPS.TOKEN_VIEW,
text: 'Send',
},
});
try {
await dispatch(
startNewDraftTransaction({
type: ASSET_TYPES.TOKEN,
details: token,
}),
);
history.push(SEND_ROUTE);
} catch (err) {
if (!err.message.includes(INVALID_ASSET_TYPE)) {
throw err;
}
}
}}
Icon={SendIcon}
label={t('send')}
data-testid="eth-overview-send"
disabled={token.isERC721}
/>
<IconButton
className="token-overview__button"
disabled={!isSwapsChain}
Icon={SwapIcon}
onClick={() => {
if (isSwapsChain) {
{formattedFiatBalance ? (
<CurrencyDisplay
className="token-overview__secondary-balance"
displayValue={formattedFiatBalance}
hideLabel
/>
) : null}
</div>
}
buttons={
<>
{isBuyable && (
<IconButton
className="token-overview__button"
Icon={BuyIcon}
label={t('buy')}
onClick={() => {
trackEvent({
event: 'Clicked Deposit: Token',
category: EVENT.CATEGORIES.NAVIGATION,
properties: {
action: 'Home',
legacy_event: true,
},
});
setShowDepositPopover(true);
}}
disabled={token.isERC721}
/>
)}
<IconButton
className="token-overview__button"
onClick={async () => {
trackEvent({
event: EVENT_NAMES.NAV_SWAP_BUTTON_CLICKED,
category: EVENT.CATEGORIES.SWAPS,
event: EVENT_NAMES.NAV_SEND_BUTTON_CLICKED,
category: EVENT.CATEGORIES.NAVIGATION,
properties: {
token_symbol: token.symbol,
location: EVENT.SOURCE.SWAPS.TOKEN_VIEW,
text: 'Swap',
text: 'Send',
},
});
dispatch(
setSwapsFromToken({
...token,
address: token.address.toLowerCase(),
iconUrl: token.image,
balance,
string: balanceToRender,
}),
);
if (usingHardwareWallet) {
global.platform.openExtensionInBrowser(BUILD_QUOTE_ROUTE);
} else {
history.push(BUILD_QUOTE_ROUTE);
try {
await dispatch(
startNewDraftTransaction({
type: ASSET_TYPES.TOKEN,
details: token,
}),
);
history.push(SEND_ROUTE);
} catch (err) {
if (!err.message.includes(INVALID_ASSET_TYPE)) {
throw err;
}
}
}}
Icon={SendIcon}
label={t('send')}
data-testid="eth-overview-send"
disabled={token.isERC721}
/>
<IconButton
className="token-overview__button"
disabled={!isSwapsChain}
Icon={SwapIcon}
onClick={() => {
if (isSwapsChain) {
trackEvent({
event: EVENT_NAMES.NAV_SWAP_BUTTON_CLICKED,
category: EVENT.CATEGORIES.SWAPS,
properties: {
token_symbol: token.symbol,
location: EVENT.SOURCE.SWAPS.TOKEN_VIEW,
text: 'Swap',
},
});
dispatch(
setSwapsFromToken({
...token,
address: token.address.toLowerCase(),
iconUrl: token.image,
balance,
string: balanceToRender,
}),
);
if (usingHardwareWallet) {
global.platform.openExtensionInBrowser(BUILD_QUOTE_ROUTE);
} else {
history.push(BUILD_QUOTE_ROUTE);
}
}
}}
label={t('swap')}
tooltipRender={
isSwapsChain
? null
: (contents) => (
<Tooltip
title={t('currentlyUnavailable')}
position="bottom"
disabled={isSwapsChain}
>
{contents}
</Tooltip>
)
}
}}
label={t('swap')}
tooltipRender={
isSwapsChain
? null
: (contents) => (
<Tooltip
title={t('currentlyUnavailable')}
position="bottom"
disabled={isSwapsChain}
>
{contents}
</Tooltip>
)
}
/>
</>
}
className={className}
icon={
<Identicon
diameter={32}
address={token.address}
image={token.image}
/>
</>
}
className={className}
icon={
<Identicon diameter={32} address={token.address} image={token.image} />
}
/>
}
/>
</>
);
};

View File

@ -708,6 +708,13 @@ export function getIsBuyableTransakChain(state) {
return Boolean(BUYABLE_CHAINS_MAP?.[chainId]?.transakCurrencies);
}
export function getIsBuyableTransakToken(state, symbol) {
const chainId = getCurrentChainId(state);
return Boolean(
BUYABLE_CHAINS_MAP?.[chainId]?.transakCurrencies?.includes(symbol),
);
}
export function getIsBuyableMoonPayChain(state) {
const chainId = getCurrentChainId(state);
return Boolean(BUYABLE_CHAINS_MAP?.[chainId]?.moonPay);
@ -722,6 +729,13 @@ export function getIsBuyableCoinbasePayChain(state) {
return Boolean(BUYABLE_CHAINS_MAP?.[chainId]?.coinbasePayCurrencies);
}
export function getIsBuyableCoinbasePayToken(state, symbol) {
const chainId = getCurrentChainId(state);
return Boolean(
BUYABLE_CHAINS_MAP?.[chainId]?.coinbasePayCurrencies?.includes(symbol),
);
}
export function getNativeCurrencyImage(state) {
const nativeCurrency = getNativeCurrency(state)?.toUpperCase();
return NATIVE_CURRENCY_TOKEN_IMAGE_MAP[nativeCurrency];

View File

@ -51,7 +51,7 @@ export const SET_HARDWARE_WALLET_DEFAULT_HD_PATH =
export const SHOW_LOADING = 'SHOW_LOADING_INDICATION';
export const HIDE_LOADING = 'HIDE_LOADING_INDICATION';
export const BUY_ETH = 'BUY_ETH';
export const BUY = 'BUY';
export const TOGGLE_ACCOUNT_MENU = 'TOGGLE_ACCOUNT_MENU';

View File

@ -2489,13 +2489,13 @@ export function showSendTokenPage() {
};
}
export function buyEth(opts) {
export function buy(opts) {
return async (dispatch) => {
const url = await getBuyUrl(opts);
if (url) {
global.platform.openTab({ url });
dispatch({
type: actionConstants.BUY_ETH,
type: actionConstants.BUY,
});
}
};