Remove related UI code from the app dir (#15384)
Co-authored-by: metamaskbot <metamaskbot@users.noreply.github.com> Co-authored-by: Brad Decker <bhdecker84@gmail.com> Co-authored-by: Akintayo A. Olusegun <akintayo.segun@gmail.com>feature/default_network_editable
parent
c2b7690119
commit
6e13524bcd
@ -1,7 +1,27 @@ |
|||||||
import getFirstPreferredLangCode from '../../../app/scripts/lib/get-first-preferred-lang-code'; |
import { memoize } from 'lodash'; |
||||||
import { setupLocale } from '../..'; |
import getFirstPreferredLangCode from '../../app/scripts/lib/get-first-preferred-lang-code'; |
||||||
|
import { |
||||||
|
fetchLocale, |
||||||
|
loadRelativeTimeFormatLocaleData, |
||||||
|
} from '../../ui/helpers/utils/i18n-helper'; |
||||||
import switchDirection from './switch-direction'; |
import switchDirection from './switch-direction'; |
||||||
|
|
||||||
|
const _setupLocale = async (currentLocale) => { |
||||||
|
const currentLocaleMessages = currentLocale |
||||||
|
? await fetchLocale(currentLocale) |
||||||
|
: {}; |
||||||
|
const enLocaleMessages = await fetchLocale('en'); |
||||||
|
|
||||||
|
await loadRelativeTimeFormatLocaleData('en'); |
||||||
|
if (currentLocale) { |
||||||
|
await loadRelativeTimeFormatLocaleData(currentLocale); |
||||||
|
} |
||||||
|
|
||||||
|
return { currentLocaleMessages, enLocaleMessages }; |
||||||
|
}; |
||||||
|
|
||||||
|
export const setupLocale = memoize(_setupLocale); |
||||||
|
|
||||||
const getLocaleContext = (currentLocaleMessages, enLocaleMessages) => { |
const getLocaleContext = (currentLocaleMessages, enLocaleMessages) => { |
||||||
return (key) => { |
return (key) => { |
||||||
let message = currentLocaleMessages[key]?.message; |
let message = currentLocaleMessages[key]?.message; |
@ -1,14 +1,14 @@ |
|||||||
import { SUPPORT_LINK } from '../constants/common'; |
import { fetchLocale } from '../../ui/helpers/utils/i18n-helper'; |
||||||
|
import { SUPPORT_LINK } from './ui-utils'; |
||||||
import { getErrorHtml } from './error-utils'; |
import { getErrorHtml } from './error-utils'; |
||||||
import { fetchLocale } from './i18n-helper'; |
|
||||||
|
|
||||||
jest.mock('./i18n-helper', () => ({ |
jest.mock('../../ui/helpers/utils/i18n-helper', () => ({ |
||||||
fetchLocale: jest.fn(), |
fetchLocale: jest.fn(), |
||||||
loadRelativeTimeFormatLocaleData: jest.fn(), |
loadRelativeTimeFormatLocaleData: jest.fn(), |
||||||
})); |
})); |
||||||
|
|
||||||
describe('Error utils Tests', () => { |
describe('Error utils Tests', function () { |
||||||
it('should get error html', async () => { |
it('should get error html', async function () { |
||||||
const mockStore = { |
const mockStore = { |
||||||
localeMessages: { |
localeMessages: { |
||||||
current: { |
current: { |
@ -1,5 +1,5 @@ |
|||||||
import { MINUTE, SECOND } from '../../../shared/constants/time'; |
import { MINUTE, SECOND } from '../constants/time'; |
||||||
import getFetchWithTimeout from '../../../shared/modules/fetch-with-timeout'; |
import getFetchWithTimeout from '../modules/fetch-with-timeout'; |
||||||
import { getStorageItem, setStorageItem } from './storage-helpers'; |
import { getStorageItem, setStorageItem } from './storage-helpers'; |
||||||
|
|
||||||
const fetchWithCache = async ( |
const fetchWithCache = async ( |
@ -0,0 +1,12 @@ |
|||||||
|
import { conversionUtil } from '../modules/conversion.utils'; |
||||||
|
|
||||||
|
export function hexToDecimal(hexValue) { |
||||||
|
return conversionUtil(hexValue, { |
||||||
|
fromNumericBase: 'hex', |
||||||
|
toNumericBase: 'dec', |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
export function getTokenValueParam(tokenData = {}) { |
||||||
|
return tokenData?.args?._value?.toString(); |
||||||
|
} |
@ -0,0 +1,320 @@ |
|||||||
|
import BigNumber from 'bignumber.js'; |
||||||
|
import log from 'loglevel'; |
||||||
|
import { CHAIN_IDS } from '../constants/network'; |
||||||
|
import { |
||||||
|
GAS_API_BASE_URL, |
||||||
|
GAS_DEV_API_BASE_URL, |
||||||
|
SWAPS_API_V2_BASE_URL, |
||||||
|
SWAPS_CHAINID_DEFAULT_TOKEN_MAP, |
||||||
|
SWAPS_CLIENT_ID, |
||||||
|
SWAPS_DEV_API_V2_BASE_URL, |
||||||
|
SWAPS_WRAPPED_TOKENS_ADDRESSES, |
||||||
|
} from '../constants/swaps'; |
||||||
|
import { SECOND } from '../constants/time'; |
||||||
|
import { isValidHexAddress } from '../modules/hexstring-utils'; |
||||||
|
import { addHexPrefix } from '../../app/scripts/lib/util'; |
||||||
|
import fetchWithCache from './fetch-with-cache'; |
||||||
|
import { decimalToHex } from './transactions-controller-utils'; |
||||||
|
|
||||||
|
const TEST_CHAIN_IDS = [CHAIN_IDS.GOERLI, CHAIN_IDS.LOCALHOST]; |
||||||
|
|
||||||
|
const clientIdHeader = { 'X-Client-Id': SWAPS_CLIENT_ID }; |
||||||
|
|
||||||
|
export const validHex = (string) => Boolean(string?.match(/^0x[a-f0-9]+$/u)); |
||||||
|
export const truthyString = (string) => Boolean(string?.length); |
||||||
|
export const truthyDigitString = (string) => |
||||||
|
truthyString(string) && Boolean(string.match(/^\d+$/u)); |
||||||
|
|
||||||
|
export function validateData(validators, object, urlUsed, logError = true) { |
||||||
|
return validators.every(({ property, type, validator }) => { |
||||||
|
const types = type.split('|'); |
||||||
|
|
||||||
|
const valid = |
||||||
|
types.some((_type) => typeof object[property] === _type) && |
||||||
|
(!validator || validator(object[property])); |
||||||
|
if (!valid && logError) { |
||||||
|
log.error( |
||||||
|
`response to GET ${urlUsed} invalid for property ${property}; value was:`, |
||||||
|
object[property], |
||||||
|
'| type was: ', |
||||||
|
typeof object[property], |
||||||
|
); |
||||||
|
} |
||||||
|
return valid; |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
export const QUOTE_VALIDATORS = [ |
||||||
|
{ |
||||||
|
property: 'trade', |
||||||
|
type: 'object', |
||||||
|
validator: (trade) => |
||||||
|
trade && |
||||||
|
validHex(trade.data) && |
||||||
|
isValidHexAddress(trade.to, { allowNonPrefixed: false }) && |
||||||
|
isValidHexAddress(trade.from, { allowNonPrefixed: false }) && |
||||||
|
truthyString(trade.value), |
||||||
|
}, |
||||||
|
{ |
||||||
|
property: 'approvalNeeded', |
||||||
|
type: 'object', |
||||||
|
validator: (approvalTx) => |
||||||
|
approvalTx === null || |
||||||
|
(approvalTx && |
||||||
|
validHex(approvalTx.data) && |
||||||
|
isValidHexAddress(approvalTx.to, { allowNonPrefixed: false }) && |
||||||
|
isValidHexAddress(approvalTx.from, { allowNonPrefixed: false })), |
||||||
|
}, |
||||||
|
{ |
||||||
|
property: 'sourceAmount', |
||||||
|
type: 'string', |
||||||
|
validator: truthyDigitString, |
||||||
|
}, |
||||||
|
{ |
||||||
|
property: 'destinationAmount', |
||||||
|
type: 'string', |
||||||
|
validator: truthyDigitString, |
||||||
|
}, |
||||||
|
{ |
||||||
|
property: 'sourceToken', |
||||||
|
type: 'string', |
||||||
|
validator: (input) => isValidHexAddress(input, { allowNonPrefixed: false }), |
||||||
|
}, |
||||||
|
{ |
||||||
|
property: 'destinationToken', |
||||||
|
type: 'string', |
||||||
|
validator: (input) => isValidHexAddress(input, { allowNonPrefixed: false }), |
||||||
|
}, |
||||||
|
{ |
||||||
|
property: 'aggregator', |
||||||
|
type: 'string', |
||||||
|
validator: truthyString, |
||||||
|
}, |
||||||
|
{ |
||||||
|
property: 'aggType', |
||||||
|
type: 'string', |
||||||
|
validator: truthyString, |
||||||
|
}, |
||||||
|
{ |
||||||
|
property: 'error', |
||||||
|
type: 'object', |
||||||
|
validator: (error) => error === null || typeof error === 'object', |
||||||
|
}, |
||||||
|
{ |
||||||
|
property: 'averageGas', |
||||||
|
type: 'number', |
||||||
|
}, |
||||||
|
{ |
||||||
|
property: 'maxGas', |
||||||
|
type: 'number', |
||||||
|
}, |
||||||
|
{ |
||||||
|
property: 'gasEstimate', |
||||||
|
type: 'number|undefined', |
||||||
|
validator: (gasEstimate) => gasEstimate === undefined || gasEstimate > 0, |
||||||
|
}, |
||||||
|
{ |
||||||
|
property: 'fee', |
||||||
|
type: 'number', |
||||||
|
}, |
||||||
|
]; |
||||||
|
|
||||||
|
/** |
||||||
|
* @param {string} type - Type of an API call, e.g. "tokens" |
||||||
|
* @param {string} chainId |
||||||
|
* @returns string |
||||||
|
*/ |
||||||
|
const getBaseUrlForNewSwapsApi = (type, chainId) => { |
||||||
|
const useDevApis = process.env.SWAPS_USE_DEV_APIS; |
||||||
|
const v2ApiBaseUrl = useDevApis |
||||||
|
? SWAPS_DEV_API_V2_BASE_URL |
||||||
|
: SWAPS_API_V2_BASE_URL; |
||||||
|
const gasApiBaseUrl = useDevApis ? GAS_DEV_API_BASE_URL : GAS_API_BASE_URL; |
||||||
|
const noNetworkSpecificTypes = ['refreshTime']; // These types don't need network info in the URL.
|
||||||
|
if (noNetworkSpecificTypes.includes(type)) { |
||||||
|
return v2ApiBaseUrl; |
||||||
|
} |
||||||
|
const chainIdDecimal = chainId && parseInt(chainId, 16); |
||||||
|
const gasApiTypes = ['gasPrices']; |
||||||
|
if (gasApiTypes.includes(type)) { |
||||||
|
return `${gasApiBaseUrl}/networks/${chainIdDecimal}`; // Gas calculations are in its own repo.
|
||||||
|
} |
||||||
|
return `${v2ApiBaseUrl}/networks/${chainIdDecimal}`; |
||||||
|
}; |
||||||
|
|
||||||
|
export const getBaseApi = function (type, chainId = CHAIN_IDS.MAINNET) { |
||||||
|
// eslint-disable-next-line no-param-reassign
|
||||||
|
chainId = TEST_CHAIN_IDS.includes(chainId) ? CHAIN_IDS.MAINNET : chainId; |
||||||
|
const baseUrl = getBaseUrlForNewSwapsApi(type, chainId); |
||||||
|
const chainIdDecimal = chainId && parseInt(chainId, 16); |
||||||
|
if (!baseUrl) { |
||||||
|
throw new Error(`Swaps API calls are disabled for chainId: ${chainId}`); |
||||||
|
} |
||||||
|
switch (type) { |
||||||
|
case 'trade': |
||||||
|
return `${baseUrl}/trades?`; |
||||||
|
case 'tokens': |
||||||
|
return `${baseUrl}/tokens`; |
||||||
|
case 'token': |
||||||
|
return `${baseUrl}/token`; |
||||||
|
case 'topAssets': |
||||||
|
return `${baseUrl}/topAssets`; |
||||||
|
case 'aggregatorMetadata': |
||||||
|
return `${baseUrl}/aggregatorMetadata`; |
||||||
|
case 'gasPrices': |
||||||
|
return `${baseUrl}/gasPrices`; |
||||||
|
case 'network': |
||||||
|
// Only use v2 for this endpoint.
|
||||||
|
return `${SWAPS_API_V2_BASE_URL}/networks/${chainIdDecimal}`; |
||||||
|
default: |
||||||
|
throw new Error('getBaseApi requires an api call type'); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
export function calcTokenValue(value, decimals) { |
||||||
|
const multiplier = Math.pow(10, Number(decimals || 0)); |
||||||
|
return new BigNumber(String(value)).times(multiplier); |
||||||
|
} |
||||||
|
|
||||||
|
export const shouldEnableDirectWrapping = ( |
||||||
|
chainId, |
||||||
|
sourceToken, |
||||||
|
destinationToken, |
||||||
|
) => { |
||||||
|
if (!sourceToken || !destinationToken) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
const wrappedToken = SWAPS_WRAPPED_TOKENS_ADDRESSES[chainId]; |
||||||
|
const nativeToken = SWAPS_CHAINID_DEFAULT_TOKEN_MAP[chainId]?.address; |
||||||
|
const sourceTokenLowerCase = sourceToken.toLowerCase(); |
||||||
|
const destinationTokenLowerCase = destinationToken.toLowerCase(); |
||||||
|
return ( |
||||||
|
(sourceTokenLowerCase === wrappedToken && |
||||||
|
destinationTokenLowerCase === nativeToken) || |
||||||
|
(sourceTokenLowerCase === nativeToken && |
||||||
|
destinationTokenLowerCase === wrappedToken) |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
|
/** |
||||||
|
* Given and object where all values are strings, returns the same object with all values |
||||||
|
* now prefixed with '0x' |
||||||
|
* |
||||||
|
* @param obj |
||||||
|
*/ |
||||||
|
export function addHexPrefixToObjectValues(obj) { |
||||||
|
return Object.keys(obj).reduce((newObj, key) => { |
||||||
|
return { ...newObj, [key]: addHexPrefix(obj[key]) }; |
||||||
|
}, {}); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Given the standard set of information about a transaction, returns a transaction properly formatted for |
||||||
|
* publishing via JSON RPC and web3 |
||||||
|
* |
||||||
|
* @param {object} options |
||||||
|
* @param {boolean} [options.sendToken] - Indicates whether or not the transaciton is a token transaction |
||||||
|
* @param {string} options.data - A hex string containing the data to include in the transaction |
||||||
|
* @param {string} options.to - A hex address of the tx recipient address |
||||||
|
* @param options.amount |
||||||
|
* @param {string} options.from - A hex address of the tx sender address |
||||||
|
* @param {string} options.gas - A hex representation of the gas value for the transaction |
||||||
|
* @param {string} options.gasPrice - A hex representation of the gas price for the transaction |
||||||
|
* @returns {object} An object ready for submission to the blockchain, with all values appropriately hex prefixed |
||||||
|
*/ |
||||||
|
export function constructTxParams({ |
||||||
|
sendToken, |
||||||
|
data, |
||||||
|
to, |
||||||
|
amount, |
||||||
|
from, |
||||||
|
gas, |
||||||
|
gasPrice, |
||||||
|
}) { |
||||||
|
const txParams = { |
||||||
|
data, |
||||||
|
from, |
||||||
|
value: '0', |
||||||
|
gas, |
||||||
|
gasPrice, |
||||||
|
}; |
||||||
|
|
||||||
|
if (!sendToken) { |
||||||
|
txParams.value = amount; |
||||||
|
txParams.to = to; |
||||||
|
} |
||||||
|
return addHexPrefixToObjectValues(txParams); |
||||||
|
} |
||||||
|
|
||||||
|
export async function fetchTradesInfo( |
||||||
|
{ |
||||||
|
slippage, |
||||||
|
sourceToken, |
||||||
|
sourceDecimals, |
||||||
|
destinationToken, |
||||||
|
value, |
||||||
|
fromAddress, |
||||||
|
exchangeList, |
||||||
|
}, |
||||||
|
{ chainId }, |
||||||
|
) { |
||||||
|
const urlParams = { |
||||||
|
destinationToken, |
||||||
|
sourceToken, |
||||||
|
sourceAmount: calcTokenValue(value, sourceDecimals).toString(10), |
||||||
|
slippage, |
||||||
|
timeout: SECOND * 10, |
||||||
|
walletAddress: fromAddress, |
||||||
|
}; |
||||||
|
|
||||||
|
if (exchangeList) { |
||||||
|
urlParams.exchangeList = exchangeList; |
||||||
|
} |
||||||
|
if (shouldEnableDirectWrapping(chainId, sourceToken, destinationToken)) { |
||||||
|
urlParams.enableDirectWrapping = true; |
||||||
|
} |
||||||
|
|
||||||
|
const queryString = new URLSearchParams(urlParams).toString(); |
||||||
|
const tradeURL = `${getBaseApi('trade', chainId)}${queryString}`; |
||||||
|
const tradesResponse = await fetchWithCache( |
||||||
|
tradeURL, |
||||||
|
{ method: 'GET', headers: clientIdHeader }, |
||||||
|
{ cacheRefreshTime: 0, timeout: SECOND * 15 }, |
||||||
|
); |
||||||
|
const newQuotes = tradesResponse.reduce((aggIdTradeMap, quote) => { |
||||||
|
if ( |
||||||
|
quote.trade && |
||||||
|
!quote.error && |
||||||
|
validateData(QUOTE_VALIDATORS, quote, tradeURL) |
||||||
|
) { |
||||||
|
const constructedTrade = constructTxParams({ |
||||||
|
to: quote.trade.to, |
||||||
|
from: quote.trade.from, |
||||||
|
data: quote.trade.data, |
||||||
|
amount: decimalToHex(quote.trade.value), |
||||||
|
gas: decimalToHex(quote.maxGas), |
||||||
|
}); |
||||||
|
|
||||||
|
let { approvalNeeded } = quote; |
||||||
|
|
||||||
|
if (approvalNeeded) { |
||||||
|
approvalNeeded = constructTxParams({ |
||||||
|
...approvalNeeded, |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
return { |
||||||
|
...aggIdTradeMap, |
||||||
|
[quote.aggregator]: { |
||||||
|
...quote, |
||||||
|
slippage, |
||||||
|
trade: constructedTrade, |
||||||
|
approvalNeeded, |
||||||
|
}, |
||||||
|
}; |
||||||
|
} |
||||||
|
return aggIdTradeMap; |
||||||
|
}, {}); |
||||||
|
|
||||||
|
return newQuotes; |
||||||
|
} |
@ -0,0 +1,20 @@ |
|||||||
|
/** |
||||||
|
* Gets the '_value' parameter of the given token transaction data |
||||||
|
* (i.e function call) per the Human Standard Token ABI, if present. |
||||||
|
* |
||||||
|
* @param {object} tokenData - ethers Interface token data. |
||||||
|
* @returns {string | undefined} A decimal string value. |
||||||
|
*/ |
||||||
|
/** |
||||||
|
* Gets either the '_tokenId' parameter or the 'id' param of the passed token transaction data., |
||||||
|
* These are the parsed tokenId values returned by `parseStandardTokenTransactionData` as defined |
||||||
|
* in the ERC721 and ERC1155 ABIs from metamask-eth-abis (https://github.com/MetaMask/metamask-eth-abis/tree/main/src/abis)
|
||||||
|
* |
||||||
|
* @param {object} tokenData - ethers Interface token data. |
||||||
|
* @returns {string | undefined} A decimal string value. |
||||||
|
*/ |
||||||
|
export function getTokenIdParam(tokenData = {}) { |
||||||
|
return ( |
||||||
|
tokenData?.args?._tokenId?.toString() ?? tokenData?.args?.id?.toString() |
||||||
|
); |
||||||
|
} |
@ -0,0 +1,172 @@ |
|||||||
|
import BigNumber from 'bignumber.js'; |
||||||
|
import { TRANSACTION_ENVELOPE_TYPES } from '../constants/transaction'; |
||||||
|
import { |
||||||
|
conversionUtil, |
||||||
|
multiplyCurrencies, |
||||||
|
subtractCurrencies, |
||||||
|
} from '../modules/conversion.utils'; |
||||||
|
import { isSwapsDefaultTokenSymbol } from '../modules/swaps.utils'; |
||||||
|
|
||||||
|
const TOKEN_TRANSFER_LOG_TOPIC_HASH = |
||||||
|
'0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef'; |
||||||
|
|
||||||
|
export const TRANSACTION_NO_CONTRACT_ERROR_KEY = 'transactionErrorNoContract'; |
||||||
|
|
||||||
|
export const TEN_SECONDS_IN_MILLISECONDS = 10_000; |
||||||
|
|
||||||
|
export function calcGasTotal(gasLimit = '0', gasPrice = '0') { |
||||||
|
return multiplyCurrencies(gasLimit, gasPrice, { |
||||||
|
toNumericBase: 'hex', |
||||||
|
multiplicandBase: 16, |
||||||
|
multiplierBase: 16, |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Given a number and specified precision, returns that number in base 10 with a maximum of precision |
||||||
|
* significant digits, but without any trailing zeros after the decimal point To be used when wishing |
||||||
|
* to display only as much digits to the user as necessary |
||||||
|
* |
||||||
|
* @param {string | number | BigNumber} n - The number to format |
||||||
|
* @param {number} precision - The maximum number of significant digits in the return value |
||||||
|
* @returns {string} The number in decimal form, with <= precision significant digits and no decimal trailing zeros |
||||||
|
*/ |
||||||
|
export function toPrecisionWithoutTrailingZeros(n, precision) { |
||||||
|
return new BigNumber(n) |
||||||
|
.toPrecision(precision) |
||||||
|
.replace(/(\.[0-9]*[1-9])0*|(\.0*)/u, '$1'); |
||||||
|
} |
||||||
|
|
||||||
|
export function calcTokenAmount(value, decimals) { |
||||||
|
const multiplier = Math.pow(10, Number(decimals || 0)); |
||||||
|
return new BigNumber(String(value)).div(multiplier); |
||||||
|
} |
||||||
|
|
||||||
|
export function getSwapsTokensReceivedFromTxMeta( |
||||||
|
tokenSymbol, |
||||||
|
txMeta, |
||||||
|
tokenAddress, |
||||||
|
accountAddress, |
||||||
|
tokenDecimals, |
||||||
|
approvalTxMeta, |
||||||
|
chainId, |
||||||
|
) { |
||||||
|
const txReceipt = txMeta?.txReceipt; |
||||||
|
const networkAndAccountSupports1559 = |
||||||
|
txMeta?.txReceipt?.type === TRANSACTION_ENVELOPE_TYPES.FEE_MARKET; |
||||||
|
if (isSwapsDefaultTokenSymbol(tokenSymbol, chainId)) { |
||||||
|
if ( |
||||||
|
!txReceipt || |
||||||
|
!txMeta || |
||||||
|
!txMeta.postTxBalance || |
||||||
|
!txMeta.preTxBalance |
||||||
|
) { |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
if (txMeta.swapMetaData && txMeta.preTxBalance === txMeta.postTxBalance) { |
||||||
|
// If preTxBalance and postTxBalance are equal, postTxBalance hasn't been updated on time
|
||||||
|
// because of the RPC provider delay, so we return an estimated receiving amount instead.
|
||||||
|
return txMeta.swapMetaData.token_to_amount; |
||||||
|
} |
||||||
|
|
||||||
|
let approvalTxGasCost = '0x0'; |
||||||
|
if (approvalTxMeta && approvalTxMeta.txReceipt) { |
||||||
|
approvalTxGasCost = calcGasTotal( |
||||||
|
approvalTxMeta.txReceipt.gasUsed, |
||||||
|
networkAndAccountSupports1559 |
||||||
|
? approvalTxMeta.txReceipt.effectiveGasPrice // Base fee + priority fee.
|
||||||
|
: approvalTxMeta.txParams.gasPrice, |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
const gasCost = calcGasTotal( |
||||||
|
txReceipt.gasUsed, |
||||||
|
networkAndAccountSupports1559 |
||||||
|
? txReceipt.effectiveGasPrice |
||||||
|
: txMeta.txParams.gasPrice, |
||||||
|
); |
||||||
|
const totalGasCost = new BigNumber(gasCost, 16) |
||||||
|
.plus(approvalTxGasCost, 16) |
||||||
|
.toString(16); |
||||||
|
|
||||||
|
const preTxBalanceLessGasCost = subtractCurrencies( |
||||||
|
txMeta.preTxBalance, |
||||||
|
totalGasCost, |
||||||
|
{ |
||||||
|
aBase: 16, |
||||||
|
bBase: 16, |
||||||
|
toNumericBase: 'hex', |
||||||
|
}, |
||||||
|
); |
||||||
|
|
||||||
|
const ethReceived = subtractCurrencies( |
||||||
|
txMeta.postTxBalance, |
||||||
|
preTxBalanceLessGasCost, |
||||||
|
{ |
||||||
|
aBase: 16, |
||||||
|
bBase: 16, |
||||||
|
fromDenomination: 'WEI', |
||||||
|
toDenomination: 'ETH', |
||||||
|
toNumericBase: 'dec', |
||||||
|
numberOfDecimals: 6, |
||||||
|
}, |
||||||
|
); |
||||||
|
return ethReceived; |
||||||
|
} |
||||||
|
const txReceiptLogs = txReceipt?.logs; |
||||||
|
if (txReceiptLogs && txReceipt?.status !== '0x0') { |
||||||
|
const tokenTransferLog = txReceiptLogs.find((txReceiptLog) => { |
||||||
|
const isTokenTransfer = |
||||||
|
txReceiptLog.topics && |
||||||
|
txReceiptLog.topics[0] === TOKEN_TRANSFER_LOG_TOPIC_HASH; |
||||||
|
const isTransferFromGivenToken = txReceiptLog.address === tokenAddress; |
||||||
|
const isTransferFromGivenAddress = |
||||||
|
txReceiptLog.topics && |
||||||
|
txReceiptLog.topics[2] && |
||||||
|
txReceiptLog.topics[2].match(accountAddress.slice(2)); |
||||||
|
return ( |
||||||
|
isTokenTransfer && |
||||||
|
isTransferFromGivenToken && |
||||||
|
isTransferFromGivenAddress |
||||||
|
); |
||||||
|
}); |
||||||
|
return tokenTransferLog |
||||||
|
? toPrecisionWithoutTrailingZeros( |
||||||
|
calcTokenAmount(tokenTransferLog.data, tokenDecimals).toString(10), |
||||||
|
6, |
||||||
|
) |
||||||
|
: ''; |
||||||
|
} |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
export const TRANSACTION_ENVELOPE_TYPE_NAMES = { |
||||||
|
FEE_MARKET: 'fee-market', |
||||||
|
LEGACY: 'legacy', |
||||||
|
}; |
||||||
|
|
||||||
|
export function hexWEIToDecGWEI(decGWEI) { |
||||||
|
return conversionUtil(decGWEI, { |
||||||
|
fromNumericBase: 'hex', |
||||||
|
toNumericBase: 'dec', |
||||||
|
fromDenomination: 'WEI', |
||||||
|
toDenomination: 'GWEI', |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
export function decimalToHex(decimal) { |
||||||
|
return conversionUtil(decimal, { |
||||||
|
fromNumericBase: 'dec', |
||||||
|
toNumericBase: 'hex', |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
export function hexWEIToDecETH(hexWEI) { |
||||||
|
return conversionUtil(hexWEI, { |
||||||
|
fromNumericBase: 'hex', |
||||||
|
toNumericBase: 'dec', |
||||||
|
fromDenomination: 'WEI', |
||||||
|
toDenomination: 'ETH', |
||||||
|
}); |
||||||
|
} |
@ -0,0 +1,7 @@ |
|||||||
|
let _supportLink = 'https://support.metamask.io'; |
||||||
|
|
||||||
|
///: BEGIN:ONLY_INCLUDE_IN(flask)
|
||||||
|
_supportLink = 'https://metamask-flask.zendesk.com/hc'; |
||||||
|
///: END:ONLY_INCLUDE_IN
|
||||||
|
|
||||||
|
export const SUPPORT_LINK = _supportLink; |
@ -0,0 +1,22 @@ |
|||||||
|
import React from 'react'; |
||||||
|
import { screen } from '@testing-library/react'; |
||||||
|
import { renderWithProvider } from '../../../../test/jest'; |
||||||
|
import configureStore from '../../../store/store'; |
||||||
|
import mockState from '../../../../test/data/mock-state.json'; |
||||||
|
import AssetList from './asset-list'; |
||||||
|
|
||||||
|
const render = () => { |
||||||
|
const store = configureStore({ |
||||||
|
metamask: { |
||||||
|
...mockState.metamask, |
||||||
|
}, |
||||||
|
}); |
||||||
|
return renderWithProvider(<AssetList />, store); |
||||||
|
}; |
||||||
|
|
||||||
|
describe('AssetList', () => { |
||||||
|
it('renders AssetList component and shows Refresh List text', () => { |
||||||
|
render(); |
||||||
|
expect(screen.getByText('Refresh list')).toBeInTheDocument(); |
||||||
|
}); |
||||||
|
}); |
@ -0,0 +1,31 @@ |
|||||||
|
import React from 'react'; |
||||||
|
import { screen } from '@testing-library/react'; |
||||||
|
import { renderWithProvider } from '../../../../test/jest'; |
||||||
|
import configureStore from '../../../store/store'; |
||||||
|
import mockState from '../../../../test/data/mock-state.json'; |
||||||
|
import CreateNewVault from './create-new-vault'; |
||||||
|
|
||||||
|
const store = configureStore({ |
||||||
|
metamask: { |
||||||
|
...mockState.metamask, |
||||||
|
}, |
||||||
|
}); |
||||||
|
|
||||||
|
describe('CreateNewVault', () => { |
||||||
|
it('renders CreateNewVault component and shows Secret Recovery Phrase text', () => { |
||||||
|
renderWithProvider(<CreateNewVault submitText="Import" />, store); |
||||||
|
expect(screen.getByText('Secret Recovery Phrase')).toBeInTheDocument(); |
||||||
|
}); |
||||||
|
|
||||||
|
it('renders CreateNewVault component and shows You can paste... text', () => { |
||||||
|
renderWithProvider( |
||||||
|
<CreateNewVault submitText="Import" includeTerms />, |
||||||
|
store, |
||||||
|
); |
||||||
|
expect( |
||||||
|
screen.getByText( |
||||||
|
'You can paste your entire secret recovery phrase into any field', |
||||||
|
), |
||||||
|
).toBeInTheDocument(); |
||||||
|
}); |
||||||
|
}); |
@ -0,0 +1,22 @@ |
|||||||
|
import React from 'react'; |
||||||
|
import { screen } from '@testing-library/react'; |
||||||
|
import { renderWithProvider } from '../../../../test/jest'; |
||||||
|
import configureStore from '../../../store/store'; |
||||||
|
import mockState from '../../../../test/data/mock-state.json'; |
||||||
|
import TransactionList from './transaction-list.component'; |
||||||
|
|
||||||
|
const render = () => { |
||||||
|
const store = configureStore({ |
||||||
|
metamask: { |
||||||
|
...mockState.metamask, |
||||||
|
}, |
||||||
|
}); |
||||||
|
return renderWithProvider(<TransactionList />, store); |
||||||
|
}; |
||||||
|
|
||||||
|
describe('TransactionList', () => { |
||||||
|
it('renders TransactionList component and shows You have no transactions text', () => { |
||||||
|
render(); |
||||||
|
expect(screen.getByText('You have no transactions')).toBeInTheDocument(); |
||||||
|
}); |
||||||
|
}); |
@ -0,0 +1,121 @@ |
|||||||
|
import React from 'react'; |
||||||
|
import { screen } from '@testing-library/react'; |
||||||
|
import { renderWithProvider } from '../../../../test/jest'; |
||||||
|
import configureStore from '../../../store/store'; |
||||||
|
import mockState from '../../../../test/data/mock-state.json'; |
||||||
|
import WhatsNewPopup from './whats-new-popup'; |
||||||
|
|
||||||
|
const render = () => { |
||||||
|
const store = configureStore({ |
||||||
|
metamask: { |
||||||
|
...mockState.metamask, |
||||||
|
announcements: { |
||||||
|
1: { |
||||||
|
date: '2021-03-17', |
||||||
|
id: 1, |
||||||
|
image: { |
||||||
|
height: '230px', |
||||||
|
placeImageBelowDescription: true, |
||||||
|
src: 'images/mobile-link-qr.svg', |
||||||
|
width: '230px', |
||||||
|
}, |
||||||
|
isShown: false, |
||||||
|
}, |
||||||
|
3: { |
||||||
|
date: '2021-03-08', |
||||||
|
id: 3, |
||||||
|
isShown: false, |
||||||
|
}, |
||||||
|
4: { |
||||||
|
date: '2021-05-11', |
||||||
|
id: 4, |
||||||
|
image: { |
||||||
|
src: 'images/source-logos-bsc.svg', |
||||||
|
width: '100%', |
||||||
|
}, |
||||||
|
isShown: false, |
||||||
|
}, |
||||||
|
5: { |
||||||
|
date: '2021-06-09', |
||||||
|
id: 5, |
||||||
|
isShown: false, |
||||||
|
}, |
||||||
|
6: { |
||||||
|
date: '2021-05-26', |
||||||
|
id: 6, |
||||||
|
isShown: false, |
||||||
|
}, |
||||||
|
7: { |
||||||
|
date: '2021-09-17', |
||||||
|
id: 7, |
||||||
|
isShown: false, |
||||||
|
}, |
||||||
|
8: { |
||||||
|
date: '2021-11-01', |
||||||
|
id: 8, |
||||||
|
isShown: false, |
||||||
|
}, |
||||||
|
9: { |
||||||
|
date: '2021-12-07', |
||||||
|
id: 9, |
||||||
|
image: { |
||||||
|
src: 'images/txinsights.png', |
||||||
|
width: '80%', |
||||||
|
}, |
||||||
|
isShown: false, |
||||||
|
}, |
||||||
|
10: { |
||||||
|
date: '2022-04-18', |
||||||
|
id: 10, |
||||||
|
image: { |
||||||
|
src: 'images/token-detection.svg', |
||||||
|
width: '100%', |
||||||
|
}, |
||||||
|
isShown: true, |
||||||
|
}, |
||||||
|
11: { |
||||||
|
date: '2022-04-18', |
||||||
|
id: 11, |
||||||
|
isShown: true, |
||||||
|
}, |
||||||
|
12: { |
||||||
|
date: '2022-05-18', |
||||||
|
id: 12, |
||||||
|
image: { |
||||||
|
src: 'images/darkmode-banner.png', |
||||||
|
width: '100%', |
||||||
|
}, |
||||||
|
isShown: false, |
||||||
|
}, |
||||||
|
13: { |
||||||
|
date: '2022-07-12', |
||||||
|
id: 13, |
||||||
|
isShown: true, |
||||||
|
}, |
||||||
|
}, |
||||||
|
}, |
||||||
|
}); |
||||||
|
return renderWithProvider(<WhatsNewPopup />, store); |
||||||
|
}; |
||||||
|
|
||||||
|
describe('WhatsNewPopup', () => { |
||||||
|
beforeEach(() => { |
||||||
|
const mockIntersectionObserver = jest.fn(); |
||||||
|
mockIntersectionObserver.mockReturnValue({ |
||||||
|
observe: () => null, |
||||||
|
unobserve: () => null, |
||||||
|
disconnect: () => null, |
||||||
|
}); |
||||||
|
window.IntersectionObserver = mockIntersectionObserver; |
||||||
|
}); |
||||||
|
|
||||||
|
it("renders WhatsNewPopup component and shows What's new text", () => { |
||||||
|
render(); |
||||||
|
expect(screen.getByText("What's new")).toBeInTheDocument(); |
||||||
|
}); |
||||||
|
|
||||||
|
it('renders WhatsNewPopup component and shows close button', () => { |
||||||
|
render(); |
||||||
|
expect(screen.getByTestId('popover-close')).toBeInTheDocument(); |
||||||
|
}); |
||||||
|
}); |
@ -0,0 +1,60 @@ |
|||||||
|
import React from 'react'; |
||||||
|
import { screen } from '@testing-library/react'; |
||||||
|
import { renderWithProvider } from '../../../../test/jest'; |
||||||
|
import configureStore from '../../../store/store'; |
||||||
|
import mockState from '../../../../test/data/mock-state.json'; |
||||||
|
import AccountList from './account-list'; |
||||||
|
|
||||||
|
const render = () => { |
||||||
|
const store = configureStore({ |
||||||
|
metamask: { |
||||||
|
...mockState.metamask, |
||||||
|
}, |
||||||
|
}); |
||||||
|
|
||||||
|
const args = { |
||||||
|
accounts: [ |
||||||
|
{ |
||||||
|
address: '0x64a845a5b02460acf8a3d84503b0d68d028b4bb4', |
||||||
|
addressLabel: 'Account 1', |
||||||
|
lastConnectedDate: 'Feb-22-2022', |
||||||
|
balance: '8.7a73149c048545a3fe58', |
||||||
|
has: () => { |
||||||
|
/** nothing to do */ |
||||||
|
}, |
||||||
|
}, |
||||||
|
], |
||||||
|
selectedAccounts: { |
||||||
|
address: '0x64a845a5b02460acf8a3d84503b0d68d028b4bb4', |
||||||
|
addressLabel: 'Account 2', |
||||||
|
lastConnectedDate: 'Feb-22-2022', |
||||||
|
balance: '8.7a73149c048545a3fe58', |
||||||
|
has: () => { |
||||||
|
/** nothing to do */ |
||||||
|
}, |
||||||
|
}, |
||||||
|
addressLastConnectedMap: { |
||||||
|
'0x64a845a5b02460acf8a3d84503b0d68d028b4bb4': 'Feb-22-2022', |
||||||
|
}, |
||||||
|
allAreSelected: () => true, |
||||||
|
nativeCurrency: 'USD', |
||||||
|
}; |
||||||
|
return renderWithProvider(<AccountList {...args} />, store); |
||||||
|
}; |
||||||
|
|
||||||
|
describe('AccountList', () => { |
||||||
|
it('renders AccountList component and shows New account text', () => { |
||||||
|
render(); |
||||||
|
expect(screen.getByText('New account')).toBeInTheDocument(); |
||||||
|
}); |
||||||
|
|
||||||
|
it('renders AccountList component and shows Account 1 text', () => { |
||||||
|
render(); |
||||||
|
expect(screen.getByText('Account 1')).toBeInTheDocument(); |
||||||
|
}); |
||||||
|
|
||||||
|
it('renders AccountList component and shows ETH text', () => { |
||||||
|
render(); |
||||||
|
expect(screen.getByText('ETH')).toBeInTheDocument(); |
||||||
|
}); |
||||||
|
}); |
@ -0,0 +1,35 @@ |
|||||||
|
import React from 'react'; |
||||||
|
import { screen } from '@testing-library/react'; |
||||||
|
import { renderWithProvider } from '../../../../test/jest'; |
||||||
|
import configureStore from '../../../store/store'; |
||||||
|
import mockState from '../../../../test/data/mock-state.json'; |
||||||
|
import NicknamePopover from './nickname-popover.component'; |
||||||
|
|
||||||
|
const store = configureStore({ |
||||||
|
metamask: { |
||||||
|
...mockState.metamask, |
||||||
|
}, |
||||||
|
}); |
||||||
|
|
||||||
|
describe('NicknamePopover', () => { |
||||||
|
it('renders NicknamePopover component and shows Add a nickname text', () => { |
||||||
|
renderWithProvider( |
||||||
|
<NicknamePopover address="0x5e6DaAD1BE117e26590F9eEcD509336ABFBe5966" />, |
||||||
|
store, |
||||||
|
); |
||||||
|
|
||||||
|
expect(screen.getByText('Add a nickname')).toBeInTheDocument(); |
||||||
|
}); |
||||||
|
|
||||||
|
it('renders NicknamePopover component and shows Edit nickname text', () => { |
||||||
|
renderWithProvider( |
||||||
|
<NicknamePopover |
||||||
|
address="0x5e6DaAD1BE117e26590F9eEcD509336ABFBe5966" |
||||||
|
nickname="John Doe" |
||||||
|
/>, |
||||||
|
store, |
||||||
|
); |
||||||
|
|
||||||
|
expect(screen.getByText('Edit nickname')).toBeInTheDocument(); |
||||||
|
}); |
||||||
|
}); |
@ -0,0 +1,29 @@ |
|||||||
|
import React from 'react'; |
||||||
|
import { screen } from '@testing-library/react'; |
||||||
|
import { renderWithProvider } from '../../../../test/jest'; |
||||||
|
import configureStore from '../../../store/store'; |
||||||
|
import mockState from '../../../../test/data/mock-state.json'; |
||||||
|
import UpdateNicknamePopover from './update-nickname-popover'; |
||||||
|
|
||||||
|
const render = () => { |
||||||
|
const store = configureStore({ |
||||||
|
metamask: { |
||||||
|
...mockState.metamask, |
||||||
|
}, |
||||||
|
}); |
||||||
|
return renderWithProvider( |
||||||
|
<UpdateNicknamePopover |
||||||
|
nickname="user_nickname" |
||||||
|
memo="This is a memo" |
||||||
|
address="0xdeDbcA0156308960E3bBa2f5a273E72179940788" |
||||||
|
/>, |
||||||
|
store, |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
|
describe('UpdateNicknamePopover', () => { |
||||||
|
it('renders UpdateNicknamePopover component and shows This is a memo text', () => { |
||||||
|
render(); |
||||||
|
expect(screen.getByText('This is a memo')).toBeInTheDocument(); |
||||||
|
}); |
||||||
|
}); |
@ -1 +0,0 @@ |
|||||||
export const TEN_SECONDS_IN_MILLISECONDS = 10_000; |
|
@ -0,0 +1,22 @@ |
|||||||
|
import React from 'react'; |
||||||
|
import { screen } from '@testing-library/react'; |
||||||
|
import { renderWithProvider } from '../../../../test/jest'; |
||||||
|
import configureStore from '../../../store/store'; |
||||||
|
import mockState from '../../../../test/data/mock-state.json'; |
||||||
|
import ExperimentalTab from './experimental-tab.component'; |
||||||
|
|
||||||
|
const render = () => { |
||||||
|
const store = configureStore({ |
||||||
|
metamask: { |
||||||
|
...mockState.metamask, |
||||||
|
}, |
||||||
|
}); |
||||||
|
return renderWithProvider(<ExperimentalTab />, store); |
||||||
|
}; |
||||||
|
|
||||||
|
describe('ExperimentalTab', () => { |
||||||
|
it('renders ExperimentalTab component and shows Enable enhanced gas fee UI text', () => { |
||||||
|
render(); |
||||||
|
expect(screen.getByText('Enable enhanced gas fee UI')).toBeInTheDocument(); |
||||||
|
}); |
||||||
|
}); |
Loading…
Reference in new issue