diff --git a/src/routes/safe/store/actions/transactions/addCancellationTransaction.js b/src/routes/safe/store/actions/transactions/addCancellationTransaction.js new file mode 100644 index 00000000..715a4059 --- /dev/null +++ b/src/routes/safe/store/actions/transactions/addCancellationTransaction.js @@ -0,0 +1,6 @@ +// @flow +import { createAction } from 'redux-actions' + +export const ADD_CANCELLATION_TRANSACTION = 'ADD_CANCELLATION_TRANSACTION' + +export const addCancellationTransaction = createAction(ADD_CANCELLATION_TRANSACTION) diff --git a/src/routes/safe/store/actions/transactions/addTransaction.js b/src/routes/safe/store/actions/transactions/addTransaction.js new file mode 100644 index 00000000..290c25cf --- /dev/null +++ b/src/routes/safe/store/actions/transactions/addTransaction.js @@ -0,0 +1,6 @@ +// @flow +import { createAction } from 'redux-actions' + +export const ADD_TRANSACTION = 'ADD_TRANSACTION' + +export const addTransaction = createAction(ADD_TRANSACTION) diff --git a/src/routes/safe/store/actions/transactions/createTransaction.js b/src/routes/safe/store/actions/transactions/createTransaction.js index cfbb9926..da017b5c 100644 --- a/src/routes/safe/store/actions/transactions/createTransaction.js +++ b/src/routes/safe/store/actions/transactions/createTransaction.js @@ -1,8 +1,9 @@ // @flow import { push } from 'connected-react-router' +import type { RecordInstance } from 'immutable' import { List } from 'immutable' import type { GetState, Dispatch as ReduxDispatch } from 'redux' -import semverSatisfies from 'semver/functions/satisfies' +// import semverSatisfies from 'semver/functions/satisfies' import { makeConfirmation } from '../../models/confirmation' @@ -20,13 +21,22 @@ import { saveTxToHistory, } from '~/logic/safe/transactions' import { estimateSafeTxGas } from '~/logic/safe/transactions/gasNew' -import { SAFE_VERSION_FOR_OFFCHAIN_SIGNATURES, tryOffchainSigning } from '~/logic/safe/transactions/offchainSigner' -import { getCurrentSafeVersion } from '~/logic/safe/utils/safeVersion' -import { ZERO_ADDRESS } from '~/logic/wallets/ethAddresses' +// import { SAFE_VERSION_FOR_OFFCHAIN_SIGNATURES, tryOffchainSigning } from '~/logic/safe/transactions/offchainSigner' +// import { getCurrentSafeVersion } from '~/logic/safe/utils/safeVersion' +import { TOKEN_REDUCER_ID } from '~/logic/tokens/store/reducer/tokens' +import { ZERO_ADDRESS, sameAddress } from '~/logic/wallets/ethAddresses' import { EMPTY_DATA } from '~/logic/wallets/ethTransactions' import { providerSelector } from '~/logic/wallets/store/selectors' import { SAFELIST_ADDRESS } from '~/routes/routes' +import { addCancellationTransaction } from '~/routes/safe/store/actions/transactions/addCancellationTransaction' +import { addTransaction } from '~/routes/safe/store/actions/transactions/addTransaction' +import type { TxServiceModel } from '~/routes/safe/store/actions/transactions/fetchTransactions/loadOutgoingTransactions' +import { buildTransactionFrom } from '~/routes/safe/store/actions/transactions/fetchTransactions/loadOutgoingTransactions' +import { updateCancellationTransaction } from '~/routes/safe/store/actions/transactions/updateCancellationTransaction' import { getLastTx, getNewTxNonce, shouldExecuteTransaction } from '~/routes/safe/store/actions/utils' +import type { TransactionProps } from '~/routes/safe/store/models/transaction' +import { CANCELLATION_TRANSACTIONS_REDUCER_ID } from '~/routes/safe/store/reducer/cancellationTransactions' +import { TRANSACTIONS_REDUCER_ID } from '~/routes/safe/store/reducer/transactions' import { type GlobalState } from '~/store' import { getErrorMessage } from '~/test/utils/ethereumErrors' @@ -44,6 +54,49 @@ export type CreateTransactionArgs = { origin?: string | null, } +async function mockMyTransaction(safeAddress: string, state, tx: {}) { + 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: TransactionProps = + state[cancellationTx ? CANCELLATION_TRANSACTIONS_REDUCER_ID : TRANSACTIONS_REDUCER_ID] + .get(safeAddress) + .find(({ nonce }) => nonce === tx.nonce) || null + + const transactionStructure: TxServiceModel = { + ...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: RecordInstance = await buildTransactionFrom( + safeAddress, + transactionStructure, + knownTokens, + null, + ) + + return { cancellationTx, existentTx, mockedTransaction } +} + const createTransaction = ({ safeAddress, to, @@ -66,12 +119,12 @@ const createTransaction = ({ const ready = await onboardUser() if (!ready) return - const { account: from, hardwareWallet, smartContractWallet } = providerSelector(state) + 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 safeVersion = await getCurrentSafeVersion(safeInstance) const safeTxGas = await estimateSafeTxGas(safeInstance, safeAddress, txData, to, valueInWei, operation) // https://docs.gnosis.io/safe/docs/docs5/#pre-validated-signatures @@ -105,25 +158,26 @@ const createTransaction = ({ 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 - const canTryOffchainSigning = - !isExecution && !smartContractWallet && semverSatisfies(safeVersion, SAFE_VERSION_FOR_OFFCHAIN_SIGNATURES) - if (false) { - 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 - } - } + // 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) @@ -143,11 +197,26 @@ const createTransaction = ({ pendingExecutionKey = showSnackbar(notificationsQueue.pendingExecution, enqueueSnackbar, closeSnackbar) try { - await saveTxToHistory({ + // TODO: let's mock the tx + const { cancellationTx, existentTx, mockedTransaction } = await mockMyTransaction(safeAddress, state, { ...txArgs, txHash, - origin, }) + 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) diff --git a/src/routes/safe/store/actions/transactions/fetchTransactions/loadOutgoingTransactions.js b/src/routes/safe/store/actions/transactions/fetchTransactions/loadOutgoingTransactions.js index 487badf9..1b9a097a 100644 --- a/src/routes/safe/store/actions/transactions/fetchTransactions/loadOutgoingTransactions.js +++ b/src/routes/safe/store/actions/transactions/fetchTransactions/loadOutgoingTransactions.js @@ -10,6 +10,7 @@ import { decodeParamsFromSafeMethod } from '~/logic/contracts/methodIds' import { buildTxServiceUrl } from '~/logic/safe/transactions/txHistory' import { getTokenInfos } from '~/logic/tokens/store/actions/fetchTokens' import { TOKEN_REDUCER_ID } from '~/logic/tokens/store/reducer/tokens' +import { ALTERNATIVE_TOKEN_ABI } from '~/logic/tokens/utils/alternativeAbi' import { SAFE_TRANSFER_FROM_WITHOUT_DATA_HASH, isMultisendTransaction, @@ -179,7 +180,7 @@ export const buildTransactionFrom = async ( }) } -const batchTxTokenRequest = (txs: any[]) => { +export const batchTxTokenRequest = (txs: any[]) => { const batch = new web3ReadOnly.BatchRequest() const whenTxsValues = txs.map((tx) => { diff --git a/src/routes/safe/store/actions/transactions/updateCancellationTransaction.js b/src/routes/safe/store/actions/transactions/updateCancellationTransaction.js new file mode 100644 index 00000000..9a96c77b --- /dev/null +++ b/src/routes/safe/store/actions/transactions/updateCancellationTransaction.js @@ -0,0 +1,6 @@ +// @flow +import { createAction } from 'redux-actions' + +export const UPDATE_CANCELLATION_TRANSACTION = 'UPDATE_CANCELLATION_TRANSACTION' + +export const updateCancellationTransaction = createAction(UPDATE_CANCELLATION_TRANSACTION) diff --git a/src/routes/safe/store/reducer/cancellationTransactions.js b/src/routes/safe/store/reducer/cancellationTransactions.js index f7be7aae..8140751b 100644 --- a/src/routes/safe/store/reducer/cancellationTransactions.js +++ b/src/routes/safe/store/reducer/cancellationTransactions.js @@ -2,7 +2,9 @@ import { List, Map } from 'immutable' import { type ActionType, handleActions } from 'redux-actions' +import { ADD_CANCELLATION_TRANSACTION } from '~/routes/safe/store/actions/transactions/addCancellationTransaction' import { ADD_CANCELLATION_TRANSACTIONS } from '~/routes/safe/store/actions/transactions/addCancellationTransactions' +import { UPDATE_CANCELLATION_TRANSACTION } from '~/routes/safe/store/actions/transactions/updateCancellationTransaction' import { type Transaction } from '~/routes/safe/store/models/transaction' export const CANCELLATION_TRANSACTIONS_REDUCER_ID = 'cancellationTransactions' @@ -11,7 +13,32 @@ export type CancelState = Map> export default handleActions( { + [ADD_CANCELLATION_TRANSACTION]: (state: CancelState, action: ActionType): CancelState => { + const { safeAddress, transaction } = action.payload + const transactionList = state.get(safeAddress) + return state.set(safeAddress, transactionList.push(transaction)) + }, [ADD_CANCELLATION_TRANSACTIONS]: (state: CancelState, action: ActionType): CancelState => action.payload, + [UPDATE_CANCELLATION_TRANSACTION]: (state: CancelState, action: ActionType): CancelState => { + const { safeAddress, transaction } = action.payload + + const transactionList = state.get(safeAddress) + + if (!transaction) { + return state + } + + const storedTransactionIndex = transactionList.findIndex((tx) => tx.safeTxHash === transaction.safeTxHash) + + if (storedTransactionIndex === -1) { + return state + } + + return state.set( + safeAddress, + transactionList.update(storedTransactionIndex, (tx) => tx.merge(transaction)), + ) + }, }, Map(), ) diff --git a/src/routes/safe/store/reducer/transactions.js b/src/routes/safe/store/reducer/transactions.js index 901385db..9aa82f1b 100644 --- a/src/routes/safe/store/reducer/transactions.js +++ b/src/routes/safe/store/reducer/transactions.js @@ -2,6 +2,7 @@ import { List, Map } from 'immutable' import { type ActionType, handleActions } from 'redux-actions' +import { ADD_TRANSACTION } from '~/routes/safe/store/actions/transactions/addTransaction' import { ADD_TRANSACTIONS } from '~/routes/safe/store/actions/transactions/addTransactions' import { UPDATE_TRANSACTION } from '~/routes/safe/store/actions/transactions/updateTransaction' import { type Transaction } from '~/routes/safe/store/models/transaction' @@ -12,6 +13,11 @@ export type State = Map> export default handleActions( { + [ADD_TRANSACTION]: (state: State, action: ActionType): State => { + const { safeAddress, transaction } = action.payload + const transactionList = state.get(safeAddress) + return state.set(safeAddress, transactionList.push(transaction)) + }, [ADD_TRANSACTIONS]: (state: State, action: ActionType): State => action.payload, [UPDATE_TRANSACTION]: (state: State, action: ActionType): State => { const { safeAddress, transaction } = action.payload