@ -18,6 +18,7 @@ import {
INVALID _RECIPIENT _ADDRESS _NOT _ETH _NETWORK _ERROR ,
KNOWN _RECIPIENT _ADDRESS _WARNING ,
NEGATIVE _ETH _ERROR ,
RECIPIENT _TYPES ,
} from '../../pages/send/send.constants' ;
import {
@ -82,6 +83,10 @@ import {
getTokens ,
getUnapprovedTxs ,
} from '../metamask/metamask' ;
import {
isSmartContractAddress ,
sumHexes ,
} from '../../helpers/utils/transactions.util' ;
import { resetEnsResolution } from '../ens' ;
import {
@ -89,7 +94,7 @@ import {
isValidHexAddress ,
toChecksumHexAddress ,
} from '../../../shared/modules/hexstring-utils' ;
import { sumHexes } from '../../helpers/utils/transactions.util' ;
import fetchEstimatedL1Fee from '../../helpers/utils/optimism/fetchEstimatedL1Fee' ;
import { TOKEN _STANDARDS , ETH } from '../../helpers/constants/common' ;
@ -383,6 +388,7 @@ export const draftTransactionInitialState = {
error : null ,
nickname : '' ,
warning : null ,
type : '' ,
recipientWarningAcknowledged : false ,
} ,
status : SEND _STATUSES . VALID ,
@ -513,7 +519,7 @@ export const computeEstimatedGasLimit = createAsyncThunk(
value :
send . amountMode === AMOUNT _MODES . MAX
? send . selectedAccount . balance
: send . amount . value ,
: draftTransaction . amount . value ,
from : send . selectedAccount . address ,
data : draftTransaction . userInputHexData ,
type : '0x0' ,
@ -598,7 +604,7 @@ export const initializeSendState = createAsyncThunk(
// For instance, in the actions.js file we dispatch this action anytime the
// chain changes.
if ( ! draftTransaction ) {
thunkApi . rejectWithValue (
return thunkApi . rejectWithValue (
'draftTransaction not found, possibly not on send flow' ,
) ;
}
@ -673,6 +679,20 @@ export const initializeSendState = createAsyncThunk(
// 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 ) ) ;
// There may be a case where the send has been canceled by the user while
// the gas estimate is being computed. So we check again to make sure that
// a currentTransactionUUID exists and matches the previous tx.
const newState = thunkApi . getState ( ) ;
if (
newState . send . currentTransactionUUID !== sendState . currentTransactionUUID
) {
return thunkApi . rejectWithValue (
` draftTransaction changed during initialization.
A new initializeSendState action must be dispatched . ` ,
) ;
}
return {
account ,
chainId : getCurrentChainId ( state ) ,
@ -1158,6 +1178,12 @@ const slice = createSlice({
draftTransaction . recipient . warning = action . payload ;
} ,
updateRecipientType : ( state , action ) => {
const draftTransaction =
state . draftTransactions [ state . currentTransactionUUID ] ;
draftTransaction . recipient . type = action . payload ;
} ,
updateDraftTransactionStatus : ( state , action ) => {
const draftTransaction =
state . draftTransactions [ state . currentTransactionUUID ] ;
@ -1398,56 +1424,60 @@ const slice = createSlice({
validateSendState : ( state ) => {
const draftTransaction =
state . draftTransactions [ state . currentTransactionUUID ] ;
switch ( true ) {
case Boolean ( draftTransaction . amount . error ) :
case Boolean ( draftTransaction . gas . error ) :
case Boolean ( draftTransaction . asset . error ) :
case draftTransaction . asset . type === ASSET _TYPES . TOKEN &&
draftTransaction . asset . details === null :
case state . stage === SEND _STAGES . ADD _RECIPIENT :
case state . stage === SEND _STAGES . INACTIVE :
case state . gasEstimateIsLoading :
case new BigNumber ( draftTransaction . gas . gasLimit , 16 ) . lessThan (
new BigNumber ( state . gasLimitMinimum ) ,
) :
draftTransaction . status = SEND _STATUSES . INVALID ;
break ;
case draftTransaction . recipient . warning === 'loading' :
case draftTransaction . recipient . warning ===
KNOWN _RECIPIENT _ADDRESS _WARNING &&
draftTransaction . recipient . recipientWarningAcknowledged === false :
draftTransaction . status = SEND _STATUSES . INVALID ;
break ;
default :
draftTransaction . status = SEND _STATUSES . VALID ;
if ( draftTransaction ) {
switch ( true ) {
case Boolean ( draftTransaction . amount . error ) :
case Boolean ( draftTransaction . gas . error ) :
case Boolean ( draftTransaction . asset . error ) :
case draftTransaction . asset . type === ASSET _TYPES . TOKEN &&
draftTransaction . asset . details === null :
case state . stage === SEND _STAGES . ADD _RECIPIENT :
case state . stage === SEND _STAGES . INACTIVE :
case state . gasEstimateIsLoading :
case new BigNumber ( draftTransaction . gas . gasLimit , 16 ) . lessThan (
new BigNumber ( state . gasLimitMinimum ) ,
) :
draftTransaction . status = SEND _STATUSES . INVALID ;
break ;
case draftTransaction . recipient . warning === 'loading' :
case draftTransaction . recipient . warning ===
KNOWN _RECIPIENT _ADDRESS _WARNING &&
draftTransaction . recipient . recipientWarningAcknowledged === false :
draftTransaction . status = SEND _STATUSES . INVALID ;
break ;
default :
draftTransaction . status = SEND _STATUSES . VALID ;
}
}
} ,
} ,
extraReducers : ( builder ) => {
builder
. addCase ( ACCOUNT _CHANGED , ( state , action ) => {
// If we are on the edit flow then we need to watch for changes to the
// current account.address in state and keep balance updated
// appropriately
if (
state . stage === SEND _STAGES . EDIT &&
action . payload . account . address === state . selectedAccount . address
) {
// This event occurs when the user's account details update due to
// background state changes. If the account that is being updated is
// the current from account on the edit flow we need to update
// the balance for the account and revalidate the send state.
state . selectedAccount . balance = action . payload . account . balance ;
// We need to update the asset balance if the asset is the native
// network asset. Once we update the balance we recompute error state.
// This event occurs when the user's account details update due to
// background state changes. If the account that is being updated is
// the current from account on the edit flow we need to update
// the balance for the account and revalidate the send state.
if ( state . stage === SEND _STAGES . EDIT && action . payload . account ) {
const draftTransaction =
state . draftTransactions [ state . currentTransactionUUID ] ;
if ( draftTransaction ? . asset . type === ASSET _TYPES . NATIVE ) {
draftTransaction . asset . balance = action . payload . account . balance ;
if (
draftTransaction &&
draftTransaction . fromAccount &&
draftTransaction . fromAccount . address ===
action . payload . account . address
) {
draftTransaction . fromAccount . balance =
action . payload . account . balance ;
// We need to update the asset balance if the asset is the native
// network asset. Once we update the balance we recompute error state.
if ( draftTransaction . asset . type === ASSET _TYPES . NATIVE ) {
draftTransaction . asset . balance = action . payload . account . balance ;
}
slice . caseReducers . validateAmountField ( state ) ;
slice . caseReducers . validateGasField ( state ) ;
slice . caseReducers . validateSendState ( state ) ;
}
slice . caseReducers . validateAmountField ( state ) ;
slice . caseReducers . validateGasField ( state ) ;
slice . caseReducers . validateSendState ( state ) ;
}
} )
. addCase ( ADDRESS _BOOK _UPDATED , ( state , action ) => {
@ -1514,26 +1544,28 @@ const slice = createSlice({
state . selectedAccount . balance = action . payload . account . balance ;
const draftTransaction =
state . draftTransactions [ state . currentTransactionUUID ] ;
draftTransaction . gas . gasLimit = action . payload . gasLimit ;
if ( draftTransaction ) {
draftTransaction . gas . gasLimit = action . payload . gasLimit ;
draftTransaction . gas . gasTotal = action . payload . gasTotal ;
if ( action . payload . chainHasChanged ) {
// If the state was reinitialized as a result of the user changing
// the network from the network dropdown, then the selected asset is
// no longer valid and should be set to the native asset for the
// network.
draftTransaction . asset . type = ASSET _TYPES . NATIVE ;
draftTransaction . asset . balance =
draftTransaction . fromAccount ? . balance ? ?
state . selectedAccount . balance ;
draftTransaction . asset . details = null ;
}
}
slice . caseReducers . updateGasFeeEstimates ( state , {
payload : {
gasFeeEstimates : action . payload . gasFeeEstimates ,
gasEstimateType : action . payload . gasEstimateType ,
} ,
} ) ;
draftTransaction . gas . gasTotal = action . payload . gasTotal ;
state . gasEstimatePollToken = action . payload . gasEstimatePollToken ;
if ( action . payload . chainHasChanged ) {
// If the state was reinitialized as a result of the user changing
// the network from the network dropdown, then the selected asset is
// no longer valid and should be set to the native asset for the
// network.
draftTransaction . asset . type = ASSET _TYPES . NATIVE ;
draftTransaction . asset . balance =
draftTransaction . fromAccount ? . balance ? ?
state . selectedAccount . balance ;
draftTransaction . asset . details = null ;
}
if ( action . payload . gasEstimatePollToken ) {
state . gasEstimateIsLoading = false ;
}
@ -1547,16 +1579,19 @@ const slice = createSlice({
} ,
} ) ;
}
if ( state . amountMode === AMOUNT _MODES . MAX ) {
slice . caseReducers . updateAmountToMax ( state ) ;
}
slice . caseReducers . validateAmountField ( state ) ;
slice . caseReducers . validateGasField ( state ) ;
slice . caseReducers . validateSendState ( state ) ;
} )
. addCase ( SELECTED _ACCOUNT _CHANGED , ( state , action ) => {
// If we are on the edit flow the account we are keyed into will be the
// original 'from' account, which may differ from the selected account
if ( state . stage !== SEND _STAGES . EDIT ) {
// This event occurs when the user selects a new account from the
// account menu, or the currently active account's balance updates.
// This event occurs when the user selects a new account from the
// account menu, or the currently active account's balance updates.
// We only care about new transactions, not edits, here, because we use
// the fromAccount and ACCOUNT_CHANGED action for that.
if ( state . stage !== SEND _STAGES . EDIT && action . payload . account ) {
state . selectedAccount . balance = action . payload . account . balance ;
state . selectedAccount . address = action . payload . account . address ;
const draftTransaction =
@ -1855,19 +1890,24 @@ export function updateRecipientUserInput(userInput) {
const inputIsValidHexAddress = isValidHexAddress ( userInput ) ;
let isProbablyAnAssetContract = false ;
if ( inputIsValidHexAddress ) {
const { symbol , decimals } = getTokenMetadata ( userInput , tokenMap ) || { } ;
const smartContractAddress = await isSmartContractAddress ( userInput ) ;
if ( smartContractAddress ) {
dispatch ( actions . updateRecipientType ( RECIPIENT _TYPES . SMART _CONTRACT ) ) ;
const { symbol , decimals } =
getTokenMetadata ( userInput , tokenMap ) || { } ;
isProbablyAnAssetContract = symbol && decimals !== undefined ;
isProbablyAnAssetContract = symbol && decimals !== undefined ;
if ( ! isProbablyAnAssetContract ) {
try {
const { standard } = await getTokenStandardAndDetails (
userInput ,
sendingAddress ,
) ;
isProbablyAnAssetContract = Boolean ( standard ) ;
} catch ( e ) {
console . log ( e ) ;
if ( ! isProbablyAnAssetContract ) {
try {
const { standard } = await getTokenStandardAndDetails (
userInput ,
sendingAddress ,
) ;
isProbablyAnAssetContract = Boolean ( standard ) ;
} catch ( e ) {
console . log ( e ) ;
}
}
}
}
@ -2238,7 +2278,10 @@ export function signTransaction() {
updateTransactionGasFees ( draftTransaction . id , editingTx . txParams ) ,
) ;
} else {
let transactionType = TRANSACTION _TYPES . SIMPLE _SEND ;
let transactionType =
draftTransaction . recipient . type === RECIPIENT _TYPES . SMART _CONTRACT
? TRANSACTION _TYPES . CONTRACT _INTERACTION
: TRANSACTION _TYPES . SIMPLE _SEND ;
if ( draftTransaction . asset . type !== ASSET _TYPES . NATIVE ) {
transactionType =
@ -2630,7 +2673,11 @@ export function isSendStateInitialized(state) {
* @ type { Selector < boolean > }
* /
export function isSendFormInvalid ( state ) {
return getCurrentDraftTransaction ( state ) . status === SEND _STATUSES . INVALID ;
const draftTransaction = getCurrentDraftTransaction ( state ) ;
if ( ! draftTransaction ) {
return true ;
}
return draftTransaction . status === SEND _STATUSES . INVALID ;
}
/ * *