diff --git a/src/logic/safe/store/actions/createTransaction.ts b/src/logic/safe/store/actions/createTransaction.ts index 4ce9c211..b2e6b347 100644 --- a/src/logic/safe/store/actions/createTransaction.ts +++ b/src/logic/safe/store/actions/createTransaction.ts @@ -12,6 +12,7 @@ import { tryOffchainSigning, } from 'src/logic/safe/transactions' import { estimateGasForTransactionCreation } from 'src/logic/safe/transactions/gas' +import * as aboutToExecuteTx from 'src/logic/safe/utils/aboutToExecuteTx' import { getCurrentSafeVersion } from 'src/logic/safe/utils/safeVersion' import { ZERO_ADDRESS } from 'src/logic/wallets/ethAddresses' import { EMPTY_DATA } from 'src/logic/wallets/ethTransactions' @@ -148,6 +149,9 @@ export const createTransaction = ( await saveTxToHistory({ ...txArgs, txHash, origin }) + // store the pending transaction's nonce + isExecution && aboutToExecuteTx.setNonce(txArgs.nonce) + dispatch(fetchTransactions(safeAddress)) }) .on('error', (error) => { @@ -156,10 +160,6 @@ export const createTransaction = ( onError?.() }) .then(async (receipt) => { - if (isExecution) { - dispatch(enqueueSnackbar(notificationsQueue.afterExecution.noMoreConfirmationsNeeded)) - } - dispatch(fetchTransactions(safeAddress)) return receipt.transactionHash diff --git a/src/logic/safe/store/actions/processTransaction.ts b/src/logic/safe/store/actions/processTransaction.ts index f54d2c12..28934b23 100644 --- a/src/logic/safe/store/actions/processTransaction.ts +++ b/src/logic/safe/store/actions/processTransaction.ts @@ -11,6 +11,7 @@ import { } from 'src/logic/safe/safeTxSigner' import { getApprovalTransaction, getExecutionTransaction, saveTxToHistory } from 'src/logic/safe/transactions' import { tryOffchainSigning } from 'src/logic/safe/transactions/offchainSigner' +import * as aboutToExecuteTx from 'src/logic/safe/utils/aboutToExecuteTx' import { getCurrentSafeVersion } from 'src/logic/safe/utils/safeVersion' import { EMPTY_DATA } from 'src/logic/wallets/ethTransactions' import { providerSelector } from 'src/logic/wallets/store/selectors' @@ -117,8 +118,6 @@ export const processTransaction = ({ dispatch(updateTransactionStatus({ txStatus: 'PENDING', safeAddress, nonce: tx.nonce, id: tx.id })) await saveTxToHistory({ ...txArgs, signature }) - // TODO: while we wait for the tx to be stored in the service and later update the tx info - // we should update the tx status in the store to disable owners' action buttons dispatch(fetchTransactions(safeAddress)) return @@ -154,6 +153,10 @@ export const processTransaction = ({ try { await saveTxToHistory({ ...txArgs, txHash }) + + // store the pending transaction's nonce + isExecution && aboutToExecuteTx.setNonce(txArgs.nonce) + dispatch(fetchTransactions(safeAddress)) } catch (e) { console.error(e) @@ -172,10 +175,6 @@ export const processTransaction = ({ console.error('Processing transaction error: ', error) }) .then(async (receipt) => { - if (isExecution) { - dispatch(enqueueSnackbar(notificationsQueue.afterExecution.noMoreConfirmationsNeeded)) - } - dispatch(fetchTransactions(safeAddress)) if (isExecution) { diff --git a/src/logic/safe/store/middleware/notificationsMiddleware.ts b/src/logic/safe/store/middleware/notificationsMiddleware.ts index f516a141..1ad8cb52 100644 --- a/src/logic/safe/store/middleware/notificationsMiddleware.ts +++ b/src/logic/safe/store/middleware/notificationsMiddleware.ts @@ -1,4 +1,5 @@ import { push } from 'connected-react-router' +import { Action } from 'redux-actions' import { NOTIFICATIONS, enhanceSnackbarForAction } from 'src/logic/notifications' import closeSnackbarAction from 'src/logic/notifications/store/actions/closeSnackbar' @@ -8,14 +9,19 @@ import { getSafeVersionInfo } from 'src/logic/safe/utils/safeVersion' import { isUserAnOwner } from 'src/logic/wallets/ethAddresses' import { userAccountSelector } from 'src/logic/wallets/store/selectors' import { grantedSelector } from 'src/routes/safe/container/selector' -import { ADD_QUEUED_TRANSACTIONS } from 'src/logic/safe/store/actions/transactions/gatewayTransactions' +import { + ADD_QUEUED_TRANSACTIONS, + ADD_HISTORY_TRANSACTIONS, +} from 'src/logic/safe/store/actions/transactions/gatewayTransactions' +import * as aboutToExecuteTx from 'src/logic/safe/utils/aboutToExecuteTx' +import { QueuedPayload } from 'src/logic/safe/store/reducer/gatewayTransactions' import { safeParamAddressFromStateSelector, safesMapSelector } from 'src/logic/safe/store/selectors' -import { isTransactionSummary } from 'src/logic/safe/store/models/types/gateway.d' +import { isTransactionSummary, TransactionGatewayResult } from 'src/logic/safe/store/models/types/gateway.d' import { loadFromStorage, saveToStorage } from 'src/utils/storage' import { ADD_OR_UPDATE_SAFE } from '../actions/addOrUpdateSafe' -const watchedActions = [ADD_OR_UPDATE_SAFE, ADD_QUEUED_TRANSACTIONS] +const watchedActions = [ADD_OR_UPDATE_SAFE, ADD_QUEUED_TRANSACTIONS, ADD_HISTORY_TRANSACTIONS] const LAST_TIME_USED_LOGGED_IN_ID = 'LAST_TIME_USED_LOGGED_IN_ID' @@ -70,9 +76,21 @@ const notificationsMiddleware = (store) => (next) => async (action) => { const state = store.getState() switch (action.type) { + case ADD_HISTORY_TRANSACTIONS: { + const userAddress: string = userAccountSelector(state) + const safes = safesMapSelector(state) + + const executedTxNotification = aboutToExecuteTx.getNotification(action.payload, userAddress, safes) + // if we have a notification, dispatch it depending on transaction's status + executedTxNotification && dispatch(enqueueSnackbar(executedTxNotification)) + + break + } case ADD_QUEUED_TRANSACTIONS: { - const { safeAddress, values } = action.payload - const transactions = values.filter((tx) => isTransactionSummary(tx)).map((item) => item.transaction) + const { safeAddress, values } = (action as Action).payload + const transactions = values + .filter((tx) => isTransactionSummary(tx)) + .map((item: TransactionGatewayResult) => item.transaction) const userAddress: string = userAccountSelector(state) const awaitingTransactions = getAwaitingGatewayTransactions(transactions, userAddress) diff --git a/src/logic/safe/store/reducer/gatewayTransactions.ts b/src/logic/safe/store/reducer/gatewayTransactions.ts index a3eada88..fbd7e314 100644 --- a/src/logic/safe/store/reducer/gatewayTransactions.ts +++ b/src/logic/safe/store/reducer/gatewayTransactions.ts @@ -344,6 +344,7 @@ export const gatewayTransactions = handleActions { + // TODO: review if is this `PENDING` status required under `queued.queued` list // prevent setting `PENDING_FAILED` status, if previous status wasn't `PENDING` if (txStatus === 'PENDING_FAILED' && txToUpdate.txStatus !== 'PENDING') { return txToUpdate diff --git a/src/logic/safe/utils/aboutToExecuteTx.ts b/src/logic/safe/utils/aboutToExecuteTx.ts new file mode 100644 index 00000000..7db235e3 --- /dev/null +++ b/src/logic/safe/utils/aboutToExecuteTx.ts @@ -0,0 +1,51 @@ +import { getNotificationsFromTxType } from 'src/logic/notifications' +import { + isStatusFailed, + isTransactionSummary, + TransactionGatewayResult, +} from 'src/logic/safe/store/models/types/gateway.d' +import { HistoryPayload } from 'src/logic/safe/store/reducer/gatewayTransactions' +import { TX_NOTIFICATION_TYPES } from 'src/logic/safe/transactions' +import { isUserAnOwner } from 'src/logic/wallets/ethAddresses' +import { SafesMap } from 'src/routes/safe/store/reducer/types/safe' + +let nonce: number | undefined + +export const setNonce = (newNonce: typeof nonce): void => { + nonce = newNonce +} + +export const getNotification = ( + { safeAddress, values }: HistoryPayload, + userAddress: string, + safes: SafesMap, +): undefined => { + const currentSafe = safes.get(safeAddress) + + // no notification if not in the current safe or if its not an owner + if (!currentSafe || !isUserAnOwner(currentSafe, userAddress)) { + return + } + + // if we have a nonce, then we have a tx that is about to be executed + if (nonce !== undefined) { + const executedTx = values + .filter(isTransactionSummary) + .map((item: TransactionGatewayResult) => item.transaction) + .find((transaction) => transaction.executionInfo?.nonce === nonce) + + // transaction that was pending, was moved into history + // that is: it was executed + if (executedTx !== undefined) { + const notificationsQueue = getNotificationsFromTxType(TX_NOTIFICATION_TYPES.STANDARD_TX) + const notification = isStatusFailed(executedTx.txStatus) + ? notificationsQueue.afterExecutionError + : notificationsQueue.afterExecution.noMoreConfirmationsNeeded + + // reset nonce value + setNonce(undefined) + + return notification + } + } +}