mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-02 06:07:06 +01:00
c63714c4f2
* Fix warning dialog when sending tokens to a known token contract address Fixing after rebase Covering missed cases Rebased and ran yarn setup Rebased Fix checkContractAddress condition Lint fix Applied requested changes Fix unit tests Applying requested changes Applied requested changes Refactor and update Lint fix Use V2 of ActionableMessage component Adding Learn More Link Updating warning copy Addressing review feedback Fix up copy changes Simplify validation of pasted addresses Improve detection of whether this is a token contract Refactor to leave updateRecipient unchanged, and to prevent the double calling of update recipient Update tests fix * Fix unit tests * Fix e2e tests * Ensure next button is disabled while recipient type is loading * Add optional chaining and a fallback to getRecipientWarningAcknowledgement * Fix lint * Don't reset recipient warning on asset change, because we should show recipient warnings regardless of asset * Update unit tests * Update unit tests Co-authored-by: Filip Sekulic <filip.sekulic@consensys.net>
268 lines
7.5 KiB
JavaScript
268 lines
7.5 KiB
JavaScript
import log from 'loglevel';
|
|
import BigNumber from 'bignumber.js';
|
|
import {
|
|
conversionUtil,
|
|
multiplyCurrencies,
|
|
} from '../../../shared/modules/conversion.utils';
|
|
import { getTokenStandardAndDetails } from '../../store/actions';
|
|
import { ERC1155, ERC721 } from '../constants/common';
|
|
import { isEqualCaseInsensitive } from '../../../shared/modules/string-utils';
|
|
import { parseStandardTokenTransactionData } from '../../../shared/modules/transaction.utils';
|
|
import * as util from './util';
|
|
import { formatCurrency } from './confirm-tx.util';
|
|
|
|
const DEFAULT_SYMBOL = '';
|
|
|
|
async function getSymbolFromContract(tokenAddress) {
|
|
const token = util.getContractAtAddress(tokenAddress);
|
|
try {
|
|
const result = await token.symbol();
|
|
return result[0];
|
|
} catch (error) {
|
|
log.warn(
|
|
`symbol() call for token at address ${tokenAddress} resulted in error:`,
|
|
error,
|
|
);
|
|
return undefined;
|
|
}
|
|
}
|
|
|
|
async function getDecimalsFromContract(tokenAddress) {
|
|
const token = util.getContractAtAddress(tokenAddress);
|
|
|
|
try {
|
|
const result = await token.decimals();
|
|
const decimalsBN = result[0];
|
|
return decimalsBN?.toString();
|
|
} catch (error) {
|
|
log.warn(
|
|
`decimals() call for token at address ${tokenAddress} resulted in error:`,
|
|
error,
|
|
);
|
|
return undefined;
|
|
}
|
|
}
|
|
|
|
export function getTokenMetadata(tokenAddress, tokenList) {
|
|
const casedTokenList = Object.keys(tokenList).reduce((acc, base) => {
|
|
return {
|
|
...acc,
|
|
[base.toLowerCase()]: tokenList[base],
|
|
};
|
|
}, {});
|
|
return tokenAddress && casedTokenList[tokenAddress.toLowerCase()];
|
|
}
|
|
|
|
async function getSymbol(tokenAddress, tokenList) {
|
|
let symbol = await getSymbolFromContract(tokenAddress);
|
|
|
|
if (!symbol) {
|
|
const contractMetadataInfo = getTokenMetadata(tokenAddress, tokenList);
|
|
|
|
if (contractMetadataInfo) {
|
|
symbol = contractMetadataInfo.symbol;
|
|
}
|
|
}
|
|
|
|
return symbol;
|
|
}
|
|
|
|
async function getDecimals(tokenAddress, tokenList) {
|
|
let decimals = await getDecimalsFromContract(tokenAddress);
|
|
|
|
if (!decimals || decimals === '0') {
|
|
const contractMetadataInfo = getTokenMetadata(tokenAddress, tokenList);
|
|
|
|
if (contractMetadataInfo) {
|
|
decimals = contractMetadataInfo.decimals?.toString();
|
|
}
|
|
}
|
|
|
|
return decimals;
|
|
}
|
|
|
|
export async function getSymbolAndDecimals(tokenAddress, tokenList) {
|
|
let symbol, decimals;
|
|
|
|
try {
|
|
symbol = await getSymbol(tokenAddress, tokenList);
|
|
decimals = await getDecimals(tokenAddress, tokenList);
|
|
} catch (error) {
|
|
log.warn(
|
|
`symbol() and decimal() calls for token at address ${tokenAddress} resulted in error:`,
|
|
error,
|
|
);
|
|
}
|
|
|
|
return {
|
|
symbol: symbol || DEFAULT_SYMBOL,
|
|
decimals,
|
|
};
|
|
}
|
|
|
|
export function tokenInfoGetter() {
|
|
const tokens = {};
|
|
|
|
return async (address, tokenList) => {
|
|
if (tokens[address]) {
|
|
return tokens[address];
|
|
}
|
|
|
|
tokens[address] = await getSymbolAndDecimals(address, tokenList);
|
|
|
|
return tokens[address];
|
|
};
|
|
}
|
|
|
|
export function calcTokenAmount(value, decimals) {
|
|
const multiplier = Math.pow(10, Number(decimals || 0));
|
|
return new BigNumber(String(value)).div(multiplier);
|
|
}
|
|
|
|
export function calcTokenValue(value, decimals) {
|
|
const multiplier = Math.pow(10, Number(decimals || 0));
|
|
return new BigNumber(String(value)).times(multiplier);
|
|
}
|
|
|
|
/**
|
|
* Attempts to get the address parameter of the given token transaction data
|
|
* (i.e. function call) per the Human Standard Token ABI, in the following
|
|
* order:
|
|
* - The '_to' parameter, if present
|
|
* - The first parameter, if present
|
|
*
|
|
* @param {Object} tokenData - ethers Interface token data.
|
|
* @returns {string | undefined} A lowercase address string.
|
|
*/
|
|
export function getTokenAddressParam(tokenData = {}) {
|
|
const value =
|
|
tokenData?.args?._to || tokenData?.args?.to || tokenData?.args?.[0];
|
|
return value?.toString().toLowerCase();
|
|
}
|
|
|
|
/**
|
|
* 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.
|
|
*/
|
|
export function getTokenValueParam(tokenData = {}) {
|
|
return tokenData?.args?._value?.toString();
|
|
}
|
|
|
|
export function getTokenApprovedParam(tokenData = {}) {
|
|
return tokenData?.args?._approved;
|
|
}
|
|
|
|
export function getTokenValue(tokenParams = []) {
|
|
const valueData = tokenParams.find((param) => param.name === '_value');
|
|
return valueData && valueData.value;
|
|
}
|
|
|
|
/**
|
|
* Get the token balance converted to fiat and optionally formatted for display
|
|
*
|
|
* @param {number} [contractExchangeRate] - The exchange rate between the current token and the native currency
|
|
* @param {number} conversionRate - The exchange rate between the current fiat currency and the native currency
|
|
* @param {string} currentCurrency - The currency code for the user's chosen fiat currency
|
|
* @param {string} [tokenAmount] - The current token balance
|
|
* @param {string} [tokenSymbol] - The token symbol
|
|
* @param {boolean} [formatted] - Whether the return value should be formatted or not
|
|
* @param {boolean} [hideCurrencySymbol] - excludes the currency symbol in the result if true
|
|
* @returns {string|undefined} The token amount in the user's chosen fiat currency, optionally formatted and localize
|
|
*/
|
|
export function getTokenFiatAmount(
|
|
contractExchangeRate,
|
|
conversionRate,
|
|
currentCurrency,
|
|
tokenAmount,
|
|
tokenSymbol,
|
|
formatted = true,
|
|
hideCurrencySymbol = false,
|
|
) {
|
|
// If the conversionRate is 0 (i.e. unknown) or the contract exchange rate
|
|
// is currently unknown, the fiat amount cannot be calculated so it is not
|
|
// shown to the user
|
|
if (
|
|
conversionRate <= 0 ||
|
|
!contractExchangeRate ||
|
|
tokenAmount === undefined
|
|
) {
|
|
return undefined;
|
|
}
|
|
|
|
const currentTokenToFiatRate = multiplyCurrencies(
|
|
contractExchangeRate,
|
|
conversionRate,
|
|
{
|
|
multiplicandBase: 10,
|
|
multiplierBase: 10,
|
|
},
|
|
);
|
|
const currentTokenInFiat = conversionUtil(tokenAmount, {
|
|
fromNumericBase: 'dec',
|
|
fromCurrency: tokenSymbol,
|
|
toCurrency: currentCurrency.toUpperCase(),
|
|
numberOfDecimals: 2,
|
|
conversionRate: currentTokenToFiatRate,
|
|
});
|
|
let result;
|
|
if (hideCurrencySymbol) {
|
|
result = formatCurrency(currentTokenInFiat, currentCurrency);
|
|
} else if (formatted) {
|
|
result = `${formatCurrency(
|
|
currentTokenInFiat,
|
|
currentCurrency,
|
|
)} ${currentCurrency.toUpperCase()}`;
|
|
} else {
|
|
result = currentTokenInFiat;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
export async function getAssetDetails(
|
|
tokenAddress,
|
|
currentUserAddress,
|
|
transactionData,
|
|
existingCollectibles,
|
|
) {
|
|
const tokenData = parseStandardTokenTransactionData(transactionData);
|
|
if (!tokenData) {
|
|
throw new Error('Unable to detect valid token data');
|
|
}
|
|
|
|
const tokenId = getTokenValueParam(tokenData);
|
|
let tokenDetails;
|
|
try {
|
|
tokenDetails = await getTokenStandardAndDetails(
|
|
tokenAddress,
|
|
currentUserAddress,
|
|
tokenId,
|
|
);
|
|
} catch (error) {
|
|
log.warn(error);
|
|
return {};
|
|
}
|
|
|
|
if (tokenDetails?.standard) {
|
|
const { standard } = tokenDetails;
|
|
if (standard === ERC721 || standard === ERC1155) {
|
|
const existingCollectible = existingCollectibles.find(({ address }) =>
|
|
isEqualCaseInsensitive(tokenAddress, address),
|
|
);
|
|
|
|
if (existingCollectible) {
|
|
return {
|
|
...existingCollectible,
|
|
standard,
|
|
};
|
|
}
|
|
}
|
|
// else if not a collectible already in state or standard === ERC20 just return tokenDetails as it contains all required data
|
|
return tokenDetails;
|
|
}
|
|
|
|
return {};
|
|
}
|