├── .nvmrc
├── .husky
├── .gitignore
└── pre-commit
├── src
├── ui
│ ├── helpModals
│ │ ├── assets.ts
│ │ ├── getRepresentationsData.ts
│ │ ├── HelpModalText.tsx
│ │ ├── getDelegateData.ts
│ │ ├── HelpModalTextButton.tsx
│ │ ├── getTestTx.ts
│ │ ├── HelpModalWrapper.tsx
│ │ ├── getTestTransactions.ts
│ │ ├── HelpModalHomeButton.tsx
│ │ ├── HelpModalContainer.tsx
│ │ ├── HelpRepresentationsTx.tsx
│ │ ├── HelpModalCaption.tsx
│ │ └── HelpDelegateTx.tsx
│ ├── primitives
│ │ ├── IconBox.tsx
│ │ ├── Image.tsx
│ │ ├── Divider.tsx
│ │ ├── NoSSR.tsx
│ │ ├── Container.tsx
│ │ └── Input.tsx
│ ├── utils
│ │ ├── getChainName.ts
│ │ ├── text-center-ellipsis.ts
│ │ ├── useDebounce.ts
│ │ ├── relativePath.ts
│ │ ├── routes.ts
│ │ ├── disablePageLoader.ts
│ │ ├── useClickOutside.tsx
│ │ ├── metaTexts.ts
│ │ ├── inputValidation.ts
│ │ ├── useMediaQuery.ts
│ │ ├── useScrollDirection.ts
│ │ └── ThemeRegistry.tsx
│ ├── store
│ │ └── uiSelectors.tsx
│ ├── components
│ │ ├── AppLoading.tsx
│ │ ├── NoDataWrapper.tsx
│ │ ├── ChainNameWithIcon.tsx
│ │ ├── TopPanelContainer.tsx
│ │ ├── TableHeaderTitle.tsx
│ │ ├── CheckBox.tsx
│ │ ├── CopyToClipboard.tsx
│ │ ├── Web3Icons
│ │ │ ├── WalletIcon.tsx
│ │ │ ├── ChainIcon.tsx
│ │ │ └── AssetIcon.tsx
│ │ ├── TableContainer.tsx
│ │ ├── CustomSkeleton.tsx
│ │ ├── NetworkIcon.tsx
│ │ ├── Tooltip.tsx
│ │ ├── Link.tsx
│ │ └── InputWithAnimation.tsx
│ ├── layouts
│ │ ├── AppGlobalStyles.tsx
│ │ └── AppLayout.tsx
│ ├── index.ts
│ └── pages
│ │ └── NotFoundPage.tsx
├── proposals
│ ├── components
│ │ ├── actionModals
│ │ │ ├── types.ts
│ │ │ ├── ActionModalContentWrapper.tsx
│ │ │ ├── ActivateVotingModal.tsx
│ │ │ ├── ExecuteProposalModal.tsx
│ │ │ ├── CloseVotingModal.tsx
│ │ │ ├── ActivateVotingOnVotingMachineModal.tsx
│ │ │ ├── CreatePayloadModal.tsx
│ │ │ ├── ExecutePayloadModal.tsx
│ │ │ ├── CreateProposalModal.tsx
│ │ │ └── SendProofsModal.tsx
│ │ ├── timeline
│ │ │ ├── static.ts
│ │ │ ├── types.ts
│ │ │ ├── TimelineLine.tsx
│ │ │ ├── TimelineItemStateWrapper.tsx
│ │ │ └── TimelineCanceledItem.tsx
│ │ ├── ProposalsPagination.tsx
│ │ ├── CreateFieldsArrayTitle.tsx
│ │ ├── proposalHistory
│ │ │ └── helpers.ts
│ │ ├── proposal
│ │ │ ├── LeftPanelWrapper.tsx
│ │ │ ├── RightPanelWrapper.tsx
│ │ │ ├── ProposalPageWrapperWithCache.tsx
│ │ │ └── PayloadCreator.tsx
│ │ ├── CreateFormWrapper.tsx
│ │ ├── VotedState.tsx
│ │ ├── RepresentationIcon.tsx
│ │ ├── ProposalStatus.tsx
│ │ ├── DetailsModalWrapper.tsx
│ │ ├── proposalList
│ │ │ ├── VotingPowerLoading.tsx
│ │ │ └── ProposalListItemWrapper.tsx
│ │ ├── VoteLine.tsx
│ │ ├── BlockWrapper.tsx
│ │ ├── ProposalStatusWithDate.tsx
│ │ └── CreateFieldArrayWrapper.tsx
│ ├── utils
│ │ ├── formatVoterAddress.ts
│ │ └── statuses.ts
│ └── store
│ │ └── proposalsListCacheSlice.ts
├── delegate
│ ├── store
│ │ └── delegationSelectors.ts
│ ├── types.ts
│ ├── utils
│ │ └── getFormDelegateData.ts
│ └── components
│ │ ├── DelegateModal.tsx
│ │ └── DelegateTableWrapper.tsx
├── web3
│ ├── store
│ │ ├── creationFeesSelectors.ts
│ │ └── web3Selectors.ts
│ ├── components
│ │ ├── ChainsIcons.tsx
│ │ ├── wallet
│ │ │ └── WalletItem.tsx
│ │ ├── representation
│ │ │ └── RepresentingButtonChainsIcon.tsx
│ │ └── powers
│ │ │ └── PowersInfoModal.tsx
│ ├── providers
│ │ ├── Web3HelperProvider.tsx
│ │ └── WagmiProvider.tsx
│ └── utils
│ │ ├── ensHelpers.tsx
│ │ └── assetsBalanceSlots.ts
├── proposalCreateOverview
│ ├── types.ts
│ └── components
│ │ └── SeatBeltReportModal.tsx
├── utils
│ ├── initialClients.ts
│ ├── configs.ts
│ ├── getScanLink.ts
│ ├── getAssetName.ts
│ ├── cacheGithubLinks.ts
│ ├── appConfig.ts
│ ├── githubCacheRequests.ts
│ └── getProposalMetadata.ts
├── representations
│ ├── store
│ │ └── representationsSelectors.ts
│ ├── utils
│ │ └── getFormRepresentationsData.ts
│ └── components
│ │ ├── RepresentationsModal.tsx
│ │ └── RepresentationsTableWrapper.tsx
├── rpcSwitcher
│ ├── store
│ │ └── rpcSwitcherSelectors.ts
│ └── components
│ │ └── RpcSwitcherTableWrapper.tsx
├── payloadsExplorer
│ └── store
│ │ └── payloadsExplorerSelectors.ts
├── transactions
│ ├── hooks
│ │ └── useLastTxLocalStatus.tsx
│ └── components
│ │ ├── TransactionsModal.tsx
│ │ ├── CopyErrorButton.tsx
│ │ ├── BasicActionModal.tsx
│ │ └── TransactionsModalContent.tsx
└── store
│ └── ZustandStoreProvider.tsx
├── public
├── favicon.ico
├── metaLogo.jpg
└── images
│ ├── icons
│ ├── check.svg
│ ├── arrowToBottom.svg
│ ├── arrowToLeft.svg
│ ├── arrowToRight.svg
│ ├── arrowToTop.svg
│ ├── cross.svg
│ ├── copy.svg
│ ├── rowIcon.svg
│ ├── arrowRight.svg
│ ├── download.svg
│ ├── link.svg
│ ├── darkTheme.svg
│ ├── columnsIcon.svg
│ ├── claimFee.svg
│ ├── toTopArrow.svg
│ ├── linkIcon.svg
│ ├── arrowToRightThin.svg
│ ├── backArrow.svg
│ ├── delegationIcon.svg
│ ├── twitterX.svg
│ ├── searchIcon.svg
│ ├── warningIcon.svg
│ ├── settings.svg
│ ├── pencil.svg
│ ├── reload.svg
│ ├── replacedIcon.svg
│ ├── web.svg
│ ├── info.svg
│ ├── settingsBorders.svg
│ └── gelato.svg
│ └── representation
│ ├── multichain.svg
│ └── representationVotingPower.svg
├── .prettierrc.json
├── pages
├── delegate.tsx
├── index.tsx
├── 404.tsx
├── rpc-switcher.tsx
├── proposal.tsx
├── create.tsx
├── representations.tsx
├── payloads-explorer.tsx
├── _app.tsx
└── proposal-create-overview.tsx
├── .env.example
├── next-env.d.ts
├── svgo.config.js
├── app
├── manifest.json
├── create
│ └── page.page.tsx
├── delegate
│ └── page.page.tsx
├── rpc-switcher
│ └── page.page.tsx
├── payloads-explorer
│ └── page.page.tsx
├── representations
│ └── page.page.tsx
├── layout.page.tsx
└── proposal-create-overview
│ └── page.page.tsx
├── .github
└── workflows
│ ├── release-please.yml
│ └── ipfs_deploy.yml
├── .gitignore
├── vercel.json
├── tsconfig.json
├── next.config.js
├── README.md
└── eslint.config.mjs
/.nvmrc:
--------------------------------------------------------------------------------
1 | 20
--------------------------------------------------------------------------------
/.husky/.gitignore:
--------------------------------------------------------------------------------
1 | _
2 |
--------------------------------------------------------------------------------
/src/ui/helpModals/assets.ts:
--------------------------------------------------------------------------------
1 | export const assets = 'AAVE + aAAVE + stkAAVE';
2 |
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | . "$(dirname "$0")/_/husky.sh"
3 |
4 | npx lint-staged
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bgd-labs/aave-governance-v3-interface/HEAD/public/favicon.ico
--------------------------------------------------------------------------------
/public/metaLogo.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bgd-labs/aave-governance-v3-interface/HEAD/public/metaLogo.jpg
--------------------------------------------------------------------------------
/.prettierrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "trailingComma": "all",
3 | "tabWidth": 2,
4 | "singleQuote": true,
5 | "jsxBracketSameLine": true
6 | }
--------------------------------------------------------------------------------
/pages/delegate.tsx:
--------------------------------------------------------------------------------
1 | import { DelegatePage } from '../src/delegate/components/DelegatePage';
2 |
3 | export default function Delegate() {
4 | return ;
5 | }
6 |
--------------------------------------------------------------------------------
/.env.example:
--------------------------------------------------------------------------------
1 | NEXT_PUBLIC_DEPLOY_FOR_IPFS=false
2 | NEXT_PUBLIC_TERMS_AND_CONDITIONS_VISIBLE=false
3 | WC_PROJECT_ID=your_wc_project_id#https://docs.walletconnect.com/2.0/cloud/relay
--------------------------------------------------------------------------------
/pages/index.tsx:
--------------------------------------------------------------------------------
1 | import { ProposalPage } from '../src/proposals/components/proposalList/ProposalPage';
2 |
3 | export default function Home() {
4 | return ;
5 | }
6 |
--------------------------------------------------------------------------------
/src/ui/primitives/IconBox.tsx:
--------------------------------------------------------------------------------
1 | import { styled } from '@mui/system';
2 |
3 | export const IconBox = styled('div')({
4 | position: 'relative',
5 | zIndex: 0,
6 | lineHeight: 0,
7 | });
8 |
--------------------------------------------------------------------------------
/src/ui/primitives/Image.tsx:
--------------------------------------------------------------------------------
1 | import { styled } from '@mui/system';
2 |
3 | export const Image = styled('img')({
4 | position: 'relative',
5 | zIndex: 0,
6 | lineHeight: 0,
7 | });
8 |
--------------------------------------------------------------------------------
/pages/404.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { NotFoundPage } from '../src/ui/pages/NotFoundPage';
4 |
5 | export default function NotFound() {
6 | return ;
7 | }
8 |
--------------------------------------------------------------------------------
/pages/rpc-switcher.tsx:
--------------------------------------------------------------------------------
1 | import { RpcSwitcherPage } from '../src/rpcSwitcher/components/RpcSwitcherPage';
2 |
3 | export default function Representations() {
4 | return ;
5 | }
6 |
--------------------------------------------------------------------------------
/pages/proposal.tsx:
--------------------------------------------------------------------------------
1 | import { ProposalClientPage } from '../src/proposals/components/proposal/ProposalClientPage';
2 |
3 | export default function Proposal() {
4 | return ;
5 | }
6 |
--------------------------------------------------------------------------------
/public/images/icons/check.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/ui/primitives/Divider.tsx:
--------------------------------------------------------------------------------
1 | import { styled } from '@mui/system';
2 |
3 | export const Divider = styled('div')(({ theme }) => ({
4 | borderBottom: `1px solid ${theme.palette.$secondaryBorder}`,
5 | }));
6 |
--------------------------------------------------------------------------------
/pages/create.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { CreateProposalPage } from '../src/ui/pages/CreateProposalPage';
4 |
5 | export default function Create() {
6 | return ;
7 | }
8 |
--------------------------------------------------------------------------------
/src/proposals/components/actionModals/types.ts:
--------------------------------------------------------------------------------
1 | export interface ActionModalBasicTypes {
2 | isOpen: boolean;
3 | setIsOpen: (value: boolean) => void;
4 | proposalId: number;
5 | fromList?: boolean;
6 | }
7 |
--------------------------------------------------------------------------------
/pages/representations.tsx:
--------------------------------------------------------------------------------
1 | import { RepresentationsPage } from '../src/representations/components/RepresentationsPage';
2 |
3 | export default function Representations() {
4 | return ;
5 | }
6 |
--------------------------------------------------------------------------------
/pages/payloads-explorer.tsx:
--------------------------------------------------------------------------------
1 | import { PayloadsExplorerPage } from '../src/payloadsExplorer/components/PayloadsExplorerPage';
2 |
3 | export default function PayloadsExplorer() {
4 | return ;
5 | }
6 |
--------------------------------------------------------------------------------
/public/images/icons/arrowToBottom.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/public/images/icons/arrowToLeft.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/public/images/icons/arrowToRight.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/public/images/icons/arrowToTop.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/public/images/icons/cross.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/src/ui/primitives/NoSSR.tsx:
--------------------------------------------------------------------------------
1 | import dynamic from 'next/dynamic';
2 | import { ReactNode } from 'react';
3 |
4 | const NoSSR = ({ children }: { children: ReactNode }) => <>{children}>;
5 |
6 | export default dynamic(() => Promise.resolve(NoSSR), { ssr: false });
7 |
--------------------------------------------------------------------------------
/public/images/icons/copy.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/public/images/icons/rowIcon.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/public/images/icons/arrowRight.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/next-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 | ///
4 |
5 | // NOTE: This file should not be edited
6 | // see https://nextjs.org/docs/basic-features/typescript for more information.
7 |
--------------------------------------------------------------------------------
/src/ui/utils/getChainName.ts:
--------------------------------------------------------------------------------
1 | import { appConfig } from '../../utils/appConfig';
2 | import { chainInfoHelper } from '../../utils/configs';
3 |
4 | export function getChainName(chainId: number) {
5 | return chainInfoHelper.getChainParameters(chainId || appConfig.govCoreChainId)
6 | .name;
7 | }
8 |
--------------------------------------------------------------------------------
/src/ui/utils/text-center-ellipsis.ts:
--------------------------------------------------------------------------------
1 | // eslint-disable-next-line import/prefer-default-export
2 | export function textCenterEllipsis(str: string, from: number, to: number) {
3 | return `${(str || '').substr(0, from)}...${(str || '').substr(
4 | (str || '').length - to,
5 | (str || '').length,
6 | )}`;
7 | }
8 |
--------------------------------------------------------------------------------
/public/images/icons/download.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/src/delegate/store/delegationSelectors.ts:
--------------------------------------------------------------------------------
1 | import { Address, zeroAddress } from 'viem';
2 |
3 | export function getToAddress(activeAddress: Address, toAddress: Address) {
4 | if (toAddress === activeAddress || toAddress === zeroAddress) {
5 | return '';
6 | } else if (toAddress) {
7 | return toAddress;
8 | }
9 | return '';
10 | }
11 |
--------------------------------------------------------------------------------
/public/images/representation/multichain.svg:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/src/web3/store/creationFeesSelectors.ts:
--------------------------------------------------------------------------------
1 | import { Address } from 'viem';
2 |
3 | import { ICreationFeesSlice } from './creationFeesSlice';
4 |
5 | export const selectCreationFeesDataByCreator = (
6 | store: ICreationFeesSlice,
7 | creator: Address,
8 | ) => {
9 | const creationFeesData = store.creationFeesData;
10 | return creationFeesData[creator] || {};
11 | };
12 |
--------------------------------------------------------------------------------
/public/images/icons/link.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/src/proposalCreateOverview/types.ts:
--------------------------------------------------------------------------------
1 | import { Address, Hex } from 'viem';
2 |
3 | export type PayloadParams = {
4 | chainId: number;
5 | accessLevel: number;
6 | payloadsController: Address;
7 | payloadId: number;
8 | };
9 |
10 | export interface InitialParams {
11 | proposalId?: number;
12 | ipfsHash?: Hex;
13 | votingPortal?: Address;
14 | payloads: PayloadParams[];
15 | }
16 |
--------------------------------------------------------------------------------
/public/images/icons/darkTheme.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/svgo.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | multipass: true,
3 | js2svg: {
4 | indent: 2,
5 | pretty: true,
6 | },
7 | plugins: [
8 | {
9 | name: 'preset-default',
10 | params: {
11 | overrides: {
12 | removeViewBox: false,
13 | cleanupIds: false,
14 | collapseGroups: false,
15 | },
16 | },
17 | }
18 | ],
19 | };
20 |
--------------------------------------------------------------------------------
/src/proposals/components/timeline/static.ts:
--------------------------------------------------------------------------------
1 | import { styled } from '@mui/system';
2 |
3 | export const TimelineLineWrapper = styled('div')({
4 | position: 'absolute',
5 | left: 0,
6 | height: 6,
7 | });
8 |
9 | export const TimelineCanceledItemWrapper = styled('div')({
10 | display: 'flex',
11 | position: 'absolute',
12 | alignItems: 'center',
13 | justifyContent: 'center',
14 | top: 2,
15 | });
16 |
--------------------------------------------------------------------------------
/src/ui/helpModals/getRepresentationsData.ts:
--------------------------------------------------------------------------------
1 | import { mainnet } from 'viem/chains';
2 |
3 | import { RepresentationDataItem } from '../../representations/store/representationsSlice';
4 |
5 | export function getRepresentationsData() {
6 | return {
7 | [mainnet.id]: {
8 | representative: '0x2Ae626304D770eed47E5C80bF64E44A2352FF53b',
9 | represented: [],
10 | },
11 | } as Record;
12 | }
13 |
--------------------------------------------------------------------------------
/src/utils/initialClients.ts:
--------------------------------------------------------------------------------
1 | import { ClientsRecord } from '@bgd-labs/frontend-web3-utils';
2 |
3 | import { appUsedNetworks } from './appConfig';
4 | import { CHAINS, createViemClient } from './chains';
5 |
6 | export const initialClients: ClientsRecord = {};
7 | appUsedNetworks.forEach((chain) => {
8 | initialClients[chain] = createViemClient(
9 | CHAINS[chain],
10 | CHAINS[chain].rpcUrls.default.http[0],
11 | );
12 | });
13 |
--------------------------------------------------------------------------------
/app/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "Aave governance v3",
3 | "name": "Aave governance v3",
4 | "description": "Non-custodial liquidity protocol",
5 | "iconPath": "aave.svg",
6 | "icons": [
7 | {
8 | "src": "aave.svg",
9 | "sizes": "32x32",
10 | "type": "image/svg"
11 | }
12 | ],
13 | "start_url": "/",
14 | "display": "standalone",
15 | "theme_color": "#000000",
16 | "background_color": "#ffffff"
17 | }
--------------------------------------------------------------------------------
/src/ui/store/uiSelectors.tsx:
--------------------------------------------------------------------------------
1 | import { IUISlice } from './uiSlice';
2 |
3 | export const selectAllTestTransactions = (state: IUISlice) => {
4 | return Object.values(state.testTransactionsPool).sort(
5 | (a, b) => b.localTimestamp - a.localTimestamp,
6 | );
7 | };
8 |
9 | export const closeHelpModal = (state: IUISlice) => {
10 | state.setIsHelpModalClosed(true);
11 | setTimeout(() => state.setIsHelpModalClosed(false), 1000);
12 | };
13 |
--------------------------------------------------------------------------------
/src/ui/utils/useDebounce.ts:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from 'react';
2 |
3 | export function useDebounce(value: T, delay?: number): T {
4 | const [debouncedValue, setDebouncedValue] = useState(value);
5 | useEffect(() => {
6 | const timer = setTimeout(() => setDebouncedValue(value), delay || 500);
7 | return () => {
8 | clearTimeout(timer);
9 | };
10 | }, [value, delay]);
11 | return debouncedValue;
12 | }
13 |
--------------------------------------------------------------------------------
/public/images/icons/columnsIcon.svg:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/public/images/representation/representationVotingPower.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/src/representations/store/representationsSelectors.ts:
--------------------------------------------------------------------------------
1 | import { RepresentativeAddress } from './representationsSlice';
2 |
3 | export const checkIsVotingAvailable = (
4 | representative: RepresentativeAddress,
5 | votingChainId: number,
6 | ) => {
7 | return representative.address !== ''
8 | ? !!representative.chainsIds.length &&
9 | !!representative.chainsIds.find((chainId) => chainId === votingChainId)
10 | : true;
11 | };
12 |
--------------------------------------------------------------------------------
/src/ui/utils/relativePath.ts:
--------------------------------------------------------------------------------
1 | import { isForIPFS } from '../../utils/appConfig';
2 |
3 | export const setRelativePath = (path: string) => {
4 | if (typeof window !== 'undefined') {
5 | const { pathname } = window.location;
6 | const ipfsMatch = /.*\/Qm\w{44}\//.exec(pathname);
7 | const basePath = ipfsMatch ? ipfsMatch[0] : '';
8 | return isForIPFS ? `${basePath}${path}` : path;
9 | } else {
10 | return path;
11 | }
12 | };
13 |
--------------------------------------------------------------------------------
/src/utils/configs.ts:
--------------------------------------------------------------------------------
1 | import { initChainInformationConfig } from '@bgd-labs/frontend-web3-utils';
2 |
3 | import { CHAINS } from './chains';
4 |
5 | // ipfs gateway to get proposals metadata
6 | export const ipfsGateway = 'https://dweb.link/ipfs';
7 | export const fallbackGateways = [
8 | 'https://ipfs.eth.aragon.network/ipfs',
9 | 'https://ipfs.runfission.com/ipfs',
10 | ];
11 |
12 | export const chainInfoHelper = initChainInformationConfig(CHAINS);
13 |
--------------------------------------------------------------------------------
/src/ui/helpModals/HelpModalText.tsx:
--------------------------------------------------------------------------------
1 | import { Box } from '@mui/system';
2 | import React, { ReactNode } from 'react';
3 |
4 | interface HelpModalTextProps {
5 | children: ReactNode;
6 | mb?: number;
7 | }
8 |
9 | export function HelpModalText({ children, mb }: HelpModalTextProps) {
10 | return (
11 |
17 | {children}
18 |
19 | );
20 | }
21 |
--------------------------------------------------------------------------------
/src/ui/helpModals/getDelegateData.ts:
--------------------------------------------------------------------------------
1 | import { DelegateItem } from '../../delegate/types';
2 | import { appConfig } from '../../utils/appConfig';
3 |
4 | export function getDelegateData() {
5 | return [
6 | {
7 | underlyingAsset: appConfig.additional.aaveAddress,
8 | symbol: 'AAVE',
9 | amount: 100,
10 | votingToAddress: '',
11 | propositionToAddress: '0x86E284421664840Cb65C5b918Da59c01ED8fA666',
12 | },
13 | ] as DelegateItem[];
14 | }
15 |
--------------------------------------------------------------------------------
/src/ui/primitives/Container.tsx:
--------------------------------------------------------------------------------
1 | import { styled } from '@mui/system';
2 |
3 | export const Container = styled('div')(({ theme }) => ({
4 | margin: '0 auto',
5 | position: 'relative',
6 | zIndex: 2,
7 | paddingLeft: 8,
8 | paddingRight: 8,
9 | maxWidth: 988,
10 | [theme.breakpoints.up('sm')]: {
11 | paddingLeft: 24,
12 | paddingRight: 24,
13 | maxWidth: 1022,
14 | },
15 | [theme.breakpoints.up('lg')]: {
16 | maxWidth: 1228,
17 | },
18 | }));
19 |
--------------------------------------------------------------------------------
/public/images/icons/claimFee.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/.github/workflows/release-please.yml:
--------------------------------------------------------------------------------
1 | on:
2 | push:
3 | branches:
4 | - main
5 |
6 | permissions:
7 | contents: write
8 | pull-requests: write
9 |
10 | name: release-please
11 |
12 | jobs:
13 | release-please:
14 | runs-on: ubuntu-latest
15 | steps:
16 | - uses: google-github-actions/release-please-action@v3
17 | with:
18 | release-type: node
19 | package-name: aave-governance-v3-interface
20 | pull-request-header: 'I have created a release'
21 |
--------------------------------------------------------------------------------
/public/images/icons/toTopArrow.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/utils/getScanLink.ts:
--------------------------------------------------------------------------------
1 | import { appConfig } from './appConfig';
2 | import { CHAINS } from './chains';
3 |
4 | export function getScanLink({
5 | chainId = appConfig.govCoreChainId,
6 | address,
7 | type = 'address',
8 | }: {
9 | chainId?: number;
10 | address: string;
11 | type?: 'address' | 'tx';
12 | }) {
13 | const baseUrl = CHAINS[chainId]?.blockExplorers?.default.url || '';
14 | const url = baseUrl.endsWith('/') ? baseUrl : `${baseUrl}/`;
15 | return `${url}${type}/${address}`;
16 | }
17 |
--------------------------------------------------------------------------------
/public/images/icons/linkIcon.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/public/images/icons/arrowToRightThin.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/proposals/utils/formatVoterAddress.ts:
--------------------------------------------------------------------------------
1 | import { VotersData } from '@bgd-labs/aave-governance-ui-helpers';
2 |
3 | import { textCenterEllipsis } from '../../ui/utils/text-center-ellipsis';
4 |
5 | export function formatVoterAddress(vote: VotersData, isBig?: boolean) {
6 | return vote.ensName
7 | ? vote.ensName.length > (isBig ? 18 : 10)
8 | ? textCenterEllipsis(vote.ensName, isBig ? 12 : 5, isBig ? 6 : 5)
9 | : vote.ensName
10 | : textCenterEllipsis(vote.address, isBig ? 8 : 5, isBig ? 8 : 5);
11 | }
12 |
--------------------------------------------------------------------------------
/public/images/icons/backArrow.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/ui/utils/routes.ts:
--------------------------------------------------------------------------------
1 | export const ROUTES = {
2 | main: '/',
3 | delegate: '/delegate',
4 | representations: '/representations',
5 | proposal: (proposalId: number, ipfsHash: string) =>
6 | `/proposal?proposalId=${proposalId}&ipfsHash=${ipfsHash}`,
7 | proposalWithoutIpfs: (proposalId: number) =>
8 | `/proposal?proposalId=${proposalId}`,
9 | rpcSwitcher: '/rpc-switcher',
10 | proposalCreateOverview: '/proposal-create-overview',
11 | payloadsExplorer: '/payloads-explorer',
12 | adi: 'https://adi.onaave.com/',
13 | };
14 |
--------------------------------------------------------------------------------
/src/ui/utils/disablePageLoader.ts:
--------------------------------------------------------------------------------
1 | import NProgress from 'nprogress';
2 |
3 | import { isForIPFS } from '../../utils/appConfig';
4 |
5 | export function disablePageLoader() {
6 | if (typeof document !== 'undefined' && !isForIPFS) {
7 | setTimeout(() => {
8 | NProgress.start();
9 | NProgress.done();
10 | document.documentElement.classList.remove('nprogress-busy');
11 | const nprogressElement = document.getElementById('nprogress');
12 | if (nprogressElement) {
13 | nprogressElement.remove();
14 | }
15 | }, 1);
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | .env
4 |
5 | # dependencies
6 | /node_modules
7 | /.pnp
8 | .pnp.js
9 |
10 | # testing
11 | /coverage
12 |
13 | # next.js
14 | /.next/
15 | /out/
16 |
17 | # production
18 | /build
19 |
20 | # misc
21 | .DS_Store
22 | *.pem
23 | .idea
24 |
25 | # debug
26 | npm-debug.log*
27 | yarn-debug.log*
28 | yarn-error.log*
29 | .pnpm-debug.log*
30 |
31 | # local env files
32 | .env*.local
33 |
34 | # typescript
35 | *.tsbuildinfo
36 |
37 | # storybooks
38 | build-storybook.log*
39 |
--------------------------------------------------------------------------------
/public/images/icons/delegationIcon.svg:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/src/rpcSwitcher/store/rpcSwitcherSelectors.ts:
--------------------------------------------------------------------------------
1 | import { Client } from 'viem';
2 |
3 | import { IRpcSwitcherSlice } from './rpcSwitcherSlice';
4 |
5 | export const selectAppClients = (store: IRpcSwitcherSlice) => {
6 | return Object.entries(store.appClients).reduce(
7 | (acc, [key, value]) => {
8 | acc[key] = value.instance;
9 | return acc;
10 | },
11 | {} as Record,
12 | );
13 | };
14 |
15 | export const selectIsRpcAppHasErrors = (store: IRpcSwitcherSlice) => {
16 | return Object.values(store.rpcAppErrors).some((error) => error.error);
17 | };
18 |
--------------------------------------------------------------------------------
/public/images/icons/twitterX.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/src/web3/store/web3Selectors.ts:
--------------------------------------------------------------------------------
1 | import { RootState } from '../../store';
2 |
3 | export const selectCurrentPowers = (store: RootState) => {
4 | if (store.representative.address) {
5 | return store.currentPowers[store.representative.address];
6 | } else if (store.activeWallet?.address) {
7 | return store.currentPowers[store.activeWallet?.address];
8 | } else {
9 | return;
10 | }
11 | };
12 |
13 | export const selectCurrentPowersForActiveWallet = (store: RootState) => {
14 | if (store.activeWallet?.address) {
15 | return store.currentPowers[store.activeWallet?.address];
16 | } else {
17 | return;
18 | }
19 | };
20 |
--------------------------------------------------------------------------------
/vercel.json:
--------------------------------------------------------------------------------
1 | {
2 | "headers": [
3 | {
4 | "source": "/(.*)",
5 | "headers": [
6 | {
7 | "key": "Referrer-Policy",
8 | "value": "no-referrer-when-downgrade"
9 | },
10 | {
11 | "key": "X-Content-Type-Options",
12 | "value": "nosniff"
13 | },
14 | {
15 | "key": "Content-Security-Policy",
16 | "value": "frame-ancestors 'self' https://app.safe.global https://metissafe.tech"
17 | },
18 | {
19 | "key": "X-XSS-Protection",
20 | "value": "1; mode=block"
21 | }
22 | ]
23 | }
24 | ]
25 | }
--------------------------------------------------------------------------------
/src/proposals/components/ProposalsPagination.tsx:
--------------------------------------------------------------------------------
1 | import { useStore } from '../../store/ZustandStoreProvider';
2 | import { Pagination } from '../../ui';
3 | import { selectProposalsPages } from '../store/proposalsSelectors';
4 |
5 | export const ProposalsPagination = () => {
6 | const pages = useStore((state) => selectProposalsPages(state));
7 | const setActivePage = useStore((state) => state.setActivePage);
8 | const activePage = useStore((state) => state.activePage);
9 |
10 | return (
11 |
16 | );
17 | };
18 |
--------------------------------------------------------------------------------
/src/proposals/components/CreateFieldsArrayTitle.tsx:
--------------------------------------------------------------------------------
1 | import { Box } from '@mui/system';
2 | import { ReactNode } from 'react';
3 |
4 | interface CreateFieldsArrayTitleProps {
5 | title: string;
6 | isTitleVisible: boolean;
7 | children: ReactNode;
8 | }
9 |
10 | export function CreateFieldsArrayTitle({
11 | title,
12 | isTitleVisible,
13 | children,
14 | }: CreateFieldsArrayTitleProps) {
15 | return (
16 |
17 | {isTitleVisible && (
18 |
19 | {title}
20 |
21 | )}
22 |
23 | {children}
24 |
25 | );
26 | }
27 |
--------------------------------------------------------------------------------
/app/create/page.page.tsx:
--------------------------------------------------------------------------------
1 | import { Metadata } from 'next';
2 | import React from 'react';
3 |
4 | import { CreateProposalPage } from '../../src/ui/pages/CreateProposalPage';
5 | import { metaTexts } from '../../src/ui/utils/metaTexts';
6 |
7 | export const metadata: Metadata = {
8 | title: `${metaTexts.main}${metaTexts.createPageMetaTitle}`,
9 | description: metaTexts.createPageMetaDescription,
10 | openGraph: {
11 | images: ['/metaLogo.jpg'],
12 | title: `${metaTexts.main}${metaTexts.createPageMetaTitle}`,
13 | description: metaTexts.createPageMetaDescription,
14 | },
15 | };
16 |
17 | export default function Page() {
18 | return ;
19 | }
20 |
--------------------------------------------------------------------------------
/app/delegate/page.page.tsx:
--------------------------------------------------------------------------------
1 | import { Metadata } from 'next';
2 | import React from 'react';
3 |
4 | import { DelegatePage } from '../../src/delegate/components/DelegatePage';
5 | import { metaTexts } from '../../src/ui/utils/metaTexts';
6 |
7 | export const metadata: Metadata = {
8 | title: `${metaTexts.main}${metaTexts.delegatePageMetaTitle}`,
9 | description: metaTexts.delegatePageMetaDescription,
10 | openGraph: {
11 | images: ['/metaLogo.jpg'],
12 | title: `${metaTexts.main}${metaTexts.delegatePageMetaTitle}`,
13 | description: metaTexts.delegatePageMetaDescription,
14 | },
15 | };
16 |
17 | export default function Page() {
18 | return ;
19 | }
20 |
--------------------------------------------------------------------------------
/src/proposals/components/proposalHistory/helpers.ts:
--------------------------------------------------------------------------------
1 | import { HistoryItemType } from '@bgd-labs/aave-governance-ui-helpers';
2 |
3 | export function getHistoryId({
4 | proposalId,
5 | type,
6 | id,
7 | chainId,
8 | }: {
9 | proposalId: number;
10 | type: HistoryItemType;
11 | id: number;
12 | chainId: number;
13 | }) {
14 | if (
15 | type === HistoryItemType.PAYLOADS_CREATED ||
16 | type === HistoryItemType.PAYLOADS_QUEUED ||
17 | type === HistoryItemType.PAYLOADS_EXECUTED ||
18 | type === HistoryItemType.PAYLOADS_EXPIRED
19 | ) {
20 | return `${proposalId}_${type}_${id}_${chainId}`;
21 | } else {
22 | return `${proposalId}_${type}`;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/payloadsExplorer/store/payloadsExplorerSelectors.ts:
--------------------------------------------------------------------------------
1 | import { Hex } from 'viem';
2 |
3 | import { RootState } from '../../store';
4 |
5 | export const selectPayloadExploreById = (
6 | store: RootState,
7 | chainId: number,
8 | address: Hex,
9 | payloadId: number,
10 | ) => {
11 | if (!store.payloadsExploreData[chainId]) {
12 | return;
13 | } else if (!store.payloadsExploreData[chainId][address]) {
14 | return;
15 | } else if (
16 | !store.payloadsExploreData[chainId][address][`${address}_${payloadId}`]
17 | ) {
18 | return;
19 | } else {
20 | return store.payloadsExploreData[chainId][address][
21 | `${address}_${payloadId}`
22 | ];
23 | }
24 | };
25 |
--------------------------------------------------------------------------------
/src/ui/components/AppLoading.tsx:
--------------------------------------------------------------------------------
1 | import { Box } from '@mui/system';
2 |
3 | import { Container } from '../primitives/Container';
4 | import NoSSR from '../primitives/NoSSR';
5 | import { texts } from '../utils/texts';
6 | import { NoDataWrapper } from './NoDataWrapper';
7 | import { RocketLoader } from './RocketLoader';
8 |
9 | export function AppLoading() {
10 | return (
11 |
12 |
13 |
14 |
15 |
16 | {texts.other.appLoading}
17 |
18 |
19 |
20 |
21 | );
22 | }
23 |
--------------------------------------------------------------------------------
/app/rpc-switcher/page.page.tsx:
--------------------------------------------------------------------------------
1 | import { Metadata } from 'next';
2 | import React from 'react';
3 |
4 | import { RpcSwitcherPage } from '../../src/rpcSwitcher/components/RpcSwitcherPage';
5 | import { metaTexts } from '../../src/ui/utils/metaTexts';
6 |
7 | export const metadata: Metadata = {
8 | title: `${metaTexts.main}${metaTexts.rpcSwitcherPageMetaTitle}`,
9 | description: metaTexts.rpcSwitcherPageMetaDescription,
10 | openGraph: {
11 | images: ['/metaLogo.jpg'],
12 | title: `${metaTexts.main}${metaTexts.rpcSwitcherPageMetaTitle}`,
13 | description: metaTexts.rpcSwitcherPageMetaDescription,
14 | },
15 | };
16 |
17 | export default function Page() {
18 | return ;
19 | }
20 |
--------------------------------------------------------------------------------
/app/payloads-explorer/page.page.tsx:
--------------------------------------------------------------------------------
1 | import { Metadata } from 'next';
2 | import React, { Suspense } from 'react';
3 |
4 | import { PayloadsExplorerPage } from '../../src/payloadsExplorer/components/PayloadsExplorerPage';
5 | import { metaTexts } from '../../src/ui/utils/metaTexts';
6 |
7 | export const metadata: Metadata = {
8 | title: metaTexts.ipfsTitle,
9 | description: metaTexts.ipfsDescription,
10 | openGraph: {
11 | images: ['/metaLogo.jpg'],
12 | title: metaTexts.ipfsTitle,
13 | description: metaTexts.ipfsDescription,
14 | },
15 | };
16 |
17 | export default function PayloadsExplorer() {
18 | return (
19 |
20 |
21 |
22 | );
23 | }
24 |
--------------------------------------------------------------------------------
/src/delegate/types.ts:
--------------------------------------------------------------------------------
1 | import { Asset } from '@bgd-labs/aave-governance-ui-helpers';
2 | import { Address } from 'viem';
3 |
4 | export type DelegateItem = {
5 | underlyingAsset: Address;
6 | symbol: Asset;
7 | amount: number;
8 | votingToAddress: Address | string;
9 | propositionToAddress: Address | string;
10 | };
11 |
12 | export type DelegateData = {
13 | underlyingAsset: Address;
14 | votingToAddress: Address | string;
15 | propositionToAddress: Address | string;
16 | };
17 |
18 | export type TxDelegateData = {
19 | symbol: Asset;
20 | underlyingAsset: Address;
21 | bothAddresses?: Address | string;
22 | votingToAddress?: Address | string;
23 | propositionToAddress?: Address | string;
24 | };
25 |
--------------------------------------------------------------------------------
/src/ui/components/NoDataWrapper.tsx:
--------------------------------------------------------------------------------
1 | import { ReactNode } from 'react';
2 |
3 | import { BoxWith3D } from './BoxWith3D';
4 |
5 | interface NoDataWrapperProps {
6 | children: ReactNode;
7 | }
8 | export function NoDataWrapper({ children }: NoDataWrapperProps) {
9 | return (
10 |
24 | {children}
25 |
26 | );
27 | }
28 |
--------------------------------------------------------------------------------
/src/utils/getAssetName.ts:
--------------------------------------------------------------------------------
1 | import { Asset } from '@bgd-labs/aave-governance-ui-helpers';
2 |
3 | import { appConfig } from './appConfig';
4 |
5 | export function getAssetName(assetAddress: string) {
6 | switch (assetAddress.toLowerCase()) {
7 | case appConfig.additional.aaveAddress.toLowerCase():
8 | return Asset.AAVE;
9 | case appConfig.additional.stkAAVEAddress.toLowerCase():
10 | return Asset.STKAAVE;
11 | case appConfig.additional.aAaveAddress.toLowerCase():
12 | return Asset.AAAVE;
13 | case appConfig.govCoreConfig.contractAddress.toLowerCase():
14 | return Asset.GOVCORE;
15 |
16 | default:
17 | console.error('Token address incorrect');
18 | return '';
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/app/representations/page.page.tsx:
--------------------------------------------------------------------------------
1 | import { Metadata } from 'next';
2 | import React from 'react';
3 |
4 | import { RepresentationsPage } from '../../src/representations/components/RepresentationsPage';
5 | import { metaTexts } from '../../src/ui/utils/metaTexts';
6 |
7 | export const metadata: Metadata = {
8 | title: `${metaTexts.main}${metaTexts.representationsPageMetaTitle}`,
9 | description: metaTexts.representationsPageMetaDescription,
10 | openGraph: {
11 | images: ['/metaLogo.jpg'],
12 | title: `${metaTexts.main}${metaTexts.representationsPageMetaTitle}`,
13 | description: metaTexts.representationsPageMetaDescription,
14 | },
15 | };
16 |
17 | export default function Page() {
18 | return ;
19 | }
20 |
--------------------------------------------------------------------------------
/src/proposals/components/timeline/types.ts:
--------------------------------------------------------------------------------
1 | import { ProposalStateWithName } from '@bgd-labs/aave-governance-ui-helpers';
2 |
3 | export interface TimelineItemType {
4 | title: string;
5 | position?: number;
6 | finished: boolean;
7 | type: TimelineItemTypeType;
8 | timestamp: number;
9 | timestampForEstimatedState?: number;
10 | visibility?: boolean;
11 | state?: ProposalStateWithName;
12 | color?:
13 | | 'achieved'
14 | | 'regular'
15 | | 'bigRegular'
16 | | 'bigSuccess'
17 | | 'bigError'
18 | | 'bigCanceled';
19 | rocketVisible?: boolean;
20 | }
21 |
22 | export enum TimelineItemTypeType {
23 | created = 'created',
24 | openToVote = 'openToVote',
25 | votingClosed = 'votingClosed',
26 | finished = 'Finished',
27 | }
28 |
--------------------------------------------------------------------------------
/public/images/icons/searchIcon.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/src/transactions/hooks/useLastTxLocalStatus.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import { useLastTxLocalStatus as baseUseTxLocalStatus } from '@bgd-labs/frontend-web3-utils';
4 | import { zeroAddress } from 'viem';
5 |
6 | import { useStore } from '../../store/ZustandStoreProvider';
7 | import { TransactionUnion } from '../store/transactionsSlice';
8 |
9 | export function useLastTxLocalStatus({
10 | type,
11 | payload,
12 | }: Pick) {
13 | const transactionsPool = useStore((store) => store.transactionsPool);
14 | const activeWallet = useStore((store) => store.activeWallet);
15 |
16 | return baseUseTxLocalStatus({
17 | transactionsPool,
18 | activeAddress: activeWallet?.address || zeroAddress,
19 | type,
20 | payload,
21 | });
22 | }
23 |
--------------------------------------------------------------------------------
/src/representations/utils/getFormRepresentationsData.ts:
--------------------------------------------------------------------------------
1 | import { RepresentationFormData } from '../store/representationsSlice';
2 |
3 | interface GetFormRepresentationsDataParams {
4 | chainId: number;
5 | representativeAddress?: string;
6 | formData?: RepresentationFormData[];
7 | }
8 |
9 | export function getFormRepresentationsData({
10 | chainId,
11 | representativeAddress,
12 | formData,
13 | }: GetFormRepresentationsDataParams) {
14 | const formRepresentationsDataItem =
15 | !!formData &&
16 | !!formData.length &&
17 | formData.find((item) => item.chainId === chainId);
18 |
19 | return typeof formRepresentationsDataItem !== 'boolean' &&
20 | typeof formRepresentationsDataItem !== 'undefined'
21 | ? formRepresentationsDataItem.representative
22 | : representativeAddress;
23 | }
24 |
--------------------------------------------------------------------------------
/public/images/icons/warningIcon.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/src/proposals/components/proposal/LeftPanelWrapper.tsx:
--------------------------------------------------------------------------------
1 | import { Box } from '@mui/system';
2 | import { ReactNode } from 'react';
3 |
4 | interface LeftPanelWrapperProps {
5 | children: ReactNode;
6 | }
7 |
8 | export function LeftPanelWrapper({ children }: LeftPanelWrapperProps) {
9 | return (
10 | ({
12 | width: '100%',
13 | [theme.breakpoints.up('sm')]: {
14 | width: 290,
15 | mr: 18,
16 | position: 'sticky',
17 | transition: 'all 0.5s ease',
18 | top: 50,
19 | },
20 | [theme.breakpoints.up('lg')]: {
21 | mr: 24,
22 | width: 340,
23 | },
24 | '@media only screen and (max-height: 550px)': {
25 | position: 'static',
26 | },
27 | })}>
28 | {children}
29 |
30 | );
31 | }
32 |
--------------------------------------------------------------------------------
/src/ui/utils/useClickOutside.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from 'react';
2 |
3 | type UseClickOutsideType = {
4 | ref: React.RefObject;
5 | outsideClickFunc: () => void;
6 | additionalCondition?: boolean;
7 | };
8 |
9 | export function useClickOutside({
10 | ref,
11 | outsideClickFunc,
12 | additionalCondition,
13 | }: UseClickOutsideType) {
14 | useEffect(() => {
15 | function handleClickOutside(event: any) {
16 | if (
17 | ref.current &&
18 | !ref.current.contains(event.target) &&
19 | additionalCondition
20 | ) {
21 | outsideClickFunc();
22 | }
23 | }
24 | document.addEventListener('mousedown', handleClickOutside);
25 | return () => {
26 | document.removeEventListener('mousedown', handleClickOutside);
27 | };
28 | }, [ref, additionalCondition]);
29 | }
30 |
--------------------------------------------------------------------------------
/src/ui/components/ChainNameWithIcon.tsx:
--------------------------------------------------------------------------------
1 | import { Box, SxProps } from '@mui/system';
2 |
3 | import { getChainName } from '../utils/getChainName';
4 | import { NetworkIcon } from './NetworkIcon';
5 |
6 | interface ChainNameWithIconProps {
7 | chainId: number;
8 | onlyIcon?: boolean;
9 | iconSize?: number;
10 | css?: SxProps;
11 | textCss?: SxProps;
12 | }
13 |
14 | export function ChainNameWithIcon({
15 | chainId,
16 | onlyIcon,
17 | iconSize,
18 | css,
19 | textCss,
20 | }: ChainNameWithIconProps) {
21 | return (
22 |
23 |
24 | {!onlyIcon && (
25 |
26 | {getChainName(chainId)}
27 |
28 | )}
29 |
30 | );
31 | }
32 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ESNext",
4 | "lib": [
5 | "dom",
6 | "dom.iterable",
7 | "esnext"
8 | ],
9 | "allowJs": true,
10 | "skipLibCheck": true,
11 | "strict": true,
12 | "forceConsistentCasingInFileNames": true,
13 | "noEmit": true,
14 | "esModuleInterop": true,
15 | "module": "ESNext",
16 | "moduleResolution": "bundler",
17 | "allowSyntheticDefaultImports": true,
18 | "resolveJsonModule": true,
19 | "isolatedModules": true,
20 | "jsx": "preserve",
21 | "incremental": true,
22 | "plugins": [
23 | {
24 | "name": "next"
25 | }
26 | ]
27 | },
28 | "include": [
29 | "next-env.d.ts",
30 | "typings/env.d.ts",
31 | "**/*.ts",
32 | "**/*.tsx",
33 | ".next/types/**/*.ts"
34 | ],
35 | "exclude": [
36 | "node_modules"
37 | ]
38 | }
39 |
--------------------------------------------------------------------------------
/src/ui/helpModals/HelpModalTextButton.tsx:
--------------------------------------------------------------------------------
1 | import { Box } from '@mui/system';
2 | import { ReactNode } from 'react';
3 |
4 | interface HelpModalTextButtonProps {
5 | onClick: () => void;
6 | children: ReactNode;
7 | white?: boolean;
8 | }
9 |
10 | export function HelpModalTextButton({
11 | onClick,
12 | children,
13 | white,
14 | }: HelpModalTextButtonProps) {
15 | return (
16 | ({
21 | display: 'inline',
22 | cursor: 'pointer',
23 | color: white ? '$textWhite' : '$textSecondary',
24 | textDecoration: 'underline',
25 | transition: 'all 0.2s ease',
26 | hover: {
27 | color: white ? theme.palette.$textSecondary : theme.palette.$text,
28 | },
29 | })}>
30 | {children}
31 |
32 | );
33 | }
34 |
--------------------------------------------------------------------------------
/src/ui/primitives/Input.tsx:
--------------------------------------------------------------------------------
1 | import { styled } from '@mui/system';
2 |
3 | export const Input = styled('input')(({ theme }) => ({
4 | position: 'relative',
5 | width: '100%',
6 | lineHeight: '1',
7 | fontWeight: '400',
8 | transition: 'all 0.2s ease',
9 |
10 | fontSize: 11,
11 | padding: '7px 23px 7px 5px',
12 | border: `1px solid ${theme.palette.$main}`,
13 | color: theme.palette.$text,
14 | backgroundColor: theme.palette.$mainLight,
15 | [theme.breakpoints.up('xsm')]: {
16 | padding: '7px 23px 7px 5px',
17 | fontSize: 12,
18 | },
19 | [theme.breakpoints.up('lg')]: {
20 | padding: '8px 25px 8px 10px',
21 | fontSize: 13,
22 | },
23 | '&:active, &:focus': {
24 | backgroundColor: theme.palette.$light,
25 | },
26 | '&::placeholder': {
27 | color: theme.palette.$textDisabled,
28 | },
29 | '&:hover': {
30 | backgroundColor: theme.palette.$light,
31 | },
32 | }));
33 |
--------------------------------------------------------------------------------
/src/proposals/components/CreateFormWrapper.tsx:
--------------------------------------------------------------------------------
1 | import { Box, useTheme } from '@mui/system';
2 | import { ReactNode } from 'react';
3 |
4 | import { BoxWith3D } from '../../ui';
5 |
6 | interface CreateFormWrapperProps {
7 | title: string;
8 | children: ReactNode;
9 | }
10 |
11 | export function CreateFormWrapper({ title, children }: CreateFormWrapperProps) {
12 | const theme = useTheme();
13 |
14 | return (
15 |
25 |
26 | {title}
27 |
28 | {children}
29 |
30 | );
31 | }
32 |
--------------------------------------------------------------------------------
/src/utils/cacheGithubLinks.ts:
--------------------------------------------------------------------------------
1 | import { coreName } from './appConfig';
2 |
3 | export const githubInitialUrl =
4 | 'https://raw.githubusercontent.com/bgd-labs/aave-governance-ui-helpers/main/cache';
5 | export const githubStartUrl = `${githubInitialUrl}/ui/${coreName}`;
6 |
7 | export const listViewPath = '/list_view_proposals.json';
8 | export const cachedProposalsIdsPath = '/cached_proposals_ids.json';
9 | export const cachedCreationFeesPath = '/creation_fees.json';
10 | export const cachedProposalsPayloadsPath = '/proposals_payloads.json';
11 |
12 | export const cachedDetailsPath = (id: number) =>
13 | `/proposals/proposal_${id}.json`;
14 |
15 | export const cachedVotesPath = (id: number) =>
16 | `/votes/vote_for_proposal_${id}.json`;
17 |
18 | export const cachedEventsPath = (id: number) =>
19 | `/events/proposal_${id}_events.json`;
20 |
21 | export const cachedIPFSDataPath = (ipfsHash: string) =>
22 | `/ipfs/${ipfsHash}.json`;
23 |
--------------------------------------------------------------------------------
/src/ui/helpModals/getTestTx.ts:
--------------------------------------------------------------------------------
1 | import {
2 | TxAdapter,
3 | TxLocalStatusTxParams,
4 | WalletType,
5 | } from '@bgd-labs/frontend-web3-utils';
6 | import dayjs from 'dayjs';
7 | import { zeroAddress, zeroHash } from 'viem';
8 |
9 | import { TransactionUnion } from '../../transactions/store/transactionsSlice';
10 | import { appConfig } from '../../utils/appConfig';
11 |
12 | export function getTestTx({
13 | txPending,
14 | txSuccess,
15 | }: {
16 | txPending: boolean;
17 | txSuccess: boolean;
18 | }) {
19 | return {
20 | adapter: TxAdapter.Ethereum,
21 | chainId: appConfig.govCoreChainId,
22 | type: 'test',
23 | from: zeroAddress,
24 | localTimestamp: dayjs().unix(),
25 | txKey: zeroHash,
26 | hash: zeroHash,
27 | walletType: WalletType.Injected,
28 | pending: txPending,
29 | isError: false,
30 | isSuccess: txSuccess,
31 | isReplaced: false,
32 | } as TxLocalStatusTxParams;
33 | }
34 |
--------------------------------------------------------------------------------
/src/proposals/components/actionModals/ActionModalContentWrapper.tsx:
--------------------------------------------------------------------------------
1 | import { Box } from '@mui/system';
2 | import React, { ReactNode } from 'react';
3 |
4 | interface ActionModalWrapperProps {
5 | children: ReactNode;
6 | }
7 |
8 | export function ActionModalTitle({ title }: { title: string }) {
9 | return (
10 |
11 |
12 | {title}
13 |
14 |
15 | );
16 | }
17 |
18 | export function ActionModalContentWrapper({
19 | children,
20 | }: ActionModalWrapperProps) {
21 | return (
22 |
33 | {children}
34 |
35 | );
36 | }
37 |
--------------------------------------------------------------------------------
/src/proposals/components/timeline/TimelineLine.tsx:
--------------------------------------------------------------------------------
1 | import { Box, SxProps, useTheme } from '@mui/system';
2 | import React from 'react';
3 |
4 | export function TimelineLine({
5 | textColor = 'light',
6 | isFull,
7 | sx,
8 | }: {
9 | textColor: 'light' | 'secondary';
10 | isFull?: boolean;
11 | sx: SxProps;
12 | }) {
13 | const theme = useTheme();
14 |
15 | return (
16 |
34 | );
35 | }
36 |
--------------------------------------------------------------------------------
/src/proposals/store/proposalsListCacheSlice.ts:
--------------------------------------------------------------------------------
1 | import { FinishedProposalForList } from '@bgd-labs/aave-governance-ui-helpers';
2 | import { StoreSlice } from '@bgd-labs/frontend-web3-utils';
3 |
4 | export interface IProposalsListCacheSlice {
5 | loadingListCache: boolean;
6 | setLoadingListCache: (value: boolean) => void;
7 |
8 | cachedProposalsIds: number[];
9 | setCachedProposalsIds: (value: number[]) => void;
10 | cachedProposals: FinishedProposalForList[];
11 | setCachedProposals: (value: FinishedProposalForList[]) => void;
12 | }
13 |
14 | export const createProposalsListCacheSlice: StoreSlice<
15 | IProposalsListCacheSlice
16 | > = (set) => ({
17 | loadingListCache: true,
18 | setLoadingListCache: (value) => set({ loadingListCache: value }),
19 |
20 | cachedProposalsIds: [],
21 | setCachedProposalsIds: (value) => set({ cachedProposalsIds: value }),
22 | cachedProposals: [],
23 | setCachedProposals: (value) => set({ cachedProposals: value }),
24 | });
25 |
--------------------------------------------------------------------------------
/public/images/icons/settings.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/proposals/components/VotedState.tsx:
--------------------------------------------------------------------------------
1 | import { Box, SxProps } from '@mui/system';
2 |
3 | import { texts } from '../../ui/utils/texts';
4 |
5 | interface VotedStateProps {
6 | support: boolean;
7 | inProcess?: boolean;
8 | isBig?: boolean;
9 | css?: SxProps;
10 | }
11 |
12 | export function VotedState({
13 | support,
14 | isBig,
15 | inProcess,
16 | css,
17 | }: VotedStateProps) {
18 | return (
19 |
22 | {inProcess ? texts.proposals.voting : texts.proposals.voted}{' '}
23 |
34 | {support ? texts.other.toggleFor : texts.other.toggleAgainst}
35 |
36 |
37 | );
38 | }
39 |
--------------------------------------------------------------------------------
/src/ui/components/TopPanelContainer.tsx:
--------------------------------------------------------------------------------
1 | import { Box } from '@mui/system';
2 | import React, { ReactNode } from 'react';
3 |
4 | import { Container } from '../index';
5 |
6 | interface TopPanelContainerProps {
7 | children: ReactNode;
8 | withoutContainer?: boolean;
9 | }
10 |
11 | export function TopPanelContainer({
12 | children,
13 | withoutContainer,
14 | }: TopPanelContainerProps) {
15 | return (
16 | <>
17 | {!withoutContainer ? (
18 |
19 | ({
21 | mb: 18,
22 | [theme.breakpoints.up('sm')]: {
23 | mb: 24,
24 | },
25 | })}>
26 | {children}
27 |
28 |
29 | ) : (
30 | ({
32 | mb: 18,
33 | [theme.breakpoints.up('sm')]: {
34 | mb: 24,
35 | },
36 | })}>
37 | {children}
38 |
39 | )}
40 | >
41 | );
42 | }
43 |
--------------------------------------------------------------------------------
/src/proposals/components/timeline/TimelineItemStateWrapper.tsx:
--------------------------------------------------------------------------------
1 | import { Box } from '@mui/system';
2 | import React, { ReactNode } from 'react';
3 |
4 | export function TimelineItemStateWrapper({
5 | children,
6 | color,
7 | }: {
8 | children: ReactNode;
9 | color: 'success' | 'error' | 'expired' | 'secondary';
10 | }) {
11 | return (
12 | ({
15 | position: 'absolute',
16 | top: 'calc(100% + 36px)',
17 | color:
18 | color === 'success'
19 | ? theme.palette.$mainFor
20 | : color === 'error'
21 | ? theme.palette.$mainAgainst
22 | : color === 'expired'
23 | ? theme.palette.$textDisabled
24 | : color === 'secondary'
25 | ? theme.palette.$text
26 | : theme.palette.$text,
27 | [theme.breakpoints.up('lg')]: {
28 | top: 'calc(100% + 40px)',
29 | },
30 | })}>
31 | {children}
32 |
33 | );
34 | }
35 |
--------------------------------------------------------------------------------
/src/ui/components/TableHeaderTitle.tsx:
--------------------------------------------------------------------------------
1 | import { Box, SxProps } from '@mui/system';
2 | import React from 'react';
3 |
4 | interface TableHeaderTitleProps {
5 | title?: string;
6 | right?: boolean;
7 | center?: boolean;
8 | css?: SxProps;
9 | }
10 |
11 | export function TableHeaderTitle({
12 | title,
13 | center,
14 | right,
15 | css,
16 | }: TableHeaderTitleProps) {
17 | return (
18 |
19 | ({
22 | typography: 'h1',
23 | display: 'inline-flex',
24 | width: '100%',
25 | alignItems: 'center',
26 | justifyContent: center ? 'center' : right ? 'flex-end' : 'flex-start',
27 | [theme.breakpoints.up('sm')]: {
28 | typography: 'h1',
29 | height: 47,
30 | },
31 | [theme.breakpoints.up('md')]: {
32 | typography: 'h1',
33 | height: 62,
34 | },
35 | })}>
36 | {title}
37 |
38 |
39 | );
40 | }
41 |
--------------------------------------------------------------------------------
/src/web3/components/ChainsIcons.tsx:
--------------------------------------------------------------------------------
1 | import { Box, useTheme } from '@mui/system';
2 |
3 | import { NetworkIcon } from '../../ui/components/NetworkIcon';
4 |
5 | interface ChainsIconsProps {
6 | chains: number[];
7 | alwaysVisible?: boolean;
8 | }
9 |
10 | export function ChainsIcons({ chains, alwaysVisible }: ChainsIconsProps) {
11 | const theme = useTheme();
12 |
13 | if (chains.length <= 1 && !alwaysVisible) return null;
14 |
15 | return (
16 |
18 | {chains.map((chainId, index) => (
19 |
35 | ))}
36 |
37 | );
38 | }
39 |
--------------------------------------------------------------------------------
/src/ui/utils/metaTexts.ts:
--------------------------------------------------------------------------------
1 | export const metaTexts = {
2 | main: 'Aave Governance - ',
3 | keywords:
4 | 'governance, voting, decentralized Finance, defi, aave, ethereum, polygon, avalanche, optimism, arbitrum, base, metis, assets, erc-20, smart contracts, open finance, trustless',
5 | proposalListMetaDescription: 'List of governance proposals',
6 | createPageMetaTitle: 'Create',
7 | createPageMetaDescription: 'Create a new proposal (dev)',
8 | notFoundPageMetaTitle: 'Page not found',
9 | notFoundPageMetaDescription: '404, page not found',
10 | delegatePageMetaTitle: 'Delegation',
11 | delegatePageMetaDescription:
12 | 'Management of voting/proposition power delegations',
13 | representationsPageMetaTitle: 'Representations',
14 | representationsPageMetaDescription:
15 | 'Management of representations and representatives',
16 | rpcSwitcherPageMetaTitle: 'RPC Switcher',
17 | rpcSwitcherPageMetaDescription: 'Management of RPC URLs',
18 | proposalId: (id: string) => `Proposal ${id}`,
19 | ipfsTitle: 'Aave Governance',
20 | ipfsDescription: 'Vote on the Aave ecosystem',
21 | };
22 |
--------------------------------------------------------------------------------
/src/proposals/utils/statuses.ts:
--------------------------------------------------------------------------------
1 | import { ProposalStateWithName } from '@bgd-labs/aave-governance-ui-helpers';
2 |
3 | export type ProposalStateForFilter = {
4 | value: number | null;
5 | title: ProposalStateWithName | 'All';
6 | };
7 |
8 | export const proposalStatuses = [
9 | {
10 | value: 0,
11 | title: ProposalStateWithName.Created,
12 | },
13 | {
14 | value: 1,
15 | title: ProposalStateWithName.Active,
16 | },
17 | {
18 | value: 2,
19 | title: ProposalStateWithName.Succeed,
20 | },
21 | {
22 | value: 3,
23 | title: ProposalStateWithName.Failed,
24 | },
25 | {
26 | value: 4,
27 | title: ProposalStateWithName.Executed,
28 | },
29 | {
30 | value: 5,
31 | title: ProposalStateWithName.Expired,
32 | },
33 | {
34 | value: 6,
35 | title: ProposalStateWithName.Canceled,
36 | },
37 | {
38 | value: 7,
39 | title: ProposalStateWithName.ActiveAll,
40 | },
41 | ];
42 |
43 | export const proposalStatusesForFilter: ProposalStateForFilter[] = [
44 | ...proposalStatuses,
45 | {
46 | value: null,
47 | title: 'All',
48 | },
49 | ];
50 |
--------------------------------------------------------------------------------
/src/ui/components/CheckBox.tsx:
--------------------------------------------------------------------------------
1 | import { Box, useTheme } from '@mui/system';
2 |
3 | interface CheckBoxProps {
4 | value: boolean;
5 | }
6 |
7 | export function CheckBox({ value }: CheckBoxProps) {
8 | const theme = useTheme();
9 |
10 | return (
11 |
30 |
38 |
39 | );
40 | }
41 |
--------------------------------------------------------------------------------
/src/proposals/components/RepresentationIcon.tsx:
--------------------------------------------------------------------------------
1 | import { useTheme } from '@mui/system';
2 | import React from 'react';
3 |
4 | import RepresentationVotingPowerIcon from '/public/images/representation/representationVotingPower.svg';
5 |
6 | import { IconBox } from '../../ui/primitives/IconBox';
7 |
8 | interface RepresentationIconProps {
9 | address: string;
10 | disabled: boolean;
11 | }
12 |
13 | export function RepresentationIcon({
14 | address,
15 | disabled,
16 | }: RepresentationIconProps) {
17 | const theme = useTheme();
18 |
19 | if (!address) return null;
20 |
21 | const color = disabled ? theme.palette.$textDisabled : theme.palette.$text;
22 |
23 | return (
24 | svg': {
30 | width: 14,
31 | height: 14,
32 | path: { stroke: color },
33 | '.borders': {
34 | stroke: color,
35 | },
36 | '.filled': {
37 | fill: color,
38 | stroke: color,
39 | },
40 | },
41 | }}>
42 |
43 |
44 | );
45 | }
46 |
--------------------------------------------------------------------------------
/src/ui/utils/inputValidation.ts:
--------------------------------------------------------------------------------
1 | import { isAddress } from 'viem';
2 |
3 | import { texts } from './texts';
4 |
5 | export const required = (value: any) =>
6 | value || value === 0 ? undefined : texts.other.requiredValidation;
7 |
8 | export const addressValidator = (value: any) =>
9 | isAddress(value) || value === '' || value === undefined
10 | ? undefined
11 | : texts.other.addressValidation;
12 |
13 | export const ensNameOrAddressValidator = (value: string) =>
14 | (value && (value.endsWith('.eth') || isAddress(value))) ||
15 | value === '' ||
16 | value === undefined
17 | ? undefined
18 | : texts.other.ensNameValidation;
19 |
20 | export const composeValidators =
21 | (...validators: any) =>
22 | (value: any) =>
23 | validators.reduce(
24 | (error: any, validator: any) => error || validator(value),
25 | undefined,
26 | );
27 |
28 | export const rpcUrlValidator = (value: string) => {
29 | if (value && value.startsWith('https://')) {
30 | try {
31 | new URL(value);
32 | return undefined;
33 | } catch {
34 | return texts.other.rpcUrlValidation;
35 | }
36 | }
37 | return texts.other.rpcUrlValidation;
38 | };
39 |
--------------------------------------------------------------------------------
/public/images/icons/pencil.svg:
--------------------------------------------------------------------------------
1 |
10 |
--------------------------------------------------------------------------------
/src/ui/layouts/AppGlobalStyles.tsx:
--------------------------------------------------------------------------------
1 | import { EmotionCache } from '@emotion/cache';
2 | import { CacheProvider } from '@emotion/react';
3 | import { ThemeProvider as PreferredThemeProvider } from 'next-themes';
4 | import React, { ReactNode, useEffect } from 'react';
5 |
6 | import { useStore } from '../../store/ZustandStoreProvider';
7 | import { createEmotionCache } from '../utils/themeMUI';
8 | import { MUIThemeProvider } from './MUIThemeProvider';
9 |
10 | // Client-side cache, shared for the whole session of the user in the browser.
11 | const clientSideEmotionCache = createEmotionCache();
12 |
13 | export function AppGlobalStyles({
14 | children,
15 | emotionCache = clientSideEmotionCache,
16 | }: {
17 | children: ReactNode;
18 | emotionCache?: EmotionCache;
19 | }) {
20 | const setIsRendered = useStore((store) => store.setIsRendered);
21 | useEffect(() => setIsRendered(), []);
22 |
23 | return (
24 |
27 |
28 | {children}
29 |
30 |
31 | );
32 | }
33 |
--------------------------------------------------------------------------------
/src/ui/components/CopyToClipboard.tsx:
--------------------------------------------------------------------------------
1 | import { Box } from '@mui/system';
2 | import React, { ReactNode, useState } from 'react';
3 |
4 | import { texts } from '../utils/texts';
5 | import { Tooltip } from './Tooltip';
6 |
7 | interface CopyToClipboardProps {
8 | copyText: string;
9 | copyTooltipText?: string;
10 | children: ReactNode;
11 | }
12 |
13 | export function CopyToClipboard({
14 | copyText,
15 | copyTooltipText,
16 | children,
17 | }: CopyToClipboardProps) {
18 | const [copied, setCopied] = useState(false);
19 |
20 | const handleCopy = async (event: React.MouseEvent) => {
21 | event.preventDefault();
22 | await navigator.clipboard.writeText(copyText);
23 | setCopied(true);
24 | setTimeout(() => setCopied(false), 1000);
25 | };
26 |
27 | return (
28 |
29 |
33 | {copied ? texts.other.copied : copyTooltipText || texts.other.copy}
34 |
35 | }>
36 | {children}
37 |
38 |
39 | );
40 | }
41 |
--------------------------------------------------------------------------------
/src/ui/components/Web3Icons/WalletIcon.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import { Web3Icon } from '@bgd-labs/react-web3-icons';
4 | import { Box, SxProps } from '@mui/system';
5 |
6 | import { CustomSkeleton } from '../CustomSkeleton';
7 |
8 | interface WalletIconProps {
9 | walletName: string;
10 | size?: number;
11 | css?: SxProps;
12 | }
13 |
14 | /**
15 | * Renders a wallet icon specified by walletName.
16 | */
17 | const WalletIcon = ({ walletName, size, css, ...props }: WalletIconProps) => {
18 | return (
19 |
26 |
38 |
39 |
40 | }
41 | {...props}
42 | />
43 |
44 | );
45 | };
46 |
47 | export default WalletIcon;
48 |
--------------------------------------------------------------------------------
/src/proposals/components/ProposalStatus.tsx:
--------------------------------------------------------------------------------
1 | import { CombineProposalState } from '@bgd-labs/aave-governance-ui-helpers';
2 | import { Box } from '@mui/system';
3 |
4 | import { proposalStatuses } from '../utils/statuses';
5 |
6 | export interface ProposalStatusProps {
7 | status: CombineProposalState;
8 | isSecondary?: boolean;
9 | isFinished?: boolean;
10 | }
11 |
12 | export function ProposalStatus({
13 | status,
14 | isFinished,
15 | isSecondary,
16 | }: ProposalStatusProps) {
17 | const statusTitle = proposalStatuses.find((s) => s.value === status)?.title;
18 |
19 | return (
20 |
38 | {statusTitle}
39 |
40 | );
41 | }
42 |
--------------------------------------------------------------------------------
/src/proposals/components/DetailsModalWrapper.tsx:
--------------------------------------------------------------------------------
1 | import { Box } from '@mui/system';
2 | import { ReactNode } from 'react';
3 |
4 | import { BasicModal, Divider } from '../../ui';
5 | import { texts } from '../../ui/utils/texts';
6 |
7 | interface DetailsModalWrapperProps {
8 | isOpen: boolean;
9 | setIsOpen: (value: boolean) => void;
10 | proposalId: number;
11 | children: ReactNode;
12 | }
13 |
14 | export function DetailsModalWrapper({
15 | proposalId,
16 | isOpen,
17 | setIsOpen,
18 | children,
19 | }: DetailsModalWrapperProps) {
20 | return (
21 |
26 | ({ [theme.breakpoints.up('sm')]: { pt: 20 } })}>
27 |
28 | {texts.proposals.detailsModalTitle(proposalId)}
29 |
30 |
31 | ({
33 | mt: 18,
34 | mb: 24,
35 | [theme.breakpoints.up('lg')]: { mt: 24, mb: 30 },
36 | })}
37 | />
38 |
39 | {children}
40 |
41 |
42 | );
43 | }
44 |
--------------------------------------------------------------------------------
/src/delegate/utils/getFormDelegateData.ts:
--------------------------------------------------------------------------------
1 | import { Address } from 'viem';
2 |
3 | import { DelegateData } from '../types';
4 |
5 | interface GetFormDelegateDataParams {
6 | underlyingAsset?: Address;
7 | votingToAddress?: Address | string;
8 | propositionToAddress?: Address | string;
9 | formDelegateData?: DelegateData[];
10 | }
11 |
12 | export function getFormDelegateData({
13 | underlyingAsset,
14 | votingToAddress,
15 | propositionToAddress,
16 | formDelegateData,
17 | }: GetFormDelegateDataParams) {
18 | const formDelegatedDataItem =
19 | !!formDelegateData &&
20 | !!formDelegateData.length &&
21 | formDelegateData.find((item) => item.underlyingAsset === underlyingAsset);
22 |
23 | const formVotingToAddress =
24 | typeof formDelegatedDataItem !== 'boolean' &&
25 | typeof formDelegatedDataItem !== 'undefined'
26 | ? formDelegatedDataItem.votingToAddress
27 | : votingToAddress;
28 |
29 | const formPropositionToAddress =
30 | typeof formDelegatedDataItem !== 'boolean' &&
31 | typeof formDelegatedDataItem !== 'undefined'
32 | ? formDelegatedDataItem.propositionToAddress
33 | : propositionToAddress;
34 |
35 | return {
36 | formVotingToAddress,
37 | formPropositionToAddress,
38 | };
39 | }
40 |
--------------------------------------------------------------------------------
/src/ui/utils/useMediaQuery.ts:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from 'react';
2 |
3 | export function useMediaQuery(query: string): boolean {
4 | const getMatches = (query: string): boolean => {
5 | // Prevents SSR issues
6 | if (typeof window !== 'undefined') {
7 | return window.matchMedia(query).matches;
8 | }
9 | return false;
10 | };
11 |
12 | const [matches, setMatches] = useState(getMatches(query));
13 |
14 | function handleChange() {
15 | setMatches(getMatches(query));
16 | }
17 |
18 | useEffect(() => {
19 | const matchMedia = window.matchMedia(query);
20 |
21 | // Triggered at the first client-side load and if query changes
22 | handleChange();
23 |
24 | // Listen matchMedia
25 | if (matchMedia.addListener) {
26 | matchMedia.addListener(handleChange);
27 | } else {
28 | matchMedia.addEventListener('change', handleChange);
29 | }
30 |
31 | return () => {
32 | if (matchMedia.removeListener) {
33 | matchMedia.removeListener(handleChange);
34 | } else {
35 | matchMedia.removeEventListener('change', handleChange);
36 | }
37 | };
38 | // eslint-disable-next-line react-hooks/exhaustive-deps
39 | }, [query]);
40 |
41 | return matches;
42 | }
43 |
--------------------------------------------------------------------------------
/src/proposals/components/proposalList/VotingPowerLoading.tsx:
--------------------------------------------------------------------------------
1 | import { Box, useTheme } from '@mui/system';
2 | import React from 'react';
3 |
4 | import { CustomSkeleton } from '../../../ui/components/CustomSkeleton';
5 |
6 | export function VotingPowerLoading() {
7 | const theme = useTheme();
8 | return (
9 |
32 |
33 |
34 |
35 |
36 |
37 | );
38 | }
39 |
--------------------------------------------------------------------------------
/src/ui/components/TableContainer.tsx:
--------------------------------------------------------------------------------
1 | import { Box, useTheme } from '@mui/system';
2 | import React, { ReactNode } from 'react';
3 |
4 | import { BoxWith3D } from './BoxWith3D';
5 |
6 | export const TableContainerChildren = ({
7 | children,
8 | forHelp,
9 | }: {
10 | children: ReactNode;
11 | forHelp?: boolean;
12 | }) => {
13 | return (
14 |
20 | {children}
21 |
22 | );
23 | };
24 |
25 | interface TableContainerProps {
26 | forHelp?: boolean;
27 | children: ReactNode;
28 | }
29 |
30 | export function TableContainer({ forHelp, children }: TableContainerProps) {
31 | const theme = useTheme();
32 |
33 | return (
34 |
49 | {children}
50 |
51 | );
52 | }
53 |
--------------------------------------------------------------------------------
/src/utils/appConfig.ts:
--------------------------------------------------------------------------------
1 | import {
2 | appConfigInit,
3 | CoreNetworkName,
4 | payloadsControllerChainIds,
5 | votingMachineChainIds,
6 | } from '@bgd-labs/aave-governance-ui-helpers';
7 | import { avalanche, goerli, polygon, sepolia } from 'viem/chains';
8 |
9 | export const isForIPFS = process.env.NEXT_PUBLIC_DEPLOY_FOR_IPFS === 'true';
10 | export const isTermsAndConditionsVisible =
11 | process.env.NEXT_PUBLIC_TERMS_AND_CONDITIONS_VISIBLE === 'true';
12 |
13 | export const coreName: CoreNetworkName = 'mainnet';
14 | export const WC_PROJECT_ID =
15 | process.env.WC_PROJECT_ID || 'e6ed0c48443e54cc875462bbaec6e3a7'; // https://docs.walletconnect.com/2.0/cloud/relay
16 |
17 | export const appUsedNetworks: number[] = [
18 | ...votingMachineChainIds[coreName],
19 | ...payloadsControllerChainIds[coreName],
20 | ].filter((value, index, self) => self.indexOf(value) === index);
21 |
22 | export const gelatoApiKeys: Record = {
23 | [polygon.id]: 'eyUjscMpge_d3qScFe2ueftb95FDZ1eChyDJGqPx2uQ_',
24 | [avalanche.id]: 'iGCOci5z6zZYDDQ1p916xsKmr2pfP5o1EAQcRhy1_fI_',
25 | // testnets
26 | [goerli.id]: 'MgZBKc6a7GHzxlrkdHCWIsazai_Niqbps42wvPlE7xE_',
27 | [sepolia.id]: 'MgZBKc6a7GHzxlrkdHCWIsazai_Niqbps42wvPlE7xE_',
28 | };
29 |
30 | export const appConfig = appConfigInit(coreName);
31 |
--------------------------------------------------------------------------------
/src/store/ZustandStoreProvider.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import { createContext, type ReactNode, useContext, useRef } from 'react';
4 | import { create, type StoreApi, useStore as useZustandStore } from 'zustand';
5 |
6 | import { createRootSlice, RootState } from './index';
7 |
8 | // provider with zustand store https://docs.pmnd.rs/zustand/guides/nextjs
9 | export const ZustandStoreContext = createContext | null>(
10 | null,
11 | );
12 |
13 | export interface ZustandStoreProviderProps {
14 | children: ReactNode;
15 | }
16 |
17 | export const ZustandStoreProvider = ({
18 | children,
19 | }: ZustandStoreProviderProps) => {
20 | const storeRef = useRef>();
21 |
22 | if (!storeRef.current) {
23 | storeRef.current = create(createRootSlice);
24 | }
25 |
26 | return (
27 |
28 | {children}
29 |
30 | );
31 | };
32 |
33 | export const useStore = (selector: (store: RootState) => T): T => {
34 | const zustandStoreContext = useContext(ZustandStoreContext);
35 |
36 | if (!zustandStoreContext) {
37 | throw new Error(`useStore must be use within ZustandStoreProvider`);
38 | }
39 |
40 | return useZustandStore(zustandStoreContext, selector);
41 | };
42 |
--------------------------------------------------------------------------------
/src/ui/components/Web3Icons/ChainIcon.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import { Web3Icon } from '@bgd-labs/react-web3-icons';
4 | import { chainsIconsPack } from '@bgd-labs/react-web3-icons/dist/iconsPacks/chainsIconsPack';
5 | import { Box, SxProps } from '@mui/system';
6 |
7 | import { CustomSkeleton } from '../CustomSkeleton';
8 |
9 | interface ChainIconProps {
10 | chainId: number;
11 | size?: number;
12 | css?: SxProps;
13 | }
14 | /**
15 | * Renders a chain icon specified by chainId.
16 | */
17 | const ChainIcon = ({ chainId, size, css, ...props }: ChainIconProps) => {
18 | return (
19 |
27 |
40 |
41 |
42 | }
43 | {...props}
44 | />
45 |
46 | );
47 | };
48 |
49 | export default ChainIcon;
50 |
--------------------------------------------------------------------------------
/src/ui/utils/useScrollDirection.ts:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from 'react';
2 |
3 | import { media } from './themeMUI';
4 | import { useMediaQuery } from './useMediaQuery';
5 |
6 | export function useScrollDirection() {
7 | const sm = useMediaQuery(media.sm);
8 |
9 | const [scrollDirection, setScrollDirection] = useState<'down' | 'up' | null>(
10 | null,
11 | );
12 |
13 | let lastScrollY = typeof window !== 'undefined' ? window.pageYOffset : 0;
14 |
15 | const updateScrollDirection = () => {
16 | if (typeof window !== 'undefined') {
17 | const scrollY = window.pageYOffset;
18 | const direction = scrollY > lastScrollY ? 'down' : 'up';
19 | if (
20 | direction !== scrollDirection &&
21 | (scrollY - lastScrollY > 0 || scrollY - lastScrollY < 0)
22 | ) {
23 | setScrollDirection(direction);
24 | }
25 | lastScrollY = scrollY > 0 ? scrollY : 0;
26 | }
27 | };
28 |
29 | useEffect(() => {
30 | if (typeof window !== 'undefined' && !sm) {
31 | window.addEventListener('scroll', updateScrollDirection); // add event listener
32 | }
33 | return () => {
34 | if (typeof window !== 'undefined') {
35 | window.removeEventListener('scroll', updateScrollDirection); // clean up
36 | }
37 | };
38 | }, [scrollDirection]);
39 |
40 | return { scrollDirection };
41 | }
42 |
--------------------------------------------------------------------------------
/src/proposals/components/VoteLine.tsx:
--------------------------------------------------------------------------------
1 | import { Box } from '@mui/system';
2 |
3 | export interface VoteLineProps {
4 | percent: number;
5 | color?: 'for' | 'against';
6 | width?: string | number;
7 | }
8 |
9 | export function VoteLine({
10 | percent,
11 | color = 'for',
12 | width = 164,
13 | }: VoteLineProps) {
14 | return (
15 | ({
18 | display: 'inline-flex',
19 | alignItems: 'center',
20 | position: 'relative',
21 | height: 8,
22 | width: width,
23 | backgroundColor: '$mainLight',
24 | borderColor: `${theme.palette.$mainElements} !important`,
25 | borderStyle: 'solid',
26 | borderWidth: '1px',
27 | borderTopWidth: 2,
28 | borderRightWidth: 2,
29 | [theme.breakpoints.up('lg')]: {
30 | height: 10,
31 | borderTopWidth: 3,
32 | borderRightWidth: 3,
33 | },
34 | })}>
35 |
47 |
48 | );
49 | }
50 |
--------------------------------------------------------------------------------
/src/proposals/components/BlockWrapper.tsx:
--------------------------------------------------------------------------------
1 | import { Box, SxProps, useTheme } from '@mui/system';
2 | import React, { ReactNode } from 'react';
3 |
4 | import { BoxWith3D } from '../../ui';
5 |
6 | interface BlockWrapperProps {
7 | children: ReactNode;
8 | contentColor?: string;
9 | leftBorderColor?: string;
10 | bottomBorderColor?: string;
11 | css?: SxProps;
12 | toBottom?: boolean;
13 | }
14 |
15 | export function BlockWrapper({
16 | children,
17 | contentColor,
18 | leftBorderColor,
19 | bottomBorderColor,
20 | css,
21 | toBottom,
22 | }: BlockWrapperProps) {
23 | const theme = useTheme();
24 |
25 | return (
26 |
42 |
48 | {children}
49 |
50 |
51 | );
52 | }
53 |
--------------------------------------------------------------------------------
/app/layout.page.tsx:
--------------------------------------------------------------------------------
1 | import { Inter } from 'next/font/google';
2 | import NextTopLoader from 'nextjs-toploader';
3 | import React from 'react';
4 |
5 | import { ZustandStoreProvider } from '../src/store/ZustandStoreProvider';
6 | import AppLayout from '../src/ui/layouts/AppLayout';
7 | import ThemeRegistry from '../src/ui/utils/ThemeRegistry';
8 | import WagmiProvider from '../src/web3/providers/WagmiProvider';
9 |
10 | export const interNextFont = Inter({
11 | weight: ['300', '400', '600', '700', '800'],
12 | style: ['normal'],
13 | subsets: ['latin'],
14 | display: 'swap',
15 | });
16 |
17 | export const metadata = {
18 | metadataBase: process.env.VERCEL_URL
19 | ? `https://${process.env.VERCEL_URL}`
20 | : `http://localhost:${process.env.PORT || 3000}`,
21 | manifest: '/manifest.json',
22 | icons: {
23 | icon: '/favicon.ico',
24 | },
25 | };
26 |
27 | export default function RootLayout({
28 | children,
29 | }: {
30 | children: React.ReactNode;
31 | }) {
32 | return (
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 | {children}
41 |
42 |
43 |
44 |
45 | );
46 | }
47 |
--------------------------------------------------------------------------------
/src/proposalCreateOverview/components/SeatBeltReportModal.tsx:
--------------------------------------------------------------------------------
1 | import { Box } from '@mui/system';
2 | import React, { useRef } from 'react';
3 |
4 | import { BasicModal } from '../../ui';
5 | import { CopyAndExternalIconsSet } from '../../ui/components/CopyAndExternalIconsSet';
6 | import { MarkdownContainer } from '../../ui/components/MarkdownContainer';
7 |
8 | interface SeatBeltReportModalProps {
9 | isOpen: boolean;
10 | setIsOpen: (value: boolean) => void;
11 | link: string;
12 | report?: string;
13 | }
14 |
15 | export function SeatBeltReportModal({
16 | isOpen,
17 | setIsOpen,
18 | link,
19 | report,
20 | }: SeatBeltReportModalProps) {
21 | const initialFocusRef = useRef(null);
22 |
23 | if (!report) return null;
24 |
25 | return (
26 |
32 |
38 | Seatbelt Report
39 |
44 |
45 |
46 |
47 |
48 | );
49 | }
50 |
--------------------------------------------------------------------------------
/src/ui/components/CustomSkeleton.tsx:
--------------------------------------------------------------------------------
1 | import { Box } from '@mui/system';
2 | import React from 'react';
3 | import Skeleton, { SkeletonProps } from 'react-loading-skeleton';
4 |
5 | import { useStore } from '../../store/ZustandStoreProvider';
6 |
7 | interface CustomSkeletonProps extends SkeletonProps {
8 | variant?: 'default' | 'dark';
9 | }
10 |
11 | export function CustomSkeleton({
12 | variant = 'default',
13 | ...rest
14 | }: CustomSkeletonProps) {
15 | const isRendered = useStore((store) => store.isRendered);
16 |
17 | return (
18 | ({
20 | '.react-loading-skeleton': {
21 | backgroundColor: isRendered
22 | ? `${theme.palette.$light} !important`
23 | : theme.palette.$light,
24 |
25 | '&:after': {
26 | backgroundImage: `linear-gradient(90deg, ${
27 | isRendered
28 | ? `${theme.palette.$light} !important`
29 | : theme.palette.$light
30 | }, ${
31 | isRendered
32 | ? `${theme.palette.$middleLight} !important`
33 | : theme.palette.$middleLight
34 | }, ${
35 | isRendered
36 | ? `${theme.palette.$light} !important`
37 | : theme.palette.$light
38 | })`,
39 | },
40 | },
41 | })}>
42 |
43 |
44 | );
45 | }
46 |
--------------------------------------------------------------------------------
/src/ui/components/Web3Icons/AssetIcon.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import { Asset } from '@bgd-labs/aave-governance-ui-helpers';
4 | import { Web3Icon } from '@bgd-labs/react-web3-icons';
5 | import { Box, SxProps } from '@mui/system';
6 |
7 | import { CustomSkeleton } from '../CustomSkeleton';
8 |
9 | /**
10 | * Renders an asset icon specified by symbol.
11 | */
12 | const AssetIcon = ({
13 | symbol,
14 | size,
15 | css,
16 | ...props
17 | }: {
18 | symbol: string;
19 | size?: number;
20 | css?: SxProps;
21 | }) => {
22 | return (
23 |
34 |
47 |
48 |
49 | }
50 | {...props}
51 | />
52 |
53 | );
54 | };
55 |
56 | export default AssetIcon;
57 |
--------------------------------------------------------------------------------
/src/proposals/components/actionModals/ActivateVotingModal.tsx:
--------------------------------------------------------------------------------
1 | import { Box } from '@mui/system';
2 | import React from 'react';
3 |
4 | import { useStore } from '../../../store/ZustandStoreProvider';
5 | import { ActionModal } from '../../../transactions/components/ActionModal';
6 | import { TxType } from '../../../transactions/store/transactionsSlice';
7 | import { texts } from '../../../ui/utils/texts';
8 | import {
9 | ActionModalContentWrapper,
10 | ActionModalTitle,
11 | } from './ActionModalContentWrapper';
12 | import { ActionModalBasicTypes } from './types';
13 |
14 | export function ActivateVotingModal({
15 | isOpen,
16 | setIsOpen,
17 | proposalId,
18 | }: ActionModalBasicTypes) {
19 | const activateVoting = useStore((state) => state.activateVoting);
20 |
21 | return (
22 | await activateVoting(proposalId)}
26 | isOpen={isOpen}
27 | setIsOpen={setIsOpen}
28 | actionButtonTitle={texts.other.confirm}
29 | topBlock={
30 |
31 | }
32 | withCancelButton>
33 |
34 |
35 | {texts.proposalActions.activateVotingDescription}
36 |
37 |
38 |
39 | );
40 | }
41 |
--------------------------------------------------------------------------------
/src/proposals/components/actionModals/ExecuteProposalModal.tsx:
--------------------------------------------------------------------------------
1 | import { Box } from '@mui/system';
2 | import React from 'react';
3 |
4 | import { useStore } from '../../../store/ZustandStoreProvider';
5 | import { ActionModal } from '../../../transactions/components/ActionModal';
6 | import { TxType } from '../../../transactions/store/transactionsSlice';
7 | import { texts } from '../../../ui/utils/texts';
8 | import {
9 | ActionModalContentWrapper,
10 | ActionModalTitle,
11 | } from './ActionModalContentWrapper';
12 | import { ActionModalBasicTypes } from './types';
13 |
14 | export function ExecuteProposalModal({
15 | isOpen,
16 | setIsOpen,
17 | proposalId,
18 | }: ActionModalBasicTypes) {
19 | const executeProposal = useStore((state) => state.executeProposal);
20 |
21 | return (
22 | await executeProposal(proposalId)}
26 | isOpen={isOpen}
27 | setIsOpen={setIsOpen}
28 | actionButtonTitle={texts.other.confirm}
29 | topBlock={
30 |
31 | }
32 | withCancelButton>
33 |
34 |
35 | {texts.proposalActions.executeProposalDescription}
36 |
37 |
38 |
39 | );
40 | }
41 |
--------------------------------------------------------------------------------
/src/web3/providers/Web3HelperProvider.tsx:
--------------------------------------------------------------------------------
1 | import { useEffect } from 'react';
2 |
3 | import { useStore } from '../../store/ZustandStoreProvider';
4 | import { appUsedNetworks } from '../../utils/appConfig';
5 | import { chainInfoHelper } from '../../utils/configs';
6 |
7 | function Child() {
8 | const activeWallet = useStore((state) => state.activeWallet);
9 | const resetL1Balances = useStore((state) => state.resetL1Balances);
10 | const fullClearSupportObject = useStore(
11 | (state) => state.fullClearSupportObject,
12 | );
13 | const getRepresentingAddress = useStore(
14 | (state) => state.getRepresentingAddress,
15 | );
16 | const representationData = useStore((state) => state.representationData);
17 | const getRepresentationData = useStore(
18 | (state) => state.getRepresentationData,
19 | );
20 | const initEns = useStore((state) => state.initEns);
21 | const initClients = useStore((state) => state.initClients);
22 |
23 | useEffect(() => {
24 | initEns();
25 | initClients(chainInfoHelper, appUsedNetworks);
26 | }, []);
27 |
28 | useEffect(() => {
29 | resetL1Balances();
30 | fullClearSupportObject();
31 | setTimeout(() => getRepresentationData(), 1);
32 | }, [activeWallet?.address]);
33 |
34 | useEffect(() => {
35 | getRepresentingAddress();
36 | }, [activeWallet?.address, representationData]);
37 |
38 | return null;
39 | }
40 |
41 | export default function Web3HelperProvider() {
42 | return ;
43 | }
44 |
--------------------------------------------------------------------------------
/pages/_app.tsx:
--------------------------------------------------------------------------------
1 | import 'react-loading-skeleton/dist/skeleton.css';
2 |
3 | import { EmotionCache } from '@emotion/cache';
4 | import { NextPage } from 'next';
5 | import type { AppProps } from 'next/app';
6 | import { useRouter } from 'next/router';
7 | import React, { useMemo } from 'react';
8 |
9 | import { ZustandStoreProvider } from '../src/store/ZustandStoreProvider';
10 | import { AppGlobalStyles } from '../src/ui';
11 | import AppLayout from '../src/ui/layouts/AppLayout';
12 | import WagmiProvider from '../src/web3/providers/WagmiProvider';
13 |
14 | type NextPageWithLayout = NextPage & {
15 | getLayout?: (page: React.ReactElement) => React.ReactNode;
16 | };
17 |
18 | interface GovernanceAppProps extends AppProps {
19 | Component: NextPageWithLayout;
20 | emotionCache?: EmotionCache;
21 | }
22 |
23 | function GovernanceApp({
24 | Component,
25 | pageProps,
26 | emotionCache,
27 | }: GovernanceAppProps) {
28 | const router = useRouter();
29 |
30 | const getLayout = Component.getLayout ?? ((page: React.ReactNode) => page);
31 |
32 | useMemo(() => {
33 | router.prefetch = async () => {};
34 | }, [router]);
35 |
36 | return (
37 | <>
38 |
39 |
40 |
41 |
42 | {getLayout()}
43 |
44 |
45 | >
46 | );
47 | }
48 |
49 | export default GovernanceApp;
50 |
--------------------------------------------------------------------------------
/src/web3/utils/ensHelpers.tsx:
--------------------------------------------------------------------------------
1 | import makeBlockie from 'ethereum-blockies-base64';
2 | import { Address, Hex, isAddress } from 'viem';
3 | import { mainnet } from 'viem/chains';
4 | import { getEnsAddress, getEnsAvatar, getEnsName, normalize } from 'viem/ens';
5 |
6 | import { chainInfoHelper } from '../../utils/configs';
7 |
8 | const client = chainInfoHelper.clientInstances[mainnet.id].instance;
9 |
10 | export const getName = async (address: Hex) => {
11 | try {
12 | const name = await getEnsName(client, { address });
13 | return name ? name : undefined;
14 | } catch (error) {
15 | console.error('ENS name lookup error', error);
16 | }
17 | };
18 |
19 | export const getAvatar = async (name: string, address: string) => {
20 | try {
21 | const background_image = await getEnsAvatar(client, { name });
22 | return background_image ? background_image : makeBlockie(address);
23 | } catch (error) {
24 | console.error('ENS avatar lookup error', error);
25 | }
26 | };
27 |
28 | export const getAddress = async (name: string) => {
29 | try {
30 | const address = await getEnsAddress(client, {
31 | name: normalize(name),
32 | });
33 | return (address ? address.toLocaleLowerCase() : undefined) as
34 | | Address
35 | | undefined;
36 | } catch (error) {
37 | console.error('ENS address lookup error', error);
38 | }
39 | };
40 |
41 | export const isEnsName = (address: string) => !isAddress(address);
42 |
43 | export const ENS_TTL = 60 * 60 * 24; // 1 day
44 |
--------------------------------------------------------------------------------
/src/proposals/components/actionModals/CloseVotingModal.tsx:
--------------------------------------------------------------------------------
1 | import { Box } from '@mui/system';
2 | import React from 'react';
3 |
4 | import { useStore } from '../../../store/ZustandStoreProvider';
5 | import { ActionModal } from '../../../transactions/components/ActionModal';
6 | import { TxType } from '../../../transactions/store/transactionsSlice';
7 | import { texts } from '../../../ui/utils/texts';
8 | import {
9 | ActionModalContentWrapper,
10 | ActionModalTitle,
11 | } from './ActionModalContentWrapper';
12 | import { ActionModalBasicTypes } from './types';
13 |
14 | export function CloseVotingModal({
15 | votingChainId,
16 | isOpen,
17 | setIsOpen,
18 | proposalId,
19 | }: { votingChainId: number } & ActionModalBasicTypes) {
20 | const closeAndSendVote = useStore((state) => state.closeAndSendVote);
21 |
22 | return (
23 |
27 | await closeAndSendVote(votingChainId, proposalId)
28 | }
29 | isOpen={isOpen}
30 | setIsOpen={setIsOpen}
31 | actionButtonTitle={texts.other.confirm}
32 | topBlock={}
33 | withCancelButton>
34 |
35 |
36 | {texts.proposalActions.closeVotingDescription}
37 |
38 |
39 |
40 | );
41 | }
42 |
--------------------------------------------------------------------------------
/src/ui/components/NetworkIcon.tsx:
--------------------------------------------------------------------------------
1 | import { Box, SxProps } from '@mui/system';
2 | import { useEffect, useState } from 'react';
3 | import { toHex } from 'viem';
4 |
5 | import { chainInfoHelper } from '../../utils/configs';
6 | import { Tooltip } from './Tooltip';
7 | import ChainIcon from './Web3Icons/ChainIcon';
8 |
9 | interface NetworkIconProps {
10 | chainId: number;
11 | size?: number;
12 | css?: SxProps;
13 | withTooltip?: boolean;
14 | }
15 |
16 | export function NetworkIcon({
17 | chainId,
18 | size,
19 | css,
20 | withTooltip,
21 | }: NetworkIconProps) {
22 | const [chain, setChain] = useState(
23 | chainInfoHelper.getChainParameters(chainId),
24 | );
25 |
26 | useEffect(() => {
27 | if (chainId) {
28 | setChain(chainInfoHelper.getChainParameters(chainId));
29 | }
30 | }, [chainId]);
31 |
32 | return (
33 | <>
34 | {withTooltip ? (
35 |
45 | {chain.name}: {chain.id}
({toHex(chain.id)})
46 |
47 | }>
48 |
49 |
50 | ) : (
51 |
52 | )}
53 | >
54 | );
55 | }
56 |
--------------------------------------------------------------------------------
/public/images/icons/reload.svg:
--------------------------------------------------------------------------------
1 |
9 |
--------------------------------------------------------------------------------
/src/utils/githubCacheRequests.ts:
--------------------------------------------------------------------------------
1 | import {
2 | CachedDetails,
3 | FinishedProposalForList,
4 | ProposalHistoryItem,
5 | VotersData,
6 | } from '@bgd-labs/aave-governance-ui-helpers';
7 | import { createAlova } from 'alova';
8 | import GlobalFetch from 'alova/GlobalFetch';
9 | import ReactHook from 'alova/react';
10 |
11 | import {
12 | cachedDetailsPath,
13 | cachedEventsPath,
14 | cachedProposalsIdsPath,
15 | cachedVotesPath,
16 | githubStartUrl,
17 | listViewPath,
18 | } from './cacheGithubLinks';
19 |
20 | const alovaInstance = createAlova({
21 | baseURL: githubStartUrl,
22 | statesHook: ReactHook,
23 | requestAdapter: GlobalFetch(),
24 | responded: {
25 | onSuccess: async (response) => {
26 | const data = await response.json();
27 | return data ? data : undefined;
28 | },
29 | },
30 | });
31 |
32 | export const getProposalListCacheFromGithub = alovaInstance.Get<{
33 | totalProposalCount: number;
34 | proposals: FinishedProposalForList[];
35 | }>(listViewPath);
36 |
37 | export const getCachedProposalsIdsFromGithub = alovaInstance.Get<{
38 | cachedProposalsIds: number[];
39 | }>(cachedProposalsIdsPath);
40 |
41 | export const getProposalDetailsCache = (id: number) =>
42 | alovaInstance.Get(cachedDetailsPath(id));
43 |
44 | export const getProposalVotesCache = (id: number) =>
45 | alovaInstance.Get<{ votes: VotersData[] }>(cachedVotesPath(id));
46 |
47 | export const getProposalEventsCache = (id: number) =>
48 | alovaInstance.Get>(cachedEventsPath(id));
49 |
--------------------------------------------------------------------------------
/public/images/icons/replacedIcon.svg:
--------------------------------------------------------------------------------
1 |
9 |
--------------------------------------------------------------------------------
/src/ui/index.ts:
--------------------------------------------------------------------------------
1 | import { BackButton3D } from './components/BackButton3D';
2 | import { BasicModal } from './components/BasicModal';
3 | import { BigButton } from './components/BigButton';
4 | import { BoxWith3D } from './components/BoxWith3D';
5 | import { CopyToClipboard } from './components/CopyToClipboard';
6 | import { FilterDropdown } from './components/FilterDropdown';
7 | import { Link } from './components/Link';
8 | import { Pagination } from './components/Pagination';
9 | import { SmallButton } from './components/SmallButton';
10 | import { Spinner } from './components/Spinner';
11 | import { ThemeSwitcher } from './components/ThemeSwitcher';
12 | import { Timer } from './components/Timer';
13 | import { ToggleButton } from './components/ToggleButton';
14 | import { Tooltip } from './components/Tooltip';
15 | import { AppGlobalStyles } from './layouts/AppGlobalStyles';
16 | import { MainLayout } from './layouts/MainLayout';
17 | import { Container } from './primitives/Container';
18 | import { Divider } from './primitives/Divider';
19 | import { Image } from './primitives/Image';
20 | import { Input } from './primitives/Input';
21 | import NoSSR from './primitives/NoSSR';
22 |
23 | export {
24 | AppGlobalStyles,
25 | BackButton3D,
26 | BasicModal,
27 | BigButton,
28 | BoxWith3D,
29 | Container,
30 | CopyToClipboard,
31 | Divider,
32 | FilterDropdown,
33 | Image,
34 | Input,
35 | Link,
36 | MainLayout,
37 | NoSSR,
38 | Pagination,
39 | SmallButton,
40 | Spinner,
41 | ThemeSwitcher,
42 | Timer,
43 | ToggleButton,
44 | Tooltip,
45 | };
46 |
--------------------------------------------------------------------------------
/src/ui/helpModals/HelpModalWrapper.tsx:
--------------------------------------------------------------------------------
1 | import { Box } from '@mui/system';
2 | import { ReactNode } from 'react';
3 |
4 | import { useStore } from '../../store/ZustandStoreProvider';
5 | import { BasicModal } from '../components/BasicModal';
6 |
7 | interface HelpModalWrapperProps {
8 | image: ReactNode;
9 | children: ReactNode;
10 | isOpen?: boolean;
11 | setIsOpen?: (value: boolean) => void;
12 | onBackButtonClick?: () => void;
13 | }
14 |
15 | export function HelpModalWrapper({
16 | image,
17 | children,
18 | isOpen,
19 | setIsOpen,
20 | onBackButtonClick,
21 | }: HelpModalWrapperProps) {
22 | const isHelpModalOpen = useStore((store) => store.isHelpModalOpen);
23 | const setIsHelpModalOpen = useStore((store) => store.setIsHelpModalOpen);
24 |
25 | return (
26 |
34 |
41 | {image}
42 |
47 | {children}
48 |
49 |
50 |
51 | );
52 | }
53 |
--------------------------------------------------------------------------------
/src/proposals/components/ProposalStatusWithDate.tsx:
--------------------------------------------------------------------------------
1 | import { ProposalWaitForState } from '@bgd-labs/aave-governance-ui-helpers';
2 | import { Box, SxProps } from '@mui/system';
3 | import dayjs from 'dayjs';
4 |
5 | import { ProposalStatus, ProposalStatusProps } from './ProposalStatus';
6 |
7 | export interface ProposalStatusWithDateProps extends ProposalStatusProps {
8 | timestamp: number;
9 | waitForState?: ProposalWaitForState;
10 | isFinished: boolean;
11 | css?: SxProps;
12 | }
13 |
14 | export function ProposalStatusWithDate({
15 | status,
16 | timestamp,
17 | isSecondary,
18 | waitForState,
19 | isFinished,
20 | css,
21 | }: ProposalStatusWithDateProps) {
22 | return (
23 |
26 | {waitForState && !isFinished ? (
27 |
34 | {waitForState}
35 |
36 | ) : (
37 | <>
38 |
43 |
46 | {dayjs.unix(timestamp).format('MMM D, YYYY')}
47 |
48 | >
49 | )}
50 |
51 | );
52 | }
53 |
--------------------------------------------------------------------------------
/src/ui/helpModals/getTestTransactions.ts:
--------------------------------------------------------------------------------
1 | import {
2 | TransactionStatus,
3 | TxAdapter,
4 | WalletType,
5 | } from '@bgd-labs/frontend-web3-utils';
6 | import { zeroAddress, zeroHash } from 'viem';
7 |
8 | import { TransactionUnion } from '../../transactions/store/transactionsSlice';
9 |
10 | export type TransactionItem = TransactionUnion & {
11 | status?: number | undefined;
12 | pending: boolean;
13 | walletType: WalletType;
14 | };
15 |
16 | export const generateStatus = () => {
17 | if (Math.round(Math.random()) > 0) {
18 | return TransactionStatus.Success;
19 | } else {
20 | return TransactionStatus.Reverted;
21 | }
22 | };
23 |
24 | export const makeTestTransaction = (
25 | timestamp: number,
26 | pending: boolean,
27 | status?: TransactionStatus,
28 | ) => {
29 | return {
30 | adapter: TxAdapter.Ethereum,
31 | txKey: zeroHash,
32 | type: 'test',
33 | chainId: 1,
34 | from: zeroAddress,
35 | hash: zeroHash,
36 | nonce: timestamp,
37 | pending: pending,
38 | to: zeroAddress,
39 | timestamp: timestamp,
40 | localTimestamp: timestamp,
41 | walletType: WalletType.Injected,
42 | status,
43 | } as TransactionItem;
44 | };
45 |
46 | export function getTestTransactionsPool() {
47 | const transactions = [...Array(5)].map((item, index) => {
48 | return makeTestTransaction(index, false, generateStatus());
49 | });
50 |
51 | const transactionsPool: Record = {};
52 | transactions.forEach((tx) => {
53 | transactionsPool[tx.localTimestamp] = tx;
54 | });
55 |
56 | return transactionsPool;
57 | }
58 |
--------------------------------------------------------------------------------
/src/ui/helpModals/HelpModalHomeButton.tsx:
--------------------------------------------------------------------------------
1 | import { Box, useTheme } from '@mui/system';
2 | import React from 'react';
3 |
4 | import { BoxWith3D } from '../components/BoxWith3D';
5 | import { setRelativePath } from '../utils/relativePath';
6 | import { texts } from '../utils/texts';
7 |
8 | export function HelpModalHomeButton() {
9 | const theme = useTheme();
10 |
11 | return (
12 |
26 |
44 |
51 | {texts.faq.other.mainMenu}
52 |
53 |
54 | );
55 | }
56 |
--------------------------------------------------------------------------------
/src/ui/components/Tooltip.tsx:
--------------------------------------------------------------------------------
1 | import { Box, SxProps } from '@mui/system';
2 | import { ReactNode } from 'react';
3 |
4 | interface TooltipProps {
5 | children: ReactNode;
6 | tooltipContent: ReactNode;
7 | color?: 'light' | 'dark';
8 | position?: 'top';
9 | tooltipCss?: SxProps;
10 | }
11 |
12 | export function Tooltip({
13 | children,
14 | tooltipContent,
15 | color = 'light',
16 | position,
17 | tooltipCss,
18 | }: TooltipProps) {
19 | return (
20 |
31 | {children}
32 |
33 |
52 | {tooltipContent}
53 |
54 |
55 | );
56 | }
57 |
--------------------------------------------------------------------------------
/src/proposals/components/proposal/RightPanelWrapper.tsx:
--------------------------------------------------------------------------------
1 | import { Box, useTheme } from '@mui/system';
2 | import { ReactNode } from 'react';
3 |
4 | import { BoxWith3D } from '../../../ui';
5 |
6 | interface RightPanelWrapperProps {
7 | children: ReactNode;
8 | onlyChildren?: boolean;
9 | }
10 |
11 | export function RightPanelWrapper({
12 | children,
13 | onlyChildren,
14 | }: RightPanelWrapperProps) {
15 | const theme = useTheme();
16 |
17 | return (
18 | <>
19 | {onlyChildren ? (
20 |
34 | {children}
35 |
36 | ) : (
37 |
55 | {children}
56 |
57 | )}
58 | >
59 | );
60 | }
61 |
--------------------------------------------------------------------------------
/src/transactions/components/TransactionsModal.tsx:
--------------------------------------------------------------------------------
1 | import { selectAllTransactionsByWallet } from '@bgd-labs/frontend-web3-utils';
2 | import React from 'react';
3 |
4 | import { useStore } from '../../store/ZustandStoreProvider';
5 | import { BasicModal } from '../../ui';
6 | import { TransactionUnion, TxType } from '../store/transactionsSlice';
7 | import { TransactionsModalContent } from './TransactionsModalContent';
8 |
9 | interface TransactionsModalProps {
10 | isOpen: boolean;
11 | setIsOpen: (value: boolean) => void;
12 | }
13 |
14 | export function TransactionsModal({
15 | isOpen,
16 | setIsOpen,
17 | }: TransactionsModalProps) {
18 | const setAccountInfoModalOpen = useStore(
19 | (store) => store.setAccountInfoModalOpen,
20 | );
21 | const activeWallet = useStore((store) => store.activeWallet);
22 | const transactionsPool = useStore((store) => store.transactionsPool);
23 |
24 | const allTransactions = activeWallet
25 | ? selectAllTransactionsByWallet(
26 | transactionsPool,
27 | activeWallet.address,
28 | )
29 | : [];
30 |
31 | return (
32 |
37 |
41 | !!Object.keys(TxType).find((key) => key === tx.type)?.length,
42 | )
43 | .sort((a, b) => b.localTimestamp - a.localTimestamp)}
44 | onBackButtonClick={() => {
45 | setIsOpen(false);
46 | setAccountInfoModalOpen(true);
47 | }}
48 | />
49 |
50 | );
51 | }
52 |
--------------------------------------------------------------------------------
/src/transactions/components/CopyErrorButton.tsx:
--------------------------------------------------------------------------------
1 | import { Box, useTheme } from '@mui/system';
2 | import React from 'react';
3 |
4 | import CopyIcon from '/public/images/icons/copy.svg';
5 |
6 | import { CopyToClipboard } from '../../ui';
7 | import { IconBox } from '../../ui/primitives/IconBox';
8 | import { texts } from '../../ui/utils/texts';
9 |
10 | interface CopyErrorButtonProps {
11 | errorMessage: Error | string;
12 | }
13 |
14 | export function CopyErrorButton({ errorMessage }: CopyErrorButtonProps) {
15 | const theme = useTheme();
16 |
17 | return (
18 |
19 |
36 | {texts.other.copyError}
37 | svg': {
43 | width: 9,
44 | height: 9,
45 | },
46 | ml: 4,
47 | path: {
48 | transition: 'all 0.2s ease',
49 | stroke: theme.palette.$textSecondary,
50 | },
51 | }}>
52 |
53 |
54 |
55 |
56 | );
57 | }
58 |
--------------------------------------------------------------------------------
/pages/proposal-create-overview.tsx:
--------------------------------------------------------------------------------
1 | import { useSearchParams } from 'next/navigation';
2 | // eslint-disable-next-line import/default
3 | import queryString from 'query-string';
4 | import React from 'react';
5 | import { Hex } from 'viem';
6 |
7 | import { ProposalCreateOverviewPage } from '../src/proposalCreateOverview/components/ProposalCreateOverviewPage';
8 | import {
9 | InitialParams,
10 | PayloadParams,
11 | } from '../src/proposalCreateOverview/types';
12 |
13 | export default function ProposalCreateOverview() {
14 | const searchParams = useSearchParams();
15 |
16 | if (!searchParams) return null;
17 |
18 | // params
19 | const proposalId = searchParams.get('proposalId')
20 | ? (Number(searchParams.get('proposalId')) as number)
21 | : undefined;
22 | const ipfsHash = searchParams.get('ipfsHash')
23 | ? (String(searchParams.get('ipfsHash')) as Hex)
24 | : undefined;
25 | const votingPortal = searchParams.get('votingPortal')
26 | ? (String(searchParams.get('votingPortal')) as Hex)
27 | : undefined;
28 |
29 | const payloads: Record = {};
30 | Object.entries(queryString.parse(searchParams.toString()))
31 | .filter((value) => value[0].startsWith('payload['))
32 | .forEach((value) => {
33 | const indexKey = Number(value[0].split('[')[1].split(']')[0]);
34 | const paramKey = value[0].split('.')[1];
35 |
36 | payloads[indexKey] = {
37 | ...payloads[indexKey],
38 | [paramKey]: value[1],
39 | };
40 | });
41 |
42 | const initialParams: InitialParams = {
43 | proposalId,
44 | ipfsHash,
45 | votingPortal,
46 | payloads: Object.values(payloads),
47 | };
48 |
49 | return ;
50 | }
51 |
--------------------------------------------------------------------------------
/src/proposals/components/actionModals/ActivateVotingOnVotingMachineModal.tsx:
--------------------------------------------------------------------------------
1 | import { Box } from '@mui/system';
2 | import React from 'react';
3 |
4 | import { useStore } from '../../../store/ZustandStoreProvider';
5 | import { ActionModal } from '../../../transactions/components/ActionModal';
6 | import { TxType } from '../../../transactions/store/transactionsSlice';
7 | import { getChainName } from '../../../ui/utils/getChainName';
8 | import { texts } from '../../../ui/utils/texts';
9 | import {
10 | ActionModalContentWrapper,
11 | ActionModalTitle,
12 | } from './ActionModalContentWrapper';
13 | import { ActionModalBasicTypes } from './types';
14 |
15 | export function ActivateVotingOnVotingMachineModal({
16 | votingChainId,
17 | isOpen,
18 | setIsOpen,
19 | proposalId,
20 | }: { votingChainId: number } & ActionModalBasicTypes) {
21 | const activateVotingOnVotingMachine = useStore(
22 | (state) => state.activateVotingOnVotingMachine,
23 | );
24 |
25 | return (
26 |
30 | await activateVotingOnVotingMachine(votingChainId, proposalId)
31 | }
32 | isOpen={isOpen}
33 | setIsOpen={setIsOpen}
34 | actionButtonTitle={texts.other.confirm}
35 | topBlock={
36 |
37 | }
38 | withCancelButton>
39 |
40 |
41 | {texts.proposalActions.activateVotingDescription}{' '}
42 | {getChainName(votingChainId)} chain
43 |
44 |
45 |
46 | );
47 | }
48 |
--------------------------------------------------------------------------------
/src/utils/getProposalMetadata.ts:
--------------------------------------------------------------------------------
1 | import {
2 | getProposalMetadata as baseGetProposalMetadata,
3 | ProposalMetadata,
4 | } from '@bgd-labs/aave-governance-ui-helpers';
5 | import matter from 'gray-matter';
6 |
7 | import { texts } from '../ui/utils/texts';
8 | import { cachedIPFSDataPath, githubStartUrl } from './cacheGithubLinks';
9 | import { fallbackGateways, ipfsGateway } from './configs';
10 |
11 | export async function getProposalMetadata({
12 | hash,
13 | setIpfsError,
14 | }: {
15 | hash: string;
16 | setIpfsError?: (ipfsHash: string, text?: string, remove?: boolean) => void;
17 | }): Promise {
18 | try {
19 | const request = await fetch(`${githubStartUrl}${cachedIPFSDataPath(hash)}`);
20 | if (request.ok) {
21 | const response = await request.json();
22 | const { content, data } = matter(response.description);
23 | return {
24 | ...response,
25 | ipfsHash: hash,
26 | description: content,
27 | ...data,
28 | };
29 | } else {
30 | console.error(
31 | "Can't fetch cached ipfs data. Try to fetch from IPFS gateway",
32 | );
33 | return await baseGetProposalMetadata({
34 | hash,
35 | gateway: ipfsGateway,
36 | setIpfsError,
37 | errorText: texts.other.fetchFromIpfsError,
38 | fallbackGateways,
39 | });
40 | }
41 | } catch (e) {
42 | console.error(
43 | 'An error occurred while fetching proposal metadata from IPFS, trying to request one more time.',
44 | );
45 | return await baseGetProposalMetadata({
46 | hash,
47 | gateway: ipfsGateway,
48 | setIpfsError,
49 | errorText: texts.other.fetchFromIpfsError,
50 | fallbackGateways,
51 | });
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/transactions/components/BasicActionModal.tsx:
--------------------------------------------------------------------------------
1 | import { SxProps } from '@mui/system';
2 | import React, { useEffect } from 'react';
3 |
4 | import { BasicModal } from '../../ui';
5 | import {
6 | ActionModalContent,
7 | ActionModalContentProps,
8 | } from './ActionModalContent';
9 |
10 | export interface BasicActionModalProps extends ActionModalContentProps {
11 | isOpen: boolean;
12 | setFullTxErrorMessage: (value: string) => void;
13 | withMinHeight?: boolean;
14 | minHeight?: number;
15 | contentCss?: SxProps;
16 | }
17 |
18 | export function BasicActionModal({
19 | isOpen,
20 | setIsOpen,
21 | topBlock,
22 | contentMinHeight,
23 | children,
24 | isTxStart,
25 | setIsTxStart,
26 | error,
27 | setError,
28 | successElement,
29 | withoutTryAgainWhenError,
30 | fullTxErrorMessage,
31 | setFullTxErrorMessage,
32 | tx,
33 | withMinHeight,
34 | minHeight,
35 | contentCss,
36 | }: BasicActionModalProps) {
37 | useEffect(() => {
38 | setIsTxStart(false);
39 | setError('');
40 | setFullTxErrorMessage('');
41 | }, [isOpen]);
42 |
43 | return (
44 |
51 |
63 | {children}
64 |
65 |
66 | );
67 | }
68 |
--------------------------------------------------------------------------------
/src/proposals/components/actionModals/CreatePayloadModal.tsx:
--------------------------------------------------------------------------------
1 | import { Box } from '@mui/system';
2 | import React from 'react';
3 |
4 | import {
5 | BasicActionModal,
6 | BasicActionModalProps,
7 | } from '../../../transactions/components/BasicActionModal';
8 | import { texts } from '../../../ui/utils/texts';
9 | import {
10 | ActionModalContentWrapper,
11 | ActionModalTitle,
12 | } from './ActionModalContentWrapper';
13 |
14 | export function CreatePayloadModal({
15 | isOpen,
16 | setIsOpen,
17 | isTxStart,
18 | tx,
19 | setIsTxStart,
20 | setError,
21 | error,
22 | payloadId,
23 | fullTxErrorMessage,
24 | setFullTxErrorMessage,
25 | }: Pick<
26 | BasicActionModalProps,
27 | | 'isOpen'
28 | | 'setIsOpen'
29 | | 'isTxStart'
30 | | 'setIsTxStart'
31 | | 'setError'
32 | | 'error'
33 | | 'fullTxErrorMessage'
34 | | 'setFullTxErrorMessage'
35 | | 'tx'
36 | > & { payloadId: number }) {
37 | return (
38 |
52 | {texts.proposalActions.createPayloadSuccess(payloadId)}
53 |
54 | }
55 | topBlock={
56 |
57 | }>
58 |
59 |
60 | {texts.proposalActions.createPayloadDescription(payloadId)}
61 |
62 |
63 |
64 | );
65 | }
66 |
--------------------------------------------------------------------------------
/src/proposals/components/actionModals/ExecutePayloadModal.tsx:
--------------------------------------------------------------------------------
1 | import { InitialPayload } from '@bgd-labs/aave-governance-ui-helpers';
2 | import { Box } from '@mui/system';
3 | import React from 'react';
4 |
5 | import { useStore } from '../../../store/ZustandStoreProvider';
6 | import { ActionModal } from '../../../transactions/components/ActionModal';
7 | import { TxType } from '../../../transactions/store/transactionsSlice';
8 | import { texts } from '../../../ui/utils/texts';
9 | import {
10 | ActionModalContentWrapper,
11 | ActionModalTitle,
12 | } from './ActionModalContentWrapper';
13 | import { ActionModalBasicTypes } from './types';
14 |
15 | export function ExecutePayloadModal({
16 | isOpen,
17 | setIsOpen,
18 | proposalId,
19 | payload,
20 | withController,
21 | }: ActionModalBasicTypes & {
22 | payload: InitialPayload;
23 | withController?: boolean;
24 | }) {
25 | const executePayload = useStore((state) => state.executePayload);
26 |
27 | return (
28 |
39 | await executePayload(proposalId, payload, withController)
40 | }
41 | isOpen={isOpen}
42 | setIsOpen={setIsOpen}
43 | actionButtonTitle={texts.other.confirm}
44 | topBlock={
45 |
46 | }
47 | withCancelButton>
48 |
49 |
50 | {texts.proposalActions.executePayloadDescription}
51 |
52 |
53 |
54 | );
55 | }
56 |
--------------------------------------------------------------------------------
/src/web3/components/wallet/WalletItem.tsx:
--------------------------------------------------------------------------------
1 | import { WalletType } from '@bgd-labs/frontend-web3-utils';
2 | import { Box } from '@mui/system';
3 | import React from 'react';
4 |
5 | import { useStore } from '../../../store/ZustandStoreProvider';
6 | import { BoxWith3D } from '../../../ui';
7 | import WalletIcon from '../../../ui/components/Web3Icons/WalletIcon';
8 |
9 | export type Wallet = {
10 | walletType: WalletType;
11 | walletName: string;
12 | onClick?: () => void;
13 | isVisible?: boolean;
14 | setOpenImpersonatedForm?: (value: boolean) => void;
15 | };
16 |
17 | export function WalletItem({
18 | walletType,
19 | walletName,
20 | onClick,
21 | setOpenImpersonatedForm,
22 | }: Wallet) {
23 | const connectWallet = useStore((state) => state.connectWallet);
24 |
25 | const iconSize = 28;
26 |
27 | const handleWalletClick = async () => {
28 | if (walletType === WalletType.Impersonated && setOpenImpersonatedForm) {
29 | setOpenImpersonatedForm(true);
30 | } else {
31 | await connectWallet(walletType);
32 | }
33 | };
34 |
35 | return (
36 |
41 |
56 |
57 | {walletName}
58 |
59 |
60 |
61 |
62 | );
63 | }
64 |
--------------------------------------------------------------------------------
/src/proposals/components/actionModals/CreateProposalModal.tsx:
--------------------------------------------------------------------------------
1 | import { Box } from '@mui/system';
2 | import React from 'react';
3 |
4 | import {
5 | BasicActionModal,
6 | BasicActionModalProps,
7 | } from '../../../transactions/components/BasicActionModal';
8 | import { texts } from '../../../ui/utils/texts';
9 | import {
10 | ActionModalContentWrapper,
11 | ActionModalTitle,
12 | } from './ActionModalContentWrapper';
13 |
14 | export function CreateProposalModal({
15 | isOpen,
16 | setIsOpen,
17 | isTxStart,
18 | setIsTxStart,
19 | setError,
20 | error,
21 | proposalId,
22 | fullTxErrorMessage,
23 | setFullTxErrorMessage,
24 | tx,
25 | }: Pick<
26 | BasicActionModalProps,
27 | | 'isOpen'
28 | | 'setIsOpen'
29 | | 'isTxStart'
30 | | 'setIsTxStart'
31 | | 'setError'
32 | | 'error'
33 | | 'fullTxErrorMessage'
34 | | 'setFullTxErrorMessage'
35 | | 'tx'
36 | > & { proposalId: number }) {
37 | return (
38 |
52 | {texts.proposalActions.createProposalSuccess(proposalId)}
53 |
54 | }
55 | topBlock={
56 |
57 | }>
58 |
59 |
60 | {texts.proposalActions.createProposalDescription(proposalId)}
61 |
62 |
63 |
64 | );
65 | }
66 |
--------------------------------------------------------------------------------
/src/ui/utils/ThemeRegistry.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | // eslint-disable-next-line import/default
4 | import createCache from '@emotion/cache';
5 | import { useServerInsertedHTML } from 'next/navigation';
6 | import React from 'react';
7 |
8 | import { AppGlobalStyles } from '../layouts/AppGlobalStyles';
9 | import { GlobalStyles } from './GlobalStyles';
10 |
11 | // This implementation is from emotion-js
12 | // https://github.com/emotion-js/emotion/issues/2928#issuecomment-1319747902
13 | export default function ThemeRegistry(props: any) {
14 | const { options, children } = props;
15 |
16 | const [{ cache, flush }] = React.useState(() => {
17 | const cache = createCache(options);
18 | cache.compat = true;
19 | const prevInsert = cache.insert;
20 | let inserted: string[] = [];
21 | cache.insert = (...args) => {
22 | const serialized = args[1];
23 | if (cache.inserted[serialized.name] === undefined) {
24 | inserted.push(serialized.name);
25 | }
26 | return prevInsert(...args);
27 | };
28 | const flush = () => {
29 | const prevInserted = inserted;
30 | inserted = [];
31 | return prevInserted;
32 | };
33 | return { cache, flush };
34 | });
35 |
36 | useServerInsertedHTML(() => {
37 | const names = flush();
38 | if (names.length === 0) {
39 | return null;
40 | }
41 | let styles = '';
42 | for (const name of names) {
43 | styles += cache.inserted[name];
44 | }
45 | return (
46 | <>
47 |
48 |
55 | >
56 | );
57 | });
58 |
59 | return {children};
60 | }
61 |
--------------------------------------------------------------------------------
/src/web3/components/representation/RepresentingButtonChainsIcon.tsx:
--------------------------------------------------------------------------------
1 | import { Box, useTheme } from '@mui/system';
2 | import React from 'react';
3 |
4 | import MultichainIcon from '/public/images/representation/multichain.svg';
5 |
6 | import { NetworkIcon } from '../../../ui/components/NetworkIcon';
7 | import { IconBox } from '../../../ui/primitives/IconBox';
8 |
9 | interface RepresentingButtonChainsIconProps {
10 | chains: number[];
11 | }
12 |
13 | export function RepresentingButtonChainsIcon({
14 | chains,
15 | }: RepresentingButtonChainsIconProps) {
16 | const theme = useTheme();
17 |
18 | return (
19 |
37 | {chains.length > 1 ? (
38 | svg': {
53 | width: 10,
54 | height: 10,
55 | [theme.breakpoints.up('lg')]: {
56 | width: 14,
57 | height: 14,
58 | },
59 | },
60 | }}>
61 |
62 |
63 | ) : (
64 |
65 | )}
66 |
67 | );
68 | }
69 |
--------------------------------------------------------------------------------
/next.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('next').NextConfig} */
2 | const isForIPFS = process.env.NEXT_PUBLIC_DEPLOY_FOR_IPFS === 'true';
3 | const withBundleAnalyzer = require('@next/bundle-analyzer')({
4 | enabled: process.env.ANALYZE === 'true',
5 | });
6 |
7 | const nextConfig = {
8 | experimental: {
9 | webpackBuildWorker: true,
10 | },
11 | webpack(config) {
12 | // config.resolve.fallback = { fs: false, path: false };
13 |
14 | // Grab the existing rule that handles SVG imports
15 | const fileLoaderRule = config.module.rules.find((rule) =>
16 | rule.test?.test?.('.svg'),
17 | );
18 |
19 | config.module.rules.push(
20 | // Reapply the existing rule, but only for svg imports ending in ?url
21 | {
22 | ...fileLoaderRule,
23 | test: /\.svg$/i,
24 | resourceQuery: /url/, // *.svg?url
25 | },
26 | // Convert all other *.svg imports to React components
27 | {
28 | test: /\.svg$/i,
29 | issuer: /\.[jt]sx?$/,
30 | resourceQuery: { not: /url/ }, // exclude if *.svg?url
31 | use: ['@svgr/webpack'],
32 | },
33 | );
34 |
35 | // Modify the file loader rule to ignore *.svg, since we have it handled now.
36 | fileLoaderRule.exclude = /\.svg$/i;
37 |
38 | config.experiments = { topLevelAwait: true, layers: true };
39 |
40 | return config;
41 | },
42 | reactStrictMode: true,
43 | trailingSlash: true,
44 | };
45 |
46 | module.exports = withBundleAnalyzer(
47 | isForIPFS
48 | ? {
49 | ...nextConfig,
50 | output: 'export',
51 | images: {
52 | unoptimized: true,
53 | },
54 | // assetPrefix: './',
55 | }
56 | : {
57 | ...nextConfig,
58 | pageExtensions: [
59 | 'page.tsx',
60 | 'page.ts',
61 | 'page.jsx',
62 | 'page.js',
63 | 'page.md',
64 | 'page.mdx',
65 | ],
66 | },
67 | );
68 |
--------------------------------------------------------------------------------
/src/transactions/components/TransactionsModalContent.tsx:
--------------------------------------------------------------------------------
1 | import { Box } from '@mui/system';
2 | import React from 'react';
3 |
4 | import { BackButton3D, Divider } from '../../ui';
5 | import { texts } from '../../ui/utils/texts';
6 | import { AllTransactions } from '../store/transactionsSlice';
7 | import { TransactionInfoItem } from './TransactionInfoItem';
8 |
9 | interface TransactionsModalContentProps {
10 | allTransactions: AllTransactions;
11 | onBackButtonClick: () => void;
12 | forTest?: boolean;
13 | }
14 |
15 | export function TransactionsModalContent({
16 | allTransactions,
17 | onBackButtonClick,
18 | forTest,
19 | }: TransactionsModalContentProps) {
20 | return (
21 |
22 |
23 | {texts.transactions.allTransactions}
24 |
25 |
26 | ({
28 | my: 14,
29 | borderBottomColor: theme.palette.$secondaryBorder,
30 | width: '100%',
31 | })}
32 | />
33 |
34 | ({
36 | overflowY: forTest ? 'scroll' : 'unset',
37 | pr: forTest ? 20 : 0,
38 | height: forTest ? 191 : '100%',
39 | [theme.breakpoints.up('sm')]: {
40 | overflowY: 'scroll',
41 | pr: 20,
42 | height: forTest ? 128 : 510,
43 | },
44 | [theme.breakpoints.up('lg')]: {
45 | height: forTest ? 139 : 580,
46 | },
47 | })}>
48 | {allTransactions.map((tx, index) => (
49 |
50 | ))}
51 |
52 |
53 |
54 |
61 |
62 |
63 | );
64 | }
65 |
--------------------------------------------------------------------------------
/src/ui/layouts/AppLayout.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import 'react-loading-skeleton/dist/skeleton.css';
4 | import 'nprogress/nprogress.css';
5 |
6 | import Router from 'next/router';
7 | import NProgress from 'nprogress';
8 | import React, { useEffect } from 'react';
9 |
10 | import { RepresentationInfoModal } from '../../representations/components/RepresentationInfoModal';
11 | import { useStore } from '../../store/ZustandStoreProvider';
12 | import { isForIPFS, isTermsAndConditionsVisible } from '../../utils/appConfig';
13 | import Web3HelperProvider from '../../web3/providers/Web3HelperProvider';
14 | import { TermsAndConditionsModal } from '../components/TermsAndConditionsModal';
15 | import { TermsPreAppModal } from '../components/TermsPreAppModal';
16 | import { HelpModalProvider } from '../helpModals/HelpModalProvider';
17 | import { MainLayout } from './MainLayout';
18 |
19 | export default function AppLayout({ children }: { children: React.ReactNode }) {
20 | const checkIsAppBlockedByTerms = useStore(
21 | (store) => store.checkIsAppBlockedByTerms,
22 | );
23 | const checkTutorialStartButtonClick = useStore(
24 | (store) => store.checkTutorialStartButtonClick,
25 | );
26 |
27 | useEffect(() => {
28 | checkTutorialStartButtonClick();
29 | checkIsAppBlockedByTerms();
30 | }, []);
31 |
32 | if (isForIPFS) {
33 | Router.events.on('routeChangeStart', () => NProgress.start());
34 | Router.events.on('routeChangeComplete', () => NProgress.done());
35 | Router.events.on('routeChangeError', () => NProgress.done());
36 | }
37 |
38 | return (
39 | <>
40 |
41 |
42 | {children}
43 |
44 |
45 |
46 |
47 | {!isForIPFS && isTermsAndConditionsVisible && (
48 | <>
49 |
50 |
51 | >
52 | )}
53 | >
54 | );
55 | }
56 |
--------------------------------------------------------------------------------
/app/proposal-create-overview/page.page.tsx:
--------------------------------------------------------------------------------
1 | import { Metadata } from 'next';
2 | import React from 'react';
3 | import { Hex } from 'viem';
4 |
5 | import { ProposalCreateOverviewPage } from '../../src/proposalCreateOverview/components/ProposalCreateOverviewPage';
6 | import {
7 | InitialParams,
8 | PayloadParams,
9 | } from '../../src/proposalCreateOverview/types';
10 | import { metaTexts } from '../../src/ui/utils/metaTexts';
11 |
12 | export const metadata: Metadata = {
13 | title: metaTexts.ipfsTitle,
14 | description: metaTexts.ipfsDescription,
15 | openGraph: {
16 | images: ['/metaLogo.jpg'],
17 | title: metaTexts.ipfsTitle,
18 | description: metaTexts.ipfsDescription,
19 | },
20 | };
21 |
22 | export default async function ProposalCreateOverview({
23 | searchParams,
24 | }: {
25 | searchParams: { [key: string]: string | string[] | undefined };
26 | }) {
27 | // params
28 | const proposalId = searchParams['proposalId']
29 | ? (Number(searchParams['proposalId']) as number)
30 | : undefined;
31 | const ipfsHash = searchParams['ipfsHash']
32 | ? (String(searchParams['ipfsHash']) as Hex)
33 | : undefined;
34 | const votingPortal = searchParams['votingPortal']
35 | ? (String(searchParams['votingPortal']) as Hex)
36 | : undefined;
37 |
38 | const payloads: Record = {};
39 | Object.entries(searchParams)
40 | .filter((value) => value[0].startsWith('payload['))
41 | .forEach((value) => {
42 | const indexKey = Number(value[0].split('[')[1].split(']')[0]);
43 | const paramKey = value[0].split('.')[1];
44 |
45 | payloads[indexKey] = {
46 | ...payloads[indexKey],
47 | [paramKey]: value[1],
48 | };
49 | });
50 |
51 | const initialParams: InitialParams = {
52 | proposalId,
53 | ipfsHash,
54 | votingPortal,
55 | payloads: Object.values(payloads),
56 | };
57 |
58 | return ;
59 | }
60 |
--------------------------------------------------------------------------------
/public/images/icons/web.svg:
--------------------------------------------------------------------------------
1 |
11 |
--------------------------------------------------------------------------------
/src/proposals/components/CreateFieldArrayWrapper.tsx:
--------------------------------------------------------------------------------
1 | import { Box } from '@mui/system';
2 | import { ReactNode } from 'react';
3 |
4 | import CloseIcon from '/public/images/icons/cross.svg';
5 |
6 | import { BoxWith3D } from '../../ui';
7 | import { IconBox } from '../../ui/primitives/IconBox';
8 |
9 | interface CreateFieldArrayWrapperProps {
10 | fieldTitle: string;
11 | onRemoveClick: () => void;
12 | children: ReactNode;
13 | }
14 |
15 | export function CreateFieldArrayWrapper({
16 | fieldTitle,
17 | onRemoveClick,
18 | children,
19 | }: CreateFieldArrayWrapperProps) {
20 | return (
21 |
31 |
38 |
39 | {fieldTitle}
40 |
41 |
52 | svg': {
57 | width: 13,
58 | height: 13,
59 | },
60 | path: (theme) => ({
61 | stroke: theme.palette.$main,
62 | fill: theme.palette.$main,
63 | }),
64 | }}>
65 |
66 |
67 |
68 |
69 |
70 | {children}
71 |
72 | );
73 | }
74 |
--------------------------------------------------------------------------------
/src/ui/helpModals/HelpModalContainer.tsx:
--------------------------------------------------------------------------------
1 | import { Box, useTheme } from '@mui/system';
2 | import React, { ReactNode } from 'react';
3 |
4 | import { HelpModalHomeButton } from './HelpModalHomeButton';
5 |
6 | interface HelpModalContainerProps {
7 | children: ReactNode;
8 | onMainButtonClick?: () => void;
9 | }
10 |
11 | export const helpModalWidth = 1210;
12 |
13 | export function HelpModalContainer({
14 | children,
15 | onMainButtonClick,
16 | }: HelpModalContainerProps) {
17 | const theme = useTheme();
18 |
19 | return (
20 |
35 |
47 | {children}
48 |
49 |
50 | {!!onMainButtonClick && (
51 |
66 |
67 |
68 | )}
69 |
70 | );
71 | }
72 |
--------------------------------------------------------------------------------
/src/representations/components/RepresentationsModal.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import {
4 | ActionModalContentWrapper,
5 | ActionModalTitle,
6 | } from '../../proposals/components/actionModals/ActionModalContentWrapper';
7 | import {
8 | BasicActionModal,
9 | BasicActionModalProps,
10 | } from '../../transactions/components/BasicActionModal';
11 | import { texts } from '../../ui/utils/texts';
12 | import { RepresentationFormData } from '../store/representationsSlice';
13 | import { TxText } from './TxText';
14 |
15 | export function RepresentationsModal({
16 | isOpen,
17 | setIsOpen,
18 | isTxStart,
19 | setIsTxStart,
20 | setError,
21 | error,
22 | formData,
23 | initialData,
24 | fullTxErrorMessage,
25 | setFullTxErrorMessage,
26 | tx,
27 | }: Pick<
28 | BasicActionModalProps,
29 | | 'isOpen'
30 | | 'setIsOpen'
31 | | 'isTxStart'
32 | | 'setIsTxStart'
33 | | 'setError'
34 | | 'error'
35 | | 'fullTxErrorMessage'
36 | | 'setFullTxErrorMessage'
37 | | 'tx'
38 | > & {
39 | initialData: RepresentationFormData[];
40 | formData: RepresentationFormData[];
41 | }) {
42 | return (
43 | }
56 | topBlock={
57 |
64 | }>
65 |
66 |
67 |
68 |
69 | );
70 | }
71 |
--------------------------------------------------------------------------------
/src/proposals/components/actionModals/SendProofsModal.tsx:
--------------------------------------------------------------------------------
1 | import { Box } from '@mui/system';
2 | import React from 'react';
3 |
4 | import { useStore } from '../../../store/ZustandStoreProvider';
5 | import { ActionModal } from '../../../transactions/components/ActionModal';
6 | import { TxType } from '../../../transactions/store/transactionsSlice';
7 | import { texts } from '../../../ui/utils/texts';
8 | import { getAssetName } from '../../../utils/getAssetName';
9 | import {
10 | ActionModalContentWrapper,
11 | ActionModalTitle,
12 | } from './ActionModalContentWrapper';
13 | import { ActionModalBasicTypes } from './types';
14 |
15 | interface SendProofsModalProps extends ActionModalBasicTypes {
16 | blockHash: string;
17 | underlyingAsset: string;
18 | votingChainId: number;
19 | baseBalanceSlotRaw: number;
20 | withSlot?: boolean;
21 | }
22 |
23 | export function SendProofsModal({
24 | isOpen,
25 | setIsOpen,
26 | proposalId,
27 | votingChainId,
28 | blockHash,
29 | underlyingAsset,
30 | baseBalanceSlotRaw,
31 | withSlot,
32 | }: SendProofsModalProps) {
33 | const sendProofs = useStore((state) => state.sendProofs);
34 |
35 | return (
36 |
40 | await sendProofs(
41 | votingChainId,
42 | proposalId,
43 | underlyingAsset,
44 | baseBalanceSlotRaw,
45 | withSlot,
46 | )
47 | }
48 | isOpen={isOpen}
49 | setIsOpen={setIsOpen}
50 | actionButtonTitle={texts.other.confirm}
51 | topBlock={
52 |
57 | }
58 | withCancelButton>
59 |
60 |
61 | {withSlot ? 'Slot' : 'Root'} will be send
62 |
63 |
64 |
65 | );
66 | }
67 |
--------------------------------------------------------------------------------
/src/rpcSwitcher/components/RpcSwitcherTableWrapper.tsx:
--------------------------------------------------------------------------------
1 | import { Box } from '@mui/system';
2 | import React, { ReactNode } from 'react';
3 | import { FieldArray } from 'react-final-form-arrays';
4 |
5 | import {
6 | TableContainer,
7 | TableContainerChildren,
8 | } from '../../ui/components/TableContainer';
9 | import {
10 | AppClientsStorage,
11 | RpcSwitcherFormData,
12 | } from '../store/rpcSwitcherSlice';
13 | import { RpcSwitcherTable } from './RpcSwitcherTable';
14 |
15 | interface RpcSwitcherTableWrapperProps {
16 | loading: boolean;
17 | rpcSwitcherData: Record;
18 | isEdit: boolean;
19 | isViewChanges: boolean;
20 | fields?: any;
21 | formData?: RpcSwitcherFormData;
22 | children: ReactNode;
23 | handleFormSubmit?: (data: any) => void;
24 | }
25 |
26 | export function RpcSwitcherTableWrapper({
27 | rpcSwitcherData,
28 | loading,
29 | isEdit,
30 | isViewChanges,
31 | fields,
32 | children,
33 | formData,
34 | handleFormSubmit,
35 | }: RpcSwitcherTableWrapperProps) {
36 | return (
37 |
38 | {typeof handleFormSubmit === 'function' ? (
39 |
40 |
41 | {({ fields }) => (
42 |
49 | )}
50 |
51 | {children}
52 |
53 | ) : (
54 | <>
55 |
63 | {children}
64 | >
65 | )}
66 |
67 | );
68 | }
69 |
--------------------------------------------------------------------------------
/.github/workflows/ipfs_deploy.yml:
--------------------------------------------------------------------------------
1 | name: Deploy to ipfs
2 |
3 | concurrency:
4 | group: ${{ github.workflow }}-${{ github.head_ref || github.ref }}
5 | cancel-in-progress: true
6 |
7 | on:
8 | push:
9 | branches: ['main']
10 |
11 | env:
12 | NEXT_PUBLIC_DEPLOY_FOR_IPFS: true
13 |
14 | jobs:
15 | ipfs_deploy:
16 | runs-on: ubuntu-latest
17 | environment:
18 | name: 'IPFS'
19 | url: https://${{ steps.pinata.outputs.hash }}.ipfs.dweb.link/
20 | outputs:
21 | pinata_hash: '${{ steps.pinata.outputs.hash }}'
22 | steps:
23 | - uses: actions/checkout@v3
24 | with:
25 | token: ${{ secrets.GITHUB_TOKEN }}
26 |
27 | - uses: actions/setup-node@v3
28 | with:
29 | node-version-file: '.nvmrc'
30 | cache: 'yarn'
31 |
32 | - name: Packages install
33 | shell: sh
34 | run: yarn --frozen-lockfile --prefer-offline
35 |
36 | - name: Build next js app
37 | run: yarn build
38 |
39 | - name: Deploy to ipfs
40 | id: pinata
41 | uses: aave/pinata-action@a3409e26f4cb859a2d9984109317caac53db5f68
42 | with:
43 | PINATA_API_KEY: ${{ secrets.PINATA_API_KEY }}
44 | PINATA_SECRET_KEY: ${{ secrets.PINATA_SECRET_KEY }}
45 | PIN_ALIAS: 'app-aave-governance-v3-${{ github.head_ref || github.ref }}'
46 | BUILD_LOCATION: './out'
47 | CID_VERSION: 1
48 |
49 | - name: Amend ipfs link to release
50 | uses: actions/github-script@v6
51 | if: contains(github.event.head_commit.message, 'chore(main):')
52 | with:
53 | retries: 3
54 | script: |
55 | const { data } = await github.rest.repos.getLatestRelease({
56 | owner: context.repo.owner,
57 | repo: context.repo.repo,
58 | })
59 |
60 | github.rest.repos.updateRelease({
61 | owner: context.repo.owner,
62 | repo: context.repo.repo,
63 | release_id: data.id,
64 | body: data.body + `\n
\n Ipfs deployment: ` + `${{steps.pinata.outputs.uri}}`,
65 | })
66 |
--------------------------------------------------------------------------------
/public/images/icons/info.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/ui/helpModals/HelpRepresentationsTx.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react';
2 |
3 | import {
4 | ActionModalContentWrapper,
5 | ActionModalTitle,
6 | } from '../../proposals/components/actionModals/ActionModalContentWrapper';
7 | import { TxText } from '../../representations/components/TxText';
8 | import { RepresentationFormData } from '../../representations/store/representationsSlice';
9 | import { ActionModalContent } from '../../transactions/components/ActionModalContent';
10 | import { texts } from '../utils/texts';
11 | import { getTestTx } from './getTestTx';
12 |
13 | interface HelpRepresentationsTxProps {
14 | txPending: boolean;
15 | txSuccess: boolean;
16 | initialData: RepresentationFormData[];
17 | formData: RepresentationFormData[];
18 | handleCancelClick: () => void;
19 | }
20 |
21 | export function HelpRepresentationsTx({
22 | txPending,
23 | txSuccess,
24 | formData,
25 | initialData,
26 | handleCancelClick,
27 | }: HelpRepresentationsTxProps) {
28 | const [error, setError] = useState('');
29 | const [isTxStart, setIsTxStart] = useState(false);
30 |
31 | useEffect(() => {
32 | if (txPending) {
33 | setIsTxStart(true);
34 | }
35 | }, [txPending]);
36 |
37 | return (
38 | <>
39 |
49 | }
50 | topBlock={
51 |
58 | }>
59 |
60 |
61 |
62 |
63 | >
64 | );
65 | }
66 |
--------------------------------------------------------------------------------
/src/web3/utils/assetsBalanceSlots.ts:
--------------------------------------------------------------------------------
1 | import {
2 | Asset,
3 | AssetsBalanceSlots,
4 | Balance,
5 | baseSlots,
6 | getVoteBalanceSlot,
7 | } from '@bgd-labs/aave-governance-ui-helpers';
8 | import { Address } from 'viem';
9 |
10 | import { appConfig } from '../../utils/appConfig';
11 |
12 | export const assetsBalanceSlots: AssetsBalanceSlots = {
13 | [appConfig.additional.stkAAVEAddress.toLowerCase()]: {
14 | ...baseSlots[Asset.STKAAVE],
15 | },
16 | [appConfig.additional.aAaveAddress.toLowerCase()]: {
17 | ...baseSlots[Asset.AAAVE],
18 | },
19 | [appConfig.additional.aaveAddress.toLowerCase()]: {
20 | ...baseSlots[Asset.AAVE],
21 | },
22 | [appConfig.govCoreConfig.contractAddress.toLowerCase()]: {
23 | ...baseSlots[Asset.GOVCORE],
24 | },
25 | };
26 |
27 | export function formatBalances(balances: Balance[], aAaveAddress: Address) {
28 | let formattedBalances = balances;
29 | const aAAVEBalance = balances.find(
30 | (balance) => balance.underlyingAsset === aAaveAddress,
31 | );
32 |
33 | const isAAAVEBalanceWithDelegation =
34 | aAAVEBalance?.isWithDelegatedPower || false;
35 |
36 | if (aAAVEBalance) {
37 | if (isAAAVEBalanceWithDelegation) {
38 | const isUserAAAVEBalance = aAAVEBalance.userBalance !== '0';
39 | if (isUserAAAVEBalance) {
40 | formattedBalances = [
41 | ...balances,
42 | {
43 | ...aAAVEBalance,
44 | isWithDelegatedPower: false,
45 | },
46 | ];
47 | }
48 | }
49 | }
50 | return formattedBalances;
51 | }
52 |
53 | export function getVotingAssetsWithSlot({
54 | balances,
55 | aAaveAddress,
56 | slots,
57 | }: {
58 | balances: Balance[];
59 | aAaveAddress: Address;
60 | slots: AssetsBalanceSlots;
61 | }) {
62 | return balances
63 | .filter((balance) => balance.value !== '0')
64 | .map((balance) => {
65 | return {
66 | underlyingAsset: balance.underlyingAsset,
67 | slot: getVoteBalanceSlot(
68 | balance.underlyingAsset,
69 | balance.isWithDelegatedPower,
70 | aAaveAddress,
71 | slots,
72 | ),
73 | };
74 | });
75 | }
76 |
--------------------------------------------------------------------------------
/src/ui/helpModals/HelpModalCaption.tsx:
--------------------------------------------------------------------------------
1 | import { Box, useTheme } from '@mui/system';
2 | import { ReactNode } from 'react';
3 |
4 | interface HelpModalCaptionProps {
5 | caption: string;
6 | image: ReactNode;
7 | children: ReactNode;
8 | withoutMargin?: boolean;
9 | }
10 |
11 | export function HelpModalCaption({
12 | caption,
13 | image,
14 | children,
15 | withoutMargin,
16 | }: HelpModalCaptionProps) {
17 | const theme = useTheme();
18 |
19 | return (
20 |
36 |
43 | {caption}
44 |
45 |
46 |
51 | {image}
52 |
53 |
54 |
55 |
66 | {caption}
67 |
68 |
74 | {children}
75 |
76 |
77 |
78 | );
79 | }
80 |
--------------------------------------------------------------------------------
/src/web3/components/powers/PowersInfoModal.tsx:
--------------------------------------------------------------------------------
1 | import { Box } from '@mui/system';
2 | import React from 'react';
3 |
4 | import { useStore } from '../../../store/ZustandStoreProvider';
5 | import { BackButton3D, BasicModal } from '../../../ui';
6 | import { GovernancePowerType } from '../../services/delegationService';
7 | import {
8 | selectCurrentPowers,
9 | selectCurrentPowersForActiveWallet,
10 | } from '../../store/web3Selectors';
11 | import { PowersModalItem } from './PowersModalItem';
12 |
13 | interface PowersInfoModalProps {
14 | isOpen: boolean;
15 | setIsOpen: (value: boolean) => void;
16 | }
17 |
18 | export function PowersInfoModal({ isOpen, setIsOpen }: PowersInfoModalProps) {
19 | const representative = useStore((store) => store.representative);
20 | const setAccountInfoModalOpen = useStore(
21 | (store) => store.setAccountInfoModalOpen,
22 | );
23 |
24 | const currentPowersAll = useStore((store) => selectCurrentPowers(store));
25 | const currentPowersActiveWallet = useStore((store) =>
26 | selectCurrentPowersForActiveWallet(store),
27 | );
28 |
29 | if (!currentPowersAll || !currentPowersActiveWallet) return null;
30 |
31 | return (
32 |
37 |
43 |
48 |
49 |
50 | {
56 | setIsOpen(false);
57 | setAccountInfoModalOpen(true);
58 | }}
59 | />
60 |
61 |
62 | );
63 | }
64 |
--------------------------------------------------------------------------------
/src/delegate/components/DelegateModal.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import {
4 | ActionModalContentWrapper,
5 | ActionModalTitle,
6 | } from '../../proposals/components/actionModals/ActionModalContentWrapper';
7 | import {
8 | BasicActionModal,
9 | BasicActionModalProps,
10 | } from '../../transactions/components/BasicActionModal';
11 | import { texts } from '../../ui/utils/texts';
12 | import { DelegateData, DelegateItem } from '../types';
13 | import { DelegatedText } from './DelegatedText';
14 |
15 | export function DelegateModal({
16 | isOpen,
17 | setIsOpen,
18 | isTxStart,
19 | setIsTxStart,
20 | setError,
21 | error,
22 | delegateData,
23 | formDelegateData,
24 | fullTxErrorMessage,
25 | setFullTxErrorMessage,
26 | tx,
27 | }: Pick<
28 | BasicActionModalProps,
29 | | 'isOpen'
30 | | 'setIsOpen'
31 | | 'isTxStart'
32 | | 'setIsTxStart'
33 | | 'setError'
34 | | 'error'
35 | | 'fullTxErrorMessage'
36 | | 'setFullTxErrorMessage'
37 | | 'tx'
38 | > & { delegateData: DelegateItem[]; formDelegateData: DelegateData[] }) {
39 | return (
40 |
57 | }
58 | topBlock={
59 |
66 | }>
67 |
68 |
73 |
74 |
75 | );
76 | }
77 |
--------------------------------------------------------------------------------
/src/proposals/components/timeline/TimelineCanceledItem.tsx:
--------------------------------------------------------------------------------
1 | import { BigNumber } from 'bignumber.js';
2 | import React, { ReactNode } from 'react';
3 |
4 | import { TimelineCanceledItemWrapper, TimelineLineWrapper } from './static';
5 |
6 | interface TimelineCanceledItemProps {
7 | children: ReactNode;
8 | timelinesLength: number;
9 | openToVoteTimestamp: number;
10 | votingClosedTimestamp: number;
11 | finishedTimestamp: number;
12 | canceledTimestamp?: number;
13 | }
14 |
15 | export function TimelineCanceledItem({
16 | children,
17 | timelinesLength,
18 | openToVoteTimestamp,
19 | votingClosedTimestamp,
20 | finishedTimestamp,
21 | canceledTimestamp,
22 | }: TimelineCanceledItemProps) {
23 | if (canceledTimestamp) {
24 | const getPercent = (start: number, end: number) =>
25 | new BigNumber(canceledTimestamp - start)
26 | .dividedBy(end - start)
27 | .multipliedBy(100)
28 | .toNumber();
29 |
30 | if (canceledTimestamp <= votingClosedTimestamp) {
31 | return (
32 |
37 |
44 | {children}
45 |
46 |
47 | );
48 | } else if (
49 | canceledTimestamp <= finishedTimestamp &&
50 | canceledTimestamp > votingClosedTimestamp
51 | ) {
52 | return (
53 |
58 |
62 | {children}
63 |
64 |
65 | );
66 | } else {
67 | return <>>;
68 | }
69 | } else {
70 | return <>>;
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/public/images/icons/settingsBorders.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/ui/helpModals/HelpDelegateTx.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react';
2 |
3 | import { DelegatedText } from '../../delegate/components/DelegatedText';
4 | import { DelegateData, DelegateItem } from '../../delegate/types';
5 | import {
6 | ActionModalContentWrapper,
7 | ActionModalTitle,
8 | } from '../../proposals/components/actionModals/ActionModalContentWrapper';
9 | import { ActionModalContent } from '../../transactions/components/ActionModalContent';
10 | import { texts } from '../utils/texts';
11 | import { getTestTx } from './getTestTx';
12 |
13 | interface HelpDelegateTxProps {
14 | txPending: boolean;
15 | txSuccess: boolean;
16 | delegateData: DelegateItem[];
17 | formDelegateData: DelegateData[];
18 | handleCancelClick: () => void;
19 | }
20 |
21 | export function HelpDelegateTx({
22 | txPending,
23 | txSuccess,
24 | delegateData,
25 | formDelegateData,
26 | handleCancelClick,
27 | }: HelpDelegateTxProps) {
28 | const [error, setError] = useState('');
29 | const [isTxStart, setIsTxStart] = useState(false);
30 |
31 | useEffect(() => {
32 | if (txPending) {
33 | setIsTxStart(true);
34 | }
35 | }, [txPending]);
36 |
37 | return (
38 | <>
39 |
52 | }
53 | topBlock={
54 |
61 | }>
62 |
63 |
68 |
69 |
70 | >
71 | );
72 | }
73 |
--------------------------------------------------------------------------------
/src/ui/components/Link.tsx:
--------------------------------------------------------------------------------
1 | import { Box, styled, SxProps } from '@mui/system';
2 | import NextLink, { LinkProps as NextLinkProps } from 'next/link';
3 | import { ReactNode, useMemo } from 'react';
4 | import { resolve } from 'url';
5 |
6 | import { isForIPFS } from '../../utils/appConfig';
7 |
8 | interface LinkProps extends NextLinkProps {
9 | title?: string;
10 | href: string;
11 | inNewWindow?: boolean;
12 | children?: ReactNode;
13 | disabled?: boolean;
14 | css?: SxProps;
15 | }
16 |
17 | const BaseLink = styled('a')({
18 | textDecoration: 'none',
19 | });
20 |
21 | export function Link({
22 | href,
23 | inNewWindow,
24 | children,
25 | title,
26 | css,
27 | as,
28 | disabled,
29 | ...props
30 | }: LinkProps) {
31 | const isExternal =
32 | href.indexOf('http') === 0 || href.indexOf('mailto:') === 0;
33 |
34 | const newAs = useMemo(() => {
35 | let baseURI_as = as || href;
36 | if (baseURI_as.toString().startsWith('/')) {
37 | baseURI_as = '.' + href;
38 | if (typeof document !== 'undefined') {
39 | baseURI_as = resolve(document.baseURI, baseURI_as);
40 | }
41 | }
42 | return baseURI_as;
43 | }, [as, href]);
44 |
45 | return (
46 | <>
47 | {disabled ? (
48 |
55 | {children}
56 |
57 | ) : (
58 | <>
59 | {!isExternal ? (
60 |
66 |
67 | {children}
68 |
69 |
70 | ) : (
71 |
77 | {children}
78 |
79 | )}
80 | >
81 | )}
82 | >
83 | );
84 | }
85 |
--------------------------------------------------------------------------------
/src/delegate/components/DelegateTableWrapper.tsx:
--------------------------------------------------------------------------------
1 | import { Box } from '@mui/system';
2 | import React, { ReactNode } from 'react';
3 | import { FieldArray } from 'react-final-form-arrays';
4 |
5 | import {
6 | TableContainer,
7 | TableContainerChildren,
8 | } from '../../ui/components/TableContainer';
9 | import { DelegateData, DelegateItem } from '../types';
10 | import { DelegateTable } from './DelegateTable';
11 |
12 | interface DelegateTableWrapperProps {
13 | loading: boolean;
14 | delegateData: DelegateItem[];
15 | isEdit: boolean;
16 | isViewChanges: boolean;
17 | fields?: any;
18 | formDelegateData?: DelegateData[];
19 | children: ReactNode;
20 | handleFormSubmit?: (formDelegateData: any) => void;
21 | forHelp?: boolean;
22 | }
23 |
24 | export function DelegateTableWrapper({
25 | loading,
26 | delegateData,
27 | isEdit,
28 | isViewChanges,
29 | fields,
30 | formDelegateData,
31 | children,
32 | handleFormSubmit,
33 | forHelp,
34 | }: DelegateTableWrapperProps) {
35 | return (
36 |
37 | {typeof handleFormSubmit === 'function' ? (
38 |
39 |
40 | {({ fields }) => (
41 |
49 | )}
50 |
51 |
52 | {children}
53 |
54 |
55 | ) : (
56 | <>
57 |
66 |
67 | {children}
68 |
69 | >
70 | )}
71 |
72 | );
73 | }
74 |
--------------------------------------------------------------------------------
/src/proposals/components/proposalList/ProposalListItemWrapper.tsx:
--------------------------------------------------------------------------------
1 | import { ProposalEstimatedState } from '@bgd-labs/aave-governance-ui-helpers';
2 | import { useTheme } from '@mui/system';
3 | import React, { ReactNode } from 'react';
4 |
5 | import { BoxWith3D } from '../../../ui';
6 | import { media } from '../../../ui/utils/themeMUI';
7 | import { useMediaQuery } from '../../../ui/utils/useMediaQuery';
8 |
9 | export function ProposalListItemWrapper({
10 | children,
11 | isVotingActive,
12 | estimatedState,
13 | isFinished,
14 | isForHelpModal,
15 | disabled,
16 | }: {
17 | children: ReactNode;
18 | isVotingActive?: boolean;
19 | estimatedState?: ProposalEstimatedState;
20 | isFinished?: boolean;
21 | isForHelpModal?: boolean;
22 | disabled?: boolean;
23 | }) {
24 | const sm = useMediaQuery(media.sm);
25 | const md = useMediaQuery(media.md);
26 | const theme = useTheme();
27 |
28 | return (
29 |
69 | {children}
70 |
71 | );
72 | }
73 |
--------------------------------------------------------------------------------
/src/ui/pages/NotFoundPage.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import { Box, useTheme } from '@mui/system';
4 | import { useRouter } from 'next/navigation';
5 |
6 | import { BackButton3D } from '../components/BackButton3D';
7 | import { BigButton } from '../components/BigButton';
8 | import { Link } from '../components/Link';
9 | import { NoDataWrapper } from '../components/NoDataWrapper';
10 | import { Container } from '../primitives/Container';
11 | import NoSSR from '../primitives/NoSSR';
12 | import { metaTexts } from '../utils/metaTexts';
13 | import { ROUTES } from '../utils/routes';
14 | import { texts } from '../utils/texts';
15 |
16 | export function NotFoundPage() {
17 | const router = useRouter();
18 | const theme = useTheme();
19 |
20 | return (
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | {theme.palette.mode === 'dark' ? (
30 | // eslint-disable-next-line @next/next/no-img-element
31 |
37 | ) : (
38 | // eslint-disable-next-line @next/next/no-img-element
39 |
45 | )}
46 |
47 |
48 |
49 | {texts.notFoundPage.title}
50 |
51 |
54 | {texts.notFoundPage.descriptionFirst}
55 |
56 | {texts.notFoundPage.descriptionSecond}
57 |
58 |
59 | {texts.notFoundPage.buttonTitle}
60 |
61 |
62 |
63 | );
64 | }
65 |
--------------------------------------------------------------------------------
/src/ui/components/InputWithAnimation.tsx:
--------------------------------------------------------------------------------
1 | import { Box, useTheme } from '@mui/system';
2 | import { DetailedHTMLProps, InputHTMLAttributes } from 'react';
3 |
4 | import { Input } from '../primitives/Input';
5 |
6 | interface InputWithAnimationProps
7 | extends DetailedHTMLProps<
8 | InputHTMLAttributes,
9 | HTMLInputElement
10 | > {
11 | animatedPlaceholder?: string;
12 | }
13 |
14 | export function InputWithAnimation({
15 | animatedPlaceholder,
16 | ...rest
17 | }: InputWithAnimationProps) {
18 | const theme = useTheme();
19 |
20 | return (
21 |
47 | {!!animatedPlaceholder && !rest.value && (
48 |
68 | {animatedPlaceholder}
69 |
70 | )}
71 |
72 |
73 | );
74 | }
75 |
--------------------------------------------------------------------------------
/src/proposals/components/proposal/ProposalPageWrapperWithCache.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | getVotingMachineProposalState,
3 | ProposalData,
4 | } from '@bgd-labs/aave-governance-ui-helpers';
5 | import { useRequest } from 'alova';
6 | import React, { useEffect } from 'react';
7 |
8 | import { useStore } from '../../../store/ZustandStoreProvider';
9 | import { getProposalDetailsCache } from '../../../utils/githubCacheRequests';
10 | import { ProposalLoading } from './ProposalLoading';
11 | import { ProposalPageWrapper } from './ProposalPageWrapper';
12 |
13 | interface ProposalPageWrapperWithCacheProps {
14 | id: number;
15 | }
16 |
17 | export function ProposalPageWrapperWithCache({
18 | id,
19 | }: ProposalPageWrapperWithCacheProps) {
20 | const setDetailedPayloadsData = useStore(
21 | (store) => store.setDetailedPayloadsData,
22 | );
23 | const setIpfsData = useStore((store) => store.setIpfsData);
24 | const setDetailedProposalsData = useStore(
25 | (store) => store.setDetailedProposalsData,
26 | );
27 |
28 | const {
29 | loading: detailsLoading,
30 | data: detailsData,
31 | error: detailsError,
32 | } = useRequest(getProposalDetailsCache(id));
33 |
34 | useEffect(() => {
35 | if (!detailsLoading && !detailsError) {
36 | const detailedProposalsData: Record = {};
37 |
38 | if (detailsData) {
39 | detailedProposalsData[id] = {
40 | ...detailsData.proposal,
41 | votingMachineState: getVotingMachineProposalState(
42 | detailsData.proposal,
43 | ),
44 | payloads: detailsData.payloads || [],
45 | title: detailsData.ipfs.title || `Proposal #${id}`,
46 | isFinished: true,
47 | };
48 |
49 | detailsData.payloads.forEach((payload) => {
50 | if (payload) {
51 | setDetailedPayloadsData(
52 | `${payload.payloadsController}_${payload.id}`,
53 | payload,
54 | );
55 | }
56 | });
57 | setIpfsData(detailsData.proposal.ipfsHash, detailsData.ipfs);
58 | setDetailedProposalsData(id, detailedProposalsData[id]);
59 | }
60 | }
61 | }, [detailsLoading, detailsError, detailsData]);
62 |
63 | if (detailsLoading && !detailsData) return ;
64 |
65 | return ;
66 | }
67 |
--------------------------------------------------------------------------------
/public/images/icons/gelato.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/proposals/components/proposal/PayloadCreator.tsx:
--------------------------------------------------------------------------------
1 | import { Box, SxProps } from '@mui/system';
2 | import React from 'react';
3 |
4 | import { NewPayload } from '../../../proposalCreateOverview/store/proposalCreateOverviewSlice';
5 | import { Link } from '../../../ui';
6 | import { CopyAndExternalIconsSet } from '../../../ui/components/CopyAndExternalIconsSet';
7 | import { textCenterEllipsis } from '../../../ui/utils/text-center-ellipsis';
8 | import { texts } from '../../../ui/utils/texts';
9 | import { getScanLink } from '../../../utils/getScanLink';
10 |
11 | interface PayloadCreatorProps {
12 | payload: NewPayload;
13 | mb?: number;
14 | ellipsisFrom?: number;
15 | mainTypography?: string;
16 | addressTypography?: string;
17 | iconSize?: number;
18 | sx?: SxProps;
19 | }
20 |
21 | export function PayloadCreator({
22 | payload,
23 | mb,
24 | ellipsisFrom,
25 | mainTypography,
26 | addressTypography,
27 | iconSize,
28 | sx,
29 | }: PayloadCreatorProps) {
30 | return (
31 |
32 |
38 | {texts.proposals.payloadsDetails.creator}:{' '}
39 |
44 |
57 | {textCenterEllipsis(payload.creator, ellipsisFrom || 8, 8)}
58 |
59 |
60 |
69 |
70 |
71 |
72 | );
73 | }
74 |
--------------------------------------------------------------------------------
/src/representations/components/RepresentationsTableWrapper.tsx:
--------------------------------------------------------------------------------
1 | import { Box } from '@mui/system';
2 | import React, { ReactNode } from 'react';
3 | import { FieldArray } from 'react-final-form-arrays';
4 |
5 | import {
6 | TableContainer,
7 | TableContainerChildren,
8 | } from '../../ui/components/TableContainer';
9 | import {
10 | RepresentationDataItem,
11 | RepresentationFormData,
12 | } from '../store/representationsSlice';
13 | import { RepresentationsTable } from './RepresentationsTable';
14 |
15 | interface RepresentationsTableWrapperProps {
16 | loading: boolean;
17 | representationData: Record;
18 | isEdit: boolean;
19 | isViewChanges: boolean;
20 | fields?: any;
21 | formData?: RepresentationFormData[];
22 | children: ReactNode;
23 | handleFormSubmit?: (data: any) => void;
24 | forHelp?: boolean;
25 | }
26 |
27 | export function RepresentationsTableWrapper({
28 | representationData,
29 | loading,
30 | isEdit,
31 | isViewChanges,
32 | fields,
33 | children,
34 | formData,
35 | handleFormSubmit,
36 | forHelp,
37 | }: RepresentationsTableWrapperProps) {
38 | return (
39 |
40 | {typeof handleFormSubmit === 'function' ? (
41 |
42 |
43 | {({ fields }) => (
44 |
52 | )}
53 |
54 |
55 | {children}
56 |
57 |
58 | ) : (
59 | <>
60 |
69 |
70 | {children}
71 |
72 | >
73 | )}
74 |
75 | );
76 | }
77 |
--------------------------------------------------------------------------------
/src/web3/providers/WagmiProvider.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import {
4 | createWagmiConfig,
5 | WagmiZustandSync,
6 | WalletType,
7 | } from '@bgd-labs/frontend-web3-utils';
8 | import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
9 | import React, { useEffect, useMemo } from 'react';
10 |
11 | import { useStore } from '../../store/ZustandStoreProvider';
12 | import { appConfig, WC_PROJECT_ID } from '../../utils/appConfig';
13 | import { CHAINS } from '../../utils/chains';
14 |
15 | const queryClient = new QueryClient();
16 |
17 | export default function WagmiProvider() {
18 | const getImpersonatedAddress = useStore(
19 | (store) => store.getImpersonatedAddress,
20 | );
21 | const setWagmiConfig = useStore((store) => store.setWagmiConfig);
22 | const setDefaultChainId = useStore((store) => store.setDefaultChainId);
23 | const changeActiveWalletAccount = useStore(
24 | (store) => store.changeActiveWalletAccount,
25 | );
26 | const connectWallet = useStore((store) => store.connectWallet);
27 |
28 | const config = useMemo(() => {
29 | return createWagmiConfig({
30 | chains: CHAINS,
31 | connectorsInitProps: {
32 | appName: 'AAVEGovernanceV3',
33 | defaultChainId: appConfig.govCoreChainId,
34 | wcParams: {
35 | projectId: WC_PROJECT_ID,
36 | metadata: {
37 | name: 'Aave governance',
38 | description:
39 | 'User interface to interact with the Aave governance v3 smart contracts',
40 | url: 'https://vote.onaave.com',
41 | icons: [
42 | 'https://imagedelivery.net/_aTEfDRm7z3tKgu9JhfeKA/c54c2635-3522-4d32-0e97-2329a733ee00/lg',
43 | ],
44 | },
45 | },
46 | },
47 | getImpersonatedAccount: getImpersonatedAddress,
48 | ssr: true,
49 | });
50 | }, []);
51 |
52 | useEffect(() => {
53 | const isInIframe = window !== window.parent;
54 | if (isInIframe) {
55 | connectWallet(WalletType.Safe);
56 | }
57 | }, [connectWallet]);
58 |
59 | return (
60 |
61 |
71 |
72 | );
73 | }
74 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Aave Governance V3 frontend
2 |
3 |
4 |
5 |
6 |
7 | React application to interact with the Aave Governance V3 smart contracts: visualize data and build blockchain transactions.
8 |
9 | ## Built on
10 |
11 | - Logic: [React](https://react.dev/), [Next.js](https://nextjs.org/), [zustand](https://docs.pmnd.rs/zustand/getting-started/introduction).
12 | - Web3: [viem](https://viem.sh/), [wagmi](https://wagmi.sh/), [@bgd-labs/frontend-web3-utils](https://github.com/bgd-labs/fe-shared).
13 | - Styling: [MUI system](https://mui.com/system/getting-started/), [headlessui](https://headlessui.com/).
14 | - Cache: [@bgd-labs/aave-governance-ui-helpers](https://github.com/bgd-labs/aave-governance-ui-helpers).
15 |
16 | ## Pre-requirements
17 |
18 | - [Node.js](https://nodejs.org/ru): version 18 or higher.
19 | - [Git](https://git-scm.com/downloads): version 2.3.x or higher.
20 | - Package manager: we recommend [Yarn](https://yarnpkg.com/), version 1.x or higher.
21 |
22 | ## Configurations
23 | - Blockchain RPC URLs can be changed here [file](./src/utils/chains.ts) and IPFS gateway URLs can be changed here [file](./src/utils/configs.ts).
24 | - It is possible to run the application in SSR (Server-Side-Rendering) or IPFS mode, by changing `NEXT_PUBLIC_DEPLOY_FOR_IPFS`. The default is `false`, which is the most optimal option for all use cases.
25 |
26 |
27 |
28 | ## How to access the Aave Governance v3 UI?
29 |
30 | ### Run locally
31 | ```sh
32 | yarn && yarn dev
33 | // or
34 | yarn && yarn build && yarn start
35 | ```
36 |
37 | ### Deploy your own Vercel instance
38 |
39 | You can deploy your version of the application using Vercel just by clicking on the following button, and following the instructions:
40 |
41 | [](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fbgd-labs%2Faave-governance-v3-interface)
42 |
43 | ### Hosted version
44 |
45 | We have our own hosted version from Vercel, you can access it on [https://vote.onaave.com/](https://vote.onaave.com/)
46 |
47 |
48 |
49 | ## License
50 |
51 | Copyright © 2023, Aave DAO, represented by its governance smart contracts.
52 |
53 | Created by BGD Labs.
54 |
55 | **IMPORTANT**. The BUSL1.1 license of this repository allows for any usage of the software, if respecting the *Additional Use Grant* limitations, forbidding any use case damaging anyhow the Aave DAO's interests.
56 |
--------------------------------------------------------------------------------
/eslint.config.mjs:
--------------------------------------------------------------------------------
1 | import js from '@eslint/js';
2 | import globals from 'globals';
3 | import tseslint from 'typescript-eslint';
4 | import eslintReact from 'eslint-plugin-react';
5 | import eslintReactHooks from 'eslint-plugin-react-hooks';
6 | import prettierPlugin from 'eslint-plugin-prettier';
7 | import eslintConfigPrettier from 'eslint-config-prettier';
8 | import eslintPluginSimpleImportSort from 'eslint-plugin-simple-import-sort';
9 | import eslintPluginImport from 'eslint-plugin-import';
10 | import eslintPluginNext from '@next/eslint-plugin-next';
11 |
12 | /** @type {import('eslint').Linter.FlatConfig[]} */
13 | export default tseslint.config(
14 | {
15 | plugins: {
16 | '@typescript-eslint': tseslint.plugin,
17 | 'react': eslintReact,
18 | 'react-hooks': eslintReactHooks,
19 | prettier: prettierPlugin,
20 | 'simple-import-sort': eslintPluginSimpleImportSort,
21 | 'import': eslintPluginImport,
22 | '@next/next': eslintPluginNext,
23 | },
24 | },
25 | {
26 | ignores: ['dist', 'node_modules', 'coverage', 'eslint.config.mjs', '.next', '.github', 'next.config.js', 'svgo.config.js'],
27 | },
28 | js.configs.recommended,
29 | ...tseslint.configs.recommended,
30 | {
31 | languageOptions: {
32 | globals: {
33 | ...globals.browser,
34 | ...globals.node,
35 | ...globals.es2020,
36 | },
37 | parserOptions: {
38 | project: ['tsconfig.json'],
39 | },
40 | parser: tseslint.parser,
41 | ecmaVersion: 'latest'
42 | },
43 | },
44 | {
45 | files: ['**/*.{ts,tsx}'],
46 | extends: [tseslint.configs.disableTypeChecked],
47 | rules: {
48 | ...eslintConfigPrettier.rules,
49 | ...eslintPluginImport.configs.recommended.rules,
50 | ...eslintPluginNext.configs.recommended.rules,
51 | '@typescript-eslint/no-empty-interface': 'off',
52 | '@typescript-eslint/no-explicit-any': 'warn',
53 | 'prettier/prettier': 'warn',
54 | 'react-hooks/rules-of-hooks': 'error',
55 | 'simple-import-sort/imports': 'error',
56 | 'simple-import-sort/exports': 'warn',
57 | 'import/first': 'error',
58 | 'import/no-duplicates': 'error',
59 | 'import/no-named-as-default': 'off',
60 | 'import/no-unresolved': 'off',
61 | 'import/no-named-as-default-member': 'off',
62 | 'import/namespace': 'off',
63 | 'react/self-closing-comp': 'warn',
64 | '@next/next/no-duplicate-head': 'off',
65 | 'no-prototype-builtins': 'off',
66 | },
67 | },
68 | );
--------------------------------------------------------------------------------