commit
334bf46fa1
85
index.html
85
index.html
@ -69,17 +69,26 @@
|
||||
<div class="panel-heading">Send Transaction</div>
|
||||
<div class="panel-body">
|
||||
<form id="send-tx">
|
||||
<div class="form-group">
|
||||
<template class="sender-template">
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon"><i>Sender</i></span>
|
||||
<input type="text" class="form-control" name="sender" placeholder="Sender ID">
|
||||
<span class="input-group-addon"><i>Balance</i></span>
|
||||
<input type="text" class="form-control" name="balance" placeholder="Balance" disabled>
|
||||
<div class="input-group-btn">
|
||||
<button class="btn btn-default" name="rm-sender" type="button">X</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<div class="form-group">
|
||||
<div class="sender-container"></div>
|
||||
<div class="input-group">
|
||||
<div class="input-group-btn">
|
||||
<button class="btn btn-default" name="check-balance" type="button">Balance</button>
|
||||
<button class="btn btn-default" name="check-balance" type="button">Check Balance</button>
|
||||
</div>
|
||||
<input type="number" class="form-control" name="balance" placeholder="Check Balance" disabled>
|
||||
<input type="number" class="form-control" name="total_balance" placeholder="Total Balance" disabled>
|
||||
</div>
|
||||
<button type="button" name="add-sender" class="btn btn-default"><b>+</b></button>
|
||||
</div>
|
||||
<template class="receiver-template">
|
||||
<div class="input-group">
|
||||
@ -110,7 +119,7 @@
|
||||
<form id="address-details">
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon"><i>Address</i></span>
|
||||
<input type="text" class="form-control" name="address" placeholder="Address">
|
||||
<input type="text" class="form-control" name="addr" placeholder="Address">
|
||||
<div class="input-group-btn">
|
||||
<button class="btn btn-default" name="submit" type="button"><b class="glyphicon glyphicon-search"></b></button>
|
||||
</div>
|
||||
@ -190,13 +199,34 @@
|
||||
}
|
||||
|
||||
let sendForm = document.forms['send-tx'];
|
||||
let sender_template = sendForm.getElementsByClassName('sender-template')[0],
|
||||
sender_container = sendForm.getElementsByClassName('sender-container')[0];
|
||||
sendForm['add-sender'].onclick = evt => {
|
||||
sender_container.appendChild(sender_template.content.cloneNode(true));
|
||||
let clone = sender_container.lastElementChild;
|
||||
clone.getElementsByTagName("button")[0].onclick = evt => sender_container.removeChild(clone);
|
||||
};
|
||||
sendForm['add-sender'].click();
|
||||
sendForm['check-balance'].onclick = evt => {
|
||||
let address = sendForm["sender"].value;
|
||||
sendForm['check-balance'].disabled = true;
|
||||
btc_api.getBalance(address)
|
||||
.then(result => sendForm["balance"].value = result)
|
||||
.catch(error => console.error(error))
|
||||
.finally(_ => sendForm['check-balance'].disabled = false)
|
||||
sendForm["total_balance"].value = 0;
|
||||
let addresses = [],
|
||||
total_balance = 0;
|
||||
if (!sendForm["sender"])
|
||||
return;
|
||||
else if (sendForm["sender"] instanceof RadioNodeList)
|
||||
for (let i = 0; i < sendForm["sender"].length; i++)
|
||||
addresses.push(sendForm["sender"][i].value);
|
||||
else
|
||||
addresses.push(sendForm["sender"].value);
|
||||
console.debug(addresses);
|
||||
let balance_inputs = sendForm["balance"] instanceof RadioNodeList ? sendForm["balance"] : [sendForm["balance"]];
|
||||
//sendForm['check-balance'].disabled = true;
|
||||
addresses.forEach((addr, i) => btc_api.getBalance(addr).then(result => {
|
||||
console.debug(addr, result)
|
||||
balance_inputs[i].value = result;
|
||||
sendForm["total_balance"].value = parseFloat(sendForm["total_balance"].value) + result;
|
||||
}).catch(error => console.error(error)))
|
||||
// .finally(_ => sendForm['check-balance'].disabled = false)
|
||||
}
|
||||
let receiver_template = sendForm.getElementsByClassName('receiver-template')[0],
|
||||
receiver_container = sendForm.getElementsByClassName('receiver-container')[0];
|
||||
@ -207,19 +237,36 @@
|
||||
};
|
||||
sendForm['add-receiver'].click();
|
||||
sendForm['submit'].onclick = evt => {
|
||||
let sender = sendForm["sender"].value,
|
||||
receivers = {},
|
||||
let senders = [],
|
||||
receivers = [],
|
||||
amounts = [],
|
||||
fee = parseFloat(sendForm["fee"].value);
|
||||
//inputs (senders)
|
||||
if (!sendForm["sender"])
|
||||
return console.warn("sender cannot be empty");
|
||||
else if (sendForm["sender"] instanceof RadioNodeList)
|
||||
for (let i = 0; i < sendForm["sender"].length; i++) {
|
||||
senders.push(sendForm["sender"][i].value);
|
||||
}
|
||||
else {
|
||||
senders.push(sendForm["sender"].value);
|
||||
}
|
||||
//outputs (receivers and amounts)
|
||||
if (!sendForm["receiver"])
|
||||
return console.warn("receiver cannot be empty");
|
||||
else if (sendForm["receiver"] instanceof RadioNodeList)
|
||||
for (let i = 0; i < sendForm["receiver"].length; i++)
|
||||
receivers[sendForm["receiver"][i].value] = parseFloat(sendForm["amount"][i].value);
|
||||
else
|
||||
receivers[sendForm["receiver"].value] = parseFloat(sendForm["amount"].value);
|
||||
let privKey = prompt("Enter Private Key:");
|
||||
for (let i = 0; i < sendForm["receiver"].length; i++) {
|
||||
receivers.push(sendForm["receiver"][i].value);
|
||||
amounts.push(parseFloat(sendForm["amount"][i].value));
|
||||
}
|
||||
else {
|
||||
receivers.push(sendForm["receiver"].value);
|
||||
amounts.push(parseFloat(sendForm["amount"].value));
|
||||
}
|
||||
console.debug(senders, receivers, amounts, fee);
|
||||
let privkeys = senders.map(s => prompt(`Enter Private Key for ${s}:`))
|
||||
sendForm['submit'].disabled = true;
|
||||
btc_api.sendTx(sender, privKey, receivers, fee).then(result => {
|
||||
btc_api.sendTx(senders, privkeys, receivers, amounts, fee).then(result => {
|
||||
console.log(result);
|
||||
alert("transaction id: " + result.txid);
|
||||
}).catch(error => console.error(error)).finally(_ => sendForm['submit'].disabled = false)
|
||||
@ -228,7 +275,7 @@
|
||||
let detailsForm = document.forms['address-details'];
|
||||
detailsForm['submit'].onclick = evt => {
|
||||
detailsForm['submit'].disabled = true;
|
||||
let address = detailsForm['address'].value;
|
||||
let address = detailsForm['addr'].value;
|
||||
let table = document.getElementById("view-details");
|
||||
table.innerHTML = '';
|
||||
detailsForm['balance'].value = '';
|
||||
|
||||
194
lib_btc.js
194
lib_btc.js
@ -1,4 +1,4 @@
|
||||
(function(GLOBAL) { //lib_btc v1.0.0
|
||||
(function(GLOBAL) { //lib_btc v1.0.1
|
||||
/* Utility Libraries required
|
||||
* All credits for these codes belong to their respective creators, moderators and owners.
|
||||
* For more info (including license and terms of use), please visit respective source.
|
||||
@ -1799,7 +1799,7 @@
|
||||
};
|
||||
} else if (this.ins[index].script.chunks[0] == 0 && this.ins[index].script.chunks[this.ins[index].script.chunks.length - 1][this.ins[index].script.chunks[this.ins[index].script.chunks.length - 1].length - 1] == 174) { // OP_CHECKMULTISIG
|
||||
// multisig script, with signature(s) included
|
||||
sigcount = 0;
|
||||
var sigcount = 0;
|
||||
for (i = 1; i < this.ins[index].script.chunks.length - 1; i++) {
|
||||
if (this.ins[index].script.chunks[i] != 0) {
|
||||
sigcount++;
|
||||
@ -1822,10 +1822,13 @@
|
||||
};
|
||||
} else if (this.ins[index].script.chunks.length == 0) {
|
||||
// empty
|
||||
//bech32 witness check
|
||||
var signed = ((this.witness[index]) && this.witness[index].length == 2) ? 'true' : 'false';
|
||||
var sigs = (signed == 'true') ? 1 : 0;
|
||||
return {
|
||||
'type': 'empty',
|
||||
'signed': 'false',
|
||||
'signatures': 0,
|
||||
'signed': signed,
|
||||
'signatures': sigs,
|
||||
'script': ''
|
||||
};
|
||||
} else {
|
||||
@ -2077,16 +2080,23 @@
|
||||
this.ins[index].script = script;
|
||||
|
||||
if (!coinjs.isArray(this.witness)) {
|
||||
this.witness = [];
|
||||
this.witness = new Array(this.ins.length);
|
||||
this.witness.fill([]);
|
||||
}
|
||||
|
||||
this.witness.push([signature, wif2['pubkey']]);
|
||||
this.witness[index] = ([signature, wif2['pubkey']]);
|
||||
|
||||
// bech32, empty redeemscript
|
||||
if (bech32['redeemscript'] == Crypto.util.bytesToHex(this.ins[index].script.chunks[0])) {
|
||||
this.ins[index].script = coinjs.script();
|
||||
}
|
||||
|
||||
/* attempt to reorder witness data as best as we can.
|
||||
data can't be easily validated at this stage as
|
||||
we dont have access to the inputs value and
|
||||
making a web call will be too slow. */
|
||||
|
||||
/*
|
||||
var witness_order = [];
|
||||
var witness_used = [];
|
||||
for (var i = 0; i < this.ins.length; i++) {
|
||||
@ -2117,6 +2127,7 @@
|
||||
}
|
||||
|
||||
this.witness = witness_order;
|
||||
*/
|
||||
}
|
||||
}
|
||||
return true;
|
||||
@ -2261,12 +2272,12 @@
|
||||
for (i = 0; i < ins; ++i) {
|
||||
var count = readVarInt();
|
||||
var vector = [];
|
||||
if (!coinjs.isArray(obj.witness[i])) {
|
||||
obj.witness[i] = [];
|
||||
}
|
||||
for (var y = 0; y < count; y++) {
|
||||
var slice = readVarInt();
|
||||
pos += slice;
|
||||
if (!coinjs.isArray(obj.witness[i])) {
|
||||
obj.witness[i] = [];
|
||||
}
|
||||
obj.witness[i].push(Crypto.util.bytesToHex(buffer.slice(pos - slice, pos)));
|
||||
}
|
||||
}
|
||||
@ -2549,7 +2560,7 @@
|
||||
})();
|
||||
})(typeof global !== "undefined" ? global : window);
|
||||
|
||||
(function(EXPORTS) { //btc_api v1.0.4b
|
||||
(function(EXPORTS) { //btc_api v1.0.5b
|
||||
const btc_api = EXPORTS;
|
||||
|
||||
const URL = "https://chain.so/api/v2/";
|
||||
@ -2618,15 +2629,23 @@
|
||||
}
|
||||
}
|
||||
|
||||
const getUTXO = addr => fetch_api(`get_tx_unspent/BTC/${addr}`);
|
||||
const validateAddress = btc_api.validateAddress = function(addr) {
|
||||
if (!addr)
|
||||
return undefined;
|
||||
let type = coinjs.addressDecode(addr).type;
|
||||
if (["standard", "multisig", "bech32"].includes(type))
|
||||
return type;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
const getBalance = btc_api.getBalance = addr => new Promise((resolve, reject) => {
|
||||
btc_api.getBalance = addr => new Promise((resolve, reject) => {
|
||||
fetch_api(`get_address_balance/BTC/${addr}`)
|
||||
.then(result => resolve(parseFloat(result.data.confirmed_balance)))
|
||||
.catch(error => reject(error))
|
||||
});
|
||||
|
||||
function getRedeemScript(addr, key) {
|
||||
function _redeemScript(addr, key) {
|
||||
let decode = coinjs.addressDecode(addr);
|
||||
switch (decode.type) {
|
||||
case "standard":
|
||||
@ -2640,55 +2659,110 @@
|
||||
}
|
||||
}
|
||||
|
||||
btc_api.sendTx = function(senderID, senderPrivKey, receivers, fee) {
|
||||
function addUTXOs(tx, senders, redeemScripts, required_amount, n = 0) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!verifyKey(senderID, senderPrivKey))
|
||||
return reject("Invalid privateKey");
|
||||
if (senderPrivKey.length === 64) //convert Hex to WIF if needed
|
||||
senderPrivKey = coinjs.privkey2wif(key);
|
||||
let redeemScript = getRedeemScript(senderID, senderPrivKey);
|
||||
getBalance(senderID).then(balance => {
|
||||
let total_amount = 0;
|
||||
for (let r in receivers)
|
||||
total_amount += receivers[r];
|
||||
total_amount = parseFloat(total_amount.toFixed(8));
|
||||
if (total_amount < fee || total_amount <= 0)
|
||||
return reject("Invalid receiver amount");
|
||||
if (balance < total_amount + fee)
|
||||
required_amount = parseFloat(required_amount.toFixed(8));
|
||||
if (required_amount <= 0 || n >= senders.length)
|
||||
return resolve(required_amount);
|
||||
let addr = senders[n],
|
||||
rs = redeemScripts[n];
|
||||
fetch_api(`get_tx_unspent/BTC/${addr}`).then(result => {
|
||||
let utxos = result.data.txs;
|
||||
console.debug("add-utxo", addr, rs, required_amount, utxos);
|
||||
for (let i = 0; i < utxos.length && required_amount > 0; i++) {
|
||||
if (!utxos[i].confirmations) //ignore unconfirmed utxo
|
||||
continue;
|
||||
required_amount -= parseFloat(utxos[i].value);
|
||||
var script;
|
||||
if (rs) { //redeemScript for segwit/bech32
|
||||
let s = coinjs.script();
|
||||
s.writeBytes(Crypto.util.hexToBytes(rs));
|
||||
s.writeOp(0);
|
||||
s.writeBytes(coinjs.numToBytes((utxos[i].value * 100000000).toFixed(0), 8));
|
||||
script = Crypto.util.bytesToHex(s.buffer);
|
||||
} else //legacy script
|
||||
script = utxos[i].script_hex;
|
||||
tx.addinput(utxos[i].txid, utxos[i].output_no, script, 0xfffffffd /*sequence*/ ); //0xfffffffd for Replace-by-fee
|
||||
}
|
||||
addUTXOs(tx, senders, redeemScripts, required_amount, n + 1)
|
||||
.then(result => resolve(result))
|
||||
.catch(error => reject(error))
|
||||
}).catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
|
||||
btc_api.sendTx = function(senders, privkeys, receivers, amounts, fee, change_addr = null) {
|
||||
return new Promise((resolve, reject) => {
|
||||
//Add values into array (if single values are passed)
|
||||
if (!Array.isArray(senders))
|
||||
senders = [senders];
|
||||
if (!Array.isArray(privkeys))
|
||||
privkeys = [privkeys];
|
||||
if (!Array.isArray(receivers))
|
||||
receivers = [receivers];
|
||||
if (!Array.isArray(amounts))
|
||||
amounts = [amounts];
|
||||
|
||||
let invalids = [];
|
||||
//validate tx-input parameters
|
||||
if (senders.length != privkeys.length)
|
||||
return reject("Array length for senders and privkeys should be equal");
|
||||
const redeemScripts = [],
|
||||
wif_keys = [];
|
||||
for (let i in senders) {
|
||||
if (!verifyKey(senders[i], privkeys[i])) //verify private-key
|
||||
invalids.push(senders[i]);
|
||||
if (privkeys[i].length === 64) //convert Hex to WIF if needed
|
||||
privkeys[i] = coinjs.privkey2wif(privkeys[i]);
|
||||
let rs = _redeemScript(senders[i], privkeys[i]); //get redeem-script (segwit/bech32)
|
||||
redeemScripts.push(rs);
|
||||
rs === false ? wif_keys.unshift(privkeys[i]) : wif_keys.push(privkeys[i]); //sorting private-keys (wif)
|
||||
}
|
||||
if (invalids.length)
|
||||
return reject("Invalid keys:" + invalids);
|
||||
if (typeof fee !== "number" || fee <= 0)
|
||||
return reject("Invalid fee:" + fee);
|
||||
|
||||
//validate tx-output parameters
|
||||
if (receivers.length != amounts.length)
|
||||
return reject("Array length for receivers and amounts should be equal");
|
||||
let total_amount = 0;
|
||||
for (let i in receivers)
|
||||
if (!validateAddress(receivers[i]))
|
||||
invalids.push(receivers[i]);
|
||||
if (invalids.length)
|
||||
return reject("Invalid receivers:" + invalids);
|
||||
for (let i in amounts) {
|
||||
if (typeof amounts[i] !== "number" || amounts[i] <= 0)
|
||||
invalids.push(amounts[i]);
|
||||
else
|
||||
total_amount += amounts[i];
|
||||
}
|
||||
if (invalids.length)
|
||||
return reject("Invalid amounts:" + invalids);
|
||||
if (change_addr && !validateAddress(change_addr))
|
||||
return reject("Invalid change_address:" + change_addr);
|
||||
|
||||
//create transaction
|
||||
var tx = coinjs.transaction();
|
||||
total_amount = parseFloat(total_amount.toFixed(8));
|
||||
addUTXOs(tx, senders, redeemScripts, total_amount + fee).then(result => {
|
||||
if (result > 0)
|
||||
return reject("Insufficient Balance");
|
||||
var tx = coinjs.transaction();
|
||||
getUTXO(senderID).then(result => {
|
||||
let utxos = result.data.txs;
|
||||
console.debug(balance, utxos);
|
||||
var input_total = 0;
|
||||
for (let i = 0; i < utxos.length && input_total < total_amount + fee; i++) {
|
||||
input_total += parseFloat(utxos[i].value);
|
||||
let script = utxos[i].script_hex;
|
||||
if (redeemScript) { //redeemScript for segwit/bech32
|
||||
let s = coinjs.script();
|
||||
s.writeBytes(Crypto.util.hexToBytes(redeemScript));
|
||||
s.writeOp(0);
|
||||
s.writeBytes(coinjs.numToBytes((utxos[i].value * 100000000).toFixed(0), 8));
|
||||
script = Crypto.util.bytesToHex(s.buffer);
|
||||
}
|
||||
tx.addinput(utxos[i].txid, utxos[i].output_no, script, 0xfffffffd /*sequence*/ );
|
||||
}
|
||||
if (input_total < total_amount + fee)
|
||||
return reject("Insufficient Balance (UTXO)")
|
||||
for (let r in receivers)
|
||||
tx.addoutput(r, receivers[r]);
|
||||
let change = parseFloat((input_total - (total_amount + fee)).toFixed(8));
|
||||
if (change)
|
||||
tx.addoutput(senderID, change);
|
||||
console.debug(input_total, total_amount, fee, change);
|
||||
console.debug("Unsigned:", tx.serialize());
|
||||
tx.sign(senderPrivKey, 1 /*sighashtype*/ ); //Sign the tx using private key WIF
|
||||
console.debug("Signed:", tx.serialize());
|
||||
debugger;
|
||||
broadcast(tx.serialize())
|
||||
.then(result => resolve(result))
|
||||
.catch(error => reject(error));
|
||||
}).catch(error => reject(error))
|
||||
for (let i in receivers)
|
||||
tx.addoutput(receivers[i], amounts[i]);
|
||||
let change = parseFloat(Math.abs(result).toFixed(8));
|
||||
if (change > 0)
|
||||
tx.addoutput(change_addr || senders[0], change);
|
||||
console.debug("amounts (total, fee, change):", total_amount, fee, change);
|
||||
console.debug("Unsigned:", tx.serialize());
|
||||
new Set(wif_keys).forEach(key => console.debug("Signing key:", key, tx.sign(key, 1 /*sighashtype*/ ))); //Sign the tx using private key WIF
|
||||
|
||||
console.debug("Signed:", tx.serialize());
|
||||
debugger;
|
||||
broadcast(tx.serialize())
|
||||
.then(result => resolve(result))
|
||||
.catch(error => reject(error));
|
||||
}).catch(error => reject(error))
|
||||
})
|
||||
};
|
||||
|
||||
Loading…
Reference in New Issue
Block a user