# Source: bchaddr.js

    /***
     * @license
     * https://github.com/ealmansi/bchaddrjs
     * Copyright (c) 2018-2020 Emilio Almansi
     * Distributed under the MIT software license, see the accompanying
     * file LICENSE or http://www.opensource.org/licenses/mit-license.php.
     */

    var bs58check = require('bs58check')
    var cashaddr = require('cashaddrjs')
    var Buffer = require('buffer/').Buffer

    /**
     * General purpose Bitcoin Cash address detection and translation.<br />
     * Supports all major Bitcoin Cash address formats.<br />
     * Currently:
     * <ul>
     *    <li> Legacy format </li>
     *    <li> Bitpay format </li>
     *    <li> Cashaddr format </li>
     * </ul>
     * @module bchaddr
     */

    /**
     * @static
     * Supported Bitcoin Cash address formats.
     */
    var Format = {}
    Format.Legacy = 'legacy'
    Format.Bitpay = 'bitpay'
    Format.Cashaddr = 'cashaddr'

    /**
     * @static
     * Supported networks.
     */
    var Network = {}
    Network.Mainnet = 'mainnet'
    Network.Testnet = 'testnet'

    /**
     * @static
     * Supported address types.
     */
    var Type = {}
    Type.P2PKH = 'p2pkh'
    Type.P2SH = 'p2sh'

    /**
     * Returns a boolean indicating whether the given input is a valid Bitcoin Cash address.
     * @static
     * @param {*} input - Any input to check for validity.
     * @returns {boolean}
     */
    function isValidAddress (input) {
      try {
        decodeAddress(input)
        return true
      } catch (error) {
        return false
      }
    }

    /**
     * Detects what is the given address' format.
     * @static
     * @param {string} address - A valid Bitcoin Cash address in any format.
     * @return {string}
     * @throws {InvalidAddressError}
     */
    function detectAddressFormat (address) {
      return decodeAddress(address).format
    }

    /**
     * Detects what is the given address' network.
     * @static
     * @param {string} address - A valid Bitcoin Cash address in any format.
     * @return {string}
     * @throws {InvalidAddressError}
     */
    function detectAddressNetwork (address) {
      return decodeAddress(address).network
    }

    /**
     * Detects what is the given address' type.
     * @static
     * @param {string} address - A valid Bitcoin Cash address in any format.
     * @return {string}
     * @throws {InvalidAddressError}
     */
    function detectAddressType (address) {
      return decodeAddress(address).type
    }

    /**
     * Translates the given address into legacy format.
     * @static
     * @param {string} address - A valid Bitcoin Cash address in any format.
     * @return {string}
     * @throws {InvalidAddressError}
     */
    function toLegacyAddress (address) {
      var decoded = decodeAddress(address)
      if (decoded.format === Format.Legacy) {
        return address
      }
      return encodeAsLegacy(decoded)
    }

    /**
     * Translates the given address into bitpay format.
     * @static
     * @param {string} address - A valid Bitcoin Cash address in any format.
     * @return {string}
     * @throws {InvalidAddressError}
     */
    function toBitpayAddress (address) {
      var decoded = decodeAddress(address)
      if (decoded.format === Format.Bitpay) {
        return address
      }
      return encodeAsBitpay(decoded)
    }

    /**
     * Translates the given address into cashaddr format.
     * @static
     * @param {string} address - A valid Bitcoin Cash address in any format.
     * @return {string}
     * @throws {InvalidAddressError}
     */
    function toCashAddress (address) {
      var decoded = decodeAddress(address)
      return encodeAsCashaddr(decoded)
    }

    /**
     * Version byte table for base58 formats.
     * @private
     */
    var VERSION_BYTE = {}
    VERSION_BYTE[Format.Legacy] = {}
    VERSION_BYTE[Format.Legacy][Network.Mainnet] = {}
    VERSION_BYTE[Format.Legacy][Network.Mainnet][Type.P2PKH] = 0
    VERSION_BYTE[Format.Legacy][Network.Mainnet][Type.P2SH] = 5
    VERSION_BYTE[Format.Legacy][Network.Testnet] = {}
    VERSION_BYTE[Format.Legacy][Network.Testnet][Type.P2PKH] = 111
    VERSION_BYTE[Format.Legacy][Network.Testnet][Type.P2SH] = 196
    VERSION_BYTE[Format.Bitpay] = {}
    VERSION_BYTE[Format.Bitpay][Network.Mainnet] = {}
    VERSION_BYTE[Format.Bitpay][Network.Mainnet][Type.P2PKH] = 28
    VERSION_BYTE[Format.Bitpay][Network.Mainnet][Type.P2SH] = 40
    VERSION_BYTE[Format.Bitpay][Network.Testnet] = {}
    VERSION_BYTE[Format.Bitpay][Network.Testnet][Type.P2PKH] = 111
    VERSION_BYTE[Format.Bitpay][Network.Testnet][Type.P2SH] = 196

    /**
     * Decodes the given address into its constituting hash, format, network and type.
     * @private
     * @param {string} address - A valid Bitcoin Cash address in any format.
     * @return {object}
     * @throws {InvalidAddressError}
     */
    function decodeAddress (address) {
      try {
        return decodeBase58Address(address)
      } catch (error) {
      }
      try {
        return decodeCashAddress(address)
      } catch (error) {
      }
      throw new InvalidAddressError()
    }

    /**
     * Length of a valid base58check encoding payload: 1 byte for
     * the version byte plus 20 bytes for a RIPEMD-160 hash.
     * @private
     */
    var BASE_58_CHECK_PAYLOAD_LENGTH = 21

    /**
     * Attempts to decode the given address assuming it is a base58 address.
     * @private
     * @param {string} address - A valid Bitcoin Cash address in any format.
     * @return {object}
     * @throws {InvalidAddressError}
     */
    function decodeBase58Address (address) {
      try {
        var payload = bs58check.decode(address)
        if (payload.length !== BASE_58_CHECK_PAYLOAD_LENGTH) {
          throw new InvalidAddressError()
        }
        var versionByte = payload[0]
        var hash = Array.prototype.slice.call(payload, 1)
        switch (versionByte) {
          case VERSION_BYTE[Format.Legacy][Network.Mainnet][Type.P2PKH]:
            return {
              hash: hash,
              format: Format.Legacy,
              network: Network.Mainnet,
              type: Type.P2PKH
            }
          case VERSION_BYTE[Format.Legacy][Network.Mainnet][Type.P2SH]:
            return {
              hash: hash,
              format: Format.Legacy,
              network: Network.Mainnet,
              type: Type.P2SH
            }
          case VERSION_BYTE[Format.Legacy][Network.Testnet][Type.P2PKH]:
            return {
              hash: hash,
              format: Format.Legacy,
              network: Network.Testnet,
              type: Type.P2PKH
            }
          case VERSION_BYTE[Format.Legacy][Network.Testnet][Type.P2SH]:
            return {
              hash: hash,
              format: Format.Legacy,
              network: Network.Testnet,
              type: Type.P2SH
            }
          case VERSION_BYTE[Format.Bitpay][Network.Mainnet][Type.P2PKH]:
            return {
              hash: hash,
              format: Format.Bitpay,
              network: Network.Mainnet,
              type: Type.P2PKH
            }
          case VERSION_BYTE[Format.Bitpay][Network.Mainnet][Type.P2SH]:
            return {
              hash: hash,
              format: Format.Bitpay,
              network: Network.Mainnet,
              type: Type.P2SH
            }
        }
      } catch (error) {
      }
      throw new InvalidAddressError()
    }

    /**
     * Attempts to decode the given address assuming it is a cashaddr address.
     * @private
     * @param {string} address - A valid Bitcoin Cash address in any format.
     * @return {object}
     * @throws {InvalidAddressError}
     */
    function decodeCashAddress (address) {
      if (address.indexOf(':') !== -1) {
        try {
          return decodeCashAddressWithPrefix(address)
        } catch (error) {
        }
      } else {
        var prefixes = ['bitcoincash', 'bchtest', 'bchreg']
        for (var i = 0; i < prefixes.length; ++i) {
          try {
            var prefix = prefixes[i]
            return decodeCashAddressWithPrefix(prefix + ':' + address)
          } catch (error) {
          }
        }
      }
      throw new InvalidAddressError()
    }

    /**
     * Attempts to decode the given address assuming it is a cashaddr address with explicit prefix.
     * @private
     * @param {string} address - A valid Bitcoin Cash address in any format.
     * @return {object}
     * @throws {InvalidAddressError}
     */
    function decodeCashAddressWithPrefix (address) {
      try {
        var decoded = cashaddr.decode(address)
        var hash = Array.prototype.slice.call(decoded.hash, 0)
        var type = decoded.type === 'P2PKH' ? Type.P2PKH : Type.P2SH
        switch (decoded.prefix) {
          case 'bitcoincash':
            return {
              hash: hash,
              format: Format.Cashaddr,
              network: Network.Mainnet,
              type: type
            }
          case 'bchtest':
          case 'bchreg':
            return {
              hash: hash,
              format: Format.Cashaddr,
              network: Network.Testnet,
              type: type
            }
        }
      } catch (error) {
      }
      throw new InvalidAddressError()
    }

    /**
     * Encodes the given decoded address into legacy format.
     * @private
     * @param {object} decoded
     * @returns {string}
     */
    function encodeAsLegacy (decoded) {
      var versionByte = VERSION_BYTE[Format.Legacy][decoded.network][decoded.type]
      var buffer = Buffer.alloc(1 + decoded.hash.length)
      buffer[0] = versionByte
      buffer.set(decoded.hash, 1)
      return bs58check.encode(buffer)
    }

    /**
     * Encodes the given decoded address into bitpay format.
     * @private
     * @param {object} decoded
     * @returns {string}
     */
    function encodeAsBitpay (decoded) {
      var versionByte = VERSION_BYTE[Format.Bitpay][decoded.network][decoded.type]
      var buffer = Buffer.alloc(1 + decoded.hash.length)
      buffer[0] = versionByte
      buffer.set(decoded.hash, 1)
      return bs58check.encode(buffer)
    }

    /**
     * Encodes the given decoded address into cashaddr format.
     * @private
     * @param {object} decoded
     * @returns {string}
     */
    function encodeAsCashaddr (decoded) {
      var prefix = decoded.network === Network.Mainnet ? 'bitcoincash' : 'bchtest'
      var type = decoded.type === Type.P2PKH ? 'P2PKH' : 'P2SH'
      var hash = new Uint8Array(decoded.hash)
      return cashaddr.encode(prefix, type, hash)
    }

    /**
     * Returns a boolean indicating whether the address is in legacy format.
     * @static
     * @param {string} address - A valid Bitcoin Cash address in any format.
     * @returns {boolean}
     * @throws {InvalidAddressError}
     */
    function isLegacyAddress (address) {
      return detectAddressFormat(address) === Format.Legacy
    }

    /**
     * Returns a boolean indicating whether the address is in bitpay format.
     * @static
     * @param {string} address - A valid Bitcoin Cash address in any format.
     * @returns {boolean}
     * @throws {InvalidAddressError}
     */
    function isBitpayAddress (address) {
      return detectAddressFormat(address) === Format.Bitpay
    }

    /**
     * Returns a boolean indicating whether the address is in cashaddr format.
     * @static
     * @param {string} address - A valid Bitcoin Cash address in any format.
     * @returns {boolean}
     * @throws {InvalidAddressError}
     */
    function isCashAddress (address) {
      return detectAddressFormat(address) === Format.Cashaddr
    }

    /**
     * Returns a boolean indicating whether the address is a mainnet address.
     * @static
     * @param {string} address - A valid Bitcoin Cash address in any format.
     * @returns {boolean}
     * @throws {InvalidAddressError}
     */
    function isMainnetAddress (address) {
      return detectAddressNetwork(address) === Network.Mainnet
    }

    /**
     * Returns a boolean indicating whether the address is a testnet address.
     * @static
     * @param {string} address - A valid Bitcoin Cash address in any format.
     * @returns {boolean}
     * @throws {InvalidAddressError}
     */
    function isTestnetAddress (address) {
      return detectAddressNetwork(address) === Network.Testnet
    }

    /**
     * Returns a boolean indicating whether the address is a p2pkh address.
     * @static
     * @param {string} address - A valid Bitcoin Cash address in any format.
     * @returns {boolean}
     * @throws {InvalidAddressError}
     */
    function isP2PKHAddress (address) {
      return detectAddressType(address) === Type.P2PKH
    }

    /**
     * Returns a boolean indicating whether the address is a p2sh address.
     * @static
     * @param {string} address - A valid Bitcoin Cash address in any format.
     * @returns {boolean}
     * @throws {InvalidAddressError}
     */
    function isP2SHAddress (address) {
      return detectAddressType(address) === Type.P2SH
    }

    /**
     * Error thrown when the address given as input is not a valid Bitcoin Cash address.
     * @constructor
     * InvalidAddressError
     */
    function InvalidAddressError () {
      var error = new Error()
      this.name = error.name = 'InvalidAddressError'
      this.message = error.message = 'Received an invalid Bitcoin Cash address as input.'
      this.stack = error.stack
    }

    InvalidAddressError.prototype = Object.create(Error.prototype)

    module.exports = {
      Format: Format,
      Network: Network,
      Type: Type,
      isValidAddress: isValidAddress,
      detectAddressFormat: detectAddressFormat,
      detectAddressNetwork: detectAddressNetwork,
      detectAddressType: detectAddressType,
      toLegacyAddress: toLegacyAddress,
      toBitpayAddress: toBitpayAddress,
      toCashAddress: toCashAddress,
      isLegacyAddress: isLegacyAddress,
      isBitpayAddress: isBitpayAddress,
      isCashAddress: isCashAddress,
      isMainnetAddress: isMainnetAddress,
      isTestnetAddress: isTestnetAddress,
      isP2PKHAddress: isP2PKHAddress,
      isP2SHAddress: isP2SHAddress,
      InvalidAddressError: InvalidAddressError
    }