DVote JS
Formerly known as dvote-client, this library aims to provide utility classes and methods to invoke decentralized operations within a voting process. It covers the typical functionality of Client applications, as well as the Process Manager or the Census Manager.
The intended functionality is to interact with a public Ethereum blockchain, to fetch data from a decentralized filesystem, to enforce data schema validity, to prepare vote packages and using decentralized messaging networks through Gateways or Relays.
Getting started
The library is built on top of Ethers.js, which is fully compatible with Web3.
npm install ethers
To interact with the blockchain, we need a Provider. In order to send transactions we need a Wallet to sign them as well.
Blockchain read-only interactions
Start by defining a provider:
const ethers = require("ethers")
import { ethers } from "ethers"
const provider = ethers.getDefaultProvider('homestead')
const altProvider = new ethers.providers.EtherscanProvider('ropsten')
const currentProvider1 = new web3.providers.HttpProvider('http://localhost:8545')
const web3Provider1 = new ethers.providers.Web3Provider(currentProvider1)
const currentProvider2 = new web3.providers.JsonRpcProvider('http://localhost:8545')
const web3Provider2 = new ethers.providers.Web3Provider(currentProvider2)
More information
Next, initialize a contract factory and use it to attach to an instance.
const { EntityResolver } = require("dvote-js")
const EntityResolverFactory = new EntityResolver({ provider })
const EntityResolverFactory2 = new EntityResolver({ providerUrl: "http://localhost:8545" })
const resolverContractAddress = "0x0123456789012345678901234567890123456789"
const resolverInstance = EntityResolverFactory.attach(resolverContractAddress)
const entityId = EntityResolver.getEntityId(myEntityAddress)
const value = await resolverInstance.text(entityId, "key-name")
console.log("value=", value)
Blockchain transcations
To send signed transactions to the blockchain, you need a funded account.
DVoteJS uses an Ethers.js wallet internally. You can simply attach to MetaMask/Mist/Parity when available and you can also provide your private key or a mnemonic on local environments (NodeJS).
More information
MetaMask, Mist or Parity
On Web3 enabled browsers, the provider can be simply borrowed from window.web3.currentProvider
. The wallet is automatically available.
import ethers from "ethers"
import { EntityResolver } from "dvote-js"
const myProvider = new ethers.providers.Web3Provider(web3.currentProvider)
const EntityResolverFactory = new EntityResolver({ web3Provider: myProvider })
NodeJS or local environments
Otherwise, you need to provide a private key or a mnemonic seed phrase (with an optional derivation path).
const { EntityResolver } = require("dvote-js")
const EntityResolverFactory = new EntityResolver({ provider, privateKey: "...." })
const EntityResolverFactory = new EntityResolver({ provider, mnemonic: "...", mnemonicPath: "m/44'/60'/0'/0/3" })
Sending actual transactions
Now, in both cases you have connected contract factories that can attach to any contract and eventually send signed transcations.
const myWallet = EntityResolverFactory.wallet
const myEntityAddress = await myWallet.getAddress()
const entityId = EntityResolver.getEntityId(myEntityAddress)
const resolverInstance = await EntityResolverFactory.deploy()
const tx = await resolverInstance.setText(entityId, "another-key-name", "My official entity")
await tx.wait()
const value = await resolverInstance.text(entityId, "another-key-name")
console.log("value=", value)
Components
const { EntityResolver, VotingProcess, Gateway } = require("dvote-js")
Entity Resolver
Mainly used to query the metadata of an Entity on a ENS resolver, but also used to set and update its values.
EntityResolver
provides a contract factory so that instances can be deployed or attached to. But it also provides utility functions to compute entityId
's or fetch JSON metadata.
Voting Process
Mainly used to query the metadata of a voting process within the contract instance. Also used to create votes, publish batches and get the encryption keys.
VotingProcess
provides a contract factory so that instances can be deployed or attached to. But it also provides utility functions to compute the processId
or fetch JSON metadata as well.
Gateway
Provides utility functions to fetch data from decentralized filesystems, sending messages and adding files to Swarm/IPFS.
Example usage
const { EntityResolver, VotingProcess } = require("dvote-js")
const EntityResolverFactory = new EntityResolver({ provider })
const VotingProcessFactory = new VotingProcess({ providerUrl: "http://localhost:8545" })
const resolverContractAddress = "0x0123456789012345678901234567890123456789"
const resolverInstance = EntityResolverFactory.attach(resolverContractAddress)
const entityId = EntityResolver.getEntityId(myEntityAddress)
const value = await resolverInstance.text(entityId, "key-name")
console.log("value=", value)
const votingProcessAddress = "0x1234567890123456789012345678901234567890"
const votingProcessInstance = VotingProcessFactory.attach(vProcessAddress)
const processNumber = 10
const pocessId = VotingProcess.getProcessId(entityId, processNumber)
const processData = await votingProcessInstance.get(processId)
const {
entityResolver,
entityAddress,
processName,
metadataContentUri,
startTime,
endTime,
voteEncryptionPublicKey,
canceled
} = processData
console.log("Voting process name:", processName)
Full API details coming soon. You can have a look at test/unit/entity-resolver
and test/unit/voting-process
meanwhile.
Example
Check out example/index.js
.
Development
Simply run npm run test
. It is an alias for npm run test:unit
and npm run test:integration
.
- Unit testing will start an internal Ganache provider and launch transactions to it
- Integration testing is still a WIP
Builders
In order to avoid tedious and repetitive testing code, you can check out the test/builders
folder. Entity and Process builders deploy a new instance and create an Entity/Process with default values. These default values can be overridden with one-liners, if needed:
const EntityBuilder = require("./test/builders/entity-resolver")
const VoteBuilder = require("./test/builders/voting-process")
const contractInstance1 = await new EntityBuilder().build()
const contractInstance2 = await new VotingProcessBuilder().build()
const contractInstance3 = await new EntityBuilder()
.withName("Another name")
.build()
const contractInstance4 = await new VotingProcessBuilder()
.withEntityResolver("0x0123456789012345678901234567890123456789")
.withVotingPublicKey("...")
.build(3)
Note: This is still under heavy development.
Mocha
When adding new test suites, don't forget to add a call to addCompletionHooks()
. Otherwise, the NodeJS process will keep up indefinitely when testing.
Simulating future timestamps
If you need a transaction to happen in a future timestamp, use test/eth-utils > increaseTimestamp()
instead of forcing your code to wait.
Be aware that from this point, if you use Date.now()
on the Javascript side, values will not match the timestamp of the blockchain. So make sure to call getBlockNumber()
and getBlock(<num>) > timestamp
.
Testing accounts
Use test/eth-utils > getAccounts()
to retrieve a list of 10 funded accounts with the following data schema:
{
privateKey: "...",
address: "0x...",
provider: <ethers-js-provider>,
wallet: <ethers-js-wallet>
}
These accounts are connected to an in-memory Ganache RPC node.