Skip to content
This repository was archived by the owner on Nov 10, 2023. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions src/logic/hooks/useLocalTxStatus.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { useSelector } from 'react-redux'
import { TransactionStatus } from '@gnosis.pm/safe-react-gateway-sdk'
import { Transaction } from 'src/logic/safe/store/models/types/gateway.d'
import { AppReduxState } from 'src/store'
import { selectTxStatus } from 'src/logic/safe/store/selectors/txStatus'
import { useState } from 'react'
import { useDebounce } from './useDebounce'

const useLocalTxStatus = (transaction: Transaction): TransactionStatus => {
const storedStatus = useSelector((state: AppReduxState) => selectTxStatus(state, transaction))
const [localStatus, setLocalStatus] = useState(storedStatus)

useDebounce(() => {
if (storedStatus) {
setLocalStatus(storedStatus)
}
}, 100)

return localStatus
}

export default useLocalTxStatus
8 changes: 4 additions & 4 deletions src/logic/safe/store/actions/__tests__/utils.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { FEATURES } from '@gnosis.pm/safe-react-gateway-sdk'
import { TransactionStatus } from '@gnosis.pm/safe-react-gateway-sdk'

import { ChainId } from 'src/config/chain.d'
import {
Expand All @@ -10,6 +9,7 @@ import {
} from 'src/logic/safe/store/actions/utils'
import { SafeRecordProps } from 'src/logic/safe/store/models/safe'
import { getMockedSafeInstance, getMockedStoredTServiceModel } from 'src/test/utils/safeHelper'
import { LocalTransactionStatus } from '../../models/types/gateway.d'
import {
inMemoryPartialSafeInformation,
localSafesInfo,
Expand Down Expand Up @@ -37,7 +37,7 @@ describe('shouldExecuteTransaction', () => {
const nonce = '1'
const threshold = '1'
const safeInstance = getMockedSafeInstance({ threshold, nonce })
const lastTxFromStoreExecuted = { ...lastTxFromStore, txStatus: TransactionStatus.SUCCESS }
const lastTxFromStoreExecuted = { ...lastTxFromStore, txStatus: LocalTransactionStatus.SUCCESS }

// when
const result = await shouldExecuteTransaction(safeInstance, nonce, lastTxFromStoreExecuted)
Expand All @@ -50,7 +50,7 @@ describe('shouldExecuteTransaction', () => {
const nonce = '10'
const threshold = '1'
const safeInstance = getMockedSafeInstance({ threshold, nonce })
const lastTxFromStoreExecuted = { ...lastTxFromStore, txStatus: TransactionStatus.SUCCESS }
const lastTxFromStoreExecuted = { ...lastTxFromStore, txStatus: LocalTransactionStatus.SUCCESS }

// when
const result = await shouldExecuteTransaction(safeInstance, nonce, lastTxFromStoreExecuted)
Expand All @@ -63,7 +63,7 @@ describe('shouldExecuteTransaction', () => {
const nonce = '10'
const threshold = '1'
const safeInstance = getMockedSafeInstance({ threshold })
const lastTxFromStoreExecuted = { ...lastTxFromStore, txStatus: TransactionStatus.FAILED }
const lastTxFromStoreExecuted = { ...lastTxFromStore, txStatus: LocalTransactionStatus.FAILED }

// when
const result = await shouldExecuteTransaction(safeInstance, nonce, lastTxFromStoreExecuted)
Expand Down
30 changes: 23 additions & 7 deletions src/logic/safe/store/actions/createTransaction.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Operation } from '@gnosis.pm/safe-react-gateway-sdk'
import { Operation, TransactionDetails } from '@gnosis.pm/safe-react-gateway-sdk'
import { AnyAction } from 'redux'
import { ThunkAction } from 'redux-thunk'

Expand Down Expand Up @@ -36,6 +36,8 @@ import { getPrefixedSafeAddressSlug, SAFE_ADDRESS_SLUG, TRANSACTION_ID_SLUG } fr
import { generatePath } from 'react-router-dom'
import { getContractErrorMessage } from 'src/logic/contracts/safeContractErrors'
import { getLastTransaction, getLastTxNonce } from '../selectors/gatewayTransactions'
import { isMultiSigExecutionDetails, LocalTransactionStatus } from '../models/types/gateway.d'
import { updateTransactionStatus } from './updateTransactionStatus'

export interface CreateTransactionArgs {
navigateToTransactionsTab?: boolean
Expand All @@ -62,11 +64,14 @@ export const isKeystoneError = (err: Error): boolean => {
return err.message.startsWith('#ktek_error')
}

const navigateToTx = (safeAddress: string, txId: string) => {
const navigateToTx = (safeAddress: string, txDetails: TransactionDetails) => {
if (!isMultiSigExecutionDetails(txDetails.detailedExecutionInfo)) {
return
}
const prefixedSafeAddress = getPrefixedSafeAddressSlug({ shortName: extractShortChainName(), safeAddress })
const txRoute = generatePath(SAFE_ROUTES.TRANSACTIONS_SINGULAR, {
[SAFE_ADDRESS_SLUG]: prefixedSafeAddress,
[TRANSACTION_ID_SLUG]: txId,
[TRANSACTION_ID_SLUG]: txDetails.detailedExecutionInfo.safeTxHash,
})

history.push(txRoute)
Expand Down Expand Up @@ -125,7 +130,7 @@ export const createTransaction =
const notificationsQueue = getNotificationsFromTxType(notifiedTransaction, origin)
const beforeExecutionKey = dispatch(enqueueSnackbar(notificationsQueue.beforeExecution))

let txHash
let txHash = ''
const txArgs: TxArgs & { sender: string } = {
safeInstance,
to,
Expand All @@ -142,8 +147,14 @@ export const createTransaction =
sigs,
}

let safeTxHash = ''

try {
const safeTxHash = await generateSafeTxHash(safeAddress, safeVersion, txArgs)
safeTxHash = await generateSafeTxHash(safeAddress, safeVersion, txArgs)

if (isExecution) {
dispatch(updateTransactionStatus({ safeTxHash, status: LocalTransactionStatus.PENDING }))
}

if (checkIfOffChainSignatureIsPossible(isExecution, smartContractWallet, safeVersion)) {
const signature = await tryOffChainSigning(safeTxHash, { ...txArgs, safeAddress }, hardwareWallet, safeVersion)
Expand All @@ -154,7 +165,7 @@ export const createTransaction =

dispatch(fetchTransactions(chainId, safeAddress))
if (navigateToTransactionsTab) {
navigateToTx(safeAddress, txDetails.txId)
navigateToTx(safeAddress, txDetails)
}
onUserConfirm?.(safeTxHash)
return
Expand All @@ -180,8 +191,9 @@ export const createTransaction =

try {
const txDetails = await saveTxToHistory({ ...txArgs, origin })

if (navigateToTransactionsTab) {
navigateToTx(safeAddress, txDetails.txId)
navigateToTx(safeAddress, txDetails)
}
} catch (err) {
logError(Errors._803, err.message)
Expand All @@ -207,6 +219,10 @@ export const createTransaction =

dispatch(closeSnackbarAction({ key: beforeExecutionKey }))

if (isExecution && safeTxHash) {
dispatch(updateTransactionStatus({ safeTxHash, status: LocalTransactionStatus.PENDING_FAILED }))
}

const executeDataUsedSignatures = safeInstance.methods
.execTransaction(to, valueInWei, txData, operation, 0, 0, 0, ZERO_ADDRESS, ZERO_ADDRESS, sigs)
.encodeABI()
Expand Down
49 changes: 8 additions & 41 deletions src/logic/safe/store/actions/processTransaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,14 @@ import { Dispatch, DispatchReturn } from './types'
import { PayableTx } from 'src/types/contracts/types'
import { updateTransactionStatus } from 'src/logic/safe/store/actions/updateTransactionStatus'
import { Confirmation } from 'src/logic/safe/store/models/types/confirmation'
import { Operation, TransactionStatus } from '@gnosis.pm/safe-react-gateway-sdk'
import { Operation } from '@gnosis.pm/safe-react-gateway-sdk'
import { isTxPendingError } from 'src/logic/wallets/getWeb3'
import { Errors, logError } from 'src/logic/exceptions/CodedException'
import { getContractErrorMessage } from 'src/logic/contracts/safeContractErrors'
import { onboardUser } from 'src/components/ConnectButton'
import { getGasParam } from '../../transactions/gas'
import { getLastTransaction, getLastTxNonce } from '../selectors/gatewayTransactions'
import { LocalTransactionStatus } from '../models/types/gateway.d'

interface ProcessTransactionArgs {
approveAndExecute: boolean
Expand Down Expand Up @@ -130,15 +131,6 @@ export const processTransaction =
if (signature) {
dispatch(closeSnackbarAction({ key: beforeExecutionKey }))

dispatch(
updateTransactionStatus({
chainId,
txStatus: TransactionStatus.PENDING,
safeAddress,
nonce: tx.nonce,
id: tx.id,
}),
)
await saveTxToHistory({ ...txArgs, signature })

dispatch(fetchTransactions(chainId, safeAddress))
Expand All @@ -162,17 +154,9 @@ export const processTransaction =
txHash = hash
dispatch(closeSnackbarAction({ key: beforeExecutionKey }))

dispatch(
updateTransactionStatus({
chainId,
txStatus: TransactionStatus.PENDING,
safeAddress,
nonce: tx.nonce,
// if we provide the tx ID that sole tx will have the _pending_ status.
// if not, all the txs that share the same nonce will have the _pending_ status.
id: tx.id,
}),
)
if (isExecution) {
dispatch(updateTransactionStatus({ safeTxHash: tx.safeTxHash, status: LocalTransactionStatus.PENDING }))
}

try {
await saveTxToHistory({ ...txArgs })
Expand All @@ -185,17 +169,6 @@ export const processTransaction =
logError(Errors._804, e.message)
}
})
.on('error', () => {
dispatch(
updateTransactionStatus({
chainId,
txStatus: TransactionStatus.PENDING_FAILED,
safeAddress,
nonce: tx.nonce,
id: tx.id,
}),
)
})
.then(async (receipt) => {
dispatch(fetchTransactions(chainId, safeAddress))

Expand All @@ -210,15 +183,9 @@ export const processTransaction =

dispatch(closeSnackbarAction({ key: beforeExecutionKey }))

dispatch(
updateTransactionStatus({
chainId,
txStatus: TransactionStatus.PENDING_FAILED,
safeAddress,
nonce: tx.nonce,
id: tx.id,
}),
)
if (isExecution) {
dispatch(updateTransactionStatus({ safeTxHash: tx.safeTxHash, status: LocalTransactionStatus.PENDING_FAILED }))
}

const executeData = safeInstance.methods.approveHash(txHash || '').encodeABI()
const contractErrorMessage = await getContractErrorMessage({
Expand Down
2 changes: 1 addition & 1 deletion src/logic/safe/store/actions/updateTransactionStatus.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { createAction } from 'redux-actions'

import { TransactionStatusPayload } from 'src/logic/safe/store/reducer/gatewayTransactions'
import { TransactionStatusPayload } from 'src/logic/safe/store/reducer/localTransactions'

export const UPDATE_TRANSACTION_STATUS = 'UPDATE_TRANSACTION_STATUS'
export const updateTransactionStatus = createAction<TransactionStatusPayload>(UPDATE_TRANSACTION_STATUS)
10 changes: 7 additions & 3 deletions src/logic/safe/store/actions/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,12 @@ import { buildModulesLinkedList } from 'src/logic/safe/utils/modules'
import { enabledFeatures, safeNeedsUpdate } from 'src/logic/safe/utils/safeVersion'
import { checksumAddress } from 'src/utils/checksumAddress'
import { ChainId } from 'src/config/chain.d'
import { SafeInfo, TransactionStatus } from '@gnosis.pm/safe-react-gateway-sdk'
import { Transaction, isMultisigExecutionInfo } from 'src/logic/safe/store/models/types/gateway.d'
import { SafeInfo } from '@gnosis.pm/safe-react-gateway-sdk'
import {
Transaction,
isMultisigExecutionInfo,
LocalTransactionStatus,
} from 'src/logic/safe/store/models/types/gateway.d'

export const getNewTxNonce = async (lastTxNonce: number | undefined, safeInstance: GnosisSafe): Promise<string> => {
// use current's safe nonce as fallback
Expand Down Expand Up @@ -44,7 +48,7 @@ export const shouldExecuteTransaction = async (
// Once the previous tx is executed, the current tx will be available to be executed
// by the user using the exec button.
if (lastTx && isMultisigExecutionInfo(lastTx.executionInfo)) {
return lastTx.txStatus === TransactionStatus.SUCCESS && lastTx.executionInfo.nonce + 1 === Number(nonce)
return lastTx.txStatus === LocalTransactionStatus.SUCCESS && lastTx.executionInfo.nonce + 1 === Number(nonce)
}

return false
Expand Down
20 changes: 19 additions & 1 deletion src/logic/safe/store/middleware/notificationsMiddleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ import { store as reduxStore } from 'src/store/index'
import { HistoryPayload } from 'src/logic/safe/store/reducer/gatewayTransactions'
import { history, extractSafeAddress, generateSafeRoute, ADDRESSED_ROUTE, SAFE_ROUTES } from 'src/routes/routes'
import { getShortName } from 'src/config'
import { localStatuses } from '../selectors/txStatus'
import { currentChainId } from 'src/logic/config/store/selectors'

const watchedActions = [ADD_OR_UPDATE_SAFE, ADD_QUEUED_TRANSACTIONS, ADD_HISTORY_TRANSACTIONS]

Expand Down Expand Up @@ -105,7 +107,23 @@ const notificationsMiddleware =
const safesMap = safesAsMap(state)
const currentSafe = safesMap.get(safeAddress)

if (!currentSafe || !isUserAnOwner(currentSafe, userAddress) || awaitingTransactions.length === 0) {
const chainId = currentChainId(state)
const localStatusedSafeTxHashes = Object.keys(localStatuses(state)?.[chainId] || {})

if (!localStatusedSafeTxHashes.length) {
break
}

const hasLocalStatus = transactions.some((tx) =>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you need to search the array or keys instead of just looking up the key in the hash map?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't have the safeTxHash. Please check my other response.

localStatusedSafeTxHashes.some((safeTxHash) => tx.id.includes(safeTxHash)),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't tx.id different than safeTxHash? Or in this case it's same?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Transaction summaries do not return the safeTxHash. The id, however, contains it.

Copy link
Member

@katspaugh katspaugh Dec 20, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it in a random place or can we extract the exact safeTxHash part and use it for a direct hash map lookup?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From my understanding, it is the last part of the id but I am not 100% sure. It would be ideal if the safeTxHash was returned as part of the summary but that is something we'd have to discuss with backend in the new year.

)

if (
hasLocalStatus ||
!currentSafe ||
!isUserAnOwner(currentSafe, userAddress) ||
awaitingTransactions.length === 0
) {
break
}

Expand Down
Loading