From 51986a472438f20a2e7d4bff743b230978f41b98 Mon Sep 17 00:00:00 2001 From: Brad Decker Date: Wed, 25 May 2022 15:54:05 -0500 Subject: [PATCH] Add types to send state (#14740) --- shared/constants/gas.js | 13 + shared/constants/transaction.js | 25 +- ui/ducks/send/send.js | 439 ++++++++++++++++++++++++-------- ui/helpers/constants/common.js | 22 ++ 4 files changed, 388 insertions(+), 111 deletions(-) diff --git a/shared/constants/gas.js b/shared/constants/gas.js index ae674e4a2..0092e9ae0 100644 --- a/shared/constants/gas.js +++ b/shared/constants/gas.js @@ -10,9 +10,22 @@ export const GAS_LIMITS = { BASE_TOKEN_ESTIMATE: addHexPrefix(ONE_HUNDRED_THOUSAND.toString(16)), }; +/** + * @typedef {Object} GasEstimateTypes + * @property {'fee-market'} FEE_MARKET - A gas estimate for a fee market + * transaction generated by our gas estimation API. + * @property {'legacy'} LEGACY - A gas estimate for a legacy Transaction + * generated by our gas estimation API. + * @property {'eth_gasPrice'} ETH_GAS_PRICE - A gas estimate provided by the + * Ethereum node via eth_gasPrice. + * @property {'none'} NONE - No gas estimate available. + */ + /** * These are already declared in @metamask/controllers but importing them from * that module and re-exporting causes the UI bundle size to expand beyond 4MB + * + * @type {GasEstimateTypes} */ export const GAS_ESTIMATE_TYPES = { FEE_MARKET: 'fee-market', diff --git a/shared/constants/transaction.js b/shared/constants/transaction.js index 411e14248..9c7c41523 100644 --- a/shared/constants/transaction.js +++ b/shared/constants/transaction.js @@ -323,13 +323,28 @@ export const TRANSACTION_EVENTS = { SUBMITTED: 'Transaction Submitted', }; +/** + * @typedef {Object} AssetTypes + * @property {'NATIVE'} NATIVE - The native asset for the current network, such + * as ETH + * @property {'TOKEN'} TOKEN - An ERC20 token. + * @property {'COLLECTIBLE'} COLLECTIBLE - An ERC721 or ERC1155 token. + * @property {'UNKNOWN'} UNKNOWN - A transaction interacting with a contract + * that isn't a token method interaction will be marked as dealing with an + * unknown asset type. + */ + +/** + * This type will work anywhere you expect a string that can be one of the + * above asset types + * + * @typedef {AssetTypes[keyof AssetTypes]} AssetTypesString + */ + /** * The types of assets that a user can send - * 1. NATIVE - The native asset for the current network, such as ETH - * 2. TOKEN - An ERC20 token. - * 3. COLLECTIBLE - An ERC721 or ERC1155 token. - * 4. UNKNOWN - A transaction interacting with a contract that isn't a token - * method interaction will be marked as dealing with an unknown asset type. + * + * @type {AssetTypes} */ export const ASSET_TYPES = { NATIVE: 'NATIVE', diff --git a/ui/ducks/send/send.js b/ui/ducks/send/send.js index 8cb00f331..af588fcac 100644 --- a/ui/ducks/send/send.js +++ b/ui/ducks/send/send.js @@ -96,13 +96,7 @@ import { sumHexes } from '../../helpers/utils/transactions.util'; import fetchEstimatedL1Fee from '../../helpers/utils/optimism/fetchEstimatedL1Fee'; import { CHAIN_ID_TO_GAS_LIMIT_BUFFER_MAP } from '../../../shared/constants/network'; -import { - ERC20, - ERC721, - ERC1155, - ETH, - GWEI, -} from '../../helpers/constants/common'; +import { TOKEN_STANDARDS, ETH, GWEI } from '../../helpers/constants/common'; import { ASSET_TYPES, TRANSACTION_ENVELOPE_TYPES, @@ -112,24 +106,61 @@ import { readAddressAsContract } from '../../../shared/modules/contract-utils'; import { INVALID_ASSET_TYPE } from '../../helpers/constants/error-keys'; import { isEqualCaseInsensitive } from '../../../shared/modules/string-utils'; import { getValueFromWeiHex } from '../../helpers/utils/confirm-tx.util'; -// typedefs +// typedef import statements /** - * @typedef {import('@reduxjs/toolkit').PayloadAction} PayloadAction + * @typedef {( + * import('immer/dist/internal').WritableDraft + * )} SendStateDraft + * @typedef {( + * import('../../../shared/constants/transaction').AssetTypesString + * )} AssetTypesString + * @typedef {( + * import( '../../helpers/constants/common').TokenStandardStrings + * )} TokenStandardStrings + * @typedef {( + * import('../../../shared/constants/transaction').TransactionTypeString + * )} TransactionTypeString + * @typedef {( + * import('@metamask/controllers').LegacyGasPriceEstimate + * )} LegacyGasPriceEstimate + * @typedef {( + * import('@metamask/controllers').GasFeeEstimates + * )} GasFeeEstimates + * @typedef {( + * import('@metamask/controllers').EthGasPriceEstimate + * )} EthGasPriceEstimate + * @typedef {( + * import('@metamask/controllers').GasEstimateType + * )} GasEstimateType */ const name = 'send'; +/** + * @typedef {Object} SendStateStages + * @property {'INACTIVE'} INACTIVE - The send state is idle, and hasn't yet + * fetched required data for gasPrice and gasLimit estimations, etc. + * @property {'ADD_RECIPIENT'} ADD_RECIPIENT - The user is selecting which + * address to send an asset to. + * @property {'DRAFT'} DRAFT - The send form is shown for a transaction yet to + * be sent to the Transaction Controller. + * @property {'EDIT'} EDIT - The send form is shown for a transaction already + * submitted to the Transaction Controller but not yet confirmed. This happens + * when a confirmation is shown for a transaction and the 'edit' button in the + * header is clicked. + */ + +/** + * This type will work anywhere you expect a string that can be one of the + * above Stages + * + * @typedef {SendStateStages[keyof SendStateStages]} SendStateStagesStrings + */ + /** * The Stages that the send slice can be in - * 1. INACTIVE - The send state is idle, and hasn't yet fetched required - * data for gasPrice and gasLimit estimations, etc. - * 2. ADD_RECIPIENT - The user is selecting which address to send an asset to - * 3. DRAFT - The send form is shown for a transaction yet to be sent to the - * Transaction Controller. - * 4. EDIT - The send form is shown for a transaction already submitted to the - * Transaction Controller but not yet confirmed. This happens when a - * confirmation is shown for a transaction and the 'edit' button in the header - * is clicked. + * + * @type {SendStateStages} */ export const SEND_STAGES = { INACTIVE: 'INACTIVE', @@ -139,33 +170,59 @@ export const SEND_STAGES = { }; /** - * The status that the send slice can be in is either - * 1. VALID - the transaction is valid and can be submitted - * 2. INVALID - the transaction is invalid and cannot be submitted + * @typedef {Object} SendStateStatuses + * @property {'VALID'} VALID - The transaction is valid and can be submitted. + * @property {'INVALID'} INVALID - The transaction is invalid and cannot be + * submitted. There are a number of cases that would result in an invalid + * send state: + * 1. The recipient is not yet defined + * 2. The amount + gasTotal is greater than the user's balance when sending + * native currency + * 3. The gasTotal is greater than the user's *native* balance + * 4. The amount of sent asset is greater than the user's *asset* balance + * 5. Gas price estimates failed to load entirely + * 6. The gasLimit is less than 21000 (0x5208) + */ + +/** + * This type will work anywhere you expect a string that can be one of the + * above statuses * - * A number of cases would result in an invalid form - * 1. The recipient is not yet defined - * 2. The amount + gasTotal is greater than the user's balance when sending - * native currency - * 3. The gasTotal is greater than the user's *native* balance - * 4. The amount of sent asset is greater than the user's *asset* balance - * 5. Gas price estimates failed to load entirely - * 6. The gasLimit is less than 21000 (0x5208) + * @typedef {SendStateStatuses[keyof SendStateStatuses]} SendStateStatusStrings + */ + +/** + * The status of the send slice + * + * @type {SendStateStatuses} */ export const SEND_STATUSES = { VALID: 'VALID', INVALID: 'INVALID', }; +/** + * @typedef {Object} SendStateGasModes + * @property {'BASIC'} BASIC - Shows the basic estimate slow/avg/fast buttons + * when on mainnet and the metaswaps API request is successful. + * @property {'INLINE'} INLINE - Shows inline gasLimit/gasPrice fields when on + * any other network or metaswaps API fails and we use eth_gasPrice. + * @property {'CUSTOM'} CUSTOM - Shows GasFeeDisplay component that is a read + * only display of the values the user has set in the advanced gas modal + * (stored in the gas duck under the customData key). + */ + +/** + * This type will work anywhere you expect a string that can be one of the + * above gas modes + * + * @typedef {SendStateGasModes[keyof SendStateGasModes]} SendStateGasModeStrings + */ + /** * Controls what is displayed in the send-gas-row component. - * 1. BASIC - Shows the basic estimate slow/avg/fast buttons when on mainnet - * and the metaswaps API request is successful. - * 2. INLINE - Shows inline gasLimit/gasPrice fields when on any other network - * or metaswaps API fails and we use eth_gasPrice - * 3. CUSTOM - Shows GasFeeDisplay component that is a read only display of the - * values the user has set in the advanced gas modal (stored in the gas duck - * under the customData key). + * + * @type {SendStateGasModes} */ export const GAS_INPUT_MODES = { BASIC: 'BASIC', @@ -173,17 +230,51 @@ export const GAS_INPUT_MODES = { CUSTOM: 'CUSTOM', }; +/** + * @typedef {Object} SendStateAmountModes + * @property {'INPUT'} INPUT - the user provides the amount by typing in the + * field. + * @property {'MAX'} MAX - The user selects the MAX button and amount is + * calculated based on balance - (amount + gasTotal). + */ + +/** + * This type will work anywhere you expect a string that can be one of the + * above gas modes + * + * @typedef {SendStateAmountModes[keyof SendStateAmountModes]} SendStateAmountModeStrings + */ + /** * The modes that the amount field can be set by - * 1. INPUT - the user provides the amount by typing in the field - * 2. MAX - The user selects the MAX button and amount is calculated based on - * balance - (amount + gasTotal) + * + * @type {SendStateAmountModes} */ export const AMOUNT_MODES = { INPUT: 'INPUT', MAX: 'MAX', }; +/** + * @typedef {Object} SendStateRecipientModes + * @property {'MY_ACCOUNTS'} MY_ACCOUNTS - the user is displayed a list of + * their own accounts to send to. + * @property {'CONTACT_LIST'} CONTACT_LIST - The user is displayed a list of + * their contacts and addresses they have recently send to. + */ + +/** + * This type will work anywhere you expect a string that can be one of the + * above recipient modes + * + * @typedef {SendStateRecipientModes[keyof SendStateRecipientModes]} SendStateRecipientModeStrings + */ + +/** + * The type of recipient list that is displayed to user + * + * @type {SendStateRecipientModes} + */ export const RECIPIENT_SEARCH_MODES = { MY_ACCOUNTS: 'MY_ACCOUNTS', CONTACT_LIST: 'CONTACT_LIST', @@ -429,8 +520,8 @@ export const computeEstimatedGasLimit = createAsyncThunk( * we receive a GWEI estimate from the controller, we still need to do this * weird conversion to get the proper rounding. * - * @param {T} gasPriceEstimate - * @returns + * @param {string} gasPriceEstimate + * @returns {string} */ function getRoundedGasPrice(gasPriceEstimate) { const gasPriceInDecGwei = conversionUtil(gasPriceEstimate, { @@ -538,7 +629,7 @@ export const initializeSendState = createAsyncThunk( }); gasLimit = estimatedGasLimit || gasLimit; } - // We have to keep the gas slice in sync with the draft send transaction + // We have to keep the gas slice in sync with the send slice state // so that it'll be initialized correctly if the gas modal is opened. await thunkApi.dispatch(setCustomGasLimit(gasLimit)); // We must determine the balance of the asset that the transaction will be @@ -585,96 +676,166 @@ export const initializeSendState = createAsyncThunk( }, ); +/** + * @typedef {Object} SendState + * @property {string} [id] - The id of a transaction that is being edited + * @property {SendStateStagesStrings} stage - The stage of the send flow that + * the user has progressed to. Defaults to 'INACTIVE' which results in the + * send screen not being shown. + * @property {SendStateStatusStrings} status - The status of the send slice + * which will be either 'VALID' or 'INVALID' + * @property {string} transactionType - Determines type of transaction being + * sent, defaulted to 0x0 (legacy). + * @property {boolean} eip1559support - tracks whether the current network + * supports EIP 1559 transactions. + * @property {Object} account - Details about the user's account. + * @property {string} [account.address] - from account address, defaults to + * selected account. will be the account the original transaction was sent + * from in the case of the EDIT stage. + * @property {string} [account.balance] - Hex string representing the balance + * of the from account. + * @property {string} [userInputHexData] - When a user has enabled custom hex + * data field in advanced options, they can supply data to the field which is + * stored under this key. + * @property {Object} gas - Details about the current gas settings + * @property {boolean} gas.isGasEstimateLoading - Indicates whether the gas + * estimate is loading. + * @property {string} [gas.gasEstimatePollToken] - String token identifying a + * listener for polling on the gasFeeController + * @property {boolean} gas.isCustomGasSet - true if the user set custom gas in + * the custom gas modal + * @property {string} gas.gasLimit - maximum gas needed for tx. + * @property {string} gas.gasPrice - price in wei to pay per gas. + * @property {string} gas.maxFeePerGas - Maximum price in wei to pay per gas. + * @property {string} gas.maxPriorityFeePerGas - Maximum priority fee in wei to + * pay per gas. + * @property {string} gas.gasPriceEstimate - Expected price in wei necessary to + * pay per gas used for a transaction to be included in a reasonable timeframe. + * Comes from the GasFeeController. + * @property {string} gas.gasTotal - maximum total price in wei to pay. + * @property {string} gas.minimumGasLimit - minimum supported gasLimit. + * @property {string} [gas.error] - error to display for gas fields. + * @property {Object} amount - An object containing information about the + * amount of currency to send. + * @property {SendStateAmountModeStrings} amount.mode - Describe whether the + * user has manually input an amount or if they have selected max to send the + * maximum amount of the selected currency. + * @property {string} amount.value - A hex string representing the amount of + * the selected currency to send. + * @property {string} [amount.error] - Error to display for the amount field. + * @property {Object} asset - An object that describes the asset that the user + * has selected to send. + * @property {AssetTypesString} asset.type - The type of asset that the user + * is attempting to send. Defaults to 'NATIVE' which represents the native + * asset of the chain. Can also be 'TOKEN' or 'COLLECTIBLE'. + * @property {string} asset.balance - A hex string representing the balance + * that the user holds of the asset that they are attempting to send. + * @property {Object} [asset.details] - An object that describes the selected + * asset in the case that the user is sending a token or collectibe. Will be + * null when asset.type is 'NATIVE'. + * @property {string} [asset.details.address] - The address of the selected + * 'TOKEN' or 'COLLECTIBLE' contract. + * @property {string} [asset.details.symbol] - The symbol of the selected + * asset. + * @property {number} [asset.details.decimals] - The number of decimals of the + * selected 'TOKEN' asset. + * @property {number} [asset.details.tokenId] - The id of the selected + * 'COLLECTIBLE' asset. + * @property {TokenStandardStrings} [asset.details.standard] - The standard + * of the selected 'TOKEN' or 'COLLECTIBLE' asset. + * @property {boolean} [asset.details.isERC721] - True when the asset is a + * ERC721 token. + * @property {string} [asset.error] - Error to display when there is an issue + * with the asset. + * @property {Object} recipient - An object that describes the intended + * recipient of the transaction. + * @property {SendStateRecipientModeStrings} recipient.mode - Describes which + * list of recipients the user is shown on the add recipient screen. When this + * key is set to 'MY_ACCOUNTS' the user is shown the list of accounts they + * own. When it is 'CONTACT_LIST' the user is shown the list of contacts they + * have saved in MetaMask and any addresses they have recently sent to. + * @property {string} recipient.address - The fully qualified address of the + * recipient. This is set after the recipient.userInput is validated, the + * userInput field is quickly updated to avoid delay between keystrokes and + * seeing the input field updated. After a debounc the address typed is + * validated and then the address field is updated. The address field is also + * set when the user selects a contact or account from the list, or an ENS + * resolution when typing ENS names. + * @property {string} recipient.userInput - The user input of the recipient + * which is updated quickly to avoid delays in the UI reflecting manual entry + * of addresses. + * @property {string} recipient.nickname - The nickname that the user has added + * to their address book for the recipient.address. + * @property {string} [recipient.error] - Error to display on the address field. + * @property {string} [recipient.warning] - Warning to display on the address + * field. + * @property {Object} multiLayerFees - An object containing attributes for use + * on chains that have layer 1 and layer 2 fees to consider for gas + * calculations. + * @property {string} multiLayerFees.layer1GasTotal - Layer 1 gas fee total on + * multi-layer fee networks + * @property {Array<{event: string, timestamp: number}>} history - An array of + * entries that describe the user's journey through the send flow. This is + * sent to the controller for attaching to state logs for troubleshooting and + * support. + */ + +/** + * @type {SendState} + */ export const initialState = { id: null, - // which stage of the send flow is the user on stage: SEND_STAGES.INACTIVE, - // status of the send slice, either VALID or INVALID status: SEND_STATUSES.VALID, - // Determines type of transaction being sent, defaulted to 0x0 (legacy) transactionType: TRANSACTION_ENVELOPE_TYPES.LEGACY, - // tracks whether the current network supports EIP 1559 transactions eip1559support: false, account: { - // from account address, defaults to selected account. will be the account - // the original transaction was sent from in the case of the EDIT stage address: null, - // balance of the from account balance: '0x0', }, userInputHexData: null, gas: { - // indicate whether the gas estimate is loading isGasEstimateLoading: true, - // String token identifying a listener for polling on the gasFeeController gasEstimatePollToken: null, - // has the user set custom gas in the custom gas modal isCustomGasSet: false, - // maximum gas needed for tx gasLimit: '0x0', - // price in wei to pay per gas gasPrice: '0x0', - // maximum price in wei to pay per gas maxFeePerGas: '0x0', - // maximum priority fee in wei to pay per gas maxPriorityFeePerGas: '0x0', - // expected price in wei necessary to pay per gas used for a transaction - // to be included in a reasonable timeframe. Comes from GasFeeController. gasPriceEstimate: '0x0', - // maximum total price in wei to pay gasTotal: '0x0', - // minimum supported gasLimit minimumGasLimit: GAS_LIMITS.SIMPLE, - // error to display for gas fields error: null, }, amount: { - // The mode to use when determining new amounts. For INPUT mode the - // provided payload is always used. For MAX it is calculated based on avail - // asset balance mode: AMOUNT_MODES.INPUT, - // Current value of the transaction, how much of the asset are we sending value: '0x0', - // error to display for amount field error: null, }, asset: { - // type can be either NATIVE such as ETH or TOKEN for ERC20 tokens type: ASSET_TYPES.NATIVE, - // the balance the user holds at the from address for this asset balance: '0x0', - // In the case of tokens, the address, decimals and symbol of the token - // will be included in details details: null, - // error to display when there is an issue with the asset error: null, }, recipient: { - // Defines which mode to use for searching for matches in the input field mode: RECIPIENT_SEARCH_MODES.CONTACT_LIST, - // Partial, not yet validated, entry into the address field. Used to share - // user input amongst the AddRecipient and EnsInput components. userInput: '', - // The address of the recipient address: '', - // The nickname stored in the user's address book for the recipient address nickname: '', - // Error to display on the address field error: null, - // Warning to display on the address field warning: null, }, multiLayerFees: { - // Layer 1 gas fee total on multi-layer fee networks layer1GasTotal: '0x0', }, history: [], }; /** - * Generates a txParams from the send state + * Generates a txParams from the send slice. * - * @param {Object} state - the Send slice state + * @param {SendState} state - the Send slice state * @returns {import( * '../../../shared/constants/transaction' * ).TxParams} A txParams object that can be used to create a transaction or @@ -765,8 +926,10 @@ const slice = createSlice({ * update current amount.value in state and run post update validation of * the amount field and the send state. * - * @param state - * @param action + * @param {SendStateDraft} state - A writable draft of the send state to be + * updated. + * @param {import('@reduxjs/toolkit').PayloadAction} action - The + * hex string to be set as the amount value. */ updateSendAmount: (state, action) => { state.amount.value = addHexPrefix(action.payload); @@ -787,7 +950,8 @@ const slice = createSlice({ * the updateSendAmount action above with the computed value, which will * revalidate the field and form. * - * @param state + * @param {SendStateDraft} state - A writable draft of the send state to be + * updated. */ updateAmountToMax: (state) => { let amount = '0x0'; @@ -822,19 +986,45 @@ const slice = createSlice({ /** * updates the userInputHexData state key * - * @param state - * @param action + * @param {SendStateDraft} state - A writable draft of the send state to be + * updated. + * @param {import('@reduxjs/toolkit').PayloadAction} action - The + * hex string to be set as the userInputHexData value. */ updateUserInputHexData: (state, action) => { state.userInputHexData = action.payload; }, + /** + * Transaction details of a previously created transaction that the user + * has selected to edit. + * + * @typedef {Object} EditTransactionPayload + * @property {string} gasLimit - The hex string maximum gas to use. + * @property {string} gasPrice - The amount in wei to pay for gas, in hex + * format. + * @property {string} amount - The amount of the currency to send, in hex + * format. + * @property {string} address - The address to send the transaction to. + * @property {string} [nickname] - The nickname the user has associated + * with the address in their contact book. + * @property {string} id - The id of the transaction in the + * TransactionController state[ + * @property {string} from - the address that the user is sending from + * @property {string} [data] - The hex data that describes the transaction. + * Used primarily for contract interactions, like token sends, but can + * also be provided by the user. + */ /** * Initiates the edit transaction flow by setting the stage to 'EDIT' and * then pulling the details of the previously submitted transaction from * the action payload. * - * @param state - * @param action + * @param {SendStateDraft} state - A writable draft of the send state to be + * updated. + * @param {import( + * '@reduxjs/toolkit' + * ).PayloadAction} action - The details of the + * transaction to be edited. */ editTransaction: (state, action) => { state.stage = SEND_STAGES.EDIT; @@ -855,9 +1045,10 @@ const slice = createSlice({ * recomputes the maximum amount if the current amount mode is 'MAX' and * sending the native token. ERC20 assets max amount is unaffected by * gasTotal so does not need to be recomputed. Finally, validates the gas - * field and send state, then updates the draft transaction. + * field and send state. * - * @param state + * @param {SendStateDraft} state - A writable draft of the send state to be + * updated. */ calculateGasTotal: (state) => { // use maxFeePerGas as the multiplier if working with a FEE_MARKET transaction @@ -885,19 +1076,37 @@ const slice = createSlice({ /** * sets the provided gasLimit in state and then recomputes the gasTotal. * - * @param state - * @param action + * @param {SendStateDraft} state - A writable draft of the send state to be + * updated. + * @param {import('@reduxjs/toolkit').PayloadAction} action - The + * gasLimit in hex to set in state. */ updateGasLimit: (state, action) => { state.gas.gasLimit = addHexPrefix(action.payload); slice.caseReducers.calculateGasTotal(state); }, + /** + * @typedef {Object} GasFeeUpdatePayload + * @property {TransactionTypeString} transactionType - The transaction type + * @property {string} [maxFeePerGas] - The maximum amount in hex wei to pay + * per gas on a FEE_MARKET transaction. + * @property {string} [maxPriorityFeePerGas] - The maximum amount in hex + * wei to pay per gas as an incentive to miners on a FEE_MARKET + * transaction. + * @property {string} [gasPrice] - The amount in hex wei to pay per gas on + * a LEGACY transaction. + * @property {boolean} [isAutomaticUpdate] - true if the update is the + * result of a gas estimate update from the controller. + */ /** * Sets the appropriate gas fees in state and determines and sets the * appropriate transactionType based on gas fee fields received. * - * @param state - * @param action + * @param {SendStateDraft} state - A writable draft of the send state to be + * updated. + * @param {import( + * '@reduxjs/toolkit' + * ).PayloadAction} action */ updateGasFees: (state, action) => { if ( @@ -929,11 +1138,22 @@ const slice = createSlice({ } slice.caseReducers.calculateGasTotal(state); }, + /** + * @typedef {Object} GasEstimateUpdatePayload + * @property {GasEstimateType} gasEstimateType - The type of gas estimation + * provided by the controller. + * @property {( + * EthGasPriceEstimate | LegacyGasPriceEstimate | GasFeeEstimates + * )} gasFeeEstimates - The gas fee estimates provided by the controller. + */ /** * Sets the appropriate gas fees in state after receiving new estimates. * - * @param state - * @param action + * @param {SendStateDraft} state - A writable draft of the send state to be + * updated. + * @param {( + * import('@reduxjs/toolkit').PayloadAction { const { gasFeeEstimates, gasEstimateType } = action.payload; @@ -982,8 +1202,10 @@ const slice = createSlice({ /** * sets the layer 1 fees total (for a multi-layer fee network) * - * @param state - * @param action + * @param {SendStateDraft} state - A writable draft of the send state to be + * updated. + * @param {import('@reduxjs/toolkit').PayloadAction} action - the + * layer1GasTotal to set in hex wei. */ updateLayer1Fees: (state, action) => { state.multiLayerFees.layer1GasTotal = action.payload; @@ -998,8 +1220,12 @@ const slice = createSlice({ * sets the amount mode to the provided value as long as it is one of the * supported modes (MAX|INPUT) * - * @param state - * @param action + * @param {SendStateDraft} state - A writable draft of the send state to be + * updated. + * @param {import( + * '@reduxjs/toolkit' + * ).PayloadAction} action - The amount mode + * to set the state to. */ updateAmountMode: (state, action) => { if (Object.values(AMOUNT_MODES).includes(action.payload)) { @@ -1051,9 +1277,9 @@ const slice = createSlice({ // to the ADD_RECIPIENT stage. state.stage = SEND_STAGES.ADD_RECIPIENT; } else { - // if an address is provided and an id exists on the draft transaction, - // we progress to the EDIT stage, otherwise we progress to the DRAFT - // stage. We also reset the search mode for recipient search. + // if an address is provided and an id exists, we progress to the EDIT + // stage, otherwise we progress to the DRAFT stage. We also reset the + // search mode for recipient search. state.stage = state.id === null ? SEND_STAGES.DRAFT : SEND_STAGES.EDIT; state.recipient.mode = RECIPIENT_SEARCH_MODES.CONTACT_LIST; } @@ -1508,7 +1734,8 @@ export function updateSendAsset({ type, details }) { ); if ( process.env.COLLECTIBLES_V1 && - (standard === ERC721 || standard === ERC1155) + (standard === TOKEN_STANDARDS.ERC721 || + standard === TOKEN_STANDARDS.ERC1155) ) { await dispatch(hideLoadingIndication()); dispatch( @@ -1530,7 +1757,7 @@ export function updateSendAsset({ type, details }) { // the currently active account. In addition its possible for the balance // check to take a decent amount of time, so we display a loading // indication so that that immediate feedback is displayed to the user. - if (details.standard === ERC20) { + if (details.standard === TOKEN_STANDARDS.ERC20) { error = null; balance = await getERC20Balance(details, userAddress); } @@ -1562,7 +1789,7 @@ export function updateSendAsset({ type, details }) { details.standard = standard; } - if (details.standard === ERC1155) { + if (details.standard === TOKEN_STANDARDS.ERC1155) { throw new Error('Sends of ERC1155 tokens are not currently supported'); } diff --git a/ui/helpers/constants/common.js b/ui/helpers/constants/common.js index 3deae2356..2663c2108 100644 --- a/ui/helpers/constants/common.js +++ b/ui/helpers/constants/common.js @@ -9,6 +9,28 @@ export const ERC20 = 'ERC20'; export const ERC721 = 'ERC721'; export const ERC1155 = 'ERC1155'; +/** + * @typedef {Object} TokenStandards + * @property {'ERC20'} ERC20 - A token that conforms to the ERC20 standard. + * @property {'ERC721'} ERC721 - A token that conforms to the ERC721 standard. + * @property {'ERC1155'} ERC1155 - A token that conforms to the ERC1155 + * standard. + * @property {'NONE'} NONE - Not a token, but rather the base asset of the + * selected chain. + */ + +/** + * This type will work anywhere you expect a string that can be one of the + * above statuses + * + * @typedef {TokenStandards[keyof TokenStandards]} TokenStandardStrings + */ + +/** + * Describes the standard which a token conforms to. + * + * @type {TokenStandards} + */ export const TOKEN_STANDARDS = { ERC20, ERC721,