'use strict';

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.network = exports.BlockchainInfoApi = exports.InsightClient = exports.BitcoindAPI = exports.LocalRegtest = exports.BlockstackNetwork = exports.BitcoinNetwork = undefined;

var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }();

var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();

var _bitcoinjsLib = require('bitcoinjs-lib');

var _bitcoinjsLib2 = _interopRequireDefault(_bitcoinjsLib);

var _formData = require('form-data');

var _formData2 = _interopRequireDefault(_formData);

var _errors = require('./errors');

var _logger = require('./logger');

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }

function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

var SATOSHIS_PER_BTC = 1e8;
var TX_BROADCAST_SERVICE_ZONE_FILE_ENDPOINT = 'zone-file';
var TX_BROADCAST_SERVICE_REGISTRATION_ENDPOINT = 'registration';
var TX_BROADCAST_SERVICE_TX_ENDPOINT = 'transaction';

var BitcoinNetwork = exports.BitcoinNetwork = function () {
  function BitcoinNetwork() {
    _classCallCheck(this, BitcoinNetwork);
  }

  _createClass(BitcoinNetwork, [{
    key: 'broadcastTransaction',
    value: function broadcastTransaction(transaction) {
      return Promise.reject(new Error('Not implemented, broadcastTransaction(' + transaction + ')'));
    }
  }, {
    key: 'getBlockHeight',
    value: function getBlockHeight() {
      return Promise.reject(new Error('Not implemented, getBlockHeight()'));
    }
  }, {
    key: 'getTransactionInfo',
    value: function getTransactionInfo(txid) {
      return Promise.reject(new Error('Not implemented, getTransactionInfo(' + txid + ')'));
    }
  }, {
    key: 'getNetworkedUTXOs',
    value: function getNetworkedUTXOs(address) {
      return Promise.reject(new Error('Not implemented, getNetworkedUTXOs(' + address + ')'));
    }
  }]);

  return BitcoinNetwork;
}();

var BlockstackNetwork = exports.BlockstackNetwork = function () {
  function BlockstackNetwork(apiUrl, broadcastServiceUrl, bitcoinAPI) {
    var network = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : _bitcoinjsLib2.default.networks.bitcoin;

    _classCallCheck(this, BlockstackNetwork);

    this.blockstackAPIUrl = apiUrl;
    this.broadcastServiceUrl = broadcastServiceUrl;
    this.layer1 = network;
    this.btc = bitcoinAPI;

    this.DUST_MINIMUM = 5500;
    this.includeUtxoMap = {};
    this.excludeUtxoSet = [];
  }

  _createClass(BlockstackNetwork, [{
    key: 'coerceAddress',
    value: function coerceAddress(address) {
      var _bitcoinjs$address$fr = _bitcoinjsLib2.default.address.fromBase58Check(address),
          hash = _bitcoinjs$address$fr.hash,
          version = _bitcoinjs$address$fr.version;

      var scriptHashes = [_bitcoinjsLib2.default.networks.bitcoin.scriptHash, _bitcoinjsLib2.default.networks.testnet.scriptHash];
      var coercedVersion = void 0;
      if (scriptHashes.indexOf(version) >= 0) {
        coercedVersion = this.layer1.scriptHash;
      } else {
        coercedVersion = this.layer1.pubKeyHash;
      }
      return _bitcoinjsLib2.default.address.toBase58Check(hash, coercedVersion);
    }
  }, {
    key: 'getDefaultBurnAddress',
    value: function getDefaultBurnAddress() {
      return this.coerceAddress('1111111111111111111114oLvT2');
    }
  }, {
    key: 'getNamePrice',
    value: function getNamePrice(fullyQualifiedName) {
      var _this = this;

      return fetch(this.blockstackAPIUrl + '/v1/prices/names/' + fullyQualifiedName).then(function (resp) {
        return resp.json();
      }).then(function (x) {
        return x.name_price.satoshis;
      }).then(function (satoshis) {
        if (satoshis) {
          if (satoshis < _this.DUST_MINIMUM) {
            return _this.DUST_MINIMUM;
          } else {
            return satoshis;
          }
        } else {
          throw new Error('Failed to parse price of name');
        }
      });
    }
  }, {
    key: 'getNamespacePrice',
    value: function getNamespacePrice(namespaceID) {
      var _this2 = this;

      return fetch(this.blockstackAPIUrl + '/v1/prices/namespaces/' + namespaceID).then(function (resp) {
        return resp.json();
      }).then(function (x) {
        return x.satoshis;
      }).then(function (satoshis) {
        if (satoshis) {
          if (satoshis < _this2.DUST_MINIMUM) {
            return _this2.DUST_MINIMUM;
          } else {
            return satoshis;
          }
        } else {
          throw new Error('Failed to parse price of namespace');
        }
      });
    }
  }, {
    key: 'getGracePeriod',
    value: function getGracePeriod() {
      return new Promise(function (resolve) {
        return resolve(5000);
      });
    }
  }, {
    key: 'getNamesOwned',
    value: function getNamesOwned(address) {
      var networkAddress = this.coerceAddress(address);
      return fetch(this.blockstackAPIUrl + '/v1/addresses/bitcoin/' + networkAddress).then(function (resp) {
        return resp.json();
      }).then(function (obj) {
        return obj.names;
      });
    }
  }, {
    key: 'getNamespaceBurnAddress',
    value: function getNamespaceBurnAddress(namespace) {
      var _this3 = this;

      return Promise.all([fetch(this.blockstackAPIUrl + '/v1/namespaces/' + namespace), this.getBlockHeight()]).then(function (_ref) {
        var _ref2 = _slicedToArray(_ref, 2),
            resp = _ref2[0],
            blockHeight = _ref2[1];

        if (resp.status === 404) {
          throw new Error('No such namespace \'' + namespace + '\'');
        } else {
          return Promise.all([resp.json(), blockHeight]);
        }
      }).then(function (_ref3) {
        var _ref4 = _slicedToArray(_ref3, 2),
            namespaceInfo = _ref4[0],
            blockHeight = _ref4[1];

        var address = '1111111111111111111114oLvT2'; // default burn address
        if (namespaceInfo.version === 2) {
          // pay-to-namespace-creator if this namespace is less than 1 year old
          if (namespaceInfo.reveal_block + 52595 >= blockHeight) {
            address = namespaceInfo.address;
          }
        }
        return address;
      }).then(function (address) {
        return _this3.coerceAddress(address);
      });
    }
  }, {
    key: 'getNameInfo',
    value: function getNameInfo(fullyQualifiedName) {
      var _this4 = this;

      console.log(this.blockstackAPIUrl);
      var nameLookupURL = this.blockstackAPIUrl + '/v1/names/' + fullyQualifiedName;
      return fetch(nameLookupURL).then(function (resp) {
        if (resp.status === 404) {
          throw new Error('Name not found');
        } else if (resp.status !== 200) {
          throw new Error('Bad response status: ' + resp.status);
        } else {
          return resp.json();
        }
      }).then(function (nameInfo) {
        console.log('nameInfo: ' + JSON.stringify(nameInfo));
        // the returned address _should_ be in the correct network ---
        //  blockstackd gets into trouble because it tries to coerce back to mainnet
        //  and the regtest transaction generation libraries want to use testnet addresses
        if (nameInfo.address) {
          return Object.assign({}, nameInfo, { address: _this4.coerceAddress(nameInfo.address) });
        } else {
          return nameInfo;
        }
      });
    }
  }, {
    key: 'getNamespaceInfo',
    value: function getNamespaceInfo(namespaceID) {
      var _this5 = this;

      return fetch(this.blockstackAPIUrl + '/v1/namespaces/' + namespaceID).then(function (resp) {
        if (resp.status === 404) {
          throw new Error('Namespace not found');
        } else if (resp.status !== 200) {
          throw new Error('Bad response status: ' + resp.status);
        } else {
          return resp.json();
        }
      }).then(function (namespaceInfo) {
        // the returned address _should_ be in the correct network ---
        //  blockstackd gets into trouble because it tries to coerce back to mainnet
        //  and the regtest transaction generation libraries want to use testnet addresses
        if (namespaceInfo.address && namespaceInfo.recipient_address) {
          return Object.assign({}, namespaceInfo, {
            address: _this5.coerceAddress(namespaceInfo.address),
            recipient_address: _this5.coerceAddress(namespaceInfo.recipient_address)
          });
        } else {
          return namespaceInfo;
        }
      });
    }
  }, {
    key: 'getZonefile',
    value: function getZonefile(zonefileHash) {
      return fetch(this.blockstackAPIUrl + '/v1/zonefiles/' + zonefileHash).then(function (resp) {
        if (resp.status === 200) {
          return resp.text().then(function (body) {
            return body;
          });
        } else {
          throw new Error('Bad response status: ' + resp.status);
        }
      });
    }

    /**
     * Performs a POST request to the given URL
     * @param  {String} endpoint  the name of
     * @param  {String} body [description]
     * @return {Promise<Object|Error>} Returns a `Promise` that resolves to the object requested.
     * In the event of an error, it rejects with:
     * * a `RemoteServiceError` if there is a problem
     * with the transaction broadcast service
     * * `MissingParameterError` if you call the function without a required
     * parameter
     *
     * @private
     */

  }, {
    key: 'broadcastServiceFetchHelper',
    value: function broadcastServiceFetchHelper(endpoint, body) {
      var requestHeaders = {
        Accept: 'application/json',
        'Content-Type': 'application/json'
      };

      var options = {
        method: 'POST',
        headers: requestHeaders,
        body: JSON.stringify(body)
      };

      var url = this.broadcastServiceUrl + '/v1/broadcast/' + endpoint;
      return fetch(url, options).then(function (response) {
        if (response.ok) {
          return response.json();
        } else {
          throw new _errors.RemoteServiceError(response);
        }
      });
    }

    /**
    * Broadcasts a signed bitcoin transaction to the network optionally waiting to broadcast the
    * transaction until a second transaction has a certain number of confirmations.
    *
    * @param  {string} transaction the hex-encoded transaction to broadcast
    * @param  {string} transactionToWatch the hex transaction id of the transaction to watch for
    * the specified number of confirmations before broadcasting the `transaction`
    * @param  {number} confirmations the number of confirmations `transactionToWatch` must have
    * before broadcasting `transaction`.
    * @return {Promise<Object|Error>} Returns a Promise that resolves to an object with a
    * `transaction_hash` key containing the transaction hash of the broadcasted transaction.
    *
    * In the event of an error, it rejects with:
    * * a `RemoteServiceError` if there is a problem
    *   with the transaction broadcast service
    * * `MissingParameterError` if you call the function without a required
    *   parameter
    * @private
    */

  }, {
    key: 'broadcastTransaction',
    value: function broadcastTransaction(transaction) {
      var transactionToWatch = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
      var confirmations = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 6;

      if (!transaction) {
        var error = new _errors.MissingParameterError('transaction');
        return Promise.reject(error);
      }

      if (!confirmations && confirmations !== 0) {
        var _error = new _errors.MissingParameterError('confirmations');
        return Promise.reject(_error);
      }

      if (transactionToWatch === null) {
        return this.btc.broadcastTransaction(transaction);
      } else {
        /*
         * POST /v1/broadcast/transaction
         * Request body:
         * JSON.stringify({
         *  transaction,
         *  transactionToWatch,
         *  confirmations
         * })
         */
        var endpoint = TX_BROADCAST_SERVICE_TX_ENDPOINT;

        var requestBody = {
          transaction: transaction,
          transactionToWatch: transactionToWatch,
          confirmations: confirmations
        };

        return this.broadcastServiceFetchHelper(endpoint, requestBody);
      }
    }

    /**
     * Broadcasts a zone file to the Atlas network via the transaction broadcast service.
     *
     * @param  {String} zoneFile the zone file to be broadcast to the Atlas network
     * @param  {String} transactionToWatch the hex transaction id of the transaction
     * to watch for confirmation before broadcasting the zone file to the Atlas network
     * @return {Promise<Object|Error>} Returns a Promise that resolves to an object with a
     * `transaction_hash` key containing the transaction hash of the broadcasted transaction.
     *
     * In the event of an error, it rejects with:
     * * a `RemoteServiceError` if there is a problem
     *   with the transaction broadcast service
     * * `MissingParameterError` if you call the function without a required
     *   parameter
     * @private
     */

  }, {
    key: 'broadcastZoneFile',
    value: function broadcastZoneFile(zoneFile) {
      var transactionToWatch = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;

      if (!zoneFile) {
        return Promise.reject(new _errors.MissingParameterError('zoneFile'));
      }

      // TODO: validate zonefile

      if (transactionToWatch) {
        // broadcast via transaction broadcast service

        /*
         * POST /v1/broadcast/zone-file
         * Request body:
         * JSON.stringify({
         *  zoneFile,
         *  transactionToWatch
         * })
         */

        var requestBody = {
          zoneFile: zoneFile,
          transactionToWatch: transactionToWatch
        };

        var endpoint = TX_BROADCAST_SERVICE_ZONE_FILE_ENDPOINT;

        return this.broadcastServiceFetchHelper(endpoint, requestBody);
      } else {
        // broadcast via core endpoint

        // zone file is two words but core's api treats it as one word 'zonefile'
        var _requestBody = { zonefile: zoneFile };

        return fetch(this.blockstackAPIUrl + '/v1/zonefile/', {
          method: 'POST',
          body: JSON.stringify(_requestBody),
          headers: {
            'Content-Type': 'application/json'
          }
        }).then(function (resp) {
          var json = resp.json();
          return json.then(function (respObj) {
            if (respObj.hasOwnProperty('error')) {
              throw new _errors.RemoteServiceError(resp);
            }
            return respObj.servers;
          });
        });
      }
    }

    /**
     * Sends the preorder and registration transactions and zone file
     * for a Blockstack name registration
     * along with the to the transaction broadcast service.
     *
     * The transaction broadcast:
     *
     * * immediately broadcasts the preorder transaction
     * * broadcasts the register transactions after the preorder transaction
     * has an appropriate number of confirmations
     * * broadcasts the zone file to the Atlas network after the register transaction
     * has an appropriate number of confirmations
     *
     * @param  {String} preorderTransaction the hex-encoded, signed preorder transaction generated
     * using the `makePreorder` function
     * @param  {String} registerTransaction the hex-encoded, signed register transaction generated
     * using the `makeRegister` function
     * @param  {String} zoneFile the zone file to be broadcast to the Atlas network
     * @return {Promise<Object|Error>} Returns a Promise that resolves to an object with a
     * `transaction_hash` key containing the transaction hash of the broadcasted transaction.
     *
     * In the event of an error, it rejects with:
     * * a `RemoteServiceError` if there is a problem
     *   with the transaction broadcast service
     * * `MissingParameterError` if you call the function without a required
     *   parameter
    <<<<<<< HEAD
     * @private
    =======
     *  @private
    >>>>>>> e63017c... Updates to the build
     */

  }, {
    key: 'broadcastNameRegistration',
    value: function broadcastNameRegistration(preorderTransaction, registerTransaction, zoneFile) {
      /*
         * POST /v1/broadcast/registration
         * Request body:
         * JSON.stringify({
         * preorderTransaction,
         * registerTransaction,
         * zoneFile
         * })
         */

      if (!preorderTransaction) {
        var error = new _errors.MissingParameterError('preorderTransaction');
        return Promise.reject(error);
      }

      if (!registerTransaction) {
        var _error2 = new _errors.MissingParameterError('registerTransaction');
        return Promise.reject(_error2);
      }

      if (!zoneFile) {
        var _error3 = new _errors.MissingParameterError('zoneFile');
        return Promise.reject(_error3);
      }

      var requestBody = {
        preorderTransaction: preorderTransaction,
        registerTransaction: registerTransaction,
        zoneFile: zoneFile
      };

      var endpoint = TX_BROADCAST_SERVICE_REGISTRATION_ENDPOINT;

      return this.broadcastServiceFetchHelper(endpoint, requestBody);
    }
  }, {
    key: 'getFeeRate',
    value: function getFeeRate() {
      return fetch('https://bitcoinfees.earn.com/api/v1/fees/recommended').then(function (resp) {
        return resp.json();
      }).then(function (rates) {
        return Math.floor(rates.fastestFee);
      });
    }
  }, {
    key: 'countDustOutputs',
    value: function countDustOutputs() {
      throw new Error('Not implemented.');
    }
  }, {
    key: 'getUTXOs',
    value: function getUTXOs(address) {
      var _this6 = this;

      return this.getNetworkedUTXOs(address).then(function (networkedUTXOs) {
        var returnSet = networkedUTXOs.concat();
        if (_this6.includeUtxoMap.hasOwnProperty(address)) {
          returnSet = networkedUTXOs.concat(_this6.includeUtxoMap[address]);
        }

        // aaron: I am *well* aware this is O(n)*O(m) runtime
        //    however, clients should clear the exclude set periodically
        var excludeSet = _this6.excludeUtxoSet;
        returnSet = returnSet.filter(function (utxo) {
          var inExcludeSet = excludeSet.reduce(function (inSet, utxoToCheck) {
            return inSet || utxoToCheck.tx_hash === utxo.tx_hash && utxoToCheck.tx_output_n === utxo.tx_output_n;
          }, false);
          return !inExcludeSet;
        });

        return returnSet;
      });
    }

    /**
     * This will modify the network's utxo set to include UTXOs
     *  from the given transaction and exclude UTXOs *spent* in
     *  that transaction
     * @param {String} txHex - the hex-encoded transaction to use
     * @return {void} no return value, this modifies the UTXO config state
     * @private
     */

  }, {
    key: 'modifyUTXOSetFrom',
    value: function modifyUTXOSetFrom(txHex) {
      var _this7 = this;

      var tx = _bitcoinjsLib2.default.Transaction.fromHex(txHex);

      var excludeSet = this.excludeUtxoSet.concat();

      tx.ins.forEach(function (utxoUsed) {
        var reverseHash = Buffer.from(utxoUsed.hash);
        reverseHash.reverse();
        excludeSet.push({
          tx_hash: reverseHash.toString('hex'),
          tx_output_n: utxoUsed.index
        });
      });

      this.excludeUtxoSet = excludeSet;

      var txHash = tx.getHash().reverse().toString('hex');
      tx.outs.forEach(function (utxoCreated, txOutputN) {
        var isNullData = function isNullData(script) {
          try {
            _bitcoinjsLib2.default.payments.embed({ output: script }, { validate: true });
            return true;
          } catch (_) {
            return false;
          }
        };
        if (isNullData(utxoCreated.script)) {
          return;
        }
        var address = _bitcoinjsLib2.default.address.fromOutputScript(utxoCreated.script, _this7.layer1);

        var includeSet = [];
        if (_this7.includeUtxoMap.hasOwnProperty(address)) {
          includeSet = includeSet.concat(_this7.includeUtxoMap[address]);
        }

        includeSet.push({
          tx_hash: txHash,
          confirmations: 0,
          value: utxoCreated.value,
          tx_output_n: txOutputN
        });
        _this7.includeUtxoMap[address] = includeSet;
      });
    }
  }, {
    key: 'resetUTXOs',
    value: function resetUTXOs(address) {
      delete this.includeUtxoMap[address];
      this.excludeUtxoSet = [];
    }
  }, {
    key: 'getConsensusHash',
    value: function getConsensusHash() {
      return fetch(this.blockstackAPIUrl + '/v1/blockchains/bitcoin/consensus').then(function (resp) {
        return resp.json();
      }).then(function (x) {
        return x.consensus_hash;
      });
    }
  }, {
    key: 'getTransactionInfo',
    value: function getTransactionInfo(txHash) {
      return this.btc.getTransactionInfo(txHash);
    }
  }, {
    key: 'getBlockHeight',
    value: function getBlockHeight() {
      return this.btc.getBlockHeight();
    }
  }, {
    key: 'getNetworkedUTXOs',
    value: function getNetworkedUTXOs(address) {
      return this.btc.getNetworkedUTXOs(address);
    }
  }]);

  return BlockstackNetwork;
}();

var LocalRegtest = exports.LocalRegtest = function (_BlockstackNetwork) {
  _inherits(LocalRegtest, _BlockstackNetwork);

  function LocalRegtest(apiUrl, broadcastServiceUrl, bitcoinAPI) {
    _classCallCheck(this, LocalRegtest);

    return _possibleConstructorReturn(this, (LocalRegtest.__proto__ || Object.getPrototypeOf(LocalRegtest)).call(this, apiUrl, broadcastServiceUrl, bitcoinAPI, _bitcoinjsLib2.default.networks.testnet));
  }

  _createClass(LocalRegtest, [{
    key: 'getFeeRate',
    value: function getFeeRate() {
      return Promise.resolve(Math.floor(0.00001000 * SATOSHIS_PER_BTC));
    }
  }]);

  return LocalRegtest;
}(BlockstackNetwork);

var BitcoindAPI = exports.BitcoindAPI = function (_BitcoinNetwork) {
  _inherits(BitcoindAPI, _BitcoinNetwork);

  function BitcoindAPI(bitcoindUrl, bitcoindCredentials) {
    _classCallCheck(this, BitcoindAPI);

    var _this9 = _possibleConstructorReturn(this, (BitcoindAPI.__proto__ || Object.getPrototypeOf(BitcoindAPI)).call(this));

    _this9.bitcoindUrl = bitcoindUrl;
    _this9.bitcoindCredentials = bitcoindCredentials;
    return _this9;
  }

  _createClass(BitcoindAPI, [{
    key: 'broadcastTransaction',
    value: function broadcastTransaction(transaction) {
      var jsonRPC = {
        jsonrpc: '1.0',
        method: 'sendrawtransaction',
        params: [transaction]
      };
      var authString = Buffer.from(this.bitcoindCredentials.username + ':' + this.bitcoindCredentials.password).toString('base64');
      var headers = { Authorization: 'Basic ' + authString };
      return fetch(this.bitcoindUrl, {
        method: 'POST',
        body: JSON.stringify(jsonRPC),
        headers: headers
      }).then(function (resp) {
        return resp.json();
      }).then(function (respObj) {
        return respObj.result;
      });
    }
  }, {
    key: 'getBlockHeight',
    value: function getBlockHeight() {
      var jsonRPC = {
        jsonrpc: '1.0',
        method: 'getblockcount'
      };
      var authString = Buffer.from(this.bitcoindCredentials.username + ':' + this.bitcoindCredentials.password).toString('base64');
      var headers = { Authorization: 'Basic ' + authString };
      return fetch(this.bitcoindUrl, {
        method: 'POST',
        body: JSON.stringify(jsonRPC),
        headers: headers
      }).then(function (resp) {
        return resp.json();
      }).then(function (respObj) {
        return respObj.result;
      });
    }
  }, {
    key: 'getTransactionInfo',
    value: function getTransactionInfo(txHash) {
      var _this10 = this;

      var jsonRPC = {
        jsonrpc: '1.0',
        method: 'gettransaction',
        params: [txHash]
      };
      var authString = Buffer.from(this.bitcoindCredentials.username + ':' + this.bitcoindCredentials.password).toString('base64');
      var headers = { Authorization: 'Basic ' + authString };
      return fetch(this.bitcoindUrl, {
        method: 'POST',
        body: JSON.stringify(jsonRPC),
        headers: headers
      }).then(function (resp) {
        return resp.json();
      }).then(function (respObj) {
        return respObj.result;
      }).then(function (txInfo) {
        return txInfo.blockhash;
      }).then(function (blockhash) {
        var jsonRPCBlock = {
          jsonrpc: '1.0',
          method: 'getblockheader',
          params: [blockhash]
        };
        headers.Authorization = 'Basic ' + authString;
        return fetch(_this10.bitcoindUrl, {
          method: 'POST',
          body: JSON.stringify(jsonRPCBlock),
          headers: headers
        });
      }).then(function (resp) {
        return resp.json();
      }).then(function (respObj) {
        return { block_height: respObj.result.height };
      });
    }
  }, {
    key: 'getNetworkedUTXOs',
    value: function getNetworkedUTXOs(address) {
      var _this11 = this;

      var jsonRPCImport = {
        jsonrpc: '1.0',
        method: 'importaddress',
        params: [address]
      };
      var jsonRPCUnspent = {
        jsonrpc: '1.0',
        method: 'listunspent',
        params: [0, 9999999, [address]]
      };
      var authString = Buffer.from(this.bitcoindCredentials.username + ':' + this.bitcoindCredentials.password).toString('base64');
      var headers = { Authorization: 'Basic ' + authString };

      return fetch(this.bitcoindUrl, {
        method: 'POST',
        body: JSON.stringify(jsonRPCImport),
        headers: headers
      }).then(function () {
        return fetch(_this11.bitcoindUrl, {
          method: 'POST',
          body: JSON.stringify(jsonRPCUnspent),
          headers: headers
        });
      }).then(function (resp) {
        return resp.json();
      }).then(function (x) {
        return x.result;
      }).then(function (utxos) {
        return utxos.map(function (x) {
          return Object({
            value: Math.round(x.amount * SATOSHIS_PER_BTC),
            confirmations: x.confirmations,
            tx_hash: x.txid,
            tx_output_n: x.vout
          });
        });
      });
    }
  }]);

  return BitcoindAPI;
}(BitcoinNetwork);

var InsightClient = exports.InsightClient = function (_BitcoinNetwork2) {
  _inherits(InsightClient, _BitcoinNetwork2);

  function InsightClient() {
    var insightUrl = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'https://utxo.technofractal.com/';

    _classCallCheck(this, InsightClient);

    var _this12 = _possibleConstructorReturn(this, (InsightClient.__proto__ || Object.getPrototypeOf(InsightClient)).call(this));

    _this12.apiUrl = insightUrl;
    return _this12;
  }

  _createClass(InsightClient, [{
    key: 'broadcastTransaction',
    value: function broadcastTransaction(transaction) {
      var jsonData = { tx: transaction };
      return fetch(this.apiUrl + '/tx/send', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(jsonData)
      }).then(function (resp) {
        return resp.json();
      });
    }
  }, {
    key: 'getBlockHeight',
    value: function getBlockHeight() {
      return fetch(this.apiUrl + '/status').then(function (resp) {
        return resp.json();
      }).then(function (status) {
        return status.blocks;
      });
    }
  }, {
    key: 'getTransactionInfo',
    value: function getTransactionInfo(txHash) {
      var _this13 = this;

      return fetch(this.apiUrl + '/tx/' + txHash).then(function (resp) {
        return resp.json();
      }).then(function (transactionInfo) {
        if (transactionInfo.error) {
          throw new Error('Error finding transaction: ' + transactionInfo.error);
        }
        return fetch(_this13.apiUrl + '/block/' + transactionInfo.blockHash);
      }).then(function (resp) {
        return resp.json();
      }).then(function (blockInfo) {
        return { block_height: blockInfo.height };
      });
    }
  }, {
    key: 'getNetworkedUTXOs',
    value: function getNetworkedUTXOs(address) {
      return fetch(this.apiUrl + '/addr/' + address + '/utxo').then(function (resp) {
        return resp.json();
      }).then(function (utxos) {
        return utxos.map(function (x) {
          return {
            value: x.satoshis,
            confirmations: x.confirmations,
            tx_hash: x.txid,
            tx_output_n: x.vout
          };
        });
      });
    }
  }]);

  return InsightClient;
}(BitcoinNetwork);

var BlockchainInfoApi = exports.BlockchainInfoApi = function (_BitcoinNetwork3) {
  _inherits(BlockchainInfoApi, _BitcoinNetwork3);

  function BlockchainInfoApi() {
    var blockchainInfoUrl = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'https://blockchain.info';

    _classCallCheck(this, BlockchainInfoApi);

    var _this14 = _possibleConstructorReturn(this, (BlockchainInfoApi.__proto__ || Object.getPrototypeOf(BlockchainInfoApi)).call(this));

    _this14.utxoProviderUrl = blockchainInfoUrl;
    return _this14;
  }

  _createClass(BlockchainInfoApi, [{
    key: 'getBlockHeight',
    value: function getBlockHeight() {
      return fetch(this.utxoProviderUrl + '/latestblock?cors=true').then(function (resp) {
        return resp.json();
      }).then(function (blockObj) {
        return blockObj.height;
      });
    }
  }, {
    key: 'getNetworkedUTXOs',
    value: function getNetworkedUTXOs(address) {
      return fetch(this.utxoProviderUrl + '/unspent?format=json&active=' + address + '&cors=true').then(function (resp) {
        if (resp.status === 500) {
          _logger.Logger.debug('UTXO provider 500 usually means no UTXOs: returning []');
          return {
            unspent_outputs: []
          };
        } else {
          return resp.json();
        }
      }).then(function (utxoJSON) {
        return utxoJSON.unspent_outputs;
      }).then(function (utxoList) {
        return utxoList.map(function (utxo) {
          var utxoOut = {
            value: utxo.value,
            tx_output_n: utxo.tx_output_n,
            confirmations: utxo.confirmations,
            tx_hash: utxo.tx_hash_big_endian
          };
          return utxoOut;
        });
      });
    }
  }, {
    key: 'getTransactionInfo',
    value: function getTransactionInfo(txHash) {
      return fetch(this.utxoProviderUrl + '/rawtx/' + txHash + '?cors=true').then(function (resp) {
        if (resp.status === 200) {
          return resp.json();
        } else {
          throw new Error('Could not lookup transaction info for \'' + txHash + '\'. Server error.');
        }
      }).then(function (respObj) {
        return { block_height: respObj.block_height };
      });
    }
  }, {
    key: 'broadcastTransaction',
    value: function broadcastTransaction(transaction) {
      var form = new _formData2.default();
      form.append('tx', transaction);
      return fetch(this.utxoProviderUrl + '/pushtx?cors=true', {
        method: 'POST',
        body: form
      }).then(function (resp) {
        var text = resp.text();
        return text.then(function (respText) {
          if (respText.toLowerCase().indexOf('transaction submitted') >= 0) {
            var txHash = _bitcoinjsLib2.default.Transaction.fromHex(transaction).getHash().reverse().toString('hex'); // big_endian
            return txHash;
          } else {
            throw new _errors.RemoteServiceError(resp, 'Broadcast transaction failed with message: ' + respText);
          }
        });
      });
    }
  }]);

  return BlockchainInfoApi;
}(BitcoinNetwork);

var LOCAL_REGTEST = new LocalRegtest('http://localhost:16268', 'http://localhost:16269', new BitcoindAPI('http://localhost:18332/', { username: 'blockstack', password: 'blockstacksystem' }));

var MAINNET_DEFAULT = new BlockstackNetwork('https://core.blockstack.org', 'https://broadcast.blockstack.org', new BlockchainInfoApi());

var network = exports.network = {
  BlockstackNetwork: BlockstackNetwork,
  LocalRegtest: LocalRegtest,
  BlockchainInfoApi: BlockchainInfoApi,
  BitcoindAPI: BitcoindAPI,
  InsightClient: InsightClient,
  defaults: { LOCAL_REGTEST: LOCAL_REGTEST, MAINNET_DEFAULT: MAINNET_DEFAULT }
};