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

Merged
merged 27 commits into from
Feb 11, 2025
Merged
Show file tree
Hide file tree
Changes from all 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
6 changes: 6 additions & 0 deletions 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 Down Expand Up @@ -151,6 +152,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
203 changes: 203 additions & 0 deletions src/components/ui/GaslessVotingToggle.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
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 { useBalance } from 'wagmi';
import { DETAILS_BOX_SHADOW } from '../../constants/common';
import { isFeatureEnabled } from '../../helpers/featureFlags';
import { useNetworkConfigStore } from '../../providers/NetworkConfig/useNetworkConfigStore';
import { useDaoInfoStore } from '../../store/daoInfo/useDaoInfoStore';
import { BigIntValuePair } from '../../types';
import { formatCoin } from '../../utils';
import EtherscanLink from './links/EtherscanLink';
import Divider from './utils/Divider';

interface GaslessVotingToggleProps {
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');
const { chain, gaslessVotingSupported } = useNetworkConfigStore();

if (!isFeatureEnabled('flag_gasless_voting')) return null;
if (!gaslessVotingSupported) 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', {
symbol: chain.nativeCurrency.symbol,
})}
</Text>
</Flex>
</Box>
</Box>
);
}

export function GaslessVotingToggleDAOSettings(
props: GaslessVotingToggleProps & {
onGasTankTopupAmountChange: (amount: BigIntValuePair) => void;
},
) {
const { t } = useTranslation('daoEdit');
const { chain, gaslessVotingSupported } = useNetworkConfigStore();

// @todo: Retrieve and use the paymaster address here for `gasTankAddress`. Replace safe.address with the paymaster address. Remove use of `useDaoInfoStore`.
const { safe } = useDaoInfoStore();
const gasTankAddress = safe?.address;

const { data: balance } = useBalance({ address: gasTankAddress, chainId: chain.id });

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

const formattedNativeTokenBalance =
balance && formatCoin(balance.value, true, balance.decimals, balance.symbol);

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

<GaslessVotingToggleContent
{...props}
isSettings
/>

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

<Flex
mt="-0.55rem"
justifyContent="space-between"
>
<Text textStyle="body-small">
{t('titleBalance', { ns: 'modals' })}:{' '}
<Text
as="span"
color="neutral-7"
>
{formattedNativeTokenBalance}
</Text>
</Text>
<Button
variant="secondary"
leftIcon={<Icon as={GasPump} />}
onClick={() => {
console.log(
'addGas. Add this action to the proposal, to be submitted via propose changes button.',
);

// @todo: Add UI to set the amount, then call onGasTankTopupAmountChange.
props.onGasTankTopupAmountChange({
value: '1',
bigintValue: 1n,
});
}}
>
{t('addGas')}
</Button>
DarksightKellar marked this conversation as resolved.
Show resolved Hide resolved
</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
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 {{symbol}} of gas fees. You can top up your balance in your DAO settings, or by sending {{symbol}} 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"
}
2 changes: 2 additions & 0 deletions src/i18n/locales/en/proposalMetadata.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
"removeProposalTemplateTitle": "Remove Proposal Template",
"removeProposalTemplateDescription": "Execution of this proposal will remove the proposal template attached to this Safe.",
"updatesSafeName": "Update Safe Name",
"enableGaslessVoting": "Enable Gasless Voting",
"topupGasTank": "Top up Gas Tank",
"updateSnapshotSpace": "Update Snapshot Space",
"lidoWithdrawalTitle": "Lido Withdrawal",
"lidoWithdrawalDescription": "This proposal will burn your Lido Withdrawal NFT and return the ETH to your Safe.",
Expand Down
51 changes: 42 additions & 9 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 All @@ -15,14 +16,18 @@ import { useCanUserCreateProposal } from '../../../../hooks/utils/useCanUserSubm
import { createAccountSubstring } from '../../../../hooks/utils/useGetAccountName';
import { useNetworkConfigStore } from '../../../../providers/NetworkConfig/useNetworkConfigStore';
import { useDaoInfoStore } from '../../../../store/daoInfo/useDaoInfoStore';
import { ProposalExecuteData } from '../../../../types';
import { BigIntValuePair, ProposalExecuteData } from '../../../../types';
import { validateENSName } from '../../../../utils/url';

export function SafeGeneralSettingsPage() {
const { t } = useTranslation(['settings', 'settingsMetadata']);
const [name, setName] = useState('');
const [snapshotENS, setSnapshotENS] = useState('');
const [snapshotENSValid, setSnapshotENSValid] = useState<boolean>();

const [isGaslessVotingEnabled, setIsGaslessVotingEnabled] = useState<boolean>(false);
const [gasTankTopupAmount, setGasTankTopupAmount] = useState<BigIntValuePair>();

const navigate = useNavigate();

const { submitProposal } = useSubmitProposal();
Expand All @@ -35,6 +40,8 @@ export function SafeGeneralSettingsPage() {

const safeAddress = safe?.address;

const currentIsGaslessVotingEnabled = subgraphInfo?.gaslessVotingEnabled ?? false;

useEffect(() => {
if (
subgraphInfo?.daoName &&
Expand Down Expand Up @@ -70,28 +77,43 @@ export function SafeGeneralSettingsPage() {

const nameChanged = name !== subgraphInfo?.daoName;
const snapshotChanged = snapshotENSValid && snapshotENS !== subgraphInfo?.daoSnapshotENS;
const gaslessVotingChanged = isGaslessVotingEnabled !== currentIsGaslessVotingEnabled;
const gasTankTopupAmountSet =
gasTankTopupAmount?.bigintValue !== undefined && gasTankTopupAmount.bigintValue > 0n;

const handleEditGeneralGovernance = () => {
const changeTitles = [];
if (nameChanged) {
changeTitles.push(t('updatesSafeName', { ns: 'proposalMetadata' }));
}
if (snapshotChanged) {
changeTitles.push(t('updateSnapshotSpace', { ns: 'proposalMetadata' }));
}
const title = changeTitles.join(` ${t('and', { ns: 'common' })} `);

const keyArgs = [];
const valueArgs = [];

if (nameChanged) {
changeTitles.push(t('updatesSafeName', { ns: 'proposalMetadata' }));
keyArgs.push('daoName');
valueArgs.push(name);
}

if (snapshotChanged) {
changeTitles.push(t('updateSnapshotSpace', { ns: 'proposalMetadata' }));
keyArgs.push('snapshotENS');
valueArgs.push(snapshotENS);
}

if (gaslessVotingChanged) {
changeTitles.push(t('enableGaslessVoting', { ns: 'proposalMetadata' }));

// @todo Is KV pairs the place we're storing this flag?
Copy link
Contributor

Choose a reason for hiding this comment

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

Thats a good question? Probably right?

keyArgs.push('gaslessVotingEnabled');
valueArgs.push(`${isGaslessVotingEnabled}`);
}

if (gasTankTopupAmountSet) {
changeTitles.push(t('topupGasTank', { ns: 'proposalMetadata' }));

// @todo add tx to send `gasTankTopupAmount` to gas tank address
}

const title = changeTitles.join(`; `);

const proposalData: ProposalExecuteData = {
metaData: {
title,
Expand Down Expand Up @@ -187,6 +209,17 @@ export function SafeGeneralSettingsPage() {
}}
/>
</Flex>

<GaslessVotingToggleDAOSettings
isEnabled={isGaslessVotingEnabled}
onToggle={() => {
console.log(
'onToggle. Add this action to the proposal, to be submitted via propose changes button.',
);
setIsGaslessVotingEnabled(!isGaslessVotingEnabled);
}}
onGasTankTopupAmountChange={setGasTankTopupAmount}
/>
{canUserCreateProposal && (
<>
<Divider
Expand Down
1 change: 1 addition & 0 deletions src/providers/NetworkConfig/networks/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ export const baseConfig: NetworkConfig = {
GovernanceType.AZORIUS_ERC20,
GovernanceType.AZORIUS_ERC721,
],
gaslessVotingSupported: false,
};

export default baseConfig;
Loading