Merge pull request #181 from gnosis/171-txhash-refactoring
Feature #181: Get transaction hash after confirmation
This commit is contained in:
commit
033284d835
|
@ -1,11 +1,12 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
import GnosisSafeSol from '@gnosis.pm/safe-contracts/build/contracts/GnosisSafe.json'
|
||||||
import { getWeb3 } from '~/logic/wallets/getWeb3'
|
import { getWeb3 } from '~/logic/wallets/getWeb3'
|
||||||
import { getStandardTokenContract } from '~/logic/tokens/store/actions/fetchTokens'
|
import { getStandardTokenContract } from '~/logic/tokens/store/actions/fetchTokens'
|
||||||
import { EMPTY_DATA } from '~/logic/wallets/ethTransactions'
|
import { EMPTY_DATA } from '~/logic/wallets/ethTransactions'
|
||||||
import { isEther } from '~/logic/tokens/utils/tokenHelpers'
|
import { isEther } from '~/logic/tokens/utils/tokenHelpers'
|
||||||
import { type Token } from '~/logic/tokens/store/model/token'
|
import { type Token } from '~/logic/tokens/store/model/token'
|
||||||
import { getGnosisSafeInstanceAt } from '~/logic/contracts/safeContracts'
|
import { getGnosisSafeInstanceAt } from '~/logic/contracts/safeContracts'
|
||||||
import { type Operation, saveTxToHistory } from '~/logic/safe/transactions'
|
import { type Operation } from '~/logic/safe/transactions'
|
||||||
import { ZERO_ADDRESS } from '~/logic/wallets/ethAddresses'
|
import { ZERO_ADDRESS } from '~/logic/wallets/ethAddresses'
|
||||||
import { getErrorMessage } from '~/test/utils/ethereumErrors'
|
import { getErrorMessage } from '~/test/utils/ethereumErrors'
|
||||||
|
|
||||||
|
@ -13,7 +14,7 @@ export const CALL = 0
|
||||||
export const TX_TYPE_EXECUTION = 'execution'
|
export const TX_TYPE_EXECUTION = 'execution'
|
||||||
export const TX_TYPE_CONFIRMATION = 'confirmation'
|
export const TX_TYPE_CONFIRMATION = 'confirmation'
|
||||||
|
|
||||||
export const approveTransaction = async (
|
export const getApprovalTransaction = async (
|
||||||
safeInstance: any,
|
safeInstance: any,
|
||||||
to: string,
|
to: string,
|
||||||
valueInWei: number | string,
|
valueInWei: number | string,
|
||||||
|
@ -22,7 +23,7 @@ export const approveTransaction = async (
|
||||||
nonce: number,
|
nonce: number,
|
||||||
sender: string,
|
sender: string,
|
||||||
) => {
|
) => {
|
||||||
const contractTxHash = await safeInstance.getTransactionHash(
|
const txHash = await safeInstance.getTransactionHash(
|
||||||
to,
|
to,
|
||||||
valueInWei,
|
valueInWei,
|
||||||
data,
|
data,
|
||||||
|
@ -37,24 +38,23 @@ export const approveTransaction = async (
|
||||||
from: sender,
|
from: sender,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
const receipt = await safeInstance.approveHash(contractTxHash, { from: sender })
|
|
||||||
|
|
||||||
await saveTxToHistory(
|
try {
|
||||||
safeInstance,
|
const web3 = getWeb3()
|
||||||
to,
|
const contract = new web3.eth.Contract(GnosisSafeSol.abi, safeInstance.address)
|
||||||
valueInWei,
|
|
||||||
data,
|
|
||||||
operation,
|
|
||||||
nonce,
|
|
||||||
receipt.tx, // tx hash,
|
|
||||||
sender,
|
|
||||||
TX_TYPE_CONFIRMATION,
|
|
||||||
)
|
|
||||||
|
|
||||||
return receipt
|
return contract.methods.approveHash(txHash)
|
||||||
|
} catch (error) {
|
||||||
|
/* eslint-disable */
|
||||||
|
const executeData = safeInstance.contract.methods.approveHash(txHash).encodeABI()
|
||||||
|
const errMsg = await getErrorMessage(safeInstance.address, 0, executeData, sender)
|
||||||
|
console.log(`Error executing the TX: ${errMsg}`)
|
||||||
|
|
||||||
|
throw error
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const executeTransaction = async (
|
export const getExecutionTransaction = async (
|
||||||
safeInstance: any,
|
safeInstance: any,
|
||||||
to: string,
|
to: string,
|
||||||
valueInWei: number | string,
|
valueInWei: number | string,
|
||||||
|
@ -75,33 +75,10 @@ export const executeTransaction = async (
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const receipt = await safeInstance.execTransaction(
|
const web3 = getWeb3()
|
||||||
to,
|
const contract = new web3.eth.Contract(GnosisSafeSol.abi, safeInstance.address)
|
||||||
valueInWei,
|
|
||||||
data,
|
|
||||||
operation,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
ZERO_ADDRESS,
|
|
||||||
ZERO_ADDRESS,
|
|
||||||
sigs,
|
|
||||||
{ from: sender },
|
|
||||||
)
|
|
||||||
|
|
||||||
await saveTxToHistory(
|
return contract.methods.execTransaction(to, valueInWei, data, operation, 0, 0, 0, ZERO_ADDRESS, ZERO_ADDRESS, sigs)
|
||||||
safeInstance,
|
|
||||||
to,
|
|
||||||
valueInWei,
|
|
||||||
data,
|
|
||||||
operation,
|
|
||||||
nonce,
|
|
||||||
receipt.tx, // tx hash,
|
|
||||||
sender,
|
|
||||||
TX_TYPE_EXECUTION,
|
|
||||||
)
|
|
||||||
|
|
||||||
return receipt
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
const executeDataUsedSignatures = safeInstance.contract.methods
|
const executeDataUsedSignatures = safeInstance.contract.methods
|
||||||
|
|
|
@ -36,8 +36,6 @@ export const sendReplaceOwner = async (
|
||||||
values: Object,
|
values: Object,
|
||||||
safeAddress: string,
|
safeAddress: string,
|
||||||
ownerAddressToRemove: string,
|
ownerAddressToRemove: string,
|
||||||
ownerNameToRemove: string,
|
|
||||||
ownersOld: List<Owner>,
|
|
||||||
openSnackbar: Function,
|
openSnackbar: Function,
|
||||||
createTransaction: Function,
|
createTransaction: Function,
|
||||||
replaceSafeOwner: Function,
|
replaceSafeOwner: Function,
|
||||||
|
@ -109,8 +107,6 @@ const ReplaceOwner = ({
|
||||||
values,
|
values,
|
||||||
safeAddress,
|
safeAddress,
|
||||||
ownerAddress,
|
ownerAddress,
|
||||||
ownerName,
|
|
||||||
owners,
|
|
||||||
openSnackbar,
|
openSnackbar,
|
||||||
createTransaction,
|
createTransaction,
|
||||||
replaceSafeOwner,
|
replaceSafeOwner,
|
||||||
|
|
|
@ -13,7 +13,8 @@ export const TRANSACTIONS_DESC_ADD_OWNER_TEST_ID = 'tx-description-add-owner'
|
||||||
export const TRANSACTIONS_DESC_REMOVE_OWNER_TEST_ID = 'tx-description-remove-owner'
|
export const TRANSACTIONS_DESC_REMOVE_OWNER_TEST_ID = 'tx-description-remove-owner'
|
||||||
export const TRANSACTIONS_DESC_CHANGE_THRESHOLD_TEST_ID = 'tx-description-change-threshold'
|
export const TRANSACTIONS_DESC_CHANGE_THRESHOLD_TEST_ID = 'tx-description-change-threshold'
|
||||||
export const TRANSACTIONS_DESC_SEND_TEST_ID = 'tx-description-send'
|
export const TRANSACTIONS_DESC_SEND_TEST_ID = 'tx-description-send'
|
||||||
export const TRANSACTIONS_DESC_CUSTOM_TEST_ID = 'tx-description-custom'
|
export const TRANSACTIONS_DESC_CUSTOM_VALUE_TEST_ID = 'tx-description-custom-value'
|
||||||
|
export const TRANSACTIONS_DESC_CUSTOM_DATA_TEST_ID = 'tx-description-custom-data'
|
||||||
|
|
||||||
export const styles = () => ({
|
export const styles = () => ({
|
||||||
txDataContainer: {
|
txDataContainer: {
|
||||||
|
@ -42,6 +43,8 @@ type DescriptionDescProps = {
|
||||||
}
|
}
|
||||||
|
|
||||||
type CustomDescProps = {
|
type CustomDescProps = {
|
||||||
|
value: string,
|
||||||
|
recipient: string,
|
||||||
data: String,
|
data: String,
|
||||||
classes: Obeject,
|
classes: Obeject,
|
||||||
}
|
}
|
||||||
|
@ -88,9 +91,24 @@ const SettingsDescription = ({ removedOwner, addedOwner, newThreshold }: Descrip
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|
||||||
const CustomDescription = ({ data, classes }: CustomDescProps) => (
|
const CustomDescription = ({
|
||||||
|
data, value = 0, recipient, classes,
|
||||||
|
}: CustomDescProps) => (
|
||||||
<>
|
<>
|
||||||
<Paragraph className={classes.txData} data-testid={TRANSACTIONS_DESC_CUSTOM_TEST_ID}>
|
<Paragraph noMargin data-testid={TRANSACTIONS_DESC_CUSTOM_VALUE_TEST_ID}>
|
||||||
|
<Bold>
|
||||||
|
Send
|
||||||
|
{' '}
|
||||||
|
{value}
|
||||||
|
{' '}
|
||||||
|
ETH
|
||||||
|
{' '}
|
||||||
|
to:
|
||||||
|
</Bold>
|
||||||
|
<br />
|
||||||
|
<EtherscanLink type="address" value={recipient} />
|
||||||
|
</Paragraph>
|
||||||
|
<Paragraph className={classes.txData} data-testid={TRANSACTIONS_DESC_CUSTOM_DATA_TEST_ID}>
|
||||||
<Bold>Data (hex encoded):</Bold>
|
<Bold>Data (hex encoded):</Bold>
|
||||||
<br />
|
<br />
|
||||||
{data}
|
{data}
|
||||||
|
@ -109,7 +127,7 @@ const TxDescription = ({ tx, classes }: Props) => {
|
||||||
<SettingsDescription removedOwner={removedOwner} newThreshold={newThreshold} addedOwner={addedOwner} />
|
<SettingsDescription removedOwner={removedOwner} newThreshold={newThreshold} addedOwner={addedOwner} />
|
||||||
)}
|
)}
|
||||||
{customTx && (
|
{customTx && (
|
||||||
<CustomDescription data={data} classes={classes} />
|
<CustomDescription data={data} value={value} recipient={recipient} classes={classes} />
|
||||||
)}
|
)}
|
||||||
{!cancellationTx && !modifySettingsTx && !customTx && (
|
{!cancellationTx && !modifySettingsTx && !customTx && (
|
||||||
<TransferDescription value={value} symbol={tx.symbol} recipient={recipient} />
|
<TransferDescription value={value} symbol={tx.symbol} recipient={recipient} />
|
||||||
|
|
|
@ -23,6 +23,11 @@ export const getTxData = (tx: Transaction): DecodedTxData => {
|
||||||
if (tx.isTokenTransfer && tx.decodedParams) {
|
if (tx.isTokenTransfer && tx.decodedParams) {
|
||||||
txData.recipient = tx.decodedParams.recipient
|
txData.recipient = tx.decodedParams.recipient
|
||||||
txData.value = fromWei(toBN(tx.decodedParams.value), 'ether')
|
txData.value = fromWei(toBN(tx.decodedParams.value), 'ether')
|
||||||
|
} else if (tx.customTx) {
|
||||||
|
txData.recipient = tx.recipient
|
||||||
|
txData.value = fromWei(toBN(tx.value), 'ether')
|
||||||
|
txData.data = tx.data
|
||||||
|
txData.customTx = true
|
||||||
} else if (Number(tx.value) > 0) {
|
} else if (Number(tx.value) > 0) {
|
||||||
txData.recipient = tx.recipient
|
txData.recipient = tx.recipient
|
||||||
txData.value = fromWei(toBN(tx.value), 'ether')
|
txData.value = fromWei(toBN(tx.value), 'ether')
|
||||||
|
@ -49,9 +54,6 @@ export const getTxData = (tx: Transaction): DecodedTxData => {
|
||||||
}
|
}
|
||||||
} else if (tx.cancellationTx) {
|
} else if (tx.cancellationTx) {
|
||||||
txData.cancellationTx = true
|
txData.cancellationTx = true
|
||||||
} else if (tx.customTx) {
|
|
||||||
txData.data = tx.data
|
|
||||||
txData.customTx = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return txData
|
return txData
|
||||||
|
|
|
@ -6,11 +6,14 @@ import fetchTransactions from '~/routes/safe/store/actions/fetchTransactions'
|
||||||
import { type GlobalState } from '~/store'
|
import { type GlobalState } from '~/store'
|
||||||
import { getGnosisSafeInstanceAt } from '~/logic/contracts/safeContracts'
|
import { getGnosisSafeInstanceAt } from '~/logic/contracts/safeContracts'
|
||||||
import {
|
import {
|
||||||
approveTransaction,
|
getApprovalTransaction,
|
||||||
executeTransaction,
|
getExecutionTransaction,
|
||||||
CALL,
|
CALL,
|
||||||
type Notifications,
|
type Notifications,
|
||||||
DEFAULT_NOTIFICATIONS,
|
DEFAULT_NOTIFICATIONS,
|
||||||
|
TX_TYPE_CONFIRMATION,
|
||||||
|
TX_TYPE_EXECUTION,
|
||||||
|
saveTxToHistory,
|
||||||
} from '~/logic/safe/transactions'
|
} from '~/logic/safe/transactions'
|
||||||
|
|
||||||
const createTransaction = (
|
const createTransaction = (
|
||||||
|
@ -31,18 +34,54 @@ const createTransaction = (
|
||||||
const isExecution = threshold.toNumber() === 1 || shouldExecute
|
const isExecution = threshold.toNumber() === 1 || shouldExecute
|
||||||
|
|
||||||
let txHash
|
let txHash
|
||||||
|
let tx
|
||||||
try {
|
try {
|
||||||
if (isExecution) {
|
if (isExecution) {
|
||||||
openSnackbar(notifications.BEFORE_EXECUTION_OR_CREATION, 'success')
|
tx = await getExecutionTransaction(safeInstance, to, valueInWei, txData, CALL, nonce, from)
|
||||||
txHash = await executeTransaction(safeInstance, to, valueInWei, txData, CALL, nonce, from)
|
|
||||||
openSnackbar(notifications.AFTER_EXECUTION, 'success')
|
|
||||||
} else {
|
} else {
|
||||||
openSnackbar(notifications.BEFORE_EXECUTION_OR_CREATION, 'success')
|
tx = await getApprovalTransaction(safeInstance, to, valueInWei, txData, CALL, nonce, from)
|
||||||
txHash = await approveTransaction(safeInstance, to, valueInWei, txData, CALL, nonce, from)
|
|
||||||
openSnackbar(notifications.CREATED_MORE_CONFIRMATIONS_NEEDED, 'success')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const sendParams = {
|
||||||
|
from,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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', (hash) => {
|
||||||
|
txHash = hash
|
||||||
|
openSnackbar(notifications.BEFORE_EXECUTION_OR_CREATION, 'success')
|
||||||
|
})
|
||||||
|
.on('error', (error) => {
|
||||||
|
console.error('Tx error: ', error)
|
||||||
|
})
|
||||||
|
.then(async (receipt) => {
|
||||||
|
await saveTxToHistory(
|
||||||
|
safeInstance,
|
||||||
|
to,
|
||||||
|
valueInWei,
|
||||||
|
txData,
|
||||||
|
CALL,
|
||||||
|
nonce,
|
||||||
|
receipt.transactionHash,
|
||||||
|
from,
|
||||||
|
isExecution ? TX_TYPE_EXECUTION : TX_TYPE_CONFIRMATION,
|
||||||
|
)
|
||||||
|
|
||||||
|
return receipt.transactionHash
|
||||||
|
})
|
||||||
|
|
||||||
|
openSnackbar(
|
||||||
|
isExecution ? notifications.AFTER_EXECUTION : notifications.CREATED_MORE_CONFIRMATIONS_NEEDED,
|
||||||
|
'success',
|
||||||
|
)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
openSnackbar(notifications.ERROR, '')
|
openSnackbar(notifications.ERROR, 'error')
|
||||||
console.error(`Error while creating transaction: ${err}`)
|
console.error(`Error while creating transaction: ${err}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,14 @@ import { userAccountSelector } from '~/logic/wallets/store/selectors'
|
||||||
import fetchTransactions from '~/routes/safe/store/actions/fetchTransactions'
|
import fetchTransactions from '~/routes/safe/store/actions/fetchTransactions'
|
||||||
import { type GlobalState } from '~/store'
|
import { type GlobalState } from '~/store'
|
||||||
import { getGnosisSafeInstanceAt } from '~/logic/contracts/safeContracts'
|
import { getGnosisSafeInstanceAt } from '~/logic/contracts/safeContracts'
|
||||||
import { approveTransaction, executeTransaction, CALL } from '~/logic/safe/transactions'
|
import {
|
||||||
|
getApprovalTransaction,
|
||||||
|
getExecutionTransaction,
|
||||||
|
CALL,
|
||||||
|
saveTxToHistory,
|
||||||
|
TX_TYPE_EXECUTION,
|
||||||
|
TX_TYPE_CONFIRMATION,
|
||||||
|
} from '~/logic/safe/transactions'
|
||||||
|
|
||||||
// https://gnosis-safe.readthedocs.io/en/latest/contracts/signatures.html#pre-validated-signatures
|
// https://gnosis-safe.readthedocs.io/en/latest/contracts/signatures.html#pre-validated-signatures
|
||||||
// https://github.com/gnosis/safe-contracts/blob/master/test/gnosisSafeTeamEdition.js#L26
|
// https://github.com/gnosis/safe-contracts/blob/master/test/gnosisSafeTeamEdition.js#L26
|
||||||
|
@ -45,16 +52,52 @@ const processTransaction = (
|
||||||
const sigs = generateSignaturesFromTxConfirmations(tx, approveAndExecute && userAddress)
|
const sigs = generateSignaturesFromTxConfirmations(tx, approveAndExecute && userAddress)
|
||||||
|
|
||||||
let txHash
|
let txHash
|
||||||
|
let transaction
|
||||||
if (shouldExecute) {
|
if (shouldExecute) {
|
||||||
openSnackbar('Transaction has been submitted', 'success')
|
transaction = await getExecutionTransaction(safeInstance, tx.recipient, tx.value, tx.data, CALL, nonce, from, sigs)
|
||||||
txHash = await executeTransaction(safeInstance, tx.recipient, tx.value, tx.data, CALL, nonce, from, sigs)
|
|
||||||
openSnackbar('Transaction has been confirmed', 'success')
|
|
||||||
} else {
|
} else {
|
||||||
openSnackbar('Approval transaction has been submitted', 'success')
|
transaction = await getApprovalTransaction(safeInstance, tx.recipient, tx.value, tx.data, CALL, nonce, from)
|
||||||
txHash = await approveTransaction(safeInstance, tx.recipient, tx.value, tx.data, CALL, nonce, from)
|
|
||||||
openSnackbar('Approval transaction has been confirmed', 'success')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const sendParams = {
|
||||||
|
from,
|
||||||
|
}
|
||||||
|
|
||||||
|
// if not set owner management tests will fail on ganache
|
||||||
|
if (process.env.NODE_ENV === 'test') {
|
||||||
|
sendParams.gas = '7000000'
|
||||||
|
}
|
||||||
|
|
||||||
|
await transaction
|
||||||
|
.send(sendParams)
|
||||||
|
.once('transactionHash', (hash) => {
|
||||||
|
txHash = hash
|
||||||
|
openSnackbar(
|
||||||
|
shouldExecute ? 'Transaction has been submitted' : 'Approval transaction has been submitted',
|
||||||
|
'success',
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.on('error', (error) => {
|
||||||
|
console.error('Processing transaction error: ', error)
|
||||||
|
})
|
||||||
|
.then(async (receipt) => {
|
||||||
|
await saveTxToHistory(
|
||||||
|
safeInstance,
|
||||||
|
tx.recipient,
|
||||||
|
tx.value,
|
||||||
|
tx.data,
|
||||||
|
CALL,
|
||||||
|
nonce,
|
||||||
|
receipt.transactionHash,
|
||||||
|
from,
|
||||||
|
shouldExecute ? TX_TYPE_EXECUTION : TX_TYPE_CONFIRMATION,
|
||||||
|
)
|
||||||
|
|
||||||
|
return receipt.transactionHash
|
||||||
|
})
|
||||||
|
|
||||||
|
openSnackbar(shouldExecute ? 'Transaction has been confirmed' : 'Approval transaction has been confirmed', 'success')
|
||||||
|
|
||||||
dispatch(fetchTransactions(safeAddress))
|
dispatch(fetchTransactions(safeAddress))
|
||||||
|
|
||||||
return txHash
|
return txHash
|
||||||
|
|
Loading…
Reference in New Issue