Skip to content

Specification and design documents for the Kosu validator governance portal.

Notifications You must be signed in to change notification settings

ParadigmFoundation/gov-portal-spec

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

23 Commits
 
 
 
 
 
 

Repository files navigation

Portal specification: Governance

The repository is home to the specification for the Kosu governance portal, primarily an interface for the ValidatorRegistry contract (a modified token-curated registry).

The repository contains the master governance.sketch file with the full UI/UX designs, and this README contains annotations of those designs and descriptions of each important state.

A custom helper library (gov-portal-helper) has been created to assist with the creation of this portal. Documentation for this library is found in the documentation section.

Background

Conceptually helpful topics and other background info required to work with this project. This document assumes familiarity with basic Ethereum concepts such as contracts, signatures, transactions, web3.js, providers, and MetaMask.

Token curated registry

This governance portal is primarily an interface for a token curated registry (TCR), a type of Ethereum contract system designed to generate an arbitrary list of data by rewarding token-holders for curating its contents.

Paradigm's TCR curates a set of validators who run the Kosu network. Any entity may apply to the registry (create a "proposal"), where after a confirmation period they are accepted into the registry as a validator, so long as they have not been challenged. Applicants submit "stake" with the proposals, an amount of tokens they include with their proposal to indicate their seriousness of intent to join. At any point (even after the confirmation period) their listing may be "challenged", when another entity puts up an equal amount of stake to trigger a vote.

The resulting vote on the challenge ends either with the challenge passing, in which case the listing owner is removed from the registry and has their tokens distributed to the challenger and winning voters, or the challenge fails, in which case the listing remains and the challengers tokens are distributed to the listing owner and the winning voters.

Vocabulary

  • Registry: the TCR, specifically the Kosu ValidatorRegistry contract and the set of listings currently in the registry
  • Listing: a non-specific term for any entity in the registry, either a validator (confirmed) or a proposal (pending, yet to be confirmed).
  • Proposal: a new listing in the registry; a pending application to be a validator that is automatically confirmed after a period of time without being challenged.
  • Validator: not only an actual validator on the proof-of-stake Kosu network, but more commonly in this context refers to a listing that has been confirmed to the registry.
  • Challenge: an active poll against a listing (either a proposal or a challenge) initiated by a token holder. A challenge resolves after a commit-reveal vote period, and results in distribution of the loser's (either the challenger or the listing) stake to the winning voters and stakeholder (the listing in the case of a failed challenge, or the challenger in the case of a successful challenge).
  • Challenger: an entity who has initiated a challenge against a proposal or a validator listing (note that a challenger who's challenge fails looses their staked tokens to the winning voters and the listing owner).
  • Voter: an entity who is participating in a challenge vote by committing their own tokens to one side of the poll (note, a voter may never loose their tokens if they vote on the losing side, they can only benefit if they win).
  • Stake: a term used to describe A) an amount of tokens that may be lost in certain outcomes, or B) the act of committing tokens into a staked position.
  • ValidatorRegistry: the specific Ethereum contract that implements the functionality of this governance system within the greater Kosu protocol.
  • Tendermint public key: (also just "public key") each Kosu validator has a validating key pair, and their public key is used to identify them within the contract system. Each primary state field (gov.validators, etc.) is a mapping of these public keys to objects with data about those listings/challenges.

Governance portal helper state/event model

The gov-portal-helper library contains a single class, Gov, which manages and abstracts most functionality needed to interact with the Kosu system.

The three main properties listed below (and in more detail in the documentation section) should be used for the main page (discussed below) that lists "proposals", "validators", and "challenges". These objects may be directly read from the Gov prototype, loaded via prototype methods, or queried each time a gov_update event is emitted from the gov.ee event-emitter object.

A Gov instance contains the following properties, which track the primary state of the portal system.

  • gov.validators - a map of Tendermint public keys (as hex-encoded strings) to Validator objects, with one entry for each current validator within the registry.
  • gov.proposals - a map of Tendermint public keys (as hex-encoded strings) to Proposal objects, with one entry for each current pending proposal within the registry.
  • gov.challenges - a map of Tendermint public keys (as hex-encoded strings) to StoreChallenge objects, with one entry for listing that is currently being challenged.

All three properties above are loaded from the current contract system state when gov.init() is called, and further updated each time the contract system's state changes. After main page load, the gov_update event can be used to detect updates to the above state fields.

Reliance on MetaMask

All sateful data (challenges, proposals, validators, past challenges, etc.) for the governance portal is loaded from the Ethereum blockchain, through the MetaMask extension which provides access to a remote Ethereum node.

As such, this portal is useless without MetaMask, and MetaMask must be connected (via a call to ethereum.enable()) prior to any data being displayed. The actual call to ethereum.enable() is handled by initialization of a Gov instance (described below).

This brings up a few points to note:

  1. Not having MetaMask installed should be treated as an error state, with a special page prompting the user to install the extension, or otherwise use a web3 browser.
  2. On or just after page load, the Gov instance should be created. A call to gov.init() handles the MetaMask connection, and prompts the user to grant site access. The gov.init() call should not be made automatically, but instead should be triggered by the user clicking "connect to MetaMask" in the top nav bar.
  3. Browser compatibility should also be handled. The only supported browsers are Chrome and Brave, unless a window.ethereum object is detected, in which case it can be assumed to be a compatible dApp browser.

More details on gov initialization are included in the description and documentation sections.

Units: wei vs. ether

Most token values (stake size, reward rate, etc.) are showed in "base units" of the token, which are also refereed to as wei, the smallest unit of most ERC-20 tokens. Becuase wei amounts will have between ~17-23 digits, amounts should be converted to ether prior to being displayed to the user.

A gov instance provides gov.weiToEther(wei) to convert from wei, and gov.etherToWei(ether) to convert the other direction.

These values are returned as strings, and should be converted to BigNumber instances where necessary. The static method Gov.BigNumber may be used to create new BigNumber instances from strings.

Also note that "Ether" (proper noun) refers to the Ether cryptocurrency of the Ethereum network, while "ether" (lowercase) refers to the unit of 1*10^18 wei.

Description

This section contains the "meat" of the specification, with diagrams from the sketch file and annotations.

Main page (pre-MetaMask connection)

Main page/overview state

  • This is the home/index page of the governance portal, which should be displayed prior to MetaMask connection
  • The "Connect to MetaMask" button in the top-right nav-bar should trigger a call to gov.init() which will prompt the user to allow the site access to their MetaMask coinbase account.
  • Various exceptions (as promise rejections) from gov.init() indicate various failure states:
    // init life-cycle (example only)
    (async () => {
      const gov = new Gov();
      try {
        await gov.init();
      } catch (error) {
        switch (error.message) {
          case "user denied site access":
            /* user clicked "reject" on MM prompt */
            break;
          case "non-ethereum browser detected":
            /* incompatible browser */
            break;
          default: {
            /* normal failure case, something unexpected went wrong */
          }
        }
      }
    })();

Main page (post-MetaMask connection)

The screenshots in this section are cropped from the same overview state, found here.

Main page/overview state (connected)

  • After a successful (no exceptions) call to gov.init(), data for the main page can be loaded.
    • This call should be triggered by the user clicking "connect to MetaMask".
  • The users balance of KOSU tokens should be displayed in the top-right nav par, next to their address.
    • The user's address is stored as gov.coinbase (after init has completed).
    • The user's Kosu balance can be loaded (as a BigNumber, units of wei) with the following code:
      // the following requires `gov.init()` to have completed successfully
      const coinbase = gov.coinbase;
      const balance = await gov.kosu.kosuToken.balanceOf(coinbase);
  • After initialization, the gov instance will begin to load validators, challenges, and proposals, into its state.
  • Each time a new proposal, challenge, or validator is loaded, the gov_update event will be emitted from gov.ee.
  • The raw map (object) for each of the following sub-sections can then be loaded from the gov instance.

Active proposals

Main page: active proposals

  • Active proposals should be loaded from gov.proposals (either by directly viewing that object, or a call to gov.currentProposals()).
    • Be sure to see the Proposal type definition as well, which defines each object in the gov.proposals map.
    • Proposals on the main page display several values, which can be loaded (or computed) from each proposal object.
    • The titular hex string for each proposal card should be the key for the proposal in the gov.proposals object. This value is the public key for the listing application, encoded as a hex string.
    • The "new proposal" or "ending soon" badges should be displayed based on the proposal.acceptUnix field:
      • If acceptUnix is within one day of the current timestamp, "ending soon" should be displayed on the listing.
      • If acceptUnix is more than one week in the future, "new proposal" should be displayed on the listing.
      • If acceptUnix is less than a week in the future, but more than a day, no badge should be displayed.
    • The title for a proposal is "X wants to become a validator," and the X should be replaced with a shortened hex-string of the proposal.owner Ethereum address (0x23ba...d2f, for example).
      • Note that this hex string is 20 bytes (an Ethereum address, 42 chars with the "0x" prefix) and the listing key is a 32 byte (66 char with the "0x" prefix) Tendermint public key.
      • That is to say proposal.owner strictly is NOT EQUAL to the key of the object within the mapping.
    • The "Stake size" field should be loaded from proposal.stakeSize, and should be converted to units of ether prior to being displayed.
    • The "Daily reward" field should be loaded from proposal.dailyReward and should be converted to units of ether period to being displayed. Decimals can be truncated after 6-8 significant digits.
    • The "Estimated vote power" field should be loaded from proposal.power, which is a decimal number from 0-100 indicating a percentage of network vote power. The decimals can be truncated after a few significant digits, based on aesthetics/spacing.
    • The "Proposal ends in:" field must be computed based on the proposal.acceptUnix field, which indicates the Unix timestamp at which the proposal is confirmed. I recommend constructing a Date object from the timestamp, and using its methods to compute days, hours, and minutes. Seconds should not be shown, as the timestamp is purely an estimate.
    • The "View" button should take the user to the detail page for the proposal (detailed in a later section).

Active challenges

Main page: active challenges

  • Active challenges should be loaded from gov.challenges (either by directly viewing that object, or a call to gov.currentChallenges()).
    • Be sure to see the StoreChallenge type definition as well, which defines each object in the gov.challenges map.
    • Challenges on the main page display a number, or index, which corresponds to the underlying pollId used to track that challenge in the contract system.
    • The number for each active challenge should be loaded from each challenge.challengeId and displayed (after conversion).
    • The "new challenge" or "ending soon" badge should be displayed on a challenge card, based on challenge.challengeEndUnix:
      • If challengeEndUnix is within a day of the current timestamp, "ending soon" should be displayed.
      • If challengeEndUnix is equal to or more than one week away, "new challenge" should be displayed.
      • If challengeEndUnix is in less than a week, but more than a day away, no badge should be displayed.
    • The "Challenger stake" field should be loaded from challenge.challengerStake and displayed after conversion to units of ether.
    • The "Potential reward" field must be computed based on the value of challenge.challengerStake:
      • The maximum possible reward refers to the proportion of listing/challenger stake that could be rewarded to participating voters.
      • This value is based on a variable contract parameter, but for now, can be assumed to be 30% of the challengerStake.
      • Keep in mind challenge.challengerStake is an instance of BigNumber, so the appropriate methods must be used to calculate 0.3 * challengerStake.
    • The "Challenge ends in:" field should be computed, much like the "Proposal ends in:" field above, from each challenge.challengeEndUnix value which is the estimated Unix timestamp (in seconds) that the challenge will end.
    • The displayed countdown should only be precise to the minute, as the provided timestamp is simply an estimation.
    • The "View" button on each card should bring the user to the detail page for that challenge (discussed in a further section).

Validators

Main page: validators table

  • Validators should be loaded from gov.validators (either by directly viewing that object, or a call to gov.currentValidators()).
    • Be sure to see the Validator type definition as well, which defines each object in the gov.validators map.
    • The validators table has the following column headers:
      • Address is the Ethereum address of a given validator.
      • Stake is the validator Kosu token stake size, displayed in ether units.
      • Daily reward is the estimated number of tokens minted to the validator per day (in units of ether).
      • Vote power is a percentage indicating a given validators influence and power during consensus.
      • Uptime is a percentage representing how many blocks the validator has signed compared to how many they should have signed, if they were online all the time. This value for now should be populated as "N/A" for each listing. Eventually, this value will be loaded from a remote RPC API.
      • Age is simply how long a given validator has been confirmed in the registry (displayed in human units of time, even though the value is represented as a block number).
    • For each validator (each validator in gov.validators), the fields described above should be loaded/computed as follows.
      • Address should be the Ethereum address loaded from validator.owner, and can be displayed as a "hyphenated" hex string.
      • Stake should be loaded from validator.stakeSize (which is a BigNumber) and converted to units of ether prior to being displayed.
      • Daily reward should be loaded from validator.dailyReward, a BigNumber instance, and converted to units of ether prior to being displayed.
      • Vote power should be loaded from validator.power which is a BigNumber instance with a value between 0 and 100. The decimal value should be truncated so as to not display too many decimals. This truncation can be done based on space, or a fixed number of significant digits. Keep in mind, a validator may have less than 1% vote power.
      • Uptime is not a real value at this time, and should be displayed as "N/A" for each listing.
      • Age should be computed based on validator.confirmationUnix which is the Unix timestamp in seconds that the validator was confirmed. The "Age" value should display the number of days, hours, and minutes since this time.
    • Clicking on a validator entry (one of the rows) should take the user to the validator detail page for that listing (described in a later section).

Past challenges

Main page: validators table

  • Past challenges is a section containing a table with information about past challenges and the results of each.
    • Be sure to be familiar with the PastChallenge and ListingSnapshot types.
    • Past challenges are stored and loaded separately from the primary gov properites (proposals, challenges, and validators) and are not updated in real time.
      • Instead, past challenges must be loaded by calling gov.getHistoricalChallenges():
        // following requires `gov.init()` to have completed successfully
        const pastChallenges = await gov.getHistoricalChallenges();
        
        // the number of challenges (use index +1 to display under "ID")
        console.log(pastChallenges.length);
  • The "Past Challenges" table has the following column headers:
    • ID is the challenge's unique ID, which increments from 0.
    • Challenger is the Ethereum address of the entity that initiated the challenge.
    • Type indicates weather the challenge was against an active "validator" or against a pending "proposal".
    • Result indicates the result of the challenge, where "accepted" indicates the challenge won, and "rejected" indicates the challenge failed/lost.
    • Tokens at Stake is the total number of tokens at stake during the challenge, equal to the challenge stake plus the listing owner's stake (see below).
    • Time indicates the time a given challenge ended (vote period ends).
  • For each table entry (loaded from the array returned by gov.getHistoricalChallenges()) and each column field above:
    • ID can be loaded from challenge.listingSnapshot.currentChallenge or can be loaded from the challenge's index within the array returned by getHistoricalChallenges.
    • Challenger should be loaded from challenge.challenger (an Ethereum address).
    • Type is computed based on challenge.listingSnapshot.status based on the following:
      • If listingSnapshot.status === 1, "Type" should be "Proposal" (blue).
      • If listingSnapshot.status === 2, "Type" should be "Validator" (orange).
      • If listingSnapshot.status is not 1 or 2, that entry should not be displayed.
    • Result should be based off the boolean challenge.passed field:
      • If challenge.passed === true then "Result" should be "Accepted" (green).
      • If challenge.passed === false then "Result" should be "Rejected" (red).
    • Tokens at Stake should be computed as the sum of challenge.balance and challenge.listingSnapshot.stakedBalance and displayed in units of ether.
      • Keep in mind both challenge.balance and challenge.listingSnapshot.stakedBalance are BigNumber instances, and stored in units of wei which must be converted prior to displaying.
    • Time should be calculated based on the timestamp of the challenge.challengeEnd block number.
      • This timestamp can be loaded from the gov.getPastBlockTimestamp(n) method, where n is passed in as challenge.challengeEnd.
      • For example:
        // gov.init() must have completed successfully prior to this working
        for (let i = 0; i < challenges.length; i++) {
          const challenge = challenges[i];
          const challengeEndBlock = challenge.challengeEnd.toNumber();
        
          // use this value to display "Time"
          const challengeEndTimestamp = await gov.getPastBlockTimestamp(
            challengeEndBlock
          );
        }
  • Clicking on a challenge should take to a past challenge detail page for that challenge (discussed in a later section).

Proposal detail page

  • Clicking on a proposal from the main page directs the user to the proposal detail page (below).
  • On the proposal detail page, the user can be prompted to challenge the proposal.
  • In addition to the screenshots below, be sure to see the mobile version, and the full sketch file.

Main detail

Proposal detail page: main

  • This is the primary detail page for active proposals.
  • The majority of the data for this page can be loaded from the corresponding gov.proposals object.
  • Each detail page should correspond to one of the objects in gov.proposals, where each proposal is key'd by the listing's Tendermint public key.
  • Page features/fields:
    • Tendermint public key (mid/top left) should correspond to the object property key of the current gov.proposals object.
    • X wants to become a validator the value for X (Ethereum address) should be loaded from proposal.owner.
    • If unchallenged... the countdown to when the proposal becomes a validator should be computed based on the proposal.acceptUnix and should be displayed as a countdown of days, hours, and minutes (seconds should be ignored due to acceptUnix being an estimate).
    • Card: Stake size should be loaded from proposal.stakeSize (remember it is a BigNumber, and must be converted to ether prior to display).
    • Card: Daily reward should be loaded from proposal.dailyReward (must be converted to ether prior to being displayed).
    • Card: Estimated vote power should be loaded from proposal.power and displayed as a percentage, keeping in mind some digits should not be displayed (i.e. trailing decimals).
    • Button: Challenge proposal triggers a change to the next state and can ultimately lead to a call to initiate a challenge (see next sub-section).

Challenge prompt

Proposal detail page: challenge

  • This state offers the user another opportunity to confirm their intent to challenge a listing.
  • It provides a text entry field for the user to provided additional information to support their challenge.
  • The "this challenge will cost" field should be loaded from the value of proposal.stakeSize and displayed in units of ether.
  • The "Challenge" button should trigger the following (async code):
    // inside async method, assume `proposal` is the proposal object from gov.proposals
    // assume `listingPubKey` is the key of that `proposal` object within the mapping
    const stringDetails = loadStringDetails() // pseudocode, load from input box
    
    // the current proposal's public key
    const listingKey = listingPubKey;
    
    const receipt = await gov.kosu.validatorRegistry.challengeListing(
      listingKey,
      stringDetails,
    );
    
    // `receipt.transactionHash` is the txId of the challenge (can be used to view on Etherscan, etc).

Active challenge detail

  • These states exist for active challenges (currently in gov.challenges).
  • The first state below ("main detail") shows information about the challenge, and allows the user to vote (see subsequent sub-sections).
  • The "Vote on this challenge" section will vary based on what phase the challenge is in:
    • Commit period starts immediately after the challenge is initiated, and it lasts until the reveal period starts.
    • Reveal period starts after commitPeriod blocks from the challenge initialization, and lasts until the challengeEnd block. After that point, the challenge is finalized and should be accessed using the getHistoricalChallenges method.
  • Be sure to view the full sketch file for un-included states that can be inferred from others.
  • There are additional screenshots in ./images for various assumed states, as well as mobile layout.

Note: detecting commit/reveal period

During the commit period, the first state below should be shown (under "main detail"), and during the reveal period, the "reveal period" state should be shown, where the two voting buttons are replaced with a single "reveal vote" button.

Example of detecting commit vs reveal period for challenge #5:

// gov.init() must have been called at this point. 
const currentBlock = await gov.currentBlockNumber();

const challengeInfo = await gov.getChallengeInfo("5");
const {
  challengeStart,
  endCommitPeriod,
  challengeEnd,
} = challengeInfo;

if (challengeStart <= currentBlock && currentBlock < endCommitPeriod) {
  // challenge is _IN COMMIT PERIOD_
  // votes _MAY BE COMMITTED_
} else if (endCommitPeriod <= currentBlock && currentBlock < challengeEnd) {
  // challenge is _IN REVEAL PERIOD_
  // votes _MAY NOT BE COMMITTED, ONLY REVEALED_
} else if (challengeEnd <= currentBlock) {
  // reveal period is OVER, and challenge is finalized
}

Main detail (commit period)

This state shows a challenge during the commit phase.

Challenge detail page: main

  • The detail page exists for each challenge in the gov.challenges object.
  • This state should be shown when challenge.challengeType === "validator".
  • Page fields/values:
    • Challenge #N where N is the challenge.challengeId value (as a string).
    • Validator's public key is the object key (public key) in gov.challenges that corresponds to this challenge object.
    • X is challenging validator Y where X is challenge.challenger and Y is challenge.listingOwner.
      • Note, this state shows a validator challenge. If the challenge is against a proposal (`challenge.challengeType === "proposal") a separate state should be shown.
    • Entity y believes that... this string should be loaded and populated from challenge.challengeDetails.
    • This challenge will end in... this countdown should be computed based on challenge.challengeEndUnix and should update in real-time (seconds should now be shown).
    • Potential reward should be equal to 30% of challenge.challengerStake (display in ether).
    • Challenger stake should be loaded from challenge.challengerStake (and displayed in ether).
    • Validator voting power should only be displayed if challenge.challengeType === "validator" and can be computed as follows:
      // assume `listingPublicKey` is the challenge-ee public key (the key in `gov.challenges`)
      const powerBigNum = await gov._getValidatorPower(listingPublicKey); // BigNumber
  • The voting section should be displayed on both "validator" and "proposal" challenges.
  • Depending on the challenge state the buttons should display either:
    • Two buttons (as seen in image), one for each option if in the commit period.
    • One "reveal" button (displayed in later section) if the challenge is in the reveal period.

Main detail (proposal, commit period)

Challenge detail page: proposal

  • If challenge.challengeType === "proposal" this state should be used (minor changes from above).
  • The commit/reveal logic is the same for when to display voting buttons vs. when to display "reveal" button (see here).

Confirm vote

Challenge detail page: confirm commit vote

  • During the commit period, users may vote to remove or vote to keep (both challenges and proposals).
  • Clicking either vote option prompts for another confirmation (displayed above).
  • Subsequent confirmation should trigger the following logic:
    // load from user input box, displayed above (keep in mind wei conversion), should be a `BigNumber`
    const tokensToCommit; 
    
    // load from challenge page, should be `BigNumber`
    const challengeId;
    
    /**
     * Vote "value" is a string, either "1" or "0":
     * - use "1" if the vote is to support the challenge (remove a validator/proposal)
     * - use "0" if the vote is to support the validator/proposal (vote against the challenge)
     */
    const value = "1"
    
    // will prompt user to sign transaction
    // will fail if user does not have enough tokens or allowance
    // this will store the vote salt and value in a cookie, to be used later for reveal
    const txId = await gov.commitVote(challengeId, value, tokensToCommit);

After commit vote

Challenge detail page: post-commit

  • This state should be shown after a successful commit vote transaction (above).
  • For now, "account page" should be a null link (#).
  • The "Add a reminder" link should use the Google Calendar URL API to create an event (see example).
  • An example of a GET request that prompts the user to create an event (copy/paste to browser to try):
    https://www.google.com/calendar/render?action=TEMPLATE&text=Reveal+vote+on+challenge+%233&details=During+the+reveal+period%2C+you+may+reveal+your+vote+on+this+challenge.+If+you+do+not+reveal+a+vote%2C+you+will+not+be+rewarded+if+you+voted+on+the+winning+side.&location=gov.kosu.io&dates=20190613T162100Z%2F20190614T162100Z
    
  • The event can be created for the duration of the "reveal period" which can be calculated as follows:
    const challengeStart = challenge.challengeEnd - gov.params.challengePeriod;
    const startReveal = challengeStart + gov.params.commitPeriod;
    const endReveal = challenge.challengeEnd;
    
    // the calendar event should be created between these two unix timestamps
    const startRevealUnix = await gov.estimateFutureBlockTimestamp(startReveal)
    const endRevealUnix = await gov.estimateFutureBlockTimestamp(endReveal);

Reveal vote

Challenge detail page: reveal period

  • This state should be shown when a challenge is in the "reveal" period.
  • Weather a challenge is in reveal period or not can be determined as described here.
  • If the user clicks "reveal", the following logic should be triggered:
    const challengeId = new BigNumber(challenge.challengeId);
    
    // will trigger MetaMask popup, assuming a vote was previously committed from the same browser
    const revealTxId = await gov.revealVote(challengeId);

Be sure to catch promise rejections from gov.revealVote in the case that the user did not commit a vote from the same browser.

Historical challenges

Historical challenges: table

  • Each historical (past) challenge will have a detail page, hyperlinked from the table shown above.
  • A historical challenge can be either against a "proposal" or against a "validator, and in both cases, the challenge can fail or pass.

Past validator challenge

Historical challenges: validator

  • A historical (resolved) challenge against a validator.
  • Display orange "validator" badge at top if challenge.listingSnapshot.status === 2 (if 1, it is a past challenge against a proposal, see below).
  • The outcome of the challenge (badge underneath "Challenge #") should be:
    • "Accepted" if challenge.passed === true, or
    • "Rejected" if challenge.passed === false.
  • Page fields/values:
    • Challenge #N where N is the ID of the challenge (its position within the array, or taken from listingSnapshot)
    • Validator X was ... challenged by Y where X is the Ethereum address of the validator, loaded from pastChallenge.listingSnapshot.owner and Y is the challenger address, loaded from pastChallenge.challenger.
    • Stake size should be loaded from challenge.balance and displayed in units of ether.
    • Daily reward should be computed based on challenge.listingSnapshot.rewardRate:
      // this value will be a `BigNumber` in units of wei (convert before displaying)
      const dailyReward = gov._estimateDailyReward(challenge.listingSnapshot.rewardRate);
    • Results:
      • Based on challenge.passed, we can assume which side has the majority of tokens.
      • The value for the majority (winning) outcome can be loaded from challenge.winningTokens.
      • The minority (losing) value can be computed as the difference between challenge.voterTotal and challenge.winningTokens.
    • Button: Go back should bring the user back to the main page.

Past proposal challenge

Historical challenges: proposal

  • The logic and values for this state are identical to the one above (past challenge against a validator), except that the badge underneath "Challenge #" shows a blue "proposal", rather than orange "validator".

Documentation

  • Below is the README for @kosu/gov-portal-helper package.
  • The published package may be found on the NPM registry.
  • For safety from number precision issues, most numerical values are expected and returned as BigNumber instances, so be sure to be familiar with that API.

@kosu/gov-portal-helper

Gov is a helper library for interacting with the Kosu validator governance system (primarily the Kosu ValidatorRegistry contract).

It is designed with the browser in mind, and is intended to be used in front- end projects for simplifying interaction with the governance system.

Installation

Add gov-portal-helper to your project via npm or yarn.

# install with yarn
yarn add @kosu/gov-portal-helper

# install with npm
yarn add @kosu/gov-portal-helper

Typedefs

Validator

Represents an active validator in the registry.

Proposal

Represents a pending listing application for a spot on the ValidatorRegistry.

StoreChallenge

Represents a current challenge (in the gov state).

PastChallenge

Represents a historical challenge, and its outcome.

ListingSnapshot

Represents a listing at the time it was challenged.

Vote

Represents a stored vote in a challenge poll.

Gov

Gov is a helper library for interacting with the Kosu validator governance system (primarily the Kosu ValidatorRegistry contract).

It is designed with the browser in mind, and is intended to be used in front- end projects for simplifying interaction with the governance system.

Methods may be used to load the current proposals, validators, and challenges from the prototype's state, or the gov.ee object (an EventEmitter) may be used to detect updates to the state, emitted as gov_update events.

After a gov_update event, the read methods for proposals, challenges, and validators must be called to load the current listings. Alternatively, access the objects directly with gov.listings, etc.

Kind: global class

new Gov()

Create a new Gov instance (gov). Requires no arguments, but can be set to "debug" mode by passing true or 1 (or another truthy object to the constructor).

Prior to using most gov functionality, the async gov.init() method must be called, which will initialize the module and load state from the Kosu contract system.

gov.init()

Main initialization function for the gov module. You must call init prior to interacting with most module functionality, and gov.init() will load the current registry status (validators, proposals, etc.) so it should be called early-on in the page life-cycle.

Performs many functions, including:

  • prompt user to connect MetaMask
  • load user's address (the "coinbase")
  • load the current Ethereum block height
  • load and process the latest ValidatorRegistry state

Kind: instance method of Gov

gov.currentProposals() ⇒ Map.<Proposal>

Load the current proposal map from state.

Kind: instance method of Gov
Returns: Map.<Proposal> -

a map where the key is the listing public key, and the value is a proposal object


Example

const proposals = gov.currentProposals();

gov.currentValidators() ⇒ Map.<Validator>

Load the current validators map from state.

Kind: instance method of Gov
Returns: Map.<Validator> -

a map where the key is the listing public key, and the value is a validator object


Example

const validators = gov.currentValidators();

gov.currentChallenges() ⇒ Map.<StoreChallenge>

Load the current challenges map from state.

Kind: instance method of Gov
Returns: Map.<StoreChallenge> -

a map where the key is the listing public key, and the value is a challenge object


Example

const challenges = gov.currentChallenges();

gov.weiToEther(wei) ⇒ string

Convert a number of tokens, denominated in the smallest unit - "wei" - to "full" units, called "ether". One ether = 1*10^18 wei.

All contract calls require amounts in wei, but the user should be shown amounts in ether.

Kind: instance method of Gov
Returns: string -

the same amount in ether, string used for precision

Param Type Description
wei BigNumber | string

the token amount in wei to convert

Example

gov.weiToEther("100000000000000000000") // > "100"
gov.weiToEther(100000000000000000000)   // > "100"

gov.etherToWei(ether) ⇒ string

Convert a number of tokens (full units, called "ether") to "wei", the smallest denomination of most ERC-20 tokens with 18 decimals.

All contract calls require amounts in wei, but the user should be shown amounts in ether.

Kind: instance method of Gov
Returns: string -

the same amount in wei, string used for precision

Param Type Description
ether BigNumber | string

the token amount to convert

Example

gov.etherToWei(10)  // > "10000000000000000000"
gov.etherToWei("1") // > "1000000000000000000"

gov.commitVote(challengeId, value, amount) ⇒ Promise.<string>

Commit a vote in an active a challenge poll.

This method creates a vote (with value and salt), encodes it, and submits it as a transaction (requires MetaMask signature).

It stores the vote data in a browser cookie so it may be revealed later, which means voters must reveal a vote with the same browser they used to commit it.

Kind: instance method of Gov
Returns: Promise.<string> -

the transaction hash of the commit tx

Param Type Description
challengeId BigNumber

the pollId of the challenge, as a string

value string

the vote value, "1" to vote for the challenge, "0" to vote against

amount BigNumber

the number of tokens (in wei) to commit in the vote

Example

// we are looking at challenge #13, and want to vote AGAINST it with 10 tokens
const pollId = new BigNumber(13);
const amount = new BigNumber(gov.etherToWei("10"));
const value = "0";

// will prompt for MetaMask signature
const commitVoteTxId = await gov.commitVote(pollId, value, amount);

// ... some time passes, we now want to reveal ...

// load vote from cookie and reveal
const revealTxId = await gov.revealVote(new BigNumber("13"));

// ... wait for Tx's to confirm or whatever, etc.

gov.revealVote(challengeId) ⇒ Promise.<string>

Reveal a previously committed vote, by challengeId (as a BigNumber).

For this method to work, the user must have committed a vote during the commit period for the given challenge.

This method must also be called during the reveal period in order for the transaction not to fail.

Calling this method will trigger a MetaMask pop-up asking for the user's signature and approval.

Kind: instance method of Gov
Returns: Promise.<string> -

the transaction hash of the reveal tx.

Param Type Description
challengeId BigNumber

the challenge to reveal a stored vote for

gov.estimateFutureBlockTimestamp(blockNumber) ⇒ Promise.<number>

Estimate the UNIX timestamp (in seconds) at which a given block will be mined.

Kind: instance method of Gov
Returns: Promise.<number> -

the block's estimated UNIX timestamp (in seconds)

Param Type Description
blockNumber number

the block number to estimate the timestamp for

Example

const block = 6102105;
const unixTs = gov.estimateFutureBlockTimestamp(block);

// use as a normal date object (multiply by 1000 to get ms)
const blockDate = new Date(ts * 1000);

gov.getPastBlockTimestamp(blockNumber) ⇒ Promise.<number>

Retrieve the Unix timestamp of a block that has already been mined. Should be used to display times of things that have happened (validator confirmed, etc.).

Kind: instance method of Gov
Returns: Promise.<number> -

the Unix timestamp of the specified blockNumber

Param Type Description
blockNumber number

the block to get the unix timestamp for

Example

await gov.getPastBlockTimestamp(515237) // > 1559346404

gov.getHistoricalChallenges() ⇒ Promise.<Array.<PastChallenge>>

This method returns an array (described below) that contains information about all past challenges. Intended to be used for the "Past Challenges" section.

Kind: instance method of Gov
Returns: Promise.<Array.<PastChallenge>> -

all historical challenges.


gov.getChallengeInfo(challengeId) ⇒ Promise.<ChallengeInfo>

Returns an object with the block numbers of important times for a given challenge. Between challengeStart and endCommitPeriod, votes may be committed (submitted) to the challenge.

Between endCommitPeriod and challengeEnd, votes may be revealed with the same salt and vote value.

Kind: instance method of Gov
Returns: Promise.<ChallengeInfo> -

the block numbers for this challenge

Param Type Description
challengeId string | number | BigNumber

the ID of the challenge to query

Example

const info = await gov.getChallengeInfo(new BigNumber(1));
const currentBlock = await gov.currentBlockNumber();

if (currentBlock < endCommitPeriod && currentBlock >= challengeStart) {
  // in "commit" period; voters may submit votes
} else if (currentBlock >= endCommitPeriod && currentBlock <= challengeEnd) {
  // in "reveal" period; voters may reveal votes
} else {
  // challenge has ended (or issues with block numbers)
}

gov.currentBlockNumber() ⇒ number

Returns the current block height (as a number).

Kind: instance method of Gov
Returns: number -

The current (or most recent) Ethereum block height.


Gov.ZERO

The value 0 as an instance ofBigNumber.

Kind: static property of Gov

Gov.ONE

The value 1 as an instance ofBigNumber.

Kind: static property of Gov

Gov.ONE_HUNDRED

The value 100 as an instance ofBigNumber.

Kind: static property of Gov

Gov.BLOCKS_PER_DAY

Estimated blocks per day (mainnet only).

Kind: static property of Gov

Validator

Represents an active validator in the registry.

Kind: global typedef
Properties

Name Type Description
owner string

the Ethereum address of the validator

stakeSize BigNumber

the staked balance (in wei) of the validator

dailyReward BigNumber

the approximate daily reward to the validator (in wei)

confirmationUnix number

the unix timestamp of the block the validator was confirmed in

power BigNumber

the validators approximate current vote power on the Kosu network

details string

arbitrary details provided by the validator when they applied

Proposal

Represents a pending listing application for a spot on the ValidatorRegistry.

Kind: global typedef
Properties

Name Type Description
owner string

the Ethereum address of the applicant

stakeSize BigNumber

the total stake the applicant is including with their proposal (in wei)

dailyReward BigNumber

the approximate daily reward (in wei) the applicant is requesting

power BigNumber

the estimated vote power the listing would receive if accepted right now

details string

arbitrary details provided by the applicant with their proposal

acceptUnix number

the approximate unix timestamp the listing will be accepted, if not challenged

StoreChallenge

Represents a current challenge (in the gov state).

Kind: global typedef
Properties

Name Type Description
listingOwner string

the Ethereum address of the owner of the challenged listing

listingStake BigNumber

the total stake of the challenged listing

listingPower BigNumber

the current vote power of the listing (if they are a validator)

challenger string

the Ethereum address of the challenger

challengeId BigNumber

the incremental ID of the current challenge

challengerStake BigNumber

the staked balance of the challenger

challengeEndUnix number

the estimated unix timestamp the challenge ends at

challengeEnd BigNumber

the block at which the challenge reveal period ends

totalTokens BigNumber

if finalized, the total number of tokens from participating voters

winningTokens BigNumber

if finalized, the number of tokens that voted on the winning side

result string

the final result of the challenge; "passed", "failed", or null if not finalized

challengeType string

the type of listing the challenge is against, either a "validator" or a "proposal"

listingDetails string

details provided by the listing holder

challengeDetails string

details provided by the challenger

PastChallenge

Represents a historical challenge, and its outcome.

Kind: global typedef
Properties

Name Type Description
balance BigNumber

the number of tokens (in wei) staked in the challenge

challengeEnd BigNumber

the block the challenge ends at

challenger string

the Ethereum address of the challenger

details string

additional details provided by the challenger

finalized boolean

true if the challenge result is final, false if it is ongoing

listingKey string

the key that corresponds to the challenged listing

listingSnapshot ListingSnapshot

an object representing the state of the challenged listing at the time of challenge

passed boolean

true if the challenge was successful, false otherwise

pollId BigNumber

the incremental ID used to identify the poll

voterTotal BigNumber

the total number of tokens participating in the vote

winningTokens BigNumber

the total number of tokens voting for the winning option

ListingSnapshot

Represents a listing at the time it was challenged.

Kind: global typedef
Properties

Name Type Description
applicationBlock BigNumber

the block the listing application was submitted

confirmationBlock BigNumber

the block the listing was confirmed (0 if unconfirmed)

currentChallenge BigNumber

the ID of the current challenge against the listing

details string

arbitrary details provided by the listing applicant

exitBlock BigNumber

the block (if any) the listing exited at

lastRewardBlock BigNumber

the last block the listing owner claimed rewards for

owner string

the Ethereum address of the listing owner

rewardRate BigNumber

the number of tokens (in wei) rewarded to the listing per reward period

stakedBalance BigNumber

the number of tokens staked by the listing owner (in wei)

status number

the number representing the listing status (0: no listing, 1: proposal, 2: validator, 3: in-challenge, 4: exiting)

tendermintPublicKey string

the 32 byte Tendermint public key of the listing holder

Vote

Represents a stored vote in a challenge poll.

Kind: global typedef
Properties

Name Type Description
id BigNumber

the challengeId the vote is for

value string

the vote value (should be "1" or "0" for challenge votes)

salt string

a secret string used to hash the vote; must use same salt in commit as reveal

encoded string

the encoded vote, as passed to the contract system

About

Specification and design documents for the Kosu validator governance portal.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published