diff --git a/src/electrumx.js b/src/electrumx.js index e4a42cc..c412aae 100644 --- a/src/electrumx.js +++ b/src/electrumx.js @@ -1,493 +1,594 @@ -/* - This library interacts with the ElectrumX bch-api REST API endpoints operated - by FullStack.cash -*/ -// Public npm libraries -import axios from 'axios' - -// Local libraries. -import Blockchain from './blockchain.js' -// const Address = require('./address') - -// let _this - -class ElectrumX { - constructor (config) { - this.restURL = config.restURL - this.authToken = config.authToken - // Use the shared axios instance if provided, otherwise fall back to axios - this.axios = config.axios || axios - - this.axiosOptions = { - headers: { - authorization: this.authToken - } - } - - // Encapsulate dependencies - this.blockchain = new Blockchain(config) - } - - /** - * @api Electrumx.utxo() utxo() - * @apiName ElectrumX Utxo - * @apiGroup ElectrumX - * @apiDescription Return a list of uxtos for an address. - * - * @apiExample Example usage: - * (async () => { - * try { - * let utxo = await bchjs.Electrumx.utxo('bitcoincash:qqh793x9au6ehvh7r2zflzguanlme760wuzehgzjh9'); - * console.log(utxo); - * } catch(error) { - * console.error(error) - * } - * })() - * - * utxo = { - * "success": true, - * "utxos": [ - * { - * "height": 602405, - * "tx_hash": "2b37bdb3b63dd0bca720437754a36671431a950e684b64c44ea910ea9d5297c7", - * "tx_pos": 0, - * "value": 1000 - * } - * ] - * } - * - * (async () => { - * try { - * let utxo = await bchjs.Electrumx.utxo(['bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf', 'bitcoincash:qpdh9s677ya8tnx7zdhfrn8qfyvy22wj4qa7nwqa5v']); - * console.log(utxo); - * } catch(error) { - * console.error(error) - * } - * })() - * - * utxos = { - * "success": true, - * "utxos": [ - * { - * "utxos": [ - * { - * "height": 604392, - * "tx_hash": "7774e449c5a3065144cefbc4c0c21e6b69c987f095856778ef9f45ddd8ae1a41", - * "tx_pos": 0, - * "value": 1000 - * }, - * { - * "height": 630834, - * "tx_hash": "4fe60a51e0d8f5134bfd8e5f872d6e502d7f01b28a6afebb27f4438a4f638d53", - * "tx_pos": 0, - * "value": 6000 - * } - * ], - * "address": "bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf" - * }, - * { - * "utxos": [], - * "address": "bitcoincash:qpdh9s677ya8tnx7zdhfrn8qfyvy22wj4qa7nwqa5v" - * } - * ] - * } - * - */ - async utxo (address) { - try { - // console.log(`electrumx.js/utxo() restURL: ${this.restURL}`) - - // Handle single address. - if (typeof address === 'string') { - const response = await this.axios.get( - `${this.restURL}fulcrum/utxos/${address}`, - this.axiosOptions - ) - return response.data - - // Handle array of addresses. - } else if (Array.isArray(address)) { - const response = await this.axios.post( - `${this.restURL}fulcrum/utxos`, - { - addresses: address - }, - this.axiosOptions - ) - - return response.data - } - - throw new Error('Input address must be a string or array of strings.') - } catch (error) { - if (error.response && error.response.data) { - const errorData = error.response.data - const errorMessage = typeof errorData === 'string' ? errorData : (errorData.error || errorData.message || JSON.stringify(errorData)) - throw new Error(errorMessage) - } else throw error - } - } - - /** - * @api Electrumx.balance() balance() - * @apiName ElectrumX Balance - * @apiGroup ElectrumX - * @apiDescription Return a list of balances for an address. - * - * @apiExample Example usage: - * (async () => { - * try { - * let balance = await bchjs.Electrumx.balance('bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf'); - * console.log(balance); - * } catch(error) { - * console.error(error) - * } - * })() - * - * balance = { - * "success": true, - * "balance": { - * "confirmed": 1000, - * "unconfirmed": 0 - * } - * } - * - * (async () => { - * try { - * let balance = await bchjs.Electrumx.balance(['bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf', 'bitcoincash:qpdh9s677ya8tnx7zdhfrn8qfyvy22wj4qa7nwqa5v']); - * console.log(balance); - * } catch(error) { - * console.error(error) - * } - * })() - * - * balance = { - * "success": true, - * "balances": [ - * { - * "balance": { - * "confirmed": 7000, - * "unconfirmed": 0 - * }, - * "address": "bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf" - * }, - * { - * "balance": { - * "confirmed": 0, - * "unconfirmed": 0 - * }, - * "address": "bitcoincash:qpdh9s677ya8tnx7zdhfrn8qfyvy22wj4qa7nwqa5v" - * } - * ] - * } - * - */ - async balance (address) { - try { - // Handle single address. - if (typeof address === 'string') { - const response = await this.axios.get( - `${this.restURL}fulcrum/balance/${address}`, - this.axiosOptions - ) - return response.data - - // Handle array of addresses. - } else if (Array.isArray(address)) { - const response = await this.axios.post( - `${this.restURL}fulcrum/balance`, - { - addresses: address - }, - this.axiosOptions - ) - - return response.data - } - - throw new Error('Input address must be a string or array of strings.') - } catch (error) { - if (error.response && error.response.data) { - const errorData = error.response.data - const errorMessage = typeof errorData === 'string' ? errorData : (errorData.error || errorData.message || JSON.stringify(errorData)) - throw new Error(errorMessage) - } else throw error - } - } - - /** - * @api Electrumx.transactions() transactions() - * @apiName ElectrumX Transactions - * @apiGroup ElectrumX - * @apiDescription Return a transaction history for an address. - * - * @apiExample Example usage: - * (async () => { - * try { - * let transactions = await bchjs.Electrumx.transactions('bitcoincash:qpdh9s677ya8tnx7zdhfrn8qfyvy22wj4qa7nwqa5v'); - * console.log(utxo); - * } catch(error) { - * console.error(error) - * } - * })() - * - * { - * "success": true, - * "transactions": [ - * { - * "height": 560430, - * "tx_hash": "3e1f3e882be9c03897eeb197224bf87f312be556a89f4308fabeeeabcf9bc851" - * }, - * { - * "height": 560534, - * "tx_hash": "4ebbeaac51ce141e262964e3a0ce11b96ca72c0dffe9b4127ce80135f503a280" - * } - * ] - * } - * - * (async () => { - * try { - * let transactions = await bchjs.Electrumx.transactions(['bitcoincash:qrl2nlsaayk6ekxn80pq0ks32dya8xfclyktem2mqj', 'bitcoincash:qpdh9s677ya8tnx7zdhfrn8qfyvy22wj4qa7nwqa5v']); - * console.log(utxo); - * } catch(error) { - * console.error(error) - * } - * })() - * - * transactions = { - * "success": true, - * "transactions": [ - * { - * "transactions": [ - * { - * "height": 631219, - * "tx_hash": "ae2daa01c8172545b5edd205ea438706bcb74e63d4084a26b9ff2a46d46dc97f" - * } - * ], - * "address": "bitcoincash:qrl2nlsaayk6ekxn80pq0ks32dya8xfclyktem2mqj" - * }, - * { - * "transactions": [ - * { - * "height": 560430, - * "tx_hash": "3e1f3e882be9c03897eeb197224bf87f312be556a89f4308fabeeeabcf9bc851" - * }, - * { - * "height": 560534, - * "tx_hash": "4ebbeaac51ce141e262964e3a0ce11b96ca72c0dffe9b4127ce80135f503a280" - * } - * ], - * "address": "bitcoincash:qpdh9s677ya8tnx7zdhfrn8qfyvy22wj4qa7nwqa5v" - * } - * ] - * } - * - */ - async transactions (address, usrObj = null, allTxs = false) { - try { - // Handle single address. - if (typeof address === 'string') { - const response = await this.axios.get( - `${this.restURL}fulcrum/transactions/${address}/${allTxs}`, - this.axiosOptions - ) - return response.data - - // Handle array of addresses. - } else if (Array.isArray(address)) { - const response = await this.axios.post( - `${this.restURL}fulcrum/transactions`, - { - addresses: address, - usrObj, // pass user data when making an internal call. - allTxs - }, - this.axiosOptions - ) - - return response.data - } - - throw new Error('Input address must be a string or array of strings.') - } catch (error) { - // console.log('Error in transactions(): ', error) - if (error.response && error.response.data) { - const errorData = error.response.data - const errorMessage = typeof errorData === 'string' ? errorData : (errorData.error || errorData.message || JSON.stringify(errorData)) - throw new Error(errorMessage) - } else throw error - } - } - - /** - * @api Electrumx.unconfirmed() unconfirmed() - * @apiName ElectrumX Unconfirmed - * @apiGroup ElectrumX - * @apiDescription Return a list of unconfirmed uxtos (mempool) for an address. - * - * @apiExample Example usage: - * (async () => { - * try { - * let mempool = await bchjs.Electrumx.unconfirmed('bitcoincash:qqh793x9au6ehvh7r2zflzguanlme760wuzehgzjh9'); - * console.log(mempool); - * } catch(error) { - * console.error(error) - * } - * })() - * - * mempool = { - * "success": true, - * "utxos": [ - * { - * "height": 602405, - * "tx_hash": "2b37bdb3b63dd0bca720437754a36671431a950e684b64c44ea910ea9d5297c7", - * "fee": 24310 - * } - * ] - * } - * - * (async () => { - * try { - * let mempool = await bchjs.Electrumx.unconfirmed(['bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf', 'bitcoincash:qpdh9s677ya8tnx7zdhfrn8qfyvy22wj4qa7nwqa5v']); - * console.log(mempool); - * } catch(error) { - * console.error(error) - * } - * })() - * - * mempool = { - * "success": true, - * "utxos": [ - * { - * "utxos": [ - * { - * "height": 604392, - * "tx_hash": "7774e449c5a3065144cefbc4c0c21e6b69c987f095856778ef9f45ddd8ae1a41", - * "fee": 24310 - * }, - * { - * "height": 630834, - * "tx_hash": "4fe60a51e0d8f5134bfd8e5f872d6e502d7f01b28a6afebb27f4438a4f638d53", - * "fee": 3000 - * } - * ], - * "address": "bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf" - * }, - * { - * "utxos": [], - * "address": "bitcoincash:qpdh9s677ya8tnx7zdhfrn8qfyvy22wj4qa7nwqa5v" - * } - * ] - * } - * - */ - async unconfirmed (address) { +/* + This library interacts with the ElectrumX bch-api REST API endpoints operated + by FullStack.cash +*/ +// Public npm libraries +import axios from 'axios' + +// Local libraries. +import Blockchain from './blockchain.js' +// const Address = require('./address') + +// let _this + +class ElectrumX { + constructor (config) { + this.restURL = config.restURL + this.authToken = config.authToken + // Use the shared axios instance if provided, otherwise fall back to axios + this.axios = config.axios || axios + + this.axiosOptions = { + headers: { + authorization: this.authToken + } + } + + // Encapsulate dependencies + this.blockchain = new Blockchain(config) + } + + /** + * @api Electrumx.utxo() utxo() + * @apiName ElectrumX Utxo + * @apiGroup ElectrumX + * @apiDescription Return a list of uxtos for an address. + * + * @apiExample Example usage: + * (async () => { + * try { + * let utxo = await bchjs.Electrumx.utxo('bitcoincash:qqh793x9au6ehvh7r2zflzguanlme760wuzehgzjh9'); + * console.log(utxo); + * } catch(error) { + * console.error(error) + * } + * })() + * + * utxo = { + * "success": true, + * "utxos": [ + * { + * "height": 602405, + * "tx_hash": "2b37bdb3b63dd0bca720437754a36671431a950e684b64c44ea910ea9d5297c7", + * "tx_pos": 0, + * "value": 1000 + * } + * ] + * } + * + * (async () => { + * try { + * let utxo = await bchjs.Electrumx.utxo(['bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf', 'bitcoincash:qpdh9s677ya8tnx7zdhfrn8qfyvy22wj4qa7nwqa5v']); + * console.log(utxo); + * } catch(error) { + * console.error(error) + * } + * })() + * + * utxos = { + * "success": true, + * "utxos": [ + * { + * "utxos": [ + * { + * "height": 604392, + * "tx_hash": "7774e449c5a3065144cefbc4c0c21e6b69c987f095856778ef9f45ddd8ae1a41", + * "tx_pos": 0, + * "value": 1000 + * }, + * { + * "height": 630834, + * "tx_hash": "4fe60a51e0d8f5134bfd8e5f872d6e502d7f01b28a6afebb27f4438a4f638d53", + * "tx_pos": 0, + * "value": 6000 + * } + * ], + * "address": "bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf" + * }, + * { + * "utxos": [], + * "address": "bitcoincash:qpdh9s677ya8tnx7zdhfrn8qfyvy22wj4qa7nwqa5v" + * } + * ] + * } + * + */ + async utxo (address) { + try { + // console.log(`electrumx.js/utxo() restURL: ${this.restURL}`) + + // Handle single address. + if (typeof address === 'string') { + const response = await this.axios.get( + `${this.restURL}fulcrum/utxos/${address}`, + this.axiosOptions + ) + return response.data + + // Handle array of addresses. + } else if (Array.isArray(address)) { + const response = await this.axios.post( + `${this.restURL}fulcrum/utxos`, + { + addresses: address + }, + this.axiosOptions + ) + + return response.data + } + + throw new Error('Input address must be a string or array of strings.') + } catch (error) { + if (error.response && error.response.data) { + const errorData = error.response.data + const errorMessage = typeof errorData === 'string' ? errorData : (errorData.error || errorData.message || JSON.stringify(errorData)) + throw new Error(errorMessage) + } else throw error + } + } + + /** + * @api Electrumx.balance() balance() + * @apiName ElectrumX Balance + * @apiGroup ElectrumX + * @apiDescription Return a list of balances for an address. + * + * @apiExample Example usage: + * (async () => { + * try { + * let balance = await bchjs.Electrumx.balance('bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf'); + * console.log(balance); + * } catch(error) { + * console.error(error) + * } + * })() + * + * balance = { + * "success": true, + * "balance": { + * "confirmed": 1000, + * "unconfirmed": 0 + * } + * } + * + * (async () => { + * try { + * let balance = await bchjs.Electrumx.balance(['bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf', 'bitcoincash:qpdh9s677ya8tnx7zdhfrn8qfyvy22wj4qa7nwqa5v']); + * console.log(balance); + * } catch(error) { + * console.error(error) + * } + * })() + * + * balance = { + * "success": true, + * "balances": [ + * { + * "balance": { + * "confirmed": 7000, + * "unconfirmed": 0 + * }, + * "address": "bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf" + * }, + * { + * "balance": { + * "confirmed": 0, + * "unconfirmed": 0 + * }, + * "address": "bitcoincash:qpdh9s677ya8tnx7zdhfrn8qfyvy22wj4qa7nwqa5v" + * } + * ] + * } + * + */ + async balance (address) { + try { + // Handle single address. + if (typeof address === 'string') { + const response = await this.axios.get( + `${this.restURL}fulcrum/balance/${address}`, + this.axiosOptions + ) + return response.data + + // Handle array of addresses. + } else if (Array.isArray(address)) { + const response = await this.axios.post( + `${this.restURL}fulcrum/balance`, + { + addresses: address + }, + this.axiosOptions + ) + + return response.data + } + + throw new Error('Input address must be a string or array of strings.') + } catch (error) { + if (error.response && error.response.data) { + const errorData = error.response.data + const errorMessage = typeof errorData === 'string' ? errorData : (errorData.error || errorData.message || JSON.stringify(errorData)) + throw new Error(errorMessage) + } else throw error + } + } + + /** + * @api Electrumx.transactions() transactions() + * @apiName ElectrumX Transactions + * @apiGroup ElectrumX + * @apiDescription Return a transaction history for an address. + * + * @apiExample Example usage: + * (async () => { + * try { + * let transactions = await bchjs.Electrumx.transactions('bitcoincash:qpdh9s677ya8tnx7zdhfrn8qfyvy22wj4qa7nwqa5v'); + * console.log(utxo); + * } catch(error) { + * console.error(error) + * } + * })() + * + * { + * "success": true, + * "transactions": [ + * { + * "height": 560430, + * "tx_hash": "3e1f3e882be9c03897eeb197224bf87f312be556a89f4308fabeeeabcf9bc851" + * }, + * { + * "height": 560534, + * "tx_hash": "4ebbeaac51ce141e262964e3a0ce11b96ca72c0dffe9b4127ce80135f503a280" + * } + * ] + * } + * + * (async () => { + * try { + * let transactions = await bchjs.Electrumx.transactions(['bitcoincash:qrl2nlsaayk6ekxn80pq0ks32dya8xfclyktem2mqj', 'bitcoincash:qpdh9s677ya8tnx7zdhfrn8qfyvy22wj4qa7nwqa5v']); + * console.log(utxo); + * } catch(error) { + * console.error(error) + * } + * })() + * + * transactions = { + * "success": true, + * "transactions": [ + * { + * "transactions": [ + * { + * "height": 631219, + * "tx_hash": "ae2daa01c8172545b5edd205ea438706bcb74e63d4084a26b9ff2a46d46dc97f" + * } + * ], + * "address": "bitcoincash:qrl2nlsaayk6ekxn80pq0ks32dya8xfclyktem2mqj" + * }, + * { + * "transactions": [ + * { + * "height": 560430, + * "tx_hash": "3e1f3e882be9c03897eeb197224bf87f312be556a89f4308fabeeeabcf9bc851" + * }, + * { + * "height": 560534, + * "tx_hash": "4ebbeaac51ce141e262964e3a0ce11b96ca72c0dffe9b4127ce80135f503a280" + * } + * ], + * "address": "bitcoincash:qpdh9s677ya8tnx7zdhfrn8qfyvy22wj4qa7nwqa5v" + * } + * ] + * } + * + */ + async transactions (address, usrObj = null, allTxs = false) { + try { + // Handle single address. + if (typeof address === 'string') { + const response = await this.axios.get( + `${this.restURL}fulcrum/transactions/${address}/${allTxs}`, + this.axiosOptions + ) + return response.data + + // Handle array of addresses. + } else if (Array.isArray(address)) { + const response = await this.axios.post( + `${this.restURL}fulcrum/transactions`, + { + addresses: address, + usrObj, // pass user data when making an internal call. + allTxs + }, + this.axiosOptions + ) + + return response.data + } + + throw new Error('Input address must be a string or array of strings.') + } catch (error) { + // console.log('Error in transactions(): ', error) + if (error.response && error.response.data) { + const errorData = error.response.data + const errorMessage = typeof errorData === 'string' ? errorData : (errorData.error || errorData.message || JSON.stringify(errorData)) + throw new Error(errorMessage) + } else throw error + } + } + + /** + * @api Electrumx.unconfirmed() unconfirmed() + * @apiName ElectrumX Unconfirmed + * @apiGroup ElectrumX + * @apiDescription Return a list of unconfirmed uxtos (mempool) for an address. + * + * @apiExample Example usage: + * (async () => { + * try { + * let mempool = await bchjs.Electrumx.unconfirmed('bitcoincash:qqh793x9au6ehvh7r2zflzguanlme760wuzehgzjh9'); + * console.log(mempool); + * } catch(error) { + * console.error(error) + * } + * })() + * + * mempool = { + * "success": true, + * "utxos": [ + * { + * "height": 602405, + * "tx_hash": "2b37bdb3b63dd0bca720437754a36671431a950e684b64c44ea910ea9d5297c7", + * "fee": 24310 + * } + * ] + * } + * + * (async () => { + * try { + * let mempool = await bchjs.Electrumx.unconfirmed(['bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf', 'bitcoincash:qpdh9s677ya8tnx7zdhfrn8qfyvy22wj4qa7nwqa5v']); + * console.log(mempool); + * } catch(error) { + * console.error(error) + * } + * })() + * + * mempool = { + * "success": true, + * "utxos": [ + * { + * "utxos": [ + * { + * "height": 604392, + * "tx_hash": "7774e449c5a3065144cefbc4c0c21e6b69c987f095856778ef9f45ddd8ae1a41", + * "fee": 24310 + * }, + * { + * "height": 630834, + * "tx_hash": "4fe60a51e0d8f5134bfd8e5f872d6e502d7f01b28a6afebb27f4438a4f638d53", + * "fee": 3000 + * } + * ], + * "address": "bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf" + * }, + * { + * "utxos": [], + * "address": "bitcoincash:qpdh9s677ya8tnx7zdhfrn8qfyvy22wj4qa7nwqa5v" + * } + * ] + * } + * + */ + async unconfirmed (address) { + try { + // Handle single address. + if (typeof address === 'string') { + const response = await this.axios.get( + `${this.restURL}fulcrum/unconfirmed/${address}`, + this.axiosOptions + ) + return response.data + + // Handle array of addresses. + } else if (Array.isArray(address)) { + const response = await this.axios.post( + `${this.restURL}fulcrum/unconfirmed`, + { + addresses: address + }, + this.axiosOptions + ) + + return response.data + } + + throw new Error('Input address must be a string or array of strings.') + } catch (error) { + if (error.response && error.response.data) { + const errorData = error.response.data + const errorMessage = typeof errorData === 'string' ? errorData : (errorData.error || errorData.message || JSON.stringify(errorData)) + throw new Error(errorMessage) + } else throw error + } + } + + /** + * @api Electrumx.blockHeader() blockHeader() + * @apiName ElectrumX Block headers + * @apiGroup ElectrumX + * @apiDescription Return block headers for a given height + * + * @apiExample Example usage: + * (async () => { + * try { + * let headers = await bchjs.Electrumx.blockHeaders(42); + * console.log(headers); + * } catch(error) { + * console.error(error) + * } + * })() + * + * headers = { + * "success": true, + * "headers": [ + * "010000008b52bbd72c2f49569059f559c1b1794de5192e4f7d6d2b03c7482bad0000000083e4f8a9d502ed0c419075c1abb5d56f878a2e9079e5612bfb76a2dc37d9c42741dd6849ffff001d2b909dd6", + * "01000000f528fac1bcb685d0cd6c792320af0300a5ce15d687c7149548904e31000000004e8985a786d864f21e9cbb7cbdf4bc9265fe681b7a0893ac55a8e919ce035c2f85de6849ffff001d385ccb7c" + * ] + * } + * + * (async () => { + * try { + * let headers = await bchjs.Electrumx.blockHeaders(42, 1); + * console.log(headers); + * } catch(error) { + * console.error(error) + * } + * })() + * + * headers = { + * "success": true, + * "headers": [ + * "010000008b52bbd72c2f49569059f559c1b1794de5192e4f7d6d2b03c7482bad0000000083e4f8a9d502ed0c419075c1abb5d56f878a2e9079e5612bfb76a2dc37d9c42741dd6849ffff001d2b909dd6" + * ] + * } + * + */ + async blockHeader (height, count = 1) { + try { + const response = await this.axios.get( + `${this.restURL}fulcrum/block/headers/${height}?count=${count}`, + this.axiosOptions + ) + return response.data + } catch (error) { + // console.log("error: ", error) + if (error.response && error.response.data) { + const errorData = error.response.data + if (errorData.error) { + throw new Error(errorData.error) + } else { + const errorMessage = typeof errorData === 'string' ? errorData : (errorData.message || JSON.stringify(errorData)) + throw new Error(errorMessage) + } + } else { + throw error + } + } + } + + /** + * @api Electrumx.txData() txData() + * @apiName ElectrumX txData + * @apiGroup ElectrumX + * @apiDescription Returns an object with transaction details of the TXID + * + * @apiExample Example usage: + * (async () => { + * try { + * let result = await bchjs.Electrumx.txData('4db095f34d632a4daf942142c291f1f2abb5ba2e1ccac919d85bdc2f671fb251') + * console.log(result); + * } catch(error) { + * console.error(error) + * } + * })() + * + * result = { + * "success": true, + * "details": { + * "blockhash": "0000000000000000002aaf94953da3b487317508ebd1003a1d75d6d6ec2e75cc", + * "blocktime": 1578327094, + * "confirmations": 31861, + * "hash": "4db095f34d632a4daf942142c291f1f2abb5ba2e1ccac919d85bdc2f671fb251", + * ... + * "vin": [ + * { + * "scriptSig": { + * ... + * "vout": [ + * { + * "n": 0, + * "scriptPubKey": { + * "addresses": [ + * "bitcoincash: pqvfecpwxvj53ayqfwkxtjaxsgpvnklcyg8xewk9hl" + * ], + * } + * ... + * } + * + * (async () => { + * try { + * let result = await bchjs.Electrumx.txData(['4db095f34d632a4daf942142c291f1f2abb5ba2e1ccac919d85bdc2f671fb251', '4db095f34d632a4daf942142c291f1f2abb5ba2e1ccac919d85bdc2f671fb251']) + * console.log(result); + * } catch(error) { + * console.error(error) + * } + * })() + * + * result = { + * "transactions": [ + * { + * "txid": "4db095f34d632a4daf942142c291f1f2abb5ba2e1ccac919d85bdc2f671fb251", + * "details": { + * "blockhash": "0000000000000000002aaf94953da3b487317508ebd1003a1d75d6d6ec2e75cc", + * "blocktime": 1578327094, + * "confirmations": 31861, + * "hash": "4db095f34d632a4daf942142c291f1f2abb5ba2e1ccac919d85bdc2f671fb251", + * ... + * } + * }, + * { + * "txid": "4db095f34d632a4daf942142c291f1f2abb5ba2e1ccac919d85bdc2f671fb251", + * "details": { + * "blockhash": "0000000000000000002aaf94953da3b487317508ebd1003a1d75d6d6ec2e75cc", + * "blocktime": 1578327094, + * ... + * } + * ] + * } + */ + async txData (txid) { try { - // Handle single address. - if (typeof address === 'string') { + // Handle single transaction. + if (typeof txid === 'string') { const response = await this.axios.get( - `${this.restURL}fulcrum/unconfirmed/${address}`, - this.axiosOptions - ) - return response.data - - // Handle array of addresses. - } else if (Array.isArray(address)) { - const response = await this.axios.post( - `${this.restURL}fulcrum/unconfirmed`, - { - addresses: address - }, - this.axiosOptions - ) - - return response.data - } - - throw new Error('Input address must be a string or array of strings.') - } catch (error) { - if (error.response && error.response.data) { - const errorData = error.response.data - const errorMessage = typeof errorData === 'string' ? errorData : (errorData.error || errorData.message || JSON.stringify(errorData)) - throw new Error(errorMessage) + `${this.restURL}fulcrum/tx/data/${txid}`, + this.axiosOptions + ) + return response.data + } else if (Array.isArray(txid)) { + const response = await this.axios.post( + `${this.restURL}fulcrum/tx/data`, + { + txids: txid + }, + this.axiosOptions + ) + + return response.data + } + + throw new Error('Input txId must be a string or array of strings.') + } catch (error) { + if (error.response && error.response.data) { + const errorData = error.response.data + const errorMessage = typeof errorData === 'string' ? errorData : (errorData.error || errorData.message || JSON.stringify(errorData)) + throw new Error(errorMessage) } else throw error } } /** - * @api Electrumx.blockHeader() blockHeader() - * @apiName ElectrumX Block headers - * @apiGroup ElectrumX - * @apiDescription Return block headers for a given height - * - * @apiExample Example usage: - * (async () => { - * try { - * let headers = await bchjs.Electrumx.blockHeaders(42); - * console.log(headers); - * } catch(error) { - * console.error(error) - * } - * })() - * - * headers = { - * "success": true, - * "headers": [ - * "010000008b52bbd72c2f49569059f559c1b1794de5192e4f7d6d2b03c7482bad0000000083e4f8a9d502ed0c419075c1abb5d56f878a2e9079e5612bfb76a2dc37d9c42741dd6849ffff001d2b909dd6", - * "01000000f528fac1bcb685d0cd6c792320af0300a5ce15d687c7149548904e31000000004e8985a786d864f21e9cbb7cbdf4bc9265fe681b7a0893ac55a8e919ce035c2f85de6849ffff001d385ccb7c" - * ] - * } - * - * (async () => { - * try { - * let headers = await bchjs.Electrumx.blockHeaders(42, 1); - * console.log(headers); - * } catch(error) { - * console.error(error) - * } - * })() - * - * headers = { - * "success": true, - * "headers": [ - * "010000008b52bbd72c2f49569059f559c1b1794de5192e4f7d6d2b03c7482bad0000000083e4f8a9d502ed0c419075c1abb5d56f878a2e9079e5612bfb76a2dc37d9c42741dd6849ffff001d2b909dd6" - * ] - * } - * - */ - async blockHeader (height, count = 1) { - try { - const response = await this.axios.get( - `${this.restURL}fulcrum/block/headers/${height}?count=${count}`, - this.axiosOptions - ) - return response.data - } catch (error) { - // console.log("error: ", error) - if (error.response && error.response.data) { - const errorData = error.response.data - if (errorData.error) { - throw new Error(errorData.error) - } else { - const errorMessage = typeof errorData === 'string' ? errorData : (errorData.message || JSON.stringify(errorData)) - throw new Error(errorMessage) - } - } else { - throw error - } - } - } - - /** - * @api Electrumx.txData() txData() - * @apiName ElectrumX txData + * @api Electrumx.merkleBranch() merkleBranch() + * @apiName ElectrumX merkleBranch * @apiGroup ElectrumX - * @apiDescription Returns an object with transaction details of the TXID + * @apiDescription Returns the merkle branch for a transaction. * * @apiExample Example usage: * (async () => { * try { - * let result = await bchjs.Electrumx.txData('4db095f34d632a4daf942142c291f1f2abb5ba2e1ccac919d85bdc2f671fb251') + * let result = await bchjs.Electrumx.merkleBranch('a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d', 617812) * console.log(result); * } catch(error) { * console.error(error) @@ -496,30 +597,18 @@ class ElectrumX { * * result = { * "success": true, - * "details": { - * "blockhash": "0000000000000000002aaf94953da3b487317508ebd1003a1d75d6d6ec2e75cc", - * "blocktime": 1578327094, - * "confirmations": 31861, - * "hash": "4db095f34d632a4daf942142c291f1f2abb5ba2e1ccac919d85bdc2f671fb251", - * ... - * "vin": [ - * { - * "scriptSig": { - * ... - * "vout": [ - * { - * "n": 0, - * "scriptPubKey": { - * "addresses": [ - * "bitcoincash: pqvfecpwxvj53ayqfwkxtjaxsgpvnklcyg8xewk9hl" - * ], - * } - * ... + * "merkle": { + * "block_height": 617812, + * "merkle": [ + * "..." + * ], + * "pos": 4 + * } * } * * (async () => { * try { - * let result = await bchjs.Electrumx.txData(['4db095f34d632a4daf942142c291f1f2abb5ba2e1ccac919d85bdc2f671fb251', '4db095f34d632a4daf942142c291f1f2abb5ba2e1ccac919d85bdc2f671fb251']) + * let result = await bchjs.Electrumx.merkleBranch([{ txid: 'a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d', height: 617812 }]) * console.log(result); * } catch(error) { * console.error(error) @@ -527,39 +616,34 @@ class ElectrumX { * })() * * result = { - * "transactions": [ - * { - * "txid": "4db095f34d632a4daf942142c291f1f2abb5ba2e1ccac919d85bdc2f671fb251", - * "details": { - * "blockhash": "0000000000000000002aaf94953da3b487317508ebd1003a1d75d6d6ec2e75cc", - * "blocktime": 1578327094, - * "confirmations": 31861, - * "hash": "4db095f34d632a4daf942142c291f1f2abb5ba2e1ccac919d85bdc2f671fb251", - * ... - * } - * }, + * "success": true, + * "branches": [ * { - * "txid": "4db095f34d632a4daf942142c291f1f2abb5ba2e1ccac919d85bdc2f671fb251", - * "details": { - * "blockhash": "0000000000000000002aaf94953da3b487317508ebd1003a1d75d6d6ec2e75cc", - * "blocktime": 1578327094, - * ... + * "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + * "height": 617812, + * "merkle": { + * "block_height": 617812, + * "merkle": [ + * "..." + * ], + * "pos": 4 + * } * } * ] * } */ - async txData (txid) { + async merkleBranch (txid, height) { try { // Handle single transaction. if (typeof txid === 'string') { const response = await this.axios.get( - `${this.restURL}fulcrum/tx/data/${txid}`, + `${this.restURL}fulcrum/tx/merkle/${txid}/${height}`, this.axiosOptions ) return response.data } else if (Array.isArray(txid)) { const response = await this.axios.post( - `${this.restURL}fulcrum/tx/data`, + `${this.restURL}fulcrum/tx/merkle`, { txids: txid }, @@ -569,7 +653,7 @@ class ElectrumX { return response.data } - throw new Error('Input txId must be a string or array of strings.') + throw new Error('Input txId must be a string or array of txid/height objects.') } catch (error) { if (error.response && error.response.data) { const errorData = error.response.data @@ -581,160 +665,160 @@ class ElectrumX { /** * @api Electrumx.broadcast() broadcast() - * @apiName ElectrumX Broadcast - * @apiGroup ElectrumX - * @apiDescription Broadcast a raw transaction and return the transaction ID on success or error on failure. - * - * (async () => { - * try { - * const txHex = "020000000265d13ef402840c8a51f39779afb7ae4d49e4b0a3c24a3d0e7742038f2c679667010000006441dd1dd72770cadede1a7fd0363574846c48468a398ddfa41a9677c74cac8d2652b682743725a3b08c6c2021a629011e11a264d9036e9d5311e35b5f4937ca7b4e4121020797d8fd4d2fa6fd7cdeabe2526bfea2b90525d6e8ad506ec4ee3c53885aa309ffffffff65d13ef402840c8a51f39779afb7ae4d49e4b0a3c24a3d0e7742038f2c679667000000006441347d7f218c11c04487c1ad8baac28928fb10e5054cd4494b94d078cfa04ccf68e064fb188127ff656c0b98e9ce87f036d183925d0d0860605877d61e90375f774121028a53f95eb631b460854fc836b2e5d31cad16364b4dc3d970babfbdcc3f2e4954ffffffff035ac355000000000017a914189ce02e332548f4804bac65cba68202c9dbf822878dfd0800000000001976a914285bb350881b21ac89724c6fb6dc914d096cd53b88acf9ef3100000000001976a91445f1f1c4a9b9419a5088a3e9c24a293d7a150e6488ac00000000" - * let result = await bchjs.Electrumx.broadcast(txHex) - * console.log(result); - * } catch(error) { - * console.error(error) - * } - * })() - * - * result = { - * "success": true, - * "txid": "..." - * } - */ - async broadcast (txHex) { - try { - if (typeof txHex === 'string') { - const response = await this.axios.post( - `${this.restURL}fulcrum/tx/broadcast`, - { txHex }, - this.axiosOptions - ) - - return response.data - } - - throw new Error('Input txHex must be a string.') - } catch (error) { - if (error.response && error.response.data) { - const errorData = error.response.data - const errorMessage = typeof errorData === 'string' ? errorData : (errorData.error || errorData.message || JSON.stringify(errorData)) - throw new Error(errorMessage) - } else throw error - } - } - - /** - * @api Electrumx.sortConfTxs() sortConfTxs() - * @apiName ElectrumX sortConfTxs - * @apiGroup ElectrumX - * @apiDescription Sort the output of Electrum.transactions() by block height. - * - * A simple sort function for the output of Electrum.transactions(). Ignores - * unconfirmed transactions. - * - * Sorts in 'DESCENDING' order by default, or 'ASCENDING' can be specified. - * Descending makes the first element the newest (largest block height). - * - * @apiExample Example usage: - * (async () => { - * const txs = await bchjs.Electrumx.transactions('bitcoincash:qpdh9s677ya8tnx7zdhfrn8qfyvy22wj4qa7nwqa5v') - * const sortedTxs = bchjs.Electrumx.sortConfTxs(txs.transactions, 'ASCENDING') - * console.log(sortedTxs) - * })() - * - * // [ - * // { - * // "height": 560430, - * // "tx_hash": "3e1f3e882be9c03897eeb197224bf87f312be556a89f4308fabeeeabcf9bc851" - * // }, - * // { - * // "height": 560534, - * // "tx_hash": "4ebbeaac51ce141e262964e3a0ce11b96ca72c0dffe9b4127ce80135f503a280" - * // } - * // ] - */ - // Sort confirmed Transactions by the block height - sortConfTxs (txs, sortingOrder = 'DESCENDING') { - try { - // console.log(`sortConfTxs txs: ${JSON.stringify(txs, null, 2)}`) - - // Filter out unconfirmed transactions, with a height of 0 or less. - txs = txs.filter(elem => elem.height > 0) - - if (sortingOrder === 'DESCENDING') { - // console.log('Sorting in descending order') - return txs.sort((a, b) => { - // console.log(`descending b.height: ${b.height}, a.height: ${a.height}`) - return b.height - a.height - }) - } - - // console.log('Sorting in ascending order') - return txs.sort((a, b) => { - // console.log(`ascending b.height: ${b.height}, a.height: ${a.height}`) - return a.height - b.height - }) - } catch (err) { - console.log('Error in util.js/sortConfTxs()') - throw err - } - } - - /** - * @api Electrumx.sortAllTxs() sortAllTxs() - * @apiName ElectrumX sortAllTxs - * @apiGroup ElectrumX - * @apiDescription Sort the output of Electrum.transactions() by block height. - * - * A simple sort function for the output of Electrum.transactions(). - * Assumes that unconfirmed transactions will make it into the next block. Any - * unconfirmed transactions have their block height with the height of the next - * block. Returns a Promise. - * - * Sorts in 'ASCENDING' order by default, or 'DESCENDING' can be specified. - * - * @apiExample Example usage: - * (async () => { - * const txs = await bchjs.Electrumx.transactions('bitcoincash:qpdh9s677ya8tnx7zdhfrn8qfyvy22wj4qa7nwqa5v') - * const sortedTxs = await bchjs.Electrumx.sortAllTxs(txs.transactions, 'ASCENDING') - * console.log(sortedTxs) - * })() - * - * // [ - * // { - * // "height": 560430, - * // "tx_hash": "3e1f3e882be9c03897eeb197224bf87f312be556a89f4308fabeeeabcf9bc851" - * // }, - * // { - * // "height": 560534, - * // "tx_hash": "4ebbeaac51ce141e262964e3a0ce11b96ca72c0dffe9b4127ce80135f503a280" - * // } - * // ] - */ - // Substitute zero-conf txs with the current block-height + 1 - async sortAllTxs (txs, sortingOrder = 'DESCENDING') { - try { - // console.log(`sortingOrder: ${sortingOrder}`) - - // Calculate the height of the next block - const nextBlock = (await this.blockchain.getBlockCount()) + 1 - - // Replace the height of any zero-conf transactions with the height of - // the next block. - const modifiedTxs = txs.map(elem => { - if (elem.height <= 0) elem.height = nextBlock - return elem - }) - // console.log(`modifiedTxs: ${JSON.stringify(modifiedTxs, null, 2)}`) - - // Sort the modified array of transactions. - return this.sortConfTxs(modifiedTxs, sortingOrder) - } catch (err) { - console.log('Error in util.js/sort0ConfTxs') - console.log('electrumx.js restURL: ', this.restURL) - - throw err - } - } -} - -export default ElectrumX + * @apiName ElectrumX Broadcast + * @apiGroup ElectrumX + * @apiDescription Broadcast a raw transaction and return the transaction ID on success or error on failure. + * + * (async () => { + * try { + * const txHex = "020000000265d13ef402840c8a51f39779afb7ae4d49e4b0a3c24a3d0e7742038f2c679667010000006441dd1dd72770cadede1a7fd0363574846c48468a398ddfa41a9677c74cac8d2652b682743725a3b08c6c2021a629011e11a264d9036e9d5311e35b5f4937ca7b4e4121020797d8fd4d2fa6fd7cdeabe2526bfea2b90525d6e8ad506ec4ee3c53885aa309ffffffff65d13ef402840c8a51f39779afb7ae4d49e4b0a3c24a3d0e7742038f2c679667000000006441347d7f218c11c04487c1ad8baac28928fb10e5054cd4494b94d078cfa04ccf68e064fb188127ff656c0b98e9ce87f036d183925d0d0860605877d61e90375f774121028a53f95eb631b460854fc836b2e5d31cad16364b4dc3d970babfbdcc3f2e4954ffffffff035ac355000000000017a914189ce02e332548f4804bac65cba68202c9dbf822878dfd0800000000001976a914285bb350881b21ac89724c6fb6dc914d096cd53b88acf9ef3100000000001976a91445f1f1c4a9b9419a5088a3e9c24a293d7a150e6488ac00000000" + * let result = await bchjs.Electrumx.broadcast(txHex) + * console.log(result); + * } catch(error) { + * console.error(error) + * } + * })() + * + * result = { + * "success": true, + * "txid": "..." + * } + */ + async broadcast (txHex) { + try { + if (typeof txHex === 'string') { + const response = await this.axios.post( + `${this.restURL}fulcrum/tx/broadcast`, + { txHex }, + this.axiosOptions + ) + + return response.data + } + + throw new Error('Input txHex must be a string.') + } catch (error) { + if (error.response && error.response.data) { + const errorData = error.response.data + const errorMessage = typeof errorData === 'string' ? errorData : (errorData.error || errorData.message || JSON.stringify(errorData)) + throw new Error(errorMessage) + } else throw error + } + } + + /** + * @api Electrumx.sortConfTxs() sortConfTxs() + * @apiName ElectrumX sortConfTxs + * @apiGroup ElectrumX + * @apiDescription Sort the output of Electrum.transactions() by block height. + * + * A simple sort function for the output of Electrum.transactions(). Ignores + * unconfirmed transactions. + * + * Sorts in 'DESCENDING' order by default, or 'ASCENDING' can be specified. + * Descending makes the first element the newest (largest block height). + * + * @apiExample Example usage: + * (async () => { + * const txs = await bchjs.Electrumx.transactions('bitcoincash:qpdh9s677ya8tnx7zdhfrn8qfyvy22wj4qa7nwqa5v') + * const sortedTxs = bchjs.Electrumx.sortConfTxs(txs.transactions, 'ASCENDING') + * console.log(sortedTxs) + * })() + * + * // [ + * // { + * // "height": 560430, + * // "tx_hash": "3e1f3e882be9c03897eeb197224bf87f312be556a89f4308fabeeeabcf9bc851" + * // }, + * // { + * // "height": 560534, + * // "tx_hash": "4ebbeaac51ce141e262964e3a0ce11b96ca72c0dffe9b4127ce80135f503a280" + * // } + * // ] + */ + // Sort confirmed Transactions by the block height + sortConfTxs (txs, sortingOrder = 'DESCENDING') { + try { + // console.log(`sortConfTxs txs: ${JSON.stringify(txs, null, 2)}`) + + // Filter out unconfirmed transactions, with a height of 0 or less. + txs = txs.filter(elem => elem.height > 0) + + if (sortingOrder === 'DESCENDING') { + // console.log('Sorting in descending order') + return txs.sort((a, b) => { + // console.log(`descending b.height: ${b.height}, a.height: ${a.height}`) + return b.height - a.height + }) + } + + // console.log('Sorting in ascending order') + return txs.sort((a, b) => { + // console.log(`ascending b.height: ${b.height}, a.height: ${a.height}`) + return a.height - b.height + }) + } catch (err) { + console.log('Error in util.js/sortConfTxs()') + throw err + } + } + + /** + * @api Electrumx.sortAllTxs() sortAllTxs() + * @apiName ElectrumX sortAllTxs + * @apiGroup ElectrumX + * @apiDescription Sort the output of Electrum.transactions() by block height. + * + * A simple sort function for the output of Electrum.transactions(). + * Assumes that unconfirmed transactions will make it into the next block. Any + * unconfirmed transactions have their block height with the height of the next + * block. Returns a Promise. + * + * Sorts in 'ASCENDING' order by default, or 'DESCENDING' can be specified. + * + * @apiExample Example usage: + * (async () => { + * const txs = await bchjs.Electrumx.transactions('bitcoincash:qpdh9s677ya8tnx7zdhfrn8qfyvy22wj4qa7nwqa5v') + * const sortedTxs = await bchjs.Electrumx.sortAllTxs(txs.transactions, 'ASCENDING') + * console.log(sortedTxs) + * })() + * + * // [ + * // { + * // "height": 560430, + * // "tx_hash": "3e1f3e882be9c03897eeb197224bf87f312be556a89f4308fabeeeabcf9bc851" + * // }, + * // { + * // "height": 560534, + * // "tx_hash": "4ebbeaac51ce141e262964e3a0ce11b96ca72c0dffe9b4127ce80135f503a280" + * // } + * // ] + */ + // Substitute zero-conf txs with the current block-height + 1 + async sortAllTxs (txs, sortingOrder = 'DESCENDING') { + try { + // console.log(`sortingOrder: ${sortingOrder}`) + + // Calculate the height of the next block + const nextBlock = (await this.blockchain.getBlockCount()) + 1 + + // Replace the height of any zero-conf transactions with the height of + // the next block. + const modifiedTxs = txs.map(elem => { + if (elem.height <= 0) elem.height = nextBlock + return elem + }) + // console.log(`modifiedTxs: ${JSON.stringify(modifiedTxs, null, 2)}`) + + // Sort the modified array of transactions. + return this.sortConfTxs(modifiedTxs, sortingOrder) + } catch (err) { + console.log('Error in util.js/sort0ConfTxs') + console.log('electrumx.js restURL: ', this.restURL) + + throw err + } + } +} + +export default ElectrumX diff --git a/test/integration/electrumx.js b/test/integration/electrumx.js index 17f1142..a9c13cb 100644 --- a/test/integration/electrumx.js +++ b/test/integration/electrumx.js @@ -1,393 +1,443 @@ -import chai from 'chai' -import sinon from 'sinon' - -import BCHJS from '../../src/bch-js.js' -const assert = chai.assert -const bchjs = new BCHJS() - -describe('#ElectrumX', () => { - let sandbox - - beforeEach(async () => { - sandbox = sinon.createSandbox() - - if (process.env.IS_USING_FREE_TIER) await sleep(1500) - }) - - afterEach(() => sandbox.restore()) - - describe('#utxo', () => { - it('should GET utxos for a single address', async () => { - const addr = 'bitcoincash:qqh793x9au6ehvh7r2zflzguanlme760wuzehgzjh9' - - const result = await bchjs.Electrumx.utxo(addr) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.property(result, 'success') - assert.equal(result.success, true) - - assert.property(result, 'utxos') - assert.isArray(result.utxos) - - assert.property(result.utxos[0], 'height') - assert.property(result.utxos[0], 'tx_hash') - assert.property(result.utxos[0], 'tx_pos') - assert.property(result.utxos[0], 'value') - }) - - it('should POST request for UTXOs for an array of addresses', async () => { - const addr = [ - 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf', - 'bitcoincash:qpdh9s677ya8tnx7zdhfrn8qfyvy22wj4qa7nwqa5v' - ] - - const result = await bchjs.Electrumx.utxo(addr) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.property(result, 'success') - assert.equal(result.success, true) - - assert.property(result, 'utxos') - assert.isArray(result.utxos) - - assert.property(result.utxos[0], 'utxos') - assert.isArray(result.utxos[0].utxos) - assert.property(result.utxos[0], 'address') - - assert.property(result.utxos[0].utxos[0], 'height') - assert.property(result.utxos[0].utxos[0], 'tx_hash') - assert.property(result.utxos[0].utxos[0], 'tx_pos') - assert.property(result.utxos[0].utxos[0], 'value') - }) - - it('should throw error on array size rate limit', async () => { - try { - const addr = [] - for (let i = 0; i < 25; i++) { - addr.push('bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf') - } - - // const result = await bchjs.Electrumx.utxo(addr) - await bchjs.Electrumx.utxo(addr) - - // console.log(`result: ${util.inspect(result)}`) - assert.equal(true, false, 'Unexpected result!') - } catch (err) { - assert.instanceOf(err, Error) - assert.include(err.message, 'Array too large') - } - }) - }) - - describe('#balance', () => { - it('should GET balance for a single address', async () => { - const addr = 'bitcoincash:qqh793x9au6ehvh7r2zflzguanlme760wuzehgzjh9' - - const result = await bchjs.Electrumx.balance(addr) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.property(result, 'success') - assert.equal(result.success, true) - - assert.property(result, 'balance') - assert.property(result.balance, 'confirmed') - assert.property(result.balance, 'unconfirmed') - }) - - it('should POST request for balances for an array of addresses', async () => { - const addr = [ - 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf', - 'bitcoincash:qpdh9s677ya8tnx7zdhfrn8qfyvy22wj4qa7nwqa5v' - ] - - const result = await bchjs.Electrumx.balance(addr) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.property(result, 'success') - assert.equal(result.success, true) - - assert.property(result, 'balances') - assert.isArray(result.balances) - - assert.property(result.balances[0], 'address') - assert.property(result.balances[0], 'balance') - assert.property(result.balances[0].balance, 'confirmed') - assert.property(result.balances[0].balance, 'unconfirmed') - }) - - it('should throw error on array size rate limit', async () => { - try { - const addr = [] - for (let i = 0; i < 25; i++) { - addr.push('bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf') - } - - // const result = await bchjs.Electrumx.balance(addr) - // console.log(`result: ${util.inspect(result)}`) - - await bchjs.Electrumx.balance(addr) - - assert.equal(true, false, 'Unexpected result!') - } catch (err) { - assert.instanceOf(err, Error) - assert.include(err.message, 'Array too large') - } - }) - }) - - describe('#transactions', () => { - it('should GET transaction history for a single address', async () => { - const addr = 'bitcoincash:qpdh9s677ya8tnx7zdhfrn8qfyvy22wj4qa7nwqa5v' - - const result = await bchjs.Electrumx.transactions(addr) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.property(result, 'success') - assert.equal(result.success, true) - - assert.property(result, 'transactions') - assert.isArray(result.transactions) - assert.property(result.transactions[0], 'height') - assert.property(result.transactions[0], 'tx_hash') - }) - - it('should POST request for transaction history for an array of addresses', async () => { - const addr = [ - 'bitcoincash:qrl2nlsaayk6ekxn80pq0ks32dya8xfclyktem2mqj', - 'bitcoincash:qpdh9s677ya8tnx7zdhfrn8qfyvy22wj4qa7nwqa5v' - ] - - const result = await bchjs.Electrumx.transactions(addr) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.property(result, 'success') - assert.equal(result.success, true) - - assert.property(result, 'transactions') - assert.isArray(result.transactions) - - assert.property(result.transactions[0], 'address') - assert.property(result.transactions[0], 'transactions') - assert.isArray(result.transactions[0].transactions) - assert.property(result.transactions[0].transactions[0], 'height') - assert.property(result.transactions[0].transactions[0], 'tx_hash') - }) - - it('should throw error on array size rate limit', async () => { - try { - const addr = [] - for (let i = 0; i < 25; i++) { - addr.push('bitcoincash:qrehqueqhw629p6e57994436w730t4rzasnly00ht0') - } - - // const result = await bchjs.Electrumx.transactions(addr) - // console.log(`result: ${util.inspect(result)}`) - - await bchjs.Electrumx.transactions(addr) - - assert.equal(true, false, 'Unexpected result!') - } catch (err) { - assert.instanceOf(err, Error) - assert.include(err.message, 'Array too large') - } - }) - }) - - describe('#unconfirmed', () => { - // These tests won't work because unconfirmed transactions are transient in nature. - /* - it(`should GET unconfirmed UTXOs (mempool) for a single address`, async () => { - const addr = "bitcoincash:qqy6qwk3wpne95hhv8uzwr4cn8m7c06cqgchl77dnv" - - const result = await bchjs.Electrumx.unconfirmed(addr) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.property(result, "success") - assert.equal(result.success, true) - - assert.property(result, "utxos") - assert.isArray(result.utxos) - - assert.property(result.utxos[0], "height") - assert.property(result.utxos[0], "tx_hash") - assert.property(result.utxos[0], "fee") - }) - - it(`should POST request for unconfirmed UTXOs (mempool) for an array of addresses`, async () => { - const addr = [ - "bitcoincash:qqy6qwk3wpne95hhv8uzwr4cn8m7c06cqgchl77dnv", - "bitcoincash:qpyrtsl9msdu2klgfpcmn2v5r22w9rk24g8pal74ts" - ] - - const result = await bchjs.Electrumx.unconfirmed(addr) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.property(result, "success") - assert.equal(result.success, true) - - assert.property(result, "utxos") - assert.isArray(result.utxos) - - assert.property(result.utxos[0], "utxos") - assert.isArray(result.utxos[0].utxos) - assert.property(result.utxos[0], "address") - - assert.property(result.utxos[0].utxos[0], "height") - assert.property(result.utxos[0].utxos[0], "tx_hash") - assert.property(result.utxos[0].utxos[0], "fee") - }) - */ - - it('should throw error on array size rate limit', async () => { - try { - const addr = [] - for (let i = 0; i < 25; i++) { - addr.push('bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf') - } - - await bchjs.Electrumx.unconfirmed(addr) - - // console.log(`result: ${util.inspect(result)}`) - assert.equal(true, false, 'Unexpected result!') - } catch (err) { - assert.instanceOf(err, Error) - assert.include(err.message, 'Array too large') - } - }) - }) - - describe('#blockHeader', () => { - it('should GET block headers for given height and count', async () => { - const height = 42 - const count = 2 - - const result = await bchjs.Electrumx.blockHeader(height, count) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - assert.property(result, 'success') - assert.equal(result.success, true) - - assert.property(result, 'headers') - assert.isArray(result.headers) - assert.equal(result.headers.length, 2) - }) - - it('should GET block headers for given height with default count = 1', async () => { - const height = 42 - - const result = await bchjs.Electrumx.blockHeader(height) - assert.property(result, 'success') - assert.equal(result.success, true) - - assert.property(result, 'headers') - assert.isArray(result.headers) - assert.equal(result.headers.length, 1) +import chai from 'chai' +import sinon from 'sinon' + +import BCHJS from '../../src/bch-js.js' +const assert = chai.assert +const bchjs = new BCHJS() + +describe('#ElectrumX', () => { + let sandbox + + beforeEach(async () => { + sandbox = sinon.createSandbox() + + if (process.env.IS_USING_FREE_TIER) await sleep(1500) + }) + + afterEach(() => sandbox.restore()) + + describe('#utxo', () => { + it('should GET utxos for a single address', async () => { + const addr = 'bitcoincash:qqh793x9au6ehvh7r2zflzguanlme760wuzehgzjh9' + + const result = await bchjs.Electrumx.utxo(addr) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.property(result, 'success') + assert.equal(result.success, true) + + assert.property(result, 'utxos') + assert.isArray(result.utxos) + + assert.property(result.utxos[0], 'height') + assert.property(result.utxos[0], 'tx_hash') + assert.property(result.utxos[0], 'tx_pos') + assert.property(result.utxos[0], 'value') + }) + + it('should POST request for UTXOs for an array of addresses', async () => { + const addr = [ + 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf', + 'bitcoincash:qpdh9s677ya8tnx7zdhfrn8qfyvy22wj4qa7nwqa5v' + ] + + const result = await bchjs.Electrumx.utxo(addr) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.property(result, 'success') + assert.equal(result.success, true) + + assert.property(result, 'utxos') + assert.isArray(result.utxos) + + assert.property(result.utxos[0], 'utxos') + assert.isArray(result.utxos[0].utxos) + assert.property(result.utxos[0], 'address') + + assert.property(result.utxos[0].utxos[0], 'height') + assert.property(result.utxos[0].utxos[0], 'tx_hash') + assert.property(result.utxos[0].utxos[0], 'tx_pos') + assert.property(result.utxos[0].utxos[0], 'value') + }) + + it('should throw error on array size rate limit', async () => { + try { + const addr = [] + for (let i = 0; i < 25; i++) { + addr.push('bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf') + } + + // const result = await bchjs.Electrumx.utxo(addr) + await bchjs.Electrumx.utxo(addr) + + // console.log(`result: ${util.inspect(result)}`) + assert.equal(true, false, 'Unexpected result!') + } catch (err) { + assert.instanceOf(err, Error) + assert.include(err.message, 'Array too large') + } + }) + }) + + describe('#balance', () => { + it('should GET balance for a single address', async () => { + const addr = 'bitcoincash:qqh793x9au6ehvh7r2zflzguanlme760wuzehgzjh9' + + const result = await bchjs.Electrumx.balance(addr) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.property(result, 'success') + assert.equal(result.success, true) + + assert.property(result, 'balance') + assert.property(result.balance, 'confirmed') + assert.property(result.balance, 'unconfirmed') + }) + + it('should POST request for balances for an array of addresses', async () => { + const addr = [ + 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf', + 'bitcoincash:qpdh9s677ya8tnx7zdhfrn8qfyvy22wj4qa7nwqa5v' + ] + + const result = await bchjs.Electrumx.balance(addr) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.property(result, 'success') + assert.equal(result.success, true) + + assert.property(result, 'balances') + assert.isArray(result.balances) + + assert.property(result.balances[0], 'address') + assert.property(result.balances[0], 'balance') + assert.property(result.balances[0].balance, 'confirmed') + assert.property(result.balances[0].balance, 'unconfirmed') + }) + + it('should throw error on array size rate limit', async () => { + try { + const addr = [] + for (let i = 0; i < 25; i++) { + addr.push('bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf') + } + + // const result = await bchjs.Electrumx.balance(addr) + // console.log(`result: ${util.inspect(result)}`) + + await bchjs.Electrumx.balance(addr) + + assert.equal(true, false, 'Unexpected result!') + } catch (err) { + assert.instanceOf(err, Error) + assert.include(err.message, 'Array too large') + } + }) + }) + + describe('#transactions', () => { + it('should GET transaction history for a single address', async () => { + const addr = 'bitcoincash:qpdh9s677ya8tnx7zdhfrn8qfyvy22wj4qa7nwqa5v' + + const result = await bchjs.Electrumx.transactions(addr) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.property(result, 'success') + assert.equal(result.success, true) + + assert.property(result, 'transactions') + assert.isArray(result.transactions) + assert.property(result.transactions[0], 'height') + assert.property(result.transactions[0], 'tx_hash') + }) + + it('should POST request for transaction history for an array of addresses', async () => { + const addr = [ + 'bitcoincash:qrl2nlsaayk6ekxn80pq0ks32dya8xfclyktem2mqj', + 'bitcoincash:qpdh9s677ya8tnx7zdhfrn8qfyvy22wj4qa7nwqa5v' + ] + + const result = await bchjs.Electrumx.transactions(addr) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.property(result, 'success') + assert.equal(result.success, true) + + assert.property(result, 'transactions') + assert.isArray(result.transactions) + + assert.property(result.transactions[0], 'address') + assert.property(result.transactions[0], 'transactions') + assert.isArray(result.transactions[0].transactions) + assert.property(result.transactions[0].transactions[0], 'height') + assert.property(result.transactions[0].transactions[0], 'tx_hash') + }) + + it('should throw error on array size rate limit', async () => { + try { + const addr = [] + for (let i = 0; i < 25; i++) { + addr.push('bitcoincash:qrehqueqhw629p6e57994436w730t4rzasnly00ht0') + } + + // const result = await bchjs.Electrumx.transactions(addr) + // console.log(`result: ${util.inspect(result)}`) + + await bchjs.Electrumx.transactions(addr) + + assert.equal(true, false, 'Unexpected result!') + } catch (err) { + assert.instanceOf(err, Error) + assert.include(err.message, 'Array too large') + } + }) + }) + + describe('#unconfirmed', () => { + // These tests won't work because unconfirmed transactions are transient in nature. + /* + it(`should GET unconfirmed UTXOs (mempool) for a single address`, async () => { + const addr = "bitcoincash:qqy6qwk3wpne95hhv8uzwr4cn8m7c06cqgchl77dnv" + + const result = await bchjs.Electrumx.unconfirmed(addr) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.property(result, "success") + assert.equal(result.success, true) + + assert.property(result, "utxos") + assert.isArray(result.utxos) + + assert.property(result.utxos[0], "height") + assert.property(result.utxos[0], "tx_hash") + assert.property(result.utxos[0], "fee") + }) + + it(`should POST request for unconfirmed UTXOs (mempool) for an array of addresses`, async () => { + const addr = [ + "bitcoincash:qqy6qwk3wpne95hhv8uzwr4cn8m7c06cqgchl77dnv", + "bitcoincash:qpyrtsl9msdu2klgfpcmn2v5r22w9rk24g8pal74ts" + ] + + const result = await bchjs.Electrumx.unconfirmed(addr) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.property(result, "success") + assert.equal(result.success, true) + + assert.property(result, "utxos") + assert.isArray(result.utxos) + + assert.property(result.utxos[0], "utxos") + assert.isArray(result.utxos[0].utxos) + assert.property(result.utxos[0], "address") + + assert.property(result.utxos[0].utxos[0], "height") + assert.property(result.utxos[0].utxos[0], "tx_hash") + assert.property(result.utxos[0].utxos[0], "fee") + }) + */ + + it('should throw error on array size rate limit', async () => { + try { + const addr = [] + for (let i = 0; i < 25; i++) { + addr.push('bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf') + } + + await bchjs.Electrumx.unconfirmed(addr) + + // console.log(`result: ${util.inspect(result)}`) + assert.equal(true, false, 'Unexpected result!') + } catch (err) { + assert.instanceOf(err, Error) + assert.include(err.message, 'Array too large') + } + }) + }) + + describe('#blockHeader', () => { + it('should GET block headers for given height and count', async () => { + const height = 42 + const count = 2 + + const result = await bchjs.Electrumx.blockHeader(height, count) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + assert.property(result, 'success') + assert.equal(result.success, true) + + assert.property(result, 'headers') + assert.isArray(result.headers) + assert.equal(result.headers.length, 2) + }) + + it('should GET block headers for given height with default count = 1', async () => { + const height = 42 + + const result = await bchjs.Electrumx.blockHeader(height) + assert.property(result, 'success') + assert.equal(result.success, true) + + assert.property(result, 'headers') + assert.isArray(result.headers) + assert.equal(result.headers.length, 1) + }) + }) + + describe('#txData', () => { + it('should GET details for a single transaction', async () => { + const txid = + '4db095f34d632a4daf942142c291f1f2abb5ba2e1ccac919d85bdc2f671fb251' + + const result = await bchjs.Electrumx.txData(txid) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.property(result, 'success') + assert.equal(result.success, true) + + assert.property(result, 'details') + assert.isObject(result.details) + + assert.property(result.details, 'blockhash') + assert.property(result.details, 'hash') + assert.property(result.details, 'hex') + assert.property(result.details, 'vin') + assert.property(result.details, 'vout') + assert.equal(result.details.hash, txid) + }) + + it('should POST details for an array of transactions', async () => { + const txids = [ + '4db095f34d632a4daf942142c291f1f2abb5ba2e1ccac919d85bdc2f671fb251', + '4db095f34d632a4daf942142c291f1f2abb5ba2e1ccac919d85bdc2f671fb251' + ] + + const result = await bchjs.Electrumx.txData(txids) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.property(result, 'success') + assert.equal(result.success, true) + + assert.property(result, 'transactions') + assert.isArray(result.transactions) + + assert.property(result.transactions[0], 'txid') + assert.property(result.transactions[0], 'details') + + assert.property(result.transactions[0].details, 'blockhash') + assert.property(result.transactions[0].details, 'hash') + assert.property(result.transactions[0].details, 'hex') + assert.property(result.transactions[0].details, 'vin') + assert.property(result.transactions[0].details, 'vout') + }) + + it('should throw error on array size rate limit', async () => { + try { + const txids = [] + for (let i = 0; i < 25; i++) { + txids.push( + '4db095f34d632a4daf942142c291f1f2abb5ba2e1ccac919d85bdc2f671fb251' + ) + } + + await bchjs.Electrumx.txData(txids) + + // console.log(`result: ${util.inspect(result)}`) + assert.equal(true, false, 'Unexpected result!') + } catch (err) { + assert.instanceOf(err, Error) + assert.include(err.message, 'Array too large') + } }) }) - describe('#txData', () => { - it('should GET details for a single transaction', async () => { + describe('#merkleBranch', () => { + it('should GET merkle branch for a single transaction', async () => { const txid = - '4db095f34d632a4daf942142c291f1f2abb5ba2e1ccac919d85bdc2f671fb251' + 'a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d' + const height = 617812 - const result = await bchjs.Electrumx.txData(txid) + const result = await bchjs.Electrumx.merkleBranch(txid, height) // console.log(`result: ${JSON.stringify(result, null, 2)}`) assert.property(result, 'success') assert.equal(result.success, true) - assert.property(result, 'details') - assert.isObject(result.details) - - assert.property(result.details, 'blockhash') - assert.property(result.details, 'hash') - assert.property(result.details, 'hex') - assert.property(result.details, 'vin') - assert.property(result.details, 'vout') - assert.equal(result.details.hash, txid) + assert.property(result, 'merkle') + assert.isObject(result.merkle) + assert.property(result.merkle, 'block_height') + assert.property(result.merkle, 'merkle') + assert.property(result.merkle, 'pos') + assert.isArray(result.merkle.merkle) }) - it('should POST details for an array of transactions', async () => { + it('should POST merkle branch for an array of transactions', async () => { const txids = [ - '4db095f34d632a4daf942142c291f1f2abb5ba2e1ccac919d85bdc2f671fb251', - '4db095f34d632a4daf942142c291f1f2abb5ba2e1ccac919d85bdc2f671fb251' + { + txid: 'a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d', + height: 617812 + }, + { + txid: 'a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d', + height: 617812 + } ] - const result = await bchjs.Electrumx.txData(txids) + const result = await bchjs.Electrumx.merkleBranch(txids) // console.log(`result: ${JSON.stringify(result, null, 2)}`) assert.property(result, 'success') assert.equal(result.success, true) - assert.property(result, 'transactions') - assert.isArray(result.transactions) - - assert.property(result.transactions[0], 'txid') - assert.property(result.transactions[0], 'details') + assert.property(result, 'branches') + assert.isArray(result.branches) - assert.property(result.transactions[0].details, 'blockhash') - assert.property(result.transactions[0].details, 'hash') - assert.property(result.transactions[0].details, 'hex') - assert.property(result.transactions[0].details, 'vin') - assert.property(result.transactions[0].details, 'vout') - }) - - it('should throw error on array size rate limit', async () => { - try { - const txids = [] - for (let i = 0; i < 25; i++) { - txids.push( - '4db095f34d632a4daf942142c291f1f2abb5ba2e1ccac919d85bdc2f671fb251' - ) - } - - await bchjs.Electrumx.txData(txids) - - // console.log(`result: ${util.inspect(result)}`) - assert.equal(true, false, 'Unexpected result!') - } catch (err) { - assert.instanceOf(err, Error) - assert.include(err.message, 'Array too large') - } + assert.property(result.branches[0], 'txid') + assert.property(result.branches[0], 'height') + assert.property(result.branches[0], 'merkle') + assert.property(result.branches[0].merkle, 'block_height') + assert.property(result.branches[0].merkle, 'merkle') + assert.property(result.branches[0].merkle, 'pos') }) }) describe('#broadcast', () => { - it('should broadcast a single transaction', async () => { - const txHex = - '020000000265d13ef402840c8a51f39779afb7ae4d49e4b0a3c24a3d0e7742038f2c679667010000006441dd1dd72770cadede1a7fd0363574846c48468a398ddfa41a9677c74cac8d2652b682743725a3b08c6c2021a629011e11a264d9036e9d5311e35b5f4937ca7b4e4121020797d8fd4d2fa6fd7cdeabe2526bfea2b90525d6e8ad506ec4ee3c53885aa309ffffffff65d13ef402840c8a51f39779afb7ae4d49e4b0a3c24a3d0e7742038f2c679667000000006441347d7f218c11c04487c1ad8baac28928fb10e5054cd4494b94d078cfa04ccf68e064fb188127ff656c0b98e9ce87f036d183925d0d0860605877d61e90375f774121028a53f95eb631b460854fc836b2e5d31cad16364b4dc3d970babfbdcc3f2e4954ffffffff035ac355000000000017a914189ce02e332548f4804bac65cba68202c9dbf822878dfd0800000000001976a914285bb350881b21ac89724c6fb6dc914d096cd53b88acf9ef3100000000001976a91445f1f1c4a9b9419a5088a3e9c24a293d7a150e6488ac00000000' - - try { - await bchjs.Electrumx.broadcast(txHex) - } catch (err) { - // console.error('err: ', err) - assert.include( - err.message, - 'the transaction was rejected by network rules' - ) - } - }) - }) - - describe('#sortAllTxs', () => { - it('should GET transaction history for a single address', async () => { - // Add delay for this endpoint. - await sleep(6000) - - const addr = 'bitcoincash:qpdh9s677ya8tnx7zdhfrn8qfyvy22wj4qa7nwqa5v' - - const txs = await bchjs.Electrumx.transactions(addr) - const sortedTransactions = await bchjs.Electrumx.sortAllTxs( - txs.transactions - ) - // console.log( - // `sortedTransactions: ${JSON.stringify(sortedTransactions, null, 2)}` - // ) - - assert.isAbove(sortedTransactions[0].height, sortedTransactions[1].height) - }) - }) -}) - -function sleep (ms) { - return new Promise(resolve => setTimeout(resolve, ms)) -} + it('should broadcast a single transaction', async () => { + const txHex = + '020000000265d13ef402840c8a51f39779afb7ae4d49e4b0a3c24a3d0e7742038f2c679667010000006441dd1dd72770cadede1a7fd0363574846c48468a398ddfa41a9677c74cac8d2652b682743725a3b08c6c2021a629011e11a264d9036e9d5311e35b5f4937ca7b4e4121020797d8fd4d2fa6fd7cdeabe2526bfea2b90525d6e8ad506ec4ee3c53885aa309ffffffff65d13ef402840c8a51f39779afb7ae4d49e4b0a3c24a3d0e7742038f2c679667000000006441347d7f218c11c04487c1ad8baac28928fb10e5054cd4494b94d078cfa04ccf68e064fb188127ff656c0b98e9ce87f036d183925d0d0860605877d61e90375f774121028a53f95eb631b460854fc836b2e5d31cad16364b4dc3d970babfbdcc3f2e4954ffffffff035ac355000000000017a914189ce02e332548f4804bac65cba68202c9dbf822878dfd0800000000001976a914285bb350881b21ac89724c6fb6dc914d096cd53b88acf9ef3100000000001976a91445f1f1c4a9b9419a5088a3e9c24a293d7a150e6488ac00000000' + + try { + await bchjs.Electrumx.broadcast(txHex) + } catch (err) { + // console.error('err: ', err) + assert.include( + err.message, + 'the transaction was rejected by network rules' + ) + } + }) + }) + + describe('#sortAllTxs', () => { + it('should GET transaction history for a single address', async () => { + // Add delay for this endpoint. + await sleep(6000) + + const addr = 'bitcoincash:qpdh9s677ya8tnx7zdhfrn8qfyvy22wj4qa7nwqa5v' + + const txs = await bchjs.Electrumx.transactions(addr) + const sortedTransactions = await bchjs.Electrumx.sortAllTxs( + txs.transactions + ) + // console.log( + // `sortedTransactions: ${JSON.stringify(sortedTransactions, null, 2)}` + // ) + + assert.isAbove(sortedTransactions[0].height, sortedTransactions[1].height) + }) + }) +}) + +function sleep (ms) { + return new Promise(resolve => setTimeout(resolve, ms)) +} diff --git a/test/unit/electrumx.js b/test/unit/electrumx.js index 840ef3e..5192ed9 100644 --- a/test/unit/electrumx.js +++ b/test/unit/electrumx.js @@ -1,494 +1,567 @@ -import chai from 'chai' -import axios from 'axios' -import sinon from 'sinon' - -import BCHJS from '../../src/bch-js.js' - -import mockData from './fixtures/electrumx-mock.js' -const assert = chai.assert -const bchjs = new BCHJS() - -describe('#ElectrumX', () => { - let sandbox - beforeEach(() => (sandbox = sinon.createSandbox())) - afterEach(() => sandbox.restore()) - - describe('#utxo', () => { - it('should throw an error for improper input', async () => { - try { - const addr = 12345 - - await bchjs.Electrumx.utxo(addr) - assert.equal(true, false, 'Unexpected result!') - } catch (err) { - // console.log(`err: `, err) - assert.include( - err.message, - 'Input address must be a string or array of strings' - ) - } - }) - - it('should GET utxos for a single address', async () => { - // Stub the network call. - sandbox.stub(axios, 'get').resolves({ data: mockData.utxo }) - - const addr = 'bitcoincash:qqh793x9au6ehvh7r2zflzguanlme760wuzehgzjh9' - - const result = await bchjs.Electrumx.utxo(addr) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.property(result, 'success') - assert.equal(result.success, true) - - assert.property(result, 'utxos') - assert.isArray(result.utxos) - - assert.property(result.utxos[0], 'height') - assert.property(result.utxos[0], 'tx_hash') - assert.property(result.utxos[0], 'tx_pos') - assert.property(result.utxos[0], 'value') - }) - - it('should POST utxo details for an array of addresses', async () => { - // Stub the network call. - sandbox.stub(axios, 'post').resolves({ data: mockData.utxos }) - - const addr = [ - 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf', - 'bitcoincash:qpdh9s677ya8tnx7zdhfrn8qfyvy22wj4qa7nwqa5v' - ] - - const result = await bchjs.Electrumx.utxo(addr) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.property(result, 'success') - assert.equal(result.success, true) - - assert.property(result, 'utxos') - assert.isArray(result.utxos) - - assert.property(result.utxos[0], 'utxos') - assert.isArray(result.utxos[0].utxos) - assert.property(result.utxos[0], 'address') - - assert.property(result.utxos[0].utxos[0], 'height') - assert.property(result.utxos[0].utxos[0], 'tx_hash') - assert.property(result.utxos[0].utxos[0], 'tx_pos') - assert.property(result.utxos[0].utxos[0], 'value') - }) - }) - - describe('#balance', () => { - it('should throw an error for improper input', async () => { - try { - const addr = 12345 - - await bchjs.Electrumx.balance(addr) - assert.equal(true, false, 'Unexpected result!') - } catch (err) { - // console.log(`err: `, err) - assert.include( - err.message, - 'Input address must be a string or array of strings' - ) - } - }) - - it('should GET balance for a single address', async () => { - // Stub the network call. - sandbox.stub(axios, 'get').resolves({ data: mockData.balance }) - - const addr = 'bitcoincash:qqh793x9au6ehvh7r2zflzguanlme760wuzehgzjh9' - - const result = await bchjs.Electrumx.balance(addr) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.property(result, 'success') - assert.equal(result.success, true) - - assert.property(result, 'balance') - assert.property(result.balance, 'confirmed') - assert.property(result.balance, 'unconfirmed') - }) - - it('should POST balance for an array of addresses', async () => { - // Stub the network call. - sandbox.stub(axios, 'post').resolves({ data: mockData.balances }) - - const addr = [ - 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf', - 'bitcoincash:qpdh9s677ya8tnx7zdhfrn8qfyvy22wj4qa7nwqa5v' - ] - - const result = await bchjs.Electrumx.balance(addr) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.property(result, 'success') - assert.equal(result.success, true) - - assert.property(result, 'balances') - assert.isArray(result.balances) - - assert.property(result.balances[0], 'address') - assert.property(result.balances[0], 'balance') - assert.property(result.balances[0].balance, 'confirmed') - assert.property(result.balances[0].balance, 'unconfirmed') - }) - }) - - describe('#transactions', () => { - it('should throw an error for improper input', async () => { - try { - const addr = 12345 - - await bchjs.Electrumx.transactions(addr) - assert.equal(true, false, 'Unexpected result!') - } catch (err) { - // console.log(`err: `, err) - assert.include( - err.message, - 'Input address must be a string or array of strings' - ) - } - }) - - it('should GET transactions for a single address', async () => { - // Stub the network call. - sandbox.stub(axios, 'get').resolves({ data: mockData.transaction }) - - const addr = 'bitcoincash:qqh793x9au6ehvh7r2zflzguanlme760wuzehgzjh9' - - const result = await bchjs.Electrumx.transactions(addr) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.property(result, 'success') - assert.equal(result.success, true) - - assert.property(result, 'transactions') - assert.isArray(result.transactions) - assert.property(result.transactions[0], 'height') - assert.property(result.transactions[0], 'tx_hash') - }) - - it('should POST transaction history for an array of addresses', async () => { - // Stub the network call. - sandbox.stub(axios, 'post').resolves({ data: mockData.transactions }) - - const addr = [ - 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf', - 'bitcoincash:qpdh9s677ya8tnx7zdhfrn8qfyvy22wj4qa7nwqa5v' - ] - - const result = await bchjs.Electrumx.transactions(addr) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.property(result, 'success') - assert.equal(result.success, true) - - assert.property(result, 'transactions') - assert.isArray(result.transactions) - - assert.property(result.transactions[0], 'address') - assert.property(result.transactions[0], 'transactions') - assert.isArray(result.transactions[0].transactions) - assert.property(result.transactions[0].transactions[0], 'height') - assert.property(result.transactions[0].transactions[0], 'tx_hash') - }) - }) - - describe('#unconfirmed', () => { - it('should throw an error for improper input', async () => { - try { - const addr = 12345 - - await bchjs.Electrumx.unconfirmed(addr) - assert.equal(true, false, 'Unexpected result!') - } catch (err) { - // console.log(`err: `, err) - assert.include( - err.message, - 'Input address must be a string or array of strings' - ) - } - }) - - it('should GET unconfirmed utxos for a single address', async () => { - // Stub the network call. - sandbox.stub(axios, 'get').resolves({ data: mockData.unconfirmed }) - - const addr = 'bitcoincash:qqh793x9au6ehvh7r2zflzguanlme760wuzehgzjh9' - - const result = await bchjs.Electrumx.unconfirmed(addr) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.property(result, 'success') - assert.equal(result.success, true) - - assert.property(result, 'utxos') - assert.isArray(result.utxos) - - assert.property(result.utxos[0], 'height') - assert.property(result.utxos[0], 'tx_hash') - assert.property(result.utxos[0], 'fee') - }) - - it('should POST unconfirmed utxo details for an array of addresses', async () => { - // Stub the network call. - sandbox.stub(axios, 'post').resolves({ data: mockData.unconfirmedArray }) - - const addr = [ - 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf', - 'bitcoincash:qpdh9s677ya8tnx7zdhfrn8qfyvy22wj4qa7nwqa5v' - ] - - const result = await bchjs.Electrumx.unconfirmed(addr) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.property(result, 'success') - assert.equal(result.success, true) - - assert.property(result, 'utxos') - assert.isArray(result.utxos) - - assert.property(result.utxos[0], 'utxos') - assert.isArray(result.utxos[0].utxos) - assert.property(result.utxos[0], 'address') - - assert.property(result.utxos[0].utxos[0], 'height') - assert.property(result.utxos[0].utxos[0], 'tx_hash') - assert.property(result.utxos[0].utxos[0], 'fee') - }) - }) - - describe('#blockHeader', () => { - it('should throw an error for improper height input', async () => { - try { - // Mock network calls. - sandbox.stub(axios, 'get').rejects({ - response: { - data: { - success: false, - error: 'height must be a positive number' - } - } - }) - - const height = -10 - await bchjs.Electrumx.blockHeader(height) - - assert.equal(true, false, 'Unexpected result!') - } catch (err) { - // console.log(`err: `, err) - assert.include(err.message, 'height must be a positive number') - } - }) - - it('should throw an error for improper count input', async () => { - try { - // Mock network calls. - sandbox.stub(axios, 'get').rejects({ - response: { - data: { - success: false, - error: 'count must be a positive number' - } - } - }) - - const height = 42 - const count = -10 - - await bchjs.Electrumx.blockHeader(height, count) - assert.equal(true, false, 'Unexpected result!') - } catch (err) { - // console.log(`err: `, err) - assert.include(err.message, 'count must be a positive number') - } - }) - - it('should GET block headers for a given height', async () => { - // Stub the network call. - sandbox.stub(axios, 'get').resolves({ data: mockData.blockHeaders }) - - const height = 42 - - const result = await bchjs.Electrumx.blockHeader(height) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - assert.isArray(result) - assert.equal(result.length, 2) +import chai from 'chai' +import axios from 'axios' +import sinon from 'sinon' + +import BCHJS from '../../src/bch-js.js' + +import mockData from './fixtures/electrumx-mock.js' +const assert = chai.assert +const bchjs = new BCHJS() + +describe('#ElectrumX', () => { + let sandbox + beforeEach(() => (sandbox = sinon.createSandbox())) + afterEach(() => sandbox.restore()) + + describe('#utxo', () => { + it('should throw an error for improper input', async () => { + try { + const addr = 12345 + + await bchjs.Electrumx.utxo(addr) + assert.equal(true, false, 'Unexpected result!') + } catch (err) { + // console.log(`err: `, err) + assert.include( + err.message, + 'Input address must be a string or array of strings' + ) + } + }) + + it('should GET utxos for a single address', async () => { + // Stub the network call. + sandbox.stub(axios, 'get').resolves({ data: mockData.utxo }) + + const addr = 'bitcoincash:qqh793x9au6ehvh7r2zflzguanlme760wuzehgzjh9' + + const result = await bchjs.Electrumx.utxo(addr) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.property(result, 'success') + assert.equal(result.success, true) + + assert.property(result, 'utxos') + assert.isArray(result.utxos) + + assert.property(result.utxos[0], 'height') + assert.property(result.utxos[0], 'tx_hash') + assert.property(result.utxos[0], 'tx_pos') + assert.property(result.utxos[0], 'value') + }) + + it('should POST utxo details for an array of addresses', async () => { + // Stub the network call. + sandbox.stub(axios, 'post').resolves({ data: mockData.utxos }) + + const addr = [ + 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf', + 'bitcoincash:qpdh9s677ya8tnx7zdhfrn8qfyvy22wj4qa7nwqa5v' + ] + + const result = await bchjs.Electrumx.utxo(addr) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.property(result, 'success') + assert.equal(result.success, true) + + assert.property(result, 'utxos') + assert.isArray(result.utxos) + + assert.property(result.utxos[0], 'utxos') + assert.isArray(result.utxos[0].utxos) + assert.property(result.utxos[0], 'address') + + assert.property(result.utxos[0].utxos[0], 'height') + assert.property(result.utxos[0].utxos[0], 'tx_hash') + assert.property(result.utxos[0].utxos[0], 'tx_pos') + assert.property(result.utxos[0].utxos[0], 'value') + }) + }) + + describe('#balance', () => { + it('should throw an error for improper input', async () => { + try { + const addr = 12345 + + await bchjs.Electrumx.balance(addr) + assert.equal(true, false, 'Unexpected result!') + } catch (err) { + // console.log(`err: `, err) + assert.include( + err.message, + 'Input address must be a string or array of strings' + ) + } + }) + + it('should GET balance for a single address', async () => { + // Stub the network call. + sandbox.stub(axios, 'get').resolves({ data: mockData.balance }) + + const addr = 'bitcoincash:qqh793x9au6ehvh7r2zflzguanlme760wuzehgzjh9' + + const result = await bchjs.Electrumx.balance(addr) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.property(result, 'success') + assert.equal(result.success, true) + + assert.property(result, 'balance') + assert.property(result.balance, 'confirmed') + assert.property(result.balance, 'unconfirmed') + }) + + it('should POST balance for an array of addresses', async () => { + // Stub the network call. + sandbox.stub(axios, 'post').resolves({ data: mockData.balances }) + + const addr = [ + 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf', + 'bitcoincash:qpdh9s677ya8tnx7zdhfrn8qfyvy22wj4qa7nwqa5v' + ] + + const result = await bchjs.Electrumx.balance(addr) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.property(result, 'success') + assert.equal(result.success, true) + + assert.property(result, 'balances') + assert.isArray(result.balances) + + assert.property(result.balances[0], 'address') + assert.property(result.balances[0], 'balance') + assert.property(result.balances[0].balance, 'confirmed') + assert.property(result.balances[0].balance, 'unconfirmed') + }) + }) + + describe('#transactions', () => { + it('should throw an error for improper input', async () => { + try { + const addr = 12345 + + await bchjs.Electrumx.transactions(addr) + assert.equal(true, false, 'Unexpected result!') + } catch (err) { + // console.log(`err: `, err) + assert.include( + err.message, + 'Input address must be a string or array of strings' + ) + } + }) + + it('should GET transactions for a single address', async () => { + // Stub the network call. + sandbox.stub(axios, 'get').resolves({ data: mockData.transaction }) + + const addr = 'bitcoincash:qqh793x9au6ehvh7r2zflzguanlme760wuzehgzjh9' + + const result = await bchjs.Electrumx.transactions(addr) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.property(result, 'success') + assert.equal(result.success, true) + + assert.property(result, 'transactions') + assert.isArray(result.transactions) + assert.property(result.transactions[0], 'height') + assert.property(result.transactions[0], 'tx_hash') + }) + + it('should POST transaction history for an array of addresses', async () => { + // Stub the network call. + sandbox.stub(axios, 'post').resolves({ data: mockData.transactions }) + + const addr = [ + 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf', + 'bitcoincash:qpdh9s677ya8tnx7zdhfrn8qfyvy22wj4qa7nwqa5v' + ] + + const result = await bchjs.Electrumx.transactions(addr) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.property(result, 'success') + assert.equal(result.success, true) + + assert.property(result, 'transactions') + assert.isArray(result.transactions) + + assert.property(result.transactions[0], 'address') + assert.property(result.transactions[0], 'transactions') + assert.isArray(result.transactions[0].transactions) + assert.property(result.transactions[0].transactions[0], 'height') + assert.property(result.transactions[0].transactions[0], 'tx_hash') + }) + }) + + describe('#unconfirmed', () => { + it('should throw an error for improper input', async () => { + try { + const addr = 12345 + + await bchjs.Electrumx.unconfirmed(addr) + assert.equal(true, false, 'Unexpected result!') + } catch (err) { + // console.log(`err: `, err) + assert.include( + err.message, + 'Input address must be a string or array of strings' + ) + } + }) + + it('should GET unconfirmed utxos for a single address', async () => { + // Stub the network call. + sandbox.stub(axios, 'get').resolves({ data: mockData.unconfirmed }) + + const addr = 'bitcoincash:qqh793x9au6ehvh7r2zflzguanlme760wuzehgzjh9' + + const result = await bchjs.Electrumx.unconfirmed(addr) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.property(result, 'success') + assert.equal(result.success, true) + + assert.property(result, 'utxos') + assert.isArray(result.utxos) + + assert.property(result.utxos[0], 'height') + assert.property(result.utxos[0], 'tx_hash') + assert.property(result.utxos[0], 'fee') + }) + + it('should POST unconfirmed utxo details for an array of addresses', async () => { + // Stub the network call. + sandbox.stub(axios, 'post').resolves({ data: mockData.unconfirmedArray }) + + const addr = [ + 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf', + 'bitcoincash:qpdh9s677ya8tnx7zdhfrn8qfyvy22wj4qa7nwqa5v' + ] + + const result = await bchjs.Electrumx.unconfirmed(addr) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.property(result, 'success') + assert.equal(result.success, true) + + assert.property(result, 'utxos') + assert.isArray(result.utxos) + + assert.property(result.utxos[0], 'utxos') + assert.isArray(result.utxos[0].utxos) + assert.property(result.utxos[0], 'address') + + assert.property(result.utxos[0].utxos[0], 'height') + assert.property(result.utxos[0].utxos[0], 'tx_hash') + assert.property(result.utxos[0].utxos[0], 'fee') + }) + }) + + describe('#blockHeader', () => { + it('should throw an error for improper height input', async () => { + try { + // Mock network calls. + sandbox.stub(axios, 'get').rejects({ + response: { + data: { + success: false, + error: 'height must be a positive number' + } + } + }) + + const height = -10 + await bchjs.Electrumx.blockHeader(height) + + assert.equal(true, false, 'Unexpected result!') + } catch (err) { + // console.log(`err: `, err) + assert.include(err.message, 'height must be a positive number') + } + }) + + it('should throw an error for improper count input', async () => { + try { + // Mock network calls. + sandbox.stub(axios, 'get').rejects({ + response: { + data: { + success: false, + error: 'count must be a positive number' + } + } + }) + + const height = 42 + const count = -10 + + await bchjs.Electrumx.blockHeader(height, count) + assert.equal(true, false, 'Unexpected result!') + } catch (err) { + // console.log(`err: `, err) + assert.include(err.message, 'count must be a positive number') + } + }) + + it('should GET block headers for a given height', async () => { + // Stub the network call. + sandbox.stub(axios, 'get').resolves({ data: mockData.blockHeaders }) + + const height = 42 + + const result = await bchjs.Electrumx.blockHeader(height) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + assert.isArray(result) + assert.equal(result.length, 2) + }) + }) + + describe('#txData', () => { + it('should throw an error for improper input', async () => { + try { + const txid = 12345 + + await bchjs.Electrumx.txData(txid) + assert.equal(true, false, 'Unexpected result!') + } catch (err) { + // console.log(`err: `, err) + assert.include( + err.message, + 'Input txId must be a string or array of strings' + ) + } + }) + + it('should GET details data for a single transaction', async () => { + // Stub the network call. + sandbox.stub(axios, 'get').resolves({ data: mockData.details }) + + const txid = + '4db095f34d632a4daf942142c291f1f2abb5ba2e1ccac919d85bdc2f671fb251' + + const result = await bchjs.Electrumx.txData(txid) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.property(result, 'success') + assert.equal(result.success, true) + + assert.property(result, 'details') + assert.isObject(result.details) + + assert.property(result.details, 'blockhash') + assert.property(result.details, 'hash') + assert.property(result.details, 'hex') + assert.property(result.details, 'vin') + assert.property(result.details, 'vout') + assert.equal(result.details.hash, txid) + }) + + it('should POST details for an array of transactions', async () => { + // Stub the network call. + sandbox.stub(axios, 'post').resolves({ data: mockData.detailsArray }) + + const txids = [ + '4db095f34d632a4daf942142c291f1f2abb5ba2e1ccac919d85bdc2f671fb251', + '4db095f34d632a4daf942142c291f1f2abb5ba2e1ccac919d85bdc2f671fb251' + ] + + const result = await bchjs.Electrumx.txData(txids) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.property(result, 'success') + assert.equal(result.success, true) + + assert.property(result, 'transactions') + assert.isArray(result.transactions) + + assert.property(result.transactions[0], 'txid') + assert.property(result.transactions[0], 'details') + + assert.property(result.transactions[0].details, 'blockhash') + assert.property(result.transactions[0].details, 'hash') + assert.property(result.transactions[0].details, 'hex') + assert.property(result.transactions[0].details, 'vin') + assert.property(result.transactions[0].details, 'vout') + + assert.equal(result.transactions.length, 2, '2 outputs for 2 inputs') }) }) - describe('#txData', () => { + describe('#merkleBranch', () => { it('should throw an error for improper input', async () => { try { const txid = 12345 - await bchjs.Electrumx.txData(txid) + await bchjs.Electrumx.merkleBranch(txid) assert.equal(true, false, 'Unexpected result!') } catch (err) { // console.log(`err: `, err) assert.include( err.message, - 'Input txId must be a string or array of strings' + 'Input txId must be a string or array of txid/height objects' ) } }) - it('should GET details data for a single transaction', async () => { + it('should GET merkle branch data for a single transaction', async () => { // Stub the network call. - sandbox.stub(axios, 'get').resolves({ data: mockData.details }) + sandbox.stub(axios, 'get').resolves({ data: mockData.merkleBranch }) const txid = '4db095f34d632a4daf942142c291f1f2abb5ba2e1ccac919d85bdc2f671fb251' + const height = 617812 - const result = await bchjs.Electrumx.txData(txid) + const result = await bchjs.Electrumx.merkleBranch(txid, height) // console.log(`result: ${JSON.stringify(result, null, 2)}`) assert.property(result, 'success') assert.equal(result.success, true) - assert.property(result, 'details') - assert.isObject(result.details) - - assert.property(result.details, 'blockhash') - assert.property(result.details, 'hash') - assert.property(result.details, 'hex') - assert.property(result.details, 'vin') - assert.property(result.details, 'vout') - assert.equal(result.details.hash, txid) + assert.property(result, 'merkle') + assert.isObject(result.merkle) + assert.property(result.merkle, 'block_height') + assert.property(result.merkle, 'merkle') + assert.property(result.merkle, 'pos') + assert.isArray(result.merkle.merkle) }) - it('should POST details for an array of transactions', async () => { + it('should POST merkle branch data for an array of transactions', async () => { // Stub the network call. - sandbox.stub(axios, 'post').resolves({ data: mockData.detailsArray }) + sandbox.stub(axios, 'post').resolves({ data: mockData.merkleBranches }) const txids = [ - '4db095f34d632a4daf942142c291f1f2abb5ba2e1ccac919d85bdc2f671fb251', - '4db095f34d632a4daf942142c291f1f2abb5ba2e1ccac919d85bdc2f671fb251' + { + txid: '4db095f34d632a4daf942142c291f1f2abb5ba2e1ccac919d85bdc2f671fb251', + height: 617812 + }, + { + txid: '4db095f34d632a4daf942142c291f1f2abb5ba2e1ccac919d85bdc2f671fb251', + height: 617812 + } ] - const result = await bchjs.Electrumx.txData(txids) + const result = await bchjs.Electrumx.merkleBranch(txids) // console.log(`result: ${JSON.stringify(result, null, 2)}`) assert.property(result, 'success') assert.equal(result.success, true) - assert.property(result, 'transactions') - assert.isArray(result.transactions) - - assert.property(result.transactions[0], 'txid') - assert.property(result.transactions[0], 'details') + assert.property(result, 'branches') + assert.isArray(result.branches) - assert.property(result.transactions[0].details, 'blockhash') - assert.property(result.transactions[0].details, 'hash') - assert.property(result.transactions[0].details, 'hex') - assert.property(result.transactions[0].details, 'vin') - assert.property(result.transactions[0].details, 'vout') + assert.property(result.branches[0], 'txid') + assert.property(result.branches[0], 'height') + assert.property(result.branches[0], 'merkle') + assert.property(result.branches[0].merkle, 'block_height') + assert.property(result.branches[0].merkle, 'merkle') + assert.property(result.branches[0].merkle, 'pos') - assert.equal(result.transactions.length, 2, '2 outputs for 2 inputs') + assert.equal(result.branches.length, 2, '2 outputs for 2 inputs') }) }) describe('#broadcast', () => { - it('should throw an error for improper input', async () => { - try { - const txHex = 12345 - - await bchjs.Electrumx.broadcast(txHex) - assert.equal(true, false, 'Unexpected result!') - } catch (err) { - // console.log(`err: `, err) - assert.include(err.message, 'Input txHex must be a string.') - } - }) - - it('should broadcast a single transaction', async () => { - // Stub the network call. - sandbox.stub(axios, 'post').resolves({ data: mockData.broadcast }) - - const tx = mockData.details - const txid = tx.details.txid - const result = await bchjs.Electrumx.broadcast(tx.details.hex) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.property(result, 'success') - assert.equal(result.success, true) - - assert.property(result, 'txid') - assert.equal(result.txid, txid) - }) - }) - - describe('#sortConfTxs', () => { - it('should sort in ascending order', () => { - const result = bchjs.Electrumx.sortConfTxs(mockData.transaction.transactions, 'ASCENDING') - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isBelow(result[0].height, result[1].height) - }) - - it('should sort in descending order by default', () => { - const result = bchjs.Electrumx.sortConfTxs(mockData.transaction.transactions) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(result[0].height, result[1].height) - }) - - it('should ignore unconfirmed txs', () => { - const result = bchjs.Electrumx.sortConfTxs(mockData.txHistoryWithUnconfirmed.transactions) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(result[0].height, 0) - }) - - it('should handle errors', () => { - try { - bchjs.Electrumx.sortConfTxs('abc') - - assert.fail('Unexpected result') - } catch (err) { - // console.log(err.message) - assert.equal(err.message, 'txs.filter is not a function') - } - }) - }) - - // These tests use mocked data that contains unconfirmed transactions. - describe('#sortAllTxs', () => { - it('should sort in ascending', async () => { - // Stub network calls - sandbox.stub(bchjs.Electrumx.blockchain, 'getBlockCount').resolves(672141) - - const result = await bchjs.Electrumx.sortAllTxs(mockData.txHistoryWithUnconfirmed.transactions, 'ASCENDING') - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isBelow(result[0].height, result[1].height) - }) - - it('should sort in descending order by default', async () => { - // Stub network calls - sandbox.stub(bchjs.Electrumx.blockchain, 'getBlockCount').resolves(672141) - - const result = await bchjs.Electrumx.sortAllTxs(mockData.txHistoryWithUnconfirmed.transactions) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isAbove(result[1].height, result[2].height) - }) - - it('should handle errors', async () => { - try { - // Stub network calls - sandbox.stub(bchjs.Electrumx.blockchain, 'getBlockCount').resolves(672141) - - await bchjs.Electrumx.sortAllTxs('abc') - - assert.fail('Unexpected result') - } catch (err) { - // console.log(err.message) - assert.equal(err.message, 'txs.map is not a function') - } - }) - }) -}) + it('should throw an error for improper input', async () => { + try { + const txHex = 12345 + + await bchjs.Electrumx.broadcast(txHex) + assert.equal(true, false, 'Unexpected result!') + } catch (err) { + // console.log(`err: `, err) + assert.include(err.message, 'Input txHex must be a string.') + } + }) + + it('should broadcast a single transaction', async () => { + // Stub the network call. + sandbox.stub(axios, 'post').resolves({ data: mockData.broadcast }) + + const tx = mockData.details + const txid = tx.details.txid + const result = await bchjs.Electrumx.broadcast(tx.details.hex) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.property(result, 'success') + assert.equal(result.success, true) + + assert.property(result, 'txid') + assert.equal(result.txid, txid) + }) + }) + + describe('#sortConfTxs', () => { + it('should sort in ascending order', () => { + const result = bchjs.Electrumx.sortConfTxs(mockData.transaction.transactions, 'ASCENDING') + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.isBelow(result[0].height, result[1].height) + }) + + it('should sort in descending order by default', () => { + const result = bchjs.Electrumx.sortConfTxs(mockData.transaction.transactions) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.isAbove(result[0].height, result[1].height) + }) + + it('should ignore unconfirmed txs', () => { + const result = bchjs.Electrumx.sortConfTxs(mockData.txHistoryWithUnconfirmed.transactions) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.isAbove(result[0].height, 0) + }) + + it('should handle errors', () => { + try { + bchjs.Electrumx.sortConfTxs('abc') + + assert.fail('Unexpected result') + } catch (err) { + // console.log(err.message) + assert.equal(err.message, 'txs.filter is not a function') + } + }) + }) + + // These tests use mocked data that contains unconfirmed transactions. + describe('#sortAllTxs', () => { + it('should sort in ascending', async () => { + // Stub network calls + sandbox.stub(bchjs.Electrumx.blockchain, 'getBlockCount').resolves(672141) + + const result = await bchjs.Electrumx.sortAllTxs(mockData.txHistoryWithUnconfirmed.transactions, 'ASCENDING') + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.isBelow(result[0].height, result[1].height) + }) + + it('should sort in descending order by default', async () => { + // Stub network calls + sandbox.stub(bchjs.Electrumx.blockchain, 'getBlockCount').resolves(672141) + + const result = await bchjs.Electrumx.sortAllTxs(mockData.txHistoryWithUnconfirmed.transactions) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.isAbove(result[1].height, result[2].height) + }) + + it('should handle errors', async () => { + try { + // Stub network calls + sandbox.stub(bchjs.Electrumx.blockchain, 'getBlockCount').resolves(672141) + + await bchjs.Electrumx.sortAllTxs('abc') + + assert.fail('Unexpected result') + } catch (err) { + // console.log(err.message) + assert.equal(err.message, 'txs.map is not a function') + } + }) + }) +}) diff --git a/test/unit/fixtures/electrumx-mock.js b/test/unit/fixtures/electrumx-mock.js index b9f15bf..113d030 100644 --- a/test/unit/fixtures/electrumx-mock.js +++ b/test/unit/fixtures/electrumx-mock.js @@ -1,258 +1,286 @@ -/* - Mocking data for unit tests for the ElectrumX library. -*/ - -const utxo = { - success: true, - utxos: [ - { - height: 602405, - tx_hash: - '2b37bdb3b63dd0bca720437754a36671431a950e684b64c44ea910ea9d5297c7', - tx_pos: 0, - value: 1000 - } - ] -} - -const utxos = { - success: true, - utxos: [ - { - utxos: [ - { - height: 604392, - tx_hash: - '7774e449c5a3065144cefbc4c0c21e6b69c987f095856778ef9f45ddd8ae1a41', - tx_pos: 0, - value: 1000 - }, - { - height: 630834, - tx_hash: - '4fe60a51e0d8f5134bfd8e5f872d6e502d7f01b28a6afebb27f4438a4f638d53', - tx_pos: 0, - value: 6000 - } - ], - address: 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf' - }, - { - utxos: [], - address: 'bitcoincash:qpdh9s677ya8tnx7zdhfrn8qfyvy22wj4qa7nwqa5v' - } - ] -} - -const balance = { - success: true, - balance: { - confirmed: 1000, - unconfirmed: 0 - } -} - -const balances = { - success: true, - balances: [ - { - balance: { - confirmed: 7000, - unconfirmed: 0 - }, - address: 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf' - }, - { - balance: { - confirmed: 0, - unconfirmed: 0 - }, - address: 'bitcoincash:qpdh9s677ya8tnx7zdhfrn8qfyvy22wj4qa7nwqa5v' - } - ] -} - -const transaction = { - success: true, - transactions: [ - { - height: 560430, - tx_hash: - '3e1f3e882be9c03897eeb197224bf87f312be556a89f4308fabeeeabcf9bc851' - }, - { - height: 560534, - tx_hash: - '4ebbeaac51ce141e262964e3a0ce11b96ca72c0dffe9b4127ce80135f503a280' - } - ] -} - -const transactions = { +/* + Mocking data for unit tests for the ElectrumX library. +*/ + +const utxo = { + success: true, + utxos: [ + { + height: 602405, + tx_hash: + '2b37bdb3b63dd0bca720437754a36671431a950e684b64c44ea910ea9d5297c7', + tx_pos: 0, + value: 1000 + } + ] +} + +const utxos = { + success: true, + utxos: [ + { + utxos: [ + { + height: 604392, + tx_hash: + '7774e449c5a3065144cefbc4c0c21e6b69c987f095856778ef9f45ddd8ae1a41', + tx_pos: 0, + value: 1000 + }, + { + height: 630834, + tx_hash: + '4fe60a51e0d8f5134bfd8e5f872d6e502d7f01b28a6afebb27f4438a4f638d53', + tx_pos: 0, + value: 6000 + } + ], + address: 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf' + }, + { + utxos: [], + address: 'bitcoincash:qpdh9s677ya8tnx7zdhfrn8qfyvy22wj4qa7nwqa5v' + } + ] +} + +const balance = { + success: true, + balance: { + confirmed: 1000, + unconfirmed: 0 + } +} + +const balances = { + success: true, + balances: [ + { + balance: { + confirmed: 7000, + unconfirmed: 0 + }, + address: 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf' + }, + { + balance: { + confirmed: 0, + unconfirmed: 0 + }, + address: 'bitcoincash:qpdh9s677ya8tnx7zdhfrn8qfyvy22wj4qa7nwqa5v' + } + ] +} + +const transaction = { + success: true, + transactions: [ + { + height: 560430, + tx_hash: + '3e1f3e882be9c03897eeb197224bf87f312be556a89f4308fabeeeabcf9bc851' + }, + { + height: 560534, + tx_hash: + '4ebbeaac51ce141e262964e3a0ce11b96ca72c0dffe9b4127ce80135f503a280' + } + ] +} + +const transactions = { + success: true, + transactions: [ + { + transactions: [ + { + height: 631219, + tx_hash: + 'ae2daa01c8172545b5edd205ea438706bcb74e63d4084a26b9ff2a46d46dc97f' + } + ], + address: 'bitcoincash:qrl2nlsaayk6ekxn80pq0ks32dya8xfclyktem2mqj' + }, + { + transactions: [ + { + height: 560430, + tx_hash: + '3e1f3e882be9c03897eeb197224bf87f312be556a89f4308fabeeeabcf9bc851' + }, + { + height: 560534, + tx_hash: + '4ebbeaac51ce141e262964e3a0ce11b96ca72c0dffe9b4127ce80135f503a280' + } + ], + address: 'bitcoincash:qpdh9s677ya8tnx7zdhfrn8qfyvy22wj4qa7nwqa5v' + } + ] +} + +const unconfirmed = { + success: true, + utxos: [ + { + height: 602405, + tx_hash: + '2b37bdb3b63dd0bca720437754a36671431a950e684b64c44ea910ea9d5297c7', + fee: 34100 + } + ] +} + +const unconfirmedArray = { + success: true, + utxos: [ + { + utxos: [ + { + height: 604392, + tx_hash: + '7774e449c5a3065144cefbc4c0c21e6b69c987f095856778ef9f45ddd8ae1a41', + fee: 34210 + }, + { + height: 630834, + tx_hash: + '4fe60a51e0d8f5134bfd8e5f872d6e502d7f01b28a6afebb27f4438a4f638d53', + value: 3000 + } + ], + address: 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf' + }, + { + utxos: [], + address: 'bitcoincash:qpdh9s677ya8tnx7zdhfrn8qfyvy22wj4qa7nwqa5v' + } + ] +} + +const blockHeaders = [ + '010000008b52bbd72c2f49569059f559c1b1794de5192e4f7d6d2b03c7482bad0000000083e4f8a9d502ed0c419075c1abb5d56f878a2e9079e5612bfb76a2dc37d9c42741dd6849ffff001d2b909dd6', + '01000000f528fac1bcb685d0cd6c792320af0300a5ce15d687c7149548904e31000000004e8985a786d864f21e9cbb7cbdf4bc9265fe681b7a0893ac55a8e919ce035c2f85de6849ffff001d385ccb7c' +] + +const txDetails = { + blockhash: '0000000000000000002aaf94953da3b487317508ebd1003a1d75d6d6ec2e75cc', + blocktime: 1578327094, + confirmations: 31861, + hash: '4db095f34d632a4daf942142c291f1f2abb5ba2e1ccac919d85bdc2f671fb251', + hex: + '020000000265d13ef402840c8a51f39779afb7ae4d49e4b0a3c24a3d0e7742038f2c679667010000006441dd1dd72770cadede1a7fd0363574846c48468a398ddfa41a9677c74cac8d2652b682743725a3b08c6c2021a629011e11a264d9036e9d5311e35b5f4937ca7b4e4121020797d8fd4d2fa6fd7cdeabe2526bfea2b90525d6e8ad506ec4ee3c53885aa309ffffffff65d13ef402840c8a51f39779afb7ae4d49e4b0a3c24a3d0e7742038f2c679667000000006441347d7f218c11c04487c1ad8baac28928fb10e5054cd4494b94d078cfa04ccf68e064fb188127ff656c0b98e9ce87f036d183925d0d0860605877d61e90375f774121028a53f95eb631b460854fc836b2e5d31cad16364b4dc3d970babfbdcc3f2e4954ffffffff035ac355000000000017a914189ce02e332548f4804bac65cba68202c9dbf822878dfd0800000000001976a914285bb350881b21ac89724c6fb6dc914d096cd53b88acf9ef3100000000001976a91445f1f1c4a9b9419a5088a3e9c24a293d7a150e6488ac00000000', + locktime: 0, + size: 392, + time: 1578327094, + txid: '4db095f34d632a4daf942142c291f1f2abb5ba2e1ccac919d85bdc2f671fb251', + version: 2, + vin: [ + { + scriptSig: { + asm: + 'dd1dd72770cadede1a7fd0363574846c48468a398ddfa41a9677c74cac8d2652b682743725a3b08c6c2021a629011e11a264d9036e9d5311e35b5f4937ca7b4e[ALL|FORKID] 020797d8fd4d2fa6fd7cdeabe2526bfea2b90525d6e8ad506ec4ee3c53885aa309', + hex: + '41dd1dd72770cadede1a7fd0363574846c48468a398ddfa41a9677c74cac8d2652b682743725a3b08c6c2021a629011e11a264d9036e9d5311e35b5f4937ca7b4e4121020797d8fd4d2fa6fd7cdeabe2526bfea2b90525d6e8ad506ec4ee3c53885aa309' + }, + sequence: 4294967295, + txid: '6796672c8f0342770e3d4ac2a3b0e4494daeb7af7997f3518a0c8402f43ed165', + vout: 1 + }, + { + scriptSig: { + asm: + '347d7f218c11c04487c1ad8baac28928fb10e5054cd4494b94d078cfa04ccf68e064fb188127ff656c0b98e9ce87f036d183925d0d0860605877d61e90375f77[ALL|FORKID] 028a53f95eb631b460854fc836b2e5d31cad16364b4dc3d970babfbdcc3f2e4954', + hex: + '41347d7f218c11c04487c1ad8baac28928fb10e5054cd4494b94d078cfa04ccf68e064fb188127ff656c0b98e9ce87f036d183925d0d0860605877d61e90375f774121028a53f95eb631b460854fc836b2e5d31cad16364b4dc3d970babfbdcc3f2e4954' + }, + sequence: 4294967295, + txid: '6796672c8f0342770e3d4ac2a3b0e4494daeb7af7997f3518a0c8402f43ed165', + vout: 0 + } + ], + vout: [ + { + n: 0, + scriptPubKey: { + addresses: ['bitcoincash: pqvfecpwxvj53ayqfwkxtjaxsgpvnklcyg8xewk9hl'], + asm: 'OP_HASH160 189ce02e332548f4804bac65cba68202c9dbf822 OP_EQUAL', + hex: 'a914189ce02e332548f4804bac65cba68202c9dbf82287', + reqSigs: 1, + type: 'scripthash' + }, + value: 0.0562057 + }, + { + n: 1, + scriptPubKey: { + addresses: ['bitcoincash: qq59hv6s3qdjrtyfwfxxldkuj9xsjmx48vrz882knz'], + asm: + 'OP_DUP OP_HASH160 285bb350881b21ac89724c6fb6dc914d096cd53b OP_EQUALVERIFY OP_CHECKSIG', + hex: '76a914285bb350881b21ac89724c6fb6dc914d096cd53b88ac', + reqSigs: 1, + type: 'pubkeyhash' + }, + value: 0.00589197 + }, + { + n: 2, + scriptPubKey: { + addresses: ['bitcoincash: qpzlruwy4xu5rxjs3z37nsj29y7h59gwvsu4ddp0u4'], + asm: + 'OP_DUP OP_HASH160 45f1f1c4a9b9419a5088a3e9c24a293d7a150e64 OP_EQUALVERIFY OP_CHECKSIG', + hex: '76a91445f1f1c4a9b9419a5088a3e9c24a293d7a150e6488ac', + reqSigs: 1, + type: 'pubkeyhash' + }, + value: 0.03272697 + } + ] +} + +const details = { + success: true, + details: txDetails +} + +const detailsArray = { success: true, transactions: [ { - transactions: [ - { - height: 631219, - tx_hash: - 'ae2daa01c8172545b5edd205ea438706bcb74e63d4084a26b9ff2a46d46dc97f' - } - ], - address: 'bitcoincash:qrl2nlsaayk6ekxn80pq0ks32dya8xfclyktem2mqj' - }, - { - transactions: [ - { - height: 560430, - tx_hash: - '3e1f3e882be9c03897eeb197224bf87f312be556a89f4308fabeeeabcf9bc851' - }, - { - height: 560534, - tx_hash: - '4ebbeaac51ce141e262964e3a0ce11b96ca72c0dffe9b4127ce80135f503a280' - } - ], - address: 'bitcoincash:qpdh9s677ya8tnx7zdhfrn8qfyvy22wj4qa7nwqa5v' - } - ] -} - -const unconfirmed = { - success: true, - utxos: [ - { - height: 602405, - tx_hash: - '2b37bdb3b63dd0bca720437754a36671431a950e684b64c44ea910ea9d5297c7', - fee: 34100 - } - ] -} - -const unconfirmedArray = { - success: true, - utxos: [ - { - utxos: [ - { - height: 604392, - tx_hash: - '7774e449c5a3065144cefbc4c0c21e6b69c987f095856778ef9f45ddd8ae1a41', - fee: 34210 - }, - { - height: 630834, - tx_hash: - '4fe60a51e0d8f5134bfd8e5f872d6e502d7f01b28a6afebb27f4438a4f638d53', - value: 3000 - } - ], - address: 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf' - }, - { - utxos: [], - address: 'bitcoincash:qpdh9s677ya8tnx7zdhfrn8qfyvy22wj4qa7nwqa5v' - } - ] -} - -const blockHeaders = [ - '010000008b52bbd72c2f49569059f559c1b1794de5192e4f7d6d2b03c7482bad0000000083e4f8a9d502ed0c419075c1abb5d56f878a2e9079e5612bfb76a2dc37d9c42741dd6849ffff001d2b909dd6', - '01000000f528fac1bcb685d0cd6c792320af0300a5ce15d687c7149548904e31000000004e8985a786d864f21e9cbb7cbdf4bc9265fe681b7a0893ac55a8e919ce035c2f85de6849ffff001d385ccb7c' -] - -const txDetails = { - blockhash: '0000000000000000002aaf94953da3b487317508ebd1003a1d75d6d6ec2e75cc', - blocktime: 1578327094, - confirmations: 31861, - hash: '4db095f34d632a4daf942142c291f1f2abb5ba2e1ccac919d85bdc2f671fb251', - hex: - '020000000265d13ef402840c8a51f39779afb7ae4d49e4b0a3c24a3d0e7742038f2c679667010000006441dd1dd72770cadede1a7fd0363574846c48468a398ddfa41a9677c74cac8d2652b682743725a3b08c6c2021a629011e11a264d9036e9d5311e35b5f4937ca7b4e4121020797d8fd4d2fa6fd7cdeabe2526bfea2b90525d6e8ad506ec4ee3c53885aa309ffffffff65d13ef402840c8a51f39779afb7ae4d49e4b0a3c24a3d0e7742038f2c679667000000006441347d7f218c11c04487c1ad8baac28928fb10e5054cd4494b94d078cfa04ccf68e064fb188127ff656c0b98e9ce87f036d183925d0d0860605877d61e90375f774121028a53f95eb631b460854fc836b2e5d31cad16364b4dc3d970babfbdcc3f2e4954ffffffff035ac355000000000017a914189ce02e332548f4804bac65cba68202c9dbf822878dfd0800000000001976a914285bb350881b21ac89724c6fb6dc914d096cd53b88acf9ef3100000000001976a91445f1f1c4a9b9419a5088a3e9c24a293d7a150e6488ac00000000', - locktime: 0, - size: 392, - time: 1578327094, - txid: '4db095f34d632a4daf942142c291f1f2abb5ba2e1ccac919d85bdc2f671fb251', - version: 2, - vin: [ - { - scriptSig: { - asm: - 'dd1dd72770cadede1a7fd0363574846c48468a398ddfa41a9677c74cac8d2652b682743725a3b08c6c2021a629011e11a264d9036e9d5311e35b5f4937ca7b4e[ALL|FORKID] 020797d8fd4d2fa6fd7cdeabe2526bfea2b90525d6e8ad506ec4ee3c53885aa309', - hex: - '41dd1dd72770cadede1a7fd0363574846c48468a398ddfa41a9677c74cac8d2652b682743725a3b08c6c2021a629011e11a264d9036e9d5311e35b5f4937ca7b4e4121020797d8fd4d2fa6fd7cdeabe2526bfea2b90525d6e8ad506ec4ee3c53885aa309' - }, - sequence: 4294967295, - txid: '6796672c8f0342770e3d4ac2a3b0e4494daeb7af7997f3518a0c8402f43ed165', - vout: 1 - }, - { - scriptSig: { - asm: - '347d7f218c11c04487c1ad8baac28928fb10e5054cd4494b94d078cfa04ccf68e064fb188127ff656c0b98e9ce87f036d183925d0d0860605877d61e90375f77[ALL|FORKID] 028a53f95eb631b460854fc836b2e5d31cad16364b4dc3d970babfbdcc3f2e4954', - hex: - '41347d7f218c11c04487c1ad8baac28928fb10e5054cd4494b94d078cfa04ccf68e064fb188127ff656c0b98e9ce87f036d183925d0d0860605877d61e90375f774121028a53f95eb631b460854fc836b2e5d31cad16364b4dc3d970babfbdcc3f2e4954' - }, - sequence: 4294967295, - txid: '6796672c8f0342770e3d4ac2a3b0e4494daeb7af7997f3518a0c8402f43ed165', - vout: 0 - } - ], - vout: [ - { - n: 0, - scriptPubKey: { - addresses: ['bitcoincash: pqvfecpwxvj53ayqfwkxtjaxsgpvnklcyg8xewk9hl'], - asm: 'OP_HASH160 189ce02e332548f4804bac65cba68202c9dbf822 OP_EQUAL', - hex: 'a914189ce02e332548f4804bac65cba68202c9dbf82287', - reqSigs: 1, - type: 'scripthash' - }, - value: 0.0562057 - }, - { - n: 1, - scriptPubKey: { - addresses: ['bitcoincash: qq59hv6s3qdjrtyfwfxxldkuj9xsjmx48vrz882knz'], - asm: - 'OP_DUP OP_HASH160 285bb350881b21ac89724c6fb6dc914d096cd53b OP_EQUALVERIFY OP_CHECKSIG', - hex: '76a914285bb350881b21ac89724c6fb6dc914d096cd53b88ac', - reqSigs: 1, - type: 'pubkeyhash' - }, - value: 0.00589197 - }, - { - n: 2, - scriptPubKey: { - addresses: ['bitcoincash: qpzlruwy4xu5rxjs3z37nsj29y7h59gwvsu4ddp0u4'], - asm: - 'OP_DUP OP_HASH160 45f1f1c4a9b9419a5088a3e9c24a293d7a150e64 OP_EQUALVERIFY OP_CHECKSIG', - hex: '76a91445f1f1c4a9b9419a5088a3e9c24a293d7a150e6488ac', - reqSigs: 1, - type: 'pubkeyhash' - }, - value: 0.03272697 + txid: txDetails.txid, + details: txDetails + }, + { + txid: txDetails.txid, + details: txDetails } ] } -const details = { +const merkleBranch = { success: true, - details: txDetails + merkle: { + block_height: 617812, + merkle: [ + '713d6c7e6ce7bbea708d61162231eaa8ecb31c4c5dd84f81c20409a90069cb24', + '03dbaec78d4a52fbaf3c7aa5d3fccd9d8654f323940716ddf5ee2e4bda458fde' + ], + pos: 4 + } } -const detailsArray = { +const merkleBranches = { success: true, - transactions: [ + branches: [ { txid: txDetails.txid, - details: txDetails + height: 617812, + merkle: merkleBranch.merkle }, { txid: txDetails.txid, - details: txDetails + height: 617812, + merkle: merkleBranch.merkle } ] } @@ -260,48 +288,50 @@ const detailsArray = { const broadcast = { success: true, txid: txDetails.txid -} - -const txHistoryWithUnconfirmed = { - success: true, - transactions: [ - { - height: 668539, - tx_hash: - 'fd9bffca3dd7a2d801c87cb36e8b43949d2e171bb8e25e3d92cf39ac5e2ba179' - }, - { - height: 668544, - tx_hash: - '9d1b438aa55d75da8ebe9dcdfa4fa96714042ce0a19c8f3c10d86e1e3dcbccf5' - }, - { - fee: 440, - height: 0, - tx_hash: - '30d3fa9bf78eab2162d4f1894223e417e7275fdf0d590fe5085381dd475ff077' - }, - { - fee: 468, - height: -1, - tx_hash: - '4b93c26a4612b6819fde2bf5d1f6387bf92f49a3375c53a472001e7d76a80e59' - } - ] -} - -export default { - utxo, - utxos, - balance, - balances, - transaction, - transactions, - unconfirmed, - unconfirmedArray, +} + +const txHistoryWithUnconfirmed = { + success: true, + transactions: [ + { + height: 668539, + tx_hash: + 'fd9bffca3dd7a2d801c87cb36e8b43949d2e171bb8e25e3d92cf39ac5e2ba179' + }, + { + height: 668544, + tx_hash: + '9d1b438aa55d75da8ebe9dcdfa4fa96714042ce0a19c8f3c10d86e1e3dcbccf5' + }, + { + fee: 440, + height: 0, + tx_hash: + '30d3fa9bf78eab2162d4f1894223e417e7275fdf0d590fe5085381dd475ff077' + }, + { + fee: 468, + height: -1, + tx_hash: + '4b93c26a4612b6819fde2bf5d1f6387bf92f49a3375c53a472001e7d76a80e59' + } + ] +} + +export default { + utxo, + utxos, + balance, + balances, + transaction, + transactions, + unconfirmed, + unconfirmedArray, blockHeaders, details, detailsArray, + merkleBranch, + merkleBranches, broadcast, txHistoryWithUnconfirmed }