index.js

import { Insight } from '@oipwg/insight-explorer'

import URLExplorer from './URLExplorer.js'

const defaultCoins = {
  bitcoin: {
    usd: {
      url: 'https://api.coingecko.com/api/v3/simple/price?ids=bitcoin&vs_currencies=usd',
      transform: function (response) {
        if (response && response.bitcoin && response.bitcoin.usd) {
          return response.bitcoin.usd
        }
      }
    }
  },
  litecoin: {
    usd: {
      url: 'https://api.coingecko.com/api/v3/simple/price?ids=litecoin&vs_currencies=usd',
      transform: function (response) {
        if (response && response.litecoin && response.litecoin.usd) {
          return response.litecoin.usd
        }
      }
    }
  },
  flo: {
    usd: {
      url: 'https://api.coingecko.com/api/v3/simple/price?ids=flo&vs_currencies=usd',
      transform: function (response) {
        if (response && response.flo && response.flo.usd) {
          return response.flo.usd
        }
      }
    }
  },
  raven: {
    usd: {
      url: 'https://api.coingecko.com/api/v3/simple/price?ids=ravencoin&vs_currencies=usd',
      transform: function (response) {
        if (response && response.ravencoin && response.ravencoin.usd) {
          return response.ravencoin.usd
        }
      }
    }
  }
}

/**
 * @typedef {object} CoinObject
 * @property {string} CoinName - The string name for the coin
 * @property {FiatObject} CoinName.Fiat - Information about the Fiat to convert for the specific coin
 * @example
 * {
 *   'bitcoin': {
 *     'usd': {
 *       'url': 'https://api.coingecko.com/api/v3/simple/price?ids=bitcoin&vs_currencies=usd',
 *       'transform': function (response) {
 *         if (response && response.bitcoin && response.bitcoin.usd) {
 *           return response.bitcoin.usd
 *         }
 *       }
 *     }
 *   }
 * }
 */

/**
 * @typedef {object} FiatObject
 * @property {string} FiatType - The Fiat Type you are defining (i.e. "usd")
 * @property {string} [FiatType.url] - The URL to scrape for the Coin to Fiat conversion
 * @property {string} [FiatType.insight] - The Insight API server to scrape for the Coin to Fiat conversion
 * @property {function} [FiatType.transform] - A function that transforms the data response from the URL/Insight server to return the float conversion value
 * @example
 * {
 *   'usd': {
 *     'url': 'https://api.coingecko.com/api/v3/simple/price?ids=bitcoin&vs_currencies=usd',
 *     'transform': function (response) {
 *       if (response && response.bitcoin && response.bitcoin.usd) {
 *         return response.bitcoin.usd
 *       }
 *     }
 *   }
 * }
 */

const DEFAULT_TRANSFORM_FUNCTION = function (response) { return response }

const aliases = {
  bitcoin: 'btc',
  litecoin: 'ltc',
  raven: 'rvn'
}

/** Calculate Exchange rates for Cryptocurrencies */
class Exchange {
  /**
   * Create a new Exchange Rate checker
   * ##### Example
   * ```
   * import Exchange from 'oip-exchange-rate';
   *
   * let exchange = new Exchange();
   * ```
   * @param  {CoinObject} [coins] - Object containing CoinsObjects
   */
  constructor (coins) {
    if (coins && typeof coins === 'object') {
      // If we are defined, attempt to merge
      this.coins = Object.assign({}, defaultCoins, coins)
    } else {
      this.coins = defaultCoins
    }

    this.explorers = {}
  }

  /**
   * @param  {string} coin - The string coin name (ex. "bitcoin")
   * @param  {string} fiat - The string fiat (ex. "usd")
   * @return {Promise<number>} Returns a Promise that will resolve to the number value of the exchange rate
   * @example <caption>Get the Exchange rate for USD per Bitcoin</caption>
   * import Exchange from 'oip-exchange-rate';
   *
   * let exchange = new Exchange();
   *
   * exchange.getExchangeRate("bitcoin", "usd").then((rate) => {
   *   console.log(rate);
   * })
   */
  async getExchangeRate (coin, fiat) {
    let matchedTransform = DEFAULT_TRANSFORM_FUNCTION

    if (coin === fiat) {
      return 1
    }

    for (const c in this.coins) {
      if (!Object.prototype.hasOwnProperty.call(this.coins, c)) continue
      if (c === coin || coin === aliases[c]) {
        for (const fiatType in this.coins[c]) {
          if (!Object.prototype.hasOwnProperty.call(this.coins[c], fiatType)) continue
          if (fiatType === fiat) {
            if (!this.explorers[c]) { this.explorers[c] = {} }

            if (!this.explorers[c][fiatType]) {
              if (this.coins[c][fiatType].insight) {
                this.explorers[c][fiatType] = new Insight(this.coins[c][fiatType].insight)
              } else if (this.coins[c][fiatType].url) {
                this.explorers[c][fiatType] = new URLExplorer(this.coins[c][fiatType].url, this.coins[c][fiatType].transform)
              }
            }

            if (this.explorers[c][fiatType]) {
              matchedTransform = this.coins[c][fiatType].transform

              const info = await this.explorers[c][fiatType].getExchangeRate()
              let rate = info

              if (this.coins[c][fiatType].transform) {
                rate = matchedTransform(info)
              }

              return rate
            }
          }
        }
      }
    }
  }
}

module.exports = Exchange