diff --git a/dapp/src/App.tsx b/dapp/src/App.tsx index f991001..7c4dd43 100644 --- a/dapp/src/App.tsx +++ b/dapp/src/App.tsx @@ -1,15 +1,14 @@ import React, { useState, useEffect } from 'react' import { Route, Link, Switch, HashRouter as Router } from 'react-router-dom' import { ThemeProvider } from '@material-ui/core/styles' -import EmbarkJS from './embarkArtifacts/embarkjs' import theme from './styles/theme' import useStyles from './styles/app' -import { SetAccount } from './types' import Header from './components/Header' import CreatePoll from './components/CreatePoll' import ListPolls from './components/ListPolls' import Poll from './components/Poll' import { MessagesProvider } from './context/messages/context' +import { grabAddress, enableEthereum } from './utils/network' declare global { interface Window { @@ -18,41 +17,6 @@ declare global { } } -function grabAddress(setAccount: SetAccount): void { - if (window.ethereum) { - accountListener(setAccount) - const { selectedAddress: account } = window.ethereum - if (account) setAccount(account) - } else { - console.log('window.ethereum not found :', {window}) - } -} - -function accountListener(setAccount: SetAccount): void { - // Not supported in status. Metamask supported - try { - window.ethereum.on('accountsChanged', function (accounts: string[]) { - const [account] = accounts - setAccount(account) - }) - } catch (error) { - console.error('accountsChanged listener : ', {error}) - } -} - -async function enableEthereum(setAccount: SetAccount): Promise { - try { - const accounts = await EmbarkJS.enableEthereum(); - const account = accounts[0] - setAccount(account) - // TODO get balances across all relvant tokens - //this.getAndSetBalances(account) - return account - } catch (error) { - console.error('Enable Ethereum :', {error}) - } -} - function App() { const classes: any = useStyles() const [account, setAccount] = useState('') diff --git a/dapp/src/components/Poll.tsx b/dapp/src/components/Poll.tsx index 3265282..b6fa595 100644 --- a/dapp/src/components/Poll.tsx +++ b/dapp/src/components/Poll.tsx @@ -1,4 +1,4 @@ -import React, { Fragment, useContext } from 'react' +import React, { Fragment, useContext, useEffect } from 'react' import useStyles from '../styles/poll' import { useParams } from "react-router-dom" import { MessagesContext } from '../context/messages/context' @@ -6,6 +6,80 @@ import Typography from '@material-ui/core/Typography' import TextField from '@material-ui/core/TextField' import MenuItem from '@material-ui/core/MenuItem' import { Formik, FormikProps } from 'formik' +import StatusButton from './base/Button' +import { sendToPublicChat } from '../utils/status' +import { prettySign } from '../utils/signing' +import Divider from '@material-ui/core/Divider' +import { + gotoPublicChat, + getChatMessages, + useChatMessages +} from '../utils/status' +import { verifyMessages } from '../utils/messages' +import { Topics, IAccountSnapshotQuery, IBalanceByAddress } from '../types' +import { ApolloClient, InMemoryCache } from '@apollo/client' +import { getAccountBalances } from '../queries' + +const SNT_ROPSTEN_SUBGRAPH = 'https://api.thegraph.com/subgraphs/name/bgits/status-snt' + +const client = new ApolloClient({ + uri: SNT_ROPSTEN_SUBGRAPH, + cache: new InMemoryCache() +}) + +async function gotoPoll(channel: string) { + await gotoPublicChat(channel) + getChatMessages() +} + +async function verifyEnrichMessages(topics: Topics, rawKey: string, setState: Function) { + const messages = { [rawKey]: topics[rawKey] } + const verified = await verifyMessages(messages) + if (!verified) return + const enriched = await enrichVotes(verified) + const merged = { ...topics, ...enriched } + setState(merged) +} + +async function enrichVotes(topics: Topics): Promise { + const keys = Object.keys(topics) + const accounts: string[] = [] + keys.map(k => { + const messages = topics[k] + messages.map(message => { + const { sigMsg } = message + if (!sigMsg || !sigMsg.address) return + // subgraph references all addresses as lowercase + accounts.push(sigMsg.address.toLowerCase()) + }) + }) + const query = await client.query({ + query: getAccountBalances, + variables: { + accounts + } + }) + const { data: { accountBalanceSnapshots } } = query + const balancesByAddress: IBalanceByAddress = {} + accountBalanceSnapshots.map((res: IAccountSnapshotQuery) => { + const { account } = res + balancesByAddress[account.id] = res + }) + + const enrichedTopics: Topics = {} + keys.map(k => { + const messages = topics[k] + + enrichedTopics[k] = messages.map(message => { + const { sigMsg } = message + if (!sigMsg || !sigMsg.address) return message + const address = sigMsg.address.toLowerCase() + const accountSnapshot: IAccountSnapshotQuery = balancesByAddress[address] + return { ...message, accountSnapshot } + }) + }) + return enrichedTopics +} type IBallot = { option: string @@ -13,29 +87,36 @@ type IBallot = { function Poll() { const { id } = useParams() + const topic = `poll-${id}` const classes: any = useStyles() + const [rawMessages] = useChatMessages() const messagesContext = useContext(MessagesContext) - const { chatMessages } = messagesContext - console.log({chatMessages}) + const { chatMessages, dispatchSetTopics } = messagesContext + useEffect(() => { + const merged = { ...chatMessages, ...rawMessages } + if(dispatchSetTopics && rawMessages) verifyEnrichMessages(merged, topic, dispatchSetTopics) + }, [rawMessages]) + if (!chatMessages) return const selectedPoll= chatMessages['polls'].find(p => p.messageId === id) if (!selectedPoll || !selectedPoll.pollInfo) return const { pollInfo } = selectedPoll const { description, title, subtitle, pollOptions } = pollInfo const options = pollOptions.split(',') - // TODO Display ^above data - // TODO Add method to vote - //TODO vote options are drop down menu // TODO Add method to tabulate votes - console.log({selectedPoll, options}) + // get all votes, filter by messageId, filter out not verified, grab all addresses, get balances, enrich votes with balances + console.log({messagesContext, rawMessages}) return ( { - console.log({values}) + onSubmit={async (values) => { + const signed = await prettySign(values.option) + const stringified = JSON.stringify(signed) + await sendToPublicChat(topic, stringified) + console.log({values, signed, topic}) }} > {({ @@ -65,6 +146,18 @@ function Poll() { ))} + + + gotoPoll(topic)} + /> )} } diff --git a/dapp/src/components/base/Button.tsx b/dapp/src/components/base/Button.tsx index a12a69c..0987144 100644 --- a/dapp/src/components/base/Button.tsx +++ b/dapp/src/components/base/Button.tsx @@ -46,16 +46,18 @@ type ButtonProps = { buttonText: string, confirmed?: boolean, loading?: boolean, - onClick: any + onClick: any, + type?: "button" | "reset" | "submit" } function StatusButton(props: ButtonProps) { const { className, disabled, buttonText, confirmed, loading, onClick } = props const classes = useStyles() const { check, formButton, disabledButton, buttonContent, progress } = classes + const buttonType = props.type ? props.type : 'submit' return ( -