Home Reference Source

src/light.ts

import NH from 'eth-ens-namehash'
import axios from 'axios'
import * as bs58 from 'bs58'
import sha256 from 'sha256'

// Lovely ABIs
import * as ResolverAbi from './smart_contracts/SV_ENS_Resolver.abi.json'
import * as IndexAbi from './smart_contracts/SVLightIndex.abi.json'
import * as BackendAbi from './smart_contracts/SVLightIndexBackend.abi.json'
import * as BBFarmAbi from './smart_contracts/BBFarm.abi.json'
import * as PaymentsAbi from './smart_contracts/SVPayments.abi.json'
import * as AuxAbi from './smart_contracts/AuxAbi.abi.json'
import * as AuctionAbi from './smart_contracts/CommAuctionIface.abi.json'
// import * as ERC20Abi from './smart_contracts/ERC20.abi.json'

export const initializeSvLight = async svConfig => {
  const { indexContractName, ensResolver, httpProvider, auxContract } = svConfig

  const Web3 = require('web3')
  const web3 = new Web3(new Web3.providers.HttpProvider(httpProvider))

  const resolver = new web3.eth.Contract(ResolverAbi, ensResolver)

  // const indexAddress =
  // console.log('indexAddress :', indexAddress);
  const index = new web3.eth.Contract(
    IndexAbi,
    await resolveEnsAddress({ resolver }, indexContractName)
  )
  const backendAddress = await getBackendAddress({ index })
  const backend = new web3.eth.Contract(BackendAbi, backendAddress)
  const aux = new web3.eth.Contract(AuxAbi, auxContract)
  const payments = new web3.eth.Contract(
    PaymentsAbi,
    await index.methods.getPayments().call()
  )

  return {
    svConfig,
    web3,
    resolver,
    index,
    backend,
    aux,
    payments
  }
}

export const resolveEnsAddress = async ({ resolver }, ensName) => {
  return await resolver.methods.addr(NH.hash(ensName)).call()
}

export const getBackendAddress = async ({ index }) => {
  return await index.methods.getBackend().call()
}

export const getDemocInfo = async ({ backend, democHash }) => {
  return await backend.methods.getDInfo(democHash).call()
}

export const getDemocNthBallot = async ({ svNetwork }, democBallotInfo) => {
  // Destructure and set the variables that are needed
  const { index, backend, aux, svConfig } = svNetwork
  const { democHash, nthBallot } = democBallotInfo
  const indexAddress = index._address
  const backendAddress = backend._address
  const archiveUrl = { svConfig }

  const bbFarmAndBallotId = await aux.methods
    .getBBFarmAddressAndBallotId(
      backendAddress,
      indexAddress,
      democHash,
      nthBallot
    )
    .call()
  // console.log('bbFarmAndBallotId :', bbFarmAndBallotId);

  const { id, bbFarmAddress } = bbFarmAndBallotId
  const userEthAddress = '0x0000000000000000000000000000000000000000'
  const ethBallotDetails = await aux.methods
    .getBallotDetails(id, bbFarmAddress, userEthAddress)
    .call()

  const ballotSpec = await getBallotSpec(archiveUrl, ethBallotDetails.specHash)
  // console.log('ballotSpec :', ballotSpec);
  // .then(x => console.log('Then called', x))
  // .catch(x => console.log('Caught error', x));

  const ballotObject = {
    ...bbFarmAndBallotId,
    ...ethBallotDetails,
    data: { ...ballotSpec.data }
  }

  return ballotObject
}

export const getBallotSpec = async (
  archiveUrl,
  ballotSpecHash
): Promise<{ data: any }> => {
  // TODO refactor to be a bit more elegant
  return new Promise<{ data: any }>((res, rej) => {
    let done = false
    const doRes = obj => {
      if (!done) {
        done = true
        res(obj)
      }
    }
    getBallotObjectFromIpfs(ballotSpecHash).then(doRes)
    setTimeout(() => {
      if (!done) {
        getBallotObjectFromS3(archiveUrl, ballotSpecHash)
          .then(doRes)
          .catch(rej)
      }
    }, 3500)
  })
}

export const getBallotObjectFromS3 = async (archiveUrl, ballotSpecHash) => {
  return axios.get(archiveUrl + ballotSpecHash + '.json')
}

export const getBallotObjectFromIpfs = async ballotSpecHash => {
  const ipfsUrl = 'https://ipfs.infura.io/api/v0/block/get?arg='
  const cidHex = '1220' + ballotSpecHash.substr(2)
  const bytes = Buffer.from(cidHex, 'hex')
  const cid = bs58.encode(bytes)
  return await axios.get(ipfsUrl + cid)
}

// Take the svNetwork object and a democHash, will return all of the ballots from the democracy in an array
export const getDemocBallots = async ({ svNetwork, democHash }) => {
  const { backend } = svNetwork
  const democInfo = await getDemocInfo({ backend, democHash })

  // Throw an error if the democ info is not correct
  const { erc20, owner } = democInfo
  if (owner === '0x0000000000000000000000000000000000000000') {
    throw new Error('Democracy Hash does not resolve to a democracy')
  }

  // TODO - Work out where / how to push an errored ballot
  // Loop through and get all the ballots
  const numBallots = democInfo.nBallots
  const ballotsArray = []
  for (let i = 0; i < numBallots; i++) {
    ballotsArray[i] = await getDemocNthBallot(
      { svNetwork },
      { democHash: democHash, nthBallot: i }
    )
  }

  return ballotsArray
}

/** Takes in the svNetwork object and returns all relevant addresses */
export const getContractAddresses = async ({ svNetwork }) => {
  const { index, resolver, backend, aux, svConfig } = svNetwork
  const { delegationContractName, lookupAddress } = svConfig

  return {
    indexAddress: index._address,
    backendAddress: backend._address,
    auxAddress: aux._address,
    lookupAddress: lookupAddress,
    resolverAddress: resolver._address,
    communityAuctionAddress: await index.methods.getCommAuction().call(),
    delegationAddress: await resolveEnsAddress(
      { resolver },
      delegationContractName
    ),
    paymentsAddress: await index.methods.getPayments().call()
  }
}

export const weiToCents = async ({ payments }, wei) => {
  return await payments.methods.weiToCents(wei).call()
}

export const getCommunityBallotPrice = async ({ payments }, democHash) => {
  return await payments.methods.getNextPrice(democHash).call()
}

export const checkIfAddressIsEditor = async (
  { svNetwork },
  { userAddress, democHash }
) => {
  const { backend } = svNetwork
  return await backend.methods.isDEditor(democHash, userAddress).call()
}

// Checks the current ethereum gas price and returns a couple of values
export const getCurrentGasPrice = async () => {
  const gasStationInfo = await axios.get(
    'https://ethgasstation.info/json/ethgasAPI.json'
  )
  const { data } = gasStationInfo

  return {
    safeLow: data.safeLow / 10,
    average: data.average / 10,
    fast: data.fast / 10,
    fastest: data.fastest / 10
  }
}
// Checks the ballot hash against the ballot content
export const checkBallotHashBSpec = (ballotSpec, assertSpecHash) => {
  let contentHash = '0x' + sha256(JSON.stringify(ballotSpec, null, 2))
  if (assertSpecHash === contentHash) {
    return true
  } else {
    return false
  }
}

// Checks the ballot hash against a ballot global ballot object
// Does this by destructuring the specHash and data out of it
export const checkBallotHashGBallot = ballotObject => {
  const { data, specHash } = ballotObject
  return checkBallotHashBSpec(data, specHash)
}