Completed
Pull Request — master (#99)
by Ruben de
01:00
created

lib/btccom.convert.js   D

Complexity

Total Complexity 67
Complexity/F 1.91

Size

Lines of Code 442
Function Count 35

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 0
wmc 67
c 1
b 0
f 0
nc 64
mnd 3
bc 65
fnc 35
dl 0
loc 442
rs 4.3333
bpm 1.8571
cpm 1.9142
noi 2

27 Functions

Rating   Name   Duplication   Size   Complexity  
A btccom.convert.js ➔ BtccomConverter 0 11 1
A btccom.convert.js ➔ prettifyAsm 0 7 2
A BtccomConverter.convertAddress 0 18 1
A BtccomConverter.convertTxs 0 12 1
A btccom.convert.js ➔ getScriptAsm 0 10 2
A btccom.convert.js ➔ getAddressScriptInfo 0 10 2
A BtccomConverter.convertAddressTxs 0 56 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.convertBlockTxs 0 16 1
B btccom.convert.js ➔ convertBtccomOutputScriptType 0 18 8
A BtccomConverter.paginationParams 0 12 3
B btccom.convert.js ➔ getBase58AddressHash160 0 21 6
A btccom.convert.js ➔ flattenAddresses 0 9 3
D btccom.convert.js ➔ convertBtccomTxToBlocktrail 0 102 10
A BtccomConverter.getUrlForTransactions 0 3 1
A btccom.convert.js ➔ utcTimestampToISODateStr 0 3 1
A BtccomConverter.getUrlForAddress 0 3 1
A BtccomConverter.getUrlForAddressTransactions 0 3 1
A BtccomConverter.getUrlForAddressUnspent 0 3 1
A BtccomConverter.handleErros 0 11 3
A BtccomConverter.getUrlForAllBlocks 0 3 1
A BtccomConverter.getUrlForBlock 0 3 1
A BtccomConverter.getUrlForTransaction 0 3 1
A BtccomConverter.getUrlForBlockTransaction 0 3 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
    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 === "") {
75
        return addressInfo.decoded.hash;
76
    }
77
78
    return null;
79
}
80
81
function convertBtccomOutputScriptType(scriptType) {
82
    switch (scriptType) {
83
        case "P2PKH_PUBKEY":
84
            return "pubkey";
85
        case "P2PKH":
86
            return "pubkeyhash";
87
        case "P2SH":
88
            return "scripthash";
89
        case "P2WSH_V0":
90
            return "witnessscripthash";
91
        case "P2WPKH":
92
            return "witnesspubkeyhash";
93
        case "coinbase":
94
            return "coinbase";
95
        default:
96
            throw new Error("Not implemented yet, script type: " + scriptType);
97
    }
98
}
99
100
function utcTimestampToISODateStr(time) {
101
    return (new Date(time * 1000)).toISOString().replace(/\.000Z$/, '+0000');
102
}
103
104
function flattenAddresses(addrs) {
105
    if (!addrs) {
106
        return addrs;
107
    } else if (addrs.length === 1) {
108
        return addrs[0];
109
    } else {
110
        return addrs;
111
    }
112
}
113
114
function convertBtccomTxToBlocktrail(tx) {
115
    /* jshint -W071, -W074 */
116
    var data = {};
117
118
    data.size = tx.vsize;
119
    data.hash = tx.hash;
120
    data.block_height = tx.block_height ;
121
    data.block_time = utcTimestampToISODateStr(tx.block_time);
122
    data.block_hash = null; // TODO: missing block_hash
123
    data.confirmations = tx.confirmations;
124
    data.is_coinbase = tx.is_coinbase;
125
126
    var totalInputValue;
127
    if (data.is_coinbase) {
128
        totalInputValue = tx.outputs[0].value - tx.fee;
129
    } else {
130
        totalInputValue = tx.inputs_value;
131
    }
132
133
    data.total_input_value = totalInputValue;
134
    data.total_output_value = tx.outputs.reduce(function(total, output) {
135
        return total + output.value;
136
    }, 0);
137
    data.total_fee = tx.fee;
138
    data.inputs = [];
139
    data.outputs = [];
140
    data.opt_in_rbf = false;
141
142
    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...
143
        var input = tx.inputs[inputIdx];
144
        var scriptType;
145
        var inputValue;
146
        var inputTxid;
147
        var outpointIdx;
148
149
        if (input.sequence < bitcoinJS.Transaction.DEFAULT_SEQUENCE - 1) {
150
            data.opt_in_rbf = true;
151
        }
152
153
        if (data.is_coinbase && input.prev_position === -1 &&
154
            input.prev_tx_hash === "0000000000000000000000000000000000000000000000000000000000000000") {
155
            scriptType = "coinbase";
156
            inputTxid = null;
157
            inputValue = totalInputValue;
158
            outpointIdx = 0xffffffff;
159
        } else {
160
            scriptType = input.prev_type;
161
            inputValue = input.prev_value;
162
            inputTxid = input.prev_tx_hash;
163
            outpointIdx = input.prev_position;
164
        }
165
166
        data.inputs.push({
167
            index: parseInt(inputIdx, 10),
168
            output_hash: inputTxid,
169
            output_index: outpointIdx,
170
            value: inputValue,
171
            sequence: input.sequence,
172
            address: flattenAddresses(input.prev_addresses),
173
            type: convertBtccomOutputScriptType(scriptType),
174
            script_signature: input.script_hex
175
        });
176
    }
177
178
    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...
179
        var output = tx.outputs[outIdx];
180
181
        data.outputs.push({
182
            index: parseInt(outIdx, 10),
183
            value: output.value,
184
            address: flattenAddresses(output.addresses),
185
            type: convertBtccomOutputScriptType(output.type),
186
            script: prettifyAsm(output.script_asm),
187
            script_hex: output.script_hex,
188
            spent_hash: output.spent_by_tx,
189
            spent_index: output.spent_by_tx_position
190
        });
191
    }
192
193
    data.size = tx.size;
194
    data.is_double_spend = tx.is_double_spend;
195
196
    data.lock_time_timestamp = null;
197
    data.lock_time_block_height = null;
198
    if (tx.lock_time) {
199
        if (tx.lock_time < blocktrail.LOCK_TIME_TIMESTAMP_THRESHOLD) {
200
            data.lock_time_block_height = tx.lock_time;
201
        } else {
202
            data.lock_time_timestamp = tx.lock_time;
203
        }
204
    }
205
206
    // Extra fields from Btc.com
207
    data.is_sw_tx = tx.is_sw_tx;
208
    data.weight = tx.weight;
209
    data.witness_hash = tx.witness_hash;
210
    data.lock_time  = tx.lock_time;
211
    data.sigops = tx.sigops;
212
    data.version = tx.version;
213
214
    return data;
215
}
216
217
BtccomConverter.prototype.paginationParams = function(params) {
218
    if (!params) {
219
        return params;
220
    }
221
222
    if (typeof params.limit !== "undefined") {
223
        params.pagesize = params.limit;
224
        delete params.limit;
225
    }
226
227
    return params;
228
};
229
230
BtccomConverter.prototype.getUrlForBlock = function(blockHash) {
231
    return "/block/" + blockHash;
232
};
233
234
BtccomConverter.prototype.getUrlForTransaction = function(txId) {
235
    return "/tx/" + txId + "?verbose=3";
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";
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.getUrlForAllBlocks = function() {
259
    return "/all-blocks";
260
};
261
262
BtccomConverter.prototype.handleErros = function(self, data) {
263
    if (data.err_no === 0 || data.data !== null) {
264
        return data;
265
    } else {
266
        return {
267
            err_no: data.err_no,
268
            err_msg: data.err_msg,
269
            data: data.data
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