Completed
Pull Request — master (#99)
by Ruben de
56s
created

lib/btccom.convert.js   F

Complexity

Total Complexity 74
Complexity/F 1.8

Size

Lines of Code 489
Function Count 41

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

Changes 4
Bugs 1 Features 0
Metric Value
cc 0
wmc 74
c 4
b 1
f 0
nc 64
mnd 3
bc 71
fnc 41
dl 0
loc 489
rs 3.931
bpm 1.7317
cpm 1.8048
noi 3

31 Functions

Rating   Name   Duplication   Size   Complexity  
A BtccomConverter.getUrlForTransactions 0 3 1
A BtccomConverter.getUrlForAddress 0 3 1
A BtccomConverter.getUrlForAddressTransactions 0 3 1
A BtccomConverter.getUrlForAddressUnspent 0 3 1
A BtccomConverter.getUrlForBlockTransaction 0 3 1
A BtccomConverter.getUrlForRawTransaction 0 3 1
A btccom.convert.js ➔ prettifyAsm 0 7 2
A BtccomConverter.convertAddress 0 18 1
B btccom.convert.js ➔ convertBtccomOutputScriptType 0 20 9
A BtccomConverter.convertBlocks 0 10 1
A BtccomConverter.paginationParams 0 12 3
B btccom.convert.js ➔ getBase58AddressHash160 0 21 6
A BtccomConverter.convertTxs 0 12 1
A btccom.convert.js ➔ getScriptAsm 0 10 2
A btccom.convert.js ➔ flattenAddresses 0 9 3
D btccom.convert.js ➔ convertBtccomTxToBlocktrail 0 103 10
A btccom.convert.js ➔ getAddressScriptInfo 0 10 2
A btccom.convert.js ➔ utcTimestampToISODateStr 0 3 1
A BtccomConverter.convertAddressTxs 0 56 1
A BtccomConverter.getUrlForBatchAddressUnspent 0 3 1
A btccom.convert.js ➔ BtccomConverter 0 4 1
A BtccomConverter.handleErros 0 11 3
A BtccomConverter.getUrlForAllBlocks 0 3 1
A BtccomConverter.getUrlForBlock 0 3 1
B BtccomConverter.convertBatchAddressUnspentOutputs 0 32 1
A btccom.convert.js ➔ getType 0 10 2
B BtccomConverter.convertBlock 0 29 1
B BtccomConverter.convertAddressUnspentOutputs 0 24 1
A BtccomConverter.convertTx 0 5 1
A BtccomConverter.getUrlForTransaction 0 3 1
A BtccomConverter.convertBlockTxs 0 15 1

How to fix   Complexity   

Complexity

Complex classes like lib/btccom.convert.js often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

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