diff --git a/abi/DelegateRegistry.json b/abi/DelegateRegistry.json new file mode 100644 index 000000000..f6414a8fd --- /dev/null +++ b/abi/DelegateRegistry.json @@ -0,0 +1,107 @@ +[ + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "delegator", + "type": "address" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "id", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "delegate", + "type": "address" + } + ], + "name": "ClearDelegate", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "delegator", + "type": "address" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "id", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "delegate", + "type": "address" + } + ], + "name": "SetDelegate", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "name": "delegation", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "id", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "delegate", + "type": "address" + } + ], + "name": "setDelegate", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "id", + "type": "bytes32" + } + ], + "name": "clearDelegate", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] diff --git a/components/governance/Claimable/index.tsx b/components/governance/Claimable/index.tsx index b21eb2840..d48a51b5a 100644 --- a/components/governance/Claimable/index.tsx +++ b/components/governance/Claimable/index.tsx @@ -167,4 +167,4 @@ const NFT: FC = () => { ); }; -export default Claimable; +export default React.memo(Claimable); diff --git a/components/governance/Delegation/index.tsx b/components/governance/Delegation/index.tsx index 4a1671fae..480286810 100644 --- a/components/governance/Delegation/index.tsx +++ b/components/governance/Delegation/index.tsx @@ -1,49 +1,67 @@ -import React, { useMemo, useState } from 'react'; -import { Avatar, Box, Button, Collapse, Divider, Skeleton, TextField, Typography } from '@mui/material'; -import { Trans, useTranslation } from 'react-i18next'; +import React, { FC, useCallback, useMemo, useState } from 'react'; +import { + Avatar, + Box, + Button, + Dialog, + Divider, + IconButton, + Skeleton, + Slide, + TextField, + Typography, + useMediaQuery, + useTheme, +} from '@mui/material'; +import CloseIcon from '@mui/icons-material/Close'; + +import { useTranslation } from 'react-i18next'; import { LoadingButton } from '@mui/lab'; -import HowToVoteIcon from '@mui/icons-material/HowToVote'; -import AddIcon from '@mui/icons-material/Add'; -import { isAddress, parseEther, zeroAddress, formatEther } from 'viem'; +import { isAddress, zeroAddress } from 'viem'; import { formatWallet } from 'utils/utils'; -import { useEXA, useEXADelegates, useEXAPrepareDelegate } from 'hooks/useEXA'; -import formatNumber from 'utils/formatNumber'; -import useBalance from 'hooks/useBalance'; import { useWeb3 } from 'hooks/useWeb3'; -import { useExaDelegate } from 'types/abi'; +import { useDelegateRegistryClearDelegate, useDelegateRegistrySetDelegate } from 'types/abi'; import { mainnet, useEnsAvatar, useEnsName, useNetwork, useSwitchNetwork, useWaitForTransaction } from 'wagmi'; import * as blockies from 'blockies-ts'; -import { useAirdropStreams } from 'hooks/useAirdrop'; -import { useSablierV2LockupLinearGetWithdrawnAmount } from 'hooks/useSablier'; - -type Props = { - amount: bigint; -}; +import { useDelegation, usePrepareClearDelegate, usePrepareDelegate } from 'hooks/useDelegateRegistry'; +import formatNumber from 'utils/formatNumber'; +import useGovernance from 'hooks/useGovernance'; -const Delegation = ({ amount }: Props) => { +const Delegation = () => { + const { votingPower: yourVotes } = useGovernance(false); + const { votingPower, fetchVotingPower } = useGovernance(); const { t } = useTranslation(); const { chain: displayNetwork, walletAddress, impersonateActive, exitImpersonate } = useWeb3(); - const [selected, setSelected] = useState<'self-delegate' | 'add-delegate'>(); + const [open, setOpen] = useState(false); const [input, setInput] = useState(''); - const exa = useEXA(); - const exaBalance = useBalance('EXA', exa?.address); - const { data: delegate, isLoading: isLoadingDelegate, refetch: refetchDelegate } = useEXADelegates(); - const { config } = useEXAPrepareDelegate({ - args: - delegate !== zeroAddress - ? [zeroAddress] - : selected === 'add-delegate' && isAddress(input) - ? [input] - : [walletAddress ?? zeroAddress], - }); - const { write, isLoading: submitLoading, data } = useExaDelegate(config); + const { data: delegate, isLoading: isLoadingDelegate, refetch: refetchDelegate } = useDelegation(); + const { config } = usePrepareDelegate(isAddress(input) ? input : zeroAddress); + const { write, isLoading: submitLoading, data } = useDelegateRegistrySetDelegate(config); + const { config: configClearDelegate } = usePrepareClearDelegate(delegate !== zeroAddress); + const { + write: writeClearDelegate, + isLoading: clearDelegateLoading, + data: clearDelegateData, + } = useDelegateRegistryClearDelegate(configClearDelegate); const { isLoading: waitingDelegate } = useWaitForTransaction({ hash: data?.hash, onSettled: () => { refetchDelegate(); + fetchVotingPower(); + setOpen(false); }, }); - const { data: delegateENS } = useEnsName({ address: delegate, chainId: mainnet.id }); + const { isLoading: waitingClearDelegate } = useWaitForTransaction({ + hash: clearDelegateData?.hash, + onSettled: () => { + refetchDelegate(); + fetchVotingPower(); + }, + }); + const { data: delegateENS } = useEnsName({ + address: delegate === zeroAddress ? walletAddress : delegate, + chainId: mainnet.id, + }); const { data: delegateENSAvatar, error: ensAvatarError } = useEnsAvatar({ name: delegateENS, chainId: mainnet.id, @@ -52,18 +70,26 @@ const Delegation = ({ amount }: Props) => { const { chain } = useNetwork(); const { switchNetwork, isLoading: switchIsLoading } = useSwitchNetwork(); - const { data: stream } = useAirdropStreams(); - const { data: withdrawn } = useSablierV2LockupLinearGetWithdrawnAmount(stream); - - const totalVotes = useMemo(() => { - return formatNumber(formatEther(parseEther(exaBalance ?? '0') + (amount - (withdrawn ?? 0n)))); - }, [exaBalance, amount, withdrawn]); - const delegateAvatar = useMemo(() => { if (!delegate) return ''; if (delegateENSAvatar && !ensAvatarError) return delegateENSAvatar; - return blockies.create({ seed: delegate.toLocaleLowerCase() }).toDataURL(); - }, [delegate, delegateENSAvatar, ensAvatarError]); + return blockies + .create({ seed: (delegate === zeroAddress ? walletAddress : delegate)?.toLocaleLowerCase() }) + .toDataURL(); + }, [delegate, delegateENSAvatar, ensAvatarError, walletAddress]); + + const openDialog = useCallback(() => { + setOpen(true); + }, []); + + const closeDialog = useCallback(() => { + setOpen(false); + }, []); + + const delegatedToYou = useMemo(() => { + if (votingPower === undefined || yourVotes === undefined) return undefined; + return delegate === zeroAddress && votingPower - yourVotes > 0 ? votingPower - yourVotes : votingPower; + }, [delegate, votingPower, yourVotes]); if (isLoadingDelegate) { return ( @@ -75,153 +101,61 @@ const Delegation = ({ amount }: Props) => { ); } - if (delegate !== zeroAddress) { - return ( - - - - {t('Votes Delegation')} - {exaBalance !== undefined ? ( - - , - }} - values={{ amount: totalVotes }} - /> - - ) : ( - - )} - - - - - {delegateENS ? delegateENS : formatWallet(delegate)} - - - {chain && chain.id !== displayNetwork.id ? ( - switchNetwork?.(displayNetwork.id)} - loading={switchIsLoading} - > - {t('Please switch to {{network}} network', { network: displayNetwork.name })} - - ) : ( - - {t('Revoke delegation')} - - )} - - ); - } - return ( - + - {t('Votes Delegation')} - {exaBalance !== undefined ? ( - - , - }} - values={{ amount: totalVotes }} - /> - - ) : ( - - )} - - - `1px solid ${palette.figma.grey[100]}`} - borderRadius="8px" - sx={{ - '&:hover': { - cursor: 'pointer', - bgcolor: 'grey.900', - color: 'grey.50', - }, - bgcolor: selected === 'self-delegate' ? 'grey.900' : '', - color: selected === 'self-delegate' ? 'grey.50' : '', - pointerEvents: submitLoading || waitingDelegate ? 'none' : 'auto', - }} - onClick={() => setSelected('self-delegate')} - > - - - {t('Self Delegate')} - - - {t('Use your voting rights to vote on proposals directly from your connected wallet.')} - - - `1px solid ${palette.figma.grey[100]}`} - borderRadius="8px" - sx={{ - '&:hover': { - cursor: 'pointer', - bgcolor: 'grey.900', - color: 'grey.50', - }, - bgcolor: selected === 'add-delegate' ? 'grey.900' : '', - color: selected === 'add-delegate' ? 'grey.50' : '', - pointerEvents: submitLoading || waitingDelegate ? 'none' : 'auto', - }} - onClick={() => setSelected('add-delegate')} - > - - - {t('Add Delegate')} - - - {t( - 'Delegate your voting rights to a trusted third-party Ethereum address. You never send EXA tokens, only your voting rights and can revoke the delegation at any time.', - )} - + + {t('Voting Power')} + {votingPower === undefined ? ( + + ) : ( + + {votingPower === 0 ? 0 : formatNumber(votingPower, 'USD', true)} + + )} - - - - {t( - 'Enter the address of the third-party you wish to delegate your voting rights to below. You can also check the Delegates List and find someone to represent you.', - )} + {votingPower !== undefined && yourVotes !== undefined && delegatedToYou !== undefined ? ( + yourVotes === 0 && votingPower === 0 ? ( + + {t('There is no voting power in the connected wallet, and no votes have been delegated to you.')} - setInput(e.target.value)} - /> - - + ) : ( + + + {t('Delegated to you')} + + {delegatedToYou === 0 ? 0 : formatNumber(delegatedToYou, 'USD', true)} + + + + {delegate === zeroAddress ? ( + {t('On connected wallet')} + ) : ( + + {t('Delegated to')} + + + + {delegateENS ? delegateENS : formatWallet(delegate === zeroAddress ? walletAddress : delegate)} + + + + )} + {yourVotes === 0 ? 0 : formatNumber(yourVotes, 'USD', true)} + + + ) + ) : ( + + )} {impersonateActive ? ( @@ -238,19 +172,111 @@ const Delegation = ({ amount }: Props) => { {t('Please switch to {{network}} network', { network: displayNetwork.name })} ) : ( + + + {t('Delegate votes')} + + {delegate !== zeroAddress && ( + + {t('Revoke delegation')} + + )} + + )} + + {t( + 'Delegate your voting rights to a trusted third-party Ethereum address. You never send EXA tokens, only your voting rights and can re-delegate or revoke the delegation at any time.', + )} + + + ); +}; + +type DelegateInputDialogProps = { + open: boolean; + onClose: () => void; + input: string; + setInput: (input: string) => void; + onDelegate?: () => void; + isLoading?: boolean; +}; + +const DelegateInputDialog: FC = ({ + open, + onClose, + input, + setInput, + onDelegate, + isLoading, +}) => { + const { walletAddress } = useWeb3(); + const { t } = useTranslation(); + const { breakpoints } = useTheme(); + const isMobile = useMediaQuery(breakpoints.down('md')); + + return ( + + + + + + {t('Votes Delegation')} + + {t( + 'Enter the address of the third-party you wish to delegate your voting rights to below. You can also check the Delegates List and find someone to represent you.', + )} + + setInput(e.target.value)} + /> - {selected === 'add-delegate' && Boolean(input && isAddress(input)) - ? `${t('Delegate Votes to')} ${formatWallet(input)}` - : t('Delegate Votes')} + {t('Delegate votes')} - )} - + + ); }; diff --git a/components/governance/Proposals/index.tsx b/components/governance/Proposals/index.tsx index 8bd1eae02..f9af33ea7 100644 --- a/components/governance/Proposals/index.tsx +++ b/components/governance/Proposals/index.tsx @@ -1,31 +1,35 @@ -import React from 'react'; -import { Box, Divider, Typography } from '@mui/material'; +import React, { useMemo } from 'react'; +import { Box, Button, Divider, Typography } from '@mui/material'; import { useTranslation } from 'react-i18next'; +import { optimism } from 'wagmi/chains'; +import { useWeb3 } from 'hooks/useWeb3'; const Proposals = () => { const { t } = useTranslation(); + const { chain } = useWeb3(); + + const spaceURL = useMemo( + () => (chain.id === optimism.id ? 'https://gov.exact.ly/' : 'https://demo.snapshot.org/#/exa.eth'), + [chain], + ); return ( - + {t('Proposals')} - - {t('Coming soon')} + + {t( + "Use your voting power to participate in discussions, propose enhancements, and cast votes to shape the Protocol's evolution on Snapchat", + )} - - {t( - "Stay tuned to our Discord and Twitter for updates, and get ready to vote and shape the protocol's evolution.", - )} - + + + ); diff --git a/components/governance/VotingPower/index.tsx b/components/governance/VotingPower/index.tsx deleted file mode 100644 index 1030572ba..000000000 --- a/components/governance/VotingPower/index.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import React, { useMemo } from 'react'; -import { Box, Skeleton, Typography } from '@mui/material'; -import { formatEther } from 'viem'; -import { useTranslation } from 'react-i18next'; -import { useEXAGetVotes, useEXADelegates } from 'hooks/useEXA'; -import formatNumber from 'utils/formatNumber'; -import { useWeb3 } from 'hooks/useWeb3'; -import { useAirdropStreams } from 'hooks/useAirdrop'; -import { useSablierV2LockupLinearGetWithdrawnAmount } from 'hooks/useSablier'; - -type Props = { - amount: bigint; -}; - -const VotingPower = ({ amount }: Props) => { - const { t } = useTranslation(); - const { walletAddress } = useWeb3(); - const { data: votes, isLoading: isLoadingGetVotes } = useEXAGetVotes(); - const { data: stream } = useAirdropStreams(); - const { data: delegatee, isLoading: isLoadingDelegatee } = useEXADelegates(); - const { data: withdrawn, isLoading: isLoadingWithdrawn } = useSablierV2LockupLinearGetWithdrawnAmount(stream); - - const totalVotes = useMemo(() => { - return (votes ?? 0n) + (delegatee === walletAddress ? amount - (withdrawn ?? 0n) : 0n); - }, [votes, amount, walletAddress, delegatee, withdrawn]); - - return ( - - - {t('Voting Power')} - {votes === undefined || isLoadingDelegatee || isLoadingWithdrawn || isLoadingGetVotes ? ( - - ) : ( - - {formatNumber(formatEther(totalVotes))} - - )} - - {votes === 0n && ( - - {t('You have no voting power in your connected wallet.')} - - )} - - ); -}; - -export default VotingPower; diff --git a/hooks/useApprove.ts b/hooks/useApprove.ts index 4846b6c1f..79c27aae3 100644 --- a/hooks/useApprove.ts +++ b/hooks/useApprove.ts @@ -108,14 +108,14 @@ function useApprove({ break; case 'repay': case 'repayAtMaturity': - quantity = (parseUnits(qty, marketAccount.decimals) * 1005n) / 1000n; + quantity = (parseUnits(qty, marketAccount.decimals) * 101n) / 100n; break; case 'borrow': case 'borrowAtMaturity': case 'withdraw': case 'withdrawAtMaturity': quantity = - ((await contract.read.previewWithdraw([parseUnits(qty, marketAccount.decimals)], opts)) * 1005n) / 1000n; + ((await contract.read.previewWithdraw([parseUnits(qty, marketAccount.decimals)], opts)) * 101n) / 100n; break; } diff --git a/hooks/useDelegateRegistry.ts b/hooks/useDelegateRegistry.ts new file mode 100644 index 000000000..1d9816c76 --- /dev/null +++ b/hooks/useDelegateRegistry.ts @@ -0,0 +1,53 @@ +import { Address, stringToHex, zeroAddress } from 'viem'; +import { + useDelegateRegistryDelegation, + usePrepareDelegateRegistryClearDelegate, + usePrepareDelegateRegistrySetDelegate, +} from 'types/abi'; +import { useWeb3 } from './useWeb3'; +import { useMemo } from 'react'; +import { optimism } from 'wagmi/chains'; + +const DELEGATE_REGISTRY_ADDRESS = '0x469788fE6E9E9681C6ebF3bF78e7Fd26Fc015446'; +const SNAPSHOT_SPACE_OPTIMISM = 'gov.exa.eth'; +const SNAPSHOT_SPACE_GOERLI = 'exa.eth'; + +export const useDelegation = () => { + const { chain, walletAddress } = useWeb3(); + const space = useMemo(() => (chain.id === optimism.id ? SNAPSHOT_SPACE_OPTIMISM : SNAPSHOT_SPACE_GOERLI), [chain.id]); + const encodedSpace = useMemo(() => stringToHex(space, { size: 32 }), [space]); + + return useDelegateRegistryDelegation({ + chainId: chain.id, + address: DELEGATE_REGISTRY_ADDRESS, + args: [walletAddress ?? zeroAddress, encodedSpace], + }); +}; + +export const usePrepareDelegate = (address: Address) => { + const { chain, walletAddress } = useWeb3(); + const space = useMemo(() => (chain.id === optimism.id ? SNAPSHOT_SPACE_OPTIMISM : SNAPSHOT_SPACE_GOERLI), [chain.id]); + const encodedSpace = useMemo(() => stringToHex(space, { size: 32 }), [space]); + + return usePrepareDelegateRegistrySetDelegate({ + enabled: address !== zeroAddress && address !== walletAddress, + chainId: chain.id, + address: DELEGATE_REGISTRY_ADDRESS, + account: walletAddress ?? zeroAddress, + args: [encodedSpace, address], + }); +}; + +export const usePrepareClearDelegate = (enabled: boolean) => { + const { chain, walletAddress } = useWeb3(); + const space = useMemo(() => (chain.id === optimism.id ? SNAPSHOT_SPACE_OPTIMISM : SNAPSHOT_SPACE_GOERLI), [chain.id]); + const encodedSpace = useMemo(() => stringToHex(space, { size: 32 }), [space]); + + return usePrepareDelegateRegistryClearDelegate({ + enabled, + chainId: chain.id, + address: DELEGATE_REGISTRY_ADDRESS, + account: walletAddress ?? zeroAddress, + args: [encodedSpace], + }); +}; diff --git a/hooks/useGovernance.ts b/hooks/useGovernance.ts new file mode 100644 index 000000000..0719d4f5b --- /dev/null +++ b/hooks/useGovernance.ts @@ -0,0 +1,62 @@ +import snapshot from '@snapshot-labs/snapshot.js'; +import { useWeb3 } from './useWeb3'; +import { useCallback, useEffect, useMemo, useState } from 'react'; +import optimismEXA from '@exactly/protocol/deployments/optimism/EXA.json'; +import goerliEXA from '@exactly/protocol/deployments/goerli/EXA.json'; +import { optimism } from 'wagmi/chains'; + +export default function useGovernance(delegation = true) { + const [votingPower, setVotingPower] = useState(undefined); + const { chain, walletAddress } = useWeb3(); + + const exaAddress = useMemo(() => (chain.id === optimism.id ? optimismEXA.address : goerliEXA.address), [chain.id]); + const space = useMemo(() => (chain.id === optimism.id ? 'gov.exa.eth' : 'exa.eth'), [chain.id]); + + const strategies = useMemo( + () => [ + { + name: 'erc20-balance-of', + params: { + symbol: 'EXA', + address: exaAddress, + decimals: 18, + }, + }, + { + name: 'sablier-v2', + params: { + policy: 'reserved-recipient', + symbol: 'EXA', + address: exaAddress, + decimals: 18, + }, + }, + ], + [exaAddress], + ); + + const url = 'https://score.snapshot.org/'; + + const fetchVotingPower = useCallback(async () => { + if (!walletAddress) return; + const { vp } = await snapshot.utils.getVp( + walletAddress, + String(chain.id), + strategies, + 'latest', + space, + delegation, + { + url, + }, + ); + setVotingPower(vp); + return vp; + }, [chain.id, delegation, space, strategies, walletAddress]); + + useEffect(() => { + fetchVotingPower(); + }, [fetchVotingPower]); + + return { votingPower, fetchVotingPower }; +} diff --git a/i18n/es/translation.json b/i18n/es/translation.json index e76a7e89d..786810c88 100644 --- a/i18n/es/translation.json +++ b/i18n/es/translation.json @@ -300,19 +300,11 @@ "EXA token holder?": "¿Tienes tokens EXA?", "Connect your wallet to check for EXA tokens and start participating in the protocol’s Governance.": "Conecta tu billetera para verificar si tienes tokens EXA y comienza a participar en la Gobernanza del protocolo.", "Votes Delegation": "Delegación de Votos", - "Delegate Votes": "Delegar Votos", + "Delegate votes": "Delegar votos", "Voting Power": "Poder de Voto", "Governance": "Gobernanza", - "Self Delegate": "Auto Delegar", - "Use your voting rights to vote on proposals directly from your connected wallet.": "Usa tus derechos de voto para votar en propuestas directamente desde tu billetera conectada.", - "Add Delegate": "Añadir Delegado", - "Delegate your voting rights to a trusted third-party Ethereum address. You never send EXA tokens, only your voting rights and can revoke the delegation at any time.": "Delega tus derechos de voto a una dirección de Ethereum de terceros de confianza. Nunca envías tokens EXA, solo tus derechos de voto y puedes revocar la delegación en cualquier momento.", "Enter the address of the third-party you wish to delegate your voting rights to below. You can also check the Delegates List and find someone to represent you.": "Ingresa la dirección del tercero al que deseas delegar tus derechos de voto a continuación. También puedes consultar la Lista de Delegados y encontrar a alguien que te represente.", - "Enter delegate address": "Ingresa la dirección del delegado", - "Delegate Votes to": "Delegar Votos a", "Proposals": "Propuestas", - "Coming soon": "Próximamente", - "Stay tuned to our Discord and Twitter for updates, and get ready to vote and shape the protocol's evolution.": "Mantente atento a nuestro Discord y Twitter para actualizaciones, y prepárate para votar y dar forma a la evolución del protocolo.", "Expired": "Expirado", "Processing transaction...": "Procesando transacción...", "Account Health Factor": "Factor de Salud de la cuenta", @@ -353,7 +345,6 @@ "Take control of your investments with strategies that balance risk and reward for long-term success.": "Toma control de tus inversiones con estrategias que equilibren el riesgo y la recompensa para el éxito a largo plazo.", "You are currently over leveraged with the selected markets.": "Actualmente estás sobre apalancado con los mercados seleccionados.", "The APR displayed comes from the Lido API.": "La TNA proviene de la API de Lido.", - "You have no voting power in your connected wallet.": "No tienes poder de voto en tu billetera conectada.", "Revoke delegation": "Revocar delegación", "Withdraw {{ value }} EXA": "Retirar {{ value }} EXA", "Exactly DAO Governance": "Governanza de Exactly DAO", @@ -481,5 +472,12 @@ "Exactly": "Exactly", "Third-Party": "Terceros", "Security": "Seguridad", - "The DebtManager contract is responsible for the leverage, deleverage, and rollover functionality of the protocol.": "El contrato DebtManager es responsable de la funcionalidad de apalancamiento, desapalancamiento y refinanciamiento del protocolo." + "The DebtManager contract is responsible for the leverage, deleverage, and rollover functionality of the protocol.": "El contrato DebtManager es responsable de la funcionalidad de apalancamiento, desapalancamiento y refinanciamiento del protocolo.", + "Delegate your voting rights to a trusted third-party Ethereum address. You never send EXA tokens, only your voting rights and can re-delegate or revoke the delegation at any time.": "Delega tus derechos de voto a una dirección de Ethereum de terceros de confianza. Nunca envías tokens EXA, solo tus derechos de voto y puedes volver a delegar o revocar la delegación en cualquier momento.", + "Use your voting power to participate in discussions, propose enhancements, and cast votes to shape the Protocol's evolution on Snapchat": "Usa tu poder de voto para participar en discusiones, proponer mejoras y emitir votos para dar forma a la evolución del Protocolo en Snapchat", + "View Proposals": "Ver Propuestas", + "There is no voting power in the connected wallet, and no votes have been delegated to you.": "No hay poder de voto en la billetera conectada, y no se te han delegado votos.", + "Delegated to you": "Delegado a ti", + "On connected wallet": "En billetera conectada", + "Delegated to": "Delegado a" } diff --git a/package-lock.json b/package-lock.json index a9ead75da..1fd0950e1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,6 +18,7 @@ "@mui/material": "^5.14.3", "@sentry/integrations": "^7.61.1", "@sentry/nextjs": "^7.61.1", + "@snapshot-labs/snapshot.js": "^0.6.1", "@socket.tech/plugin": "^1.2.1", "@upstash/ratelimit": "^0.4.4", "@vercel/kv": "^0.2.2", @@ -359,6 +360,11 @@ "version": "0.3.1", "license": "MIT" }, + "node_modules/@ensdomains/eth-ens-namehash": { + "version": "2.0.15", + "resolved": "https://registry.npmjs.org/@ensdomains/eth-ens-namehash/-/eth-ens-namehash-2.0.15.tgz", + "integrity": "sha512-JRDFP6+Hczb1E0/HhIg0PONgBYasfGfDheujmfxaZaAv/NAH4jE6Kf48WbqfRZdxt4IZI3jl3Ri7sZ1nP09lgw==" + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.4.0", "dev": true, @@ -2337,6 +2343,50 @@ "mkdirp": "bin/cmd.js" } }, + "node_modules/@snapshot-labs/snapshot.js": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/@snapshot-labs/snapshot.js/-/snapshot.js-0.6.1.tgz", + "integrity": "sha512-kIJJPE7N7izOxZM5FvrHrAdH3DWVa3p1d2w3P1TqrBDI6129pq4YyHXaFVFu3shWSAm4z243aVRDHwYgQbkkbQ==", + "dependencies": { + "@ensdomains/eth-ens-namehash": "^2.0.15", + "@ethersproject/abi": "^5.6.4", + "@ethersproject/address": "^5.6.1", + "@ethersproject/bytes": "^5.6.1", + "@ethersproject/contracts": "^5.6.2", + "@ethersproject/hash": "^5.7.0", + "@ethersproject/providers": "^5.6.8", + "@ethersproject/units": "^5.7.0", + "@ethersproject/wallet": "^5.6.2", + "ajv": "^8.11.0", + "ajv-formats": "^2.1.1", + "cross-fetch": "^3.1.6", + "json-to-graphql-query": "^2.2.4", + "lodash.set": "^4.3.2" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@snapshot-labs/snapshot.js/node_modules/ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@snapshot-labs/snapshot.js/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + }, "node_modules/@socket.tech/ll-core": { "version": "0.1.47", "resolved": "https://registry.npmjs.org/@socket.tech/ll-core/-/ll-core-0.1.47.tgz", @@ -4496,6 +4546,42 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-formats/node_modules/ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + }, "node_modules/ajv-keywords": { "version": "3.5.2", "devOptional": true, @@ -7742,7 +7828,6 @@ }, "node_modules/fast-deep-equal": { "version": "3.1.3", - "devOptional": true, "license": "MIT" }, "node_modules/fast-diff": { @@ -9291,6 +9376,11 @@ "version": "5.0.1", "license": "ISC" }, + "node_modules/json-to-graphql-query": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/json-to-graphql-query/-/json-to-graphql-query-2.2.5.tgz", + "integrity": "sha512-5Nom9inkIMrtY992LMBBG1Zaekrc10JaRhyZgprwHBVMDtRgllTvzl0oBbg13wJsVZoSoFNNMaeIVQs0P04vsA==" + }, "node_modules/json5": { "version": "2.2.3", "dev": true, @@ -9488,6 +9578,11 @@ "dev": true, "license": "MIT" }, + "node_modules/lodash.set": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/lodash.set/-/lodash.set-4.3.2.tgz", + "integrity": "sha512-4hNPN5jlm/N/HLMCO43v8BXKq9Z7QdAGc/VGrRD61w8gN9g/6jF9A4L1pbUgBLCffi0w9VsXfTOij5x8iTyFvg==" + }, "node_modules/log-symbols": { "version": "4.1.0", "dev": true, @@ -10901,7 +10996,6 @@ }, "node_modules/punycode": { "version": "2.3.0", - "devOptional": true, "license": "MIT", "engines": { "node": ">=6" @@ -11458,6 +11552,14 @@ "node": ">=0.10.0" } }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/require-main-filename": { "version": "2.0.0", "license": "ISC" @@ -13677,7 +13779,6 @@ }, "node_modules/uri-js": { "version": "4.4.1", - "devOptional": true, "license": "BSD-2-Clause", "dependencies": { "punycode": "^2.1.0" diff --git a/package.json b/package.json index 12860c323..25de3a8e7 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "@mui/material": "^5.14.3", "@sentry/integrations": "^7.61.1", "@sentry/nextjs": "^7.61.1", + "@snapshot-labs/snapshot.js": "^0.6.1", "@socket.tech/plugin": "^1.2.1", "@upstash/ratelimit": "^0.4.4", "@vercel/kv": "^0.2.2", diff --git a/pages/governance.tsx b/pages/governance.tsx index 40596dc53..9c5715661 100644 --- a/pages/governance.tsx +++ b/pages/governance.tsx @@ -8,7 +8,6 @@ import { useWeb3 } from 'hooks/useWeb3'; import ConnectWalletGovernance from 'components/governance/ConnectWalletGovernance'; import Claimable from 'components/governance/Claimable'; import Delegation from 'components/governance/Delegation'; -import VotingPower from 'components/governance/VotingPower'; import Proposals from 'components/governance/Proposals'; import useMerkleTree from 'hooks/useMerkleTree'; @@ -44,8 +43,7 @@ const Governance: NextPage = () => { bgcolor={({ palette }) => (palette.mode === 'dark' ? 'grey.100' : 'white')} > {mTree.canClaim && } - - + ) : ( diff --git a/wagmi.config.ts b/wagmi.config.ts index 8c827e399..499637c26 100644 --- a/wagmi.config.ts +++ b/wagmi.config.ts @@ -21,6 +21,7 @@ import InterestRateModel from '@exactly/protocol/deployments/goerli/InterestRate import SablierV2LockupLinear from '@exactly/protocol/deployments/goerli/SablierV2LockupLinear.json' assert { type: 'json' }; import SablierV2NFTDescriptor from '@exactly/protocol/deployments/goerli/SablierV2NFTDescriptor.json' assert { type: 'json' }; import ExtraFinanceLendingABI from './abi/extraFinanceLending.json' assert { type: 'json' }; +import DelegateRegistryABI from './abi/DelegateRegistry.json' assert { type: 'json' }; import { Abi } from 'viem'; @@ -47,6 +48,7 @@ export default defineConfig({ { name: 'SablierV2LockupLinear', abi: SablierV2LockupLinear.abi as Abi }, { name: 'SablierV2NFTDescriptor', abi: SablierV2NFTDescriptor.abi as Abi }, { name: 'ExtraFinanceLending', abi: ExtraFinanceLendingABI as Abi }, + { name: 'DelegateRegistry', abi: DelegateRegistryABI as Abi }, ], plugins: [ react({