Update stdops
- Added option parameter to multisig.createTx in messenger module. option values {} for btcOperator.createMultiSigTx {fee_from_receiver, change_address}
This commit is contained in:
parent
59b0744b3f
commit
1e9c414bad
@ -1,4 +1,4 @@
|
|||||||
(function (EXPORTS) { //btcOperator v1.0.8
|
(function (EXPORTS) { //btcOperator v1.0.13b
|
||||||
/* BTC Crypto and API Operator */
|
/* BTC Crypto and API Operator */
|
||||||
const btcOperator = EXPORTS;
|
const btcOperator = EXPORTS;
|
||||||
|
|
||||||
@ -16,14 +16,14 @@
|
|||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
const SIGN_SIZE = 73;
|
const SATOSHI_IN_BTC = 1e8;
|
||||||
|
|
||||||
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(result.regular))
|
.then(result => resolve(parseFloat((result.regular / SATOSHI_IN_BTC).toFixed(8))))
|
||||||
.catch(error => reject(error));
|
.catch(error => reject(error));
|
||||||
else
|
else
|
||||||
reject(response);
|
reject(response);
|
||||||
@ -31,17 +31,31 @@
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const broadcast = btcOperator.broadcast = rawtx => new Promise((resolve, reject) => {
|
const broadcastTx = btcOperator.broadcastTx = rawTxHex => new Promise((resolve, reject) => {
|
||||||
$.ajax({
|
let url = 'https://coinb.in/api/?uid=1&key=12345678901234567890123456789012&setmodule=bitcoin&request=sendrawtransaction';
|
||||||
type: "POST",
|
fetch(url, {
|
||||||
url: URL + "send_tx/BTC/",
|
method: 'POST',
|
||||||
data: {
|
headers: {
|
||||||
"tx_hex": rawtx
|
'Content-Type': 'application/x-www-form-urlencoded'
|
||||||
},
|
},
|
||||||
dataType: "json",
|
body: "rawtx=" + rawTxHex
|
||||||
error: e => reject(e.responseJSON),
|
}).then(response => {
|
||||||
success: r => r.status === "success" ? resolve(r.data) : reject(r)
|
response.text().then(resultText => {
|
||||||
})
|
let r = resultText.match(/<result>.*<\/result>/);
|
||||||
|
if (!r)
|
||||||
|
reject(resultText);
|
||||||
|
else {
|
||||||
|
r = r.pop().replace('<result>', '').replace('</result>', '');
|
||||||
|
if (r == '1') {
|
||||||
|
let txid = resultText.match(/<txid>.*<\/txid>/).pop().replace('<txid>', '').replace('</txid>', '');
|
||||||
|
resolve(txid);
|
||||||
|
} else if (r == '0') {
|
||||||
|
let error = resultText.match(/<response>.*<\/response>/).pop().replace('<response>', '').replace('</response>', '');
|
||||||
|
reject(decodeURIComponent(error.replace(/\+/g, " ")));
|
||||||
|
} else reject(resultText);
|
||||||
|
}
|
||||||
|
}).catch(error => reject(error))
|
||||||
|
}).catch(error => reject(error))
|
||||||
});
|
});
|
||||||
|
|
||||||
Object.defineProperties(btcOperator, {
|
Object.defineProperties(btcOperator, {
|
||||||
@ -204,6 +218,17 @@
|
|||||||
.catch(error => reject(error))
|
.catch(error => reject(error))
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const BASE_TX_SIZE = 12,
|
||||||
|
BASE_INPUT_SIZE = 41,
|
||||||
|
LEGACY_INPUT_SIZE = 107,
|
||||||
|
BECH32_INPUT_SIZE = 27,
|
||||||
|
SEGWIT_INPUT_SIZE = 59,
|
||||||
|
MULTISIG_INPUT_SIZE_ES = 351,
|
||||||
|
BASE_OUTPUT_SIZE = 9,
|
||||||
|
LEGACY_OUTPUT_SIZE = 25,
|
||||||
|
BECH32_OUTPUT_SIZE = 23,
|
||||||
|
SEGWIT_OUTPUT_SIZE = 23;
|
||||||
|
|
||||||
function _redeemScript(addr, key) {
|
function _redeemScript(addr, key) {
|
||||||
let decode = coinjs.addressDecode(addr);
|
let decode = coinjs.addressDecode(addr);
|
||||||
switch (decode.type) {
|
switch (decode.type) {
|
||||||
@ -218,6 +243,39 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function _sizePerInput(addr, rs) {
|
||||||
|
switch (coinjs.addressDecode(addr).type) {
|
||||||
|
case "standard":
|
||||||
|
return BASE_INPUT_SIZE + LEGACY_INPUT_SIZE;
|
||||||
|
case "bech32":
|
||||||
|
return BASE_INPUT_SIZE + BECH32_INPUT_SIZE;
|
||||||
|
case "multisig":
|
||||||
|
switch (coinjs.script().decodeRedeemScript(rs).type) {
|
||||||
|
case "segwit__":
|
||||||
|
return BASE_INPUT_SIZE + SEGWIT_INPUT_SIZE;
|
||||||
|
case "multisig__":
|
||||||
|
return BASE_INPUT_SIZE + MULTISIG_INPUT_SIZE_ES;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function _sizePerOutput(addr) {
|
||||||
|
switch (coinjs.addressDecode(addr).type) {
|
||||||
|
case "standard":
|
||||||
|
return BASE_OUTPUT_SIZE + LEGACY_OUTPUT_SIZE;
|
||||||
|
case "bech32":
|
||||||
|
return BASE_OUTPUT_SIZE + BECH32_OUTPUT_SIZE;
|
||||||
|
case "multisig":
|
||||||
|
return BASE_OUTPUT_SIZE + SEGWIT_OUTPUT_SIZE;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function validateTxParameters(parameters) {
|
function validateTxParameters(parameters) {
|
||||||
let invalids = [];
|
let invalids = [];
|
||||||
//sender-ids
|
//sender-ids
|
||||||
@ -249,8 +307,8 @@
|
|||||||
parameters.receivers.forEach(id => !validateAddress(id) ? invalids.push(id) : null);
|
parameters.receivers.forEach(id => !validateAddress(id) ? invalids.push(id) : null);
|
||||||
if (invalids.length)
|
if (invalids.length)
|
||||||
throw "Invalid receivers:" + invalids;
|
throw "Invalid receivers:" + invalids;
|
||||||
if (parameters.change_addr && !validateAddress(parameters.change_addr))
|
if (parameters.change_address && !validateAddress(parameters.change_address))
|
||||||
throw "Invalid change_address:" + parameters.change_addr;
|
throw "Invalid change_address:" + parameters.change_address;
|
||||||
//fee and amounts
|
//fee and amounts
|
||||||
if ((typeof parameters.fee !== "number" || parameters.fee <= 0) && parameters.fee !== null) //fee = null (auto calc)
|
if ((typeof parameters.fee !== "number" || parameters.fee <= 0) && parameters.fee !== null) //fee = null (auto calc)
|
||||||
throw "Invalid fee:" + parameters.fee;
|
throw "Invalid fee:" + parameters.fee;
|
||||||
@ -265,48 +323,88 @@
|
|||||||
return parameters;
|
return parameters;
|
||||||
}
|
}
|
||||||
|
|
||||||
const TMP_FEE = 0.00001;
|
function createTransaction(senders, redeemScripts, receivers, amounts, fee, change_address, fee_from_receiver) {
|
||||||
|
|
||||||
function createTransaction(senders, redeemScripts, receivers, amounts, fee, change_addr) {
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
let auto_fee = false,
|
let total_amount = parseFloat(amounts.reduce((t, a) => t + a, 0).toFixed(8));
|
||||||
total_amount = parseFloat(amounts.reduce((t, a) => t + a, 0).toFixed(8));
|
|
||||||
if (fee === null) {
|
|
||||||
auto_fee = true;
|
|
||||||
fee = TMP_FEE;
|
|
||||||
}
|
|
||||||
const tx = coinjs.transaction();
|
const tx = coinjs.transaction();
|
||||||
addUTXOs(tx, senders, redeemScripts, total_amount + fee).then(result => {
|
let output_size = addOutputs(tx, receivers, amounts, change_address);
|
||||||
if (result > 0)
|
addInputs(tx, senders, redeemScripts, total_amount, fee, output_size, fee_from_receiver).then(result => {
|
||||||
return reject("Insufficient Balance");
|
if (result.change_amount > 0) //add change amount if any
|
||||||
let change = addOutputs(tx, receivers, amounts, Math.abs(result), change_addr);
|
tx.outs[tx.outs.length - 1].value = parseInt(result.change_amount * SATOSHI_IN_BTC); //values are in satoshi
|
||||||
if (!auto_fee)
|
if (fee_from_receiver) { //deduce fee from receivers if fee_from_receiver
|
||||||
return resolve(tx);
|
let fee_remaining = parseInt(result.fee * SATOSHI_IN_BTC);
|
||||||
autoFeeCalc(tx).then(fee_calc => {
|
for (let i = 0; i < tx.outs.length - 1 && fee_remaining > 0; i++) {
|
||||||
fee = Math.round((fee * 1) * 1e8); //satoshi convertion
|
if (fee_remaining < tx.outs[i].value) {
|
||||||
if (!change)
|
tx.outs[i].value -= fee_remaining;
|
||||||
tx.addoutput(change_addr, 0);
|
fee_remaining = 0;
|
||||||
editFee(tx, fee, fee_calc);
|
} else {
|
||||||
resolve(tx);
|
fee_remaining -= tx.outs[i].value;
|
||||||
|
tx.outs[i].value = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (fee_remaining > 0)
|
||||||
|
return reject("Send amount is less than fee");
|
||||||
|
|
||||||
|
}
|
||||||
|
tx.outs = tx.outs.filter(o => o.value !== 0); //remove all output with value 0
|
||||||
|
result.output_size = output_size;
|
||||||
|
result.output_amount = total_amount - (fee_from_receiver ? result.fee : 0);
|
||||||
|
result.total_size = BASE_TX_SIZE + output_size + result.input_size;
|
||||||
|
result.transaction = tx;
|
||||||
|
resolve(result);
|
||||||
}).catch(error => reject(error))
|
}).catch(error => reject(error))
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function addInputs(tx, senders, redeemScripts, total_amount, fee, output_size, fee_from_receiver) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if (fee !== null) {
|
||||||
|
addUTXOs(tx, senders, redeemScripts, fee_from_receiver ? total_amount : total_amount + fee, false).then(result => {
|
||||||
|
result.fee = fee;
|
||||||
|
resolve(result);
|
||||||
|
}).catch(error => reject(error))
|
||||||
|
} else {
|
||||||
|
get_fee_rate().then(fee_rate => {
|
||||||
|
let net_fee = BASE_TX_SIZE * fee_rate;
|
||||||
|
net_fee += (output_size * fee_rate);
|
||||||
|
(fee_from_receiver ?
|
||||||
|
addUTXOs(tx, senders, redeemScripts, total_amount, false) :
|
||||||
|
addUTXOs(tx, senders, redeemScripts, total_amount + net_fee, fee_rate)
|
||||||
|
).then(result => {
|
||||||
|
result.fee = parseFloat((net_fee + (result.input_size * fee_rate)).toFixed(8));
|
||||||
|
result.fee_rate = fee_rate;
|
||||||
|
resolve(result);
|
||||||
|
}).catch(error => reject(error))
|
||||||
|
}).catch(error => reject(error))
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function addUTXOs(tx, senders, redeemScripts, required_amount, n = 0) {
|
function addUTXOs(tx, senders, redeemScripts, required_amount, fee_rate, rec_args = {}) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
required_amount = parseFloat(required_amount.toFixed(8));
|
required_amount = parseFloat(required_amount.toFixed(8));
|
||||||
if (required_amount <= 0 || n >= senders.length)
|
if (typeof rec_args.n === "undefined") {
|
||||||
return resolve(required_amount);
|
rec_args.n = 0;
|
||||||
let addr = senders[n],
|
rec_args.input_size = 0;
|
||||||
rs = redeemScripts[n];
|
rec_args.input_amount = 0;
|
||||||
|
}
|
||||||
|
if (required_amount <= 0)
|
||||||
|
return resolve({
|
||||||
|
input_size: rec_args.input_size,
|
||||||
|
input_amount: rec_args.input_amount,
|
||||||
|
change_amount: required_amount * -1 //required_amount will be -ve of change_amount
|
||||||
|
});
|
||||||
|
else if (rec_args.n >= senders.length)
|
||||||
|
return reject("Insufficient Balance");
|
||||||
|
let addr = senders[rec_args.n],
|
||||||
|
rs = redeemScripts[rec_args.n];
|
||||||
|
let size_per_input = _sizePerInput(addr, rs);
|
||||||
fetch_api(`get_tx_unspent/BTC/${addr}`).then(result => {
|
fetch_api(`get_tx_unspent/BTC/${addr}`).then(result => {
|
||||||
let utxos = result.data.txs;
|
let utxos = result.data.txs;
|
||||||
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;
|
||||||
required_amount -= parseFloat(utxos[i].value);
|
|
||||||
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_hex;
|
||||||
@ -315,29 +413,38 @@
|
|||||||
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 * 100000000).toFixed(0), 8));
|
s.writeBytes(coinjs.numToBytes((utxos[i].value * SATOSHI_IN_BTC).toFixed(0), 8));
|
||||||
script = Crypto.util.bytesToHex(s.buffer);
|
script = Crypto.util.bytesToHex(s.buffer);
|
||||||
} else //redeemScript for multisig
|
} else //redeemScript for multisig
|
||||||
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].txid, utxos[i].output_no, script, 0xfffffffd /*sequence*/); //0xfffffffd for Replace-by-fee
|
||||||
|
//update track values
|
||||||
|
rec_args.input_size += size_per_input;
|
||||||
|
rec_args.input_amount += parseFloat(utxos[i].value);
|
||||||
|
required_amount -= parseFloat(utxos[i].value);
|
||||||
|
if (fee_rate) //automatic fee calculation (dynamic)
|
||||||
|
required_amount += size_per_input * fee_rate;
|
||||||
}
|
}
|
||||||
addUTXOs(tx, senders, redeemScripts, required_amount, n + 1)
|
rec_args.n += 1;
|
||||||
|
addUTXOs(tx, senders, redeemScripts, required_amount, fee_rate, rec_args)
|
||||||
.then(result => resolve(result))
|
.then(result => resolve(result))
|
||||||
.catch(error => reject(error))
|
.catch(error => reject(error))
|
||||||
}).catch(error => reject(error))
|
}).catch(error => reject(error))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function addOutputs(tx, receivers, amounts, change, change_addr) {
|
function addOutputs(tx, receivers, amounts, change_address) {
|
||||||
for (let i in receivers)
|
let size = 0;
|
||||||
|
for (let i in receivers) {
|
||||||
tx.addoutput(receivers[i], amounts[i]);
|
tx.addoutput(receivers[i], amounts[i]);
|
||||||
if (parseFloat(change.toFixed(8)) > 0) {
|
size += _sizePerOutput(receivers[i]);
|
||||||
tx.addoutput(change_addr, change);
|
}
|
||||||
return true;
|
tx.addoutput(change_address, 0);
|
||||||
} else
|
size += _sizePerOutput(change_address);
|
||||||
return false;
|
return size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
function autoFeeCalc(tx) {
|
function autoFeeCalc(tx) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
get_fee_rate().then(fee_rate => {
|
get_fee_rate().then(fee_rate => {
|
||||||
@ -362,18 +469,30 @@
|
|||||||
|
|
||||||
function editFee(tx, current_fee, target_fee, index = -1) {
|
function editFee(tx, current_fee, target_fee, index = -1) {
|
||||||
//values are in satoshi
|
//values are in satoshi
|
||||||
index = parseInt(index >= 0 ? index : tx.out.length - index);
|
index = parseInt(index >= 0 ? index : tx.outs.length - index);
|
||||||
if (index < 0 || index >= tx.out.length)
|
if (index < 0 || index >= tx.outs.length)
|
||||||
throw "Invalid index";
|
throw "Invalid index";
|
||||||
let edit_value = parseInt(current_fee - target_fee), //rip of any decimal places
|
let edit_value = parseInt(current_fee - target_fee), //rip of any decimal places
|
||||||
current_value = tx.out[index].value; //could be BigInterger
|
current_value = tx.outs[index].value; //could be BigInterger
|
||||||
if (edit_value < 0 && edit_value > current_value)
|
if (edit_value < 0 && edit_value > current_value)
|
||||||
throw "Insufficient value at vout";
|
throw "Insufficient value at vout";
|
||||||
tx.out[index].value = current_value instanceof BigInteger ?
|
tx.outs[index].value = current_value instanceof BigInteger ?
|
||||||
current_value.add(new BigInteger('' + edit_value)) : parseInt(current_value + edit_value);
|
current_value.add(new BigInteger('' + edit_value)) : parseInt(current_value + edit_value);
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
btcOperator.sendTx = function (senders, privkeys, receivers, amounts, fee, change_addr = null) {
|
btcOperator.sendTx = function (senders, privkeys, receivers, amounts, fee = null, options = {}) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
createSignedTx(senders, privkeys, receivers, amounts, fee, options).then(result => {
|
||||||
|
debugger;
|
||||||
|
broadcastTx(result.transaction.serialize())
|
||||||
|
.then(txid => resolve(txid))
|
||||||
|
.catch(error => reject(error));
|
||||||
|
}).catch(error => reject(error))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const createSignedTx = btcOperator.createSignedTx = function (senders, privkeys, receivers, amounts, fee = null, options = {}) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
try {
|
try {
|
||||||
({
|
({
|
||||||
@ -387,7 +506,7 @@
|
|||||||
receivers,
|
receivers,
|
||||||
amounts,
|
amounts,
|
||||||
fee,
|
fee,
|
||||||
change_addr
|
change_address: options.change_address
|
||||||
}));
|
}));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return reject(e)
|
return reject(e)
|
||||||
@ -402,19 +521,17 @@
|
|||||||
if (redeemScripts.includes(null)) //TODO: segwit
|
if (redeemScripts.includes(null)) //TODO: segwit
|
||||||
return reject("Unable to get redeem-script");
|
return reject("Unable to get redeem-script");
|
||||||
//create transaction
|
//create transaction
|
||||||
createTransaction(senders, redeemScripts, receivers, amounts, fee, change_addr || senders[0]).then(tx => {
|
createTransaction(senders, redeemScripts, receivers, amounts, fee, options.change_address || senders[0], options.fee_from_receiver).then(result => {
|
||||||
|
let tx = result.transaction;
|
||||||
console.debug("Unsigned:", tx.serialize());
|
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
|
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());
|
console.debug("Signed:", tx.serialize());
|
||||||
debugger;
|
resolve(result);
|
||||||
broadcast(tx.serialize())
|
|
||||||
.then(result => resolve(result))
|
|
||||||
.catch(error => reject(error));
|
|
||||||
}).catch(error => reject(error));
|
}).catch(error => reject(error));
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
btcOperator.createTx = function (senders, receivers, amounts, fee = null, change_addr = null) {
|
btcOperator.createTx = function (senders, receivers, amounts, fee = null, options = {}) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
try {
|
try {
|
||||||
({
|
({
|
||||||
@ -426,7 +543,7 @@
|
|||||||
receivers,
|
receivers,
|
||||||
amounts,
|
amounts,
|
||||||
fee,
|
fee,
|
||||||
change_addr
|
change_address: options.change_address
|
||||||
}));
|
}));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return reject(e)
|
return reject(e)
|
||||||
@ -435,13 +552,15 @@
|
|||||||
if (redeemScripts.includes(null)) //TODO: segwit
|
if (redeemScripts.includes(null)) //TODO: segwit
|
||||||
return reject("Unable to get redeem-script");
|
return reject("Unable to get redeem-script");
|
||||||
//create transaction
|
//create transaction
|
||||||
createTransaction(senders, redeemScripts, receivers, amounts, fee, change_addr || senders[0])
|
createTransaction(senders, redeemScripts, receivers, amounts, fee, options.change_address || senders[0], options.fee_from_receiver).then(result => {
|
||||||
.then(tx => resolve(tx.serialize()))
|
result.tx_hex = result.transaction.serialize();
|
||||||
.catch(error => reject(error))
|
delete result.transaction;
|
||||||
|
resolve(result);
|
||||||
|
}).catch(error => reject(error))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
btcOperator.createMultiSigTx = function (sender, redeemScript, receivers, amounts, fee) {
|
btcOperator.createMultiSigTx = function (sender, redeemScript, receivers, amounts, fee = null, options = {}) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
//validate tx parameters
|
//validate tx parameters
|
||||||
if (validateAddress(sender) !== "multisig")
|
if (validateAddress(sender) !== "multisig")
|
||||||
@ -459,15 +578,19 @@
|
|||||||
} = validateTxParameters({
|
} = validateTxParameters({
|
||||||
receivers,
|
receivers,
|
||||||
amounts,
|
amounts,
|
||||||
fee
|
fee,
|
||||||
|
change_address: options.change_address
|
||||||
}));
|
}));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return reject(e)
|
return reject(e)
|
||||||
}
|
}
|
||||||
//create transaction
|
//create transaction
|
||||||
createTransaction([sender], [redeemScript], receivers, amounts, fee, sender)
|
createTransaction([sender], [redeemScript], receivers, amounts, fee, options.change_address || sender, options.fee_from_receiver).then(result => {
|
||||||
.then(tx => resolve(tx.serialize()))
|
result.tx_hex = result.transaction.serialize();
|
||||||
.catch(error => reject(error))
|
delete result.transaction;
|
||||||
|
resolve(result);
|
||||||
|
}).catch(error => reject(error))
|
||||||
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -494,7 +617,7 @@
|
|||||||
return tx.serialize();
|
return tx.serialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
btcOperator.checkSigned = function (tx, bool = true) {
|
const checkSigned = btcOperator.checkSigned = function (tx, bool = true) {
|
||||||
tx = deserializeTx(tx);
|
tx = deserializeTx(tx);
|
||||||
let n = [];
|
let n = [];
|
||||||
for (let i in tx.ins) {
|
for (let i in tx.ins) {
|
||||||
@ -533,6 +656,63 @@
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getTxOutput = (txid, i) => new Promise((resolve, reject) => {
|
||||||
|
fetch_api(`get_tx_outputs/BTC/${txid}/${i}`)
|
||||||
|
.then(result => resolve(result.data.outputs))
|
||||||
|
.catch(error => reject(error))
|
||||||
|
});
|
||||||
|
|
||||||
|
btcOperator.parseTransaction = function (tx) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
tx = deserializeTx(tx);
|
||||||
|
let result = {};
|
||||||
|
let promises = [];
|
||||||
|
//Parse Inputs
|
||||||
|
for (let i = 0; i < tx.ins.length; i++)
|
||||||
|
promises.push(getTxOutput(tx.ins[i].outpoint.hash, tx.ins[i].outpoint.index));
|
||||||
|
Promise.all(promises).then(inputs => {
|
||||||
|
result.inputs = inputs.map(inp => Object({
|
||||||
|
address: inp.address,
|
||||||
|
value: parseFloat(inp.value)
|
||||||
|
}));
|
||||||
|
let signed = checkSigned(tx, false);
|
||||||
|
result.inputs.forEach((inp, i) => inp.signed = signed[i]);
|
||||||
|
//Parse Outputs
|
||||||
|
result.outputs = tx.outs.map(out => {
|
||||||
|
var address;
|
||||||
|
switch (out.script.chunks[0]) {
|
||||||
|
case 0: //bech32
|
||||||
|
address = encodeBech32(Crypto.util.bytesToHex(out.script.chunks[1]), coinjs.bech32.version, coinjs.bech32.hrp);
|
||||||
|
break;
|
||||||
|
case 169: //multisig, segwit
|
||||||
|
address = encodeLegacy(Crypto.util.bytesToHex(out.script.chunks[1]), coinjs.multisig);
|
||||||
|
break;
|
||||||
|
case 118: //legacy
|
||||||
|
address = encodeLegacy(Crypto.util.bytesToHex(out.script.chunks[2]), coinjs.pub);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
address,
|
||||||
|
value: parseFloat(out.value / SATOSHI_IN_BTC)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
//Parse Totals
|
||||||
|
result.total_input = parseFloat(result.inputs.reduce((a, inp) => a += inp.value, 0).toFixed(8));
|
||||||
|
result.total_output = parseFloat(result.outputs.reduce((a, out) => a += out.value, 0).toFixed(8));
|
||||||
|
result.fee = parseFloat((result.total_input - result.total_output).toFixed(8));
|
||||||
|
resolve(result);
|
||||||
|
}).catch(error => reject(error))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
btcOperator.transactionID = function (tx) {
|
||||||
|
tx = deserializeTx(tx);
|
||||||
|
let clone = coinjs.clone(tx);
|
||||||
|
clone.witness = null;
|
||||||
|
let raw_bytes = Crypto.util.hexToBytes(clone.serialize());
|
||||||
|
let txid = Crypto.SHA256(Crypto.SHA256(raw_bytes, { asBytes: true }), { asBytes: true }).reverse();
|
||||||
|
return Crypto.util.bytesToHex(txid);
|
||||||
|
}
|
||||||
|
|
||||||
btcOperator.getTx = txid => new Promise((resolve, reject) => {
|
btcOperator.getTx = txid => new Promise((resolve, reject) => {
|
||||||
fetch_api(`get_tx/BTC/${txid}`)
|
fetch_api(`get_tx/BTC/${txid}`)
|
||||||
.then(result => resolve(result.data))
|
.then(result => resolve(result.data))
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
(function(EXPORTS) { //floBlockchainAPI v2.3.3b
|
(function (EXPORTS) { //floBlockchainAPI v2.3.3d
|
||||||
/* FLO Blockchain Operator to send/receive data from blockchain using API calls*/
|
/* FLO Blockchain Operator to send/receive data from blockchain using API calls*/
|
||||||
'use strict';
|
'use strict';
|
||||||
const floBlockchainAPI = EXPORTS;
|
const floBlockchainAPI = EXPORTS;
|
||||||
@ -11,6 +11,7 @@
|
|||||||
},
|
},
|
||||||
sendAmt: 0.001,
|
sendAmt: 0.001,
|
||||||
fee: 0.0005,
|
fee: 0.0005,
|
||||||
|
minChangeAmt: 0.0005,
|
||||||
receiverID: floGlobals.adminID
|
receiverID: floGlobals.adminID
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -171,7 +172,7 @@
|
|||||||
else {
|
else {
|
||||||
trx.addoutput(receiverAddr, sendAmt);
|
trx.addoutput(receiverAddr, sendAmt);
|
||||||
var change = utxoAmt - sendAmt - fee;
|
var change = utxoAmt - sendAmt - fee;
|
||||||
if (change > 0)
|
if (change > DEFAULT.minChangeAmt)
|
||||||
trx.addoutput(senderAddr, change);
|
trx.addoutput(senderAddr, change);
|
||||||
trx.addflodata(floData.replace(/\n/g, ' '));
|
trx.addflodata(floData.replace(/\n/g, ' '));
|
||||||
var signedTxHash = trx.sign(privKey, 1);
|
var signedTxHash = trx.sign(privKey, 1);
|
||||||
@ -484,8 +485,8 @@
|
|||||||
floBlockchainAPI.readData = function (addr, options = {}) {
|
floBlockchainAPI.readData = function (addr, options = {}) {
|
||||||
options.limit = options.limit || 0;
|
options.limit = options.limit || 0;
|
||||||
options.ignoreOld = options.ignoreOld || 0;
|
options.ignoreOld = options.ignoreOld || 0;
|
||||||
if (typeof options.sender === "string") options.sender = [options.sender];
|
if (typeof options.senders === "string") options.senders = [options.senders];
|
||||||
if (typeof options.receiver === "string") options.receiver = [options.receiver];
|
if (typeof options.receivers === "string") options.receivers = [options.receivers];
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
promisedAPI(`api/addrs/${addr}/txs?from=0&to=1`).then(response => {
|
promisedAPI(`api/addrs/${addr}/txs?from=0&to=1`).then(response => {
|
||||||
var newItems = response.totalItems - options.ignoreOld;
|
var newItems = response.totalItems - options.ignoreOld;
|
||||||
@ -520,10 +521,10 @@
|
|||||||
}
|
}
|
||||||
if (!flag) continue;
|
if (!flag) continue;
|
||||||
}
|
}
|
||||||
if (Array.isArray(options.sender)) {
|
if (Array.isArray(options.senders)) {
|
||||||
let flag = false;
|
let flag = false;
|
||||||
for (let vin of response.items[i].vin)
|
for (let vin of response.items[i].vin)
|
||||||
if (options.sender.includes(vin.addr)) {
|
if (options.senders.includes(vin.addr)) {
|
||||||
flag = true;
|
flag = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -538,10 +539,10 @@
|
|||||||
}
|
}
|
||||||
if (!flag) continue;
|
if (!flag) continue;
|
||||||
}
|
}
|
||||||
if (Array.isArray(options.receiver)) {
|
if (Array.isArray(options.receivers)) {
|
||||||
let flag = false;
|
let flag = false;
|
||||||
for (let vout of response.items[i].vout)
|
for (let vout of response.items[i].vout)
|
||||||
if (options.receiver.includes(vout.scriptPubKey.addresses[0])) {
|
if (options.receivers.includes(vout.scriptPubKey.addresses[0])) {
|
||||||
flag = true;
|
flag = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -555,6 +556,8 @@
|
|||||||
d.txid = response.items[i].txid;
|
d.txid = response.items[i].txid;
|
||||||
d.time = response.items[i].time;
|
d.time = response.items[i].time;
|
||||||
d.blockheight = response.items[i].blockheight;
|
d.blockheight = response.items[i].blockheight;
|
||||||
|
d.senders = new Set(response.items[i].vin.map(v => v.addr));
|
||||||
|
d.receivers = new Set(response.items[i].vout.map(v => v.scriptPubKey.addresses[0]));
|
||||||
d.data = response.items[i].floData;
|
d.data = response.items[i].floData;
|
||||||
filteredData.push(d);
|
filteredData.push(d);
|
||||||
} else
|
} else
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
(function(EXPORTS) { //floCloudAPI v2.4.2d
|
(function (EXPORTS) { //floCloudAPI v2.4.3
|
||||||
/* FLO Cloud operations to send/request application data*/
|
/* FLO Cloud operations to send/request application data*/
|
||||||
'use strict';
|
'use strict';
|
||||||
const floCloudAPI = EXPORTS;
|
const floCloudAPI = EXPORTS;
|
||||||
@ -79,6 +79,9 @@
|
|||||||
get: () => generalData,
|
get: () => generalData,
|
||||||
set: data => generalData = data
|
set: data => generalData = data
|
||||||
},
|
},
|
||||||
|
generalDataset: {
|
||||||
|
value: (type, options = {}) => generalData[filterKey(type, options)]
|
||||||
|
},
|
||||||
lastVC: {
|
lastVC: {
|
||||||
get: () => lastVC,
|
get: () => lastVC,
|
||||||
set: vc => lastVC = vc
|
set: vc => lastVC = vc
|
||||||
@ -375,7 +378,7 @@
|
|||||||
return JSON.parse(decodeURIComponent(escape(atob(message))))
|
return JSON.parse(decodeURIComponent(escape(atob(message))))
|
||||||
}
|
}
|
||||||
|
|
||||||
const filterKey = util.filterKey = function(type, options) {
|
const filterKey = util.filterKey = function (type, options = {}) {
|
||||||
return type + (options.comment ? ':' + options.comment : '') +
|
return type + (options.comment ? ':' + options.comment : '') +
|
||||||
'|' + (options.group || options.receiverID || DEFAULT.adminID) +
|
'|' + (options.group || options.receiverID || DEFAULT.adminID) +
|
||||||
'|' + (options.application || DEFAULT.application);
|
'|' + (options.application || DEFAULT.application);
|
||||||
@ -983,23 +986,23 @@
|
|||||||
}, {});
|
}, {});
|
||||||
};
|
};
|
||||||
|
|
||||||
const mergeRecursive = (obj1, obj2) => {
|
const mergeRecursive = (obj1, obj2, deleteMode = false) => {
|
||||||
for (var p in obj2) {
|
for (var p in obj2) {
|
||||||
try {
|
try {
|
||||||
if (obj2[p].constructor == Object)
|
if (obj2[p].constructor == Object)
|
||||||
obj1[p] = mergeRecursive(obj1[p], obj2[p]);
|
obj1[p] = mergeRecursive(obj1[p], obj2[p], deleteMode);
|
||||||
// Property in destination object set; update its value.
|
// Property in destination object set; update its value.
|
||||||
else if (Ext.isArray(obj2[p])) {
|
else if (Array.isArray(obj2[p])) {
|
||||||
// obj1[p] = [];
|
// obj1[p] = [];
|
||||||
if (obj2[p].length < 1)
|
if (obj2[p].length < 1)
|
||||||
obj1[p] = obj2[p];
|
obj1[p] = obj2[p];
|
||||||
else
|
else
|
||||||
obj1[p] = mergeRecursive(obj1[p], obj2[p]);
|
obj1[p] = mergeRecursive(obj1[p], obj2[p], deleteMode);
|
||||||
} else
|
} else
|
||||||
obj1[p] = obj2[p];
|
obj1[p] = deleteMode && obj2[p] === null ? undefined : obj2[p];
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Property in destination object not set; create it and set its value.
|
// Property in destination object not set; create it and set its value.
|
||||||
obj1[p] = obj2[p];
|
obj1[p] = deleteMode && obj2[p] === null ? undefined : obj2[p];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return obj1;
|
return obj1;
|
||||||
@ -1008,20 +1011,13 @@
|
|||||||
const cleanse = (obj) => {
|
const cleanse = (obj) => {
|
||||||
Object.keys(obj).forEach(key => {
|
Object.keys(obj).forEach(key => {
|
||||||
var value = obj[key];
|
var value = obj[key];
|
||||||
if (typeof value === "object" && value !== null) {
|
if (typeof value === "object" && value !== null)
|
||||||
// Recurse...
|
obj[key] = cleanse(value);
|
||||||
cleanse(value);
|
else if (typeof value === 'undefined')
|
||||||
// ...and remove if now "empty" (NOTE: insert your definition of "empty" here)
|
delete obj[key]; // undefined, remove it
|
||||||
//if (!Object.keys(value).length)
|
|
||||||
// delete obj[key];
|
|
||||||
} else if (value === null)
|
|
||||||
delete obj[key]; // null, remove it
|
|
||||||
});
|
});
|
||||||
if (obj.constructor.toString().indexOf("Array") != -1) {
|
if (Array.isArray(obj))
|
||||||
obj = obj.filter(function(el) {
|
obj = obj.filter(v => typeof v !== 'undefined');
|
||||||
return el != null;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1037,7 +1033,7 @@
|
|||||||
if (Object.keys(diff.updated).length !== 0)
|
if (Object.keys(diff.updated).length !== 0)
|
||||||
obj = mergeRecursive(obj, diff.updated)
|
obj = mergeRecursive(obj, diff.updated)
|
||||||
if (Object.keys(diff.deleted).length !== 0) {
|
if (Object.keys(diff.deleted).length !== 0) {
|
||||||
obj = mergeRecursive(obj, diff.deleted)
|
obj = mergeRecursive(obj, diff.deleted, true)
|
||||||
obj = cleanse(obj)
|
obj = cleanse(obj)
|
||||||
}
|
}
|
||||||
if (Object.keys(diff.added).length !== 0)
|
if (Object.keys(diff.added).length !== 0)
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
(function(EXPORTS) { //floCrypto v2.3.3d
|
(function (EXPORTS) { //floCrypto v2.3.3e
|
||||||
/* FLO Crypto Operators */
|
/* FLO Crypto Operators */
|
||||||
'use strict';
|
'use strict';
|
||||||
const floCrypto = EXPORTS;
|
const floCrypto = EXPORTS;
|
||||||
@ -276,12 +276,18 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Convert the given address (any blockchain) to equivalent floID
|
//Convert the given address (any blockchain) to equivalent floID
|
||||||
floCrypto.toFloID = function(address) {
|
floCrypto.toFloID = function (address, options = null) {
|
||||||
if (!address)
|
if (!address)
|
||||||
return;
|
return;
|
||||||
let raw = decodeAddress(address);
|
let raw = decodeAddress(address);
|
||||||
if (!raw)
|
if (!raw)
|
||||||
return;
|
return;
|
||||||
|
else if (options) {
|
||||||
|
if (typeof raw.version !== 'undefined' && (!options.std || !options.std.includes(raw.version)))
|
||||||
|
return;
|
||||||
|
if (typeof raw.bech_version !== 'undefined' && (!options.bech || !options.bech.includes(raw.bech_version)))
|
||||||
|
return;
|
||||||
|
}
|
||||||
raw.bytes.unshift(bitjs.pub);
|
raw.bytes.unshift(bitjs.pub);
|
||||||
let hash = Crypto.SHA256(Crypto.SHA256(raw.bytes, {
|
let hash = Crypto.SHA256(Crypto.SHA256(raw.bytes, {
|
||||||
asBytes: true
|
asBytes: true
|
||||||
|
|||||||
@ -1126,7 +1126,7 @@
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
MultiSig.createTx = function(address, redeemScript, receivers, amounts, fee = null) {
|
MultiSig.createTx = function (address, redeemScript, receivers, amounts, fee = null, options = {}) {
|
||||||
return new Promise(async (resolve, reject) => {
|
return new Promise(async (resolve, reject) => {
|
||||||
let decode = coinjs.script().decodeRedeemScript(redeemScript);
|
let decode = coinjs.script().decodeRedeemScript(redeemScript);
|
||||||
if (!decode || decode.address !== address || decode.type !== "multisig__")
|
if (!decode || decode.address !== address || decode.type !== "multisig__")
|
||||||
@ -1137,10 +1137,10 @@
|
|||||||
return reject("Invalid multisig (required is greater than users)");
|
return reject("Invalid multisig (required is greater than users)");
|
||||||
let co_owners = decode.pubkeys.map(p => floCrypto.getFloID(p));
|
let co_owners = decode.pubkeys.map(p => floCrypto.getFloID(p));
|
||||||
let privateKey = await floDapps.user.private;
|
let privateKey = await floDapps.user.private;
|
||||||
btcOperator.createMultiSigTx(address, redeemScript, receivers, amounts, fee).then(tx => {
|
btcOperator.createMultiSigTx(address, redeemScript, receivers, amounts, fee, options).then(({ tx_hex }) => {
|
||||||
tx = btcOperator.signTx(tx, privateKey);
|
tx_hex = btcOperator.signTx(tx_hex, privateKey);
|
||||||
createPipeline(TYPE_BTC_MULTISIG, co_owners, 32).then(pipeline => {
|
createPipeline(TYPE_BTC_MULTISIG, co_owners, 32).then(pipeline => {
|
||||||
let message = encrypt(tx, pipeline.eKey);
|
let message = encrypt(tx_hex, pipeline.eKey);
|
||||||
sendRaw(message, pipeline.id, "TRANSACTION", false)
|
sendRaw(message, pipeline.id, "TRANSACTION", false)
|
||||||
.then(result => resolve(pipeline.id))
|
.then(result => resolve(pipeline.id))
|
||||||
.catch(error => reject(error))
|
.catch(error => reject(error))
|
||||||
@ -1165,8 +1165,7 @@
|
|||||||
tx_hex: tx_hex_signed
|
tx_hex: tx_hex_signed
|
||||||
});
|
});
|
||||||
debugger;
|
debugger;
|
||||||
btcOperator.broadcast(tx_hex_signed).then(result => {
|
btcOperator.broadcastTx(tx_hex_signed).then(txid => {
|
||||||
let txid = result.txid;
|
|
||||||
console.debug(txid);
|
console.debug(txid);
|
||||||
sendRaw(encrypt(txid, pipeline.eKey), pipeline.id, "BROADCAST", false)
|
sendRaw(encrypt(txid, pipeline.eKey), pipeline.id, "BROADCAST", false)
|
||||||
.then(result => resolve({
|
.then(result => resolve({
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user