Skip to content

Commit

Permalink
fix: allow non-owner spending limit benficiaries to transact (#1572)
Browse files Browse the repository at this point in the history
* fix: allow spending limit benficiaries to transact

* fix: filter available tokens also based on wallet
  • Loading branch information
iamacook authored Jan 31, 2023
1 parent 42491c2 commit 277ec15
Show file tree
Hide file tree
Showing 5 changed files with 82 additions and 22 deletions.
8 changes: 6 additions & 2 deletions src/components/sidebar/NewTxButton/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,19 @@ import useIsWrongChain from '@/hooks/useIsWrongChain'
import css from './styles.module.css'
import { trackEvent, OVERVIEW_EVENTS } from '@/services/analytics'
import ChainSwitcher from '@/components/common/ChainSwitcher'
import useIsOnlySpendingLimitBeneficiary from '@/hooks/useIsOnlySpendingLimitBeneficiary'

const NewTxModal = dynamic(() => import('@/components/tx/modals/NewTxModal'))

const NewTxButton = (): ReactElement => {
const [txOpen, setTxOpen] = useState<boolean>(false)
const wallet = useWallet()
const isSafeOwner = useIsSafeOwner()
const isOnlySpendingLimitBeneficiary = useIsOnlySpendingLimitBeneficiary()
const isWrongChain = useIsWrongChain()

const canCreateTx = isSafeOwner || isOnlySpendingLimitBeneficiary

const onClick = () => {
setTxOpen(true)

Expand All @@ -30,12 +34,12 @@ const NewTxButton = (): ReactElement => {
onClick={onClick}
variant="contained"
size="small"
disabled={!isSafeOwner || isWrongChain}
disabled={!canCreateTx || isWrongChain}
fullWidth
className={css.button}
disableElevation
>
{!wallet ? 'Not connected' : isSafeOwner ? 'New transaction' : 'Read only'}
{!wallet ? 'Not connected' : canCreateTx ? 'New transaction' : 'Read only'}
</Button>

{txOpen && (
Expand Down
16 changes: 10 additions & 6 deletions src/components/tx/SpendingLimitRow/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type { BigNumber } from '@ethersproject/bignumber'
import { safeFormatUnits } from '@/utils/formatters'
import type { TokenInfo } from '@safe-global/safe-gateway-typescript-sdk'
import { SendAssetsField, SendTxType } from '@/components/tx/modals/TokenTransferModal/SendAssetsForm'
import useIsOnlySpendingLimitBeneficiary from '@/hooks/useIsOnlySpendingLimitBeneficiary'

const SpendingLimitRow = ({
availableAmount,
Expand All @@ -13,6 +14,7 @@ const SpendingLimitRow = ({
selectedToken: TokenInfo | undefined
}) => {
const { control } = useFormContext()
const isOnlySpendLimitBeneficiary = useIsOnlySpendingLimitBeneficiary()

const formattedAmount = safeFormatUnits(availableAmount, selectedToken?.decimals)

Expand All @@ -26,12 +28,14 @@ const SpendingLimitRow = ({
name={SendAssetsField.type}
render={({ field }) => (
<RadioGroup {...field} defaultValue={SendTxType.multiSig}>
<FormControlLabel
value={SendTxType.multiSig}
label="Multisig Transaction"
control={<Radio />}
componentsProps={{ typography: { variant: 'body2' } }}
/>
{!isOnlySpendLimitBeneficiary && (
<FormControlLabel
value={SendTxType.multiSig}
label="Multisig Transaction"
control={<Radio />}
componentsProps={{ typography: { variant: 'body2' } }}
/>
)}
<FormControlLabel
value={SendTxType.spendingLimit}
label={`Spending Limit Transaction (${formattedAmount} ${selectedToken?.symbol})`}
Expand Down
32 changes: 19 additions & 13 deletions src/components/tx/modals/NewTxModal/CreationModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { useRemoteSafeApps } from '@/hooks/safe-apps/useRemoteSafeApps'
import { AppRoutes } from '@/config/routes'
import { SafeAppsTag } from '@/config/constants'
import TxButton, { SendNFTsButton, SendTokensButton } from './TxButton'
import useIsOnlySpendingLimitBeneficiary from '@/hooks/useIsOnlySpendingLimitBeneficiary'

const useTxBuilderApp = (): { app?: SafeAppData; link: UrlObject } => {
const [matchingApps] = useRemoteSafeApps(SafeAppsTag.TX_BUILDER)
Expand Down Expand Up @@ -39,6 +40,7 @@ const CreationModal = ({
onContractInteraction: () => void
shouldShowTxBuilder: boolean
}) => {
const isOnlySpendingLimitBeneficiary = useIsOnlySpendingLimitBeneficiary()
const txBuilder = useTxBuilderApp()

return (
Expand All @@ -47,20 +49,24 @@ const CreationModal = ({
<Box display="flex" flexDirection="column" alignItems="center" gap={2} pt={7} pb={4} width={240} m="auto">
<SendTokensButton onClick={onTokenModalOpen} />

{onNFTModalOpen && <SendNFTsButton onClick={onNFTModalOpen} />}
{!isOnlySpendingLimitBeneficiary && (
<>
{onNFTModalOpen && <SendNFTsButton onClick={onNFTModalOpen} />}

{txBuilder.app && shouldShowTxBuilder && (
<Link href={txBuilder.link} passHref>
<a style={{ width: '100%' }}>
<TxButton
startIcon={<img src={txBuilder.app.iconUrl} height={20} width="auto" alt={txBuilder.app.name} />}
variant="outlined"
onClick={onContractInteraction}
>
Contract interaction
</TxButton>
</a>
</Link>
{txBuilder.app && shouldShowTxBuilder && (
<Link href={txBuilder.link} passHref>
<a style={{ width: '100%' }}>
<TxButton
startIcon={<img src={txBuilder.app.iconUrl} height={20} width="auto" alt={txBuilder.app.name} />}
variant="outlined"
onClick={onContractInteraction}
>
Contract interaction
</TxButton>
</a>
</Link>
)}
</>
)}
</Box>
</DialogContent>
Expand Down
24 changes: 23 additions & 1 deletion src/components/tx/modals/TokenTransferModal/SendAssetsForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ import InfoIcon from '@/public/images/notifications/info.svg'
import useIsSafeTokenPaused from '@/components/tx/modals/TokenTransferModal/useIsSafeTokenPaused'
import NumberField from '@/components/common/NumberField'
import { useVisibleBalances } from '@/hooks/useVisibleBalances'
import useIsOnlySpendingLimitBeneficiary from '@/hooks/useIsOnlySpendingLimitBeneficiary'
import { useAppSelector } from '@/store'
import { selectSpendingLimits } from '@/store/spendingLimitsSlice'
import useWallet from '@/hooks/wallets/useWallet'

export const AutocompleteItem = (item: { tokenInfo: TokenInfo; balance: string }): ReactElement => (
<Grid container alignItems="center" gap={1}>
Expand Down Expand Up @@ -72,12 +76,20 @@ type SendAssetsFormProps = {
onSubmit: (formData: SendAssetsFormData) => void
}

const SendAssetsForm = ({ onSubmit, formData, disableSpendingLimit = false }: SendAssetsFormProps): ReactElement => {
const SendAssetsForm = ({
onSubmit,
formData,
// Spending limits only disabled upon replacement, which pure spending limit beneficiaries can't do
disableSpendingLimit = false,
}: SendAssetsFormProps): ReactElement => {
const { balances } = useVisibleBalances()
const addressBook = useAddressBook()
const chainId = useChainId()
const safeTokenAddress = getSafeTokenAddress(chainId)
const isSafeTokenPaused = useIsSafeTokenPaused()
const isOnlySpendingLimitBeneficiary = useIsOnlySpendingLimitBeneficiary()
const spendingLimits = useAppSelector(selectSpendingLimits)
const wallet = useWallet()

const formMethods = useForm<SendAssetsFormData>({
defaultValues: {
Expand All @@ -86,6 +98,8 @@ const SendAssetsForm = ({ onSubmit, formData, disableSpendingLimit = false }: Se
[SendAssetsField.amount]: formData?.[SendAssetsField.amount] || '',
[SendAssetsField.type]: disableSpendingLimit
? SendTxType.multiSig
: isOnlySpendingLimitBeneficiary
? SendTxType.spendingLimit
: formData?.[SendAssetsField.type] || SendTxType.multiSig,
},
mode: 'onChange',
Expand Down Expand Up @@ -117,6 +131,14 @@ const SendAssetsForm = ({ onSubmit, formData, disableSpendingLimit = false }: Se

const spendingLimitAmount = spendingLimit ? BigNumber.from(spendingLimit.amount).sub(spendingLimit.spent) : undefined

const items = isOnlySpendingLimitBeneficiary
? balances.items.filter(({ tokenInfo }) => {
return spendingLimits?.some(({ beneficiary, token }) => {
return sameAddress(beneficiary, wallet?.address || '') && sameAddress(tokenInfo.address, token)
})
})
: balances.items

const onMaxAmountClick = () => {
if (!selectedToken) return

Expand Down
24 changes: 24 additions & 0 deletions src/hooks/useIsOnlySpendingLimitBeneficiary.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { FEATURES } from '@safe-global/safe-gateway-typescript-sdk'

import { useCurrentChain } from '@/hooks/useChains'
import { hasFeature } from '@/utils/chains'
import { useAppSelector } from '@/store'
import { selectSpendingLimits } from '@/store/spendingLimitsSlice'
import useWallet from '@/hooks/wallets/useWallet'
import useIsSafeOwner from '@/hooks/useIsSafeOwner'

const useIsOnlySpendingLimitBeneficiary = (): boolean => {
const currentChain = useCurrentChain()
const isEnabled = currentChain && hasFeature(currentChain, FEATURES.SPENDING_LIMIT)
const spendingLimits = useAppSelector(selectSpendingLimits)
const wallet = useWallet()
const isSafeOwner = useIsSafeOwner()

if (isSafeOwner || !isEnabled || spendingLimits.length === 0) {
return false
}

return spendingLimits.some(({ beneficiary }) => beneficiary === wallet?.address)
}

export default useIsOnlySpendingLimitBeneficiary

0 comments on commit 277ec15

Please sign in to comment.