Governance

What is Cover Incident?#

The term cover incident refers to a state in which all cover rules and exclusions have been met.

In the Neptune Mutual Protocol, a cover incident must go through a governance process to reach a resolution. Once an incident is reported, the resolution can be either Incident Occurred or False Reporting. The protocol is designed to punish users who try to cheat the governance system by confiscating all their stakes. To incentivize participation, the users who report in the governance system may earn some extra NPM rewards in a very short span of time (usually one week).

When a cover incident occurs, anyone can report it and earn a 5% commission on the protocol's fee earnings. Before submitting any incident, make sure you understand the following:

  • For the event to qualify as a cover incident, all terms and rules of the cover must be met.
  • The users who agree with the incident can add more votes/stakes or add attestations to the report.
  • The users who disagree with the incident reporting can dispute or refute. Disputing allows anyone to mark the reported incident as False Reporting once. After that, the users who believe the incident reporting is a mistake can add more NPM votes/stakes by refuting.
  • There is a 7-day reporting period for users to participate in the governance process. The end of the reporting period is called Resolution Date.
  • The outcome of the cover incident will be achieved on the resolution date. The users who vote against the majority will lose all their stake to the majority.

Get Minimum Stake#

Returns the minimum number of NPM tokens you need to stake to report an incident. Note that this number does not apply afterwards once a report has be submitted.

import { ChainId, governance } from '@neptunemutual/sdk'
import { getProvider } from '../provider.js'
import { weiAsNpm } from '../bn.js'
import { info } from '../configs/info.js'

const getMinStake = async () => {
  try {
    const { key: coverKey } = info
    const provider = getProvider()

    const response = await governance.getMinStake(ChainId.Mumbai, coverKey, provider)
    console.info('Minimum Reporting Stake: %s', weiAsNpm(response.result))
  } catch (error) {
    console.error(error)
  }
}

getMinStake()

/*****************************************************************************
[info] Minimum Reporting Stake: 3,400.00 NPM
*****************************************************************************/

Report an Incident (Incident Happened)#

The first user who reports a cover incident becomes the Cover Reporter. You will need to stake a minimum number NPM tokens as configured in the cover pool to report an incident.

import { ChainId, governance, utils } from '@neptunemutual/sdk'
import { info } from './info.js'
import { getProvider } from './provider.js'
import { ether } from './bn.js'

const payload = {
  title: 'Test Exploit',
  observed: new Date(),
  proofOfIncident: 'https://etherscan.io/tokenholdings?a=0xA9AD3537C819ae0530623aFb458Fee8456C47d33',
  description: 'DeFi protocol Learn Finance has reported that its vault was exploited by a hacker to the tune of $11 million on Dec 25.',
  stake: ether(3400)
}

const report = async () => {
  try {
    const { key: coverKey } = info
    const productKey = utils.keyUtil.toBytes32('')
    const provider = getProvider()

    let response = await governance.approveStake(ChainId.Mumbai, { amount: payload.stake }, provider)
    await response.result.wait()

    response = await governance.report(ChainId.Mumbai, coverKey, productKey, payload, provider)
    console.info(response)

    await response.result.tx.wait()
  } catch (error) {
    console.error(error)
  }
}

report()

/*****************************************************************************
[info] {
  status: 'Success',
  result: {
    storage: {
      hashBytes32: '0x01d5ff954cff5d1c5e3de56676599eee60f75a539c6bba1ef3b6a20dac6da244',
      hash: 'QmNTpNuTjzQivpCGrAi1HixmbP2smwQ7KjH2uVFkD8s8Hh',
      permalink: 'https://ipfs.infura.io/ipfs/QmNTpNuTjzQivpCGrAi1HixmbP2smwQ7KjH2uVFkD8s8Hh'
    },
    tx: {
      type: 2,
      chainId: 80001,
      nonce: 24,
      maxPriorityFeePerGas: [BigNumber],
      maxFeePerGas: [BigNumber],
      gasPrice: null,
      gasLimit: [BigNumber],
      to: '0x073580951bBBB22682e6129Fc3D43D963084bCCf',
      value: [BigNumber],
      data: '0x73bbb1a5616e696d617465642d6272616e64730000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001d5ff954cff5d1c5e3de56676599eee60f75a539c6bba1ef3b6a20dac6da2440000000000000000000000000000000000000000000000b8507a820728200000',
      accessList: [],
      hash: '0x57855253aa90f0ec2a159eed3f9375698f83c9f358c351616f088eaef396e1b6',
      v: 1,
      r: '0xc708122ab0b17553fbaecaa53b547447cd728ef080e956d1f7ad089489586ddd',
      s: '0x1d7f82fb4e5e4f856a4e33102d901b522fcd80a7a93ab3e1c1a5ca198d37a63d',
      from: '0x2DAc3776B9f4243DF6445515eBE6F6Cd003B3681',
      confirmations: 0,
      wait: [Function (anonymous)]
    }
  }
}
*****************************************************************************/

Dispute an Incident (False Reporting)#

The first user who submits their NPM stake to disagree with a reported cover incident becomes the Candidate Cover Reporter. You will need to stake equal number of tokens as the reporter to dispute a reported incident.

import { ChainId, governance, utils } from '@neptunemutual/sdk'
import { info } from './info.js'
import { getProvider } from './provider.js'
import { ether } from './bn.js'

const payload = {
  title: 'The exploit was wrongly reported',
  proofOfDispute: 'https://etherscan.io/tokenholdings?a=0xA9AD3537C819ae0530623aFb458Fee8456C47d33',
  description: 'DeFi protocol Learn Finance had confirmed that its vault is safe and no funds are lost on Dec 29.',
  stake: ether(3400)
}

const dispute = async () => {
  try {
    const { key: coverKey } = info
    const productKey = utils.keyUtil.toBytes32('')
    const provider = getProvider()

    let response = await governance.approveStake(ChainId.Mumbai, { amount: payload.stake }, provider)
    await response.result.wait()

    response = await governance.dispute(ChainId.Mumbai, coverKey, productKey, payload, provider)
    await response.result.tx.wait()

    console.info(response)
  } catch (error) {
    console.error(error)
  }
}

dispute()

/*****************************************************************************
[info] {
  status: 'Success',
  result: {
    storage: {
      hashBytes32: '0x91c3f49289cbe3dae1dc1514fc5b638498feef8220d5316d0ec54ccf4d49ca01',
      hash: 'QmY9f8UKbXa1UP9rXdPDkYddE3gwk1ukgsRuxwC1kugum2',
      permalink: 'https://ipfs.infura.io/ipfs/QmY9f8UKbXa1UP9rXdPDkYddE3gwk1ukgsRuxwC1kugum2'
    },
    tx: {
      type: 2,
      chainId: 80001,
      nonce: 28,
      maxPriorityFeePerGas: [BigNumber],
      maxFeePerGas: [BigNumber],
      gasPrice: null,
      gasLimit: [BigNumber],
      to: '0x073580951bBBB22682e6129Fc3D43D963084bCCf',
      value: [BigNumber],
      data: '0xc3faf9ef616e696d617465642d6272616e6473000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000062c1ae8691c3f49289cbe3dae1dc1514fc5b638498feef8220d5316d0ec54ccf4d49ca010000000000000000000000000000000000000000000000b8507a820728200000',
      accessList: [],
      hash: '0x416b93be28512d2d0df9f2ad5816625d02b1728708c709c06976293a393eac5f',
      v: 1,
      r: '0x8079b3cb08a3d2eac1ccd7bc06ae82d0933632f1b8dfa45ba695d11256d2c508',
      s: '0x2dc51f93bdbd17bb1d394a9dedf25b2820a58ec4b4b27d54bb9f267de8da9fb6',
      from: '0x2DAc3776B9f4243DF6445515eBE6F6Cd003B3681',
      confirmations: 0,
      wait: [Function (anonymous)]
    }
  }
}
*****************************************************************************/

Add an Attestation (Incident Happened)#

Adding an attestation means that you believe and support the reported incident. You can stake any number of NPM tokens and vote for the support of the Incident Happened side.

import { ChainId, governance, utils } from '@neptunemutual/sdk'
import { info } from './info.js'
import { getProvider } from './provider.js'
import { ether, weiAsNpm } from './bn.js'

const attest = async () => {
  try {
    const { key: coverKey, coverName } = info
    const productKey = utils.keyUtil.toBytes32('')
    const provider = getProvider()

    const stake = ether(100)
    const incidentDate = (await governance.getIncidentDate(ChainId.Mumbai, coverKey, productKey, provider)).result

    let response = await governance.getStakes(ChainId.Mumbai, coverKey, productKey, incidentDate, provider)
    console.info('[%s Reporting Stake: Yes] Before: %s', coverName, weiAsNpm(response.result.yes))

    response = await governance.approveStake(ChainId.Mumbai, { amount: stake }, provider)
    await response.result.wait()

    response = await governance.attest(ChainId.Mumbai, coverKey, productKey, stake, provider)
    console.info(response)

    await response.result.wait()

    response = await governance.getStakes(ChainId.Mumbai, coverKey, productKey, incidentDate, provider)
    console.info('[%s Reporting Stake: Yes] After: %s', coverName, weiAsNpm(response.result.yes))
  } catch (error) {
    console.error(error)
  }
}

attest()

/*****************************************************************************
[info] [Animated Brands Reporting Stake: Yes] Before: 3,400.00 NPM
[info] {
  status: 'Success',
  result: {
    type: 2,
    chainId: 80001,
    nonce: 26,
    maxPriorityFeePerGas: BigNumber { _hex: '0x0f8a45d534', _isBigNumber: true },
    maxFeePerGas: BigNumber { _hex: '0x0f8a45d534', _isBigNumber: true },
    gasPrice: null,
    gasLimit: BigNumber { _hex: '0x02ecbe', _isBigNumber: true },
    to: '0x073580951bBBB22682e6129Fc3D43D963084bCCf',
    value: BigNumber { _hex: '0x00', _isBigNumber: true },
    data: '0xd42de68a616e696d617465642d6272616e6473000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000062c1ae860000000000000000000000000000000000000000000000056bc75e2d63100000',
    accessList: [],
    hash: '0xf736fbdb4ae900d41981ea74267ffa34485f583badeb651bab91463936037313',
    v: 0,
    r: '0xee2a279d31b613f4b42f82117ac866c1c29df1a24fe25e516b169770c7b19155',
    s: '0x0bc5cbf0ff6b07dfa6996aece288bfb2b2ad753630adb67f5a3e1da790f5b2f3',
    from: '0x2DAc3776B9f4243DF6445515eBE6F6Cd003B3681',
    confirmations: 0,
    wait: [Function (anonymous)]
  }
}
[info] [Animated Brands Reporting Stake: Yes] After: 3,500.00 NPM
*****************************************************************************/

Add a Refutation (False Reporting)#

By refuting, you prove your support for the side who disagree with the reported incident. You can stake any number of NPM tokens and vote to support the False Reporting side.

import { ChainId, governance, utils } from '@neptunemutual/sdk'
import { info } from './info.js'
import { getProvider } from './provider.js'
import { ether, weiAsNpm } from './bn.js'

const refute = async () => {
  try {
    const { key, coverName } = info
    const productKey = utils.keyUtil.toBytes32('')
    const provider = getProvider()

    const stake = ether(100)
    const incidentDate = (await governance.getIncidentDate(ChainId.Mumbai, key, productKey, provider)).result

    let response = await governance.getStakes(ChainId.Mumbai, key, productKey, incidentDate, provider)
    console.info('[%s Reporting Stake: No] Before: %s', coverName, weiAsNpm(response.result.no))

    response = await governance.approveStake(ChainId.Mumbai, { amount: stake }, provider)
    await response.result.wait()

    response = await governance.refute(ChainId.Mumbai, key, productKey, stake, provider)
    console.info(response)

    await response.result.wait()

    response = await governance.getStakes(ChainId.Mumbai, key, productKey, incidentDate, provider)
    console.info('[%s Reporting Stake: No] After: %s', coverName, weiAsNpm(response.result.no))
  } catch (error) {
    console.error(error)
  }
}

refute()

/*****************************************************************************
[info] [Animated Brands Reporting Stake: No] Before: 3,400.00 NPM
[info] {
  status: 'Success',
  result: {
    type: 2,
    chainId: 80001,
    nonce: 31,
    maxPriorityFeePerGas: BigNumber { _hex: '0x1bf08eb000', _isBigNumber: true },
    maxFeePerGas: BigNumber { _hex: '0x1bf08eb000', _isBigNumber: true },
    gasPrice: null,
    gasLimit: BigNumber { _hex: '0x0422dc', _isBigNumber: true },
    to: '0x073580951bBBB22682e6129Fc3D43D963084bCCf',
    value: BigNumber { _hex: '0x00', _isBigNumber: true },
    data: '0x2c11e1d4616e696d617465642d6272616e6473000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000062c1ae860000000000000000000000000000000000000000000000056bc75e2d63100000',
    accessList: [],
    hash: '0x724121dd03690380c18fd0eaf1e7570538b86f1871d6cae2ca0aace50186fb58',
    v: 0,
    r: '0xe465976ddc833dc6a7c1210d3a02a8eb1e102477c132fac548702b75ea1ab247',
    s: '0x1f4d56397c8e9065fb7cf37b494a70abb3257a33982dbbad3f4482deab8042c3',
    from: '0x2DAc3776B9f4243DF6445515eBE6F6Cd003B3681',
    confirmations: 0,
    wait: [Function (anonymous)]
  }
}
[info] [Animated Brands Reporting Stake: No] After: 3,500.00 NPM
*****************************************************************************/

Get Status#

Gets the concensus status of a given cover.

import { ChainId, governance, utils } from '@neptunemutual/sdk'
import { info } from '../configs/info.js'
import { getProvider } from '../provider.js'

const getStatus = async () => {
  try {
    const { key, coverName } = info
    const productKey = utils.keyUtil.toBytes32('')
    const provider = getProvider()

    const response = await governance.getStatus(ChainId.Mumbai, key, productKey, provider)
    console.info('[%s] Status: %s', coverName, JSON.stringify(response.result))
  } catch (error) {
    console.error(error)
  }
}

getStatus()

/*****************************************************************************
[info] [Animated Brands] Status: {"key":"FalseReporting","value":0}
*****************************************************************************/

Get Incident Date#

Gets the incident date by the given cover key.

import { ChainId, governance, utils } from '@neptunemutual/sdk'
import { info } from '../configs/info.js'
import { getProvider } from '../provider.js'
import { toDate } from '../bn.js'

const getIncidentDate = async () => {
  try {
    const { key, coverName } = info
    const productKey = utils.keyUtil.toBytes32('')
    const provider = getProvider()

    const response = await governance.getIncidentDate(ChainId.Mumbai, key, productKey, provider)
    console.info('[%s] Incident Date: %s', coverName, toDate(response.result).toUTCString())
  } catch (error) {
    console.error(error)
  }
}

getIncidentDate()

/*****************************************************************************
[info] [Animated Brands] Incident Date: Tue, 20 Jul 2021 12:32:34 GMT
*****************************************************************************/

Get Reporter#

Gets the reporter of the given cover (if any).

import { ChainId, governance, utils } from '@neptunemutual/sdk'
import { info } from '../configs/info.js'
import { getProvider } from '../provider.js'

const getReporter = async () => {
  try {
    const { key, coverName } = info
    const productKey = utils.keyUtil.toBytes32('')
    const provider = getProvider()

    const incidentDate = (await governance.getIncidentDate(ChainId.Mumbai, key, productKey, provider)).result

    const response = await governance.getReporter(ChainId.Mumbai, key, productKey, incidentDate, provider)
    console.info('[%s] Reporter: %s', coverName, response.result)
  } catch (error) {
    console.error(error)
  }
}

getReporter()

/*****************************************************************************
[info] [Animated Brands] Reporter: 0x076F91C0A411197e6Fce476F37c6385CCeacd26D
*****************************************************************************/

Based on the stakes of each side, the returned value is either the original cover reporter or the candidate cover reporter.

Get Stakes#

Returns the stakes submitted by the users on each side.

import { ChainId, governance, utils } from '@neptunemutual/sdk'
import { info } from '../configs/info.js'
import { getProvider } from '../provider.js'
import { weiAsNpm } from '../bn.js'

const getStakes = async () => {
  try {
    const { key, coverName } = info
    const productKey = utils.keyUtil.toBytes32('')
    const provider = getProvider()

    const incidentDate = (await governance.getIncidentDate(ChainId.Mumbai, key, productKey, provider)).result
    const response = await governance.getStakes(ChainId.Mumbai, key, productKey, incidentDate, provider)
    const { yes, no } = response.result

    console.info('[%s Stakes] Yes: %s. No: %s', coverName, weiAsNpm(yes), weiAsNpm(no))
  } catch (error) {
    console.error(error)
  }
}

getStakes()

/*****************************************************************************
[info] [Animated Brands Stakes] Yes: 3500.00 NPM. No: 3500.00 NPM
*****************************************************************************/

Get Stakes of an Account#

Returns the stakes submitted by the given account on each side.

import { ChainId, governance, utils } from '@neptunemutual/sdk'
import { info } from '../configs/info.js'
import { getProvider } from '../provider.js'
import { weiAsNpm } from '../bn.js'

const getStakesOf = async () => {
  try {
    const { key, coverName } = info
    const productKey = utils.keyUtil.toBytes32('')
    const provider = getProvider()

    const account = provider.address
    const incidentDate = (await governance.getIncidentDate(ChainId.Mumbai, key, productKey, provider)).result
    const response = await governance.getStakesOf(ChainId.Mumbai, key, productKey, incidentDate, account, provider)
    const { yes, no } = response.result

    console.info('[%s Stakes Of %s] Yes: %s. No: %s', coverName, account, weiAsNpm(yes), weiAsNpm(no))
  } catch (error) {
    console.error(error)
  }
}

getStakesOf()

/*****************************************************************************
[info] [Animated Brands Stakes Of 0x076F91C0A411197e6Fce476F37c6385CCeacd26D] Yes: 350.00 NPM. No: 100.00 NPM
*****************************************************************************/

Finalize the Reporting#

A report can be finalized after the claim expiry period. Once finalized, policies for the cover in question can be purchased again.

await resolution.finalize(<ChainId>, <coverKey>, <productKey>, <incidentDate>, <provider>)

This feature is only accessible to the governance system or the protocol admins.

Always refer to the Protocol Fee document for the latest information since the fees are configurable and can change.