btcOperator v1.1.0: updating API provider
updating API link to blockchain.info
This commit is contained in:
parent
c2b836c069
commit
c2a94e6246
156
btcOperator.js
156
btcOperator.js
@ -1,16 +1,16 @@
|
|||||||
(function (EXPORTS) { //btcOperator v1.0.14b
|
(function (EXPORTS) { //btcOperator v1.1.0
|
||||||
/* BTC Crypto and API Operator */
|
/* BTC Crypto and API Operator */
|
||||||
const btcOperator = EXPORTS;
|
const btcOperator = EXPORTS;
|
||||||
|
|
||||||
//This library uses API provided by chain.so (https://chain.so/)
|
//This library uses API provided by chain.so (https://chain.so/)
|
||||||
const URL = "https://chain.so/api/v2/";
|
const URL = "https://blockchain.info/";
|
||||||
|
|
||||||
const fetch_api = btcOperator.fetch = function (api) {
|
const fetch_api = btcOperator.fetch = function (api) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
console.debug(URL + api);
|
console.debug(URL + api);
|
||||||
fetch(URL + api).then(response => {
|
fetch(URL + api).then(response => {
|
||||||
response.json()
|
response.json()
|
||||||
.then(result => result.status === "success" ? resolve(result) : reject(result))
|
.then(result => result.error ? reject(result) : resolve(result))
|
||||||
.catch(error => reject(error))
|
.catch(error => reject(error))
|
||||||
}).catch(error => reject(error))
|
}).catch(error => reject(error))
|
||||||
})
|
})
|
||||||
@ -18,12 +18,17 @@
|
|||||||
|
|
||||||
const SATOSHI_IN_BTC = 1e8;
|
const SATOSHI_IN_BTC = 1e8;
|
||||||
|
|
||||||
|
const util = btcOperator.util = {};
|
||||||
|
|
||||||
|
util.Sat_to_BTC = value => parseFloat((value / SATOSHI_IN_BTC).toFixed(8));
|
||||||
|
util.BTC_to_Sat = value => parseInt(value * SATOSHI_IN_BTC);
|
||||||
|
|
||||||
function get_fee_rate() {
|
function get_fee_rate() {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
fetch('https://api.blockchain.info/mempool/fees').then(response => {
|
fetch('https://api.blockchain.info/mempool/fees').then(response => {
|
||||||
if (response.ok)
|
if (response.ok)
|
||||||
response.json()
|
response.json()
|
||||||
.then(result => resolve(parseFloat((result.regular / SATOSHI_IN_BTC).toFixed(8))))
|
.then(result => resolve(util.Sat_to_BTC(result.regular)))
|
||||||
.catch(error => reject(error));
|
.catch(error => reject(error));
|
||||||
else
|
else
|
||||||
reject(response);
|
reject(response);
|
||||||
@ -216,8 +221,8 @@
|
|||||||
//BTC blockchain APIs
|
//BTC blockchain APIs
|
||||||
|
|
||||||
btcOperator.getBalance = addr => new Promise((resolve, reject) => {
|
btcOperator.getBalance = addr => new Promise((resolve, reject) => {
|
||||||
fetch_api(`get_address_balance/BTC/${addr}`)
|
fetch_api(`q/addressbalance/${addr}`)
|
||||||
.then(result => resolve(parseFloat(result.data.confirmed_balance)))
|
.then(result => resolve(util.Sat_to_BTC(result)))
|
||||||
.catch(error => reject(error))
|
.catch(error => reject(error))
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -308,7 +313,7 @@
|
|||||||
parameters.privkeys[i] = coinjs.privkey2wif(key);
|
parameters.privkeys[i] = coinjs.privkey2wif(key);
|
||||||
});
|
});
|
||||||
if (invalids.length)
|
if (invalids.length)
|
||||||
throw "Invalid keys:" + invalids;
|
throw "Invalid private key for address:" + invalids;
|
||||||
}
|
}
|
||||||
//receiver-ids (and change-id)
|
//receiver-ids (and change-id)
|
||||||
if (!Array.isArray(parameters.receivers))
|
if (!Array.isArray(parameters.receivers))
|
||||||
@ -339,9 +344,9 @@
|
|||||||
let output_size = addOutputs(tx, receivers, amounts, change_address);
|
let output_size = addOutputs(tx, receivers, amounts, change_address);
|
||||||
addInputs(tx, senders, redeemScripts, total_amount, fee, output_size, fee_from_receiver).then(result => {
|
addInputs(tx, senders, redeemScripts, total_amount, fee, output_size, fee_from_receiver).then(result => {
|
||||||
if (result.change_amount > 0 && result.change_amount > result.fee) //add change amount if any (ignore dust change)
|
if (result.change_amount > 0 && result.change_amount > result.fee) //add change amount if any (ignore dust change)
|
||||||
tx.outs[tx.outs.length - 1].value = parseInt(result.change_amount * SATOSHI_IN_BTC); //values are in satoshi
|
tx.outs[tx.outs.length - 1].value = util.BTC_to_Sat(result.change_amount); //values are in satoshi
|
||||||
if (fee_from_receiver) { //deduce fee from receivers if fee_from_receiver
|
if (fee_from_receiver) { //deduce fee from receivers if fee_from_receiver
|
||||||
let fee_remaining = parseInt(result.fee * SATOSHI_IN_BTC);
|
let fee_remaining = util.BTC_to_Sat(result.fee);
|
||||||
for (let i = 0; i < tx.outs.length - 1 && fee_remaining > 0; i++) {
|
for (let i = 0; i < tx.outs.length - 1 && fee_remaining > 0; i++) {
|
||||||
if (fee_remaining < tx.outs[i].value) {
|
if (fee_remaining < tx.outs[i].value) {
|
||||||
tx.outs[i].value -= fee_remaining;
|
tx.outs[i].value -= fee_remaining;
|
||||||
@ -409,29 +414,29 @@
|
|||||||
rs = redeemScripts[rec_args.n];
|
rs = redeemScripts[rec_args.n];
|
||||||
let addr_type = coinjs.addressDecode(addr).type;
|
let addr_type = coinjs.addressDecode(addr).type;
|
||||||
let size_per_input = _sizePerInput(addr, rs);
|
let size_per_input = _sizePerInput(addr, rs);
|
||||||
fetch_api(`get_tx_unspent/BTC/${addr}`).then(result => {
|
fetch_api(`unspent?active=${addr}`).then(result => {
|
||||||
let utxos = result.data.txs;
|
let utxos = result.unspent_outputs;
|
||||||
console.debug("add-utxo", addr, rs, required_amount, utxos);
|
console.debug("add-utxo", addr, rs, required_amount, utxos);
|
||||||
for (let i = 0; i < utxos.length && required_amount > 0; i++) {
|
for (let i = 0; i < utxos.length && required_amount > 0; i++) {
|
||||||
if (!utxos[i].confirmations) //ignore unconfirmed utxo
|
if (!utxos[i].confirmations) //ignore unconfirmed utxo
|
||||||
continue;
|
continue;
|
||||||
var script;
|
var script;
|
||||||
if (!rs || !rs.length) //legacy script
|
if (!rs || !rs.length) //legacy script
|
||||||
script = utxos[i].script_hex;
|
script = utxos[i].script;
|
||||||
else if (((rs.match(/^00/) && rs.length == 44)) || (rs.length == 40 && rs.match(/^[a-f0-9]+$/gi)) || addr_type === 'multisigBech32') {
|
else if (((rs.match(/^00/) && rs.length == 44)) || (rs.length == 40 && rs.match(/^[a-f0-9]+$/gi)) || addr_type === 'multisigBech32') {
|
||||||
//redeemScript for segwit/bech32 and multisig (bech32)
|
//redeemScript for segwit/bech32 and multisig (bech32)
|
||||||
let s = coinjs.script();
|
let s = coinjs.script();
|
||||||
s.writeBytes(Crypto.util.hexToBytes(rs));
|
s.writeBytes(Crypto.util.hexToBytes(rs));
|
||||||
s.writeOp(0);
|
s.writeOp(0);
|
||||||
s.writeBytes(coinjs.numToBytes((utxos[i].value * SATOSHI_IN_BTC).toFixed(0), 8));
|
s.writeBytes(coinjs.numToBytes(utxos[i].value.toFixed(0), 8));
|
||||||
script = Crypto.util.bytesToHex(s.buffer);
|
script = Crypto.util.bytesToHex(s.buffer);
|
||||||
} else //redeemScript for multisig (segwit)
|
} else //redeemScript for multisig (segwit)
|
||||||
script = rs;
|
script = rs;
|
||||||
tx.addinput(utxos[i].txid, utxos[i].output_no, script, 0xfffffffd /*sequence*/); //0xfffffffd for Replace-by-fee
|
tx.addinput(utxos[i].tx_hash_big_endian, utxos[i].tx_output_n, script, 0xfffffffd /*sequence*/); //0xfffffffd for Replace-by-fee
|
||||||
//update track values
|
//update track values
|
||||||
rec_args.input_size += size_per_input;
|
rec_args.input_size += size_per_input;
|
||||||
rec_args.input_amount += parseFloat(utxos[i].value);
|
rec_args.input_amount += util.Sat_to_BTC(utxos[i].value);
|
||||||
required_amount -= parseFloat(utxos[i].value);
|
required_amount -= util.Sat_to_BTC(utxos[i].value);
|
||||||
if (fee_rate) //automatic fee calculation (dynamic)
|
if (fee_rate) //automatic fee calculation (dynamic)
|
||||||
required_amount += size_per_input * fee_rate;
|
required_amount += size_per_input * fee_rate;
|
||||||
}
|
}
|
||||||
@ -670,8 +675,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
const getTxOutput = (txid, i) => new Promise((resolve, reject) => {
|
const getTxOutput = (txid, i) => new Promise((resolve, reject) => {
|
||||||
fetch_api(`get_tx_outputs/BTC/${txid}/${i}`)
|
fetch_api(`rawtx/${txid}`)
|
||||||
.then(result => resolve(result.data.outputs))
|
.then(result => resolve(result.out[i]))
|
||||||
.catch(error => reject(error))
|
.catch(error => reject(error))
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -685,8 +690,8 @@
|
|||||||
promises.push(getTxOutput(tx.ins[i].outpoint.hash, tx.ins[i].outpoint.index));
|
promises.push(getTxOutput(tx.ins[i].outpoint.hash, tx.ins[i].outpoint.index));
|
||||||
Promise.all(promises).then(inputs => {
|
Promise.all(promises).then(inputs => {
|
||||||
result.inputs = inputs.map(inp => Object({
|
result.inputs = inputs.map(inp => Object({
|
||||||
address: inp.address,
|
address: inp.addr,
|
||||||
value: parseFloat(inp.value)
|
value: util.Sat_to_BTC(inp.value)
|
||||||
}));
|
}));
|
||||||
let signed = checkSigned(tx, false);
|
let signed = checkSigned(tx, false);
|
||||||
result.inputs.forEach((inp, i) => inp.signed = signed[i]);
|
result.inputs.forEach((inp, i) => inp.signed = signed[i]);
|
||||||
@ -705,7 +710,7 @@
|
|||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
address,
|
address,
|
||||||
value: parseFloat(out.value / SATOSHI_IN_BTC)
|
value: util.Sat_to_BTC(out.value)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
//Parse Totals
|
//Parse Totals
|
||||||
@ -726,22 +731,111 @@
|
|||||||
return Crypto.util.bytesToHex(txid);
|
return Crypto.util.bytesToHex(txid);
|
||||||
}
|
}
|
||||||
|
|
||||||
btcOperator.getTx = txid => new Promise((resolve, reject) => {
|
const getLatestBlock = btcOperator.getLatestBlock = () => new Promise((resolve, reject) => {
|
||||||
fetch_api(`get_tx/BTC/${txid}`)
|
fetch_api(`q/getblockcount`)
|
||||||
.then(result => resolve(result.data))
|
.then(result => resolve(result))
|
||||||
.catch(error => reject(error))
|
.catch(error => reject(error))
|
||||||
|
})
|
||||||
|
|
||||||
|
btcOperator.getTx = txid => new Promise((resolve, reject) => {
|
||||||
|
fetch_api(`rawtx/${txid}`).then(result => {
|
||||||
|
getLatestBlock().then(latest_block => {
|
||||||
|
resolve({
|
||||||
|
block: result.block_height,
|
||||||
|
txid: result.hash,
|
||||||
|
time: result.time * 1000,
|
||||||
|
confirmations: latest_block - result.block_height, //calculate confirmations using latest block number as api doesnt relay it
|
||||||
|
size: result.size,
|
||||||
|
fee: util.Sat_to_BTC(result.fee),
|
||||||
|
inputs: result.inputs.map(i => Object({ address: i.prev_out.addr, value: util.Sat_to_BTC(i.prev_out.value) })),
|
||||||
|
total_input_value: util.Sat_to_BTC(result.inputs.reduce((a, i) => a + i.prev_out.value, 0)),
|
||||||
|
outputs: result.out.map(o => Object({ address: o.addr, value: util.Sat_to_BTC(o.value) })),
|
||||||
|
total_output_value: util.Sat_to_BTC(result.out.reduce((a, o) => a += o.value, 0)),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}).catch(error => reject(error))
|
||||||
});
|
});
|
||||||
|
|
||||||
btcOperator.getAddressData = addr => new Promise((resolve, reject) => {
|
btcOperator.getAddressData = address => new Promise((resolve, reject) => {
|
||||||
fetch_api(`address/BTC/${addr}`)
|
fetch_api(`rawaddr/${address}`).then(data => {
|
||||||
.then(result => resolve(result.data))
|
let details = {};
|
||||||
.catch(error => reject(error))
|
details.balance = util.Sat_to_BTC(data.final_balance);
|
||||||
|
details.address = data.address;
|
||||||
|
details.txs = data.txs.map(tx => {
|
||||||
|
let d = {
|
||||||
|
txid: tx.hash,
|
||||||
|
time: tx.time * 1000, //s to ms
|
||||||
|
block: tx.block_height,
|
||||||
|
}
|
||||||
|
//sender list
|
||||||
|
d.tx_senders = {};
|
||||||
|
tx.inputs.forEach(i => {
|
||||||
|
if (i.prev_out.addr in d.tx_senders)
|
||||||
|
d.tx_senders[i.prev_out.addr] += i.prev_out.value;
|
||||||
|
else d.tx_senders[i.prev_out.addr] = i.prev_out.value;
|
||||||
|
});
|
||||||
|
d.tx_input_value = 0;
|
||||||
|
for (let s in d.tx_senders) {
|
||||||
|
let val = d.tx_senders[s];
|
||||||
|
d.tx_senders[s] = util.Sat_to_BTC(val);
|
||||||
|
d.tx_input_value += val;
|
||||||
|
}
|
||||||
|
d.tx_input_value = util.Sat_to_BTC(d.tx_input_value);
|
||||||
|
//receiver list
|
||||||
|
d.tx_receivers = {};
|
||||||
|
tx.out.forEach(o => {
|
||||||
|
if (o.addr in d.tx_receivers)
|
||||||
|
d.tx_receivers[o.addr] += o.value;
|
||||||
|
else d.tx_receivers[o.addr] = o.value;
|
||||||
|
});
|
||||||
|
d.tx_output_value = 0;
|
||||||
|
for (let r in d.tx_receivers) {
|
||||||
|
let val = d.tx_receivers[r];
|
||||||
|
d.tx_receivers[r] = util.Sat_to_BTC(val);
|
||||||
|
d.tx_output_value += val;
|
||||||
|
}
|
||||||
|
d.tx_output_value = util.Sat_to_BTC(d.tx_output_value);
|
||||||
|
d.tx_fee = util.Sat_to_BTC(tx.fee);
|
||||||
|
//tx type
|
||||||
|
if (tx.result > 0) { //net > 0, balance inc => type=in
|
||||||
|
d.type = "in";
|
||||||
|
d.amount = util.Sat_to_BTC(tx.result);
|
||||||
|
d.sender = Object.keys(d.tx_senders).filter(s => s !== address);
|
||||||
|
} else if (Object.keys(d.tx_receivers).some(r => r !== address)) { //net < 0, balance dec & receiver present => type=out
|
||||||
|
d.type = "out";
|
||||||
|
d.amount = util.Sat_to_BTC(tx.result * -1);
|
||||||
|
d.receiver = Object.keys(d.tx_receivers).filter(r => r !== address);
|
||||||
|
d.fee = d.tx_fee;
|
||||||
|
} else { //net < 0 (fee) & no other id in receiver list => type=self
|
||||||
|
d.type = "self";
|
||||||
|
d.amount = d.tx_receivers[address];
|
||||||
|
d.address = address
|
||||||
|
}
|
||||||
|
return d;
|
||||||
|
})
|
||||||
|
resolve(details);
|
||||||
|
}).catch(error => reject(error))
|
||||||
});
|
});
|
||||||
|
|
||||||
btcOperator.getBlock = block => new Promise((resolve, reject) => {
|
btcOperator.getBlock = block => new Promise((resolve, reject) => {
|
||||||
fetch_api(`get_block/BTC/${block}`)
|
fetch_api(`rawblock/${block}`).then(result => resolve({
|
||||||
.then(result => resolve(result.data))
|
height: result.height,
|
||||||
.catch(error => reject(error))
|
hash: result.hash,
|
||||||
|
merkle_root: result.mrkl_root,
|
||||||
|
prev_block: result.prev_block,
|
||||||
|
next_block: result.next_block[0],
|
||||||
|
size: result.size,
|
||||||
|
time: result.time * 1000, //s to ms
|
||||||
|
txs: result.tx.map(t => Object({
|
||||||
|
fee: t.fee,
|
||||||
|
size: t.size,
|
||||||
|
inputs: t.inputs.map(i => Object({ address: i.prev_out.addr, value: util.Sat_to_BTC(i.prev_out.value) })),
|
||||||
|
total_input_value: util.Sat_to_BTC(t.inputs.reduce((a, i) => a + i.prev_out.value, 0)),
|
||||||
|
outputs: t.out.map(o => Object({ address: o.addr, value: util.Sat_to_BTC(o.value) })),
|
||||||
|
total_output_value: util.Sat_to_BTC(t.out.reduce((a, o) => a += o.value, 0)),
|
||||||
|
}))
|
||||||
|
|
||||||
|
})).catch(error => reject(error))
|
||||||
});
|
});
|
||||||
|
|
||||||
})('object' === typeof module ? module.exports : window.btcOperator = {});
|
})('object' === typeof module ? module.exports : window.btcOperator = {});
|
||||||
Loading…
Reference in New Issue
Block a user