mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-11-22 09:57:02 +01:00
feat(mme-17214): migrate L33 files to typescript (#17372)
This commit is contained in:
parent
8a7c897a1c
commit
2958d68af8
@ -4,6 +4,9 @@ import { SWAPS_API_V2_BASE_URL } from '../../../shared/constants/swaps';
|
|||||||
import {
|
import {
|
||||||
BUYABLE_CHAINS_MAP,
|
BUYABLE_CHAINS_MAP,
|
||||||
CHAIN_IDS,
|
CHAIN_IDS,
|
||||||
|
WyreChainSettings,
|
||||||
|
CurrencySymbol,
|
||||||
|
ChainId,
|
||||||
} from '../../../shared/constants/network';
|
} from '../../../shared/constants/network';
|
||||||
import getFetchWithTimeout from '../../../shared/modules/fetch-with-timeout';
|
import getFetchWithTimeout from '../../../shared/modules/fetch-with-timeout';
|
||||||
import {
|
import {
|
||||||
@ -18,13 +21,17 @@ const fetchWithTimeout = getFetchWithTimeout();
|
|||||||
/**
|
/**
|
||||||
* Create a Wyre purchase URL.
|
* Create a Wyre purchase URL.
|
||||||
*
|
*
|
||||||
* @param {string} walletAddress - Ethereum destination address
|
* @param walletAddress - Ethereum destination address
|
||||||
* @param {string} chainId - Current chain ID
|
* @param chainId - Current chain ID
|
||||||
* @param {string|undefined} symbol - Token symbol to buy
|
* @param symbol - Token symbol to buy
|
||||||
* @returns String
|
* @returns String
|
||||||
*/
|
*/
|
||||||
const createWyrePurchaseUrl = async (walletAddress, chainId, symbol) => {
|
const createWyrePurchaseUrl = async (
|
||||||
const { wyre = {} } = BUYABLE_CHAINS_MAP[chainId];
|
walletAddress: string,
|
||||||
|
chainId: keyof typeof BUYABLE_CHAINS_MAP,
|
||||||
|
symbol: CurrencySymbol,
|
||||||
|
): Promise<any> => {
|
||||||
|
const { wyre = {} as WyreChainSettings } = BUYABLE_CHAINS_MAP[chainId];
|
||||||
const { srn, currencyCode } = wyre;
|
const { srn, currencyCode } = wyre;
|
||||||
|
|
||||||
const networkId = parseInt(chainId, 16);
|
const networkId = parseInt(chainId, 16);
|
||||||
@ -57,12 +64,16 @@ const createWyrePurchaseUrl = async (walletAddress, chainId, symbol) => {
|
|||||||
* Create a Transak Checkout URL.
|
* Create a Transak Checkout URL.
|
||||||
* API docs here: https://www.notion.so/Query-Parameters-9ec523df3b874ec58cef4fa3a906f238
|
* API docs here: https://www.notion.so/Query-Parameters-9ec523df3b874ec58cef4fa3a906f238
|
||||||
*
|
*
|
||||||
* @param {string} walletAddress - Ethereum destination address
|
* @param walletAddress - Ethereum destination address
|
||||||
* @param {string} chainId - Current chain ID
|
* @param chainId - Current chain ID
|
||||||
* @param {string|undefined} symbol - Token symbol to buy
|
* @param symbol - Token symbol to buy
|
||||||
* @returns String
|
* @returns String
|
||||||
*/
|
*/
|
||||||
const createTransakUrl = (walletAddress, chainId, symbol) => {
|
const createTransakUrl = (
|
||||||
|
walletAddress: string,
|
||||||
|
chainId: keyof typeof BUYABLE_CHAINS_MAP,
|
||||||
|
symbol: CurrencySymbol,
|
||||||
|
): string => {
|
||||||
const { nativeCurrency, network } = BUYABLE_CHAINS_MAP[chainId];
|
const { nativeCurrency, network } = BUYABLE_CHAINS_MAP[chainId];
|
||||||
|
|
||||||
const queryParams = new URLSearchParams({
|
const queryParams = new URLSearchParams({
|
||||||
@ -79,13 +90,17 @@ const createTransakUrl = (walletAddress, chainId, symbol) => {
|
|||||||
/**
|
/**
|
||||||
* Create a MoonPay Checkout URL.
|
* Create a MoonPay Checkout URL.
|
||||||
*
|
*
|
||||||
* @param {string} walletAddress - Destination address
|
* @param walletAddress - Destination address
|
||||||
* @param {string} chainId - Current chain ID
|
* @param chainId - Current chain ID
|
||||||
* @param {string|undefined} symbol - Token symbol to buy
|
* @param symbol - Token symbol to buy
|
||||||
* @returns String
|
* @returns String
|
||||||
*/
|
*/
|
||||||
const createMoonPayUrl = async (walletAddress, chainId, symbol) => {
|
const createMoonPayUrl = async (
|
||||||
const { moonPay: { defaultCurrencyCode, showOnlyCurrencies } = {} } =
|
walletAddress: string,
|
||||||
|
chainId: keyof typeof BUYABLE_CHAINS_MAP,
|
||||||
|
symbol: CurrencySymbol,
|
||||||
|
): Promise<string> => {
|
||||||
|
const { moonPay: { defaultCurrencyCode, showOnlyCurrencies } = {} as any } =
|
||||||
BUYABLE_CHAINS_MAP[chainId];
|
BUYABLE_CHAINS_MAP[chainId];
|
||||||
const moonPayQueryParams = new URLSearchParams({
|
const moonPayQueryParams = new URLSearchParams({
|
||||||
apiKey: MOONPAY_API_KEY,
|
apiKey: MOONPAY_API_KEY,
|
||||||
@ -121,12 +136,16 @@ const createMoonPayUrl = async (walletAddress, chainId, symbol) => {
|
|||||||
/**
|
/**
|
||||||
* Create a Coinbase Pay Checkout URL.
|
* Create a Coinbase Pay Checkout URL.
|
||||||
*
|
*
|
||||||
* @param {string} walletAddress - Ethereum destination address
|
* @param walletAddress - Ethereum destination address
|
||||||
* @param {string} chainId - Current chain ID
|
* @param chainId - Current chain ID
|
||||||
* @param {string|undefined} symbol - Token symbol to buy
|
* @param symbol - Token symbol to buy
|
||||||
* @returns String
|
* @returns String
|
||||||
*/
|
*/
|
||||||
const createCoinbasePayUrl = (walletAddress, chainId, symbol) => {
|
const createCoinbasePayUrl = (
|
||||||
|
walletAddress: string,
|
||||||
|
chainId: keyof typeof BUYABLE_CHAINS_MAP,
|
||||||
|
symbol: CurrencySymbol,
|
||||||
|
): string => {
|
||||||
// since coinbasePayCurrencies is going to be extended to include all tokens supported
|
// 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
|
// we now default to nativeCurrency instead of the 2 previous tokens + eth that we had before
|
||||||
const { nativeCurrency } = BUYABLE_CHAINS_MAP[chainId];
|
const { nativeCurrency } = BUYABLE_CHAINS_MAP[chainId];
|
||||||
@ -146,15 +165,25 @@ const createCoinbasePayUrl = (walletAddress, chainId, symbol) => {
|
|||||||
/**
|
/**
|
||||||
* Gives the caller a url at which the user can acquire eth, depending on the network they are in
|
* Gives the caller a url at which the user can acquire eth, depending on the network they are in
|
||||||
*
|
*
|
||||||
* @param {object} opts - Options required to determine the correct url
|
* @param opts - Options required to determine the correct url
|
||||||
* @param {string} opts.chainId - The chainId for which to return a url
|
* @param 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.address - The address the bought ETH should be sent to. Only relevant if chainId === '0x1'.
|
||||||
* @param opts.service
|
* @param opts.service
|
||||||
* @param {string|undefined} opts.symbol - The symbol of the token to buy. Only relevant if buying a token.
|
* @param 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
|
* @returns 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.
|
* 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, symbol }) {
|
export default async function getBuyUrl({
|
||||||
|
chainId,
|
||||||
|
address,
|
||||||
|
service,
|
||||||
|
symbol,
|
||||||
|
}: {
|
||||||
|
chainId: keyof typeof BUYABLE_CHAINS_MAP;
|
||||||
|
address: string;
|
||||||
|
service: string;
|
||||||
|
symbol: CurrencySymbol;
|
||||||
|
}): Promise<string> {
|
||||||
// default service by network if not specified
|
// default service by network if not specified
|
||||||
if (!service) {
|
if (!service) {
|
||||||
// eslint-disable-next-line no-param-reassign
|
// eslint-disable-next-line no-param-reassign
|
||||||
@ -183,7 +212,7 @@ export default async function getBuyUrl({ chainId, address, service, symbol }) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDefaultServiceForChain(chainId) {
|
function getDefaultServiceForChain(chainId: ChainId): string {
|
||||||
switch (chainId) {
|
switch (chainId) {
|
||||||
case CHAIN_IDS.MAINNET:
|
case CHAIN_IDS.MAINNET:
|
||||||
return 'wyre';
|
return 'wyre';
|
@ -174,15 +174,19 @@ async function verifyEnglishLocale() {
|
|||||||
// and gradually phase out the key based search
|
// and gradually phase out the key based search
|
||||||
const globsToStrictSearch = [
|
const globsToStrictSearch = [
|
||||||
'ui/components/app/metamask-translation/*.js',
|
'ui/components/app/metamask-translation/*.js',
|
||||||
|
'ui/components/app/metamask-translation/*.ts',
|
||||||
'ui/pages/confirmation/templates/*.js',
|
'ui/pages/confirmation/templates/*.js',
|
||||||
|
'ui/pages/confirmation/templates/*.ts',
|
||||||
];
|
];
|
||||||
const testGlob = '**/*.test.js';
|
const testGlob = '**/*.test.js';
|
||||||
const javascriptFiles = await glob(
|
const javascriptFiles = await glob(
|
||||||
[
|
[
|
||||||
'ui/**/*.js',
|
'ui/**/*.js',
|
||||||
|
'ui/**/*.ts',
|
||||||
'shared/**/*.js',
|
'shared/**/*.js',
|
||||||
'shared/**/*.ts',
|
'shared/**/*.ts',
|
||||||
'app/scripts/constants/**/*.js',
|
'app/scripts/constants/**/*.js',
|
||||||
|
'app/scripts/constants/**/*.ts',
|
||||||
],
|
],
|
||||||
{
|
{
|
||||||
ignore: [...globsToStrictSearch, testGlob],
|
ignore: [...globsToStrictSearch, testGlob],
|
||||||
|
@ -306,7 +306,7 @@
|
|||||||
"labeled-stream-splicer": "^2.0.2",
|
"labeled-stream-splicer": "^2.0.2",
|
||||||
"localforage": "^1.9.0",
|
"localforage": "^1.9.0",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"loglevel": "^1.4.1",
|
"loglevel": "^1.8.1",
|
||||||
"luxon": "^3.2.1",
|
"luxon": "^3.2.1",
|
||||||
"nanoid": "^2.1.6",
|
"nanoid": "^2.1.6",
|
||||||
"nonce-tracker": "^1.0.0",
|
"nonce-tracker": "^1.0.0",
|
||||||
|
@ -17,7 +17,8 @@ export type ChainId = typeof CHAIN_IDS[keyof typeof CHAIN_IDS];
|
|||||||
* This type is non-exhaustive, and cannot be used for areas where the user
|
* This type is non-exhaustive, and cannot be used for areas where the user
|
||||||
* or dapp may supply their own symbol.
|
* or dapp may supply their own symbol.
|
||||||
*/
|
*/
|
||||||
type CurrencySymbol = typeof CURRENCY_SYMBOLS[keyof typeof CURRENCY_SYMBOLS];
|
export type CurrencySymbol =
|
||||||
|
typeof CURRENCY_SYMBOLS[keyof typeof CURRENCY_SYMBOLS];
|
||||||
/**
|
/**
|
||||||
* A type that is a union type for the supported symbols on different onramp providers.
|
* A type that is a union type for the supported symbols on different onramp providers.
|
||||||
*/
|
*/
|
||||||
@ -41,7 +42,7 @@ type MoonPayNetworkAbbreviation = 'BSC' | 'CCHAIN' | 'POLYGON';
|
|||||||
* MoonPay requires some settings that are configured per network that it is
|
* MoonPay requires some settings that are configured per network that it is
|
||||||
* enabled on. This type describes those settings.
|
* enabled on. This type describes those settings.
|
||||||
*/
|
*/
|
||||||
type MoonPayChainSettings = {
|
export type MoonPayChainSettings = {
|
||||||
/**
|
/**
|
||||||
* What should the default onramp currency be, for example 'eth' on 'mainnet'
|
* What should the default onramp currency be, for example 'eth' on 'mainnet'
|
||||||
* This type matches a single SupportedCurrencySymbol or a
|
* This type matches a single SupportedCurrencySymbol or a
|
||||||
@ -77,7 +78,7 @@ type RPCPreferences = {
|
|||||||
/**
|
/**
|
||||||
* An object that describes a network to be used inside of MetaMask
|
* An object that describes a network to be used inside of MetaMask
|
||||||
*/
|
*/
|
||||||
type RPCDefinition = {
|
export type RPCDefinition = {
|
||||||
/**
|
/**
|
||||||
* The hex encoded ChainId for the network
|
* The hex encoded ChainId for the network
|
||||||
*/
|
*/
|
||||||
@ -104,7 +105,7 @@ type RPCDefinition = {
|
|||||||
* Wyre is a fiat onramp provider. We must provide some settings for networks
|
* Wyre is a fiat onramp provider. We must provide some settings for networks
|
||||||
* that support Wyre.
|
* that support Wyre.
|
||||||
*/
|
*/
|
||||||
type WyreChainSettings = {
|
export type WyreChainSettings = {
|
||||||
/**
|
/**
|
||||||
* The network name
|
* The network name
|
||||||
*/
|
*/
|
||||||
|
@ -20,7 +20,7 @@ export const SWAPS_FETCH_ORDER_CONFLICT = 'swaps-fetch-order-conflict';
|
|||||||
// in place of the token address that ERC-20 tokens have
|
// in place of the token address that ERC-20 tokens have
|
||||||
const DEFAULT_TOKEN_ADDRESS = '0x0000000000000000000000000000000000000000';
|
const DEFAULT_TOKEN_ADDRESS = '0x0000000000000000000000000000000000000000';
|
||||||
|
|
||||||
interface SwapsTokenObject {
|
export interface SwapsTokenObject {
|
||||||
/**
|
/**
|
||||||
* The symbol of token object
|
* The symbol of token object
|
||||||
*/
|
*/
|
||||||
|
@ -143,12 +143,13 @@ const getBaseUrlForNewSwapsApi = (type, chainId) => {
|
|||||||
return `${v2ApiBaseUrl}/networks/${chainIdDecimal}`;
|
return `${v2ApiBaseUrl}/networks/${chainIdDecimal}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getBaseApi = function (type, chainId = CHAIN_IDS.MAINNET) {
|
export const getBaseApi = function (type, chainId) {
|
||||||
// eslint-disable-next-line no-param-reassign
|
const _chainId = TEST_CHAIN_IDS.includes(chainId)
|
||||||
chainId = TEST_CHAIN_IDS.includes(chainId) ? CHAIN_IDS.MAINNET : chainId;
|
? CHAIN_IDS.MAINNET
|
||||||
const baseUrl = getBaseUrlForNewSwapsApi(type, chainId);
|
: chainId;
|
||||||
|
const baseUrl = getBaseUrlForNewSwapsApi(type, _chainId);
|
||||||
if (!baseUrl) {
|
if (!baseUrl) {
|
||||||
throw new Error(`Swaps API calls are disabled for chainId: ${chainId}`);
|
throw new Error(`Swaps API calls are disabled for chainId: ${_chainId}`);
|
||||||
}
|
}
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'trade':
|
case 'trade':
|
||||||
|
1
shared/modules/declare-modules.d.ts
vendored
Normal file
1
shared/modules/declare-modules.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
declare module 'human-standard-token-abi';
|
@ -49,7 +49,7 @@ import {
|
|||||||
isContractAddressValid,
|
isContractAddressValid,
|
||||||
getSwapsLivenessForNetwork,
|
getSwapsLivenessForNetwork,
|
||||||
parseSmartTransactionsError,
|
parseSmartTransactionsError,
|
||||||
stxErrorTypes,
|
StxErrorTypes,
|
||||||
} from '../../pages/swaps/swaps.util';
|
} from '../../pages/swaps/swaps.util';
|
||||||
import {
|
import {
|
||||||
addHexes,
|
addHexes,
|
||||||
@ -206,9 +206,9 @@ const slice = createSlice({
|
|||||||
state.customGas.fallBackPrice = action.payload;
|
state.customGas.fallBackPrice = action.payload;
|
||||||
},
|
},
|
||||||
setCurrentSmartTransactionsError: (state, action) => {
|
setCurrentSmartTransactionsError: (state, action) => {
|
||||||
const errorType = Object.values(stxErrorTypes).includes(action.payload)
|
const errorType = Object.values(StxErrorTypes).includes(action.payload)
|
||||||
? action.payload
|
? action.payload
|
||||||
: stxErrorTypes.UNAVAILABLE;
|
: StxErrorTypes.UNAVAILABLE;
|
||||||
state.currentSmartTransactionsError = errorType;
|
state.currentSmartTransactionsError = errorType;
|
||||||
},
|
},
|
||||||
setSwapsSTXSubmitLoading: (state, action) => {
|
setSwapsSTXSubmitLoading: (state, action) => {
|
||||||
@ -554,7 +554,7 @@ const disableStxIfRegularTxInProgress = (dispatch, transactions) => {
|
|||||||
for (const transaction of transactions) {
|
for (const transaction of transactions) {
|
||||||
if (IN_PROGRESS_TRANSACTION_STATUSES.includes(transaction.status)) {
|
if (IN_PROGRESS_TRANSACTION_STATUSES.includes(transaction.status)) {
|
||||||
dispatch(
|
dispatch(
|
||||||
setCurrentSmartTransactionsError(stxErrorTypes.REGULAR_TX_IN_PROGRESS),
|
setCurrentSmartTransactionsError(StxErrorTypes.REGULAR_TX_IN_PROGRESS),
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -581,8 +581,8 @@ export const fetchSwapsLivenessAndFeatureFlags = () => {
|
|||||||
disableStxIfRegularTxInProgress(dispatch, transactions);
|
disableStxIfRegularTxInProgress(dispatch, transactions);
|
||||||
}
|
}
|
||||||
swapsLivenessForNetwork = getSwapsLivenessForNetwork(
|
swapsLivenessForNetwork = getSwapsLivenessForNetwork(
|
||||||
swapsFeatureFlags,
|
|
||||||
chainId,
|
chainId,
|
||||||
|
swapsFeatureFlags,
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error(
|
log.error(
|
||||||
@ -621,8 +621,8 @@ export const fetchQuotesAndSetQuoteState = (
|
|||||||
try {
|
try {
|
||||||
const swapsFeatureFlags = await fetchSwapsFeatureFlags();
|
const swapsFeatureFlags = await fetchSwapsFeatureFlags();
|
||||||
swapsLivenessForNetwork = getSwapsLivenessForNetwork(
|
swapsLivenessForNetwork = getSwapsLivenessForNetwork(
|
||||||
swapsFeatureFlags,
|
|
||||||
chainId,
|
chainId,
|
||||||
|
swapsFeatureFlags,
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error('Failed to fetch Swaps liveness, defaulting to false.', error);
|
log.error('Failed to fetch Swaps liveness, defaulting to false.', error);
|
||||||
@ -939,7 +939,7 @@ export const signAndSendSwapsSmartTransaction = ({
|
|||||||
if (!fees) {
|
if (!fees) {
|
||||||
log.error('"fetchSwapsSmartTransactionFees" failed');
|
log.error('"fetchSwapsSmartTransactionFees" failed');
|
||||||
dispatch(setSwapsSTXSubmitLoading(false));
|
dispatch(setSwapsSTXSubmitLoading(false));
|
||||||
dispatch(setCurrentSmartTransactionsError(stxErrorTypes.UNAVAILABLE));
|
dispatch(setCurrentSmartTransactionsError(StxErrorTypes.UNAVAILABLE));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (approveTxParams) {
|
if (approveTxParams) {
|
||||||
@ -1020,8 +1020,8 @@ export const signAndSendTransactions = (
|
|||||||
try {
|
try {
|
||||||
const swapsFeatureFlags = await fetchSwapsFeatureFlags();
|
const swapsFeatureFlags = await fetchSwapsFeatureFlags();
|
||||||
swapsLivenessForNetwork = getSwapsLivenessForNetwork(
|
swapsLivenessForNetwork = getSwapsLivenessForNetwork(
|
||||||
swapsFeatureFlags,
|
|
||||||
chainId,
|
chainId,
|
||||||
|
swapsFeatureFlags,
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error('Failed to fetch Swaps liveness, defaulting to false.', error);
|
log.error('Failed to fetch Swaps liveness, defaulting to false.', error);
|
||||||
@ -1329,7 +1329,7 @@ export function fetchSwapsSmartTransactionFees({
|
|||||||
const errorObj = parseSmartTransactionsError(e.message);
|
const errorObj = parseSmartTransactionsError(e.message);
|
||||||
if (
|
if (
|
||||||
fallbackOnNotEnoughFunds ||
|
fallbackOnNotEnoughFunds ||
|
||||||
errorObj?.error !== stxErrorTypes.NOT_ENOUGH_FUNDS
|
errorObj?.error !== StxErrorTypes.NOT_ENOUGH_FUNDS
|
||||||
) {
|
) {
|
||||||
dispatch(setCurrentSmartTransactionsError(errorObj?.error));
|
dispatch(setCurrentSmartTransactionsError(errorObj?.error));
|
||||||
}
|
}
|
||||||
|
@ -129,7 +129,7 @@ describe('i18n helper', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should correctly render falsey substitutions', () => {
|
it('should correctly render falsy substitutions', () => {
|
||||||
const result = t(TEST_KEY_4, [0, -0, '', false, NaN]);
|
const result = t(TEST_KEY_4, [0, -0, '', false, NaN]);
|
||||||
expect(result).toStrictEqual('0 - 0 - - false - NaN');
|
expect(result).toStrictEqual('0 - 0 - - false - NaN');
|
||||||
});
|
});
|
||||||
|
@ -1,25 +1,60 @@
|
|||||||
// cross-browser connection to extension i18n API
|
// cross-browser connection to extension i18n API
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import log from 'loglevel';
|
import log from 'loglevel';
|
||||||
|
import { Json } from '@metamask/utils';
|
||||||
import * as Sentry from '@sentry/browser';
|
import * as Sentry from '@sentry/browser';
|
||||||
import getFetchWithTimeout from '../../../shared/modules/fetch-with-timeout';
|
import getFetchWithTimeout from '../../../shared/modules/fetch-with-timeout';
|
||||||
|
|
||||||
const fetchWithTimeout = getFetchWithTimeout();
|
const fetchWithTimeout = getFetchWithTimeout();
|
||||||
|
|
||||||
const warned = {};
|
// From app/_locales folders there is a messages.json file such as app/_locales/en, comes with key and translated results
|
||||||
const missingMessageErrors = {};
|
// and we use as t('reject') to get the translated message in the codebase
|
||||||
const missingSubstitutionErrors = {};
|
// and in i18n lib, the translated message is an object (I18NMessage) with message & description -
|
||||||
|
// message is the string that will replace the translationKey, and that message may contain replacement variables such as $1, $2, etc.
|
||||||
|
// Description is key describing the usage of the message.
|
||||||
|
interface I18NMessage {
|
||||||
|
message: string;
|
||||||
|
description?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The overall translation file is made of same entries
|
||||||
|
// translationKey (string) and the I18NMessage as the value.
|
||||||
|
interface I18NMessageDict {
|
||||||
|
[translationKey: string]: I18NMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
// A parameterized type (or generic type) of maps that use the same structure (translationKey) key
|
||||||
|
interface I18NMessageDictMap<R> {
|
||||||
|
[translationKey: string]: R;
|
||||||
|
}
|
||||||
|
|
||||||
|
const warned: { [localeCode: string]: I18NMessageDictMap<boolean> } = {};
|
||||||
|
const missingMessageErrors: I18NMessageDictMap<Error> = {};
|
||||||
|
const missingSubstitutionErrors: {
|
||||||
|
[localeCode: string]: I18NMessageDictMap<boolean>;
|
||||||
|
} = {};
|
||||||
|
|
||||||
|
function getHasSubstitutions(
|
||||||
|
substitutions?: string[],
|
||||||
|
): substitutions is string[] {
|
||||||
|
return (substitutions?.length ?? 0) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a localized message for the given key
|
* Returns a localized message for the given key
|
||||||
*
|
*
|
||||||
* @param {string} localeCode - The code for the current locale
|
* @param localeCode - The code for the current locale
|
||||||
* @param {object} localeMessages - The map of messages for the current locale
|
* @param localeMessages - The map of messages for the current locale
|
||||||
* @param {string} key - The message key
|
* @param key - The message key
|
||||||
* @param {string[]} substitutions - A list of message substitution replacements
|
* @param substitutions - A list of message substitution replacements can replace $n in given message
|
||||||
* @returns {null|string} The localized message
|
* @returns The localized message
|
||||||
*/
|
*/
|
||||||
export const getMessage = (localeCode, localeMessages, key, substitutions) => {
|
export const getMessage = (
|
||||||
|
localeCode: string,
|
||||||
|
localeMessages: I18NMessageDict,
|
||||||
|
key: string,
|
||||||
|
substitutions?: string[],
|
||||||
|
): JSX.Element | string | null => {
|
||||||
if (!localeMessages) {
|
if (!localeMessages) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -46,23 +81,22 @@ export const getMessage = (localeCode, localeMessages, key, substitutions) => {
|
|||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const entry = localeMessages[key];
|
|
||||||
let phrase = entry.message;
|
|
||||||
|
|
||||||
const hasSubstitutions = Boolean(substitutions && substitutions.length);
|
const hasSubstitutions = getHasSubstitutions(substitutions);
|
||||||
const hasReactSubstitutions =
|
const hasReactSubstitutions =
|
||||||
hasSubstitutions &&
|
hasSubstitutions &&
|
||||||
substitutions.some(
|
substitutions?.some(
|
||||||
(element) =>
|
(element) =>
|
||||||
element !== null &&
|
element !== null &&
|
||||||
(typeof element === 'function' || typeof element === 'object'),
|
(typeof element === 'function' || typeof element === 'object'),
|
||||||
);
|
);
|
||||||
|
const entry = localeMessages[key];
|
||||||
|
const phrase = entry.message;
|
||||||
// perform substitutions
|
// perform substitutions
|
||||||
if (hasSubstitutions) {
|
if (hasSubstitutions) {
|
||||||
const parts = phrase.split(/(\$\d)/gu);
|
const parts = phrase.split(/(\$\d)/gu);
|
||||||
|
|
||||||
const substitutedParts = parts.map((part) => {
|
const substitutedParts = parts.map((part: string) => {
|
||||||
const subMatch = part.match(/\$(\d)/u);
|
const subMatch = part.match(/\$(\d)/u);
|
||||||
if (!subMatch) {
|
if (!subMatch) {
|
||||||
return part;
|
return part;
|
||||||
@ -83,20 +117,21 @@ export const getMessage = (localeCode, localeMessages, key, substitutions) => {
|
|||||||
log.error(error);
|
log.error(error);
|
||||||
Sentry.captureException(error);
|
Sentry.captureException(error);
|
||||||
}
|
}
|
||||||
return substitutions[substituteIndex];
|
return substitutions?.[substituteIndex];
|
||||||
});
|
});
|
||||||
|
|
||||||
phrase = hasReactSubstitutions ? (
|
return hasReactSubstitutions ? (
|
||||||
<span> {substitutedParts} </span>
|
<span> {substitutedParts} </span>
|
||||||
) : (
|
) : (
|
||||||
substitutedParts.join('')
|
substitutedParts.join('')
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return phrase;
|
return phrase;
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function fetchLocale(localeCode) {
|
export async function fetchLocale(
|
||||||
|
localeCode: string,
|
||||||
|
): Promise<I18NMessageDict> {
|
||||||
try {
|
try {
|
||||||
const response = await fetchWithTimeout(
|
const response = await fetchWithTimeout(
|
||||||
`./_locales/${localeCode}/messages.json`,
|
`./_locales/${localeCode}/messages.json`,
|
||||||
@ -110,19 +145,21 @@ export async function fetchLocale(localeCode) {
|
|||||||
|
|
||||||
const relativeTimeFormatLocaleData = new Set();
|
const relativeTimeFormatLocaleData = new Set();
|
||||||
|
|
||||||
export async function loadRelativeTimeFormatLocaleData(localeCode) {
|
export async function loadRelativeTimeFormatLocaleData(
|
||||||
|
localeCode: string,
|
||||||
|
): Promise<void> {
|
||||||
const languageTag = localeCode.split('_')[0];
|
const languageTag = localeCode.split('_')[0];
|
||||||
if (
|
if (
|
||||||
Intl.RelativeTimeFormat &&
|
Intl.RelativeTimeFormat &&
|
||||||
typeof Intl.RelativeTimeFormat.__addLocaleData === 'function' &&
|
typeof (Intl.RelativeTimeFormat as any).__addLocaleData === 'function' &&
|
||||||
!relativeTimeFormatLocaleData.has(languageTag)
|
!relativeTimeFormatLocaleData.has(languageTag)
|
||||||
) {
|
) {
|
||||||
const localeData = await fetchRelativeTimeFormatData(languageTag);
|
const localeData = await fetchRelativeTimeFormatData(languageTag);
|
||||||
Intl.RelativeTimeFormat.__addLocaleData(localeData);
|
(Intl.RelativeTimeFormat as any).__addLocaleData(localeData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchRelativeTimeFormatData(languageTag) {
|
async function fetchRelativeTimeFormatData(languageTag: string): Promise<Json> {
|
||||||
const response = await fetchWithTimeout(
|
const response = await fetchWithTimeout(
|
||||||
`./intl/${languageTag}/relative-time-format-data.json`,
|
`./intl/${languageTag}/relative-time-format-data.json`,
|
||||||
);
|
);
|
@ -3,7 +3,10 @@ import {
|
|||||||
CHAIN_IDS,
|
CHAIN_IDS,
|
||||||
} from '../../../shared/constants/network';
|
} from '../../../shared/constants/network';
|
||||||
|
|
||||||
export const formatMoonpaySymbol = (symbol, chainId = CHAIN_IDS.MAINNET) => {
|
export const formatMoonpaySymbol = (
|
||||||
|
symbol: string | null,
|
||||||
|
chainId: keyof typeof BUYABLE_CHAINS_MAP,
|
||||||
|
): string | null => {
|
||||||
if (!symbol) {
|
if (!symbol) {
|
||||||
return symbol;
|
return symbol;
|
||||||
}
|
}
|
@ -11,7 +11,16 @@ describe('txHelper', () => {
|
|||||||
c: { metamaskNetworkId, time: 2 },
|
c: { metamaskNetworkId, time: 2 },
|
||||||
};
|
};
|
||||||
|
|
||||||
const sorted = txHelper(txs, null, null, metamaskNetworkId, chainId);
|
const sorted = txHelper(
|
||||||
|
txs,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
metamaskNetworkId,
|
||||||
|
chainId,
|
||||||
|
);
|
||||||
expect(sorted[0].time).toStrictEqual(1);
|
expect(sorted[0].time).toStrictEqual(1);
|
||||||
expect(sorted[2].time).toStrictEqual(3);
|
expect(sorted[2].time).toStrictEqual(3);
|
||||||
});
|
});
|
||||||
|
@ -3,15 +3,15 @@ import { transactionMatchesNetwork } from '../../../shared/modules/transaction.u
|
|||||||
import { valuesFor } from './util';
|
import { valuesFor } from './util';
|
||||||
|
|
||||||
export default function txHelper(
|
export default function txHelper(
|
||||||
unapprovedTxs,
|
unapprovedTxs: Record<string, any> | null,
|
||||||
unapprovedMsgs,
|
unapprovedMsgs: Record<string, any> | null,
|
||||||
personalMsgs,
|
personalMsgs: Record<string, any> | null,
|
||||||
decryptMsgs,
|
decryptMsgs: Record<string, any> | null,
|
||||||
encryptionPublicKeyMsgs,
|
encryptionPublicKeyMsgs: Record<string, any> | null,
|
||||||
typedMessages,
|
typedMessages: Record<string, any> | null,
|
||||||
network,
|
network: string,
|
||||||
chainId,
|
chainId: string,
|
||||||
) {
|
): Record<string, any> {
|
||||||
log.debug('tx-helper called with params:');
|
log.debug('tx-helper called with params:');
|
||||||
log.debug({
|
log.debug({
|
||||||
unapprovedTxs,
|
unapprovedTxs,
|
@ -173,6 +173,7 @@ const mapDispatchToProps = (dispatch) => ({
|
|||||||
setRecoveryPhraseReminderLastShown: (lastShown) =>
|
setRecoveryPhraseReminderLastShown: (lastShown) =>
|
||||||
dispatch(setRecoveryPhraseReminderLastShown(lastShown)),
|
dispatch(setRecoveryPhraseReminderLastShown(lastShown)),
|
||||||
setNewNetworkAdded: (newNetwork) => {
|
setNewNetworkAdded: (newNetwork) => {
|
||||||
|
console.log({ newNetwork });
|
||||||
dispatch(setNewNetworkAdded(newNetwork));
|
dispatch(setNewNetworkAdded(newNetwork));
|
||||||
},
|
},
|
||||||
setNewCollectibleAddedMessage: (message) => {
|
setNewCollectibleAddedMessage: (message) => {
|
||||||
|
@ -267,8 +267,8 @@ describe('Swaps Util', () => {
|
|||||||
};
|
};
|
||||||
expect(
|
expect(
|
||||||
getSwapsLivenessForNetwork(
|
getSwapsLivenessForNetwork(
|
||||||
MOCKS.createFeatureFlagsResponse(),
|
|
||||||
CHAIN_IDS.LOCALHOST,
|
CHAIN_IDS.LOCALHOST,
|
||||||
|
MOCKS.createFeatureFlagsResponse(),
|
||||||
),
|
),
|
||||||
).toMatchObject(expectedSwapsLiveness);
|
).toMatchObject(expectedSwapsLiveness);
|
||||||
});
|
});
|
||||||
@ -279,8 +279,8 @@ describe('Swaps Util', () => {
|
|||||||
};
|
};
|
||||||
expect(
|
expect(
|
||||||
getSwapsLivenessForNetwork(
|
getSwapsLivenessForNetwork(
|
||||||
MOCKS.createFeatureFlagsResponse(),
|
|
||||||
CHAIN_IDS.GOERLI,
|
CHAIN_IDS.GOERLI,
|
||||||
|
MOCKS.createFeatureFlagsResponse(),
|
||||||
),
|
),
|
||||||
).toMatchObject(expectedSwapsLiveness);
|
).toMatchObject(expectedSwapsLiveness);
|
||||||
});
|
});
|
||||||
@ -291,8 +291,8 @@ describe('Swaps Util', () => {
|
|||||||
};
|
};
|
||||||
expect(
|
expect(
|
||||||
getSwapsLivenessForNetwork(
|
getSwapsLivenessForNetwork(
|
||||||
MOCKS.createFeatureFlagsResponse(),
|
|
||||||
CHAIN_IDS.SEPOLIA,
|
CHAIN_IDS.SEPOLIA,
|
||||||
|
MOCKS.createFeatureFlagsResponse(),
|
||||||
),
|
),
|
||||||
).toMatchObject(expectedSwapsLiveness);
|
).toMatchObject(expectedSwapsLiveness);
|
||||||
});
|
});
|
||||||
@ -303,8 +303,8 @@ describe('Swaps Util', () => {
|
|||||||
};
|
};
|
||||||
expect(
|
expect(
|
||||||
getSwapsLivenessForNetwork(
|
getSwapsLivenessForNetwork(
|
||||||
MOCKS.createFeatureFlagsResponse(),
|
|
||||||
CHAIN_IDS.MAINNET,
|
CHAIN_IDS.MAINNET,
|
||||||
|
MOCKS.createFeatureFlagsResponse(),
|
||||||
),
|
),
|
||||||
).toMatchObject(expectedSwapsLiveness);
|
).toMatchObject(expectedSwapsLiveness);
|
||||||
});
|
});
|
||||||
@ -316,7 +316,7 @@ describe('Swaps Util', () => {
|
|||||||
const swapsFeatureFlags = MOCKS.createFeatureFlagsResponse();
|
const swapsFeatureFlags = MOCKS.createFeatureFlagsResponse();
|
||||||
swapsFeatureFlags[ETHEREUM].extensionActive = false;
|
swapsFeatureFlags[ETHEREUM].extensionActive = false;
|
||||||
expect(
|
expect(
|
||||||
getSwapsLivenessForNetwork(swapsFeatureFlags, CHAIN_IDS.MAINNET),
|
getSwapsLivenessForNetwork(CHAIN_IDS.MAINNET, swapsFeatureFlags),
|
||||||
).toMatchObject(expectedSwapsLiveness);
|
).toMatchObject(expectedSwapsLiveness);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,24 +1,27 @@
|
|||||||
import BigNumber from 'bignumber.js';
|
import { BigNumber } from 'bignumber.js';
|
||||||
import abi from 'human-standard-token-abi';
|
import abi from 'human-standard-token-abi';
|
||||||
|
import { Json } from '@metamask/controller-utils';
|
||||||
|
import { IndividualTxFees } from '@metamask/smart-transactions-controller/dist/types';
|
||||||
import {
|
import {
|
||||||
SWAPS_CHAINID_DEFAULT_TOKEN_MAP,
|
|
||||||
ALLOWED_CONTRACT_ADDRESSES,
|
ALLOWED_CONTRACT_ADDRESSES,
|
||||||
ETHEREUM,
|
|
||||||
POLYGON,
|
|
||||||
BSC,
|
|
||||||
GOERLI,
|
|
||||||
AVALANCHE,
|
|
||||||
OPTIMISM,
|
|
||||||
ARBITRUM,
|
ARBITRUM,
|
||||||
|
AVALANCHE,
|
||||||
|
BSC,
|
||||||
|
ETHEREUM,
|
||||||
|
GOERLI,
|
||||||
|
OPTIMISM,
|
||||||
|
POLYGON,
|
||||||
SWAPS_API_V2_BASE_URL,
|
SWAPS_API_V2_BASE_URL,
|
||||||
SWAPS_DEV_API_V2_BASE_URL,
|
SWAPS_CHAINID_DEFAULT_TOKEN_MAP,
|
||||||
SWAPS_CLIENT_ID,
|
SWAPS_CLIENT_ID,
|
||||||
|
SWAPS_DEV_API_V2_BASE_URL,
|
||||||
|
SwapsTokenObject,
|
||||||
} from '../../../shared/constants/swaps';
|
} from '../../../shared/constants/swaps';
|
||||||
import {
|
import {
|
||||||
isSwapsDefaultTokenAddress,
|
isSwapsDefaultTokenAddress,
|
||||||
isSwapsDefaultTokenSymbol,
|
isSwapsDefaultTokenSymbol,
|
||||||
} from '../../../shared/modules/swaps.utils';
|
} from '../../../shared/modules/swaps.utils';
|
||||||
import { CHAIN_IDS, CURRENCY_SYMBOLS } from '../../../shared/constants/network';
|
import { CHAIN_IDS } from '../../../shared/constants/network';
|
||||||
import { formatCurrency } from '../../helpers/utils/confirm-tx.util';
|
import { formatCurrency } from '../../helpers/utils/confirm-tx.util';
|
||||||
import fetchWithCache from '../../../shared/lib/fetch-with-cache';
|
import fetchWithCache from '../../../shared/lib/fetch-with-cache';
|
||||||
|
|
||||||
@ -38,13 +41,20 @@ import {
|
|||||||
getValueFromWeiHex,
|
getValueFromWeiHex,
|
||||||
sumHexes,
|
sumHexes,
|
||||||
} from '../../../shared/modules/conversion.utils';
|
} from '../../../shared/modules/conversion.utils';
|
||||||
|
import { EtherDenomination } from '../../../shared/constants/common';
|
||||||
|
|
||||||
const CACHE_REFRESH_FIVE_MINUTES = 300000;
|
const CACHE_REFRESH_FIVE_MINUTES = 300000;
|
||||||
const USD_CURRENCY_CODE = 'usd';
|
const USD_CURRENCY_CODE = 'usd';
|
||||||
|
|
||||||
const clientIdHeader = { 'X-Client-Id': SWAPS_CLIENT_ID };
|
const clientIdHeader = { 'X-Client-Id': SWAPS_CLIENT_ID };
|
||||||
|
|
||||||
const TOKEN_VALIDATORS = [
|
interface Validator {
|
||||||
|
property: string;
|
||||||
|
type: string;
|
||||||
|
validator: (a: string) => boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const TOKEN_VALIDATORS: Validator[] = [
|
||||||
{
|
{
|
||||||
property: 'address',
|
property: 'address',
|
||||||
type: 'string',
|
type: 'string',
|
||||||
@ -64,7 +74,7 @@ const TOKEN_VALIDATORS = [
|
|||||||
|
|
||||||
const TOP_ASSET_VALIDATORS = TOKEN_VALIDATORS.slice(0, 2);
|
const TOP_ASSET_VALIDATORS = TOKEN_VALIDATORS.slice(0, 2);
|
||||||
|
|
||||||
const AGGREGATOR_METADATA_VALIDATORS = [
|
const AGGREGATOR_METADATA_VALIDATORS: Validator[] = [
|
||||||
{
|
{
|
||||||
property: 'color',
|
property: 'color',
|
||||||
type: 'string',
|
type: 'string',
|
||||||
@ -82,10 +92,10 @@ const AGGREGATOR_METADATA_VALIDATORS = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const isValidDecimalNumber = (string) =>
|
const isValidDecimalNumber = (string: any): boolean =>
|
||||||
!isNaN(string) && string.match(/^[.0-9]+$/u) && !isNaN(parseFloat(string));
|
!isNaN(string) && string.match(/^[.0-9]+$/u) && !isNaN(parseFloat(string));
|
||||||
|
|
||||||
const SWAP_GAS_PRICE_VALIDATOR = [
|
const SWAP_GAS_PRICE_VALIDATOR: Validator[] = [
|
||||||
{
|
{
|
||||||
property: 'SafeGasPrice',
|
property: 'SafeGasPrice',
|
||||||
type: 'string',
|
type: 'string',
|
||||||
@ -103,17 +113,22 @@ const SWAP_GAS_PRICE_VALIDATOR = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export async function fetchToken(contractAddress, chainId) {
|
export async function fetchToken(
|
||||||
|
contractAddress: string,
|
||||||
|
chainId: any,
|
||||||
|
): Promise<Json> {
|
||||||
const tokenUrl = getBaseApi('token', chainId);
|
const tokenUrl = getBaseApi('token', chainId);
|
||||||
const token = await fetchWithCache(
|
return await fetchWithCache(
|
||||||
`${tokenUrl}?address=${contractAddress}`,
|
`${tokenUrl}?address=${contractAddress}`,
|
||||||
{ method: 'GET', headers: clientIdHeader },
|
{ method: 'GET', headers: clientIdHeader },
|
||||||
{ cacheRefreshTime: CACHE_REFRESH_FIVE_MINUTES },
|
{ cacheRefreshTime: CACHE_REFRESH_FIVE_MINUTES },
|
||||||
);
|
);
|
||||||
return token;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function fetchTokens(chainId) {
|
type Token = { symbol: string; address: string };
|
||||||
|
export async function fetchTokens(
|
||||||
|
chainId: keyof typeof SWAPS_CHAINID_DEFAULT_TOKEN_MAP,
|
||||||
|
): Promise<SwapsTokenObject[]> {
|
||||||
const tokensUrl = getBaseApi('tokens', chainId);
|
const tokensUrl = getBaseApi('tokens', chainId);
|
||||||
const tokens = await fetchWithCache(
|
const tokens = await fetchWithCache(
|
||||||
tokensUrl,
|
tokensUrl,
|
||||||
@ -121,9 +136,10 @@ export async function fetchTokens(chainId) {
|
|||||||
{ cacheRefreshTime: CACHE_REFRESH_FIVE_MINUTES },
|
{ cacheRefreshTime: CACHE_REFRESH_FIVE_MINUTES },
|
||||||
);
|
);
|
||||||
const logError = false;
|
const logError = false;
|
||||||
const filteredTokens = [
|
const tokenObject = SWAPS_CHAINID_DEFAULT_TOKEN_MAP[chainId] || null;
|
||||||
SWAPS_CHAINID_DEFAULT_TOKEN_MAP[chainId],
|
return [
|
||||||
...tokens.filter((token) => {
|
tokenObject,
|
||||||
|
...tokens.filter((token: Token) => {
|
||||||
return (
|
return (
|
||||||
validateData(TOKEN_VALIDATORS, token, tokensUrl, logError) &&
|
validateData(TOKEN_VALIDATORS, token, tokensUrl, logError) &&
|
||||||
!(
|
!(
|
||||||
@ -133,17 +149,16 @@ export async function fetchTokens(chainId) {
|
|||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
return filteredTokens;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function fetchAggregatorMetadata(chainId) {
|
export async function fetchAggregatorMetadata(chainId: any): Promise<object> {
|
||||||
const aggregatorMetadataUrl = getBaseApi('aggregatorMetadata', chainId);
|
const aggregatorMetadataUrl = getBaseApi('aggregatorMetadata', chainId);
|
||||||
const aggregators = await fetchWithCache(
|
const aggregators = await fetchWithCache(
|
||||||
aggregatorMetadataUrl,
|
aggregatorMetadataUrl,
|
||||||
{ method: 'GET', headers: clientIdHeader },
|
{ method: 'GET', headers: clientIdHeader },
|
||||||
{ cacheRefreshTime: CACHE_REFRESH_FIVE_MINUTES },
|
{ cacheRefreshTime: CACHE_REFRESH_FIVE_MINUTES },
|
||||||
);
|
);
|
||||||
const filteredAggregators = {};
|
const filteredAggregators = {} as any;
|
||||||
for (const aggKey in aggregators) {
|
for (const aggKey in aggregators) {
|
||||||
if (
|
if (
|
||||||
validateData(
|
validateData(
|
||||||
@ -158,7 +173,7 @@ export async function fetchAggregatorMetadata(chainId) {
|
|||||||
return filteredAggregators;
|
return filteredAggregators;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function fetchTopAssets(chainId) {
|
export async function fetchTopAssets(chainId: any): Promise<object> {
|
||||||
const topAssetsUrl = getBaseApi('topAssets', chainId);
|
const topAssetsUrl = getBaseApi('topAssets', chainId);
|
||||||
const response =
|
const response =
|
||||||
(await fetchWithCache(
|
(await fetchWithCache(
|
||||||
@ -166,28 +181,30 @@ export async function fetchTopAssets(chainId) {
|
|||||||
{ method: 'GET', headers: clientIdHeader },
|
{ method: 'GET', headers: clientIdHeader },
|
||||||
{ cacheRefreshTime: CACHE_REFRESH_FIVE_MINUTES },
|
{ cacheRefreshTime: CACHE_REFRESH_FIVE_MINUTES },
|
||||||
)) || [];
|
)) || [];
|
||||||
const topAssetsMap = response.reduce((_topAssetsMap, asset, index) => {
|
const topAssetsMap = response.reduce(
|
||||||
|
(_topAssetsMap: any, asset: { address: string }, index: number) => {
|
||||||
if (validateData(TOP_ASSET_VALIDATORS, asset, topAssetsUrl)) {
|
if (validateData(TOP_ASSET_VALIDATORS, asset, topAssetsUrl)) {
|
||||||
return { ..._topAssetsMap, [asset.address]: { index: String(index) } };
|
return { ..._topAssetsMap, [asset.address]: { index: String(index) } };
|
||||||
}
|
}
|
||||||
return _topAssetsMap;
|
return _topAssetsMap;
|
||||||
}, {});
|
},
|
||||||
|
{},
|
||||||
|
);
|
||||||
return topAssetsMap;
|
return topAssetsMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function fetchSwapsFeatureFlags() {
|
export async function fetchSwapsFeatureFlags(): Promise<any> {
|
||||||
const v2ApiBaseUrl = process.env.SWAPS_USE_DEV_APIS
|
const v2ApiBaseUrl = process.env.SWAPS_USE_DEV_APIS
|
||||||
? SWAPS_DEV_API_V2_BASE_URL
|
? SWAPS_DEV_API_V2_BASE_URL
|
||||||
: SWAPS_API_V2_BASE_URL;
|
: SWAPS_API_V2_BASE_URL;
|
||||||
const response = await fetchWithCache(
|
return await fetchWithCache(
|
||||||
`${v2ApiBaseUrl}/featureFlags`,
|
`${v2ApiBaseUrl}/featureFlags`,
|
||||||
{ method: 'GET', headers: clientIdHeader },
|
{ method: 'GET', headers: clientIdHeader },
|
||||||
{ cacheRefreshTime: 600000 },
|
{ cacheRefreshTime: 600000 },
|
||||||
);
|
);
|
||||||
return response;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function fetchTokenPrice(address) {
|
export async function fetchTokenPrice(address: string): Promise<any> {
|
||||||
const query = `contract_addresses=${address}&vs_currencies=eth`;
|
const query = `contract_addresses=${address}&vs_currencies=eth`;
|
||||||
|
|
||||||
const prices = await fetchWithCache(
|
const prices = await fetchWithCache(
|
||||||
@ -195,19 +212,28 @@ export async function fetchTokenPrice(address) {
|
|||||||
{ method: 'GET' },
|
{ method: 'GET' },
|
||||||
{ cacheRefreshTime: 60000 },
|
{ cacheRefreshTime: 60000 },
|
||||||
);
|
);
|
||||||
return prices && prices[address]?.eth;
|
return prices?.[address]?.eth;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function fetchTokenBalance(address, userAddress) {
|
export async function fetchTokenBalance(
|
||||||
const tokenContract = global.eth.contract(abi).at(address);
|
address: string,
|
||||||
|
userAddress: string,
|
||||||
|
): Promise<any> {
|
||||||
|
const tokenContract = (global as any).eth.contract(abi).at(address);
|
||||||
const tokenBalancePromise = tokenContract
|
const tokenBalancePromise = tokenContract
|
||||||
? tokenContract.balanceOf(userAddress)
|
? tokenContract.balanceOf(userAddress)
|
||||||
: Promise.resolve();
|
: Promise.resolve();
|
||||||
const usersToken = await tokenBalancePromise;
|
return await tokenBalancePromise;
|
||||||
return usersToken;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function fetchSwapsGasPrices(chainId) {
|
export async function fetchSwapsGasPrices(chainId: any): Promise<
|
||||||
|
| any
|
||||||
|
| {
|
||||||
|
safeLow: string;
|
||||||
|
average: string;
|
||||||
|
fast: string;
|
||||||
|
}
|
||||||
|
> {
|
||||||
const gasPricesUrl = getBaseApi('gasPrices', chainId);
|
const gasPricesUrl = getBaseApi('gasPrices', chainId);
|
||||||
const response = await fetchWithCache(
|
const response = await fetchWithCache(
|
||||||
gasPricesUrl,
|
gasPricesUrl,
|
||||||
@ -244,11 +270,18 @@ export const getFeeForSmartTransaction = ({
|
|||||||
USDConversionRate,
|
USDConversionRate,
|
||||||
nativeCurrencySymbol,
|
nativeCurrencySymbol,
|
||||||
feeInWeiDec,
|
feeInWeiDec,
|
||||||
|
}: {
|
||||||
|
chainId: keyof typeof SWAPS_CHAINID_DEFAULT_TOKEN_MAP;
|
||||||
|
currentCurrency: string;
|
||||||
|
conversionRate: number;
|
||||||
|
USDConversionRate?: number;
|
||||||
|
nativeCurrencySymbol: string;
|
||||||
|
feeInWeiDec: number;
|
||||||
}) => {
|
}) => {
|
||||||
const feeInWeiHex = decimalToHex(feeInWeiDec);
|
const feeInWeiHex = decimalToHex(feeInWeiDec);
|
||||||
const ethFee = getValueFromWeiHex({
|
const ethFee = getValueFromWeiHex({
|
||||||
value: feeInWeiHex,
|
value: feeInWeiHex,
|
||||||
toDenomination: CURRENCY_SYMBOLS.ETH,
|
toDenomination: EtherDenomination.ETH,
|
||||||
numberOfDecimals: 5,
|
numberOfDecimals: 5,
|
||||||
});
|
});
|
||||||
const rawNetworkFees = getValueFromWeiHex({
|
const rawNetworkFees = getValueFromWeiHex({
|
||||||
@ -270,7 +303,7 @@ export const getFeeForSmartTransaction = ({
|
|||||||
}
|
}
|
||||||
const formattedNetworkFee = formatCurrency(rawNetworkFees, currentCurrency);
|
const formattedNetworkFee = formatCurrency(rawNetworkFees, currentCurrency);
|
||||||
const chainCurrencySymbolToUse =
|
const chainCurrencySymbolToUse =
|
||||||
nativeCurrencySymbol || SWAPS_CHAINID_DEFAULT_TOKEN_MAP[chainId].symbol;
|
nativeCurrencySymbol || SWAPS_CHAINID_DEFAULT_TOKEN_MAP[chainId]?.symbol;
|
||||||
return {
|
return {
|
||||||
feeInUsd,
|
feeInUsd,
|
||||||
feeInFiat: formattedNetworkFee,
|
feeInFiat: formattedNetworkFee,
|
||||||
@ -292,7 +325,27 @@ export function getRenderableNetworkFeesForQuote({
|
|||||||
chainId,
|
chainId,
|
||||||
nativeCurrencySymbol,
|
nativeCurrencySymbol,
|
||||||
multiLayerL1FeeTotal,
|
multiLayerL1FeeTotal,
|
||||||
}) {
|
}: {
|
||||||
|
tradeGas: string;
|
||||||
|
approveGas: string;
|
||||||
|
gasPrice: string;
|
||||||
|
currentCurrency: string;
|
||||||
|
conversionRate: number;
|
||||||
|
USDConversionRate?: number;
|
||||||
|
tradeValue: number;
|
||||||
|
sourceSymbol: string;
|
||||||
|
sourceAmount: number;
|
||||||
|
chainId: keyof typeof SWAPS_CHAINID_DEFAULT_TOKEN_MAP;
|
||||||
|
nativeCurrencySymbol?: string;
|
||||||
|
multiLayerL1FeeTotal: string | null;
|
||||||
|
}): {
|
||||||
|
rawNetworkFees: string | number | BigNumber;
|
||||||
|
feeInUsd: string | number | BigNumber;
|
||||||
|
rawEthFee: string | number | BigNumber;
|
||||||
|
feeInFiat: string;
|
||||||
|
feeInEth: string;
|
||||||
|
nonGasFee: string;
|
||||||
|
} {
|
||||||
const totalGasLimitForCalculation = new BigNumber(tradeGas || '0x0', 16)
|
const totalGasLimitForCalculation = new BigNumber(tradeGas || '0x0', 16)
|
||||||
.plus(approveGas || '0x0', 16)
|
.plus(approveGas || '0x0', 16)
|
||||||
.toString(16);
|
.toString(16);
|
||||||
@ -314,10 +367,9 @@ export function getRenderableNetworkFeesForQuote({
|
|||||||
const totalWeiCost = new BigNumber(gasTotalInWeiHex, 16)
|
const totalWeiCost = new BigNumber(gasTotalInWeiHex, 16)
|
||||||
.plus(nonGasFee, 16)
|
.plus(nonGasFee, 16)
|
||||||
.toString(16);
|
.toString(16);
|
||||||
|
|
||||||
const ethFee = getValueFromWeiHex({
|
const ethFee = getValueFromWeiHex({
|
||||||
value: totalWeiCost,
|
value: totalWeiCost,
|
||||||
toDenomination: 'ETH',
|
toDenomination: EtherDenomination.ETH,
|
||||||
numberOfDecimals: 5,
|
numberOfDecimals: 5,
|
||||||
});
|
});
|
||||||
const rawNetworkFees = getValueFromWeiHex({
|
const rawNetworkFees = getValueFromWeiHex({
|
||||||
@ -353,7 +405,7 @@ export function getRenderableNetworkFeesForQuote({
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function quotesToRenderableData(
|
export function quotesToRenderableData({
|
||||||
quotes,
|
quotes,
|
||||||
gasPrice,
|
gasPrice,
|
||||||
conversionRate,
|
conversionRate,
|
||||||
@ -364,7 +416,18 @@ export function quotesToRenderableData(
|
|||||||
smartTransactionEstimatedGas,
|
smartTransactionEstimatedGas,
|
||||||
nativeCurrencySymbol,
|
nativeCurrencySymbol,
|
||||||
multiLayerL1ApprovalFeeTotal,
|
multiLayerL1ApprovalFeeTotal,
|
||||||
) {
|
}: {
|
||||||
|
quotes: object;
|
||||||
|
gasPrice: string;
|
||||||
|
conversionRate: number;
|
||||||
|
currentCurrency: string;
|
||||||
|
approveGas: string;
|
||||||
|
tokenConversionRates: Record<string, any>;
|
||||||
|
chainId: keyof typeof SWAPS_CHAINID_DEFAULT_TOKEN_MAP;
|
||||||
|
smartTransactionEstimatedGas: IndividualTxFees;
|
||||||
|
nativeCurrencySymbol: string;
|
||||||
|
multiLayerL1ApprovalFeeTotal: string | null;
|
||||||
|
}): Record<string, any> {
|
||||||
return Object.values(quotes).map((quote) => {
|
return Object.values(quotes).map((quote) => {
|
||||||
const {
|
const {
|
||||||
destinationAmount = 0,
|
destinationAmount = 0,
|
||||||
@ -426,7 +489,7 @@ export function quotesToRenderableData(
|
|||||||
currentCurrency,
|
currentCurrency,
|
||||||
conversionRate,
|
conversionRate,
|
||||||
nativeCurrencySymbol,
|
nativeCurrencySymbol,
|
||||||
estimatedFeeInWeiDec: smartTransactionEstimatedGas.feeEstimate,
|
feeInWeiDec: smartTransactionEstimatedGas.feeEstimate,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -494,7 +557,7 @@ export function quotesToRenderableData(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function formatSwapsValueForDisplay(destinationAmount) {
|
export function formatSwapsValueForDisplay(destinationAmount: string): string {
|
||||||
let amountToDisplay = toPrecisionWithoutTrailingZeros(destinationAmount, 12);
|
let amountToDisplay = toPrecisionWithoutTrailingZeros(destinationAmount, 12);
|
||||||
if (amountToDisplay.match(/e[+-]/u)) {
|
if (amountToDisplay.match(/e[+-]/u)) {
|
||||||
amountToDisplay = new BigNumber(amountToDisplay).toFixed();
|
amountToDisplay = new BigNumber(amountToDisplay).toFixed();
|
||||||
@ -505,30 +568,30 @@ export function formatSwapsValueForDisplay(destinationAmount) {
|
|||||||
/**
|
/**
|
||||||
* Checks whether a contract address is valid before swapping tokens.
|
* Checks whether a contract address is valid before swapping tokens.
|
||||||
*
|
*
|
||||||
* @param {string} contractAddress - E.g. "0x881d40237659c251811cec9c364ef91dc08d300c" for mainnet
|
* @param contractAddress - E.g. "0x881d40237659c251811cec9c364ef91dc08d300c" for mainnet
|
||||||
* @param {string} chainId - The hex encoded chain ID to check
|
* @param chainId - The hex encoded chain ID to check
|
||||||
* @returns {boolean} Whether a contract address is valid or not
|
* @returns Whether a contract address is valid or not
|
||||||
*/
|
*/
|
||||||
export const isContractAddressValid = (
|
export const isContractAddressValid = (
|
||||||
contractAddress,
|
contractAddress: string,
|
||||||
chainId = CHAIN_IDS.MAINNET,
|
chainId: keyof typeof ALLOWED_CONTRACT_ADDRESSES,
|
||||||
) => {
|
): boolean => {
|
||||||
if (!contractAddress || !ALLOWED_CONTRACT_ADDRESSES[chainId]) {
|
if (!contractAddress || !ALLOWED_CONTRACT_ADDRESSES[chainId]) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return ALLOWED_CONTRACT_ADDRESSES[chainId].some(
|
return ALLOWED_CONTRACT_ADDRESSES[chainId].some(
|
||||||
// Sometimes we get a contract address with a few upper-case chars and since addresses are
|
// Sometimes we get a contract address with a few upper-case chars and since addresses are
|
||||||
// case-insensitive, we compare lowercase versions for validity.
|
// case-insensitive, we compare lowercase versions for validity.
|
||||||
(allowedContractAddress) =>
|
(allowedContractAddress: string) =>
|
||||||
contractAddress.toLowerCase() === allowedContractAddress.toLowerCase(),
|
contractAddress.toLowerCase() === allowedContractAddress.toLowerCase(),
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} chainId
|
* @param chainId
|
||||||
* @returns string e.g. ethereum, bsc or polygon
|
* @returns string e.g. ethereum, bsc or polygon
|
||||||
*/
|
*/
|
||||||
export const getNetworkNameByChainId = (chainId) => {
|
export const getNetworkNameByChainId = (chainId: string): string => {
|
||||||
switch (chainId) {
|
switch (chainId) {
|
||||||
case CHAIN_IDS.MAINNET:
|
case CHAIN_IDS.MAINNET:
|
||||||
return ETHEREUM;
|
return ETHEREUM;
|
||||||
@ -552,11 +615,14 @@ export const getNetworkNameByChainId = (chainId) => {
|
|||||||
/**
|
/**
|
||||||
* It returns info about if Swaps are enabled and if we should use our new APIs for it.
|
* It returns info about if Swaps are enabled and if we should use our new APIs for it.
|
||||||
*
|
*
|
||||||
* @param {object} swapsFeatureFlags
|
* @param chainId
|
||||||
* @param {string} chainId
|
* @param swapsFeatureFlags
|
||||||
* @returns object with 2 items: "swapsFeatureIsLive"
|
* @returns object with 2 items: "swapsFeatureIsLive"
|
||||||
*/
|
*/
|
||||||
export const getSwapsLivenessForNetwork = (swapsFeatureFlags = {}, chainId) => {
|
export const getSwapsLivenessForNetwork = (
|
||||||
|
chainId: any,
|
||||||
|
swapsFeatureFlags: any = {},
|
||||||
|
) => {
|
||||||
const networkName = getNetworkNameByChainId(chainId);
|
const networkName = getNetworkNameByChainId(chainId);
|
||||||
// Use old APIs for testnet and Goerli.
|
// Use old APIs for testnet and Goerli.
|
||||||
if ([CHAIN_IDS.LOCALHOST, CHAIN_IDS.GOERLI].includes(chainId)) {
|
if ([CHAIN_IDS.LOCALHOST, CHAIN_IDS.GOERLI].includes(chainId)) {
|
||||||
@ -583,17 +649,19 @@ export const getSwapsLivenessForNetwork = (swapsFeatureFlags = {}, chainId) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {number} value
|
* @param value
|
||||||
* @returns number
|
* @returns number
|
||||||
*/
|
*/
|
||||||
export const countDecimals = (value) => {
|
export const countDecimals = (value: any): number => {
|
||||||
if (!value || Math.floor(value) === value) {
|
if (!value || Math.floor(value) === value) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
return value.toString().split('.')[1]?.length || 0;
|
return value.toString().split('.')[1]?.length || 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const showRemainingTimeInMinAndSec = (remainingTimeInSec) => {
|
export const showRemainingTimeInMinAndSec = (
|
||||||
|
remainingTimeInSec: any,
|
||||||
|
): string => {
|
||||||
if (!Number.isInteger(remainingTimeInSec)) {
|
if (!Number.isInteger(remainingTimeInSec)) {
|
||||||
return '0:00';
|
return '0:00';
|
||||||
}
|
}
|
||||||
@ -602,25 +670,28 @@ export const showRemainingTimeInMinAndSec = (remainingTimeInSec) => {
|
|||||||
return `${minutes}:${seconds.toString().padStart(2, '0')}`;
|
return `${minutes}:${seconds.toString().padStart(2, '0')}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const stxErrorTypes = {
|
export enum StxErrorTypes {
|
||||||
UNAVAILABLE: 'unavailable',
|
unavailable = 'unavailable',
|
||||||
NOT_ENOUGH_FUNDS: 'not_enough_funds',
|
notEnoughFunds = 'not_enough_funds',
|
||||||
REGULAR_TX_IN_PROGRESS: 'regular_tx_pending',
|
regularTxPending = 'regular_tx_pending',
|
||||||
};
|
}
|
||||||
|
|
||||||
export const getTranslatedStxErrorMessage = (errorType, t) => {
|
export const getTranslatedStxErrorMessage = (
|
||||||
|
errorType: StxErrorTypes,
|
||||||
|
t: (...args: any[]) => string,
|
||||||
|
): string => {
|
||||||
switch (errorType) {
|
switch (errorType) {
|
||||||
case stxErrorTypes.UNAVAILABLE:
|
case StxErrorTypes.unavailable:
|
||||||
case stxErrorTypes.REGULAR_TX_IN_PROGRESS:
|
case StxErrorTypes.regularTxPending:
|
||||||
return t('stxErrorUnavailable');
|
return t('stxErrorUnavailable');
|
||||||
case stxErrorTypes.NOT_ENOUGH_FUNDS:
|
case StxErrorTypes.notEnoughFunds:
|
||||||
return t('stxErrorNotEnoughFunds');
|
return t('stxErrorNotEnoughFunds');
|
||||||
default:
|
default:
|
||||||
return t('stxErrorUnavailable');
|
return t('stxErrorUnavailable');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const parseSmartTransactionsError = (errorMessage) => {
|
export const parseSmartTransactionsError = (errorMessage: string): string => {
|
||||||
const errorJson = errorMessage.slice(12);
|
const errorJson = errorMessage.slice(12);
|
||||||
return JSON.parse(errorJson.trim());
|
return JSON.parse(errorJson.trim());
|
||||||
};
|
};
|
@ -297,20 +297,23 @@ export default function ViewQuote() {
|
|||||||
const approveGas = approveTxParams?.gas;
|
const approveGas = approveTxParams?.gas;
|
||||||
|
|
||||||
const renderablePopoverData = useMemo(() => {
|
const renderablePopoverData = useMemo(() => {
|
||||||
return quotesToRenderableData(
|
return quotesToRenderableData({
|
||||||
quotes,
|
quotes,
|
||||||
networkAndAccountSupports1559 ? baseAndPriorityFeePerGas : gasPrice,
|
gasPrice: networkAndAccountSupports1559
|
||||||
|
? baseAndPriorityFeePerGas
|
||||||
|
: gasPrice,
|
||||||
conversionRate,
|
conversionRate,
|
||||||
currentCurrency,
|
currentCurrency,
|
||||||
approveGas,
|
approveGas,
|
||||||
memoizedTokenConversionRates,
|
tokenConversionRates: memoizedTokenConversionRates,
|
||||||
chainId,
|
chainId,
|
||||||
|
smartTransactionEstimatedGas:
|
||||||
smartTransactionsEnabled &&
|
smartTransactionsEnabled &&
|
||||||
smartTransactionsOptInStatus &&
|
smartTransactionsOptInStatus &&
|
||||||
smartTransactionFees?.tradeTxFees,
|
smartTransactionFees?.tradeTxFees,
|
||||||
nativeCurrencySymbol,
|
nativeCurrencySymbol,
|
||||||
multiLayerL1ApprovalFeeTotal,
|
multiLayerL1ApprovalFeeTotal,
|
||||||
);
|
});
|
||||||
}, [
|
}, [
|
||||||
quotes,
|
quotes,
|
||||||
gasPrice,
|
gasPrice,
|
||||||
|
10
yarn.lock
10
yarn.lock
@ -23360,10 +23360,10 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"loglevel@npm:^1.4.1":
|
"loglevel@npm:^1.8.1":
|
||||||
version: 1.6.0
|
version: 1.8.1
|
||||||
resolution: "loglevel@npm:1.6.0"
|
resolution: "loglevel@npm:1.8.1"
|
||||||
checksum: 85e59bde900d8c7ddb6bd8f4ac466ca59fab54239e9bbf99193181c316fe49b067e3ed5945524894404c77db1a3a4d17f5fe272a808f05a8a8914a527628dea4
|
checksum: a1a62db40291aaeaef2f612334c49e531bff71cc1d01a2acab689ab80d59e092f852ab164a5aedc1a752fdc46b7b162cb097d8a9eb2cf0b299511106c29af61d
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
@ -24305,7 +24305,7 @@ __metadata:
|
|||||||
localforage: ^1.9.0
|
localforage: ^1.9.0
|
||||||
lockfile-lint: ^4.9.6
|
lockfile-lint: ^4.9.6
|
||||||
lodash: ^4.17.21
|
lodash: ^4.17.21
|
||||||
loglevel: ^1.4.1
|
loglevel: ^1.8.1
|
||||||
loose-envify: ^1.4.0
|
loose-envify: ^1.4.0
|
||||||
luxon: ^3.2.1
|
luxon: ^3.2.1
|
||||||
madge: ^5.0.1
|
madge: ^5.0.1
|
||||||
|
Loading…
Reference in New Issue
Block a user