const { ethers } = require('hardhat'); const { getContractAddressByNetworkName } = require('./contract-config'); const readFromApi = require('./util/read-from-api'); const network = process.env.HARDHAT_NETWORK; let dao; let work1; let onboarding; let account; let validationPools; let reputation; let posts; const fetchReputation = async () => { reputation = await dao.balanceOf(account); console.log(`reputation: ${reputation}`); }; const fetchPost = async (postIndex) => { const { id, sender, author, contentId, } = await dao.posts(postIndex); const { content, embeddedData } = await readFromApi(contentId); const post = { id, sender, author, contentId, content, embeddedData, }; posts[postIndex] = post; return post; }; const fetchValidationPool = async (poolIndex) => { const { id, postIndex, sender, stakeCount, fee, duration, endTime, resolved, outcome, } = await dao.validationPools(poolIndex); const pool = { id, postIndex, sender, stakeCount, fee, duration, endTime, resolved, outcome, }; pool.post = await fetchPost(pool.postIndex); validationPools[poolIndex] = pool; return pool; }; const fetchValidationPools = async () => { const count = await dao.validationPoolCount(); console.log(`validation pool count: ${count}`); const promises = []; validationPools = []; for (let i = 0; i < count; i += 1) { promises.push(fetchValidationPool(i)); } await Promise.all(promises); }; const initialize = async () => { const getContract = (name) => ethers.getContractAt( name, getContractAddressByNetworkName(network, name), ); dao = await getContract('DAO'); work1 = await getContract('Work1'); onboarding = await getContract('Onboarding'); [account] = await ethers.getSigners(); const address = await account.getAddress(); console.log(`account: ${address}`); posts = []; await fetchReputation(); await fetchValidationPools(); }; const poolIsActive = (pool) => { if (new Date() >= new Date(Number(pool.endTime) * 1000)) return false; if (pool.resolved) return false; return true; }; const poolIsValidWorkContract = (pool) => { switch (pool.sender) { case getContractAddressByNetworkName(network, 'Work1'): { // If this is a valid work evidence // TODO: Can we decode from the post, a reference to the work request? // The work request does have its own contentId, the work contract has that // under availabilityStakes const expectedContent = 'This is a work evidence post'; return pool.post.content.startsWith(expectedContent); } case getContractAddressByNetworkName(network, 'Onboarding'): { const expectedContent = 'This is an onboarding work evidence post'; return pool.post.content.startsWith(expectedContent); } default: return false; } }; const poolIsProposal = (pool) => pool.sender === getContractAddressByNetworkName(network, 'Proposals'); const getPoolStatus = (pool) => { if (poolIsActive(pool)) return 'Active'; if (!pool.resolved) return 'Ready to Evaluate'; if (pool.outcome) return 'Accepted'; return 'Rejected'; }; const stake = async (pool, amount, inFavor) => { console.log(`staking ${amount} ${inFavor ? 'in favor of' : 'against'} pool ${pool.id.toString()}`); await dao.stake(pool.id, amount, true); await fetchReputation(); }; const conditionalStake = async (pool, amountToStake) => { if (poolIsValidWorkContract(pool)) { await stake(pool, amountToStake, true); } else if (poolIsProposal(pool)) { // We leave these alone at the moment. // We could consider automatic followup staking, // as a convenience if you decide early to favor a proposal } else { console.log('Unrecognized sender %s', pool.sender); await stake(pool, amountToStake, false); } }; const conditionalStakeEach = async (pools, amountPerPool) => { const promises = []; pools.forEach(async (pool) => { promises.push(conditionalStake(pool, amountPerPool)); }); await Promise.all(promises); }; const printPool = (pool) => { const dataStr = pool.post?.embeddedData ? `data: ${JSON.stringify(pool.post.embeddedData)},\n ` : ''; console.log(`pool ${pool.id.toString()}\n ` + `status: ${getPoolStatus(pool)},\n ` + `is valid work contract: ${poolIsValidWorkContract(pool)},\n ` + `is proposal: ${poolIsProposal(pool)},\n ` + `${dataStr}post content: ${pool.post?.content}`); }; async function main() { await initialize(); validationPools.forEach(printPool); // Stake half of available reputation on any active pools const activePools = validationPools.filter(poolIsActive); if (activePools.length && reputation > 0) { const amountPerPool = reputation / BigInt(2) / BigInt(activePools.length); await conditionalStakeEach(activePools, amountPerPool); } // Listen for new validation pools dao.on('ValidationPoolInitiated', async (poolIndex) => { const pool = await fetchValidationPool(poolIndex); printPool(pool); await fetchReputation(); if (!reputation) return; const amountToStake = reputation / BigInt(2); await conditionalStake(pool, amountToStake); }); dao.on('ValidationPoolResolved', async (poolIndex, votePasses) => { console.log(`pool ${poolIndex} resolved, status: ${votePasses ? 'accepted' : 'rejected'}`); fetchValidationPool(poolIndex); fetchReputation(); }); work1.on('AvailabilityStaked', async () => { fetchReputation(); }); onboarding.on('AvailabilityStaked', async () => { fetchReputation(); }); } main().catch((error) => { console.error(error); process.exitCode = 1; });