diff --git a/.env b/.env index 2c8419708b..a8ef16088f 100644 --- a/.env +++ b/.env @@ -12,6 +12,12 @@ VITE_APP_ALCHEMY_MAINNET_API_KEY="" VITE_APP_ALCHEMY_POLYGON_API_KEY="" # Alchemy provider API key, used on Sepolia VITE_APP_ALCHEMY_SEPOLIA_API_KEY="" +# Alchemy provider API key, used on Base Sepolia +VITE_APP_ALCHEMY_BASE_SEPOLIA_API_KEY="" +# Alchemy provider API key, used on Base +VITE_APP_ALCHEMY_BASE_API_KEY="" +# Alchemy provider API key, used on Optimism +VITE_APP_ALCHEMY_OPTIMISM_API_KEY="" # Alchemy automated testing workflows VITE_APP_ALCHEMY_TESTING_API_KEY="" @@ -21,6 +27,12 @@ VITE_APP_ETHERSCAN_MAINNET_API_KEY="" VITE_APP_ETHERSCAN_POLYGON_API_KEY="" # ABI selector, used on Sepolia VITE_APP_ETHERSCAN_SEPOLIA_API_KEY="" +# ABI selector, used on Base Sepolia +VITE_APP_ETHERSCAN_BASE_SEPOLIA_API_KEY="" +# ABI selector, used on Base +VITE_APP_ETHERSCAN_BASE_API_KEY="" +# ABI selector, used on Optimism +VITE_APP_ETHERSCAN_OPTIMISM_API_KEY="" # IPFS pinning VITE_APP_INFURA_IPFS_API_KEY="" diff --git a/.graphclientrc.yml b/.graphclientrc.yml index 06bfdca034..a7d596586f 100644 --- a/.graphclientrc.yml +++ b/.graphclientrc.yml @@ -2,7 +2,7 @@ sources: - name: fractal handler: graphql: - endpoint: https://api.studio.thegraph.com/query/43215/fractal-{context.chainName:sepolia}/version/latest + endpoint: https://api.studio.thegraph.com/query/{context.subgraphSpace:71032}/{context.subgraphSlug:fractal-mainnet}/{context.subgraphVersion:v0.0.1} documents: - ./src/graphql/DAO.graphql diff --git a/docs/NETWORK_SUPPORT.md b/docs/NETWORK_SUPPORT.md deleted file mode 100644 index 83153cc1b3..0000000000 --- a/docs/NETWORK_SUPPORT.md +++ /dev/null @@ -1,18 +0,0 @@ -# Current Networks - -Fractal currently supports: - -- [Ethereum Mainnet](https://ethereum.org) -- [Polygon Mainnet](https://polygon.technology/) -- Sepolia Testnet - -It is possible to easily deploy Fractal on any EVM chain that has [Safe{Wallet}](https://safe.global/wallet), [Wagmi](https://www.npmjs.com/package/@wagmi/chains), and [Graph](https://thegraph.com) support. - -# Adding EVM network support - -The steps to add support for additional EVM networks include: - -1. Deploy the [Fractal contracts](https://github.com/decent-dao/fractal-contracts) to the new EVM chain and publish the contract addresses to our NPM package. -2. Update to this new NPM package version in the `package.json` file. -3. Add a `NetworkConfig` file under `src/providers/NetworkConfig/networks/{your new network}.ts`. -4. Add the network to the `supportedChains` array in `src/providers/NetworkConfig/NetworkConfigProvider.tsx`. diff --git a/netlify/functions/tokenPrices.mts b/netlify/functions/tokenPrices.mts index 530399cf55..4b335a3a51 100644 --- a/netlify/functions/tokenPrices.mts +++ b/netlify/functions/tokenPrices.mts @@ -1,6 +1,6 @@ -// eslint-disable-next-line import/named -import { Store, getStore } from '@netlify/blobs'; -import { ethers } from 'ethers'; +import { getStore } from '@netlify/blobs'; +import type { Store } from '@netlify/blobs'; +import { isAddress } from 'viem'; const PUBLIC_DEMO_API_BASE_URL = 'https://api.coingecko.com/api/v3/'; const AUTH_QUERY_PARAM = `?x_cg_demo_api_key=${process.env.COINGECKO_API_KEY}`; @@ -27,7 +27,7 @@ type SupportedNetworks = (typeof SUPPORTED_NETWORKS)[number]; function sanitizeUserInput(tokensString: string, network: SupportedNetworks) { const rawTokenAddresses = tokensString.split(','); const needNativeAsset = rawTokenAddresses.map(address => address.toLowerCase()).includes(network); - const validTokenAddresses = rawTokenAddresses.filter(address => ethers.utils.isAddress(address)); + const validTokenAddresses = rawTokenAddresses.filter(address => isAddress(address)); const lowerCaseTokenAddresses = validTokenAddresses.map(address => address.toLowerCase()); const tokens = [...new Set(lowerCaseTokenAddresses)]; if (needNativeAsset) tokens.push(network); diff --git a/package-lock.json b/package-lock.json index c44819cfc6..1227202c32 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,6 +7,7 @@ "name": "fractal-interface", "hasInstallScript": true, "dependencies": { + "@adraffy/ens-normalize": "^1.10.1", "@apollo/client": "^3.7.10", "@chakra-ui/react": "^2.8.2", "@decent-org/fractal-ui": "^0.1.25", @@ -16,12 +17,12 @@ "@ethersproject/providers": "^5.7.2", "@fontsource/ibm-plex-mono": "^4.5.12", "@fontsource/ibm-plex-sans": "^4.5.13", - "@fractal-framework/fractal-contracts": "^0.4.0", + "@fractal-framework/fractal-contracts": "^0.7.0", "@graphprotocol/client-apollo": "^1.0.16", "@lido-sdk/contracts": "^3.0.2", "@netlify/blobs": "^6.5.0", "@netlify/functions": "^2.6.0", - "@safe-global/safe-deployments": "^1.23.0", + "@safe-global/safe-deployments": "^1.34.0", "@safe-global/safe-ethers-lib": "^1.9.2", "@safe-global/safe-service-client": "^1.5.2", "@sentry/react": "^7.104.0", @@ -95,9 +96,9 @@ "dev": true }, "node_modules/@adraffy/ens-normalize": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.10.0.tgz", - "integrity": "sha512-nA9XHtlAkYfJxY7bce8DcN7eKxWWCWkU+1GR9d+U6MbNpfwQp8TI7vqOsBsMcHoT4mBu2kypKoSKnghEzOOq5Q==" + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.10.1.tgz", + "integrity": "sha512-96Z2IP3mYmF1Xg2cDm8f1gWGf/HUVedQ3FMifV4kG/PQ4yEP51xDtRAEfhVNt5f/uzpNkZHwWQuUcu6D6K+Ekw==" }, "node_modules/@ampproject/remapping": { "version": "2.2.0", @@ -4917,9 +4918,9 @@ "integrity": "sha512-7oDAqvuIArnMJck/JBqCnQZwqL4za+5xG+1Gu4enYbGcGqmUPktiuOy1i0N3XrzrNO70ZvsSt1Rdkxr2oemE6Q==" }, "node_modules/@fractal-framework/fractal-contracts": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/@fractal-framework/fractal-contracts/-/fractal-contracts-0.4.0.tgz", - "integrity": "sha512-kFtpv/pPPZ4neimnRu4ILWUyrJJlaNTP+jc2/uyfq183UxmcN35AU7eZmDfE+5pFwXaQeKioHsnNVy9wcQZNMQ==", + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@fractal-framework/fractal-contracts/-/fractal-contracts-0.7.0.tgz", + "integrity": "sha512-6cyjgqgRmHeA/pU6akj9iMCWWZUw9P2e+cFerjGC+LZRncgHnZIg8uE6efkpj5BQk4MwTtyZLKLMK8ap3nbyOg==", "dependencies": { "solidity-docgen": "^0.6.0-beta.35" } @@ -10175,6 +10176,11 @@ "viem": "^1.0.0" } }, + "node_modules/@safe-global/safe-apps-sdk/node_modules/@adraffy/ens-normalize": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.10.0.tgz", + "integrity": "sha512-nA9XHtlAkYfJxY7bce8DcN7eKxWWCWkU+1GR9d+U6MbNpfwQp8TI7vqOsBsMcHoT4mBu2kypKoSKnghEzOOq5Q==" + }, "node_modules/@safe-global/safe-apps-sdk/node_modules/@scure/bip32": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.3.2.tgz", @@ -10295,9 +10301,9 @@ } }, "node_modules/@safe-global/safe-deployments": { - "version": "1.23.0", - "resolved": "https://registry.npmjs.org/@safe-global/safe-deployments/-/safe-deployments-1.23.0.tgz", - "integrity": "sha512-8kyARY3DHZrAnig3LaM6AKoQtSvkhnKpyZ3jsxNCdnNb38DVuMXWcKA63UdZMWSnKJfkieVXGhof2Kt5cUHTEw==", + "version": "1.34.0", + "resolved": "https://registry.npmjs.org/@safe-global/safe-deployments/-/safe-deployments-1.34.0.tgz", + "integrity": "sha512-J55iHhB1tiNoPeVQ5s943zrfeKRYPqBtnz/EM7d878WzUmmDlTGKHN98qPYKBxkRKP1UjEWuQDrZxy80lx1rJw==", "dependencies": { "semver": "^7.3.7" } @@ -22783,6 +22789,11 @@ } } }, + "node_modules/mipd/node_modules/@adraffy/ens-normalize": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.10.0.tgz", + "integrity": "sha512-nA9XHtlAkYfJxY7bce8DcN7eKxWWCWkU+1GR9d+U6MbNpfwQp8TI7vqOsBsMcHoT4mBu2kypKoSKnghEzOOq5Q==" + }, "node_modules/mipd/node_modules/@scure/bip32": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.3.2.tgz", @@ -28740,6 +28751,11 @@ } } }, + "node_modules/viem/node_modules/@adraffy/ens-normalize": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.10.0.tgz", + "integrity": "sha512-nA9XHtlAkYfJxY7bce8DcN7eKxWWCWkU+1GR9d+U6MbNpfwQp8TI7vqOsBsMcHoT4mBu2kypKoSKnghEzOOq5Q==" + }, "node_modules/viem/node_modules/@scure/bip32": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.3.2.tgz", diff --git a/package.json b/package.json index 73740da9c2..9175d797b3 100644 --- a/package.json +++ b/package.json @@ -2,6 +2,7 @@ "name": "fractal-interface", "private": true, "dependencies": { + "@adraffy/ens-normalize": "^1.10.1", "@apollo/client": "^3.7.10", "@chakra-ui/react": "^2.8.2", "@decent-org/fractal-ui": "^0.1.25", @@ -11,12 +12,12 @@ "@ethersproject/providers": "^5.7.2", "@fontsource/ibm-plex-mono": "^4.5.12", "@fontsource/ibm-plex-sans": "^4.5.13", - "@fractal-framework/fractal-contracts": "^0.4.0", + "@fractal-framework/fractal-contracts": "^0.7.0", "@graphprotocol/client-apollo": "^1.0.16", "@lido-sdk/contracts": "^3.0.2", "@netlify/blobs": "^6.5.0", "@netlify/functions": "^2.6.0", - "@safe-global/safe-deployments": "^1.23.0", + "@safe-global/safe-deployments": "^1.34.0", "@safe-global/safe-ethers-lib": "^1.9.2", "@safe-global/safe-service-client": "^1.5.2", "@sentry/react": "^7.104.0", @@ -62,7 +63,7 @@ "dev": "VITE_APP_GIT_HASH=`git rev-parse HEAD` vite --force", "start": "vite start", "preview": "vite preview", - "build": "npm run graphql:build && VITE_APP_GIT_HASH=`git rev-parse HEAD` vite build", + "build": "VITE_APP_GIT_HASH=`git rev-parse HEAD` vite build", "graphql:build": "graphclient build", "graphql:dev-server": "graphclient serve-dev", "test": "vitest --dir=test", diff --git a/src/components/CreateProposalTemplate/ProposalTemplateMetadata.tsx b/src/components/CreateProposalTemplate/ProposalTemplateMetadata.tsx deleted file mode 100644 index 3d58b366e3..0000000000 --- a/src/components/CreateProposalTemplate/ProposalTemplateMetadata.tsx +++ /dev/null @@ -1,64 +0,0 @@ -import { Button, Divider, VStack } from '@chakra-ui/react'; -import { FormikProps } from 'formik'; -import { useTranslation } from 'react-i18next'; -import { - CreateProposalTemplateForm, - CreateProposalTemplateFormState, -} from '../../types/createProposalTemplate'; -import { InputComponent, TextareaComponent } from '../ui/forms/InputComponent'; - -export interface ProposalTemplateMetadataProps extends FormikProps { - setFormState: (state: CreateProposalTemplateFormState) => void; -} - -export default function ProposalTemplateMetadata({ - values: { proposalTemplateMetadata }, - setFieldValue, - errors: { proposalTemplateMetadata: proposalTemplateMetadataError }, - setFormState, -}: ProposalTemplateMetadataProps) { - const { t } = useTranslation(['proposalTemplate', 'common']); - - return ( - <> - - setFieldValue('proposalTemplateMetadata.title', e.target.value)} - disabled={false} - testId="metadata.title" - maxLength={50} - /> - setFieldValue('proposalTemplateMetadata.description', e.target.value)} - disabled={false} - rows={12} - /> - - - - - ); -} diff --git a/src/components/CreateProposalTemplate/constants.ts b/src/components/CreateProposalTemplate/constants.ts deleted file mode 100644 index 4c7930e184..0000000000 --- a/src/components/CreateProposalTemplate/constants.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { CreateProposalTemplateTransaction } from '../../types/createProposalTemplate'; - -export const DEFAULT_PROPOSAL_TEMPLATE_TRANSACTION: CreateProposalTemplateTransaction = { - targetAddress: '', - ethValue: { value: '', bigNumberValue: undefined }, - functionName: '', - parameters: [ - { - signature: '', - label: '', - value: '', - }, - ], -}; - -export const DEFAULT_PROPOSAL_TEMPLATE = { - nonce: undefined, - proposalTemplateMetadata: { - title: '', - description: '', - }, - transactions: [DEFAULT_PROPOSAL_TEMPLATE_TRANSACTION], -}; diff --git a/src/components/DaoCreator/constants.ts b/src/components/DaoCreator/constants.ts index 399b83af86..d29a0f36a7 100644 --- a/src/components/DaoCreator/constants.ts +++ b/src/components/DaoCreator/constants.ts @@ -1,4 +1,3 @@ -import { BigNumber } from 'ethers'; import { CreatorFormState, GovernanceType, @@ -8,11 +7,13 @@ import { export const DEFAULT_TOKEN_DECIMALS = 18; +// @todo make the time lengths dynamic for whatever real-life values we're targeting here + export const initialState: CreatorFormState = { essentials: { daoName: '', governance: GovernanceType.MULTISIG, - snapshotURL: '', + snapshotENS: '', }, erc20Token: { tokenCreationType: TokenCreationType.NEW, @@ -45,7 +46,7 @@ export const initialState: CreatorFormState = { ], quorumThreshold: { value: '10', - bigNumberValue: BigNumber.from(10), + bigintValue: 10n, }, }, /** @@ -58,42 +59,42 @@ export const initialState: CreatorFormState = { azorius: { quorumPercentage: { value: '4', - bigNumberValue: BigNumber.from(4), + bigintValue: 4n, }, timelock: { value: '1440', - bigNumberValue: BigNumber.from(1440), + bigintValue: 1440n, }, votingPeriod: { value: '10080', - bigNumberValue: BigNumber.from(10080), + bigintValue: 10080n, }, executionPeriod: { value: '2880', - bigNumberValue: BigNumber.from(2880), + bigintValue: 2880n, }, votingStrategyType: VotingStrategyType.LINEAR_ERC20, }, freeze: { executionPeriod: { value: '2880', - bigNumberValue: BigNumber.from(2880), + bigintValue: 2880n, }, timelockPeriod: { value: '1440', - bigNumberValue: BigNumber.from(1440), + bigintValue: 1440n, }, freezeVotesThreshold: { value: '1', - bigNumberValue: BigNumber.from(1), + bigintValue: 1n, }, freezeProposalPeriod: { value: '10080', - bigNumberValue: BigNumber.from(10080), + bigintValue: 10080n, }, freezePeriod: { value: '10080', - bigNumberValue: BigNumber.from(10080), + bigintValue: 10080n, }, }, multisig: { diff --git a/src/components/DaoCreator/formComponents/AzoriusGovernance.tsx b/src/components/DaoCreator/formComponents/AzoriusGovernance.tsx index 5f02a6f0bb..bea29eaca5 100644 --- a/src/components/DaoCreator/formComponents/AzoriusGovernance.tsx +++ b/src/components/DaoCreator/formComponents/AzoriusGovernance.tsx @@ -11,7 +11,7 @@ import { Info } from '@decent-org/fractal-ui'; import { useTranslation } from 'react-i18next'; import { ICreationStepProps, CreatorSteps, VotingStrategyType } from '../../../types'; import ContentBoxTitle from '../../ui/containers/ContentBox/ContentBoxTitle'; -import { BigNumberInput } from '../../ui/forms/BigNumberInput'; +import { BigIntInput } from '../../ui/forms/BigIntInput'; import { LabelComponent } from '../../ui/forms/InputComponent'; import { StepButtons } from '../StepButtons'; import { StepWrapper } from '../StepWrapper'; @@ -39,8 +39,8 @@ export function AzoriusGovernance(props: ICreationStepProps) { isRequired > - setFieldValue('azorius.votingPeriod', valuePair)} decimalPlaces={0} min="1" @@ -63,8 +63,8 @@ export function AzoriusGovernance(props: ICreationStepProps) { isRequired > - setFieldValue('azorius.quorumPercentage', valuePair)} max="100" decimalPlaces={0} @@ -79,8 +79,8 @@ export function AzoriusGovernance(props: ICreationStepProps) { helper={t('helperQuorumThreshold')} isRequired > - setFieldValue('erc721Token.quorumThreshold', valuePair)} decimalPlaces={0} min="1" @@ -94,8 +94,8 @@ export function AzoriusGovernance(props: ICreationStepProps) { isRequired > - setFieldValue('azorius.timelock', valuePair)} decimalPlaces={0} data-testid="govConfig-timelock" @@ -116,8 +116,8 @@ export function AzoriusGovernance(props: ICreationStepProps) { isRequired > - setFieldValue('azorius.executionPeriod', valuePair)} decimalPlaces={0} min="1" diff --git a/src/components/DaoCreator/formComponents/AzoriusNFTDetail.tsx b/src/components/DaoCreator/formComponents/AzoriusNFTDetail.tsx index 1d02982bd4..2e1cdf6485 100644 --- a/src/components/DaoCreator/formComponents/AzoriusNFTDetail.tsx +++ b/src/components/DaoCreator/formComponents/AzoriusNFTDetail.tsx @@ -2,10 +2,10 @@ import { Flex, Box, Text } from '@chakra-ui/react'; import { ethers } from 'ethers'; import { useState, useEffect } from 'react'; import { useTranslation } from 'react-i18next'; -import { erc721Abi } from 'viem'; +import { erc721Abi, isAddress } from 'viem'; import useDisplayName from '../../../hooks/utils/useDisplayName'; import { useEthersProvider } from '../../../providers/Ethers/hooks/useEthersProvider'; -import { BigNumberValuePair, ERC721TokenConfig } from '../../../types'; +import { BigIntValuePair, ERC721TokenConfig } from '../../../types'; import { BarLoader } from '../../ui/loaders/BarLoader'; type TokenDetails = { @@ -18,7 +18,7 @@ export default function AzoriusNFTDetail({ nft, hasAddressError, }: { - nft: ERC721TokenConfig; + nft: ERC721TokenConfig; hasAddressError: boolean; }) { const [loading, setLoading] = useState(); @@ -36,7 +36,7 @@ export default function AzoriusNFTDetail({ setLoading(true); try { - if (nft.tokenAddress && ethers.utils.isAddress(nft.tokenAddress)) { + if (nft.tokenAddress && isAddress(nft.tokenAddress)) { const tokenContract = new ethers.Contract(nft.tokenAddress, erc721Abi, provider); const [name, symbol] = await Promise.all([tokenContract.name(), tokenContract.symbol()]); setTokenDetails({ diff --git a/src/components/DaoCreator/formComponents/AzoriusNFTDetails.tsx b/src/components/DaoCreator/formComponents/AzoriusNFTDetails.tsx index fb4b62ec8c..909bf51096 100644 --- a/src/components/DaoCreator/formComponents/AzoriusNFTDetails.tsx +++ b/src/components/DaoCreator/formComponents/AzoriusNFTDetails.tsx @@ -8,10 +8,10 @@ import { ICreationStepProps, CreatorSteps, ERC721TokenConfig, - BigNumberValuePair, + BigIntValuePair, } from '../../../types'; import ContentBoxTitle from '../../ui/containers/ContentBox/ContentBoxTitle'; -import { BigNumberInput } from '../../ui/forms/BigNumberInput'; +import { BigIntInput } from '../../ui/forms/BigIntInput'; import { LabelComponent } from '../../ui/forms/InputComponent'; import { StepButtons } from '../StepButtons'; import { StepWrapper } from '../StepWrapper'; @@ -70,7 +70,7 @@ export default function AzoriusNFTDetails(props: ICreationStepProps) { {values.erc721Token.nfts.map((nft, i) => { const nftError = ( errors?.erc721Token?.nfts as FormikErrors< - ERC721TokenConfig[] | undefined + ERC721TokenConfig[] | undefined > )?.[i]; const addressErrorMessage = @@ -135,8 +135,8 @@ export default function AzoriusNFTDetails(props: ICreationStepProps) { }} > - setFieldValue(`erc721Token.nfts.${i}.tokenWeight`, valuePair) } @@ -198,7 +198,7 @@ export default function AzoriusNFTDetails(props: ICreationStepProps) { {values.erc721Token.nfts.map((nft, i) => { const nftError = ( errors?.erc721Token?.nfts as FormikErrors< - ERC721TokenConfig[] | undefined + ERC721TokenConfig[] | undefined > )?.[i]; const addressErrorMessage = diff --git a/src/components/DaoCreator/formComponents/AzoriusTokenAllocation.tsx b/src/components/DaoCreator/formComponents/AzoriusTokenAllocation.tsx index c066bdda74..bd73002b2a 100644 --- a/src/components/DaoCreator/formComponents/AzoriusTokenAllocation.tsx +++ b/src/components/DaoCreator/formComponents/AzoriusTokenAllocation.tsx @@ -1,9 +1,8 @@ import { IconButton, Box } from '@chakra-ui/react'; import { LabelWrapper, Minus } from '@decent-org/fractal-ui'; -import { BigNumber } from 'ethers'; import { Field, FieldAttributes } from 'formik'; import { useFormHelpers } from '../../../hooks/utils/useFormHelpers'; -import { BigNumberInput } from '../../ui/forms/BigNumberInput'; +import { BigIntInput } from '../../ui/forms/BigIntInput'; import { AddressInput } from '../../ui/forms/EthAddressInput'; interface ITokenAllocations { index: number; @@ -11,7 +10,7 @@ interface ITokenAllocations { setFieldValue: (field: string, value: any) => void; addressErrorMessage: string | null; amountErrorMessage: string | null; - amountInputValue: BigNumber | undefined; + amountInputValue?: bigint; allocationLength: number; } @@ -39,7 +38,7 @@ export function AzoriusTokenAllocation({ - setFieldValue(`erc20Token.tokenAllocations.${index}.amount`, valuePair) diff --git a/src/components/DaoCreator/formComponents/AzoriusTokenAllocations.tsx b/src/components/DaoCreator/formComponents/AzoriusTokenAllocations.tsx index 72279a143d..6dfde6e9be 100644 --- a/src/components/DaoCreator/formComponents/AzoriusTokenAllocations.tsx +++ b/src/components/DaoCreator/formComponents/AzoriusTokenAllocations.tsx @@ -15,12 +15,12 @@ import { BACKGROUND_SEMI_TRANSPARENT } from '../../../constants/common'; import { useFractal } from '../../../providers/App/AppProvider'; import { AzoriusGovernance, - BigNumberValuePair, + BigIntValuePair, ICreationStepProps, TokenAllocation, } from '../../../types'; import ContentBoxTitle from '../../ui/containers/ContentBox/ContentBoxTitle'; -import { BigNumberInput } from '../../ui/forms/BigNumberInput'; +import { BigIntInput } from '../../ui/forms/BigIntInput'; import { LabelComponent } from '../../ui/forms/InputComponent'; import { AzoriusTokenAllocation } from './AzoriusTokenAllocation'; @@ -60,7 +60,7 @@ export function AzoriusTokenAllocations(props: ICreationStepProps) { {values.erc20Token.tokenAllocations.map((tokenAllocation, index) => { const tokenAllocationError = ( errors?.erc20Token?.tokenAllocations as FormikErrors< - TokenAllocation[] | undefined + TokenAllocation[] | undefined > )?.[index]; @@ -72,7 +72,7 @@ export function AzoriusTokenAllocations(props: ICreationStepProps) { const amountErrorMessage = values.erc20Token.tokenSupply.value && tokenAllocationError?.amount?.value && - !tokenAllocation.amount.bigNumberValue?.isZero() + tokenAllocation.amount.bigintValue !== 0n ? tokenAllocationError.amount.value : null; @@ -83,9 +83,7 @@ export function AzoriusTokenAllocations(props: ICreationStepProps) { remove={remove} addressErrorMessage={addressErrorMessage} amountErrorMessage={amountErrorMessage} - amountInputValue={ - values.erc20Token.tokenAllocations[index].amount.bigNumberValue - } + amountInputValue={values.erc20Token.tokenAllocations[index].amount.bigintValue} allocationLength={values.erc20Token.tokenAllocations.length} {...props} /> @@ -140,14 +138,14 @@ export function AzoriusTokenAllocations(props: ICreationStepProps) { - setFieldValue('erc20Token.parentAllocationAmount', valuePair) } diff --git a/src/components/DaoCreator/formComponents/AzoriusTokenDetails.tsx b/src/components/DaoCreator/formComponents/AzoriusTokenDetails.tsx index 27787e9495..ffc2e2add8 100644 --- a/src/components/DaoCreator/formComponents/AzoriusTokenDetails.tsx +++ b/src/components/DaoCreator/formComponents/AzoriusTokenDetails.tsx @@ -1,9 +1,9 @@ import { Box, Flex, Input, RadioGroup, Text } from '@chakra-ui/react'; import { LabelWrapper } from '@decent-org/fractal-ui'; -import { BigNumber, constants, ethers, utils } from 'ethers'; +import { ethers } from 'ethers'; import { useCallback, useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { erc20Abi } from 'viem'; +import { erc20Abi, isAddress, zeroAddress } from 'viem'; import { BACKGROUND_SEMI_TRANSPARENT } from '../../../constants/common'; import { createAccountSubstring } from '../../../hooks/utils/useDisplayName'; import { useEthersProvider } from '../../../providers/Ethers/hooks/useEthersProvider'; @@ -49,18 +49,21 @@ export function AzoriusTokenDetails(props: ICreationStepProps) { const updateImportFields = useCallback(async () => { const importAddress = values.erc20Token.tokenImportAddress; const importError = errors?.erc20Token?.tokenImportAddress; - if (importAddress && !importError && utils.isAddress(importAddress)) { + if (importAddress && !importError && isAddress(importAddress)) { const isVotesToken = await checkVotesToken(importAddress); const tokenContract = new ethers.Contract(importAddress, erc20Abi, provider); const name: string = await tokenContract.name(); const symbol: string = await tokenContract.symbol(); const decimals: number = await tokenContract.decimals(); + + // @dev: this turns "total supply" into the human-readable form (without decimals) const totalSupply: number = (await tokenContract.totalSupply()) / 10 ** decimals; + setFieldValue( 'erc20Token.tokenSupply', { value: totalSupply, - bigNumberValue: BigNumber.from(totalSupply), + bigintValue: BigInt(totalSupply), }, true, ); @@ -156,7 +159,7 @@ export function AzoriusTokenDetails(props: ICreationStepProps) { name="erc20Token.tokenImportAddress" onChange={handleChange} value={values.erc20Token.tokenImportAddress} - placeholder={createAccountSubstring(constants.AddressZero)} + placeholder={createAccountSubstring(zeroAddress)} /> {!isImportedVotesToken && !errors.erc20Token?.tokenImportAddress && ( diff --git a/src/components/DaoCreator/formComponents/EstablishEssentials.tsx b/src/components/DaoCreator/formComponents/EstablishEssentials.tsx index 89de33253d..0349479ea5 100644 --- a/src/components/DaoCreator/formComponents/EstablishEssentials.tsx +++ b/src/components/DaoCreator/formComponents/EstablishEssentials.tsx @@ -1,6 +1,7 @@ +import { ens_normalize } from '@adraffy/ens-normalize'; import { Box, Divider, Input, RadioGroup } from '@chakra-ui/react'; import { LabelWrapper } from '@decent-org/fractal-ui'; -import { useEffect } from 'react'; +import { useEffect, useState } from 'react'; import { Trans, useTranslation } from 'react-i18next'; import { BACKGROUND_SEMI_TRANSPARENT } from '../../../constants/common'; import { URL_DOCS_GOV_TYPES } from '../../../constants/url'; @@ -29,8 +30,10 @@ export function EstablishEssentials(props: ICreationStepProps) { const { t } = useTranslation(['daoCreate', 'common']); const { values, setFieldValue, isSubmitting, transactionPending, isSubDAO, errors, mode } = props; + const [isSnapshotSpaceValid, setSnapshotSpaceValid] = useState(true); + const { - node: { daoName, daoSnapshotURL, daoAddress }, + node: { daoName, daoSnapshotENS, daoAddress }, } = useFractal(); const isEdit = mode === DAOCreateMode.EDIT; @@ -38,14 +41,18 @@ export function EstablishEssentials(props: ICreationStepProps) { useEffect(() => { if (isEdit) { setFieldValue('essentials.daoName', daoName, false); - if (createAccountSubstring(daoAddress!) !== daoName) - setFieldValue('essentials.snapshotURL', daoSnapshotURL || '', false); + if (createAccountSubstring(daoAddress!) !== daoName) { + // Pre-fill the snapshot URL form field when editing + setFieldValue('essentials.snapshotENS', daoSnapshotENS || '', false); + } } - }, [setFieldValue, mode, daoName, daoSnapshotURL, isEdit, daoAddress]); + }, [setFieldValue, mode, daoName, daoSnapshotENS, isEdit, daoAddress]); const daoNameDisabled = isEdit && !!daoName && !!daoAddress && createAccountSubstring(daoAddress) !== daoName; - const snapshotURLDisabled = isEdit && !!daoSnapshotURL; + + // If in governance edit mode and snapshot URL is already set, disable the field + const snapshotENSDisabled = isEdit && !!daoSnapshotENS; const handleGovernanceChange = (value: string) => { if (value === GovernanceType.AZORIUS_ERC20) { @@ -57,6 +64,24 @@ export function EstablishEssentials(props: ICreationStepProps) { setFieldValue('essentials.governance', value); }; + const handleSnapshotSpaceChange = (value: string) => { + setFieldValue('essentials.snapshotENS', value, true); + + // If there's no input in the snapshot URL field, we don't need to check if it's valid + if (!value) { + setSnapshotSpaceValid(true); + return; + } + + try { + ens_normalize(value); + setSnapshotSpaceValid(true); + } catch (error) { + console.log(error); + setSnapshotSpaceValid(false); + } + }; + const { createOptions } = useNetworkConfig(); return ( @@ -160,14 +185,13 @@ export function EstablishEssentials(props: ICreationStepProps) { helper={t('snapshotHelper')} isRequired={false} > - + setFieldValue('essentials.snapshotURL', cEvent.target.value, true)} - isDisabled={snapshotURLDisabled} - data-testid="essentials-snapshotURL" + value={values.essentials.snapshotENS} + onChange={cEvent => handleSnapshotSpaceChange(cEvent.target.value)} + isDisabled={snapshotENSDisabled} + data-testid="essentials-snapshotENS" placeholder="example.eth" - maxLength={30} /> @@ -179,6 +203,7 @@ export function EstablishEssentials(props: ICreationStepProps) { (); - const [totalParentVotes, setTotalParentVotes] = useState(); + const [totalParentVotes, setTotalParentVotes] = useState(); const { t } = useTranslation(['daoCreate', 'common', 'proposal']); const minutes = t('minutes', { ns: 'common' }); const azoriusGovernance = governance as AzoriusGovernance; @@ -63,7 +63,7 @@ function GuardDetails(props: ICreationStepProps) { if (!totalParentVotes) { if (!type) return; - let parentVotes: BigNumber; + let parentVotes: bigint; switch (type) { case GovernanceType.AZORIUS_ERC20: @@ -74,38 +74,38 @@ function GuardDetails(props: ICreationStepProps) { ) return; if (azoriusGovernance.votesToken) { - const normalized = ethers.utils.formatUnits( + const normalized = formatUnits( azoriusGovernance.votesToken.totalSupply, azoriusGovernance.votesToken.decimals, ); - parentVotes = BigNumber.from(normalized.substring(0, normalized.indexOf('.'))); + parentVotes = BigInt(normalized.substring(0, normalized.indexOf('.'))); } else if (azoriusGovernance.erc721Tokens) { parentVotes = azoriusGovernance.erc721Tokens!.reduce( - (prev, curr) => curr.votingWeight.mul(curr.totalSupply || 1).add(prev), - BigNumber.from(0), + (prev, curr) => curr.votingWeight * (curr.totalSupply || 1n) + prev, + 0n, ); } else { - parentVotes = BigNumber.from(1); + parentVotes = 1n; } break; case GovernanceType.MULTISIG: default: if (!safe) return; - parentVotes = BigNumber.from(safe.owners.length); + parentVotes = BigInt(safe.owners.length); } - let thresholdDefault: BigNumberValuePair; + let thresholdDefault: BigIntValuePair; - if (parentVotes.eq(1)) { + if (parentVotes === 1n) { thresholdDefault = { value: '1', - bigNumberValue: parentVotes, + bigintValue: parentVotes, }; } else { thresholdDefault = { value: parentVotes.toString(), - bigNumberValue: parentVotes.div(2), + bigintValue: parentVotes / 2n, }; } @@ -122,7 +122,7 @@ function GuardDetails(props: ICreationStepProps) { ]); const freezeHelper = totalParentVotes - ? t('helperFreezeVotesThreshold', { totalVotes: formatBigNumberDisplay(totalParentVotes) }) + ? t('helperFreezeVotesThreshold', { totalVotes: formatBigIntDisplay(totalParentVotes) }) : null; return ( @@ -145,8 +145,8 @@ function GuardDetails(props: ICreationStepProps) { isRequired > - setFieldValue('freeze.timelockPeriod', valuePair)} decimalPlaces={0} min="1" @@ -168,8 +168,8 @@ function GuardDetails(props: ICreationStepProps) { isRequired > - setFieldValue('freeze.executionPeriod', valuePair)} decimalPlaces={0} min="1" @@ -193,8 +193,8 @@ function GuardDetails(props: ICreationStepProps) { helper={freezeHelper || ''} isRequired > - setFieldValue('freeze.freezeVotesThreshold', valuePair)} decimalPlaces={0} data-testid="guardConfig-freezeVotesThreshold" @@ -206,8 +206,8 @@ function GuardDetails(props: ICreationStepProps) { isRequired > - setFieldValue('freeze.freezeProposalPeriod', valuePair)} decimalPlaces={0} min="1" @@ -229,8 +229,8 @@ function GuardDetails(props: ICreationStepProps) { isRequired > - setFieldValue('freeze.freezePeriod', valuePair)} decimalPlaces={0} min="1" diff --git a/src/components/DaoCreator/formComponents/VotesTokenImport.tsx b/src/components/DaoCreator/formComponents/VotesTokenImport.tsx index 27d22d38ad..64cb0901ef 100644 --- a/src/components/DaoCreator/formComponents/VotesTokenImport.tsx +++ b/src/components/DaoCreator/formComponents/VotesTokenImport.tsx @@ -53,7 +53,7 @@ export function VotesTokenImport(props: ICreationStepProps) { disabled={true} > - setFieldValue('erc20Token.tokenSupply', valuePair)} data-testid="tokenVoting-tokenSupplyInput" onKeyDown={restrictChars} diff --git a/src/components/DaoCreator/hooks/usePrepareFormData.ts b/src/components/DaoCreator/hooks/usePrepareFormData.ts index 33ae1832c5..851415239c 100644 --- a/src/components/DaoCreator/hooks/usePrepareFormData.ts +++ b/src/components/DaoCreator/hooks/usePrepareFormData.ts @@ -6,7 +6,7 @@ import { useEthersSigner } from '../../../providers/Ethers/hooks/useEthersSigner import { SafeMultisigDAO, DAOFreezeGuardConfig, - BigNumberValuePair, + BigIntValuePair, TokenCreationType, AzoriusERC20DAO, AzoriusERC721DAO, @@ -14,7 +14,7 @@ import { import { getEstimatedNumberOfBlocks } from '../../../utils/contract'; import { couldBeENS } from '../../../utils/url'; -type FreezeGuardConfigParam = { freezeGuard?: DAOFreezeGuardConfig }; +type FreezeGuardConfigParam = { freezeGuard?: DAOFreezeGuardConfig }; export function usePrepareFormData() { const signer = useEthersSigner(); @@ -23,25 +23,25 @@ export function usePrepareFormData() { // Helper function to prepare freezeGuard data const prepareFreezeGuardData = useCallback( async ( - freezeGuard: DAOFreezeGuardConfig, + freezeGuard: DAOFreezeGuardConfig, ): Promise => { if (provider) { return { executionPeriod: await getEstimatedNumberOfBlocks( - freezeGuard.executionPeriod.bigNumberValue!, + freezeGuard.executionPeriod.bigintValue!, provider, ), timelockPeriod: await getEstimatedNumberOfBlocks( - freezeGuard.timelockPeriod.bigNumberValue!, + freezeGuard.timelockPeriod.bigintValue!, provider, ), - freezeVotesThreshold: freezeGuard.freezeVotesThreshold.bigNumberValue!, + freezeVotesThreshold: freezeGuard.freezeVotesThreshold.bigintValue!, freezeProposalPeriod: await getEstimatedNumberOfBlocks( - freezeGuard.freezeProposalPeriod.bigNumberValue!, + freezeGuard.freezeProposalPeriod.bigintValue!, provider, ), freezePeriod: await getEstimatedNumberOfBlocks( - freezeGuard.freezePeriod.bigNumberValue!, + freezeGuard.freezePeriod.bigintValue!, provider, ), }; @@ -107,7 +107,7 @@ export function usePrepareFormData() { tokenImportAddress, tokenCreationType, ...rest - }: AzoriusERC20DAO & FreezeGuardConfigParam): Promise< + }: AzoriusERC20DAO & FreezeGuardConfigParam): Promise< AzoriusERC20DAO | undefined > => { if (provider) { @@ -117,7 +117,7 @@ export function usePrepareFormData() { if (couldBeENS(address)) { address = await signer!.resolveName(allocation.address); } - return { amount: allocation.amount.bigNumberValue!, address: address }; + return { amount: allocation.amount.bigintValue!, address: address }; }), ); let freezeGuardData; @@ -131,15 +131,12 @@ export function usePrepareFormData() { isVotesToken = await checkVotesToken(tokenImportAddress); } return { - tokenSupply: tokenSupply.bigNumberValue!, - parentAllocationAmount: parentAllocationAmount?.bigNumberValue!, - quorumPercentage: quorumPercentage.bigNumberValue!, - timelock: await getEstimatedNumberOfBlocks(timelock.bigNumberValue!, provider), - executionPeriod: await getEstimatedNumberOfBlocks( - executionPeriod.bigNumberValue!, - provider, - ), - votingPeriod: await getEstimatedNumberOfBlocks(votingPeriod.bigNumberValue!, provider), + tokenSupply: tokenSupply.bigintValue!, + parentAllocationAmount: parentAllocationAmount?.bigintValue!, + quorumPercentage: quorumPercentage.bigintValue!, + timelock: await getEstimatedNumberOfBlocks(timelock.bigintValue!, provider), + executionPeriod: await getEstimatedNumberOfBlocks(executionPeriod.bigintValue!, provider), + votingPeriod: await getEstimatedNumberOfBlocks(votingPeriod.bigintValue!, provider), tokenAllocations: resolvedTokenAllocations, tokenImportAddress, tokenCreationType, @@ -163,7 +160,7 @@ export function usePrepareFormData() { nfts, quorumThreshold, ...rest - }: AzoriusERC721DAO & FreezeGuardConfigParam): Promise< + }: AzoriusERC721DAO & FreezeGuardConfigParam): Promise< AzoriusERC721DAO | undefined > => { if (provider) { @@ -180,21 +177,18 @@ export function usePrepareFormData() { } return { tokenAddress: address, - tokenWeight: nft.tokenWeight.bigNumberValue!, + tokenWeight: nft.tokenWeight.bigintValue!, }; }), ); return { - quorumPercentage: quorumPercentage.bigNumberValue!, - timelock: await getEstimatedNumberOfBlocks(timelock.bigNumberValue!, provider), - executionPeriod: await getEstimatedNumberOfBlocks( - executionPeriod.bigNumberValue!, - provider, - ), - votingPeriod: await getEstimatedNumberOfBlocks(votingPeriod.bigNumberValue!, provider), + quorumPercentage: quorumPercentage.bigintValue!, + timelock: await getEstimatedNumberOfBlocks(timelock.bigintValue!, provider), + executionPeriod: await getEstimatedNumberOfBlocks(executionPeriod.bigintValue!, provider), + votingPeriod: await getEstimatedNumberOfBlocks(votingPeriod.bigintValue!, provider), nfts: resolvedNFTs, - quorumThreshold: quorumThreshold.bigNumberValue!, + quorumThreshold: quorumThreshold.bigintValue!, ...freezeGuardData, ...rest, }; diff --git a/src/components/DaoCreator/index.tsx b/src/components/DaoCreator/index.tsx index e4c3ce5980..646cdd6eeb 100644 --- a/src/components/DaoCreator/index.tsx +++ b/src/components/DaoCreator/index.tsx @@ -29,6 +29,7 @@ function DaoCreator({ onSubmit={async values => { const choosenGovernance = values.essentials.governance; const freezeGuard = isSubDAO ? values.freeze : undefined; + switch (choosenGovernance) { case GovernanceType.MULTISIG: { const data = await prepareMultisigFormData({ diff --git a/src/components/CreateProposalTemplate/ProposalTemplateDetails.tsx b/src/components/ProposalBuilder/ProposalDetails.tsx similarity index 79% rename from src/components/CreateProposalTemplate/ProposalTemplateDetails.tsx rename to src/components/ProposalBuilder/ProposalDetails.tsx index a470459623..69a743d4ca 100644 --- a/src/components/CreateProposalTemplate/ProposalTemplateDetails.tsx +++ b/src/components/ProposalBuilder/ProposalDetails.tsx @@ -3,7 +3,7 @@ import { FormikProps } from 'formik'; import { Fragment, PropsWithChildren } from 'react'; import { useTranslation } from 'react-i18next'; import { BACKGROUND_SEMI_TRANSPARENT } from '../../constants/common'; -import { CreateProposalTemplateForm } from '../../types/createProposalTemplate'; +import { CreateProposalForm, ProposalBuilderMode } from '../../types/proposalBuilder'; import Markdown from '../ui/proposal/Markdown'; import '../../assets/css/Markdown.css'; @@ -29,10 +29,11 @@ export function TransactionValueContainer({ } export default function ProposalTemplateDetails({ - values: { proposalTemplateMetadata, transactions }, -}: FormikProps) { + values: { proposalMetadata, transactions }, + mode, +}: FormikProps & { mode: ProposalBuilderMode }) { const { t } = useTranslation(['proposalTemplate', 'proposal']); - const trimmedTitle = proposalTemplateMetadata.title?.trim(); + const trimmedTitle = proposalMetadata.title?.trim(); return ( {t('previewTitle')} {trimmedTitle} - - {t('previewThumnbail')} - {trimmedTitle && ( - title.slice(0, 2)} - /> - )} - + {mode === ProposalBuilderMode.TEMPLATE && ( + + {t('previewThumnbail')} + {trimmedTitle && ( + title.slice(0, 2)} + /> + )} + + )} {t('proposalTemplateDescription')} {transactions.map((transaction, i) => { - const valueBiggerThanZero = transaction.ethValue.bigNumberValue - ? transaction.ethValue.bigNumberValue.gt(0) + const valueBiggerThanZero = transaction.ethValue.bigintValue + ? transaction.ethValue.bigintValue > 0n : false; return ( diff --git a/src/components/ProposalBuilder/ProposalMetadata.tsx b/src/components/ProposalBuilder/ProposalMetadata.tsx new file mode 100644 index 0000000000..65bc1b51ff --- /dev/null +++ b/src/components/ProposalBuilder/ProposalMetadata.tsx @@ -0,0 +1,79 @@ +import { Button, Divider, VStack } from '@chakra-ui/react'; +import { FormikProps } from 'formik'; +import { useTranslation } from 'react-i18next'; +import { CreateProposalState } from '../../types'; +import { CreateProposalForm, ProposalBuilderMode } from '../../types/proposalBuilder'; +import { InputComponent, TextareaComponent } from '../ui/forms/InputComponent'; + +export interface ProposalMetadataProps extends FormikProps { + setFormState: (state: CreateProposalState) => void; + mode: ProposalBuilderMode; +} + +export default function ProposalMetadata({ + values: { proposalMetadata }, + setFieldValue, + errors: { proposalMetadata: proposalMetadataError }, + setFormState, + mode, +}: ProposalMetadataProps) { + const { t } = useTranslation(['proposalTemplate', 'proposal', 'common']); + const isProposalMode = mode === ProposalBuilderMode.PROPOSAL; + + return ( + <> + + setFieldValue('proposalMetadata.title', e.target.value)} + disabled={false} + testId="metadata.title" + maxLength={50} + /> + setFieldValue('proposalMetadata.description', e.target.value)} + disabled={false} + rows={12} + /> + + + + + ); +} diff --git a/src/components/CreateProposalTemplate/ProposalTemplateTransaction.tsx b/src/components/ProposalBuilder/ProposalTransaction.tsx similarity index 78% rename from src/components/CreateProposalTemplate/ProposalTemplateTransaction.tsx rename to src/components/ProposalBuilder/ProposalTransaction.tsx index 33e5c5bdd5..6793d61d1f 100644 --- a/src/components/CreateProposalTemplate/ProposalTemplateTransaction.tsx +++ b/src/components/ProposalBuilder/ProposalTransaction.tsx @@ -2,29 +2,32 @@ import { VStack, HStack, Text, Box, Flex, IconButton } from '@chakra-ui/react'; import { AddPlus, Minus } from '@decent-org/fractal-ui'; import { useCallback } from 'react'; import { useTranslation } from 'react-i18next'; -import { CreateProposalTemplateTransaction } from '../../types/createProposalTemplate'; +import { CreateProposalTransaction, ProposalBuilderMode } from '../../types/proposalBuilder'; import ABISelector, { ABIElement } from '../ui/forms/ABISelector'; import ExampleLabel from '../ui/forms/ExampleLabel'; -import { BigNumberComponent, InputComponent } from '../ui/forms/InputComponent'; -import { DEFAULT_PROPOSAL_TEMPLATE_TRANSACTION } from './constants'; +import { BigIntComponent, InputComponent } from '../ui/forms/InputComponent'; +import { DEFAULT_PROPOSAL_TRANSACTION } from './constants'; -interface ProposalTemplateTransactionProps { - transaction: CreateProposalTemplateTransaction; +interface ProposalTransactionProps { + transaction: CreateProposalTransaction; transactionIndex: number; transactionPending: boolean; txAddressError?: string; txFunctionError?: string; setFieldValue: (field: string, value: any, shouldValidate?: boolean | undefined) => void; + mode: ProposalBuilderMode; } -export default function ProposalTemplateTransaction({ +export default function ProposalTransaction({ transaction, transactionIndex, transactionPending, txAddressError, txFunctionError, setFieldValue, -}: ProposalTemplateTransactionProps) { + mode, +}: ProposalTransactionProps) { + const isProposalMode = mode === ProposalBuilderMode.PROPOSAL; const { t } = useTranslation(['proposal', 'proposalTemplate', 'common']); const handleABISelectorChange = useCallback( (value: ABIElement) => { @@ -123,7 +126,7 @@ export default function ProposalTemplateTransaction({ onClick={() => setFieldValue(`transactions.${transactionIndex}.parameters`, [ ...transaction.parameters, - DEFAULT_PROPOSAL_TEMPLATE_TRANSACTION, + DEFAULT_PROPOSAL_TRANSACTION, ]) } > @@ -170,34 +173,38 @@ export default function ProposalTemplateTransaction({ alignItems="center" mt={4} > - - setFieldValue( - `transactions.${transactionIndex}.parameters.${i}.label`, - e.target.value, - ) - } - disabled={transactionPending || !!parameter.value} - testId={`transactions.${transactionIndex}.parameters.${i}.label`} - subLabel={ - - {t('helperParameterLabel', { ns: 'proposalTemplate' })} - - } - gridContainerProps={{ - display: 'inline-flex', - flexWrap: 'wrap', - width: '30%', - }} - inputContainerProps={{ - width: '100%', - }} - /> - {t('or', { ns: 'common' })} + {!isProposalMode && ( + <> + + setFieldValue( + `transactions.${transactionIndex}.parameters.${i}.label`, + e.target.value, + ) + } + disabled={transactionPending || !!parameter.value} + testId={`transactions.${transactionIndex}.parameters.${i}.label`} + subLabel={ + + {t('helperParameterLabel', { ns: 'proposalTemplate' })} + + } + gridContainerProps={{ + display: 'inline-flex', + flexWrap: 'wrap', + width: '30%', + }} + inputContainerProps={{ + width: '100%', + }} + /> + {t('or', { ns: 'common' })} + + )} {t('example', { ns: 'common' })}: value - - {t('proposalTemplateLeaveBlank', { ns: 'proposalTemplate' })} - + {!isProposalMode && ( + + {t('proposalTemplateLeaveBlank', { ns: 'proposalTemplate' })} + + )} } @@ -268,7 +277,7 @@ export default function ProposalTemplateTransaction({ mt={6} pl={10} > - {`${t('example', { ns: 'common' })}:`} {'1.2'} - {t('ethParemeterHelper', { ns: 'proposalTemplate' })} + {!isProposalMode && ( + {t('ethParemeterHelper', { ns: 'proposalTemplate' })} + )} } errorMessage={undefined} - value={transaction.ethValue.bigNumberValue} + value={transaction.ethValue.bigintValue} onChange={e => { setFieldValue(`transactions.${transactionIndex}.ethValue`, e); }} diff --git a/src/components/CreateProposalTemplate/ProposalTemplateTransactions.tsx b/src/components/ProposalBuilder/ProposalTransactions.tsx similarity index 85% rename from src/components/CreateProposalTemplate/ProposalTemplateTransactions.tsx rename to src/components/ProposalBuilder/ProposalTransactions.tsx index 1b4c5b4849..eac985a805 100644 --- a/src/components/CreateProposalTemplate/ProposalTemplateTransactions.tsx +++ b/src/components/ProposalBuilder/ProposalTransactions.tsx @@ -11,26 +11,29 @@ import { ArrowDown, ArrowRight, Minus } from '@decent-org/fractal-ui'; import { FormikErrors, FormikProps } from 'formik'; import { Dispatch, SetStateAction } from 'react'; import { useTranslation } from 'react-i18next'; -import { BigNumberValuePair } from '../../types'; +import { BigIntValuePair } from '../../types'; import { - CreateProposalTemplateForm, - CreateProposalTemplateTransaction, -} from '../../types/createProposalTemplate'; -import ProposalTemplateTransaction from './ProposalTemplateTransaction'; + CreateProposalForm, + CreateProposalTransaction, + ProposalBuilderMode, +} from '../../types/proposalBuilder'; +import ProposalTransaction from './ProposalTransaction'; -interface ProposalTemplateTransactionsProps extends FormikProps { +interface ProposalTransactionsProps extends FormikProps { pendingTransaction: boolean; expandedIndecies: number[]; setExpandedIndecies: Dispatch>; + mode: ProposalBuilderMode; } -export default function ProposalTemplateTransactions({ +export default function ProposalTransactions({ values: { transactions }, errors, setFieldValue, pendingTransaction, expandedIndecies, setExpandedIndecies, -}: ProposalTemplateTransactionsProps) { + mode, +}: ProposalTransactionsProps) { const { t } = useTranslation(['proposal', 'proposalTemplate', 'common']); const removeTransaction = (transactionIndex: number) => { @@ -46,7 +49,7 @@ export default function ProposalTemplateTransactions({ > {transactions.map((_, index) => { const txErrors = errors?.transactions?.[index] as - | FormikErrors> + | FormikErrors> | undefined; const txAddressError = txErrors?.targetAddress; const txFunctionError = txErrors?.functionName; @@ -101,13 +104,14 @@ export default function ProposalTemplateTransactions({ )} - diff --git a/src/components/CreateProposalTemplate/ProposalTemplateTransactionsForm.tsx b/src/components/ProposalBuilder/ProposalTransactionsForm.tsx similarity index 78% rename from src/components/CreateProposalTemplate/ProposalTemplateTransactionsForm.tsx rename to src/components/ProposalBuilder/ProposalTransactionsForm.tsx index 080570b312..25e267eb00 100644 --- a/src/components/CreateProposalTemplate/ProposalTemplateTransactionsForm.tsx +++ b/src/components/ProposalBuilder/ProposalTransactionsForm.tsx @@ -3,24 +3,21 @@ import { Info } from '@decent-org/fractal-ui'; import { FormikProps } from 'formik'; import { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { - CreateProposalTemplateForm, - CreateProposalTemplateFormState, -} from '../../types/createProposalTemplate'; +import { CreateProposalState } from '../../types'; +import { CreateProposalForm, ProposalBuilderMode } from '../../types/proposalBuilder'; import { scrollToBottom } from '../../utils/ui'; -import ProposalTemplateTransactions from './ProposalTemplateTransactions'; -import { DEFAULT_PROPOSAL_TEMPLATE_TRANSACTION } from './constants'; +import ProposalTransactions from './ProposalTransactions'; +import { DEFAULT_PROPOSAL_TRANSACTION } from './constants'; -interface ProposalTemplateTransactionsFormProps extends FormikProps { +interface ProposalTransactionsFormProps extends FormikProps { pendingTransaction: boolean; - setFormState: (state: CreateProposalTemplateFormState) => void; + setFormState: (state: CreateProposalState) => void; canUserCreateProposal?: boolean; safeNonce?: number; + mode: ProposalBuilderMode; } -export default function ProposalTemplateTransactionsForm( - props: ProposalTemplateTransactionsFormProps, -) { +export default function ProposalTransactionsForm(props: ProposalTransactionsFormProps) { const { pendingTransaction, setFormState, @@ -41,7 +38,7 @@ export default function ProposalTemplateTransactionsForm( return ( - { - setFieldValue('transactions', [...transactions, DEFAULT_PROPOSAL_TEMPLATE_TRANSACTION]); + setFieldValue('transactions', [...transactions, DEFAULT_PROPOSAL_TRANSACTION]); setExpandedIndecies([transactions.length]); scrollToBottom(); }} @@ -85,7 +82,7 @@ export default function ProposalTemplateTransactionsForm( textStyle="text-md-mono-regular" color="gold.500" cursor="pointer" - onClick={() => setFormState(CreateProposalTemplateFormState.METADATA_FORM)} + onClick={() => setFormState(CreateProposalState.METADATA_FORM)} mb={4} > {t('back', { ns: 'common' })} diff --git a/src/components/ProposalBuilder/constants.ts b/src/components/ProposalBuilder/constants.ts new file mode 100644 index 0000000000..644c703897 --- /dev/null +++ b/src/components/ProposalBuilder/constants.ts @@ -0,0 +1,23 @@ +import { CreateProposalTransaction } from '../../types/proposalBuilder'; + +export const DEFAULT_PROPOSAL_TRANSACTION: CreateProposalTransaction = { + targetAddress: '', + ethValue: { value: '', bigintValue: undefined }, + functionName: '', + parameters: [ + { + signature: '', + label: '', + value: '', + }, + ], +}; + +export const DEFAULT_PROPOSAL = { + nonce: undefined, + proposalMetadata: { + title: '', + description: '', + }, + transactions: [DEFAULT_PROPOSAL_TRANSACTION], +}; diff --git a/src/components/ProposalBuilder/index.tsx b/src/components/ProposalBuilder/index.tsx new file mode 100644 index 0000000000..57e2fc3038 --- /dev/null +++ b/src/components/ProposalBuilder/index.tsx @@ -0,0 +1,204 @@ +import { Box, Flex, Grid, GridItem, Text } from '@chakra-ui/react'; +import { Trash } from '@decent-org/fractal-ui'; +import { Formik, FormikProps } from 'formik'; +import { useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useNavigate } from 'react-router-dom'; +import { BACKGROUND_SEMI_TRANSPARENT } from '../../constants/common'; +import { DAO_ROUTES, BASE_ROUTES } from '../../constants/routes'; +import useSubmitProposal from '../../hooks/DAO/proposal/useSubmitProposal'; +import useCreateProposalSchema from '../../hooks/schemas/proposalBuilder/useCreateProposalSchema'; +import { useCanUserCreateProposal } from '../../hooks/utils/useCanUserSubmitProposal'; +import { useFractal } from '../../providers/App/AppProvider'; +import { useNetworkConfig } from '../../providers/NetworkConfig/NetworkConfigProvider'; +import { CreateProposalState, ProposalExecuteData } from '../../types'; +import { CreateProposalForm, ProposalBuilderMode } from '../../types/proposalBuilder'; +import { CustomNonceInput } from '../ui/forms/CustomNonceInput'; +import PageHeader from '../ui/page/Header/PageHeader'; +import ProposalDetails from './ProposalDetails'; +import ProposalMetadata from './ProposalMetadata'; +import ProposalTransactionsForm from './ProposalTransactionsForm'; + +interface IProposalBuilder { + mode: ProposalBuilderMode; + prepareProposalData: (values: CreateProposalForm) => Promise; + initialValues: CreateProposalForm; +} + +const templateAreaTwoCol = '"content details"'; +const templateAreaSingleCol = `"content" + "details"`; + +export default function ProposalBuilder({ + mode, + initialValues, + prepareProposalData, +}: IProposalBuilder) { + const [formState, setFormState] = useState(CreateProposalState.METADATA_FORM); + const { t } = useTranslation(['proposalTemplate', 'proposal']); + + const isProposalMode = mode === ProposalBuilderMode.PROPOSAL; + + const navigate = useNavigate(); + const { + node: { daoAddress, safe }, + } = useFractal(); + const { addressPrefix } = useNetworkConfig(); + const { submitProposal, pendingCreateTx } = useSubmitProposal(); + const { canUserCreateProposal } = useCanUserCreateProposal(); + const { createProposalValidation } = useCreateProposalSchema(); + + const successCallback = () => { + if (daoAddress) { + // Redirecting to proposals page so that user will see Proposal for Proposal Template creation + navigate(DAO_ROUTES.proposals.relative(addressPrefix, daoAddress)); + } + }; + + return ( + + validationSchema={createProposalValidation} + initialValues={initialValues} + enableReinitialize + onSubmit={async values => { + if (canUserCreateProposal) { + const proposalData = await prepareProposalData(values); + if (proposalData) { + submitProposal({ + proposalData, + nonce: values?.nonce, + pendingToastMessage: t('proposalCreatePendingToastMessage', { ns: 'proposal' }), + successToastMessage: t('proposalCreateSuccessToastMessage', { ns: 'proposal' }), + failedToastMessage: t('proposalCreateFailureToastMessage', { ns: 'proposal' }), + successCallback, + }); + } + } + }} + > + {(formikProps: FormikProps) => { + const { handleSubmit } = formikProps; + + if (!daoAddress) { + return; + } + + return ( +
+ + + navigate( + daoAddress + ? isProposalMode + ? DAO_ROUTES.proposals.relative(addressPrefix, daoAddress) + : DAO_ROUTES.proposalTemplates.relative(addressPrefix, daoAddress) + : BASE_ROUTES.landing, + ) + } + isButtonDisabled={pendingCreateTx} + /> + + + + + {formState === CreateProposalState.METADATA_FORM ? ( + + ) : ( + <> + + + {formikProps.values.proposalMetadata.title} + + formikProps.setFieldValue('nonce', newNonce)} + align="end" + /> + + + + )} + + + + + + + + +
+ ); + }} + + ); +} diff --git a/src/components/ProposalCreate/ProposalDetails.tsx b/src/components/ProposalCreate/ProposalDetails.tsx deleted file mode 100644 index 2592d2f3de..0000000000 --- a/src/components/ProposalCreate/ProposalDetails.tsx +++ /dev/null @@ -1,70 +0,0 @@ -import { Box, Divider, Flex, HStack, Text } from '@chakra-ui/react'; -import { useTranslation } from 'react-i18next'; -import { BACKGROUND_SEMI_TRANSPARENT } from '../../constants/common'; -import { useFractal } from '../../providers/App/AppProvider'; -import { AzoriusGovernance, GovernanceType } from '../../types'; -import ContentBoxTitle from '../ui/containers/ContentBox/ContentBoxTitle'; -import { BarLoader } from '../ui/loaders/BarLoader'; - -export function ProposalDetails() { - const { - node: { daoAddress, safe }, - governance, - } = useFractal(); - const { type } = governance; - const azoriusGovernance = governance as AzoriusGovernance; - const { t } = useTranslation(['proposal']); - return ( - - {!type || !daoAddress || !safe ? ( - - - - ) : ( - - {t('proposalSummaryTitle')} - - {type === GovernanceType.MULTISIG ? ( - - {t('labelProposalSigners')} - - {safe.threshold}/{safe.owners?.length} - - - ) : ( - <> - - {t('labelProposalVotingPeriod')} - {azoriusGovernance.votingStrategy?.votingPeriod?.formatted} - - - {t('labelProposalQuorum')} - - {azoriusGovernance.votingStrategy?.quorumPercentage?.formatted || - azoriusGovernance.votingStrategy?.quorumThreshold?.formatted} - - - - {t('labelProposalTimelock')} - {azoriusGovernance.votingStrategy?.timeLockPeriod?.formatted} - - - )} - - )} - - ); -} diff --git a/src/components/ProposalCreate/ProposalHeader.tsx b/src/components/ProposalCreate/ProposalHeader.tsx deleted file mode 100644 index 38983b7773..0000000000 --- a/src/components/ProposalCreate/ProposalHeader.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import { HStack, Spacer, Text, Tooltip } from '@chakra-ui/react'; -import { Alert } from '@decent-org/fractal-ui'; -import { useTranslation } from 'react-i18next'; -import { TOOLTIP_MAXW } from '../../constants/common'; -import { CustomNonceInput } from '../ui/forms/CustomNonceInput'; - -export function ProposalHeader({ - isAzorius, - metadataTitle, - nonce, - setNonce, -}: { - isAzorius?: boolean; - metadataTitle?: string; - nonce?: number; - setNonce: (nonce?: number) => void; -}) { - const { t } = useTranslation('proposal'); - - return ( - - - {metadataTitle ? metadataTitle : t('proposal', { ns: 'proposal' })} - - {!isAzorius && ( - - - - )} - - {!isAzorius && ( - - )} - - ); -} diff --git a/src/components/ProposalCreate/ProposalMetadata.tsx b/src/components/ProposalCreate/ProposalMetadata.tsx deleted file mode 100644 index a25642fc5c..0000000000 --- a/src/components/ProposalCreate/ProposalMetadata.tsx +++ /dev/null @@ -1,81 +0,0 @@ -import { Button, Divider, VStack } from '@chakra-ui/react'; -import { FormikProps } from 'formik'; -import { useTranslation } from 'react-i18next'; -import { CreateProposalForm, CreateProposalState } from '../../types'; -import { InputComponent, TextareaComponent } from '../ui/forms/InputComponent'; - -export interface AzoriusMetadataProps extends FormikProps { - isVisible: boolean; - setFormState: (state: CreateProposalState) => void; -} - -function ProposalMetadata(props: AzoriusMetadataProps) { - const { - values: { proposalMetadata }, - setFieldValue, - errors: { proposalMetadata: proposalMetadataError }, - isVisible, - setFormState, - } = props; - const { t } = useTranslation(['proposal', 'common']); - - if (!isVisible) return null; - - return ( - <> - - setFieldValue('proposalMetadata.title', e.target.value)} - disabled={false} - placeholder={t('proposalTitlePlaceholder')} - testId="metadata.title" - /> - setFieldValue('proposalMetadata.description', e.target.value)} - disabled={false} - placeholder={t('proposalDescriptionPlaceholder')} - rows={9} - /> - setFieldValue('proposalMetadata.documentationUrl', e.target.value)} - value={proposalMetadata.documentationUrl} - disabled={false} - placeholder={t('proposalAdditionalResourcesPlaceholder')} - errorMessage={ - proposalMetadata.documentationUrl && proposalMetadataError?.documentationUrl - } - testId="metadata.documentationUrl" - /> - - - - - ); -} - -export default ProposalMetadata; diff --git a/src/components/ProposalCreate/Transaction.tsx b/src/components/ProposalCreate/Transaction.tsx deleted file mode 100644 index 481328f087..0000000000 --- a/src/components/ProposalCreate/Transaction.tsx +++ /dev/null @@ -1,131 +0,0 @@ -import { VStack, HStack, Text } from '@chakra-ui/react'; -import { useTranslation } from 'react-i18next'; -import { CreateProposalTransaction } from '../../types/createProposal'; -import ExampleLabel from '../ui/forms/ExampleLabel'; -import { BigNumberComponent, InputComponent } from '../ui/forms/InputComponent'; - -interface TransactionProps { - transaction: CreateProposalTransaction; - transactionIndex: number; - transactionPending: boolean; - txAddressError?: string; - txFunctionError?: string; - setFieldValue: (field: string, value: any, shouldValidate?: boolean | undefined) => void; -} - -function Transaction({ - transaction, - transactionIndex, - transactionPending, - txAddressError, - txFunctionError, - setFieldValue, -}: TransactionProps) { - const { t } = useTranslation(['proposal', 'common']); - - return ( - - - {`${t('example', { ns: 'common' })}:`} - 0x4168592... - - } - errorMessage={transaction.targetAddress && txAddressError ? txAddressError : undefined} - value={transaction.targetAddress} - testId="transaction.targetAddress" - onChange={e => - setFieldValue(`transactions.${transactionIndex}.targetAddress`, e.target.value) - } - /> - - - setFieldValue(`transactions.${transactionIndex}.functionName`, e.target.value) - } - disabled={transactionPending} - subLabel={ - - {`${t('example', { ns: 'common' })}:`} - transfer - - } - // @todo update withn new error messages - errorMessage={undefined} - testId="transaction.functionName" - /> - - setFieldValue(`transactions.${transactionIndex}.functionSignature`, e.target.value) - } - disabled={transactionPending} - subLabel={ - - {`${t('example', { ns: 'common' })}:`} - address, uint256 - - } - testId="transaction.functionSignature" - errorMessage={ - transaction.functionSignature && txFunctionError ? txFunctionError : undefined - } - /> - setFieldValue(`transactions.${transactionIndex}.parameters`, e.target.value)} - disabled={transactionPending} - subLabel={ - - {`${t('example', { ns: 'common' })}:`} - - {'0xADC74eE329a23060d3CB431Be0AB313740c191E7, 1000000000'} - - - } - testId="transaction.parameters" - errorMessage={transaction.parameters && txFunctionError ? txFunctionError : undefined} - /> - - - {`${t('example', { ns: 'common' })}:`} - {'1.2'} - - } - errorMessage={undefined} - value={transaction.ethValue.bigNumberValue} - onChange={e => { - setFieldValue(`transactions.${transactionIndex}.ethValue`, e); - }} - decimalPlaces={18} - /> - - ); -} - -export default Transaction; diff --git a/src/components/ProposalCreate/Transactions.tsx b/src/components/ProposalCreate/Transactions.tsx deleted file mode 100644 index 29207f54e5..0000000000 --- a/src/components/ProposalCreate/Transactions.tsx +++ /dev/null @@ -1,118 +0,0 @@ -import { - Box, - Accordion, - AccordionButton, - AccordionItem, - AccordionPanel, - HStack, - IconButton, -} from '@chakra-ui/react'; -import { ArrowDown, ArrowRight, Minus } from '@decent-org/fractal-ui'; -import { FormikErrors, FormikProps } from 'formik'; -import { Dispatch, SetStateAction } from 'react'; -import { useTranslation } from 'react-i18next'; -import { BigNumberValuePair, CreateProposalForm, CreateProposalTransaction } from '../../types'; -import Transaction from './Transaction'; - -interface TransactionsProps extends FormikProps { - isVisible: boolean; - pendingTransaction: boolean; - expandedIndecies: number[]; - setExpandedIndecies: Dispatch>; -} -function Transactions({ - values: { transactions }, - errors, - setFieldValue, - pendingTransaction, - expandedIndecies, - setExpandedIndecies, -}: TransactionsProps) { - const { t } = useTranslation(['proposal', 'common']); - - const removeTransaction = (transactionIndex: number) => { - const transactionsArr = [...transactions]; - transactionsArr.splice(transactionIndex, 1); - setFieldValue('transactions', transactionsArr); - }; - return ( - - {transactions.map((_, index) => { - const txErrors = errors?.transactions?.[index] as - | FormikErrors> - | undefined; - const txAddressError = txErrors?.targetAddress; - const txFunctionError = txErrors?.encodedFunctionData; - - return ( - - {({ isExpanded }) => ( - - - { - setExpandedIndecies(indexArray => { - if (indexArray.includes(index)) { - const newTxArr = [...indexArray]; - newTxArr.splice(newTxArr.indexOf(index), 1); - return newTxArr; - } else { - return [...indexArray, index]; - } - }); - }} - p={0} - textStyle="text-button-md-semibold" - color="grayscale.100" - > - {isExpanded ? : } - {t('transaction')} {index + 1} - - {index !== 0 || transactions.length !== 1 ? ( - } - aria-label={t('removetransactionlabel')} - variant="unstyled" - onClick={() => removeTransaction(index)} - minWidth="auto" - _hover={{ color: 'gold.500' }} - _disabled={{ opacity: 0.4, cursor: 'default' }} - sx={{ '&:disabled:hover': { color: 'inherit', opacity: 0.4 } }} - isDisabled={pendingTransaction} - /> - ) : ( - - )} - - - - - - )} - - ); - })} - - ); -} - -export default Transactions; diff --git a/src/components/ProposalCreate/TransactionsForm.tsx b/src/components/ProposalCreate/TransactionsForm.tsx deleted file mode 100644 index 43d83bd404..0000000000 --- a/src/components/ProposalCreate/TransactionsForm.tsx +++ /dev/null @@ -1,97 +0,0 @@ -import { Button, Box, Flex, Text, VStack, Divider, Alert, AlertTitle } from '@chakra-ui/react'; -import { Info } from '@decent-org/fractal-ui'; -import { FormikProps } from 'formik'; -import { useState } from 'react'; -import { useTranslation } from 'react-i18next'; -import { CreateProposalForm, CreateProposalState } from '../../types'; -import { scrollToBottom } from '../../utils/ui'; -import Transactions from './Transactions'; -import { DEFAULT_TRANSACTION } from './constants'; - -interface TransactionsFormProps extends FormikProps { - isVisible: boolean; - pendingTransaction: boolean; - setFormState: (state: CreateProposalState) => void; - canUserCreateProposal?: boolean; -} - -function TransactionsForm(props: TransactionsFormProps) { - const { - isVisible, - pendingTransaction, - setFormState, - setFieldValue, - values: { transactions }, - errors: { transactions: transactionsError }, - canUserCreateProposal, - } = props; - const { t } = useTranslation(['proposal', 'common']); - const [expandedIndecies, setExpandedIndecies] = useState([0]); - - if (!isVisible) return null; - - return ( - - - - - - - - - {t('transactionExecutionAlertMessage')} - - - - - - - - - - - ); -} - -export default TransactionsForm; diff --git a/src/components/ProposalCreate/constants.ts b/src/components/ProposalCreate/constants.ts deleted file mode 100644 index 6c8c3a7486..0000000000 --- a/src/components/ProposalCreate/constants.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { BigNumber } from 'ethers'; -export const DEFAULT_TRANSACTION = { - targetAddress: '', - ethValue: { value: '0', bigNumberValue: BigNumber.from('0') }, - functionName: '', - functionSignature: '', - parameters: '', - encodedFunctionData: undefined, -}; - -export const DEFAULT_PROPOSAL = { - proposalMetadata: { - title: '', - description: '', - documentationUrl: '', - }, - transactions: [DEFAULT_TRANSACTION], - nonce: 0, -}; diff --git a/src/components/ProposalTemplates/ProposalTemplateCard.tsx b/src/components/ProposalTemplates/ProposalTemplateCard.tsx index cc8336146a..fcecd49fdf 100644 --- a/src/components/ProposalTemplates/ProposalTemplateCard.tsx +++ b/src/components/ProposalTemplates/ProposalTemplateCard.tsx @@ -9,7 +9,7 @@ import useSubmitProposal from '../../hooks/DAO/proposal/useSubmitProposal'; import { useCanUserCreateProposal } from '../../hooks/utils/useCanUserSubmitProposal'; import { useFractal } from '../../providers/App/AppProvider'; import { useNetworkConfig } from '../../providers/NetworkConfig/NetworkConfigProvider'; -import { ProposalTemplate } from '../../types/createProposalTemplate'; +import { ProposalTemplate } from '../../types/proposalBuilder'; import ContentBox from '../ui/containers/ContentBox'; import { OptionMenu } from '../ui/menus/OptionMenu'; import { ModalType } from '../ui/modals/ModalProvider'; diff --git a/src/components/Proposals/MultisigProposalDetails/TxActions.tsx b/src/components/Proposals/MultisigProposalDetails/TxActions.tsx index 30cd4ec77a..263b9ee51d 100644 --- a/src/components/Proposals/MultisigProposalDetails/TxActions.tsx +++ b/src/components/Proposals/MultisigProposalDetails/TxActions.tsx @@ -29,14 +29,14 @@ export function TxActions({ proposal }: { proposal: MultisigProposal }) { const signerOrProvider = useSignerOrProvider(); const safeAPI = useSafeAPI(); - const { chainId } = useNetworkConfig(); + const { chain } = useNetworkConfig(); const { t } = useTranslation(['proposal', 'common', 'transaction']); const [asyncRequest, asyncRequestPending] = useAsyncRequest(); const [contractCall, contractCallPending] = useTransaction(); const loadSafeMultisigProposals = useSafeMultisigProposals(); const baseContracts = useSafeContracts(); - if (user.votingWeight.eq(0)) return <>; + if (user.votingWeight === 0n) return <>; const multisigTx = proposal.transaction as SafeMultisigTransactionWithTransfersResponse; @@ -54,7 +54,7 @@ export function TxActions({ proposal }: { proposal: MultisigProposal }) { asyncRequest({ asyncFunc: () => (signerOrProvider as Signer & TypedDataSigner)._signTypedData( - { verifyingContract: safe.address, chainId: chainId }, + { verifyingContract: safe.address, chainId: chain.id }, EIP712_SAFE_TX_TYPE, safeTx, ), diff --git a/src/components/Proposals/ProposalActions/CastVote.tsx b/src/components/Proposals/ProposalActions/CastVote.tsx index 635ce912b2..fb0b5443f4 100644 --- a/src/components/Proposals/ProposalActions/CastVote.tsx +++ b/src/components/Proposals/ProposalActions/CastVote.tsx @@ -59,7 +59,7 @@ function Vote({ !isSnapshotProposal && isCurrentBlockLoaded && currentBlockNumber && - azoriusProposal.startBlock.gte(currentBlockNumber), + azoriusProposal.startBlock >= currentBlockNumber, ); const disabled = diff --git a/src/components/Proposals/ProposalActions/ProposalAction.tsx b/src/components/Proposals/ProposalActions/ProposalAction.tsx index 1779600201..553dd97c8e 100644 --- a/src/components/Proposals/ProposalActions/ProposalAction.tsx +++ b/src/components/Proposals/ProposalActions/ProposalAction.tsx @@ -63,7 +63,7 @@ export function ProposalAction({ const showActionButton = (isSnapshotProposal && canVote && isActiveProposal) || - (user.votingWeight.gt(0) && + (user.votingWeight > 0n && (isActiveProposal || proposal.state === FractalProposalState.EXECUTABLE || proposal.state === FractalProposalState.TIMELOCKABLE || @@ -106,7 +106,7 @@ export function ProposalAction({ } if (expandedView) { - if (!isSnapshotProposal && (user.votingWeight.eq(0) || (isActiveProposal && !canVote))) + if (!isSnapshotProposal && (user.votingWeight === 0n || (isActiveProposal && !canVote))) return null; return ( diff --git a/src/components/Proposals/ProposalInfo.tsx b/src/components/Proposals/ProposalInfo.tsx index c72d393241..4241c6eb4e 100644 --- a/src/components/Proposals/ProposalInfo.tsx +++ b/src/components/Proposals/ProposalInfo.tsx @@ -20,7 +20,7 @@ export function ProposalInfo({ const metaData = useGetMetadata(proposal); const { t } = useTranslation('proposal'); const { - node: { daoSnapshotURL }, + node: { daoSnapshotENS }, } = useFractal(); const { isSnapshotProposal } = useSnapshotProposal(proposal); @@ -36,7 +36,7 @@ export function ProposalInfo({ {isSnapshotProposal && ( <> {(proposal as ExtendedSnapshotProposal).privacy === 'shutter' && ( diff --git a/src/components/Proposals/ProposalSummary.tsx b/src/components/Proposals/ProposalSummary.tsx index 9f893aed7a..1c0c5f307a 100644 --- a/src/components/Proposals/ProposalSummary.tsx +++ b/src/components/Proposals/ProposalSummary.tsx @@ -2,7 +2,6 @@ import { Text, Box, Button, Divider, Flex, Tooltip } from '@chakra-ui/react'; import { ArrowAngleUp } from '@decent-org/fractal-ui'; import { format } from 'date-fns'; import { formatInTimeZone } from 'date-fns-tz'; -import { BigNumber } from 'ethers'; import { useMemo, useState, useEffect } from 'react'; import { useTranslation } from 'react-i18next'; import { BACKGROUND_SEMI_TRANSPARENT } from '../../constants/common'; @@ -41,19 +40,19 @@ export default function ProposalSummary({ const azoriusGovernance = governance as AzoriusGovernance; const { votesToken, type, erc721Tokens, votingStrategy } = azoriusGovernance; const { t } = useTranslation(['proposal', 'common', 'navigation']); - const startBlockTimeStamp = useBlockTimestamp(startBlock.toNumber()); + const startBlockTimeStamp = useBlockTimestamp(Number(startBlock)); const [proposalsERC20VotingWeight, setProposalsERC20VotingWeight] = useState('0'); - const totalVotesCasted = useMemo(() => yes.add(no).add(abstain), [yes, no, abstain]); + const totalVotesCasted = useMemo(() => yes + no + abstain, [yes, no, abstain]); const totalVotingWeight = useMemo( () => erc721Tokens?.reduce( - (prev, curr) => prev.add(curr.totalSupply?.mul(curr.votingWeight) || BigNumber.from(0)), - BigNumber.from(0), + (prev, curr) => prev + (curr.totalSupply ? curr.totalSupply * curr.votingWeight : 0n), + 0n, ), [erc721Tokens], ); const votesTokenDecimalsDenominator = useMemo( - () => BigNumber.from(10).pow(votesToken?.decimals || 0), + () => 10n ** BigInt(votesToken?.decimals || 0), [votesToken?.decimals], ); const [showVotingPower, setShowVotingPower] = useState(false); @@ -66,9 +65,9 @@ export default function ProposalSummary({ const tokenContract = baseContracts.votesTokenMasterCopyContract.asProvider.attach( votesToken.address, ); - const pastVotingWeight = await tokenContract.getPastVotes(address, startBlock); + const pastVotingWeight = (await tokenContract.getPastVotes(address, startBlock)).toBigInt(); setProposalsERC20VotingWeight( - pastVotingWeight.div(votesTokenDecimalsDenominator).toString(), + (pastVotingWeight / votesTokenDecimalsDenominator).toString(), ); } } @@ -91,22 +90,20 @@ export default function ProposalSummary({ const strategyQuorum = votesToken && isERC20 - ? votingStrategy.quorumPercentage!.value.toNumber() + ? votingStrategy.quorumPercentage!.value : isERC721 - ? votingStrategy.quorumThreshold!.value.toNumber() - : 1; + ? votingStrategy.quorumThreshold!.value + : 1n; const reachedQuorum = isERC721 - ? totalVotesCasted.sub(no).toString() + ? totalVotesCasted - no : votesToken - ? totalVotesCasted.sub(no).div(votesTokenDecimalsDenominator).toString() - : '0'; + ? (totalVotesCasted - no) / votesTokenDecimalsDenominator + : 0n; const totalQuorum = isERC721 - ? strategyQuorum.toString() - : votesToken?.totalSupply - .div(votesTokenDecimalsDenominator) - .div(100) - .mul(strategyQuorum) - .toString(); + ? strategyQuorum + : votesToken + ? (votesToken.totalSupply / votesTokenDecimalsDenominator / 100n) * strategyQuorum + : undefined; const ShowVotingPowerButton = ( @@ -193,11 +192,11 @@ export default function MetadataContainer() {
- {safe?.guard && safe?.guard !== ethers.constants.AddressZero ? ( + {safe?.guard && safe?.guard !== zeroAddress ? ( {safe.guard} diff --git a/src/components/pages/DaoSettings/components/Signers/hooks/useAddSigner.ts b/src/components/pages/DaoSettings/components/Signers/hooks/useAddSigner.ts index ab669f1ac9..2a436f6a11 100644 --- a/src/components/pages/DaoSettings/components/Signers/hooks/useAddSigner.ts +++ b/src/components/pages/DaoSettings/components/Signers/hooks/useAddSigner.ts @@ -1,4 +1,3 @@ -import { BigNumber } from 'ethers'; import { useCallback } from 'react'; import { useTranslation } from 'react-i18next'; import useSubmitProposal from '../../../../../../hooks/DAO/proposal/useSubmitProposal'; @@ -18,8 +17,8 @@ const useAddSigner = () => { close, }: { newSigner: string; - threshold: number | undefined; - nonce: number | undefined; + threshold: number; + nonce: number; daoAddress: string | null; close: () => void; }) => { @@ -32,13 +31,13 @@ const useAddSigner = () => { const calldatas = [ safeSingletonContract.asSigner.interface.encodeFunctionData('addOwnerWithThreshold', [ newSigner, - BigNumber.from(threshold), + BigInt(threshold), ]), ]; const proposalData: ProposalExecuteData = { targets: [daoAddress!], - values: [BigNumber.from('0')], + values: [0n], calldatas: calldatas, metaData: { title: 'Add Signer', diff --git a/src/components/pages/DaoSettings/components/Signers/hooks/useRemoveSigner.ts b/src/components/pages/DaoSettings/components/Signers/hooks/useRemoveSigner.ts index 16ee429dbd..763f1162bd 100644 --- a/src/components/pages/DaoSettings/components/Signers/hooks/useRemoveSigner.ts +++ b/src/components/pages/DaoSettings/components/Signers/hooks/useRemoveSigner.ts @@ -1,4 +1,3 @@ -import { BigNumber } from 'ethers'; import { useCallback } from 'react'; import { useTranslation } from 'react-i18next'; import useSubmitProposal from '../../../../../../hooks/DAO/proposal/useSubmitProposal'; @@ -33,13 +32,13 @@ const useRemoveSigner = ({ safeSingletonContract.asProvider.interface.encodeFunctionData('removeOwner', [ prevSigner, signerToRemove, - BigNumber.from(threshold), + BigInt(threshold), ]), ]; const proposalData: ProposalExecuteData = { targets: [daoAddress!], - values: [BigNumber.from('0')], + values: [0n], calldatas: calldatas, metaData: { title: 'Remove Signers', diff --git a/src/components/pages/DaoSettings/components/Signers/modals/RemoveSignerModal.tsx b/src/components/pages/DaoSettings/components/Signers/modals/RemoveSignerModal.tsx index c046011e0e..77352af879 100644 --- a/src/components/pages/DaoSettings/components/Signers/modals/RemoveSignerModal.tsx +++ b/src/components/pages/DaoSettings/components/Signers/modals/RemoveSignerModal.tsx @@ -38,10 +38,10 @@ function RemoveSignerModal({ const [prevSigner, setPrevSigner] = useState(''); const [threshold, setThreshold] = useState(currentThreshold); const [nonce, setNonce] = useState(safe!.nonce); - const { chainId } = useNetworkConfig(); + const { chain } = useNetworkConfig(); const { data: ensName } = useEnsName({ address: selectedSigner as Address, - chainId, + chainId: chain.id, }); const { t } = useTranslation(['modals', 'common']); const tooltipContainer = useRef(null); diff --git a/src/components/ui/badges/QuorumBadge.tsx b/src/components/ui/badges/QuorumBadge.tsx index 73d6435129..e13e21e307 100644 --- a/src/components/ui/badges/QuorumBadge.tsx +++ b/src/components/ui/badges/QuorumBadge.tsx @@ -1,6 +1,5 @@ import { Box, Flex } from '@chakra-ui/react'; import { Check } from '@decent-org/fractal-ui'; -import { BigNumber } from 'ethers'; import { useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { useFractal } from '../../../providers/App/AppProvider'; @@ -23,13 +22,13 @@ export default function QuorumBadge({ proposal }: { proposal: FractalProposal }) const { votesSummary } = proposal as AzoriusProposal; const totalVotesCasted = useMemo(() => { if (votesSummary) { - return votesSummary.yes.add(votesSummary.no).add(votesSummary.abstain); + return votesSummary.yes + votesSummary.no + votesSummary.abstain; } - return BigNumber.from(0); + return 0n; }, [votesSummary]); const votesTokenDecimalsDenominator = useMemo( - () => BigNumber.from(10).pow(votesToken?.decimals || 0), + () => 10n ** BigInt(votesToken?.decimals || 0), [votesToken?.decimals], ); @@ -50,28 +49,25 @@ export default function QuorumBadge({ proposal }: { proposal: FractalProposal }) const strategyQuorum = erc721Tokens !== undefined - ? votingStrategy.quorumThreshold!.value.toNumber() + ? votingStrategy.quorumThreshold!.value : votesToken !== undefined - ? votingStrategy.quorumPercentage!.value.toNumber() - : 0; + ? votingStrategy.quorumPercentage!.value + : 0n; const reachedQuorum = erc721Tokens !== undefined - ? totalVotesCasted.sub(votesSummary.no).toString() + ? totalVotesCasted - votesSummary.no : votesToken !== undefined - ? totalVotesCasted.sub(votesSummary.no).div(votesTokenDecimalsDenominator).toString() - : '0'; - const totalQuorum = erc721Tokens !== undefined ? strategyQuorum.toString() : 0; + ? (totalVotesCasted - votesSummary.no) / votesTokenDecimalsDenominator + : 0n; + const totalQuorum = erc721Tokens !== undefined ? strategyQuorum : 0n; const meetsQuorum = votesToken - ? votesToken.totalSupply - .div(votesTokenDecimalsDenominator) - .div(100) - .mul(strategyQuorum) - .toString() + ? (votesToken.totalSupply / votesTokenDecimalsDenominator / 100n) * strategyQuorum < + reachedQuorum : reachedQuorum >= totalQuorum; const displayColor = - !totalVotesCasted.isZero() && meetsQuorum ? quorumReachedColor : quorumNotReachedColor; + totalVotesCasted !== 0n && meetsQuorum ? quorumReachedColor : quorumNotReachedColor; return ( )} - {node.daoSnapshotURL && } + {node.daoSnapshotENS && } ); diff --git a/src/components/ui/forms/BigNumberInput.tsx b/src/components/ui/forms/BigIntInput.tsx similarity index 74% rename from src/components/ui/forms/BigNumberInput.tsx rename to src/components/ui/forms/BigIntInput.tsx index 02214580b5..58ef224efd 100644 --- a/src/components/ui/forms/BigNumberInput.tsx +++ b/src/components/ui/forms/BigIntInput.tsx @@ -6,33 +6,33 @@ import { InputRightElement, Button, } from '@chakra-ui/react'; -import { utils, BigNumber, constants } from 'ethers'; import { useState, useCallback, useEffect } from 'react'; import { useTranslation } from 'react-i18next'; -import { BigNumberValuePair } from '../../../types'; -export interface BigNumberInputProps +import { parseUnits, formatUnits, maxUint256 } from 'viem'; +import { BigIntValuePair } from '../../../types'; +export interface BigIntInputProps extends Omit, FormControlOptions { - value: BigNumber | undefined; - onChange: (value: BigNumberValuePair) => void; + value?: bigint; + onChange: (value: BigIntValuePair) => void; decimalPlaces?: number; min?: string; max?: string; - maxValue?: BigNumber; + maxValue?: bigint; } /** - * This component will add a chakra Input component that accepts and sets a BigNumber + * This component will add a chakra Input component that accepts and sets a bigint * - * @param value input value to the control as a BigNumber. If undefined is set then the component will be blank. - * @param onChange event is raised whenever the component changes. Sends back a value / BigNumber pair. The value sent back is a string representation of the BigNumber as a decimal number. - * @param decimalPlaces number of decimal places to be used to parse the value to set the BigNumber + * @param value input value to the control as a bigint. If undefined is set then the component will be blank. + * @param onChange event is raised whenever the component changes. Sends back a value / bigint pair. The value sent back is a string representation of the bigint as a decimal number. + * @param decimalPlaces number of decimal places to be used to parse the value to set the bigint * @param min Setting a minimum value will reset the Input value to min when the component's focus is lost. Can set decimal number for minimum, but must respect the decimalPlaces value. * @param max Setting this will cause the value of the Input control to be reset to the maximum when a number larger than it is inputted. * @param maxValue The maximum value that can be inputted. This is used to set the max value of the Input control. * @parma ...rest component accepts all properties for Input and FormControl * @returns */ -export function BigNumberInput({ +export function BigIntInput({ value, onChange, decimalPlaces = 18, @@ -40,10 +40,10 @@ export function BigNumberInput({ max, maxValue, ...rest -}: BigNumberInputProps) { +}: BigIntInputProps) { const { t } = useTranslation('common'); const [inputValue, setInputValue] = useState( - value && !value.isZero() ? utils.formatUnits(value, decimalPlaces) : '', + value && value !== 0n ? formatUnits(value, decimalPlaces) : '', ); // this will insure the caret in the input component does not shift to the end of the input when the value is changed @@ -82,7 +82,7 @@ export function BigNumberInput({ if (stringValue === '') { onChange({ value: stringValue, - bigNumberValue: undefined, + bigintValue: undefined, }); setInputValue(''); return; @@ -100,18 +100,19 @@ export function BigNumberInput({ } let newValue = truncateDecimalPlaces(stringValue); - let bigNumberValue = utils.parseUnits(removeOnlyDecimalPoint(newValue), decimalPlaces); + + let bigintValue = parseUnits(removeOnlyDecimalPoint(newValue), decimalPlaces); //set value to max if greater than max - const maxBigNumber = max ? utils.parseUnits(max, decimalPlaces) : constants.MaxUint256; - if (bigNumberValue.gt(maxBigNumber)) { - newValue = utils.formatUnits(maxBigNumber, decimalPlaces); - bigNumberValue = maxBigNumber; + const maxBigint = max ? parseUnits(max, decimalPlaces) : maxUint256; + if (bigintValue > maxBigint) { + newValue = formatUnits(maxBigint, decimalPlaces); + bigintValue = maxBigint; } onChange({ value: removeOnlyDecimalPoint(newValue), - bigNumberValue, + bigintValue: bigintValue, }); if (event && newValue !== event.target.value) { resetCaretPositionForInput(event); @@ -127,16 +128,12 @@ export function BigNumberInput({ if (min) { const eventValue = event.target.value; const hasValidValue = eventValue && eventValue !== '.'; - const bigNumberValue = hasValidValue - ? utils.parseUnits(eventValue, decimalPlaces) - : BigNumber.from('0'); - const minBigNumber = hasValidValue - ? utils.parseUnits(min, decimalPlaces) - : BigNumber.from('0'); - if (bigNumberValue.lte(minBigNumber)) { + const bigintValue = hasValidValue ? parseUnits(eventValue, decimalPlaces) : 0n; + const minBigint = hasValidValue ? parseUnits(min, decimalPlaces) : 0n; + if (bigintValue <= minBigint) { onChange({ value: min, - bigNumberValue: BigNumber.from(minBigNumber), + bigintValue: minBigint, }); setInputValue(min); } @@ -161,14 +158,14 @@ export function BigNumberInput({ type="number" {...rest} /> - {maxValue && ( + {maxValue !== undefined && ( - {decentGovernance.lockedVotesToken?.balance && ( - - )} + {decentGovernance.lockedVotesToken?.balance !== null && + decentGovernance.lockedVotesToken?.balance !== undefined && ( + + )} )} diff --git a/src/components/ui/modals/ForkProposalTemplateModal.tsx b/src/components/ui/modals/ForkProposalTemplateModal.tsx index 00b9ad7bfd..71b86644d5 100644 --- a/src/components/ui/modals/ForkProposalTemplateModal.tsx +++ b/src/components/ui/modals/ForkProposalTemplateModal.tsx @@ -9,7 +9,7 @@ import { useCanUserCreateProposal } from '../../../hooks/utils/useCanUserSubmitP import useSignerOrProvider from '../../../hooks/utils/useSignerOrProvider'; import { useFractal } from '../../../providers/App/AppProvider'; import { useNetworkConfig } from '../../../providers/NetworkConfig/NetworkConfigProvider'; -import { ProposalTemplate } from '../../../types/createProposalTemplate'; +import { ProposalTemplate } from '../../../types/proposalBuilder'; import { InputComponent } from '../forms/InputComponent'; interface IForkProposalTemplateModalProps { @@ -30,7 +30,7 @@ export default function ForkProposalTemplateModal({ const { t } = useTranslation('proposalTemplate'); const navigate = useNavigate(); const signerOrProvider = useSignerOrProvider(); - const { name, addressPrefix } = useNetworkConfig(); + const { chain, addressPrefix } = useNetworkConfig(); const { node: { proposalTemplatesHash }, } = useFractal(); @@ -65,13 +65,13 @@ export default function ForkProposalTemplateModal({ return false; } } else { - setError(t('errorFailedSearch', { ns: 'dashboard', chain: name })); + setError(t('errorFailedSearch', { ns: 'dashboard', chain: chain.name })); return false; } } return isValidAddress; - }, [getCanUserCreateProposal, inputValue, isSafe, isSafeLoading, name, signerOrProvider, t]); + }, [getCanUserCreateProposal, inputValue, isSafe, isSafeLoading, chain, signerOrProvider, t]); const handleSubmit = () => { navigate( diff --git a/src/components/ui/modals/ProposalTemplateModal.tsx b/src/components/ui/modals/ProposalTemplateModal.tsx index 210849e214..71791f8631 100644 --- a/src/components/ui/modals/ProposalTemplateModal.tsx +++ b/src/components/ui/modals/ProposalTemplateModal.tsx @@ -8,7 +8,6 @@ import { Switch, VStack, } from '@chakra-ui/react'; -import { utils, BigNumber } from 'ethers'; import { useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useNavigate } from 'react-router-dom'; @@ -19,11 +18,10 @@ import useSubmitProposal from '../../../hooks/DAO/proposal/useSubmitProposal'; import { useCanUserCreateProposal } from '../../../hooks/utils/useCanUserSubmitProposal'; import { useFractal } from '../../../providers/App/AppProvider'; import { useNetworkConfig } from '../../../providers/NetworkConfig/NetworkConfigProvider'; -import { BigNumberValuePair } from '../../../types'; -import { ProposalTemplate } from '../../../types/createProposalTemplate'; -import { isValidUrl } from '../../../utils/url'; +import { BigIntValuePair } from '../../../types'; +import { ProposalTemplate } from '../../../types/proposalBuilder'; import { CustomNonceInput } from '../forms/CustomNonceInput'; -import { BigNumberComponent, InputComponent } from '../forms/InputComponent'; +import { BigIntComponent, InputComponent } from '../forms/InputComponent'; import Markdown from '../proposal/Markdown'; interface IProposalTemplateModalProps { @@ -54,7 +52,7 @@ export default function ProposalTemplateModal({ value, }: { transactionIndex: number; - value: BigNumberValuePair; + value: BigIntValuePair; }) => { setFilledProposalTransactions(prevState => prevState.map((transaction, txIndex) => { @@ -115,27 +113,10 @@ export default function ProposalTemplateModal({ documentationUrl: '', }; - const proposalTransactions = filledProposalTransactions.map( - ({ targetAddress, ethValue, functionName, parameters }) => { - return { - targetAddress: utils.getAddress(targetAddress), // Safe proposal creation/execution might fail if targetAddress is not checksummed - ethValue, - functionName, - functionSignature: parameters.map(parameter => parameter.signature.trim()).join(', '), - parameters: parameters - .map(parameter => - isValidUrl(parameter.value!.trim()) - ? encodeURIComponent(parameter.value!.trim()) // If parameter.value is valid URL with special symbols like ":" or "?" - decoding might fail, thus we need to encode URL - : parameter.value!.trim(), - ) - .join(', '), - }; - }, - ); try { const proposalData = await prepareProposal({ proposalMetadata, - transactions: proposalTransactions, + transactions: filledProposalTransactions, }); submitProposal({ @@ -202,19 +183,19 @@ export default function ProposalTemplateModal({ ), )} {(showAll || - !transactions[transactionIndex].ethValue.bigNumberValue || - BigNumber.from(transactions[transactionIndex].ethValue.bigNumberValue).eq(0)) && ( + !transactions[transactionIndex].ethValue.bigintValue || + transactions[transactionIndex].ethValue.bigintValue === 0n) && ( - { handleEthValueChange({ transactionIndex, value }); }} diff --git a/src/components/ui/modals/SendAssetsModal.tsx b/src/components/ui/modals/SendAssetsModal.tsx index 212791f78c..e9c96e86e5 100644 --- a/src/components/ui/modals/SendAssetsModal.tsx +++ b/src/components/ui/modals/SendAssetsModal.tsx @@ -1,18 +1,17 @@ import { Box, Divider, Flex, Select, HStack, Text, Button } from '@chakra-ui/react'; import { LabelWrapper } from '@decent-org/fractal-ui'; import { SafeBalanceUsdResponse } from '@safe-global/safe-service-client'; -import { BigNumber } from 'ethers'; import { useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useFractal } from '../../../providers/App/AppProvider'; -import { BigNumberValuePair } from '../../../types'; +import { BigIntValuePair } from '../../../types'; import { formatCoinFromAsset, formatCoinUnitsFromAsset, formatUSD, } from '../../../utils/numberFormats'; import useSendAssets from '../../pages/DAOTreasury/hooks/useSendAssets'; -import { BigNumberInput } from '../forms/BigNumberInput'; +import { BigIntInput } from '../forms/BigIntInput'; import { CustomNonceInput } from '../forms/CustomNonceInput'; import { EthAddressInput } from '../forms/EthAddressInput'; @@ -29,7 +28,7 @@ export function SendAssetsModal({ close }: { close: () => void }) { const [selectedAsset, setSelectedAsset] = useState( fungibleAssetsWithBalance[0], ); - const [inputAmount, setInputAmount] = useState(); + const [inputAmount, setInputAmount] = useState(); const [nonceInput, setNonceInput] = useState(safe!.nonce); const [destination, setDestination] = useState(''); @@ -41,18 +40,18 @@ export function SendAssetsModal({ close }: { close: () => void }) { ); const sendAssets = useSendAssets({ - transferAmount: inputAmount?.bigNumberValue || BigNumber.from(0), + transferAmount: inputAmount?.bigintValue || 0n, asset: selectedAsset, destinationAddress: destination, nonce: nonceInput, }); const handleCoinChange = (index: string) => { - setInputAmount({ value: '0', bigNumberValue: BigNumber.from(0) }); + setInputAmount({ value: '0', bigintValue: 0n }); setSelectedAsset(fungibleAssetsWithBalance[Number(index)]); }; - const onChangeAmount = (value: BigNumberValuePair) => { + const onChangeAmount = (value: BigIntValuePair) => { setInputAmount(value); }; @@ -62,8 +61,10 @@ export function SendAssetsModal({ close }: { close: () => void }) { const overDraft = Number(inputAmount?.value || '0') > formatCoinUnitsFromAsset(selectedAsset); - const isSubmitDisabled = - !isValidAddress || !inputAmount || inputAmount?.bigNumberValue?.isZero() || overDraft; + // @dev next couple of lines are written like this, to keep typing equivalent during the conversion from BN to bigint + const inputBigint = inputAmount?.bigintValue; + const inputBigintIsZero = inputBigint ? inputBigint === 0n : undefined; + const isSubmitDisabled = !isValidAddress || !inputAmount || inputBigintIsZero || overDraft; const onSubmit = async () => { await sendAssets(); @@ -105,12 +106,12 @@ export function SendAssetsModal({ close }: { close: () => void }) { > {t('amountLabel')} - diff --git a/src/components/ui/modals/Stake.tsx b/src/components/ui/modals/Stake.tsx index dc5bc783c9..cd2feb27a4 100644 --- a/src/components/ui/modals/Stake.tsx +++ b/src/components/ui/modals/Stake.tsx @@ -1,6 +1,5 @@ import { Box, Button, Flex, Text } from '@chakra-ui/react'; import { SafeBalanceUsdResponse } from '@safe-global/safe-service-client'; -import { BigNumber } from 'ethers'; import { useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useNavigate } from 'react-router-dom'; @@ -8,8 +7,8 @@ import { DAO_ROUTES } from '../../../constants/routes'; import useLidoStaking from '../../../hooks/stake/lido/useLidoStaking'; import { useFractal } from '../../../providers/App/AppProvider'; import { useNetworkConfig } from '../../../providers/NetworkConfig/NetworkConfigProvider'; -import { BigNumberValuePair } from '../../../types'; -import { BigNumberInput } from '../forms/BigNumberInput'; +import { BigIntValuePair } from '../../../types'; +import { BigIntInput } from '../forms/BigIntInput'; export default function StakeModal({ close }: { close: () => void }) { const { @@ -23,15 +22,15 @@ export default function StakeModal({ close }: { close: () => void }) { const fungibleAssetsWithBalance = assetsFungible.filter(asset => parseFloat(asset.balance) > 0); const [selectedAsset] = useState(fungibleAssetsWithBalance[0]); - const [inputAmount, setInputAmount] = useState(); - const onChangeAmount = (value: BigNumberValuePair) => { + const [inputAmount, setInputAmount] = useState(); + const onChangeAmount = (value: BigIntValuePair) => { setInputAmount(value); }; const { handleStake } = useLidoStaking(); const handleSubmit = async () => { - if (inputAmount?.bigNumberValue) { - await handleStake(inputAmount?.bigNumberValue); + if (inputAmount?.bigintValue) { + await handleStake(inputAmount?.bigintValue); close(); if (daoAddress) { navigate(DAO_ROUTES.proposals.relative(addressPrefix, daoAddress)); @@ -48,12 +47,12 @@ export default function StakeModal({ close }: { close: () => void }) { > {t('stakeAmount')} -