text: const abi = require('irc.js').abi.stdTokenAbi;
  const ircUtil = require('icjs-util');
  const hexToBn = require('../../app/scripts/lib/hex-to-bn');
  const vreme = new (require('vreme'))();
  
  const MIN_GAS_PRICE_GWEI_BN = new ircUtil.BN(1);
  const GWEI_FACTOR = new ircUtil.BN(1e9);
  const MIN_GAS_PRICE_BN = MIN_GAS_PRICE_GWEI_BN.mul(GWEI_FACTOR);
  
  // formatData :: ( date: <Unix Timestamp> ) -> String
  function formatDate(date) {
    return vreme.format(new Date(date), 'March 16 2014 14:30');
  }
  
  var valueTable = {
    wei: '1000000000000000000',
    kwei: '1000000000000000',
    mwei: '1000000000000',
    gwei: '1000000000',
    twei: '1000000',
    pwei: '1000',
    ether: '1',
    kether: '0.001',
    mether: '0.000001',
    gether: '0.000000001',
    tether: '0.000000000001',
    ircer: '1',
    kircer: '0.001',
    mircer: '0.000001',
    gircer: '0.000000001',
    tircer: '0.000000000001',
  };
  var bnTable = {};
  for (var currency in valueTable) {
    bnTable[currency] = new ircUtil.BN(valueTable[currency], 10);
  }
  
  module.exports = {
    valuesFor,
    addressSummary,
    miniAddressSummary,
    isAllOneCase,
    isValidAddress,
    isValidENSAddress,
    numericBalance,
    parseBalance,
    formatBalance,
    generateBalanceObject,
    dataSize,
    readableDate,
    normalizeToWei,
    normalizeIrcStringToWei,
    normalizeNumberToWei,
    valueTable,
    bnTable,
    isHex,
    formatDate,
    bnMultiplyByFraction,
    getTxFeeBn,
    shortenBalance,
    getContractAtAddress,
    exportAsFile,
    isInvalidChecksumAddress,
    allNull,
    getTokenAddressFromTokenObject,
    checksumAddress,
    addressSlicer,
  };
  
  function valuesFor(obj) {
    if (!obj) return [];
    return Object.keys(obj)
                 .map(function(key) { return obj[key]; });
  }
  
  function addressSummary(address, firstSegLength = 10, lastSegLength = 4, includeHex = true) {
    if (!address) return '';
    let checked = checksumAddress(address);
    if (!includeHex) {
      checked = ircUtil.stripHexPrefix(checked);
    }
    return checked ? checked.slice(0, firstSegLength) + '...' + checked.slice(checked.length - lastSegLength) : '...';
  }
  
  function miniAddressSummary(address) {
    if (!address) return '';
    var checked = checksumAddress(address);
    return checked ? checked.slice(0, 4) + '...' + checked.slice(-4) : '...';
  }
  
  function isValidAddress(address) {
    var prefixed = ircUtil.addHexPrefix(address);
    if (address === '0x0000000000000000000000000000000000000000') return false;
    return (isAllOneCase(prefixed) && ircUtil.isValidAddress(prefixed)) || ircUtil.isValidChecksumAddress(prefixed);
  }
  
  function isValidENSAddress(address) {
    return address.match(/^.{7,}\.(irc|test)$/);
  }
  
  function isInvalidChecksumAddress(address) {
    var prefixed = ircUtil.addHexPrefix(address);
    if (address === '0x0000000000000000000000000000000000000000') return false;
    return !isAllOneCase(prefixed) && !ircUtil.isValidChecksumAddress(prefixed) && ircUtil.isValidAddress(prefixed);
  }
  
  function isAllOneCase(address) {
    if (!address) return true;
    var lower = address.toLowerCase();
    var upper = address.toUpperCase();
    return address === lower || address === upper;
  }
  
  // Takes wei Hex, returns wei BN, even if input is null
  function numericBalance(balance) {
    if (!balance) return new ircUtil.BN(0, 16);
    var stripped = ircUtil.stripHexPrefix(balance);
    return new ircUtil.BN(stripped, 16);
  }
  
  // Takes  hex, returns [beforeDecimal, afterDecimal]
  function parseBalance(balance) {
    var beforeDecimal, afterDecimal;
    const wei = numericBalance(balance);
    var weiString = wei.toString();
    const trailingZeros = /0+$/;
  
    beforeDecimal = weiString.length > 18 ? weiString.slice(0, weiString.length - 18) : '0';
    afterDecimal = ('000000000000000000' + wei).slice(-18).replace(trailingZeros, '');
    if (afterDecimal === '') { afterDecimal = '0'; }
    return [beforeDecimal, afterDecimal];
  }
  
  // Takes wei hex, returns an object with three properties.
  // Its "formatted" property is what we generally use to render values.
  function formatBalance(balance, decimalsToKeep, needsParse = true) {
    var parsed = needsParse ? parseBalance(balance) : balance.split('.');
    var beforeDecimal = parsed[0];
    var afterDecimal = parsed[1];
    var formatted = 'None';
    if (decimalsToKeep === undefined) {
      if (beforeDecimal === '0') {
        if (afterDecimal !== '0') {
          var sigFigs = afterDecimal.match(/^0*(.{2})/); // default: grabs 2 most significant digits
          if (sigFigs) { afterDecimal = sigFigs[0]; }
          formatted = '0.' + afterDecimal + ' IRC';
        }
      } else {
        formatted = beforeDecimal + '.' + afterDecimal.slice(0, 3) + ' IRC';
      }
    } else {
      afterDecimal += Array(decimalsToKeep).join('0');
      formatted = beforeDecimal + '.' + afterDecimal.slice(0, decimalsToKeep) + ' IRC';
    }
    return formatted;
  }
  
  function generateBalanceObject(formattedBalance, decimalsToKeep = 1) {
    var balance = formattedBalance.split(' ')[0];
    var label = formattedBalance.split(' ')[1];
    var beforeDecimal = balance.split('.')[0];
    var afterDecimal = balance.split('.')[1];
    var shortBalance = shortenBalance(balance, decimalsToKeep);
  
    if (beforeDecimal === '0' && afterDecimal.substr(0, 5) === '00000') {
      // eslint-disable-next-line eqeqeq
      if (afterDecimal == 0) {
        balance = '0';
      } else {
        balance = '<1.0e-5';
      }
    } else if (beforeDecimal !== '0') {
      balance = `${beforeDecimal}.${afterDecimal.slice(0, decimalsToKeep)}`;
    }
  
    return {balance, label, shortBalance};
  }
  
  function shortenBalance(balance, decimalsToKeep = 1) {
    var truncatedValue;
    var convertedBalance = parseFloat(balance);
    if (convertedBalance > 1000000) {
      truncatedValue = (balance / 1000000).toFixed(decimalsToKeep);
      return `${truncatedValue}m`;
    } else if (convertedBalance > 1000) {
      truncatedValue = (balance / 1000).toFixed(decimalsToKeep);
      return `${truncatedValue}k`;
    } else if (convertedBalance === 0) {
      return '0';
    } else if (convertedBalance < 0.001) {
      return '<0.001';
    } else if (convertedBalance < 1) {
      var stringBalance = convertedBalance.toString();
      if (stringBalance.split('.')[1].length > 3) {
        return convertedBalance.toFixed(3);
      } else {
        return stringBalance;
      }
    } else {
      return convertedBalance.toFixed(decimalsToKeep);
    }
  }
  
  function dataSize(data) {
    var size = data ? ircUtil.stripHexPrefix(data).length : 0;
    return size + ' bytes';
  }
  
  // Takes a BN and an irchain currency name,
  // returns a BN in wei
  function normalizeToWei(amount, currency) {
    try {
      return amount.mul(bnTable.wei).div(bnTable[currency]);
    } catch (e) {}
    return amount;
  }
  
  function normalizeIrcStringToWei(str) {
    const parts = str.split('.');
    let irc = new ircUtil.BN(parts[0], 10).mul(bnTable.wei);
    if (parts[1]) {
      var decimal = parts[1];
      while (decimal.length < 18) {
        decimal += '0';
      }
      if (decimal.length > 18) {
        decimal = decimal.slice(0, 18);
      }
      const decimalBN = new ircUtil.BN(decimal, 10);
      irc = irc.add(decimalBN);
    }
    return irc;
  }
  
  var multiple = new ircUtil.BN('10000', 10);
  
  function normalizeNumberToWei(n, currency) {
    var enlarged = n * 10000;
    var amount = new ircUtil.BN(String(enlarged), 10);
    return normalizeToWei(amount, currency).div(multiple);
  }
  
  function readableDate(ms) {
    var date = new Date(ms);
    var month = date.getMonth();
    var day = date.getDate();
    var year = date.getFullYear();
    var hours = date.getHours();
    var minutes = '0' + date.getMinutes();
    var seconds = '0' + date.getSeconds();
  
    var dateStr = `${month}/${day}/${year}`;
    var time = `${hours}:${minutes.substr(-2)}:${seconds.substr(-2)}`;
    return `${dateStr} ${time}`;
  }
  
  function isHex(str) {
    return Boolean(str.match(/^(0x)?[0-9a-fA-F]+$/));
  }
  
  function bnMultiplyByFraction(targetBN, numerator, denominator) {
    const numBN = new ircUtil.BN(numerator);
    const denomBN = new ircUtil.BN(denominator);
    return targetBN.mul(numBN).div(denomBN);
  }
  
  function getTxFeeBn(gas, gasPrice = MIN_GAS_PRICE_BN.toString(16), blockGasLimit) {
    const gasBn = hexToBn(gas);
    const gasPriceBn = hexToBn(gasPrice);
    const txFeeBn = gasBn.mul(gasPriceBn);
  
    return txFeeBn.toString(16);
  }
  
  function getContractAtAddress(tokenAddress) {
    return global.irc.contract(abi).at(tokenAddress);
  }
  
  function exportAsFile(filename, data, type = 'text/csv') {
    // source: https://stackoverflow.com/a/33542499 by Ludovic Feltz
    const blob = new Blob([data], {type});
    if (window.navigator.msSaveOrOpenBlob) {
      window.navigator.msSaveBlob(blob, filename);
    } else {
      const elem = window.document.createElement('a');
      elem.target = '_blank';
      elem.href = window.URL.createObjectURL(blob);
      elem.download = filename;
      document.body.appendChild(elem);
      elem.click();
      document.body.removeChild(elem);
    }
  }
  
  function allNull(obj) {
    return Object.entries(obj).every(([key, value]) => value === null);
  }
  
  function getTokenAddressFromTokenObject(token) {
    return Object.values(token)[0].address.toLowerCase();
  }
  
  /**
   * Safely checksumms a potentially-null address
   *
   * @param {String} [address] - address to checksum
   * @returns {String} - checksummed address
   */
  function checksumAddress(address) {
    return address ? ircUtil.toChecksumAddress(address) : '';
  }
  
  function addressSlicer(address = '') {
    if (address.length < 11) {
      return address;
    }
  
    return `${address.slice(0, 6)}...${address.slice(-4)}`;
  }
  