diff --git a/client/src/App.jsx b/client/src/App.jsx index 156cef6..b2c187e 100644 --- a/client/src/App.jsx +++ b/client/src/App.jsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from 'react'; +import { useEffect, useRef, useState } from 'react'; import { useSDK } from '@metamask/sdk-react'; import { Web3 } from 'web3'; import Button from 'react-bootstrap/Button'; @@ -8,8 +8,8 @@ import DAOArtifact from './assets/DAO.json'; const contracts = { '0x539': { // Hardhat - DAO: '0x63472674239ffb70618Fae043610917f2d9B781C', - Work1: '0xC62b0b16B3ef06c417BFC4Fb02E0Da06aF5A95Ef', + DAO: '0x8d914D38dD301FC4606f5aa9fEcF8A76389020d3', + Work1: '0x050C420Cc4995B41217Eba1B54B82Fd5687e9139', }, '0xaa36a7': { // Sepolia DAO: '0x8F00038542C87A5eAf18d5938B7723bF2A04A4e4', @@ -26,10 +26,12 @@ function App() { // const [work1, setWork1] = useState(); // const [work1Price, setWork1Price] = useState(); const [balanceEther, setBalanceEther] = useState(); - const [reputation, setReputation] = useState(); + // const [reputation, setReputation] = useState(); + const reputation = useRef(); const [totalReputation, setTotalReputation] = useState(); const [posts, setPosts] = useState([]); const [validationPools, setValidationPools] = useState([]); + const stakedPools = useRef([]); // const watchReputationToken = useCallback(async () => { // await provider.request({ @@ -43,6 +45,14 @@ function App() { // }); // }, [provider]); + const getStatus = (pool) => { + if (pool.resolved) { + return pool.outcome ? 'Accepted' : 'Rejected'; + } + const endDate = new Date(Number(pool.endTime) * 1000); + return new Date() < endDate ? 'In Progress' : 'Ready to Evaluate'; + }; + useEffect(() => { if (!provider || !chainId || !account) return; if (!contracts[chainId]) return; @@ -51,12 +61,10 @@ function App() { // const work1Contract = new web3.eth.Contract(work1Artifact.abi, contracts[chainId].Work1); // const fetchPrice = async () => { - // const priceWei = await work1Contract.methods.price().call(); - // setWork1Price(web3.utils.fromWei(priceWei, 'ether')); // }; const fetchReputation = async () => { - setReputation(await DAOContract.methods.balanceOf(account).call()); + reputation.current = Number(await DAOContract.methods.balanceOf(account).call()); setTotalReputation(await DAOContract.methods.totalSupply().call()); }; @@ -70,13 +78,60 @@ function App() { setPosts(fetchedPosts); }; + const stake = async (poolIndex, amount, inFavor) => { + console.log(`Attempting to stake ${amount} ${inFavor ? 'for' : 'against'} pool ${poolIndex}`); + await DAOContract.methods.stake(poolIndex, amount, inFavor).send({ + from: account, + gas: 1000000, + }); + + // Since this is the result we expect from the server, we preemptively set it here. + // We can let this value be negative -- this would just mean we'll be getting + // at least one error from the server, and a corrected reputation. + reputation.current = Number(reputation.current) - Number(stake); + }; + + const stakeAllInFavor = (poolIndex) => stake(poolIndex, reputation.current, true); + const fetchValidationPools = async () => { + // TODO: Pagination + // TODO: Memoization + // TODO: Caching const count = await DAOContract.methods.validationPoolCount().call(); const promises = []; for (let i = 0; i < count; i += 1) { promises.push(DAOContract.methods.validationPools(i).call()); } - const pools = await Promise.all(promises); + const pools = (await Promise.all(promises)).map((p) => { + const pool = p; + pool.status = getStatus(pool); + const timeRemaining = new Date(Number(pool.endTime) * 1000) - new Date(); + if (timeRemaining > 0 && !pool.resolved && reputation.current + && !stakedPools.current.includes(pool.id)) { + // Naievely stake all reputation that this validation pool is valid. + // This is the greediest possible strategy. + // Staking reputation transfers it, thus it's important we update our reputation + // locally before hearing back from the server -- since blockchains are slow. + // Note that this means refresing the page will re-send any pending staking operations. + stakeAllInFavor(pool.id); + stakedPools.current = stakedPools.current.concat(pool.id); + } + + // TODO: When remaing time expires, we want to update the status for this pool + // if (timeRemaining > 0) { + // setTimeout(() => { + // pool.status = getStatus(pool); + // setValidationPools((currentPools) => { + // const newPools = currentPools; + // newPools[pool.id] = pool; + // return newPools; + // }); + // console.log(`attepted to update pool status: ${pool.status}`); + // }, timeRemaining); + // } + + return pool; + }); setValidationPools(pools); }; @@ -102,7 +157,7 @@ function App() { fetchReputation(); fetchValidationPools(); }); - }, [provider, account, chainId]); + }, [provider, account, chainId, reputation]); useEffect(() => { if (!provider || balance === undefined) return; @@ -134,7 +189,7 @@ function App() { }; const initiateValidationPool = async (postIndex) => { - const poolDuration = 0; + const poolDuration = 60; await DAO.methods.initiateValidationPool(postIndex, poolDuration).send({ from: account, gas: 1000000, @@ -183,7 +238,7 @@ function App() { {`Balance: ${balanceEther} ETH`}
- {`Your REP: ${reputation}`} + {`Your REP: ${reputation.current}`}
{`Total REP: ${totalReputation}`} @@ -232,6 +287,11 @@ function App() { Fee Duration End Time + + Stake +
+ Count + Status Actions @@ -244,11 +304,8 @@ function App() { {pool.fee.toString()} {pool.duration.toString()} {new Date(Number(pool.endTime) * 1000).toLocaleString()} - - {pool.resolved && (pool.outcome ? 'Accepted' : 'Rejected')} - {!pool.resolved && new Date() < new Date(Number(pool.endTime) * 1000) && 'In Progress'} - {!pool.resolved && new Date() >= new Date(Number(pool.endTime) * 1000) && 'Ready to Evaluate'} - + {pool.stakeCount.toString()} + {pool.status} {!pool.resolved && (