diff --git a/packages/core/src/classes/WakuPolling.ts b/packages/core/src/classes/WakuPolling.ts index 7eb0205..416fa17 100644 --- a/packages/core/src/classes/WakuPolling.ts +++ b/packages/core/src/classes/WakuPolling.ts @@ -8,10 +8,11 @@ import { TimedPollVoteMsg } from '../models/TimedPollVoteMsg' import { DetailedTimedPoll } from '../models/DetailedTimedPoll' import { createWaku } from '../utils/createWaku' import { WakuVoting } from './WakuVoting' +import { Provider } from '@ethersproject/providers' export class WakuPolling extends WakuVoting { - protected constructor(appName: string, tokenAddress: string, waku: Waku) { - super(appName, tokenAddress, waku) + protected constructor(appName: string, tokenAddress: string, waku: Waku, provider: Provider, chainId: number) { + super(appName, tokenAddress, waku, provider, chainId) this.wakuMessages['pollInit'] = { topic: `/${this.appName}/waku-polling/timed-polls-init/proto/`, hashMap: {}, @@ -34,8 +35,9 @@ export class WakuPolling extends WakuVoting { this.setObserver() } - public static async create(appName: string, tokenAddress: string, waku?: Waku) { - const wakuPolling = new WakuPolling(appName, tokenAddress, await createWaku(waku)) + public static async create(appName: string, tokenAddress: string, provider: Provider, waku?: Waku) { + const network = await provider.getNetwork() + const wakuPolling = new WakuPolling(appName, tokenAddress, await createWaku(waku), provider, network.chainId) return wakuPolling } @@ -47,7 +49,7 @@ export class WakuPolling extends WakuVoting { minToken?: BigNumber, endTime?: number ) { - const pollInit = await PollInitMsg.create(signer, question, answers, pollType, minToken, endTime) + const pollInit = await PollInitMsg.create(signer, question, answers, pollType, this.chainId, minToken, endTime) await this.sendWakuMessage(this.wakuMessages['pollInit'], pollInit) } @@ -57,7 +59,7 @@ export class WakuPolling extends WakuVoting { selectedAnswer: number, tokenAmount?: BigNumber ) { - const pollVote = await TimedPollVoteMsg.create(signer, pollId, selectedAnswer, tokenAmount) + const pollVote = await TimedPollVoteMsg.create(signer, pollId, selectedAnswer, this.chainId, tokenAmount) await this.sendWakuMessage(this.wakuMessages['pollVote'], pollVote) } diff --git a/packages/core/src/classes/WakuVoting.ts b/packages/core/src/classes/WakuVoting.ts index 61a8a86..eb7dce0 100644 --- a/packages/core/src/classes/WakuVoting.ts +++ b/packages/core/src/classes/WakuVoting.ts @@ -1,6 +1,7 @@ import { Waku } from 'js-waku' import { WakuMessage } from 'js-waku' import { createWaku } from '../utils/createWaku' +import { Provider } from '@ethersproject/providers' type WakuMessageStore = { topic: string @@ -17,21 +18,27 @@ 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) { + 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 static async create(appName: string, tokenAddress: string, waku?: Waku) { - return new WakuVoting(appName, tokenAddress, await createWaku(waku)) + public static async create(appName: string, tokenAddress: string, provider: Provider, waku?: Waku) { + const network = await provider.getNetwork() + const wakuVoting = new WakuVoting(appName, tokenAddress, await createWaku(waku), provider, network.chainId) + return wakuVoting } public cleanUp() { this.observers.forEach((observer) => this.waku.relay.deleteObserver(observer.callback, observer.topics)) + this.wakuMessages = {} } protected async setObserver() { @@ -49,12 +56,12 @@ export class WakuVoting { protected decodeMsgAndSetArray( messages: WakuMessage[], - decode: (payload: Uint8Array | undefined, timestamp: Date | undefined) => T | undefined, + 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)) + .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) { diff --git a/packages/core/src/models/PollInitMsg.ts b/packages/core/src/models/PollInitMsg.ts index 46a6e88..cb1471c 100644 --- a/packages/core/src/models/PollInitMsg.ts +++ b/packages/core/src/models/PollInitMsg.ts @@ -2,7 +2,7 @@ import { PollType } from '../types/PollType' import { BigNumber, utils, Wallet } from 'ethers' import { JsonRpcSigner } from '@ethersproject/providers' import protons, { PollInit } from 'protons' -import { createSignedMsg } from '../utils/createSignedMsg' +import { createSignFunction } from '../utils/createSignFunction' import { verifySignature } from '../utils/verifySignature' const proto = protons(` @@ -32,11 +32,12 @@ type Message = { minToken?: BigNumber } -export function createSignMsgParams(message: Message) { +export function createSignMsgParams(message: Message, chainId: number) { const msgParams: any = { domain: { name: 'Waku polling', version: '1', + chainId, }, message: { ...message, @@ -49,6 +50,7 @@ export function createSignMsgParams(message: Message) { EIP712Domain: [ { name: 'name', type: 'string' }, { name: 'version', type: 'string' }, + { name: 'chainId', type: 'uint256' }, ], Mail: [ { name: 'owner', type: 'address' }, @@ -78,8 +80,8 @@ export class PollInitMsg { public endTime: number public signature: string public id: string - - constructor(signature: string, msg: Message) { + public chainId: number + constructor(signature: string, msg: Message, chainId: number) { this.id = utils.id([msg.owner, msg.timestamp, signature].join()) this.signature = signature this.owner = msg.owner @@ -89,18 +91,16 @@ export class PollInitMsg { this.pollType = msg.pollType this.minToken = msg.minToken this.endTime = msg.endTime + this.chainId = chainId } static async _createWithSignFunction( - signFunction: ( - msg: any, - params: string[], - Class: new (sig: string, msg: any) => PollInitMsg - ) => Promise, + signFunction: (params: string[]) => Promise, signer: JsonRpcSigner | Wallet, question: string, answers: string[], pollType: PollType, + chainId: number, minToken?: BigNumber, endTime?: number ): Promise { @@ -117,8 +117,13 @@ export class PollInitMsg { endTime: endTime ? endTime : timestamp + 100000000, minToken, } - const params = [msg.owner, JSON.stringify(createSignMsgParams(msg))] - return signFunction(msg, params, PollInitMsg) + const params = [msg.owner, JSON.stringify(createSignMsgParams(msg, chainId))] + const signature = await signFunction(params) + if (signature) { + return new PollInitMsg(signature, msg, chainId) + } else { + return undefined + } } static async create( @@ -126,10 +131,20 @@ export class PollInitMsg { question: string, answers: string[], pollType: PollType, + chainId: number, minToken?: BigNumber, endTime?: number ): Promise { - return this._createWithSignFunction(createSignedMsg(signer), signer, question, answers, pollType, minToken, endTime) + return this._createWithSignFunction( + createSignFunction(signer), + signer, + question, + answers, + pollType, + chainId, + minToken, + endTime + ) } encode() { @@ -161,6 +176,7 @@ export class PollInitMsg { static decode( rawPayload: Uint8Array | undefined, timestamp: Date | undefined, + chainId: number, verifyFunction?: (params: any, address: string) => boolean ) { try { @@ -180,13 +196,13 @@ export class PollInitMsg { } const signature = utils.hexlify(payload.signature) const params = { - data: createSignMsgParams(msg), + data: createSignMsgParams(msg, chainId), sig: signature, } if (verifyFunction ? !verifyFunction : !verifySignature(params, msg.owner)) { return undefined } - return new PollInitMsg(signature, msg) + return new PollInitMsg(signature, msg, chainId) } catch { return undefined } diff --git a/packages/core/src/models/TimedPollVoteMsg.ts b/packages/core/src/models/TimedPollVoteMsg.ts index 0dbe00d..61cee7f 100644 --- a/packages/core/src/models/TimedPollVoteMsg.ts +++ b/packages/core/src/models/TimedPollVoteMsg.ts @@ -2,7 +2,7 @@ import { BigNumber, utils } from 'ethers' import { JsonRpcSigner } from '@ethersproject/providers' import protons, { TimedPollVote } from 'protons' import { Wallet } from 'ethers' -import { createSignedMsg } from '../utils/createSignedMsg' +import { createSignFunction } from '../utils/createSignFunction' import { verifySignature } from '../utils/verifySignature' const proto = protons(` @@ -24,11 +24,12 @@ type Message = { tokenAmount?: BigNumber } -export function createSignMsgParams(message: Message) { +export function createSignMsgParams(message: Message, chainId: number) { const msgParams: any = { domain: { name: 'Waku polling', version: '1', + chainId, }, message: { ...message, @@ -39,6 +40,7 @@ export function createSignMsgParams(message: Message) { EIP712Domain: [ { name: 'name', type: 'string' }, { name: 'version', type: 'string' }, + { name: 'chainId', type: 'uint256' }, ], Mail: [ { name: 'pollId', type: 'string' }, @@ -64,8 +66,8 @@ export class TimedPollVoteMsg { public tokenAmount?: BigNumber public signature: string public id: string - - constructor(signature: string, msg: Message) { + public chainId: number + constructor(signature: string, msg: Message, chainId: number) { this.id = utils.id([msg.voter, msg.timestamp, signature].join()) this.pollId = msg.pollId this.voter = msg.voter @@ -73,31 +75,35 @@ export class TimedPollVoteMsg { this.answer = msg.answer this.tokenAmount = msg.tokenAmount this.signature = signature + this.chainId = chainId } static async _createWithSignFunction( - signFunction: ( - msg: any, - params: string[], - Class: new (sig: string, msg: any) => TimedPollVoteMsg - ) => Promise, + signFunction: (params: string[]) => Promise, signer: JsonRpcSigner | Wallet, pollId: string, answer: number, + chainId: number, tokenAmount?: BigNumber ): Promise { const voter = await signer.getAddress() const msg = { pollId, voter, timestamp: Date.now(), answer, tokenAmount } - const params = [msg.voter, JSON.stringify(createSignMsgParams(msg))] - return signFunction(msg, params, TimedPollVoteMsg) + const params = [msg.voter, JSON.stringify(createSignMsgParams(msg, chainId))] + const signature = await signFunction(params) + if (signature) { + return new TimedPollVoteMsg(signature, msg, chainId) + } else { + return undefined + } } static async create( signer: JsonRpcSigner | Wallet, pollId: string, answer: number, + chainId: number, tokenAmount?: BigNumber ): Promise { - return this._createWithSignFunction(createSignedMsg(signer), signer, pollId, answer, tokenAmount) + return this._createWithSignFunction(createSignFunction(signer), signer, pollId, answer, chainId, tokenAmount) } encode() { @@ -119,6 +125,7 @@ export class TimedPollVoteMsg { static decode( rawPayload: Uint8Array | undefined, timestamp: Date | undefined, + chainId: number, verifyFunction?: (params: any, address: string) => boolean ) { try { @@ -137,13 +144,13 @@ export class TimedPollVoteMsg { } const params = { - data: createSignMsgParams(msg), + data: createSignMsgParams(msg, chainId), sig: signature, } if (verifyFunction ? !verifyFunction : !verifySignature(params, msg.voter)) { return undefined } - return new TimedPollVoteMsg(signature, msg) + return new TimedPollVoteMsg(signature, msg, chainId) } catch { return undefined } diff --git a/packages/core/src/utils/createSignedMsg.ts b/packages/core/src/utils/createSignFunction.ts similarity index 60% rename from packages/core/src/utils/createSignedMsg.ts rename to packages/core/src/utils/createSignFunction.ts index 9587da6..03b0c6b 100644 --- a/packages/core/src/utils/createSignedMsg.ts +++ b/packages/core/src/utils/createSignFunction.ts @@ -1,13 +1,13 @@ import { JsonRpcSigner } from '@ethersproject/providers' import { Wallet } from 'ethers' -export function createSignedMsg(signer: JsonRpcSigner | Wallet) { - return async (msg: any, params: string[], Class: new (sig: string, msg: any) => T): Promise => { +export function createSignFunction(signer: JsonRpcSigner | Wallet) { + return async (params: string[]) => { if ('send' in signer.provider) { try { const signature = await signer.provider.send('eth_signTypedData_v4', params) if (signature) { - return new Class(signature, msg) + return signature } } catch { return undefined diff --git a/packages/core/test/index.test.ts b/packages/core/test/index.test.ts index 34d1db7..df2418f 100644 --- a/packages/core/test/index.test.ts +++ b/packages/core/test/index.test.ts @@ -1,10 +1,11 @@ +import { MockProvider } from '@ethereum-waffle/provider' import { expect } from 'chai' import { Waku } from 'js-waku' import { WakuVoting } from '../src' describe('WakuVoting', () => { it('success', async () => { - const wakuVoting = await WakuVoting.create('test', '0x0', {} as unknown as Waku) + const wakuVoting = await WakuVoting.create('test', '0x0', new MockProvider(), {} as unknown as Waku) expect(wakuVoting).to.not.be.undefined }) diff --git a/packages/core/test/models/PollInitMsg.test.ts b/packages/core/test/models/PollInitMsg.test.ts index 003c60c..1f94bc9 100644 --- a/packages/core/test/models/PollInitMsg.test.ts +++ b/packages/core/test/models/PollInitMsg.test.ts @@ -10,11 +10,12 @@ describe('PollInitMsg', () => { describe('create', () => { it('success', async () => { const poll = await PollInitMsg._createWithSignFunction( - async (e) => new PollInitMsg('0x01', e), + async () => '0x01', alice, 'test', ['one', 'two', 'three'], - PollType.WEIGHTED + PollType.WEIGHTED, + 0 ) expect(poll).to.not.be.undefined @@ -32,11 +33,12 @@ describe('PollInitMsg', () => { it('success NON_WEIGHTED', async () => { const poll = await PollInitMsg._createWithSignFunction( - async (e) => new PollInitMsg('0x01', e), + async () => '0x01', alice, 'test', ['one', 'two', 'three'], PollType.NON_WEIGHTED, + 0, BigNumber.from(123) ) expect(poll).to.not.be.undefined @@ -48,11 +50,12 @@ describe('PollInitMsg', () => { it('NON_WEIGHTED no minToken', async () => { const poll = await PollInitMsg._createWithSignFunction( - async (e) => new PollInitMsg('0x01', e), + async () => '0x01', alice, 'test', ['one', 'two', 'three'], - PollType.NON_WEIGHTED + PollType.NON_WEIGHTED, + 0 ) expect(poll?.minToken?.toNumber()).to.eq(1) @@ -64,11 +67,12 @@ describe('PollInitMsg', () => { it('specific end time', async () => { const poll = await PollInitMsg._createWithSignFunction( - async (e) => new PollInitMsg('0x01', e), + async () => '0x01', alice, 'test', ['one', 'two', 'three'], PollType.NON_WEIGHTED, + 0, undefined, 100 ) @@ -79,33 +83,35 @@ describe('PollInitMsg', () => { describe('decode/encode', () => { it('success', async () => { const data = await PollInitMsg._createWithSignFunction( - async (e) => new PollInitMsg('0x01', e), + async () => '0x01', alice, 'whats up', ['ab', 'cd', 'ef'], - PollType.WEIGHTED + PollType.WEIGHTED, + 0 ) 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) + expect(PollInitMsg.decode(payload, new Date(data.timestamp), 0, () => 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 + expect(PollInitMsg.decode(new Uint8Array([12, 12, 3, 32, 31, 212, 31, 32, 23]), new Date(10), 0)).to.be.undefined }) it('NON_WEIGHTED init', async () => { const data = await PollInitMsg._createWithSignFunction( - async (e) => new PollInitMsg('0x01', e), + async () => '0x01', alice, 'whats up', ['ab', 'cd', 'ef'], PollType.NON_WEIGHTED, + 0, BigNumber.from(10) ) expect(data).to.not.be.undefined @@ -113,18 +119,19 @@ describe('PollInitMsg', () => { 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) + expect(PollInitMsg.decode(payload, new Date(data.timestamp), 0, () => true)).to.deep.eq(data) } } }) it('NON_WEIGHTED no min token', async () => { const data = await PollInitMsg._createWithSignFunction( - async (e) => new PollInitMsg('0x01', e), + async () => '0x01', alice, 'whats up', ['ab', 'cd', 'ef'], - PollType.NON_WEIGHTED + PollType.NON_WEIGHTED, + 0 ) expect(data).to.not.be.undefined if (data) { @@ -132,7 +139,7 @@ describe('PollInitMsg', () => { expect(payload).to.not.be.undefined if (payload) { - expect(PollInitMsg.decode(payload, new Date(data.timestamp), () => true)).to.deep.eq({ + expect(PollInitMsg.decode(payload, new Date(data.timestamp), 0, () => true)).to.deep.eq({ ...data, minToken: BigNumber.from(1), }) diff --git a/packages/core/test/models/TimedPollVoteMsg.test.ts b/packages/core/test/models/TimedPollVoteMsg.test.ts index bd303a6..595cb51 100644 --- a/packages/core/test/models/TimedPollVoteMsg.test.ts +++ b/packages/core/test/models/TimedPollVoteMsg.test.ts @@ -10,12 +10,7 @@ describe('TimedPollVoteMsg', () => { describe('create', () => { it('success', async () => { - const vote = await TimedPollVoteMsg._createWithSignFunction( - async (e) => new TimedPollVoteMsg('0x01', e), - alice, - pollId, - 0 - ) + const vote = await TimedPollVoteMsg._createWithSignFunction(async () => '0x01', alice, pollId, 0, 0) if (vote) { expect(vote.voter).to.eq(alice.address) @@ -28,10 +23,11 @@ describe('TimedPollVoteMsg', () => { it('success token amount', async () => { const vote = await TimedPollVoteMsg._createWithSignFunction( - async (e) => new TimedPollVoteMsg('0x01', e), + async () => '0x01', alice, pollId, 1, + 0, BigNumber.from(100) ) @@ -48,12 +44,7 @@ describe('TimedPollVoteMsg', () => { describe('decode/encode', () => { it('success', async () => { - const data = await TimedPollVoteMsg._createWithSignFunction( - async (e) => new TimedPollVoteMsg('0x01', e), - alice, - pollId, - 0 - ) + const data = await TimedPollVoteMsg._createWithSignFunction(async () => '0x01', alice, pollId, 0, 0) expect(data).to.not.be.undefined if (data) { @@ -61,22 +52,23 @@ describe('TimedPollVoteMsg', () => { expect(payload).to.not.be.undefined if (payload) { - expect(await TimedPollVoteMsg.decode(payload, new Date(data.timestamp), () => true)).to.deep.eq(data) + expect(await TimedPollVoteMsg.decode(payload, new Date(data.timestamp), 0, () => 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 + expect(TimedPollVoteMsg.decode(new Uint8Array([12, 12, 3, 32, 31, 212, 31, 32, 23]), new Date(10), 0)).to.be .undefined }) it('data with token', async () => { const data = await TimedPollVoteMsg._createWithSignFunction( - async (e) => new TimedPollVoteMsg('0x01', e), + async () => '0x01', alice, pollId, 0, + 0, BigNumber.from(120) ) expect(data).to.not.be.undefined @@ -85,7 +77,7 @@ describe('TimedPollVoteMsg', () => { expect(payload).to.not.be.undefined if (payload) { - expect(TimedPollVoteMsg.decode(payload, new Date(data.timestamp), () => true)).to.deep.eq({ + expect(TimedPollVoteMsg.decode(payload, new Date(data.timestamp), 0, () => true)).to.deep.eq({ ...data, tokenAmount: BigNumber.from(120), }) diff --git a/packages/polling-hooks/package.json b/packages/polling-hooks/package.json index c57e0e2..0b09d83 100644 --- a/packages/polling-hooks/package.json +++ b/packages/polling-hooks/package.json @@ -7,7 +7,9 @@ "license": "MIT", "watch": { "build": { - "patterns": ["src"], + "patterns": [ + "src" + ], "extensions": "ts,tsx", "runOnChangeOnly": false } @@ -27,6 +29,7 @@ }, "dependencies": { "@status-waku-voting/core": "^0.1.0", + "@usedapp/core": "^0.4.7", "ethers": "^5.4.4", "react": "^17.0.2", "styled-components": "^5.3.0" diff --git a/packages/polling-hooks/src/hooks/useWakuPolling.ts b/packages/polling-hooks/src/hooks/useWakuPolling.ts index d48e3cb..8243564 100644 --- a/packages/polling-hooks/src/hooks/useWakuPolling.ts +++ b/packages/polling-hooks/src/hooks/useWakuPolling.ts @@ -1,11 +1,28 @@ -import { useEffect, useState } from 'react' +import { useEffect, useState, useRef } from 'react' import { WakuPolling } from '@status-waku-voting/core' +import { useEthers } from '@usedapp/core' +import { Provider } from '@ethersproject/providers' export function useWakuPolling(appName: string, tokenAddress: string) { const [wakuPolling, setWakuPolling] = useState(undefined) + const queue = useRef(0) + const queuePos = useRef(0) + + const { library } = useEthers() useEffect(() => { - WakuPolling.create(appName, tokenAddress).then((e) => setWakuPolling(e)) + const createNewWaku = async (queuePosition: number) => { + while (queuePosition != queuePos.current) { + await new Promise((r) => setTimeout(r, 1000)) + } + wakuPolling?.cleanUp() + const newWakuPoll = await WakuPolling.create(appName, tokenAddress, library as unknown as Provider) + setWakuPolling(newWakuPoll) + queuePos.current++ + } + if (library) { + createNewWaku(queue.current++) + } return () => wakuPolling?.cleanUp() - }, []) + }, [library]) return wakuPolling }