Use waku relay and refactor waku polling (#45)
This commit is contained in:
parent
fbca38cf04
commit
dc37715bb8
|
@ -1,6 +1,6 @@
|
|||
import { ethers } from 'ethers'
|
||||
import { deployContract } from 'ethereum-waffle'
|
||||
import { VotingContract, Directory } from '../abi'
|
||||
import { VotingContract } from '../abi'
|
||||
|
||||
const deploy = async () => {
|
||||
const providerName = process.env.ETHEREUM_PROVIDER
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
import ERC20 from './ERC20.json'
|
||||
|
||||
export { ERC20 }
|
|
@ -3,63 +3,116 @@ import { JsonRpcSigner } from '@ethersproject/providers'
|
|||
import { PollInitMsg } from './models/PollInitMsg'
|
||||
import { PollType } from './types/PollType'
|
||||
import { BigNumber, Wallet } from 'ethers'
|
||||
import PollInit from './utils/proto/PollInit'
|
||||
import { WakuMessage, StoreCodec } from 'js-waku'
|
||||
import { TimedPollVoteMsg } from './models/TimedPollVoteMsg'
|
||||
import TimedPollVote from './utils/proto/TimedPollVote'
|
||||
import { DetailedTimedPoll } from './models/DetailedTimedPoll'
|
||||
import { isTruthy } from './utils'
|
||||
import { createWaku } from './utils/createWaku'
|
||||
|
||||
function decodeWakuMessages<T>(
|
||||
messages: WakuMessage[] | null | undefined,
|
||||
decode: (payload: Uint8Array | undefined, timestamp: Date | undefined) => T | undefined
|
||||
) {
|
||||
return messages?.map((msg) => decode(msg.payload, msg.timestamp)).filter(isTruthy) ?? []
|
||||
type WakuMessageStore = {
|
||||
topic: string
|
||||
hashMap: { [id: string]: boolean }
|
||||
arr: any[]
|
||||
updateFunction: (msg: WakuMessage[]) => void
|
||||
}
|
||||
|
||||
async function receiveNewWakuMessages(lastTimestamp: number, topic: string, waku: Waku | undefined) {
|
||||
const messages = await waku?.store.queryHistory([topic])
|
||||
|
||||
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 []
|
||||
type WakuMessageStores = {
|
||||
[messageType: string]: WakuMessageStore
|
||||
}
|
||||
|
||||
class WakuVoting {
|
||||
private appName: string
|
||||
private waku: Waku | undefined
|
||||
protected appName: string
|
||||
protected waku: Waku
|
||||
public tokenAddress: string
|
||||
private pollInitTopic: string
|
||||
private timedPollVoteTopic: string
|
||||
|
||||
private timedPollInitMessages: PollInitMsg[] = []
|
||||
private timedPollVotesMessages: TimedPollVoteMsg[] = []
|
||||
private asyncUpdating = false
|
||||
|
||||
private constructor(appName: string, tokenAddress: string, waku: Waku) {
|
||||
protected wakuMessages: WakuMessageStores = {}
|
||||
protected observers: { callback: (msg: WakuMessage) => void; topics: string[] }[] = []
|
||||
protected constructor(appName: string, tokenAddress: string, waku: Waku) {
|
||||
this.appName = appName
|
||||
this.tokenAddress = tokenAddress
|
||||
this.pollInitTopic = `/${this.appName}/waku-polling/timed-polls-init/proto/`
|
||||
this.timedPollVoteTopic = `/${this.appName}/waku-polling/votes/proto/`
|
||||
this.waku = waku
|
||||
}
|
||||
|
||||
public static async create(appName: string, tokenAddress: string, 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 new WakuVoting(appName, tokenAddress, await createWaku(waku))
|
||||
}
|
||||
|
||||
public cleanUp() {
|
||||
this.observers.forEach((observer) => this.waku.relay.deleteObserver(observer.callback, observer.topics))
|
||||
}
|
||||
|
||||
protected async setObserver(msgObj: WakuMessageStore) {
|
||||
const storeMessages = await this.waku?.store.queryHistory([msgObj.topic])
|
||||
if (storeMessages) {
|
||||
msgObj.updateFunction(storeMessages)
|
||||
}
|
||||
return new WakuVoting(appName, tokenAddress, waku)
|
||||
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
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
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(
|
||||
|
@ -71,84 +124,28 @@ class WakuVoting {
|
|||
endTime?: number
|
||||
) {
|
||||
const pollInit = await PollInitMsg.create(signer, question, answers, pollType, minToken, endTime)
|
||||
if (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 }
|
||||
await this.sendWakuMessage(this.wakuMessages['pollInit'], pollInit)
|
||||
}
|
||||
|
||||
public async sendTimedPollVote(
|
||||
signer: JsonRpcSigner | Wallet,
|
||||
id: string,
|
||||
pollId: string,
|
||||
selectedAnswer: number,
|
||||
tokenAmount?: BigNumber
|
||||
) {
|
||||
const pollVote = await TimedPollVoteMsg.create(signer, id, selectedAnswer, tokenAmount)
|
||||
if (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 }
|
||||
const pollVote = await TimedPollVoteMsg.create(signer, pollId, selectedAnswer, tokenAmount)
|
||||
await this.sendWakuMessage(this.wakuMessages['pollVote'], pollVote)
|
||||
}
|
||||
|
||||
public async getDetailedTimedPolls() {
|
||||
let updated = false
|
||||
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) =>
|
||||
new DetailedTimedPoll(
|
||||
poll,
|
||||
this.timedPollVotesMessages.filter((vote) => vote.id === poll.id)
|
||||
)
|
||||
),
|
||||
updated,
|
||||
}
|
||||
return this.wakuMessages['pollInit'].arr.map(
|
||||
(poll) =>
|
||||
new DetailedTimedPoll(
|
||||
poll,
|
||||
this.wakuMessages['pollVote'].arr.filter((vote) => vote.pollId === poll.id)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default WakuVoting
|
||||
export { WakuVoting, WakuPolling }
|
||||
|
|
|
@ -1,8 +1,27 @@
|
|||
import { PollType } from '../types/PollType'
|
||||
import { BigNumber, utils, Wallet } from 'ethers'
|
||||
import { JsonRpcSigner } from '@ethersproject/providers'
|
||||
import { PollInit } from 'protons'
|
||||
import protons, { PollInit } from 'protons'
|
||||
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 = {
|
||||
owner: string
|
||||
|
@ -114,24 +133,63 @@ export class PollInitMsg {
|
|||
return this._createWithSignFunction(createSignedMsg(signer), signer, question, answers, pollType, minToken, endTime)
|
||||
}
|
||||
|
||||
static fromProto(payload: PollInit, recoverFunction: ({ data, sig }: { data: any; sig: string }) => string) {
|
||||
const signature = utils.hexlify(payload.signature)
|
||||
encode() {
|
||||
try {
|
||||
const arrayify = utils.arrayify
|
||||
const pollProto: PollInit = {
|
||||
owner: arrayify(this.owner),
|
||||
timestamp: this.timestamp,
|
||||
question: this.question,
|
||||
answers: this.answers,
|
||||
pollType: this.pollType,
|
||||
endTime: this.endTime,
|
||||
signature: arrayify(this.signature),
|
||||
}
|
||||
|
||||
const msg = {
|
||||
...payload,
|
||||
owner: utils.getAddress(utils.hexlify(payload.owner)),
|
||||
minToken: payload.minToken ? BigNumber.from(payload.minToken) : undefined,
|
||||
}
|
||||
|
||||
const params = createSignMsgParams(msg)
|
||||
const verifiedAddress = recoverFunction({
|
||||
data: params,
|
||||
sig: signature,
|
||||
})
|
||||
if (verifiedAddress != msg.owner) {
|
||||
if (this.pollType === PollType.NON_WEIGHTED) {
|
||||
if (this.minToken) {
|
||||
pollProto.minToken = arrayify(this.minToken)
|
||||
} else {
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
return proto.PollInit.encode(pollProto)
|
||||
} catch {
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
return new PollInitMsg(signature, msg)
|
||||
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
|
||||
}
|
||||
|
||||
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)
|
||||
} catch {
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +1,24 @@
|
|||
import { BigNumber, utils } from 'ethers'
|
||||
import { JsonRpcSigner } from '@ethersproject/providers'
|
||||
import { TimedPollVote } from 'protons'
|
||||
import protons, { TimedPollVote } from 'protons'
|
||||
import { Wallet } from 'ethers'
|
||||
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 = {
|
||||
id: string
|
||||
pollId: string
|
||||
voter: string
|
||||
timestamp: number
|
||||
answer: number
|
||||
|
@ -29,7 +42,7 @@ export function createSignMsgParams(message: Message) {
|
|||
{ name: 'version', type: 'string' },
|
||||
],
|
||||
Mail: [
|
||||
{ name: 'id', type: 'string' },
|
||||
{ name: 'pollId', type: 'string' },
|
||||
{ name: 'voter', type: 'string' },
|
||||
{ name: 'timestamp', type: 'string' },
|
||||
{ name: 'answer', type: 'string' },
|
||||
|
@ -45,20 +58,21 @@ export function createSignMsgParams(message: Message) {
|
|||
}
|
||||
|
||||
export class TimedPollVoteMsg {
|
||||
public id: string
|
||||
public pollId: string
|
||||
public voter: string
|
||||
public timestamp: number
|
||||
public answer: number
|
||||
public tokenAmount?: BigNumber
|
||||
public signature: string
|
||||
public id: string
|
||||
|
||||
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.timestamp = msg.timestamp
|
||||
this.answer = msg.answer
|
||||
this.tokenAmount = msg.tokenAmount
|
||||
|
||||
this.signature = signature
|
||||
}
|
||||
|
||||
|
@ -69,45 +83,70 @@ export class TimedPollVoteMsg {
|
|||
Class: new (sig: string, msg: any) => TimedPollVoteMsg
|
||||
) => Promise<TimedPollVoteMsg | undefined>,
|
||||
signer: JsonRpcSigner | Wallet,
|
||||
id: string,
|
||||
pollId: string,
|
||||
answer: number,
|
||||
tokenAmount?: BigNumber
|
||||
): Promise<TimedPollVoteMsg | undefined> {
|
||||
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))]
|
||||
|
||||
return signFunction(msg, params, TimedPollVoteMsg)
|
||||
}
|
||||
static async create(
|
||||
signer: JsonRpcSigner | Wallet,
|
||||
id: string,
|
||||
pollId: string,
|
||||
answer: number,
|
||||
tokenAmount?: BigNumber
|
||||
): 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) {
|
||||
const signature = utils.hexlify(payload.signature)
|
||||
|
||||
const msg = {
|
||||
id: utils.hexlify(payload.id),
|
||||
answer: payload.answer,
|
||||
voter: utils.getAddress(utils.hexlify(payload.voter)),
|
||||
timestamp: payload.timestamp,
|
||||
tokenAmount: payload.tokenAmount ? BigNumber.from(payload.tokenAmount) : undefined,
|
||||
}
|
||||
|
||||
const params = createSignMsgParams(msg)
|
||||
const verifiedAddress = recoverFunction({
|
||||
data: params,
|
||||
sig: signature,
|
||||
})
|
||||
if (verifiedAddress != msg.voter) {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
return new TimedPollVoteMsg(signature, msg)
|
||||
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 msg = {
|
||||
pollId: utils.hexlify(payload.pollId),
|
||||
answer: payload.answer,
|
||||
voter: utils.getAddress(utils.hexlify(payload.voter)),
|
||||
timestamp: payload.timestamp,
|
||||
tokenAmount: payload.tokenAmount ? BigNumber.from(payload.tokenAmount) : undefined,
|
||||
}
|
||||
|
||||
const params = {
|
||||
data: createSignMsgParams(msg),
|
||||
sig: signature,
|
||||
}
|
||||
if (verifyFunction ? !verifyFunction : !verifySignature(params, msg.voter)) {
|
||||
return undefined
|
||||
}
|
||||
return new TimedPollVoteMsg(signature, msg)
|
||||
} catch {
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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 }
|
|
@ -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 }
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
import { expect } from 'chai'
|
||||
import { Waku } from 'js-waku'
|
||||
import WakuVoting from '../src'
|
||||
import { WakuVoting } from '../src'
|
||||
|
||||
describe('WakuVoting', () => {
|
||||
it('success', async () => {
|
||||
|
|
|
@ -7,72 +7,137 @@ import { BigNumber } from 'ethers'
|
|||
describe('PollInitMsg', () => {
|
||||
const provider = new MockProvider()
|
||||
const [alice] = provider.getWallets()
|
||||
describe('create', () => {
|
||||
it('success', async () => {
|
||||
const poll = await PollInitMsg._createWithSignFunction(
|
||||
async (e) => new PollInitMsg('0x01', e),
|
||||
alice,
|
||||
'test',
|
||||
['one', 'two', 'three'],
|
||||
PollType.WEIGHTED
|
||||
)
|
||||
|
||||
it('success', async () => {
|
||||
const poll = await PollInitMsg._createWithSignFunction(
|
||||
async (e) => new PollInitMsg('0x01', e),
|
||||
alice,
|
||||
'test',
|
||||
['one', 'two', 'three'],
|
||||
PollType.WEIGHTED
|
||||
)
|
||||
expect(poll).to.not.be.undefined
|
||||
if (poll) {
|
||||
expect(poll.owner).to.eq(alice.address)
|
||||
expect(poll.endTime).to.eq(poll.timestamp + 100000000)
|
||||
expect(poll.answers).to.deep.eq(['one', 'two', 'three'])
|
||||
expect(poll.minToken).to.be.undefined
|
||||
expect(poll.pollType).to.eq(PollType.WEIGHTED)
|
||||
expect(poll.question).to.eq('test')
|
||||
|
||||
expect(poll).to.not.be.undefined
|
||||
if (poll) {
|
||||
expect(poll.owner).to.eq(alice.address)
|
||||
expect(poll.endTime).to.eq(poll.timestamp + 100000000)
|
||||
expect(poll.answers).to.deep.eq(['one', 'two', 'three'])
|
||||
expect(poll.minToken).to.be.undefined
|
||||
expect(poll.pollType).to.eq(PollType.WEIGHTED)
|
||||
expect(poll.question).to.eq('test')
|
||||
expect(poll.signature).to.eq('0x01')
|
||||
}
|
||||
})
|
||||
|
||||
expect(poll.signature).to.eq('0x01')
|
||||
}
|
||||
it('success NON_WEIGHTED', async () => {
|
||||
const poll = await PollInitMsg._createWithSignFunction(
|
||||
async (e) => new PollInitMsg('0x01', e),
|
||||
alice,
|
||||
'test',
|
||||
['one', 'two', 'three'],
|
||||
PollType.NON_WEIGHTED,
|
||||
BigNumber.from(123)
|
||||
)
|
||||
expect(poll).to.not.be.undefined
|
||||
expect(poll?.minToken?.toNumber()).to.eq(123)
|
||||
if (poll) {
|
||||
expect(poll.signature).to.eq('0x01')
|
||||
}
|
||||
})
|
||||
|
||||
it('NON_WEIGHTED no minToken', async () => {
|
||||
const poll = await PollInitMsg._createWithSignFunction(
|
||||
async (e) => new PollInitMsg('0x01', e),
|
||||
alice,
|
||||
'test',
|
||||
['one', 'two', 'three'],
|
||||
PollType.NON_WEIGHTED
|
||||
)
|
||||
|
||||
expect(poll?.minToken?.toNumber()).to.eq(1)
|
||||
expect(poll).to.not.be.undefined
|
||||
if (poll) {
|
||||
expect(poll.signature).to.eq('0x01')
|
||||
}
|
||||
})
|
||||
|
||||
it('specific end time', async () => {
|
||||
const poll = await PollInitMsg._createWithSignFunction(
|
||||
async (e) => new PollInitMsg('0x01', e),
|
||||
alice,
|
||||
'test',
|
||||
['one', 'two', 'three'],
|
||||
PollType.NON_WEIGHTED,
|
||||
undefined,
|
||||
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('success NON_WEIGHTED', async () => {
|
||||
const poll = await PollInitMsg._createWithSignFunction(
|
||||
async (e) => new PollInitMsg('0x01', e),
|
||||
alice,
|
||||
'test',
|
||||
['one', 'two', 'three'],
|
||||
PollType.NON_WEIGHTED,
|
||||
BigNumber.from(123)
|
||||
)
|
||||
expect(poll).to.not.be.undefined
|
||||
expect(poll?.minToken?.toNumber()).to.eq(123)
|
||||
if (poll) {
|
||||
expect(poll.signature).to.eq('0x01')
|
||||
}
|
||||
})
|
||||
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 no minToken', async () => {
|
||||
const poll = await PollInitMsg._createWithSignFunction(
|
||||
async (e) => new PollInitMsg('0x01', e),
|
||||
alice,
|
||||
'test',
|
||||
['one', 'two', 'three'],
|
||||
PollType.NON_WEIGHTED
|
||||
)
|
||||
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)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
expect(poll?.minToken?.toNumber()).to.eq(1)
|
||||
expect(poll).to.not.be.undefined
|
||||
if (poll) {
|
||||
expect(poll.signature).to.eq('0x01')
|
||||
}
|
||||
})
|
||||
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()
|
||||
|
||||
it('specific end time', async () => {
|
||||
const poll = await PollInitMsg._createWithSignFunction(
|
||||
async (e) => new PollInitMsg('0x01', e),
|
||||
alice,
|
||||
'test',
|
||||
['one', 'two', 'three'],
|
||||
PollType.NON_WEIGHTED,
|
||||
undefined,
|
||||
100
|
||||
)
|
||||
|
||||
expect(poll?.endTime).to.eq(100)
|
||||
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),
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -7,40 +7,91 @@ describe('TimedPollVoteMsg', () => {
|
|||
const provider = new MockProvider()
|
||||
const [alice] = provider.getWallets()
|
||||
const pollId = '0x14c336ef626274f156d094fc1d7ffad2bbc83cccc9817598dd55e42a86b56b72'
|
||||
it('success', async () => {
|
||||
const poll = await TimedPollVoteMsg._createWithSignFunction(
|
||||
async (e) => new TimedPollVoteMsg('0x01', e),
|
||||
alice,
|
||||
pollId,
|
||||
0
|
||||
)
|
||||
|
||||
expect(poll).to.not.be.undefined
|
||||
if (poll) {
|
||||
expect(poll.voter).to.eq(alice.address)
|
||||
expect(poll.answer).to.eq(0)
|
||||
expect(poll.id).to.be.eq(pollId)
|
||||
expect(poll.tokenAmount).to.be.undefined
|
||||
expect(poll.signature).to.eq('0x01')
|
||||
}
|
||||
describe('create', () => {
|
||||
it('success', async () => {
|
||||
const vote = await TimedPollVoteMsg._createWithSignFunction(
|
||||
async (e) => new TimedPollVoteMsg('0x01', e),
|
||||
alice,
|
||||
pollId,
|
||||
0
|
||||
)
|
||||
console.log(alice.address)
|
||||
|
||||
if (vote) {
|
||||
expect(vote.voter).to.eq(alice.address)
|
||||
expect(vote.answer).to.eq(0)
|
||||
expect(vote.pollId).to.be.eq(pollId)
|
||||
expect(vote.tokenAmount).to.be.undefined
|
||||
expect(vote.signature).to.eq('0x01')
|
||||
}
|
||||
})
|
||||
|
||||
it('success token amount', async () => {
|
||||
const vote = await TimedPollVoteMsg._createWithSignFunction(
|
||||
async (e) => new TimedPollVoteMsg('0x01', e),
|
||||
alice,
|
||||
pollId,
|
||||
1,
|
||||
BigNumber.from(100)
|
||||
)
|
||||
|
||||
expect(vote).to.not.be.undefined
|
||||
if (vote) {
|
||||
expect(vote.voter).to.eq(alice.address)
|
||||
expect(vote.answer).to.eq(1)
|
||||
expect(vote.pollId).to.be.eq(pollId)
|
||||
expect(vote.tokenAmount).to.deep.eq(BigNumber.from(100))
|
||||
expect(vote.signature).to.eq('0x01')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
it('success token amount', async () => {
|
||||
const poll = await TimedPollVoteMsg._createWithSignFunction(
|
||||
async (e) => new TimedPollVoteMsg('0x01', e),
|
||||
alice,
|
||||
pollId,
|
||||
1,
|
||||
BigNumber.from(100)
|
||||
)
|
||||
describe('decode/encode', () => {
|
||||
it('success', async () => {
|
||||
const data = await TimedPollVoteMsg._createWithSignFunction(
|
||||
async (e) => new TimedPollVoteMsg('0x01', e),
|
||||
alice,
|
||||
pollId,
|
||||
0
|
||||
)
|
||||
|
||||
expect(poll).to.not.be.undefined
|
||||
if (poll) {
|
||||
expect(poll.voter).to.eq(alice.address)
|
||||
expect(poll.answer).to.eq(1)
|
||||
expect(poll.id).to.be.eq(pollId)
|
||||
expect(poll.tokenAmount).to.deep.eq(BigNumber.from(100))
|
||||
expect(poll.signature).to.eq('0x01')
|
||||
}
|
||||
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),
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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),
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
|
@ -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),
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
|
@ -15,7 +15,7 @@ declare module 'protons' {
|
|||
}
|
||||
|
||||
export type TimedPollVote = {
|
||||
id: Uint8Array
|
||||
pollId: Uint8Array
|
||||
voter: Uint8Array
|
||||
timestamp: number
|
||||
answer: number
|
||||
|
@ -26,11 +26,11 @@ declare module 'protons' {
|
|||
function protons(init: string): {
|
||||
PollInit: {
|
||||
encode: (pollInit: PollInit) => Uint8Array,
|
||||
decode: (payload: Uint8Array) => PollInit
|
||||
decode: (payload: Uint8Array | undefined) => PollInit
|
||||
}
|
||||
TimedPollVote:{
|
||||
encode: (timedPollVote: TimedPollVote) => Uint8Array,
|
||||
decode: (payload: Uint8Array) => TimedPollVote
|
||||
decode: (payload: Uint8Array | undefined) => TimedPollVote
|
||||
}
|
||||
}
|
||||
export = protons
|
||||
|
|
|
@ -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 { Wallet, BigNumber } from 'ethers'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
|
@ -11,11 +11,11 @@ import { useEthers } from '@usedapp/core'
|
|||
|
||||
type PollProps = {
|
||||
poll: DetailedTimedPoll
|
||||
wakuVoting: WakuVoting | undefined
|
||||
wakuPolling: WakuPolling | undefined
|
||||
signer: Wallet | JsonRpcSigner | undefined
|
||||
}
|
||||
|
||||
export function Poll({ poll, wakuVoting, signer }: PollProps) {
|
||||
export function Poll({ poll, wakuPolling, signer }: PollProps) {
|
||||
const { account } = useEthers()
|
||||
const [selectedAnswer, setSelectedAnswer] = useState<number | undefined>(undefined)
|
||||
const [tokenAmount, setTokenAmount] = useState(0)
|
||||
|
@ -64,8 +64,8 @@ export function Poll({ poll, wakuVoting, signer }: PollProps) {
|
|||
<SmallButton
|
||||
disabled={!signer || !account}
|
||||
onClick={() => {
|
||||
if (wakuVoting && signer) {
|
||||
wakuVoting.sendTimedPollVote(
|
||||
if (wakuPolling && signer) {
|
||||
wakuPolling.sendTimedPollVote(
|
||||
signer,
|
||||
poll.poll.id,
|
||||
selectedAnswer ?? 0,
|
||||
|
|
|
@ -3,7 +3,7 @@ import { Wallet } from 'ethers'
|
|||
import { JsonRpcSigner } from '@ethersproject/providers'
|
||||
import styled from 'styled-components'
|
||||
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'
|
||||
|
||||
function getLocaleIsoTime(dateTime: Date) {
|
||||
|
@ -15,11 +15,11 @@ function getLocaleIsoTime(dateTime: Date) {
|
|||
|
||||
type PollCreationProps = {
|
||||
signer: JsonRpcSigner | Wallet
|
||||
wakuVoting: WakuVoting | undefined
|
||||
wakuPolling: WakuPolling | undefined
|
||||
setShowPollCreation: (val: boolean) => void
|
||||
}
|
||||
|
||||
export function PollCreation({ signer, wakuVoting, setShowPollCreation }: PollCreationProps) {
|
||||
export function PollCreation({ signer, wakuPolling, setShowPollCreation }: PollCreationProps) {
|
||||
const [answers, setAnswers] = useState<string[]>(['', ''])
|
||||
const [question, setQuestion] = useState('')
|
||||
const [showCreateConfirmation, setShowCreateConfirmation] = useState(false)
|
||||
|
@ -65,7 +65,7 @@ export function PollCreation({ signer, wakuVoting, setShowPollCreation }: PollCr
|
|||
<SmallButton
|
||||
onClick={async (e) => {
|
||||
e.preventDefault()
|
||||
await wakuVoting?.createTimedPoll(
|
||||
await wakuPolling?.createTimedPoll(
|
||||
signer,
|
||||
question,
|
||||
answers,
|
||||
|
|
|
@ -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 { Wallet } from 'ethers'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
|
@ -7,24 +7,22 @@ import { JsonRpcSigner } from '@ethersproject/providers'
|
|||
import styled from 'styled-components'
|
||||
|
||||
type PollListProps = {
|
||||
wakuVoting: WakuVoting | undefined
|
||||
wakuPolling: WakuPolling | undefined
|
||||
signer: Wallet | JsonRpcSigner | undefined
|
||||
}
|
||||
|
||||
export function PollList({ wakuVoting, signer }: PollListProps) {
|
||||
export function PollList({ wakuPolling, signer }: PollListProps) {
|
||||
const [polls, setPolls] = useState<DetailedTimedPoll[]>([])
|
||||
const [dividedPolls, setDividedPolls] = useState<DetailedTimedPoll[][]>([[], [], []])
|
||||
useEffect(() => {
|
||||
const interval = setInterval(async () => {
|
||||
if (wakuVoting) {
|
||||
const { DetailedTimedPolls, updated } = await wakuVoting.getDetailedTimedPolls()
|
||||
if (updated) {
|
||||
setPolls(DetailedTimedPolls)
|
||||
}
|
||||
if (wakuPolling) {
|
||||
const DetailedTimedPolls = await wakuPolling.getDetailedTimedPolls()
|
||||
setPolls(DetailedTimedPolls)
|
||||
}
|
||||
}, 1000)
|
||||
return () => clearInterval(interval)
|
||||
}, [wakuVoting])
|
||||
}, [wakuPolling])
|
||||
|
||||
useEffect(() => {
|
||||
let arrayNo = 0
|
||||
|
@ -45,7 +43,7 @@ export function PollList({ wakuVoting, signer }: PollListProps) {
|
|||
return (
|
||||
<ColumnWrapper key={idx}>
|
||||
{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>
|
||||
)
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -1,3 +1,3 @@
|
|||
import { useWakuVoting } from './hooks/useWakuVoting'
|
||||
import { useWakuPolling } from './hooks/useWakuPolling'
|
||||
|
||||
export { useWakuVoting }
|
||||
export { useWakuPolling }
|
||||
|
|
|
@ -4,7 +4,7 @@ import { useEthers } from '@usedapp/core'
|
|||
import styled from 'styled-components'
|
||||
import { PollList, PollCreation } from '@status-waku-voting/polling-components'
|
||||
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'
|
||||
|
||||
type WakuPollingProps = {
|
||||
|
@ -16,11 +16,11 @@ export function WakuPolling({ appName, signer }: WakuPollingProps) {
|
|||
const { activateBrowserWallet, account } = useEthers()
|
||||
const [showPollCreation, setShowPollCreation] = useState(false)
|
||||
const [selectConnect, setSelectConnect] = useState(false)
|
||||
const wakuVoting = useWakuVoting(appName, '0x01')
|
||||
const wakuPolling = useWakuPolling(appName, '0x01')
|
||||
return (
|
||||
<Wrapper>
|
||||
{showPollCreation && signer && (
|
||||
<PollCreation signer={signer} wakuVoting={wakuVoting} setShowPollCreation={setShowPollCreation} />
|
||||
<PollCreation signer={signer} wakuPolling={wakuPolling} setShowPollCreation={setShowPollCreation} />
|
||||
)}
|
||||
{account ? (
|
||||
<CreatePollButton disabled={!signer} onClick={() => setShowPollCreation(true)}>
|
||||
|
@ -43,7 +43,7 @@ export function WakuPolling({ appName, signer }: WakuPollingProps) {
|
|||
</Modal>
|
||||
)}
|
||||
|
||||
<PollList wakuVoting={wakuVoting} signer={signer} />
|
||||
<PollList wakuPolling={wakuPolling} signer={signer} />
|
||||
</Wrapper>
|
||||
)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue