Completed
Pull Request — master (#99)
by
unknown
01:22
created

btccom.convert.js ➔ convertBtccomOutputScriptType   B

Complexity

Conditions 8
Paths 6

Size

Total Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 8
c 1
b 0
f 0
nc 6
dl 0
loc 18
rs 7.7777
nop 1
1
var RestClient = require('./rest_client');
2
var Wallet = require('./wallet');
3
var blocktrail = require('./blocktrail');
4
var bitcoinJS = require('bitcoinjs-lib');
5
6
var BtccomConverter = function(network, useNewCashAddr) {
7
    this.network = network;
8
    this.useNewCashAddr = useNewCashAddr;
9
10
    this.specialRawTxClientOnly = new RestClient({
11
        host: "btc.com",
12
        https: true,
13
        port: 443,
14
        endpoint: "/"
15
    });
16
};
17
18
function getAddressScriptInfo(address, network, useNewCashAddr) {
19
    var addressScriptInfo;
20
21
    try {
22
        addressScriptInfo = bitcoinJS.address.toOutputScript(address, network, useNewCashAddr);
23
    } catch (e) {
24
        addressScriptInfo = null;
25
    }
26
    return addressScriptInfo;
27
}
28
29
function getScriptAsm(chunks) {
30
    var scriptAsm;
31
32
    try {
33
        scriptAsm = bitcoinJS.script.toASM(chunks);
34
    } catch (e) {
35
        scriptAsm = null;
36
    }
37
    return scriptAsm;
38
}
39
40
function prettifyAsm(asm) {
41
    if (!asm) {
42
        return asm;
43
    }
44
45
    return asm.replace(/^0 /, "OP_0 ");
46
}
47
48
function getType(script) {
49
    var type;
50
51
    try {
52
        type = bitcoinJS.script.classifyOutput(script);
53
    } catch (e) {
54
        type = null;
55
    }
56
    return type;
57
}
58
59
function getBase58AddressHash160(address, network, useNewCashAddr) {
60
    var addressInfo;
61
    try {
62
        addressInfo = Wallet.getAddressAndType(address, network, useNewCashAddr);
63
    } catch (e) {
64
        return null;
65
    }
66
67
    if (addressInfo.type === "base58") {
68
        return addressInfo.decoded.hash;
69
    } else if (addressInfo.type === "bech32") {
70
        if (addressInfo.data.length === 20) {
71
            return addressInfo.decoded.hash;
72
        }
73
        return null;
74
    } else if (addressInfo.type === "") {
0 ignored issues
show
Complexity Best Practice introduced by
There is no return statement if addressInfo.type === "" is false. Are you sure this is correct? If so, consider adding return; explicitly.

This check looks for functions where a return statement is found in some execution paths, but not in all.

Consider this little piece of code

function isBig(a) {
    if (a > 5000) {
        return "yes";
    }
}

console.log(isBig(5001)); //returns yes
console.log(isBig(42)); //returns undefined

The function isBig will only return a specific value when its parameter is bigger than 5000. In any other case, it will implicitly return undefined.

This behaviour may not be what you had intended. In any case, you can add a return undefined to the other execution path to make the return value explicit.

Loading history...
75
        return addressInfo.decoded.hash;
76
    }
77
}
78
79
function convertBtccomOutputScriptType(scriptType) {
80
    switch (scriptType) {
81
        case "P2PKH_PUBKEY":
82
            return "pubkey";
83
        case "P2PKH":
84
            return "pubkeyhash";
85
        case "P2SH":
86
            return "scripthash";
87
        case "P2WSH_V0":
88
            return "witnessscripthash";
89
        case "P2WPKH":
90
            return "witnesspubkeyhash";
91
        case "coinbase":
92
            return "coinbase";
93
        default:
94
            throw new Error("Not implemented yet, script type: " + scriptType);
95
    }
96
}
97
98
function utcTimestampToISODateStr(time) {
99
    return (new Date(time * 1000)).toISOString().replace(/\.000Z$/, '+0000');
100
}
101
102
function flattenAddresses(addrs) {
103
    if (!addrs) {
104
        return addrs;
105
    } else if (addrs.length === 1) {
106
        return addrs[0];
107
    } else {
108
        return addrs;
109
    }
110
}
111
112
function convertBtccomTxToBlocktrail(tx) {
113
    /* jshint -W071, -W074 */
114
    var data = {};
115
116
    data.size = tx.vsize;
117
    data.hash = tx.hash;
118
    data.block_height = tx.block_height ;
119
    data.block_time = utcTimestampToISODateStr(tx.block_time);
120
    data.block_hash = null; // TODO: missing block_hash
121
    data.confirmations = tx.confirmations;
122
    data.is_coinbase = tx.is_coinbase;
123
124
    var totalInputValue;
125
    if (data.is_coinbase) {
126
        totalInputValue = tx.outputs[0].value - tx.fee;
127
    } else {
128
        totalInputValue = tx.inputs_value;
129
    }
130
131
    data.total_input_value = totalInputValue;
132
    data.total_output_value = tx.outputs.reduce(function(total, output) {
133
        return total + output.value;
134
    }, 0);
135
    data.total_fee = tx.fee;
136
    data.inputs = [];
137
    data.outputs = [];
138
    data.opt_in_rbf = false;
139
140
    for (var inputIdx in tx.inputs) {
0 ignored issues
show
Complexity introduced by
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...
141
        var input = tx.inputs[inputIdx];
142
        var scriptType;
143
        var inputValue;
144
        var inputTxid;
145
        var outpointIdx;
146
147
        if (input.sequence < bitcoinJS.Transaction.DEFAULT_SEQUENCE - 1) {
148
            data.opt_in_rbf = true;
149
        }
150
151
        if (data.is_coinbase && input.prev_position === -1 &&
152
            input.prev_tx_hash === "0000000000000000000000000000000000000000000000000000000000000000") {
153
            scriptType = "coinbase";
154
            inputTxid = null;
155
            inputValue = totalInputValue;
156
            outpointIdx = 0xffffffff;
157
        } else {
158
            scriptType = input.prev_type;
159
            inputValue = input.prev_value;
160
            inputTxid = input.prev_tx_hash;
161
            outpointIdx = input.prev_position;
162
        }
163
164
        data.inputs.push({
165
            index: parseInt(inputIdx, 10),
166
            output_hash: inputTxid,
167
            output_index: outpointIdx,
168
            value: inputValue,
169
            sequence: input.sequence,
170
            address: flattenAddresses(input.prev_addresses),
171
            type: convertBtccomOutputScriptType(scriptType),
172
            script_signature: input.script_hex
173
        });
174
    }
175
176
    for (var outIdx in tx.outputs) {
0 ignored issues
show
Complexity introduced by
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...
177
        var output = tx.outputs[outIdx];
178
179
        data.outputs.push({
180
            index: parseInt(outIdx, 10),
181
            value: output.value,
182
            address: flattenAddresses(output.addresses),
183
            type: convertBtccomOutputScriptType(output.type),
184
            script: prettifyAsm(output.script_asm),
185
            script_hex: output.script_hex,
186
            spent_hash: output.spent_by_tx,
187
            spent_index: output.spent_by_tx_position
188
        });
189
    }
190
191
    data.size = tx.size;
192
    data.is_double_spend = tx.is_double_spend;
193
194
    data.lock_time_timestamp = null;
195
    data.lock_time_block_height = null;
196
    if (tx.lock_time) {
197
        if (tx.lock_time < blocktrail.LOCK_TIME_TIMESTAMP_THRESHOLD) {
198
            data.lock_time_block_height = tx.lock_time;
199
        } else {
200
            data.lock_time_timestamp = tx.lock_time;
201
        }
202
    }
203
204
    // Extra fields from Btc.com
205
    data.is_sw_tx = tx.is_sw_tx;
206
    data.weight = tx.weight;
207
    data.witness_hash = tx.witness_hash;
208
    data.lock_time  = tx.lock_time;
209
    data.sigops = tx.sigops;
210
    data.version = tx.version;
211
212
    return data;
213
}
214
215
BtccomConverter.prototype.paginationParams = function(params) {
216
    if (!params) {
217
        return params;
218
    }
219
220
    if (typeof params.limit !== "undefined") {
221
        params.pagesize = params.limit;
222
        delete params.limit;
223
    }
224
225
    return params;
226
};
227
228
BtccomConverter.prototype.getUrlForBlock = function(blockHash) {
229
    return "/block/" + blockHash;
230
};
231
232
BtccomConverter.prototype.getUrlForTransaction = function(txId) {
233
    return "/tx/" + txId + "?verbose=3";
234
};
235
236
BtccomConverter.prototype.getUrlForTransactions = function(txIds) {
237
    return "/tx/" + txIds.join(",") + "?verbose=3";
238
};
239
240
BtccomConverter.prototype.getUrlForBlockTransaction = function(blockHash) {
241
    return "/block/" + blockHash + "/tx";
242
};
243
244
BtccomConverter.prototype.getUrlForAddress = function(address) {
245
    return "/address/" + address;
246
};
247
248
BtccomConverter.prototype.getUrlForAddressTransactions = function(address) {
249
    return "/address/" + address + "/tx?verbose=3";
250
};
251
252
BtccomConverter.prototype.getUrlForAddressUnspent = function(address) {
253
    return "/address/" + address + "/unspent";
254
};
255
256
BtccomConverter.prototype.getUrlForAllBlocks = function() {
257
    return "/all-blocks";
258
};
259
260
BtccomConverter.prototype.handleErros = function(self, data) {
261
    if (self.converter instanceof BtccomConverter) {
0 ignored issues
show
Complexity Best Practice introduced by
There is no return statement if self.converter instanceof BtccomConverter is false. Are you sure this is correct? If so, consider adding return; explicitly.

This check looks for functions where a return statement is found in some execution paths, but not in all.

Consider this little piece of code

function isBig(a) {
    if (a > 5000) {
        return "yes";
    }
}

console.log(isBig(5001)); //returns yes
console.log(isBig(42)); //returns undefined

The function isBig will only return a specific value when its parameter is bigger than 5000. In any other case, it will implicitly return undefined.

This behaviour may not be what you had intended. In any case, you can add a return undefined to the other execution path to make the return value explicit.

Loading history...
262
        if (data.err_no === 0 || data.data !== null) {
263
            return data;
264
        } else {
265
            return {
266
                err_no: data.err_no,
267
                err_msg: data.err_msg,
268
                data: data.data
269
            };
270
        }
271
    }
272
};
273
274
BtccomConverter.prototype.convertBlock = function(oldData) {
275
    var data = {
276
        hash: oldData.data.hash,
277
        version: oldData.data.version,
278
        height: oldData.data.height,
279
        block_time: utcTimestampToISODateStr(oldData.data.timestamp),
280
        arrival_time: utcTimestampToISODateStr(oldData.data.timestamp),
281
        bits: oldData.data.bits,
282
        nonce: oldData.data.nonce,
283
        merkleroot: oldData.data.mrkl_root,
284
        prev_block: oldData.data.prev_block_hash,
285
        next_block: oldData.data.next_block_hash,
286
        byte_size: oldData.data.stripped_size,
287
        difficulty: Math.floor(oldData.data.difficulty),
288
        transactions: oldData.data.tx_count,
289
        reward_block: oldData.data.reward_block,
290
        reward_fees: oldData.data.reward_fees,
291
        created_at: oldData.data.created_at,
292
        confirmations: oldData.data.confirmations,
293
        is_orphan: oldData.data.is_orphan,
294
        is_sw_block: oldData.data.is_sw_block,
295
        weight: oldData.data.weight,
296
        miningpool_name: oldData.data.miningpool_name || null,
297
        miningpool_url: oldData.data.miningpool_url || null,
298
        miningpool_slug: oldData.data.miningpool_slug || null
299
    };
300
301
    return data;
302
};
303
304
305
BtccomConverter.prototype.convertBlockTxs = function(oldData) {
306
307
    var list = [];
308
    oldData.data.list.forEach(function(oldTx) {
309
        var resTx = convertBtccomTxToBlocktrail(oldTx);
310
311
        list.push(resTx);
312
    });
313
314
    return {
315
        data: list,
316
        current_page: oldData.data.page,
317
        per_page: oldData.data.pagesize,
318
        total: oldData.data.total_count
319
    };
320
};
321
322
BtccomConverter.prototype.convertTx = function(oldData, rawTx) {
323
    var data = convertBtccomTxToBlocktrail(oldData.data);
324
    data.raw = rawTx;
325
    return data;
326
};
327
328
BtccomConverter.prototype.convertTxs = function(oldData) {
329
    var res = {};
330
331
    oldData.data
332
        .filter(function(tx) { return !!tx; })
333
        .forEach(function(oldTx) {
334
            var tx = convertBtccomTxToBlocktrail(oldTx);
335
            res[tx.hash] = tx;
336
        });
337
338
    return {data: res};
339
};
340
341
BtccomConverter.prototype.convertAddressTxs = function(oldData) {
342
    var data = oldData.data.list.map(function(tx) {
343
        var res = {};
344
345
        res.hash = tx.hash;
346
        res.time = utcTimestampToISODateStr(tx.block_time);
347
        res.confirmations = tx.confirmations;
348
        res.block_height = tx.block_height;
349
        res.block_hash = null; // @TODO: missing block_hash
350
        res.is_coinbase = tx.is_coinbase;
351
        res.total_input_value = tx.inputs_value;
352
        res.total_output_value = tx.outputs_value;
353
        res.total_fee = tx.fee;
354
355
        res.inputs = tx.inputs.map(function(input, inIdx) {
356
            return {
357
                index: inIdx,
358
                output_hash: input.prev_tx_hash,
359
                output_index: input.prev_position,
360
                value: input.prev_value,
361
                address: flattenAddresses(input.prev_addresses),
362
                type:  res.is_coinbase ? res.is_coinbase : convertBtccomOutputScriptType(input.prev_type),
363
                script_signature: input.script_hex
364
            };
365
        });
366
367
        res.outputs = tx.outputs.map(function(output, outIdx) {
368
            return {
369
                index: outIdx,
370
                value: output.value,
371
                address: flattenAddresses(output.addresses),
372
                type: convertBtccomOutputScriptType(output.type),
373
                script: prettifyAsm(output.script_asm),
374
                spent_hash: output.spent_by_tx || null,
375
                script_hex: output.script_hex,
376
                spent_index: output.spent_by_tx_position
377
            };
378
        });
379
380
        // Extra fields from Btc.com
381
        res.is_double_spend = tx.is_double_spend;
382
        res.is_sw_tx = tx.is_sw_tx;
383
        res.weight = tx.weight;
384
        res.witness_hash = tx.witness_hash;
385
        res.version = tx.version;
386
387
        return res;
388
    });
389
390
    return {
391
        data: data,
392
        current_page: oldData.data.page,
393
        per_page: oldData.data.pagesize,
394
        total: oldData.data.total_count
395
    };
396
};
397
398
BtccomConverter.prototype.convertAddress = function(oldData) {
399
    var data = {};
400
401
    data.address = oldData.data.address;
402
    data.hash160 = getBase58AddressHash160(oldData.data.address, this.network, this.useNewCashAddr).toString("hex").toUpperCase();
403
    data.balance = oldData.data.balance;
404
    data.received = oldData.data.received;
405
    data.sent = oldData.data.sent;
406
    data.transactions = oldData.data.tx_count;
407
    data.utxos = oldData.data.unspent_tx_count;
408
    data.unconfirmed_received = oldData.data.unconfirmed_received;
409
    data.unconfirmed_sent = oldData.data.unconfirmed_sent;
410
    data.unconfirmed_transactions = oldData.data.unconfirmed_tx_count;
411
    data.first_tx = oldData.data.first_tx;
412
    data.last_tx = oldData.data.last_tx;
413
414
    return data;
415
};
416
417
BtccomConverter.prototype.convertAddressUnspentOutputs = function(oldData, address) {
418
    var script = getAddressScriptInfo(address, this.network, this.useNewCashAddr);
419
    var script_hex = script.toString("hex");
420
    var script_asm = getScriptAsm(script);
421
    var type = getType(script);
422
    var data = oldData.data.list.map(function(utxo) {
423
        return {
424
            hash: utxo.tx_hash,
425
            confirmations: utxo.confirmations,
426
            value: utxo.value,
427
            index: utxo.tx_output_n,
428
            address: address,
429
            type: type,
430
            script: script_asm,
431
            script_hex: script_hex
432
        };
433
    });
434
435
    return {
436
        data: data,
437
        current_page: oldData.data.page,
438
        total: oldData.data.total_count
439
    };
440
};
441
442
exports = module.exports = BtccomConverter;
443