diff --git a/.env.local.sample b/.env.local.sample index 5ba064f..71d75dc 100644 --- a/.env.local.sample +++ b/.env.local.sample @@ -1,5 +1,6 @@ NEXT_PUBLIC_INFURA_RPC="" NEXT_PUBLIC_INFURA_ID="" +NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID="" MONGODB_URL="" MONGODB_DB="" NOTIFICATION_HOOK="" diff --git a/components/header.js b/components/header.js index 496e510..f3e66ab 100644 --- a/components/header.js +++ b/components/header.js @@ -45,14 +45,12 @@ export default function Header() { {/* Logo */}
- - Compound logo - + Compound logo
@@ -60,21 +58,21 @@ export default function Header() {
@@ -128,14 +126,10 @@ export default function Header() { > {address ? ( diff --git a/containers/delegate.js b/containers/delegate.js index 149dfe5..d514918 100644 --- a/containers/delegate.js +++ b/containers/delegate.js @@ -1,90 +1,51 @@ import axios from "axios"; // Axios requests import { web3p } from "containers"; // Web3 -import { COMP_ABI } from "helpers/abi"; // Compound (COMP) Governance Token ABI +import { COMP_ABI, COMP_ADDRESS } from "helpers/abi"; // Compound (COMP) Governance Token ABI import { useState, useEffect } from "react"; // State management import { createContainer } from "unstated-next"; // Unstated-next containerization // Reference implementation: https://github.com/TennisBowling/comp.vote/blob/master/bySig/delegate_by_signature.html function useDelegate() { // Context - const { web3, address } = web3p.useContainer(); + const { publicClient, walletClient, address } = web3p.useContainer(); // Local state const [currentDelegate, setCurrentDelegate] = useState(null); // Current delegate /** - * Generate delegation message + * Sign an EIP-712 delegation message * @param {string} delegatee address to delegate voting power to - * @param {integer} nonce transaction nonce + * @param {bigint} nonce transaction nonce */ - const createDelegateBySigMessage = (delegatee, nonce = 0) => { - // Types - const types = { - EIP712Domain: [ - { name: "name", type: "string" }, - { name: "chainId", type: "uint256" }, - { name: "verifyingContract", type: "address" }, - ], - Delegation: [ - { name: "delegatee", type: "address" }, - { name: "nonce", type: "uint256" }, - { name: "expiry", type: "uint256" }, - ], - }; - - // Return message to sign - return JSON.stringify({ - types, - primaryType: "Delegation", - // Compound COMP token contract + const signDelegation = async (delegatee, nonce) => { + return walletClient.signTypedData({ + account: address, domain: { name: "Compound", chainId: 1, - verifyingContract: "0xc00e94cb662c3520282e6f5717214004a7f26888", + verifyingContract: COMP_ADDRESS, + }, + types: { + Delegation: [ + { name: "delegatee", type: "address" }, + { name: "nonce", type: "uint256" }, + { name: "expiry", type: "uint256" }, + ], }, - // Message + primaryType: "Delegation", message: { - // Delegatee address delegatee, - nonce: nonce, - expiry: 10e9, + nonce, + expiry: BigInt(10e9), }, }); }; - /** - * Returns promise of web3 signature - * @param {string} msgParams to sign - */ - const signDelegation = async (msgParams) => { - // Return promise - return new Promise((resolve, reject) => { - // Sign message - web3.currentProvider.sendAsync( - { - method: "eth_signTypedData_v4", - params: [address, msgParams], - from: address, - }, - async (error, result) => { - // If no error - if (!error) { - // Resolve promise with resulting signature - resolve(result.result); - } else { - // Reject promise with resulting error - reject(error); - } - } - ); - }); - }; - /** * POSTS delegation to back-end * @param {string} delegatee address to delegate voting power to - * @param {integer} nonce transaction nonce - * @param {string} signedMsg from Web3 + * @param {bigint} nonce transaction nonce + * @param {string} signedMsg hex signature from wallet */ const castDelegation = async (delegatee, nonce, signedMsg) => { // Collect r, s, v @@ -101,39 +62,31 @@ function useDelegate() { v, expiry: 10e9, delegatee, - nonce, + nonce: nonce.toString(), }) - // If successful .then(() => { - // Alert successful alert("Success!"); }) - // Else, .catch((error) => { - // Alert error message alert("Error: " + error.response.data.message); }); }; /** * Create a delegation to delegatee - * @param {string} delegate address to delegate voting power to + * @param {string} delegatee address to delegate voting power to */ const createDelegation = async (delegatee) => { - // Compound (COMP) Governance token contract - const compoundContract = new web3.eth.Contract( - COMP_ABI, - "0xc00e94cb662c3520282e6f5717214004a7f26888" - ); - - // Collect interaction nonce - const nonce = await compoundContract.methods.nonces(address).call(); + const nonce = await publicClient.readContract({ + address: COMP_ADDRESS, + abi: COMP_ABI, + functionName: "nonces", + args: [address], + }); - // Generate delegation message to sign - const msgParams = createDelegateBySigMessage(delegatee, nonce); - const signedMsg = await signDelegation(msgParams); + const signedMsg = await signDelegation(delegatee, nonce); - // POST vote to server + // POST delegation to server await castDelegation(delegatee, nonce, signedMsg); }; @@ -141,16 +94,13 @@ function useDelegate() { * Checks if a user has an existing delegation */ const checkDelegation = async () => { - // Compound (COMP) Governance token contract - const compoundContract = new web3.eth.Contract( - COMP_ABI, - "0xc00e94cb662c3520282e6f5717214004a7f26888" - ); - - // Collect current delegate - const delegate = await compoundContract.methods.delegates(address).call(); + const delegate = await publicClient.readContract({ + address: COMP_ADDRESS, + abi: COMP_ABI, + functionName: "delegates", + args: [address], + }); - // Update delegate in state const noDelegate = "0x0000000000000000000000000000000000000000"; if (delegate !== noDelegate) setCurrentDelegate(delegate); }; @@ -161,7 +111,7 @@ function useDelegate() { setCurrentDelegate(null); // If authenticated - if (web3 && address) { + if (publicClient && address) { // Recheck delegation status checkDelegation(); } diff --git a/containers/vote.js b/containers/vote.js index ac628ac..f74c8fa 100644 --- a/containers/vote.js +++ b/containers/vote.js @@ -5,131 +5,52 @@ import { GOVERNOR_CHARLIE_ADDRESS, GOVERNOR_CHARLIE_ABI } from "helpers/abi"; function useVote() { // Context - const { web3, address } = web3p.useContainer(); + const { publicClient, walletClient, address } = web3p.useContainer(); /** - * Generate voting message + * Sign an EIP-712 ballot for the given proposal and support value * @param {Number} proposalId for Compound Governance proposal - * @param {boolean} support for or against - * @param {string} voter address of the voter signing the message + * @param {Number} support 0 = against, 1 = for, 2 = abstain */ - const createVoteBySigMessage = async (proposalId, support) => { - // Fetch unused nonce - const governorCharlie = new web3.eth.Contract( - GOVERNOR_CHARLIE_ABI, - GOVERNOR_CHARLIE_ADDRESS - ); - - const nonce = await governorCharlie.methods.nonces(address).call(); - - // Types - const types = { - EIP712Domain: [ - { name: "name", type: "string" }, - { name: "version", type: "string" }, - { name: "chainId", type: "uint256" }, - { name: "verifyingContract", type: "address" }, - ], - Ballot: [ - { name: "proposalId", type: "uint256" }, - { name: "support", type: "uint8" }, - { name: "voter", type: "address" }, - { name: "nonce", type: "uint256" }, - ], - }; + const signBallot = async (proposalId, support) => { + const nonce = await publicClient.readContract({ + address: GOVERNOR_CHARLIE_ADDRESS, + abi: GOVERNOR_CHARLIE_ABI, + functionName: "nonces", + args: [address], + }); - // Return message to sign - return JSON.stringify({ - types, - primaryType: "Ballot", - // Compound Governor contract + return walletClient.signTypedData({ + account: address, domain: { name: "Compound Governor", version: "1", chainId: 1, verifyingContract: GOVERNOR_CHARLIE_ADDRESS, }, - // Message + types: { + Ballot: [ + { name: "proposalId", type: "uint256" }, + { name: "support", type: "uint8" }, + { name: "voter", type: "address" }, + { name: "nonce", type: "uint256" }, + ], + }, + primaryType: "Ballot", message: { - proposalId, - support: support, + proposalId: BigInt(proposalId), + support, voter: address, nonce, }, }); }; - /** - * Returns promise of web3 signature - * @param {string} msgParams to sign - */ - const signVote = async (msgParams) => { - return new Promise((resolve, reject) => { - // Sign message - web3.currentProvider.sendAsync( - { - method: "eth_signTypedData_v4", - params: [address, msgParams], - from: address, - }, - async (error, result) => { - // If no error - if (!error) { - // Resolve promise with resulting signature - resolve(result.result); - } else { - // Reject promise with resulting error - reject(error); - } - } - ); - }); - }; - - /** - * Generate a FOR vote for the proposalId - * @param {Number} proposalId of Compound governance proposal - */ - const voteFor = async (proposalId) => { - // Generate and sign message - const msgParams = await createVoteBySigMessage(proposalId, 1); - const signedMsg = await signVote(msgParams); - - // POST vote to server - await castVote(proposalId, 1, signedMsg); - }; - - /** - * Generate an AGAINST vote for the proposalId - * @param {Number} proposalId of Compound governance proposal - */ - const voteAgainst = async (proposalId) => { - // Generate and sign message - const msgParams = await createVoteBySigMessage(proposalId, 0); - const signedMsg = await signVote(msgParams); - - // POST vote to server - await castVote(proposalId, 0, signedMsg); - }; - - /** - * Generate an ABSTAIN vote for the proposalId - * @param {Number} proposalId of Compound governance proposal - */ - const voteAbstain = async (proposalId) => { - // Generate and sign message - const msgParams = await createVoteBySigMessage(proposalId, 2); - const signedMsg = await signVote(msgParams); - - // POST vote to server - await castVote(proposalId, 2, signedMsg); - }; - /** * POSTS vote to back-end * @param {Number} proposalId of compound governance proposal - * @param {boolean} support indicating for || against status for proposal - * @param {string} signedMsg from Web3 + * @param {Number} support indicating for || against || abstain status + * @param {string} signedMsg hex signature from wallet */ const castVote = async (proposalId, support, signedMsg) => { // Collect r, s, v @@ -147,18 +68,31 @@ function useVote() { proposalId, support, }) - // If successful - .then(() => { - // Alert successful - alert("Success!"); + .then((res) => { + alert( + `Success! View your transaction here https://etherscan.io/tx/${res.data.txHash}` + ); }) - // Else, .catch((error) => { - // Alert error message alert("Error: " + error.response.data.message); }); }; + const voteFor = async (proposalId) => { + const sig = await signBallot(proposalId, 1); + await castVote(proposalId, 1, sig); + }; + + const voteAgainst = async (proposalId) => { + const sig = await signBallot(proposalId, 0); + await castVote(proposalId, 0, sig); + }; + + const voteAbstain = async (proposalId) => { + const sig = await signBallot(proposalId, 2); + await castVote(proposalId, 2, sig); + }; + return { voteFor, voteAgainst, diff --git a/containers/web3p.js b/containers/web3p.js index 2f9713d..d4fb810 100644 --- a/containers/web3p.js +++ b/containers/web3p.js @@ -1,106 +1,67 @@ -import { useContext } from "react"; -import Web3 from "web3"; // Web3 -import Web3Modal from "web3modal"; // Web3Modal -import { useState, useEffect } from "react"; // State management -import { createContainer } from "unstated-next"; // Unstated-next containerization -import WalletConnectProvider from "@walletconnect/web3-provider"; // WalletConnectProvider (Web3Modal) -import { RPCWeb3Provider } from '@compound-finance/comet-extension'; -import { useRPC } from '../components/hooks/useRPC'; -import { Embedded } from "containers"; // Embedded - -// Web3Modal provider options -const providerOptions = { - walletconnect: { - package: WalletConnectProvider, - options: { - // Inject Infura - infuraId: process.env.NEXT_PUBLIC_INFURA_ID, - }, - }, -}; +import { useContext, useState, useEffect } from "react"; +import { useAccount, useDisconnect, usePublicClient, useWalletClient } from "wagmi"; +import { useConnectModal } from "@rainbow-me/rainbowkit"; +import { isAddress, createPublicClient, createWalletClient, custom } from "viem"; +import { mainnet } from "viem/chains"; +import { createContainer } from "unstated-next"; +import { RPCWeb3Provider } from "@compound-finance/comet-extension"; +import { useRPC } from "../components/hooks/useRPC"; +import { Embedded } from "containers"; function useWeb3() { const embedded = useContext(Embedded); const rpc = useRPC(); - const [web3, setWeb3] = useState(null); // Web3 provider - const [modal, setModal] = useState(null); // Web3Modal - const [address, setAddress] = useState(null); // ETH address - /** - * Sets up web3Modal and saves to state - */ - const setupWeb3Modal = () => { - // Create new web3Modal - const web3Modal = new Web3Modal({ - network: "mainnet", - cacheProvider: true, - providerOptions: providerOptions, - }); + // Wagmi state (non-embedded mode) + const { address: wagmiAddress } = useAccount(); + const { disconnect } = useDisconnect(); + const { openConnectModal } = useConnectModal(); + const wagmiPublicClient = usePublicClient(); + const { data: wagmiWalletClient } = useWalletClient(); - // Set web3Modal - setModal(web3Modal); - }; + // Embedded mode state + const [embeddedAddress, setEmbeddedAddress] = useState(null); + const [embeddedPublicClient, setEmbeddedPublicClient] = useState(null); + const [embeddedWalletClient, setEmbeddedWalletClient] = useState(null); - /** - * Authenticate, save web3 provider, and save eth address - */ - const authenticate = async () => { - // Toggle modal - let provider; - if (!embedded) { - provider = await modal.connect(); - } else { - provider = new RPCWeb3Provider(rpc.sendRPC); - } + useEffect(() => { + if (!embedded) return; - // Generate web3 object and save - const web3 = new Web3(provider); - setWeb3(web3); + const provider = new RPCWeb3Provider(rpc.sendRPC); + const transport = custom(provider); + const pubClient = createPublicClient({ chain: mainnet, transport }); + const walClient = createWalletClient({ chain: mainnet, transport }); - // Collect address - const accounts = await web3.eth.getAccounts(); - const address = accounts[0]; - setAddress(address); - return; - }; + setEmbeddedPublicClient(pubClient); + setEmbeddedWalletClient(walClient); - /** - * Unauthenticate and clear cache - */ - const unauthenticate = async () => { - // Check if logged in - if (web3 && web3.currentProvider && web3.currentProvider.close) { - // Close provider - await web3.currentProvider.close(); - } + walClient.getAddresses().then(([addr]) => { + setEmbeddedAddress(addr ?? null); + }); + }, [embedded]); - // Nullify web3 provider and address - setAddress(null); - setWeb3(null); - }; + const address = embedded ? embeddedAddress : wagmiAddress; + const publicClient = embedded ? embeddedPublicClient : wagmiPublicClient; + const walletClient = embedded ? embeddedWalletClient : wagmiWalletClient; - /** - * Checks validity of Ethereum address - * @param {String} address to check - * @returns {Boolean} true if address is valid - */ - const isValidAddress = (address) => { - return web3.utils.isAddress(address); + const authenticate = () => { + if (!embedded) openConnectModal?.(); }; - // On mount - useEffect(() => { - // Setup web3modal - if (!embedded) { - setupWeb3Modal(); + const unauthenticate = () => { + if (embedded) { + setEmbeddedAddress(null); } else { - authenticate(); + disconnect(); } - }, []); + }; + + const isValidAddress = (addr) => isAddress(addr); return { - web3, address, + publicClient, + walletClient, authenticate, unauthenticate, isValidAddress, diff --git a/helpers/database/awaitingTxs.js b/helpers/database/awaitingTxs.js index cd80aee..e8a9e7a 100644 --- a/helpers/database/awaitingTxs.js +++ b/helpers/database/awaitingTxs.js @@ -81,8 +81,9 @@ const insertVoteTx = async (tx) => { * has already submitted a sig for this proposal. * @param {String} address * @param {Number} proposalId + * @param {Number} nonce Nonce to be used */ -const voteAllowed = async (address, proposalId) => { +const voteAllowed = async (address, proposalId, nonce) => { // Collect database connection const { db } = await connectToDatabase(); @@ -91,7 +92,28 @@ const voteAllowed = async (address, proposalId) => { .find({ from: address, executed: false, type: "vote" }) .toArray(); - // If existing transactions, throw error + const existingVoteForProposal = await db + .find({ from: address, proposalId, type: "vote" }) + .toArray(); + + const existingVoteWithNonce = await db + .find({ from: address, nonce, type: "vote" }) + .toArray(); + + if (existingVoteForProposal.length > 0) { + const error = new Error("user has already voted for this proposal"); + error.code = 409; + throw error; + } + + if (existingVoteWithNonce.length > 0) { + const error = new Error( + "user has already voted with this nonce. Please try again later." + ); + error.code = 409; + throw error; + } + if (existingUserTxs.length > 0) { const error = new Error("only one pending vote at a time"); error.code = 409; diff --git a/helpers/index.js b/helpers/index.js index 8469aa4..cc3d2c7 100644 --- a/helpers/index.js +++ b/helpers/index.js @@ -19,7 +19,7 @@ import { import Web3 from "web3"; // Web3 import axios from "axios"; // Axios requests import { recoverTypedSignature } from "@metamask/eth-sig-util"; // EIP-712 sig verification -import { Relayer } from "@openzeppelin/defender-relay-client"; +import { Defender } from "@openzeppelin/defender-sdk"; /** * Instantiates server-side web3 connection @@ -220,8 +220,9 @@ const canDelegate = async (address, delegatee = "0x") => { * Checks if an address can vote by sig for the given proposal * @param {String} address * @param {Number} proposalId + * @param {Number} nonce Nonce to be used for the vote */ -const canVote = async (address, proposalId) => { +const canVote = async (address, proposalId, nonce) => { // Collect Web3 + contracts const { web3, compToken, governorCharlie } = Web3Handler(); @@ -268,7 +269,7 @@ const canVote = async (address, proposalId) => { // Collect current block number web3.eth.getBlockNumber(), // Check if vote is allowed from db - voteAllowed(address, proposalId), + voteAllowed(address, proposalId, nonce), ]); if (currentBlock >= snapshotBlock) { @@ -291,7 +292,7 @@ const canVote = async (address, proposalId) => { } // Not ongoing proposal. Leaves a block buffer for last relay - if (!(currentBlock < endBlock - 2025) || proposalState == 2 /* canceled */) { + if (!(currentBlock < endBlock - 100) || proposalState == 2 /* canceled */) { const error = new Error("proposal voting period is not active"); error.code = 400; throw error; @@ -400,7 +401,7 @@ const vote = async (address, proposalId, support, v, r, s) => { } try { - await canVote(address, proposalId); + await canVote(address, proposalId, data.message.nonce); } catch (error) { // Pass error from db if (typeof error.code == "number") { @@ -422,9 +423,13 @@ const vote = async (address, proposalId, support, v, r, s) => { proposalId, type: "vote", createdAt: new Date(), - executed: false, + executed: true, // will relay immediately, + nonce: data.message.nonce, }; + // Relay TX + const txHash = await relayVote(newTx); + // Insert vote transaction to db await insertVoteTx(newTx); @@ -432,6 +437,37 @@ const vote = async (address, proposalId, support, v, r, s) => { if (typeof process.env.NOTIFICATION_HOOK != "undefined") { await axios.get(process.env.NOTIFICATION_HOOK + "New comp.vote voting sig"); } + + return txHash; +}; + +const relayVote = async (voteSignature) => { + const { governorCharlie } = Web3Handler(); + + const client = new Defender({ + relayerApiKey: process.env.DEFENDER_API_KEY, + relayerApiSecret: process.env.DEFENDER_API_SECRET, + }); + + const web3jsTx = governorCharlie.methods.castVoteBySig( + voteSignature.proposalId, + voteSignature.support, + voteSignature.from, + `${voteSignature.r}${voteSignature.s.substring( + 2 + )}${voteSignature.v.substring(2)}` + ); + + const tx = { + to: GOVERNOR_CHARLIE_ADDRESS, + data: web3jsTx.encodeABI(), + gasLimit: Math.round((await web3jsTx.estimateGas()) * 1.2), + value: 0, + maxPriorityFeePerGas: "500000000", + maxFeePerGas: "50000000000" + }; + + return (await client.relaySigner.sendTransaction(tx)).hash; }; /** diff --git a/lib/wagmi.js b/lib/wagmi.js new file mode 100644 index 0000000..68dec93 --- /dev/null +++ b/lib/wagmi.js @@ -0,0 +1,13 @@ +import { getDefaultConfig } from "@rainbow-me/rainbowkit"; +import { mainnet } from "wagmi/chains"; +import { http } from "wagmi"; + +export const wagmiConfig = getDefaultConfig({ + appName: "comp.vote", + projectId: process.env.NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID, + chains: [mainnet], + transports: { + [mainnet.id]: http(process.env.NEXT_PUBLIC_INFURA_RPC), + }, + ssr: true, +}); diff --git a/next.config.js b/next.config.js index cc695f8..07caea0 100644 --- a/next.config.js +++ b/next.config.js @@ -1,5 +1,9 @@ module.exports = { - future: { - webpack5: false, + webpack: (config) => { + config.resolve.alias = { + ...config.resolve.alias, + '@react-native-async-storage/async-storage': false, + }; + return config; }, }; diff --git a/package.json b/package.json index 91d57e2..78435e4 100644 --- a/package.json +++ b/package.json @@ -6,30 +6,32 @@ "node": "20.x" }, "scripts": { - "dev": "NODE_OPTIONS=\"--openssl-legacy-provider\" next dev", - "build": "NODE_OPTIONS=\"--openssl-legacy-provider\" next build", - "start": "NODE_OPTIONS=\"--openssl-legacy-provider\" next start" + "dev": "next dev", + "build": "next build", + "start": "next start" }, "dependencies": { "@compound-finance/comet-extension": "^0.0.11", "@davatar/react": "^1.8.1", "@ethersproject/providers": "^5.7.2", "@metamask/eth-sig-util": "^4.0.0", - "@openzeppelin/defender-relay-client": "^1.48.0", + "@openzeppelin/defender-sdk": "^2.5.0", + "@rainbow-me/rainbowkit": "^2.0.0", + "@tanstack/react-query": "^5.17.0", "@vercel/analytics": "^1.2.2", - "@walletconnect/web3-provider": "^1.6.6", "axios": "^0.21.0", "dayjs": "^1.9.6", "mongodb": "^3.6.3", - "next": "10.2.3", + "next": "^13.5.6", "nprogress": "^0.2.0", - "react": "17.0.1", - "react-dom": "17.0.1", + "react": "^18.2.0", + "react-dom": "^18.2.0", "react-hamburger-menu": "^1.2.1", "react-spinners": "^0.10.2", "sass": "^1.29.0", "unstated-next": "^1.1.0", - "web3": "1.7.3", - "web3modal": "^1.9.2" + "viem": "^2.9.0", + "wagmi": "^2.5.0", + "web3": "1.7.3" } } diff --git a/pages/_app.js b/pages/_app.js index 05ccecf..864732f 100644 --- a/pages/_app.js +++ b/pages/_app.js @@ -1,24 +1,34 @@ import "styles/global.scss"; // Global styles +import "@rainbow-me/rainbowkit/styles.css"; // RainbowKit styles import Router from "next/router"; // Next Router import nProgress from "nprogress"; // nProgress loading bar import GlobalProvider from "containers"; // Context provider import "node_modules/nprogress/nprogress.css"; // NProgress styles -import { Analytics } from 'node_modules/@vercel/analytics/dist/react/index.js'; +import { Analytics } from "node_modules/@vercel/analytics/dist/react/index.js"; +import { WagmiProvider } from "wagmi"; +import { RainbowKitProvider } from "@rainbow-me/rainbowkit"; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { wagmiConfig } from "../lib/wagmi"; // Router load animations Router.events.on("routeChangeStart", () => nProgress.start()); Router.events.on("routeChangeComplete", () => nProgress.done()); Router.events.on("routeChangeErorr", () => nProgress.done()); +const queryClient = new QueryClient(); + // Application export default function CompVote({ Component, pageProps }) { return ( - // Wrap page in context provider - <> - - - - - + + + + + + + + + + ); } diff --git a/pages/api/governance/proposals.js b/pages/api/governance/proposals.js index f0c60c8..6e2f83b 100644 --- a/pages/api/governance/proposals.js +++ b/pages/api/governance/proposals.js @@ -24,6 +24,7 @@ const statesKey = [ /// Global defining titles for misformatted proposals const MISFORMATTED_PROPOSAL_TITLES = { 380: "[Gauntlet] Supply Cap Recommendations (12/09/24)", + 450: "OpenZeppelin Security Partnership - Annual Renewal 2025", }; const initialProposalBravo = 42; diff --git a/pages/api/vote.js b/pages/api/vote.js index ac594cf..ed223ad 100644 --- a/pages/api/vote.js +++ b/pages/api/vote.js @@ -16,9 +16,10 @@ export default async (req, res) => { vInput = transaction.v; } + let txHash; try { // Send vote - await vote( + txHash = await vote( transaction.address, transaction.proposalId, transaction.support, @@ -35,5 +36,7 @@ export default async (req, res) => { } // Else, return success - res.status(200).end(); + res.status(200).send({ + txHash, + }); }; diff --git a/pages/delegate.js b/pages/delegate.js index b8e4313..bf388f9 100644 --- a/pages/delegate.js +++ b/pages/delegate.js @@ -18,8 +18,8 @@ export default function Delegate({ const [buttonLoading, setButtonLoading] = useState(null); // Delegation button loading state const [accounts, setAccounts] = useState(defaultAccounts); // Accounts array - // Web3 + Authenticate function from context - const { web3, address, authenticate, isValidAddress } = web3p.useContainer(); + // Address + Authenticate function from context + const { address, authenticate, isValidAddress } = web3p.useContainer(); const { currentDelegate, createDelegation } = delegate.useContainer(); /** @@ -117,7 +117,7 @@ export default function Delegate({
{/* Card header */}
-

Addresses by Voting Weight

+

Addresses by Proposals Voted

{/* Card legend */} @@ -208,7 +208,7 @@ export default function Delegate({
- {web3 ? ( + {address ? ( <> {buttonLoading === 0 ? ( - + ) : ( "Delegate" )} @@ -284,7 +284,7 @@ export default function Delegate({ className={styles.info} > {buttonLoading === -1 ? ( - + ) : ( "Self-delegate" )} diff --git a/pages/index.js b/pages/index.js index dcfa4e7..dfe18ac 100644 --- a/pages/index.js +++ b/pages/index.js @@ -56,8 +56,8 @@ function ProposalsContent({ defaultProposals, pages, setPages }) { const [proposals, setProposals] = useState(defaultProposals); // Proposals array const [buttonLoading, setButtonLoading] = useState({ id: null, type: null }); // Current button loading state - // Web3 + Authenticate function from context - const { web3, authenticate } = web3p.useContainer(); + // Address + Authenticate function from context + const { address, authenticate } = web3p.useContainer(); const { voteFor, voteAgainst, voteAbstain } = vote.useContainer(); /** @@ -174,10 +174,9 @@ function ProposalsContent({ defaultProposals, pages, setPages }) { > Info - {proposal.state.value === "Active" || - proposal.state.value === "Pending" ? ( + {proposal.state.value === "Active" ? ( // Check if proposal is active - web3 ? ( + address ? ( // If authenticated and proposal active, return voting + info buttons <>