ecoweb3.js

const _ = require('lodash');

const { initProvider, initApiProvider } = require('./providers');
const Contract = require('./contract');
const HttpProvider = require('./providers/http-provider');
const ApiProvider = require('./providers/api-provider');
const Encoder = require('./formatters/encoder');
const Decoder = require('./formatters/decoder');
const ErcToken = require('./erctoken/erc-token');
const Utils = require('./utils');
const ecocjs = require('./ecocjs');

class Ecoweb3 {
  /**
   * Ecoweb3 constructor.
   * @param {string|Ecoweb3Provider} provider Either URL string to create HttpProvider or a Ecoweb3 compatible provider.
   */
  constructor(provider, network = 'Mainnet') {
    this.provider = initProvider(provider);
    this.encoder = Encoder;
    this.decoder = Decoder;
    this.utils = Utils;
    this.Network = network;
    this.ECPair = ecocjs.ECPair;
    this.txs = ecocjs.txs;
    this.script = ecocjs.script;
  }

  /**
   * Constructs a new Contract instance.
   * @param {string} address Address of the contract.
   * @param {array} abi ABI of the contract.
   * @return {Contract} Contract instance.
   */
  Contract(address, abi) {
    return new Contract(this.provider, address, abi);
  }

  TxBuilder() {
    return new ecocjs.TransactionBuilder(this.getNetwork());
  }

  TxFromHex(rawtx) {
    return ecocjs.Transaction.fromHex(rawtx);
  }

  TxFromBuffer(rawtx) {
    return ecocjs.Transaction.fromBuffer(rawtx);
  }

  /**
   * Constructs a new HttpProvider instance.
   * @param {string} urlString URL of the blockchain API. eg. http://username:password@the.no.de.ip:port
   * @return {HttpProvider} HttpProvider instance.
   */
  HttpProvider(urlString) {
    return new HttpProvider(urlString);
  }

  /** ******** MISC ********* */
  /**
   * Checks if the blockchain is connected.
   * @return If blockchain is connected.
   */
  async isConnected() {
    try {
      const res = await this.provider.rawCall('getnetworkinfo');
      return typeof res === 'object';
    } catch (err) {
      return false;
    }
  }

  /** ******** BLOCKCHAIN ********* */
  /**
   * Returns the block info for a given block hash.
   * @param {string} blockHash The block hash to look up.
   * @param {boolean} verbose True for a json object or false for the hex encoded data.
   * @return {Promise} Latest block info or Error.
   */
  getNetwork() {
    return (this.Network === 'Mainnet') ? ecocjs.networks.ecoc : ecocjs.networks.ecoc_testnet;
  }
  getBlock(blockHash, verbose = true) {
    return this.provider.rawCall('getblock', [blockHash, verbose]);
  }

  /**
   * Returns various state info regarding blockchain processing.
   * @return {Promise} Latest block info or Error.
   */
  getBlockchainInfo() {
    return this.provider.rawCall('getblockchaininfo');
  }

  /**
   * Returns the current block height that is synced.
   * @return {Promise} Current block count or Error.
   */
  getBlockCount() {
    return this.provider.rawCall('getblockcount');
  }

  /**
   * Returns the block hash of the block height number specified.
   * @param {number} blockNum The block number to look up.
   * @return {Promise} Block hash or Error.
   */
  getBlockHash(blockNum) {
    return this.provider.rawCall('getblockhash', [blockNum]);
  }

  /**
   * Returns the transaction receipt given the txid.
   * @param {string} txid The transaction id to look up.
   * @return {Promise} Transaction receipt or Error.
   */
  getTransactionReceipt(txid) {
    return this.provider.rawCall('gettransactionreceipt', [txid]);
  }

  /**
   * Returns an array of deployed contract addresses.
   * @param {number} startingAcctIndex The starting account index.
   * @param {number} maxDisplay Max accounts to list.
   * @return {Promise} Array of contract addresses or Error.
   */
  listContracts(startingAcctIndex = 1, maxDisplay = 20) {
    return this.provider.rawCall('listcontracts', [startingAcctIndex, maxDisplay]);
  }

  /**
   * Search logs with given filters
   * @param  {number} fromBlock Starting block to search.
   * @param  {number} toBlock Ending block to search. Use -1 for latest.
   * @param  {string | array} addresses One or more addresses to search against
   * @param  {string | array} topics One or more topic hashes to search against
   * @param  {object} contractMetadata Metadata of all contracts and their events with topic hashes
   * @param  {bool} removeHexPrefix Flag to indicate whether to remove the hex prefix (0x) from hex values
   * @return {Promise} Promise containing returned logs or Error
   */
  searchLogs(fromBlock, toBlock, addresses, topics, contractMetadata, removeHexPrefix) {
    if (!_.isNumber(fromBlock)) {
      throw new Error(`fromBlock expects a number. Got ${fromBlock} instead.`);
    }
    if (!_.isNumber(toBlock)) {
      throw new Error(`toBlock expects a number. Got ${toBlock} instead.`);
    }

    const addrObj = { addresses: undefined };
    if (_.isString(addresses)) {
      addrObj.addresses = [addresses];
    } else if (_.isArray(addresses)) {
      addrObj.addresses = addresses;
    } else {
      throw new Error('addresses expects a string or an array.');
    }

    const topicsObj = { topics: undefined };
    if (_.isString(topics)) {
      topicsObj.topics = [topics];
    } else if (_.isArray(topics)) {
      topicsObj.topics = topics;
    } else {
      throw new Error('topics expects a string or an array.');
    }

    return this.provider.rawCall('searchlogs', [fromBlock, toBlock, addrObj, topicsObj])
      .then(results => Decoder.decodeSearchLog(results, contractMetadata, removeHexPrefix));
  }

  /** ******** CONTROL ********* */
  /**
   * Get the blockchain info.
   * @return {Promise} Blockchain info object or Error
   */
  getInfo() {
    return this.provider.rawCall('getinfo');
  }

  /** ******** NETWORK ********* */
  /**
   * Returns data about each connected network node as a json array of objects.
   * @return {Promise} Node info object or Error
   */
  getPeerInfo() {
    return this.provider.rawCall('getpeerinfo');
  }

  /** ******** RAW TRANSACTIONS ********* */
  /**
   * Get the hex address of an ecochain address.
   * @param {string} address ecochain address
   * @return {Promise} Hex string of the converted address or Error
   */
  getHexAddress(address) {
    return this.provider.rawCall('gethexaddress', [address]);
  }

  /**
   * Converts a hex address to an ecochain address.
   * @param {string} hexAddress ecochain address in hex format.
   * @return {Promise} ecochain address or Error.
   */
  fromHexAddress(hexAddress) {
    return this.provider.rawCall('fromhexaddress', [hexAddress]);
  }

  /** ******** UTIL ********* */
  /**
   * Validates if a valid ecochain address.
   * @param {string} address ecochain address to validate.
   * @return {Promise} Object with validation info or Error.
   */
  validateAddress(address) {
    return this.provider.rawCall('validateaddress', [address]);
  }

  /** ******** WALLET ********* */
  /**
   * Backs up the wallet.
   * @param {string} destination The destination directory or file.
   * @return {Promise} Success or Error.
   */
  backupWallet(destination) {
    return this.provider.rawCall('backupwallet', [destination]);
  }

  /**
   * Reveals the private key corresponding to the address.
   * @param {string} address The ecochain address for the private key.
   * @return {Promise} Private key or Error.
   */
  dumpPrivateKey(address) {
    return this.provider.rawCall('dumpprivkey', [address]);
  }

  /**
   * Encrypts the wallet for the first time. This will shut down the ecochain server.
   * @param {string} passphrase The passphrase to encrypt the wallet with. Must be at least 1 character.
   * @return {Promise} Success or Error.
   */
  encryptWallet(passphrase) {
    return this.provider.rawCall('encryptwallet', [passphrase]);
  }

  /**
   * Gets the account name associated with the ecochain address.
   * @param {string} address The ecochain address for account lookup.
   * @return {Promise} Account name or Error.
   */
  getAccount(address) {
    return this.provider.rawCall('getaccount', [address]);
  }

  /**
   * Gets the ecochain address based on the account name.
   * @param {string} acctName The account name for the address ("" for default).
   * @return {Promise} ecochain address or Error.
   */
  getAccountAddress(acctName = '') {
    return this.provider.rawCall('getaccountaddress', [acctName]);
  }

  /**
   * Gets the ecochain address with the account name.
   * @param {string} acctName The account name ("" for default).
   * @return {Promise} ecochain address array or Error.
   */
  getAddressesByAccount(acctName = '') {
    return this.provider.rawCall('getaddressesbyaccount', [acctName]);
  }

  /**
   * Gets a new ecochain address for receiving payments.
   * @param {string} acctName The account name for the address to be linked to ("" for default).
   * @return {Promise} ecochain address or Error.
   */
  getNewAddress(acctName = '') {
    return this.provider.rawCall('getnewaddress', [acctName]);
  }

  /**
   * Get transaction details by txid
   * @param {string} txid The transaction id (64 char hex string).
   * @return {Promise} Promise containing result object or Error
   */
  getTransaction(txid) {
    return this.provider.rawCall('gettransaction', [txid]);
  }

  /**
   * Gets the wallet info
   * @return {Promise} Promise containing result object or Error
   */
  getWalletInfo() {
    return this.provider.rawCall('getwalletinfo');
  }

  /**
   * Gets the total unconfirmed balance.
   * @return {Promise} Unconfirmed balance or Error.
   */
  getUnconfirmedBalance() {
    return this.provider.rawCall('getunconfirmedbalance');
  }

  /**
   * Adds an address that is watch-only. Cannot be used to spend.
   * @param {string} address The hex-encoded script (or address).
   * @param {string} label An optional label.
   * @param {boolean} rescan Rescan the wallet for transactions.
   * @return {Promise} Success or Error.
   */
  importAddress(address, label = '', rescan = true) {
    return this.provider.rawCall('importaddress', [address, label, rescan]);
  }

  /**
   * Adds an address by private key.
   * @param {string} privateKey The private key.
   * @param {string} label An optional label.
   * @param {boolean} rescan Rescan the wallet for transactions.
   * @return {Promise} Success or Error.
   */
  importPrivateKey(privateKey, label = '', rescan = true) {
    return this.provider.rawCall('importprivkey', [privateKey, label, rescan]);
  }

  /**
   * Adds an watch-only address by public key. Cannot be used to spend.
   * @param {string} publicKey The public key.
   * @param {string} label An optional label.
   * @param {boolean} rescan Rescan the wallet for transactions.
   * @return {Promise} Success or Error.
   */
  importPublicKey(publicKey, label = '', rescan = true) {
    return this.provider.rawCall('importpubkey', [publicKey, label, rescan]);
  }

  /**
   * Imports keys from a wallet dump file
   * @param {string} filename The wallet file.
   * @return {Promise} Success or Error.
   */
  importWallet(filename) {
    return this.provider.rawCall('importwallet', [filename]);
  }

  /**
   * Lists groups of addresses which have had their common ownership made public by common use as inputs
   *  or as the resulting change in past transactions.
   * @return {Promise} Array of addresses with ECOC balances or Error.
   */
  listAddressGroupings() {
    return this.provider.rawCall('listaddressgroupings');
  }

  /**
   * Lists temporary unspendable outputs.
   * @return {Promise} Array of unspendable outputs or Error
   */
  listLockUnspent() {
    return this.provider.rawCall('listlockunspent');
  }

  /**
   * Lists unspent transaction outputs.
   * @return {Promise} Array of unspent transaction outputs or Error
   */
  listUnspent() {
    return this.provider.rawCall('listunspent');
  }

  /**
   * Lists unspent transaction outputs.
   * @param {string} address Address to send ECOC to.
   * @param {number} amount Amount of ECOC to send.
   * @param {string} comment Comment used to store what the transaction is for.
   * @param {string} commentTo Comment to store name/organization to which you're sending the transaction.
   * @param {boolean} subtractFeeFromAmount The fee will be deducted from the amount being sent.
   * @param {boolean} replaceable Allow this transaction to be replaced by a transaction with higher fees via BIP 125.
   * @param {number} confTarget Confirmation target (in blocks).
   * @param {string} estimateMode The fee estimate mode, must be one of: "UNSET", "ECONOMICAL", "CONSERVATIVE"
   * @param {string} senderAddress The ECOC address that will be used to send money from.
   * @param {boolean} changeToSender Return the change to the sender.
   * @return {Promise} Transaction ID or Error
   */
  sendToAddress(
    address,
    amount,
    comment = '',
    commentTo = '',
    subtractFeeFromAmount = false,
    replaceable = true,
    confTarget = 6,
    estimateMode = 'UNSET',
    senderAddress,
    changeToSender = false,
  ) {
    return this.provider.rawCall('sendtoaddress', [
      address,
      amount,
      comment,
      commentTo,
      subtractFeeFromAmount,
      replaceable,
      confTarget,
      estimateMode,
      senderAddress,
      changeToSender,
    ]);
  }

  /**
   * Set the transaction fee per kB. Overwrites the paytxfee parameter.
   * @param {bumber} amount The transaction fee in ECOC/kB.
   * @return {Promise} True/false for success or Error.
   */
  setTxFee(amount) {
    return this.provider.rawCall('settxfee', [amount]);
  }

  sendRawTx(hexString) {
    return this.provider.rawCall('sendrawtransaction', [hexString]);
  }

  /**
   * Locks the encrypted wallet.
   * @return {Promise} Success or Error.
   */
  walletLock() {
    return this.provider.rawCall('walletlock');
  }

  /**
   * Unlocks the encrypted wallet with the wallet passphrase.
   * @param {string} passphrase The wallet passphrase.
   * @param {number} timeout The number of seconds to keep the wallet unlocked.
   * @param {boolean} stakingOnly Unlock wallet for staking only.
   * @return {Promise} Success or Error.
   */
  walletPassphrase(passphrase, timeout, stakingOnly = false) {
    return this.provider.rawCall('walletpassphrase', [passphrase, timeout, stakingOnly]);
  }

  /**
   * Changes the encrypted wallets passphrase.
   * @param {string} oldPassphrase The old wallet passphrase.
   * @param {string} newPassphrase The new wallet passphrase.
   * @return {Promise} Success or Error.
   */
  walletPassphraseChange(oldPassphrase, newPassphrase) {
    return this.provider.rawCall('walletpassphrasechange', [oldPassphrase, newPassphrase]);
  }


  /**
   * ERC-20 Token Contract Implementation
   */
  ercToken(tokenAddress) {
    return new ErcToken(this.provider, tokenAddress);
  }

  /**
  * the API service's method needed to configure api
  */

  // Configure the Api service provider
  ApiProvider(urlString, apiPrefix) {
    return new ApiProvider(urlString, apiPrefix);
  }

  ApiConfig(provider, apiPrefix) {
    this.apiProvider = initApiProvider(provider, apiPrefix);
  }

  async isApiConnected() {
    try {
      const res = await this.apiProvider.Get('/sync');
      return typeof res === 'object';
    } catch (err) {
      return false;
    }
  }

  getUtxoList(address) {
    return this.apiProvider.Get(`/addrs/${address}/utxo`);
  }
}

module.exports = Ecoweb3;