Add proposal creation and list (#61)
This commit is contained in:
parent
e492997c83
commit
26cb1823d9
|
@ -12,7 +12,7 @@ const deploy = async () => {
|
|||
const provider = ethers.getDefaultProvider(process.env.ETHEREUM_PROVIDER)
|
||||
const wallet = new ethers.Wallet(privateKey, provider)
|
||||
|
||||
const votingContract = await deployContract(wallet, VotingContract,[process.env.ETHEREUM_TOKEN_ADDRESS])
|
||||
const votingContract = await deployContract(wallet, VotingContract,[process.env.ETHEREUM_TOKEN_ADDRESS,1000])
|
||||
console.log(`Voting contract deployed with address: ${votingContract.address}`)
|
||||
|
||||
}
|
||||
|
|
|
@ -41,6 +41,7 @@
|
|||
"typescript": "^4.3.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"@status-waku-voting/contracts": "^0.0.1",
|
||||
"eth-sig-util": "^3.0.1",
|
||||
"ethers": "^5.4.4",
|
||||
"js-waku": "^0.11.0",
|
||||
|
|
|
@ -0,0 +1,83 @@
|
|||
import { Waku } from 'js-waku'
|
||||
import { WakuMessage } from 'js-waku'
|
||||
import { Provider } from '@ethersproject/providers'
|
||||
|
||||
type WakuMessageStore = {
|
||||
topic: string
|
||||
hashMap: { [id: string]: boolean }
|
||||
arr: any[]
|
||||
updateFunction: (msg: WakuMessage[]) => void
|
||||
}
|
||||
|
||||
type WakuMessageStores = {
|
||||
[messageType: string]: WakuMessageStore
|
||||
}
|
||||
|
||||
export class WakuMessaging {
|
||||
protected appName: string
|
||||
protected waku: Waku
|
||||
public tokenAddress: string
|
||||
protected provider: Provider
|
||||
protected chainId = 0
|
||||
protected wakuMessages: WakuMessageStores = {}
|
||||
protected observers: { callback: (msg: WakuMessage) => void; topics: string[] }[] = []
|
||||
|
||||
protected constructor(appName: string, tokenAddress: string, waku: Waku, provider: Provider, chainId: number) {
|
||||
this.appName = appName
|
||||
this.tokenAddress = tokenAddress
|
||||
this.waku = waku
|
||||
this.provider = provider
|
||||
this.chainId = chainId
|
||||
}
|
||||
|
||||
public cleanUp() {
|
||||
this.observers.forEach((observer) => this.waku.relay.deleteObserver(observer.callback, observer.topics))
|
||||
this.wakuMessages = {}
|
||||
}
|
||||
|
||||
protected async setObserver() {
|
||||
await Promise.all(
|
||||
Object.values(this.wakuMessages).map(async (msgObj) => {
|
||||
const storeMessages = await this.waku?.store.queryHistory([msgObj.topic])
|
||||
if (storeMessages) {
|
||||
msgObj.updateFunction(storeMessages)
|
||||
}
|
||||
this.waku.relay.addObserver((msg) => msgObj.updateFunction([msg]), [msgObj.topic])
|
||||
this.observers.push({ callback: (msg) => msgObj.updateFunction([msg]), topics: [msgObj.topic] })
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
protected decodeMsgAndSetArray<T extends { id: string; timestamp: number }>(
|
||||
messages: WakuMessage[],
|
||||
decode: (payload: Uint8Array | undefined, timestamp: Date | undefined, chainId: number) => T | undefined,
|
||||
msgObj: WakuMessageStore,
|
||||
filterFunction?: (e: T) => boolean
|
||||
) {
|
||||
messages
|
||||
.map((msg) => decode(msg.payload, msg.timestamp, this.chainId))
|
||||
.sort((a, b) => ((a?.timestamp ?? new Date(0)) > (b?.timestamp ?? new Date(0)) ? 1 : -1))
|
||||
.forEach((e) => {
|
||||
if (e) {
|
||||
if (filterFunction ? filterFunction(e) : true && !msgObj.hashMap?.[e.id]) {
|
||||
msgObj.arr.unshift(e)
|
||||
msgObj.hashMap[e.id] = true
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
protected async sendWakuMessage<T extends { encode: () => Uint8Array | undefined; timestamp: number }>(
|
||||
msgObj: WakuMessageStore,
|
||||
decodedMsg: T | undefined
|
||||
) {
|
||||
const payload = decodedMsg?.encode()
|
||||
if (payload && decodedMsg) {
|
||||
const wakuMessage = await WakuMessage.fromBytes(payload, msgObj.topic, {
|
||||
timestamp: new Date(decodedMsg.timestamp),
|
||||
})
|
||||
await this.waku?.relay.send(wakuMessage)
|
||||
msgObj.updateFunction([wakuMessage])
|
||||
}
|
||||
}
|
||||
}
|
|
@ -7,7 +7,7 @@ import { WakuMessage } from 'js-waku'
|
|||
import { TimedPollVoteMsg } from '../models/TimedPollVoteMsg'
|
||||
import { DetailedTimedPoll } from '../models/DetailedTimedPoll'
|
||||
import { createWaku } from '../utils/createWaku'
|
||||
import { WakuVoting } from './WakuVoting'
|
||||
import { WakuMessaging } from './WakuMessaging'
|
||||
import { Provider } from '@ethersproject/providers'
|
||||
import { Contract } from '@ethersproject/contracts'
|
||||
import { Interface } from '@ethersproject/abi'
|
||||
|
@ -23,7 +23,7 @@ export enum MESSEGAGE_SENDING_RESULT {
|
|||
pollNotFound = 3,
|
||||
}
|
||||
|
||||
export class WakuPolling extends WakuVoting {
|
||||
export class WakuPolling extends WakuMessaging {
|
||||
protected multicall: string
|
||||
|
||||
protected constructor(
|
||||
|
|
|
@ -1,83 +1,102 @@
|
|||
import { Waku } from 'js-waku'
|
||||
import { WakuMessage } from 'js-waku'
|
||||
import { VotingContract } from '@status-waku-voting/contracts/abi'
|
||||
import { WakuMessaging } from './WakuMessaging'
|
||||
import { Contract, Wallet, BigNumber } from 'ethers'
|
||||
import { Waku, WakuMessage } from 'js-waku'
|
||||
import { Provider } from '@ethersproject/abstract-provider'
|
||||
import { createWaku } from '../utils/createWaku'
|
||||
import { Provider } from '@ethersproject/providers'
|
||||
import { JsonRpcSigner } from '@ethersproject/providers'
|
||||
import { VoteMsg } from '../models/VoteMsg'
|
||||
|
||||
type WakuMessageStore = {
|
||||
topic: string
|
||||
hashMap: { [id: string]: boolean }
|
||||
arr: any[]
|
||||
updateFunction: (msg: WakuMessage[]) => void
|
||||
}
|
||||
const ABI = [
|
||||
'function aggregate(tuple(address target, bytes callData)[] calls) view returns (uint256 blockNumber, bytes[] returnData)',
|
||||
]
|
||||
|
||||
type WakuMessageStores = {
|
||||
[messageType: string]: WakuMessageStore
|
||||
}
|
||||
export class WakuVoting extends WakuMessaging {
|
||||
private multicall: Contract
|
||||
private votingContract: Contract
|
||||
|
||||
export class WakuVoting {
|
||||
protected appName: string
|
||||
protected waku: Waku
|
||||
public tokenAddress: string
|
||||
protected provider: Provider
|
||||
protected chainId = 0
|
||||
protected wakuMessages: WakuMessageStores = {}
|
||||
protected observers: { callback: (msg: WakuMessage) => void; topics: string[] }[] = []
|
||||
protected constructor(appName: string, tokenAddress: string, waku: Waku, provider: Provider, chainId: number) {
|
||||
this.appName = appName
|
||||
this.tokenAddress = tokenAddress
|
||||
this.waku = waku
|
||||
this.provider = provider
|
||||
this.chainId = chainId
|
||||
constructor(
|
||||
appName: string,
|
||||
votingContract: Contract,
|
||||
token: string,
|
||||
waku: Waku,
|
||||
provider: Provider,
|
||||
chainId: number,
|
||||
multicallAddress: string
|
||||
) {
|
||||
super(appName, token, waku, provider, chainId)
|
||||
this.votingContract = votingContract
|
||||
this.multicall = new Contract(multicallAddress, ABI, this.provider)
|
||||
this.wakuMessages['vote'] = {
|
||||
topic: `/${this.appName}/waku-voting/votes/proto/`,
|
||||
hashMap: {},
|
||||
arr: [],
|
||||
updateFunction: (msg: WakuMessage[]) =>
|
||||
this.decodeMsgAndSetArray(
|
||||
msg,
|
||||
(payload, timestamp, chainId) => VoteMsg.decode(payload, timestamp, chainId, this.votingContract.address),
|
||||
this.wakuMessages['vote']
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
public cleanUp() {
|
||||
this.observers.forEach((observer) => this.waku.relay.deleteObserver(observer.callback, observer.topics))
|
||||
this.wakuMessages = {}
|
||||
}
|
||||
|
||||
protected async setObserver() {
|
||||
await Promise.all(
|
||||
Object.values(this.wakuMessages).map(async (msgObj) => {
|
||||
const storeMessages = await this.waku?.store.queryHistory([msgObj.topic])
|
||||
if (storeMessages) {
|
||||
msgObj.updateFunction(storeMessages)
|
||||
}
|
||||
this.waku.relay.addObserver((msg) => msgObj.updateFunction([msg]), [msgObj.topic])
|
||||
this.observers.push({ callback: (msg) => msgObj.updateFunction([msg]), topics: [msgObj.topic] })
|
||||
})
|
||||
public static async create(
|
||||
appName: string,
|
||||
contractAddress: string,
|
||||
provider: Provider,
|
||||
multicall: string,
|
||||
waku?: Waku
|
||||
) {
|
||||
const network = await provider.getNetwork()
|
||||
const votingContract = new Contract(contractAddress, VotingContract.abi, provider)
|
||||
const tokenAddress = await votingContract.token()
|
||||
return new WakuVoting(
|
||||
appName,
|
||||
votingContract,
|
||||
tokenAddress,
|
||||
await createWaku(waku),
|
||||
provider,
|
||||
network.chainId,
|
||||
multicall
|
||||
)
|
||||
}
|
||||
|
||||
protected decodeMsgAndSetArray<T extends { id: string; timestamp: number }>(
|
||||
messages: WakuMessage[],
|
||||
decode: (payload: Uint8Array | undefined, timestamp: Date | undefined, chainId: number) => T | undefined,
|
||||
msgObj: WakuMessageStore,
|
||||
filterFunction?: (e: T) => boolean
|
||||
public async createVote(
|
||||
signer: JsonRpcSigner | Wallet,
|
||||
question: string,
|
||||
descripiton: string,
|
||||
tokenAmount: BigNumber
|
||||
) {
|
||||
messages
|
||||
.map((msg) => decode(msg.payload, msg.timestamp, this.chainId))
|
||||
.sort((a, b) => ((a?.timestamp ?? new Date(0)) > (b?.timestamp ?? new Date(0)) ? 1 : -1))
|
||||
.forEach((e) => {
|
||||
if (e) {
|
||||
if (filterFunction ? filterFunction(e) : true && !msgObj.hashMap?.[e.id]) {
|
||||
msgObj.arr.unshift(e)
|
||||
msgObj.hashMap[e.id] = true
|
||||
}
|
||||
}
|
||||
})
|
||||
this.votingContract = await this.votingContract.connect(signer)
|
||||
await this.votingContract.initializeVotingRoom(question, descripiton, tokenAmount)
|
||||
}
|
||||
|
||||
protected async sendWakuMessage<T extends { encode: () => Uint8Array | undefined; timestamp: number }>(
|
||||
msgObj: WakuMessageStore,
|
||||
decodedMsg: T | undefined
|
||||
) {
|
||||
const payload = decodedMsg?.encode()
|
||||
if (payload && decodedMsg) {
|
||||
const wakuMessage = await WakuMessage.fromBytes(payload, msgObj.topic, {
|
||||
timestamp: new Date(decodedMsg.timestamp),
|
||||
})
|
||||
await this.waku?.relay.send(wakuMessage)
|
||||
msgObj.updateFunction([wakuMessage])
|
||||
private lastPolls: any[] = []
|
||||
private lastGetPollsBlockNumber = 0
|
||||
|
||||
public async getVotes() {
|
||||
const blockNumber = await this.provider.getBlockNumber()
|
||||
if (blockNumber != this.lastGetPollsBlockNumber) {
|
||||
this.lastGetPollsBlockNumber = blockNumber
|
||||
this.lastPolls = await this.votingContract.getVotingRooms()
|
||||
}
|
||||
return this.lastPolls
|
||||
}
|
||||
|
||||
public async sendVote(
|
||||
signer: JsonRpcSigner | Wallet,
|
||||
roomId: number,
|
||||
selectedAnswer: number,
|
||||
tokenAmount: BigNumber
|
||||
) {
|
||||
const vote = await VoteMsg._createWithSignFunction(
|
||||
signer,
|
||||
roomId,
|
||||
selectedAnswer,
|
||||
this.chainId,
|
||||
tokenAmount,
|
||||
this.votingContract.address
|
||||
)
|
||||
await this.sendWakuMessage(this.wakuMessages['vote'], vote)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { WakuPolling } from './classes/WakuPolling'
|
||||
import { WakuMessaging } from './classes/WakuMessaging'
|
||||
import { WakuVoting } from './classes/WakuVoting'
|
||||
|
||||
export { WakuVoting, WakuPolling }
|
||||
export { WakuMessaging, WakuPolling, WakuVoting }
|
||||
|
|
|
@ -0,0 +1,163 @@
|
|||
import { utils } from 'ethers'
|
||||
import protons, { Vote } from 'protons'
|
||||
import { BigNumber, Wallet } from 'ethers'
|
||||
import { JsonRpcSigner } from '@ethersproject/providers'
|
||||
import { createSignFunction } from '../utils/createSignFunction'
|
||||
import { verifySignature } from '../utils/verifySignature'
|
||||
|
||||
const proto = protons(`
|
||||
message Vote {
|
||||
bytes voter = 1;
|
||||
int64 timestamp = 2;
|
||||
int64 answer = 3;
|
||||
bytes roomId = 4;
|
||||
bytes tokenAmount = 5;
|
||||
bytes signature = 6;
|
||||
}
|
||||
`)
|
||||
|
||||
type Message = {
|
||||
roomIdAndType: string
|
||||
tokenAmount: string
|
||||
voter: string
|
||||
}
|
||||
|
||||
export function createSignMsgParams(message: Message, chainId: number, verifyingContract: string) {
|
||||
const msgParams: any = {
|
||||
domain: {
|
||||
name: 'Waku proposal',
|
||||
version: '1',
|
||||
chainId,
|
||||
verifyingContract,
|
||||
},
|
||||
message: {
|
||||
...message,
|
||||
},
|
||||
primaryType: 'Vote',
|
||||
types: {
|
||||
EIP712Domain: [
|
||||
{ name: 'name', type: 'string' },
|
||||
{ name: 'version', type: 'string' },
|
||||
{ name: 'chainId', type: 'uint256' },
|
||||
{ name: 'verifyingContract', type: 'address' },
|
||||
],
|
||||
Vote: [
|
||||
{ name: 'roomIdAndType', type: 'string' },
|
||||
{ name: 'tokenAmount', type: 'string' },
|
||||
{ name: 'voter', type: 'string' },
|
||||
],
|
||||
},
|
||||
}
|
||||
return msgParams
|
||||
}
|
||||
|
||||
export class VoteMsg {
|
||||
public roomId: number
|
||||
public voter: string
|
||||
public timestamp: number
|
||||
public answer: number
|
||||
public tokenAmount: BigNumber
|
||||
public signature: string
|
||||
public id: string
|
||||
public chainId: number
|
||||
|
||||
constructor(
|
||||
signature: string,
|
||||
roomId: number,
|
||||
voter: string,
|
||||
answer: number,
|
||||
tokenAmount: BigNumber,
|
||||
chainId: number,
|
||||
timestamp: number
|
||||
) {
|
||||
this.id = utils.id([voter, timestamp, signature].join())
|
||||
this.roomId = roomId
|
||||
this.voter = voter
|
||||
this.timestamp = timestamp
|
||||
this.answer = answer
|
||||
this.tokenAmount = tokenAmount
|
||||
this.signature = signature
|
||||
this.chainId = chainId
|
||||
}
|
||||
|
||||
static async _createWithSignFunction(
|
||||
signer: JsonRpcSigner | Wallet,
|
||||
roomId: number,
|
||||
answer: number,
|
||||
chainId: number,
|
||||
tokenAmount: BigNumber,
|
||||
contractAddress: string
|
||||
): Promise<VoteMsg | undefined> {
|
||||
const signFunction = createSignFunction(signer)
|
||||
const voter = await signer.getAddress()
|
||||
const msg = {
|
||||
roomIdAndType: BigNumber.from(roomId).mul(2).add(answer).toHexString(),
|
||||
tokenAmount: tokenAmount.toHexString(),
|
||||
voter,
|
||||
}
|
||||
const params = [msg.voter, JSON.stringify(createSignMsgParams(msg, chainId, contractAddress))]
|
||||
const signature = await signFunction(params)
|
||||
if (signature) {
|
||||
return new VoteMsg(signature, roomId, voter, answer, tokenAmount, chainId, Date.now())
|
||||
} else {
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
encode() {
|
||||
try {
|
||||
const voteProto: Vote = {
|
||||
voter: utils.arrayify(this.voter),
|
||||
timestamp: this.timestamp,
|
||||
answer: this.answer,
|
||||
tokenAmount: utils.arrayify(this.tokenAmount),
|
||||
roomId: utils.arrayify(BigNumber.from(this.roomId)),
|
||||
signature: utils.arrayify(this.signature),
|
||||
}
|
||||
return proto.Vote.encode(voteProto)
|
||||
} catch {
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
static decode(
|
||||
rawPayload: Uint8Array | undefined,
|
||||
timestamp: Date | undefined,
|
||||
chainId: number,
|
||||
contractAddress: string,
|
||||
verifyFunction?: (params: any, address: string) => boolean
|
||||
) {
|
||||
try {
|
||||
const payload = proto.Vote.decode(rawPayload)
|
||||
if (!timestamp || !payload.timestamp || timestamp?.getTime() != payload.timestamp) {
|
||||
return undefined
|
||||
}
|
||||
const signature = utils.hexlify(payload.signature)
|
||||
|
||||
const msg = {
|
||||
roomIdAndType: BigNumber.from(payload.roomId).mul(2).add(payload.answer).toHexString(),
|
||||
tokenAmount: utils.hexlify(payload.tokenAmount),
|
||||
voter: utils.getAddress(utils.hexlify(payload.voter)),
|
||||
}
|
||||
|
||||
const params = {
|
||||
data: createSignMsgParams(msg, chainId, contractAddress),
|
||||
sig: signature,
|
||||
}
|
||||
if (verifyFunction ? !verifyFunction : !verifySignature(params, msg.voter)) {
|
||||
return undefined
|
||||
}
|
||||
return new VoteMsg(
|
||||
signature,
|
||||
BigNumber.from(payload.roomId).toNumber(),
|
||||
utils.getAddress(utils.hexlify(payload.voter)),
|
||||
payload.answer,
|
||||
BigNumber.from(payload.tokenAmount),
|
||||
chainId,
|
||||
payload.timestamp
|
||||
)
|
||||
} catch {
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
import { MockProvider } from '@ethereum-waffle/provider'
|
||||
import { expect } from 'chai'
|
||||
import { Waku } from 'js-waku'
|
||||
import { WakuVoting } from '../src'
|
||||
import { WakuMessaging } from '../src'
|
||||
|
||||
describe('WakuVoting', () => {
|
||||
it('success', async () => {
|
||||
|
|
|
@ -23,6 +23,15 @@ declare module 'protons' {
|
|||
signature: Uint8Array
|
||||
}
|
||||
|
||||
export type Vote = {
|
||||
voter: Uint8Array
|
||||
timestamp:number
|
||||
answer: number
|
||||
roomId: Uint8Array
|
||||
tokenAmount: Uint8Array
|
||||
signature: Uint8Array
|
||||
}
|
||||
|
||||
function protons(init: string): {
|
||||
PollInit: {
|
||||
encode: (pollInit: PollInit) => Uint8Array,
|
||||
|
@ -32,6 +41,10 @@ declare module 'protons' {
|
|||
encode: (timedPollVote: TimedPollVote) => Uint8Array,
|
||||
decode: (payload: Uint8Array | undefined) => TimedPollVote
|
||||
}
|
||||
Vote:{
|
||||
encode: (vote: Vote) => Uint8Array,
|
||||
decode: (payload: Uint8Array | undefined) => Vote
|
||||
}
|
||||
}
|
||||
export = protons
|
||||
}
|
|
@ -35,6 +35,7 @@
|
|||
"@status-waku-voting/core": "^0.1.0",
|
||||
"@status-waku-voting/proposal-hooks": "^0.1.0",
|
||||
"@status-waku-voting/react-components": "^0.1.0",
|
||||
"@usedapp/core": "^0.4.7",
|
||||
"ethers": "^5.4.4",
|
||||
"humanize-duration": "^3.27.0",
|
||||
"react": "^17.0.2",
|
||||
|
|
|
@ -3,16 +3,18 @@ import styled from 'styled-components'
|
|||
import { ProposalHeader } from './ProposalHeader'
|
||||
import { blueTheme } from '@status-waku-voting/react-components/dist/esm/src/style/themes'
|
||||
import { ProposalList } from './ProposalList'
|
||||
import { VotingEmpty } from './VotingEmpty'
|
||||
import { NotificationItem } from './NotificationItem'
|
||||
import { WakuVoting } from '@status-waku-voting/core'
|
||||
|
||||
export function Proposal() {
|
||||
type ProposalProps = {
|
||||
wakuVoting: WakuVoting
|
||||
}
|
||||
|
||||
export function Proposal({ wakuVoting }: ProposalProps) {
|
||||
return (
|
||||
<ProposalWrapper>
|
||||
<VotingEmpty theme={blueTheme} />
|
||||
<ProposalHeader theme={blueTheme} />
|
||||
<ProposalList theme={blueTheme} />
|
||||
{/* <VotingEmpty theme={blueTheme} /> */}
|
||||
<ProposalHeader theme={blueTheme} wakuVoting={wakuVoting} />
|
||||
<ProposalList theme={blueTheme} wakuVoting={wakuVoting} />
|
||||
<NotificationItem text={'Proposal you finalized will be settled after 10 confirmations.'} address={'#'} />
|
||||
</ProposalWrapper>
|
||||
)
|
||||
|
|
|
@ -5,13 +5,16 @@ import { Modal, Networks, CreateButton } from '@status-waku-voting/react-compone
|
|||
import { Theme } from '@status-waku-voting/react-components/dist/esm/src/style/themes'
|
||||
import { ProposeModal } from './ProposeModal'
|
||||
import { ProposeVoteModal } from './ProposeVoteModal'
|
||||
import { WakuVoting } from '@status-waku-voting/core'
|
||||
import { BigNumber } from 'ethers'
|
||||
|
||||
type ProposalHeaderProps = {
|
||||
theme: Theme
|
||||
wakuVoting: WakuVoting
|
||||
}
|
||||
|
||||
export function ProposalHeader({ theme }: ProposalHeaderProps) {
|
||||
const { activateBrowserWallet, account } = useEthers()
|
||||
export function ProposalHeader({ theme, wakuVoting }: ProposalHeaderProps) {
|
||||
const { activateBrowserWallet, account, library } = useEthers()
|
||||
const [selectConnect, setSelectConnect] = useState(false)
|
||||
const [showProposeModal, setShowProposeModal] = useState(false)
|
||||
const [showProposeVoteModal, setShowProposeVoteModal] = useState(false)
|
||||
|
@ -46,6 +49,7 @@ export function ProposalHeader({ theme }: ProposalHeaderProps) {
|
|||
{showProposeVoteModal && (
|
||||
<Modal heading="Create proposal" theme={theme} setShowModal={setShowProposeVoteModal}>
|
||||
<ProposeVoteModal
|
||||
wakuVoting={wakuVoting}
|
||||
title={title}
|
||||
text={text}
|
||||
availableAmount={6524354}
|
||||
|
@ -57,7 +61,12 @@ export function ProposalHeader({ theme }: ProposalHeaderProps) {
|
|||
)}
|
||||
|
||||
{account ? (
|
||||
<CreateButton theme={theme} onClick={() => setShowProposeModal(true)}>
|
||||
<CreateButton
|
||||
theme={theme}
|
||||
onClick={() => {
|
||||
setShowProposeModal(true)
|
||||
}}
|
||||
>
|
||||
Create proposal
|
||||
</CreateButton>
|
||||
) : (
|
||||
|
|
|
@ -1,32 +1,30 @@
|
|||
import React from 'react'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import styled from 'styled-components'
|
||||
import { Theme } from '@status-waku-voting/react-components'
|
||||
import { ProposalCard } from './ProposalCard'
|
||||
import { WakuVoting } from '@status-waku-voting/core'
|
||||
import { VotingEmpty } from './VotingEmpty'
|
||||
|
||||
type ProposalListProps = {
|
||||
theme: Theme
|
||||
wakuVoting: WakuVoting
|
||||
}
|
||||
export function ProposalList({ theme }: ProposalListProps) {
|
||||
export function ProposalList({ theme, wakuVoting }: ProposalListProps) {
|
||||
const [votes, setVotes] = useState<any[]>([])
|
||||
|
||||
useEffect(() => {
|
||||
const interval = setInterval(async () => {
|
||||
setVotes(await wakuVoting.getVotes())
|
||||
}, 10000)
|
||||
return () => clearInterval(interval)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<List>
|
||||
<ProposalCard
|
||||
heading={'This is a very long, explainative and sophisticated title for a proposal.'}
|
||||
text={
|
||||
'This is a longer description of the proposal. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque interdum rutrum sodales. Nullam mattis fermentum libero, non volutpat. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque interdum rutrum sodales. Nullam mattis fermentum libero. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque interdum rutrum sodales. Nullam mattis fermentum libero.'
|
||||
}
|
||||
address={'#'}
|
||||
vote={2345678}
|
||||
voteWinner={2}
|
||||
theme={theme}
|
||||
/>
|
||||
<ProposalCard
|
||||
heading={'Short proposal title'}
|
||||
text={
|
||||
'This is a shorter description of the proposal. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque interdum rutrum sodales.'
|
||||
}
|
||||
address={'#'}
|
||||
theme={theme}
|
||||
/>
|
||||
{votes.map((vote, idx) => {
|
||||
return <ProposalCard heading={vote[2]} text={vote[3]} address={'#'} theme={theme} key={idx} />
|
||||
})}
|
||||
{votes && votes?.length === 0 && <VotingEmpty wakuVoting={wakuVoting} theme={theme} />}
|
||||
</List>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,11 +1,15 @@
|
|||
import { WakuVoting } from '@status-waku-voting/core'
|
||||
import { useEthers } from '@usedapp/core'
|
||||
import React, { useState } from 'react'
|
||||
import styled from 'styled-components'
|
||||
import { ProposingBtn } from './Buttons'
|
||||
import { CardHeading, CardText } from './ProposalInfo'
|
||||
import { ProposingData } from './ProposeModal'
|
||||
import { VotePropose } from './VotePropose'
|
||||
import { BigNumber } from 'ethers'
|
||||
|
||||
interface ProposeVoteModalProps {
|
||||
wakuVoting: WakuVoting
|
||||
availableAmount: number
|
||||
title: string
|
||||
text: string
|
||||
|
@ -15,6 +19,7 @@ interface ProposeVoteModalProps {
|
|||
}
|
||||
|
||||
export function ProposeVoteModal({
|
||||
wakuVoting,
|
||||
availableAmount,
|
||||
title,
|
||||
text,
|
||||
|
@ -22,6 +27,7 @@ export function ProposeVoteModal({
|
|||
setTitle,
|
||||
setText,
|
||||
}: ProposeVoteModalProps) {
|
||||
const { library } = useEthers()
|
||||
const [proposingAmount, setProposingAmount] = useState(0)
|
||||
return (
|
||||
<ProposingData>
|
||||
|
@ -38,7 +44,10 @@ export function ProposeVoteModal({
|
|||
|
||||
<ProposingBtn
|
||||
onClick={() => {
|
||||
setShowModal(false), setTitle(''), setText('')
|
||||
if (library) wakuVoting.createVote(library.getSigner(), title, text, BigNumber.from(proposingAmount))
|
||||
setShowModal(false)
|
||||
setTitle('')
|
||||
setText('')
|
||||
}}
|
||||
>
|
||||
Create proposal
|
||||
|
|
|
@ -4,12 +4,14 @@ import styled from 'styled-components'
|
|||
import { CreateButton, Modal, Networks, Theme } from '@status-waku-voting/react-components'
|
||||
import { ProposeModal } from './ProposeModal'
|
||||
import { ProposeVoteModal } from './ProposeVoteModal'
|
||||
import { WakuVoting } from '@status-waku-voting/core'
|
||||
|
||||
type VotingEmptyProps = {
|
||||
theme: Theme
|
||||
wakuVoting: WakuVoting
|
||||
}
|
||||
|
||||
export function VotingEmpty({ theme }: VotingEmptyProps) {
|
||||
export function VotingEmpty({ wakuVoting, theme }: VotingEmptyProps) {
|
||||
const { account, activateBrowserWallet } = useEthers()
|
||||
const [selectConnect, setSelectConnect] = useState(false)
|
||||
const [showProposeModal, setShowProposeModal] = useState(false)
|
||||
|
@ -46,6 +48,7 @@ export function VotingEmpty({ theme }: VotingEmptyProps) {
|
|||
{showProposeVoteModal && (
|
||||
<Modal heading="Create proposal" theme={theme} setShowModal={setShowProposeVoteModal}>
|
||||
<ProposeVoteModal
|
||||
wakuVoting={wakuVoting}
|
||||
title={title}
|
||||
text={text}
|
||||
availableAmount={6524354}
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
export function useTest() {
|
||||
console.log('test')
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
import { WakuVoting } from '@status-waku-voting/core'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { providers } from 'ethers'
|
||||
|
||||
export function useWakuProposal() {
|
||||
;(window as any).ethereum.on('chainChanged', () => window.location.reload())
|
||||
const [waku, setWaku] = useState<WakuVoting | undefined>(undefined)
|
||||
|
||||
useEffect(() => {
|
||||
const createWaku = async () => {
|
||||
const provider = new providers.Web3Provider((window as any).ethereum)
|
||||
const wak = await WakuVoting.create(
|
||||
'test',
|
||||
'0x5795A64A70cde4073DBa9EEBC5C6b675B15C815a',
|
||||
provider,
|
||||
'0x53c43764255c17bd724f74c4ef150724ac50a3ed'
|
||||
)
|
||||
setWaku(wak)
|
||||
}
|
||||
createWaku()
|
||||
}, [])
|
||||
|
||||
return waku
|
||||
}
|
|
@ -1,3 +1,3 @@
|
|||
import { useTest } from './hooks/useTest'
|
||||
import { useWakuProposal } from './hooks/useWakuProposal'
|
||||
|
||||
export { useTest }
|
||||
export { useWakuProposal }
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import React from 'react'
|
||||
import { useTest } from '@status-waku-voting/proposal-hooks'
|
||||
import { useWakuProposal } from '@status-waku-voting/proposal-hooks'
|
||||
import { Proposal } from '@status-waku-voting/proposal-components'
|
||||
import { TopBar, GlobalStyle } from '@status-waku-voting/react-components'
|
||||
import votingIcon from './assets/images/voting.svg'
|
||||
|
@ -26,6 +26,8 @@ const config = {
|
|||
|
||||
function Proposals() {
|
||||
const { account, library, activateBrowserWallet, deactivate } = useEthers()
|
||||
const waku = useWakuProposal()
|
||||
|
||||
return (
|
||||
<Wrapper>
|
||||
<TopBar
|
||||
|
@ -36,7 +38,7 @@ function Proposals() {
|
|||
account={account}
|
||||
deactivate={deactivate}
|
||||
/>
|
||||
<Proposal />
|
||||
{waku && <Proposal wakuVoting={waku} />}
|
||||
</Wrapper>
|
||||
)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue