diff --git a/apps/govern/common-util/functions/requests.ts b/apps/govern/common-util/functions/requests.ts index f4d3de8b..0904e1ff 100644 --- a/apps/govern/common-util/functions/requests.ts +++ b/apps/govern/common-util/functions/requests.ts @@ -1,20 +1,29 @@ import { readContract, readContracts } from '@wagmi/core'; import { ethers } from 'ethers'; -import { AbiFunction, TransactionReceipt, parseUnits } from 'viem'; +import { Abi, AbiFunction, TransactionReceipt, parseUnits } from 'viem'; import { Address } from 'viem'; import { mainnet } from 'viem/chains'; + + import { sendTransaction } from '@autonolas/frontend-library'; -import { STAKING_FACTORY, VE_OLAS } from 'libs/util-contracts/src/lib/abiAndAddresses'; + + +import { SERVICE_REGISTRY, STAKING_FACTORY, VE_OLAS } from 'libs/util-contracts/src/lib/abiAndAddresses'; import { getEstimatedGasLimit } from 'libs/util-functions/src'; + + import { SUPPORTED_CHAINS, wagmiConfig } from 'common-util/config/wagmi'; import { RPC_URLS } from 'common-util/constants/rpcs'; + + import { getAddressFromBytes32 } from './addresses'; import { getUnixNextWeekStartTimestamp } from './time'; -import { getOlasContract, getVeOlasContract, getVoteWeightingContract } from './web3'; +import { getOlasContract, getTokenomicsContract, getTreasuryContract, getVeOlasContract, getVoteWeightingContract } from './web3'; + type VoteForNomineeWeightsParams = { account: Address | undefined; @@ -288,3 +297,91 @@ export const withdrawVeolasRequest = async ({ account }: { account: Address }) = throw error; } }; + +/** + * Start new epoch + */ +export const checkpointRequest = async ({ account }: { account: Address }) => { + const contract = getTokenomicsContract(); + try { + const checkpointFn = contract.methods.checkpoint(); + const estimatedGas = await getEstimatedGasLimit(checkpointFn, account); + const fn = checkpointFn.send({ from: account, gasLimit: estimatedGas }); + + const response = await sendTransaction(fn, account, { + supportedChains: SUPPORTED_CHAINS, + rpcUrls: RPC_URLS, + }); + + return (response as TransactionReceipt)?.transactionHash; + } catch (error) { + window.console.log('Error occurred on starting new epoch'); + throw error; + } +}; + +/** + * Check services are eligible for donating + */ + +export const checkServicesTerminatedOrNotDeployed = async (ids: string[]) => { + const invalidServiceIds: string[] = []; + + try { + const response = await readContracts(wagmiConfig, { + contracts: ids.map((id) => ({ + abi: SERVICE_REGISTRY.abi as Abi, + address: (SERVICE_REGISTRY.addresses as Record)[mainnet.id], + chainId: mainnet.id, + functionName: 'getService', + args: [id], + })), + }); + + response.forEach((service, index) => { + const serviceData = service.result as { state: number } | null; + if (serviceData && serviceData.state !== 4 && serviceData.state !== 5) { + invalidServiceIds.push(ids[index]); + } + }); + } catch (error) { + window.console.log('Error on checking service status'); + throw error; + } + + return invalidServiceIds; +}; + +/** + * Donate to services + */ + +export const depositServiceDonationRequest = async ({ + account, + serviceIds, + amounts, + totalAmount, +}: { + account: Address; + serviceIds: string[]; + amounts: string[]; + totalAmount: string; +}) => { + const contract = getTreasuryContract(); + + try { + const depositFn = contract.methods.depositServiceDonationsETH(serviceIds, amounts); + const estimatedGas = await getEstimatedGasLimit(depositFn, account, totalAmount); + const fn = depositFn.send({ from: account, value: totalAmount, gasLimit: estimatedGas }); + + const response = await sendTransaction(fn, account, { + supportedChains: SUPPORTED_CHAINS, + rpcUrls: RPC_URLS, + }); + + return (response as TransactionReceipt)?.transactionHash; + } catch (error) { + window.console.log('Error occurred on depositing service donation'); + throw error; + } +}; \ No newline at end of file diff --git a/apps/govern/common-util/functions/web3.ts b/apps/govern/common-util/functions/web3.ts index 3362c14a..390a43e8 100644 --- a/apps/govern/common-util/functions/web3.ts +++ b/apps/govern/common-util/functions/web3.ts @@ -2,10 +2,15 @@ import { mainnet } from 'viem/chains'; import Web3 from 'web3'; import { AbiItem } from 'web3-utils'; -import { OLAS, VE_OLAS, VOTE_WEIGHTING } from 'libs/util-contracts/src/lib/abiAndAddresses'; + + +import { OLAS, TOKENOMICS, TREASURY, VE_OLAS, VOTE_WEIGHTING } from 'libs/util-contracts/src/lib/abiAndAddresses'; + + import { getChainId, getProvider } from 'common-util/functions/frontend-library'; + /** * returns the web3 details */ @@ -46,3 +51,17 @@ export const getVeOlasContract = () => { const contract = getContract(abi, address); return contract; }; + +export const getTokenomicsContract = () => { + const abi = TOKENOMICS.abi as unknown as AbiItem[]; + const address = TOKENOMICS.addresses[mainnet.id]; + const contract = getContract(abi, address); + return contract; +}; + +export const getTreasuryContract = () => { + const abi = TREASURY.abi as AbiItem[]; + const address = TREASURY.addresses[mainnet.id]; + const contract = getContract(abi, address); + return contract; +}; \ No newline at end of file diff --git a/apps/govern/components/Donate/DonateForm.tsx b/apps/govern/components/Donate/DonateForm.tsx new file mode 100644 index 00000000..2131405d --- /dev/null +++ b/apps/govern/components/Donate/DonateForm.tsx @@ -0,0 +1,134 @@ +import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons'; +import { Button, Form, Grid, InputNumber, Space, Typography } from 'antd'; +import styled from 'styled-components'; +import { useAccount } from 'wagmi'; + +const { Text } = Typography; +const { useBreakpoint } = Grid; + +export const DynamicFormContainer = styled.div` + max-width: 720px; + .ant-input-number { + width: 200px; + } +`; + +type DonateFormProps = { + isLoading: boolean; + onSubmit: ({ unitIds, amounts }: { unitIds: number[]; amounts: number[] }) => Promise; +}; + +export const DonateForm = ({ isLoading, onSubmit }: DonateFormProps) => { + const { address: account } = useAccount(); + const [form] = Form.useForm(); + + const screens = useBreakpoint(); + const inputStyle = screens.xs ? { width: '140px' } : { width: 'auto' }; + + const onFinish = async (values: { units: { unitId: number; amount: number }[] }) => { + if (onSubmit) { + try { + await onSubmit({ + unitIds: values.units.map((unit) => unit.unitId), + amounts: values.units.map((unit) => unit.amount), + }); + + form.resetFields(); + } catch (error) { + window.console.error(error); + } + } + }; + + return ( + +
+ { + if (!units || units?.length === 0) { + return Promise.reject(new Error('At least 1 unit is required')); + } + return Promise.resolve(); + }, + }, + ]} + > + {(fields, { add, remove }, { errors }) => ( + <> + {fields.map((field) => ( + + prevValues.units !== curValues.units} + > + {() => ( + + + + )} + + + + + + + {fields.length > 1 && remove(field.name)} />} + + ))} + + + + + + + + )} + + + + + + {!account && ( + + To donate, connect a wallet + + )} + +
+
+ ); +}; diff --git a/apps/govern/components/Donate/hooks.ts b/apps/govern/components/Donate/hooks.ts new file mode 100644 index 00000000..589d7fbb --- /dev/null +++ b/apps/govern/components/Donate/hooks.ts @@ -0,0 +1,105 @@ +import { ethers } from 'ethers'; +import { useCallback, useMemo } from 'react'; +import { mainnet } from 'viem/chains'; +import { useReadContract } from 'wagmi'; + + + +import { TOKENOMICS, TREASURY } from 'libs/util-contracts/src/lib/abiAndAddresses'; + + +const useVeOLASThreshold = () => + useReadContract({ + address: TOKENOMICS.addresses[mainnet.id], + abi: TOKENOMICS.abi, + chainId: mainnet.id, + functionName: 'veOLASThreshold', + query: { + select: (data) => ethers.formatEther(`${data}`), + }, + }); + +const useMinAcceptedETH = () => + useReadContract({ + address: TREASURY.addresses[mainnet.id], + abi: TREASURY.abi, + chainId: mainnet.id, + functionName: 'minAcceptedETH', + }); + +const useEpochCounter = () => + useReadContract({ + address: TOKENOMICS.addresses[mainnet.id], + abi: TOKENOMICS.abi, + chainId: mainnet.id, + functionName: 'epochCounter', + }); + +const useEpochTokenomics = (epochCounter: number | undefined) => + useReadContract({ + address: TOKENOMICS.addresses[mainnet.id], + abi: TOKENOMICS.abi, + chainId: mainnet.id, + functionName: 'mapEpochTokenomics', + args: [BigInt(epochCounter || 0)], + query: { + enabled: epochCounter !== undefined, + }, + }); + +const useEpochLength = () => + useReadContract({ + address: TOKENOMICS.addresses[mainnet.id], + abi: TOKENOMICS.abi, + chainId: mainnet.id, + functionName: 'epochLen', + }); + +export const useThresholdData = () => { + const { data: veOLASThreshold, isFetching: isVeOLASThresholdFetching, refetch: refetchVeOLASThreshold } = useVeOLASThreshold(); + const { data: minAcceptedETH, isFetching: isMinAcceptedETHFetching, refetch: refetchMinAcceptedETH } = useMinAcceptedETH(); + const { data: epochCounter, isFetching: isEpochCounterFetching, refetch: refetchEpochCounter } = useEpochCounter(); + const { data: prevEpochPoint, isFetching: isPrevEpochPointFetching, refetch: refetchPrevEpochPoint } = useEpochTokenomics( + epochCounter !== undefined ? Number(epochCounter) - 1 : undefined, + ); + const { + data: epochLength, + isFetching: isEpochLengthFetching, + refetch: refetchEpochLength, + } = useEpochLength(); + + const nextEpochEndTime = useMemo(() => { + if (prevEpochPoint === undefined) return null; + if (epochLength === undefined) return null; + return prevEpochPoint.endTime + epochLength; + }, [prevEpochPoint, epochLength]); + + const refetchData = useCallback(async () => { + const promises = [ + refetchVeOLASThreshold(), + refetchMinAcceptedETH(), + refetchEpochCounter(), + refetchPrevEpochPoint(), + refetchEpochLength(), + ]; + + return Promise.all(promises); + // console.log('res', ress) + }, []); + + return { + veOLASThreshold, + minAcceptedETH: minAcceptedETH as bigint | undefined, + epochCounter, + prevEpochEndTime: prevEpochPoint?.endTime, + epochLength, + nextEpochEndTime, + isDataLoading: + isVeOLASThresholdFetching || + isMinAcceptedETHFetching || + isEpochCounterFetching || + isPrevEpochPointFetching || + isEpochLengthFetching, + refetchData + }; +}; \ No newline at end of file diff --git a/apps/govern/components/Donate/index.tsx b/apps/govern/components/Donate/index.tsx new file mode 100644 index 00000000..ac93b3b1 --- /dev/null +++ b/apps/govern/components/Donate/index.tsx @@ -0,0 +1,206 @@ +import { Alert, Button, Skeleton, Typography } from 'antd'; +import { ethers } from 'ethers'; +import isNumber from 'lodash/isNumber'; +import Link from 'next/link'; +import { useState } from 'react'; +import { useAccount } from 'wagmi'; + +import { NA, getFullFormattedDate, notifySuccess } from '@autonolas/frontend-library'; + +import { notifyError } from 'libs/util-functions/src'; + +import { + checkServicesTerminatedOrNotDeployed, + checkpointRequest, + depositServiceDonationRequest, +} from 'common-util/functions'; + +import { DonateForm } from './DonateForm'; +import { useThresholdData } from './hooks'; +import { DonateContainer, EpochCheckpointRow, EpochStatus } from './styles'; + +const { Title, Paragraph, Text } = Typography; + +const sortUnitIdsAndAmounts = (unitIds: number[], amounts: number[]) => { + const sortedUnitIds = [...unitIds].sort((a, b) => a - b); + const sortedAmounts = sortedUnitIds.map((e) => amounts[unitIds.indexOf(e)]); + return [sortedUnitIds, sortedAmounts]; +}; + +export const DonatePage = () => { + const { address: account } = useAccount(); + + const [isDonationLoading, setIsDonationLoading] = useState(false); + const [isCheckpointLoading, setIsCheckpointLoading] = useState(false); + + const { + isDataLoading, + refetchData, + veOLASThreshold, + minAcceptedETH, + epochCounter, + prevEpochEndTime, + epochLength, + nextEpochEndTime, + } = useThresholdData(); + + const onDepositServiceDonationSubmit = async (values: { + unitIds: number[]; + amounts: number[]; + }) => { + if (!account) return; + + try { + setIsDonationLoading(true); + + const [sortedUnitIds, sortedAmounts] = sortUnitIdsAndAmounts(values.unitIds, values.amounts); + + const serviceIds = sortedUnitIds.map((e) => `${e}`); + + const invalidServices = await checkServicesTerminatedOrNotDeployed(serviceIds); + + // deposit only if all services are deployed or not terminated + if (invalidServices.length > 0) { + throw new Error( + `Provided service IDs are not deployed or terminated ${invalidServices.join(', ')}`, + ); + } else { + const amounts = sortedAmounts.map((e) => ethers.parseUnits(`${e}`, 18).toString()); + const totalAmount = amounts.reduce( + (a, b) => ethers.toBigInt(a) + ethers.toBigInt(b), + BigInt(0), + ); + + if (minAcceptedETH !== undefined && minAcceptedETH > totalAmount) { + throw new Error( + `At least ${ethers.formatEther( + `${minAcceptedETH}`, + )} ETH of donations is required to trigger boosts.`, + ); + } else { + const params = { + account, + serviceIds, + amounts, + totalAmount: totalAmount.toString(), + }; + + await depositServiceDonationRequest(params); + notifySuccess('Deposited service donation successfully'); + } + } + } catch (error) { + console.error(error); + const errorMessage = + (error as Error).message || 'Error occurred on depositing service donation'; + notifyError(errorMessage); + throw error; + } finally { + setIsDonationLoading(false); + } + }; + + const onCheckpoint = async () => { + if (!account) return; + + try { + setIsCheckpointLoading(true); + await checkpointRequest({ account }); + await refetchData(); // update epoch details after checkpoint + notifySuccess('Started new epoch'); + } catch (error) { + console.error(error); + const errorMessage = (error as Error).message || 'Error occurred on starting new epoch'; + notifyError(errorMessage); + } finally { + setIsCheckpointLoading(false); + } + }; + + const epochStatusList = [ + { + text: 'Earliest possible expected end time', + value: nextEpochEndTime ? getFullFormattedDate(nextEpochEndTime * 1000) : NA, + }, + { + text: 'Epoch length', + value: isNumber(epochLength) ? `${epochLength / 3600 / 24} days` : NA, + }, + { + text: 'Previous epoch end time', + value: prevEpochEndTime ? getFullFormattedDate(prevEpochEndTime * 1000) : NA, + }, + { + text: 'Epoch counter', + value: epochCounter || NA, + }, + ]; + + // disable checkpoint button if expected end time is in the future + const isExpectedEndTimeInFuture = (nextEpochEndTime || 0) * 1000 > Date.now(); + + return ( + +
+ + Donate + + + Show appreciation for the value of an autonomous service by making a donation. The + protocol will reward devs who have contributed code for that service. + + + + To boost rewards of devs with freshly minted OLAS, you must hold at least  + {veOLASThreshold || NA} +  veOLAS. Grab your veOLAS by locking OLAS  + here. At least  + + {minAcceptedETH ? ethers.formatEther(`${minAcceptedETH}`) : NA} +  ETH + +  of donations is required to trigger boosts. + + } + className="mb-16" + /> + + +
+ +
+ + Epoch Status + + + {epochStatusList.map(({ text, value }, index) => ( + + {`${text}:`} + {isDataLoading ? ( + + ) : ( + {value} + )} + + ))} + + + + New epochs must be manually triggered by community members + +
+
+ ); +}; diff --git a/apps/govern/components/Donate/styles.tsx b/apps/govern/components/Donate/styles.tsx new file mode 100644 index 00000000..59ba1325 --- /dev/null +++ b/apps/govern/components/Donate/styles.tsx @@ -0,0 +1,50 @@ +import styled from 'styled-components'; + +import { COLOR, MEDIA_QUERY } from 'libs/ui-theme/src'; + +export const DonateContainer = styled.div` + display: flex; + padding: 0 16px; + + .donate-section { + width: 720px; + } + .last-epoch-section { + padding-left: 1rem; + margin-left: 1rem; + border-left: 1px solid ${COLOR.BORDER_GREY_2}; + } + + ${MEDIA_QUERY.mobileL} { + flex-direction: column; + .donate-section { + width: 100%; + } + .last-epoch-section { + padding-left: 0; + margin-left: 0; + border-left: none; + } + } +`; + +export const EpochStatus = styled.div` + display: flex; + align-items: center; + gap: 0.5rem; + margin-bottom: 0.75rem; + h5, + div { + margin: 0; + } +`; + +export const EpochCheckpointRow = styled.div` + display: flex; + flex-direction: column; + gap: 4px; + margin-top: 1rem; + .ant-btn { + width: 200px; + } +`; diff --git a/apps/govern/components/Layout/Menu.tsx b/apps/govern/components/Layout/Menu.tsx index 6f4cb926..d981bd4a 100644 --- a/apps/govern/components/Layout/Menu.tsx +++ b/apps/govern/components/Layout/Menu.tsx @@ -12,6 +12,7 @@ const items: MenuItem[] = [ { label: 'Staking contracts', key: 'contracts', path: '/contracts' }, { label: 'Proposals', key: 'proposals', path: '/proposals' }, { label: 'veOLAS', key: 'veolas', path: '/veolas' }, + { label: 'Donate', key: 'donate', path: '/donate' }, { label: 'Docs', key: 'docs', path: '/docs' }, ]; diff --git a/apps/govern/components/Proposals/Proposals.spec.tsx b/apps/govern/components/Proposals/index.spec.tsx similarity index 100% rename from apps/govern/components/Proposals/Proposals.spec.tsx rename to apps/govern/components/Proposals/index.spec.tsx diff --git a/apps/govern/components/Proposals/Proposals.tsx b/apps/govern/components/Proposals/index.tsx similarity index 100% rename from apps/govern/components/Proposals/Proposals.tsx rename to apps/govern/components/Proposals/index.tsx diff --git a/apps/govern/pages/donate.tsx b/apps/govern/pages/donate.tsx new file mode 100644 index 00000000..a655e295 --- /dev/null +++ b/apps/govern/pages/donate.tsx @@ -0,0 +1,3 @@ +import { DonatePage } from 'components/Donate'; + +export default DonatePage; diff --git a/apps/govern/pages/proposals.tsx b/apps/govern/pages/proposals.tsx index 1d63f204..c983755b 100644 --- a/apps/govern/pages/proposals.tsx +++ b/apps/govern/pages/proposals.tsx @@ -1,3 +1,3 @@ -import { ProposalsPage } from 'components/Proposals/Proposals'; +import { ProposalsPage } from 'components/Proposals'; export default ProposalsPage; diff --git a/apps/tokenomics/components/Donate/DonateForm.tsx b/apps/tokenomics/components/Donate/DonateForm.tsx new file mode 100644 index 00000000..39a0f9ba --- /dev/null +++ b/apps/tokenomics/components/Donate/DonateForm.tsx @@ -0,0 +1,192 @@ +import { Button, Form, Input, InputNumber, Space, Typography } from 'antd'; +import PropTypes from 'prop-types'; +import { useEffect } from 'react'; +import styled from 'styled-components'; + +import { FORM_TYPES } from 'common-util/enums'; + +// import { useHelpers } from '../hooks/useHelpers'; +// import { FormList } from './FormList'; +import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons'; + +const { Text } = Typography; + +export const DynamicFormContainer = styled.div` + max-width: 720px; + .ant-input-number { + width: 200px; + } +`; + +export const DynamicFieldsForm = ({ + isUnitTypeInput, + inputOneLabel, + inputTwoLabel, + buttonText, + isLoading, + submitButtonText, + onSubmit, + canResetOnSubmit, + dynamicFormType, + showAscendingOrderMessage, +}) => { + const { account } = useHelpers(); + const [form] = Form.useForm(); + + useEffect(() => { + if (account) { + if (dynamicFormType === FORM_TYPES.CLAIMABLE_INCENTIVES) { + form.setFieldsValue({ address: account }); + } + } + }, [account]); + + const onFinish = async (values) => { + if (onSubmit) { + try { + await onSubmit({ + unitIds: values.units.map((unit) => unit.unitId), + unitTypes: values.units.map((unit) => unit.unitType), + address: values.address, + }); + + if (canResetOnSubmit) { + form.resetFields(); + } + } catch (error) { + window.console.error(error); + } + } + }; + + return ( + +
+ {/* address input is only visible for claimable incentives */} + {dynamicFormType === FORM_TYPES.CLAIMABLE_INCENTIVES && ( + + + + )} + + { + if (!units || units?.length === 0) { + return Promise.reject(new Error('At least 1 unit is required')); + } + return Promise.resolve(); + }, + }, + ]} + > + {(fields, { add, remove }, { errors }) => ( + <> + {fields.map((field) => ( + + prevValues.units !== curValues.units} + > + {() => ( + + + + )} + + + + + + + {fields.length > 1 && remove(field.name)} />} + + ))} + + + + + + + + )} + + + {/* */} + + + + + {!account && ( + + To donate, connect a wallet + + )} + + +
+ ); +}; + +DynamicFieldsForm.propTypes = { + onSubmit: PropTypes.func.isRequired, + inputOneLabel: PropTypes.string, + inputTwoLabel: PropTypes.string, + buttonText: PropTypes.string, + submitButtonText: PropTypes.string, + isLoading: PropTypes.bool, + isUnitTypeInput: PropTypes.bool, + dynamicFormType: PropTypes.string, + canResetOnSubmit: PropTypes.bool, + showAscendingOrderMessage: PropTypes.bool, +}; + +DynamicFieldsForm.defaultProps = { + inputOneLabel: 'Unit ID', + inputTwoLabel: 'Unit Type', + buttonText: 'Add row', + submitButtonText: 'Submit', + isLoading: false, + isUnitTypeInput: true, + dynamicFormType: null, + canResetOnSubmit: false, + showAscendingOrderMessage: false, +}; diff --git a/apps/tokenomics/components/Donate/index.jsx b/apps/tokenomics/components/Donate/index.jsx index 74d416ff..c10c6982 100644 --- a/apps/tokenomics/components/Donate/index.jsx +++ b/apps/tokenomics/components/Donate/index.jsx @@ -1,4 +1,4 @@ -import { Alert, Button, Skeleton, Typography } from 'antd'; +import { Alert, Button, Typography } from 'antd'; import { ethers } from 'ethers'; import isNumber from 'lodash/isNumber'; import { useEffect, useState } from 'react'; @@ -194,11 +194,7 @@ export const DepositServiceDonation = () => { {epochStatusList.map((e, index) => ( {`${e.text}:`} - {isEpochDetailsLoading ? ( - - ) : ( - {e.value} - )} + {e.value} ))} diff --git a/libs/util-contracts/src/lib/abiAndAddresses/treasury.js b/libs/util-contracts/src/lib/abiAndAddresses/treasury.ts similarity index 99% rename from libs/util-contracts/src/lib/abiAndAddresses/treasury.js rename to libs/util-contracts/src/lib/abiAndAddresses/treasury.ts index aeb306c3..f8766fb7 100644 --- a/libs/util-contracts/src/lib/abiAndAddresses/treasury.js +++ b/libs/util-contracts/src/lib/abiAndAddresses/treasury.ts @@ -1,4 +1,6 @@ -export const TREASURY = { +import { Contract } from "./types"; + +export const TREASURY: Contract = { contractName: 'Treasury', addresses: { 1: '0xa0DA53447C0f6C4987964d8463da7e6628B30f82', diff --git a/libs/util-functions/src/lib/requests.ts b/libs/util-functions/src/lib/requests.ts index 36533382..3ac16366 100644 --- a/libs/util-functions/src/lib/requests.ts +++ b/libs/util-functions/src/lib/requests.ts @@ -1,5 +1,6 @@ import { Contract } from 'ethers'; + const ESTIMATED_GAS_LIMIT = 500_000; /** @@ -8,17 +9,18 @@ const ESTIMATED_GAS_LIMIT = 500_000; export const getEstimatedGasLimit = async ( fn: Contract['methods'], account: `0x${string}` | string | undefined, + value?: string, ) => { if (!account) { throw new Error('Invalid account passed to estimate gas limit'); } try { - const estimatedGas = await fn.estimateGas({ from: account }); + const estimatedGas = await fn.estimateGas({ from: account, value }); return Math.ceil(Number(estimatedGas) * 1.2); } catch (error) { window.console.warn(`Error occurred on estimating gas, defaulting to ${ESTIMATED_GAS_LIMIT}`); } return ESTIMATED_GAS_LIMIT; -}; +}; \ No newline at end of file