├── .gitignore ├── .jshintrc ├── .npmignore ├── .travis.yml ├── README.md ├── lib ├── addresses.js ├── blocks.js ├── common.js ├── currency.js ├── index.js ├── messages.js ├── ratelimiter.js ├── service.js ├── status.js ├── transactions.js └── utils.js ├── package.json ├── pools.json ├── test ├── addresses.js ├── blocks.js ├── currency.js ├── data │ └── blocks.json ├── index.js ├── messages.js ├── ratelimiter.js ├── status.js ├── transactions.js └── utils.js └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | # from https://github.com/github/gitignore/blob/master/Node.gitignore 2 | lib-cov 3 | *.seed 4 | *.log 5 | *.csv 6 | *.dat 7 | *.out 8 | *.pid 9 | *.gz 10 | *.swp 11 | tags 12 | pids 13 | logs 14 | results 15 | build 16 | 17 | node_modules 18 | 19 | # extras 20 | *.swp 21 | *.swo 22 | *~ 23 | .project 24 | peerdb.json 25 | 26 | npm-debug.log 27 | .nodemonignore 28 | 29 | .DS_Store 30 | db/txs/* 31 | db/txs 32 | db/testnet/txs/* 33 | db/testnet/txs 34 | db/blocks/* 35 | db/blocks 36 | db/testnet/blocks/* 37 | db/testnet/blocks 38 | 39 | README.html 40 | public 41 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, // Enable globals available when code is running inside of the NodeJS runtime environment. 3 | "browser": true, // Standard browser globals e.g. `window`, `document`. 4 | "esnext": true, // Allow ES.next specific features such as `const` and `let`. 5 | "bitwise": false, // Prohibit bitwise operators (&, |, ^, etc.). 6 | "camelcase": false, // Permit only camelcase for `var` and `object indexes`. 7 | "curly": false, // Require {} for every new block or scope. 8 | "eqeqeq": true, // Require triple equals i.e. `===`. 9 | "immed": true, // Require immediate invocations to be wrapped in parens e.g. `( function(){}() );` 10 | "latedef": true, // Prohibit variable use before definition. 11 | "newcap": true, // Require capitalization of all constructor functions e.g. `new F()`. 12 | "noarg": true, // Prohibit use of `arguments.caller` and `arguments.callee`. 13 | "quotmark": "single", // Define quotes to string values. 14 | "regexp": true, // Prohibit `.` and `[^...]` in regular expressions. 15 | "undef": true, // Require all non-global variables be declared before they are used. 16 | "unused": true, // Warn unused variables. 17 | "strict": true, // Require `use strict` pragma in every file. 18 | "trailing": true, // Prohibit trailing whitespaces. 19 | "smarttabs": false, // Suppresses warnings about mixed tabs and spaces 20 | "globals": { // Globals variables. 21 | "angular": true 22 | }, 23 | "predef": [ // Extra globals. 24 | "define", 25 | "require", 26 | "exports", 27 | "module", 28 | "describe", 29 | "before", 30 | "beforeEach", 31 | "after", 32 | "afterEach", 33 | "it", 34 | "inject", 35 | "$", 36 | "io", 37 | "app", 38 | "moment" 39 | ], 40 | "indent": false, // Specify indentation spacing 41 | "devel": true, // Allow development statements e.g. `console.log();`. 42 | "noempty": true // Prohibit use of empty blocks. 43 | } 44 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | lib-cov 2 | *.seed 3 | *.log 4 | *.csv 5 | *.dat 6 | *.out 7 | *.pid 8 | *.gz 9 | *.swp 10 | tags 11 | pids 12 | logs 13 | results 14 | build 15 | 16 | node_modules 17 | 18 | # extras 19 | *.swp 20 | *.swo 21 | *~ 22 | .project 23 | peerdb.json 24 | 25 | npm-debug.log 26 | .nodemonignore 27 | 28 | .DS_Store 29 | db/txs/* 30 | db/txs 31 | db/testnet/txs/* 32 | db/testnet/txs 33 | db/blocks/* 34 | db/blocks 35 | db/testnet/blocks/* 36 | db/testnet/blocks 37 | 38 | README.html 39 | k* 40 | public 41 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 'v0.12.7' 4 | - 'v4' 5 | install: 6 | - npm install 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Insight API [DEPRECATED] 2 | ======= 3 | 4 | **Important Notice**: `insight-lite-api` and the litecore stack are now deprecated. 5 | Please switch to alternative packages. `insight-lite-api` should be considered unsafe for production use. 6 | 7 | Alternatives include: 8 | - [insight](https://github.com/bitpay/bitcore/tree/master/packages/insight): a modern fork of insight-lite-api with similar api (now supports litecoin) 9 | 10 | ## License 11 | (The MIT License) 12 | 13 | Permission is hereby granted, free of charge, to any person obtaining 14 | a copy of this software and associated documentation files (the 15 | 'Software'), to deal in the Software without restriction, including 16 | without limitation the rights to use, copy, modify, merge, publish, 17 | distribute, sublicense, and/or sell copies of the Software, and to 18 | permit persons to whom the Software is furnished to do so, subject to 19 | the following conditions: 20 | 21 | The above copyright notice and this permission notice shall be 22 | included in all copies or substantial portions of the Software. 23 | 24 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 25 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 26 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 27 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 28 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 29 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 30 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 31 | -------------------------------------------------------------------------------- /lib/addresses.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var bitcore = require('litecore-lib'); 4 | var async = require('async'); 5 | var TxController = require('./transactions'); 6 | var Common = require('./common'); 7 | 8 | function AddressController(node) { 9 | this.node = node; 10 | this.txController = new TxController(node); 11 | this.common = new Common({log: this.node.log}); 12 | } 13 | 14 | AddressController.prototype.show = function(req, res) { 15 | var self = this; 16 | var options = { 17 | noTxList: parseInt(req.query.noTxList) 18 | }; 19 | 20 | if (req.query.from && req.query.to) { 21 | options.from = parseInt(req.query.from); 22 | options.to = parseInt(req.query.to); 23 | } 24 | 25 | this.getAddressSummary(req.addr, options, function(err, data) { 26 | if(err) { 27 | return self.common.handleErrors(err, res); 28 | } 29 | 30 | res.jsonp(data); 31 | }); 32 | }; 33 | 34 | AddressController.prototype.balance = function(req, res) { 35 | this.addressSummarySubQuery(req, res, 'balanceSat'); 36 | }; 37 | 38 | AddressController.prototype.totalReceived = function(req, res) { 39 | this.addressSummarySubQuery(req, res, 'totalReceivedSat'); 40 | }; 41 | 42 | AddressController.prototype.totalSent = function(req, res) { 43 | this.addressSummarySubQuery(req, res, 'totalSentSat'); 44 | }; 45 | 46 | AddressController.prototype.unconfirmedBalance = function(req, res) { 47 | this.addressSummarySubQuery(req, res, 'unconfirmedBalanceSat'); 48 | }; 49 | 50 | AddressController.prototype.addressSummarySubQuery = function(req, res, param) { 51 | var self = this; 52 | this.getAddressSummary(req.addr, {}, function(err, data) { 53 | if(err) { 54 | return self.common.handleErrors(err, res); 55 | } 56 | 57 | res.jsonp(data[param]); 58 | }); 59 | }; 60 | 61 | AddressController.prototype.getAddressSummary = function(address, options, callback) { 62 | 63 | this.node.getAddressSummary(address, options, function(err, summary) { 64 | if(err) { 65 | return callback(err); 66 | } 67 | 68 | var transformed = { 69 | addrStr: address, 70 | balance: summary.balance / 1e8, 71 | balanceSat: summary.balance, 72 | totalReceived: summary.totalReceived / 1e8, 73 | totalReceivedSat: summary.totalReceived, 74 | totalSent: summary.totalSpent / 1e8, 75 | totalSentSat: summary.totalSpent, 76 | unconfirmedBalance: summary.unconfirmedBalance / 1e8, 77 | unconfirmedBalanceSat: summary.unconfirmedBalance, 78 | unconfirmedTxApperances: summary.unconfirmedAppearances, // misspelling - ew 79 | txApperances: summary.appearances, // yuck 80 | transactions: summary.txids 81 | }; 82 | 83 | callback(null, transformed); 84 | }); 85 | }; 86 | 87 | AddressController.prototype.checkAddr = function(req, res, next) { 88 | req.addr = req.params.addr; 89 | this.check(req, res, next, [req.addr]); 90 | }; 91 | 92 | AddressController.prototype.checkAddrs = function(req, res, next) { 93 | if(req.body.addrs) { 94 | req.addrs = req.body.addrs.split(','); 95 | } else { 96 | req.addrs = req.params.addrs.split(','); 97 | } 98 | 99 | this.check(req, res, next, req.addrs); 100 | }; 101 | 102 | AddressController.prototype.check = function(req, res, next, addresses) { 103 | var self = this; 104 | if(!addresses.length || !addresses[0]) { 105 | return self.common.handleErrors({ 106 | message: 'Must include address', 107 | code: 1 108 | }, res); 109 | } 110 | 111 | for(var i = 0; i < addresses.length; i++) { 112 | try { 113 | var a = new bitcore.Address(addresses[i]); 114 | } catch(e) { 115 | return self.common.handleErrors({ 116 | message: 'Invalid address: ' + e.message, 117 | code: 1 118 | }, res); 119 | } 120 | } 121 | 122 | next(); 123 | }; 124 | 125 | AddressController.prototype.utxo = function(req, res) { 126 | var self = this; 127 | 128 | this.node.getAddressUnspentOutputs(req.addr, {}, function(err, utxos) { 129 | if(err) { 130 | return self.common.handleErrors(err, res); 131 | } else if (!utxos.length) { 132 | return res.jsonp([]); 133 | } 134 | res.jsonp(utxos.map(self.transformUtxo.bind(self))); 135 | }); 136 | }; 137 | 138 | AddressController.prototype.multiutxo = function(req, res) { 139 | var self = this; 140 | this.node.getAddressUnspentOutputs(req.addrs, true, function(err, utxos) { 141 | if(err && err.code === -5) { 142 | return res.jsonp([]); 143 | } else if(err) { 144 | return self.common.handleErrors(err, res); 145 | } 146 | 147 | res.jsonp(utxos.map(self.transformUtxo.bind(self))); 148 | }); 149 | }; 150 | 151 | AddressController.prototype.transformUtxo = function(utxoArg) { 152 | var utxo = { 153 | address: utxoArg.address, 154 | txid: utxoArg.txid, 155 | vout: utxoArg.outputIndex, 156 | scriptPubKey: utxoArg.script, 157 | amount: utxoArg.satoshis / 1e8, 158 | satoshis: utxoArg.satoshis 159 | }; 160 | if (utxoArg.height && utxoArg.height > 0) { 161 | utxo.height = utxoArg.height; 162 | utxo.confirmations = this.node.services.bitcoind.height - utxoArg.height + 1; 163 | } else { 164 | utxo.confirmations = 0; 165 | } 166 | if (utxoArg.timestamp) { 167 | utxo.ts = utxoArg.timestamp; 168 | } 169 | return utxo; 170 | }; 171 | 172 | AddressController.prototype._getTransformOptions = function(req) { 173 | return { 174 | noAsm: parseInt(req.query.noAsm) ? true : false, 175 | noScriptSig: parseInt(req.query.noScriptSig) ? true : false, 176 | noSpent: parseInt(req.query.noSpent) ? true : false 177 | }; 178 | }; 179 | 180 | AddressController.prototype.multitxs = function(req, res, next) { 181 | var self = this; 182 | 183 | var options = { 184 | from: parseInt(req.query.from) || parseInt(req.body.from) || 0 185 | }; 186 | 187 | options.to = parseInt(req.query.to) || parseInt(req.body.to) || parseInt(options.from) + 10; 188 | 189 | self.node.getAddressHistory(req.addrs, options, function(err, result) { 190 | if(err) { 191 | return self.common.handleErrors(err, res); 192 | } 193 | 194 | var transformOptions = self._getTransformOptions(req); 195 | 196 | self.transformAddressHistoryForMultiTxs(result.items, transformOptions, function(err, items) { 197 | if (err) { 198 | return self.common.handleErrors(err, res); 199 | } 200 | res.jsonp({ 201 | totalItems: result.totalCount, 202 | from: options.from, 203 | to: Math.min(options.to, result.totalCount), 204 | items: items 205 | }); 206 | }); 207 | 208 | }); 209 | }; 210 | 211 | AddressController.prototype.transformAddressHistoryForMultiTxs = function(txinfos, options, callback) { 212 | var self = this; 213 | 214 | var items = txinfos.map(function(txinfo) { 215 | return txinfo.tx; 216 | }).filter(function(value, index, self) { 217 | return self.indexOf(value) === index; 218 | }); 219 | 220 | async.map( 221 | items, 222 | function(item, next) { 223 | self.txController.transformTransaction(item, options, next); 224 | }, 225 | callback 226 | ); 227 | }; 228 | 229 | 230 | 231 | module.exports = AddressController; 232 | -------------------------------------------------------------------------------- /lib/blocks.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var async = require('async'); 4 | var bitcore = require('litecore-lib'); 5 | var _ = bitcore.deps._; 6 | var pools = require('../pools.json'); 7 | var BN = bitcore.crypto.BN; 8 | var LRU = require('lru-cache'); 9 | var Common = require('./common'); 10 | 11 | function BlockController(options) { 12 | var self = this; 13 | this.node = options.node; 14 | 15 | this.blockSummaryCache = LRU(options.blockSummaryCacheSize || BlockController.DEFAULT_BLOCKSUMMARY_CACHE_SIZE); 16 | this.blockCacheConfirmations = 6; 17 | this.blockCache = LRU(options.blockCacheSize || BlockController.DEFAULT_BLOCK_CACHE_SIZE); 18 | 19 | this.poolStrings = {}; 20 | pools.forEach(function(pool) { 21 | pool.searchStrings.forEach(function(s) { 22 | self.poolStrings[s] = { 23 | poolName: pool.poolName, 24 | url: pool.url 25 | }; 26 | }); 27 | }); 28 | 29 | this.common = new Common({log: this.node.log}); 30 | } 31 | 32 | var BLOCK_LIMIT = 200; 33 | 34 | BlockController.DEFAULT_BLOCKSUMMARY_CACHE_SIZE = 1000000; 35 | BlockController.DEFAULT_BLOCK_CACHE_SIZE = 1000; 36 | 37 | function isHexadecimal(hash) { 38 | if (!_.isString(hash)) { 39 | return false; 40 | } 41 | return /^[0-9a-fA-F]+$/.test(hash); 42 | } 43 | 44 | BlockController.prototype.checkBlockHash = function(req, res, next) { 45 | var self = this; 46 | var hash = req.params.blockHash; 47 | if (hash.length < 64 || !isHexadecimal(hash)) { 48 | return self.common.handleErrors(null, res); 49 | } 50 | next(); 51 | }; 52 | 53 | /** 54 | * Find block by hash ... 55 | */ 56 | BlockController.prototype.block = function(req, res, next) { 57 | var self = this; 58 | var hash = req.params.blockHash; 59 | var blockCached = self.blockCache.get(hash); 60 | 61 | if (blockCached) { 62 | blockCached.confirmations = self.node.services.bitcoind.height - blockCached.height + 1; 63 | req.block = blockCached; 64 | next(); 65 | } else { 66 | self.node.getBlock(hash, function(err, block) { 67 | if((err && err.code === -5) || (err && err.code === -8)) { 68 | return self.common.handleErrors(null, res); 69 | } else if(err) { 70 | return self.common.handleErrors(err, res); 71 | } 72 | self.node.services.bitcoind.getBlockHeader(hash, function(err, info) { 73 | if (err) { 74 | return self.common.handleErrors(err, res); 75 | } 76 | var blockResult = self.transformBlock(block, info); 77 | if (blockResult.confirmations >= self.blockCacheConfirmations) { 78 | self.blockCache.set(hash, blockResult); 79 | } 80 | req.block = blockResult; 81 | next(); 82 | }); 83 | }); 84 | } 85 | }; 86 | 87 | /** 88 | * Find rawblock by hash and height... 89 | */ 90 | BlockController.prototype.rawBlock = function(req, res, next) { 91 | var self = this; 92 | var blockHash = req.params.blockHash; 93 | 94 | self.node.getRawBlock(blockHash, function(err, blockBuffer) { 95 | if((err && err.code === -5) || (err && err.code === -8)) { 96 | return self.common.handleErrors(null, res); 97 | } else if(err) { 98 | return self.common.handleErrors(err, res); 99 | } 100 | req.rawBlock = { 101 | rawblock: blockBuffer.toString('hex') 102 | }; 103 | next(); 104 | }); 105 | 106 | }; 107 | 108 | BlockController.prototype._normalizePrevHash = function(hash) { 109 | // TODO fix bitcore to give back null instead of null hash 110 | if (hash !== '0000000000000000000000000000000000000000000000000000000000000000') { 111 | return hash; 112 | } else { 113 | return null; 114 | } 115 | }; 116 | 117 | BlockController.prototype.transformBlock = function(block, info) { 118 | var blockObj = block.toObject(); 119 | var transactionIds = blockObj.transactions.map(function(tx) { 120 | return tx.hash; 121 | }); 122 | return { 123 | hash: block.hash, 124 | size: block.toBuffer().length, 125 | height: info.height, 126 | version: blockObj.header.version, 127 | merkleroot: blockObj.header.merkleRoot, 128 | tx: transactionIds, 129 | time: blockObj.header.time, 130 | nonce: blockObj.header.nonce, 131 | bits: blockObj.header.bits.toString(16), 132 | difficulty: block.header.getDifficulty(), 133 | chainwork: info.chainWork, 134 | confirmations: info.confirmations, 135 | previousblockhash: this._normalizePrevHash(blockObj.header.prevHash), 136 | nextblockhash: info.nextHash, 137 | reward: this.getBlockReward(info.height) / 1e8, 138 | isMainChain: (info.confirmations !== -1), 139 | poolInfo: this.getPoolInfo(block) 140 | }; 141 | }; 142 | 143 | /** 144 | * Show block 145 | */ 146 | BlockController.prototype.show = function(req, res) { 147 | if (req.block) { 148 | res.jsonp(req.block); 149 | } 150 | }; 151 | 152 | BlockController.prototype.showRaw = function(req, res) { 153 | if (req.rawBlock) { 154 | res.jsonp(req.rawBlock); 155 | } 156 | }; 157 | 158 | BlockController.prototype.blockIndex = function(req, res) { 159 | var self = this; 160 | var height = req.params.height; 161 | this.node.services.bitcoind.getBlockHeader(parseInt(height), function(err, info) { 162 | if (err) { 163 | return self.common.handleErrors(err, res); 164 | } 165 | res.jsonp({ 166 | blockHash: info.hash 167 | }); 168 | }); 169 | }; 170 | 171 | BlockController.prototype._getBlockSummary = function(hash, moreTimestamp, next) { 172 | var self = this; 173 | 174 | function finish(result) { 175 | if (moreTimestamp > result.time) { 176 | moreTimestamp = result.time; 177 | } 178 | return next(null, result); 179 | } 180 | 181 | var summaryCache = self.blockSummaryCache.get(hash); 182 | 183 | if (summaryCache) { 184 | finish(summaryCache); 185 | } else { 186 | self.node.services.bitcoind.getRawBlock(hash, function(err, blockBuffer) { 187 | if (err) { 188 | return next(err); 189 | } 190 | 191 | var br = new bitcore.encoding.BufferReader(blockBuffer); 192 | 193 | // take a shortcut to get number of transactions and the blocksize. 194 | // Also reads the coinbase transaction and only that. 195 | // Old code parsed all transactions in every block _and_ then encoded 196 | // them all back together to get the binary size of the block. 197 | // FIXME: This code might still read the whole block. Fixing that 198 | // would require changes in bitcore-node. 199 | var header = bitcore.BlockHeader.fromBufferReader(br); 200 | var info = {}; 201 | var txlength = br.readVarintNum(); 202 | info.transactions = [bitcore.Transaction().fromBufferReader(br)]; 203 | 204 | self.node.services.bitcoind.getBlockHeader(hash, function(err, blockHeader) { 205 | if (err) { 206 | return next(err); 207 | } 208 | var height = blockHeader.height; 209 | 210 | var summary = { 211 | height: height, 212 | size: blockBuffer.length, 213 | hash: hash, 214 | time: header.time, 215 | txlength: txlength, 216 | poolInfo: self.getPoolInfo(info) 217 | }; 218 | 219 | var confirmations = self.node.services.bitcoind.height - height + 1; 220 | if (confirmations >= self.blockCacheConfirmations) { 221 | self.blockSummaryCache.set(hash, summary); 222 | } 223 | 224 | finish(summary); 225 | }); 226 | }); 227 | 228 | } 229 | }; 230 | 231 | // List blocks by date 232 | BlockController.prototype.list = function(req, res) { 233 | var self = this; 234 | 235 | var dateStr; 236 | var todayStr = this.formatTimestamp(new Date()); 237 | var isToday; 238 | 239 | if (req.query.blockDate) { 240 | dateStr = req.query.blockDate; 241 | var datePattern = /\d{4}-\d{2}-\d{2}/; 242 | if(!datePattern.test(dateStr)) { 243 | return self.common.handleErrors(new Error('Please use yyyy-mm-dd format'), res); 244 | } 245 | 246 | isToday = dateStr === todayStr; 247 | } else { 248 | dateStr = todayStr; 249 | isToday = true; 250 | } 251 | 252 | var gte = Math.round((new Date(dateStr)).getTime() / 1000); 253 | 254 | //pagination 255 | var lte = parseInt(req.query.startTimestamp) || gte + 86400; 256 | var prev = this.formatTimestamp(new Date((gte - 86400) * 1000)); 257 | var next = lte ? this.formatTimestamp(new Date(lte * 1000)) : null; 258 | var limit = parseInt(req.query.limit || BLOCK_LIMIT); 259 | var more = false; 260 | var moreTimestamp = lte; 261 | 262 | self.node.services.bitcoind.getBlockHashesByTimestamp(lte, gte, function(err, hashes) { 263 | if(err) { 264 | return self.common.handleErrors(err, res); 265 | } 266 | 267 | hashes.reverse(); 268 | 269 | if(hashes.length > limit) { 270 | more = true; 271 | hashes = hashes.slice(0, limit); 272 | } 273 | 274 | async.mapSeries( 275 | hashes, 276 | function(hash, next) { 277 | self._getBlockSummary(hash, moreTimestamp, next); 278 | }, 279 | function(err, blocks) { 280 | if(err) { 281 | return self.common.handleErrors(err, res); 282 | } 283 | 284 | blocks.sort(function(a, b) { 285 | return b.height - a.height; 286 | }); 287 | 288 | var data = { 289 | blocks: blocks, 290 | length: blocks.length, 291 | pagination: { 292 | next: next, 293 | prev: prev, 294 | currentTs: lte - 1, 295 | current: dateStr, 296 | isToday: isToday, 297 | more: more 298 | } 299 | }; 300 | 301 | if(more) { 302 | data.pagination.moreTs = moreTimestamp; 303 | } 304 | 305 | res.jsonp(data); 306 | } 307 | ); 308 | }); 309 | }; 310 | 311 | BlockController.prototype.getPoolInfo = function(block) { 312 | var coinbaseBuffer = block.transactions[0].inputs[0]._scriptBuffer; 313 | 314 | for(var k in this.poolStrings) { 315 | if (coinbaseBuffer.toString('utf-8').match(k)) { 316 | return this.poolStrings[k]; 317 | } 318 | } 319 | 320 | return {}; 321 | }; 322 | 323 | //helper to convert timestamps to yyyy-mm-dd format 324 | BlockController.prototype.formatTimestamp = function(date) { 325 | var yyyy = date.getUTCFullYear().toString(); 326 | var mm = (date.getUTCMonth() + 1).toString(); // getMonth() is zero-based 327 | var dd = date.getUTCDate().toString(); 328 | 329 | return yyyy + '-' + (mm[1] ? mm : '0' + mm[0]) + '-' + (dd[1] ? dd : '0' + dd[0]); //padding 330 | }; 331 | 332 | BlockController.prototype.getBlockReward = function(height) { 333 | var halvings = Math.floor(height / 840000); 334 | // Force block reward to zero when right shift is undefined. 335 | if (halvings >= 64) { 336 | return 0; 337 | } 338 | 339 | // Subsidy is cut in half every 210,000 blocks which will occur approximately every 4 years. 340 | var subsidy = new BN(50 * 1e8); 341 | subsidy = subsidy.shrn(halvings); 342 | 343 | return parseInt(subsidy.toString(10)); 344 | }; 345 | 346 | module.exports = BlockController; 347 | -------------------------------------------------------------------------------- /lib/common.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function Common(options) { 4 | this.log = options.log; 5 | } 6 | 7 | Common.prototype.notReady = function (err, res, p) { 8 | res.status(503).send('Server not yet ready. Sync Percentage:' + p); 9 | }; 10 | 11 | Common.prototype.handleErrors = function (err, res) { 12 | if (err) { 13 | if (err.code) { 14 | res.status(400).send(err.message + '. Code:' + err.code); 15 | } else { 16 | this.log.error(err.stack); 17 | res.status(503).send(err.message); 18 | } 19 | } else { 20 | res.status(404).send('Not found'); 21 | } 22 | }; 23 | 24 | module.exports = Common; 25 | -------------------------------------------------------------------------------- /lib/currency.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var request = require('request'); 4 | 5 | function CurrencyController(options) { 6 | this.node = options.node; 7 | var refresh = options.currencyRefresh || CurrencyController.DEFAULT_CURRENCY_DELAY; 8 | this.currencyDelay = refresh * 60000; 9 | this.bitstampRate = 0; 10 | this.timestamp = Date.now(); 11 | } 12 | 13 | CurrencyController.DEFAULT_CURRENCY_DELAY = 10; 14 | 15 | CurrencyController.prototype.index = function(req, res) { 16 | var self = this; 17 | var currentTime = Date.now(); 18 | if (self.bitstampRate === 0 || currentTime >= (self.timestamp + self.currencyDelay)) { 19 | self.timestamp = currentTime; 20 | request('https://www.bitstamp.net/api/v2/ticker/ltcusd', function(err, response, body) { 21 | if (err) { 22 | self.node.log.error(err); 23 | } 24 | if (!err && response.statusCode === 200) { 25 | self.bitstampRate = parseFloat(JSON.parse(body).last); 26 | } 27 | res.jsonp({ 28 | status: 200, 29 | data: { 30 | bitstamp: self.bitstampRate 31 | } 32 | }); 33 | }); 34 | } else { 35 | res.jsonp({ 36 | status: 200, 37 | data: { 38 | bitstamp: self.bitstampRate 39 | } 40 | }); 41 | } 42 | 43 | }; 44 | 45 | module.exports = CurrencyController; 46 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Writable = require('stream').Writable; 4 | var bodyParser = require('body-parser'); 5 | var compression = require('compression'); 6 | var BaseService = require('./service'); 7 | var inherits = require('util').inherits; 8 | var BlockController = require('./blocks'); 9 | var TxController = require('./transactions'); 10 | var AddressController = require('./addresses'); 11 | var StatusController = require('./status'); 12 | var MessagesController = require('./messages'); 13 | var UtilsController = require('./utils'); 14 | var CurrencyController = require('./currency'); 15 | var RateLimiter = require('./ratelimiter'); 16 | var morgan = require('morgan'); 17 | var bitcore = require('litecore-lib'); 18 | var _ = bitcore.deps._; 19 | var $ = bitcore.util.preconditions; 20 | var Transaction = bitcore.Transaction; 21 | var EventEmitter = require('events').EventEmitter; 22 | 23 | /** 24 | * A service for Bitcore to enable HTTP routes to query information about the blockchain. 25 | * 26 | * @param {Object} options 27 | * @param {Boolean} options.enableCache - This will enable cache-control headers 28 | * @param {Number} options.cacheShortSeconds - The time to cache short lived cache responses. 29 | * @param {Number} options.cacheLongSeconds - The time to cache long lived cache responses. 30 | * @param {String} options.routePrefix - The URL route prefix 31 | */ 32 | var InsightAPI = function(options) { 33 | BaseService.call(this, options); 34 | 35 | // in minutes 36 | this.currencyRefresh = options.currencyRefresh || CurrencyController.DEFAULT_CURRENCY_DELAY; 37 | 38 | this.subscriptions = { 39 | inv: [] 40 | }; 41 | 42 | if (!_.isUndefined(options.enableCache)) { 43 | $.checkArgument(_.isBoolean(options.enableCache)); 44 | this.enableCache = options.enableCache; 45 | } 46 | this.cacheShortSeconds = options.cacheShortSeconds; 47 | this.cacheLongSeconds = options.cacheLongSeconds; 48 | 49 | this.rateLimiterOptions = options.rateLimiterOptions; 50 | this.disableRateLimiter = options.disableRateLimiter; 51 | 52 | this.blockSummaryCacheSize = options.blockSummaryCacheSize || BlockController.DEFAULT_BLOCKSUMMARY_CACHE_SIZE; 53 | this.blockCacheSize = options.blockCacheSize || BlockController.DEFAULT_BLOCK_CACHE_SIZE; 54 | 55 | if (!_.isUndefined(options.routePrefix)) { 56 | this.routePrefix = options.routePrefix; 57 | } else { 58 | this.routePrefix = this.name; 59 | } 60 | 61 | this.txController = new TxController(this.node); 62 | }; 63 | 64 | InsightAPI.dependencies = ['bitcoind', 'web']; 65 | 66 | inherits(InsightAPI, BaseService); 67 | 68 | InsightAPI.prototype.cache = function(maxAge) { 69 | var self = this; 70 | return function(req, res, next) { 71 | if (self.enableCache) { 72 | res.header('Cache-Control', 'public, max-age=' + maxAge); 73 | } 74 | next(); 75 | }; 76 | }; 77 | 78 | InsightAPI.prototype.cacheShort = function() { 79 | var seconds = this.cacheShortSeconds || 30; // thirty seconds 80 | return this.cache(seconds); 81 | }; 82 | 83 | InsightAPI.prototype.cacheLong = function() { 84 | var seconds = this.cacheLongSeconds || 86400; // one day 85 | return this.cache(seconds); 86 | }; 87 | 88 | InsightAPI.prototype.getRoutePrefix = function() { 89 | return this.routePrefix; 90 | }; 91 | 92 | InsightAPI.prototype.start = function(callback) { 93 | this.node.services.bitcoind.on('tx', this.transactionEventHandler.bind(this)); 94 | this.node.services.bitcoind.on('block', this.blockEventHandler.bind(this)); 95 | setImmediate(callback); 96 | }; 97 | 98 | InsightAPI.prototype.createLogInfoStream = function() { 99 | var self = this; 100 | 101 | function Log(options) { 102 | Writable.call(this, options); 103 | } 104 | inherits(Log, Writable); 105 | 106 | Log.prototype._write = function (chunk, enc, callback) { 107 | self.node.log.info(chunk.slice(0, chunk.length - 1)); // remove new line and pass to logger 108 | callback(); 109 | }; 110 | var stream = new Log(); 111 | 112 | return stream; 113 | }; 114 | 115 | InsightAPI.prototype.getRemoteAddress = function(req) { 116 | if (req.headers['cf-connecting-ip']) { 117 | return req.headers['cf-connecting-ip']; 118 | } 119 | return req.socket.remoteAddress; 120 | }; 121 | 122 | InsightAPI.prototype._getRateLimiter = function() { 123 | var rateLimiterOptions = _.isUndefined(this.rateLimiterOptions) ? {} : _.clone(this.rateLimiterOptions); 124 | rateLimiterOptions.node = this.node; 125 | var limiter = new RateLimiter(rateLimiterOptions); 126 | return limiter; 127 | }; 128 | 129 | InsightAPI.prototype.setupRoutes = function(app) { 130 | 131 | var self = this; 132 | 133 | //Enable rate limiter 134 | if (!this.disableRateLimiter) { 135 | var limiter = this._getRateLimiter(); 136 | app.use(limiter.middleware()); 137 | } 138 | 139 | //Setup logging 140 | morgan.token('remote-forward-addr', function(req){ 141 | return self.getRemoteAddress(req); 142 | }); 143 | var logFormat = ':remote-forward-addr ":method :url" :status :res[content-length] :response-time ":user-agent" '; 144 | var logStream = this.createLogInfoStream(); 145 | app.use(morgan(logFormat, {stream: logStream})); 146 | 147 | //Enable compression 148 | app.use(compression()); 149 | 150 | //Enable urlencoded data 151 | app.use(bodyParser.urlencoded({extended: true})); 152 | 153 | //Enable CORS 154 | app.use(function(req, res, next) { 155 | 156 | res.header('Access-Control-Allow-Origin', '*'); 157 | res.header('Access-Control-Allow-Methods', 'GET, HEAD, PUT, POST, OPTIONS'); 158 | res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept, Content-Length, Cache-Control, cf-connecting-ip'); 159 | 160 | var method = req.method && req.method.toUpperCase && req.method.toUpperCase(); 161 | 162 | if (method === 'OPTIONS') { 163 | res.statusCode = 204; 164 | res.end(); 165 | } else { 166 | next(); 167 | } 168 | }); 169 | 170 | //Block routes 171 | var blockOptions = { 172 | node: this.node, 173 | blockSummaryCacheSize: this.blockSummaryCacheSize, 174 | blockCacheSize: this.blockCacheSize 175 | }; 176 | var blocks = new BlockController(blockOptions); 177 | app.get('/blocks', this.cacheShort(), blocks.list.bind(blocks)); 178 | 179 | app.get('/block/:blockHash', this.cacheShort(), blocks.checkBlockHash.bind(blocks), blocks.show.bind(blocks)); 180 | app.param('blockHash', blocks.block.bind(blocks)); 181 | 182 | app.get('/rawblock/:blockHash', this.cacheLong(), blocks.checkBlockHash.bind(blocks), blocks.showRaw.bind(blocks)); 183 | app.param('blockHash', blocks.rawBlock.bind(blocks)); 184 | 185 | app.get('/block-index/:height', this.cacheShort(), blocks.blockIndex.bind(blocks)); 186 | app.param('height', blocks.blockIndex.bind(blocks)); 187 | 188 | // Transaction routes 189 | var transactions = new TxController(this.node); 190 | app.get('/tx/:txid', this.cacheShort(), transactions.show.bind(transactions)); 191 | app.param('txid', transactions.transaction.bind(transactions)); 192 | app.get('/txs', this.cacheShort(), transactions.list.bind(transactions)); 193 | app.post('/tx/send', transactions.send.bind(transactions)); 194 | 195 | // Raw Routes 196 | app.get('/rawtx/:txid', this.cacheLong(), transactions.showRaw.bind(transactions)); 197 | app.param('txid', transactions.rawTransaction.bind(transactions)); 198 | 199 | // Address routes 200 | var addresses = new AddressController(this.node); 201 | app.get('/addr/:addr', this.cacheShort(), addresses.checkAddr.bind(addresses), addresses.show.bind(addresses)); 202 | app.get('/addr/:addr/utxo', this.cacheShort(), addresses.checkAddr.bind(addresses), addresses.utxo.bind(addresses)); 203 | app.get('/addrs/:addrs/utxo', this.cacheShort(), addresses.checkAddrs.bind(addresses), addresses.multiutxo.bind(addresses)); 204 | app.post('/addrs/utxo', this.cacheShort(), addresses.checkAddrs.bind(addresses), addresses.multiutxo.bind(addresses)); 205 | app.get('/addrs/:addrs/txs', this.cacheShort(), addresses.checkAddrs.bind(addresses), addresses.multitxs.bind(addresses)); 206 | app.post('/addrs/txs', this.cacheShort(), addresses.checkAddrs.bind(addresses), addresses.multitxs.bind(addresses)); 207 | 208 | // Address property routes 209 | app.get('/addr/:addr/balance', this.cacheShort(), addresses.checkAddr.bind(addresses), addresses.balance.bind(addresses)); 210 | app.get('/addr/:addr/totalReceived', this.cacheShort(), addresses.checkAddr.bind(addresses), addresses.totalReceived.bind(addresses)); 211 | app.get('/addr/:addr/totalSent', this.cacheShort(), addresses.checkAddr.bind(addresses), addresses.totalSent.bind(addresses)); 212 | app.get('/addr/:addr/unconfirmedBalance', this.cacheShort(), addresses.checkAddr.bind(addresses), addresses.unconfirmedBalance.bind(addresses)); 213 | 214 | // Status route 215 | var status = new StatusController(this.node); 216 | app.get('/status', this.cacheShort(), status.show.bind(status)); 217 | app.get('/sync', this.cacheShort(), status.sync.bind(status)); 218 | app.get('/peer', this.cacheShort(), status.peer.bind(status)); 219 | app.get('/version', this.cacheShort(), status.version.bind(status)); 220 | 221 | // Address routes 222 | var messages = new MessagesController(this.node); 223 | app.get('/messages/verify', messages.verify.bind(messages)); 224 | app.post('/messages/verify', messages.verify.bind(messages)); 225 | 226 | // Utils route 227 | var utils = new UtilsController(this.node); 228 | app.get('/utils/estimatefee', utils.estimateFee.bind(utils)); 229 | 230 | // Currency 231 | var currency = new CurrencyController({ 232 | node: this.node, 233 | currencyRefresh: this.currencyRefresh 234 | }); 235 | app.get('/currency', currency.index.bind(currency)); 236 | 237 | // Not Found 238 | app.use(function(req, res) { 239 | res.status(404).jsonp({ 240 | status: 404, 241 | url: req.originalUrl, 242 | error: 'Not found' 243 | }); 244 | }); 245 | 246 | }; 247 | 248 | InsightAPI.prototype.getPublishEvents = function() { 249 | return [ 250 | { 251 | name: 'inv', 252 | scope: this, 253 | subscribe: this.subscribe.bind(this), 254 | unsubscribe: this.unsubscribe.bind(this), 255 | extraEvents: ['tx', 'block'] 256 | } 257 | ]; 258 | }; 259 | 260 | InsightAPI.prototype.blockEventHandler = function(hashBuffer) { 261 | // Notify inv subscribers 262 | for (var i = 0; i < this.subscriptions.inv.length; i++) { 263 | this.subscriptions.inv[i].emit('block', hashBuffer.toString('hex')); 264 | } 265 | }; 266 | InsightAPI.prototype.transactionEventHandler = function(txBuffer) { 267 | var tx = new Transaction().fromBuffer(txBuffer); 268 | var result = this.txController.transformInvTransaction(tx); 269 | 270 | for (var i = 0; i < this.subscriptions.inv.length; i++) { 271 | this.subscriptions.inv[i].emit('tx', result); 272 | } 273 | }; 274 | 275 | InsightAPI.prototype.subscribe = function(emitter) { 276 | $.checkArgument(emitter instanceof EventEmitter, 'First argument is expected to be an EventEmitter'); 277 | 278 | var emitters = this.subscriptions.inv; 279 | var index = emitters.indexOf(emitter); 280 | if(index === -1) { 281 | emitters.push(emitter); 282 | } 283 | }; 284 | 285 | InsightAPI.prototype.unsubscribe = function(emitter) { 286 | $.checkArgument(emitter instanceof EventEmitter, 'First argument is expected to be an EventEmitter'); 287 | 288 | var emitters = this.subscriptions.inv; 289 | var index = emitters.indexOf(emitter); 290 | if(index > -1) { 291 | emitters.splice(index, 1); 292 | } 293 | }; 294 | 295 | module.exports = InsightAPI; 296 | -------------------------------------------------------------------------------- /lib/messages.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var bitcore = require('litecore-lib'); 4 | var _ = bitcore.deps._; 5 | var Message = require('litecore-message'); 6 | var Common = require('./common'); 7 | 8 | function MessagesController(node) { 9 | this.node = node; 10 | this.common = new Common({log: this.node.log}); 11 | } 12 | 13 | MessagesController.prototype.verify = function(req, res) { 14 | var self = this; 15 | var address = req.body.address || req.query.address; 16 | var signature = req.body.signature || req.query.signature; 17 | var message = req.body.message || req.query.message; 18 | if(_.isUndefined(address) || _.isUndefined(signature) || _.isUndefined(message)) { 19 | return self.common.handleErrors({ 20 | message: 'Missing parameters (expected "address", "signature" and "message")', 21 | code: 1 22 | }, res); 23 | } 24 | var valid; 25 | try { 26 | valid = new Message(message).verify(address, signature); 27 | } catch(err) { 28 | return self.common.handleErrors({ 29 | message: 'Unexpected error: ' + err.message, 30 | code: 1 31 | }, res); 32 | } 33 | res.json({'result': valid}); 34 | }; 35 | 36 | module.exports = MessagesController; 37 | -------------------------------------------------------------------------------- /lib/ratelimiter.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var THREE_HOURS = 3 * 60 * 60 * 1000; 4 | 5 | /** 6 | * A rate limiter to be used as an express middleware. 7 | * 8 | * @param {Object} options 9 | * @param {Object} options.node - The bitcore node object 10 | * @param {Number} options.limit - Number of requests for normal rate limiter 11 | * @param {Number} options.interval - Interval of the normal rate limiter 12 | * @param {Array} options.whitelist - IP addresses that should have whitelist rate limiting 13 | * @param {Array} options.blacklist - IP addresses that should be blacklist rate limiting 14 | * @param {Number} options.whitelistLimit - Number of requests for whitelisted clients 15 | * @param {Number} options.whitelistInterval - Interval for whitelisted clients 16 | * @param {Number} options.blacklistLimit - Number of requests for blacklisted clients 17 | * @param {Number} options.blacklistInterval - Interval for blacklisted clients 18 | */ 19 | function RateLimiter(options) { 20 | if (!(this instanceof RateLimiter)) { 21 | return new RateLimiter(options); 22 | } 23 | 24 | if (!options){ 25 | options = {}; 26 | } 27 | 28 | this.node = options.node; 29 | this.clients = {}; 30 | this.whitelist = options.whitelist || []; 31 | this.blacklist = options.blacklist || []; 32 | 33 | this.config = { 34 | whitelist: { 35 | totalRequests: options.whitelistLimit || 3 * 60 * 60 * 10, // 108,000 36 | interval: options.whitelistInterval || THREE_HOURS 37 | }, 38 | blacklist: { 39 | totalRequests: options.blacklistLimit || 0, 40 | interval: options.blacklistInterval || THREE_HOURS 41 | }, 42 | normal: { 43 | totalRequests: options.limit || 3 * 60 * 60, // 10,800 44 | interval: options.interval || THREE_HOURS 45 | } 46 | }; 47 | 48 | } 49 | 50 | RateLimiter.prototype.middleware = function() { 51 | var self = this; 52 | return function(req, res, next) { 53 | self._middleware(req, res, next); 54 | }; 55 | }; 56 | 57 | RateLimiter.prototype._middleware = function(req, res, next) { 58 | 59 | var name = this.getClientName(req); 60 | var client = this.clients[name]; 61 | 62 | res.ratelimit = { 63 | clients: this.clients, 64 | exceeded: false 65 | }; 66 | 67 | if (!client) { 68 | client = this.addClient(name); 69 | } 70 | 71 | res.setHeader('X-RateLimit-Limit', this.config[client.type].totalRequests); 72 | res.setHeader('X-RateLimit-Remaining', this.config[client.type].totalRequests - client.visits); 73 | 74 | res.ratelimit.exceeded = this.exceeded(client); 75 | res.ratelimit.client = client; 76 | 77 | if (!this.exceeded(client)) { 78 | client.visits++; 79 | next(); 80 | } else { 81 | this.node.log.warn('Rate limited:', client); 82 | res.status(429).jsonp({ 83 | status: 429, 84 | error: 'Rate limit exceeded' 85 | }); 86 | } 87 | }; 88 | 89 | RateLimiter.prototype.exceeded = function(client) { 90 | if (this.config[client.type].totalRequests === -1) { 91 | return false; 92 | } else { 93 | return client.visits > this.config[client.type].totalRequests; 94 | } 95 | }; 96 | 97 | RateLimiter.prototype.getClientType = function(name) { 98 | if (this.whitelist.indexOf(name) > -1) { 99 | return 'whitelist'; 100 | } 101 | if (this.blacklist.indexOf(name) > -1) { 102 | return 'blacklist'; 103 | } 104 | return 'normal'; 105 | }; 106 | 107 | RateLimiter.prototype.getClientName = function(req) { 108 | var name = req.headers['cf-connecting-ip'] || req.headers['x-forwarded-for'] || req.connection.remoteAddress; 109 | return name; 110 | }; 111 | 112 | RateLimiter.prototype.addClient = function(name) { 113 | var self = this; 114 | 115 | var client = { 116 | name: name, 117 | type: this.getClientType(name), 118 | visits: 1 119 | }; 120 | 121 | var resetTime = this.config[client.type].interval; 122 | 123 | setTimeout(function() { 124 | delete self.clients[name]; 125 | }, resetTime).unref(); 126 | 127 | this.clients[name] = client; 128 | 129 | return client; 130 | 131 | }; 132 | 133 | module.exports = RateLimiter; 134 | -------------------------------------------------------------------------------- /lib/service.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var util = require('util'); 4 | var EventEmitter = require('events').EventEmitter; 5 | 6 | var Service = function(options) { 7 | EventEmitter.call(this); 8 | 9 | this.node = options.node; 10 | this.name = options.name; 11 | }; 12 | 13 | util.inherits(Service, EventEmitter); 14 | 15 | /** 16 | * Describes the dependencies that should be loaded before this service. 17 | */ 18 | Service.dependencies = []; 19 | 20 | /** 21 | * blockHandler 22 | * @param {Block} block - the block being added or removed from the chain 23 | * @param {Boolean} add - whether the block is being added or removed 24 | * @param {Function} callback - call with the leveldb database operations to perform 25 | */ 26 | Service.prototype.blockHandler = function(block, add, callback) { 27 | // implement in the child class 28 | setImmediate(function() { 29 | callback(null, []); 30 | }); 31 | }; 32 | 33 | /** 34 | * the bus events available for subscription 35 | * @return {Array} an array of event info 36 | */ 37 | Service.prototype.getPublishEvents = function() { 38 | // Example: 39 | // return [ 40 | // ['eventname', this, this.subscribeEvent, this.unsubscribeEvent], 41 | // ]; 42 | return []; 43 | }; 44 | 45 | /** 46 | * the API methods to expose 47 | * @return {Array} return array of methods 48 | */ 49 | Service.prototype.getAPIMethods = function() { 50 | // Example: 51 | // return [ 52 | // ['getData', this, this.getData, 1] 53 | // ]; 54 | 55 | return []; 56 | }; 57 | 58 | // Example: 59 | // Service.prototype.getData = function(arg1, callback) { 60 | // 61 | // }; 62 | 63 | /** 64 | * Function which is called when module is first initialized 65 | */ 66 | Service.prototype.start = function(done) { 67 | setImmediate(done); 68 | }; 69 | 70 | /** 71 | * Function to be called when bitcore-node is stopped 72 | */ 73 | Service.prototype.stop = function(done) { 74 | setImmediate(done); 75 | }; 76 | 77 | /** 78 | * Setup express routes 79 | * @param {Express} app 80 | */ 81 | Service.prototype.setupRoutes = function(app) { 82 | // Setup express routes here 83 | }; 84 | 85 | Service.prototype.getRoutePrefix = function() { 86 | return this.name; 87 | }; 88 | 89 | 90 | 91 | module.exports = Service; 92 | -------------------------------------------------------------------------------- /lib/status.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Common = require('./common'); 4 | 5 | function StatusController(node) { 6 | this.node = node; 7 | this.common = new Common({log: this.node.log}); 8 | } 9 | 10 | StatusController.prototype.show = function(req, res) { 11 | var self = this; 12 | var option = req.query.q; 13 | 14 | switch(option) { 15 | case 'getDifficulty': 16 | this.getDifficulty(function(err, result) { 17 | if (err) { 18 | return self.common.handleErrors(err, res); 19 | } 20 | res.jsonp(result); 21 | }); 22 | break; 23 | case 'getLastBlockHash': 24 | res.jsonp(this.getLastBlockHash()); 25 | break; 26 | case 'getBestBlockHash': 27 | this.getBestBlockHash(function(err, result) { 28 | if (err) { 29 | return self.common.handleErrors(err, res); 30 | } 31 | res.jsonp(result); 32 | }); 33 | break; 34 | case 'getInfo': 35 | default: 36 | this.getInfo(function(err, result) { 37 | if (err) { 38 | return self.common.handleErrors(err, res); 39 | } 40 | res.jsonp({ 41 | info: result 42 | }); 43 | }); 44 | } 45 | }; 46 | 47 | StatusController.prototype.getInfo = function(callback) { 48 | this.node.services.bitcoind.getInfo(function(err, result) { 49 | if (err) { 50 | return callback(err); 51 | } 52 | var info = { 53 | version: result.version, 54 | protocolversion: result.protocolVersion, 55 | blocks: result.blocks, 56 | timeoffset: result.timeOffset, 57 | connections: result.connections, 58 | proxy: result.proxy, 59 | difficulty: result.difficulty, 60 | testnet: result.testnet, 61 | relayfee: result.relayFee, 62 | errors: result.errors, 63 | network: result.network 64 | }; 65 | callback(null, info); 66 | }); 67 | }; 68 | 69 | StatusController.prototype.getLastBlockHash = function() { 70 | var hash = this.node.services.bitcoind.tiphash; 71 | return { 72 | syncTipHash: hash, 73 | lastblockhash: hash 74 | }; 75 | }; 76 | 77 | StatusController.prototype.getBestBlockHash = function(callback) { 78 | this.node.services.bitcoind.getBestBlockHash(function(err, hash) { 79 | if (err) { 80 | return callback(err); 81 | } 82 | callback(null, { 83 | bestblockhash: hash 84 | }); 85 | }); 86 | }; 87 | 88 | StatusController.prototype.getDifficulty = function(callback) { 89 | this.node.services.bitcoind.getInfo(function(err, info) { 90 | if (err) { 91 | return callback(err); 92 | } 93 | callback(null, { 94 | difficulty: info.difficulty 95 | }); 96 | }); 97 | }; 98 | 99 | StatusController.prototype.sync = function(req, res) { 100 | var self = this; 101 | var status = 'syncing'; 102 | 103 | this.node.services.bitcoind.isSynced(function(err, synced) { 104 | if (err) { 105 | return self.common.handleErrors(err, res); 106 | } 107 | if (synced) { 108 | status = 'finished'; 109 | } 110 | 111 | self.node.services.bitcoind.syncPercentage(function(err, percentage) { 112 | if (err) { 113 | return self.common.handleErrors(err, res); 114 | } 115 | var info = { 116 | status: status, 117 | blockChainHeight: self.node.services.bitcoind.height, 118 | syncPercentage: Math.round(percentage), 119 | height: self.node.services.bitcoind.height, 120 | error: null, 121 | type: 'bitcore node' 122 | }; 123 | 124 | res.jsonp(info); 125 | 126 | }); 127 | 128 | }); 129 | 130 | }; 131 | 132 | // Hard coded to make insight ui happy, but not applicable 133 | StatusController.prototype.peer = function(req, res) { 134 | res.jsonp({ 135 | connected: true, 136 | host: '127.0.0.1', 137 | port: null 138 | }); 139 | }; 140 | 141 | StatusController.prototype.version = function(req, res) { 142 | var pjson = require('../package.json'); 143 | res.jsonp({ 144 | version: pjson.version 145 | }); 146 | }; 147 | 148 | module.exports = StatusController; 149 | -------------------------------------------------------------------------------- /lib/transactions.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var bitcore = require('litecore-lib'); 4 | var _ = bitcore.deps._; 5 | var $ = bitcore.util.preconditions; 6 | var Common = require('./common'); 7 | var async = require('async'); 8 | 9 | var MAXINT = 0xffffffff; // Math.pow(2, 32) - 1; 10 | 11 | function TxController(node) { 12 | this.node = node; 13 | this.common = new Common({log: this.node.log}); 14 | } 15 | 16 | TxController.prototype.show = function(req, res) { 17 | if (req.transaction) { 18 | res.jsonp(req.transaction); 19 | } 20 | }; 21 | 22 | /** 23 | * Find transaction by hash ... 24 | */ 25 | TxController.prototype.transaction = function(req, res, next) { 26 | var self = this; 27 | var txid = req.params.txid; 28 | 29 | this.node.getDetailedTransaction(txid, function(err, transaction) { 30 | if (err && err.code === -5) { 31 | return self.common.handleErrors(null, res); 32 | } else if(err) { 33 | return self.common.handleErrors(err, res); 34 | } 35 | 36 | self.transformTransaction(transaction, function(err, transformedTransaction) { 37 | if (err) { 38 | return self.common.handleErrors(err, res); 39 | } 40 | req.transaction = transformedTransaction; 41 | next(); 42 | }); 43 | 44 | }); 45 | }; 46 | 47 | TxController.prototype.transformTransaction = function(transaction, options, callback) { 48 | if (_.isFunction(options)) { 49 | callback = options; 50 | options = {}; 51 | } 52 | $.checkArgument(_.isFunction(callback)); 53 | 54 | var confirmations = 0; 55 | if(transaction.height >= 0) { 56 | confirmations = this.node.services.bitcoind.height - transaction.height + 1; 57 | } 58 | 59 | var transformed = { 60 | txid: transaction.hash, 61 | version: transaction.version, 62 | locktime: transaction.locktime 63 | }; 64 | 65 | if(transaction.coinbase) { 66 | transformed.vin = [ 67 | { 68 | coinbase: transaction.inputs[0].script, 69 | sequence: transaction.inputs[0].sequence, 70 | n: 0 71 | } 72 | ]; 73 | } else { 74 | transformed.vin = transaction.inputs.map(this.transformInput.bind(this, options)); 75 | } 76 | 77 | transformed.vout = transaction.outputs.map(this.transformOutput.bind(this, options)); 78 | 79 | transformed.blockhash = transaction.blockHash; 80 | transformed.blockheight = transaction.height; 81 | transformed.confirmations = confirmations; 82 | // TODO consider mempool txs with receivedTime? 83 | var time = transaction.blockTimestamp ? transaction.blockTimestamp : Math.round(Date.now() / 1000); 84 | transformed.time = time; 85 | if (transformed.confirmations) { 86 | transformed.blocktime = transformed.time; 87 | } 88 | 89 | if(transaction.coinbase) { 90 | transformed.isCoinBase = true; 91 | } 92 | 93 | transformed.valueOut = transaction.outputSatoshis / 1e8; 94 | transformed.size = transaction.size; // can be virtual size (not equal to actual) if segwit 95 | if (!transaction.coinbase) { 96 | transformed.valueIn = transaction.inputSatoshis / 1e8; 97 | transformed.fees = transaction.feeSatoshis / 1e8; 98 | } 99 | 100 | callback(null, transformed); 101 | }; 102 | 103 | TxController.prototype.transformInput = function(options, input, index) { 104 | // Input scripts are validated and can be assumed to be valid 105 | var transformed = { 106 | txid: input.prevTxId, 107 | vout: input.outputIndex, 108 | sequence: input.sequence, 109 | n: index 110 | }; 111 | 112 | if (!options.noScriptSig) { 113 | transformed.scriptSig = { 114 | hex: input.script 115 | }; 116 | if (!options.noAsm) { 117 | transformed.scriptSig.asm = input.scriptAsm; 118 | } 119 | } 120 | 121 | transformed.addr = input.address; 122 | transformed.valueSat = input.satoshis; 123 | transformed.value = input.satoshis / 1e8; 124 | transformed.doubleSpentTxID = null; // TODO 125 | //transformed.isConfirmed = null; // TODO 126 | //transformed.confirmations = null; // TODO 127 | //transformed.unconfirmedInput = null; // TODO 128 | 129 | return transformed; 130 | }; 131 | 132 | TxController.prototype.transformOutput = function(options, output, index) { 133 | var transformed = { 134 | value: (output.satoshis / 1e8).toFixed(8), 135 | n: index, 136 | scriptPubKey: { 137 | hex: output.script 138 | } 139 | }; 140 | 141 | if (!options.noAsm) { 142 | transformed.scriptPubKey.asm = output.scriptAsm; 143 | } 144 | 145 | if (!options.noSpent) { 146 | transformed.spentTxId = output.spentTxId || null; 147 | transformed.spentIndex = _.isUndefined(output.spentIndex) ? null : output.spentIndex; 148 | transformed.spentHeight = output.spentHeight || null; 149 | } 150 | 151 | if (output.address) { 152 | transformed.scriptPubKey.addresses = [output.address]; 153 | var address = bitcore.Address(output.address); //TODO return type from bitcore-node 154 | transformed.scriptPubKey.type = address.type; 155 | } 156 | return transformed; 157 | }; 158 | 159 | TxController.prototype.transformInvTransaction = function(transaction) { 160 | var self = this; 161 | 162 | var valueOut = 0; 163 | var vout = []; 164 | for (var i = 0; i < transaction.outputs.length; i++) { 165 | var output = transaction.outputs[i]; 166 | valueOut += output.satoshis; 167 | if (output.script) { 168 | var address = output.script.toAddress(self.node.network); 169 | if (address) { 170 | var obj = {}; 171 | obj[address.toString()] = output.satoshis; 172 | vout.push(obj); 173 | } 174 | } 175 | } 176 | 177 | var isRBF = _.any(_.pluck(transaction.inputs, 'sequenceNumber'), function(seq) { 178 | return seq < MAXINT - 1; 179 | }); 180 | 181 | var transformed = { 182 | txid: transaction.hash, 183 | valueOut: valueOut / 1e8, 184 | vout: vout, 185 | isRBF: isRBF, 186 | }; 187 | 188 | return transformed; 189 | }; 190 | 191 | TxController.prototype.rawTransaction = function(req, res, next) { 192 | var self = this; 193 | var txid = req.params.txid; 194 | 195 | this.node.getTransaction(txid, function(err, transaction) { 196 | if (err && err.code === -5) { 197 | return self.common.handleErrors(null, res); 198 | } else if(err) { 199 | return self.common.handleErrors(err, res); 200 | } 201 | 202 | req.rawTransaction = { 203 | 'rawtx': transaction.toBuffer().toString('hex') 204 | }; 205 | 206 | next(); 207 | }); 208 | }; 209 | 210 | TxController.prototype.showRaw = function(req, res) { 211 | if (req.rawTransaction) { 212 | res.jsonp(req.rawTransaction); 213 | } 214 | }; 215 | 216 | TxController.prototype.list = function(req, res) { 217 | var self = this; 218 | 219 | var blockHash = req.query.block; 220 | var address = req.query.address; 221 | var page = parseInt(req.query.pageNum) || 0; 222 | var pageLength = 10; 223 | var pagesTotal = 1; 224 | 225 | if(blockHash) { 226 | self.node.getBlockOverview(blockHash, function(err, block) { 227 | if(err && err.code === -5) { 228 | return self.common.handleErrors(null, res); 229 | } else if(err) { 230 | return self.common.handleErrors(err, res); 231 | } 232 | 233 | var totalTxs = block.txids.length; 234 | var txids; 235 | 236 | if(!_.isUndefined(page)) { 237 | var start = page * pageLength; 238 | txids = block.txids.slice(start, start + pageLength); 239 | pagesTotal = Math.ceil(totalTxs / pageLength); 240 | } else { 241 | txids = block.txids; 242 | } 243 | 244 | async.mapSeries(txids, function(txid, next) { 245 | self.node.getDetailedTransaction(txid, function(err, transaction) { 246 | if (err) { 247 | return next(err); 248 | } 249 | self.transformTransaction(transaction, next); 250 | }); 251 | }, function(err, transformed) { 252 | if(err) { 253 | return self.common.handleErrors(err, res); 254 | } 255 | 256 | res.jsonp({ 257 | pagesTotal: pagesTotal, 258 | txs: transformed 259 | }); 260 | }); 261 | 262 | }); 263 | } else if(address) { 264 | var options = { 265 | from: page * pageLength, 266 | to: (page + 1) * pageLength 267 | }; 268 | 269 | self.node.getAddressHistory(address, options, function(err, result) { 270 | if(err) { 271 | return self.common.handleErrors(err, res); 272 | } 273 | 274 | var txs = result.items.map(function(info) { 275 | return info.tx; 276 | }).filter(function(value, index, self) { 277 | return self.indexOf(value) === index; 278 | }); 279 | 280 | async.map( 281 | txs, 282 | function(tx, next) { 283 | self.transformTransaction(tx, next); 284 | }, 285 | function(err, transformed) { 286 | if (err) { 287 | return self.common.handleErrors(err, res); 288 | } 289 | res.jsonp({ 290 | pagesTotal: Math.ceil(result.totalCount / pageLength), 291 | txs: transformed 292 | }); 293 | } 294 | ); 295 | }); 296 | } else { 297 | return self.common.handleErrors(new Error('Block hash or address expected'), res); 298 | } 299 | }; 300 | 301 | TxController.prototype.send = function(req, res) { 302 | var self = this; 303 | this.node.sendTransaction(req.body.rawtx, function(err, txid) { 304 | if(err) { 305 | // TODO handle specific errors 306 | return self.common.handleErrors(err, res); 307 | } 308 | 309 | res.json({'txid': txid}); 310 | }); 311 | }; 312 | 313 | module.exports = TxController; 314 | -------------------------------------------------------------------------------- /lib/utils.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var _ = require('lodash'); 4 | var async = require('async'); 5 | var Common = require('./common'); 6 | 7 | function UtilsController(node) { 8 | this.node = node; 9 | this.common = new Common({log: this.node.log}); 10 | } 11 | 12 | UtilsController.prototype.estimateFee = function(req, res) { 13 | var self = this; 14 | var args = req.query.nbBlocks || '2'; 15 | var nbBlocks = args.split(','); 16 | 17 | async.map(nbBlocks, function(n, next) { 18 | var num = parseInt(n); 19 | // Insight and Bitcoin JSON-RPC return bitcoin for this value (instead of satoshis). 20 | self.node.services.bitcoind.estimateFee(num, function(err, fee) { 21 | if (err) { 22 | return next(err); 23 | } 24 | next(null, [num, fee]); 25 | }); 26 | }, function(err, result) { 27 | if (err) { 28 | return self.common.handleErrors(err, res); 29 | } 30 | res.jsonp(_.zipObject(result)); 31 | }); 32 | 33 | }; 34 | 35 | module.exports = UtilsController; 36 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "insight-lite-api", 3 | "description": "A Litecoin blockchain REST and web socket API service for Litecore Node.", 4 | "version": "0.4.4", 5 | "repository": "git://github.com/litecoin-project/insight-lite-api.git", 6 | "contributors": [ 7 | { 8 | "name": "Gustavo Cortez", 9 | "email": "cmgustavo83@gmail.com" 10 | }, 11 | { 12 | "name": "Ivan Socolsky", 13 | "email": "jungans@gmail.com" 14 | }, 15 | { 16 | "name": "Juan Ignacio Sosa Lopez", 17 | "email": "bechilandia@gmail.com" 18 | }, 19 | { 20 | "name": "Manuel Araoz", 21 | "email": "manuelaraoz@gmail.com" 22 | }, 23 | { 24 | "name": "Matias Alejo Garcia", 25 | "email": "ematiu@gmail.com" 26 | }, 27 | { 28 | "name": "Mario Colque", 29 | "email": "colquemario@gmail.com" 30 | }, 31 | { 32 | "name": "Patrick Nagurny", 33 | "email": "patrick@bitpay.com" 34 | }, 35 | { 36 | "name": "Braydon Fuller", 37 | "email": "braydon@bitpay.com" 38 | } 39 | ], 40 | "bugs": { 41 | "url": "https://github.com/litecoin-project/insight-lite-api/issues" 42 | }, 43 | "homepage": "https://github.com/litecoin-project/insight-lite-api", 44 | "license": "MIT", 45 | "keywords": [ 46 | "insight", 47 | "insight api", 48 | "blockchain", 49 | "litecoin api", 50 | "blockchain api", 51 | "json", 52 | "litecore" 53 | ], 54 | "engines": { 55 | "node": ">=0.12.0" 56 | }, 57 | "scripts": { 58 | "test": "NODE_ENV=test mocha -R spec --recursive" 59 | }, 60 | "main": "lib", 61 | "bitcoreNode": "lib", 62 | "dependencies": { 63 | "async": "*", 64 | "litecore-lib": "^0.13.22", 65 | "litecore-message": "^1.0.5", 66 | "body-parser": "^1.13.3", 67 | "compression": "^1.6.1", 68 | "lodash": "^2.4.1", 69 | "lru-cache": "^4.0.1", 70 | "morgan": "^1.7.0", 71 | "request": "^2.64.0" 72 | }, 73 | "devDependencies": { 74 | "chai": "^3.5.0", 75 | "mocha": "^2.4.5", 76 | "proxyquire": "^1.7.2", 77 | "should": "^8.3.1", 78 | "sinon": "^1.10.3" 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /pools.json: -------------------------------------------------------------------------------- 1 | [{ 2 | "poolName": "AntMiner", 3 | "url": "https://bitmaintech.com/", 4 | "searchStrings": [ 5 | "AntPool" 6 | ] 7 | }, 8 | { 9 | "poolName": "Discus Fish", 10 | "url": "http://f2pool.com/", 11 | "searchStrings": [ 12 | "🐟", 13 | "七彩神仙鱼", 14 | "Made in China", 15 | "Mined by user", 16 | "Mined by gumi", 17 | "Mined by " 18 | ] 19 | }, 20 | { 21 | "poolName": "Batpool", 22 | "url": "https://batpool.com", 23 | "searchStrings": [ 24 | "BATPOOL" 25 | ] 26 | }, 27 | { 28 | "poolName": "BW", 29 | "url": "https://www.bw.com", 30 | "searchStrings": [ 31 | "/BW/" 32 | ] 33 | }, 34 | { 35 | "poolName": "LTC.TOP", 36 | "url": "http://", 37 | "searchStrings": [ 38 | "LTC.TOP" 39 | ] 40 | }, 41 | { 42 | "poolName": "LitecoinPool", 43 | "url": "https://litecoinpool.org", 44 | "searchStrings": [ 45 | "/LP/" 46 | ] 47 | }, 48 | { 49 | "poolName": "ViaLTC", 50 | "url": "https://www.vialtc.com/", 51 | "searchStrings": [ 52 | "/ViaLTC/" 53 | ] 54 | }, 55 | { 56 | "poolName": "TBDice", 57 | "url": "http://ltc.tbdice.org/", 58 | "searchStrings": [ 59 | "TBDice" 60 | ] 61 | } 62 | ] 63 | -------------------------------------------------------------------------------- /test/addresses.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var sinon = require('sinon'); 3 | var should = require('should'); 4 | var AddressController = require('../lib/addresses'); 5 | var _ = require('lodash'); 6 | var bitcore = require('litecore-lib'); 7 | 8 | var txinfos = { 9 | totalCount: 2, 10 | items: [ 11 | { 12 | 'address': 'mkPvAKZ2rar6qeG3KjBtJHHMSP1wFZH7Er', 13 | 'satoshis': 2782729129, 14 | 'height': 534105, 15 | 'confirmations': 123, 16 | 'timestamp': 1441068774, 17 | 'fees': 35436, 18 | 'outputIndexes': [ 19 | 0 20 | ], 21 | 'inputIndexes': [], 22 | 'tx': { 23 | 'hash': 'bb0ec3b96209fac9529570ea6f83a86af2cceedde4aaf2bfcc4796680d23f1c7', 24 | 'version': 1, 25 | 'inputs': [ 26 | { 27 | 'prevTxId': 'ea5e5bafbf29cdf6f6097ab344128477e67889d4d6203cb43594836daa6cc425', 28 | 'outputIndex': 1, 29 | 'sequenceNumber': 4294967294, 30 | 'script': '483045022100f4d169783bef70e3943d2a617cce55d9fe4e33fc6f9880b8277265e2f619a97002201238648abcdf52960500664e969046d41755f7fc371971ebc78002fc418465a6012103acdcd31d51272403ce0829447e59e2ac9e08ed0bf92011cbf7420addf24534e6', 31 | 'scriptString': '72 0x3045022100f4d169783bef70e3943d2a617cce55d9fe4e33fc6f9880b8277265e2f619a97002201238648abcdf52960500664e969046d41755f7fc371971ebc78002fc418465a601 33 0x03acdcd31d51272403ce0829447e59e2ac9e08ed0bf92011cbf7420addf24534e6', 32 | 'output': { 33 | 'satoshis': 2796764565, 34 | 'script': '76a91488b1fe8aec5ae4358a11447a2f22b2781faedb9b88ac' 35 | } 36 | } 37 | ], 38 | 'outputs': [ 39 | { 40 | 'satoshis': 2782729129, 41 | 'script': '76a9143583efb5e64a4668c6c54bb5fcc30af4417b4f2d88ac' 42 | }, 43 | { 44 | 'satoshis': 14000000, 45 | 'script': '76a9149713201957f42379e574d7c70d506ee49c2c8ad688ac' 46 | } 47 | ], 48 | 'nLockTime': 534089 49 | } 50 | }, 51 | { 52 | 'address': 'mkPvAKZ2rar6qeG3KjBtJHHMSP1wFZH7Er', 53 | 'satoshis': -2782729129, 54 | 'height': 534110, 55 | 'confirmations': 118, 56 | 'timestamp': 1441072817, 57 | 'fees': 35437, 58 | 'outputIndexes': [], 59 | 'inputIndexes': [ 60 | '0' 61 | ], 62 | 'tx': { 63 | 'hash': '01f700df84c466f2a389440e5eeacdc47d04f380c39e5d19dce2ce91a11ecba3', 64 | 'version': 1, 65 | 'inputs': [ 66 | { 67 | 'prevTxId': 'bb0ec3b96209fac9529570ea6f83a86af2cceedde4aaf2bfcc4796680d23f1c7', 68 | 'outputIndex': 0, 69 | 'sequenceNumber': 4294967294, 70 | 'script': '47304402201ee69281db6b95bb1aa3074059b67581635b719e8f64e4c2694db6ec56ad9447022011e91528996ea459b1fb2c0b59363fecbefe4bc2ca90f7b2382bdaa358f2d5640121034cc057b12a68ee79df998004b9a1341bbb18b17ea4939bebaa3bac001e940f24', 71 | 'scriptString': '71 0x304402201ee69281db6b95bb1aa3074059b67581635b719e8f64e4c2694db6ec56ad9447022011e91528996ea459b1fb2c0b59363fecbefe4bc2ca90f7b2382bdaa358f2d56401 33 0x034cc057b12a68ee79df998004b9a1341bbb18b17ea4939bebaa3bac001e940f24', 72 | 'output': { 73 | 'satoshis': 2782729129, 74 | 'script': '76a9143583efb5e64a4668c6c54bb5fcc30af4417b4f2d88ac' 75 | } 76 | } 77 | ], 78 | 'outputs': [ 79 | { 80 | 'satoshis': 2764693692, 81 | 'script': '76a91456e446bc3489543d8324c6d0271524c0bd0506dd88ac' 82 | }, 83 | { 84 | 'satoshis': 18000000, 85 | 'script': '76a914011d2963b619186a318f768dddfd98cd553912a088ac' 86 | } 87 | ], 88 | 'nLockTime': 534099 89 | } 90 | } 91 | ] 92 | }; 93 | 94 | var tx = { 95 | height: 534181, 96 | blockTimestamp: 1441116143, 97 | blockHash: '0000000000000041ddc94ecf4f86a456a83b2e320c36c6f0c13ff92c7e75f013', 98 | hex: '0100000002f379708395d0a0357514205a3758a0317926428356e54a09089852fc6f7297ea010000008a473044022054233934268b30be779fad874ef42e8db928ba27a1b612d5f111b3ee95eb271c022024272bbaf2dcc4050bd3b9dfa3c93884f6ba6ad7d257598b8245abb65b5ab1e40141040682fdb281a8533e21e13dfd1fcfa424912a85b6cdc4136b5842c85de05ac1f0e4a013f20702adeb53329de13b2ef388e5ed6244676f4f1ee4ee685ab607964dffffffffb758ffd4c31693d9620f326385404530a079d5e60a90b94e46d3c2dbc29c0a98020000008a473044022044938ac3f8fcb8da29011df6397ed28cc7e894cdc35d596d4f3623bd8c7e465f022014829c6e0bd7ee97a1bcfef6b85c5fd232653f289394fc6ce6ebb41c73403f1b014104d9ccf88efc6e5be3151fae5e848efd94c91d75e7bf621f9f724a8caff51415338525d3239fae6b93826edf759dd562f77693e55dfa852ffd96a92d683db590f2ffffffff03605b0300000000001976a914b9bbd76588d9e4e09f0369a9aa0b2749a11c4e8d88ac40992d03000000001976a914d2ec20bb8e5f25a52f730384b803d95683250e0b88ac256c0400000000001976a914583df9fa56ad961051e00ca93e68dfaf1eab9ec588ac00000000', 99 | hash: '63b68becb0e514b32317f4b29a5cf0627d4087e54ac17f686fcb1d9a27680f73', 100 | version: 1, 101 | inputSatoshis: 53839829, 102 | outputSatoshis: 53829829, 103 | feeSatoshis: 10000, 104 | inputs: [ 105 | { 106 | address: 'moFfnRwt77pApKnnU6m5uocFaa43aAYpt5', 107 | prevTxId: 'ea97726ffc529808094ae5568342267931a058375a20147535a0d095837079f3', 108 | outputIndex: 1, 109 | sequence: 4294967295, 110 | script: '473044022054233934268b30be779fad874ef42e8db928ba27a1b612d5f111b3ee95eb271c022024272bbaf2dcc4050bd3b9dfa3c93884f6ba6ad7d257598b8245abb65b5ab1e40141040682fdb281a8533e21e13dfd1fcfa424912a85b6cdc4136b5842c85de05ac1f0e4a013f20702adeb53329de13b2ef388e5ed6244676f4f1ee4ee685ab607964d', 111 | scriptAsm: '3044022054233934268b30be779fad874ef42e8db928ba27a1b612d5f111b3ee95eb271c022024272bbaf2dcc4050bd3b9dfa3c93884f6ba6ad7d257598b8245abb65b5ab1e401 040682fdb281a8533e21e13dfd1fcfa424912a85b6cdc4136b5842c85de05ac1f0e4a013f20702adeb53329de13b2ef388e5ed6244676f4f1ee4ee685ab607964d', 112 | satoshis: 53540000, 113 | }, 114 | { 115 | address: 'n1XJBAyU4hNR4xRtY3UxnmAteoJX83p5qv', 116 | prevTxId: '980a9cc2dbc2d3464eb9900ae6d579a03045408563320f62d99316c3d4ff58b7', 117 | outputIndex: 2, 118 | sequence: 4294967295, 119 | script: '473044022044938ac3f8fcb8da29011df6397ed28cc7e894cdc35d596d4f3623bd8c7e465f022014829c6e0bd7ee97a1bcfef6b85c5fd232653f289394fc6ce6ebb41c73403f1b014104d9ccf88efc6e5be3151fae5e848efd94c91d75e7bf621f9f724a8caff51415338525d3239fae6b93826edf759dd562f77693e55dfa852ffd96a92d683db590f2', 120 | scriptAsm: '3044022044938ac3f8fcb8da29011df6397ed28cc7e894cdc35d596d4f3623bd8c7e465f022014829c6e0bd7ee97a1bcfef6b85c5fd232653f289394fc6ce6ebb41c73403f1b01 04d9ccf88efc6e5be3151fae5e848efd94c91d75e7bf621f9f724a8caff51415338525d3239fae6b93826edf759dd562f77693e55dfa852ffd96a92d683db590f2', 121 | satoshis: 299829, 122 | } 123 | ], 124 | outputs: [ 125 | { 126 | satoshis: 220000, 127 | script: '76a914b9bbd76588d9e4e09f0369a9aa0b2749a11c4e8d88ac', 128 | scriptAsm: 'OP_DUP OP_HASH160 b9bbd76588d9e4e09f0369a9aa0b2749a11c4e8d OP_EQUALVERIFY OP_CHECKSIG', 129 | address: 'mxT2KzTUQvsaYYothDtjcdvyAdaHA9ofMp' 130 | }, 131 | { 132 | satoshis: 53320000, 133 | address: 'mzkD4nmQ8ixqxySdBgsXTpgvAMK5iRZpNK', 134 | script: '76a914d2ec20bb8e5f25a52f730384b803d95683250e0b88ac', 135 | scriptAsm: 'OP_DUP OP_HASH160 d2ec20bb8e5f25a52f730384b803d95683250e0b OP_EQUALVERIFY OP_CHECKSIG' 136 | }, 137 | { 138 | address: 'moZY18rGNmh4YCPeugtGW46AkkWMQttBUD', 139 | satoshis: 289829, 140 | script: '76a914583df9fa56ad961051e00ca93e68dfaf1eab9ec588ac', 141 | scriptAsm: 'OP_DUP OP_HASH160 583df9fa56ad961051e00ca93e68dfaf1eab9ec5 OP_EQUALVERIFY OP_CHECKSIG' 142 | } 143 | ], 144 | locktime: 0 145 | }; 146 | 147 | var txinfos2 = { 148 | totalCount: 1, 149 | items: [ 150 | { 151 | tx: tx 152 | } 153 | ] 154 | }; 155 | 156 | var utxos = [ 157 | { 158 | 'address': 'mzkD4nmQ8ixqxySdBgsXTpgvAMK5iRZpNK', 159 | 'txid': '63b68becb0e514b32317f4b29a5cf0627d4087e54ac17f686fcb1d9a27680f73', 160 | 'outputIndex': 1, 161 | 'timestamp': 1441116143, 162 | 'satoshis': 53320000, 163 | 'script': '76a914d2ec20bb8e5f25a52f730384b803d95683250e0b88ac', 164 | 'height': 534181, 165 | 'confirmations': 50 166 | }, 167 | { 168 | 'address': 'moZY18rGNmh4YCPeugtGW46AkkWMQttBUD', 169 | 'txid': '63b68becb0e514b32317f4b29a5cf0627d4087e54ac17f686fcb1d9a27680f73', 170 | 'outputIndex': 2, 171 | 'timestamp': 1441116143, 172 | 'satoshis': 289829, 173 | 'script': '76a914583df9fa56ad961051e00ca93e68dfaf1eab9ec588ac', 174 | 'height': 534181, 175 | 'confirmations': 50 176 | } 177 | ]; 178 | 179 | describe('Addresses', function() { 180 | var summary = { 181 | balance: 0, 182 | totalReceived: 2782729129, 183 | totalSpent: 2782729129, 184 | unconfirmedBalance: 0, 185 | appearances: 2, 186 | unconfirmedAppearances: 0, 187 | txids: [ 188 | 'bb0ec3b96209fac9529570ea6f83a86af2cceedde4aaf2bfcc4796680d23f1c7', 189 | '01f700df84c466f2a389440e5eeacdc47d04f380c39e5d19dce2ce91a11ecba3' 190 | ] 191 | }; 192 | describe('/addr/:addr', function() { 193 | var node = { 194 | getAddressSummary: sinon.stub().callsArgWith(2, null, summary) 195 | }; 196 | 197 | var addresses = new AddressController(node); 198 | var req = { 199 | addr: 'mkPvAKZ2rar6qeG3KjBtJHHMSP1wFZH7Er', 200 | query: {} 201 | }; 202 | 203 | it('should have correct data', function(done) { 204 | var insight = { 205 | 'addrStr': 'mkPvAKZ2rar6qeG3KjBtJHHMSP1wFZH7Er', 206 | 'balance': 0, 207 | 'balanceSat': 0, 208 | 'totalReceived': 27.82729129, 209 | 'totalReceivedSat': 2782729129, 210 | 'totalSent': 27.82729129, 211 | 'totalSentSat': 2782729129, 212 | 'unconfirmedBalance': 0, 213 | 'unconfirmedBalanceSat': 0, 214 | 'unconfirmedTxApperances': 0, 215 | 'txApperances': 2, 216 | 'transactions': [ 217 | 'bb0ec3b96209fac9529570ea6f83a86af2cceedde4aaf2bfcc4796680d23f1c7', 218 | '01f700df84c466f2a389440e5eeacdc47d04f380c39e5d19dce2ce91a11ecba3' 219 | ] 220 | }; 221 | 222 | var res = { 223 | jsonp: function(data) { 224 | should(data).eql(insight); 225 | done(); 226 | } 227 | }; 228 | addresses.show(req, res); 229 | }); 230 | 231 | it('handle error', function() { 232 | var testnode = {}; 233 | testnode.log = {}; 234 | testnode.log.error = sinon.stub(); 235 | var controller = new AddressController(testnode); 236 | controller.getAddressSummary = sinon.stub().callsArgWith(2, new Error('test')); 237 | var req = { 238 | query: { 239 | noTxList: 1 240 | }, 241 | addr: 'mkPvAKZ2rar6qeG3KjBtJHHMSP1wFZH7Er' 242 | }; 243 | var send = sinon.stub(); 244 | var status = sinon.stub().returns({send: send}); 245 | var res = { 246 | status: status 247 | }; 248 | controller.show(req, res); 249 | send.callCount.should.equal(1); 250 | status.callCount.should.equal(1); 251 | status.args[0][0].should.equal(503); 252 | send.args[0][0].should.equal('test'); 253 | }); 254 | 255 | it('/balance', function(done) { 256 | var insight = 0; 257 | 258 | var res = { 259 | jsonp: function(data) { 260 | should(data).eql(insight); 261 | done(); 262 | } 263 | }; 264 | addresses.balance(req, res); 265 | }); 266 | 267 | it('/totalReceived', function(done) { 268 | var insight = 2782729129; 269 | 270 | var res = { 271 | jsonp: function(data) { 272 | should(data).eql(insight); 273 | done(); 274 | } 275 | }; 276 | 277 | addresses.totalReceived(req, res); 278 | }); 279 | 280 | it('/totalSent', function(done) { 281 | var insight = 2782729129; 282 | 283 | var res = { 284 | jsonp: function(data) { 285 | should(data).eql(insight); 286 | done(); 287 | } 288 | }; 289 | 290 | addresses.totalSent(req, res); 291 | }); 292 | 293 | it('/unconfirmedBalance', function(done) { 294 | var insight = 0; 295 | 296 | var res = { 297 | jsonp: function(data) { 298 | should(data).eql(insight); 299 | done(); 300 | } 301 | }; 302 | 303 | addresses.unconfirmedBalance(req, res); 304 | }); 305 | }); 306 | 307 | describe('/addr/:addr/utxo', function() { 308 | it('should have correct data', function(done) { 309 | var insight = [ 310 | { 311 | 'address': 'mzkD4nmQ8ixqxySdBgsXTpgvAMK5iRZpNK', 312 | 'txid': '63b68becb0e514b32317f4b29a5cf0627d4087e54ac17f686fcb1d9a27680f73', 313 | 'vout': 1, 314 | 'ts': 1441116143, 315 | 'scriptPubKey': '76a914d2ec20bb8e5f25a52f730384b803d95683250e0b88ac', 316 | 'amount': 0.5332, 317 | 'confirmations': 50, 318 | 'height': 534181, 319 | 'satoshis': 53320000, 320 | 'confirmationsFromCache': true 321 | } 322 | ]; 323 | 324 | var todos = [ 325 | { 326 | confirmationsFromCache: true 327 | } 328 | ]; 329 | 330 | var node = { 331 | services: { 332 | bitcoind: { 333 | height: 534230 334 | } 335 | }, 336 | getAddressUnspentOutputs: sinon.stub().callsArgWith(2, null, utxos.slice(0, 1)) 337 | }; 338 | 339 | var addresses = new AddressController(node); 340 | 341 | var req = { 342 | addr: 'mzkD4nmQ8ixqxySdBgsXTpgvAMK5iRZpNK' 343 | }; 344 | 345 | var res = { 346 | jsonp: function(data) { 347 | var merged = _.merge(data, todos); 348 | should(merged).eql(insight); 349 | done(); 350 | } 351 | }; 352 | 353 | addresses.utxo(req, res); 354 | }); 355 | }); 356 | 357 | describe('/addrs/:addrs/utxo', function() { 358 | it('should have the correct data', function(done) { 359 | var insight = [ 360 | { 361 | 'address': 'mzkD4nmQ8ixqxySdBgsXTpgvAMK5iRZpNK', 362 | 'txid': '63b68becb0e514b32317f4b29a5cf0627d4087e54ac17f686fcb1d9a27680f73', 363 | 'vout': 1, 364 | 'ts': 1441116143, 365 | 'scriptPubKey': '76a914d2ec20bb8e5f25a52f730384b803d95683250e0b88ac', 366 | 'amount': 0.5332, 367 | 'height': 534181, 368 | 'satoshis': 53320000, 369 | 'confirmations': 50, 370 | 'confirmationsFromCache': true 371 | }, 372 | { 373 | 'address': 'moZY18rGNmh4YCPeugtGW46AkkWMQttBUD', 374 | 'txid': '63b68becb0e514b32317f4b29a5cf0627d4087e54ac17f686fcb1d9a27680f73', 375 | 'vout': 2, 376 | 'ts': 1441116143, 377 | 'scriptPubKey': '76a914583df9fa56ad961051e00ca93e68dfaf1eab9ec588ac', 378 | 'amount': 0.00289829, 379 | 'height': 534181, 380 | 'satoshis': 289829, 381 | 'confirmations': 50, 382 | 'confirmationsFromCache': true 383 | } 384 | ]; 385 | 386 | var todos = [ 387 | { 388 | confirmationsFromCache: true 389 | }, { 390 | confirmationsFromCache: true 391 | } 392 | ]; 393 | 394 | var node = { 395 | services: { 396 | bitcoind: { 397 | height: 534230 398 | } 399 | }, 400 | getAddressUnspentOutputs: sinon.stub().callsArgWith(2, null, utxos) 401 | }; 402 | 403 | var addresses = new AddressController(node); 404 | 405 | var req = { 406 | addrs: 'mzkD4nmQ8ixqxySdBgsXTpgvAMK5iRZpNK,moZY18rGNmh4YCPeugtGW46AkkWMQttBUD' 407 | }; 408 | 409 | var res = { 410 | jsonp: function(data) { 411 | var merged = _.merge(data, todos); 412 | should(merged).eql(insight); 413 | done(); 414 | } 415 | }; 416 | 417 | addresses.multiutxo(req, res); 418 | }); 419 | }); 420 | 421 | describe('/addrs/:addrs/txs', function() { 422 | it('should have correct data', function(done) { 423 | var insight = { 424 | 'totalItems': 1, 425 | 'from': 0, 426 | 'to': 1, 427 | 'items': [ 428 | { 429 | 'txid': '63b68becb0e514b32317f4b29a5cf0627d4087e54ac17f686fcb1d9a27680f73', 430 | 'version': 1, 431 | 'locktime': 0, 432 | 'vin': [ 433 | { 434 | 'txid': 'ea97726ffc529808094ae5568342267931a058375a20147535a0d095837079f3', 435 | 'vout': 1, 436 | 'scriptSig': { 437 | 'asm': '3044022054233934268b30be779fad874ef42e8db928ba27a1b612d5f111b3ee95eb271c022024272bbaf2dcc4050bd3b9dfa3c93884f6ba6ad7d257598b8245abb65b5ab1e401 040682fdb281a8533e21e13dfd1fcfa424912a85b6cdc4136b5842c85de05ac1f0e4a013f20702adeb53329de13b2ef388e5ed6244676f4f1ee4ee685ab607964d', 438 | 'hex': '473044022054233934268b30be779fad874ef42e8db928ba27a1b612d5f111b3ee95eb271c022024272bbaf2dcc4050bd3b9dfa3c93884f6ba6ad7d257598b8245abb65b5ab1e40141040682fdb281a8533e21e13dfd1fcfa424912a85b6cdc4136b5842c85de05ac1f0e4a013f20702adeb53329de13b2ef388e5ed6244676f4f1ee4ee685ab607964d' 439 | }, 440 | 'sequence': 4294967295, 441 | 'n': 0, 442 | 'addr': 'moFfnRwt77pApKnnU6m5uocFaa43aAYpt5', 443 | 'valueSat': 53540000, 444 | 'value': 0.5354, 445 | 'doubleSpentTxID': null 446 | }, 447 | { 448 | 'txid': '980a9cc2dbc2d3464eb9900ae6d579a03045408563320f62d99316c3d4ff58b7', 449 | 'vout': 2, 450 | 'scriptSig': { 451 | 'asm': '3044022044938ac3f8fcb8da29011df6397ed28cc7e894cdc35d596d4f3623bd8c7e465f022014829c6e0bd7ee97a1bcfef6b85c5fd232653f289394fc6ce6ebb41c73403f1b01 04d9ccf88efc6e5be3151fae5e848efd94c91d75e7bf621f9f724a8caff51415338525d3239fae6b93826edf759dd562f77693e55dfa852ffd96a92d683db590f2', 452 | 'hex': '473044022044938ac3f8fcb8da29011df6397ed28cc7e894cdc35d596d4f3623bd8c7e465f022014829c6e0bd7ee97a1bcfef6b85c5fd232653f289394fc6ce6ebb41c73403f1b014104d9ccf88efc6e5be3151fae5e848efd94c91d75e7bf621f9f724a8caff51415338525d3239fae6b93826edf759dd562f77693e55dfa852ffd96a92d683db590f2' 453 | }, 454 | 'sequence': 4294967295, 455 | 'n': 1, 456 | 'addr': 'n1XJBAyU4hNR4xRtY3UxnmAteoJX83p5qv', 457 | 'valueSat': 299829, 458 | 'value': 0.00299829, 459 | 'doubleSpentTxID': null 460 | } 461 | ], 462 | 'vout': [ 463 | { 464 | 'value': '0.00220000', 465 | 'n': 0, 466 | 'scriptPubKey': { 467 | 'asm': 'OP_DUP OP_HASH160 b9bbd76588d9e4e09f0369a9aa0b2749a11c4e8d OP_EQUALVERIFY OP_CHECKSIG', 468 | 'hex': '76a914b9bbd76588d9e4e09f0369a9aa0b2749a11c4e8d88ac', 469 | 'reqSigs': 1, 470 | 'type': 'pubkeyhash', 471 | 'addresses': [ 472 | 'mxT2KzTUQvsaYYothDtjcdvyAdaHA9ofMp' 473 | ] 474 | }, 475 | 'spentHeight': null, 476 | 'spentIndex': null, 477 | 'spentTxId': null 478 | }, 479 | { 480 | 'value': '0.53320000', 481 | 'n': 1, 482 | 'scriptPubKey': { 483 | 'asm': 'OP_DUP OP_HASH160 d2ec20bb8e5f25a52f730384b803d95683250e0b OP_EQUALVERIFY OP_CHECKSIG', 484 | 'hex': '76a914d2ec20bb8e5f25a52f730384b803d95683250e0b88ac', 485 | 'reqSigs': 1, 486 | 'type': 'pubkeyhash', 487 | 'addresses': [ 488 | 'mzkD4nmQ8ixqxySdBgsXTpgvAMK5iRZpNK' 489 | ], 490 | }, 491 | 'spentHeight': null, 492 | 'spentIndex': null, 493 | 'spentTxId': null 494 | }, 495 | { 496 | 'value': '0.00289829', 497 | 'n': 2, 498 | 'scriptPubKey': { 499 | 'asm': 'OP_DUP OP_HASH160 583df9fa56ad961051e00ca93e68dfaf1eab9ec5 OP_EQUALVERIFY OP_CHECKSIG', 500 | 'hex': '76a914583df9fa56ad961051e00ca93e68dfaf1eab9ec588ac', 501 | 'reqSigs': 1, 502 | 'type': 'pubkeyhash', 503 | 'addresses': [ 504 | 'moZY18rGNmh4YCPeugtGW46AkkWMQttBUD' 505 | ] 506 | }, 507 | 'spentHeight': null, 508 | 'spentIndex': null, 509 | 'spentTxId': null 510 | } 511 | ], 512 | 'blockhash': '0000000000000041ddc94ecf4f86a456a83b2e320c36c6f0c13ff92c7e75f013', 513 | 'blockheight': 534181, 514 | 'confirmations': 52, 515 | 'time': 1441116143, 516 | 'blocktime': 1441116143, 517 | 'valueOut': 0.53829829, 518 | 'size': 470, 519 | 'valueIn': 0.53839829, 520 | 'fees': 0.0001, 521 | 'firstSeenTs': 1441108193 522 | } 523 | ] 524 | }; 525 | 526 | var todos = { 527 | 'items': [ 528 | { 529 | 'vout': [ 530 | { 531 | 'scriptPubKey': { 532 | 'reqSigs': 1, 533 | } 534 | }, 535 | { 536 | 'scriptPubKey': { 537 | 'reqSigs': 1, 538 | } 539 | }, 540 | { 541 | 'scriptPubKey': { 542 | 'reqSigs': 1, 543 | } 544 | } 545 | ], 546 | 'firstSeenTs': 1441108193 547 | } 548 | ] 549 | }; 550 | 551 | var node = { 552 | getAddressHistory: sinon.stub().callsArgWith(2, null, txinfos2), 553 | services: { 554 | bitcoind: { 555 | height: 534232 556 | } 557 | }, 558 | network: 'testnet' 559 | }; 560 | 561 | var addresses = new AddressController(node); 562 | 563 | var req = { 564 | addrs: 'mzkD4nmQ8ixqxySdBgsXTpgvAMK5iRZpNK,moZY18rGNmh4YCPeugtGW46AkkWMQttBUD', 565 | query: {}, 566 | body: {} 567 | }; 568 | 569 | var res = { 570 | jsonp: function(data) { 571 | var merged = _.merge(data, todos); 572 | should(merged).eql(insight); 573 | done(); 574 | } 575 | }; 576 | 577 | addresses.multitxs(req, res); 578 | }); 579 | it('should have trimmed data', function(done) { 580 | var insight = { 581 | 'totalItems': 1, 582 | 'from': 0, 583 | 'to': 1, 584 | 'items': [ 585 | { 586 | 'txid': '63b68becb0e514b32317f4b29a5cf0627d4087e54ac17f686fcb1d9a27680f73', 587 | 'version': 1, 588 | 'locktime': 0, 589 | 'vin': [ 590 | { 591 | 'txid': 'ea97726ffc529808094ae5568342267931a058375a20147535a0d095837079f3', 592 | 'vout': 1, 593 | 'sequence': 4294967295, 594 | 'n': 0, 595 | 'addr': 'moFfnRwt77pApKnnU6m5uocFaa43aAYpt5', 596 | 'valueSat': 53540000, 597 | 'value': 0.5354, 598 | 'doubleSpentTxID': null 599 | }, 600 | { 601 | 'txid': '980a9cc2dbc2d3464eb9900ae6d579a03045408563320f62d99316c3d4ff58b7', 602 | 'vout': 2, 603 | 'sequence': 4294967295, 604 | 'n': 1, 605 | 'addr': 'n1XJBAyU4hNR4xRtY3UxnmAteoJX83p5qv', 606 | 'valueSat': 299829, 607 | 'value': 0.00299829, 608 | 'doubleSpentTxID': null 609 | } 610 | ], 611 | 'vout': [ 612 | { 613 | 'value': '0.00220000', 614 | 'n': 0, 615 | 'scriptPubKey': { 616 | 'hex': '76a914b9bbd76588d9e4e09f0369a9aa0b2749a11c4e8d88ac', 617 | 'reqSigs': 1, 618 | 'type': 'pubkeyhash', 619 | 'addresses': [ 620 | 'mxT2KzTUQvsaYYothDtjcdvyAdaHA9ofMp' 621 | ] 622 | } 623 | }, 624 | { 625 | 'value': '0.53320000', 626 | 'n': 1, 627 | 'scriptPubKey': { 628 | 'hex': '76a914d2ec20bb8e5f25a52f730384b803d95683250e0b88ac', 629 | 'reqSigs': 1, 630 | 'type': 'pubkeyhash', 631 | 'addresses': [ 632 | 'mzkD4nmQ8ixqxySdBgsXTpgvAMK5iRZpNK' 633 | ], 634 | } 635 | }, 636 | { 637 | 'value': '0.00289829', 638 | 'n': 2, 639 | 'scriptPubKey': { 640 | 'hex': '76a914583df9fa56ad961051e00ca93e68dfaf1eab9ec588ac', 641 | 'reqSigs': 1, 642 | 'type': 'pubkeyhash', 643 | 'addresses': [ 644 | 'moZY18rGNmh4YCPeugtGW46AkkWMQttBUD' 645 | ] 646 | } 647 | } 648 | ], 649 | 'blockhash': '0000000000000041ddc94ecf4f86a456a83b2e320c36c6f0c13ff92c7e75f013', 650 | 'blockheight': 534181, 651 | 'confirmations': 52, 652 | 'time': 1441116143, 653 | 'blocktime': 1441116143, 654 | 'valueOut': 0.53829829, 655 | 'size': 470, 656 | 'valueIn': 0.53839829, 657 | 'fees': 0.0001, 658 | 'firstSeenTs': 1441108193 659 | } 660 | ] 661 | }; 662 | 663 | var todos = { 664 | 'items': [ 665 | { 666 | 'vout': [ 667 | { 668 | 'scriptPubKey': { 669 | 'reqSigs': 1, 670 | } 671 | }, 672 | { 673 | 'scriptPubKey': { 674 | 'reqSigs': 1, 675 | } 676 | }, 677 | { 678 | 'scriptPubKey': { 679 | 'reqSigs': 1, 680 | } 681 | } 682 | ], 683 | 'firstSeenTs': 1441108193 684 | } 685 | ] 686 | }; 687 | 688 | var node = { 689 | getAddressHistory: sinon.stub().callsArgWith(2, null, txinfos2), 690 | services: { 691 | bitcoind: { 692 | height: 534232 693 | } 694 | }, 695 | network: 'testnet' 696 | }; 697 | 698 | var addresses = new AddressController(node); 699 | 700 | var req = { 701 | addrs: 'mzkD4nmQ8ixqxySdBgsXTpgvAMK5iRZpNK,moZY18rGNmh4YCPeugtGW46AkkWMQttBUD', 702 | query: {noSpent: '1', noScriptSig: '1', noAsm: '1'}, 703 | body: {} 704 | }; 705 | 706 | var res = { 707 | jsonp: function(data) { 708 | var merged = _.merge(data, todos); 709 | should(merged).eql(insight); 710 | done(); 711 | } 712 | }; 713 | 714 | addresses.multitxs(req, res); 715 | }); 716 | }); 717 | describe('#_getTransformOptions', function() { 718 | it('will return false with value of string "0"', function() { 719 | var node = {}; 720 | var addresses = new AddressController(node); 721 | var req = { 722 | query: { 723 | noAsm: '0', 724 | noScriptSig: '0', 725 | noSpent: '0' 726 | } 727 | }; 728 | var options = addresses._getTransformOptions(req); 729 | options.should.eql({ 730 | noAsm: false, 731 | noScriptSig: false, 732 | noSpent: false 733 | }); 734 | }); 735 | it('will return true with value of string "1"', function() { 736 | var node = {}; 737 | var addresses = new AddressController(node); 738 | var req = { 739 | query: { 740 | noAsm: '1', 741 | noScriptSig: '1', 742 | noSpent: '1' 743 | } 744 | }; 745 | var options = addresses._getTransformOptions(req); 746 | options.should.eql({ 747 | noAsm: true, 748 | noScriptSig: true, 749 | noSpent: true 750 | }); 751 | }); 752 | it('will return true with value of number "1"', function() { 753 | var node = {}; 754 | var addresses = new AddressController(node); 755 | var req = { 756 | query: { 757 | noAsm: 1, 758 | noScriptSig: 1, 759 | noSpent: 1 760 | } 761 | }; 762 | var options = addresses._getTransformOptions(req); 763 | options.should.eql({ 764 | noAsm: true, 765 | noScriptSig: true, 766 | noSpent: true 767 | }); 768 | }); 769 | }); 770 | }); 771 | -------------------------------------------------------------------------------- /test/blocks.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var should = require('should'); 4 | var sinon = require('sinon'); 5 | var BlockController = require('../lib/blocks'); 6 | var bitcore = require('litecore-lib'); 7 | var _ = require('lodash'); 8 | 9 | var blocks = require('./data/blocks.json'); 10 | 11 | var blockIndexes = { 12 | '0000000000000afa0c3c0afd450c793a1e300ec84cbe9555166e06132f19a8f7': { 13 | hash: '0000000000000afa0c3c0afd450c793a1e300ec84cbe9555166e06132f19a8f7', 14 | chainWork: '0000000000000000000000000000000000000000000000054626b1839ade284a', 15 | prevHash: '00000000000001a55f3214e9172eb34b20e0bc5bd6b8007f3f149fca2c8991a4', 16 | nextHash: '000000000001e866a8057cde0c650796cb8a59e0e6038dc31c69d7ca6649627d', 17 | confirmations: 119, 18 | height: 533974 19 | }, 20 | '000000000008fbb2e358e382a6f6948b2da24563bba183af447e6e2542e8efc7': { 21 | hash: '000000000008fbb2e358e382a6f6948b2da24563bba183af447e6e2542e8efc7', 22 | chainWork: '00000000000000000000000000000000000000000000000544ea52e1575ca753', 23 | prevHash: '00000000000006bd8fe9e53780323c0e85719eca771022e1eb6d10c62195c441', 24 | confirmations: 119, 25 | height: 533951 26 | }, 27 | '00000000000006bd8fe9e53780323c0e85719eca771022e1eb6d10c62195c441': { 28 | hash: '00000000000006bd8fe9e53780323c0e85719eca771022e1eb6d10c62195c441', 29 | chainWork: '00000000000000000000000000000000000000000000000544ea52e0575ba752', 30 | prevHash: '000000000001b9c41e6c4a7b81a068b50cf3f522ee4ac1e942e75ec16e090547', 31 | height: 533950 32 | }, 33 | '000000000000000004a118407a4e3556ae2d5e882017e7ce526659d8073f13a4': { 34 | hash: '000000000000000004a118407a4e3556ae2d5e882017e7ce526659d8073f13a4', 35 | prevHash: '00000000000000000a9d74a7b527f7b995fc21ceae5aa21087b443469351a362', 36 | height: 375493 37 | }, 38 | 533974: { 39 | hash: '0000000000000afa0c3c0afd450c793a1e300ec84cbe9555166e06132f19a8f7', 40 | chainWork: '0000000000000000000000000000000000000000000000054626b1839ade284a', 41 | prevHash: '00000000000001a55f3214e9172eb34b20e0bc5bd6b8007f3f149fca2c8991a4', 42 | height: 533974 43 | } 44 | }; 45 | 46 | describe('Blocks', function() { 47 | describe('/blocks/:blockHash route', function() { 48 | var insight = { 49 | 'hash': '0000000000000afa0c3c0afd450c793a1e300ec84cbe9555166e06132f19a8f7', 50 | 'confirmations': 119, 51 | 'size': 1011, 52 | 'height': 533974, 53 | 'version': 536870919, 54 | 'merkleroot': 'b06437355844b8178173f3e18ca141472e4b0861daa81ef0f701cf9e51f0283e', 55 | 'tx': [ 56 | '25a988e54b02e0e5df146a0f8fa7b9db56210533a9f04bdfda5f4ceb6f77aadd', 57 | 'b85334bf2df35c6dd5b294efe92ffc793a78edff75a2ca666fc296ffb04bbba0', 58 | '2e01c7a4a0e335112236b711c4aaddd02e8dc59ba2cda416e8f80ff06dddd7e1' 59 | ], 60 | 'time': 1440987503, 61 | 'nonce': 1868753784, 62 | 'bits': '1a0cf267', 63 | 'difficulty': 1295829.93087696, 64 | 'chainwork': '0000000000000000000000000000000000000000000000054626b1839ade284a', 65 | 'previousblockhash': '00000000000001a55f3214e9172eb34b20e0bc5bd6b8007f3f149fca2c8991a4', 66 | 'nextblockhash': '000000000001e866a8057cde0c650796cb8a59e0e6038dc31c69d7ca6649627d', 67 | 'reward': 12.5, 68 | 'isMainChain': true, 69 | 'poolInfo': {} 70 | }; 71 | 72 | var bitcoreBlock = bitcore.Block.fromBuffer(new Buffer(blocks['0000000000000afa0c3c0afd450c793a1e300ec84cbe9555166e06132f19a8f7'], 'hex')); 73 | 74 | var node = { 75 | log: sinon.stub(), 76 | getBlock: sinon.stub().callsArgWith(1, null, bitcoreBlock), 77 | services: { 78 | bitcoind: { 79 | getBlockHeader: sinon.stub().callsArgWith(1, null, blockIndexes['0000000000000afa0c3c0afd450c793a1e300ec84cbe9555166e06132f19a8f7']), 80 | isMainChain: sinon.stub().returns(true), 81 | height: 534092 82 | } 83 | } 84 | }; 85 | 86 | it('block data should be correct', function(done) { 87 | var controller = new BlockController({node: node}); 88 | var hash = '0000000000000afa0c3c0afd450c793a1e300ec84cbe9555166e06132f19a8f7'; 89 | var req = { 90 | params: { 91 | blockHash: hash 92 | } 93 | }; 94 | var res = {}; 95 | var next = function() { 96 | should.exist(req.block); 97 | var block = req.block; 98 | should(block).eql(insight); 99 | done(); 100 | }; 101 | controller.block(req, res, next); 102 | }); 103 | 104 | it('block pool info should be correct', function(done) { 105 | var block = bitcore.Block.fromString(blocks['000000000000000004a118407a4e3556ae2d5e882017e7ce526659d8073f13a4']); 106 | var node = { 107 | log: sinon.stub(), 108 | getBlock: sinon.stub().callsArgWith(1, null, block), 109 | services: { 110 | bitcoind: { 111 | getBlockHeader: sinon.stub().callsArgWith(1, null, blockIndexes['000000000000000004a118407a4e3556ae2d5e882017e7ce526659d8073f13a4']), 112 | isMainChain: sinon.stub().returns(true), 113 | height: 534092 114 | } 115 | } 116 | }; 117 | var controller = new BlockController({node: node}); 118 | var req = { 119 | params: { 120 | blockHash: hash 121 | } 122 | }; 123 | var res = {}; 124 | var next = function() { 125 | should.exist(req.block); 126 | var block = req.block; 127 | req.block.poolInfo.poolName.should.equal('Discus Fish'); 128 | req.block.poolInfo.url.should.equal('http://f2pool.com/'); 129 | done(); 130 | }; 131 | 132 | var hash = '000000000000000004a118407a4e3556ae2d5e882017e7ce526659d8073f13a4'; 133 | 134 | controller.block(req, res, next); 135 | }); 136 | 137 | }); 138 | 139 | describe('/blocks route', function() { 140 | 141 | var insight = { 142 | 'blocks': [ 143 | { 144 | 'height': 533951, 145 | 'size': 206, 146 | 'hash': '000000000008fbb2e358e382a6f6948b2da24563bba183af447e6e2542e8efc7', 147 | 'time': 1440978683, 148 | 'txlength': 1, 149 | 'poolInfo': { 150 | 'poolName': 'AntMiner', 151 | 'url': 'https://bitmaintech.com/' 152 | } 153 | }, 154 | { 155 | 'height': 533950, 156 | 'size': 206, 157 | 'hash': '00000000000006bd8fe9e53780323c0e85719eca771022e1eb6d10c62195c441', 158 | 'time': 1440977479, 159 | 'txlength': 1, 160 | 'poolInfo': { 161 | 'poolName': 'AntMiner', 162 | 'url': 'https://bitmaintech.com/' 163 | } 164 | } 165 | ], 166 | 'length': 2, 167 | 'pagination': { 168 | 'current': '2015-08-30', 169 | 'currentTs': 1440979199, 170 | 'isToday': false, 171 | 'more': false, 172 | 'next': '2015-08-31', 173 | 'prev': '2015-08-29' 174 | } 175 | }; 176 | 177 | var stub = sinon.stub(); 178 | stub.onFirstCall().callsArgWith(1, null, new Buffer(blocks['000000000008fbb2e358e382a6f6948b2da24563bba183af447e6e2542e8efc7'], 'hex')); 179 | stub.onSecondCall().callsArgWith(1, null, new Buffer(blocks['00000000000006bd8fe9e53780323c0e85719eca771022e1eb6d10c62195c441'], 'hex')); 180 | 181 | var hashes = [ 182 | '00000000000006bd8fe9e53780323c0e85719eca771022e1eb6d10c62195c441', 183 | '000000000008fbb2e358e382a6f6948b2da24563bba183af447e6e2542e8efc7' 184 | ]; 185 | var node = { 186 | log: sinon.stub(), 187 | services: { 188 | bitcoind: { 189 | getRawBlock: stub, 190 | getBlockHeader: function(hash, callback) { 191 | callback(null, blockIndexes[hash]); 192 | }, 193 | getBlockHashesByTimestamp: sinon.stub().callsArgWith(2, null, hashes) 194 | } 195 | } 196 | }; 197 | 198 | it('should have correct data', function(done) { 199 | var blocks = new BlockController({node: node}); 200 | 201 | var req = { 202 | query: { 203 | limit: 2, 204 | blockDate: '2015-08-30' 205 | } 206 | }; 207 | 208 | var res = { 209 | jsonp: function(data) { 210 | should(data).eql(insight); 211 | done(); 212 | } 213 | }; 214 | 215 | blocks.list(req, res); 216 | }); 217 | }); 218 | 219 | describe('/block-index/:height route', function() { 220 | var node = { 221 | log: sinon.stub(), 222 | services: { 223 | bitcoind: { 224 | getBlockHeader: function(height, callback) { 225 | callback(null, blockIndexes[height]); 226 | } 227 | } 228 | } 229 | }; 230 | 231 | it('should have correct data', function(done) { 232 | var blocks = new BlockController({node: node}); 233 | 234 | var insight = { 235 | 'blockHash': '0000000000000afa0c3c0afd450c793a1e300ec84cbe9555166e06132f19a8f7' 236 | }; 237 | 238 | var height = 533974; 239 | 240 | var req = { 241 | params: { 242 | height: height 243 | } 244 | }; 245 | var res = { 246 | jsonp: function(data) { 247 | should(data).eql(insight); 248 | done(); 249 | } 250 | }; 251 | 252 | blocks.blockIndex(req, res); 253 | }); 254 | }); 255 | 256 | describe('#getBlockReward', function() { 257 | var node = { 258 | log: sinon.stub() 259 | }; 260 | var blocks = new BlockController({node: node}); 261 | 262 | it('should give a block reward of 50 * 1e8 for block before first halvening', function() { 263 | blocks.getBlockReward(100000).should.equal(50 * 1e8); 264 | }); 265 | 266 | it('should give a block reward of 25 * 1e8 for block between first and second halvenings', function() { 267 | blocks.getBlockReward(373011).should.equal(25 * 1e8); 268 | }); 269 | 270 | it('should give a block reward of 12.5 * 1e8 for block between second and third halvenings', function() { 271 | blocks.getBlockReward(500000).should.equal(12.5 * 1e8); 272 | }); 273 | }); 274 | }); 275 | -------------------------------------------------------------------------------- /test/currency.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var should = require('should'); 4 | var sinon = require('sinon'); 5 | var proxyquire = require('proxyquire'); 6 | var CurrencyController = require('../lib/currency'); 7 | 8 | describe('Currency', function() { 9 | 10 | var bitstampData = { 11 | high: 239.44, 12 | last: 237.90, 13 | timestamp: 1443798711, 14 | bid: 237.61, 15 | vwap: 237.88, 16 | volume: 21463.27736401, 17 | low: 235.00, 18 | ask: 237.90 19 | }; 20 | 21 | it.skip('will make live request to bitstamp', function(done) { 22 | var currency = new CurrencyController({}); 23 | var req = {}; 24 | var res = { 25 | jsonp: function(response) { 26 | response.status.should.equal(200); 27 | should.exist(response.data.bitstamp); 28 | (typeof response.data.bitstamp).should.equal('number'); 29 | done(); 30 | } 31 | }; 32 | currency.index(req, res); 33 | }); 34 | 35 | it('will retrieve a fresh value', function(done) { 36 | var TestCurrencyController = proxyquire('../lib/currency', { 37 | request: sinon.stub().callsArgWith(1, null, {statusCode: 200}, JSON.stringify(bitstampData)) 38 | }); 39 | var node = { 40 | log: { 41 | error: sinon.stub() 42 | } 43 | }; 44 | var currency = new TestCurrencyController({node: node}); 45 | currency.bitstampRate = 220.20; 46 | currency.timestamp = Date.now() - 61000 * CurrencyController.DEFAULT_CURRENCY_DELAY; 47 | var req = {}; 48 | var res = { 49 | jsonp: function(response) { 50 | response.status.should.equal(200); 51 | should.exist(response.data.bitstamp); 52 | response.data.bitstamp.should.equal(237.90); 53 | done(); 54 | } 55 | }; 56 | currency.index(req, res); 57 | }); 58 | 59 | it('will log an error from request', function(done) { 60 | var TestCurrencyController = proxyquire('../lib/currency', { 61 | request: sinon.stub().callsArgWith(1, new Error('test')) 62 | }); 63 | var node = { 64 | log: { 65 | error: sinon.stub() 66 | } 67 | }; 68 | var currency = new TestCurrencyController({node: node}); 69 | currency.bitstampRate = 237.90; 70 | currency.timestamp = Date.now() - 65000 * CurrencyController.DEFAULT_CURRENCY_DELAY; 71 | var req = {}; 72 | var res = { 73 | jsonp: function(response) { 74 | response.status.should.equal(200); 75 | should.exist(response.data.bitstamp); 76 | response.data.bitstamp.should.equal(237.90); 77 | node.log.error.callCount.should.equal(1); 78 | done(); 79 | } 80 | }; 81 | currency.index(req, res); 82 | }); 83 | 84 | it('will retrieve a cached value', function(done) { 85 | var request = sinon.stub(); 86 | var TestCurrencyController = proxyquire('../lib/currency', { 87 | request: request 88 | }); 89 | var node = { 90 | log: { 91 | error: sinon.stub() 92 | } 93 | }; 94 | var currency = new TestCurrencyController({node: node}); 95 | currency.bitstampRate = 237.90; 96 | currency.timestamp = Date.now(); 97 | var req = {}; 98 | var res = { 99 | jsonp: function(response) { 100 | response.status.should.equal(200); 101 | should.exist(response.data.bitstamp); 102 | response.data.bitstamp.should.equal(237.90); 103 | request.callCount.should.equal(0); 104 | done(); 105 | } 106 | }; 107 | currency.index(req, res); 108 | }); 109 | 110 | }); 111 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var should = require('should'); 4 | var sinon = require('sinon'); 5 | var InsightAPI = require('../lib/index'); 6 | 7 | describe('Index', function() { 8 | describe('@constructor', function() { 9 | it('will set rate limiter options', function() { 10 | var options = {}; 11 | var node = {}; 12 | var index = new InsightAPI({ 13 | rateLimiterOptions: options, 14 | node: node 15 | }); 16 | index.rateLimiterOptions.should.equal(options); 17 | }); 18 | it('will set disable rate limiter option', function() { 19 | var node = {}; 20 | var index = new InsightAPI({ 21 | disableRateLimiter: true, 22 | node: node 23 | }); 24 | index.disableRateLimiter.should.equal(true); 25 | }); 26 | }); 27 | describe('#_getRateLimiter', function() { 28 | it('will pass options to rate limiter', function() { 29 | var options = { 30 | whitelist: ['127.0.0.1'] 31 | }; 32 | var node = {}; 33 | var index = new InsightAPI({ 34 | rateLimiterOptions: options, 35 | node: node 36 | }); 37 | var limiter = index._getRateLimiter(); 38 | limiter.whitelist.should.eql(['127.0.0.1']); 39 | }); 40 | }); 41 | describe('#cache', function() { 42 | it('will set cache control header', function(done) { 43 | var node = { 44 | log: sinon.stub() 45 | }; 46 | var index = new InsightAPI({ 47 | enableCache: true, 48 | node: node 49 | }); 50 | var req = {}; 51 | var res = { 52 | header: sinon.stub() 53 | }; 54 | var middle = index.cache(10); 55 | middle(req, res, function() { 56 | res.header.callCount.should.equal(1); 57 | res.header.args[0][0].should.equal('Cache-Control'); 58 | res.header.args[0][1].should.equal('public, max-age=10'); 59 | done(); 60 | }); 61 | }); 62 | it('will NOT set cache control header', function(done) { 63 | var node = { 64 | log: sinon.stub() 65 | }; 66 | var index = new InsightAPI({ 67 | enableCache: false, 68 | node: node 69 | }); 70 | var req = {}; 71 | var res = { 72 | header: sinon.stub() 73 | }; 74 | var middle = index.cache(10); 75 | middle(req, res, function() { 76 | res.header.callCount.should.equal(0); 77 | done(); 78 | }); 79 | }); 80 | }); 81 | describe('#cacheShort', function() { 82 | it('will set SHORT cache control header', function(done) { 83 | var node = { 84 | log: sinon.stub() 85 | }; 86 | var index = new InsightAPI({ 87 | enableCache: true, 88 | cacheShortSeconds: 35, 89 | node: node 90 | }); 91 | var req = {}; 92 | var res = { 93 | header: sinon.stub() 94 | }; 95 | var middle = index.cacheShort(); 96 | middle(req, res, function() { 97 | res.header.callCount.should.equal(1); 98 | res.header.args[0][0].should.equal('Cache-Control'); 99 | res.header.args[0][1].should.equal('public, max-age=35'); 100 | done(); 101 | }); 102 | }); 103 | it('will set SHORT DEFAULT cache control header', function(done) { 104 | var node = { 105 | log: sinon.stub() 106 | }; 107 | var index = new InsightAPI({ 108 | enableCache: true, 109 | node: node 110 | }); 111 | var req = {}; 112 | var res = { 113 | header: sinon.stub() 114 | }; 115 | var middle = index.cacheShort(); 116 | middle(req, res, function() { 117 | res.header.callCount.should.equal(1); 118 | res.header.args[0][0].should.equal('Cache-Control'); 119 | res.header.args[0][1].should.equal('public, max-age=30'); 120 | done(); 121 | }); 122 | }); 123 | }); 124 | describe('#cacheLong', function() { 125 | it('will set LONG cache control header', function(done) { 126 | var node = { 127 | log: sinon.stub() 128 | }; 129 | var index = new InsightAPI({ 130 | enableCache: true, 131 | cacheLongSeconds: 86400000, 132 | node: node 133 | }); 134 | var req = {}; 135 | var res = { 136 | header: sinon.stub() 137 | }; 138 | var middle = index.cacheLong(); 139 | middle(req, res, function() { 140 | res.header.callCount.should.equal(1); 141 | res.header.args[0][0].should.equal('Cache-Control'); 142 | res.header.args[0][1].should.equal('public, max-age=86400000'); 143 | done(); 144 | }); 145 | }); 146 | it('will set LONG DEFAULT cache control header', function(done) { 147 | var node = { 148 | log: sinon.stub() 149 | }; 150 | var index = new InsightAPI({ 151 | enableCache: true, 152 | node: node 153 | }); 154 | var req = {}; 155 | var res = { 156 | header: sinon.stub() 157 | }; 158 | var middle = index.cacheLong(); 159 | middle(req, res, function() { 160 | res.header.callCount.should.equal(1); 161 | res.header.args[0][0].should.equal('Cache-Control'); 162 | res.header.args[0][1].should.equal('public, max-age=86400'); 163 | done(); 164 | }); 165 | }); 166 | }); 167 | describe('#setupRoutes', function() { 168 | it('will use rate limiter by default', function() { 169 | var node = {}; 170 | var index = new InsightAPI({ 171 | node: node 172 | }); 173 | var middlewareFunc = sinon.stub(); 174 | var middleware = sinon.stub().returns(middlewareFunc); 175 | var limiter = { 176 | middleware: middleware 177 | }; 178 | index._getRateLimiter = sinon.stub().returns(limiter); 179 | var use = sinon.stub(); 180 | var app = { 181 | use: use, 182 | get: sinon.stub(), 183 | param: sinon.stub(), 184 | post: sinon.stub() 185 | }; 186 | index.setupRoutes(app); 187 | use.callCount.should.be.above(0); 188 | use.args[0][0].should.equal(middlewareFunc); 189 | middleware.callCount.should.equal(1); 190 | }); 191 | it('will NOT use rate limiter if disabled', function() { 192 | var node = {}; 193 | var index = new InsightAPI({ 194 | node: node, 195 | disableRateLimiter: true 196 | }); 197 | index._getRateLimiter = sinon.stub(); 198 | var use = sinon.stub(); 199 | var app = { 200 | use: use, 201 | get: sinon.stub(), 202 | param: sinon.stub(), 203 | post: sinon.stub() 204 | }; 205 | index.setupRoutes(app); 206 | index._getRateLimiter.callCount.should.equal(0); 207 | }); 208 | }); 209 | }); 210 | -------------------------------------------------------------------------------- /test/messages.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var should = require('should'); 4 | var sinon = require('sinon'); 5 | var MessagesController = require('../lib/messages'); 6 | var bitcore = require('litecore-lib'); 7 | var _ = require('lodash'); 8 | 9 | describe('Messages', function() { 10 | 11 | var privateKey = bitcore.PrivateKey.fromWIF('cQwApHAg8hw9AZuxiU4a7g9kFWdaemhPxVZXWiAKgJTx6dPP32fN'); 12 | var address = 'mswTKCE2tYSFvUNnNPBKZfeNmugYL1rZMx'; 13 | var badAddress = 'mswTKCE2tYSFvUNnNPBKZfeNmuhYL1rZMm'; 14 | var signature = 'IA4sIwhcLMPPsYtB8tN0PI+aQuwDyl+/4Ksa89llNSAeVaRdMyyIxpo1H5N3GHbPl9LQqZ7CvaokeQgsOkK9fn4='; 15 | var message = 'cellar door'; 16 | 17 | it('will verify a message (true)', function(done) { 18 | 19 | var controller = new MessagesController({node: {}}); 20 | 21 | var req = { 22 | body: { 23 | 'address': address, 24 | 'signature': signature, 25 | 'message': message 26 | }, 27 | query: {} 28 | }; 29 | var res = { 30 | json: function(data) { 31 | data.result.should.equal(true); 32 | done(); 33 | } 34 | }; 35 | 36 | controller.verify(req, res); 37 | }); 38 | 39 | it('will verify a message (false)', function(done) { 40 | 41 | var controller = new MessagesController({node: {}}); 42 | 43 | var req = { 44 | body: { 45 | 'address': address, 46 | 'signature': signature, 47 | 'message': 'wrong message' 48 | }, 49 | query: {} 50 | }; 51 | var res = { 52 | json: function(data) { 53 | data.result.should.equal(false); 54 | done(); 55 | } 56 | }; 57 | 58 | controller.verify(req, res); 59 | }); 60 | 61 | it('handle an error from message verification', function(done) { 62 | var controller = new MessagesController({node: {}}); 63 | var req = { 64 | body: { 65 | 'address': badAddress, 66 | 'signature': signature, 67 | 'message': message 68 | }, 69 | query: {} 70 | }; 71 | var send = sinon.stub(); 72 | var status = sinon.stub().returns({send: send}); 73 | var res = { 74 | status: status, 75 | }; 76 | controller.verify(req, res); 77 | status.args[0][0].should.equal(400); 78 | send.args[0][0].should.equal('Unexpected error: Checksum mismatch. Code:1'); 79 | done(); 80 | }); 81 | 82 | it('handle error with missing parameters', function(done) { 83 | var controller = new MessagesController({node: {}}); 84 | var req = { 85 | body: {}, 86 | query: {} 87 | }; 88 | var send = sinon.stub(); 89 | var status = sinon.stub().returns({send: send}); 90 | var res = { 91 | status: status 92 | }; 93 | controller.verify(req, res); 94 | status.args[0][0].should.equal(400); 95 | send.args[0][0].should.match(/^Missing parameters/); 96 | done(); 97 | }); 98 | 99 | }); 100 | -------------------------------------------------------------------------------- /test/ratelimiter.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var should = require('should'); 4 | var sinon = require('sinon'); 5 | 6 | var RateLimiter = require('../lib/ratelimiter'); 7 | 8 | describe('RateLimiter', function() { 9 | 10 | describe('@constructor', function() { 11 | it('will instantiate without options', function() { 12 | var limiter = new RateLimiter(); 13 | should.exist(limiter); 14 | }); 15 | it('will instantiate without new', function() { 16 | /* jshint newcap:false */ 17 | var limiter = RateLimiter(); 18 | should.exist(limiter); 19 | }); 20 | it('will instantiate with options', function() { 21 | var whitelist = []; 22 | var blacklist = []; 23 | var node = {}; 24 | var limiter = new RateLimiter({ 25 | node: node, 26 | whitelist: whitelist, 27 | blacklist: blacklist, 28 | limit: 1, 29 | interval: 1, 30 | whitelistLimit: 1, 31 | whitelistInterval: 1, 32 | blacklistLimit: 1, 33 | blacklistInterval: 1 34 | }); 35 | should.exist(limiter); 36 | should.exist(limiter.config); 37 | should.exist(limiter.clients); 38 | should.exist(limiter.node); 39 | limiter.whitelist.should.equal(whitelist); 40 | limiter.blacklist.should.equal(blacklist); 41 | limiter.config.whitelist.totalRequests.should.equal(1); 42 | limiter.config.whitelist.interval.should.equal(1); 43 | limiter.config.blacklist.totalRequests.should.equal(1); 44 | limiter.config.blacklist.interval.should.equal(1); 45 | limiter.config.normal.interval.should.equal(1); 46 | limiter.config.normal.totalRequests.should.equal(1); 47 | }); 48 | }); 49 | 50 | describe('#middleware', function() { 51 | it('will set ratelimit headers', function(done) { 52 | var limiter = new RateLimiter(); 53 | var req = { 54 | headers: { 55 | 'cf-connecting-ip': '127.0.0.1' 56 | } 57 | }; 58 | var setHeader = sinon.stub(); 59 | var res = { 60 | setHeader: setHeader 61 | }; 62 | limiter.middleware()(req, res, function() { 63 | setHeader.callCount.should.equal(2); 64 | setHeader.args[0][0].should.equal('X-RateLimit-Limit'); 65 | setHeader.args[0][1].should.equal(10800); 66 | setHeader.args[1][0].should.equal('X-RateLimit-Remaining'); 67 | setHeader.args[1][1].should.equal(10799); 68 | done(); 69 | }); 70 | }); 71 | it('will give rate limit error', function() { 72 | var node = { 73 | log: { 74 | warn: sinon.stub() 75 | } 76 | }; 77 | var limiter = new RateLimiter({node: node}); 78 | limiter.exceeded = sinon.stub().returns(true); 79 | var req = { 80 | headers: { 81 | 'cf-connecting-ip': '127.0.0.1' 82 | } 83 | }; 84 | var jsonp = sinon.stub(); 85 | var status = sinon.stub().returns({ 86 | jsonp: jsonp 87 | }); 88 | var res = { 89 | status: status, 90 | setHeader: sinon.stub() 91 | }; 92 | limiter.middleware()(req, res); 93 | status.callCount.should.equal(1); 94 | status.args[0][0].should.equal(429); 95 | jsonp.callCount.should.equal(1); 96 | jsonp.args[0][0].should.eql({ 97 | status: 429, 98 | error: 'Rate limit exceeded' 99 | }); 100 | }); 101 | }); 102 | 103 | describe('#exceeded', function() { 104 | it('should not be exceeded', function() { 105 | var node = {}; 106 | var limiter = new RateLimiter({node: node}); 107 | var client = limiter.addClient('127.0.0.1'); 108 | var exceeded = limiter.exceeded(client); 109 | exceeded.should.equal(false); 110 | }); 111 | it('should be exceeded', function() { 112 | var node = {}; 113 | var limiter = new RateLimiter({node: node}); 114 | var client = limiter.addClient('127.0.0.1'); 115 | client.visits = 3 * 60 * 60 + 1; 116 | var exceeded = limiter.exceeded(client); 117 | exceeded.should.equal(true); 118 | }); 119 | it('should exclude whitelisted with no limit', function() { 120 | var node = {}; 121 | var limiter = new RateLimiter({ 122 | whitelist: [ 123 | '127.0.0.1' 124 | ], 125 | node: node, 126 | whitelistLimit: -1 127 | }); 128 | var client = limiter.addClient('127.0.0.1'); 129 | client.visits = Infinity; 130 | var exceeded = limiter.exceeded(client); 131 | exceeded.should.equal(false); 132 | }); 133 | }); 134 | 135 | describe('#getClientName', function() { 136 | it('should get client name from cloudflare header', function() { 137 | var node = {}; 138 | var limiter = new RateLimiter({node: node}); 139 | var req = { 140 | headers: { 141 | 'cf-connecting-ip': '127.0.0.1' 142 | } 143 | }; 144 | var name = limiter.getClientName(req); 145 | name.should.equal('127.0.0.1'); 146 | }); 147 | it('should get client name from x forwarded header', function() { 148 | var node = {}; 149 | var limiter = new RateLimiter({node: node}); 150 | var req = { 151 | headers: { 152 | 'x-forwarded-for': '127.0.0.1' 153 | } 154 | }; 155 | var name = limiter.getClientName(req); 156 | name.should.equal('127.0.0.1'); 157 | }); 158 | it('should get client name from connection remote address', function() { 159 | var node = {}; 160 | var limiter = new RateLimiter({node: node}); 161 | var req = { 162 | headers: {}, 163 | connection: { 164 | remoteAddress: '127.0.0.1' 165 | } 166 | }; 167 | var name = limiter.getClientName(req); 168 | name.should.equal('127.0.0.1'); 169 | }); 170 | }); 171 | 172 | describe('#addClient', function() { 173 | var sandbox = sinon.sandbox.create(); 174 | afterEach(function() { 175 | sandbox.restore(); 176 | }); 177 | it('will remove client after interval', function() { 178 | var THREE_HOURS_PLUS = 3 * 60 * 60 * 1000 + 1; 179 | var clock = sandbox.useFakeTimers(); 180 | var node = {}; 181 | var limiter = new RateLimiter({node: node}); 182 | limiter.addClient('127.0.0.1'); 183 | should.exist(limiter.clients['127.0.0.1']); 184 | clock.tick(THREE_HOURS_PLUS); 185 | should.not.exist(limiter.clients['127.0.0.1']); 186 | }); 187 | }); 188 | 189 | }); 190 | -------------------------------------------------------------------------------- /test/status.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var sinon = require('sinon'); 4 | var should = require('should'); 5 | var StatusController = require('../lib/status'); 6 | 7 | describe('Status', function() { 8 | describe('/status', function() { 9 | var info = { 10 | version: 110000, 11 | protocolVersion: 70002, 12 | blocks: 548645, 13 | timeOffset: 0, 14 | connections: 8, 15 | difficulty: 21546.906405522557, 16 | testnet: true, 17 | relayFee: 1000, 18 | errors: '' 19 | }; 20 | 21 | var outSetInfo = { 22 | height: 151, 23 | bestblock: '20b6cc0600037171b8bb634bbd04ea754945be44db8d9199b74798f1abdb382d', 24 | transactions: 151, 25 | txouts: 151, 26 | bytes_serialized: 10431, 27 | hash_serialized: 'c165d5dcb22a897745ee2ee274b47133b995bbcf8dd4a7572fedad87541c7df8', 28 | total_amount: 750000000000 29 | }; 30 | 31 | var node = { 32 | services: { 33 | bitcoind: { 34 | getInfo: sinon.stub().callsArgWith(0, null, info), 35 | getBestBlockHash: sinon.stub().callsArgWith(0, null, outSetInfo.bestblock), 36 | tiphash: outSetInfo.bestblock 37 | } 38 | } 39 | }; 40 | 41 | var status = new StatusController(node); 42 | 43 | it('getInfo', function(done) { 44 | var req = { 45 | query: {} 46 | }; 47 | var res = { 48 | jsonp: function(data) { 49 | should.exist(data.info.version); 50 | should.exist(data.info.protocolversion); 51 | should.exist(data.info.blocks); 52 | should.exist(data.info.timeoffset); 53 | should.exist(data.info.connections); 54 | should.exist(data.info.difficulty); 55 | should.exist(data.info.testnet); 56 | should.exist(data.info.relayfee); 57 | done(); 58 | } 59 | }; 60 | 61 | status.show(req, res); 62 | }); 63 | 64 | it('getDifficulty', function(done) { 65 | var req = { 66 | query: { 67 | q: 'getDifficulty' 68 | } 69 | }; 70 | var res = { 71 | jsonp: function(data) { 72 | data.difficulty.should.equal(info.difficulty); 73 | done(); 74 | } 75 | }; 76 | 77 | status.show(req, res); 78 | }); 79 | 80 | it('getBestBlockHash', function(done) { 81 | var req = { 82 | query: { 83 | q: 'getBestBlockHash' 84 | } 85 | }; 86 | var res = { 87 | jsonp: function(data) { 88 | data.bestblockhash.should.equal(outSetInfo.bestblock); 89 | done(); 90 | } 91 | }; 92 | status.show(req, res); 93 | }); 94 | 95 | it('getLastBlockHash', function(done) { 96 | var req = { 97 | query: { 98 | q: 'getLastBlockHash' 99 | } 100 | }; 101 | var res = { 102 | jsonp: function(data) { 103 | data.syncTipHash.should.equal(outSetInfo.bestblock); 104 | data.lastblockhash.should.equal(outSetInfo.bestblock); 105 | done(); 106 | } 107 | }; 108 | status.show(req, res); 109 | }); 110 | 111 | }); 112 | 113 | describe('/sync', function() { 114 | it('should have correct data', function(done) { 115 | var node = { 116 | services: { 117 | bitcoind: { 118 | height: 500000, 119 | isSynced: sinon.stub().callsArgWith(0, null, true), 120 | syncPercentage: sinon.stub().callsArgWith(0, null, 99.99) 121 | } 122 | } 123 | }; 124 | 125 | var expected = { 126 | status: 'finished', 127 | blockChainHeight: 500000, 128 | syncPercentage: 100, 129 | height: 500000, 130 | error: null, 131 | type: 'bitcore node' 132 | }; 133 | 134 | var status = new StatusController(node); 135 | 136 | var req = {}; 137 | var res = { 138 | jsonp: function(data) { 139 | should(data).eql(expected); 140 | done(); 141 | } 142 | }; 143 | status.sync(req, res); 144 | }); 145 | }); 146 | 147 | describe('/peer', function() { 148 | it('should have correct data', function(done) { 149 | var node = {}; 150 | 151 | var expected = { 152 | connected: true, 153 | host: '127.0.0.1', 154 | port: null 155 | }; 156 | 157 | var req = {}; 158 | var res = { 159 | jsonp: function(data) { 160 | should(data).eql(expected); 161 | done(); 162 | } 163 | }; 164 | 165 | var status = new StatusController(node); 166 | 167 | status.peer(req, res); 168 | }); 169 | }); 170 | 171 | describe('/version', function() { 172 | it('should have correct data', function(done) { 173 | var node = {}; 174 | var expected = { 175 | version: require('../package.json').version 176 | }; 177 | 178 | var req = {}; 179 | var res = { 180 | jsonp: function(data) { 181 | should(data).eql(expected); 182 | done(); 183 | } 184 | }; 185 | 186 | var status = new StatusController(node); 187 | status.version(req, res); 188 | }); 189 | }); 190 | }); 191 | -------------------------------------------------------------------------------- /test/transactions.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var should = require('should'); 3 | var sinon = require('sinon'); 4 | var bitcore = require('litecore-lib'); 5 | var TxController = require('../lib/transactions'); 6 | var _ = require('lodash'); 7 | 8 | describe('Transactions', function() { 9 | describe('/tx/:txid', function() { 10 | it('should have correct data', function(done) { 11 | var insight = { 12 | 'txid': 'b85334bf2df35c6dd5b294efe92ffc793a78edff75a2ca666fc296ffb04bbba0', 13 | 'version': 1, 14 | 'locktime': 0, 15 | 'vin': [ 16 | { 17 | 'txid': '87c9b0f27571fff14b8c2d69e55614eacedd0f59fcc490b721320f9dae145aad', 18 | 'vout': 0, 19 | 'scriptSig': { 20 | 'asm': '30450221008e5df62719cd92d7b137d00bbd27f153f2909bcad3a300960bc1020ec6d5e961022039df51600ff4fb5da5a794d1648c6b47c1f7d277fd5877fb5e52a730a3595f8c01 04eb1e0ccd9afcac42229348dd776e991c69551ae3474340fada12e787e51758397e1d3afdba360d6374261125ea3b6ea079a5f202c150dfd729e1062d9176a307', 21 | 'hex': '4830450221008e5df62719cd92d7b137d00bbd27f153f2909bcad3a300960bc1020ec6d5e961022039df51600ff4fb5da5a794d1648c6b47c1f7d277fd5877fb5e52a730a3595f8c014104eb1e0ccd9afcac42229348dd776e991c69551ae3474340fada12e787e51758397e1d3afdba360d6374261125ea3b6ea079a5f202c150dfd729e1062d9176a307' 22 | }, 23 | 'sequence': 4294967295, 24 | 'n': 0, 25 | 'addr': 'mqdofsXHpePPGBFXuwwypAqCcXi48Xhb2f', 26 | 'valueSat': 18535505, 27 | 'value': 0.18535505, 28 | 'doubleSpentTxID': null, 29 | 'isConfirmed': true, 30 | 'confirmations': 242, 31 | 'unconfirmedInput': false 32 | }, 33 | { 34 | 'txid': 'd8a10aaedf3dd33b5ddf8979273f3dbf61e4638d1aa6a93c59ea22bc65ac2196', 35 | 'vout': 0, 36 | 'scriptSig': { 37 | 'asm': '30440220761464d7bab9515d92260762a97af82a9b25d202d8f7197b1aaec81b6fed541f022059f99606de6b06e17b2cd102dceb3807ebdd9e777a5b77c9a0b3672f5eabcb3101 04eb1e0ccd9afcac42229348dd776e991c69551ae3474340fada12e787e51758397e1d3afdba360d6374261125ea3b6ea079a5f202c150dfd729e1062d9176a307', 38 | 'hex': '4730440220761464d7bab9515d92260762a97af82a9b25d202d8f7197b1aaec81b6fed541f022059f99606de6b06e17b2cd102dceb3807ebdd9e777a5b77c9a0b3672f5eabcb31014104eb1e0ccd9afcac42229348dd776e991c69551ae3474340fada12e787e51758397e1d3afdba360d6374261125ea3b6ea079a5f202c150dfd729e1062d9176a307' 39 | }, 40 | 'sequence': 4294967295, 41 | 'n': 1, 42 | 'addr': 'mqdofsXHpePPGBFXuwwypAqCcXi48Xhb2f', 43 | 'valueSat': 16419885, 44 | 'value': 0.16419885, 45 | 'doubleSpentTxID': null, 46 | 'isConfirmed': true, 47 | 'confirmations': 242, 48 | 'unconfirmedInput': false 49 | } 50 | ], 51 | 'vout': [ 52 | { 53 | 'value': '0.21247964', 54 | 'n': 0, 55 | 'scriptPubKey': { 56 | 'asm': 'OP_DUP OP_HASH160 4b7b335f978f130269fe661423258ae9642df8a1 OP_EQUALVERIFY OP_CHECKSIG', 57 | 'hex': '76a9144b7b335f978f130269fe661423258ae9642df8a188ac', 58 | 'reqSigs': 1, 59 | 'type': 'pubkeyhash', 60 | 'addresses': [ 61 | 'mnQ4ZaGessNgdxmWPxbTHcfx4b8R6eUr1X' 62 | ] 63 | }, 64 | 'spentTxId': null, 65 | 'spentIndex': null, 66 | 'spentHeight': null 67 | }, 68 | { 69 | 'value': '0.13677426', 70 | 'n': 1, 71 | 'scriptPubKey': { 72 | 'asm': 'OP_DUP OP_HASH160 6efcf883b4b6f9997be9a0600f6c095fe2bd2d92 OP_EQUALVERIFY OP_CHECKSIG', 73 | 'hex': '76a9146efcf883b4b6f9997be9a0600f6c095fe2bd2d9288ac', 74 | 'reqSigs': 1, 75 | 'type': 'pubkeyhash', 76 | 'addresses': [ 77 | 'mqdofsXHpePPGBFXuwwypAqCcXi48Xhb2f' 78 | ] 79 | }, 80 | 'spentTxId': '614fe1708825f9c21732394e4784cc6808ac1d8b939736bfdead970567561eec', 81 | 'spentIndex': 1, 82 | 'spentHeight': 10, 83 | 'spentTs': 1440997099 84 | } 85 | ], 86 | 'blockhash': '0000000000000afa0c3c0afd450c793a1e300ec84cbe9555166e06132f19a8f7', 87 | 'blockheight': 533974, 88 | 'confirmations': 230, 89 | 'time': 1440987503, 90 | 'blocktime': 1440987503, 91 | 'valueOut': 0.3492539, 92 | 'size': 437, 93 | 'valueIn': 0.3495539, 94 | 'fees': 0.0003 95 | }; 96 | 97 | var spentTxId = '614fe1708825f9c21732394e4784cc6808ac1d8b939736bfdead970567561eec'; 98 | var spentIndex = 1; 99 | var detailedTransaction = { 100 | hex: '7b5485d3628922f004f470f497f6a83f6df4df347e1bce15831a964623f8072b565f7c7bc5dcbc717c6e2a2301a2f6b4a19e65042ad88c9f5d037628de38603c4f137f625e135691e2bd0169cab74e1368abe858f3c3d116e9d13c4c85ead129d9edf0245a3fb1b35561bd230607dca0dcaf3cffc735a3982d8384a1ecc5d622a7bb4db8b5d47d061701978b1f45e2e39946d66c3394f8a20b8ac8c931a6786f761da2d0f3fa2c7c93edee9f2a94de7c47510498767c3d87afe68815bd6058710bf5d8c850a5d20fc217943d9c00da58a4908d92a0912578247746f2086e54cb7b81b6a9e3cc1741457e956d41bdeaae06c441db96ec39a2d17147dd8f468eeaeaaa78dc2e53d66188a791c46b2a4965639ad72a2b90ee52786e36db1a8cf924346b105a40b41a3027dae657782ef7e8b56d6da86062184cb5366d4886cd2ce27471d9d62d1df447f2e5a9641e1f8d1f2b628054d3bd915bf7932bcec6f2dd4965e2406b1dba445b5493ee475757de332618220318dd806b880a7364370c5c0c3b736a653f97b2901fdb5cf4b5b2230b09b2d7bd324a392633d51c598765f9bd286421239a1f25db34a9a61f645eb601e59f10fc1b', 101 | hash: 'b85334bf2df35c6dd5b294efe92ffc793a78edff75a2ca666fc296ffb04bbba0', 102 | version: 1, 103 | blockHash: '0000000000000afa0c3c0afd450c793a1e300ec84cbe9555166e06132f19a8f7', 104 | height: 533974, 105 | blockTimestamp: 1440987503, 106 | inputSatoshis: 34955390, 107 | outputSatoshis: 34925390, 108 | feeSatoshis: 30000, 109 | inputs: [ 110 | { 111 | address: 'mqdofsXHpePPGBFXuwwypAqCcXi48Xhb2f', 112 | prevTxId: '87c9b0f27571fff14b8c2d69e55614eacedd0f59fcc490b721320f9dae145aad', 113 | outputIndex: 0, 114 | sequence: 4294967295, 115 | script: '4830450221008e5df62719cd92d7b137d00bbd27f153f2909bcad3a300960bc1020ec6d5e961022039df51600ff4fb5da5a794d1648c6b47c1f7d277fd5877fb5e52a730a3595f8c014104eb1e0ccd9afcac42229348dd776e991c69551ae3474340fada12e787e51758397e1d3afdba360d6374261125ea3b6ea079a5f202c150dfd729e1062d9176a307', 116 | scriptAsm: '30450221008e5df62719cd92d7b137d00bbd27f153f2909bcad3a300960bc1020ec6d5e961022039df51600ff4fb5da5a794d1648c6b47c1f7d277fd5877fb5e52a730a3595f8c01 04eb1e0ccd9afcac42229348dd776e991c69551ae3474340fada12e787e51758397e1d3afdba360d6374261125ea3b6ea079a5f202c150dfd729e1062d9176a307', 117 | satoshis: 18535505, 118 | }, 119 | { 120 | address: 'mqdofsXHpePPGBFXuwwypAqCcXi48Xhb2f', 121 | prevTxId: 'd8a10aaedf3dd33b5ddf8979273f3dbf61e4638d1aa6a93c59ea22bc65ac2196', 122 | outputIndex: 0, 123 | sequence: 4294967295, 124 | script: '4730440220761464d7bab9515d92260762a97af82a9b25d202d8f7197b1aaec81b6fed541f022059f99606de6b06e17b2cd102dceb3807ebdd9e777a5b77c9a0b3672f5eabcb31014104eb1e0ccd9afcac42229348dd776e991c69551ae3474340fada12e787e51758397e1d3afdba360d6374261125ea3b6ea079a5f202c150dfd729e1062d9176a307', 125 | scriptAsm: '30440220761464d7bab9515d92260762a97af82a9b25d202d8f7197b1aaec81b6fed541f022059f99606de6b06e17b2cd102dceb3807ebdd9e777a5b77c9a0b3672f5eabcb3101 04eb1e0ccd9afcac42229348dd776e991c69551ae3474340fada12e787e51758397e1d3afdba360d6374261125ea3b6ea079a5f202c150dfd729e1062d9176a307', 126 | satoshis: 16419885, 127 | } 128 | ], 129 | outputs: [ 130 | { 131 | satoshis: 21247964, 132 | script: '76a9144b7b335f978f130269fe661423258ae9642df8a188ac', 133 | scriptAsm: 'OP_DUP OP_HASH160 4b7b335f978f130269fe661423258ae9642df8a1 OP_EQUALVERIFY OP_CHECKSIG', 134 | address: 'mnQ4ZaGessNgdxmWPxbTHcfx4b8R6eUr1X' 135 | }, 136 | { 137 | address: 'mqdofsXHpePPGBFXuwwypAqCcXi48Xhb2f', 138 | satoshis: 13677426, 139 | scriptAsm: 'OP_DUP OP_HASH160 6efcf883b4b6f9997be9a0600f6c095fe2bd2d92 OP_EQUALVERIFY OP_CHECKSIG', 140 | script: '76a9146efcf883b4b6f9997be9a0600f6c095fe2bd2d9288ac', 141 | spentTxId: spentTxId, 142 | spentIndex: spentIndex, 143 | spentHeight: 10 144 | } 145 | ], 146 | locktime: 0 147 | }; 148 | 149 | var todos = { 150 | vin: [ 151 | { 152 | isConfirmed: true, 153 | confirmations: 242, 154 | unconfirmedInput: false 155 | }, 156 | { 157 | isConfirmed: true, 158 | confirmations: 242, 159 | unconfirmedInput: false 160 | } 161 | ], 162 | vout: [ 163 | { 164 | scriptPubKey: { 165 | reqSigs: 1 166 | } 167 | }, 168 | { 169 | scriptPubKey: { 170 | reqSigs: 1 171 | }, 172 | spentTs: 1440997099 173 | } 174 | ] 175 | }; 176 | 177 | var node = { 178 | getDetailedTransaction: sinon.stub().callsArgWith(1, null, detailedTransaction), 179 | services: { 180 | bitcoind: { 181 | height: 534203 182 | }, 183 | }, 184 | network: 'testnet' 185 | }; 186 | 187 | var transactions = new TxController(node); 188 | var txid = 'b85334bf2df35c6dd5b294efe92ffc793a78edff75a2ca666fc296ffb04bbba0'; 189 | var req = { 190 | params: { 191 | txid: txid 192 | } 193 | }; 194 | var res = {}; 195 | var next = function() { 196 | var merged = _.merge(req.transaction, todos); 197 | should(merged).eql(insight); 198 | done(); 199 | }; 200 | 201 | transactions.transaction(req, res, next); 202 | }); 203 | }); 204 | 205 | describe('/txs', function() { 206 | var sandbox = sinon.sandbox.create(); 207 | afterEach(function() { 208 | sandbox.restore(); 209 | }); 210 | it('by block hash', function(done) { 211 | var blockOverview = { 212 | hash: '0000000000000afa0c3c0afd450c793a1e300ec84cbe9555166e06132f19a8f7', 213 | height: 533974, 214 | chainWork: '0000000000000000000000000000000000000000000000054626b1839ade284a', 215 | prevHash: '00000000000001a55f3214e9172eb34b20e0bc5bd6b8007f3f149fca2c8991a4', 216 | txids: [ 217 | '25a988e54b02e0e5df146a0f8fa7b9db56210533a9f04bdfda5f4ceb6f77aadd', 218 | 'b85334bf2df35c6dd5b294efe92ffc793a78edff75a2ca666fc296ffb04bbba0', 219 | '2e01c7a4a0e335112236b711c4aaddd02e8dc59ba2cda416e8f80ff06dddd7e1' 220 | ] 221 | }; 222 | 223 | var transactionDetails = { 224 | '25a988e54b02e0e5df146a0f8fa7b9db56210533a9f04bdfda5f4ceb6f77aadd': { 225 | hex: '01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff2303d6250800feb0aae355fe263600000963676d696e6572343208ae5800000000000000ffffffff01c018824a000000001976a91468bedce8982d25c3b6b03f6238cbad00378b8ead88ac00000000', 226 | coinbase: true, 227 | version: 1, 228 | blockTimestamp: 1440987503, 229 | blockHash: '0000000000000afa0c3c0afd450c793a1e300ec84cbe9555166e06132f19a8f7', 230 | height: 533974, 231 | inputSatoshis: 0, 232 | outputSatoshis: 1250040000, 233 | feeSatoshis: 0, 234 | locktime: 0, 235 | hash: '25a988e54b02e0e5df146a0f8fa7b9db56210533a9f04bdfda5f4ceb6f77aadd', 236 | inputs: [ 237 | { 238 | script: '03d6250800feb0aae355fe263600000963676d696e6572343208ae5800000000000000', 239 | sequence: 4294967295 240 | } 241 | ], 242 | outputs: [ 243 | { 244 | address: 'mq4oDPjmNWnBxbzx7qouzhpCSTMePUtYDF', 245 | script: '76a91468bedce8982d25c3b6b03f6238cbad00378b8ead88ac', 246 | scriptAsm: 'OP_DUP OP_HASH160 68bedce8982d25c3b6b03f6238cbad00378b8ead OP_EQUALVERIFY OP_CHECKSIG', 247 | satoshis: 1250040000 248 | } 249 | ] 250 | }, 251 | 'b85334bf2df35c6dd5b294efe92ffc793a78edff75a2ca666fc296ffb04bbba0': { 252 | hex: '0100000002ad5a14ae9d0f3221b790c4fc590fddceea1456e5692d8c4bf1ff7175f2b0c987000000008b4830450221008e5df62719cd92d7b137d00bbd27f153f2909bcad3a300960bc1020ec6d5e961022039df51600ff4fb5da5a794d1648c6b47c1f7d277fd5877fb5e52a730a3595f8c014104eb1e0ccd9afcac42229348dd776e991c69551ae3474340fada12e787e51758397e1d3afdba360d6374261125ea3b6ea079a5f202c150dfd729e1062d9176a307ffffffff9621ac65bc22ea593ca9a61a8d63e461bf3d3f277989df5d3bd33ddfae0aa1d8000000008a4730440220761464d7bab9515d92260762a97af82a9b25d202d8f7197b1aaec81b6fed541f022059f99606de6b06e17b2cd102dceb3807ebdd9e777a5b77c9a0b3672f5eabcb31014104eb1e0ccd9afcac42229348dd776e991c69551ae3474340fada12e787e51758397e1d3afdba360d6374261125ea3b6ea079a5f202c150dfd729e1062d9176a307ffffffff02dc374401000000001976a9144b7b335f978f130269fe661423258ae9642df8a188ac72b3d000000000001976a9146efcf883b4b6f9997be9a0600f6c095fe2bd2d9288ac00000000', 253 | inputSatoshis: 34955390, 254 | outputSatoshis: 34925390, 255 | feeSatoshis: 30000, 256 | version: 1, 257 | hash: 'b85334bf2df35c6dd5b294efe92ffc793a78edff75a2ca666fc296ffb04bbba0', 258 | blockTimestamp: 1440987503, 259 | height: 533974, 260 | blockHash: '0000000000000afa0c3c0afd450c793a1e300ec84cbe9555166e06132f19a8f7', 261 | locktime: 0, 262 | inputs: [ 263 | { 264 | satoshis: 18535505, 265 | address: 'mqdofsXHpePPGBFXuwwypAqCcXi48Xhb2f', 266 | script: '4830450221008e5df62719cd92d7b137d00bbd27f153f2909bcad3a300960bc1020ec6d5e961022039df51600ff4fb5da5a794d1648c6b47c1f7d277fd5877fb5e52a730a3595f8c014104eb1e0ccd9afcac42229348dd776e991c69551ae3474340fada12e787e51758397e1d3afdba360d6374261125ea3b6ea079a5f202c150dfd729e1062d9176a307', 267 | scriptAsm: '30450221008e5df62719cd92d7b137d00bbd27f153f2909bcad3a300960bc1020ec6d5e961022039df51600ff4fb5da5a794d1648c6b47c1f7d277fd5877fb5e52a730a3595f8c01 04eb1e0ccd9afcac42229348dd776e991c69551ae3474340fada12e787e51758397e1d3afdba360d6374261125ea3b6ea079a5f202c150dfd729e1062d9176a307', 268 | prevTxId: '87c9b0f27571fff14b8c2d69e55614eacedd0f59fcc490b721320f9dae145aad', 269 | outputIndex: 0, 270 | sequence: 4294967295 271 | }, 272 | { 273 | address: 'mqdofsXHpePPGBFXuwwypAqCcXi48Xhb2f', 274 | script: '4730440220761464d7bab9515d92260762a97af82a9b25d202d8f7197b1aaec81b6fed541f022059f99606de6b06e17b2cd102dceb3807ebdd9e777a5b77c9a0b3672f5eabcb31014104eb1e0ccd9afcac42229348dd776e991c69551ae3474340fada12e787e51758397e1d3afdba360d6374261125ea3b6ea079a5f202c150dfd729e1062d9176a307', 275 | scriptAsm: '30440220761464d7bab9515d92260762a97af82a9b25d202d8f7197b1aaec81b6fed541f022059f99606de6b06e17b2cd102dceb3807ebdd9e777a5b77c9a0b3672f5eabcb3101 04eb1e0ccd9afcac42229348dd776e991c69551ae3474340fada12e787e51758397e1d3afdba360d6374261125ea3b6ea079a5f202c150dfd729e1062d9176a307', 276 | satoshis: 16419885, 277 | sequence: 4294967295, 278 | prevTxId: 'd8a10aaedf3dd33b5ddf8979273f3dbf61e4638d1aa6a93c59ea22bc65ac2196', 279 | outputIndex: 0 280 | } 281 | ], 282 | outputs: [ 283 | { 284 | address: 'mnQ4ZaGessNgdxmWPxbTHcfx4b8R6eUr1X', 285 | script: '76a9144b7b335f978f130269fe661423258ae9642df8a188ac', 286 | scriptAsm: 'OP_DUP OP_HASH160 4b7b335f978f130269fe661423258ae9642df8a1 OP_EQUALVERIFY OP_CHECKSIG', 287 | satoshis: 21247964 288 | }, 289 | { 290 | script: '76a9146efcf883b4b6f9997be9a0600f6c095fe2bd2d9288ac', 291 | scriptAsm: 'OP_DUP OP_HASH160 6efcf883b4b6f9997be9a0600f6c095fe2bd2d92 OP_EQUALVERIFY OP_CHECKSIG', 292 | address: 'mqdofsXHpePPGBFXuwwypAqCcXi48Xhb2f', 293 | satoshis: 13677426, 294 | spentTxId: '614fe1708825f9c21732394e4784cc6808ac1d8b939736bfdead970567561eec', 295 | spentIndex: 1, 296 | spentHeight: 200 297 | } 298 | ] 299 | }, 300 | '2e01c7a4a0e335112236b711c4aaddd02e8dc59ba2cda416e8f80ff06dddd7e1': { 301 | hex: '0100000002060d3cb6dfb7ffe85e2908010fea63190c9707e96fc7448128eb895b5e222771030000006b483045022100f67cffc0ae23adb236ff3edb4a9736e277605db30cc7708dfab8cf1e1483bbce022052396aa5d664ec1cb65992c423fd9a17e94dc7af328d2d559e90746dd195ca5901210346134da14907581d8190d3980caaf46d95e4eb9c1ca8e70f1fc6007fefb1909dfeffffff7b2d8a8263cffbdb722e2a5c74166e6f2258634e277c0b08f51b578b667e2fba000000006a473044022077222a91cda23af69179377c62d84a176fb12caff6c5cbf6ae9e5957ff3b1afe0220768edead76819228dcba18cca3c9a5a5d4c32919720f21df21a297ba375bbe5c012103371ea5a4dfe356b3ea4042a537d7ab7ee0faabd43e21b6cc076fda2240629eeefeffffff02209a1d00000000001976a9148e451eec7ca0a1764b4ab119274efdd2727b3c8588ac40420f00000000001976a914d0fce8f064cd1059a6a11501dd66fe42368572b088accb250800', 302 | blockHash: '0000000000000afa0c3c0afd450c793a1e300ec84cbe9555166e06132f19a8f7', 303 | blockTimestamp: 1440987503, 304 | height: 533974, 305 | locktime: 533963, 306 | inputSatoshis: 2950000, 307 | outputSatoshis: 2940000, 308 | feeSatoshis: 10000, 309 | version: 1, 310 | hash: '2e01c7a4a0e335112236b711c4aaddd02e8dc59ba2cda416e8f80ff06dddd7e1', 311 | inputs: [ 312 | { 313 | address: 'mgZK8zpudWoAaAwpLQSgc9t9PJJyEBpBdJ', 314 | satoshis: 990000, 315 | script: '483045022100f67cffc0ae23adb236ff3edb4a9736e277605db30cc7708dfab8cf1e1483bbce022052396aa5d664ec1cb65992c423fd9a17e94dc7af328d2d559e90746dd195ca5901210346134da14907581d8190d3980caaf46d95e4eb9c1ca8e70f1fc6007fefb1909d', 316 | scriptAsm: '3045022100f67cffc0ae23adb236ff3edb4a9736e277605db30cc7708dfab8cf1e1483bbce022052396aa5d664ec1cb65992c423fd9a17e94dc7af328d2d559e90746dd195ca5901 0346134da14907581d8190d3980caaf46d95e4eb9c1ca8e70f1fc6007fefb1909d', 317 | sequence: 4294967294, 318 | outputIndex: 3, 319 | prevTxId: '7127225e5b89eb288144c76fe907970c1963ea0f0108295ee8ffb7dfb63c0d06' 320 | }, 321 | { 322 | address: 'n4oM7bPuC4ZPdCEDvtw9xGYQC7jmi5S6F4', 323 | satoshis: 1960000, 324 | script: '473044022077222a91cda23af69179377c62d84a176fb12caff6c5cbf6ae9e5957ff3b1afe0220768edead76819228dcba18cca3c9a5a5d4c32919720f21df21a297ba375bbe5c012103371ea5a4dfe356b3ea4042a537d7ab7ee0faabd43e21b6cc076fda2240629eee', 325 | scriptAsm: '3044022077222a91cda23af69179377c62d84a176fb12caff6c5cbf6ae9e5957ff3b1afe0220768edead76819228dcba18cca3c9a5a5d4c32919720f21df21a297ba375bbe5c01 03371ea5a4dfe356b3ea4042a537d7ab7ee0faabd43e21b6cc076fda2240629eee', 326 | prevTxId: 'ba2f7e668b571bf5080b7c274e6358226f6e16745c2a2e72dbfbcf63828a2d7b', 327 | sequence: 4294967294, 328 | outputIndex : 0 329 | } 330 | ], 331 | outputs: [ 332 | { 333 | spentTxId: '9a213b879da9073a9a30606f9046f35f36f268cbf03f6242993a97c4c07c00b9', 334 | spentIndex: 1, 335 | spentHeight: 200, 336 | satoshis: 1940000, 337 | script: '76a9148e451eec7ca0a1764b4ab119274efdd2727b3c8588ac', 338 | scriptAsm: 'OP_DUP OP_HASH160 8e451eec7ca0a1764b4ab119274efdd2727b3c85 OP_EQUALVERIFY OP_CHECKSIG', 339 | address: 'mtVD3tdifBNujYzZ5N7PgXfKk4Bc85tDKA' 340 | }, 341 | { 342 | spentTxId: '418d3eb60275957b3456b96902e908abf962e71be4c4f09486564254664951bc', 343 | spentIndex: 34, 344 | spentHeight: 200, 345 | script: '76a914d0fce8f064cd1059a6a11501dd66fe42368572b088ac', 346 | scriptAsm: 'OP_DUP OP_HASH160 d0fce8f064cd1059a6a11501dd66fe42368572b0 OP_EQUALVERIFY OP_CHECKSIG', 347 | address: 'mzZypShcs1B35udnkqeYeJy8rUdgHDDvKG', 348 | satoshis: 1000000 349 | } 350 | ] 351 | } 352 | }; 353 | 354 | var node = { 355 | getBlockOverview: sinon.stub().callsArgWith(1, null, blockOverview), 356 | getDetailedTransaction: function(txid, callback) { 357 | callback(null, transactionDetails[txid]); 358 | }, 359 | services: { 360 | bitcoind: { 361 | height: 534209 362 | } 363 | }, 364 | network: 'testnet' 365 | }; 366 | 367 | var transactions = new TxController(node); 368 | 369 | var insight = { 370 | 'pagesTotal': 1, 371 | 'txs': [ 372 | { 373 | 'txid': '25a988e54b02e0e5df146a0f8fa7b9db56210533a9f04bdfda5f4ceb6f77aadd', 374 | 'version': 1, 375 | 'locktime': 0, 376 | 'vin': [ 377 | { 378 | 'coinbase': '03d6250800feb0aae355fe263600000963676d696e6572343208ae5800000000000000', 379 | 'sequence': 4294967295, 380 | 'n': 0 381 | } 382 | ], 383 | 'vout': [ 384 | { 385 | 'value': '12.50040000', 386 | 'n': 0, 387 | 'scriptPubKey': { 388 | 'asm': 'OP_DUP OP_HASH160 68bedce8982d25c3b6b03f6238cbad00378b8ead OP_EQUALVERIFY OP_CHECKSIG', 389 | 'hex': '76a91468bedce8982d25c3b6b03f6238cbad00378b8ead88ac', 390 | 'reqSigs': 1, 391 | 'type': 'pubkeyhash', 392 | 'addresses': [ 393 | 'mq4oDPjmNWnBxbzx7qouzhpCSTMePUtYDF' 394 | ] 395 | }, 396 | 'spentTxId': null, 397 | 'spentIndex': null, 398 | 'spentHeight': null 399 | } 400 | ], 401 | 'blockhash': '0000000000000afa0c3c0afd450c793a1e300ec84cbe9555166e06132f19a8f7', 402 | 'blockheight': 533974, 403 | 'confirmations': 236, 404 | 'time': 1440987503, 405 | 'blocktime': 1440987503, 406 | 'isCoinBase': true, 407 | 'valueOut': 12.5004, 408 | 'size': 120 409 | }, 410 | { 411 | 'txid': 'b85334bf2df35c6dd5b294efe92ffc793a78edff75a2ca666fc296ffb04bbba0', 412 | 'version': 1, 413 | 'locktime': 0, 414 | 'vin': [ 415 | { 416 | 'txid': '87c9b0f27571fff14b8c2d69e55614eacedd0f59fcc490b721320f9dae145aad', 417 | 'vout': 0, 418 | 'scriptSig': { 419 | 'asm': '30450221008e5df62719cd92d7b137d00bbd27f153f2909bcad3a300960bc1020ec6d5e961022039df51600ff4fb5da5a794d1648c6b47c1f7d277fd5877fb5e52a730a3595f8c01 04eb1e0ccd9afcac42229348dd776e991c69551ae3474340fada12e787e51758397e1d3afdba360d6374261125ea3b6ea079a5f202c150dfd729e1062d9176a307', 420 | 'hex': '4830450221008e5df62719cd92d7b137d00bbd27f153f2909bcad3a300960bc1020ec6d5e961022039df51600ff4fb5da5a794d1648c6b47c1f7d277fd5877fb5e52a730a3595f8c014104eb1e0ccd9afcac42229348dd776e991c69551ae3474340fada12e787e51758397e1d3afdba360d6374261125ea3b6ea079a5f202c150dfd729e1062d9176a307' 421 | }, 422 | 'sequence': 4294967295, 423 | 'n': 0, 424 | 'addr': 'mqdofsXHpePPGBFXuwwypAqCcXi48Xhb2f', 425 | 'valueSat': 18535505, 426 | 'value': 0.18535505, 427 | 'doubleSpentTxID': null 428 | }, 429 | { 430 | 'txid': 'd8a10aaedf3dd33b5ddf8979273f3dbf61e4638d1aa6a93c59ea22bc65ac2196', 431 | 'vout': 0, 432 | 'scriptSig': { 433 | 'asm': '30440220761464d7bab9515d92260762a97af82a9b25d202d8f7197b1aaec81b6fed541f022059f99606de6b06e17b2cd102dceb3807ebdd9e777a5b77c9a0b3672f5eabcb3101 04eb1e0ccd9afcac42229348dd776e991c69551ae3474340fada12e787e51758397e1d3afdba360d6374261125ea3b6ea079a5f202c150dfd729e1062d9176a307', 434 | 'hex': '4730440220761464d7bab9515d92260762a97af82a9b25d202d8f7197b1aaec81b6fed541f022059f99606de6b06e17b2cd102dceb3807ebdd9e777a5b77c9a0b3672f5eabcb31014104eb1e0ccd9afcac42229348dd776e991c69551ae3474340fada12e787e51758397e1d3afdba360d6374261125ea3b6ea079a5f202c150dfd729e1062d9176a307' 435 | }, 436 | 'sequence': 4294967295, 437 | 'n': 1, 438 | 'addr': 'mqdofsXHpePPGBFXuwwypAqCcXi48Xhb2f', 439 | 'valueSat': 16419885, 440 | 'value': 0.16419885, 441 | 'doubleSpentTxID': null 442 | } 443 | ], 444 | 'vout': [ 445 | { 446 | 'value': '0.21247964', 447 | 'n': 0, 448 | 'scriptPubKey': { 449 | 'asm': 'OP_DUP OP_HASH160 4b7b335f978f130269fe661423258ae9642df8a1 OP_EQUALVERIFY OP_CHECKSIG', 450 | 'hex': '76a9144b7b335f978f130269fe661423258ae9642df8a188ac', 451 | 'reqSigs': 1, 452 | 'type': 'pubkeyhash', 453 | 'addresses': [ 454 | 'mnQ4ZaGessNgdxmWPxbTHcfx4b8R6eUr1X' 455 | ] 456 | }, 457 | 'spentTxId': null, 458 | 'spentIndex': null, 459 | 'spentHeight': null 460 | }, 461 | { 462 | 'value': '0.13677426', 463 | 'n': 1, 464 | 'scriptPubKey': { 465 | 'asm': 'OP_DUP OP_HASH160 6efcf883b4b6f9997be9a0600f6c095fe2bd2d92 OP_EQUALVERIFY OP_CHECKSIG', 466 | 'hex': '76a9146efcf883b4b6f9997be9a0600f6c095fe2bd2d9288ac', 467 | 'reqSigs': 1, 468 | 'type': 'pubkeyhash', 469 | 'addresses': [ 470 | 'mqdofsXHpePPGBFXuwwypAqCcXi48Xhb2f' 471 | ] 472 | }, 473 | 'spentTxId': '614fe1708825f9c21732394e4784cc6808ac1d8b939736bfdead970567561eec', 474 | 'spentIndex': 1, 475 | 'spentHeight': 200, 476 | 'spentTs': 1440997099 477 | } 478 | ], 479 | 'blockhash': '0000000000000afa0c3c0afd450c793a1e300ec84cbe9555166e06132f19a8f7', 480 | 'blockheight': 533974, 481 | 'confirmations': 236, 482 | 'time': 1440987503, 483 | 'blocktime': 1440987503, 484 | 'valueOut': 0.3492539, 485 | 'size': 437, 486 | 'valueIn': 0.3495539, 487 | 'fees': 0.0003 488 | }, 489 | { 490 | 'txid': '2e01c7a4a0e335112236b711c4aaddd02e8dc59ba2cda416e8f80ff06dddd7e1', 491 | 'version': 1, 492 | 'locktime': 533963, 493 | 'vin': [ 494 | { 495 | 'txid': '7127225e5b89eb288144c76fe907970c1963ea0f0108295ee8ffb7dfb63c0d06', 496 | 'vout': 3, 497 | 'scriptSig': { 498 | 'asm': '3045022100f67cffc0ae23adb236ff3edb4a9736e277605db30cc7708dfab8cf1e1483bbce022052396aa5d664ec1cb65992c423fd9a17e94dc7af328d2d559e90746dd195ca5901 0346134da14907581d8190d3980caaf46d95e4eb9c1ca8e70f1fc6007fefb1909d', 499 | 'hex': '483045022100f67cffc0ae23adb236ff3edb4a9736e277605db30cc7708dfab8cf1e1483bbce022052396aa5d664ec1cb65992c423fd9a17e94dc7af328d2d559e90746dd195ca5901210346134da14907581d8190d3980caaf46d95e4eb9c1ca8e70f1fc6007fefb1909d' 500 | }, 501 | 'sequence': 4294967294, 502 | 'n': 0, 503 | 'addr': 'mgZK8zpudWoAaAwpLQSgc9t9PJJyEBpBdJ', 504 | 'valueSat': 990000, 505 | 'value': 0.0099, 506 | 'doubleSpentTxID': null 507 | }, 508 | { 509 | 'txid': 'ba2f7e668b571bf5080b7c274e6358226f6e16745c2a2e72dbfbcf63828a2d7b', 510 | 'vout': 0, 511 | 'scriptSig': { 512 | 'asm': '3044022077222a91cda23af69179377c62d84a176fb12caff6c5cbf6ae9e5957ff3b1afe0220768edead76819228dcba18cca3c9a5a5d4c32919720f21df21a297ba375bbe5c01 03371ea5a4dfe356b3ea4042a537d7ab7ee0faabd43e21b6cc076fda2240629eee', 513 | 'hex': '473044022077222a91cda23af69179377c62d84a176fb12caff6c5cbf6ae9e5957ff3b1afe0220768edead76819228dcba18cca3c9a5a5d4c32919720f21df21a297ba375bbe5c012103371ea5a4dfe356b3ea4042a537d7ab7ee0faabd43e21b6cc076fda2240629eee' 514 | }, 515 | 'sequence': 4294967294, 516 | 'n': 1, 517 | 'addr': 'n4oM7bPuC4ZPdCEDvtw9xGYQC7jmi5S6F4', 518 | 'valueSat': 1960000, 519 | 'value': 0.0196, 520 | 'doubleSpentTxID': null 521 | } 522 | ], 523 | 'vout': [ 524 | { 525 | 'value': '0.01940000', 526 | 'n': 0, 527 | 'scriptPubKey': { 528 | 'asm': 'OP_DUP OP_HASH160 8e451eec7ca0a1764b4ab119274efdd2727b3c85 OP_EQUALVERIFY OP_CHECKSIG', 529 | 'hex': '76a9148e451eec7ca0a1764b4ab119274efdd2727b3c8588ac', 530 | 'reqSigs': 1, 531 | 'type': 'pubkeyhash', 532 | 'addresses': [ 533 | 'mtVD3tdifBNujYzZ5N7PgXfKk4Bc85tDKA' 534 | ] 535 | }, 536 | 'spentTxId': '9a213b879da9073a9a30606f9046f35f36f268cbf03f6242993a97c4c07c00b9', 537 | 'spentIndex': 1, 538 | 'spentHeight': 200, 539 | 'spentTs': 1440992946 540 | }, 541 | { 542 | 'value': '0.01000000', 543 | 'n': 1, 544 | 'scriptPubKey': { 545 | 'asm': 'OP_DUP OP_HASH160 d0fce8f064cd1059a6a11501dd66fe42368572b0 OP_EQUALVERIFY OP_CHECKSIG', 546 | 'hex': '76a914d0fce8f064cd1059a6a11501dd66fe42368572b088ac', 547 | 'reqSigs': 1, 548 | 'type': 'pubkeyhash', 549 | 'addresses': [ 550 | 'mzZypShcs1B35udnkqeYeJy8rUdgHDDvKG' 551 | ] 552 | }, 553 | 'spentTxId': '418d3eb60275957b3456b96902e908abf962e71be4c4f09486564254664951bc', 554 | 'spentIndex': 34, 555 | 'spentHeight': 200, 556 | 'spentTs': 1440999118 557 | } 558 | ], 559 | 'blockhash': '0000000000000afa0c3c0afd450c793a1e300ec84cbe9555166e06132f19a8f7', 560 | 'blockheight': 533974, 561 | 'confirmations': 236, 562 | 'time': 1440987503, 563 | 'blocktime': 1440987503, 564 | 'valueOut': 0.0294, 565 | 'size': 373, 566 | 'valueIn': 0.0295, 567 | 'fees': 0.0001 568 | } 569 | ] 570 | }; 571 | 572 | var todos = { 573 | txs: [ 574 | { 575 | vout: [ 576 | { 577 | scriptPubKey: { 578 | reqSigs: 1 579 | } 580 | } 581 | ] 582 | }, 583 | { 584 | vin: [ 585 | ], 586 | vout: [ 587 | { 588 | scriptPubKey: { 589 | reqSigs: 1 590 | } 591 | }, 592 | { 593 | scriptPubKey: { 594 | reqSigs: 1 595 | }, 596 | spentTs: 1440997099 597 | } 598 | ] 599 | }, 600 | { 601 | vin: [ 602 | ], 603 | vout: [ 604 | { 605 | scriptPubKey: { 606 | reqSigs: 1 607 | }, 608 | spentTs: 1440992946 609 | }, 610 | { 611 | scriptPubKey: { 612 | reqSigs: 1 613 | }, 614 | spentTs: 1440999118 615 | } 616 | ] 617 | } 618 | ] 619 | }; 620 | 621 | var req = { 622 | query: { 623 | block: '0000000000000afa0c3c0afd450c793a1e300ec84cbe9555166e06132f19a8f7' 624 | } 625 | }; 626 | 627 | var res = { 628 | jsonp: function(data) { 629 | _.merge(data, todos); 630 | should(data).eql(insight); 631 | done(); 632 | } 633 | }; 634 | 635 | transactions.list(req, res); 636 | }); 637 | it('by address', function(done) { 638 | 639 | var txinfos = [ 640 | { 641 | tx: { 642 | hex: '010000000125c46caa6d839435b43c20d6d48978e677841244b37a09f6f6cd29bfaf5b5eea010000006b483045022100f4d169783bef70e3943d2a617cce55d9fe4e33fc6f9880b8277265e2f619a97002201238648abcdf52960500664e969046d41755f7fc371971ebc78002fc418465a6012103acdcd31d51272403ce0829447e59e2ac9e08ed0bf92011cbf7420addf24534e6feffffff02a913dda5000000001976a9143583efb5e64a4668c6c54bb5fcc30af4417b4f2d88ac809fd500000000001976a9149713201957f42379e574d7c70d506ee49c2c8ad688ac49260800', 643 | hash: 'bb0ec3b96209fac9529570ea6f83a86af2cceedde4aaf2bfcc4796680d23f1c7', 644 | version: 1, 645 | inputs: [ 646 | { 647 | prevTxId: 'ea5e5bafbf29cdf6f6097ab344128477e67889d4d6203cb43594836daa6cc425', 648 | outputIndex: 1, 649 | sequence: 4294967294, 650 | script: '483045022100f4d169783bef70e3943d2a617cce55d9fe4e33fc6f9880b8277265e2f619a97002201238648abcdf52960500664e969046d41755f7fc371971ebc78002fc418465a6012103acdcd31d51272403ce0829447e59e2ac9e08ed0bf92011cbf7420addf24534e6', 651 | scriptAsm: '3045022100f4d169783bef70e3943d2a617cce55d9fe4e33fc6f9880b8277265e2f619a97002201238648abcdf52960500664e969046d41755f7fc371971ebc78002fc418465a601 03acdcd31d51272403ce0829447e59e2ac9e08ed0bf92011cbf7420addf24534e6', 652 | satoshis: 2796764565, 653 | address: 'msyjRQQ88MabQmyafpKCjBHUwuJ49tVjcb' 654 | } 655 | ], 656 | outputs: [ 657 | { 658 | satoshis: 2782729129, 659 | address: 'mkPvAKZ2rar6qeG3KjBtJHHMSP1wFZH7Er', 660 | script: '76a9143583efb5e64a4668c6c54bb5fcc30af4417b4f2d88ac', 661 | scriptAsm: 'OP_DUP OP_HASH160 3583efb5e64a4668c6c54bb5fcc30af4417b4f2d OP_EQUALVERIFY OP_CHECKSIG' 662 | }, 663 | { 664 | satoshis: 14000000, 665 | address: 'muHmEsjhjmATf9i3T9gHyeQoce9LXe2dWz', 666 | script: '76a9149713201957f42379e574d7c70d506ee49c2c8ad688ac', 667 | scriptAsm: 'OP_DUP OP_HASH160 9713201957f42379e574d7c70d506ee49c2c8ad6 OP_EQUALVERIFY OP_CHECKSIG' 668 | } 669 | ], 670 | inputSatoshis: 2796764565, 671 | outputSatoshis: 2796729129, 672 | feeSatoshis: 35436, 673 | locktime: 534089 674 | } 675 | }, 676 | { 677 | tx: { 678 | hex: '0100000001c7f1230d689647ccbff2aae4ddeeccf26aa8836fea709552c9fa0962b9c30ebb000000006a47304402201ee69281db6b95bb1aa3074059b67581635b719e8f64e4c2694db6ec56ad9447022011e91528996ea459b1fb2c0b59363fecbefe4bc2ca90f7b2382bdaa358f2d5640121034cc057b12a68ee79df998004b9a1341bbb18b17ea4939bebaa3bac001e940f24feffffff02bce0c9a4000000001976a91456e446bc3489543d8324c6d0271524c0bd0506dd88ac80a81201000000001976a914011d2963b619186a318f768dddfd98cd553912a088ac53260800', 679 | hash: '01f700df84c466f2a389440e5eeacdc47d04f380c39e5d19dce2ce91a11ecba3', 680 | version: 1, 681 | inputs: [ 682 | { 683 | prevTxId: 'bb0ec3b96209fac9529570ea6f83a86af2cceedde4aaf2bfcc4796680d23f1c7', 684 | outputIndex: 0, 685 | sequence: 4294967294, 686 | script: '47304402201ee69281db6b95bb1aa3074059b67581635b719e8f64e4c2694db6ec56ad9447022011e91528996ea459b1fb2c0b59363fecbefe4bc2ca90f7b2382bdaa358f2d5640121034cc057b12a68ee79df998004b9a1341bbb18b17ea4939bebaa3bac001e940f24', 687 | scriptAsm: '304402201ee69281db6b95bb1aa3074059b67581635b719e8f64e4c2694db6ec56ad9447022011e91528996ea459b1fb2c0b59363fecbefe4bc2ca90f7b2382bdaa358f2d56401 034cc057b12a68ee79df998004b9a1341bbb18b17ea4939bebaa3bac001e940f24', 688 | satoshis: 2782729129, 689 | address: 'mkPvAKZ2rar6qeG3KjBtJHHMSP1wFZH7Er' 690 | } 691 | ], 692 | outputs: [ 693 | { 694 | satoshis: 2764693692, 695 | address: 'moSPsU4p2C2gssiniJ1JNH4fB9xs633tLv', 696 | script: '76a91456e446bc3489543d8324c6d0271524c0bd0506dd88ac', 697 | scriptAsm: 'OP_DUP OP_HASH160 56e446bc3489543d8324c6d0271524c0bd0506dd OP_EQUALVERIFY OP_CHECKSIG' 698 | }, 699 | { 700 | satoshis: 18000000, 701 | scriptAsm: 'OP_DUP OP_HASH160 011d2963b619186a318f768dddfd98cd553912a0 OP_EQUALVERIFY OP_CHECKSIG', 702 | script: '76a914011d2963b619186a318f768dddfd98cd553912a088ac', 703 | address: 'mfcquSAitCkUKXaYRZTRZQDfUegnL3kDew', 704 | spentTxId: '71a9e60c0341c9c258367f1a6d4253276f16e207bf84f41ff7412d8958a81bed' 705 | } 706 | ], 707 | inputSatoshis: 2782729129, 708 | outputSatoshis: 2782693692, 709 | feeSatoshis: 35437, 710 | locktime: 534099 711 | } 712 | } 713 | ]; 714 | 715 | var historyResult = { 716 | totalCount: txinfos.length, 717 | items: txinfos 718 | }; 719 | 720 | txinfos[0].tx.blockHash = '00000000000001001aba15de213648f370607fb048288dd27b96f7e833a73520'; 721 | txinfos[0].tx.blockTimestamp = 1441068774; 722 | txinfos[0].tx.height = 534105; 723 | 724 | txinfos[1].tx.blockHash = '0000000000000a3acc1f7fe72917eb48bb319ed96c125a6dfcc0ba6acab3c4d0'; 725 | txinfos[1].tx.blockTimestamp = 1441072817; 726 | txinfos[1].tx.height = 534110; 727 | 728 | txinfos[0].tx.outputs[0].spentTxId = '01f700df84c466f2a389440e5eeacdc47d04f380c39e5d19dce2ce91a11ecba3'; 729 | txinfos[0].tx.outputs[0].spentIndex = 0; 730 | txinfos[0].tx.outputs[0].spentHeight = 199; 731 | 732 | txinfos[1].tx.outputs[0].spentTxId = '661194e5533a395ce9076f292b7e0fb28fe94cd8832a81b4aa0517ff58c1ddd2'; 733 | txinfos[1].tx.outputs[0].spentIndex = 0; 734 | txinfos[1].tx.outputs[0].spentHeight = 134; 735 | 736 | txinfos[1].tx.outputs[1].spentTxId = '71a9e60c0341c9c258367f1a6d4253276f16e207bf84f41ff7412d8958a81bed'; 737 | txinfos[1].tx.outputs[1].spentIndex = 0; 738 | txinfos[1].tx.outputs[1].spentHeight = 112; 739 | 740 | var node = { 741 | getAddressHistory: sinon.stub().callsArgWith(2, null, historyResult), 742 | services: { 743 | bitcoind: { 744 | height: 534223 745 | } 746 | }, 747 | network: 'testnet' 748 | }; 749 | 750 | var insight = { 751 | 'pagesTotal': 1, 752 | 'txs': [ 753 | { 754 | 'txid': 'bb0ec3b96209fac9529570ea6f83a86af2cceedde4aaf2bfcc4796680d23f1c7', 755 | 'version': 1, 756 | 'locktime': 534089, 757 | 'vin': [ 758 | { 759 | 'txid': 'ea5e5bafbf29cdf6f6097ab344128477e67889d4d6203cb43594836daa6cc425', 760 | 'vout': 1, 761 | 'scriptSig': { 762 | 'asm': '3045022100f4d169783bef70e3943d2a617cce55d9fe4e33fc6f9880b8277265e2f619a97002201238648abcdf52960500664e969046d41755f7fc371971ebc78002fc418465a601 03acdcd31d51272403ce0829447e59e2ac9e08ed0bf92011cbf7420addf24534e6', 763 | 'hex': '483045022100f4d169783bef70e3943d2a617cce55d9fe4e33fc6f9880b8277265e2f619a97002201238648abcdf52960500664e969046d41755f7fc371971ebc78002fc418465a6012103acdcd31d51272403ce0829447e59e2ac9e08ed0bf92011cbf7420addf24534e6' 764 | }, 765 | 'sequence': 4294967294, 766 | 'n': 0, 767 | 'addr': 'msyjRQQ88MabQmyafpKCjBHUwuJ49tVjcb', 768 | 'valueSat': 2796764565, 769 | 'value': 27.96764565, 770 | 'doubleSpentTxID': null 771 | } 772 | ], 773 | 'vout': [ 774 | { 775 | 'value': '27.82729129', 776 | 'n': 0, 777 | 'scriptPubKey': { 778 | 'asm': 'OP_DUP OP_HASH160 3583efb5e64a4668c6c54bb5fcc30af4417b4f2d OP_EQUALVERIFY OP_CHECKSIG', 779 | 'hex': '76a9143583efb5e64a4668c6c54bb5fcc30af4417b4f2d88ac', 780 | 'reqSigs': 1, 781 | 'type': 'pubkeyhash', 782 | 'addresses': [ 783 | 'mkPvAKZ2rar6qeG3KjBtJHHMSP1wFZH7Er' 784 | ] 785 | }, 786 | 'spentTxId': '01f700df84c466f2a389440e5eeacdc47d04f380c39e5d19dce2ce91a11ecba3', 787 | 'spentIndex': 0, 788 | 'spentHeight': 199, 789 | 'spentTs': 1441072817 790 | }, 791 | { 792 | 'value': '0.14000000', 793 | 'n': 1, 794 | 'scriptPubKey': { 795 | 'asm': 'OP_DUP OP_HASH160 9713201957f42379e574d7c70d506ee49c2c8ad6 OP_EQUALVERIFY OP_CHECKSIG', 796 | 'hex': '76a9149713201957f42379e574d7c70d506ee49c2c8ad688ac', 797 | 'reqSigs': 1, 798 | 'type': 'pubkeyhash', 799 | 'addresses': [ 800 | 'muHmEsjhjmATf9i3T9gHyeQoce9LXe2dWz' 801 | ] 802 | }, 803 | 'spentTxId': null, 804 | 'spentIndex': null, 805 | 'spentHeight': null 806 | } 807 | ], 808 | 'blockhash': '00000000000001001aba15de213648f370607fb048288dd27b96f7e833a73520', 809 | 'blockheight': 534105, 810 | 'confirmations': 119, 811 | 'time': 1441068774, 812 | 'blocktime': 1441068774, 813 | 'valueOut': 27.96729129, 814 | 'size': 226, 815 | 'valueIn': 27.96764565, 816 | 'fees': 0.00035436 817 | }, 818 | { 819 | 'txid': '01f700df84c466f2a389440e5eeacdc47d04f380c39e5d19dce2ce91a11ecba3', 820 | 'version': 1, 821 | 'locktime': 534099, 822 | 'vin': [ 823 | { 824 | 'txid': 'bb0ec3b96209fac9529570ea6f83a86af2cceedde4aaf2bfcc4796680d23f1c7', 825 | 'vout': 0, 826 | 'scriptSig': { 827 | 'asm': '304402201ee69281db6b95bb1aa3074059b67581635b719e8f64e4c2694db6ec56ad9447022011e91528996ea459b1fb2c0b59363fecbefe4bc2ca90f7b2382bdaa358f2d56401 034cc057b12a68ee79df998004b9a1341bbb18b17ea4939bebaa3bac001e940f24', 828 | 'hex': '47304402201ee69281db6b95bb1aa3074059b67581635b719e8f64e4c2694db6ec56ad9447022011e91528996ea459b1fb2c0b59363fecbefe4bc2ca90f7b2382bdaa358f2d5640121034cc057b12a68ee79df998004b9a1341bbb18b17ea4939bebaa3bac001e940f24' 829 | }, 830 | 'sequence': 4294967294, 831 | 'n': 0, 832 | 'addr': 'mkPvAKZ2rar6qeG3KjBtJHHMSP1wFZH7Er', 833 | 'valueSat': 2782729129, 834 | 'value': 27.82729129, 835 | 'doubleSpentTxID': null 836 | } 837 | ], 838 | 'vout': [ 839 | { 840 | 'value': '27.64693692', 841 | 'n': 0, 842 | 'scriptPubKey': { 843 | 'asm': 'OP_DUP OP_HASH160 56e446bc3489543d8324c6d0271524c0bd0506dd OP_EQUALVERIFY OP_CHECKSIG', 844 | 'hex': '76a91456e446bc3489543d8324c6d0271524c0bd0506dd88ac', 845 | 'reqSigs': 1, 846 | 'type': 'pubkeyhash', 847 | 'addresses': [ 848 | 'moSPsU4p2C2gssiniJ1JNH4fB9xs633tLv' 849 | ] 850 | }, 851 | 'spentTxId': '661194e5533a395ce9076f292b7e0fb28fe94cd8832a81b4aa0517ff58c1ddd2', 852 | 'spentIndex': 0, 853 | 'spentHeight': 134, 854 | 'spentTs': 1441077236 855 | }, 856 | { 857 | 'value': '0.18000000', 858 | 'n': 1, 859 | 'scriptPubKey': { 860 | 'asm': 'OP_DUP OP_HASH160 011d2963b619186a318f768dddfd98cd553912a0 OP_EQUALVERIFY OP_CHECKSIG', 861 | 'hex': '76a914011d2963b619186a318f768dddfd98cd553912a088ac', 862 | 'reqSigs': 1, 863 | 'type': 'pubkeyhash', 864 | 'addresses': [ 865 | 'mfcquSAitCkUKXaYRZTRZQDfUegnL3kDew' 866 | ] 867 | }, 868 | 'spentTxId': '71a9e60c0341c9c258367f1a6d4253276f16e207bf84f41ff7412d8958a81bed', 869 | 'spentIndex': 0, 870 | 'spentHeight': 112, 871 | 'spentTs': 1441069523 872 | } 873 | ], 874 | 'blockhash': '0000000000000a3acc1f7fe72917eb48bb319ed96c125a6dfcc0ba6acab3c4d0', 875 | 'blockheight': 534110, 876 | 'confirmations': 114, 877 | 'time': 1441072817, 878 | 'blocktime': 1441072817, 879 | 'valueOut': 27.82693692, 880 | 'size': 225, 881 | 'valueIn': 27.82729129, 882 | 'fees': 0.00035437 883 | } 884 | ] 885 | }; 886 | 887 | var todos = { 888 | 'txs': [ 889 | { 890 | 'vin': [ 891 | ], 892 | 'vout': [ 893 | { 894 | 'scriptPubKey': { 895 | 'reqSigs': 1 896 | }, 897 | 'spentTs': 1441072817 898 | }, 899 | { 900 | 'scriptPubKey': { 901 | 'reqSigs': 1 902 | } 903 | } 904 | ] 905 | }, 906 | { 907 | 'vin': [ 908 | ], 909 | 'vout': [ 910 | { 911 | 'scriptPubKey': { 912 | 'reqSigs': 1 913 | }, 914 | 'spentTs': 1441077236 915 | }, 916 | { 917 | 'scriptPubKey': { 918 | 'reqSigs': 1 919 | }, 920 | 'spentTs': 1441069523 921 | } 922 | ] 923 | } 924 | ] 925 | }; 926 | 927 | var req = { 928 | query: { 929 | address: 'mkPvAKZ2rar6qeG3KjBtJHHMSP1wFZH7Er' 930 | } 931 | }; 932 | 933 | var res = { 934 | jsonp: function(data) { 935 | var merged = _.merge(data, todos); 936 | should(merged).eql(insight); 937 | done(); 938 | } 939 | }; 940 | 941 | var transactions = new TxController(node); 942 | transactions.list(req, res); 943 | }); 944 | }); 945 | 946 | describe('/rawtx/:txid', function() { 947 | it('should give the correct data', function(done) { 948 | var hex = '01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff2303d6250800feb0aae355fe263600000963676d696e6572343208ae5800000000000000ffffffff01c018824a000000001976a91468bedce8982d25c3b6b03f6238cbad00378b8ead88ac00000000'; 949 | 950 | var node = { 951 | getTransaction: sinon.stub().callsArgWith(1, null, bitcore.Transaction().fromBuffer(new Buffer(hex, 'hex'))) 952 | }; 953 | 954 | var transactions = new TxController(node); 955 | 956 | var res = {}; 957 | var req = { 958 | params: { 959 | txid: txid 960 | } 961 | }; 962 | var next = function() { 963 | should(req.rawTransaction.rawtx).eql(hex); 964 | done(); 965 | }; 966 | var txid = '25a988e54b02e0e5df146a0f8fa7b9db56210533a9f04bdfda5f4ceb6f77aadd'; 967 | transactions.rawTransaction(req, res, next); 968 | }); 969 | }); 970 | 971 | describe('#transformInvTransaction', function() { 972 | it('should give the correct data', function() { 973 | var insight = { 974 | 'txid': 'a15a7c257af596704390d345ff3ea2eed4cd02ce8bfb8afb700bff82257e49fb', 975 | 'valueOut': 0.02038504, 976 | 'vout': [ 977 | { 978 | '3DQYCLG6rZdtV2Xw8y4YtozZjNHYoKsLuo': 45000 979 | }, 980 | { 981 | '12WvZmssxT85f81dD6wcmWznxbnFkEpNMS': 1993504 982 | } 983 | ], 984 | 'isRBF': false 985 | }; 986 | 987 | var rawTx = '01000000011760bc271a397bfb65b7506d430d96ebb1faff467ed957516238a9670e806a86010000006b483045022100f0056ae68a34cdb4194d424bd727c18f82653bca2a198e0d55ab6b4ee88bbdb902202a5745af4f72a5dbdca1e3d683af4667728a8b20e8001e0f8308a4d329ce3f96012102f3af6e66b61c9d99c74d9a9c3c1bec014a8c05d28bf339c8f5f395b5ce319e7dffffffff02c8af00000000000017a9148083b541ea15f1d18c5ca5e1fd47f9035cce24ed87206b1e00000000001976a91410a0e70cd91a45e0e6e409e227ab285bd61592b188ac00000000'; 988 | var tx = bitcore.Transaction().fromBuffer(new Buffer(rawTx, 'hex')); 989 | 990 | var node = { 991 | network: bitcore.Networks.livenet 992 | }; 993 | 994 | var transactions = new TxController(node); 995 | 996 | var result = transactions.transformInvTransaction(tx); 997 | should(result).eql(insight); 998 | }); 999 | it('will not include null values in vout array', function() { 1000 | var insight = { 1001 | 'txid': '716d54157c31e52c820494c6c2b8af1b64352049f4dcc80632aa15742a7f82c4', 1002 | 'valueOut': 12.5002, 1003 | 'vout': [ 1004 | { 1005 | 'n4eY3qiP9pi32MWC6FcJFHciSsfNiYFYgR': 12.5002 * 1e8 1006 | } 1007 | ], 1008 | 'isRBF': false 1009 | }; 1010 | 1011 | var rawTx = '01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0403ebc108ffffffff04a0ca814a000000001976a914fdb9fb622b0db8d9121475a983288a0876f4de4888ac0000000000000000226a200000000000000000000000000000000000000000000000000000ffff0000000000000000000000001b6a1976a914fdb9fb622b0db8d9121475a983288a0876f4de4888ac0000000000000000326a303a791c8e85200500d89769b4f958e4db6b3ec388ddaa30233c4517d942d440c24ae903bff40d97ca06465fcf2714000000000000'; 1012 | var tx = bitcore.Transaction().fromBuffer(new Buffer(rawTx, 'hex')); 1013 | 1014 | var node = { 1015 | network: bitcore.Networks.testnet 1016 | }; 1017 | 1018 | var transactions = new TxController(node); 1019 | 1020 | var result = transactions.transformInvTransaction(tx); 1021 | should(result).eql(insight); 1022 | }); 1023 | it('should detect RBF txs', function() { 1024 | var testCases = [ 1025 | { 1026 | rawTx: '01000000017fa897c3556271c34cb28c03c196c2d912093264c9d293cb4980a2635474467d010000000f5355540b6f93598893578893588851ffffffff01501e0000000000001976a914aa2482ce71d219018ef334f6cc551ee88abd920888ac00000000', 1027 | expected: false, 1028 | }, { 1029 | rawTx: '01000000017fa897c3556271c34cb28c03c196c2d912093264c9d293cb4980a2635474467d010000000f5355540b6f935988935788935888510000000001501e0000000000001976a914aa2482ce71d219018ef334f6cc551ee88abd920888ac00000000', 1030 | expected: true, 1031 | }, 1032 | ]; 1033 | 1034 | var node = { 1035 | network: bitcore.Networks.livenet 1036 | }; 1037 | 1038 | var transactions = new TxController(node); 1039 | 1040 | _.each(testCases, function(tc) { 1041 | var tx = bitcore.Transaction().fromBuffer(new Buffer(tc.rawTx, 'hex')); 1042 | var result = transactions.transformInvTransaction(tx); 1043 | should.exist(result.isRBF); 1044 | result.isRBF.should.equal(tc.expected); 1045 | }); 1046 | }); 1047 | 1048 | }); 1049 | }); 1050 | -------------------------------------------------------------------------------- /test/utils.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var sinon = require('sinon'); 4 | var should = require('should'); 5 | var UtilsController = require('../lib/utils'); 6 | 7 | describe('Utils', function() { 8 | describe('/utils/estimatefee', function() { 9 | it('should give the correct fee', function(done) { 10 | var node = { 11 | services: { 12 | bitcoind: { 13 | estimateFee: function(blocks, callback) { 14 | switch(blocks) { 15 | case 1: 16 | return callback(null, 1000 / 1e8); 17 | case 3: 18 | return callback(null, 3000 / 1e8); 19 | } 20 | } 21 | } 22 | } 23 | }; 24 | var utils = new UtilsController(node); 25 | 26 | var req = { 27 | query: { 28 | nbBlocks: '1, 3' 29 | } 30 | }; 31 | 32 | var res = { 33 | jsonp: function(fees) { 34 | should(fees).eql({1: 0.00001, 3: 0.00003}); 35 | done(); 36 | } 37 | }; 38 | utils.estimateFee(req, res); 39 | }); 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | accepts@~1.3.3: 6 | version "1.3.3" 7 | resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.3.tgz#c3ca7434938648c3e0d9c1e328dd68b622c284ca" 8 | dependencies: 9 | mime-types "~2.1.11" 10 | negotiator "0.6.1" 11 | 12 | ajv@^4.9.1: 13 | version "4.11.5" 14 | resolved "https://registry.yarnpkg.com/ajv/-/ajv-4.11.5.tgz#b6ee74657b993a01dce44b7944d56f485828d5bd" 15 | dependencies: 16 | co "^4.6.0" 17 | json-stable-stringify "^1.0.1" 18 | 19 | asn1@~0.2.3: 20 | version "0.2.3" 21 | resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.3.tgz#dac8787713c9966849fc8180777ebe9c1ddf3b86" 22 | 23 | assert-plus@1.0.0, assert-plus@^1.0.0: 24 | version "1.0.0" 25 | resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" 26 | 27 | assert-plus@^0.2.0: 28 | version "0.2.0" 29 | resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-0.2.0.tgz#d74e1b87e7affc0db8aadb7021f3fe48101ab234" 30 | 31 | assertion-error@^1.0.1: 32 | version "1.0.2" 33 | resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.0.2.tgz#13ca515d86206da0bac66e834dd397d87581094c" 34 | 35 | async@*: 36 | version "2.1.5" 37 | resolved "https://registry.yarnpkg.com/async/-/async-2.1.5.tgz#e587c68580994ac67fc56ff86d3ac56bdbe810bc" 38 | dependencies: 39 | lodash "^4.14.0" 40 | 41 | asynckit@^0.4.0: 42 | version "0.4.0" 43 | resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" 44 | 45 | aws-sign2@~0.6.0: 46 | version "0.6.0" 47 | resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.6.0.tgz#14342dd38dbcc94d0e5b87d763cd63612c0e794f" 48 | 49 | aws4@^1.2.1: 50 | version "1.6.0" 51 | resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.6.0.tgz#83ef5ca860b2b32e4a0deedee8c771b9db57471e" 52 | 53 | basic-auth@~1.1.0: 54 | version "1.1.0" 55 | resolved "https://registry.yarnpkg.com/basic-auth/-/basic-auth-1.1.0.tgz#45221ee429f7ee1e5035be3f51533f1cdfd29884" 56 | 57 | bcrypt-pbkdf@^1.0.0: 58 | version "1.0.1" 59 | resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz#63bc5dcb61331b92bc05fd528953c33462a06f8d" 60 | dependencies: 61 | tweetnacl "^0.14.3" 62 | 63 | bn.js@=2.0.4, bn.js@^2.0.0: 64 | version "2.0.4" 65 | resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-2.0.4.tgz#220a7cd677f7f1bfa93627ff4193776fe7819480" 66 | 67 | body-parser@^1.13.3: 68 | version "1.17.1" 69 | resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.17.1.tgz#75b3bc98ddd6e7e0d8ffe750dfaca5c66993fa47" 70 | dependencies: 71 | bytes "2.4.0" 72 | content-type "~1.0.2" 73 | debug "2.6.1" 74 | depd "~1.1.0" 75 | http-errors "~1.6.1" 76 | iconv-lite "0.4.15" 77 | on-finished "~2.3.0" 78 | qs "6.4.0" 79 | raw-body "~2.2.0" 80 | type-is "~1.6.14" 81 | 82 | boom@2.x.x: 83 | version "2.10.1" 84 | resolved "https://registry.yarnpkg.com/boom/-/boom-2.10.1.tgz#39c8918ceff5799f83f9492a848f625add0c766f" 85 | dependencies: 86 | hoek "2.x.x" 87 | 88 | brorand@^1.0.1: 89 | version "1.1.0" 90 | resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" 91 | 92 | bs58@=2.0.0: 93 | version "2.0.0" 94 | resolved "https://registry.yarnpkg.com/bs58/-/bs58-2.0.0.tgz#72b713bed223a0ac518bbda0e3ce3f4817f39eb5" 95 | 96 | buffer-compare@=1.0.0: 97 | version "1.0.0" 98 | resolved "https://registry.yarnpkg.com/buffer-compare/-/buffer-compare-1.0.0.tgz#acaa7a966e98eee9fae14b31c39a5f158fb3c4a2" 99 | 100 | bytes@2.3.0: 101 | version "2.3.0" 102 | resolved "https://registry.yarnpkg.com/bytes/-/bytes-2.3.0.tgz#d5b680a165b6201739acb611542aabc2d8ceb070" 103 | 104 | bytes@2.4.0: 105 | version "2.4.0" 106 | resolved "https://registry.yarnpkg.com/bytes/-/bytes-2.4.0.tgz#7d97196f9d5baf7f6935e25985549edd2a6c2339" 107 | 108 | caseless@~0.12.0: 109 | version "0.12.0" 110 | resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" 111 | 112 | chai@^3.5.0: 113 | version "3.5.0" 114 | resolved "https://registry.yarnpkg.com/chai/-/chai-3.5.0.tgz#4d02637b067fe958bdbfdd3a40ec56fef7373247" 115 | dependencies: 116 | assertion-error "^1.0.1" 117 | deep-eql "^0.1.3" 118 | type-detect "^1.0.0" 119 | 120 | co@^4.6.0: 121 | version "4.6.0" 122 | resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" 123 | 124 | combined-stream@^1.0.5, combined-stream@~1.0.5: 125 | version "1.0.5" 126 | resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.5.tgz#938370a57b4a51dea2c77c15d5c5fdf895164009" 127 | dependencies: 128 | delayed-stream "~1.0.0" 129 | 130 | commander@0.6.1: 131 | version "0.6.1" 132 | resolved "https://registry.yarnpkg.com/commander/-/commander-0.6.1.tgz#fa68a14f6a945d54dbbe50d8cdb3320e9e3b1a06" 133 | 134 | commander@2.3.0: 135 | version "2.3.0" 136 | resolved "https://registry.yarnpkg.com/commander/-/commander-2.3.0.tgz#fd430e889832ec353b9acd1de217c11cb3eef873" 137 | 138 | compressible@~2.0.8: 139 | version "2.0.9" 140 | resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.9.tgz#6daab4e2b599c2770dd9e21e7a891b1c5a755425" 141 | dependencies: 142 | mime-db ">= 1.24.0 < 2" 143 | 144 | compression@^1.6.1: 145 | version "1.6.2" 146 | resolved "https://registry.yarnpkg.com/compression/-/compression-1.6.2.tgz#cceb121ecc9d09c52d7ad0c3350ea93ddd402bc3" 147 | dependencies: 148 | accepts "~1.3.3" 149 | bytes "2.3.0" 150 | compressible "~2.0.8" 151 | debug "~2.2.0" 152 | on-headers "~1.0.1" 153 | vary "~1.1.0" 154 | 155 | content-type@~1.0.2: 156 | version "1.0.2" 157 | resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.2.tgz#b7d113aee7a8dd27bd21133c4dc2529df1721eed" 158 | 159 | cryptiles@2.x.x: 160 | version "2.0.5" 161 | resolved "https://registry.yarnpkg.com/cryptiles/-/cryptiles-2.0.5.tgz#3bdfecdc608147c1c67202fa291e7dca59eaa3b8" 162 | dependencies: 163 | boom "2.x.x" 164 | 165 | dashdash@^1.12.0: 166 | version "1.14.1" 167 | resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" 168 | dependencies: 169 | assert-plus "^1.0.0" 170 | 171 | debug@2.2.0, debug@~2.2.0: 172 | version "2.2.0" 173 | resolved "https://registry.yarnpkg.com/debug/-/debug-2.2.0.tgz#f87057e995b1a1f6ae6a4960664137bc56f039da" 174 | dependencies: 175 | ms "0.7.1" 176 | 177 | debug@2.6.1: 178 | version "2.6.1" 179 | resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.1.tgz#79855090ba2c4e3115cc7d8769491d58f0491351" 180 | dependencies: 181 | ms "0.7.2" 182 | 183 | deep-eql@^0.1.3: 184 | version "0.1.3" 185 | resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-0.1.3.tgz#ef558acab8de25206cd713906d74e56930eb69f2" 186 | dependencies: 187 | type-detect "0.1.1" 188 | 189 | delayed-stream@~1.0.0: 190 | version "1.0.0" 191 | resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" 192 | 193 | depd@1.1.0, depd@~1.1.0: 194 | version "1.1.0" 195 | resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.0.tgz#e1bd82c6aab6ced965b97b88b17ed3e528ca18c3" 196 | 197 | diff@1.4.0: 198 | version "1.4.0" 199 | resolved "https://registry.yarnpkg.com/diff/-/diff-1.4.0.tgz#7f28d2eb9ee7b15a97efd89ce63dcfdaa3ccbabf" 200 | 201 | ecc-jsbn@~0.1.1: 202 | version "0.1.1" 203 | resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz#0fc73a9ed5f0d53c38193398523ef7e543777505" 204 | dependencies: 205 | jsbn "~0.1.0" 206 | 207 | ee-first@1.1.1: 208 | version "1.1.1" 209 | resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" 210 | 211 | elliptic@=3.0.3: 212 | version "3.0.3" 213 | resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-3.0.3.tgz#865c9b420bfbe55006b9f969f97a0d2c44966595" 214 | dependencies: 215 | bn.js "^2.0.0" 216 | brorand "^1.0.1" 217 | hash.js "^1.0.0" 218 | inherits "^2.0.1" 219 | 220 | escape-string-regexp@1.0.2: 221 | version "1.0.2" 222 | resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.2.tgz#4dbc2fe674e71949caf3fb2695ce7f2dc1d9a8d1" 223 | 224 | extend@~3.0.0: 225 | version "3.0.0" 226 | resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.0.tgz#5a474353b9f3353ddd8176dfd37b91c83a46f1d4" 227 | 228 | extsprintf@1.0.2: 229 | version "1.0.2" 230 | resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.0.2.tgz#e1080e0658e300b06294990cc70e1502235fd550" 231 | 232 | fill-keys@^1.0.2: 233 | version "1.0.2" 234 | resolved "https://registry.yarnpkg.com/fill-keys/-/fill-keys-1.0.2.tgz#9a8fa36f4e8ad634e3bf6b4f3c8882551452eb20" 235 | dependencies: 236 | is-object "~1.0.1" 237 | merge-descriptors "~1.0.0" 238 | 239 | forever-agent@~0.6.1: 240 | version "0.6.1" 241 | resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" 242 | 243 | form-data@~2.1.1: 244 | version "2.1.2" 245 | resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.1.2.tgz#89c3534008b97eada4cbb157d58f6f5df025eae4" 246 | dependencies: 247 | asynckit "^0.4.0" 248 | combined-stream "^1.0.5" 249 | mime-types "^2.1.12" 250 | 251 | formatio@1.1.1: 252 | version "1.1.1" 253 | resolved "https://registry.yarnpkg.com/formatio/-/formatio-1.1.1.tgz#5ed3ccd636551097383465d996199100e86161e9" 254 | dependencies: 255 | samsam "~1.1" 256 | 257 | getpass@^0.1.1: 258 | version "0.1.6" 259 | resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.6.tgz#283ffd9fc1256840875311c1b60e8c40187110e6" 260 | dependencies: 261 | assert-plus "^1.0.0" 262 | 263 | glob@3.2.11: 264 | version "3.2.11" 265 | resolved "https://registry.yarnpkg.com/glob/-/glob-3.2.11.tgz#4a973f635b9190f715d10987d5c00fd2815ebe3d" 266 | dependencies: 267 | inherits "2" 268 | minimatch "0.3" 269 | 270 | growl@1.9.2: 271 | version "1.9.2" 272 | resolved "https://registry.yarnpkg.com/growl/-/growl-1.9.2.tgz#0ea7743715db8d8de2c5ede1775e1b45ac85c02f" 273 | 274 | har-schema@^1.0.5: 275 | version "1.0.5" 276 | resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-1.0.5.tgz#d263135f43307c02c602afc8fe95970c0151369e" 277 | 278 | har-validator@~4.2.1: 279 | version "4.2.1" 280 | resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-4.2.1.tgz#33481d0f1bbff600dd203d75812a6a5fba002e2a" 281 | dependencies: 282 | ajv "^4.9.1" 283 | har-schema "^1.0.5" 284 | 285 | hash.js@^1.0.0: 286 | version "1.0.3" 287 | resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.0.3.tgz#1332ff00156c0a0ffdd8236013d07b77a0451573" 288 | dependencies: 289 | inherits "^2.0.1" 290 | 291 | hawk@~3.1.3: 292 | version "3.1.3" 293 | resolved "https://registry.yarnpkg.com/hawk/-/hawk-3.1.3.tgz#078444bd7c1640b0fe540d2c9b73d59678e8e1c4" 294 | dependencies: 295 | boom "2.x.x" 296 | cryptiles "2.x.x" 297 | hoek "2.x.x" 298 | sntp "1.x.x" 299 | 300 | hoek@2.x.x: 301 | version "2.16.3" 302 | resolved "https://registry.yarnpkg.com/hoek/-/hoek-2.16.3.tgz#20bb7403d3cea398e91dc4710a8ff1b8274a25ed" 303 | 304 | http-errors@~1.6.1: 305 | version "1.6.1" 306 | resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.1.tgz#5f8b8ed98aca545656bf572997387f904a722257" 307 | dependencies: 308 | depd "1.1.0" 309 | inherits "2.0.3" 310 | setprototypeof "1.0.3" 311 | statuses ">= 1.3.1 < 2" 312 | 313 | http-signature@~1.1.0: 314 | version "1.1.1" 315 | resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.1.1.tgz#df72e267066cd0ac67fb76adf8e134a8fbcf91bf" 316 | dependencies: 317 | assert-plus "^0.2.0" 318 | jsprim "^1.2.2" 319 | sshpk "^1.7.0" 320 | 321 | iconv-lite@0.4.15: 322 | version "0.4.15" 323 | resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.15.tgz#fe265a218ac6a57cfe854927e9d04c19825eddeb" 324 | 325 | inherits@2, inherits@2.0.3: 326 | version "2.0.3" 327 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" 328 | 329 | inherits@2.0.1, inherits@=2.0.1, inherits@^2.0.1: 330 | version "2.0.1" 331 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1" 332 | 333 | is-object@~1.0.1: 334 | version "1.0.1" 335 | resolved "https://registry.yarnpkg.com/is-object/-/is-object-1.0.1.tgz#8952688c5ec2ffd6b03ecc85e769e02903083470" 336 | 337 | is-typedarray@~1.0.0: 338 | version "1.0.0" 339 | resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" 340 | 341 | isstream@~0.1.2: 342 | version "0.1.2" 343 | resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" 344 | 345 | jade@0.26.3: 346 | version "0.26.3" 347 | resolved "https://registry.yarnpkg.com/jade/-/jade-0.26.3.tgz#8f10d7977d8d79f2f6ff862a81b0513ccb25686c" 348 | dependencies: 349 | commander "0.6.1" 350 | mkdirp "0.3.0" 351 | 352 | jodid25519@^1.0.0: 353 | version "1.0.2" 354 | resolved "https://registry.yarnpkg.com/jodid25519/-/jodid25519-1.0.2.tgz#06d4912255093419477d425633606e0e90782967" 355 | dependencies: 356 | jsbn "~0.1.0" 357 | 358 | jsbn@~0.1.0: 359 | version "0.1.1" 360 | resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" 361 | 362 | json-schema@0.2.3: 363 | version "0.2.3" 364 | resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" 365 | 366 | json-stable-stringify@^1.0.1: 367 | version "1.0.1" 368 | resolved "https://registry.yarnpkg.com/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz#9a759d39c5f2ff503fd5300646ed445f88c4f9af" 369 | dependencies: 370 | jsonify "~0.0.0" 371 | 372 | json-stringify-safe@~5.0.1: 373 | version "5.0.1" 374 | resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" 375 | 376 | jsonify@~0.0.0: 377 | version "0.0.0" 378 | resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73" 379 | 380 | jsprim@^1.2.2: 381 | version "1.4.0" 382 | resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.0.tgz#a3b87e40298d8c380552d8cc7628a0bb95a22918" 383 | dependencies: 384 | assert-plus "1.0.0" 385 | extsprintf "1.0.2" 386 | json-schema "0.2.3" 387 | verror "1.3.6" 388 | 389 | litecore-lib@^0.13.19: 390 | version "0.13.21" 391 | resolved "https://registry.yarnpkg.com/litecore-lib/-/litecore-lib-0.13.21.tgz#a56bfca333b641c9cffb3d3e266de83132ab0697" 392 | dependencies: 393 | bn.js "=2.0.4" 394 | bs58 "=2.0.0" 395 | buffer-compare "=1.0.0" 396 | elliptic "=3.0.3" 397 | inherits "=2.0.1" 398 | lodash "=3.10.1" 399 | scryptsy "=2.0.0" 400 | 401 | litecore-message@^1.0.4: 402 | version "1.0.4" 403 | resolved "https://registry.yarnpkg.com/litecore-message/-/litecore-message-1.0.4.tgz#8998a0dd844ddd7a6e8a50ce31822ac6b6af6a1a" 404 | dependencies: 405 | litecore-lib "^0.13.19" 406 | 407 | lodash@=3.10.1: 408 | version "3.10.1" 409 | resolved "https://registry.yarnpkg.com/lodash/-/lodash-3.10.1.tgz#5bf45e8e49ba4189e17d482789dfd15bd140b7b6" 410 | 411 | lodash@^2.4.1: 412 | version "2.4.2" 413 | resolved "https://registry.yarnpkg.com/lodash/-/lodash-2.4.2.tgz#fadd834b9683073da179b3eae6d9c0d15053f73e" 414 | 415 | lodash@^4.14.0: 416 | version "4.17.4" 417 | resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae" 418 | 419 | lolex@1.3.2: 420 | version "1.3.2" 421 | resolved "https://registry.yarnpkg.com/lolex/-/lolex-1.3.2.tgz#7c3da62ffcb30f0f5a80a2566ca24e45d8a01f31" 422 | 423 | lru-cache@2: 424 | version "2.7.3" 425 | resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-2.7.3.tgz#6d4524e8b955f95d4f5b58851ce21dd72fb4e952" 426 | 427 | lru-cache@^4.0.1: 428 | version "4.0.2" 429 | resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.0.2.tgz#1d17679c069cda5d040991a09dbc2c0db377e55e" 430 | dependencies: 431 | pseudomap "^1.0.1" 432 | yallist "^2.0.0" 433 | 434 | media-typer@0.3.0: 435 | version "0.3.0" 436 | resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" 437 | 438 | merge-descriptors@~1.0.0: 439 | version "1.0.1" 440 | resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" 441 | 442 | "mime-db@>= 1.24.0 < 2": 443 | version "1.27.0" 444 | resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.27.0.tgz#820f572296bbd20ec25ed55e5b5de869e5436eb1" 445 | 446 | mime-db@~1.26.0: 447 | version "1.26.0" 448 | resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.26.0.tgz#eaffcd0e4fc6935cf8134da246e2e6c35305adff" 449 | 450 | mime-types@^2.1.12, mime-types@~2.1.11, mime-types@~2.1.13, mime-types@~2.1.7: 451 | version "2.1.14" 452 | resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.14.tgz#f7ef7d97583fcaf3b7d282b6f8b5679dab1e94ee" 453 | dependencies: 454 | mime-db "~1.26.0" 455 | 456 | minimatch@0.3: 457 | version "0.3.0" 458 | resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-0.3.0.tgz#275d8edaac4f1bb3326472089e7949c8394699dd" 459 | dependencies: 460 | lru-cache "2" 461 | sigmund "~1.0.0" 462 | 463 | minimist@0.0.8: 464 | version "0.0.8" 465 | resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" 466 | 467 | mkdirp@0.3.0: 468 | version "0.3.0" 469 | resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.3.0.tgz#1bbf5ab1ba827af23575143490426455f481fe1e" 470 | 471 | mkdirp@0.5.1: 472 | version "0.5.1" 473 | resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" 474 | dependencies: 475 | minimist "0.0.8" 476 | 477 | mocha@^2.4.5: 478 | version "2.5.3" 479 | resolved "https://registry.yarnpkg.com/mocha/-/mocha-2.5.3.tgz#161be5bdeb496771eb9b35745050b622b5aefc58" 480 | dependencies: 481 | commander "2.3.0" 482 | debug "2.2.0" 483 | diff "1.4.0" 484 | escape-string-regexp "1.0.2" 485 | glob "3.2.11" 486 | growl "1.9.2" 487 | jade "0.26.3" 488 | mkdirp "0.5.1" 489 | supports-color "1.2.0" 490 | to-iso-string "0.0.2" 491 | 492 | module-not-found-error@^1.0.0: 493 | version "1.0.1" 494 | resolved "https://registry.yarnpkg.com/module-not-found-error/-/module-not-found-error-1.0.1.tgz#cf8b4ff4f29640674d6cdd02b0e3bc523c2bbdc0" 495 | 496 | morgan@^1.7.0: 497 | version "1.8.1" 498 | resolved "https://registry.yarnpkg.com/morgan/-/morgan-1.8.1.tgz#f93023d3887bd27b78dfd6023cea7892ee27a4b1" 499 | dependencies: 500 | basic-auth "~1.1.0" 501 | debug "2.6.1" 502 | depd "~1.1.0" 503 | on-finished "~2.3.0" 504 | on-headers "~1.0.1" 505 | 506 | ms@0.7.1: 507 | version "0.7.1" 508 | resolved "https://registry.yarnpkg.com/ms/-/ms-0.7.1.tgz#9cd13c03adbff25b65effde7ce864ee952017098" 509 | 510 | ms@0.7.2: 511 | version "0.7.2" 512 | resolved "https://registry.yarnpkg.com/ms/-/ms-0.7.2.tgz#ae25cf2512b3885a1d95d7f037868d8431124765" 513 | 514 | negotiator@0.6.1: 515 | version "0.6.1" 516 | resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.1.tgz#2b327184e8992101177b28563fb5e7102acd0ca9" 517 | 518 | oauth-sign@~0.8.1: 519 | version "0.8.2" 520 | resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43" 521 | 522 | on-finished@~2.3.0: 523 | version "2.3.0" 524 | resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" 525 | dependencies: 526 | ee-first "1.1.1" 527 | 528 | on-headers@~1.0.1: 529 | version "1.0.1" 530 | resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.1.tgz#928f5d0f470d49342651ea6794b0857c100693f7" 531 | 532 | performance-now@^0.2.0: 533 | version "0.2.0" 534 | resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-0.2.0.tgz#33ef30c5c77d4ea21c5a53869d91b56d8f2555e5" 535 | 536 | proxyquire@^1.7.2: 537 | version "1.7.11" 538 | resolved "https://registry.yarnpkg.com/proxyquire/-/proxyquire-1.7.11.tgz#13b494eb1e71fb21cc3ebe3699e637d3bec1af9e" 539 | dependencies: 540 | fill-keys "^1.0.2" 541 | module-not-found-error "^1.0.0" 542 | resolve "~1.1.7" 543 | 544 | pseudomap@^1.0.1: 545 | version "1.0.2" 546 | resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" 547 | 548 | punycode@^1.4.1: 549 | version "1.4.1" 550 | resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" 551 | 552 | qs@6.4.0, qs@~6.4.0: 553 | version "6.4.0" 554 | resolved "https://registry.yarnpkg.com/qs/-/qs-6.4.0.tgz#13e26d28ad6b0ffaa91312cd3bf708ed351e7233" 555 | 556 | raw-body@~2.2.0: 557 | version "2.2.0" 558 | resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.2.0.tgz#994976cf6a5096a41162840492f0bdc5d6e7fb96" 559 | dependencies: 560 | bytes "2.4.0" 561 | iconv-lite "0.4.15" 562 | unpipe "1.0.0" 563 | 564 | request@^2.64.0: 565 | version "2.81.0" 566 | resolved "https://registry.yarnpkg.com/request/-/request-2.81.0.tgz#c6928946a0e06c5f8d6f8a9333469ffda46298a0" 567 | dependencies: 568 | aws-sign2 "~0.6.0" 569 | aws4 "^1.2.1" 570 | caseless "~0.12.0" 571 | combined-stream "~1.0.5" 572 | extend "~3.0.0" 573 | forever-agent "~0.6.1" 574 | form-data "~2.1.1" 575 | har-validator "~4.2.1" 576 | hawk "~3.1.3" 577 | http-signature "~1.1.0" 578 | is-typedarray "~1.0.0" 579 | isstream "~0.1.2" 580 | json-stringify-safe "~5.0.1" 581 | mime-types "~2.1.7" 582 | oauth-sign "~0.8.1" 583 | performance-now "^0.2.0" 584 | qs "~6.4.0" 585 | safe-buffer "^5.0.1" 586 | stringstream "~0.0.4" 587 | tough-cookie "~2.3.0" 588 | tunnel-agent "^0.6.0" 589 | uuid "^3.0.0" 590 | 591 | resolve@~1.1.7: 592 | version "1.1.7" 593 | resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b" 594 | 595 | safe-buffer@^5.0.1: 596 | version "5.0.1" 597 | resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.0.1.tgz#d263ca54696cd8a306b5ca6551e92de57918fbe7" 598 | 599 | samsam@1.1.2, samsam@~1.1: 600 | version "1.1.2" 601 | resolved "https://registry.yarnpkg.com/samsam/-/samsam-1.1.2.tgz#bec11fdc83a9fda063401210e40176c3024d1567" 602 | 603 | scryptsy@=2.0.0: 604 | version "2.0.0" 605 | resolved "https://registry.yarnpkg.com/scryptsy/-/scryptsy-2.0.0.tgz#262c36f0231cfa7654e2363fa394cd2dec66f378" 606 | 607 | setprototypeof@1.0.3: 608 | version "1.0.3" 609 | resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.0.3.tgz#66567e37043eeb4f04d91bd658c0cbefb55b8e04" 610 | 611 | should-equal@0.8.0: 612 | version "0.8.0" 613 | resolved "https://registry.yarnpkg.com/should-equal/-/should-equal-0.8.0.tgz#a3f05732ff45bac1b7ba412f8408856819641299" 614 | dependencies: 615 | should-type "0.2.0" 616 | 617 | should-format@0.3.2: 618 | version "0.3.2" 619 | resolved "https://registry.yarnpkg.com/should-format/-/should-format-0.3.2.tgz#a59831e01a2ddee149911bc7148be5c80319e1ff" 620 | dependencies: 621 | should-type "0.2.0" 622 | 623 | should-type@0.2.0: 624 | version "0.2.0" 625 | resolved "https://registry.yarnpkg.com/should-type/-/should-type-0.2.0.tgz#6707ef95529d989dcc098fe0753ab1f9136bb7f6" 626 | 627 | should@^8.3.1: 628 | version "8.4.0" 629 | resolved "https://registry.yarnpkg.com/should/-/should-8.4.0.tgz#5e60889d3e644bbdd397a30cd34fad28fcf90bc0" 630 | dependencies: 631 | should-equal "0.8.0" 632 | should-format "0.3.2" 633 | should-type "0.2.0" 634 | 635 | sigmund@~1.0.0: 636 | version "1.0.1" 637 | resolved "https://registry.yarnpkg.com/sigmund/-/sigmund-1.0.1.tgz#3ff21f198cad2175f9f3b781853fd94d0d19b590" 638 | 639 | sinon@^1.10.3: 640 | version "1.17.7" 641 | resolved "https://registry.yarnpkg.com/sinon/-/sinon-1.17.7.tgz#4542a4f49ba0c45c05eb2e9dd9d203e2b8efe0bf" 642 | dependencies: 643 | formatio "1.1.1" 644 | lolex "1.3.2" 645 | samsam "1.1.2" 646 | util ">=0.10.3 <1" 647 | 648 | sntp@1.x.x: 649 | version "1.0.9" 650 | resolved "https://registry.yarnpkg.com/sntp/-/sntp-1.0.9.tgz#6541184cc90aeea6c6e7b35e2659082443c66198" 651 | dependencies: 652 | hoek "2.x.x" 653 | 654 | sshpk@^1.7.0: 655 | version "1.11.0" 656 | resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.11.0.tgz#2d8d5ebb4a6fab28ffba37fa62a90f4a3ea59d77" 657 | dependencies: 658 | asn1 "~0.2.3" 659 | assert-plus "^1.0.0" 660 | dashdash "^1.12.0" 661 | getpass "^0.1.1" 662 | optionalDependencies: 663 | bcrypt-pbkdf "^1.0.0" 664 | ecc-jsbn "~0.1.1" 665 | jodid25519 "^1.0.0" 666 | jsbn "~0.1.0" 667 | tweetnacl "~0.14.0" 668 | 669 | "statuses@>= 1.3.1 < 2": 670 | version "1.3.1" 671 | resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.3.1.tgz#faf51b9eb74aaef3b3acf4ad5f61abf24cb7b93e" 672 | 673 | stringstream@~0.0.4: 674 | version "0.0.5" 675 | resolved "https://registry.yarnpkg.com/stringstream/-/stringstream-0.0.5.tgz#4e484cd4de5a0bbbee18e46307710a8a81621878" 676 | 677 | supports-color@1.2.0: 678 | version "1.2.0" 679 | resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-1.2.0.tgz#ff1ed1e61169d06b3cf2d588e188b18d8847e17e" 680 | 681 | to-iso-string@0.0.2: 682 | version "0.0.2" 683 | resolved "https://registry.yarnpkg.com/to-iso-string/-/to-iso-string-0.0.2.tgz#4dc19e664dfccbe25bd8db508b00c6da158255d1" 684 | 685 | tough-cookie@~2.3.0: 686 | version "2.3.2" 687 | resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.2.tgz#f081f76e4c85720e6c37a5faced737150d84072a" 688 | dependencies: 689 | punycode "^1.4.1" 690 | 691 | tunnel-agent@^0.6.0: 692 | version "0.6.0" 693 | resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" 694 | dependencies: 695 | safe-buffer "^5.0.1" 696 | 697 | tweetnacl@^0.14.3, tweetnacl@~0.14.0: 698 | version "0.14.5" 699 | resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" 700 | 701 | type-detect@0.1.1: 702 | version "0.1.1" 703 | resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-0.1.1.tgz#0ba5ec2a885640e470ea4e8505971900dac58822" 704 | 705 | type-detect@^1.0.0: 706 | version "1.0.0" 707 | resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-1.0.0.tgz#762217cc06db258ec48908a1298e8b95121e8ea2" 708 | 709 | type-is@~1.6.14: 710 | version "1.6.14" 711 | resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.14.tgz#e219639c17ded1ca0789092dd54a03826b817cb2" 712 | dependencies: 713 | media-typer "0.3.0" 714 | mime-types "~2.1.13" 715 | 716 | unpipe@1.0.0: 717 | version "1.0.0" 718 | resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" 719 | 720 | "util@>=0.10.3 <1": 721 | version "0.10.3" 722 | resolved "https://registry.yarnpkg.com/util/-/util-0.10.3.tgz#7afb1afe50805246489e3db7fe0ed379336ac0f9" 723 | dependencies: 724 | inherits "2.0.1" 725 | 726 | uuid@^3.0.0: 727 | version "3.0.1" 728 | resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.0.1.tgz#6544bba2dfda8c1cf17e629a3a305e2bb1fee6c1" 729 | 730 | vary@~1.1.0: 731 | version "1.1.0" 732 | resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.0.tgz#e1e5affbbd16ae768dd2674394b9ad3022653140" 733 | 734 | verror@1.3.6: 735 | version "1.3.6" 736 | resolved "https://registry.yarnpkg.com/verror/-/verror-1.3.6.tgz#cff5df12946d297d2baaefaa2689e25be01c005c" 737 | dependencies: 738 | extsprintf "1.0.2" 739 | 740 | yallist@^2.0.0: 741 | version "2.1.2" 742 | resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" 743 | --------------------------------------------------------------------------------