import { useEffect, useRef, useState } from 'react'; import { useSDK } from '@metamask/sdk-react'; import { Web3 } from 'web3'; import Button from 'react-bootstrap/Button'; // import './App.css'; import DAOArtifact from './assets/DAO.json'; // import work1Artifact from './assets/Work1.json'; const contracts = { '0x539': { // Hardhat DAO: '0x8d914D38dD301FC4606f5aa9fEcF8A76389020d3', Work1: '0x050C420Cc4995B41217Eba1B54B82Fd5687e9139', }, '0xaa36a7': { // Sepolia DAO: '0x8F00038542C87A5eAf18d5938B7723bF2A04A4e4', Work1: '0x42b79f8d8408c36aD4347ab72f826684440a7a8F', }, }; function App() { const { sdk, connected, provider, chainId, account, balance, } = useSDK(); const [DAO, setDAO] = useState(); // const [work1, setWork1] = useState(); // const [work1Price, setWork1Price] = useState(); const [balanceEther, setBalanceEther] = 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({ // method: 'wallet_watchAsset', // params: { // type: 'ERC20', // options: { // address: DAOAddress, // }, // }, // }); // }, [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; const web3 = new Web3(provider); const DAOContract = new web3.eth.Contract(DAOArtifact.abi, contracts[chainId].DAO); // const work1Contract = new web3.eth.Contract(work1Artifact.abi, contracts[chainId].Work1); // const fetchPrice = async () => { // }; const fetchReputation = async () => { reputation.current = Number(await DAOContract.methods.balanceOf(account).call()); setTotalReputation(await DAOContract.methods.totalSupply().call()); }; const fetchPosts = async () => { const count = await DAOContract.methods.postCount().call(); const promises = []; for (let i = 0; i < count; i += 1) { promises.push(DAOContract.methods.posts(i).call()); } const fetchedPosts = await Promise.all(promises); 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)).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); }; // fetchPrice(); fetchReputation(); fetchPosts(); fetchValidationPools(); // setWork1(work1Contract); setDAO(DAOContract); DAOContract.events.PostAdded({ fromBlock: 'latest' }).on('data', (event) => { console.log('event: post added', event); fetchPosts(); }); DAOContract.events.ValidationPoolInitiated({ fromBlock: 'latest' }).on('data', (event) => { console.log('event: validation pool initiated', event); fetchValidationPools(); }); DAOContract.events.ValidationPoolResolved({ fromBlock: 'latest' }).on('data', (event) => { console.log('event: validation pool resolved', event); fetchReputation(); fetchValidationPools(); }); }, [provider, account, chainId, reputation]); useEffect(() => { if (!provider || balance === undefined) return; const web3 = new Web3(provider); setBalanceEther(web3.utils.fromWei(balance, 'ether')); }, [provider, balance]); const connect = async () => { try { await sdk?.connect(); } catch (err) { console.warn('failed to connect..', err); } }; const disconnect = async () => { try { sdk?.terminate(); } catch (err) { console.warn('failed to disconnect..', err); } }; const addPost = async () => { await DAO.methods.addPost(account).send({ from: account, gas: 1000000, }); }; const initiateValidationPool = async (postIndex) => { const poolDuration = 3600; await DAO.methods.initiateValidationPool(postIndex, poolDuration).send({ from: account, gas: 1000000, value: 100, }); }; const evaluateOutcome = async (poolIndex) => { await DAO.methods.evaluateOutcome(poolIndex).send({ from: account, gas: 1000000, }); }; // const stakeAvailability = async () => { } // const requestWork = async () => { // work1.events.WorkAssigned(() => { // console.log('event callback'); // }); // await work1.methods.requestWork().send({ // from: account, // gas: 1000000, // }); // }; return ( <> {!connected && } {connected && ( <>
{!contracts[chainId] && (
Please switch MetaMask to Sepolia testnet!
)}
{chainId && `Chain ID: ${chainId}`}
{`Account: ${account}`}
{`Balance: ${balanceEther} ETH`}
{`Your REP: ${reputation.current}`}
{`Total REP: ${totalReputation}`}
{`Posts count: ${posts.length}`}
{posts.map((post) => ( ))}
ID Author Actions
{post.id.toString()} {post.author}
{`Validation Pool Count: ${validationPools.length}`}
{validationPools.map((pool) => ( ))}
ID Post ID Fee Duration End Time Stake
Count
Status Actions
{pool.id.toString()} {pool.postIndex.toString()} {pool.fee.toString()} {pool.duration.toString()} {new Date(Number(pool.endTime) * 1000).toLocaleString()} {pool.stakeCount.toString()} {pool.status} {!pool.resolved && ( )}
{ /* `Work1 Price: ${work1Price} ETH` */ }
{ /* */ }
)} ); } export default App;