From 8ab6e2fa433f9b7116773ffca5281d18c8e3e16c Mon Sep 17 00:00:00 2001 From: MrX-SNX Date: Thu, 8 Aug 2024 09:08:53 +0100 Subject: [PATCH] added signer and lodash --- .../Admin/AdminPanel/Grants/CancelButton.js | 41 +- .../Admin/AdminPanel/Grants/GrantModal.js | 381 +++++++++++------- frontend/package-lock.json | 6 + frontend/package.json | 11 +- frontend/utils/ethers.js | 21 + 5 files changed, 292 insertions(+), 168 deletions(-) create mode 100644 frontend/utils/ethers.js diff --git a/frontend/components/Admin/AdminPanel/Grants/CancelButton.js b/frontend/components/Admin/AdminPanel/Grants/CancelButton.js index bd0311b..e4d24c7 100644 --- a/frontend/components/Admin/AdminPanel/Grants/CancelButton.js +++ b/frontend/components/Admin/AdminPanel/Grants/CancelButton.js @@ -1,42 +1,47 @@ -import { ethers } from 'ethers' -import { useToast } from '@chakra-ui/react' -import { DeleteIcon } from '@chakra-ui/icons' -import vesterAbi from '../../../../abis/Vester.json' -import { useSigner } from 'wagmi' +import { ethers } from "ethers"; +import { useToast } from "@chakra-ui/react"; +import { DeleteIcon } from "@chakra-ui/icons"; +import vesterAbi from "../../../../abis/Vester.json"; +import { useEthersSigner } from "../../../../utils/ethers"; +import { useAccount } from "wagmi"; export default function CancelButton({ tokenId }) { const toast = useToast(); - const { data: signer } = useSigner() + const { chain } = useAccount(); + const signer = useEthersSigner({ chainId: chain.id }); const executeCancel = async () => { - const vesterContract = new ethers.Contract(process.env.NEXT_PUBLIC_VESTER_CONTRACT_ADDRESS, vesterAbi.abi, signer); + const vesterContract = new ethers.Contract( + process.env.NEXT_PUBLIC_VESTER_CONTRACT_ADDRESS, + vesterAbi.abi, + signer + ); try { - await vesterContract.cancelGrant(tokenId) + await vesterContract.cancelGrant(tokenId); } catch (err) { - console.log(err) + console.log(err); toast({ title: "Error", description: err?.data?.message || err?.error?.data?.message, status: "error", isClosable: true, }); - return + return; } toast({ - title: 'Transaction Submitted', + title: "Transaction Submitted", description: - 'Refer to your wallet for the latest status of this transaction. Refresh the page for an updated list of grants.', - status: 'info', - position: 'top', + "Refer to your wallet for the latest status of this transaction. Refresh the page for an updated list of grants.", + status: "info", + position: "top", duration: 10000, isClosable: true, - }) - - } + }); + }; return ( executeCancel()} cursor="pointer" boxSize={4} /> - ) + ); } diff --git a/frontend/components/Admin/AdminPanel/Grants/GrantModal.js b/frontend/components/Admin/AdminPanel/Grants/GrantModal.js index b446e9c..9bea118 100644 --- a/frontend/components/Admin/AdminPanel/Grants/GrantModal.js +++ b/frontend/components/Admin/AdminPanel/Grants/GrantModal.js @@ -1,4 +1,4 @@ -import { useState } from 'react' +import { useState } from "react"; import { Button, Modal, @@ -15,50 +15,60 @@ import { Input, LightMode, InputGroup, - InputRightAddon -} from '@chakra-ui/react' -import { format } from 'date-fns' -import { EditIcon } from '@chakra-ui/icons' -import { ethers } from 'ethers' -import vesterAbi from '../../../../abis/Vester.json' -import { useToast } from '@chakra-ui/react' -import { useSigner } from 'wagmi' + InputRightAddon, +} from "@chakra-ui/react"; +import { format } from "date-fns"; +import { EditIcon } from "@chakra-ui/icons"; +import { ethers } from "ethers"; +import vesterAbi from "../../../../abis/Vester.json"; +import { useToast } from "@chakra-ui/react"; +import { useAccount } from "wagmi"; +import { useEthersSigner } from "../../../../utils/ethers"; export default function GrantModal({ grant }) { - const { data: signer } = useSigner() - const { isOpen, onOpen, onClose } = useDisclosure() + const { chain } = useAccount(); + const signer = useEthersSigner({ chainId: chain.id }); + const { isOpen, onOpen, onClose } = useDisclosure(); const toast = useToast(); - const [granteeAddress, setGranteeAddress] = useState('') - const [tokenAddress, setTokenAddress] = useState('') - const [startTimestamp, setStartTimestamp] = useState('0') - const [cliffTimestamp, setCliffTimestamp] = useState('0') - const [vestAmount, setVestAmount] = useState('0') - const [totalAmount, setTotalAmount] = useState('0') - const [amountRedeemed, setAmountRedeemed] = useState('0') - const [vestInterval, setVestInterval] = useState('0') + const [granteeAddress, setGranteeAddress] = useState(""); + const [tokenAddress, setTokenAddress] = useState(""); + const [startTimestamp, setStartTimestamp] = useState("0"); + const [cliffTimestamp, setCliffTimestamp] = useState("0"); + const [vestAmount, setVestAmount] = useState("0"); + const [totalAmount, setTotalAmount] = useState("0"); + const [amountRedeemed, setAmountRedeemed] = useState("0"); + const [vestInterval, setVestInterval] = useState("0"); const openHandler = () => { if (grant) { - setTokenAddress(grant.tokenAddress) - setStartTimestamp(grant.startTimestamp) - setCliffTimestamp(grant.cliffTimestamp) - setVestAmount(parseFloat(ethers.utils.formatUnits(grant.vestAmount, 18))) - setTotalAmount(parseFloat(ethers.utils.formatUnits(grant.totalAmount, 18))) - setAmountRedeemed(parseFloat(ethers.utils.formatUnits(grant.amountRedeemed, 18))) - setVestInterval(grant.vestInterval) + setTokenAddress(grant.tokenAddress); + setStartTimestamp(grant.startTimestamp); + setCliffTimestamp(grant.cliffTimestamp); + setVestAmount(parseFloat(ethers.utils.formatUnits(grant.vestAmount, 18))); + setTotalAmount( + parseFloat(ethers.utils.formatUnits(grant.totalAmount, 18)) + ); + setAmountRedeemed( + parseFloat(ethers.utils.formatUnits(grant.amountRedeemed, 18)) + ); + setVestInterval(grant.vestInterval); } onOpen(); - } + }; const executeTransaction = async () => { - const vesterContract = new ethers.Contract(process.env.NEXT_PUBLIC_VESTER_CONTRACT_ADDRESS, vesterAbi.abi, signer); + const vesterContract = new ethers.Contract( + process.env.NEXT_PUBLIC_VESTER_CONTRACT_ADDRESS, + vesterAbi.abi, + signer + ); let checksummedTokenAddress, checksummedGranteeAddress; try { - checksummedTokenAddress = ethers.utils.getAddress(tokenAddress) + checksummedTokenAddress = ethers.utils.getAddress(tokenAddress); if (!grant) { - checksummedGranteeAddress = ethers.utils.getAddress(granteeAddress) + checksummedGranteeAddress = ethers.utils.getAddress(granteeAddress); } } catch { toast({ @@ -67,143 +77,224 @@ export default function GrantModal({ grant }) { status: "error", isClosable: true, }); - return + return; } - const args = grant ? [ - grant.tokenId, - checksummedTokenAddress, - startTimestamp, - cliffTimestamp, - ethers.utils.parseEther(vestAmount.toString()), - ethers.utils.parseEther(totalAmount.toString()), - ethers.utils.parseEther(amountRedeemed.toString()), - vestInterval, - ] : [ - checksummedGranteeAddress, - checksummedTokenAddress, - startTimestamp, - cliffTimestamp, - ethers.utils.parseEther(vestAmount.toString()), - ethers.utils.parseEther(totalAmount.toString()), - ethers.utils.parseEther(amountRedeemed.toString()), - vestInterval, - ] + const args = grant + ? [ + grant.tokenId, + checksummedTokenAddress, + startTimestamp, + cliffTimestamp, + ethers.utils.parseEther(vestAmount.toString()), + ethers.utils.parseEther(totalAmount.toString()), + ethers.utils.parseEther(amountRedeemed.toString()), + vestInterval, + ] + : [ + checksummedGranteeAddress, + checksummedTokenAddress, + startTimestamp, + cliffTimestamp, + ethers.utils.parseEther(vestAmount.toString()), + ethers.utils.parseEther(totalAmount.toString()), + ethers.utils.parseEther(amountRedeemed.toString()), + vestInterval, + ]; try { if (grant) { - await vesterContract.replaceGrant(...args) + await vesterContract.replaceGrant(...args); } else { - await vesterContract.mint(...args) + await vesterContract.mint(...args); } } catch (err) { - console.log(err) + console.log(err); toast({ title: "Error", description: err?.data?.message || JSON.stringify(err), status: "error", isClosable: true, }); - return + return; } toast({ - title: 'Transaction Submitted', + title: "Transaction Submitted", description: - 'Refer to your wallet for the latest status of this transaction. Refresh the page for an updated list of grants.', - status: 'info', - position: 'top', + "Refer to your wallet for the latest status of this transaction. Refresh the page for an updated list of grants.", + status: "info", + position: "top", duration: 10000, isClosable: true, - }) - - } + }); + }; function displayTime(input) { - return format(new Date(input * 1000), 'MMM. d ’yy, p') + return format(new Date(input * 1000), "MMM. d ’yy, p"); } - return (<> - {grant ? - : - - } - - - - - {grant ? - `Replace Grant #${grant.tokenId}` : - "Create Grant" - } - - - - - {!grant && - Grantee Address - setGranteeAddress(e.target.value)} id='granteeAddress' /> - This is wallet address of the grant recipient. - } - - - Token Address - setTokenAddress(e.target.value)} id='tokenAddress' /> - This is the address of the ERC-20 token being provided by this grant. - - - - Start Timestamp - - setStartTimestamp(e.target.value)} id='startTimestamp' type="number" /> - {startTimestamp > 0 && {displayTime(startTimestamp)}} - - This is the time at which the the grant begins to vest. The current timestamp is {Math.floor(Date.now() / 1000)}. - - - - Cliff Timestamp - - setCliffTimestamp(e.target.value)} id='cliffTimestamp' type="number" /> - {cliffTimestamp > 0 && {displayTime(cliffTimestamp)}} - - This is the time before which no tokens may be redeemed. The timestamp six months from now is {Math.floor(Date.now() / 1000) + (7889400 * 2)}. - - - - Vesting Interval - setVestInterval(e.target.value)} id='vestInterval' type="number" /> - This is the number of seconds that must pass for each vesting amount to become available to the grantee. There are 7889400 seconds in each quarter. - - - - Vesting Amount - setVestAmount(e.target.value)} type="number" id='vestAmount' /> - This is the amount of tokens that are made available to the grantee each vesting interval. - - - - Amount Redeemed - setAmountRedeemed(e.target.value)} type="number" id='amountRedeemed' /> - This is the amount of tokens that have already been redeemed from this grant. - - - - Total Grant Amount - setTotalAmount(e.target.value)} type="number" id='totalAmount' /> - This is total amount of tokens awarded over the lifetime of this grant. - - - - - - - - - - - - ) + return ( + <> + {grant ? ( + + ) : ( + + + + )} + + + + + {grant ? `Replace Grant #${grant.tokenId}` : "Create Grant"} + + + + {!grant && ( + + Grantee Address + setGranteeAddress(e.target.value)} + id="granteeAddress" + /> + + This is wallet address of the grant recipient. + + + )} + + + Token Address + setTokenAddress(e.target.value)} + id="tokenAddress" + /> + + This is the address of the ERC-20 token being provided by this + grant. + + + + + Start Timestamp + + setStartTimestamp(e.target.value)} + id="startTimestamp" + type="number" + /> + {startTimestamp > 0 && ( + + {displayTime(startTimestamp)} + + )} + + + This is the time at which the the grant begins to vest. The + current timestamp is {Math.floor(Date.now() / 1000)}. + + + + + Cliff Timestamp + + setCliffTimestamp(e.target.value)} + id="cliffTimestamp" + type="number" + /> + {cliffTimestamp > 0 && ( + + {displayTime(cliffTimestamp)} + + )} + + + This is the time before which no tokens may be redeemed. The + timestamp six months from now is{" "} + {Math.floor(Date.now() / 1000) + 7889400 * 2}. + + + + + Vesting Interval + setVestInterval(e.target.value)} + id="vestInterval" + type="number" + /> + + This is the number of seconds that must pass for each vesting + amount to become available to the grantee. There are 7889400 + seconds in each quarter. + + + + + Vesting Amount + setVestAmount(e.target.value)} + type="number" + id="vestAmount" + /> + + This is the amount of tokens that are made available to the + grantee each vesting interval. + + + + + Amount Redeemed + setAmountRedeemed(e.target.value)} + type="number" + id="amountRedeemed" + /> + + This is the amount of tokens that have already been redeemed + from this grant. + + + + + Total Grant Amount + setTotalAmount(e.target.value)} + type="number" + id="totalAmount" + /> + + This is total amount of tokens awarded over the lifetime of this + grant. + + + + + + + + + + + + + ); } diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 46d48d9..727ce43 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -15,6 +15,7 @@ "date-fns": "^2.27.0", "ethers": "5.7.2", "framer-motion": "^5.6.0", + "lodash": "^4.17.21", "next": "^14.2.3", "react": "^18.3.0", "react-dom": "^18.3.0", @@ -13046,6 +13047,11 @@ "node": ">=8" } }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, "node_modules/lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", diff --git a/frontend/package.json b/frontend/package.json index 496813d..3dfc1df 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -14,15 +14,16 @@ "@emotion/styled": "^11.13.0", "@rainbow-me/rainbowkit": "^2.1.4", "@tanstack/react-query": "^5.51.11", - "next": "^14.2.3", - "react": "^18.3.0", - "react-dom": "^18.3.0", - "wagmi": "^2.12.0", "date-fns": "^2.27.0", "ethers": "5.7.2", "framer-motion": "^5.6.0", + "lodash": "^4.17.21", + "next": "^14.2.3", + "react": "^18.3.0", + "react-dom": "^18.3.0", "react-icons": "^4.3.1", - "recoil": "^0.5.2" + "recoil": "^0.5.2", + "wagmi": "^2.12.0" }, "devDependencies": { "eslint": "8.5.0", diff --git a/frontend/utils/ethers.js b/frontend/utils/ethers.js new file mode 100644 index 0000000..0be2733 --- /dev/null +++ b/frontend/utils/ethers.js @@ -0,0 +1,21 @@ +import { providers } from "ethers"; +import { useMemo } from "react"; +import { useConnectorClient } from "wagmi"; + +export function clientToSigner(client) { + const { account, chain, transport } = client; + const network = { + chainId: chain.id, + name: chain.name, + ensAddress: chain.contracts?.ensRegistry?.address, + }; + const provider = new providers.Web3Provider(transport, network); + const signer = provider.getSigner(account.address); + return signer; +} + +/** Hook to convert a Viem Client to an ethers.js Signer. */ +export function useEthersSigner({ chainId } = {}) { + const { data: client } = useConnectorClient < Config > { chainId }; + return useMemo(() => (client ? clientToSigner(client) : undefined), [client]); +}