Compare commits

...

26 Commits

Author SHA1 Message Date
dependabot[bot] c69f9d3c08
Bump webpack-dev-server from 2.11.2 to 3.1.11
Bumps [webpack-dev-server](https://github.com/webpack/webpack-dev-server) from 2.11.2 to 3.1.11.
- [Release notes](https://github.com/webpack/webpack-dev-server/releases)
- [Changelog](https://github.com/webpack/webpack-dev-server/blob/master/CHANGELOG.md)
- [Commits](https://github.com/webpack/webpack-dev-server/compare/v2.11.2...v3.1.11)

Signed-off-by: dependabot[bot] <support@github.com>
2022-04-27 10:35:13 +00:00
varasev e3f8b7de19
Add POA-GNO merge warning (#223) 2022-04-27 13:33:09 +03:00
varasev 8cafb1fd7f
Merge pull request #220 from poanetwork/core-metamask-provider-updated
(Fix) Update getWeb3 script according to MetaMask breaking changes
2020-11-17 10:39:27 +03:00
POA 356baf1386 Small improvement in getWeb3 2020-11-17 10:32:51 +03:00
PoaMan 5e34b51b30 Refactor getNetId function 2020-11-16 16:01:16 +03:00
PoaMan 889fa30e7c Update src/utils/getWeb3.js
due to MetaMask breaking changes: https://docs.metamask.io/guide/provider-migration.html
2020-11-16 15:19:21 +03:00
varasev 89feb07a88
Merge pull request #219 from poanetwork/increase-ballots-loading-speed
Increase ballots loading speed
2020-04-21 14:42:00 +03:00
Vadim a1bde70929 Small fixes 2020-04-21 14:35:19 +03:00
Max Alekseenko cbf63a0b9d store finalized ballots in local storage 2020-04-17 21:58:06 +03:00
Max Alekseenko e569caf9f6 increase ballots loading speed 2020-04-17 15:12:59 +03:00
varasev 5bb4a3d5ed
Merge pull request #218 from poanetwork/remove-xDai
(Fix) Remove xDai from the list of supported chains
2020-04-14 12:02:23 +03:00
Vadim 94ef8f009d Remove xDai from the list of supported chains 2020-04-14 11:37:42 +03:00
Victor Baranov 7769fd21cf
Merge pull request #217 from poanetwork/dai-branch-fix
(Fix) Set exact repo tree for xDai since poa-chain-spec/contracts.json changed
2020-03-24 21:51:57 +03:00
Vadim 617152a903 Set exact repo tree for xDai since poa-chain-spec/contracts.json changed 2020-03-24 21:42:28 +03:00
varasev 23ee507584
Merge pull request #213 from poanetwork/enable-wallet-only-when-needed
Call `window.ethereum.enable` only when that's really needed
2020-01-09 17:38:53 +03:00
Vadim 8f7279519e Improvements and optimizations 2020-01-09 16:02:58 +03:00
varasev 557bc54778
Merge branch 'core' into enable-wallet-only-when-needed 2020-01-09 11:48:13 +03:00
Max Alekseenko 8248992d46 Fetch the threshold for each ballot (#214)
* load threshold for each ballot

* Fix minThreshold for ballot

Co-authored-by: varasev <33550681+varasev@users.noreply.github.com>
2019-12-30 14:58:01 +03:00
Max Alekseenko b314def1a9 Select the network from metamask when first visiting (#212)
* select the network from metamask when first visiting

* Keep netId in browser's storage and use it when page first loading if possible

Co-authored-by: varasev <33550681+varasev@users.noreply.github.com>
2019-12-30 13:45:14 +03:00
phahulin 0d04cebeb5
Merge pull request #216 from poanetwork/metamask-legacy-account-switcher
(Fix) Remove subscriber for legacy MetaMask account switcher event
2019-12-04 10:59:06 +03:00
Vadim 893620c76b Remove subscriber for MetaMask legacy account switcher 2019-12-04 10:28:53 +03:00
Max Alekseenko 051b1a1d57 remove comment 2019-11-19 15:37:48 +03:00
Max Alekseenko c8047f9520 getBallotsLimits after the wallet was enabled 2019-11-19 14:38:53 +03:00
Max Alekseenko 9559fe4273 enable wallet only when it's needed 2019-11-19 14:04:14 +03:00
varasev 795472ec57
Optimize BallotCard (#211) 2019-11-04 16:55:41 +03:00
Max Alekseenko 1cecd5d169 Add in-app network switch (#203)
* add network select

* new network select component

* improve network switch

* Fixes and refactoring

* Refactoring
2019-10-29 09:43:19 +03:00
38 changed files with 6454 additions and 5239 deletions

View File

@ -3,7 +3,6 @@
## Base supported networks
- Core POA network (RPC endpoint: `https://core.poa.network`)
- xDai chain (RPC endpoint: `https://dai.poa.network`)
- Sokol testnet (RPC endpoint: `https://sokol.poa.network`)
- Kovan testnet (RPC endpoint: `https://kovan.infura.io/`)
@ -24,7 +23,7 @@ Please check related posts in wiki [Governance Overview](https://github.com/poan
## Configuration
Governance DApp is configured with [POA Network governance contracts](https://github.com/poanetwork/poa-network-consensus-contracts)
There are contracts' addresses for [Sokol](https://github.com/poanetwork/poa-chain-spec/blob/sokol/contracts.json), [Core](https://github.com/poanetwork/poa-chain-spec/blob/core/contracts.json), [xDai](https://github.com/poanetwork/poa-chain-spec/blob/dai/contracts.json), and [Kovan](https://github.com/poanetwork/poa-chain-spec/blob/kovan/contracts.json).
There are contracts' addresses for [Sokol](https://github.com/poanetwork/poa-chain-spec/blob/sokol/contracts.json), [Core](https://github.com/poanetwork/poa-chain-spec/blob/core/contracts.json), and [Kovan](https://github.com/poanetwork/poa-chain-spec/blob/kovan/contracts.json).
## Building from source

10508
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -64,7 +64,7 @@
"url-loader": "0.6.2",
"web3": "1.0.0-beta.34",
"webpack": "3.12.0",
"webpack-dev-server": "2.11.2",
"webpack-dev-server": "3.1.11",
"webpack-manifest-plugin": "1.3.2",
"whatwg-fetch": "2.0.3"
},

View File

@ -7,7 +7,7 @@
<link rel="manifest" href="%PUBLIC_URL%/manifest.json">
<link rel="shortcut icon" href="%PUBLIC_URL%/favicons/favicon.ico">
<link href="https://fonts.googleapis.com/css?family=Nunito:300,400,700" rel="stylesheet">
<title>POA Network Governance DApp</title>
<title>POA Governance DApp</title>
<script src="https://maps.googleapis.com/maps/api/js?key=AIzaSyD97qDOBYZ2fH86Wq1vzhDOiSUsZGVqbVQ&libraries=places"></script>
</head>
<body>

View File

@ -5,7 +5,8 @@ import { Route, Redirect } from 'react-router-dom'
import { constants } from './utils/constants'
import { getNetworkBranch } from './utils/utils'
import { inject, observer } from 'mobx-react'
import { messages } from './utils/messages'
import messages from './utils/messages'
import { enableWallet } from './utils/getWeb3'
import './assets/stylesheets/index.css'
@ -41,16 +42,32 @@ class App extends Component {
onNewBallotRender = () => {
const { commonStore, contractsStore } = this.props
if (!contractsStore.web3Instance) {
if (!commonStore.loading) {
swal({
title: 'Error',
html: messages.NO_METAMASK_MSG,
icon: 'error',
type: 'error'
if (!commonStore.loading) {
enableWallet(contractsStore.updateKeys)
.then(() => {
if (!contractsStore.injectedWeb3) {
swal({
title: 'Error',
html: messages.NO_METAMASK_MSG,
type: 'error'
})
} else if (!contractsStore.networkMatch) {
swal({
title: 'Warning!',
html: messages.networkMatchError(contractsStore.netId),
type: 'warning'
})
} else if (!contractsStore.isValidVotingKey) {
swal({
title: 'Warning!',
html: messages.invalidVotingKeyMsg(contractsStore.votingKey),
type: 'warning'
})
}
})
.catch(error => {
swal('Error', error.message, 'error')
})
}
return null
}
return <NewBallot networkBranch={this.getVotingNetworkBranch()} />
}
@ -60,8 +77,7 @@ class App extends Component {
}
onSearch = e => {
const { commonStore } = this.props
commonStore.setSearchTerm(e.target.value.toLowerCase())
this.setSearchTerm(e.target.value)
}
hideSearch = () => {
@ -85,13 +101,28 @@ class App extends Component {
return 'All'
}
setSearchTerm = term => {
const { commonStore } = this.props
commonStore.setSearchTerm(term.toLowerCase())
if (this.refs.searchBar) {
this.refs.searchBar.setSearchTerm(term)
}
}
onNetworkChange = e => {
this.setSearchTerm('')
this.props.onNetworkChange(e)
}
isNewBallotPage() {
return `${constants.rootPath}/new` === this.props.location.pathname
}
render() {
const { commonStore, contractsStore } = this.props
const networkBranch = this.getVotingNetworkBranch()
const networkBranch = commonStore.loadingNetworkBranch
? commonStore.loadingNetworkBranch
: this.getVotingNetworkBranch()
return networkBranch ? (
<div
@ -104,10 +135,18 @@ class App extends Component {
baseRootPath={commonStore.rootPath}
netId={contractsStore.netId}
networkBranch={networkBranch}
onChange={this.onNetworkChange}
onMenuToggle={this.toggleMobileMenu}
showMobileMenu={this.state.showMobileMenu}
/>
{this.hideSearch() ? null : <SearchBar networkBranch={networkBranch} onSearch={this.onSearch} />}
{this.hideSearch() ? null : (
<SearchBar
networkBranch={networkBranch}
onSearch={this.onSearch}
searchTerm={commonStore.searchTerm}
ref="searchBar"
/>
)}
<MainTitle text={this.getTitle()} />
<section
className={`lo-App_Content lo-App_Content-${networkBranch} ${

View File

@ -7,7 +7,6 @@
height: $header-height;
justify-content: center;
left: 0;
overflow: hidden;
position: fixed;
right: 0;
top: 0;
@ -58,4 +57,4 @@
@media (min-width: $breakpoint-md) {
display: flex;
}
}
}

View File

@ -0,0 +1,3 @@
.nl-IconNetwork_Path {
@include menu-icon-colors();
}

View File

@ -0,0 +1,101 @@
.NetworkSelect {
display: flex;
@media (min-width: $breakpoint-md) {
position: relative;
margin-left: 34px;
&:hover {
.NetworkSelect_List {
display: block;
}
}
}
}
.sw-Header_Content .NetworkSelect {
display: none;
@media (min-width: $breakpoint-md) {
display: flex;
}
}
.NetworkSelect_Top {
display: flex;
align-items: center;
opacity: .7;
transition: .15s ease-in;
height: 50px;
width: 100%;
justify-content: center;
cursor: pointer;
&:hover {
opacity: 1;
@media (min-width: $breakpoint-md) {
.nl-IconNetwork_Arrow {
transform: rotate(180deg);
}
}
}
}
.nl-NavigationLinks_Link.opacityFull {
opacity: 1;
flex-direction: column;
height: auto !important;
}
.nl-IconNetwork_Arrow {
margin-left: 10px;
transition: .15s ease-in;
path {
fill: #fff;
opacity: .52;
}
}
.NetworkSelect_List {
font-size: 14px;
font-weight: 600;
list-style: none;
padding-left: 0;
width: 100%;
margin: 0;
@media (min-width: $breakpoint-md) {
display: none;
position: absolute;
top: 100%;
right: 0;
background-color: #fff;
width: 180px;
border-radius: 4px 0 4px 4px;
box-shadow: 0 5px 10px rgba(#000, .05);
padding-top: 8px;
padding-bottom: 8px;
}
li.currentNetwork {
@media (max-width: $breakpoint-md) {
display: none;
}
}
button {
display: flex;
width: 100%;
align-items: center;
justify-content: center;
text-decoration: none;
color: #fff;
transition: .15s ease-in;
height: 50px;
background-color: transparent;
border: none;
cursor: pointer;
@media (min-width: $breakpoint-md) {
padding: 8px 18px;
height: auto;
color: $base-text-color;
justify-content: flex-start;
&:hover {
background-color: #f7f7f7;
}
}
}
}

View File

@ -0,0 +1,8 @@
.top-Select {
flex-grow: 1;
max-width: 140px;
@media (min-width: $breakpoint-xl) {
margin-left: 20px;
}
}

View File

@ -30,6 +30,7 @@
@import "IconGithub";
@import "IconLoadMore";
@import "IconMobileMenu";
@import "IconNetwork";
@import "IconPOA";
@import "IconTelegram";
@import "IconToFinalize";
@ -47,9 +48,11 @@
@import "NewBallot";
@import "NewBallotMenu";
@import "NewBallotMenuInfo";
@import "NetworkSelect";
@import "SearchBar";
@import "Select";
@import "Separator";
@import "SocialIcons";
@import "Validator";
@import "VoteProgressBar";
@import "Votes";
@import "Votes";

File diff suppressed because one or more lines are too long

View File

@ -7,9 +7,10 @@ import { BallotInfoContainer } from '../BallotInfoContainer'
import { Votes } from '../Votes'
import { getNetworkBranch } from '../../utils/utils'
import { inject, observer } from 'mobx-react'
import { messages } from '../../utils/messages'
import messages from '../../utils/messages'
import { observable, action, computed } from 'mobx'
import { sendTransactionByVotingKey } from '../../utils/helpers'
import { enableWallet } from '../../utils/getWeb3'
const ACCEPT = 1
const REJECT = 2
@ -59,6 +60,7 @@ export class BallotCard extends React.Component {
@observable memo
@observable quorumState
@observable minBallotDuration
@observable minThreshold
@computed
get cancelOrFinalizeButtonDisplayName() {
@ -302,6 +304,9 @@ export class BallotCard extends React.Component {
this.getHasAlreadyVoted()
}
// minThreshold
this.getMinThreshold()
if (votingType === 'votingToManageEmissionFunds') {
this.getQuorumState()
}
@ -317,9 +322,19 @@ export class BallotCard extends React.Component {
return formattedMs
}
@action('ballot min threshold of voters')
getMinThreshold = async () => {
const { contractsStore, id, votingType } = this.props
this.minThreshold = await this.getContract(contractsStore, votingType).getMinThresholdOfVoters(id)
}
@action('validator has already voted')
getHasAlreadyVoted = async () => {
const { contractsStore, id, votingType } = this.props
if (contractsStore.miningKey === '0x0000000000000000000000000000000000000000') {
this.hasAlreadyVoted = false
return
}
let _hasAlreadyVoted = false
try {
_hasAlreadyVoted = await this.getContract(contractsStore, votingType).hasAlreadyVoted(
@ -360,6 +375,27 @@ export class BallotCard extends React.Component {
this.quorumState = await this.repeatGetProperty(contractsStore, votingType, id, 'getQuorumState', 0)
}
networkAndKeyValidation = async () => {
const { contractsStore } = this.props
try {
await enableWallet(contractsStore.updateKeys)
} catch (error) {
swal('Error', error.message, 'error')
return false
}
if (contractsStore.isEmptyVotingKey) {
swal('Warning!', messages.NO_METAMASK_MSG, 'warning')
return false
} else if (!contractsStore.networkMatch) {
swal('Warning!', messages.networkMatchError(contractsStore.netId), 'warning')
return false
} else if (!contractsStore.isValidVotingKey) {
swal('Warning!', messages.invalidVotingKeyMsg(contractsStore.votingKey), 'warning')
return false
}
return true
}
vote = async ({ choice }) => {
if (this.isCanceled) {
swal('Warning!', messages.INVALID_VOTE_MSG, 'warning')
@ -369,15 +405,13 @@ export class BallotCard extends React.Component {
swal('Warning!', messages.ballotIsNotActiveMsg(this.timeTo.displayValue), 'warning')
return
}
const { commonStore, contractsStore, id, votingType, ballotsStore, pos } = this.props
const { push } = this.props.routing
if (!contractsStore.votingKey) {
swal('Warning!', messages.NO_METAMASK_MSG, 'warning')
return
} else if (!contractsStore.isValidVotingKey) {
swal('Warning!', messages.invalidVotingKeyMsg(contractsStore.votingKey), 'warning')
if (!(await this.networkAndKeyValidation())) {
return
}
const { commonStore, contractsStore, id, votingType, ballotsStore, pos } = this.props
const { push } = this.props.routing
commonStore.showLoading()
let isValidVote = await this.isValidVote()
if (!isValidVote) {
@ -456,6 +490,11 @@ export class BallotCard extends React.Component {
const { votingState, contractsStore, commonStore, ballotsStore, votingType, id, pos } = this.props
const { push } = this.props.routing
const contract = this.getContract(contractsStore, votingType)
if (!(await this.networkAndKeyValidation())) {
return
}
let canCancel = true
if (!this.timeToCancel.val) {
@ -508,13 +547,11 @@ export class BallotCard extends React.Component {
}
const { commonStore, contractsStore, id, votingType, ballotsStore, pos } = this.props
const { push } = this.props.routing
if (!contractsStore.votingKey) {
swal('Warning!', messages.NO_METAMASK_MSG, 'warning')
return
} else if (!contractsStore.isValidVotingKey) {
swal('Warning!', messages.invalidVotingKeyMsg(contractsStore.votingKey), 'warning')
if (!(await this.networkAndKeyValidation())) {
return
}
if (this.isFinalized) {
swal('Warning!', messages.ALREADY_FINALIZED_MSG, 'warning')
return
@ -600,21 +637,6 @@ export class BallotCard extends React.Component {
}
}
getThreshold(contractsStore, votingType) {
switch (votingType) {
case 'votingToChangeKeys':
return contractsStore.keysBallotThreshold
case 'votingToChangeMinThreshold':
return contractsStore.minThresholdBallotThreshold
case 'votingToChangeProxy':
return contractsStore.proxyBallotThreshold
case 'votingToManageEmissionFunds':
return contractsStore.emissionFundsBallotThreshold
default:
return contractsStore.keysBallotThreshold
}
}
getMinBallotDuration(contractsStore, votingType) {
switch (votingType) {
case 'votingToChangeKeys':
@ -672,10 +694,9 @@ export class BallotCard extends React.Component {
}
render() {
let { contractsStore, votingType, children } = this.props
let { votingType, children } = this.props
let votes
const threshold = this.getThreshold(contractsStore, votingType)
const networkBranch = this.getVotingNetworkBranch()
if (votingType === 'votingToManageEmissionFunds') {
@ -734,12 +755,7 @@ export class BallotCard extends React.Component {
/>
</div>
<Votes networkBranch={networkBranch} votes={votes} />
<BallotInfoContainer
memo={this.memo}
networkBranch={networkBranch}
threshold={threshold}
validatorsLength={contractsStore.validatorsLength}
/>
<BallotInfoContainer memo={this.memo} networkBranch={networkBranch} threshold={this.minThreshold} />
<BallotFooter
buttonState={this.cancelOrFinalizeButtonState}
buttonText={this.cancelOrFinalizeButtonDisplayName}

View File

@ -18,7 +18,7 @@ export class BallotInfoContainer extends React.Component {
}
render() {
let { memo = '', threshold, validatorsLength, networkBranch } = this.props
let { memo = '', threshold, networkBranch } = this.props
let toggleShowMore =
memo.length > MAX_DETAILS_LENGTH ? (
<span
@ -34,7 +34,7 @@ export class BallotInfoContainer extends React.Component {
return (
<div className="bc-BallotInfoContainer">
<div className="bc-BallotInfoContainer_Info bc-BallotInfoContainer_Info-minimum">
Minimum {threshold} from {validatorsLength} validators are required to pass the proposal
Minimum {threshold} validators are required to pass the proposal
</div>
<div
className={`bc-BallotInfoContainer_Info bc-BallotInfoContainer_Info-details ${

View File

@ -45,7 +45,7 @@ export class BallotKeysMetadata extends React.Component {
<FormSelect
disabled={ballotStore.isNewValidatorPersonalData}
hint="Mining key address of validator to vote for.<br />Example: 0xc70760D23557A4FDE612C0bE63b26EBD023C51Ee."
id="mining-key"
id="mining-key-select"
name="form-field-name"
networkBranch={networkBranch}
onChange={ballotStore.setMiningKey}

View File

@ -2,14 +2,12 @@ import React from 'react'
import { FormInput } from '../FormInput'
import { FormSelect } from '../FormSelect'
import { inject, observer } from 'mobx-react'
import { constants } from '../../utils/constants'
import { getNetworkName } from '../../utils/utils'
@inject('ballotStore', 'contractsStore')
@inject('ballotStore')
@observer
export class BallotProxyMetadata extends React.Component {
render() {
const { ballotStore, contractsStore, networkBranch } = this.props
const { ballotStore, networkBranch } = this.props
let options = [
/*0*/ { value: '', label: '' },
/*1*/ { value: '1', label: ballotStore.ProxyBallotType[1] }, // KeysManager

View File

@ -1,20 +1,31 @@
import React from 'react'
import { ButtonNewBallot } from '../ButtonNewBallot'
import { IconMobileMenu } from '../IconMobileMenu'
import { Logo } from '../Logo'
import { MobileMenuLinks } from '../MobileMenuLinks'
import { NavigationLinks } from '../NavigationLinks'
import NetworkSelect from '../NetworkSelect'
export const Header = ({ baseRootPath = '', networkBranch = undefined, onMenuToggle, showMobileMenu = false }) => {
export const Header = ({
baseRootPath = '',
networkBranch = undefined,
onChange,
onMenuToggle,
showMobileMenu = false
}) => {
return (
<header className={`sw-Header sw-Header-${networkBranch} ${showMobileMenu ? 'sw-Header-menu-open' : ''}`}>
{showMobileMenu ? <MobileMenuLinks networkBranch={networkBranch} onClick={onMenuToggle} /> : null}
{showMobileMenu ? (
<MobileMenuLinks networkBranch={networkBranch} onClick={onMenuToggle} onNetworkChange={onChange} />
) : null}
<div className="sw-Header_Content">
<Logo networkBranch={networkBranch} href={baseRootPath} />
<div className="sw-Header_Links">
<NavigationLinks networkBranch={networkBranch} />
<ButtonNewBallot networkBranch={networkBranch} />
</div>
<NetworkSelect networkBranch={networkBranch} onChange={onChange} />
<IconMobileMenu networkBranch={networkBranch} isOpen={showMobileMenu} onClick={onMenuToggle} />
</div>
</header>

View File

@ -1,5 +1,5 @@
import React from 'react'
import xDaiLogo from './xdai.svg'
//import xDaiLogo from './xdai.svg'
import poaLogo from './core.svg'
import sokolLogo from './sokol.svg'
import kovanLogo from './kovan.svg'
@ -9,7 +9,7 @@ const getLogoSrc = networkBranch => {
{
core: poaLogo,
sokol: sokolLogo,
dai: xDaiLogo,
//dai: xDaiLogo,
kovan: kovanLogo
}[networkBranch] || poaLogo
)

View File

@ -2,7 +2,7 @@ import React from 'react'
import { LogoPOA } from '../LogoPOA'
import { LogoSokol } from '../LogoSokol'
import { LogoKovan } from '../LogoKovan'
import { LogoDai } from '../LogoDai'
//import { LogoDai } from '../LogoDai'
import { constants } from '../../utils/constants'
export const Logo = ({ href = null, extraClass = '', networkBranch = '' }) => {
@ -11,8 +11,8 @@ export const Logo = ({ href = null, extraClass = '', networkBranch = '' }) => {
return <LogoSokol href={href} extraClass={extraClass} />
case constants.KOVAN:
return <LogoKovan href={href} extraClass={extraClass} />
case constants.DAI:
return <LogoDai href={href} extraClass={extraClass} />
//case constants.DAI:
// return <LogoDai href={href} extraClass={extraClass} />
default:
return <LogoPOA href={href} extraClass={extraClass} />
}

View File

@ -1,12 +1,14 @@
import React from 'react'
import { ButtonNewBallot } from '../ButtonNewBallot'
import { NavigationLinks } from '../NavigationLinks'
import NetworkSelect from '../NetworkSelect'
export const MobileMenuLinks = ({ onClick, networkBranch }) => {
export const MobileMenuLinks = ({ onClick, networkBranch, onNetworkChange }) => {
return (
<div className={`hd-MobileMenuLinks hd-MobileMenuLinks-${networkBranch}`} onClick={onClick}>
<NavigationLinks networkBranch={networkBranch} />
<ButtonNewBallot networkBranch={networkBranch} />
<NetworkSelect networkBranch={networkBranch} onChange={onNetworkChange} />
</div>
)
}

View File

@ -0,0 +1,72 @@
import React, { Component } from 'react'
import { constants } from '../../utils/constants'
export default class NetworkSelect extends Component {
changeNetworkRPC(e) {
e.preventDefault()
let getCurrentClickedLink = e.target.innerHTML
let getCurrentClickedLinkId = ''
for (const _netId in constants.NETWORKS) {
if (constants.NETWORKS[_netId].FULLNAME === getCurrentClickedLink) {
getCurrentClickedLinkId = _netId
}
}
this.props.onChange({ value: getCurrentClickedLinkId })
}
render() {
let networkFullNames = []
let currentNetworkFullName = ''
const networks = constants.NETWORKS
let netIds = []
Object.keys(networks)
.sort((a, b) => (networks[a].SORTORDER > networks[b].SORTORDER ? 1 : -1))
.forEach(function(_netId) {
netIds.push(_netId)
})
let selectedNetworkIndex = -1
netIds.forEach(_netId => {
networkFullNames.push(networks[_netId].FULLNAME)
if (networks[_netId].BRANCH === this.props.networkBranch) {
currentNetworkFullName = networks[_netId].FULLNAME
selectedNetworkIndex = networkFullNames.length - 1
}
})
const listItems = networkFullNames.map((name, index) => {
let className = ''
if (index === selectedNetworkIndex) {
className = 'currentNetwork'
}
return (
<li key={name.toString()} className={className}>
<button onClick={e => this.changeNetworkRPC(e)}>{name}</button>
</li>
)
})
return (
<div className={`NetworkSelect nl-NavigationLinks_Link opacityFull`}>
<div className={`NetworkSelect_Top`}>
<svg className={`nl-IconNetwork`} xmlns="http://www.w3.org/2000/svg" width="18" height="18">
<path
className={`nl-IconNetwork_Path nl-IconNetwork_Path-${this.props.networkBranch}`}
d="M9 18a9 9 0 0 1-9-9 9 9 0 0 1 9-9 9 9 0 0 1 9 9 9 9 0 0 1-9 9zm6.923-8h-1.974c-.116 1.85-.525 3.539-1.167 4.876A6.993 6.993 0 0 0 15.923 10zM9 16c1.51 0 2.747-2.612 2.957-6H6.043c.21 3.388 1.447 6 2.957 6zm-3.782-1.124C4.576 13.539 4.167 11.85 4.051 10H2.077a6.993 6.993 0 0 0 3.141 4.876zM2.077 8h1.974c.116-1.85.525-3.538 1.167-4.876A6.993 6.993 0 0 0 2.077 8zM9 2C7.49 2 6.253 4.612 6.043 8h5.914C11.747 4.612 10.51 2 9 2zm3.782 1.124C13.424 4.462 13.833 6.15 13.949 8h1.974a6.993 6.993 0 0 0-3.141-4.876z"
/>
</svg>
<span className={`nl-NavigationLinks_Text nl-NavigationLinks_Text-${this.props.networkBranch}`}>
{currentNetworkFullName}
</span>
<svg className={`nl-IconNetwork_Arrow`} xmlns="http://www.w3.org/2000/svg" width="8" height="4">
<path d="M0 0h8L4 4 0 0z" />
</svg>
</div>
<ul className={`NetworkSelect_List`}>{listItems}</ul>
</div>
)
}
}

View File

@ -15,8 +15,9 @@ import { Validator } from '../Validator'
import { constants } from '../../utils/constants'
import { getNetworkBranch } from '../../utils/utils'
import { inject, observer } from 'mobx-react'
import { messages } from '../../utils/messages'
import messages from '../../utils/messages'
import { sendTransactionByVotingKey } from '../../utils/helpers'
import { enableWallet } from '../../utils/getWeb3'
@inject('commonStore', 'ballotStore', 'validatorStore', 'contractsStore', 'routing', 'ballotsStore')
@observer
@ -40,9 +41,10 @@ export class NewBallot extends React.Component {
}
checkValidation() {
const { commonStore, contractsStore, ballotStore, validatorStore } = this.props
const { commonStore, contractsStore, ballotStore } = this.props
// Temporarily commented (until we implement https://github.com/poanetwork/poa-dapps-voting/issues/120)
// const { validatorStore } = this.props
// if (ballotStore.isNewValidatorPersonalData) {
// for (let validatorProp in validatorStore) {
// if (validatorStore[validatorProp].length === 0) {
@ -252,9 +254,19 @@ export class NewBallot extends React.Component {
const { commonStore, contractsStore, ballotStore, ballotsStore } = this.props
const { push } = this.props.routing
if (!contractsStore.votingKey) {
try {
await enableWallet(contractsStore.updateKeys)
} catch (error) {
swal('Error', error.message, 'error')
return
}
if (contractsStore.isEmptyVotingKey) {
swal('Warning!', messages.NO_METAMASK_MSG, 'warning')
return
} else if (!contractsStore.networkMatch) {
swal('Warning!', messages.networkMatchError(contractsStore.netId), 'warning')
return
} else if (!contractsStore.isValidVotingKey) {
swal('Warning!', messages.invalidVotingKeyMsg(contractsStore.votingKey), 'warning')
return

View File

@ -8,11 +8,11 @@ export const NewBallotMenuInfo = ({ minThreshold, validatorsLength, keys, valida
<li className="mn-NewBallotMenuInfo_ListItem">
Minimum {minThreshold} from {validatorsLength} validators are required to pass the&nbsp; proposal
</li>
<li className="mn-NewBallotMenuInfo_ListItem">You can create {keys} ballot(s) for keys</li>
<li className="mn-NewBallotMenuInfo_ListItem">You can create {Number(keys)} ballot(s) for keys</li>
<li className="mn-NewBallotMenuInfo_ListItem">
You can create {validatorLimitsMinThreshold} ballot(s) for consensus
You can create {Number(validatorLimitsMinThreshold)} ballot(s) for consensus
</li>
<li className="mn-NewBallotMenuInfo_ListItem">You can create {proxy} ballot(s) for proxy</li>
<li className="mn-NewBallotMenuInfo_ListItem">You can create {Number(proxy)} ballot(s) for proxy</li>
</ul>
</div>
)

View File

@ -1,16 +1,36 @@
import React from 'react'
export const SearchBar = ({ extraClassName = '', networkBranch = false, onSearch }) => {
return (
<div className={`sw-SearchBar sw-SearchBar-${networkBranch} ${extraClassName}`}>
<div className="sw-SearchBar_Content">
<input
className={`sw-SearchBar_Input sw-SearchBar_Input-${networkBranch}`}
onChange={onSearch}
placeholder="Search..."
type="search"
/>
export class SearchBar extends React.Component {
constructor(props) {
super(props)
this.state = { searchTerm: '' }
}
setSearchTerm(searchTerm) {
this.setState({ searchTerm })
}
componentDidMount() {
const { searchTerm } = this.props
if (searchTerm !== undefined) {
this.setSearchTerm(searchTerm)
}
}
render() {
const { networkBranch, onSearch } = this.props
return (
<div className={`sw-SearchBar sw-SearchBar-${networkBranch}`}>
<div className="sw-SearchBar_Content">
<input
className={`sw-SearchBar_Input sw-SearchBar_Input-${networkBranch}`}
onChange={onSearch}
placeholder="Search..."
type="search"
value={this.state.searchTerm}
/>
</div>
</div>
</div>
)
)
}
}

View File

@ -1,7 +1,7 @@
import React from 'react'
import { FormAutocomplete } from '../FormAutocomplete'
import { FormInput } from '../FormInput'
import { FormSelect } from '../FormSelect'
//import { FormAutocomplete } from '../FormAutocomplete'
//import { FormInput } from '../FormInput'
//import { FormSelect } from '../FormSelect'
import { constants } from '../../utils/constants'
import { geocodeByAddress } from 'react-places-autocomplete'
import { inject, observer } from 'mobx-react'
@ -64,6 +64,7 @@ export class Validator extends React.Component {
render() {
return null // Temporarily empty (until we implement https://github.com/poanetwork/poa-dapps-voting/issues/120)
/*
const { validatorStore, networkBranch } = this.props
const inputProps = {
value: validatorStore.address,
@ -190,5 +191,6 @@ export class Validator extends React.Component {
</div>
</div>
)
*/
}
}

View File

@ -62,6 +62,10 @@ export default class VotingToChangeKeys {
return this.instance.methods.votingState(_id).call()
}
getMinThresholdOfVoters(_id) {
return this.instance.methods.getMinThresholdOfVoters(_id).call()
}
hasAlreadyVoted(_id, votingKey) {
return this.instance.methods.hasAlreadyVoted(_id, votingKey).call()
}
@ -86,7 +90,7 @@ export default class VotingToChangeKeys {
return _limitPerValidator - _activeBallots
}
async minBallotDuration() {
return await this.instance.methods.minBallotDuration().call()
minBallotDuration() {
return this.instance.methods.minBallotDuration().call()
}
}

View File

@ -54,6 +54,10 @@ export default class VotingToChangeMinThreshold {
return this.instance.methods.votingState(_id).call()
}
getMinThresholdOfVoters(_id) {
return this.instance.methods.getMinThresholdOfVoters(_id).call()
}
hasAlreadyVoted(_id, votingKey) {
return this.instance.methods.hasAlreadyVoted(_id, votingKey).call()
}
@ -78,7 +82,7 @@ export default class VotingToChangeMinThreshold {
return _limitPerValidator - _activeBallots
}
async minBallotDuration() {
return await this.instance.methods.minBallotDuration().call()
minBallotDuration() {
return this.instance.methods.minBallotDuration().call()
}
}

View File

@ -50,6 +50,10 @@ export default class VotingToChangeProxy {
return this.instance.methods.votingState(_id).call()
}
getMinThresholdOfVoters(_id) {
return this.instance.methods.getMinThresholdOfVoters(_id).call()
}
hasAlreadyVoted(_id, votingKey) {
return this.instance.methods.hasAlreadyVoted(_id, votingKey).call()
}
@ -74,7 +78,7 @@ export default class VotingToChangeProxy {
return _limitPerValidator - _activeBallots
}
async minBallotDuration() {
return await this.instance.methods.minBallotDuration().call()
minBallotDuration() {
return this.instance.methods.minBallotDuration().call()
}
}

View File

@ -69,6 +69,10 @@ export default class VotingToManageEmissionFunds {
return this.instance.methods.getBallotInfo(_id).call()
}
getMinThresholdOfVoters(_id) {
return this.instance.methods.getMinThresholdOfVoters(_id).call()
}
getQuorumState(_id) {
return this.instance.methods.getQuorumState(_id).call()
}

View File

@ -1,5 +1,5 @@
import { constants } from '../utils/constants'
import { messages } from '../utils/messages'
import messages from '../utils/messages'
import swal from 'sweetalert2'
function addressesURL(branch) {

View File

@ -16,6 +16,8 @@ import { Router, Route } from 'react-router-dom'
import { RouterStore, syncHistoryWithStore } from 'mobx-react-router'
import { constants } from './utils/constants'
import { getContractsAddresses } from './contracts/addresses'
import { getNetworkBranch } from './utils/utils'
import messages from './utils/messages'
const browserHistory = createBrowserHistory()
const routingStore = new RouterStore()
@ -32,57 +34,24 @@ class AppMainRouter extends Component {
super(props)
commonStore.showLoading()
getWeb3()
window.addEventListener('load', () => this.initChain())
}
initChain = () => {
const netId = window.sessionStorage.netId
getWeb3(netId, contractsStore.updateKeys)
.then(async web3Config => {
await getContractsAddresses(constants.NETWORKS[web3Config.netId].BRANCH)
contractsStore.setWeb3Instance(web3Config)
const setPoaConsensus = contractsStore.setPoaConsensus(web3Config)
const setBallotsStorage = contractsStore.setBallotsStorage(web3Config)
const setKeysManager = contractsStore.setKeysManager(web3Config)
const setProxyStorage = contractsStore.setProxyStorage(web3Config)
const setVotingToChangeKeys = contractsStore.setVotingToChangeKeys(web3Config)
const setVotingToChangeMinThreshold = contractsStore.setVotingToChangeMinThreshold(web3Config)
const setVotingToChangeProxy = contractsStore.setVotingToChangeProxy(web3Config)
const setValidatorMetadata = contractsStore.setValidatorMetadata(web3Config)
let promises = [
setPoaConsensus,
setBallotsStorage,
setKeysManager,
setProxyStorage,
setVotingToChangeKeys,
setVotingToChangeMinThreshold,
setVotingToChangeProxy,
setValidatorMetadata
]
const networkName = constants.NETWORKS[web3Config.netId].NAME.toLowerCase()
if (networkName === constants.CORE || networkName === constants.SOKOL) {
// if we're in Core or Sokol
promises.push(contractsStore.setEmissionFunds(web3Config))
promises.push(contractsStore.setVotingToManageEmissionFunds(web3Config))
}
await Promise.all(promises)
await contractsStore.setMiningKey(web3Config)
await contractsStore.setVotingKey(web3Config)
contractsStore.getKeysBallotThreshold()
contractsStore.getProxyBallotThreshold()
contractsStore.getBallotCancelingThreshold()
await contractsStore.getBallotsLimits()
await contractsStore.getAllValidatorMetadata()
await contractsStore.getAllBallots()
console.log('votingKey', contractsStore.votingKey)
console.log('miningKey', contractsStore.miningKey)
await this.initialize(web3Config)
commonStore.hideLoading()
if (web3Config.netId === 99) {
// if it's POA Core network
const currentTimestamp = Math.floor(Date.now() / 1000)
swal({
title: 'Attention',
html: generateElement(currentTimestamp < 1651698000 ? messages.poaGnoMerging : messages.poaGnoMerged),
type: 'warning'
})
}
})
.catch(error => {
console.error(error.message)
@ -90,17 +59,67 @@ class AppMainRouter extends Component {
swal({
title: 'Error',
html: generateElement(error.message),
icon: 'error',
type: 'error'
})
})
}
initialize = async web3Config => {
await getContractsAddresses(constants.NETWORKS[web3Config.netId].BRANCH)
contractsStore.setWeb3Instance(web3Config)
const setPoaConsensus = contractsStore.setPoaConsensus(web3Config)
const setBallotsStorage = contractsStore.setBallotsStorage(web3Config)
const setKeysManager = contractsStore.setKeysManager(web3Config)
const setProxyStorage = contractsStore.setProxyStorage(web3Config)
const setVotingToChangeKeys = contractsStore.setVotingToChangeKeys(web3Config)
const setVotingToChangeMinThreshold = contractsStore.setVotingToChangeMinThreshold(web3Config)
const setVotingToChangeProxy = contractsStore.setVotingToChangeProxy(web3Config)
const setValidatorMetadata = contractsStore.setValidatorMetadata(web3Config)
let promises = [
setPoaConsensus,
setBallotsStorage,
setKeysManager,
setProxyStorage,
setVotingToChangeKeys,
setVotingToChangeMinThreshold,
setVotingToChangeProxy,
setValidatorMetadata
]
const networkName = constants.NETWORKS[web3Config.netId].NAME.toLowerCase()
if (networkName === constants.CORE || networkName === constants.SOKOL) {
// if we're in Core or Sokol
promises.push(contractsStore.setEmissionFunds(web3Config))
promises.push(contractsStore.setVotingToManageEmissionFunds(web3Config))
}
await Promise.all(promises)
await contractsStore.updateKeys(web3Config.defaultAccount)
await contractsStore.getMinBallotDurationsAndThresholds()
await contractsStore.getAllValidatorMetadata()
await contractsStore.getAllBallots()
}
onNetworkChange = e => {
commonStore.showLoading(getNetworkBranch(e.value))
window.localStorage.netId = e.value
window.sessionStorage.netId = e.value
contractsStore.resetContracts()
ballotsStore.reset()
this.initChain()
}
render() {
return (
<Provider {...stores}>
<Router history={history}>
<Route component={App} />
<Route component={props => <App onNetworkChange={this.onNetworkChange} {...props} />} />
</Router>
</Provider>
)

View File

@ -4,6 +4,10 @@ class BallotsStore {
@observable ballotCards
constructor() {
this.reset()
}
reset() {
this.ballotCards = []
}
}

View File

@ -2,6 +2,7 @@ import { observable, action } from 'mobx'
class CommonStore {
@observable loading
@observable loadingNetworkBranch
@observable rootPath
@observable isActiveFilter
@observable isToFinalizeFilter
@ -10,6 +11,7 @@ class CommonStore {
constructor() {
this.loading = false
this.loadingNetworkBranch = null
this.isActiveFilter = false
this.isToFinalizeFilter = false
this.rootPath = '/poa-dapps-voting'
@ -17,13 +19,15 @@ class CommonStore {
}
@action('show loading')
showLoading() {
showLoading(loadingNetworkBranch) {
this.loading = true
this.loadingNetworkBranch = loadingNetworkBranch
}
@action('hide loading')
hideLoading() {
this.loading = false
this.loadingNetworkBranch = null
}
@action('set search term')

View File

@ -46,44 +46,49 @@ class ContractsStore {
@observable minBallotDuration
@observable validatorsMetadata
@observable netId
@observable injectedWeb3
constructor() {
this.votingKey = null
this.miningKey = null
this.votingKey = '0x0000000000000000000000000000000000000000'
this.miningKey = '0x0000000000000000000000000000000000000000'
this.validatorsMetadata = {}
this.validatorLimits = { keys: null, minThreshold: null, proxy: null }
this.minBallotDuration = { keys: 0, minThreshold: 0, proxy: 0 }
this.injectedWeb3 = false
}
@computed
get isEmptyVotingKey() {
return !this.votingKey || this.votingKey === '0x0000000000000000000000000000000000000000'
}
@computed
get isValidVotingKey() {
if (this.isEmptyVotingKey) return false
if (this.miningKey && this.miningKey !== '0x0000000000000000000000000000000000000000') return true
return false
}
@action('Get keys ballot threshold')
getKeysBallotThreshold = async () => {
this.keysBallotThreshold = await this.ballotsStorage.instance.methods.getBallotThreshold(1).call()
this.minThresholdBallotThreshold = this.keysBallotThreshold
}
@action('Get proxy ballot threshold')
getProxyBallotThreshold = async () => {
this.proxyBallotThreshold = await this.ballotsStorage.instance.methods.getProxyThreshold().call()
this.emissionFundsBallotThreshold = this.proxyBallotThreshold
}
@action('Get ballot canceling threshold')
getBallotCancelingThreshold = async () => {
this.ballotCancelingThreshold = this.votingToManageEmissionFunds
? Number(await this.votingToManageEmissionFunds.ballotCancelingThreshold())
: 0
}
@action('Set web3Instance')
setWeb3Instance = web3Config => {
this.web3Instance = web3Config.web3Instance
this.netId = web3Config.netId
this.injectedWeb3 = web3Config.injectedWeb3
this.networkMatch = web3Config.networkMatch
}
@action('Reset contracts')
resetContracts = () => {
this.poaConsensus = null
this.ballotsStorage = null
this.emissionFunds = null
this.keysManager = null
this.proxyStorage = null
this.votingToChangeKeys = null
this.votingToChangeMinThreshold = null
this.votingToChangeProxy = null
this.votingToManageEmissionFunds = null
this.validatorMetadata = null
}
@action('Set PoA Consensus contract')
@ -182,18 +187,38 @@ class ContractsStore {
}
@action('Set voting key')
setVotingKey = web3Config => {
this.votingKey = web3Config.defaultAccount
setVotingKey = account => {
this.votingKey = account
}
@action('Set mining key')
setMiningKey = async web3Config => {
try {
this.miningKey = await this.keysManager.instance.methods.miningKeyByVoting(web3Config.defaultAccount).call()
} catch (e) {
console.log(e)
this.miningKey = '0x0000000000000000000000000000000000000000'
setMiningKey = async account => {
let miningKey = '0x0000000000000000000000000000000000000000'
if (account && account !== '0x0000000000000000000000000000000000000000') {
try {
miningKey = await this.keysManager.instance.methods.miningKeyByVoting(account).call()
} catch (e) {
console.log(e)
}
}
this.miningKey = miningKey
}
@action('Update keys')
updateKeys = async account => {
account = account || '0x0000000000000000000000000000000000000000'
if (this.votingKey && this.votingKey.toLowerCase() === account.toLowerCase()) {
return
}
this.setVotingKey(account)
await this.setMiningKey(account)
console.log('votingKey', this.votingKey)
console.log('miningKey', this.miningKey)
await this.getBallotsLimits()
}
@action('Get all keys ballots')
@ -217,12 +242,26 @@ class ContractsStore {
console.log(e.message)
}
const allKeysPromise = this.getCards(keysNextBallotId, 'votingToChangeKeys')
const allMinThresholdPromise = this.getCards(minThresholdNextBallotId, 'votingToChangeMinThreshold')
const allProxyPromise = this.getCards(proxyNextBallotId, 'votingToChangeProxy')
const allEmissionFundsPromise = this.getCards(emissionFundsNextBallotId, 'votingToManageEmissionFunds')
const [keysBallots, minThresholdBallots, proxyBallots, emissionFundsBallots] = await Promise.all([
this.getBallots(keysNextBallotId, 'votingToChangeKeys'),
this.getBallots(minThresholdNextBallotId, 'votingToChangeMinThreshold'),
this.getBallots(proxyNextBallotId, 'votingToChangeProxy'),
this.getBallots(emissionFundsNextBallotId, 'votingToManageEmissionFunds')
])
await Promise.all([allKeysPromise, allMinThresholdPromise, allProxyPromise, allEmissionFundsPromise])
const ballots = [...keysBallots, ...minThresholdBallots, ...proxyBallots, ...emissionFundsBallots]
ballotsStore.ballotCards = this.mapBallotsToCards(ballots)
const finalizedOrCancelled = item => item.isFinalized || item.isCanceled
window.localStorage.setItem(
`ballots-${this.netId}`,
JSON.stringify({
votingToChangeKeys: keysBallots.filter(finalizedOrCancelled),
votingToChangeMinThreshold: minThresholdBallots.filter(finalizedOrCancelled),
votingToChangeProxy: proxyBallots.filter(finalizedOrCancelled),
votingToManageEmissionFunds: emissionFundsBallots.filter(finalizedOrCancelled)
})
)
const allBallotsIDsLength =
keysNextBallotId + minThresholdNextBallotId + proxyNextBallotId + emissionFundsNextBallotId
@ -309,72 +348,59 @@ class ContractsStore {
return votingState
}
getCard = async (id, contractType) => {
mapBallotsToCards = ballots => {
return ballots.map((ballot, pos) => {
let component
let params = {
id: ballot.id,
key: ballot.type + ballot.id,
pos,
votingState: ballot
}
switch (ballot.type) {
case 'votingToChangeKeys':
component = <BallotKeysCard {...params} type={ballotStore.BallotType.keys} />
break
case 'votingToChangeMinThreshold':
component = <BallotMinThresholdCard {...params} type={ballotStore.BallotType.minThreshold} />
break
case 'votingToChangeProxy':
component = <BallotProxyCard {...params} type={ballotStore.BallotType.proxy} />
break
case 'votingToManageEmissionFunds':
component = <BallotEmissionFundsCard {...params} type={ballotStore.BallotType.emissionFunds} />
break
default:
break
}
return component
})
}
getBallot = async (id, contractType) => {
let votingState
try {
votingState = await this[contractType].getBallotInfo(id, this.votingKey)
votingState = this.fillCardVotingState(votingState, contractType)
votingState.type = contractType
votingState.id = id
} catch (e) {
console.log(e.message)
}
let card
switch (contractType) {
case 'votingToChangeKeys':
card = (
<BallotKeysCard
id={id}
type={ballotStore.BallotType.keys}
key={ballotsStore.ballotCards.length}
pos={ballotsStore.ballotCards.length}
votingState={votingState}
/>
)
break
case 'votingToChangeMinThreshold':
card = (
<BallotMinThresholdCard
id={id}
type={ballotStore.BallotType.minThreshold}
key={ballotsStore.ballotCards.length}
pos={ballotsStore.ballotCards.length}
votingState={votingState}
/>
)
break
case 'votingToChangeProxy':
card = (
<BallotProxyCard
id={id}
type={ballotStore.BallotType.proxy}
key={ballotsStore.ballotCards.length}
pos={ballotsStore.ballotCards.length}
votingState={votingState}
/>
)
break
case 'votingToManageEmissionFunds':
card = (
<BallotEmissionFundsCard
id={id}
type={ballotStore.BallotType.emissionFunds}
key={ballotsStore.ballotCards.length}
pos={ballotsStore.ballotCards.length}
votingState={votingState}
/>
)
break
default:
break
}
return card
return votingState
}
getCards = async (nextBallotId, contractType) => {
for (let id = nextBallotId - 1; id >= 0; id--) {
ballotsStore.ballotCards.push(await this.getCard(id, contractType))
}
getBallots = async (nextBallotId, contractType) => {
const ballotsObject = JSON.parse(window.localStorage.getItem(`ballots-${this.netId}`) || '{}')
const existingBallots = ballotsObject[contractType] || []
const existingBallotsIds = existingBallots.map(item => item.id)
const allBallotsIds = Array(nextBallotId)
.fill(undefined)
.map((item, index) => index)
const newBallotsIds = allBallotsIds.filter(item => !existingBallotsIds.includes(item))
const promises = newBallotsIds.map(id => this.getBallot(id, contractType))
const newBallots = await Promise.all(promises)
return existingBallots.concat(newBallots)
}
@action('Get all keys next ballot ids')
@ -392,41 +418,63 @@ class ContractsStore {
async getBallotsLimits() {
return new Promise(async resolve => {
if (this.web3Instance && this.netId) {
const limitPerValidator = await this.ballotsStorage.instance.methods.getBallotLimitPerValidator().call()
let keysLimit = 0
let minThresholdLimit = 0
let proxyLimit = 0
const getKeysLimit = await this.votingToChangeKeys.getBallotLimit(this.miningKey, limitPerValidator)
const getMinThresholdLimit = await this.votingToChangeMinThreshold.getBallotLimit(
this.miningKey,
limitPerValidator
)
const getProxyLimit = await this.votingToChangeProxy.getBallotLimit(this.miningKey, limitPerValidator)
if (this.isValidVotingKey) {
const limitPerValidator = await this.ballotsStorage.instance.methods.getBallotLimitPerValidator().call()
keysLimit = await this.votingToChangeKeys.getBallotLimit(this.miningKey, limitPerValidator)
minThresholdLimit = await this.votingToChangeMinThreshold.getBallotLimit(this.miningKey, limitPerValidator)
proxyLimit = await this.votingToChangeProxy.getBallotLimit(this.miningKey, limitPerValidator)
}
const getKeysMinBallotDuration = await this.votingToChangeKeys.minBallotDuration()
const getMinThresholdMinBallotDuration = await this.votingToChangeMinThreshold.minBallotDuration()
const getProxyMinBallotDuration = await this.votingToChangeProxy.minBallotDuration()
this.validatorLimits.keys = keysLimit
this.validatorLimits.minThreshold = minThresholdLimit
this.validatorLimits.proxy = proxyLimit
}
resolve()
})
}
@action
async getMinBallotDurationsAndThresholds() {
return new Promise(async resolve => {
if (this.web3Instance && this.netId) {
const getKeysMinBallotDuration = this.votingToChangeKeys.minBallotDuration()
const getMinThresholdMinBallotDuration = this.votingToChangeMinThreshold.minBallotDuration()
const getProxyMinBallotDuration = this.votingToChangeProxy.minBallotDuration()
const getBallotThreshold = this.ballotsStorage.instance.methods.getBallotThreshold(1).call()
const getProxyThreshold = this.ballotsStorage.instance.methods.getProxyThreshold().call()
const getBallotCancelingThreshold = this.votingToManageEmissionFunds
? this.votingToManageEmissionFunds.ballotCancelingThreshold()
: 0
await Promise.all([
getKeysLimit,
getMinThresholdLimit,
getProxyLimit,
getKeysMinBallotDuration,
getMinThresholdMinBallotDuration,
getProxyMinBallotDuration
getProxyMinBallotDuration,
getBallotThreshold,
getProxyThreshold,
getBallotCancelingThreshold
]).then(
([
keysLimit,
minThresholdLimit,
proxyLimit,
keysMinBallotDuration,
minThresholdMinBallotDuration,
proxyMinBallotDuration
proxyMinBallotDuration,
keysBallotThreshold,
proxyBallotThreshold,
cancelingThreshold
]) => {
this.validatorLimits.keys = keysLimit
this.validatorLimits.minThreshold = minThresholdLimit
this.validatorLimits.proxy = proxyLimit
this.minBallotDuration.keys = keysMinBallotDuration
this.minBallotDuration.minThreshold = minThresholdMinBallotDuration
this.minBallotDuration.proxy = proxyMinBallotDuration
this.keysBallotThreshold = keysBallotThreshold
this.minThresholdBallotThreshold = keysBallotThreshold
this.proxyBallotThreshold = proxyBallotThreshold
this.emissionFundsBallotThreshold = proxyBallotThreshold
this.ballotCancelingThreshold = cancelingThreshold
resolve()
}
)

View File

@ -53,33 +53,29 @@ constants.navigationData = [
constants.SOKOL = 'sokol'
constants.CORE = 'core'
constants.DAI = 'dai'
constants.KOVAN = 'kovan'
constants.NETWORKS = {
'42': {
NAME: 'Kovan',
FULLNAME: 'Kovan Testnet',
RPC: 'https://kovan.infura.io/v3/1125fe73d87c4e5396678f4e3089b3dd',
BRANCH: constants.KOVAN,
TESTNET: true
SORTORDER: 3
},
'77': {
NAME: 'Sokol',
FULLNAME: 'Sokol Testnet',
RPC: 'https://sokol.poa.network',
BRANCH: constants.SOKOL,
TESTNET: true
SORTORDER: 4
},
'99': {
NAME: 'Core',
FULLNAME: 'POA Core',
RPC: 'https://core.poa.network',
BRANCH: constants.CORE,
TESTNET: false
},
'100': {
NAME: 'Dai',
RPC: 'https://dai.poa.network',
BRANCH: constants.DAI,
TESTNET: false
SORTORDER: 1
}
}

View File

@ -1,80 +1,143 @@
import Web3 from 'web3'
import { messages } from './messages'
import helpers from './helpers'
import { constants } from './constants'
import { netIdByName } from './helpers'
import messages from './messages'
let getWeb3 = () => {
return new Promise((resolve, reject) => {
// Wait for loading completion to avoid race conditions with web3 injection timing.
window.addEventListener('load', async () => {
let web3 = null
const defaultNetId = helpers.netIdByBranch(constants.CORE)
// Checking if Web3 has been injected by the browser (Mist/MetaMask)
if (window.ethereum) {
web3 = new Web3(window.ethereum)
console.log('Injected web3 detected.')
try {
await window.ethereum.enable()
} catch (e) {
console.error('User denied account access')
reject({ message: messages.USER_DENIED_ACCOUNT_ACCESS })
return
}
} else if (typeof window.web3 !== 'undefined') {
web3 = new Web3(window.web3.currentProvider)
console.log('Injected web3 detected.')
}
let errorMsg = null
let netIdName
let netId
let defaultAccount = null
if (web3) {
netId = await web3.eth.net.getId()
console.log('netId', netId)
if (!(netId in constants.NETWORKS)) {
netIdName = 'ERROR'
errorMsg = messages.WRONG_NETWORK_MSG
console.log('This is an unknown network.')
} else {
netIdName = constants.NETWORKS[netId].NAME
console.log(`This is ${netIdName}`)
}
const accounts = await web3.eth.getAccounts()
defaultAccount = accounts[0] || null
} else {
// Fallback to local if no web3 injection.
console.log('No web3 instance injected, using Local web3.')
console.error('Metamask not found')
netId = netIdByName(constants.CORE)
const network = constants.NETWORKS[netId]
web3 = new Web3(new Web3.providers.HttpProvider(network.RPC))
netIdName = network.NAME
}
document.title = `${netIdName} - POA Network Governance DApp`
if (errorMsg !== null) {
reject({ message: errorMsg })
return
}
resolve({
web3Instance: web3,
netIdName,
netId,
defaultAccount
})
})
})
async function getAccounts(web3) {
let accounts
if (window.ethereum) {
accounts = await window.ethereum.request({ method: 'eth_accounts' })
} else {
accounts = await web3.eth.getAccounts()
}
return accounts
}
export default getWeb3
async function getNetId(web3) {
let netId
if (window.ethereum) {
const { chainId } = window.ethereum
netId = web3.utils.isHex(chainId) ? web3.utils.hexToNumber(chainId) : chainId
} else {
netId = await web3.eth.net.getId()
}
return netId
}
export async function enableWallet(updateKeys) {
if (window.ethereum) {
try {
await window.ethereum.request({ method: 'eth_requestAccounts' })
} catch (e) {
await updateKeys(null)
throw Error(messages.USER_DENIED_ACCOUNT_ACCESS)
}
const web3 = new Web3(window.ethereum)
const accounts = await getAccounts(web3)
await updateKeys(accounts[0])
}
}
export default async function getWeb3(netId, updateKeys) {
let web3 = null
// Checking if Web3 has been injected by the browser (Mist/MetaMask)
if (window.ethereum) {
web3 = new Web3(window.ethereum)
console.log('Injected web3 detected.')
if (!window.ethereum.autoRefreshOnNetworkChange) {
window.ethereum.on('chainChanged', () => {
window.location.reload()
})
}
} else if (window.web3) {
web3 = new Web3(window.web3.currentProvider)
console.log('Injected web3 detected.')
}
if (!netId) {
// Load for the first time in the current browser's session
if (web3) {
// MetaMask (or another plugin) is injected
netId = await getNetId(web3)
if (!(netId in constants.NETWORKS)) {
// If plugin's netId is unsupported, try to use
// the previously chosen netId
netId = window.localStorage.netId
}
} else {
// MetaMask (or another plugin) is not injected,
// so try to use the previously chosen netId
netId = window.localStorage.netId
}
if (!(netId in constants.NETWORKS)) {
// If plugin's netId and/or previously chosen netId are not supported,
// fallback to default netId
netId = defaultNetId
}
window.localStorage.netId = netId
window.sessionStorage.netId = netId
}
netId = Number(netId)
const network = constants.NETWORKS[netId]
const injectedWeb3 = web3 !== null
let netIdName = network.NAME
let defaultAccount = null
let networkMatch = false
if (web3) {
const accounts = await getAccounts(web3)
defaultAccount = accounts[0] || null
if (!defaultAccount) {
console.log('Unlock your wallet')
}
let currentAccount = defaultAccount ? defaultAccount.toLowerCase() : null
function onUpdateAccount(account) {
if (account && account !== currentAccount) {
currentAccount = account
updateKeys(account)
}
}
if (window.ethereum) {
window.ethereum.on('accountsChanged', accs => {
const account = accs && accs.length > 0 ? accs[0].toLowerCase() : null
onUpdateAccount(account)
})
} else if (web3.currentProvider.publicConfigStore) {
web3.currentProvider.publicConfigStore.on('update', obj => {
const account = obj.selectedAddress ? obj.selectedAddress.toLowerCase() : null
onUpdateAccount(account)
})
}
const web3NetId = await getNetId(web3)
if (web3NetId === netId) {
networkMatch = true
} else {
web3 = null
}
}
if (!web3) {
web3 = new Web3(new Web3.providers.HttpProvider(network.RPC))
}
document.title = `${netIdName} - POA Governance DApp`
return {
web3Instance: web3,
netId,
netIdName,
injectedWeb3,
defaultAccount,
networkMatch
}
}

View File

@ -58,10 +58,10 @@ function sendTransactionByVotingKey(props, to, data, cb, warning) {
)
}
function netIdByName(netName) {
const netNameLowerCase = netName.toLowerCase()
function netIdByBranch(branch) {
const branchLowerCase = branch.toLowerCase()
for (let netId in constants.NETWORKS) {
if (constants.NETWORKS[netId].NAME.toLowerCase() === netNameLowerCase) {
if (constants.NETWORKS[netId].BRANCH.toLowerCase() === branchLowerCase) {
return netId
}
}
@ -71,5 +71,5 @@ function netIdByName(netName) {
module.exports = {
toAscii,
sendTransactionByVotingKey,
netIdByName
netIdByBranch
}

View File

@ -1,6 +1,8 @@
import { getNetworkFullName } from './utils'
let messages = {}
messages.invalidVotingKeyMsg = key => {
return `The key ${key} is not valid voting Key! Please make sure you have loaded correct voting key in Metamask`
return `The key ${key} is not valid Voting Key! Please make sure you have loaded correct Voting Key in MetaMask.`
}
messages.VOTED_SUCCESS_MSG = 'You successfully voted'
messages.BALLOT_CREATED_SUCCESS_MSG = 'You successfully created a new ballot'
@ -16,12 +18,7 @@ messages.PROPOSED_ADDRESS_IS_NOT_ADDRESS_MSG = "Proposed address isn't address"
messages.END_TIME_SHOULD_BE_GREATER_THAN_NOW_MSG = 'Ballot end time should be greater than now'
messages.BALLOT_TYPE_IS_EMPTY_MSG = 'Ballot type is empty'
messages.USER_DENIED_ACCOUNT_ACCESS = 'You have denied access to your accounts'
messages.NO_METAMASK_MSG = `You haven't chosen any account in MetaMask.
Please, choose your voting key in MetaMask and reload the page.
Check POA Network <a href='https://github.com/poanetwork/wiki' target='blank'>wiki</a> for more info.`
messages.WRONG_NETWORK_MSG = `You aren't connected to POA Network.
Please, switch on POA plugin and refresh the page.
Check POA Network <a href='https://github.com/poanetwork/wiki' target='blank'>wiki</a> for more info.`
messages.NO_METAMASK_MSG = 'Your MetaMask is locked or not installed.'
messages.ballotIsNotActiveMsg = timeToStart => {
return `The ballot is not active yet. Time to start: ${timeToStart}`
}
@ -38,17 +35,26 @@ messages.EMISSION_RELEASE_TIME_IN_FUTURE = emissionReleaseTime => {
}
messages.PREVIOUS_BALLOT_NOT_FINALIZED = 'Previous ballot should be finalized first.'
messages.BALLOT_CREATE_FAILED_TX = `Your transaction was failed. Please make sure you set correct parameters for ballot creation.
Make sure you don't have Transaction Error. Exception thrown in contract code message in Metamask before you sign it.`
Make sure you don't have Transaction Error. Exception thrown in contract code message in MetaMask before you sign it.`
messages.VOTE_FAILED_TX = `Your transaction was failed. Please make sure you haven't already voted for this ballot.
Make sure you don't have Transaction Error. Exception thrown in contract code message in Metamask before you sign it.`
Make sure you don't have Transaction Error. Exception thrown in contract code message in MetaMask before you sign it.`
messages.FINALIZE_FAILED_TX = `Your transaction was failed. Make sure you don't have Transaction Error.
Exception thrown in contract code message in Metamask before you sign it.`
Exception thrown in contract code message in MetaMask before you sign it.`
messages.CANCEL_BALLOT_FAILED_TX = `Your transaction was failed. Make sure you don't have Transaction Error.
Exception thrown in contract code message in Metamask before you sign it.`
Exception thrown in contract code message in MetaMask before you sign it.`
messages.DESCRIPTION_IS_EMPTY = 'Description cannot be empty'
messages.wrongRepo = repo => {
return `There is no contracts.json in configured repo ${repo}`
}
module.exports = {
messages
messages.networkMatchError = function(netId) {
const networkName = getNetworkFullName(Number(netId))
return `Networks in DApp and MetaMask do not match. Switch MetaMask to <b>${networkName}</b> or change the network in DApp.`
}
messages.poaGnoMerging =
'POA is joining the Gnosis Chain ecosystem, and token holders can now swap POA for STAKE and then STAKE for GNO on the Gnosis Chain! More info and instructions <a href="https://www.poa.network/" target="_blank">here</a>.'
messages.poaGnoMerged =
'POA Network merged with the Gnosis Chain.<br /><a href="https://www.poa.network/" target="_blank">More information</a> about the merger.'
export default messages

View File

@ -1,19 +1,11 @@
import { constants } from './constants'
export const isTestnet = netId => {
return netId in constants.NETWORKS && constants.NETWORKS[netId].TESTNET
}
export const isValidNetwork = netId => {
return netId in constants.NETWORKS
}
export const getNetworkBranch = netId => {
return constants.NETWORKS[netId].BRANCH
}
export const getNetworkName = netId => {
return constants.NETWORKS[netId].NAME
export const getNetworkFullName = netId => {
return constants.NETWORKS[netId].FULLNAME
}
export const scrollToTop = () => {