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

Gasless voting UI #2692

Open
wants to merge 27 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
83fa276
Add GasslessVotingToggleCard
DarksightKellar Jan 27, 2025
61e24f1
Add GasslessVotingToggleDAOSettings
DarksightKellar Jan 27, 2025
af5ada3
Move GasslessVotingToggle
DarksightKellar Jan 27, 2025
2db640a
Add "add gas" button
DarksightKellar Jan 27, 2025
6c86f3e
Move feature behing flag
DarksightKellar Jan 27, 2025
f540143
Include feature for multisig create
DarksightKellar Jan 27, 2025
259aa58
`gassless` -> `gasless` 🤦🏾‍♂️
DarksightKellar Jan 28, 2025
d168e33
cleanup
DarksightKellar Jan 28, 2025
1e623e0
Reduce instances of hardcoded demo address to 1
DarksightKellar Jan 29, 2025
f8afb0d
Remove balance prop. Read data from native token
DarksightKellar Jan 29, 2025
e113bbc
Balance and address don't make sense for create DAO as there's no suc…
DarksightKellar Jan 29, 2025
cfb7a1a
Don't show balance if not loaded
DarksightKellar Jan 29, 2025
305ebf1
Some cleanup
DarksightKellar Jan 31, 2025
3094fdf
Add gasless supported flag to network config
DarksightKellar Jan 31, 2025
21e4423
Merge branch 'develop' into eng-25-gassless-voting-ui
DarksightKellar Jan 31, 2025
e8e024f
Move propose changes button to bottom of page. bugfix
DarksightKellar Feb 4, 2025
748f9ce
Add `updateDAOInfo` to useDaoInfoStore, for setting gasless voting fl…
DarksightKellar Feb 4, 2025
a206d50
Remove unused types
DarksightKellar Feb 4, 2025
5db5d82
Add some more placeholder logic to onToggle and add gas button click
DarksightKellar Feb 4, 2025
1d54f0b
DRY
DarksightKellar Feb 4, 2025
bbbe6fe
dont hardcode ETH
DarksightKellar Feb 4, 2025
0a254bb
Merge branch 'develop' into eng-25-gassless-voting-ui
DarksightKellar Feb 4, 2025
d94b010
Move gaslessVotingSupported conditional rendering into `GaslessVoting…
DarksightKellar Feb 10, 2025
c044f3b
re-arrange some stuff
DarksightKellar Feb 10, 2025
4f91044
Move dao gasless state props to subgraphInfo
DarksightKellar Feb 10, 2025
714237a
Asked AI to simplify. Boom. Nifty lil thing.
DarksightKellar Feb 10, 2025
efd973f
fix types
DarksightKellar Feb 10, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/components/DaoCreator/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export const initialState: CreatorFormState = {
daoName: '',
governance: GovernanceType.AZORIUS_ERC20,
snapshotENS: '',
gaslessVoting: false,
DarksightKellar marked this conversation as resolved.
Show resolved Hide resolved
},
erc20Token: {
tokenCreationType: TokenCreationType.IMPORTED,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { useTranslation } from 'react-i18next';
import { isFeatureEnabled } from '../../../helpers/featureFlags';
import { useDaoInfoStore } from '../../../store/daoInfo/useDaoInfoStore';
import { FractalModuleType, ICreationStepProps, VotingStrategyType } from '../../../types';
import { GaslessVotingToggleDAOCreate } from '../../ui/GaslessVotingToggle';
import { BigIntInput } from '../../ui/forms/BigIntInput';
import { CustomNonceInput } from '../../ui/forms/CustomNonceInput';
import { LabelComponent } from '../../ui/forms/InputComponent';
Expand Down Expand Up @@ -252,6 +253,10 @@ export function AzoriusGovernance(props: ICreationStepProps) {
/>
</Box>
)}
<GaslessVotingToggleDAOCreate
isEnabled={values.essentials.gaslessVoting}
onToggle={() => setFieldValue('essentials.gaslessVoting', !values.essentials.gaslessVoting)}
/>
<StepButtons
{...props}
isEdit={mode === DAOCreateMode.EDIT}
Expand Down
7 changes: 6 additions & 1 deletion src/components/DaoCreator/formComponents/Multisig.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { MinusCircle, Plus } from '@phosphor-icons/react';
import { Field, FieldAttributes } from 'formik';
import { useTranslation } from 'react-i18next';
import { ICreationStepProps } from '../../../types';
import { GaslessVotingToggleDAOCreate } from '../../ui/GaslessVotingToggle';
import { AddressInput } from '../../ui/forms/EthAddressInput';
import { LabelComponent } from '../../ui/forms/InputComponent';
import LabelWrapper from '../../ui/forms/LabelWrapper';
Expand All @@ -11,7 +12,6 @@ import { StepButtons } from '../StepButtons';
import { StepWrapper } from '../StepWrapper';
import useStepRedirect from '../hooks/useStepRedirect';
import { DAOCreateMode } from './EstablishEssentials';

export function Multisig(props: ICreationStepProps) {
const { values, errors, setFieldValue, isSubmitting, transactionPending, isSubDAO, mode } = props;
const { t } = useTranslation('daoCreate');
Expand Down Expand Up @@ -151,6 +151,11 @@ export function Multisig(props: ICreationStepProps) {
</LabelComponent>
</Flex>
</StepWrapper>

<GaslessVotingToggleDAOCreate
isEnabled={values.essentials.gaslessVoting}
onToggle={() => setFieldValue('essentials.gaslessVoting', !values.essentials.gaslessVoting)}
/>
<StepButtons
{...props}
isEdit={mode === DAOCreateMode.EDIT}
Expand Down
176 changes: 176 additions & 0 deletions src/components/ui/GaslessVotingToggle.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
import { Box, Text, HStack, Switch, Flex, Icon, Button } from '@chakra-ui/react';
import { GasPump, WarningCircle } from '@phosphor-icons/react';
import { useTranslation } from 'react-i18next';
import { Address } from 'viem';
import { DETAILS_BOX_SHADOW } from '../../constants/common';
import { isFeatureEnabled } from '../../helpers/featureFlags';
import { useNativeToken } from '../../hooks/useNativeToken';
import EtherscanLink from './links/EtherscanLink';
import Divider from './utils/Divider';

interface GaslessVotingToggleProps {
address?: Address;
isEnabled: boolean;
onToggle: () => void;
}

function GaslessVotingToggleContent({
isEnabled,
onToggle,
isSettings,
}: GaslessVotingToggleProps & { isSettings?: boolean }) {
const { t } = useTranslation('daoCreate');

return (
<Box
display="flex"
flexDirection="column"
gap="1.5rem"
w="100%"
>
<HStack
justify="space-between"
width="100%"
alignItems="flex-start"
>
<Flex
flexDirection="column"
gap="0.25rem"
>
<Text textStyle={isSettings ? 'heading-small' : 'helper-text'}>
{isSettings
? t('gaslessVotingLabelSettings', { ns: 'daoEdit' })
: t('gaslessVotingLabel')}
</Text>
<Text
textStyle={isSettings ? 'label-large' : 'helper-text'}
color="neutral-7"
w="17.25rem"
>
{isSettings
? t('gaslessVotingDescriptionSettings', { ns: 'daoEdit' })
: t('gaslessVotingDescription')}
</Text>
</Flex>
<Switch
size="md"
isChecked={isEnabled}
onChange={() => onToggle()}
variant="secondary"
/>
</HStack>
</Box>
);
}

export function GaslessVotingToggleDAOCreate(props: GaslessVotingToggleProps) {
const { t } = useTranslation('daoCreate');

if (!isFeatureEnabled('flag_gasless_voting')) return null;

return (
<Box
borderRadius="0.75rem"
bg="neutral-2"
p="1.5rem"
display="flex"
flexDirection="column"
alignItems="flex-start"
gap="1.5rem"
boxShadow={DETAILS_BOX_SHADOW}
mt={2}
>
<GaslessVotingToggleContent {...props} />

<Box
p="1rem"
bg="neutral-3"
borderRadius="0.75rem"
>
<Flex alignItems="center">
<Icon
as={WarningCircle}
color="lilac-0"
width="1.5rem"
height="1.5rem"
/>
<Text
color="lilac-0"
marginLeft="1rem"
>
{t('gaslessVotingGettingStarted')}
</Text>
</Flex>
</Box>
</Box>
);
}

export function GaslessVotingToggleDAOSettings(props: GaslessVotingToggleProps) {
const { t } = useTranslation('daoEdit');
const { formattedNativeTokenBalance } = useNativeToken();

if (!isFeatureEnabled('flag_gasless_voting')) return null;

// @todo: Remove this once we have a real address
const address = props.address || '0x01168475F8B9e46F710Ff3654cbD9405e8ADb421';

return (
<Box
gap="1.5rem"
display="flex"
flexDirection="column"
>
<Divider
my="1rem"
w={{ base: 'calc(100% + 1.5rem)', md: 'calc(100% + 3rem)' }}
mx={{ base: '-0.75rem', md: '-1.5rem' }}
/>

<GaslessVotingToggleContent
{...props}
isSettings
/>

{address && (
<Box
borderRadius="0.75rem"
border="1px solid"
borderColor="neutral-3"
p="1rem 0.5rem"
w="100%"
>
<EtherscanLink
type="address"
value={address}
isTextLink
>
<Text as="span">{address}</Text>
</EtherscanLink>
</Box>
)}

<Flex
mt="-0.55rem"
justifyContent="space-between"
>
<Text textStyle="body-small">
{t('titleBalance', { ns: 'modals' })}:{' '}
<Text
as="span"
color="neutral-7"
>
{/* @todo: Should this not be the paymaster balance instead?? */}
{formattedNativeTokenBalance}
DarksightKellar marked this conversation as resolved.
Show resolved Hide resolved
</Text>
</Text>
<Button
variant="secondary"
leftIcon={<Icon as={GasPump} />}
>
{t('addGas')}
</Button>
</Flex>
</Box>
);
}
2 changes: 1 addition & 1 deletion src/helpers/featureFlags.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export const FEATURE_FLAGS = ['flag_dev', 'flag_yelling'] as const;
export const FEATURE_FLAGS = ['flag_dev', 'flag_gasless_voting', 'flag_yelling'] as const;

export type FeatureFlagKeys = typeof FEATURE_FLAGS;
export type FeatureFlagKey = (typeof FEATURE_FLAGS)[number];
Expand Down
25 changes: 25 additions & 0 deletions src/hooks/useNativeToken.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { useFractal } from '../providers/App/AppProvider';
import { formatCoin } from '../utils';
import { MOCK_MORALIS_ETH_ADDRESS } from '../utils/address';

export const useNativeToken = () => {
DarksightKellar marked this conversation as resolved.
Show resolved Hide resolved
const {
treasury: { assetsFungible },
} = useFractal();

// @todo: Confirm this works for all networks
const nativeToken = assetsFungible.find(
asset =>
!asset.tokenAddress ||
asset.tokenAddress.toLowerCase() === MOCK_MORALIS_ETH_ADDRESS.toLowerCase(),
);

const formattedNativeTokenBalance = nativeToken
? formatCoin(nativeToken?.balance, true, nativeToken?.decimals, nativeToken?.symbol)
: null;

return {
nativeToken,
formattedNativeTokenBalance,
};
};
5 changes: 4 additions & 1 deletion src/i18n/locales/en/daoCreate.json
Original file line number Diff line number Diff line change
Expand Up @@ -104,5 +104,8 @@
"networks": "Networks",
"networkDescription": "What network would you like to deploy this DAO on?",
"attachFractalModuleDescription": "This setting controls whether Parent DAO will be able to execute arbitrary transactions on Child DAO bypassing voting process on Child DAO.",
"fractalModuleAttachedDescription": "This setting can not be modified as Fractal Module already attached to the DAO."
"fractalModuleAttachedDescription": "This setting can not be modified as Fractal Module already attached to the DAO.",
"gaslessVotingLabel": "Gasless Voting",
"gaslessVotingDescription": "Sponsor gas for votes and proposals.",
"gaslessVotingGettingStarted": "To get you started, we're covering your first 0.1 ETH of gas fees. You can top up your balance in your DAO settings, or by sending ETH to this address directly in your wallet."
}
5 changes: 4 additions & 1 deletion src/i18n/locales/en/daoEdit.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
{
"cannotModifyGovernance": "You do not have permissions to modify this Safe's governance."
"cannotModifyGovernance": "You do not have permissions to modify this Safe's governance.",
"gaslessVotingLabelSettings": "Sponsor Gas",
"gaslessVotingDescriptionSettings": "Fund transaction fees for DAO voters from a shared DAO gas tank.",
"addGas": "Add Gas"
}
8 changes: 8 additions & 0 deletions src/pages/dao/settings/general/SafeGeneralSettingsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { useTranslation } from 'react-i18next';
import { useNavigate } from 'react-router-dom';
import { encodeFunctionData, zeroAddress } from 'viem';
import { SettingsContentBox } from '../../../../components/SafeSettings/SettingsContentBox';
import { GaslessVotingToggleDAOSettings } from '../../../../components/ui/GaslessVotingToggle';
import { InputComponent } from '../../../../components/ui/forms/InputComponent';
import { BarLoader } from '../../../../components/ui/loaders/BarLoader';
import NestedPageHeader from '../../../../components/ui/page/Header/NestedPageHeader';
Expand Down Expand Up @@ -205,6 +206,13 @@ export function SafeGeneralSettingsPage() {
</Button>
</>
)}

<GaslessVotingToggleDAOSettings
isEnabled={false}
onToggle={function (): void {
throw new Error('Function not implemented.');
}}
/>
</SettingsContentBox>
) : (
<Flex
Expand Down
1 change: 1 addition & 0 deletions src/types/createDAO.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ export type DAOEssentials = {
daoName: string;
governance: GovernanceType;
snapshotENS: string;
gaslessVoting: boolean;
};

export type DAOGovernorERC20Token<T = bigint> = {
Expand Down