mirror of
https://github.com/status-im/safe-react.git
synced 2025-01-11 18:44:07 +00:00
(Fix) Estimate gas for wallet connect (#1806)
* Guard for empty result * Type TextField * Fix warning of InputAdornment in SendFunds modal * Re-enable gas estimation for wallet connect * Replace web3.call on parseRequiredTxGasResponse with axios post to infura * Adds estimateGasWithInfura and estimateGasWithWeb3Provider for changing the estimation method if we are in a non-infura-supported network * Revert calculateMinimumGasForTransaction change to leave the change for the already-open pr * Renames estimateGasWithInfura with estimateGasWithRPCCall Replaces web3 with web3ReadOnly in estimateGasWithRPCCall Co-authored-by: Mati Dastugue <matias.dastugue@altoros.com> Co-authored-by: Fernando <fernando.greco@gmail.com> Co-authored-by: Daniel Sanchez <daniel.sanchez@gnosis.pm>
This commit is contained in:
parent
3d43db039b
commit
758fc3c0c0
@ -3,10 +3,6 @@ import { EstimationStatus } from 'src/logic/hooks/useEstimateTransactionGas'
|
|||||||
import Paragraph from 'src/components/layout/Paragraph'
|
import Paragraph from 'src/components/layout/Paragraph'
|
||||||
import { getNetworkInfo } from 'src/config'
|
import { getNetworkInfo } from 'src/config'
|
||||||
import { TransactionFailText } from 'src/components/TransactionFailText'
|
import { TransactionFailText } from 'src/components/TransactionFailText'
|
||||||
import { useSelector } from 'react-redux'
|
|
||||||
import { providerNameSelector } from 'src/logic/wallets/store/selectors'
|
|
||||||
import { sameString } from 'src/utils/strings'
|
|
||||||
import { WALLETS } from 'src/config/networks/network.d'
|
|
||||||
|
|
||||||
type TransactionFailTextProps = {
|
type TransactionFailTextProps = {
|
||||||
txEstimationExecutionStatus: EstimationStatus
|
txEstimationExecutionStatus: EstimationStatus
|
||||||
@ -24,8 +20,6 @@ export const TransactionFees = ({
|
|||||||
isOffChainSignature,
|
isOffChainSignature,
|
||||||
txEstimationExecutionStatus,
|
txEstimationExecutionStatus,
|
||||||
}: TransactionFailTextProps): React.ReactElement | null => {
|
}: TransactionFailTextProps): React.ReactElement | null => {
|
||||||
const providerName = useSelector(providerNameSelector)
|
|
||||||
|
|
||||||
let transactionAction
|
let transactionAction
|
||||||
if (isCreation) {
|
if (isCreation) {
|
||||||
transactionAction = 'create'
|
transactionAction = 'create'
|
||||||
@ -35,11 +29,6 @@ export const TransactionFees = ({
|
|||||||
transactionAction = 'approve'
|
transactionAction = 'approve'
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME this should be removed when estimating with WalletConnect correctly
|
|
||||||
if (!providerName || sameString(providerName, WALLETS.WALLET_CONNECT)) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Paragraph>
|
<Paragraph>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import MuiTextField from '@material-ui/core/TextField'
|
import MuiTextField from '@material-ui/core/TextField'
|
||||||
import { withStyles } from '@material-ui/core/styles'
|
import { createStyles, makeStyles } from '@material-ui/core/styles'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
|
||||||
import { lg } from 'src/theme/variables'
|
import { lg } from 'src/theme/variables'
|
||||||
@ -10,18 +10,46 @@ const overflowStyle = {
|
|||||||
width: '100%',
|
width: '100%',
|
||||||
}
|
}
|
||||||
|
|
||||||
const styles = () => ({
|
const styles = () =>
|
||||||
|
createStyles({
|
||||||
root: {
|
root: {
|
||||||
paddingTop: lg,
|
paddingTop: lg,
|
||||||
paddingBottom: '12px',
|
paddingBottom: '12px',
|
||||||
lineHeight: 0,
|
lineHeight: 0,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
class TextField extends React.PureComponent<any> {
|
const useStyles = makeStyles(styles)
|
||||||
render() {
|
|
||||||
|
type Props = {
|
||||||
|
input: {
|
||||||
|
name: string
|
||||||
|
onChange?: () => void
|
||||||
|
value: string
|
||||||
|
placeholder: string
|
||||||
|
type: string
|
||||||
|
}
|
||||||
|
meta: {
|
||||||
|
touched?: boolean
|
||||||
|
pristine?: boolean
|
||||||
|
valid?: boolean
|
||||||
|
error?: string
|
||||||
|
modifiedSinceLastSubmit?: boolean
|
||||||
|
submitError?: boolean
|
||||||
|
active?: boolean
|
||||||
|
}
|
||||||
|
inputAdornment?: { endAdornment: React.ReactElement } | undefined
|
||||||
|
multiline: boolean
|
||||||
|
rows?: string
|
||||||
|
testId: string
|
||||||
|
text: string
|
||||||
|
disabled?: boolean
|
||||||
|
rowsMax?: number
|
||||||
|
className?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const TextField = (props: Props): React.ReactElement => {
|
||||||
const {
|
const {
|
||||||
classes,
|
|
||||||
input: { name, onChange, value, ...restInput },
|
input: { name, onChange, value, ...restInput },
|
||||||
inputAdornment,
|
inputAdornment,
|
||||||
meta,
|
meta,
|
||||||
@ -30,7 +58,8 @@ class TextField extends React.PureComponent<any> {
|
|||||||
testId,
|
testId,
|
||||||
text,
|
text,
|
||||||
...rest
|
...rest
|
||||||
} = this.props
|
} = props
|
||||||
|
const classes = useStyles()
|
||||||
const helperText = value ? text : undefined
|
const helperText = value ? text : undefined
|
||||||
const showError = (meta.touched || !meta.pristine) && !meta.valid
|
const showError = (meta.touched || !meta.pristine) && !meta.valid
|
||||||
const hasError = !!meta.error || (!meta.modifiedSinceLastSubmit && !!meta.submitError)
|
const hasError = !!meta.error || (!meta.modifiedSinceLastSubmit && !!meta.submitError)
|
||||||
@ -68,7 +97,6 @@ class TextField extends React.PureComponent<any> {
|
|||||||
{...rest}
|
{...rest}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withStyles(styles as any)(TextField)
|
export default TextField
|
||||||
|
@ -17,6 +17,8 @@ export const getNetworkId = (): ETHEREUM_NETWORK => ETHEREUM_NETWORK[NETWORK]
|
|||||||
|
|
||||||
export const getNetworkName = (): string => ETHEREUM_NETWORK[getNetworkId()]
|
export const getNetworkName = (): string => ETHEREUM_NETWORK[getNetworkId()]
|
||||||
|
|
||||||
|
export const usesInfuraRPC = [ETHEREUM_NETWORK.MAINNET, ETHEREUM_NETWORK.RINKEBY].includes(getNetworkId())
|
||||||
|
|
||||||
const getCurrentEnvironment = (): string => {
|
const getCurrentEnvironment = (): string => {
|
||||||
switch (NODE_ENV) {
|
switch (NODE_ENV) {
|
||||||
case 'test': {
|
case 'test': {
|
||||||
@ -76,15 +78,8 @@ export const getGasPrice = (): number | undefined => getConfig()?.gasPrice
|
|||||||
|
|
||||||
export const getGasPriceOracle = (): GasPriceOracle | undefined => getConfig()?.gasPriceOracle
|
export const getGasPriceOracle = (): GasPriceOracle | undefined => getConfig()?.gasPriceOracle
|
||||||
|
|
||||||
export const getRpcServiceUrl = (): string => {
|
export const getRpcServiceUrl = (): string =>
|
||||||
const usesInfuraRPC = [ETHEREUM_NETWORK.MAINNET, ETHEREUM_NETWORK.RINKEBY].includes(getNetworkId())
|
usesInfuraRPC ? `${getConfig().rpcServiceUrl}/${INFURA_TOKEN}` : getConfig().rpcServiceUrl
|
||||||
|
|
||||||
if (usesInfuraRPC) {
|
|
||||||
return `${getConfig().rpcServiceUrl}/${INFURA_TOKEN}`
|
|
||||||
}
|
|
||||||
|
|
||||||
return getConfig().rpcServiceUrl
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getSafeServiceBaseUrl = (safeAddress: string) => `${getTxServiceUrl()}/safes/${safeAddress}`
|
export const getSafeServiceBaseUrl = (safeAddress: string) => `${getTxServiceUrl()}/safes/${safeAddress}`
|
||||||
|
|
||||||
|
@ -22,7 +22,6 @@ import { Confirmation } from 'src/logic/safe/store/models/types/confirmation'
|
|||||||
import { checkIfOffChainSignatureIsPossible } from 'src/logic/safe/safeTxSigner'
|
import { checkIfOffChainSignatureIsPossible } from 'src/logic/safe/safeTxSigner'
|
||||||
import { ZERO_ADDRESS } from 'src/logic/wallets/ethAddresses'
|
import { ZERO_ADDRESS } from 'src/logic/wallets/ethAddresses'
|
||||||
import { sameString } from 'src/utils/strings'
|
import { sameString } from 'src/utils/strings'
|
||||||
import { WALLETS } from 'src/config/networks/network.d'
|
|
||||||
|
|
||||||
export enum EstimationStatus {
|
export enum EstimationStatus {
|
||||||
LOADING = 'LOADING',
|
LOADING = 'LOADING',
|
||||||
@ -177,10 +176,6 @@ export const useEstimateTransactionGas = ({
|
|||||||
if (!txData.length) {
|
if (!txData.length) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// FIXME this should be removed when estimating with WalletConnect correctly
|
|
||||||
if (!providerName || sameString(providerName, WALLETS.WALLET_CONNECT)) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
const isExecution = checkIfTxIsExecution(Number(threshold), preApprovingOwner, txConfirmations?.size, txType)
|
const isExecution = checkIfTxIsExecution(Number(threshold), preApprovingOwner, txConfirmations?.size, txType)
|
||||||
const isCreation = checkIfTxIsCreation(txConfirmations?.size || 0, txType)
|
const isCreation = checkIfTxIsCreation(txConfirmations?.size || 0, txType)
|
||||||
|
@ -1,12 +1,14 @@
|
|||||||
import { BigNumber } from 'bignumber.js'
|
import { BigNumber } from 'bignumber.js'
|
||||||
import { getGnosisSafeInstanceAt } from 'src/logic/contracts/safeContracts'
|
import { getGnosisSafeInstanceAt } from 'src/logic/contracts/safeContracts'
|
||||||
import { calculateGasOf, EMPTY_DATA } from 'src/logic/wallets/ethTransactions'
|
import { calculateGasOf, EMPTY_DATA } from 'src/logic/wallets/ethTransactions'
|
||||||
import { getWeb3 } from 'src/logic/wallets/getWeb3'
|
import { getWeb3, web3ReadOnly } from 'src/logic/wallets/getWeb3'
|
||||||
import { sameString } from 'src/utils/strings'
|
|
||||||
import { ZERO_ADDRESS } from 'src/logic/wallets/ethAddresses'
|
import { ZERO_ADDRESS } from 'src/logic/wallets/ethAddresses'
|
||||||
import { generateSignaturesFromTxConfirmations } from 'src/logic/safe/safeTxSigner'
|
import { generateSignaturesFromTxConfirmations } from 'src/logic/safe/safeTxSigner'
|
||||||
import { List } from 'immutable'
|
import { List } from 'immutable'
|
||||||
import { Confirmation } from 'src/logic/safe/store/models/types/confirmation'
|
import { Confirmation } from 'src/logic/safe/store/models/types/confirmation'
|
||||||
|
import axios from 'axios'
|
||||||
|
import { getRpcServiceUrl, usesInfuraRPC } from 'src/config'
|
||||||
|
import { sameString } from 'src/utils/strings'
|
||||||
|
|
||||||
// Receives the response data of the safe method requiredTxGas() and parses it to get the gas amount
|
// Receives the response data of the safe method requiredTxGas() and parses it to get the gas amount
|
||||||
const parseRequiredTxGasResponse = (data: string): number => {
|
const parseRequiredTxGasResponse = (data: string): number => {
|
||||||
@ -88,7 +90,7 @@ export const getDataFromNodeErrorMessage = (errorMessage: string): string | unde
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getGasEstimationTxResponse = async (txConfig: {
|
const estimateGasWithWeb3Provider = async (txConfig: {
|
||||||
to: string
|
to: string
|
||||||
from: string
|
from: string
|
||||||
data: string
|
data: string
|
||||||
@ -116,12 +118,56 @@ export const getGasEstimationTxResponse = async (txConfig: {
|
|||||||
|
|
||||||
return new BigNumber(estimationData.substring(138), 16).toNumber()
|
return new BigNumber(estimationData.substring(138), 16).toNumber()
|
||||||
}
|
}
|
||||||
|
|
||||||
// This will fail in case that we receive an EMPTY_DATA on the GETH node gas estimation (for version < v1.9.24 of geth nodes)
|
|
||||||
// We cannot throw this error above because it will be captured again on the catch block bellow
|
|
||||||
throw new Error('Error while estimating the gas required for tx')
|
throw new Error('Error while estimating the gas required for tx')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const estimateGasWithRPCCall = async (txConfig: {
|
||||||
|
to: string
|
||||||
|
from: string
|
||||||
|
data: string
|
||||||
|
gasPrice?: number
|
||||||
|
gas?: number
|
||||||
|
}): Promise<number> => {
|
||||||
|
try {
|
||||||
|
const { data } = await axios.post(getRpcServiceUrl(), {
|
||||||
|
jsonrpc: '2.0',
|
||||||
|
method: 'eth_call',
|
||||||
|
id: 1,
|
||||||
|
params: [
|
||||||
|
{
|
||||||
|
...txConfig,
|
||||||
|
gasPrice: web3ReadOnly.utils.toHex(txConfig.gasPrice || 0),
|
||||||
|
gas: txConfig.gas ? web3ReadOnly.utils.toHex(txConfig.gas) : undefined,
|
||||||
|
},
|
||||||
|
'latest',
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
const { error } = data
|
||||||
|
if (error?.data) {
|
||||||
|
return new BigNumber(data.error.data.substring(138), 16).toNumber()
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log('Gas estimation endpoint errored: ', error.message)
|
||||||
|
}
|
||||||
|
throw new Error('Error while estimating the gas required for tx')
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getGasEstimationTxResponse = async (txConfig: {
|
||||||
|
to: string
|
||||||
|
from: string
|
||||||
|
data: string
|
||||||
|
gasPrice?: number
|
||||||
|
gas?: number
|
||||||
|
}): Promise<number> => {
|
||||||
|
// If we are in a infura supported network we estimate using infura
|
||||||
|
if (usesInfuraRPC) {
|
||||||
|
return estimateGasWithRPCCall(txConfig)
|
||||||
|
}
|
||||||
|
// Otherwise we estimate using the current connected provider
|
||||||
|
return estimateGasWithWeb3Provider(txConfig)
|
||||||
|
}
|
||||||
|
|
||||||
const calculateMinimumGasForTransaction = async (
|
const calculateMinimumGasForTransaction = async (
|
||||||
additionalGasBatches: number[],
|
additionalGasBatches: number[],
|
||||||
safeAddress: string,
|
safeAddress: string,
|
||||||
|
@ -75,6 +75,10 @@ type SendFundsProps = {
|
|||||||
amount?: string
|
amount?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const InputAdornmentChildSymbol = ({ symbol }: { symbol?: string }): ReactElement => {
|
||||||
|
return <>{symbol}</>
|
||||||
|
}
|
||||||
|
|
||||||
const SendFunds = ({ onClose, onNext, recipientAddress, selectedToken = '', amount }: SendFundsProps): ReactElement => {
|
const SendFunds = ({ onClose, onNext, recipientAddress, selectedToken = '', amount }: SendFundsProps): ReactElement => {
|
||||||
const classes = useStyles()
|
const classes = useStyles()
|
||||||
const tokens = useSelector(extendedSafeTokensSelector)
|
const tokens = useSelector(extendedSafeTokensSelector)
|
||||||
@ -295,7 +299,11 @@ const SendFunds = ({ onClose, onNext, recipientAddress, selectedToken = '', amou
|
|||||||
<Field
|
<Field
|
||||||
component={TextField}
|
component={TextField}
|
||||||
inputAdornment={{
|
inputAdornment={{
|
||||||
endAdornment: <InputAdornment position="end">{selectedToken?.symbol}</InputAdornment>,
|
endAdornment: (
|
||||||
|
<InputAdornment position="end">
|
||||||
|
<InputAdornmentChildSymbol symbol={selectedToken?.symbol} />
|
||||||
|
</InputAdornment>
|
||||||
|
),
|
||||||
}}
|
}}
|
||||||
name="amount"
|
name="amount"
|
||||||
placeholder="Amount*"
|
placeholder="Amount*"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user