UNPKG

blocktrail-sdk

Version:

BlockTrail's Developer Friendly API binding for NodeJS

1,427 lines (1,222 loc) 2.31 MB
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.blocktrailSDK = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){ (function (process,Buffer){ /* globals onLoadWorkerLoadAsmCrypto */ var _ = require('lodash'), q = require('q'), bitcoin = require('bitcoinjs-lib'), bitcoinMessage = require('bitcoinjs-message'), bip39 = require("bip39"), Wallet = require('./wallet'), BtccomConverter = require('./btccom.convert'), BlocktrailConverter = require('./blocktrail.convert'), RestClient = require('./rest_client'), Encryption = require('./encryption'), KeyDerivation = require('./keyderivation'), EncryptionMnemonic = require('./encryption_mnemonic'), blocktrail = require('./blocktrail'), randomBytes = require('randombytes'), CryptoJS = require('crypto-js'), webworkifier = require('./webworkifier'); /** * * @param opt * @returns {*} */ function networkFromOptions(opt) { if (opt.bitcoinCash) { if (opt.regtest) { return bitcoin.networks.bitcoincashregtest; } else if (opt.testnet) { return bitcoin.networks.bitcoincashtestnet; } else { return bitcoin.networks.bitcoincash; } } else { if (opt.regtest) { return bitcoin.networks.regtest; } else if (opt.testnet) { return bitcoin.networks.testnet; } else { return bitcoin.networks.bitcoin; } } } var useWebWorker = require('./use-webworker')(); /** * helper to wrap a promise so that the callback get's called when it succeeds or fails * * @param promise {q.Promise} * @param cb function * @return q.Promise */ function callbackify(promise, cb) { // add a .then to trigger the cb for people using callbacks if (cb) { promise .then(function(res) { // use q.nextTick for asyncness q.nextTick(function() { cb(null, res); }); }, function(err) { // use q.nextTick for asyncness q.nextTick(function() { cb(err, null); }); }); } // return the promise for people using promises return promise; } /** * Bindings to consume the BlockTrail API * * @param options object{ * apiKey: 'API_KEY', * apiSecret: 'API_SECRET', * host: 'defaults to wallet-api.btc.com', * network: 'BTC|LTC', * testnet: true|false * } * @constructor */ var APIClient = function(options) { var self = this; // handle constructor call without 'new' if (!(this instanceof APIClient)) { return new APIClient(options); } var normalizedNetwork = APIClient.normalizeNetworkFromOptions(options); options.network = normalizedNetwork[0]; options.testnet = normalizedNetwork[1]; options.regtest = normalizedNetwork[2]; // apiNetwork we allow to be customized for debugging purposes options.apiNetwork = options.apiNetwork || normalizedNetwork[3]; self.bitcoinCash = options.network === "BCC"; self.regtest = options.regtest; self.testnet = options.testnet; self.network = networkFromOptions(self); self.feeSanityCheck = typeof options.feeSanityCheck !== "undefined" ? options.feeSanityCheck : true; self.feeSanityCheckBaseFeeMultiplier = options.feeSanityCheckBaseFeeMultiplier || 200; options.apiNetwork = options.apiNetwork || ((self.testnet ? "t" : "") + (options.network || 'BTC').toUpperCase()); if (typeof options.btccom === "undefined") { options.btccom = true; } /** * @type RestClient */ var dataOptions = _.omit(options, 'host'); self.dataClient = APIClient.initRestClient(dataOptions); /** * @type RestClient */ self.blocktrailClient = APIClient.initRestClient(_.merge({}, options, {btccom: false})); if (options.btccom) { self.converter = new BtccomConverter(self.network, true); } else { self.converter = new BlocktrailConverter(); } }; APIClient.normalizeNetworkFromOptions = function(options) { /* jshint -W071, -W074 */ var network = 'BTC'; var testnet = false; var regtest = false; var apiNetwork = "BTC"; var prefix; var done = false; if (options.network) { var lower = options.network.toLowerCase(); var m = lower.match(/^([rt])?(btc|bch|bcc)$/); if (!m) { throw new Error("Invalid network [" + options.network + "]"); } if (m[2] === 'btc') { network = "BTC"; } else { network = "BCC"; } prefix = m[1]; if (prefix) { // if there's a prefix then we're "done", won't apply options.regtest and options.testnet after done = true; if (prefix === 'r') { testnet = true; regtest = true; } else if (prefix === 't') { testnet = true; } } } // if we're not already done then apply options.regtest and options.testnet if (!done) { if (options.regtest) { testnet = true; regtest = true; prefix = "r"; } else if (options.testnet) { testnet = true; prefix = "t"; } } apiNetwork = (prefix || "") + network; return [network, testnet, regtest, apiNetwork]; }; APIClient.updateHostOptions = function(options) { /* jshint -W071, -W074 */ // BLOCKTRAIL_SDK_API_ENDPOINT overwrite for development if (!options.btccom && process.env.BLOCKTRAIL_SDK_API_ENDPOINT) { options.host = process.env.BLOCKTRAIL_SDK_API_ENDPOINT; } if (options.btccom && process.env.BLOCKTRAIL_SDK_BTCCOM_API_ENDPOINT) { options.host = process.env.BLOCKTRAIL_SDK_BTCCOM_API_ENDPOINT; } if (options.btccom && process.env.BLOCKTRAIL_SDK_THROTTLE_BTCCOM) { options.throttleRequestsTimeout = process.env.BLOCKTRAIL_SDK_THROTTLE_BTCCOM; } if (options.btccom) { if (!options.host) { options.host = options.btccomhost || (options.network === 'BCC' ? 'bch-chain.api.btc.com' : 'chain.api.btc.com'); } if (options.testnet && !options.host.match(/tchain/)) { options.host = options.host.replace(/chain/, 'tchain'); } if (!options.endpoint) { options.endpoint = options.btccomendpoint || ("/" + (options.apiVersion || "v3")); } } else { if (!options.host) { options.host = 'wallet-api.btc.com'; } if (!options.endpoint) { options.endpoint = "/" + (options.apiVersion || "v1") + (options.apiNetwork ? ("/" + options.apiNetwork) : ""); } } // trim off leading https?:// if (options.host && options.host.indexOf("https://") === 0) { options.https = true; options.host = options.host.substr(8); } else if (options.host && options.host.indexOf("http://") === 0) { options.https = false; options.host = options.host.substr(7); } if (typeof options.https === "undefined") { options.https = true; } if (!options.port) { options.port = options.https ? 443 : 80; } return options; }; APIClient.initRestClient = function(options) { options = APIClient.updateHostOptions(options); return new RestClient(options); }; var determineDataStorageV2_3 = function(options) { return q.when(options) .then(function(options) { // legacy if (options.storePrimaryMnemonic) { options.storeDataOnServer = options.storePrimaryMnemonic; } // storeDataOnServer=false when primarySeed is provided if (typeof options.storeDataOnServer === "undefined") { options.storeDataOnServer = !options.primarySeed; } return options; }); }; var produceEncryptedDataV2 = function(options, notify) { return q.when(options) .then(function(options) { if (options.storeDataOnServer) { if (!options.secret) { if (!options.passphrase) { throw new blocktrail.WalletCreateError("Can't encrypt data without a passphrase"); } notify(APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_SECRET); options.secret = randomBytes(Wallet.WALLET_ENTROPY_BITS / 8).toString('hex'); // string because we use it as passphrase options.encryptedSecret = CryptoJS.AES.encrypt(options.secret, options.passphrase).toString(CryptoJS.format.OpenSSL); // 'base64' string } notify(APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_PRIMARY); options.encryptedPrimarySeed = CryptoJS.AES.encrypt(options.primarySeed.toString('base64'), options.secret) .toString(CryptoJS.format.OpenSSL); // 'base64' string options.recoverySecret = randomBytes(Wallet.WALLET_ENTROPY_BITS / 8).toString('hex'); // string because we use it as passphrase notify(APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_RECOVERY); options.recoveryEncryptedSecret = CryptoJS.AES.encrypt(options.secret, options.recoverySecret) .toString(CryptoJS.format.OpenSSL); // 'base64' string } return options; }); }; APIClient.prototype.promisedEncrypt = function(pt, pw, iter) { if (useWebWorker && typeof onLoadWorkerLoadAsmCrypto === "function") { // generate randomness outside of webworker because many browsers don't have crypto.getRandomValues inside webworkers var saltBuf = Encryption.generateSalt(); var iv = Encryption.generateIV(); return webworkifier.workify(APIClient.prototype.promisedEncrypt, function factory() { return require('./webworker'); }, onLoadWorkerLoadAsmCrypto, { method: 'Encryption.encryptWithSaltAndIV', pt: pt, pw: pw, saltBuf: saltBuf, iv: iv, iterations: iter }) .then(function(data) { return Buffer.from(data.cipherText.buffer); }); } else { try { return q.when(Encryption.encrypt(pt, pw, iter)); } catch (e) { return q.reject(e); } } }; APIClient.prototype.promisedDecrypt = function(ct, pw) { if (useWebWorker && typeof onLoadWorkerLoadAsmCrypto === "function") { return webworkifier.workify(APIClient.prototype.promisedDecrypt, function() { return require('./webworker'); }, onLoadWorkerLoadAsmCrypto, { method: 'Encryption.decrypt', ct: ct, pw: pw }) .then(function(data) { return Buffer.from(data.plainText.buffer); }); } else { try { return q.when(Encryption.decrypt(ct, pw)); } catch (e) { return q.reject(e); } } }; APIClient.prototype.produceEncryptedDataV3 = function(options, notify) { var self = this; return q.when(options) .then(function(options) { if (options.storeDataOnServer) { return q.when() .then(function() { if (!options.secret) { if (!options.passphrase) { throw new blocktrail.WalletCreateError("Can't encrypt data without a passphrase"); } notify(APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_SECRET); // -> now a buffer options.secret = randomBytes(Wallet.WALLET_ENTROPY_BITS / 8); // -> now a buffer return self.promisedEncrypt(options.secret, new Buffer(options.passphrase), KeyDerivation.defaultIterations) .then(function(encryptedSecret) { options.encryptedSecret = encryptedSecret; }); } else { if (!(options.secret instanceof Buffer)) { throw new Error('Secret must be a buffer'); } } }) .then(function() { notify(APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_PRIMARY); return self.promisedEncrypt(options.primarySeed, options.secret, KeyDerivation.subkeyIterations) .then(function(encryptedPrimarySeed) { options.encryptedPrimarySeed = encryptedPrimarySeed; }); }) .then(function() { // skip generating recovery secret when explicitly set to false if (options.recoverySecret === false) { return; } notify(APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_RECOVERY); if (!options.recoverySecret) { options.recoverySecret = randomBytes(Wallet.WALLET_ENTROPY_BITS / 8); } return self.promisedEncrypt(options.secret, options.recoverySecret, KeyDerivation.defaultIterations) .then(function(recoveryEncryptedSecret) { options.recoveryEncryptedSecret = recoveryEncryptedSecret; }); }) .then(function() { return options; }); } else { return options; } }); }; var doRemainingWalletDataV2_3 = function(options, network, notify) { return q.when(options) .then(function(options) { if (!options.backupPublicKey) { options.backupSeed = options.backupSeed || randomBytes(Wallet.WALLET_ENTROPY_BITS / 8); } notify(APIClient.CREATE_WALLET_PROGRESS_PRIMARY); options.primaryPrivateKey = bitcoin.HDNode.fromSeedBuffer(options.primarySeed, network); notify(APIClient.CREATE_WALLET_PROGRESS_BACKUP); if (!options.backupPublicKey) { options.backupPrivateKey = bitcoin.HDNode.fromSeedBuffer(options.backupSeed, network); options.backupPublicKey = options.backupPrivateKey.neutered(); } options.primaryPublicKey = options.primaryPrivateKey.deriveHardened(options.keyIndex).neutered(); notify(APIClient.CREATE_WALLET_PROGRESS_SUBMIT); return options; }); }; APIClient.prototype.mnemonicToPrivateKey = function(mnemonic, passphrase, cb) { var self = this; var deferred = q.defer(); deferred.promise.spreadNodeify(cb); deferred.resolve(q.fcall(function() { return self.mnemonicToSeedHex(mnemonic, passphrase).then(function(seedHex) { return bitcoin.HDNode.fromSeedHex(seedHex, self.network); }); })); return deferred.promise; }; APIClient.prototype.mnemonicToSeedHex = function(mnemonic, passphrase) { var self = this; if (useWebWorker) { return webworkifier.workify(self.mnemonicToSeedHex, function() { return require('./webworker'); }, {method: 'mnemonicToSeedHex', mnemonic: mnemonic, passphrase: passphrase}) .then(function(data) { return data.seed; }); } else { try { return q.when(bip39.mnemonicToSeedHex(mnemonic, passphrase)); } catch (e) { return q.reject(e); } } }; APIClient.prototype.resolvePrimaryPrivateKeyFromOptions = function(options, cb) { var self = this; var deferred = q.defer(); deferred.promise.nodeify(cb); try { // avoid conflicting options if (options.passphrase && options.password) { throw new blocktrail.WalletCreateError("Can't specify passphrase and password"); } // normalize passphrase/password options.passphrase = options.passphrase || options.password; delete options.password; // avoid conflicting options if (options.primaryMnemonic && options.primarySeed) { throw new blocktrail.WalletInitError("Can only specify one of; Primary Mnemonic or Primary Seed"); } // avoid deprecated options if (options.primaryPrivateKey) { throw new blocktrail.WalletInitError("Can't specify; Primary PrivateKey"); } // make sure we have at least one thing to use if (!options.primaryMnemonic && !options.primarySeed) { throw new blocktrail.WalletInitError("Need to specify at least one of; Primary Mnemonic or Primary Seed"); } if (options.primarySeed) { self.primarySeed = options.primarySeed; options.primaryPrivateKey = bitcoin.HDNode.fromSeedBuffer(self.primarySeed, self.network); deferred.resolve(options); } else { if (!options.passphrase) { throw new blocktrail.WalletInitError("Can't init wallet with Primary Mnemonic without a passphrase"); } self.mnemonicToSeedHex(options.primaryMnemonic, options.passphrase) .then(function(seedHex) { try { options.primarySeed = new Buffer(seedHex, 'hex'); options.primaryPrivateKey = bitcoin.HDNode.fromSeedBuffer(options.primarySeed, self.network); deferred.resolve(options); } catch (e) { deferred.reject(e); } }, function(e) { deferred.reject(e); }); } } catch (e) { deferred.reject(e); } return deferred.promise; }; APIClient.prototype.resolveBackupPublicKeyFromOptions = function(options, cb) { var self = this; var deferred = q.defer(); deferred.promise.nodeify(cb); try { // avoid conflicting options if (options.backupMnemonic && options.backupPublicKey) { throw new blocktrail.WalletInitError("Can only specify one of; Backup Mnemonic or Backup PublicKey"); } // make sure we have at least one thing to use if (!options.backupMnemonic && !options.backupPublicKey) { throw new blocktrail.WalletInitError("Need to specify at least one of; Backup Mnemonic or Backup PublicKey"); } if (options.backupPublicKey) { if (options.backupPublicKey instanceof bitcoin.HDNode) { deferred.resolve(options); } else { options.backupPublicKey = bitcoin.HDNode.fromBase58(options.backupPublicKey, self.network); deferred.resolve(options); } } else { self.mnemonicToPrivateKey(options.backupMnemonic, "").then(function(backupPrivateKey) { options.backupPublicKey = backupPrivateKey.neutered(); deferred.resolve(options); }, function(e) { deferred.reject(e); }); } } catch (e) { deferred.reject(e); } return deferred.promise; }; APIClient.prototype.debugAuth = function(cb) { var self = this; return self.dataClient.get("/debug/http-signature", null, true, cb); }; /** * get a single address * * @param address string address hash * @param [cb] function callback function to call when request is complete * @return q.Promise */ APIClient.prototype.address = function(address, cb) { var self = this; return callbackify(self.dataClient.get(self.converter.getUrlForAddress(address), null) .then(function(data) { return self.converter.handleErrors(self, data); }) .then(function(data) { if (data === null) { return data; } else { return self.converter.convertAddress(data); } }), cb); }; APIClient.prototype.addresses = function(addresses, cb) { var self = this; return callbackify(self.dataClient.post("/address", null, {"addresses": addresses}), cb); }; /** * get all transactions for an address (paginated) * * @param address string address hash * @param [params] object pagination: {page: 1, limit: 20, sort_dir: 'asc'} * @param [cb] function callback function to call when request is complete * @return q.Promise */ APIClient.prototype.addressTransactions = function(address, params, cb) { var self = this; if (typeof params === "function") { cb = params; params = null; } return callbackify(self.dataClient.get(self.converter.getUrlForAddressTransactions(address), self.converter.paginationParams(params)) .then(function(data) { return self.converter.handleErrors(self, data); }) .then(function(data) { return data.data === null ? data : self.converter.convertAddressTxs(data); }), cb); }; /** * get all transactions for a batch of addresses (paginated) * * @param addresses array address hashes * @param [params] object pagination: {page: 1, limit: 20, sort_dir: 'asc'} * @param [cb] function callback function to call when request is complete * @return q.Promise */ APIClient.prototype.batchAddressHasTransactions = function(addresses, params, cb) { var self = this; if (typeof params === "function") { cb = params; params = null; } var deferred = q.defer(); var promise = q(); addresses.forEach(function(address) { promise = promise.then(function(hasTxs) { if (hasTxs) { return hasTxs; } return q(address) .then(function(address) { console.log(address); return self.addressTransactions(address, params) .then(function(res) { // err_no=1 is no txs found if (res.err_no === 1) { return false; } else if (res.err_no) { throw new Error("err: " + res.err_msg); } return res.data && res.data.length > 0; }); }); }); }); promise.then(function(hasTxs) { deferred.resolve({has_transactions: hasTxs}); }, function(err) { deferred.reject(err); }); return callbackify(deferred.promise, cb); }; /** * get all unconfirmed transactions for an address (paginated) * * @param address string address hash * @param [params] object pagination: {page: 1, limit: 20, sort_dir: 'asc'} * @param [cb] function callback function to call when request is complete * @return q.Promise */ APIClient.prototype.addressUnconfirmedTransactions = function(address, params, cb) { var self = this; if (typeof params === "function") { cb = params; params = null; } return callbackify(self.dataClient.get(self.converter.getUrlForAddressTransactions(address), self.converter.paginationParams(params)) .then(function(data) { return self.converter.handleErrors(self, data); }) .then(function(data) { if (data.data === null) { return data; } var res = self.converter.convertAddressTxs(data); res.data = res.data.filter(function(tx) { return !tx.confirmations; }); return res; }), cb); }; /** * get all unspent outputs for an address (paginated) * * @param address string address hash * @param [params] object pagination: {page: 1, limit: 20, sort_dir: 'asc'} * @param [cb] function callback function to call when request is complete * @return q.Promise */ APIClient.prototype.addressUnspentOutputs = function(address, params, cb) { var self = this; if (typeof params === "function") { cb = params; params = null; } return callbackify(self.dataClient.get(self.converter.getUrlForAddressUnspent(address), self.converter.paginationParams(params)) .then(function(data) { return self.converter.handleErrors(self, data); }) .then(function(data) { return data.data === null ? data : self.converter.convertAddressUnspentOutputs(data, address); }), cb); }; /** * get all unspent outputs for a batch of addresses (paginated) * * @param addresses array address hashes * @param [params] object pagination: {page: 1, limit: 20, sort_dir: 'asc'} * @param [cb] function callback function to call when request is complete * @return q.Promise */ APIClient.prototype.batchAddressUnspentOutputs = function(addresses, params, cb) { var self = this; if (self.converter instanceof BtccomConverter) { return callbackify(self.dataClient.get(self.converter.getUrlForBatchAddressUnspent(addresses), self.converter.paginationParams(params)) .then(function(data) { return self.converter.handleErrors(self, data); }) .then(function(data) { return data.data === null ? data : self.converter.convertBatchAddressUnspentOutputs(data); }), cb); } else { if (typeof params === "function") { cb = params; params = null; } return callbackify(self.dataClient.post("/address/unspent-outputs", params, {"addresses": addresses}), cb); } }; /** * verify ownership of an address * * @param address string address hash * @param signature string a signed message (the address hash) using the private key of the address * @param [cb] function callback function to call when request is complete * @return q.Promise */ APIClient.prototype.verifyAddress = function(address, signature, cb) { var self = this; return self.verifyMessage(address, address, signature, cb); }; /** * * get all blocks (paginated) * ASK * @param [params] object pagination: {page: 1, limit: 20, sort_dir: 'asc'} * @param [cb] function callback function to call when request is complete * @return q.Promise */ APIClient.prototype.allBlocks = function(params, cb) { var self = this; if (typeof params === "function") { cb = params; params = null; } return callbackify(self.dataClient.get(self.converter.getUrlForAllBlocks(), self.converter.paginationParams(params)) .then(function(data) { return self.converter.handleErrors(self, data); }) .then(function(data) { return data.data === null ? data : self.converter.convertBlocks(data); }), cb); }; /** * get a block * * @param block string|int a block hash or a block height * @param [cb] function callback function to call when request is complete * @return q.Promise */ APIClient.prototype.block = function(block, cb) { var self = this; return callbackify(self.dataClient.get(self.converter.getUrlForBlock(block), null) .then(function(data) { return self.converter.handleErrors(self, data); }) .then(function(data) { return data.data === null ? data : self.converter.convertBlock(data.data); }), cb); }; /** * get the latest block * * @param [cb] function callback function to call when request is complete * @return q.Promise */ APIClient.prototype.blockLatest = function(cb) { var self = this; return callbackify(self.dataClient.get(self.converter.getUrlForBlock("latest"), null) .then(function(data) { return self.converter.handleErrors(self, data); }) .then(function(data) { return data.data === null ? data : self.converter.convertBlock(data.data); }), cb); }; /** * get all transactions for a block (paginated) * * @param block string|int a block hash or a block height * @param [params] object pagination: {page: 1, limit: 20, sort_dir: 'asc'} * @param [cb] function callback function to call when request is complete * @return q.Promise */ APIClient.prototype.blockTransactions = function(block, params, cb) { var self = this; if (typeof params === "function") { cb = params; params = null; } return callbackify(self.dataClient.get(self.converter.getUrlForBlockTransaction(block), self.converter.paginationParams(params)) .then(function(data) { return self.converter.handleErrors(self, data); }) .then(function(data) { return data.data === null ? data : self.converter.convertBlockTxs(data); }), cb); }; /** * get a single transaction * * @param tx string transaction hash * @param [cb] function callback function to call when request is complete * @return q.Promise */ APIClient.prototype.transaction = function(tx, cb) { var self = this; return callbackify(self.dataClient.get(self.converter.getUrlForTransaction(tx), null) .then(function(data) { return self.converter.handleErrors(self, data); }) .then(function(data) { if (data.data === null) { return data; } else { // for BTC.com API we need to fetch the raw hex from the BTC.com explorer endpoint if (self.converter instanceof BtccomConverter) { return self.dataClient.get(self.converter.getUrlForRawTransaction(tx), null) .then(function(rawData) { return [data, rawData.data]; }) .then(function(dataAndTx) { if (dataAndTx !== null) { var data = dataAndTx[0]; var rawTx = dataAndTx[1]; return self.converter.convertTx(data, rawTx); } else { return dataAndTx; } }); } else { return self.converter.convertTx(data); } } }), cb); }; /** * get a batch of transactions * * @param txs string[] list of transaction hashes (txId) * @param [cb] function callback function to call when request is complete * @return q.Promise */ APIClient.prototype.transactions = function(txs, cb) { var self = this; if (self.converter instanceof BtccomConverter) { return callbackify(self.dataClient.get(self.converter.getUrlForTransactions(txs), null) .then(function(data) { return self.converter.handleErrors(self, data); }) .then(function(data) { if (data.data === null) { return data; } else { return self.converter.convertTxs(data); } }), cb); } else { return callbackify(self.dataClient.post("/transactions", null, txs, null, false), cb); } }; /** * get a paginated list of all webhooks associated with the api user * * @param [params] object pagination: {page: 1, limit: 20} * @param [cb] function callback function to call when request is complete * @return q.Promise */ APIClient.prototype.allWebhooks = function(params, cb) { var self = this; if (typeof params === "function") { cb = params; params = null; } return self.blocktrailClient.get("/webhooks", params, cb); }; /** * create a new webhook * * @param url string the url to receive the webhook events * @param [identifier] string a unique identifier associated with the webhook * @param [cb] function callback function to call when request is complete * @return q.Promise */ APIClient.prototype.setupWebhook = function(url, identifier, cb) { var self = this; if (typeof identifier === "function") { //mimic function overloading cb = identifier; identifier = null; } return self.blocktrailClient.post("/webhook", null, {url: url, identifier: identifier}, cb); }; /** * Converts a cash address to the legacy (base58) format * @param {string} input * @returns {string} */ APIClient.prototype.getLegacyBitcoinCashAddress = function(input) { if (this.network === bitcoin.networks.bitcoincash || this.network === bitcoin.networks.bitcoincashtestnet || this.network === bitcoin.networks.bitcoincashregtest) { var address; try { bitcoin.address.fromBase58Check(input, this.network); return input; } catch (e) {} address = bitcoin.address.fromCashAddress(input, this.network); var prefix; if (address.version === bitcoin.script.types.P2PKH) { prefix = this.network.pubKeyHash; } else if (address.version === bitcoin.script.types.P2SH) { prefix = this.network.scriptHash; } else { throw new Error("Unsupported address type"); } return bitcoin.address.toBase58Check(address.hash, prefix); } throw new Error("Cash addresses only work on bitcoin cash"); }; /** * Converts a legacy bitcoin to the new cashaddr format * @param {string} input * @returns {string} */ APIClient.prototype.getCashAddressFromLegacyAddress = function(input) { if (this.network === bitcoin.networks.bitcoincash || this.network === bitcoin.networks.bitcoincashtestnet || this.network === bitcoin.networks.bitcoincashregtest ) { var address; try { bitcoin.address.fromCashAddress(input, this.network); return input; } catch (e) {} address = bitcoin.address.fromBase58Check(input, this.network); var scriptType; if (address.version === this.network.pubKeyHash) { scriptType = bitcoin.script.types.P2PKH; } else if (address.version === this.network.scriptHash) { scriptType = bitcoin.script.types.P2SH; } else { throw new Error("Unsupported address type"); } return bitcoin.address.toCashAddress(address.hash, scriptType, this.network.cashAddrPrefix); } throw new Error("Cash addresses only work on bitcoin cash"); }; /** * get an existing webhook by it's identifier * * @param identifier string the unique identifier of the webhook to get * @param [cb] function callback function to call when request is complete * @return q.Promise */ APIClient.prototype.getWebhook = function(identifier, cb) { var self = this; return self.blocktrailClient.get("/webhook/" + identifier, null, cb); }; /** * update an existing webhook * * @param identifier string the unique identifier of the webhook * @param webhookData object the data to update: {identifier: newIdentifier, url:newUrl} * @param [cb] function callback function to call when request is complete * @return q.Promise */ APIClient.prototype.updateWebhook = function(identifier, webhookData, cb) { var self = this; return self.blocktrailClient.put("/webhook/" + identifier, null, webhookData, cb); }; /** * deletes an existing webhook and any event subscriptions associated with it * * @param identifier string the unique identifier of the webhook * @param [cb] function callback function to call when request is complete * @return q.Promise */ APIClient.prototype.deleteWebhook = function(identifier, cb) { var self = this; return self.blocktrailClient.delete("/webhook/" + identifier, null, null, cb); }; /** * get a paginated list of all the events a webhook is subscribed to * * @param identifier string the unique identifier of the webhook * @param [params] object pagination: {page: 1, limit: 20} * @param [cb] function callback function to call when request is complete * @return q.Promise */ APIClient.prototype.getWebhookEvents = function(identifier, params, cb) { var self = this; if (typeof params === "function") { cb = params; params = null; } return self.blocktrailClient.get("/webhook/" + identifier + "/events", params, cb); }; /** * subscribes a webhook to transaction events for a particular transaction * * @param identifier string the unique identifier of the webhook * @param transaction string the transaction hash * @param confirmations integer the amount of confirmations to send * @param [cb] function callback function to call when request is complete * @return q.Promise */ APIClient.prototype.subscribeTransaction = function(identifier, transaction, confirmations, cb) { var self = this; var postData = { 'event_type': 'transaction', 'transaction': transaction, 'confirmations': confirmations }; return self.blocktrailClient.post("/webhook/" + identifier + "/events", null, postData, cb); }; /** * subscribes a webhook to transaction events on a particular address * * @param identifier string the unique identifier of the webhook * @param address string the address hash * @param confirmations integer the amount of confirmations to send * @param [cb] function callback function to call when request is complete * @return q.Promise */ APIClient.prototype.subscribeAddressTransactions = function(identifier, address, confirmations, cb) { var self = this; var postData = { 'event_type': 'address-transactions', 'address': address, 'confirmations': confirmations }; return self.blocktrailClient.post("/webhook/" + identifier + "/events", null, postData, cb); }; /** * batch subscribes a webhook to multiple transaction events * * @param identifier string the unique identifier of the webhook * @param batchData array An array of objects containing batch event data: * {address : 'address', confirmations : 'confirmations'] * where address is the address to subscribe to and confirmations (optional) is the amount of confirmations to send * @param [cb] function callback function to call when request is complete * @return q.Promise */ APIClient.prototype.batchSubscribeAddressTransactions = function(identifier, batchData, cb) { var self = this; batchData.forEach(function(record) { record.event_type = 'address-transactions'; }); return self.blocktrailClient.post("/webhook/" + identifier + "/events/batch", null, batchData, cb); }; /** * subscribes a webhook to a new block event * * @param identifier string the unique identifier of the webhook * @param [cb] function callback function to call when request is complete * @return q.Promise */ APIClient.prototype.subscribeNewBlocks = function(identifier, cb) { var self = this; var postData = { 'event_type': 'block' }; return self.blocktrailClient.post("/webhook/" + identifier + "/events", null, postData, cb); }; /** * removes an address transaction event subscription from a webhook * * @param identifier string the unique identifier of the webhook * @param address string the address hash * @param [cb] function callback function to call when request is complete * @return q.Promise */ APIClient.prototype.unsubscribeAddressTransactions = function(identifier, address, cb) { var self = this; return self.blocktrailClient.delete("/webhook/" + identifier + "/address-transactions/" + address, null, null, cb); }; /** * removes an transaction event subscription from a webhook * * @param identifier string the unique identifier of the webhook * @param transaction string the transaction hash * @param [cb] function callback function to call when request is complete * @return q.Promise */ APIClient.prototype.unsubscribeTransaction = function(identifier, transaction, cb) { var self = this; return self.blocktrailClient.delete("/webhook/" + identifier + "/transaction/" + transaction, null, null, cb); }; /** * removes a block event subscription from a webhook * * @param identifier string the unique identifier of the webhook * @param [cb] function callback function to call when request is complete * @return q.Promise */ APIClient.prototype.unsubscribeNewBlocks = function(identifier, cb) { var self = this; return self.blocktrailClient.delete("/webhook/" + identifier + "/block", null, null, cb); }; /** * Returns ['hash' => x, 'height' => y] for wallet API's chain tip * * @param [cb] function callback function to call when request is complete * @return q.Promise */ APIClient.prototype.getWalletLatestBlock = function(cb) { var self = this; return self.blocktrailClient.get("/block/latest", null, cb); }; /** * initialize an existing wallet * * Either takes two argument: * @param options object {} * @param [cb] function callback(err, wallet, primaryMnemonic, backupMnemonic, blocktrailPubKeys) * * Or takes three arguments (old, deprecated syntax): * @param identifier string the wallet identifier to be initialized * @param passphrase string the password to decrypt the mnemonic with * @param [cb] function callback(err, wallet, primaryMnemonic, backupMnemonic, blocktrailPubKeys) * * @returns {q.Promise} */ APIClient.prototype.initWallet = function(options, cb) { var self = this; if (typeof options !== "object") { // get the old-style arguments options = { identifier: arguments[0], passphrase: arguments[1] }; cb = arguments[2]; } if (options.check_backup_key) { if (typeof options.check_backup_key !== "string") { throw new Error("Invalid input, must provide the backup key as a string (the xpub)"); } } var deferred = q.defer(); deferred.promise.spreadNodeify(cb); var identifier = options.identifier; if (!identifier) { deferred.reject(new blocktrail.WalletInitError("Identifier is required")); return deferred.promise; } deferred.resolve(self.blocktrailClient.get("/wallet/" + identifier, null, true).then(function(result) { var keyIndex = options.keyIndex || result.key_index; options.walletVersion = result.wallet_version; if (options.check_backup_key) { if (options.check_backup_key !== result.backup_public_key[0]) { throw new Error("Backup key returned from server didn't match our own copy"); } } var backupPublicKey = bitcoin.HDNode.fromBase58(result.backup_public_key[0], self.network); var blocktrailPublicKeys = _.mapValues(result.blocktrail_public_keys, function(blocktrailPublicKey) { return bitcoin.HDNode.fromBase58(blocktrailPublicKey[0], self.network); }); var primaryPublicKeys = _.mapValues(result.primary_public_keys, function(primaryPublicKey) { return bitcoin.HDNode.fromBase58(primaryPublicKey[0], self.network); }); // initialize wallet var wallet = new Wallet( self, identifier, options.walletVersion, result.primary_mnemonic, result.encrypted_primary_seed, result.encrypted_secret, primaryPublicKeys, backupPublicKey, blocktrailPublicKeys, keyIndex, result.segwit || 0, self.testnet, self.regtest, result.checksum, result.upgrade_key_index, options.useCashAddress, options.bypassNewAddressCheck ); wallet.recoverySecret = result.recovery_secret; if (!options.readOnly) { return wallet.unlock(options).then(function() { return wallet; }); } else { return wallet; } })); return deferred.promise; }; APIClient.CREATE_WALLET_PROGRESS_START = 0; APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_SECRET = 4; APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_PRIMARY = 5; APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_RECOVERY = 6; APIClient.CREATE_WALLET_PROGRESS_PRIMARY = 10; APIClient.CREATE_WALLET_PROGRESS_BACKUP = 20; APIClient.CREATE_WALLET_PROGRESS_SUBMIT = 30; APIClient.CREATE_WALLET_PROGRESS_INIT = 40; APIClient.CREATE_WALLET_PROGRESS_DONE = 100; /** * create a new wallet * - will generate a new primary seed and backup seed * * Either takes two argument: * @param options object {} * @param [cb] function callback(err, wallet, primaryMnemonic, backupMnemonic, blocktrailPubKeys) * * For v1 wallets (explicitly specify options.walletVersion=v1): * @param options object {} * @param [cb] function callback(err, wallet, primaryMnemonic, backupMnemonic, blocktrailPubKeys) * * Or takes four arguments (old, deprecated syntax): * @param identifier string the wallet identifier to be initialized * @param passphrase string the password to decrypt the mnemonic with * @param keyIndex int override for the blocktrail cosign key to use (for development purposes) * @param [cb] function callback(err, wallet, primaryMnemonic, backupMnemonic, blocktrailPubKeys) * @returns {q.Promise} */ APIClient.prototype.createNewWallet = function(options, cb) { /* jshint -W071, -W074 */ var self = this; if (typeof options !== "object") { // get the old-style arguments var identifier = arguments[0]; var passphrase = arguments[1]; var keyIndex = arguments[2]; cb = arguments[3]; // keyIndex is optional if (typeof keyIndex === "function") { cb = keyIndex; keyIndex = null; } options = { identifier: identifier, passphrase: passphrase, keyIndex: keyIndex }; } // default to v3 options.walletVersion = options.walletVersion || Wallet.WALLET_VERSION_V3; var deferred = q.defer(); deferred.promise.spreadNodeify(cb); q.nextTick(function() { deferred.notify(APIClient.CREATE_WALLET_PROGRESS_START); options.keyIndex = options.keyIndex || 0; options.passphrase = options.passphrase || options.password; delete options.password; if (!options.identifier) { deferred.reject(new blocktrail.WalletCreateError("Identifier is required")); return deferred.promise; } if (options.walletVersion === Wallet.WALLET_VERSION_V1) { self._createNewWalletV1(options) .progress(function(p) { deferred.notify(p); }) .then(function(r) { deferred.resolve(r); }, function(e) { deferred.reject(e); }) ; } else if (options.walletVersion === Wallet.WALLET_VERSION_V2) { self._createNewWalletV2(options) .progress(function(p) { deferred.notify(p); }) .then(function(r) { deferred.resolve(r); }, function(e) { deferred.reject(e); }) ; } else if (options.walletVersion === Wallet.WALLET_VERSION_V3) { self._createNewWalletV3(options) .progress(function(p) { deferred.notify(p); }) .then(function(r) { deferred.resolve(r); }, function(e) { deferred.reject(e); }) ; } else { deferred.reject(new blocktrail.WalletCreateError("Invalid wallet version!")); } }); return deferred.promise; }; APIClient.prototype._createNewWalletV1 = function(options) { var self = this; var deferred = q.defer(); q.nextTick(function() { if (!options.primaryMnemonic && !options.p