refactor: (cancellation)transactions reducers and actions
This commit is contained in:
parent
d0dbd8a28c
commit
89261d0ed3
|
@ -1,5 +0,0 @@
|
|||
import { createAction } from 'redux-actions'
|
||||
|
||||
export const ADD_CANCELLATION_TRANSACTION = 'ADD_CANCELLATION_TRANSACTION'
|
||||
|
||||
export const addCancellationTransaction = createAction(ADD_CANCELLATION_TRANSACTION)
|
|
@ -0,0 +1,5 @@
|
|||
import { createAction } from 'redux-actions'
|
||||
|
||||
export const ADD_OR_UPDATE_CANCELLATION_TRANSACTIONS = 'ADD_OR_UPDATE_CANCELLATION_TRANSACTIONS'
|
||||
|
||||
export const addOrUpdateCancellationTransactions = createAction(ADD_OR_UPDATE_CANCELLATION_TRANSACTIONS)
|
|
@ -0,0 +1,5 @@
|
|||
import { createAction } from 'redux-actions'
|
||||
|
||||
export const ADD_OR_UPDATE_TRANSACTIONS = 'ADD_OR_UPDATE_TRANSACTIONS'
|
||||
|
||||
export const addOrUpdateTransactions = createAction(ADD_OR_UPDATE_TRANSACTIONS)
|
|
@ -1,5 +0,0 @@
|
|||
import { createAction } from 'redux-actions'
|
||||
|
||||
export const ADD_TRANSACTION = 'ADD_TRANSACTION'
|
||||
|
||||
export const addTransaction = createAction(ADD_TRANSACTION)
|
|
@ -1,252 +0,0 @@
|
|||
import { push } from 'connected-react-router'
|
||||
import { List } from 'immutable'
|
||||
// import semverSatisfies from 'semver/functions/satisfies'
|
||||
|
||||
import { makeConfirmation } from '../../models/confirmation'
|
||||
|
||||
import fetchTransactions from './fetchTransactions'
|
||||
import updateTransaction from './updateTransaction'
|
||||
|
||||
import { onboardUser } from 'src/components/ConnectButton'
|
||||
import { getGnosisSafeInstanceAt } from 'src/logic/contracts/safeContracts'
|
||||
import { getNotificationsFromTxType, showSnackbar } from 'src/logic/notifications'
|
||||
import { CALL, getApprovalTransaction, getExecutionTransaction, saveTxToHistory } from 'src/logic/safe/transactions'
|
||||
import { estimateSafeTxGas } from 'src/logic/safe/transactions/gasNew'
|
||||
// import { SAFE_VERSION_FOR_OFFCHAIN_SIGNATURES, tryOffchainSigning } from 'src/logic/safe/transactions/offchainSigner'
|
||||
// import { getCurrentSafeVersion } from 'src/logic/safe/utils/safeVersion'
|
||||
import { TOKEN_REDUCER_ID } from 'src/logic/tokens/store/reducer/tokens'
|
||||
import { ZERO_ADDRESS, sameAddress } from 'src/logic/wallets/ethAddresses'
|
||||
import { EMPTY_DATA } from 'src/logic/wallets/ethTransactions'
|
||||
import { providerSelector } from 'src/logic/wallets/store/selectors'
|
||||
import { SAFELIST_ADDRESS } from 'src/routes/routes'
|
||||
import { addCancellationTransaction } from 'src/routes/safe/store/actions/transactions/addCancellationTransaction'
|
||||
import { addTransaction } from 'src/routes/safe/store/actions/transactions/addTransaction'
|
||||
import { buildTransactionFrom } from 'src/routes/safe/store/actions/transactions/fetchTransactions/loadOutgoingTransactions'
|
||||
import { updateCancellationTransaction } from 'src/routes/safe/store/actions/transactions/updateCancellationTransaction'
|
||||
import { getLastTx, getNewTxNonce, shouldExecuteTransaction } from 'src/routes/safe/store/actions/utils'
|
||||
import { CANCELLATION_TRANSACTIONS_REDUCER_ID } from 'src/routes/safe/store/reducer/cancellationTransactions'
|
||||
import { TRANSACTIONS_REDUCER_ID } from 'src/routes/safe/store/reducer/transactions'
|
||||
import { getErrorMessage } from 'src/test/utils/ethereumErrors'
|
||||
|
||||
async function mockMyTransaction(safeAddress: string, state, tx: any) {
|
||||
const submissionDate = new Date().toISOString()
|
||||
const knownTokens = state[TOKEN_REDUCER_ID]
|
||||
const cancellationTx = sameAddress(tx.to, safeAddress) && Number(tx.value) === 0 && !tx.data
|
||||
const existentTx =
|
||||
state[cancellationTx ? CANCELLATION_TRANSACTIONS_REDUCER_ID : TRANSACTIONS_REDUCER_ID]
|
||||
.get(safeAddress)
|
||||
.find(({ nonce }) => nonce === tx.nonce) || null
|
||||
|
||||
const transactionStructure = {
|
||||
...tx,
|
||||
value: tx.valueInWei,
|
||||
blockNumber: null,
|
||||
confirmations: [], // this is used to determine if a tx is pending or not. See `getTxStatus` selector
|
||||
confirmationsRequired: null,
|
||||
dataDecoded: {},
|
||||
ethGasPrice: null,
|
||||
executionDate: null,
|
||||
executor: null,
|
||||
fee: null,
|
||||
gasUsed: null,
|
||||
isExecuted: false,
|
||||
isSuccessful: null,
|
||||
origin: null,
|
||||
safeTxHash: null,
|
||||
signatures: null,
|
||||
transactionHash: null,
|
||||
...existentTx,
|
||||
modified: submissionDate,
|
||||
submissionDate,
|
||||
safe: safeAddress,
|
||||
}
|
||||
|
||||
const mockedTransaction = await buildTransactionFrom(safeAddress, transactionStructure, knownTokens, null)
|
||||
|
||||
return { cancellationTx, existentTx, mockedTransaction }
|
||||
}
|
||||
|
||||
const createTransaction = ({
|
||||
safeAddress,
|
||||
to,
|
||||
valueInWei,
|
||||
txData = EMPTY_DATA,
|
||||
notifiedTransaction,
|
||||
enqueueSnackbar,
|
||||
closeSnackbar,
|
||||
txNonce,
|
||||
operation = CALL,
|
||||
navigateToTransactionsTab = true,
|
||||
origin = null,
|
||||
}) => async (dispatch, getState) => {
|
||||
const state = getState()
|
||||
|
||||
if (navigateToTransactionsTab) {
|
||||
dispatch(push(`${SAFELIST_ADDRESS}/${safeAddress}/transactions`))
|
||||
}
|
||||
|
||||
const ready = await onboardUser()
|
||||
if (!ready) return
|
||||
|
||||
const { account: from /*, hardwareWallet, smartContractWallet*/ } = providerSelector(state)
|
||||
const safeInstance = await getGnosisSafeInstanceAt(safeAddress)
|
||||
const lastTx = await getLastTx(safeAddress)
|
||||
const nonce = await getNewTxNonce(txNonce, lastTx, safeInstance)
|
||||
const isExecution = await shouldExecuteTransaction(safeInstance, nonce, lastTx)
|
||||
// const safeVersion = await getCurrentSafeVersion(safeInstance)
|
||||
const safeTxGas = await estimateSafeTxGas(safeInstance, safeAddress, txData, to, valueInWei, operation)
|
||||
|
||||
// https://docs.gnosis.io/safe/docs/docs5/#pre-validated-signatures
|
||||
const sigs = `0x000000000000000000000000${from.replace(
|
||||
'0x',
|
||||
'',
|
||||
)}000000000000000000000000000000000000000000000000000000000000000001`
|
||||
|
||||
const notificationsQueue = getNotificationsFromTxType(notifiedTransaction, origin)
|
||||
const beforeExecutionKey = showSnackbar(notificationsQueue.beforeExecution, enqueueSnackbar, closeSnackbar)
|
||||
let pendingExecutionKey
|
||||
|
||||
let txHash
|
||||
let tx
|
||||
const txArgs = {
|
||||
safeInstance,
|
||||
to,
|
||||
valueInWei,
|
||||
data: txData,
|
||||
operation,
|
||||
nonce,
|
||||
safeTxGas,
|
||||
baseGas: 0,
|
||||
gasPrice: 0,
|
||||
gasToken: ZERO_ADDRESS,
|
||||
refundReceiver: ZERO_ADDRESS,
|
||||
sender: from,
|
||||
sigs,
|
||||
}
|
||||
|
||||
try {
|
||||
// Here we're checking that safe contract version is greater or equal 1.1.1, but
|
||||
// theoretically EIP712 should also work for 1.0.0 contracts
|
||||
// TODO: revert this
|
||||
// const canTryOffchainSigning =
|
||||
// !isExecution && !smartContractWallet && semverSatisfies(safeVersion, SAFE_VERSION_FOR_OFFCHAIN_SIGNATURES)
|
||||
// if (canTryOffchainSigning) {
|
||||
// const signature = await tryOffchainSigning({ ...txArgs, safeAddress }, hardwareWallet)
|
||||
//
|
||||
// if (signature) {
|
||||
// closeSnackbar(beforeExecutionKey)
|
||||
//
|
||||
// await saveTxToHistory({
|
||||
// ...txArgs,
|
||||
// signature,
|
||||
// origin,
|
||||
// })
|
||||
// showSnackbar(notificationsQueue.afterExecution.moreConfirmationsNeeded, enqueueSnackbar, closeSnackbar)
|
||||
//
|
||||
// dispatch(fetchTransactions(safeAddress))
|
||||
// return
|
||||
// }
|
||||
// }
|
||||
|
||||
tx = isExecution ? await getExecutionTransaction(txArgs) : await getApprovalTransaction(txArgs)
|
||||
|
||||
const sendParams: any = { from, value: 0 }
|
||||
|
||||
// if not set owner management tests will fail on ganache
|
||||
if (process.env.NODE_ENV === 'test') {
|
||||
sendParams.gas = '7000000'
|
||||
}
|
||||
|
||||
await tx
|
||||
.send(sendParams)
|
||||
.once('transactionHash', async (hash) => {
|
||||
txHash = hash
|
||||
closeSnackbar(beforeExecutionKey)
|
||||
|
||||
pendingExecutionKey = showSnackbar(notificationsQueue.pendingExecution, enqueueSnackbar, closeSnackbar)
|
||||
|
||||
try {
|
||||
// TODO: let's mock the tx
|
||||
const { cancellationTx, existentTx, mockedTransaction } = await mockMyTransaction(safeAddress, state, {
|
||||
...txArgs,
|
||||
txHash,
|
||||
})
|
||||
if (cancellationTx) {
|
||||
if (existentTx) {
|
||||
dispatch(updateCancellationTransaction({ safeAddress, transaction: mockedTransaction }))
|
||||
} else {
|
||||
dispatch(addCancellationTransaction({ safeAddress, transaction: mockedTransaction }))
|
||||
}
|
||||
} else {
|
||||
if (existentTx) {
|
||||
dispatch(updateTransaction({ safeAddress, transaction: mockedTransaction }))
|
||||
} else {
|
||||
dispatch(addTransaction({ safeAddress, transaction: mockedTransaction }))
|
||||
}
|
||||
}
|
||||
|
||||
await saveTxToHistory({ ...txArgs, txHash, origin })
|
||||
await dispatch(fetchTransactions(safeAddress))
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
}
|
||||
})
|
||||
.on('error', (error) => {
|
||||
console.error('Tx error: ', error)
|
||||
})
|
||||
.then((receipt) => {
|
||||
closeSnackbar(pendingExecutionKey)
|
||||
const safeTxHash = isExecution
|
||||
? receipt.events.ExecutionSuccess.returnValues[0]
|
||||
: receipt.events.ApproveHash.returnValues[0]
|
||||
|
||||
dispatch(
|
||||
updateTransaction({
|
||||
safeAddress,
|
||||
transaction: {
|
||||
safeTxHash,
|
||||
isExecuted: isExecution,
|
||||
isSuccessful: isExecution ? true : null,
|
||||
executionTxHash: isExecution ? receipt.transactionHash : null,
|
||||
executor: isExecution ? from : null,
|
||||
confirmations: List([
|
||||
makeConfirmation({
|
||||
type: 'confirmation',
|
||||
hash: receipt.transactionHash,
|
||||
signature: sigs,
|
||||
owner: from,
|
||||
}),
|
||||
]),
|
||||
},
|
||||
}),
|
||||
)
|
||||
|
||||
showSnackbar(
|
||||
isExecution
|
||||
? notificationsQueue.afterExecution.noMoreConfirmationsNeeded
|
||||
: notificationsQueue.afterExecution.moreConfirmationsNeeded,
|
||||
enqueueSnackbar,
|
||||
closeSnackbar,
|
||||
)
|
||||
|
||||
dispatch(fetchTransactions(safeAddress))
|
||||
|
||||
return receipt.transactionHash
|
||||
})
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
closeSnackbar(beforeExecutionKey)
|
||||
closeSnackbar(pendingExecutionKey)
|
||||
showSnackbar(notificationsQueue.afterExecutionError, enqueueSnackbar, closeSnackbar)
|
||||
|
||||
const executeDataUsedSignatures = safeInstance.contract.methods
|
||||
.execTransaction(to, valueInWei, txData, operation, 0, 0, 0, ZERO_ADDRESS, ZERO_ADDRESS, sigs)
|
||||
.encodeABI()
|
||||
const errMsg = await getErrorMessage(safeInstance.address, 0, executeDataUsedSignatures, from)
|
||||
console.error(`Error creating the TX: ${errMsg}`)
|
||||
}
|
||||
|
||||
return txHash
|
||||
}
|
||||
|
||||
export default createTransaction
|
|
@ -0,0 +1,5 @@
|
|||
import { createAction } from 'redux-actions'
|
||||
|
||||
export const REMOVE_CANCELLATION_TRANSACTION = 'REMOVE_CANCELLATION_TRANSACTION'
|
||||
|
||||
export const removeCancellationTransaction = createAction(REMOVE_CANCELLATION_TRANSACTION)
|
|
@ -0,0 +1,5 @@
|
|||
import { createAction } from 'redux-actions'
|
||||
|
||||
export const REMOVE_TRANSACTION = 'REMOVE_TRANSACTION'
|
||||
|
||||
export const removeTransaction = createAction(REMOVE_TRANSACTION)
|
|
@ -1,5 +0,0 @@
|
|||
import { createAction } from 'redux-actions'
|
||||
|
||||
export const UPDATE_CANCELLATION_TRANSACTION = 'UPDATE_CANCELLATION_TRANSACTION'
|
||||
|
||||
export const updateCancellationTransaction = createAction(UPDATE_CANCELLATION_TRANSACTION)
|
|
@ -1,7 +0,0 @@
|
|||
import { createAction } from 'redux-actions'
|
||||
|
||||
export const UPDATE_TRANSACTION = 'UPDATE_TRANSACTION'
|
||||
|
||||
const updateTransaction = createAction(UPDATE_TRANSACTION)
|
||||
|
||||
export default updateTransaction
|
|
@ -1,13 +1,57 @@
|
|||
import { Map } from 'immutable'
|
||||
import { handleActions } from 'redux-actions'
|
||||
|
||||
import { ADD_CANCELLATION_TRANSACTIONS } from 'src/routes/safe/store/actions/addCancellationTransactions'
|
||||
import { ADD_OR_UPDATE_CANCELLATION_TRANSACTIONS } from 'src/routes/safe/store/actions/transactions/addOrUpdateCancellationTransactions'
|
||||
import { REMOVE_CANCELLATION_TRANSACTION } from 'src/routes/safe/store/actions/transactions/removeCancellationTransaction'
|
||||
|
||||
export const CANCELLATION_TRANSACTIONS_REDUCER_ID = 'cancellationTransactions'
|
||||
|
||||
export default handleActions(
|
||||
{
|
||||
[ADD_CANCELLATION_TRANSACTIONS]: (state, action) => action.payload,
|
||||
[ADD_OR_UPDATE_CANCELLATION_TRANSACTIONS]: (state, action) => {
|
||||
const { safeAddress, transactions } = action.payload
|
||||
|
||||
if (!safeAddress || !transactions || !transactions.size) {
|
||||
return state
|
||||
}
|
||||
|
||||
return state.withMutations((map) => {
|
||||
const stateTransactionsMap = map.get(safeAddress)
|
||||
|
||||
if (stateTransactionsMap) {
|
||||
transactions.forEach((updateTx) => {
|
||||
const keyPath = [safeAddress, `${updateTx.nonce}`]
|
||||
|
||||
if (updateTx.confirmations.size) {
|
||||
// if there are confirmations then we replace what's stored with the new tx
|
||||
// as we assume that this is the newest tx returned by the server
|
||||
map.setIn(keyPath, updateTx)
|
||||
} else {
|
||||
// if there's no confirmations, we assume this is a mocked tx
|
||||
// as txs without confirmation are not being returned by the server (?has_confirmations=true)
|
||||
map.mergeDeepIn(keyPath, updateTx)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
map.set(safeAddress, transactions)
|
||||
}
|
||||
})
|
||||
},
|
||||
[REMOVE_CANCELLATION_TRANSACTION]: (state, action) => {
|
||||
const { safeAddress, transaction } = action.payload
|
||||
|
||||
if (!safeAddress || !transaction) {
|
||||
return state
|
||||
}
|
||||
|
||||
return state.withMutations((map) => {
|
||||
const stateTransactionsMap = map.get(safeAddress)
|
||||
|
||||
if (stateTransactionsMap) {
|
||||
map.deleteIn([safeAddress, `${transaction.nonce}`])
|
||||
}
|
||||
})
|
||||
},
|
||||
},
|
||||
Map(),
|
||||
)
|
||||
|
|
|
@ -1,13 +1,70 @@
|
|||
import { Map } from 'immutable'
|
||||
import { handleActions } from 'redux-actions'
|
||||
|
||||
import { ADD_TRANSACTIONS } from 'src/routes/safe/store/actions/addTransactions'
|
||||
import { ADD_OR_UPDATE_TRANSACTIONS } from 'src/routes/safe/store/actions/transactions/addOrUpdateTransactions'
|
||||
import { REMOVE_TRANSACTION } from 'src/routes/safe/store/actions/transactions/removeTransaction'
|
||||
|
||||
export const TRANSACTIONS_REDUCER_ID = 'transactions'
|
||||
|
||||
export default handleActions(
|
||||
{
|
||||
[ADD_TRANSACTIONS]: (state, action) => action.payload,
|
||||
[ADD_OR_UPDATE_TRANSACTIONS]: (state, action) => {
|
||||
const { safeAddress, transactions } = action.payload
|
||||
|
||||
if (!safeAddress || !transactions || !transactions.size) {
|
||||
return state
|
||||
}
|
||||
|
||||
return state.withMutations((map) => {
|
||||
const stateTransactionsList = map.get(safeAddress)
|
||||
|
||||
if (stateTransactionsList) {
|
||||
const txsToStore = stateTransactionsList.withMutations((txsList) => {
|
||||
transactions.forEach((updateTx) => {
|
||||
const storedTxIndex = txsList.findIndex((txIterator) => txIterator.nonce === updateTx.nonce)
|
||||
|
||||
if (storedTxIndex !== -1) {
|
||||
// Update
|
||||
if (updateTx.confirmations.size) {
|
||||
// if there are confirmations then we replace what's stored with the new tx
|
||||
// as we assume that this is the newest tx returned by the server
|
||||
txsList.update(storedTxIndex, () => updateTx)
|
||||
} else {
|
||||
// if there's no confirmations, we assume this is a mocked tx
|
||||
// as txs without confirmation are not being returned by the server (?has_confirmations=true)
|
||||
txsList.update(storedTxIndex, (storedTx) => storedTx.mergeDeep(updateTx))
|
||||
}
|
||||
} else {
|
||||
// Add new
|
||||
txsList.unshift(updateTx)
|
||||
}
|
||||
})
|
||||
})
|
||||
map.set(safeAddress, txsToStore)
|
||||
} else {
|
||||
map.set(safeAddress, transactions)
|
||||
}
|
||||
})
|
||||
},
|
||||
[REMOVE_TRANSACTION]: (state, action) => {
|
||||
const { safeAddress, transaction } = action.payload
|
||||
|
||||
if (!safeAddress || !transaction) {
|
||||
return state
|
||||
}
|
||||
|
||||
return state.withMutations((map) => {
|
||||
const stateTransactionsList = map.get(safeAddress)
|
||||
|
||||
if (stateTransactionsList) {
|
||||
const storedTxIndex = stateTransactionsList.findIndex((storedTx) => storedTx.equals(transaction))
|
||||
|
||||
if (storedTxIndex !== -1) {
|
||||
map.deleteIn([safeAddress, storedTxIndex])
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
},
|
||||
Map(),
|
||||
)
|
||||
|
|
Loading…
Reference in New Issue