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);
}
![]() |
|||
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: