diff --git a/src/components/TransactionsFees/index.tsx b/src/components/TransactionsFees/index.tsx new file mode 100644 index 0000000000..c5e54f1970 --- /dev/null +++ b/src/components/TransactionsFees/index.tsx @@ -0,0 +1,43 @@ +import React from 'react' +import { EstimationStatus } from 'src/logic/hooks/useEstimateTransactionGas' +import Paragraph from 'src/components/layout/Paragraph' +import { getNetworkInfo } from 'src/config' +import { TransactionFailText } from 'src/components/TransactionFailText' + +type TransactionFailTextProps = { + txEstimationExecutionStatus: EstimationStatus + gasCostFormatted: string + isExecution: boolean + isCreation: boolean + isOffChainSignature: boolean +} +const { nativeCoin } = getNetworkInfo() + +export const TransactionFees = ({ + gasCostFormatted, + isExecution, + isCreation, + isOffChainSignature, + txEstimationExecutionStatus, +}: TransactionFailTextProps): React.ReactElement | null => { + let transactionAction + if (isCreation) { + transactionAction = 'create' + } else if (isExecution) { + transactionAction = 'execute' + } else { + transactionAction = 'approve' + } + + return ( + <> + + You're about to {transactionAction} a transaction and will have to confirm it with your currently connected + wallet. + {!isOffChainSignature && + ` Make sure you have ${gasCostFormatted} (fee price) ${nativeCoin.name} in this wallet to fund this confirmation.`} + + + + ) +} diff --git a/src/logic/hooks/useEstimateTransactionGas.tsx b/src/logic/hooks/useEstimateTransactionGas.tsx index 2b3ee7ea4d..0acb86d8f7 100644 --- a/src/logic/hooks/useEstimateTransactionGas.tsx +++ b/src/logic/hooks/useEstimateTransactionGas.tsx @@ -1,11 +1,26 @@ import { useEffect, useState } from 'react' -import { estimateTransactionGas } from 'src/logic/safe/transactions/gas' +import { + estimateGasForTransactionApproval, + estimateGasForTransactionCreation, + estimateGasForTransactionExecution, +} from 'src/logic/safe/transactions/gas' import { fromTokenUnit } from 'src/logic/tokens/utils/humanReadableValue' import { formatAmount } from 'src/logic/tokens/utils/formatAmount' import { calculateGasPrice } from 'src/logic/wallets/ethTransactions' import { getNetworkInfo } from 'src/config' import { useSelector } from 'react-redux' -import { safeParamAddressFromStateSelector, safeThresholdSelector } from 'src/logic/safe/store/selectors' +import { + safeCurrentVersionSelector, + safeParamAddressFromStateSelector, + safeThresholdSelector, +} from 'src/logic/safe/store/selectors' +import { CALL } from 'src/logic/safe/transactions' +import { providerSelector } from '../wallets/store/selectors' + +import { List } from 'immutable' +import { Confirmation } from 'src/logic/safe/store/models/types/confirmation' +import { checkIfOffChainSignatureIsPossible } from 'src/logic/safe/safeTxSigner' +import { ZERO_ADDRESS } from 'src/logic/wallets/ethAddresses' export enum EstimationStatus { LOADING = 'LOADING', @@ -16,13 +31,90 @@ export enum EstimationStatus { const checkIfTxIsExecution = (threshold: number, preApprovingOwner?: string, txConfirmations?: number): boolean => txConfirmations === threshold || !!preApprovingOwner || threshold === 1 +const checkIfTxIsApproveAndExecution = (threshold: number, txConfirmations: number): boolean => + txConfirmations + 1 === threshold + +const checkIfTxIsCreation = (txConfirmations: number): boolean => txConfirmations === 0 + +type TransactionEstimationProps = { + txData: string + safeAddress: string + txRecipient: string + txConfirmations?: List + txAmount?: string + operation?: number + gasPrice?: string + gasToken?: string + refundReceiver?: string // Address of receiver of gas payment (or 0 if tx.origin). + safeTxGas?: number + from?: string + isExecution: boolean + isCreation: boolean + isOffChainSignature?: boolean + approvalAndExecution?: boolean +} + +const estimateTransactionGas = async ({ + txData, + safeAddress, + txRecipient, + txConfirmations, + txAmount, + operation, + gasPrice, + gasToken, + refundReceiver, + safeTxGas, + from, + isExecution, + isCreation, + isOffChainSignature = false, + approvalAndExecution, +}: TransactionEstimationProps): Promise => { + if (isCreation) { + return estimateGasForTransactionCreation(safeAddress, txData, txRecipient, txAmount || '0', operation || CALL) + } + + if (!from) { + throw new Error('No from provided for approving or execute transaction') + } + + if (isExecution) { + return estimateGasForTransactionExecution({ + safeAddress, + txRecipient, + txConfirmations, + txAmount: txAmount || '0', + txData, + operation: operation || CALL, + from, + gasPrice: gasPrice || '0', + gasToken: gasToken || ZERO_ADDRESS, + refundReceiver: refundReceiver || ZERO_ADDRESS, + safeTxGas: safeTxGas || 0, + approvalAndExecution, + }) + } + + return estimateGasForTransactionApproval({ + safeAddress, + operation: operation || CALL, + txData, + txAmount: txAmount || '0', + txRecipient, + from, + isOffChainSignature, + }) +} + type UseEstimateTransactionGasProps = { txData: string txRecipient: string - txConfirmations?: number + txConfirmations?: List txAmount?: string preApprovingOwner?: string operation?: number + safeTxGas?: number } type TransactionGasEstimationResult = { @@ -32,6 +124,8 @@ type TransactionGasEstimationResult = { gasCostFormatted: string // Cost of gas in format '< | > 100' gasPrice: string // Current price of gas unit isExecution: boolean // Returns true if the user will execute the tx or false if it just signs it + isCreation: boolean // Returns true if the transaction is a creation transaction + isOffChainSignature: boolean // Returns true if offChainSignature is available } export const useEstimateTransactionGas = ({ @@ -41,6 +135,7 @@ export const useEstimateTransactionGas = ({ txAmount, preApprovingOwner, operation, + safeTxGas, }: UseEstimateTransactionGasProps): TransactionGasEstimationResult => { const [gasEstimation, setGasEstimation] = useState({ txEstimationExecutionStatus: EstimationStatus.LOADING, @@ -49,10 +144,14 @@ export const useEstimateTransactionGas = ({ gasCostFormatted: '< 0.001', gasPrice: '0', isExecution: false, + isCreation: false, + isOffChainSignature: false, }) const { nativeCoin } = getNetworkInfo() const safeAddress = useSelector(safeParamAddressFromStateSelector) const threshold = useSelector(safeThresholdSelector) + const safeVersion = useSelector(safeCurrentVersionSelector) + const { account: from, smartContractWallet } = useSelector(providerSelector) useEffect(() => { const estimateGas = async () => { @@ -60,31 +159,50 @@ export const useEstimateTransactionGas = ({ return } + const isExecution = checkIfTxIsExecution(Number(threshold), preApprovingOwner, txConfirmations?.size) + const isCreation = checkIfTxIsCreation(txConfirmations?.size || 0) + const approvalAndExecution = checkIfTxIsApproveAndExecution(Number(threshold), txConfirmations?.size || 0) + try { - const isExecution = checkIfTxIsExecution(Number(threshold), preApprovingOwner, txConfirmations) + const isOffChainSignature = checkIfOffChainSignatureIsPossible(isExecution, smartContractWallet, safeVersion) const gasEstimation = await estimateTransactionGas({ safeAddress, txRecipient, txData, txAmount, + txConfirmations, isExecution, + isCreation, + isOffChainSignature, operation, + from, + safeTxGas, + approvalAndExecution, }) const gasPrice = await calculateGasPrice() const estimatedGasCosts = gasEstimation * parseInt(gasPrice, 10) const gasCost = fromTokenUnit(estimatedGasCosts, nativeCoin.decimals) const gasCostFormatted = formatAmount(gasCost) + let txEstimationExecutionStatus = EstimationStatus.SUCCESS + + if (gasEstimation <= 0) { + txEstimationExecutionStatus = isOffChainSignature ? EstimationStatus.SUCCESS : EstimationStatus.FAILURE + } + setGasEstimation({ - txEstimationExecutionStatus: gasEstimation <= 0 ? EstimationStatus.FAILURE : EstimationStatus.SUCCESS, + txEstimationExecutionStatus, gasEstimation, gasCost, gasCostFormatted, gasPrice, isExecution, + isCreation, + isOffChainSignature, }) } catch (error) { + console.warn(error.message) // We put a fixed the amount of gas to let the user try to execute the tx, but it's not accurate so it will probably fail const gasEstimation = 10000 const gasCost = fromTokenUnit(gasEstimation, nativeCoin.decimals) @@ -95,7 +213,9 @@ export const useEstimateTransactionGas = ({ gasCost, gasCostFormatted, gasPrice: '1', - isExecution: false, + isExecution, + isCreation, + isOffChainSignature: false, }) } } @@ -110,7 +230,11 @@ export const useEstimateTransactionGas = ({ preApprovingOwner, nativeCoin.decimals, threshold, + from, operation, + safeVersion, + smartContractWallet, + safeTxGas, ]) return gasEstimation diff --git a/src/logic/safe/safeTxSigner.ts b/src/logic/safe/safeTxSigner.ts index 52de1a3b68..4fce148dd0 100644 --- a/src/logic/safe/safeTxSigner.ts +++ b/src/logic/safe/safeTxSigner.ts @@ -1,31 +1,61 @@ -// https://docs.gnosis.io/safe/docs/docs5/#pre-validated-signatures -// https://github.com/gnosis/safe-contracts/blob/master/test/gnosisSafeTeamEdition.js#L26 -export const generateSignaturesFromTxConfirmations = (confirmations, preApprovingOwner) => { - // The constant parts need to be sorted so that the recovered signers are sorted ascending - // (natural order) by address (not checksummed). - const confirmationsMap = confirmations.reduce((map, obj) => { - map[obj.owner.toLowerCase()] = obj // eslint-disable-line no-param-reassign - return map - }, {}) +import { List } from 'immutable' +import { Confirmation } from 'src/logic/safe/store/models/types/confirmation' +import { EMPTY_DATA } from 'src/logic/wallets/ethTransactions' +import semverSatisfies from 'semver/functions/satisfies' +import { SAFE_VERSION_FOR_OFFCHAIN_SIGNATURES } from './transactions/offchainSigner' + +// 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 +// Also, offchain signatures are not working for ledger/trezor wallet because of a bug in their library: +// https://github.com/LedgerHQ/ledgerjs/issues/378 +// Couldn't find an issue for trezor but the error is almost the same +export const checkIfOffChainSignatureIsPossible = ( + isExecution: boolean, + isSmartContractWallet: boolean, + safeVersion?: string, +): boolean => + !isExecution && + !isSmartContractWallet && + !!safeVersion && + semverSatisfies(safeVersion, SAFE_VERSION_FOR_OFFCHAIN_SIGNATURES) + +// https://docs.gnosis.io/safe/docs/contracts_signatures/#pre-validated-signatures +export const getPreValidatedSignatures = (from: string, initialString: string = EMPTY_DATA): string => { + return `${initialString}000000000000000000000000${from.replace( + EMPTY_DATA, + '', + )}000000000000000000000000000000000000000000000000000000000000000001` +} + +export const generateSignaturesFromTxConfirmations = ( + confirmations?: List, + preApprovingOwner?: string, +): string => { + let confirmationsMap = + confirmations?.map((value) => { + return { + signature: value.signature, + owner: value.owner.toLowerCase(), + } + }) || List([]) if (preApprovingOwner) { - confirmationsMap[preApprovingOwner.toLowerCase()] = { owner: preApprovingOwner } + confirmationsMap = confirmationsMap.push({ owner: preApprovingOwner, signature: null }) } + // The constant parts need to be sorted so that the recovered signers are sorted ascending + // (natural order) by address (not checksummed). + confirmationsMap = confirmationsMap.sort((ownerA, ownerB) => ownerA.owner.localeCompare(ownerB.owner)) + let sigs = '0x' - Object.keys(confirmationsMap) - .sort() - .forEach((addr) => { - const conf = confirmationsMap[addr] - if (conf.signature) { - sigs += conf.signature.slice(2) - } else { - // https://docs.gnosis.io/safe/docs/docs5/#pre-validated-signatures - sigs += `000000000000000000000000${addr.replace( - '0x', - '', - )}000000000000000000000000000000000000000000000000000000000000000001` - } - }) + confirmationsMap.forEach(({ signature, owner }) => { + if (signature) { + sigs += signature.slice(2) + } else { + // https://docs.gnosis.io/safe/docs/contracts_signatures/#pre-validated-signatures + sigs += getPreValidatedSignatures(owner, '') + } + }) + return sigs } diff --git a/src/logic/safe/store/actions/__tests__/utils.test.ts b/src/logic/safe/store/actions/__tests__/utils.test.ts index db063f9ace..b11ebcb35f 100644 --- a/src/logic/safe/store/actions/__tests__/utils.test.ts +++ b/src/logic/safe/store/actions/__tests__/utils.test.ts @@ -3,22 +3,8 @@ import { GnosisSafe } from 'src/types/contracts/GnosisSafe.d' import { TxServiceModel } from 'src/logic/safe/store/actions/transactions/fetchTransactions/loadOutgoingTransactions' describe('Store actions utils > getNewTxNonce', () => { - it(`Should return passed predicted transaction nonce if it's a valid value`, async () => { - // Given - const txNonce = '45' - const lastTx = { nonce: 44 } as TxServiceModel - const safeInstance = {} - - // When - const nonce = await getNewTxNonce(txNonce, lastTx, safeInstance as GnosisSafe) - - // Then - expect(nonce).toBe('45') - }) - it(`Should return nonce of a last transaction + 1 if passed nonce is less than last transaction or invalid`, async () => { // Given - const txNonce = '' const lastTx = { nonce: 44 } as TxServiceModel const safeInstance = { methods: { @@ -29,7 +15,7 @@ describe('Store actions utils > getNewTxNonce', () => { } // When - const nonce = await getNewTxNonce(txNonce, lastTx, safeInstance as GnosisSafe) + const nonce = await getNewTxNonce(lastTx, safeInstance as GnosisSafe) // Then expect(nonce).toBe('45') @@ -37,7 +23,6 @@ describe('Store actions utils > getNewTxNonce', () => { it(`Should retrieve contract's instance nonce value as a fallback, if txNonce and lastTx are not valid`, async () => { // Given - const txNonce = '' const lastTx = null const safeInstance = { methods: { @@ -48,7 +33,7 @@ describe('Store actions utils > getNewTxNonce', () => { } // When - const nonce = await getNewTxNonce(txNonce, lastTx, safeInstance as GnosisSafe) + const nonce = await getNewTxNonce(lastTx, safeInstance as GnosisSafe) // Then expect(nonce).toBe('45') diff --git a/src/logic/safe/store/actions/createTransaction.ts b/src/logic/safe/store/actions/createTransaction.ts index 5052b5cca3..0e318e3391 100644 --- a/src/logic/safe/store/actions/createTransaction.ts +++ b/src/logic/safe/store/actions/createTransaction.ts @@ -1,5 +1,4 @@ import { push } from 'connected-react-router' -import semverSatisfies from 'semver/functions/satisfies' import { ThunkAction } from 'redux-thunk' import { onboardUser } from 'src/components/ConnectButton' @@ -10,11 +9,10 @@ import { CALL, getApprovalTransaction, getExecutionTransaction, - SAFE_VERSION_FOR_OFFCHAIN_SIGNATURES, saveTxToHistory, tryOffchainSigning, } from 'src/logic/safe/transactions' -import { estimateExecTransactionGas, getPreValidatedSignatures } from 'src/logic/safe/transactions/gas' +import { estimateGasForTransactionCreation } from 'src/logic/safe/transactions/gas' import { getCurrentSafeVersion } from 'src/logic/safe/utils/safeVersion' import { ZERO_ADDRESS } from 'src/logic/wallets/ethAddresses' import { EMPTY_DATA } from 'src/logic/wallets/ethTransactions' @@ -40,6 +38,7 @@ import { AnyAction } from 'redux' import { PayableTx } from 'src/types/contracts/types.d' import { AppReduxState } from 'src/store' import { Dispatch, DispatchReturn } from './types' +import { checkIfOffChainSignatureIsPossible, getPreValidatedSignatures } from 'src/logic/safe/safeTxSigner' export interface CreateTransactionArgs { navigateToTransactionsTab?: boolean @@ -87,13 +86,18 @@ const createTransaction = ( const { account: from, hardwareWallet, smartContractWallet } = providerSelector(state) const safeInstance = await getGnosisSafeInstanceAt(safeAddress) const lastTx = await getLastTx(safeAddress) - const nonce = await getNewTxNonce(txNonce?.toString(), lastTx, safeInstance) + const nonce = txNonce ? txNonce.toString() : await getNewTxNonce(lastTx, safeInstance) const isExecution = await shouldExecuteTransaction(safeInstance, nonce, lastTx) const safeVersion = await getCurrentSafeVersion(safeInstance) - const safeTxGas = safeTxGasArg || (await estimateExecTransactionGas(safeAddress, txData, to, valueInWei, operation)) + let safeTxGas + try { + safeTxGas = + safeTxGasArg || (await estimateGasForTransactionCreation(safeAddress, txData, to, valueInWei, operation)) + } catch (error) { + safeTxGas = safeTxGasArg || 0 + } const sigs = getPreValidatedSignatures(from) - const notificationsQueue = getNotificationsFromTxType(notifiedTransaction, origin) const beforeExecutionKey = dispatch(enqueueSnackbar(notificationsQueue.beforeExecution)) @@ -118,11 +122,7 @@ const createTransaction = ( const safeTxHash = generateSafeTxHash(safeAddress, txArgs) 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 (canTryOffchainSigning) { + if (checkIfOffChainSignatureIsPossible(isExecution, smartContractWallet, safeVersion)) { const signature = await tryOffchainSigning(safeTxHash, { ...txArgs, safeAddress }, hardwareWallet) if (signature) { @@ -136,9 +136,7 @@ const createTransaction = ( } } - const tx = isExecution - ? await getExecutionTransaction(txArgs) - : await getApprovalTransaction(safeInstance, safeTxHash) + const tx = isExecution ? getExecutionTransaction(txArgs) : getApprovalTransaction(safeInstance, safeTxHash) const sendParams: PayableTx = { from, value: 0 } // if not set owner management tests will fail on ganache diff --git a/src/logic/safe/store/actions/processTransaction.ts b/src/logic/safe/store/actions/processTransaction.ts index eced5a75c8..8d31d343ab 100644 --- a/src/logic/safe/store/actions/processTransaction.ts +++ b/src/logic/safe/store/actions/processTransaction.ts @@ -1,12 +1,15 @@ import { AnyAction } from 'redux' import { ThunkAction } from 'redux-thunk' -import semverSatisfies from 'semver/functions/satisfies' import { getGnosisSafeInstanceAt } from 'src/logic/contracts/safeContracts' import { getNotificationsFromTxType } from 'src/logic/notifications' -import { generateSignaturesFromTxConfirmations } from 'src/logic/safe/safeTxSigner' +import { + checkIfOffChainSignatureIsPossible, + generateSignaturesFromTxConfirmations, + getPreValidatedSignatures, +} from 'src/logic/safe/safeTxSigner' import { getApprovalTransaction, getExecutionTransaction, saveTxToHistory } from 'src/logic/safe/transactions' -import { SAFE_VERSION_FOR_OFFCHAIN_SIGNATURES, tryOffchainSigning } from 'src/logic/safe/transactions/offchainSigner' +import { tryOffchainSigning } from 'src/logic/safe/transactions/offchainSigner' import { getCurrentSafeVersion } from 'src/logic/safe/utils/safeVersion' import { EMPTY_DATA } from 'src/logic/wallets/ethTransactions' import { providerSelector } from 'src/logic/wallets/store/selectors' @@ -33,7 +36,7 @@ interface ProcessTransactionArgs { type ProcessTransactionAction = ThunkAction, AppReduxState, DispatchReturn, AnyAction> -const processTransaction = ({ +export const processTransaction = ({ approveAndExecute, notifiedTransaction, safeAddress, @@ -49,17 +52,15 @@ const processTransaction = ({ const safeInstance = await getGnosisSafeInstanceAt(safeAddress) const lastTx = await getLastTx(safeAddress) - const nonce = await getNewTxNonce(undefined, lastTx, safeInstance) + const nonce = await getNewTxNonce(lastTx, safeInstance) const isExecution = approveAndExecute || (await shouldExecuteTransaction(safeInstance, nonce, lastTx)) const safeVersion = await getCurrentSafeVersion(safeInstance) - let sigs = generateSignaturesFromTxConfirmations(tx.confirmations, approveAndExecute && userAddress) - // https://docs.gnosis.io/safe/docs/docs5/#pre-validated-signatures + const preApprovingOwner = approveAndExecute ? userAddress : undefined + let sigs = generateSignaturesFromTxConfirmations(tx.confirmations, preApprovingOwner) + if (!sigs) { - sigs = `0x000000000000000000000000${from.replace( - '0x', - '', - )}000000000000000000000000000000000000000000000000000000000000000001` + sigs = getPreValidatedSignatures(from) } const notificationsQueue = getNotificationsFromTxType(notifiedTransaction, tx.origin) @@ -86,14 +87,7 @@ const processTransaction = ({ } 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 - // Also, offchain signatures are not working for ledger/trezor wallet because of a bug in their library: - // https://github.com/LedgerHQ/ledgerjs/issues/378 - // Couldn't find an issue for trezor but the error is almost the same - const canTryOffchainSigning = - !isExecution && !smartContractWallet && semverSatisfies(safeVersion, SAFE_VERSION_FOR_OFFCHAIN_SIGNATURES) - if (canTryOffchainSigning) { + if (checkIfOffChainSignatureIsPossible(isExecution, smartContractWallet, safeVersion)) { const signature = await tryOffchainSigning(tx.safeTxHash, { ...txArgs, safeAddress }, hardwareWallet) if (signature) { @@ -109,9 +103,7 @@ const processTransaction = ({ } } - transaction = isExecution - ? await getExecutionTransaction(txArgs) - : await getApprovalTransaction(safeInstance, tx.safeTxHash) + transaction = isExecution ? getExecutionTransaction(txArgs) : getApprovalTransaction(safeInstance, tx.safeTxHash) const sendParams: any = { from, value: 0 } @@ -196,5 +188,3 @@ const processTransaction = ({ return txHash } - -export default processTransaction diff --git a/src/logic/safe/store/actions/utils.ts b/src/logic/safe/store/actions/utils.ts index 74b59b0d3c..828c6e92e6 100644 --- a/src/logic/safe/store/actions/utils.ts +++ b/src/logic/safe/store/actions/utils.ts @@ -16,15 +16,7 @@ export const getLastTx = async (safeAddress: string): Promise => { - if (txNonce) { - return txNonce - } - +export const getNewTxNonce = async (lastTx: TxServiceModel | null, safeInstance: GnosisSafe): Promise => { // use current's safe nonce as fallback return lastTx ? `${lastTx.nonce + 1}` : (await safeInstance.methods.nonce().call()).toString() } diff --git a/src/logic/safe/transactions/gas.ts b/src/logic/safe/transactions/gas.ts index b9ae685837..fa220bacd8 100644 --- a/src/logic/safe/transactions/gas.ts +++ b/src/logic/safe/transactions/gas.ts @@ -1,10 +1,12 @@ import { BigNumber } from 'bignumber.js' -import { CALL } from '.' import { getGnosisSafeInstanceAt } from 'src/logic/contracts/safeContracts' -import { ZERO_ADDRESS } from 'src/logic/wallets/ethAddresses' -import { EMPTY_DATA, calculateGasOf } from 'src/logic/wallets/ethTransactions' -import { getAccountFrom, getWeb3 } from 'src/logic/wallets/getWeb3' +import { calculateGasOf, EMPTY_DATA } from 'src/logic/wallets/ethTransactions' +import { getWeb3 } from 'src/logic/wallets/getWeb3' import { sameString } from 'src/utils/strings' +import { ZERO_ADDRESS } from 'src/logic/wallets/ethAddresses' +import { generateSignaturesFromTxConfirmations } from 'src/logic/safe/safeTxSigner' +import { List } from 'immutable' +import { Confirmation } from 'src/logic/safe/store/models/types/confirmation' // Receives the response data of the safe method requiredTxGas() and parses it to get the gas amount const parseRequiredTxGasResponse = (data: string): number => { @@ -23,66 +25,6 @@ const parseRequiredTxGasResponse = (data: string): number => { return data.match(/.{2}/g)?.reduce(reducer, 0) } -// https://docs.gnosis.io/safe/docs/contracts_signatures/#pre-validated-signatures -export const getPreValidatedSignatures = (from: string): string => { - return `0x000000000000000000000000${from.replace( - EMPTY_DATA, - '', - )}000000000000000000000000000000000000000000000000000000000000000001` -} - -export type TransactionEstimationProps = { - txData: string - safeAddress: string - txRecipient: string - isExecution: boolean - txAmount?: string - operation?: number -} - -export const estimateTransactionGas = async ({ - txAmount, - txData, - txRecipient, - isExecution, - safeAddress, - operation = CALL, -}: TransactionEstimationProps): Promise => { - const web3 = getWeb3() - const from = await getAccountFrom(web3) - - if (!from) { - return 0 - } - - if (isExecution) { - // Gas of executing a transaction within the safe (threshold reached and transaction ready to be executed) - return estimateExecTransactionGas(safeAddress, txData, txRecipient, txAmount || '0', operation) - } - - const safeInstance = await getGnosisSafeInstanceAt(safeAddress) - const nonce = await safeInstance.methods.nonce().call() - const txHash = await safeInstance.methods - .getTransactionHash( - txRecipient, - txAmount || '0', - txData, - operation as number, - 0, - 0, - 0, - ZERO_ADDRESS, - ZERO_ADDRESS, - nonce, - ) - .call({ - from, - }) - // Gas of approving the transaction (threshold not reached or user did not executed the transaction) - const approveTransactionTxData = await safeInstance.methods.approveHash(txHash).encodeABI() - return calculateGasOf(approveTransactionTxData, from, safeAddress) -} - // Parses the result from the error message (GETH, OpenEthereum/Parity and Nethermind) and returns the data value export const getDataFromNodeErrorMessage = (errorMessage: string): string | undefined => { // Replace illegal characters that often comes within the error string (like � for example) @@ -185,7 +127,7 @@ const calculateMinimumGasForTransaction = async ( return 0 } -export const estimateExecTransactionGas = async ( +export const estimateGasForTransactionCreation = async ( safeAddress: string, data: string, to: string, @@ -217,6 +159,97 @@ export const estimateExecTransactionGas = async ( ) } catch (error) { console.info('Error calculating tx gas estimation', error.message) + throw error + } +} + +type TransactionExecutionEstimationProps = { + txData: string + safeAddress: string + txRecipient: string + txConfirmations?: List + txAmount: string + operation: number + gasPrice: string + gasToken: string + refundReceiver: string // Address of receiver of gas payment (or 0 if tx.origin). + safeTxGas: number + from: string + approvalAndExecution?: boolean +} + +export const estimateGasForTransactionExecution = async ({ + safeAddress, + txRecipient, + txConfirmations, + txAmount, + txData, + operation, + gasPrice, + gasToken, + refundReceiver, + safeTxGas, + approvalAndExecution, +}: TransactionExecutionEstimationProps): Promise => { + const safeInstance = await getGnosisSafeInstanceAt(safeAddress) + try { + if (approvalAndExecution) { + console.info(`Estimating transaction success for execution & approval...`) + // @todo (agustin) once we solve the problem with the preApprovingOwner, we need to use the method bellow (execTransaction) with sigs = generateSignaturesFromTxConfirmations(txConfirmations,from) + const gasEstimation = await estimateGasForTransactionCreation( + safeAddress, + txData, + txRecipient, + txAmount, + operation, + ) + console.info(`Gas estimation successfully finished with gas amount: ${gasEstimation}`) + return gasEstimation + } + const sigs = generateSignaturesFromTxConfirmations(txConfirmations) + console.info(`Estimating transaction success for with gas amount: ${safeTxGas}...`) + await safeInstance.methods + .execTransaction(txRecipient, txAmount, txData, operation, safeTxGas, 0, gasPrice, gasToken, refundReceiver, sigs) + .call() + + console.info(`Gas estimation successfully finished with gas amount: ${safeTxGas}`) + return safeTxGas + } catch (error) { + throw new Error(`Gas estimation failed with gas amount: ${safeTxGas}`) + } +} + +type TransactionApprovalEstimationProps = { + txData: string + safeAddress: string + txRecipient: string + txAmount: string + operation: number + from: string + isOffChainSignature: boolean +} + +export const estimateGasForTransactionApproval = async ({ + safeAddress, + txRecipient, + txAmount, + txData, + operation, + from, + isOffChainSignature, +}: TransactionApprovalEstimationProps): Promise => { + if (isOffChainSignature) { return 0 } + + const safeInstance = await getGnosisSafeInstanceAt(safeAddress) + + const nonce = await safeInstance.methods.nonce().call() + const txHash = await safeInstance.methods + .getTransactionHash(txRecipient, txAmount, txData, operation, 0, 0, 0, ZERO_ADDRESS, ZERO_ADDRESS, nonce) + .call({ + from, + }) + const approveTransactionTxData = await safeInstance.methods.approveHash(txHash).encodeABI() + return calculateGasOf(approveTransactionTxData, from, safeAddress) } diff --git a/src/logic/safe/transactions/send.ts b/src/logic/safe/transactions/send.ts index 67bbab2f1a..289c8fe355 100644 --- a/src/logic/safe/transactions/send.ts +++ b/src/logic/safe/transactions/send.ts @@ -30,10 +30,7 @@ export const getTransactionHash = async ({ return txHash } -export const getApprovalTransaction = async ( - safeInstance: GnosisSafe, - txHash: string, -): Promise> => { +export const getApprovalTransaction = (safeInstance: GnosisSafe, txHash: string): NonPayableTransactionObject => { try { return safeInstance.methods.approveHash(txHash) } catch (err) { diff --git a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/Review/index.tsx b/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/Review/index.tsx index 4199510a3b..532bf33b38 100644 --- a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/Review/index.tsx +++ b/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/Review/index.tsx @@ -22,7 +22,7 @@ import createTransaction from 'src/logic/safe/store/actions/createTransaction' import { safeParamAddressFromStateSelector } from 'src/logic/safe/store/selectors' import { generateFormFieldKey, getValueFromTxInputs } from '../utils' import { useEstimateTransactionGas } from 'src/logic/hooks/useEstimateTransactionGas' -import { TransactionFailText } from 'src/components/TransactionFailText' +import { TransactionFees } from 'src/components/TransactionsFees' const useStyles = makeStyles(styles) @@ -52,7 +52,13 @@ const ContractInteractionReview = ({ onClose, onPrev, tx }: Props): React.ReactE txAmount: string }>({ txData: '', txAmount: '', txRecipient: '' }) - const { gasCostFormatted, txEstimationExecutionStatus, isExecution } = useEstimateTransactionGas({ + const { + gasCostFormatted, + txEstimationExecutionStatus, + isExecution, + isOffChainSignature, + isCreation, + } = useEstimateTransactionGas({ txRecipient: txParameters?.txRecipient, txAmount: txParameters?.txAmount, txData: txParameters?.txData, @@ -156,10 +162,13 @@ const ContractInteractionReview = ({ onClose, onPrev, tx }: Props): React.ReactE - - {`You're about to create a transaction and will have to confirm it with your currently connected wallet. Make sure you have ${gasCostFormatted} (fee price) ${nativeCoin.name} in this wallet to fund this confirmation.`} - - + diff --git a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/ReviewCustomTx/index.tsx b/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/ReviewCustomTx/index.tsx index c8a92d7c3b..cda1aead48 100644 --- a/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/ReviewCustomTx/index.tsx +++ b/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/ReviewCustomTx/index.tsx @@ -28,7 +28,7 @@ import ArrowDown from '../../assets/arrow-down.svg' import { styles } from './style' import { ExplorerButton } from '@gnosis.pm/safe-react-components' import { useEstimateTransactionGas } from 'src/logic/hooks/useEstimateTransactionGas' -import { TransactionFailText } from 'src/components/TransactionFailText' +import { TransactionFees } from 'src/components/TransactionsFees' export type CustomTx = { contractAddress?: string @@ -51,7 +51,13 @@ const ReviewCustomTx = ({ onClose, onPrev, tx }: Props): React.ReactElement => { const dispatch = useDispatch() const safeAddress = useSelector(safeParamAddressFromStateSelector) - const { gasCostFormatted, txEstimationExecutionStatus, isExecution } = useEstimateTransactionGas({ + const { + gasCostFormatted, + txEstimationExecutionStatus, + isExecution, + isCreation, + isOffChainSignature, + } = useEstimateTransactionGas({ txRecipient: tx.contractAddress as string, txData: tx.data ? tx.data.trim() : '', txAmount: tx.value ? toTokenUnit(tx.value, nativeCoin.decimals) : '0', @@ -145,10 +151,13 @@ const ReviewCustomTx = ({ onClose, onPrev, tx }: Props): React.ReactElement => { - - {`You're about to create a transaction and will have to confirm it with your currently connected wallet. Make sure you have ${gasCostFormatted} (fee price) ${nativeCoin.name} in this wallet to fund this confirmation.`} - - + diff --git a/src/routes/safe/components/Balances/SendModal/screens/ReviewCollectible/index.tsx b/src/routes/safe/components/Balances/SendModal/screens/ReviewCollectible/index.tsx index 68c49da891..63d4c942de 100644 --- a/src/routes/safe/components/Balances/SendModal/screens/ReviewCollectible/index.tsx +++ b/src/routes/safe/components/Balances/SendModal/screens/ReviewCollectible/index.tsx @@ -3,7 +3,7 @@ import { useDispatch, useSelector } from 'react-redux' import IconButton from '@material-ui/core/IconButton' import { makeStyles } from '@material-ui/core/styles' import Close from '@material-ui/icons/Close' -import { getExplorerInfo, getNetworkInfo } from 'src/config' +import { getExplorerInfo } from 'src/config' import CopyBtn from 'src/components/CopyBtn' import Identicon from 'src/components/Identicon' import Block from 'src/components/layout/Block' @@ -28,9 +28,7 @@ import ArrowDown from '../assets/arrow-down.svg' import { styles } from './style' import { ExplorerButton } from '@gnosis.pm/safe-react-components' import { useEstimateTransactionGas } from 'src/logic/hooks/useEstimateTransactionGas' -import { TransactionFailText } from 'src/components/TransactionFailText' - -const { nativeCoin } = getNetworkInfo() +import { TransactionFees } from 'src/components/TransactionsFees' const useStyles = makeStyles(styles) @@ -58,7 +56,13 @@ const ReviewCollectible = ({ onClose, onPrev, tx }: Props): React.ReactElement = ) const [data, setData] = useState('') - const { gasCostFormatted, txEstimationExecutionStatus, isExecution } = useEstimateTransactionGas({ + const { + gasCostFormatted, + txEstimationExecutionStatus, + isExecution, + isOffChainSignature, + isCreation, + } = useEstimateTransactionGas({ txData: data, txRecipient: tx.recipientAddress, }) @@ -160,10 +164,13 @@ const ReviewCollectible = ({ onClose, onPrev, tx }: Props): React.ReactElement = )} - - {`You're about to create a transaction and will have to confirm it with your currently connected wallet. Make sure you have ${gasCostFormatted} (fee price) ${nativeCoin.name} in this wallet to fund this confirmation.`} - - + diff --git a/src/routes/safe/components/Balances/SendModal/screens/ReviewTx/index.tsx b/src/routes/safe/components/Balances/SendModal/screens/ReviewTx/index.tsx index af1cd966a7..8dbb6b3cf2 100644 --- a/src/routes/safe/components/Balances/SendModal/screens/ReviewTx/index.tsx +++ b/src/routes/safe/components/Balances/SendModal/screens/ReviewTx/index.tsx @@ -36,7 +36,7 @@ import { ExplorerButton } from '@gnosis.pm/safe-react-components' import { TokenProps } from 'src/logic/tokens/store/model/token' import { RecordOf } from 'immutable' import { useEstimateTransactionGas } from 'src/logic/hooks/useEstimateTransactionGas' -import { TransactionFailText } from 'src/components/TransactionFailText' +import { TransactionFees } from 'src/components/TransactionsFees' const useStyles = makeStyles(styles) const { nativeCoin } = getNetworkInfo() @@ -111,7 +111,13 @@ const ReviewTx = ({ onClose, onPrev, tx }: ReviewTxProps): React.ReactElement => const txAmount = useTxAmount(tx, isSendingNativeToken, txToken) const data = useTxData(isSendingNativeToken, txAmount, tx.recipientAddress, txToken) - const { gasCostFormatted, txEstimationExecutionStatus, isExecution } = useEstimateTransactionGas({ + const { + gasCostFormatted, + txEstimationExecutionStatus, + isExecution, + isCreation, + isOffChainSignature, + } = useEstimateTransactionGas({ txData: data, txRecipient, }) @@ -218,10 +224,13 @@ const ReviewTx = ({ onClose, onPrev, tx }: ReviewTxProps): React.ReactElement => - - {`You're about to create a transaction and will have to confirm it with your currently connected wallet. Make sure you have ${gasCostFormatted} (fee price) ${nativeCoin.name} in this wallet to fund this confirmation.`} - - + diff --git a/src/routes/safe/components/Settings/ManageOwners/AddOwnerModal/screens/Review/index.tsx b/src/routes/safe/components/Settings/ManageOwners/AddOwnerModal/screens/Review/index.tsx index 8c1ba1ae73..9329feebaf 100644 --- a/src/routes/safe/components/Settings/ManageOwners/AddOwnerModal/screens/Review/index.tsx +++ b/src/routes/safe/components/Settings/ManageOwners/AddOwnerModal/screens/Review/index.tsx @@ -4,7 +4,7 @@ import Close from '@material-ui/icons/Close' import classNames from 'classnames' import React, { useEffect, useState } from 'react' import { useSelector } from 'react-redux' -import { getExplorerInfo, getNetworkInfo } from 'src/config' +import { getExplorerInfo } from 'src/config' import CopyBtn from 'src/components/CopyBtn' import Identicon from 'src/components/Identicon' import Block from 'src/components/layout/Block' @@ -19,12 +19,10 @@ import { safeNameSelector, safeOwnersSelector, safeParamAddressFromStateSelector import { styles } from './style' import { ExplorerButton } from '@gnosis.pm/safe-react-components' import { useEstimateTransactionGas } from 'src/logic/hooks/useEstimateTransactionGas' -import { TransactionFailText } from 'src/components/TransactionFailText' +import { TransactionFees } from 'src/components/TransactionsFees' export const ADD_OWNER_SUBMIT_BTN_TEST_ID = 'add-owner-submit-btn' -const { nativeCoin } = getNetworkInfo() - const useStyles = makeStyles(styles) type ReviewAddOwnerProps = { @@ -45,7 +43,13 @@ export const ReviewAddOwner = ({ onClickBack, onClose, onSubmit, values }: Revie const safeName = useSelector(safeNameSelector) const owners = useSelector(safeOwnersSelector) - const { gasCostFormatted, txEstimationExecutionStatus, isExecution } = useEstimateTransactionGas({ + const { + gasCostFormatted, + txEstimationExecutionStatus, + isExecution, + isOffChainSignature, + isCreation, + } = useEstimateTransactionGas({ txData: data, txRecipient: safeAddress, }) @@ -176,12 +180,13 @@ export const ReviewAddOwner = ({ onClickBack, onClose, onSubmit, values }: Revie - - You're about to create a transaction and will have to confirm it with your currently connected wallet. -
- {`Make sure you have ${gasCostFormatted} (fee price) ${nativeCoin.name} in this wallet to fund this confirmation.`} -
- +
diff --git a/src/routes/safe/components/Settings/ManageOwners/RemoveOwnerModal/screens/Review/index.tsx b/src/routes/safe/components/Settings/ManageOwners/RemoveOwnerModal/screens/Review/index.tsx index 3391f3fa09..191e7c89a0 100644 --- a/src/routes/safe/components/Settings/ManageOwners/RemoveOwnerModal/screens/Review/index.tsx +++ b/src/routes/safe/components/Settings/ManageOwners/RemoveOwnerModal/screens/Review/index.tsx @@ -4,7 +4,7 @@ import Close from '@material-ui/icons/Close' import classNames from 'classnames' import React, { useEffect, useState } from 'react' import { useSelector } from 'react-redux' -import { getExplorerInfo, getNetworkInfo } from 'src/config' +import { getExplorerInfo } from 'src/config' import CopyBtn from 'src/components/CopyBtn' import Identicon from 'src/components/Identicon' import Block from 'src/components/layout/Block' @@ -22,12 +22,10 @@ import { getOwnersWithNameFromAddressBook } from 'src/logic/addressBook/utils' import { List } from 'immutable' import { addressBookSelector } from 'src/logic/addressBook/store/selectors' import { useEstimateTransactionGas } from 'src/logic/hooks/useEstimateTransactionGas' -import { TransactionFailText } from 'src/components/TransactionFailText' +import { TransactionFees } from 'src/components/TransactionsFees' export const REMOVE_OWNER_REVIEW_BTN_TEST_ID = 'remove-owner-review-btn' -const { nativeCoin } = getNetworkInfo() - const useStyles = makeStyles(styles) type ReviewRemoveOwnerProps = { @@ -55,7 +53,13 @@ export const ReviewRemoveOwnerModal = ({ const addressBook = useSelector(addressBookSelector) const ownersWithAddressBookName = owners ? getOwnersWithNameFromAddressBook(addressBook, owners) : List([]) - const { gasCostFormatted, txEstimationExecutionStatus, isExecution } = useEstimateTransactionGas({ + const { + gasCostFormatted, + txEstimationExecutionStatus, + isExecution, + isCreation, + isOffChainSignature, + } = useEstimateTransactionGas({ txData: data, txRecipient: safeAddress, }) @@ -194,12 +198,13 @@ export const ReviewRemoveOwnerModal = ({ - - You're about to create a transaction and will have to confirm it with your currently connected wallet. -
- {`Make sure you have ${gasCostFormatted} (fee price) ${nativeCoin.name} in this wallet to fund this confirmation.`} -
- +
diff --git a/src/routes/safe/components/Settings/ManageOwners/ReplaceOwnerModal/screens/Review/index.tsx b/src/routes/safe/components/Settings/ManageOwners/ReplaceOwnerModal/screens/Review/index.tsx index a93382399d..a56a069f6a 100644 --- a/src/routes/safe/components/Settings/ManageOwners/ReplaceOwnerModal/screens/Review/index.tsx +++ b/src/routes/safe/components/Settings/ManageOwners/ReplaceOwnerModal/screens/Review/index.tsx @@ -7,7 +7,7 @@ import { useSelector } from 'react-redux' import { List } from 'immutable' import { ExplorerButton } from '@gnosis.pm/safe-react-components' -import { getExplorerInfo, getNetworkInfo } from 'src/config' +import { getExplorerInfo } from 'src/config' import CopyBtn from 'src/components/CopyBtn' import Identicon from 'src/components/Identicon' import Block from 'src/components/layout/Block' @@ -28,12 +28,10 @@ import { addressBookSelector } from 'src/logic/addressBook/store/selectors' import { styles } from './style' import { useEstimateTransactionGas } from 'src/logic/hooks/useEstimateTransactionGas' -import { TransactionFailText } from 'src/components/TransactionFailText' +import { TransactionFees } from 'src/components/TransactionsFees' export const REPLACE_OWNER_SUBMIT_BTN_TEST_ID = 'replace-owner-submit-btn' -const { nativeCoin } = getNetworkInfo() - const useStyles = makeStyles(styles) type ReplaceOwnerProps = { @@ -65,7 +63,13 @@ export const ReviewReplaceOwnerModal = ({ const addressBook = useSelector(addressBookSelector) const ownersWithAddressBookName = owners ? getOwnersWithNameFromAddressBook(addressBook, owners) : List([]) - const { gasCostFormatted, txEstimationExecutionStatus, isExecution } = useEstimateTransactionGas({ + const { + gasCostFormatted, + txEstimationExecutionStatus, + isExecution, + isCreation, + isOffChainSignature, + } = useEstimateTransactionGas({ txData: data, txRecipient: safeAddress, }) @@ -218,12 +222,13 @@ export const ReviewReplaceOwnerModal = ({ - - You're about to create a transaction and will have to confirm it with your currently connected wallet. -
- {`Make sure you have ${gasCostFormatted} (fee price) ${nativeCoin.name} in this wallet to fund this confirmation.`} -
- +
diff --git a/src/routes/safe/components/Settings/ThresholdSettings/ChangeThreshold/index.tsx b/src/routes/safe/components/Settings/ThresholdSettings/ChangeThreshold/index.tsx index 3b838f705f..d28cbb5dbe 100644 --- a/src/routes/safe/components/Settings/ThresholdSettings/ChangeThreshold/index.tsx +++ b/src/routes/safe/components/Settings/ThresholdSettings/ChangeThreshold/index.tsx @@ -3,7 +3,6 @@ import MenuItem from '@material-ui/core/MenuItem' import { makeStyles } from '@material-ui/core/styles' import Close from '@material-ui/icons/Close' import React, { useEffect, useState } from 'react' -import { getNetworkInfo } from 'src/config' import { styles } from './style' import Field from 'src/components/forms/Field' @@ -21,12 +20,10 @@ import { SafeOwner } from 'src/logic/safe/store/models/safe' import { List } from 'immutable' import { useEstimateTransactionGas } from 'src/logic/hooks/useEstimateTransactionGas' -import { TransactionFailText } from 'src/components/TransactionFailText' +import { TransactionFees } from 'src/components/TransactionsFees' const THRESHOLD_FIELD_NAME = 'threshold' -const { nativeCoin } = getNetworkInfo() - const useStyles = makeStyles(styles) type ChangeThresholdModalProps = { @@ -47,7 +44,13 @@ export const ChangeThresholdModal = ({ const classes = useStyles() const [data, setData] = useState('') - const { gasCostFormatted, txEstimationExecutionStatus } = useEstimateTransactionGas({ + const { + gasCostFormatted, + txEstimationExecutionStatus, + isCreation, + isExecution, + isOffChainSignature, + } = useEstimateTransactionGas({ txData: data, txRecipient: safeAddress, }) @@ -124,10 +127,13 @@ export const ChangeThresholdModal = ({ - - {`You're about to create a transaction and will have to confirm it with your currently connected wallet. Make sure you have ${gasCostFormatted} (fee price) ${nativeCoin.name} in this wallet to fund this confirmation.`} - - + diff --git a/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/ApproveTxModal/index.tsx b/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/ApproveTxModal/index.tsx index edc6376799..88c5c99437 100644 --- a/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/ApproveTxModal/index.tsx +++ b/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/ApproveTxModal/index.tsx @@ -5,7 +5,6 @@ import { makeStyles } from '@material-ui/core/styles' import Close from '@material-ui/icons/Close' import React, { useState } from 'react' import { useDispatch, useSelector } from 'react-redux' -import { getNetworkInfo } from 'src/config' import { styles } from './style' @@ -18,12 +17,12 @@ import Paragraph from 'src/components/layout/Paragraph' import Row from 'src/components/layout/Row' import { TX_NOTIFICATION_TYPES } from 'src/logic/safe/transactions' import { userAccountSelector } from 'src/logic/wallets/store/selectors' -import processTransaction from 'src/logic/safe/store/actions/processTransaction' +import { processTransaction } from 'src/logic/safe/store/actions/processTransaction' import { safeParamAddressFromStateSelector, safeThresholdSelector } from 'src/logic/safe/store/selectors' import { Transaction } from 'src/logic/safe/store/models/types/transaction' import { useEstimateTransactionGas } from 'src/logic/hooks/useEstimateTransactionGas' -import { TransactionFailText } from 'src/components/TransactionFailText' +import { TransactionFees } from 'src/components/TransactionsFees' const useStyles = makeStyles(styles) @@ -61,9 +60,8 @@ type Props = { thresholdReached: boolean tx: Transaction } -const { nativeCoin } = getNetworkInfo() -const ApproveTxModal = ({ +export const ApproveTxModal = ({ canExecute, isCancelTx = false, isOpen, @@ -81,12 +79,19 @@ const ApproveTxModal = ({ const oneConfirmationLeft = !thresholdReached && tx.confirmations.size + 1 === threshold const isTheTxReadyToBeExecuted = oneConfirmationLeft ? true : thresholdReached - const { gasCostFormatted, txEstimationExecutionStatus, isExecution } = useEstimateTransactionGas({ + const { + gasCostFormatted, + txEstimationExecutionStatus, + isExecution, + isOffChainSignature, + isCreation, + } = useEstimateTransactionGas({ txRecipient: tx.recipient, txData: tx.data || '', - txConfirmations: tx.confirmations.size, + txConfirmations: tx.confirmations, txAmount: tx.value, preApprovingOwner: approveAndExecute ? userAddress : undefined, + safeTxGas: tx.safeTxGas, operation: tx.operation, }) @@ -141,16 +146,13 @@ const ApproveTxModal = ({ )}
- - - {`You're about to ${ - approveAndExecute ? 'execute' : 'approve' - } a transaction and will have to confirm it with your currently connected wallet. Make sure you have ${gasCostFormatted} (fee price) ${ - nativeCoin.name - } in this wallet to fund this confirmation.`} - - - +