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 */}
@@ -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
<>