diff --git a/cypress/e2e/smoke/create_tx.cy.js b/cypress/e2e/smoke/create_tx.cy.js index 49b879bb2d..0cb795d874 100644 --- a/cypress/e2e/smoke/create_tx.cy.js +++ b/cypress/e2e/smoke/create_tx.cy.js @@ -102,7 +102,6 @@ describe('Queue a transaction on 1/N', () => { // Asserting the sponsored info is present cy.contains('Sponsored by').should('be.visible') - cy.contains('Gnosis Chain').should('be.visible') cy.get('span').contains('Estimated fee').next().should('have.css', 'text-decoration-line', 'line-through') cy.contains('Transactions per hour') diff --git a/src/components/dashboard/Relaying/index.tsx b/src/components/dashboard/Relaying/index.tsx index 7b746d2bea..07e955bb1b 100644 --- a/src/components/dashboard/Relaying/index.tsx +++ b/src/components/dashboard/Relaying/index.tsx @@ -8,10 +8,13 @@ import GasStationIcon from '@/public/images/common/gas-station.svg' import ExternalLink from '@/components/common/ExternalLink' import classnames from 'classnames' import css from './styles.module.css' +import { useCurrentChain } from '@/hooks/useChains' +import { SPONSOR_LOGOS } from '@/components/tx/SponsoredBy' const RELAYING_HELP_ARTICLE = 'https://help.safe.global/en/articles/7224713-what-is-gas-fee-sponsoring' const Relaying = () => { + const chain = useCurrentChain() const [relays, relaysError] = useRelaysBySafe() const limit = relays?.limit || MAX_HOUR_RELAYS @@ -32,9 +35,9 @@ const Relaying = () => { Gas fees sponsored by - Gnosis Chain + {chain?.chainName} - Gnosis Chain + {chain?.chainName} diff --git a/src/components/new-safe/create/steps/ReviewStep/index.tsx b/src/components/new-safe/create/steps/ReviewStep/index.tsx index 29691bc83d..5284d71cd0 100644 --- a/src/components/new-safe/create/steps/ReviewStep/index.tsx +++ b/src/components/new-safe/create/steps/ReviewStep/index.tsx @@ -1,4 +1,4 @@ -import { useMemo } from 'react' +import { useMemo, useState } from 'react' import { Button, Grid, Typography, Divider, Box } from '@mui/material' import { lightPalette } from '@safe-global/safe-react-components' import ChainIndicator from '@/components/common/ChainIndicator' @@ -22,9 +22,10 @@ import ArrowBackIcon from '@mui/icons-material/ArrowBack' import NetworkWarning from '@/components/new-safe/create/NetworkWarning' import useIsWrongChain from '@/hooks/useIsWrongChain' import ReviewRow from '@/components/new-safe/ReviewRow' -import SponsoredBy from '@/components/tx/SponsoredBy' +import { ExecutionMethodSelector, ExecutionMethod } from '@/components/tx/ExecutionMethodSelector' import { useLeastRemainingRelays } from '@/hooks/useRemainingRelays' import classnames from 'classnames' +import { hasRemainingRelays } from '@/utils/relaying' const ReviewStep = ({ data, onSubmit, onBack, setStep }: StepRenderProps) => { const isWrongChain = useIsWrongChain() @@ -35,12 +36,14 @@ const ReviewStep = ({ data, onSubmit, onBack, setStep }: StepRenderProps Date.now(), []) const [_, setPendingSafe] = useLocalStorage(SAFE_PENDING_CREATION_STORAGE_KEY) + const [executionMethod, setExecutionMethod] = useState(ExecutionMethod.RELAY) const ownerAddresses = useMemo(() => data.owners.map((owner) => owner.address), [data.owners]) const [minRelays] = useLeastRemainingRelays(ownerAddresses) - // Chain supports relaying and relay transactions are available - const willRelay = minRelays && minRelays.remaining > 0 + // Every owner has remaining relays and relay method is selected + const canRelay = hasRemainingRelays(minRelays) + const willRelay = canRelay && executionMethod === ExecutionMethod.RELAY const safeParams = useMemo(() => { return { @@ -121,8 +124,22 @@ const ReviewStep = ({ data, onSubmit, onBack, setStep }: StepRenderProps - - + + {canRelay && ( + + + } + /> + + )} + } /> - {willRelay ? } /> : null} diff --git a/src/components/tx/ExecutionMethodSelector/index.tsx b/src/components/tx/ExecutionMethodSelector/index.tsx index fb1426f9e1..75c6efecc2 100644 --- a/src/components/tx/ExecutionMethodSelector/index.tsx +++ b/src/components/tx/ExecutionMethodSelector/index.tsx @@ -1,4 +1,3 @@ -import classNames from 'classnames' import { Box, FormControl, FormControlLabel, SvgIcon, Radio, RadioGroup, Typography } from '@mui/material' import type { Dispatch, SetStateAction, ReactElement, ChangeEvent } from 'react' @@ -19,10 +18,14 @@ export const ExecutionMethodSelector = ({ executionMethod, setExecutionMethod, relays, + noLabel, + tooltip, }: { executionMethod: ExecutionMethod setExecutionMethod: Dispatch> relays?: RelayResponse + noLabel?: boolean + tooltip?: string }): ReactElement | null => { const wallet = useWallet() @@ -33,18 +36,20 @@ export const ExecutionMethodSelector = ({ } return ( - - `${shape.borderRadius}px` }}> + `${shape.borderRadius}px` }}> + - palette.text.secondary }}> - Choose execution method - + {!noLabel ? ( + + Choose execution method: + + ) : null} + Relayer @@ -55,7 +60,7 @@ export const ExecutionMethodSelector = ({ sx={{ flex: 1 }} value={ExecutionMethod.WALLET} label={ - + Connected wallet } @@ -65,11 +70,7 @@ export const ExecutionMethodSelector = ({ - {shouldRelay && relays ? ( - - - - ) : null} + {shouldRelay && relays ? : null} ) } diff --git a/src/components/tx/ExecutionMethodSelector/styles.module.css b/src/components/tx/ExecutionMethodSelector/styles.module.css index 82c7f59d1f..76109da156 100644 --- a/src/components/tx/ExecutionMethodSelector/styles.module.css +++ b/src/components/tx/ExecutionMethodSelector/styles.module.css @@ -1,22 +1,19 @@ .container { border: 1px solid var(--color-border-light); - padding: var(--space-2) var(--space-2) var(--space-1) var(--space-2); - margin-top: -1px; +} + +.method { + padding: var(--space-1) var(--space-2); } .label { + padding-top: var(--space-1); + color: var(--color-text-secondary); +} + +.radioLabel { font-weight: 700; display: flex; align-items: center; gap: calc(var(--space-1) / 2); } - -.noTopBorderRadius > div { - margin-top: -1px; - border-top-left-radius: 0; - border-top-right-radius: 0; -} - -.noBorderRadius > div { - border-radius: 0; -} diff --git a/src/components/tx/SignOrExecuteForm/index.tsx b/src/components/tx/SignOrExecuteForm/index.tsx index ed19a2cdda..0e13af5d62 100644 --- a/src/components/tx/SignOrExecuteForm/index.tsx +++ b/src/components/tx/SignOrExecuteForm/index.tsx @@ -1,5 +1,5 @@ import { type ReactElement, type ReactNode, type SyntheticEvent, useEffect, useState } from 'react' -import { Button, DialogContent, Typography } from '@mui/material' +import { Box, Button, DialogContent, Typography } from '@mui/material' import type { SafeTransaction } from '@safe-global/safe-core-sdk-types' import useGasLimit from '@/hooks/useGasLimit' @@ -21,6 +21,7 @@ import UnknownContractError from './UnknownContractError' import { useRelaysBySafe } from '@/hooks/useRemainingRelays' import useWalletCanRelay from '@/hooks/useWalletCanRelay' import { ExecutionMethod, ExecutionMethodSelector } from '../ExecutionMethodSelector' +import { hasRemainingRelays } from '@/utils/relaying' type SignOrExecuteProps = { safeTx?: SafeTransaction @@ -71,16 +72,14 @@ const SignOrExecuteForm = ({ // If checkbox is checked and the transaction is executable, execute it, otherwise sign it const willExecute = (onlyExecute || shouldExecute) && canExecute - // SC wallets can relay fully signed transactions - const [walletCanRelay] = useWalletCanRelay(tx) - - // The transaction can be relayed - const canRelay = willExecute && !!relays && relays.remaining > 0 && !!walletCanRelay - // We default to relay, but the option is only shown if we canRelay const [executionMethod, setExecutionMethod] = useState(ExecutionMethod.RELAY) - // The transaction will be executed through relaying + // SC wallets can relay fully signed transactions + const [walletCanRelay] = useWalletCanRelay(tx) + + // The transaction can/will be relayed + const canRelay = hasRemainingRelays(relays) && !!walletCanRelay && willExecute const willRelay = canRelay && executionMethod === ExecutionMethod.RELAY // Synchronize the tx with the safeTx @@ -183,11 +182,21 @@ const SignOrExecuteForm = ({ /> {canRelay && ( - + div': { + marginTop: '-1px', + borderTopLeftRadius: 0, + borderTopRightRadius: 0, + }, + }} + > + + )} { @@ -21,7 +22,7 @@ const SponsoredBy = ({ relays, tooltip }: { relays: RelayResponse; tooltip?: str Sponsored by - {chain?.chainName} + {chain?.chainName} {chain?.chainName} diff --git a/src/components/tx/SponsoredBy/styles.module.css b/src/components/tx/SponsoredBy/styles.module.css index 29d150709a..2df83cc5b5 100644 --- a/src/components/tx/SponsoredBy/styles.module.css +++ b/src/components/tx/SponsoredBy/styles.module.css @@ -1,8 +1,9 @@ .sponsoredBy { padding: 8px 12px; background-color: var(--color-background-main); - border-radius: 6px; - border: 1px solid var(--color-border-light); + border-bottom-left-radius: 6px; + border-bottom-right-radius: 6px; + border-top: 1px solid var(--color-border-light); display: flex; } diff --git a/src/components/tx/modals/BatchExecuteModal/ReviewBatchExecute.tsx b/src/components/tx/modals/BatchExecuteModal/ReviewBatchExecute.tsx index 64c136df00..b38e0603d9 100644 --- a/src/components/tx/modals/BatchExecuteModal/ReviewBatchExecute.tsx +++ b/src/components/tx/modals/BatchExecuteModal/ReviewBatchExecute.tsx @@ -15,21 +15,24 @@ import DecodedTxs from '@/components/tx/modals/BatchExecuteModal/DecodedTxs' import { getMultiSendTxs, getTxsWithDetails } from '@/utils/transactions' import { TxSimulation } from '@/components/tx/TxSimulation' import { useRelaysBySafe } from '@/hooks/useRemainingRelays' -import SponsoredBy from '@/components/tx/SponsoredBy' +import { ExecutionMethod, ExecutionMethodSelector } from '../../ExecutionMethodSelector' import { dispatchBatchExecution, dispatchBatchExecutionRelay } from '@/services/tx/tx-sender' import useOnboard from '@/hooks/wallets/useOnboard' import { WrongChainWarning } from '@/components/tx/WrongChainWarning' import { useWeb3 } from '@/hooks/wallets/web3' +import { hasRemainingRelays } from '@/utils/relaying' const ReviewBatchExecute = ({ data, onSubmit }: { data: BatchExecuteData; onSubmit: (data: null) => void }) => { const [isSubmittable, setIsSubmittable] = useState(true) const [submitError, setSubmitError] = useState() + const [executionMethod, setExecutionMethod] = useState(ExecutionMethod.RELAY) const chain = useCurrentChain() const { safe } = useSafeInfo() const [relays] = useRelaysBySafe() // Chain has relaying feature and available relays - const willRelay = relays && relays.remaining > 0 + const canRelay = hasRemainingRelays(relays) + const willRelay = canRelay && executionMethod === ExecutionMethod.RELAY const onboard = useOnboard() const web3 = useWeb3() @@ -125,12 +128,14 @@ const ReviewBatchExecute = ({ data, onSubmit }: { data: BatchExecuteData; onSubm - {willRelay ? ( + {canRelay ? ( <> Gas fees: - { + return !!relays && relays.remaining > 0 +}