Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Counterfactual] Show notification when submitting counterfactual tx #3226

Merged
merged 10 commits into from
Feb 12, 2024
19 changes: 13 additions & 6 deletions src/features/counterfactual/ActivateAccount.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import ErrorMessage from '@/components/tx/ErrorMessage'
import { ExecutionMethod, ExecutionMethodSelector } from '@/components/tx/ExecutionMethodSelector'
import useDeployGasLimit from '@/features/counterfactual/hooks/useDeployGasLimit'
import { removeUndeployedSafe, selectUndeployedSafe } from '@/features/counterfactual/store/undeployedSafesSlice'
import { showSubmitNotification } from '@/features/counterfactual/utils'
import useChainId from '@/hooks/useChainId'
import { useCurrentChain } from '@/hooks/useChains'
import useGasPrice, { getTotalFeeFormatted } from '@/hooks/useGasPrice'
Expand Down Expand Up @@ -44,12 +45,12 @@ const useActivateAccount = () => {
? {
maxFeePerGas: maxFeePerGas?.toString(),
maxPriorityFeePerGas: maxPriorityFeePerGas?.toString(),
gasLimit: gasLimit?.toString(),
gasLimit: gasLimit?.totalGas.toString(),
}
: { gasPrice: maxFeePerGas?.toString(), gasLimit: gasLimit?.toString() }
: { gasPrice: maxFeePerGas?.toString(), gasLimit: gasLimit?.totalGas.toString() }

const totalFee = getTotalFeeFormatted(maxFeePerGas, maxPriorityFeePerGas, gasLimit, chain)
const walletCanPay = useWalletCanPay({ gasLimit, maxFeePerGas, maxPriorityFeePerGas })
const totalFee = getTotalFeeFormatted(maxFeePerGas, maxPriorityFeePerGas, gasLimit?.totalGas, chain)
const walletCanPay = useWalletCanPay({ gasLimit: gasLimit?.totalGas, maxFeePerGas, maxPriorityFeePerGas })

return { options, totalFee, walletCanPay }
}
Expand Down Expand Up @@ -83,6 +84,11 @@ const ActivateAccountFlow = () => {
dispatch(removeUndeployedSafe({ chainId, address: safeAddress }))
}

const onSubmit = (txHash?: string) => {
showSubmitNotification(dispatch, chain, txHash)
setTxFlow(undefined)
}

const createSafe = async () => {
if (!provider || !chain) return

Expand All @@ -98,6 +104,8 @@ const ActivateAccountFlow = () => {
Number(undeployedSafe.safeDeploymentConfig?.saltNonce!),
)

onSubmit()

waitForCreateSafeTx(taskId, (status) => {
if (status === SafeCreationStatus.SUCCESS) {
onSuccess()
Expand All @@ -108,6 +116,7 @@ const ActivateAccountFlow = () => {
safeAccountConfig: undeployedSafe.safeAccountConfig,
saltNonce: undeployedSafe.safeDeploymentConfig?.saltNonce,
options,
callback: onSubmit,
})
onSuccess()
}
Expand All @@ -117,8 +126,6 @@ const ActivateAccountFlow = () => {
setSubmitError(err)
return
}

setTxFlow(undefined)
}

const submitDisabled = !isSubmittable
Expand Down
33 changes: 29 additions & 4 deletions src/features/counterfactual/CounterfactualForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,15 @@ import useDeployGasLimit from '@/features/counterfactual/hooks/useDeployGasLimit
import { removeUndeployedSafe } from '@/features/counterfactual/store/undeployedSafesSlice'
import { deploySafeAndExecuteTx } from '@/features/counterfactual/utils'
import useChainId from '@/hooks/useChainId'
import { getTotalFeeFormatted } from '@/hooks/useGasPrice'
import useSafeInfo from '@/hooks/useSafeInfo'
import useWalletCanPay from '@/hooks/useWalletCanPay'
import useOnboard from '@/hooks/wallets/useOnboard'
import useWallet from '@/hooks/wallets/useWallet'
import { useAppDispatch } from '@/store'
import madProps from '@/utils/mad-props'
import React, { type ReactElement, type SyntheticEvent, useContext, useState } from 'react'
import { CircularProgress, Box, Button, CardActions, Divider } from '@mui/material'
import { CircularProgress, Box, Button, CardActions, Divider, Alert } from '@mui/material'
import classNames from 'classnames'

import ErrorMessage from '@/components/tx/ErrorMessage'
Expand Down Expand Up @@ -46,6 +47,7 @@ export const CounterfactualForm = ({
}): ReactElement => {
const wallet = useWallet()
const onboard = useOnboard()
const chain = useCurrentChain()
const chainId = useChainId()
const dispatch = useAppDispatch()
const { safeAddress } = useSafeInfo()
Expand All @@ -61,7 +63,7 @@ export const CounterfactualForm = ({

// Estimate gas limit
const { gasLimit, gasLimitError } = useDeployGasLimit(safeTx)
const [advancedParams, setAdvancedParams] = useAdvancedParams(gasLimit)
const [advancedParams, setAdvancedParams] = useAdvancedParams(gasLimit?.totalGas)

// On modal submit
const handleSubmit = async (e: SyntheticEvent) => {
Expand Down Expand Up @@ -91,12 +93,11 @@ export const CounterfactualForm = ({
return
}

// TODO: Show a success or status screen
setTxFlow(undefined)
}

const walletCanPay = useWalletCanPay({
gasLimit,
gasLimit: gasLimit?.totalGas,
maxFeePerGas: advancedParams.maxFeePerGas,
maxPriorityFeePerGas: advancedParams.maxPriorityFeePerGas,
})
Expand All @@ -113,6 +114,30 @@ export const CounterfactualForm = ({
return (
<>
<form onSubmit={handleSubmit}>
<Alert severity="info" sx={{ mb: 2 }}>
Executing this transaction will also activate your account. Additional network fees will apply. Base fee is{' '}
<strong>
{getTotalFeeFormatted(
advancedParams.maxFeePerGas,
advancedParams.maxPriorityFeePerGas,
BigInt(gasLimit?.baseGas || '0') + BigInt(gasLimit?.safeTxGas || '0'),
chain,
)}{' '}
{chain?.nativeCurrency.symbol}
</strong>
, activation fee is{' '}
usame-algan marked this conversation as resolved.
Show resolved Hide resolved
<strong>
{getTotalFeeFormatted(
advancedParams.maxFeePerGas,
advancedParams.maxPriorityFeePerGas,
BigInt(gasLimit?.safeDeploymentGas || '0'),
chain,
)}{' '}
{chain?.nativeCurrency.symbol}
</strong>
. This is an estimation and the final cost can be higher.
</Alert>

<div className={classNames(css.params)}>
<AdvancedParams
willExecute
Expand Down
15 changes: 12 additions & 3 deletions src/features/counterfactual/hooks/useDeployGasLimit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,24 +21,33 @@ const getExtraGasForSafety = (safeTx?: SafeTransaction): bigint => {
: 0n
}

type DeployGasLimitProps = {
baseGas: string
safeTxGas: string
safeDeploymentGas: string
totalGas: bigint
}

const useDeployGasLimit = (safeTx?: SafeTransaction) => {
const onboard = useOnboard()
const chainId = useChainId()

const [gasLimit, gasLimitError, gasLimitLoading] = useAsync<bigint | undefined>(async () => {
const [gasLimit, gasLimitError, gasLimitLoading] = useAsync<DeployGasLimitProps | undefined>(async () => {
if (!onboard) return

const sdk = await getSafeSDKWithSigner(onboard, chainId)

const extraGasForSafety = getExtraGasForSafety(safeTx)

const [gas, safeTxGas, safeDeploymentGas] = await Promise.all([
const [baseGas, safeTxGas, safeDeploymentGas] = await Promise.all([
safeTx ? estimateTxBaseGas(sdk, safeTx) : '0',
safeTx ? estimateSafeTxGas(sdk, safeTx) : '0',
estimateSafeDeploymentGas(sdk),
])

return BigInt(gas) + BigInt(safeTxGas) + BigInt(safeDeploymentGas) + extraGasForSafety
const totalGas = BigInt(baseGas) + BigInt(safeTxGas) + BigInt(safeDeploymentGas) + extraGasForSafety
usame-algan marked this conversation as resolved.
Show resolved Hide resolved

return { baseGas, safeTxGas, safeDeploymentGas, totalGas }
}, [onboard, chainId, safeTx])

return { gasLimit, gasLimitError, gasLimitLoading }
Expand Down
25 changes: 21 additions & 4 deletions src/features/counterfactual/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ import { txDispatch, TxEvent } from '@/services/tx/txEvents'
import type { AppDispatch } from '@/store'
import { addOrUpdateSafe } from '@/store/addedSafesSlice'
import { upsertAddressBookEntry } from '@/store/addressBookSlice'
import { showNotification } from '@/store/notificationsSlice'
import { defaultSafeInfo } from '@/store/safeInfoSlice'
import { getBlockExplorerLink } from '@/utils/chains'
import { didReprice, didRevert, type EthersError } from '@/utils/ethers-utils'
import { assertOnboard, assertTx, assertWallet } from '@/utils/helpers'
import type { DeploySafeProps, PredictedSafeProps } from '@safe-global/protocol-kit'
Expand Down Expand Up @@ -65,8 +67,11 @@ export const dispatchTxExecutionAndDeploySafe = async (

const deploymentTx = await sdkUnchecked.wrapSafeTransactionIntoDeploymentBatch(signedTx, txOptions)

// We need to estimate the actual gasLimit after the user has signed since it is more accurate than what useDeployGasLimit returns
const gas = await signer.estimateGas({ data: deploymentTx.data, value: deploymentTx.value, to: deploymentTx.to })
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we compare this to the estimation and give a prompt / warning if the difference is more than e.g. 50k gas?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the idea but this would only happen after the user has pressed Sign in their wallet. I am not sure how helpful it would be to see a warning somewhere in our UI at that point so I would leave it out for now and hope there are no substantial differences between those first transactions. Worst case we have the info text to warn the user about higher cost on execution.


// @ts-ignore TODO: Check why TransactionResponse type doesn't work
result = await signer.sendTransaction(deploymentTx)
result = await signer.sendTransaction({ deploymentTx, gasLimit: gas })
txDispatch(TxEvent.EXECUTING, eventParams)
} catch (error) {
txDispatch(TxEvent.FAILED, { ...eventParams, error: asError(error) })
Expand All @@ -84,20 +89,19 @@ export const dispatchTxExecutionAndDeploySafe = async (
txDispatch(TxEvent.REVERTED, { ...eventParams, error: new Error('Transaction reverted by EVM') })
} else {
txDispatch(TxEvent.PROCESSED, { ...eventParams, safeAddress })
onSuccess?.()
}
})
.catch((err) => {
const error = err as EthersError

if (didReprice(error)) {
txDispatch(TxEvent.PROCESSED, { ...eventParams, safeAddress })
onSuccess?.()
} else {
txDispatch(TxEvent.FAILED, { ...eventParams, error: asError(error) })
}
})
.finally(() => {
onSuccess?.()
})

return result!.hash
}
Expand Down Expand Up @@ -178,3 +182,16 @@ export const createCounterfactualSafe = (
)
router.push({ pathname: AppRoutes.home, query: { safe: `${chain.shortName}:${safeAddress}` } })
}

export const showSubmitNotification = (dispatch: AppDispatch, chain?: ChainInfo, txHash?: string) => {
const link = chain && txHash ? getBlockExplorerLink(chain, txHash) : undefined
dispatch(
showNotification({
variant: 'info',
groupKey: 'cf-activate-account',
message: 'Transaction submitted',
detailedMessage: 'Your Safe Account will be deployed onchain after the transaction is executed.',
link: link ? { href: link.href, title: link.title } : undefined,
}),
)
}
Loading