From 493530293ad2508c28fc481723a09bf4d3aa36dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20B=C3=A9ranger?= Date: Sun, 26 May 2024 11:18:53 +0200 Subject: [PATCH] Add subgraph (#77) * successful integration * rm unused options * integration done * fix order --- README.md | 14 +--- package.json | 1 + pnpm-lock.yaml | 64 +++++++++++++++++ src/pages/index.tsx | 166 ++++++++++++++++++++++++++++++++------------ 4 files changed, 189 insertions(+), 56 deletions(-) diff --git a/README.md b/README.md index 2d58f48..5d41875 100644 --- a/README.md +++ b/README.md @@ -42,21 +42,15 @@ pnpm build You can use this repo as a template for your own DAO. -You can replace the `GOV_CONTRACT_ADDRESS` and `startBlock` in the [`config.ts`](https://github.com/w3hc/gov-ui/blob/main/src/utils/config.ts) file. Also, make sure the network id is correct everywhere in the app (in [`index.ts`](https://github.com/w3hc/gov-ui/blob/main/src/pages/index.tsx#L51), but also in [`proposal/\[proposalId\].tsx`](https://github.com/w3hc/gov-ui/blob/main/src/pages/proposal/%5BproposalId%5D.tsx#L109)). - -Feel free to [contact me](https://github.com/w3hc/gov-ui/tree/main?tab=readme-ov-file#support) if you need anything. +You can replace the `GOV_CONTRACT_ADDRESS` in the [`config.ts`](https://github.com/w3hc/gov-ui/blob/main/src/utils/config.ts) file. Also, make sure the network id is correct everywhere in the app (in [`index.ts`](https://github.com/w3hc/gov-ui/blob/main/src/pages/index.tsx#L51), but also in [`proposal/\[proposalId\].tsx`](https://github.com/w3hc/gov-ui/blob/main/src/pages/proposal/%5BproposalId%5D.tsx#L109)). ## Includes - [Next.js](https://nextjs.org/docs) - [Chakra UI](https://chakra-ui.com/) - [Ethers v6](https://docs.ethers.org/v6/) -- [viem](https://viem.sh/) -- [wagmi](https://wagmi.sh/) - [Web3Modal SDK from WalletConnect](https://docs.walletconnect.com/) -- [Sign-In with Ethereum](https://www.login.xyz/) -- [Gitcoin Passport](https://docs.passport.gitcoin.co/) -- [usehooks-ts](https://usehooks-ts.com/) +- [Subgraph](https://thegraph.com/docs/en/) - [next-SEO](https://github.com/garmeeh/next-seo) - [TypeScript](https://www.typescriptlang.org/) - [eslint](https://eslint.org/) @@ -66,7 +60,3 @@ Feel free to [contact me](https://github.com/w3hc/gov-ui/tree/main?tab=readme-ov ## Support You can contact me via [Element](https://matrix.to/#/@julienbrg:matrix.org), [Farcaster](https://warpcast.com/julien-), [Telegram](https://t.me/julienbrg), [Twitter](https://twitter.com/julienbrg), [Discord](https://discordapp.com/users/julienbrg), or [LinkedIn](https://www.linkedin.com/in/julienberanger/). - -## Credits - -[Nexth](https://github.com/wslyvh/nexth/) boilerplate was built by [wslyvh](https://github.com/wslyvh) and [others](https://github.com/wslyvh/nexth/graphs/contributors). diff --git a/package.json b/package.json index e972b1e..48d727a 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "@types/react-dom": "18.2.1", "@web3modal/ethers": "^4.1.6", "autoprefixer": "10.4.14", + "axios": "^1.7.2", "eslint": "8.39.0", "eslint-config-next": "13.3.1", "ethers": "^6.11.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index def533a..fae803d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -35,6 +35,9 @@ dependencies: autoprefixer: specifier: 10.4.14 version: 10.4.14(postcss@8.4.23) + axios: + specifier: ^1.7.2 + version: 1.7.2 eslint: specifier: 8.39.0 version: 8.39.0 @@ -2935,6 +2938,10 @@ packages: tslib: 2.6.2 dev: false + /asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + dev: false + /atomic-sleep@1.0.0: resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==} engines: {node: '>=8.0.0'} @@ -2968,6 +2975,16 @@ packages: engines: {node: '>=4'} dev: false + /axios@1.7.2: + resolution: {integrity: sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw==} + dependencies: + follow-redirects: 1.15.6 + form-data: 4.0.0 + proxy-from-env: 1.1.0 + transitivePeerDependencies: + - debug + dev: false + /axobject-query@3.2.1: resolution: {integrity: sha512-jsyHu61e6N4Vbz/v18DHwWYKK0bSWLqn47eeDSKPB7m8tqMHF9YJ+mhIk2lVteyZrY8tnSj/jHOv4YiTCuCJgg==} dependencies: @@ -3181,6 +3198,13 @@ packages: resolution: {integrity: sha512-zW190nQTIoXcGCaU08DvVNFTmQhUpnJfVuAKfWqUQkflXKpaDdpaYoM0iluLS9lgJNHyBF58KKA2FBEwkD7wog==} dev: false + /combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + dependencies: + delayed-stream: 1.0.0 + dev: false + /comma-separated-tokens@2.0.3: resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} dev: false @@ -3365,6 +3389,11 @@ packages: resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} dev: false + /delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + dev: false + /dequal@2.0.3: resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} engines: {node: '>=6'} @@ -4064,12 +4093,31 @@ packages: tslib: 2.6.2 dev: false + /follow-redirects@1.15.6: + resolution: {integrity: sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + dev: false + /for-each@0.3.3: resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} dependencies: is-callable: 1.2.7 dev: false + /form-data@4.0.0: + resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==} + engines: {node: '>= 6'} + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + mime-types: 2.1.35 + dev: false + /fraction.js@4.3.7: resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==} dev: false @@ -5221,6 +5269,18 @@ packages: picomatch: 2.3.1 dev: false + /mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + dev: false + + /mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + dependencies: + mime-db: 1.52.0 + dev: false + /mime@3.0.0: resolution: {integrity: sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==} engines: {node: '>=10.0.0'} @@ -5733,6 +5793,10 @@ packages: resolution: {integrity: sha512-oyfc0Tx87Cpwva5ZXezSp5V9vht1c7dZBhvuV/y3ctkgMVUmiAGDVeeB0dKhGSyT0v1ZTEQYpe/RXlBVBNuCLA==} dev: false + /proxy-from-env@1.1.0: + resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + dev: false + /punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 6754904..8827b4b 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -9,12 +9,14 @@ import { HeadingComponent } from '../components/layout/HeadingComponent' import { ArrowForwardIcon, WarningIcon } from '@chakra-ui/icons' import Image from 'next/image' import { firstIteration } from '../utils/config' +import axios from 'axios' export default function Home() { const { address, chainId, isConnected } = useWeb3ModalAccount() const { walletProvider } = useWeb3ModalProvider() const customProvider = new ethers.JsonRpcProvider(process.env.NEXT_PUBLIC_RPC_ENDPOINT_URL) const toast = useToast() + const queryURL: string = 'https://api.studio.thegraph.com/query/52496/gov-subgraph/version/latest' const [initialized, setInitialized] = useState(false) const [isLoading, setIsLoading] = useState(false) @@ -41,6 +43,18 @@ export default function Home() { } const makeProposalObject = async () => { + const uniswapGraphQuery: string = `query proposalsCreated { + proposalCreateds(orderDirection: desc, orderBy: voteEnd) { + proposalId + blockNumber + calldatas + description + voteEnd + voteStart + transactionHash + } + }` + try { console.log('fetching proposals...') if (initialized) { @@ -49,56 +63,45 @@ export default function Home() { return } setIsLoading(true) - const gov = new ethers.Contract(govContract.address, govContract.abi, customProvider) setProposal([]) - - if (typeof gov.getProposalCreatedBlocks === 'function') { - const proposalCreatedBlocks = await gov.getProposalCreatedBlocks() - let proposalRaw = proposal - for (let i = firstIteration; i < proposalCreatedBlocks.length; i++) { - console.log('iteration:', i) - /////////////////*******////////////// - - const proposals = (await gov.queryFilter('ProposalCreated', proposalCreatedBlocks[i])) as any - - if (proposals.length > 0) { - proposalRaw.push( - ...[ - { - id: String(proposals[0].args[0]), - link: baseUrl + String(proposals[0].args[0]), - title: proposals[0].args[8].substring(proposals[0].args[8] == '#' ? 2 : 2, proposals[0].args[8].indexOf('\n')), - state: Number(await getState(proposals[0].args[0])), - }, - ] - ) - } else { - console.log('\nNo proposals found at block #' + Number(proposalCreatedBlocks[i])) - } + const response = await axios.post( + queryURL, + { + query: uniswapGraphQuery, + }, + { + headers: { 'Content-Type': 'application/json' }, } + ) - // TODO: fix executed twice... - // Remove duplicates based on the `id` property - const uniqueProposals = proposal.filter((item, index, self) => index === self.findIndex((t) => t.id === item.id)) - setProposal(uniqueProposals) + const proposals = response.data.data.proposalCreateds + console.log('proposals', proposals) + let proposalRaw = proposal - console.log('all proposals fetched ✅') - setInitialized(true) - setIsLoading(false) + console.log('proposals[0].proposalId', proposals[0].proposalId) + console.log('proposals.length', proposals.length) + if (proposals.length > 0) { + for (let i = 0; i < proposals.length; i++) { + proposalRaw.push( + ...[ + { + id: String(proposals[i].proposalId), + link: baseUrl + String(proposals[i].proposalId), + title: proposals[i].description.substring(proposals[i].description == '#' ? 2 : 2, proposals[i].description.indexOf('\n')), + state: Number(await getState(proposals[i].proposalId)), + }, + ] + ) + } } else { - console.error('getProposalCreatedBlocks method not available on this DAO contract') - setInitialized(true) - setIsLoading(false) - toast({ - title: 'Oh no!', - description: 'The getProposalCreatedBlocks method is NOT available on this contract', - status: 'error', - position: 'bottom', - variant: 'subtle', - duration: 9000, - isClosable: true, - }) + console.log('\nNo proposals found') } + + const uniqueProposals = proposal.filter((item, index, self) => index === self.findIndex((t) => t.id === item.id)) + setProposal(uniqueProposals) + console.log('all proposals fetched ✅') + setInitialized(true) + setIsLoading(false) } catch (error: any) { setIsLoading(false) setInitialized(true) @@ -115,6 +118,81 @@ export default function Home() { }) } } + + // try { + // console.log('fetching proposals...') + // if (initialized) { + // console.log('already initialized') + // setIsLoading(false) + // return + // } + // setIsLoading(true) + // const gov = new ethers.Contract(govContract.address, govContract.abi, customProvider) + // setProposal([]) + + // if (typeof gov.getProposalCreatedBlocks === 'function') { + // const proposalCreatedBlocks = await gov.getProposalCreatedBlocks() + // let proposalRaw = proposal + // for (let i = firstIteration; i < proposalCreatedBlocks.length; i++) { + // console.log('iteration:', i) + // /////////////////*******////////////// + + // const proposals = (await gov.queryFilter('ProposalCreated', proposalCreatedBlocks[i])) as any + + // if (proposals.length > 0) { + // proposalRaw.push( + // ...[ + // { + // id: String(proposals[0].args[0]), + // link: baseUrl + String(proposals[0].args[0]), + // title: proposals[0].args[8].substring(proposals[0].args[8] == '#' ? 2 : 2, proposals[0].args[8].indexOf('\n')), + // state: Number(await getState(proposals[0].args[0])), + // }, + // ] + // ) + // } else { + // console.log('\nNo proposals found at block #' + Number(proposalCreatedBlocks[i])) + // } + // } + + // // TODO: fix executed twice... + // // Remove duplicates based on the `id` property + // const uniqueProposals = proposal.filter((item, index, self) => index === self.findIndex((t) => t.id === item.id)) + // setProposal(uniqueProposals) + + // console.log('all proposals fetched ✅') + // setInitialized(true) + // setIsLoading(false) + // } else { + // console.error('getProposalCreatedBlocks method not available on this DAO contract') + // setInitialized(true) + // setIsLoading(false) + // toast({ + // title: 'Oh no!', + // description: 'The getProposalCreatedBlocks method is NOT available on this contract', + // status: 'error', + // position: 'bottom', + // variant: 'subtle', + // duration: 9000, + // isClosable: true, + // }) + // } + // } catch (error: any) { + // setIsLoading(false) + // setInitialized(true) + // console.error('error:', error) + // if (!error.message.includes('could not decode result data')) { + // toast({ + // title: 'Woops', + // description: 'Something went wrong...', + // status: 'error', + // position: 'bottom', + // variant: 'subtle', + // duration: 9000, + // isClosable: true, + // }) + // } + // } } useEffect(() => {