blocktrail /
blocktrail-sdk-nodejs
| 1 | var Wallet = require('./wallet'); |
||
| 2 | var blocktrail = require('./blocktrail'); |
||
| 3 | var bitcoinJS = require('bitcoinjs-lib'); |
||
| 4 | |||
| 5 | var BtccomConverter = function(network, useNewCashAddr) { |
||
| 6 | this.network = network; |
||
| 7 | this.useNewCashAddr = useNewCashAddr; |
||
| 8 | }; |
||
| 9 | |||
| 10 | function getAddressScriptInfo(address, network, useNewCashAddr) { |
||
| 11 | var addressScriptInfo; |
||
| 12 | |||
| 13 | try { |
||
| 14 | addressScriptInfo = bitcoinJS.address.toOutputScript(address, network, useNewCashAddr); |
||
| 15 | } catch (e) { |
||
| 16 | addressScriptInfo = null; |
||
| 17 | } |
||
| 18 | return addressScriptInfo; |
||
| 19 | } |
||
| 20 | |||
| 21 | function getScriptAsm(chunks) { |
||
| 22 | var scriptAsm; |
||
| 23 | |||
| 24 | try { |
||
| 25 | scriptAsm = bitcoinJS.script.toASM(chunks); |
||
| 26 | } catch (e) { |
||
| 27 | scriptAsm = null; |
||
| 28 | } |
||
| 29 | return scriptAsm; |
||
| 30 | } |
||
| 31 | |||
| 32 | function prettifyAsm(asm) { |
||
| 33 | if (!asm) { |
||
| 34 | return asm; |
||
| 35 | } |
||
| 36 | |||
| 37 | return asm.replace(/^0 /, "OP_0 "); |
||
| 38 | } |
||
| 39 | |||
| 40 | function getType(script) { |
||
| 41 | var type; |
||
| 42 | |||
| 43 | try { |
||
| 44 | type = bitcoinJS.script.classifyOutput(script); |
||
| 45 | } catch (e) { |
||
| 46 | type = null; |
||
| 47 | } |
||
| 48 | return type; |
||
| 49 | } |
||
| 50 | |||
| 51 | function getBase58AddressHash160(address, network, useNewCashAddr) { |
||
| 52 | var addressInfo; |
||
| 53 | try { |
||
| 54 | addressInfo = Wallet.getAddressAndType(address, network, useNewCashAddr); |
||
| 55 | } catch (e) { |
||
| 56 | return null; |
||
| 57 | } |
||
| 58 | |||
| 59 | if (addressInfo.type === "base58") { |
||
| 60 | return addressInfo.decoded.hash; |
||
| 61 | } else if (addressInfo.type === "bech32") { |
||
| 62 | if (addressInfo.data.length === 20) { |
||
| 63 | return addressInfo.decoded.hash; |
||
| 64 | } |
||
| 65 | return null; |
||
| 66 | } else if (addressInfo.type === "") { |
||
| 67 | return addressInfo.decoded.hash; |
||
| 68 | } |
||
| 69 | |||
| 70 | return null; |
||
| 71 | } |
||
| 72 | |||
| 73 | function convertBtccomOutputScriptType(scriptType) { |
||
| 74 | switch (scriptType) { |
||
| 75 | case "P2PKH_PUBKEY": |
||
| 76 | return "pubkey"; |
||
| 77 | case "P2PKH": |
||
| 78 | return "pubkeyhash"; |
||
| 79 | case "P2SH": |
||
| 80 | return "scripthash"; |
||
| 81 | case "P2WSH_V0": |
||
| 82 | return "witnessscripthash"; |
||
| 83 | case "P2WPKH_V0": |
||
| 84 | return "witnesspubkeyhash"; |
||
| 85 | case "NULL_DATA": |
||
| 86 | return "op_return"; |
||
| 87 | case "coinbase": |
||
| 88 | return "coinbase"; |
||
| 89 | default: |
||
| 90 | throw new Error("Not implemented yet, script type: " + scriptType); |
||
| 91 | } |
||
| 92 | } |
||
| 93 | |||
| 94 | function utcTimestampToISODateStr(time) { |
||
| 95 | return (new Date(time * 1000)).toISOString().replace(/\.000Z$/, '+0000'); |
||
| 96 | } |
||
| 97 | |||
| 98 | function flattenAddresses(addrs) { |
||
| 99 | if (!addrs) { |
||
| 100 | return addrs; |
||
| 101 | } else if (addrs.length === 1) { |
||
| 102 | return addrs[0]; |
||
| 103 | } else { |
||
| 104 | return addrs; |
||
| 105 | } |
||
| 106 | } |
||
| 107 | |||
| 108 | function convertBtccomTxToBlocktrail(tx) { |
||
| 109 | /* jshint -W071, -W074 */ |
||
| 110 | var data = {}; |
||
| 111 | |||
| 112 | data.size = tx.vsize; |
||
| 113 | data.hash = tx.hash; |
||
| 114 | data.block_height = tx.block_height; |
||
| 115 | data.time = |
||
| 116 | data.block_time = utcTimestampToISODateStr(tx.block_time); |
||
| 117 | data.block_hash = tx.block_hash; |
||
| 118 | data.confirmations = tx.confirmations; |
||
| 119 | data.is_coinbase = tx.is_coinbase; |
||
| 120 | |||
| 121 | var totalInputValue; |
||
| 122 | if (data.is_coinbase) { |
||
| 123 | totalInputValue = tx.outputs[0].value - tx.fee; |
||
| 124 | } else { |
||
| 125 | totalInputValue = tx.inputs_value; |
||
| 126 | } |
||
| 127 | |||
| 128 | data.total_input_value = totalInputValue; |
||
| 129 | data.total_output_value = tx.outputs.reduce(function(total, output) { |
||
| 130 | return total + output.value; |
||
| 131 | }, 0); |
||
| 132 | data.total_fee = tx.fee; |
||
| 133 | data.inputs = []; |
||
| 134 | data.outputs = []; |
||
| 135 | data.opt_in_rbf = false; |
||
| 136 | |||
| 137 | for (var inputIdx in tx.inputs) { |
||
|
0 ignored issues
–
show
|
|||
| 138 | var input = tx.inputs[inputIdx]; |
||
| 139 | var scriptType; |
||
| 140 | var inputValue; |
||
| 141 | var inputTxid; |
||
| 142 | var outpointIdx; |
||
| 143 | |||
| 144 | if (input.sequence < bitcoinJS.Transaction.DEFAULT_SEQUENCE - 1) { |
||
| 145 | data.opt_in_rbf = true; |
||
| 146 | } |
||
| 147 | |||
| 148 | if (data.is_coinbase && input.prev_position === -1 && |
||
| 149 | input.prev_tx_hash === "0000000000000000000000000000000000000000000000000000000000000000") { |
||
| 150 | scriptType = "coinbase"; |
||
| 151 | inputTxid = null; |
||
| 152 | inputValue = totalInputValue; |
||
| 153 | outpointIdx = 0xffffffff; |
||
| 154 | } else { |
||
| 155 | scriptType = input.prev_type; |
||
| 156 | inputValue = input.prev_value; |
||
| 157 | inputTxid = input.prev_tx_hash; |
||
| 158 | outpointIdx = input.prev_position; |
||
| 159 | } |
||
| 160 | |||
| 161 | data.inputs.push({ |
||
| 162 | index: parseInt(inputIdx, 10), |
||
| 163 | output_hash: inputTxid, |
||
| 164 | output_index: outpointIdx, |
||
| 165 | value: inputValue, |
||
| 166 | sequence: input.sequence, |
||
| 167 | address: flattenAddresses(input.prev_addresses), |
||
| 168 | type: convertBtccomOutputScriptType(scriptType), |
||
| 169 | script_signature: input.script_hex |
||
| 170 | }); |
||
| 171 | } |
||
| 172 | |||
| 173 | for (var outIdx in tx.outputs) { |
||
|
0 ignored issues
–
show
A for in loop automatically includes the property of any prototype object, consider checking the key using
hasOwnProperty.
When iterating over the keys of an object, this includes not only the keys of the object, but also keys contained in the prototype of that object. It is generally a best practice to check for these keys specifically: var someObject;
for (var key in someObject) {
if ( ! someObject.hasOwnProperty(key)) {
continue; // Skip keys from the prototype.
}
doSomethingWith(key);
}
Loading history...
|
|||
| 174 | var output = tx.outputs[outIdx]; |
||
| 175 | |||
| 176 | data.outputs.push({ |
||
| 177 | index: parseInt(outIdx, 10), |
||
| 178 | value: output.value, |
||
| 179 | address: flattenAddresses(output.addresses), |
||
| 180 | type: convertBtccomOutputScriptType(output.type), |
||
| 181 | script: prettifyAsm(output.script_asm), |
||
| 182 | script_hex: output.script_hex, |
||
| 183 | spent_hash: output.spent_by_tx, |
||
| 184 | spent_index: output.spent_by_tx_position |
||
| 185 | }); |
||
| 186 | } |
||
| 187 | |||
| 188 | data.size = tx.size; |
||
| 189 | data.is_double_spend = tx.is_double_spend; |
||
| 190 | |||
| 191 | data.lock_time_timestamp = null; |
||
| 192 | data.lock_time_block_height = null; |
||
| 193 | if (tx.lock_time) { |
||
| 194 | if (tx.lock_time < blocktrail.LOCK_TIME_TIMESTAMP_THRESHOLD) { |
||
| 195 | data.lock_time_block_height = tx.lock_time; |
||
| 196 | } else { |
||
| 197 | data.lock_time_timestamp = tx.lock_time; |
||
| 198 | } |
||
| 199 | } |
||
| 200 | |||
| 201 | // Extra fields from Btc.com |
||
| 202 | data.is_sw_tx = tx.is_sw_tx; |
||
| 203 | data.weight = tx.weight; |
||
| 204 | data.witness_hash = tx.witness_hash; |
||
| 205 | data.lock_time = tx.lock_time; |
||
| 206 | data.sigops = tx.sigops; |
||
| 207 | data.version = tx.version; |
||
| 208 | |||
| 209 | return data; |
||
| 210 | } |
||
| 211 | |||
| 212 | BtccomConverter.prototype.paginationParams = function(params) { |
||
| 213 | if (!params) { |
||
| 214 | return params; |
||
| 215 | } |
||
| 216 | |||
| 217 | if (typeof params.limit !== "undefined") { |
||
| 218 | params.pagesize = params.limit; |
||
| 219 | delete params.limit; |
||
| 220 | } |
||
| 221 | |||
| 222 | return params; |
||
| 223 | }; |
||
| 224 | |||
| 225 | BtccomConverter.prototype.getUrlForBlock = function(blockHash) { |
||
| 226 | return "/block/" + blockHash; |
||
| 227 | }; |
||
| 228 | |||
| 229 | BtccomConverter.prototype.getUrlForTransaction = function(txId) { |
||
| 230 | return "/tx/" + txId + "?verbose=3"; |
||
| 231 | }; |
||
| 232 | |||
| 233 | BtccomConverter.prototype.getUrlForRawTransaction = function(txId) { |
||
| 234 | return "/tx/" + txId + "/raw"; |
||
| 235 | }; |
||
| 236 | |||
| 237 | BtccomConverter.prototype.getUrlForTransactions = function(txIds) { |
||
| 238 | return "/tx/" + txIds.join(",") + "?verbose=3"; |
||
| 239 | }; |
||
| 240 | |||
| 241 | BtccomConverter.prototype.getUrlForBlockTransaction = function(blockHash) { |
||
| 242 | return "/block/" + blockHash + "/tx?verbose=3"; |
||
| 243 | }; |
||
| 244 | |||
| 245 | BtccomConverter.prototype.getUrlForAddress = function(address) { |
||
| 246 | return "/address/" + address; |
||
| 247 | }; |
||
| 248 | |||
| 249 | BtccomConverter.prototype.getUrlForAddressTransactions = function(address) { |
||
| 250 | return "/address/" + address + "/tx?verbose=3"; |
||
| 251 | }; |
||
| 252 | |||
| 253 | BtccomConverter.prototype.getUrlForAddressUnspent = function(address) { |
||
| 254 | return "/address/" + address + "/unspent"; |
||
| 255 | }; |
||
| 256 | |||
| 257 | BtccomConverter.prototype.getUrlForBatchAddressUnspent = function(addresses) { |
||
| 258 | return "/multi-address/" + addresses.join(",") + "/unspent"; |
||
| 259 | }; |
||
| 260 | |||
| 261 | |||
| 262 | BtccomConverter.prototype.getUrlForAllBlocks = function() { |
||
| 263 | return "/block/list"; |
||
| 264 | }; |
||
| 265 | |||
| 266 | BtccomConverter.prototype.handleErros = function(self, data) { |
||
| 267 | if (data.err_no === 0 || data.data !== null) { |
||
| 268 | return data; |
||
| 269 | } else { |
||
| 270 | return { |
||
| 271 | err_no: data.err_no, |
||
| 272 | err_msg: data.err_msg, |
||
| 273 | data: data.data |
||
| 274 | }; |
||
| 275 | } |
||
| 276 | }; |
||
| 277 | |||
| 278 | BtccomConverter.prototype.convertBlock = function(oldData) { |
||
| 279 | var data = { |
||
| 280 | hash: oldData.hash, |
||
| 281 | version: oldData.version, |
||
| 282 | height: oldData.height, |
||
| 283 | block_time: utcTimestampToISODateStr(oldData.timestamp), |
||
| 284 | arrival_time: utcTimestampToISODateStr(oldData.timestamp), |
||
| 285 | bits: oldData.bits, |
||
| 286 | nonce: oldData.nonce, |
||
| 287 | merkleroot: oldData.mrkl_root, |
||
| 288 | prev_block: oldData.prev_block_hash, |
||
| 289 | next_block: oldData.next_block_hash, |
||
| 290 | byte_size: oldData.stripped_size, |
||
| 291 | difficulty: Math.floor(oldData.difficulty), |
||
| 292 | transactions: oldData.tx_count, |
||
| 293 | reward_block: oldData.reward_block, |
||
| 294 | reward_fees: oldData.reward_fees, |
||
| 295 | created_at: oldData.created_at, |
||
| 296 | confirmations: oldData.confirmations, |
||
| 297 | is_orphan: oldData.is_orphan, |
||
| 298 | is_sw_block: oldData.is_sw_block, |
||
| 299 | weight: oldData.weight, |
||
| 300 | miningpool_name: oldData.miningpool_name || null, |
||
| 301 | miningpool_url: oldData.miningpool_url || null, |
||
| 302 | miningpool_slug: oldData.miningpool_slug || null |
||
| 303 | }; |
||
| 304 | |||
| 305 | return data; |
||
| 306 | }; |
||
| 307 | |||
| 308 | BtccomConverter.prototype.convertBlocks = function(oldData) { |
||
| 309 | return { |
||
| 310 | data: oldData.data.list, |
||
| 311 | current_page: oldData.data.page, |
||
| 312 | per_page: oldData.data.pagesize, |
||
| 313 | total: oldData.data.total_count |
||
| 314 | }; |
||
| 315 | }; |
||
| 316 | |||
| 317 | BtccomConverter.prototype.convertBlockTxs = function(oldData) { |
||
| 318 | var list = []; |
||
| 319 | oldData.data.list.forEach(function(oldTx) { |
||
| 320 | var resTx = convertBtccomTxToBlocktrail(oldTx); |
||
| 321 | |||
| 322 | list.push(resTx); |
||
| 323 | }); |
||
| 324 | |||
| 325 | return { |
||
| 326 | data: list, |
||
| 327 | current_page: oldData.data.page, |
||
| 328 | per_page: oldData.data.pagesize, |
||
| 329 | total: oldData.data.total_count |
||
| 330 | }; |
||
| 331 | }; |
||
| 332 | |||
| 333 | BtccomConverter.prototype.convertTx = function(oldData, rawTx) { |
||
| 334 | var data = convertBtccomTxToBlocktrail(oldData.data); |
||
| 335 | data.raw = rawTx; |
||
| 336 | return data; |
||
| 337 | }; |
||
| 338 | |||
| 339 | BtccomConverter.prototype.convertTxs = function(oldData) { |
||
| 340 | var res = {}; |
||
| 341 | |||
| 342 | oldData.data |
||
| 343 | .filter(function(tx) { return !!tx; }) |
||
| 344 | .forEach(function(oldTx) { |
||
| 345 | var tx = convertBtccomTxToBlocktrail(oldTx); |
||
| 346 | res[tx.hash] = tx; |
||
| 347 | }); |
||
| 348 | |||
| 349 | return {data: res}; |
||
| 350 | }; |
||
| 351 | |||
| 352 | BtccomConverter.prototype.convertAddressTxs = function(oldData) { |
||
| 353 | var data = oldData.data.list.map(function(tx) { |
||
| 354 | var res = {}; |
||
| 355 | |||
| 356 | res.hash = tx.hash; |
||
| 357 | res.time = utcTimestampToISODateStr(tx.block_time); |
||
| 358 | res.confirmations = tx.confirmations; |
||
| 359 | res.block_height = tx.block_height; |
||
| 360 | res.block_hash = tx.block_hash; |
||
| 361 | res.is_coinbase = tx.is_coinbase; |
||
| 362 | res.total_input_value = tx.inputs_value; |
||
| 363 | res.total_output_value = tx.outputs_value; |
||
| 364 | res.total_fee = tx.fee; |
||
| 365 | |||
| 366 | res.inputs = tx.inputs.map(function(input, inIdx) { |
||
| 367 | return { |
||
| 368 | index: inIdx, |
||
| 369 | output_hash: input.prev_tx_hash, |
||
| 370 | output_index: input.prev_position, |
||
| 371 | value: input.prev_value, |
||
| 372 | address: flattenAddresses(input.prev_addresses), |
||
| 373 | type: res.is_coinbase ? res.is_coinbase : convertBtccomOutputScriptType(input.prev_type), |
||
| 374 | script_signature: input.script_hex |
||
| 375 | }; |
||
| 376 | }); |
||
| 377 | |||
| 378 | res.outputs = tx.outputs.map(function(output, outIdx) { |
||
| 379 | return { |
||
| 380 | index: outIdx, |
||
| 381 | value: output.value, |
||
| 382 | address: flattenAddresses(output.addresses), |
||
| 383 | type: convertBtccomOutputScriptType(output.type), |
||
| 384 | script: prettifyAsm(output.script_asm), |
||
| 385 | spent_hash: output.spent_by_tx || null, |
||
| 386 | script_hex: output.script_hex, |
||
| 387 | spent_index: output.spent_by_tx_position |
||
| 388 | }; |
||
| 389 | }); |
||
| 390 | |||
| 391 | // Extra fields from Btc.com |
||
| 392 | res.is_double_spend = tx.is_double_spend; |
||
| 393 | res.is_sw_tx = tx.is_sw_tx; |
||
| 394 | res.weight = tx.weight; |
||
| 395 | res.witness_hash = tx.witness_hash; |
||
| 396 | res.version = tx.version; |
||
| 397 | |||
| 398 | return res; |
||
| 399 | }); |
||
| 400 | |||
| 401 | return { |
||
| 402 | data: data, |
||
| 403 | current_page: oldData.data.page, |
||
| 404 | per_page: oldData.data.pagesize, |
||
| 405 | total: oldData.data.total_count |
||
| 406 | }; |
||
| 407 | }; |
||
| 408 | |||
| 409 | BtccomConverter.prototype.convertAddress = function(oldData) { |
||
| 410 | var data = {}; |
||
| 411 | |||
| 412 | data.address = oldData.data.address; |
||
| 413 | data.hash160 = getBase58AddressHash160(oldData.data.address, this.network, this.useNewCashAddr).toString("hex").toUpperCase(); |
||
| 414 | data.balance = oldData.data.balance; |
||
| 415 | data.received = oldData.data.received; |
||
| 416 | data.sent = oldData.data.sent; |
||
| 417 | data.transactions = oldData.data.tx_count; |
||
| 418 | data.utxos = oldData.data.unspent_tx_count; |
||
| 419 | data.unconfirmed_received = oldData.data.unconfirmed_received; |
||
| 420 | data.unconfirmed_sent = oldData.data.unconfirmed_sent; |
||
| 421 | data.unconfirmed_transactions = oldData.data.unconfirmed_tx_count; |
||
| 422 | data.first_tx = oldData.data.first_tx; |
||
| 423 | data.last_tx = oldData.data.last_tx; |
||
| 424 | |||
| 425 | return data; |
||
| 426 | }; |
||
| 427 | |||
| 428 | BtccomConverter.prototype.convertAddressUnspentOutputs = function(oldData, address) { |
||
| 429 | var script = getAddressScriptInfo(address, this.network, this.useNewCashAddr); |
||
| 430 | var script_hex = script.toString("hex"); |
||
| 431 | var script_asm = getScriptAsm(script); |
||
| 432 | var type = getType(script); |
||
| 433 | var data = oldData.data.list.map(function(utxo) { |
||
| 434 | return { |
||
| 435 | hash: utxo.tx_hash, |
||
| 436 | confirmations: utxo.confirmations, |
||
| 437 | value: utxo.value, |
||
| 438 | index: utxo.tx_output_n, |
||
| 439 | address: address, |
||
| 440 | type: type, |
||
| 441 | script: script_asm, |
||
| 442 | script_hex: script_hex |
||
| 443 | }; |
||
| 444 | }); |
||
| 445 | |||
| 446 | return { |
||
| 447 | data: data, |
||
| 448 | current_page: oldData.data.page, |
||
| 449 | total: oldData.data.total_count |
||
| 450 | }; |
||
| 451 | }; |
||
| 452 | |||
| 453 | BtccomConverter.prototype.convertBatchAddressUnspentOutputs = function(data) { |
||
| 454 | var res = []; |
||
| 455 | var total = 0; |
||
| 456 | |||
| 457 | data.data.forEach(function(row) { |
||
| 458 | var script = getAddressScriptInfo(row.address, this.network, this.useNewCashAddr); |
||
| 459 | var script_hex = script.toString("hex"); |
||
| 460 | var script_asm = getScriptAsm(script); |
||
| 461 | var type = getType(script); |
||
| 462 | |||
| 463 | row.list.forEach(function(utxo) { |
||
| 464 | total++; |
||
| 465 | res.push({ |
||
| 466 | hash: utxo.tx_hash, |
||
| 467 | index: utxo.tx_output_n, |
||
| 468 | value: utxo.value, |
||
| 469 | confirmations: utxo.confirmations, |
||
| 470 | address: row.address, |
||
| 471 | script: script_asm, |
||
| 472 | script_hex: script_hex, |
||
| 473 | type: type |
||
| 474 | }); |
||
| 475 | }); |
||
| 476 | }); |
||
| 477 | |||
| 478 | return { |
||
| 479 | data: res, |
||
| 480 | current_page: null, |
||
| 481 | per_page: null, |
||
| 482 | total: total |
||
| 483 | }; |
||
| 484 | }; |
||
| 485 | |||
| 486 | exports = module.exports = BtccomConverter; |
||
| 487 |
When iterating over the keys of an object, this includes not only the keys of the object, but also keys contained in the prototype of that object. It is generally a best practice to check for these keys specifically: