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 provider = ethers.getDefaultProvider(process.env.ETHEREUM_PROVIDER)
|
||||||
const wallet = new ethers.Wallet(privateKey, 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}`)
|
console.log(`Voting contract deployed with address: ${votingContract.address}`)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,6 +41,7 @@
|
||||||
"typescript": "^4.3.5"
|
"typescript": "^4.3.5"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@status-waku-voting/contracts": "^0.0.1",
|
||||||
"eth-sig-util": "^3.0.1",
|
"eth-sig-util": "^3.0.1",
|
||||||
"ethers": "^5.4.4",
|
"ethers": "^5.4.4",
|
||||||
"js-waku": "^0.11.0",
|
"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 { TimedPollVoteMsg } from '../models/TimedPollVoteMsg'
|
||||||
import { DetailedTimedPoll } from '../models/DetailedTimedPoll'
|
import { DetailedTimedPoll } from '../models/DetailedTimedPoll'
|
||||||
import { createWaku } from '../utils/createWaku'
|
import { createWaku } from '../utils/createWaku'
|
||||||
import { WakuVoting } from './WakuVoting'
|
import { WakuMessaging } from './WakuMessaging'
|
||||||
import { Provider } from '@ethersproject/providers'
|
import { Provider } from '@ethersproject/providers'
|
||||||
import { Contract } from '@ethersproject/contracts'
|
import { Contract } from '@ethersproject/contracts'
|
||||||
import { Interface } from '@ethersproject/abi'
|
import { Interface } from '@ethersproject/abi'
|
||||||
|
@ -23,7 +23,7 @@ export enum MESSEGAGE_SENDING_RESULT {
|
||||||
pollNotFound = 3,
|
pollNotFound = 3,
|
||||||
}
|
}
|
||||||
|
|
||||||
export class WakuPolling extends WakuVoting {
|
export class WakuPolling extends WakuMessaging {
|
||||||
protected multicall: string
|
protected multicall: string
|
||||||
|
|
||||||
protected constructor(
|
protected constructor(
|
||||||
|
|
|
@ -1,83 +1,102 @@
|
||||||
import { Waku } from 'js-waku'
|
import { VotingContract } from '@status-waku-voting/contracts/abi'
|
||||||
import { WakuMessage } from 'js-waku'
|
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 { createWaku } from '../utils/createWaku'
|
||||||
import { Provider } from '@ethersproject/providers'
|
import { JsonRpcSigner } from '@ethersproject/providers'
|
||||||
|
import { VoteMsg } from '../models/VoteMsg'
|
||||||
|
|
||||||
type WakuMessageStore = {
|
const ABI = [
|
||||||
topic: string
|
'function aggregate(tuple(address target, bytes callData)[] calls) view returns (uint256 blockNumber, bytes[] returnData)',
|
||||||
hashMap: { [id: string]: boolean }
|
]
|
||||||
arr: any[]
|
|
||||||
updateFunction: (msg: WakuMessage[]) => void
|
|
||||||
}
|
|
||||||
|
|
||||||
type WakuMessageStores = {
|
export class WakuVoting extends WakuMessaging {
|
||||||
[messageType: string]: WakuMessageStore
|
private multicall: Contract
|
||||||
}
|
private votingContract: Contract
|
||||||
|
|
||||||
export class WakuVoting {
|
constructor(
|
||||||
protected appName: string
|
appName: string,
|
||||||
protected waku: Waku
|
votingContract: Contract,
|
||||||
public tokenAddress: string
|
token: string,
|
||||||
protected provider: Provider
|
waku: Waku,
|
||||||
protected chainId = 0
|
provider: Provider,
|
||||||
protected wakuMessages: WakuMessageStores = {}
|
chainId: number,
|
||||||
protected observers: { callback: (msg: WakuMessage) => void; topics: string[] }[] = []
|
multicallAddress: string
|
||||||
protected constructor(appName: string, tokenAddress: string, waku: Waku, provider: Provider, chainId: number) {
|
) {
|
||||||
this.appName = appName
|
super(appName, token, waku, provider, chainId)
|
||||||
this.tokenAddress = tokenAddress
|
this.votingContract = votingContract
|
||||||
this.waku = waku
|
this.multicall = new Contract(multicallAddress, ABI, this.provider)
|
||||||
this.provider = provider
|
this.wakuMessages['vote'] = {
|
||||||
this.chainId = chainId
|
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() {
|
public static async create(
|
||||||
this.observers.forEach((observer) => this.waku.relay.deleteObserver(observer.callback, observer.topics))
|
appName: string,
|
||||||
this.wakuMessages = {}
|
contractAddress: string,
|
||||||
}
|
provider: Provider,
|
||||||
|
multicall: string,
|
||||||
protected async setObserver() {
|
waku?: Waku
|
||||||
await Promise.all(
|
) {
|
||||||
Object.values(this.wakuMessages).map(async (msgObj) => {
|
const network = await provider.getNetwork()
|
||||||
const storeMessages = await this.waku?.store.queryHistory([msgObj.topic])
|
const votingContract = new Contract(contractAddress, VotingContract.abi, provider)
|
||||||
if (storeMessages) {
|
const tokenAddress = await votingContract.token()
|
||||||
msgObj.updateFunction(storeMessages)
|
return new WakuVoting(
|
||||||
}
|
appName,
|
||||||
this.waku.relay.addObserver((msg) => msgObj.updateFunction([msg]), [msgObj.topic])
|
votingContract,
|
||||||
this.observers.push({ callback: (msg) => msgObj.updateFunction([msg]), topics: [msgObj.topic] })
|
tokenAddress,
|
||||||
})
|
await createWaku(waku),
|
||||||
|
provider,
|
||||||
|
network.chainId,
|
||||||
|
multicall
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
protected decodeMsgAndSetArray<T extends { id: string; timestamp: number }>(
|
public async createVote(
|
||||||
messages: WakuMessage[],
|
signer: JsonRpcSigner | Wallet,
|
||||||
decode: (payload: Uint8Array | undefined, timestamp: Date | undefined, chainId: number) => T | undefined,
|
question: string,
|
||||||
msgObj: WakuMessageStore,
|
descripiton: string,
|
||||||
filterFunction?: (e: T) => boolean
|
tokenAmount: BigNumber
|
||||||
) {
|
) {
|
||||||
messages
|
this.votingContract = await this.votingContract.connect(signer)
|
||||||
.map((msg) => decode(msg.payload, msg.timestamp, this.chainId))
|
await this.votingContract.initializeVotingRoom(question, descripiton, tokenAmount)
|
||||||
.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 }>(
|
private lastPolls: any[] = []
|
||||||
msgObj: WakuMessageStore,
|
private lastGetPollsBlockNumber = 0
|
||||||
decodedMsg: T | undefined
|
|
||||||
) {
|
public async getVotes() {
|
||||||
const payload = decodedMsg?.encode()
|
const blockNumber = await this.provider.getBlockNumber()
|
||||||
if (payload && decodedMsg) {
|
if (blockNumber != this.lastGetPollsBlockNumber) {
|
||||||
const wakuMessage = await WakuMessage.fromBytes(payload, msgObj.topic, {
|
this.lastGetPollsBlockNumber = blockNumber
|
||||||
timestamp: new Date(decodedMsg.timestamp),
|
this.lastPolls = await this.votingContract.getVotingRooms()
|
||||||
})
|
|
||||||
await this.waku?.relay.send(wakuMessage)
|
|
||||||
msgObj.updateFunction([wakuMessage])
|
|
||||||
}
|
}
|
||||||
|
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 { WakuPolling } from './classes/WakuPolling'
|
||||||
|
import { WakuMessaging } from './classes/WakuMessaging'
|
||||||
import { WakuVoting } from './classes/WakuVoting'
|
import { WakuVoting } from './classes/WakuVoting'
|
||||||
|
export { WakuMessaging, WakuPolling, WakuVoting }
|
||||||
export { WakuVoting, WakuPolling }
|
|
||||||
|
|
|
@ -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 { MockProvider } from '@ethereum-waffle/provider'
|
||||||
import { expect } from 'chai'
|
import { expect } from 'chai'
|
||||||
import { Waku } from 'js-waku'
|
import { Waku } from 'js-waku'
|
||||||
import { WakuVoting } from '../src'
|
import { WakuMessaging } from '../src'
|
||||||
|
|
||||||
describe('WakuVoting', () => {
|
describe('WakuVoting', () => {
|
||||||
it('success', async () => {
|
it('success', async () => {
|
||||||
|
|
|
@ -23,6 +23,15 @@ declare module 'protons' {
|
||||||
signature: Uint8Array
|
signature: Uint8Array
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type Vote = {
|
||||||
|
voter: Uint8Array
|
||||||
|
timestamp:number
|
||||||
|
answer: number
|
||||||
|
roomId: Uint8Array
|
||||||
|
tokenAmount: Uint8Array
|
||||||
|
signature: Uint8Array
|
||||||
|
}
|
||||||
|
|
||||||
function protons(init: string): {
|
function protons(init: string): {
|
||||||
PollInit: {
|
PollInit: {
|
||||||
encode: (pollInit: PollInit) => Uint8Array,
|
encode: (pollInit: PollInit) => Uint8Array,
|
||||||
|
@ -32,6 +41,10 @@ declare module 'protons' {
|
||||||
encode: (timedPollVote: TimedPollVote) => Uint8Array,
|
encode: (timedPollVote: TimedPollVote) => Uint8Array,
|
||||||
decode: (payload: Uint8Array | undefined) => TimedPollVote
|
decode: (payload: Uint8Array | undefined) => TimedPollVote
|
||||||
}
|
}
|
||||||
|
Vote:{
|
||||||
|
encode: (vote: Vote) => Uint8Array,
|
||||||
|
decode: (payload: Uint8Array | undefined) => Vote
|
||||||
|
}
|
||||||
}
|
}
|
||||||
export = protons
|
export = protons
|
||||||
}
|
}
|
|
@ -35,6 +35,7 @@
|
||||||
"@status-waku-voting/core": "^0.1.0",
|
"@status-waku-voting/core": "^0.1.0",
|
||||||
"@status-waku-voting/proposal-hooks": "^0.1.0",
|
"@status-waku-voting/proposal-hooks": "^0.1.0",
|
||||||
"@status-waku-voting/react-components": "^0.1.0",
|
"@status-waku-voting/react-components": "^0.1.0",
|
||||||
|
"@usedapp/core": "^0.4.7",
|
||||||
"ethers": "^5.4.4",
|
"ethers": "^5.4.4",
|
||||||
"humanize-duration": "^3.27.0",
|
"humanize-duration": "^3.27.0",
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
|
|
|
@ -3,16 +3,18 @@ import styled from 'styled-components'
|
||||||
import { ProposalHeader } from './ProposalHeader'
|
import { ProposalHeader } from './ProposalHeader'
|
||||||
import { blueTheme } from '@status-waku-voting/react-components/dist/esm/src/style/themes'
|
import { blueTheme } from '@status-waku-voting/react-components/dist/esm/src/style/themes'
|
||||||
import { ProposalList } from './ProposalList'
|
import { ProposalList } from './ProposalList'
|
||||||
import { VotingEmpty } from './VotingEmpty'
|
|
||||||
import { NotificationItem } from './NotificationItem'
|
import { NotificationItem } from './NotificationItem'
|
||||||
|
import { WakuVoting } from '@status-waku-voting/core'
|
||||||
|
|
||||||
export function Proposal() {
|
type ProposalProps = {
|
||||||
|
wakuVoting: WakuVoting
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Proposal({ wakuVoting }: ProposalProps) {
|
||||||
return (
|
return (
|
||||||
<ProposalWrapper>
|
<ProposalWrapper>
|
||||||
<VotingEmpty theme={blueTheme} />
|
<ProposalHeader theme={blueTheme} wakuVoting={wakuVoting} />
|
||||||
<ProposalHeader theme={blueTheme} />
|
<ProposalList theme={blueTheme} wakuVoting={wakuVoting} />
|
||||||
<ProposalList theme={blueTheme} />
|
|
||||||
{/* <VotingEmpty theme={blueTheme} /> */}
|
|
||||||
<NotificationItem text={'Proposal you finalized will be settled after 10 confirmations.'} address={'#'} />
|
<NotificationItem text={'Proposal you finalized will be settled after 10 confirmations.'} address={'#'} />
|
||||||
</ProposalWrapper>
|
</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 { Theme } from '@status-waku-voting/react-components/dist/esm/src/style/themes'
|
||||||
import { ProposeModal } from './ProposeModal'
|
import { ProposeModal } from './ProposeModal'
|
||||||
import { ProposeVoteModal } from './ProposeVoteModal'
|
import { ProposeVoteModal } from './ProposeVoteModal'
|
||||||
|
import { WakuVoting } from '@status-waku-voting/core'
|
||||||
|
import { BigNumber } from 'ethers'
|
||||||
|
|
||||||
type ProposalHeaderProps = {
|
type ProposalHeaderProps = {
|
||||||
theme: Theme
|
theme: Theme
|
||||||
|
wakuVoting: WakuVoting
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ProposalHeader({ theme }: ProposalHeaderProps) {
|
export function ProposalHeader({ theme, wakuVoting }: ProposalHeaderProps) {
|
||||||
const { activateBrowserWallet, account } = useEthers()
|
const { activateBrowserWallet, account, library } = useEthers()
|
||||||
const [selectConnect, setSelectConnect] = useState(false)
|
const [selectConnect, setSelectConnect] = useState(false)
|
||||||
const [showProposeModal, setShowProposeModal] = useState(false)
|
const [showProposeModal, setShowProposeModal] = useState(false)
|
||||||
const [showProposeVoteModal, setShowProposeVoteModal] = useState(false)
|
const [showProposeVoteModal, setShowProposeVoteModal] = useState(false)
|
||||||
|
@ -46,6 +49,7 @@ export function ProposalHeader({ theme }: ProposalHeaderProps) {
|
||||||
{showProposeVoteModal && (
|
{showProposeVoteModal && (
|
||||||
<Modal heading="Create proposal" theme={theme} setShowModal={setShowProposeVoteModal}>
|
<Modal heading="Create proposal" theme={theme} setShowModal={setShowProposeVoteModal}>
|
||||||
<ProposeVoteModal
|
<ProposeVoteModal
|
||||||
|
wakuVoting={wakuVoting}
|
||||||
title={title}
|
title={title}
|
||||||
text={text}
|
text={text}
|
||||||
availableAmount={6524354}
|
availableAmount={6524354}
|
||||||
|
@ -57,7 +61,12 @@ export function ProposalHeader({ theme }: ProposalHeaderProps) {
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{account ? (
|
{account ? (
|
||||||
<CreateButton theme={theme} onClick={() => setShowProposeModal(true)}>
|
<CreateButton
|
||||||
|
theme={theme}
|
||||||
|
onClick={() => {
|
||||||
|
setShowProposeModal(true)
|
||||||
|
}}
|
||||||
|
>
|
||||||
Create proposal
|
Create proposal
|
||||||
</CreateButton>
|
</CreateButton>
|
||||||
) : (
|
) : (
|
||||||
|
|
|
@ -1,32 +1,30 @@
|
||||||
import React from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
import { Theme } from '@status-waku-voting/react-components'
|
import { Theme } from '@status-waku-voting/react-components'
|
||||||
import { ProposalCard } from './ProposalCard'
|
import { ProposalCard } from './ProposalCard'
|
||||||
|
import { WakuVoting } from '@status-waku-voting/core'
|
||||||
|
import { VotingEmpty } from './VotingEmpty'
|
||||||
|
|
||||||
type ProposalListProps = {
|
type ProposalListProps = {
|
||||||
theme: Theme
|
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 (
|
return (
|
||||||
<List>
|
<List>
|
||||||
<ProposalCard
|
{votes.map((vote, idx) => {
|
||||||
heading={'This is a very long, explainative and sophisticated title for a proposal.'}
|
return <ProposalCard heading={vote[2]} text={vote[3]} address={'#'} theme={theme} key={idx} />
|
||||||
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.'
|
{votes && votes?.length === 0 && <VotingEmpty wakuVoting={wakuVoting} theme={theme} />}
|
||||||
}
|
|
||||||
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}
|
|
||||||
/>
|
|
||||||
</List>
|
</List>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,15 @@
|
||||||
|
import { WakuVoting } from '@status-waku-voting/core'
|
||||||
|
import { useEthers } from '@usedapp/core'
|
||||||
import React, { useState } from 'react'
|
import React, { useState } from 'react'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
import { ProposingBtn } from './Buttons'
|
import { ProposingBtn } from './Buttons'
|
||||||
import { CardHeading, CardText } from './ProposalInfo'
|
import { CardHeading, CardText } from './ProposalInfo'
|
||||||
import { ProposingData } from './ProposeModal'
|
import { ProposingData } from './ProposeModal'
|
||||||
import { VotePropose } from './VotePropose'
|
import { VotePropose } from './VotePropose'
|
||||||
|
import { BigNumber } from 'ethers'
|
||||||
|
|
||||||
interface ProposeVoteModalProps {
|
interface ProposeVoteModalProps {
|
||||||
|
wakuVoting: WakuVoting
|
||||||
availableAmount: number
|
availableAmount: number
|
||||||
title: string
|
title: string
|
||||||
text: string
|
text: string
|
||||||
|
@ -15,6 +19,7 @@ interface ProposeVoteModalProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ProposeVoteModal({
|
export function ProposeVoteModal({
|
||||||
|
wakuVoting,
|
||||||
availableAmount,
|
availableAmount,
|
||||||
title,
|
title,
|
||||||
text,
|
text,
|
||||||
|
@ -22,6 +27,7 @@ export function ProposeVoteModal({
|
||||||
setTitle,
|
setTitle,
|
||||||
setText,
|
setText,
|
||||||
}: ProposeVoteModalProps) {
|
}: ProposeVoteModalProps) {
|
||||||
|
const { library } = useEthers()
|
||||||
const [proposingAmount, setProposingAmount] = useState(0)
|
const [proposingAmount, setProposingAmount] = useState(0)
|
||||||
return (
|
return (
|
||||||
<ProposingData>
|
<ProposingData>
|
||||||
|
@ -38,7 +44,10 @@ export function ProposeVoteModal({
|
||||||
|
|
||||||
<ProposingBtn
|
<ProposingBtn
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setShowModal(false), setTitle(''), setText('')
|
if (library) wakuVoting.createVote(library.getSigner(), title, text, BigNumber.from(proposingAmount))
|
||||||
|
setShowModal(false)
|
||||||
|
setTitle('')
|
||||||
|
setText('')
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Create proposal
|
Create proposal
|
||||||
|
|
|
@ -4,12 +4,14 @@ import styled from 'styled-components'
|
||||||
import { CreateButton, Modal, Networks, Theme } from '@status-waku-voting/react-components'
|
import { CreateButton, Modal, Networks, Theme } from '@status-waku-voting/react-components'
|
||||||
import { ProposeModal } from './ProposeModal'
|
import { ProposeModal } from './ProposeModal'
|
||||||
import { ProposeVoteModal } from './ProposeVoteModal'
|
import { ProposeVoteModal } from './ProposeVoteModal'
|
||||||
|
import { WakuVoting } from '@status-waku-voting/core'
|
||||||
|
|
||||||
type VotingEmptyProps = {
|
type VotingEmptyProps = {
|
||||||
theme: Theme
|
theme: Theme
|
||||||
|
wakuVoting: WakuVoting
|
||||||
}
|
}
|
||||||
|
|
||||||
export function VotingEmpty({ theme }: VotingEmptyProps) {
|
export function VotingEmpty({ wakuVoting, theme }: VotingEmptyProps) {
|
||||||
const { account, activateBrowserWallet } = useEthers()
|
const { account, activateBrowserWallet } = useEthers()
|
||||||
const [selectConnect, setSelectConnect] = useState(false)
|
const [selectConnect, setSelectConnect] = useState(false)
|
||||||
const [showProposeModal, setShowProposeModal] = useState(false)
|
const [showProposeModal, setShowProposeModal] = useState(false)
|
||||||
|
@ -46,6 +48,7 @@ export function VotingEmpty({ theme }: VotingEmptyProps) {
|
||||||
{showProposeVoteModal && (
|
{showProposeVoteModal && (
|
||||||
<Modal heading="Create proposal" theme={theme} setShowModal={setShowProposeVoteModal}>
|
<Modal heading="Create proposal" theme={theme} setShowModal={setShowProposeVoteModal}>
|
||||||
<ProposeVoteModal
|
<ProposeVoteModal
|
||||||
|
wakuVoting={wakuVoting}
|
||||||
title={title}
|
title={title}
|
||||||
text={text}
|
text={text}
|
||||||
availableAmount={6524354}
|
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 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 { Proposal } from '@status-waku-voting/proposal-components'
|
||||||
import { TopBar, GlobalStyle } from '@status-waku-voting/react-components'
|
import { TopBar, GlobalStyle } from '@status-waku-voting/react-components'
|
||||||
import votingIcon from './assets/images/voting.svg'
|
import votingIcon from './assets/images/voting.svg'
|
||||||
|
@ -26,6 +26,8 @@ const config = {
|
||||||
|
|
||||||
function Proposals() {
|
function Proposals() {
|
||||||
const { account, library, activateBrowserWallet, deactivate } = useEthers()
|
const { account, library, activateBrowserWallet, deactivate } = useEthers()
|
||||||
|
const waku = useWakuProposal()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Wrapper>
|
<Wrapper>
|
||||||
<TopBar
|
<TopBar
|
||||||
|
@ -36,7 +38,7 @@ function Proposals() {
|
||||||
account={account}
|
account={account}
|
||||||
deactivate={deactivate}
|
deactivate={deactivate}
|
||||||
/>
|
/>
|
||||||
<Proposal />
|
{waku && <Proposal wakuVoting={waku} />}
|
||||||
</Wrapper>
|
</Wrapper>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue