1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-12-22 17:33:23 +01:00

Ability to buy tokens with Moonpay (#15924)

* Ability to buy tokens with Moonpay

* fix for test cases failing

* updated description for MoonPayChainSettings type

* removed test results
This commit is contained in:
Nicolas Ferro 2022-09-23 18:38:40 +02:00 committed by GitHub
parent d520fc57cb
commit 3f801e377d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 129 additions and 37 deletions

3
.gitignore vendored
View File

@ -53,3 +53,6 @@ notes.txt
# TypeScript
tsout/
# Test results
test-results/

View File

@ -11,6 +11,7 @@ import {
MOONPAY_API_KEY,
COINBASEPAY_API_KEY,
} from '../constants/on-ramp';
import { formatMoonpaySymbol } from '../../../ui/helpers/utils/moonpay';
const fetchWithTimeout = getFetchWithTimeout();
@ -75,15 +76,17 @@ const createTransakUrl = (walletAddress, chainId, symbol) => {
*
* @param {string} walletAddress - Destination address
* @param {string} chainId - Current chain ID
* @param {string|undefined} symbol - Token symbol to buy
* @returns String
*/
const createMoonPayUrl = async (walletAddress, chainId) => {
const createMoonPayUrl = async (walletAddress, chainId, symbol) => {
const { moonPay: { defaultCurrencyCode, showOnlyCurrencies } = {} } =
BUYABLE_CHAINS_MAP[chainId];
const moonPayQueryParams = new URLSearchParams({
apiKey: MOONPAY_API_KEY,
walletAddress,
defaultCurrencyCode,
defaultCurrencyCode:
formatMoonpaySymbol(symbol, chainId) || defaultCurrencyCode,
showOnlyCurrencies,
});
const queryParams = new URLSearchParams({
@ -159,7 +162,7 @@ export default async function getBuyUrl({ chainId, address, service, symbol }) {
case 'transak':
return createTransakUrl(address, chainId, symbol);
case 'moonpay':
return createMoonPayUrl(address, chainId);
return createMoonPayUrl(address, chainId, symbol);
case 'coinbase':
return createCoinbasePayUrl(address, chainId, symbol);
case 'metamask-faucet':

View File

@ -117,11 +117,11 @@ describe('buy-url', () => {
nock(SWAPS_API_V2_BASE_URL)
.get(`/moonpaySign/?${queryParams}`)
.reply(200, {
url: `https://buy.moonpay.com/?apiKey=${MOONPAY_API_KEY}&walletAddress=${MAINNET.address}&defaultCurrencyCode=${defaultCurrencyCode}&showOnlyCurrencies=eth%2Cusdt%2Cusdc%2Cdai&signature=laefTlgkESEc2hv8AZEH9F25VjLEJUADY27D6MccE54%3D`,
url: `https://buy.moonpay.com/?apiKey=${MOONPAY_API_KEY}&walletAddress=${MAINNET.address}&defaultCurrencyCode=${defaultCurrencyCode}&showOnlyCurrencies=${showOnlyCurrencies}&signature=laefTlgkESEc2hv8AZEH9F25VjLEJUADY27D6MccE54%3D`,
});
const moonPayUrl = await getBuyUrl({ ...MAINNET, service: 'moonpay' });
expect(moonPayUrl).toStrictEqual(
`https://buy.moonpay.com/?apiKey=${MOONPAY_API_KEY}&walletAddress=${MAINNET.address}&defaultCurrencyCode=${defaultCurrencyCode}&showOnlyCurrencies=eth%2Cusdt%2Cusdc%2Cdai&signature=laefTlgkESEc2hv8AZEH9F25VjLEJUADY27D6MccE54%3D`,
`https://buy.moonpay.com/?apiKey=${MOONPAY_API_KEY}&walletAddress=${MAINNET.address}&defaultCurrencyCode=${defaultCurrencyCode}&showOnlyCurrencies=${showOnlyCurrencies}&signature=laefTlgkESEc2hv8AZEH9F25VjLEJUADY27D6MccE54%3D`,
);
nock.cleanAll();
});

View File

@ -23,11 +23,6 @@ type CurrencySymbol = typeof CURRENCY_SYMBOLS[keyof typeof CURRENCY_SYMBOLS];
*/
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.
*/
type LowercaseCurrencySymbol = Lowercase<CurrencySymbol>;
/**
* Test networks have special symbols that combine the network name and 'ETH'
* so that they are distinct from mainnet and other networks that use 'ETH'.
@ -40,7 +35,7 @@ export type TestNetworkCurrencySymbol =
* inform the MoonPay API which network the user is attempting to onramp into.
* This type reflects those possible values.
*/
type MoonPayNetworkAbbreviation = 'bsc' | 'cchain' | 'polygon';
type MoonPayNetworkAbbreviation = 'BSC' | 'CCHAIN' | 'POLYGON';
/**
* MoonPay requires some settings that are configured per network that it is
@ -49,25 +44,20 @@ type MoonPayNetworkAbbreviation = 'bsc' | 'cchain' | 'polygon';
type MoonPayChainSettings = {
/**
* What should the default onramp currency be, for example 'eth' on 'mainnet'
* This type matches a single LowercaseCurrencySymbol or a
* LowercaseCurrencySymbol and a MoonPayNetworkAbbreviation joined by a '_'.
* This type matches a single SupportedCurrencySymbol or a
* SupportedCurrencySymbol and a MoonPayNetworkAbbreviation joined by a '_'.
*/
defaultCurrencyCode:
| LowercaseCurrencySymbol
| `${LowercaseCurrencySymbol}_${MoonPayNetworkAbbreviation}`;
| SupportedCurrencySymbol
| `${SupportedCurrencySymbol}_${MoonPayNetworkAbbreviation}`;
/**
* We must also configure all possible onramp currencies we wish to support.
* This type matches 1 to 3 LowercaseCurrencySymbols, joined by ','. It also
* matches 1 or 2 LowercaseCurrencySymbols with a
* MoonPayNetworkAbbreviation joined by a '_', and concatenated with ','.
* This type matches either an array of SupportedCurrencySymbol or
* an array of SupportedCurrencySymbol and a MoonPayNetworkAbbreviation joined by a '_'.
*/
showOnlyCurrencies:
| `${LowercaseCurrencySymbol}`
| `${LowercaseCurrencySymbol},${LowercaseCurrencySymbol}`
| `${LowercaseCurrencySymbol},${LowercaseCurrencySymbol},${LowercaseCurrencySymbol}`
| `${LowercaseCurrencySymbol},${LowercaseCurrencySymbol},${LowercaseCurrencySymbol},${LowercaseCurrencySymbol}`
| `${LowercaseCurrencySymbol}_${MoonPayNetworkAbbreviation}`
| `${LowercaseCurrencySymbol}_${MoonPayNetworkAbbreviation},${LowercaseCurrencySymbol}_${MoonPayNetworkAbbreviation}`;
| SupportedCurrencySymbol[]
| `${SupportedCurrencySymbol}_${MoonPayNetworkAbbreviation}`[];
};
/**
@ -318,6 +308,7 @@ const SUPPORTED_CURRENCY_SYMBOLS = {
ASM: 'ASM',
AUCTION: 'AUCTION',
AXS: 'AXS',
AVAX: 'AVAX',
BADGER: 'BADGER',
BAL: 'BAL',
BAND: 'BAND',
@ -351,6 +342,7 @@ const SUPPORTED_CURRENCY_SYMBOLS = {
GTH: 'GTH',
HEX: 'HEX',
IOTX: 'IOTX',
IMX: 'IMX',
JASMY: 'JASMY',
KEEP: 'KEEP',
KNC: 'KNC',
@ -371,6 +363,7 @@ const SUPPORTED_CURRENCY_SYMBOLS = {
NU: 'NU',
OGN: 'OGN',
OMG: 'OMG',
ORN: 'ORN',
OXT: 'OXT',
PAX: 'PAX',
PERP: 'PERP',
@ -685,8 +678,17 @@ export const BUYABLE_CHAINS_MAP: {
SUPPORTED_CURRENCY_SYMBOLS.YLD,
],
moonPay: {
defaultCurrencyCode: 'eth',
showOnlyCurrencies: 'eth,usdt,usdc,dai',
defaultCurrencyCode: SUPPORTED_CURRENCY_SYMBOLS.ETH,
showOnlyCurrencies: [
SUPPORTED_CURRENCY_SYMBOLS.ETH,
SUPPORTED_CURRENCY_SYMBOLS.USDT,
SUPPORTED_CURRENCY_SYMBOLS.USDC,
SUPPORTED_CURRENCY_SYMBOLS.DAI,
SUPPORTED_CURRENCY_SYMBOLS.MATIC,
SUPPORTED_CURRENCY_SYMBOLS.ORN,
SUPPORTED_CURRENCY_SYMBOLS.WETH,
SUPPORTED_CURRENCY_SYMBOLS.IMX,
],
},
wyre: {
srn: 'ethereum',
@ -821,8 +823,11 @@ export const BUYABLE_CHAINS_MAP: {
SUPPORTED_CURRENCY_SYMBOLS.BUSD,
],
moonPay: {
defaultCurrencyCode: 'bnb_bsc',
showOnlyCurrencies: 'bnb_bsc,busd_bsc',
defaultCurrencyCode: `${SUPPORTED_CURRENCY_SYMBOLS.BNB}_BSC`,
showOnlyCurrencies: [
`${SUPPORTED_CURRENCY_SYMBOLS.BNB}_BSC`,
`${SUPPORTED_CURRENCY_SYMBOLS.BUSD}_BSC`,
],
},
},
[CHAIN_IDS.POLYGON]: {
@ -835,8 +840,11 @@ export const BUYABLE_CHAINS_MAP: {
SUPPORTED_CURRENCY_SYMBOLS.DAI,
],
moonPay: {
defaultCurrencyCode: 'matic_polygon',
showOnlyCurrencies: 'matic_polygon,usdc_polygon',
defaultCurrencyCode: `${SUPPORTED_CURRENCY_SYMBOLS.BNB}_POLYGON`,
showOnlyCurrencies: [
`${SUPPORTED_CURRENCY_SYMBOLS.MATIC}_POLYGON`,
`${SUPPORTED_CURRENCY_SYMBOLS.USDC}_POLYGON`,
],
},
wyre: {
srn: 'matic',
@ -848,8 +856,8 @@ export const BUYABLE_CHAINS_MAP: {
network: 'avaxcchain',
transakCurrencies: [SUPPORTED_CURRENCY_SYMBOLS.AVALANCHE],
moonPay: {
defaultCurrencyCode: 'avax_cchain',
showOnlyCurrencies: 'avax_cchain',
defaultCurrencyCode: `${SUPPORTED_CURRENCY_SYMBOLS.AVAX}_CCHAIN`,
showOnlyCurrencies: [`${SUPPORTED_CURRENCY_SYMBOLS.AVAX}_CCHAIN`],
},
wyre: {
srn: 'avalanche',
@ -867,8 +875,8 @@ export const BUYABLE_CHAINS_MAP: {
network: 'celo',
transakCurrencies: [SUPPORTED_CURRENCY_SYMBOLS.CELO],
moonPay: {
defaultCurrencyCode: 'celo',
showOnlyCurrencies: 'celo',
defaultCurrencyCode: SUPPORTED_CURRENCY_SYMBOLS.CELO,
showOnlyCurrencies: [SUPPORTED_CURRENCY_SYMBOLS.CELO],
},
},
};

View File

@ -28,6 +28,7 @@ import {
getIsBuyableCoinbasePayChain,
getIsBuyableCoinbasePayToken,
getIsBuyableTransakToken,
getIsBuyableMoonpayToken,
} from '../../../selectors/selectors';
import OnRampItem from './on-ramp-item';
@ -53,6 +54,9 @@ const DepositPopover = ({ onClose, token }) => {
const isTokenBuyableTransak = useSelector((state) =>
getIsBuyableTransakToken(state, token?.symbol),
);
const isTokenBuyableMoonpay = useSelector((state) =>
getIsBuyableMoonpayToken(state, token?.symbol),
);
const networkName = NETWORK_TO_NAME_MAP[chainId];
const symbol = token
@ -77,7 +81,9 @@ const DepositPopover = ({ onClose, token }) => {
);
};
const toMoonPay = () => {
dispatch(buy({ service: 'moonpay', address, chainId }));
dispatch(
buy({ service: 'moonpay', address, chainId, symbol: token?.symbol }),
);
};
const toWyre = () => {
dispatch(buy({ service: 'wyre', address, chainId }));
@ -153,7 +159,11 @@ const DepositPopover = ({ onClose, token }) => {
});
toMoonPay();
}}
hide={isTokenDeposit || !isBuyableMoonPayChain}
hide={
isTokenDeposit
? !isBuyableMoonPayChain || !isTokenBuyableMoonpay
: !isBuyableMoonPayChain
}
/>
<OnRampItem

View File

@ -21,6 +21,7 @@ import {
getIsSwapsChain,
getIsBuyableCoinbasePayToken,
getIsBuyableTransakToken,
getIsBuyableMoonpayToken,
} from '../../../selectors/selectors';
import BuyIcon from '../../ui/icon/overview-buy-icon.component';
@ -59,7 +60,11 @@ const TokenOverview = ({ className, token }) => {
const isTokenBuyableTransak = useSelector((state) =>
getIsBuyableTransakToken(state, token.symbol),
);
const isBuyable = isTokenBuyableCoinbasePay || isTokenBuyableTransak;
const isTokenBuyableMoonpay = useSelector((state) =>
getIsBuyableMoonpayToken(state, token.symbol),
);
const isBuyable =
isTokenBuyableCoinbasePay || isTokenBuyableTransak || isTokenBuyableMoonpay;
useEffect(() => {
if (token.isERC721 && process.env.COLLECTIBLES_V1) {

View File

@ -0,0 +1,19 @@
import {
BUYABLE_CHAINS_MAP,
CHAIN_IDS,
} from '../../../shared/constants/network';
export const formatMoonpaySymbol = (symbol, chainId = CHAIN_IDS.MAINNET) => {
if (!symbol) {
return symbol;
}
let _symbol = symbol;
if (chainId === CHAIN_IDS.POLYGON || chainId === CHAIN_IDS.BSC) {
_symbol = `${_symbol}_${BUYABLE_CHAINS_MAP?.[
chainId
]?.network.toUpperCase()}`;
} else if (chainId === CHAIN_IDS.AVALANCHE) {
_symbol = `${_symbol}_CCHAIN`;
}
return _symbol;
};

View File

@ -0,0 +1,33 @@
import { CHAIN_IDS } from '../../../shared/constants/network';
import { formatMoonpaySymbol } from './moonpay';
describe('Moonpay Utils', () => {
describe('formatMoonpaySymbol', () => {
it('should return the same input if falsy input is provided', () => {
expect(formatMoonpaySymbol()).toBe(undefined);
expect(formatMoonpaySymbol(null)).toBe(null);
expect(formatMoonpaySymbol('')).toBe('');
});
it('should return the symbol in uppercase if no chainId is provided', () => {
const result = formatMoonpaySymbol('ETH');
expect(result).toStrictEqual('ETH');
});
it('should return the symbol in uppercase if chainId is different than Avalanche/BSC/Polygon', () => {
const result = formatMoonpaySymbol('ETH', CHAIN_IDS.MAINNET);
expect(result).toStrictEqual('ETH');
const result2 = formatMoonpaySymbol('CELO', CHAIN_IDS.CELO);
expect(result2).toStrictEqual('CELO');
});
it('should return the symbol in uppercase with the network name if chainId is Avalanche/BSC/Polygon', () => {
const result = formatMoonpaySymbol('BNB', CHAIN_IDS.BSC);
expect(result).toStrictEqual('BNB_BSC');
const result2 = formatMoonpaySymbol('MATIC', CHAIN_IDS.POLYGON);
expect(result2).toStrictEqual('MATIC_POLYGON');
const result3 = formatMoonpaySymbol('AVAX', CHAIN_IDS.AVALANCHE);
expect(result3).toStrictEqual('AVAX_CCHAIN');
});
});
});

View File

@ -63,6 +63,7 @@ import {
} from '../ducks/app/app';
import { isEqualCaseInsensitive } from '../../shared/modules/string-utils';
import { hexToDecimal } from '../../shared/lib/metamask-controller-utils';
import { formatMoonpaySymbol } from '../helpers/utils/moonpay';
///: BEGIN:ONLY_INCLUDE_IN(flask)
import { SNAPS_VIEW_ROUTE } from '../helpers/constants/routes';
import { getPermissionSubjects } from './permissions';
@ -716,6 +717,16 @@ export function getIsBuyableTransakToken(state, symbol) {
);
}
export function getIsBuyableMoonpayToken(state, symbol) {
const chainId = getCurrentChainId(state);
const _symbol = formatMoonpaySymbol(symbol, chainId);
return Boolean(
BUYABLE_CHAINS_MAP?.[chainId]?.moonPay.showOnlyCurrencies?.includes(
_symbol,
),
);
}
export function getIsBuyableMoonPayChain(state) {
const chainId = getCurrentChainId(state);
return Boolean(BUYABLE_CHAINS_MAP?.[chainId]?.moonPay);