Use waku relay and refactor waku polling (#45)

This commit is contained in:
Szymon Szlachtowicz 2021-09-03 09:45:09 +02:00 committed by GitHub
parent fbca38cf04
commit dc37715bb8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 752 additions and 563 deletions

View File

@ -1,6 +1,6 @@
import { ethers } from 'ethers' import { ethers } from 'ethers'
import { deployContract } from 'ethereum-waffle' import { deployContract } from 'ethereum-waffle'
import { VotingContract, Directory } from '../abi' import { VotingContract } from '../abi'
const deploy = async () => { const deploy = async () => {
const providerName = process.env.ETHEREUM_PROVIDER const providerName = process.env.ETHEREUM_PROVIDER

View File

@ -0,0 +1,230 @@
{
"contractName": "ERC20",
"abi": [
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"name": "from",
"type": "address"
},
{
"indexed": true,
"name": "to",
"type": "address"
},
{
"indexed": false,
"name": "value",
"type": "uint256"
}
],
"name": "Transfer",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"name": "owner",
"type": "address"
},
{
"indexed": true,
"name": "spender",
"type": "address"
},
{
"indexed": false,
"name": "value",
"type": "uint256"
}
],
"name": "Approval",
"type": "event"
},
{
"constant": true,
"inputs": [],
"name": "totalSupply",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [
{
"name": "owner",
"type": "address"
}
],
"name": "balanceOf",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [
{
"name": "owner",
"type": "address"
},
{
"name": "spender",
"type": "address"
}
],
"name": "allowance",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "to",
"type": "address"
},
{
"name": "value",
"type": "uint256"
}
],
"name": "transfer",
"outputs": [
{
"name": "",
"type": "bool"
}
],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "spender",
"type": "address"
},
{
"name": "value",
"type": "uint256"
}
],
"name": "approve",
"outputs": [
{
"name": "",
"type": "bool"
}
],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "from",
"type": "address"
},
{
"name": "to",
"type": "address"
},
{
"name": "value",
"type": "uint256"
}
],
"name": "transferFrom",
"outputs": [
{
"name": "",
"type": "bool"
}
],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "spender",
"type": "address"
},
{
"name": "addedValue",
"type": "uint256"
}
],
"name": "increaseAllowance",
"outputs": [
{
"name": "",
"type": "bool"
}
],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "spender",
"type": "address"
},
{
"name": "subtractedValue",
"type": "uint256"
}
],
"name": "decreaseAllowance",
"outputs": [
{
"name": "",
"type": "bool"
}
],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
}
],
"bytecode": "0x608060405234801561001057600080fd5b506105dd806100206000396000f3fe608060405234801561001057600080fd5b50600436106100a5576000357c01000000000000000000000000000000000000000000000000000000009004806370a082311161007857806370a0823114610166578063a457c2d71461018c578063a9059cbb146101b8578063dd62ed3e146101e4576100a5565b8063095ea7b3146100aa57806318160ddd146100ea57806323b872dd14610104578063395093511461013a575b600080fd5b6100d6600480360360408110156100c057600080fd5b50600160a060020a038135169060200135610212565b604080519115158252519081900360200190f35b6100f2610290565b60408051918252519081900360200190f35b6100d66004803603606081101561011a57600080fd5b50600160a060020a03813581169160208101359091169060400135610296565b6100d66004803603604081101561015057600080fd5b50600160a060020a03813516906020013561035f565b6100f26004803603602081101561017c57600080fd5b5035600160a060020a031661040f565b6100d6600480360360408110156101a257600080fd5b50600160a060020a03813516906020013561042a565b6100d6600480360360408110156101ce57600080fd5b50600160a060020a038135169060200135610475565b6100f2600480360360408110156101fa57600080fd5b50600160a060020a038135811691602001351661048b565b6000600160a060020a038316151561022957600080fd5b336000818152600160209081526040808320600160a060020a03881680855290835292819020869055805186815290519293927f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925929181900390910190a350600192915050565b60025490565b600160a060020a03831660009081526001602090815260408083203384529091528120546102ca908363ffffffff6104b616565b600160a060020a03851660009081526001602090815260408083203384529091529020556102f98484846104cb565b600160a060020a0384166000818152600160209081526040808320338085529083529281902054815190815290519293927f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925929181900390910190a35060019392505050565b6000600160a060020a038316151561037657600080fd5b336000908152600160209081526040808320600160a060020a03871684529091529020546103aa908363ffffffff61059816565b336000818152600160209081526040808320600160a060020a0389168085529083529281902085905580519485525191937f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925929081900390910190a350600192915050565b600160a060020a031660009081526020819052604090205490565b6000600160a060020a038316151561044157600080fd5b336000908152600160209081526040808320600160a060020a03871684529091529020546103aa908363ffffffff6104b616565b60006104823384846104cb565b50600192915050565b600160a060020a03918216600090815260016020908152604080832093909416825291909152205490565b6000828211156104c557600080fd5b50900390565b600160a060020a03821615156104e057600080fd5b600160a060020a038316600090815260208190526040902054610509908263ffffffff6104b616565b600160a060020a03808516600090815260208190526040808220939093559084168152205461053e908263ffffffff61059816565b600160a060020a038084166000818152602081815260409182902094909455805185815290519193928716927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef92918290030190a3505050565b6000828201838110156105aa57600080fd5b939250505056fea165627a7a72305820722c0187518ce2856a424bdba350d5a263c8f98fcb19cb4cc161372bc3b794c90029",
"deployedBytecode": "0x608060405234801561001057600080fd5b50600436106100a5576000357c01000000000000000000000000000000000000000000000000000000009004806370a082311161007857806370a0823114610166578063a457c2d71461018c578063a9059cbb146101b8578063dd62ed3e146101e4576100a5565b8063095ea7b3146100aa57806318160ddd146100ea57806323b872dd14610104578063395093511461013a575b600080fd5b6100d6600480360360408110156100c057600080fd5b50600160a060020a038135169060200135610212565b604080519115158252519081900360200190f35b6100f2610290565b60408051918252519081900360200190f35b6100d66004803603606081101561011a57600080fd5b50600160a060020a03813581169160208101359091169060400135610296565b6100d66004803603604081101561015057600080fd5b50600160a060020a03813516906020013561035f565b6100f26004803603602081101561017c57600080fd5b5035600160a060020a031661040f565b6100d6600480360360408110156101a257600080fd5b50600160a060020a03813516906020013561042a565b6100d6600480360360408110156101ce57600080fd5b50600160a060020a038135169060200135610475565b6100f2600480360360408110156101fa57600080fd5b50600160a060020a038135811691602001351661048b565b6000600160a060020a038316151561022957600080fd5b336000818152600160209081526040808320600160a060020a03881680855290835292819020869055805186815290519293927f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925929181900390910190a350600192915050565b60025490565b600160a060020a03831660009081526001602090815260408083203384529091528120546102ca908363ffffffff6104b616565b600160a060020a03851660009081526001602090815260408083203384529091529020556102f98484846104cb565b600160a060020a0384166000818152600160209081526040808320338085529083529281902054815190815290519293927f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925929181900390910190a35060019392505050565b6000600160a060020a038316151561037657600080fd5b336000908152600160209081526040808320600160a060020a03871684529091529020546103aa908363ffffffff61059816565b336000818152600160209081526040808320600160a060020a0389168085529083529281902085905580519485525191937f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925929081900390910190a350600192915050565b600160a060020a031660009081526020819052604090205490565b6000600160a060020a038316151561044157600080fd5b336000908152600160209081526040808320600160a060020a03871684529091529020546103aa908363ffffffff6104b616565b60006104823384846104cb565b50600192915050565b600160a060020a03918216600090815260016020908152604080832093909416825291909152205490565b6000828211156104c557600080fd5b50900390565b600160a060020a03821615156104e057600080fd5b600160a060020a038316600090815260208190526040902054610509908263ffffffff6104b616565b600160a060020a03808516600090815260208190526040808220939093559084168152205461053e908263ffffffff61059816565b600160a060020a038084166000818152602081815260409182902094909455805185815290519193928716927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef92918290030190a3505050565b6000828201838110156105aa57600080fd5b939250505056fea165627a7a72305820722c0187518ce2856a424bdba350d5a263c8f98fcb19cb4cc161372bc3b794c90029",
"compiler": {
"name": "solc",
"version": "0.5.4+commit.9549d8ff.Emscripten.clang"
}
}

View File

@ -0,0 +1,3 @@
import ERC20 from './ERC20.json'
export { ERC20 }

View File

@ -3,63 +3,116 @@ import { JsonRpcSigner } from '@ethersproject/providers'
import { PollInitMsg } from './models/PollInitMsg' import { PollInitMsg } from './models/PollInitMsg'
import { PollType } from './types/PollType' import { PollType } from './types/PollType'
import { BigNumber, Wallet } from 'ethers' import { BigNumber, Wallet } from 'ethers'
import PollInit from './utils/proto/PollInit'
import { WakuMessage, StoreCodec } from 'js-waku' import { WakuMessage, StoreCodec } from 'js-waku'
import { TimedPollVoteMsg } from './models/TimedPollVoteMsg' import { TimedPollVoteMsg } from './models/TimedPollVoteMsg'
import TimedPollVote from './utils/proto/TimedPollVote'
import { DetailedTimedPoll } from './models/DetailedTimedPoll' import { DetailedTimedPoll } from './models/DetailedTimedPoll'
import { isTruthy } from './utils' import { isTruthy } from './utils'
import { createWaku } from './utils/createWaku'
function decodeWakuMessages<T>( type WakuMessageStore = {
messages: WakuMessage[] | null | undefined, topic: string
decode: (payload: Uint8Array | undefined, timestamp: Date | undefined) => T | undefined hashMap: { [id: string]: boolean }
) { arr: any[]
return messages?.map((msg) => decode(msg.payload, msg.timestamp)).filter(isTruthy) ?? [] updateFunction: (msg: WakuMessage[]) => void
} }
async function receiveNewWakuMessages(lastTimestamp: number, topic: string, waku: Waku | undefined) { type WakuMessageStores = {
const messages = await waku?.store.queryHistory([topic]) [messageType: string]: WakuMessageStore
if (messages) {
messages.sort((a, b) => (a.timestamp && b.timestamp && a.timestamp?.getTime() < b.timestamp?.getTime() ? 1 : -1))
const lastMessageIndex = messages.findIndex((message) => message.timestamp?.getTime() === lastTimestamp)
const newMessages = lastMessageIndex === -1 ? messages : messages.slice(0, lastMessageIndex)
return newMessages
}
return []
} }
class WakuVoting { class WakuVoting {
private appName: string protected appName: string
private waku: Waku | undefined protected waku: Waku
public tokenAddress: string public tokenAddress: string
private pollInitTopic: string
private timedPollVoteTopic: string
private timedPollInitMessages: PollInitMsg[] = [] protected wakuMessages: WakuMessageStores = {}
private timedPollVotesMessages: TimedPollVoteMsg[] = [] protected observers: { callback: (msg: WakuMessage) => void; topics: string[] }[] = []
private asyncUpdating = false protected constructor(appName: string, tokenAddress: string, waku: Waku) {
private constructor(appName: string, tokenAddress: string, waku: Waku) {
this.appName = appName this.appName = appName
this.tokenAddress = tokenAddress this.tokenAddress = tokenAddress
this.pollInitTopic = `/${this.appName}/waku-polling/timed-polls-init/proto/`
this.timedPollVoteTopic = `/${this.appName}/waku-polling/votes/proto/`
this.waku = waku this.waku = waku
} }
public static async create(appName: string, tokenAddress: string, waku?: Waku) { public static async create(appName: string, tokenAddress: string, waku?: Waku) {
if (!waku) { return new WakuVoting(appName, tokenAddress, await createWaku(waku))
waku = await Waku.create({ bootstrap: true }) }
await new Promise((resolve) => {
waku?.libp2p.peerStore.on('change:protocols', ({ protocols }) => { public cleanUp() {
if (protocols.includes(StoreCodec)) { this.observers.forEach((observer) => this.waku.relay.deleteObserver(observer.callback, observer.topics))
resolve('') }
protected async setObserver(msgObj: WakuMessageStore) {
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) => T | undefined,
msgObj: WakuMessageStore,
filterFunction?: (e: T) => boolean
) {
messages
.map((msg) => decode(msg.payload, msg.timestamp))
.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
}
} }
}) })
})
} }
return new WakuVoting(appName, tokenAddress, waku)
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])
}
}
}
class WakuPolling extends WakuVoting {
protected constructor(appName: string, tokenAddress: string, waku: Waku) {
super(appName, tokenAddress, waku)
this.wakuMessages['pollInit'] = {
topic: `/${this.appName}/waku-polling/timed-polls-init/proto/`,
hashMap: {},
arr: [],
updateFunction: (msg: WakuMessage[]) =>
this.decodeMsgAndSetArray(
msg,
PollInitMsg.decode,
this.wakuMessages['pollInit'],
(e) => e.endTime > Date.now()
),
}
this.wakuMessages['pollVote'] = {
topic: `/${this.appName}/waku-polling/votes/proto/`,
hashMap: {},
arr: [],
updateFunction: (msg: WakuMessage[]) =>
this.decodeMsgAndSetArray(msg, TimedPollVoteMsg.decode, this.wakuMessages['pollVote']),
}
}
public static async create(appName: string, tokenAddress: string, waku?: Waku) {
const wakuPolling = new WakuPolling(appName, tokenAddress, await createWaku(waku))
wakuPolling.setObserver(wakuPolling.wakuMessages['pollInit'])
wakuPolling.setObserver(wakuPolling.wakuMessages['pollVote'])
return wakuPolling
} }
public async createTimedPoll( public async createTimedPoll(
@ -71,84 +124,28 @@ class WakuVoting {
endTime?: number endTime?: number
) { ) {
const pollInit = await PollInitMsg.create(signer, question, answers, pollType, minToken, endTime) const pollInit = await PollInitMsg.create(signer, question, answers, pollType, minToken, endTime)
if (pollInit) { await this.sendWakuMessage(this.wakuMessages['pollInit'], pollInit)
const payload = PollInit.encode(pollInit)
if (payload) {
const wakuMessage = await WakuMessage.fromBytes(payload, this.pollInitTopic, {
timestamp: new Date(pollInit.timestamp),
})
await this.waku?.relay.send(wakuMessage)
}
}
}
private async getTimedPolls() {
const lastTimestamp = this.timedPollInitMessages?.[0]?.timestamp ?? 0
let updated = false
const newMessages = await receiveNewWakuMessages(lastTimestamp, this.pollInitTopic, this.waku)
const newPollInitMessages = decodeWakuMessages(newMessages, PollInit.decode)
if (newPollInitMessages.length > 0) {
updated = true
this.timedPollInitMessages = [...newPollInitMessages, ...this.timedPollInitMessages]
}
const arrayLen = this.timedPollInitMessages.length
this.timedPollInitMessages = this.timedPollInitMessages.filter((e) => e.endTime > Date.now())
if (arrayLen != this.timedPollInitMessages.length) {
updated = true
}
return { polls: this.timedPollInitMessages, updatedPolls: updated }
} }
public async sendTimedPollVote( public async sendTimedPollVote(
signer: JsonRpcSigner | Wallet, signer: JsonRpcSigner | Wallet,
id: string, pollId: string,
selectedAnswer: number, selectedAnswer: number,
tokenAmount?: BigNumber tokenAmount?: BigNumber
) { ) {
const pollVote = await TimedPollVoteMsg.create(signer, id, selectedAnswer, tokenAmount) const pollVote = await TimedPollVoteMsg.create(signer, pollId, selectedAnswer, tokenAmount)
if (pollVote) { await this.sendWakuMessage(this.wakuMessages['pollVote'], pollVote)
const payload = TimedPollVote.encode(pollVote)
if (payload) {
const wakuMessage = await WakuMessage.fromBytes(payload, this.timedPollVoteTopic, {
timestamp: new Date(pollVote.timestamp),
})
await this.waku?.relay.send(wakuMessage)
}
}
}
private async getTimedPollsVotes() {
const lastTimestamp = this.timedPollVotesMessages?.[0]?.timestamp ?? 0
let updated = false
const newMessages = await receiveNewWakuMessages(lastTimestamp, this.timedPollVoteTopic, this.waku)
const newVoteMessages = decodeWakuMessages(newMessages, TimedPollVote.decode)
if (newVoteMessages.length > 0) {
updated = true
this.timedPollVotesMessages = [...newVoteMessages, ...this.timedPollVotesMessages]
}
return { votes: this.timedPollVotesMessages, updatedVotes: updated }
} }
public async getDetailedTimedPolls() { public async getDetailedTimedPolls() {
let updated = false return this.wakuMessages['pollInit'].arr.map(
if (!this.asyncUpdating) {
this.asyncUpdating = true
const { updatedPolls } = await this.getTimedPolls()
const { updatedVotes } = await this.getTimedPollsVotes()
updated = updatedPolls || updatedVotes
this.asyncUpdating = false
}
return {
DetailedTimedPolls: this.timedPollInitMessages.map(
(poll) => (poll) =>
new DetailedTimedPoll( new DetailedTimedPoll(
poll, poll,
this.timedPollVotesMessages.filter((vote) => vote.id === poll.id) this.wakuMessages['pollVote'].arr.filter((vote) => vote.pollId === poll.id)
)
) )
),
updated,
}
} }
} }
export default WakuVoting export { WakuVoting, WakuPolling }

View File

@ -1,8 +1,27 @@
import { PollType } from '../types/PollType' import { PollType } from '../types/PollType'
import { BigNumber, utils, Wallet } from 'ethers' import { BigNumber, utils, Wallet } from 'ethers'
import { JsonRpcSigner } from '@ethersproject/providers' import { JsonRpcSigner } from '@ethersproject/providers'
import { PollInit } from 'protons' import protons, { PollInit } from 'protons'
import { createSignedMsg } from '../utils/createSignedMsg' import { createSignedMsg } from '../utils/createSignedMsg'
import { recoverTypedSignature_v4 } from 'eth-sig-util'
import { verifySignature } from '../utils/verifySignature'
const proto = protons(`
message PollInit {
bytes owner = 1;
int64 timestamp = 2;
string question = 3;
repeated string answers = 4;
enum PollType {
WEIGHTED = 0;
NON_WEIGHTED = 1;
}
PollType pollType = 5;
optional bytes minToken = 6;
int64 endTime = 7;
bytes signature = 8;
}
`)
type Message = { type Message = {
owner: string owner: string
@ -114,24 +133,63 @@ export class PollInitMsg {
return this._createWithSignFunction(createSignedMsg(signer), signer, question, answers, pollType, minToken, endTime) return this._createWithSignFunction(createSignedMsg(signer), signer, question, answers, pollType, minToken, endTime)
} }
static fromProto(payload: PollInit, recoverFunction: ({ data, sig }: { data: any; sig: string }) => string) { encode() {
const signature = utils.hexlify(payload.signature) try {
const arrayify = utils.arrayify
const msg = { const pollProto: PollInit = {
...payload, owner: arrayify(this.owner),
owner: utils.getAddress(utils.hexlify(payload.owner)), timestamp: this.timestamp,
minToken: payload.minToken ? BigNumber.from(payload.minToken) : undefined, question: this.question,
answers: this.answers,
pollType: this.pollType,
endTime: this.endTime,
signature: arrayify(this.signature),
} }
const params = createSignMsgParams(msg) if (this.pollType === PollType.NON_WEIGHTED) {
const verifiedAddress = recoverFunction({ if (this.minToken) {
data: params, pollProto.minToken = arrayify(this.minToken)
sig: signature, } else {
}) return undefined
if (verifiedAddress != msg.owner) { }
}
return proto.PollInit.encode(pollProto)
} catch {
return undefined
}
}
static decode(
rawPayload: Uint8Array | undefined,
timestamp: Date | undefined,
verifyFunction?: (params: any, address: string) => boolean
) {
try {
const payload = proto.PollInit.decode(rawPayload)
if (!timestamp || timestamp.getTime() != payload.timestamp) {
return undefined return undefined
} }
const msg: Message = {
timestamp: payload.timestamp,
question: payload.question,
answers: payload.answers,
pollType: payload.pollType,
endTime: payload.endTime,
owner: utils.getAddress(utils.hexlify(payload.owner)),
minToken: payload.minToken ? BigNumber.from(payload.minToken) : undefined,
}
const signature = utils.hexlify(payload.signature)
const params = {
data: createSignMsgParams(msg),
sig: signature,
}
if (verifyFunction ? !verifyFunction : !verifySignature(params, msg.owner)) {
return undefined
}
return new PollInitMsg(signature, msg) return new PollInitMsg(signature, msg)
} catch {
return undefined
}
} }
} }

View File

@ -1,11 +1,24 @@
import { BigNumber, utils } from 'ethers' import { BigNumber, utils } from 'ethers'
import { JsonRpcSigner } from '@ethersproject/providers' import { JsonRpcSigner } from '@ethersproject/providers'
import { TimedPollVote } from 'protons' import protons, { TimedPollVote } from 'protons'
import { Wallet } from 'ethers' import { Wallet } from 'ethers'
import { createSignedMsg } from '../utils/createSignedMsg' import { createSignedMsg } from '../utils/createSignedMsg'
import { recoverTypedSignature_v4 } from 'eth-sig-util'
import { verifySignature } from '../utils/verifySignature'
const proto = protons(`
message TimedPollVote {
bytes pollId = 1; // id of a poll
bytes voter = 2; // Address of a voter
int64 timestamp = 3; // Timestamp of a waku message
int64 answer = 4; // specified poll answer
optional bytes tokenAmount = 5; // amount of token used for WEIGHTED voting
bytes signature = 6; // signature of all above fields
}
`)
type Message = { type Message = {
id: string pollId: string
voter: string voter: string
timestamp: number timestamp: number
answer: number answer: number
@ -29,7 +42,7 @@ export function createSignMsgParams(message: Message) {
{ name: 'version', type: 'string' }, { name: 'version', type: 'string' },
], ],
Mail: [ Mail: [
{ name: 'id', type: 'string' }, { name: 'pollId', type: 'string' },
{ name: 'voter', type: 'string' }, { name: 'voter', type: 'string' },
{ name: 'timestamp', type: 'string' }, { name: 'timestamp', type: 'string' },
{ name: 'answer', type: 'string' }, { name: 'answer', type: 'string' },
@ -45,20 +58,21 @@ export function createSignMsgParams(message: Message) {
} }
export class TimedPollVoteMsg { export class TimedPollVoteMsg {
public id: string public pollId: string
public voter: string public voter: string
public timestamp: number public timestamp: number
public answer: number public answer: number
public tokenAmount?: BigNumber public tokenAmount?: BigNumber
public signature: string public signature: string
public id: string
constructor(signature: string, msg: Message) { constructor(signature: string, msg: Message) {
this.id = msg.id this.id = utils.id([msg.voter, msg.timestamp, signature].join())
this.pollId = msg.pollId
this.voter = msg.voter this.voter = msg.voter
this.timestamp = msg.timestamp this.timestamp = msg.timestamp
this.answer = msg.answer this.answer = msg.answer
this.tokenAmount = msg.tokenAmount this.tokenAmount = msg.tokenAmount
this.signature = signature this.signature = signature
} }
@ -69,45 +83,70 @@ export class TimedPollVoteMsg {
Class: new (sig: string, msg: any) => TimedPollVoteMsg Class: new (sig: string, msg: any) => TimedPollVoteMsg
) => Promise<TimedPollVoteMsg | undefined>, ) => Promise<TimedPollVoteMsg | undefined>,
signer: JsonRpcSigner | Wallet, signer: JsonRpcSigner | Wallet,
id: string, pollId: string,
answer: number, answer: number,
tokenAmount?: BigNumber tokenAmount?: BigNumber
): Promise<TimedPollVoteMsg | undefined> { ): Promise<TimedPollVoteMsg | undefined> {
const voter = await signer.getAddress() const voter = await signer.getAddress()
const msg = { id, voter, timestamp: Date.now(), answer, tokenAmount } const msg = { pollId, voter, timestamp: Date.now(), answer, tokenAmount }
const params = [msg.voter, JSON.stringify(createSignMsgParams(msg))] const params = [msg.voter, JSON.stringify(createSignMsgParams(msg))]
return signFunction(msg, params, TimedPollVoteMsg) return signFunction(msg, params, TimedPollVoteMsg)
} }
static async create( static async create(
signer: JsonRpcSigner | Wallet, signer: JsonRpcSigner | Wallet,
id: string, pollId: string,
answer: number, answer: number,
tokenAmount?: BigNumber tokenAmount?: BigNumber
): Promise<TimedPollVoteMsg | undefined> { ): Promise<TimedPollVoteMsg | undefined> {
return this._createWithSignFunction(createSignedMsg(signer), signer, id, answer, tokenAmount) return this._createWithSignFunction(createSignedMsg(signer), signer, pollId, answer, tokenAmount)
} }
static fromProto(payload: TimedPollVote, recoverFunction: ({ data, sig }: { data: any; sig: string }) => string) { encode() {
try {
const voteProto: TimedPollVote = {
pollId: utils.arrayify(this.pollId),
voter: utils.arrayify(this.voter),
timestamp: this.timestamp,
answer: this.answer,
tokenAmount: this.tokenAmount ? utils.arrayify(this.tokenAmount) : undefined,
signature: utils.arrayify(this.signature),
}
return proto.TimedPollVote.encode(voteProto)
} catch {
return undefined
}
}
static decode(
rawPayload: Uint8Array | undefined,
timestamp: Date | undefined,
verifyFunction?: (params: any, address: string) => boolean
) {
try {
const payload = proto.TimedPollVote.decode(rawPayload)
if (!timestamp || !payload.timestamp || timestamp?.getTime() != payload.timestamp) {
return undefined
}
const signature = utils.hexlify(payload.signature) const signature = utils.hexlify(payload.signature)
const msg = { const msg = {
id: utils.hexlify(payload.id), pollId: utils.hexlify(payload.pollId),
answer: payload.answer, answer: payload.answer,
voter: utils.getAddress(utils.hexlify(payload.voter)), voter: utils.getAddress(utils.hexlify(payload.voter)),
timestamp: payload.timestamp, timestamp: payload.timestamp,
tokenAmount: payload.tokenAmount ? BigNumber.from(payload.tokenAmount) : undefined, tokenAmount: payload.tokenAmount ? BigNumber.from(payload.tokenAmount) : undefined,
} }
const params = createSignMsgParams(msg) const params = {
const verifiedAddress = recoverFunction({ data: createSignMsgParams(msg),
data: params,
sig: signature, sig: signature,
}) }
if (verifiedAddress != msg.voter) { if (verifyFunction ? !verifyFunction : !verifySignature(params, msg.voter)) {
return undefined return undefined
} }
return new TimedPollVoteMsg(signature, msg) return new TimedPollVoteMsg(signature, msg)
} catch {
return undefined
}
} }
} }

View File

@ -0,0 +1,15 @@
import { StoreCodec, Waku } from 'js-waku'
export async function createWaku(waku?: Waku) {
if (!waku) {
waku = await Waku.create({ bootstrap: true })
await new Promise((resolve) => {
waku?.libp2p.peerStore.on('change:protocols', ({ protocols }) => {
if (protocols.includes(StoreCodec)) {
resolve('')
}
})
})
}
return waku
}

View File

@ -1,84 +0,0 @@
import protons, { PollInit } from 'protons'
import { PollType } from '../../types/PollType'
import { utils } from 'ethers'
import { PollInitMsg } from '../../models/PollInitMsg'
import { recoverTypedSignature_v4 } from 'eth-sig-util'
const proto = protons(`
message PollInit {
bytes owner = 1;
int64 timestamp = 2;
string question = 3;
repeated string answers = 4;
enum PollType {
WEIGHTED = 0;
NON_WEIGHTED = 1;
}
PollType pollType = 5;
optional bytes minToken = 6;
int64 endTime = 7;
bytes signature = 8;
}
`)
export function encode(pollInit: PollInitMsg) {
try {
const arrayify = utils.arrayify
const pollProto: PollInit = {
owner: arrayify(pollInit.owner),
timestamp: pollInit.timestamp,
question: pollInit.question,
answers: pollInit.answers,
pollType: pollInit.pollType,
endTime: pollInit.endTime,
signature: arrayify(pollInit.signature),
}
if (pollInit.pollType === PollType.NON_WEIGHTED) {
if (pollInit.minToken) {
pollProto.minToken = arrayify(pollInit.minToken)
} else {
return undefined
}
}
return proto.PollInit.encode(pollProto)
} catch {
return undefined
}
}
export function decode(
payload: Uint8Array | undefined,
timestamp: Date | undefined,
recoverFunction?: ({ data, sig }: { data: any; sig: string }) => string
) {
if (!payload) {
return undefined
}
try {
const msg = proto.PollInit.decode(payload)
if (!timestamp || timestamp.getTime() != msg.timestamp) {
return undefined
}
if (
msg.owner &&
msg.timestamp &&
msg.question &&
msg.answers &&
msg.pollType != undefined &&
msg.endTime &&
msg.signature
) {
if (recoverFunction) {
return PollInitMsg.fromProto(msg, recoverFunction)
}
return PollInitMsg.fromProto(msg, (e) => utils.getAddress(recoverTypedSignature_v4(e)))
}
} catch {
return undefined
}
return undefined
}
export default { encode, decode }

View File

@ -1,61 +0,0 @@
import protons, { TimedPollVote } from 'protons'
import { utils } from 'ethers'
import { TimedPollVoteMsg } from '../../models/TimedPollVoteMsg'
import { recoverTypedSignature_v4 } from 'eth-sig-util'
const proto = protons(`
message TimedPollVote {
bytes id = 1; // id of a poll
bytes voter = 2; // Address of a voter
int64 timestamp = 3; // Timestamp of a waku message
int64 answer = 4; // specified poll answer
optional bytes tokenAmount = 5; // amount of token used for WEIGHTED voting
bytes signature = 6; // signature of all above fields
}
`)
export function encode(timedPollVote: TimedPollVoteMsg) {
try {
const arrayify = utils.arrayify
const voteProto: TimedPollVote = {
id: arrayify(timedPollVote.id),
voter: arrayify(timedPollVote.voter),
timestamp: timedPollVote.timestamp,
answer: timedPollVote.answer,
tokenAmount: timedPollVote.tokenAmount ? arrayify(timedPollVote.tokenAmount) : undefined,
signature: arrayify(timedPollVote.signature),
}
return proto.TimedPollVote.encode(voteProto)
} catch {
return undefined
}
}
export function decode(
payload: Uint8Array | undefined,
timestamp: Date | undefined,
recoverFunction?: ({ data, sig }: { data: any; sig: string }) => string
) {
if (!payload) {
return undefined
}
try {
const msg = proto.TimedPollVote.decode(payload)
if (!timestamp || timestamp.getTime() != msg.timestamp) {
return undefined
}
if (msg.id && msg.voter && msg.timestamp && msg.answer != undefined && msg.signature) {
if (recoverFunction) {
return TimedPollVoteMsg.fromProto(msg, recoverFunction)
}
return TimedPollVoteMsg.fromProto(msg, (e) => utils.getAddress(recoverTypedSignature_v4(e)))
}
} catch {
return undefined
}
return undefined
}
export default { encode, decode }

View File

@ -0,0 +1,14 @@
import { recoverTypedSignature_v4 } from 'eth-sig-util'
import { utils } from 'ethers'
export function verifySignature(params: any, address: string) {
try {
const verifiedAddress = utils.getAddress(recoverTypedSignature_v4(params))
if (!verifiedAddress || verifiedAddress != address) {
return false
}
return true
} catch {
return false
}
}

View File

@ -1,6 +1,6 @@
import { expect } from 'chai' import { expect } from 'chai'
import { Waku } from 'js-waku' import { Waku } from 'js-waku'
import WakuVoting from '../src' import { WakuVoting } from '../src'
describe('WakuVoting', () => { describe('WakuVoting', () => {
it('success', async () => { it('success', async () => {

View File

@ -7,7 +7,7 @@ import { BigNumber } from 'ethers'
describe('PollInitMsg', () => { describe('PollInitMsg', () => {
const provider = new MockProvider() const provider = new MockProvider()
const [alice] = provider.getWallets() const [alice] = provider.getWallets()
describe('create', () => {
it('success', async () => { it('success', async () => {
const poll = await PollInitMsg._createWithSignFunction( const poll = await PollInitMsg._createWithSignFunction(
async (e) => new PollInitMsg('0x01', e), async (e) => new PollInitMsg('0x01', e),
@ -76,3 +76,68 @@ describe('PollInitMsg', () => {
expect(poll?.endTime).to.eq(100) expect(poll?.endTime).to.eq(100)
}) })
}) })
describe('decode/encode', () => {
it('success', async () => {
const data = await PollInitMsg._createWithSignFunction(
async (e) => new PollInitMsg('0x01', e),
alice,
'whats up',
['ab', 'cd', 'ef'],
PollType.WEIGHTED
)
expect(data).to.not.be.undefined
if (data) {
const payload = data.encode()
expect(payload).to.not.be.undefined
if (payload) {
expect(PollInitMsg.decode(payload, new Date(data.timestamp), () => true)).to.deep.eq(data)
}
}
})
it('random decode', async () => {
expect(PollInitMsg.decode(new Uint8Array([12, 12, 3, 32, 31, 212, 31, 32, 23]), new Date(10))).to.be.undefined
})
it('NON_WEIGHTED init', async () => {
const data = await PollInitMsg._createWithSignFunction(
async (e) => new PollInitMsg('0x01', e),
alice,
'whats up',
['ab', 'cd', 'ef'],
PollType.NON_WEIGHTED,
BigNumber.from(10)
)
expect(data).to.not.be.undefined
if (data) {
const payload = data.encode()
expect(payload).to.not.be.undefined
if (payload) {
expect(PollInitMsg.decode(payload, new Date(data.timestamp), () => true)).to.deep.eq(data)
}
}
})
it('NON_WEIGHTED no min token', async () => {
const data = await PollInitMsg._createWithSignFunction(
async (e) => new PollInitMsg('0x01', e),
alice,
'whats up',
['ab', 'cd', 'ef'],
PollType.NON_WEIGHTED
)
expect(data).to.not.be.undefined
if (data) {
const payload = data.encode()
expect(payload).to.not.be.undefined
if (payload) {
expect(PollInitMsg.decode(payload, new Date(data.timestamp), () => true)).to.deep.eq({
...data,
minToken: BigNumber.from(1),
})
}
}
})
})
})

View File

@ -7,26 +7,28 @@ describe('TimedPollVoteMsg', () => {
const provider = new MockProvider() const provider = new MockProvider()
const [alice] = provider.getWallets() const [alice] = provider.getWallets()
const pollId = '0x14c336ef626274f156d094fc1d7ffad2bbc83cccc9817598dd55e42a86b56b72' const pollId = '0x14c336ef626274f156d094fc1d7ffad2bbc83cccc9817598dd55e42a86b56b72'
describe('create', () => {
it('success', async () => { it('success', async () => {
const poll = await TimedPollVoteMsg._createWithSignFunction( const vote = await TimedPollVoteMsg._createWithSignFunction(
async (e) => new TimedPollVoteMsg('0x01', e), async (e) => new TimedPollVoteMsg('0x01', e),
alice, alice,
pollId, pollId,
0 0
) )
console.log(alice.address)
expect(poll).to.not.be.undefined if (vote) {
if (poll) { expect(vote.voter).to.eq(alice.address)
expect(poll.voter).to.eq(alice.address) expect(vote.answer).to.eq(0)
expect(poll.answer).to.eq(0) expect(vote.pollId).to.be.eq(pollId)
expect(poll.id).to.be.eq(pollId) expect(vote.tokenAmount).to.be.undefined
expect(poll.tokenAmount).to.be.undefined expect(vote.signature).to.eq('0x01')
expect(poll.signature).to.eq('0x01')
} }
}) })
it('success token amount', async () => { it('success token amount', async () => {
const poll = await TimedPollVoteMsg._createWithSignFunction( const vote = await TimedPollVoteMsg._createWithSignFunction(
async (e) => new TimedPollVoteMsg('0x01', e), async (e) => new TimedPollVoteMsg('0x01', e),
alice, alice,
pollId, pollId,
@ -34,13 +36,62 @@ describe('TimedPollVoteMsg', () => {
BigNumber.from(100) BigNumber.from(100)
) )
expect(poll).to.not.be.undefined expect(vote).to.not.be.undefined
if (poll) { if (vote) {
expect(poll.voter).to.eq(alice.address) expect(vote.voter).to.eq(alice.address)
expect(poll.answer).to.eq(1) expect(vote.answer).to.eq(1)
expect(poll.id).to.be.eq(pollId) expect(vote.pollId).to.be.eq(pollId)
expect(poll.tokenAmount).to.deep.eq(BigNumber.from(100)) expect(vote.tokenAmount).to.deep.eq(BigNumber.from(100))
expect(poll.signature).to.eq('0x01') expect(vote.signature).to.eq('0x01')
} }
}) })
}) })
describe('decode/encode', () => {
it('success', async () => {
const data = await TimedPollVoteMsg._createWithSignFunction(
async (e) => new TimedPollVoteMsg('0x01', e),
alice,
pollId,
0
)
expect(data).to.not.be.undefined
if (data) {
const payload = await data.encode()
expect(payload).to.not.be.undefined
if (payload) {
expect(await TimedPollVoteMsg.decode(payload, new Date(data.timestamp), () => true)).to.deep.eq(data)
}
}
})
it('random decode', async () => {
expect(TimedPollVoteMsg.decode(new Uint8Array([12, 12, 3, 32, 31, 212, 31, 32, 23]), new Date(10))).to.be
.undefined
})
it('data with token', async () => {
const data = await TimedPollVoteMsg._createWithSignFunction(
async (e) => new TimedPollVoteMsg('0x01', e),
alice,
pollId,
0,
BigNumber.from(120)
)
expect(data).to.not.be.undefined
if (data) {
const payload = data.encode()
expect(payload).to.not.be.undefined
if (payload) {
expect(TimedPollVoteMsg.decode(payload, new Date(data.timestamp), () => true)).to.deep.eq({
...data,
tokenAmount: BigNumber.from(120),
})
}
}
})
})
})

View File

@ -1,77 +0,0 @@
import { expect } from 'chai'
import { PollType } from '../../../src/types/PollType'
import PollInit from '../../../src/utils/proto/PollInit'
import { BigNumber } from 'ethers'
import { PollInitMsg } from '../../../src/models/PollInitMsg'
import { MockProvider } from 'ethereum-waffle'
describe('PollInit', () => {
const provider = new MockProvider()
const [alice] = provider.getWallets()
it('success', async () => {
const data = await PollInitMsg._createWithSignFunction(
async (e) => new PollInitMsg('0x01', e),
alice,
'whats up',
['ab', 'cd', 'ef'],
PollType.WEIGHTED
)
expect(data).to.not.be.undefined
if (data) {
const payload = PollInit.encode(data)
expect(payload).to.not.be.undefined
if (payload) {
expect(PollInit.decode(payload, new Date(data.timestamp), () => alice.address)).to.deep.eq(data)
}
}
})
it('random decode', async () => {
expect(PollInit.decode(new Uint8Array([12, 12, 3, 32, 31, 212, 31, 32, 23]), new Date(10))).to.be.undefined
})
it('random data', async () => {
expect(PollInit.encode({ sadf: '0x0' } as unknown as PollInitMsg)).to.be.undefined
})
it('NON_WEIGHTED init', async () => {
const data = await PollInitMsg._createWithSignFunction(
async (e) => new PollInitMsg('0x01', e),
alice,
'whats up',
['ab', 'cd', 'ef'],
PollType.NON_WEIGHTED,
BigNumber.from(10)
)
expect(data).to.not.be.undefined
if (data) {
const payload = PollInit.encode(data)
expect(payload).to.not.be.undefined
if (payload) {
expect(PollInit.decode(payload, new Date(data.timestamp), () => alice.address)).to.deep.eq(data)
}
}
})
it('NON_WEIGHTED no min token', async () => {
const data = await PollInitMsg._createWithSignFunction(
async (e) => new PollInitMsg('0x01', e),
alice,
'whats up',
['ab', 'cd', 'ef'],
PollType.NON_WEIGHTED
)
expect(data).to.not.be.undefined
if (data) {
const payload = PollInit.encode(data)
expect(payload).to.not.be.undefined
if (payload) {
expect(PollInit.decode(payload, new Date(data.timestamp), () => alice.address)).to.deep.eq({
...data,
minToken: BigNumber.from(1),
})
}
}
})
})

View File

@ -1,60 +0,0 @@
import { expect } from 'chai'
import TimedPollVote from '../../../src/utils/proto/TimedPollVote'
import { BigNumber } from 'ethers'
import { TimedPollVoteMsg } from '../../../src/models/TimedPollVoteMsg'
import { MockProvider } from 'ethereum-waffle'
describe('TimedPollVote', () => {
const provider = new MockProvider()
const [alice] = provider.getWallets()
const pollId = '0x14c336ef626274f156d094fc1d7ffad2bbc83cccc9817598dd55e42a86b56b72'
it('success', async () => {
const data = await TimedPollVoteMsg._createWithSignFunction(
async (e) => new TimedPollVoteMsg('0x01', e),
alice,
pollId,
0
)
expect(data).to.not.be.undefined
if (data) {
const payload = TimedPollVote.encode(data)
expect(payload).to.not.be.undefined
if (payload) {
expect(TimedPollVote.decode(payload, new Date(data.timestamp), () => alice.address)).to.deep.eq(data)
}
}
})
it('random decode', async () => {
expect(TimedPollVote.decode(new Uint8Array([12, 12, 3, 32, 31, 212, 31, 32, 23]), new Date(10))).to.be.undefined
})
it('random data', async () => {
expect(TimedPollVote.encode({ sadf: '0x0' } as unknown as TimedPollVoteMsg)).to.be.undefined
})
it('data with token', async () => {
const data = await TimedPollVoteMsg._createWithSignFunction(
async (e) => new TimedPollVoteMsg('0x01', e),
alice,
pollId,
0,
BigNumber.from(120)
)
expect(data).to.not.be.undefined
if (data) {
const payload = TimedPollVote.encode(data)
expect(payload).to.not.be.undefined
if (payload) {
expect(TimedPollVote.decode(payload, new Date(data.timestamp), () => alice.address)).to.deep.eq({
...data,
tokenAmount: BigNumber.from(120),
})
}
}
})
})

View File

@ -15,7 +15,7 @@ declare module 'protons' {
} }
export type TimedPollVote = { export type TimedPollVote = {
id: Uint8Array pollId: Uint8Array
voter: Uint8Array voter: Uint8Array
timestamp: number timestamp: number
answer: number answer: number
@ -26,11 +26,11 @@ declare module 'protons' {
function protons(init: string): { function protons(init: string): {
PollInit: { PollInit: {
encode: (pollInit: PollInit) => Uint8Array, encode: (pollInit: PollInit) => Uint8Array,
decode: (payload: Uint8Array) => PollInit decode: (payload: Uint8Array | undefined) => PollInit
} }
TimedPollVote:{ TimedPollVote:{
encode: (timedPollVote: TimedPollVote) => Uint8Array, encode: (timedPollVote: TimedPollVote) => Uint8Array,
decode: (payload: Uint8Array) => TimedPollVote decode: (payload: Uint8Array | undefined) => TimedPollVote
} }
} }
export = protons export = protons

View File

@ -1,4 +1,4 @@
import WakuVoting from '@status-waku-voting/core' import { WakuPolling } from '@status-waku-voting/core'
import { DetailedTimedPoll } from '@status-waku-voting/core/dist/esm/src/models/DetailedTimedPoll' import { DetailedTimedPoll } from '@status-waku-voting/core/dist/esm/src/models/DetailedTimedPoll'
import { Wallet, BigNumber } from 'ethers' import { Wallet, BigNumber } from 'ethers'
import React, { useEffect, useState } from 'react' import React, { useEffect, useState } from 'react'
@ -11,11 +11,11 @@ import { useEthers } from '@usedapp/core'
type PollProps = { type PollProps = {
poll: DetailedTimedPoll poll: DetailedTimedPoll
wakuVoting: WakuVoting | undefined wakuPolling: WakuPolling | undefined
signer: Wallet | JsonRpcSigner | undefined signer: Wallet | JsonRpcSigner | undefined
} }
export function Poll({ poll, wakuVoting, signer }: PollProps) { export function Poll({ poll, wakuPolling, signer }: PollProps) {
const { account } = useEthers() const { account } = useEthers()
const [selectedAnswer, setSelectedAnswer] = useState<number | undefined>(undefined) const [selectedAnswer, setSelectedAnswer] = useState<number | undefined>(undefined)
const [tokenAmount, setTokenAmount] = useState(0) const [tokenAmount, setTokenAmount] = useState(0)
@ -64,8 +64,8 @@ export function Poll({ poll, wakuVoting, signer }: PollProps) {
<SmallButton <SmallButton
disabled={!signer || !account} disabled={!signer || !account}
onClick={() => { onClick={() => {
if (wakuVoting && signer) { if (wakuPolling && signer) {
wakuVoting.sendTimedPollVote( wakuPolling.sendTimedPollVote(
signer, signer,
poll.poll.id, poll.poll.id,
selectedAnswer ?? 0, selectedAnswer ?? 0,

View File

@ -3,7 +3,7 @@ import { Wallet } from 'ethers'
import { JsonRpcSigner } from '@ethersproject/providers' import { JsonRpcSigner } from '@ethersproject/providers'
import styled from 'styled-components' import styled from 'styled-components'
import { PollType } from '@status-waku-voting/core/dist/esm/src/types/PollType' import { PollType } from '@status-waku-voting/core/dist/esm/src/types/PollType'
import WakuVoting from '@status-waku-voting/core' import { WakuPolling } from '@status-waku-voting/core'
import { Input, addIcon, SmallButton, Modal } from '@status-waku-voting/react-components' import { Input, addIcon, SmallButton, Modal } from '@status-waku-voting/react-components'
function getLocaleIsoTime(dateTime: Date) { function getLocaleIsoTime(dateTime: Date) {
@ -15,11 +15,11 @@ function getLocaleIsoTime(dateTime: Date) {
type PollCreationProps = { type PollCreationProps = {
signer: JsonRpcSigner | Wallet signer: JsonRpcSigner | Wallet
wakuVoting: WakuVoting | undefined wakuPolling: WakuPolling | undefined
setShowPollCreation: (val: boolean) => void setShowPollCreation: (val: boolean) => void
} }
export function PollCreation({ signer, wakuVoting, setShowPollCreation }: PollCreationProps) { export function PollCreation({ signer, wakuPolling, setShowPollCreation }: PollCreationProps) {
const [answers, setAnswers] = useState<string[]>(['', '']) const [answers, setAnswers] = useState<string[]>(['', ''])
const [question, setQuestion] = useState('') const [question, setQuestion] = useState('')
const [showCreateConfirmation, setShowCreateConfirmation] = useState(false) const [showCreateConfirmation, setShowCreateConfirmation] = useState(false)
@ -65,7 +65,7 @@ export function PollCreation({ signer, wakuVoting, setShowPollCreation }: PollCr
<SmallButton <SmallButton
onClick={async (e) => { onClick={async (e) => {
e.preventDefault() e.preventDefault()
await wakuVoting?.createTimedPoll( await wakuPolling?.createTimedPoll(
signer, signer,
question, question,
answers, answers,

View File

@ -1,4 +1,4 @@
import WakuVoting from '@status-waku-voting/core' import { WakuPolling } from '@status-waku-voting/core'
import { DetailedTimedPoll } from '@status-waku-voting/core/dist/esm/src/models/DetailedTimedPoll' import { DetailedTimedPoll } from '@status-waku-voting/core/dist/esm/src/models/DetailedTimedPoll'
import { Wallet } from 'ethers' import { Wallet } from 'ethers'
import React, { useEffect, useState } from 'react' import React, { useEffect, useState } from 'react'
@ -7,24 +7,22 @@ import { JsonRpcSigner } from '@ethersproject/providers'
import styled from 'styled-components' import styled from 'styled-components'
type PollListProps = { type PollListProps = {
wakuVoting: WakuVoting | undefined wakuPolling: WakuPolling | undefined
signer: Wallet | JsonRpcSigner | undefined signer: Wallet | JsonRpcSigner | undefined
} }
export function PollList({ wakuVoting, signer }: PollListProps) { export function PollList({ wakuPolling, signer }: PollListProps) {
const [polls, setPolls] = useState<DetailedTimedPoll[]>([]) const [polls, setPolls] = useState<DetailedTimedPoll[]>([])
const [dividedPolls, setDividedPolls] = useState<DetailedTimedPoll[][]>([[], [], []]) const [dividedPolls, setDividedPolls] = useState<DetailedTimedPoll[][]>([[], [], []])
useEffect(() => { useEffect(() => {
const interval = setInterval(async () => { const interval = setInterval(async () => {
if (wakuVoting) { if (wakuPolling) {
const { DetailedTimedPolls, updated } = await wakuVoting.getDetailedTimedPolls() const DetailedTimedPolls = await wakuPolling.getDetailedTimedPolls()
if (updated) {
setPolls(DetailedTimedPolls) setPolls(DetailedTimedPolls)
} }
}
}, 1000) }, 1000)
return () => clearInterval(interval) return () => clearInterval(interval)
}, [wakuVoting]) }, [wakuPolling])
useEffect(() => { useEffect(() => {
let arrayNo = 0 let arrayNo = 0
@ -45,7 +43,7 @@ export function PollList({ wakuVoting, signer }: PollListProps) {
return ( return (
<ColumnWrapper key={idx}> <ColumnWrapper key={idx}>
{pollArray.map((poll) => { {pollArray.map((poll) => {
return <Poll key={poll.poll.id} poll={poll} wakuVoting={wakuVoting} signer={signer} /> return <Poll key={poll.poll.id} poll={poll} wakuPolling={wakuPolling} signer={signer} />
})} })}
</ColumnWrapper> </ColumnWrapper>
) )

View File

@ -0,0 +1,11 @@
import { useEffect, useState } from 'react'
import { WakuPolling } from '@status-waku-voting/core'
export function useWakuPolling(appName: string, tokenAddress: string) {
const [wakuPolling, setWakuPolling] = useState<WakuPolling | undefined>(undefined)
useEffect(() => {
WakuPolling.create(appName, tokenAddress).then((e) => setWakuPolling(e))
return () => wakuPolling?.cleanUp()
}, [])
return wakuPolling
}

View File

@ -1,10 +0,0 @@
import { useEffect, useState } from 'react'
import WakuVoting from '@status-waku-voting/core'
export function useWakuVoting(appName: string, tokenAddress: string) {
const [wakuVoting, setWakuVoting] = useState<WakuVoting | undefined>(undefined)
useEffect(() => {
WakuVoting.create(appName, tokenAddress).then((e) => setWakuVoting(e))
}, [])
return wakuVoting
}

View File

@ -1,3 +1,3 @@
import { useWakuVoting } from './hooks/useWakuVoting' import { useWakuPolling } from './hooks/useWakuPolling'
export { useWakuVoting } export { useWakuPolling }

View File

@ -4,7 +4,7 @@ import { useEthers } from '@usedapp/core'
import styled from 'styled-components' import styled from 'styled-components'
import { PollList, PollCreation } from '@status-waku-voting/polling-components' import { PollList, PollCreation } from '@status-waku-voting/polling-components'
import { JsonRpcSigner } from '@ethersproject/providers' import { JsonRpcSigner } from '@ethersproject/providers'
import { useWakuVoting } from '@status-waku-voting/polling-hooks' import { useWakuPolling } from '@status-waku-voting/polling-hooks'
import { Modal, Networks, Button } from '@status-waku-voting/react-components' import { Modal, Networks, Button } from '@status-waku-voting/react-components'
type WakuPollingProps = { type WakuPollingProps = {
@ -16,11 +16,11 @@ export function WakuPolling({ appName, signer }: WakuPollingProps) {
const { activateBrowserWallet, account } = useEthers() const { activateBrowserWallet, account } = useEthers()
const [showPollCreation, setShowPollCreation] = useState(false) const [showPollCreation, setShowPollCreation] = useState(false)
const [selectConnect, setSelectConnect] = useState(false) const [selectConnect, setSelectConnect] = useState(false)
const wakuVoting = useWakuVoting(appName, '0x01') const wakuPolling = useWakuPolling(appName, '0x01')
return ( return (
<Wrapper> <Wrapper>
{showPollCreation && signer && ( {showPollCreation && signer && (
<PollCreation signer={signer} wakuVoting={wakuVoting} setShowPollCreation={setShowPollCreation} /> <PollCreation signer={signer} wakuPolling={wakuPolling} setShowPollCreation={setShowPollCreation} />
)} )}
{account ? ( {account ? (
<CreatePollButton disabled={!signer} onClick={() => setShowPollCreation(true)}> <CreatePollButton disabled={!signer} onClick={() => setShowPollCreation(true)}>
@ -43,7 +43,7 @@ export function WakuPolling({ appName, signer }: WakuPollingProps) {
</Modal> </Modal>
)} )}
<PollList wakuVoting={wakuVoting} signer={signer} /> <PollList wakuPolling={wakuPolling} signer={signer} />
</Wrapper> </Wrapper>
) )
} }