commit
fe424d56dd
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
CREATE TABLE LastTx(
|
CREATE TABLE LastTx(
|
||||||
floID CHAR(34) NOT NULL,
|
floID CHAR(34) NOT NULL,
|
||||||
num INT,
|
txid VARCHAR(128),
|
||||||
PRIMARY KEY(floID)
|
PRIMARY KEY(floID)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@ -26,9 +26,9 @@ function listTables() {
|
|||||||
|
|
||||||
function checksumTable(table) {
|
function checksumTable(table) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
DB.query("CHECKSUM TABLE " + table).then(result => {
|
DB.query("CHECKSUM TABLE ??", [table]).then(result => {
|
||||||
let checksum = result[0].Checksum;
|
let checksum = result[0].Checksum;
|
||||||
DB.query("SELECT COUNT(*) AS rec_count FROM " + table)
|
DB.query("SELECT COUNT(*) AS rec_count FROM ??", [table])
|
||||||
.then(result => resolve({ table, rec_count: result[0].rec_count, checksum }))
|
.then(result => resolve({ table, rec_count: result[0].rec_count, checksum }))
|
||||||
.catch(error => reject(error))
|
.catch(error => reject(error))
|
||||||
}).catch(error => reject(error))
|
}).catch(error => reject(error))
|
||||||
|
|||||||
@ -5,7 +5,7 @@
|
|||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>RanchiMall exchange</title>
|
<title>RanchiMall Exchange</title>
|
||||||
<meta name="description" content="Trade FLO and FLO based RanchiMall tokens.">
|
<meta name="description" content="Trade FLO and FLO based RanchiMall tokens.">
|
||||||
<meta name="theme-color" content="#516beb" />
|
<meta name="theme-color" content="#516beb" />
|
||||||
<script src="scripts/components.js" defer></script>
|
<script src="scripts/components.js" defer></script>
|
||||||
@ -1703,7 +1703,7 @@
|
|||||||
// code if page is hidden
|
// code if page is hidden
|
||||||
} else {
|
} else {
|
||||||
if (floGlobals.exchangeApiLoaded)
|
if (floGlobals.exchangeApiLoaded)
|
||||||
updateRate();
|
updateRate().catch(err => console.log(err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1808,7 +1808,7 @@
|
|||||||
return listedAssets.hasOwnProperty(asset) ? listedAssets[asset].icon : listedAssets.default.icon;
|
return listedAssets.hasOwnProperty(asset) ? listedAssets[asset].icon : listedAssets.default.icon;
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatAmount(amount, shorten = false) {
|
function formatAmount(amount = 0, shorten = false) {
|
||||||
return amount.toLocaleString(`en-IN`, { style: 'currency', currency: 'INR', maximumFractionDigits: shorten ? 2 : 8 })
|
return amount.toLocaleString(`en-IN`, { style: 'currency', currency: 'INR', maximumFractionDigits: shorten ? 2 : 8 })
|
||||||
}
|
}
|
||||||
// Convert milliseconds to time left in HH:MM:SS format
|
// Convert milliseconds to time left in HH:MM:SS format
|
||||||
@ -1821,9 +1821,14 @@
|
|||||||
|
|
||||||
// remove digitals after specified decimal places without rounding
|
// remove digitals after specified decimal places without rounding
|
||||||
function toFixed(num, fixed = 8) {
|
function toFixed(num, fixed = 8) {
|
||||||
var re = new RegExp('^-?\\d+(?:\.\\d{0,' + (fixed || -1) + '})?');
|
const re = new RegExp(`^-?\\d+(?:\\.\\d{0,${fixed}})?`);
|
||||||
return parseFloat(num.toString().match(re)[0]);
|
const match = num.toString().match(re);
|
||||||
|
if (!match) {
|
||||||
|
return NaN;
|
||||||
}
|
}
|
||||||
|
return parseFloat(match[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
function getSuggestedPrice(asset = pagesData.params.asset || 'FLO') {
|
function getSuggestedPrice(asset = pagesData.params.asset || 'FLO') {
|
||||||
return toFixed(parseFloat(floGlobals.exchangeRates[asset]) * deviation[tradeType])
|
return toFixed(parseFloat(floGlobals.exchangeRates[asset]) * deviation[tradeType])
|
||||||
@ -1884,7 +1889,7 @@
|
|||||||
clone.querySelector('.listed-asset__countdown').style.setProperty('--path-length', pathLength)
|
clone.querySelector('.listed-asset__countdown').style.setProperty('--path-length', pathLength)
|
||||||
}, 1000);
|
}, 1000);
|
||||||
floGlobals.countDowns.timeouts[asset] = setTimeout(() => {
|
floGlobals.countDowns.timeouts[asset] = setTimeout(() => {
|
||||||
updateRate()
|
updateRate().catch(console.error)
|
||||||
delete floGlobals.countDowns.timeouts[asset]
|
delete floGlobals.countDowns.timeouts[asset]
|
||||||
}, countDown - Date.now());
|
}, countDown - Date.now());
|
||||||
return clone
|
return clone
|
||||||
@ -1976,8 +1981,10 @@
|
|||||||
new ResizeObserver(entries => {
|
new ResizeObserver(entries => {
|
||||||
chartDimensions.height = entries[0].contentRect.height - 10;
|
chartDimensions.height = entries[0].contentRect.height - 10;
|
||||||
chartDimensions.width = entries[0].contentRect.width;
|
chartDimensions.width = entries[0].contentRect.width;
|
||||||
if (chart)
|
if (chart) {
|
||||||
|
if (chartDimensions.width <= 0 || chartDimensions.height <= 0) return;
|
||||||
chart.applyOptions({ width: chartDimensions.width, height: chartDimensions.height });
|
chart.applyOptions({ width: chartDimensions.width, height: chartDimensions.height });
|
||||||
|
}
|
||||||
}).observe(getRef('price_chart_wrapper'));
|
}).observe(getRef('price_chart_wrapper'));
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
@ -2118,6 +2125,7 @@
|
|||||||
const chartObserver = new IntersectionObserver(entries => {
|
const chartObserver = new IntersectionObserver(entries => {
|
||||||
if (entries[0].isIntersecting && chart) {
|
if (entries[0].isIntersecting && chart) {
|
||||||
const { width, height } = entries[0].target.getBoundingClientRect()
|
const { width, height } = entries[0].target.getBoundingClientRect()
|
||||||
|
if (width <= 0 || height <= 0 || chartDimensions.height === height - 10 && chartDimensions.width === width) return
|
||||||
chartDimensions.height = height - 10;
|
chartDimensions.height = height - 10;
|
||||||
chartDimensions.width = width;
|
chartDimensions.width = width;
|
||||||
chart.applyOptions({ width: chartDimensions.width, height: chartDimensions.height });
|
chart.applyOptions({ width: chartDimensions.width, height: chartDimensions.height });
|
||||||
@ -2763,6 +2771,7 @@
|
|||||||
showSuggestedPrice()
|
showSuggestedPrice()
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
notify(error.message, 'error');
|
notify(error.message, 'error');
|
||||||
|
reject(error)
|
||||||
})
|
})
|
||||||
}).catch(error => console.error(error))
|
}).catch(error => console.error(error))
|
||||||
}
|
}
|
||||||
@ -2793,7 +2802,7 @@
|
|||||||
if (refreshButton)
|
if (refreshButton)
|
||||||
buttonLoader('portfolio_balance_refresh_button', false);
|
buttonLoader('portfolio_balance_refresh_button', false);
|
||||||
}
|
}
|
||||||
})
|
}).catch(error => console.error(error))
|
||||||
}
|
}
|
||||||
|
|
||||||
let accountDetails = {}
|
let accountDetails = {}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
(function (EXPORTS) { //btcOperator v1.1.1
|
(function (EXPORTS) { //btcOperator v1.1.2a
|
||||||
/* BTC Crypto and API Operator */
|
/* BTC Crypto and API Operator */
|
||||||
const btcOperator = EXPORTS;
|
const btcOperator = EXPORTS;
|
||||||
|
|
||||||
@ -130,50 +130,84 @@
|
|||||||
return coinjs.pubkeys2MultisigAddress(pubKeys, minRequired);
|
return coinjs.pubkeys2MultisigAddress(pubKeys, minRequired);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
btcOperator.decodeRedeemScript = function (redeemScript, bech32 = true) {
|
||||||
|
let script = coinjs.script();
|
||||||
|
let decoded = (bech32) ?
|
||||||
|
script.decodeRedeemScriptBech32(redeemScript) :
|
||||||
|
script.decodeRedeemScript(redeemScript);
|
||||||
|
if (!decoded)
|
||||||
|
return null;
|
||||||
|
return {
|
||||||
|
address: decoded.address,
|
||||||
|
pubKeys: decoded.pubkeys,
|
||||||
|
redeemScript: decoded.redeemscript,
|
||||||
|
required: decoded.signaturesRequired
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
//convert from one blockchain to another blockchain (target version)
|
//convert from one blockchain to another blockchain (target version)
|
||||||
btcOperator.convert = {};
|
btcOperator.convert = {};
|
||||||
|
|
||||||
btcOperator.convert.wif = function (source_wif, target_version = coinjs.priv) {
|
btcOperator.convert.wif = function (source_wif, target_version = coinjs.priv) {
|
||||||
let keyHex = decodeLegacy(source_wif).hex;
|
let keyHex = util.decodeLegacy(source_wif).hex;
|
||||||
if (!keyHex || keyHex.length < 66 || !/01$/.test(keyHex))
|
if (!keyHex || keyHex.length < 66 || !/01$/.test(keyHex))
|
||||||
return null;
|
return null;
|
||||||
else
|
else
|
||||||
return encodeLegacy(keyHex, target_version);
|
return util.encodeLegacy(keyHex, target_version);
|
||||||
}
|
}
|
||||||
|
|
||||||
btcOperator.convert.legacy2legacy = function (source_addr, target_version = coinjs.pub) {
|
btcOperator.convert.legacy2legacy = function (source_addr, target_version = coinjs.pub) {
|
||||||
let rawHex = decodeLegacy(source_addr).hex;
|
let rawHex = util.decodeLegacy(source_addr).hex;
|
||||||
if (!rawHex)
|
if (!rawHex)
|
||||||
return null;
|
return null;
|
||||||
else
|
else
|
||||||
return encodeLegacy(rawHex, target_version);
|
return util.encodeLegacy(rawHex, target_version);
|
||||||
}
|
}
|
||||||
|
|
||||||
btcOperator.convert.legacy2bech = function (source_addr, target_version = coinjs.bech32.version, target_hrp = coinjs.bech32.hrp) {
|
btcOperator.convert.legacy2bech = function (source_addr, target_version = coinjs.bech32.version, target_hrp = coinjs.bech32.hrp) {
|
||||||
let rawHex = decodeLegacy(source_addr).hex;
|
let rawHex = util.decodeLegacy(source_addr).hex;
|
||||||
if (!rawHex)
|
if (!rawHex)
|
||||||
return null;
|
return null;
|
||||||
else
|
else
|
||||||
return encodeBech32(rawHex, target_version, target_hrp);
|
return util.encodeBech32(rawHex, target_version, target_hrp);
|
||||||
}
|
}
|
||||||
|
|
||||||
btcOperator.convert.bech2bech = function (source_addr, target_version = coinjs.bech32.version, target_hrp = coinjs.bech32.hrp) {
|
btcOperator.convert.bech2bech = function (source_addr, target_version = coinjs.bech32.version, target_hrp = coinjs.bech32.hrp) {
|
||||||
let rawHex = decodeBech32(source_addr).hex;
|
let rawHex = util.decodeBech32(source_addr).hex;
|
||||||
if (!rawHex)
|
if (!rawHex)
|
||||||
return null;
|
return null;
|
||||||
else
|
else
|
||||||
return encodeBech32(rawHex, target_version, target_hrp);
|
return util.encodeBech32(rawHex, target_version, target_hrp);
|
||||||
}
|
}
|
||||||
|
|
||||||
btcOperator.convert.bech2legacy = function (source_addr, target_version = coinjs.pub) {
|
btcOperator.convert.bech2legacy = function (source_addr, target_version = coinjs.pub) {
|
||||||
let rawHex = decodeBech32(source_addr).hex;
|
let rawHex = util.decodeBech32(source_addr).hex;
|
||||||
if (!rawHex)
|
if (!rawHex)
|
||||||
return null;
|
return null;
|
||||||
else
|
else
|
||||||
return encodeLegacy(rawHex, target_version);
|
return util.encodeLegacy(rawHex, target_version);
|
||||||
}
|
}
|
||||||
|
|
||||||
function decodeLegacy(source) {
|
btcOperator.convert.multisig2multisig = function (source_addr, target_version = coinjs.multisig) {
|
||||||
|
let rawHex = util.decodeLegacy(source_addr).hex;
|
||||||
|
if (!rawHex)
|
||||||
|
return null;
|
||||||
|
else
|
||||||
|
return util.encodeLegacy(rawHex, target_version);
|
||||||
|
}
|
||||||
|
|
||||||
|
btcOperator.convert.bech2multisig = function (source_addr, target_version = coinjs.multisig) {
|
||||||
|
let rawHex = util.decodeBech32(source_addr).hex;
|
||||||
|
if (!rawHex)
|
||||||
|
return null;
|
||||||
|
else {
|
||||||
|
rawHex = Crypto.util.bytesToHex(ripemd160(Crypto.util.hexToBytes(rawHex), { asBytes: true }));
|
||||||
|
return util.encodeLegacy(rawHex, target_version);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
util.decodeLegacy = function (source) {
|
||||||
var decode = coinjs.base58decode(source);
|
var decode = coinjs.base58decode(source);
|
||||||
var raw = decode.slice(0, decode.length - 4),
|
var raw = decode.slice(0, decode.length - 4),
|
||||||
checksum = decode.slice(decode.length - 4);
|
checksum = decode.slice(decode.length - 4);
|
||||||
@ -183,7 +217,7 @@
|
|||||||
asBytes: true
|
asBytes: true
|
||||||
});
|
});
|
||||||
if (hash[0] != checksum[0] || hash[1] != checksum[1] || hash[2] != checksum[2] || hash[3] != checksum[3])
|
if (hash[0] != checksum[0] || hash[1] != checksum[1] || hash[2] != checksum[2] || hash[3] != checksum[3])
|
||||||
return null;
|
return false;
|
||||||
let version = raw.shift();
|
let version = raw.shift();
|
||||||
return {
|
return {
|
||||||
version: version,
|
version: version,
|
||||||
@ -191,7 +225,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function encodeLegacy(hex, version) {
|
util.encodeLegacy = function (hex, version) {
|
||||||
var bytes = Crypto.util.hexToBytes(hex);
|
var bytes = Crypto.util.hexToBytes(hex);
|
||||||
bytes.unshift(version);
|
bytes.unshift(version);
|
||||||
var hash = Crypto.SHA256(Crypto.SHA256(bytes, {
|
var hash = Crypto.SHA256(Crypto.SHA256(bytes, {
|
||||||
@ -203,10 +237,10 @@
|
|||||||
return coinjs.base58encode(bytes.concat(checksum));
|
return coinjs.base58encode(bytes.concat(checksum));
|
||||||
}
|
}
|
||||||
|
|
||||||
function decodeBech32(source) {
|
util.decodeBech32 = function (source) {
|
||||||
let decode = coinjs.bech32_decode(source);
|
let decode = coinjs.bech32_decode(source);
|
||||||
if (!decode)
|
if (!decode)
|
||||||
return null;
|
return false;
|
||||||
var raw = decode.data;
|
var raw = decode.data;
|
||||||
let version = raw.shift();
|
let version = raw.shift();
|
||||||
raw = coinjs.bech32_convert(raw, 5, 8, false);
|
raw = coinjs.bech32_convert(raw, 5, 8, false);
|
||||||
@ -217,7 +251,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function encodeBech32(hex, version, hrp) {
|
util.encodeBech32 = function (hex, version, hrp) {
|
||||||
var bytes = Crypto.util.hexToBytes(hex);
|
var bytes = Crypto.util.hexToBytes(hex);
|
||||||
bytes = coinjs.bech32_convert(bytes, 8, 5, true);
|
bytes = coinjs.bech32_convert(bytes, 8, 5, true);
|
||||||
bytes.unshift(version)
|
bytes.unshift(version)
|
||||||
@ -669,12 +703,15 @@
|
|||||||
btcOperator.checkIfSameTx = function (tx1, tx2) {
|
btcOperator.checkIfSameTx = function (tx1, tx2) {
|
||||||
tx1 = deserializeTx(tx1);
|
tx1 = deserializeTx(tx1);
|
||||||
tx2 = deserializeTx(tx2);
|
tx2 = deserializeTx(tx2);
|
||||||
|
//compare input and output length
|
||||||
if (tx1.ins.length !== tx2.ins.length || tx1.outs.length !== tx2.outs.length)
|
if (tx1.ins.length !== tx2.ins.length || tx1.outs.length !== tx2.outs.length)
|
||||||
return false;
|
return false;
|
||||||
|
//compare inputs
|
||||||
for (let i = 0; i < tx1.ins.length; i++)
|
for (let i = 0; i < tx1.ins.length; i++)
|
||||||
if (tx1.ins[i].outpoint.hash !== tx2.ins[i].outpoint.hash || tx1.ins[i].outpoint.index !== tx2.ins[i].outpoint.index)
|
if (tx1.ins[i].outpoint.hash !== tx2.ins[i].outpoint.hash || tx1.ins[i].outpoint.index !== tx2.ins[i].outpoint.index)
|
||||||
return false;
|
return false;
|
||||||
for (let i = 0; i < tx2.ins.length; i++)
|
//compare outputs
|
||||||
|
for (let i = 0; i < tx1.outs.length; i++)
|
||||||
if (tx1.outs[i].value !== tx2.outs[i].value || Crypto.util.bytesToHex(tx1.outs[i].script.buffer) !== Crypto.util.bytesToHex(tx2.outs[i].script.buffer))
|
if (tx1.outs[i].value !== tx2.outs[i].value || Crypto.util.bytesToHex(tx1.outs[i].script.buffer) !== Crypto.util.bytesToHex(tx2.outs[i].script.buffer))
|
||||||
return false;
|
return false;
|
||||||
return true;
|
return true;
|
||||||
@ -706,13 +743,13 @@
|
|||||||
var address;
|
var address;
|
||||||
switch (out.script.chunks[0]) {
|
switch (out.script.chunks[0]) {
|
||||||
case 0: //bech32, multisig-bech32
|
case 0: //bech32, multisig-bech32
|
||||||
address = encodeBech32(Crypto.util.bytesToHex(out.script.chunks[1]), coinjs.bech32.version, coinjs.bech32.hrp);
|
address = util.encodeBech32(Crypto.util.bytesToHex(out.script.chunks[1]), coinjs.bech32.version, coinjs.bech32.hrp);
|
||||||
break;
|
break;
|
||||||
case 169: //segwit, multisig-segwit
|
case 169: //segwit, multisig-segwit
|
||||||
address = encodeLegacy(Crypto.util.bytesToHex(out.script.chunks[1]), coinjs.multisig);
|
address = util.encodeLegacy(Crypto.util.bytesToHex(out.script.chunks[1]), coinjs.multisig);
|
||||||
break;
|
break;
|
||||||
case 118: //legacy
|
case 118: //legacy
|
||||||
address = encodeLegacy(Crypto.util.bytesToHex(out.script.chunks[2]), coinjs.pub);
|
address = util.encodeLegacy(Crypto.util.bytesToHex(out.script.chunks[2]), coinjs.pub);
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
address,
|
address,
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
(function (EXPORTS) { //floBlockchainAPI v2.3.3d
|
(function (EXPORTS) { //floBlockchainAPI v2.5.6b
|
||||||
/* 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;
|
||||||
@ -6,15 +6,24 @@
|
|||||||
const DEFAULT = {
|
const DEFAULT = {
|
||||||
blockchain: floGlobals.blockchain,
|
blockchain: floGlobals.blockchain,
|
||||||
apiURL: {
|
apiURL: {
|
||||||
FLO: ['https://flosight.duckdns.org/'],
|
FLO: ['https://flosight.ranchimall.net/'],
|
||||||
FLO_TEST: ['https://testnet-flosight.duckdns.org', 'https://testnet.flocha.in/']
|
FLO_TEST: ['https://flosight-testnet.ranchimall.net/']
|
||||||
},
|
},
|
||||||
sendAmt: 0.001,
|
sendAmt: 0.0003,
|
||||||
fee: 0.0005,
|
fee: 0.0002,
|
||||||
minChangeAmt: 0.0005,
|
minChangeAmt: 0.0002,
|
||||||
receiverID: floGlobals.adminID
|
receiverID: floGlobals.adminID
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const SATOSHI_IN_BTC = 1e8;
|
||||||
|
const isUndefined = val => typeof val === 'undefined';
|
||||||
|
|
||||||
|
const util = floBlockchainAPI.util = {};
|
||||||
|
|
||||||
|
util.Sat_to_FLO = value => parseFloat((value / SATOSHI_IN_BTC).toFixed(8));
|
||||||
|
util.FLO_to_Sat = value => parseInt(value * SATOSHI_IN_BTC);
|
||||||
|
util.toFixed = value => parseFloat((value).toFixed(8));
|
||||||
|
|
||||||
Object.defineProperties(floBlockchainAPI, {
|
Object.defineProperties(floBlockchainAPI, {
|
||||||
sendAmt: {
|
sendAmt: {
|
||||||
get: () => DEFAULT.sendAmt,
|
get: () => DEFAULT.sendAmt,
|
||||||
@ -103,9 +112,11 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
//Promised function to get data from API
|
//Promised function to get data from API
|
||||||
const promisedAPI = floBlockchainAPI.promisedAPI = floBlockchainAPI.fetch = function (apicall) {
|
const promisedAPI = floBlockchainAPI.promisedAPI = floBlockchainAPI.fetch = function (apicall, query_params = undefined) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
//console.log(apicall);
|
if (!isUndefined(query_params))
|
||||||
|
apicall += '?' + new URLSearchParams(JSON.parse(JSON.stringify(query_params))).toString();
|
||||||
|
//console.debug(apicall);
|
||||||
fetch_api(apicall)
|
fetch_api(apicall)
|
||||||
.then(result => resolve(result))
|
.then(result => resolve(result))
|
||||||
.catch(error => reject(error));
|
.catch(error => reject(error));
|
||||||
@ -113,25 +124,55 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Get balance for the given Address
|
//Get balance for the given Address
|
||||||
const getBalance = floBlockchainAPI.getBalance = function (addr) {
|
const getBalance = floBlockchainAPI.getBalance = function (addr, after = null) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
promisedAPI(`api/addr/${addr}/balance`)
|
let api = `api/addr/${addr}/balance`, query_params = {};
|
||||||
.then(balance => resolve(parseFloat(balance)))
|
if (after) {
|
||||||
.catch(error => reject(error));
|
if (typeof after === 'string' && /^[0-9a-z]{64}$/i.test(after))
|
||||||
|
query_params.after = after;
|
||||||
|
else return reject("Invalid 'after' parameter");
|
||||||
|
}
|
||||||
|
promisedAPI(api, query_params).then(result => {
|
||||||
|
if (typeof result === 'object' && result.lastItem) {
|
||||||
|
getBalance(addr, result.lastItem)
|
||||||
|
.then(r => resolve(util.toFixed(r + result.data)))
|
||||||
|
.catch(error => reject(error))
|
||||||
|
} else resolve(result);
|
||||||
|
}).catch(error => reject(error))
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
//Send Tx to blockchain
|
const getUTXOs = address => new Promise((resolve, reject) => {
|
||||||
const sendTx = floBlockchainAPI.sendTx = function (senderAddr, receiverAddr, sendAmt, privKey, floData = '', strict_utxo = true) {
|
promisedAPI(`api/addr/${address}/utxo`)
|
||||||
|
.then(utxo => resolve(utxo))
|
||||||
|
.catch(error => reject(error))
|
||||||
|
})
|
||||||
|
|
||||||
|
const getUnconfirmedSpent = address => new Promise((resolve, reject) => {
|
||||||
|
readTxs(address, { mempool: "only" }).then(result => {
|
||||||
|
let unconfirmedSpent = {};
|
||||||
|
for (let tx of result.items)
|
||||||
|
if (tx.confirmations == 0)
|
||||||
|
for (let vin of tx.vin)
|
||||||
|
if (vin.addr === address) {
|
||||||
|
if (Array.isArray(unconfirmedSpent[vin.txid]))
|
||||||
|
unconfirmedSpent[vin.txid].push(vin.vout);
|
||||||
|
else
|
||||||
|
unconfirmedSpent[vin.txid] = [vin.vout];
|
||||||
|
}
|
||||||
|
resolve(unconfirmedSpent);
|
||||||
|
}).catch(error => reject(error))
|
||||||
|
})
|
||||||
|
|
||||||
|
//create a transaction with single sender
|
||||||
|
const createTx = function (senderAddr, receiverAddr, sendAmt, floData = '', strict_utxo = true) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
if (!floCrypto.validateASCII(floData))
|
if (!floCrypto.validateASCII(floData))
|
||||||
return reject("Invalid FLO_Data: only printable ASCII characters are allowed");
|
return reject("Invalid FLO_Data: only printable ASCII characters are allowed");
|
||||||
else if (!floCrypto.validateFloID(senderAddr))
|
else if (!floCrypto.validateFloID(senderAddr, true))
|
||||||
return reject(`Invalid address : ${senderAddr}`);
|
return reject(`Invalid address : ${senderAddr}`);
|
||||||
else if (!floCrypto.validateFloID(receiverAddr))
|
else if (!floCrypto.validateFloID(receiverAddr))
|
||||||
return reject(`Invalid address : ${receiverAddr}`);
|
return reject(`Invalid address : ${receiverAddr}`);
|
||||||
else if (privKey.length < 1 || !floCrypto.verifyPrivKey(privKey, senderAddr))
|
|
||||||
return reject("Invalid Private key!");
|
|
||||||
else if (typeof sendAmt !== 'number' || sendAmt <= 0)
|
else if (typeof sendAmt !== 'number' || sendAmt <= 0)
|
||||||
return reject(`Invalid sendAmt : ${sendAmt}`);
|
return reject(`Invalid sendAmt : ${sendAmt}`);
|
||||||
|
|
||||||
@ -139,21 +180,8 @@
|
|||||||
var fee = DEFAULT.fee;
|
var fee = DEFAULT.fee;
|
||||||
if (balance < sendAmt + fee)
|
if (balance < sendAmt + fee)
|
||||||
return reject("Insufficient FLO balance!");
|
return reject("Insufficient FLO balance!");
|
||||||
//get unconfirmed tx list
|
getUnconfirmedSpent(senderAddr).then(unconfirmedSpent => {
|
||||||
promisedAPI(`api/addr/${senderAddr}`).then(result => {
|
getUTXOs(senderAddr).then(utxos => {
|
||||||
readTxs(senderAddr, 0, result.unconfirmedTxApperances).then(result => {
|
|
||||||
let unconfirmedSpent = {};
|
|
||||||
for (let tx of result.items)
|
|
||||||
if (tx.confirmations == 0)
|
|
||||||
for (let vin of tx.vin)
|
|
||||||
if (vin.addr === senderAddr) {
|
|
||||||
if (Array.isArray(unconfirmedSpent[vin.txid]))
|
|
||||||
unconfirmedSpent[vin.txid].push(vin.vout);
|
|
||||||
else
|
|
||||||
unconfirmedSpent[vin.txid] = [vin.vout];
|
|
||||||
}
|
|
||||||
//get utxos list
|
|
||||||
promisedAPI(`api/addr/${senderAddr}/utxo`).then(utxos => {
|
|
||||||
//form/construct the transaction data
|
//form/construct the transaction data
|
||||||
var trx = bitjs.transaction();
|
var trx = bitjs.transaction();
|
||||||
var utxoAmt = 0.0;
|
var utxoAmt = 0.0;
|
||||||
@ -175,14 +203,34 @@
|
|||||||
if (change > DEFAULT.minChangeAmt)
|
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);
|
resolve(trx);
|
||||||
broadcastTx(signedTxHash)
|
|
||||||
.then(txid => resolve(txid))
|
|
||||||
.catch(error => reject(error))
|
|
||||||
}
|
}
|
||||||
}).catch(error => reject(error))
|
}).catch(error => reject(error))
|
||||||
}).catch(error => reject(error))
|
}).catch(error => reject(error))
|
||||||
}).catch(error => reject(error))
|
}).catch(error => reject(error))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
floBlockchainAPI.createTx = function (senderAddr, receiverAddr, sendAmt, floData = '', strict_utxo = true) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
createTx(senderAddr, receiverAddr, sendAmt, floData, strict_utxo)
|
||||||
|
.then(trx => resolve(trx.serialize()))
|
||||||
|
.catch(error => reject(error))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
//Send Tx to blockchain
|
||||||
|
const sendTx = floBlockchainAPI.sendTx = function (senderAddr, receiverAddr, sendAmt, privKey, floData = '', strict_utxo = true) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if (!floCrypto.validateFloID(senderAddr, true))
|
||||||
|
return reject(`Invalid address : ${senderAddr}`);
|
||||||
|
else if (privKey.length < 1 || !floCrypto.verifyPrivKey(privKey, senderAddr))
|
||||||
|
return reject("Invalid Private key!");
|
||||||
|
createTx(senderAddr, receiverAddr, sendAmt, floData, strict_utxo).then(trx => {
|
||||||
|
var signedTxHash = trx.sign(privKey, 1);
|
||||||
|
broadcastTx(signedTxHash)
|
||||||
|
.then(txid => resolve(txid))
|
||||||
|
.catch(error => reject(error))
|
||||||
}).catch(error => reject(error))
|
}).catch(error => reject(error))
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -203,7 +251,7 @@
|
|||||||
//merge all UTXOs of a given floID into a single UTXO
|
//merge all UTXOs of a given floID into a single UTXO
|
||||||
floBlockchainAPI.mergeUTXOs = function (floID, privKey, floData = '') {
|
floBlockchainAPI.mergeUTXOs = function (floID, privKey, floData = '') {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
if (!floCrypto.validateFloID(floID))
|
if (!floCrypto.validateFloID(floID, true))
|
||||||
return reject(`Invalid floID`);
|
return reject(`Invalid floID`);
|
||||||
if (!floCrypto.verifyPrivKey(privKey, floID))
|
if (!floCrypto.verifyPrivKey(privKey, floID))
|
||||||
return reject("Invalid Private Key");
|
return reject("Invalid Private Key");
|
||||||
@ -212,7 +260,7 @@
|
|||||||
var trx = bitjs.transaction();
|
var trx = bitjs.transaction();
|
||||||
var utxoAmt = 0.0;
|
var utxoAmt = 0.0;
|
||||||
var fee = DEFAULT.fee;
|
var fee = DEFAULT.fee;
|
||||||
promisedAPI(`api/addr/${floID}/utxo`).then(utxos => {
|
getUTXOs(floID).then(utxos => {
|
||||||
for (var i = utxos.length - 1; i >= 0; i--)
|
for (var i = utxos.length - 1; i >= 0; i--)
|
||||||
if (utxos[i].confirmations) {
|
if (utxos[i].confirmations) {
|
||||||
trx.addinput(utxos[i].txid, utxos[i].vout, utxos[i].scriptPubKey);
|
trx.addinput(utxos[i].txid, utxos[i].vout, utxos[i].scriptPubKey);
|
||||||
@ -228,6 +276,56 @@
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//split sufficient UTXOs of a given floID for a parallel sending
|
||||||
|
floBlockchainAPI.splitUTXOs = function (floID, privKey, count, floData = '') {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if (!floCrypto.validateFloID(floID, true))
|
||||||
|
return reject(`Invalid floID`);
|
||||||
|
if (!floCrypto.verifyPrivKey(privKey, floID))
|
||||||
|
return reject("Invalid Private Key");
|
||||||
|
if (!floCrypto.validateASCII(floData))
|
||||||
|
return reject("Invalid FLO_Data: only printable ASCII characters are allowed");
|
||||||
|
var fee = DEFAULT.fee;
|
||||||
|
var splitAmt = DEFAULT.sendAmt + fee;
|
||||||
|
var totalAmt = splitAmt * count;
|
||||||
|
getBalance(floID).then(balance => {
|
||||||
|
var fee = DEFAULT.fee;
|
||||||
|
if (balance < totalAmt + fee)
|
||||||
|
return reject("Insufficient FLO balance!");
|
||||||
|
//get unconfirmed tx list
|
||||||
|
getUnconfirmedSpent(floID).then(unconfirmedSpent => {
|
||||||
|
getUTXOs(floID).then(utxos => {
|
||||||
|
var trx = bitjs.transaction();
|
||||||
|
var utxoAmt = 0.0;
|
||||||
|
for (let i = utxos.length - 1; (i >= 0) && (utxoAmt < totalAmt + fee); i--) {
|
||||||
|
//use only utxos with confirmations (strict_utxo mode)
|
||||||
|
if (utxos[i].confirmations || !strict_utxo) {
|
||||||
|
if (utxos[i].txid in unconfirmedSpent && unconfirmedSpent[utxos[i].txid].includes(utxos[i].vout))
|
||||||
|
continue; //A transaction has already used the utxo, but is unconfirmed.
|
||||||
|
trx.addinput(utxos[i].txid, utxos[i].vout, utxos[i].scriptPubKey);
|
||||||
|
utxoAmt += utxos[i].amount;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (utxoAmt < totalAmt + fee)
|
||||||
|
reject("Insufficient FLO: Some UTXOs are unconfirmed");
|
||||||
|
else {
|
||||||
|
for (let i = 0; i < count; i++)
|
||||||
|
trx.addoutput(floID, splitAmt);
|
||||||
|
var change = utxoAmt - totalAmt - fee;
|
||||||
|
if (change > DEFAULT.minChangeAmt)
|
||||||
|
trx.addoutput(floID, change);
|
||||||
|
trx.addflodata(floData.replace(/\n/g, ' '));
|
||||||
|
var signedTxHash = trx.sign(privKey, 1);
|
||||||
|
broadcastTx(signedTxHash)
|
||||||
|
.then(txid => resolve(txid))
|
||||||
|
.catch(error => reject(error))
|
||||||
|
}
|
||||||
|
}).catch(error => reject(error))
|
||||||
|
}).catch(error => reject(error))
|
||||||
|
}).catch(error => reject(error))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/**Write data into blockchain from (and/or) to multiple floID
|
/**Write data into blockchain from (and/or) to multiple floID
|
||||||
* @param {Array} senderPrivKeys List of sender private-keys
|
* @param {Array} senderPrivKeys List of sender private-keys
|
||||||
* @param {string} data FLO data of the txn
|
* @param {string} data FLO data of the txn
|
||||||
@ -235,11 +333,11 @@
|
|||||||
* @param {boolean} preserveRatio (optional) preserve ratio or equal contribution
|
* @param {boolean} preserveRatio (optional) preserve ratio or equal contribution
|
||||||
* @return {Promise}
|
* @return {Promise}
|
||||||
*/
|
*/
|
||||||
floBlockchainAPI.writeDataMultiple = function (senderPrivKeys, data, receivers = [DEFAULT.receiverID], preserveRatio = true) {
|
floBlockchainAPI.writeDataMultiple = function (senderPrivKeys, data, receivers = [DEFAULT.receiverID], options = {}) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
if (!Array.isArray(senderPrivKeys))
|
if (!Array.isArray(senderPrivKeys))
|
||||||
return reject("Invalid senderPrivKeys: SenderPrivKeys must be Array");
|
return reject("Invalid senderPrivKeys: SenderPrivKeys must be Array");
|
||||||
if (!preserveRatio) {
|
if (options.preserveRatio === false) {
|
||||||
let tmp = {};
|
let tmp = {};
|
||||||
let amount = (DEFAULT.sendAmt * receivers.length) / senderPrivKeys.length;
|
let amount = (DEFAULT.sendAmt * receivers.length) / senderPrivKeys.length;
|
||||||
senderPrivKeys.forEach(key => tmp[key] = amount);
|
senderPrivKeys.forEach(key => tmp[key] = amount);
|
||||||
@ -249,7 +347,7 @@
|
|||||||
return reject("Invalid receivers: Receivers must be Array");
|
return reject("Invalid receivers: Receivers must be Array");
|
||||||
else {
|
else {
|
||||||
let tmp = {};
|
let tmp = {};
|
||||||
let amount = DEFAULT.sendAmt;
|
let amount = options.sendAmt || DEFAULT.sendAmt;
|
||||||
receivers.forEach(floID => tmp[floID] = amount);
|
receivers.forEach(floID => tmp[floID] = amount);
|
||||||
receivers = tmp
|
receivers = tmp
|
||||||
}
|
}
|
||||||
@ -379,9 +477,8 @@
|
|||||||
//Get the UTXOs of the senders
|
//Get the UTXOs of the senders
|
||||||
let promises = [];
|
let promises = [];
|
||||||
for (let floID in senders)
|
for (let floID in senders)
|
||||||
promises.push(promisedAPI(`api/addr/${floID}/utxo`));
|
promises.push(getUTXOs(floID));
|
||||||
Promise.all(promises).then(results => {
|
Promise.all(promises).then(results => {
|
||||||
let wifSeq = [];
|
|
||||||
var trx = bitjs.transaction();
|
var trx = bitjs.transaction();
|
||||||
for (let floID in senders) {
|
for (let floID in senders) {
|
||||||
let utxos = results.shift();
|
let utxos = results.shift();
|
||||||
@ -391,13 +488,11 @@
|
|||||||
sendAmt = totalSendAmt * ratio;
|
sendAmt = totalSendAmt * ratio;
|
||||||
} else
|
} else
|
||||||
sendAmt = senders[floID].coins + dividedFee;
|
sendAmt = senders[floID].coins + dividedFee;
|
||||||
let wif = senders[floID].wif;
|
|
||||||
let utxoAmt = 0.0;
|
let utxoAmt = 0.0;
|
||||||
for (let i = utxos.length - 1;
|
for (let i = utxos.length - 1;
|
||||||
(i >= 0) && (utxoAmt < sendAmt); i--) {
|
(i >= 0) && (utxoAmt < sendAmt); i--) {
|
||||||
if (utxos[i].confirmations) {
|
if (utxos[i].confirmations) {
|
||||||
trx.addinput(utxos[i].txid, utxos[i].vout, utxos[i].scriptPubKey);
|
trx.addinput(utxos[i].txid, utxos[i].vout, utxos[i].scriptPubKey);
|
||||||
wifSeq.push(wif);
|
|
||||||
utxoAmt += utxos[i].amount;
|
utxoAmt += utxos[i].amount;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -410,8 +505,8 @@
|
|||||||
for (let floID in receivers)
|
for (let floID in receivers)
|
||||||
trx.addoutput(floID, receivers[floID]);
|
trx.addoutput(floID, receivers[floID]);
|
||||||
trx.addflodata(floData.replace(/\n/g, ' '));
|
trx.addflodata(floData.replace(/\n/g, ' '));
|
||||||
for (let i = 0; i < wifSeq.length; i++)
|
for (let floID in senders)
|
||||||
trx.signinput(i, wifSeq[i], 1);
|
trx.sign(senders[floID].wif, 1);
|
||||||
var signedTxHash = trx.serialize();
|
var signedTxHash = trx.serialize();
|
||||||
broadcastTx(signedTxHash)
|
broadcastTx(signedTxHash)
|
||||||
.then(txid => resolve(txid))
|
.then(txid => resolve(txid))
|
||||||
@ -421,6 +516,259 @@
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Create a multisig transaction
|
||||||
|
const createMultisigTx = function (redeemScript, receivers, amounts, floData = '', strict_utxo = true) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
var multisig = floCrypto.decodeRedeemScript(redeemScript);
|
||||||
|
|
||||||
|
//validate multisig script and flodata
|
||||||
|
if (!multisig)
|
||||||
|
return reject(`Invalid redeemScript`);
|
||||||
|
var senderAddr = multisig.address;
|
||||||
|
if (!floCrypto.validateFloID(senderAddr))
|
||||||
|
return reject(`Invalid multisig : ${senderAddr}`);
|
||||||
|
else if (!floCrypto.validateASCII(floData))
|
||||||
|
return reject("Invalid FLO_Data: only printable ASCII characters are allowed");
|
||||||
|
//validate receiver addresses
|
||||||
|
if (!Array.isArray(receivers))
|
||||||
|
receivers = [receivers];
|
||||||
|
for (let r of receivers)
|
||||||
|
if (!floCrypto.validateFloID(r))
|
||||||
|
return reject(`Invalid address : ${r}`);
|
||||||
|
//validate amounts
|
||||||
|
if (!Array.isArray(amounts))
|
||||||
|
amounts = [amounts];
|
||||||
|
if (amounts.length != receivers.length)
|
||||||
|
return reject("Receivers and amounts have different length");
|
||||||
|
var sendAmt = 0;
|
||||||
|
for (let a of amounts) {
|
||||||
|
if (typeof a !== 'number' || a <= 0)
|
||||||
|
return reject(`Invalid amount : ${a}`);
|
||||||
|
sendAmt += a;
|
||||||
|
}
|
||||||
|
|
||||||
|
getBalance(senderAddr).then(balance => {
|
||||||
|
var fee = DEFAULT.fee;
|
||||||
|
if (balance < sendAmt + fee)
|
||||||
|
return reject("Insufficient FLO balance!");
|
||||||
|
getUnconfirmedSpent(senderAddr).then(unconfirmedSpent => {
|
||||||
|
getUTXOs(senderAddr).then(utxos => {
|
||||||
|
//form/construct the transaction data
|
||||||
|
var trx = bitjs.transaction();
|
||||||
|
var utxoAmt = 0.0;
|
||||||
|
for (var i = utxos.length - 1;
|
||||||
|
(i >= 0) && (utxoAmt < sendAmt + fee); i--) {
|
||||||
|
//use only utxos with confirmations (strict_utxo mode)
|
||||||
|
if (utxos[i].confirmations || !strict_utxo) {
|
||||||
|
if (utxos[i].txid in unconfirmedSpent && unconfirmedSpent[utxos[i].txid].includes(utxos[i].vout))
|
||||||
|
continue; //A transaction has already used the utxo, but is unconfirmed.
|
||||||
|
trx.addinput(utxos[i].txid, utxos[i].vout, redeemScript); //for multisig, script=redeemScript
|
||||||
|
utxoAmt += utxos[i].amount;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (utxoAmt < sendAmt + fee)
|
||||||
|
reject("Insufficient FLO: Some UTXOs are unconfirmed");
|
||||||
|
else {
|
||||||
|
for (let i in receivers)
|
||||||
|
trx.addoutput(receivers[i], amounts[i]);
|
||||||
|
var change = utxoAmt - sendAmt - fee;
|
||||||
|
if (change > DEFAULT.minChangeAmt)
|
||||||
|
trx.addoutput(senderAddr, change);
|
||||||
|
trx.addflodata(floData.replace(/\n/g, ' '));
|
||||||
|
resolve(trx);
|
||||||
|
}
|
||||||
|
}).catch(error => reject(error))
|
||||||
|
}).catch(error => reject(error))
|
||||||
|
}).catch(error => reject(error))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
//Same as above, but explict call should return serialized tx-hex
|
||||||
|
floBlockchainAPI.createMultisigTx = function (redeemScript, receivers, amounts, floData = '', strict_utxo = true) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
createMultisigTx(redeemScript, receivers, amounts, floData, strict_utxo)
|
||||||
|
.then(trx => resolve(trx.serialize()))
|
||||||
|
.catch(error => reject(error))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
//Create and send multisig transaction
|
||||||
|
const sendMultisigTx = floBlockchainAPI.sendMultisigTx = function (redeemScript, privateKeys, receivers, amounts, floData = '', strict_utxo = true) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
var multisig = floCrypto.decodeRedeemScript(redeemScript);
|
||||||
|
if (!multisig)
|
||||||
|
return reject(`Invalid redeemScript`);
|
||||||
|
if (privateKeys.length < multisig.required)
|
||||||
|
return reject(`Insufficient privateKeys (required ${multisig.required})`);
|
||||||
|
for (let pk of privateKeys) {
|
||||||
|
var flag = false;
|
||||||
|
for (let pub of multisig.pubkeys)
|
||||||
|
if (floCrypto.verifyPrivKey(pk, pub, false))
|
||||||
|
flag = true;
|
||||||
|
if (!flag)
|
||||||
|
return reject(`Invalid Private key`);
|
||||||
|
}
|
||||||
|
createMultisigTx(redeemScript, receivers, amounts, floData, strict_utxo).then(trx => {
|
||||||
|
for (let pk of privateKeys)
|
||||||
|
trx.sign(pk, 1);
|
||||||
|
var signedTxHash = trx.serialize();
|
||||||
|
broadcastTx(signedTxHash)
|
||||||
|
.then(txid => resolve(txid))
|
||||||
|
.catch(error => reject(error))
|
||||||
|
}).catch(error => reject(error))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
floBlockchainAPI.writeMultisigData = function (redeemScript, data, privatekeys, receiverAddr = DEFAULT.receiverID, options = {}) {
|
||||||
|
let strict_utxo = options.strict_utxo === false ? false : true,
|
||||||
|
sendAmt = isNaN(options.sendAmt) ? DEFAULT.sendAmt : options.sendAmt;
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if (!floCrypto.validateFloID(receiverAddr))
|
||||||
|
return reject(`Invalid receiver: ${receiverAddr}`);
|
||||||
|
sendMultisigTx(redeemScript, privatekeys, receiverAddr, sendAmt, data, strict_utxo)
|
||||||
|
.then(txid => resolve(txid))
|
||||||
|
.catch(error => reject(error))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function deserializeTx(tx) {
|
||||||
|
if (typeof tx === 'string' || Array.isArray(tx)) {
|
||||||
|
try {
|
||||||
|
tx = bitjs.transaction(tx);
|
||||||
|
} catch {
|
||||||
|
throw "Invalid transaction hex";
|
||||||
|
}
|
||||||
|
} else if (typeof tx !== 'object' || typeof tx.sign !== 'function')
|
||||||
|
throw "Invalid transaction object";
|
||||||
|
return tx;
|
||||||
|
}
|
||||||
|
|
||||||
|
floBlockchainAPI.signTx = function (tx, privateKey, sighashtype = 1) {
|
||||||
|
if (!floCrypto.getFloID(privateKey))
|
||||||
|
throw "Invalid Private key";
|
||||||
|
//deserialize if needed
|
||||||
|
tx = deserializeTx(tx);
|
||||||
|
var signedTxHex = tx.sign(privateKey, sighashtype);
|
||||||
|
return signedTxHex;
|
||||||
|
}
|
||||||
|
|
||||||
|
const checkSigned = floBlockchainAPI.checkSigned = function (tx, bool = true) {
|
||||||
|
tx = deserializeTx(tx);
|
||||||
|
let n = [];
|
||||||
|
for (let i = 0; i < tx.inputs.length; i++) {
|
||||||
|
var s = tx.scriptDecode(i);
|
||||||
|
if (s['type'] === 'scriptpubkey')
|
||||||
|
n.push(s.signed);
|
||||||
|
else if (s['type'] === 'multisig') {
|
||||||
|
var rs = tx.decodeRedeemScript(s['rs']);
|
||||||
|
let x = {
|
||||||
|
s: 0,
|
||||||
|
r: rs['required'],
|
||||||
|
t: rs['pubkeys'].length
|
||||||
|
};
|
||||||
|
//check input script for signatures
|
||||||
|
var script = Array.from(tx.inputs[i].script);
|
||||||
|
if (script[0] == 0) { //script with signatures
|
||||||
|
script = tx.parseScript(script);
|
||||||
|
for (var k = 0; k < script.length; k++)
|
||||||
|
if (Array.isArray(script[k]) && script[k][0] == 48) //0x30 DERSequence
|
||||||
|
x.s++;
|
||||||
|
}
|
||||||
|
//validate counts
|
||||||
|
if (x.r > x.t)
|
||||||
|
throw "signaturesRequired is more than publicKeys";
|
||||||
|
else if (x.s < x.r)
|
||||||
|
n.push(x);
|
||||||
|
else
|
||||||
|
n.push(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return bool ? !(n.filter(x => x !== true).length) : n;
|
||||||
|
}
|
||||||
|
|
||||||
|
floBlockchainAPI.checkIfSameTx = function (tx1, tx2) {
|
||||||
|
tx1 = deserializeTx(tx1);
|
||||||
|
tx2 = deserializeTx(tx2);
|
||||||
|
//compare input and output length
|
||||||
|
if (tx1.inputs.length !== tx2.inputs.length || tx1.outputs.length !== tx2.outputs.length)
|
||||||
|
return false;
|
||||||
|
//compare flodata
|
||||||
|
if (tx1.floData !== tx2.floData)
|
||||||
|
return false
|
||||||
|
//compare inputs
|
||||||
|
for (let i = 0; i < tx1.inputs.length; i++)
|
||||||
|
if (tx1.inputs[i].outpoint.hash !== tx2.inputs[i].outpoint.hash || tx1.inputs[i].outpoint.index !== tx2.inputs[i].outpoint.index)
|
||||||
|
return false;
|
||||||
|
//compare outputs
|
||||||
|
for (let i = 0; i < tx1.outputs.length; i++)
|
||||||
|
if (tx1.outputs[i].value !== tx2.outputs[i].value || Crypto.util.bytesToHex(tx1.outputs[i].script) !== Crypto.util.bytesToHex(tx2.outputs[i].script))
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
floBlockchainAPI.transactionID = function (tx) {
|
||||||
|
tx = deserializeTx(tx);
|
||||||
|
let clone = bitjs.clone(tx);
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
const getTxOutput = (txid, i) => new Promise((resolve, reject) => {
|
||||||
|
promisedAPI(`api/tx/${txid}`)
|
||||||
|
.then(result => resolve(result.vout[i]))
|
||||||
|
.catch(error => reject(error))
|
||||||
|
});
|
||||||
|
|
||||||
|
function getOutputAddress(outscript) {
|
||||||
|
var bytes, version;
|
||||||
|
switch (outscript[0]) {
|
||||||
|
case 118: //legacy
|
||||||
|
bytes = outscript.slice(3, outscript.length - 2);
|
||||||
|
version = bitjs.pub;
|
||||||
|
break
|
||||||
|
case 169: //multisig
|
||||||
|
bytes = outscript.slice(2, outscript.length - 1);
|
||||||
|
version = bitjs.multisig;
|
||||||
|
break;
|
||||||
|
default: return; //unknown
|
||||||
|
}
|
||||||
|
bytes.unshift(version);
|
||||||
|
var hash = Crypto.SHA256(Crypto.SHA256(bytes, { asBytes: true }), { asBytes: true });
|
||||||
|
var checksum = hash.slice(0, 4);
|
||||||
|
return bitjs.Base58.encode(bytes.concat(checksum));
|
||||||
|
}
|
||||||
|
|
||||||
|
floBlockchainAPI.parseTransaction = function (tx) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
tx = deserializeTx(tx);
|
||||||
|
let result = {};
|
||||||
|
let promises = [];
|
||||||
|
//Parse Inputs
|
||||||
|
for (let i = 0; i < tx.inputs.length; i++)
|
||||||
|
promises.push(getTxOutput(tx.inputs[i].outpoint.hash, tx.inputs[i].outpoint.index));
|
||||||
|
Promise.all(promises).then(inputs => {
|
||||||
|
result.inputs = inputs.map(inp => Object({
|
||||||
|
address: inp.scriptPubKey.addresses[0],
|
||||||
|
value: parseFloat(inp.value)
|
||||||
|
}));
|
||||||
|
let signed = checkSigned(tx, false);
|
||||||
|
result.inputs.forEach((inp, i) => inp.signed = signed[i]);
|
||||||
|
//Parse Outputs
|
||||||
|
result.outputs = tx.outputs.map(out => Object({
|
||||||
|
address: getOutputAddress(out.script),
|
||||||
|
value: util.Sat_to_FLO(out.value)
|
||||||
|
}))
|
||||||
|
//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));
|
||||||
|
result.floData = tx.floData;
|
||||||
|
resolve(result);
|
||||||
|
}).catch(error => reject(error))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
//Broadcast signed Tx in blockchain using API
|
//Broadcast signed Tx in blockchain using API
|
||||||
const broadcastTx = floBlockchainAPI.broadcastTx = function (signedTxHash) {
|
const broadcastTx = floBlockchainAPI.broadcastTx = function (signedTxHash) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
@ -442,7 +790,7 @@
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
floBlockchainAPI.getTx = function (txid) {
|
const getTx = floBlockchainAPI.getTx = function (txid) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
promisedAPI(`api/tx/${txid}`)
|
promisedAPI(`api/tx/${txid}`)
|
||||||
.then(response => resolve(response))
|
.then(response => resolve(response))
|
||||||
@ -450,30 +798,89 @@
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
//Read Txs of Address between from and to
|
/**Wait for the given txid to get confirmation in blockchain
|
||||||
const readTxs = floBlockchainAPI.readTxs = function (addr, from, to) {
|
* @param {string} txid of the transaction to wait for
|
||||||
|
* @param {int} max_retry: maximum number of retries before exiting wait. negative number = Infinite retries (DEFAULT: -1 ie, infinite retries)
|
||||||
|
* @param {Array} retry_timeout: time (seconds) between retries (DEFAULT: 20 seconds)
|
||||||
|
* @return {Promise} resolves when tx gets confirmation
|
||||||
|
*/
|
||||||
|
const waitForConfirmation = floBlockchainAPI.waitForConfirmation = function (txid, max_retry = -1, retry_timeout = 20) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
promisedAPI(`api/addrs/${addr}/txs?from=${from}&to=${to}`)
|
setTimeout(function () {
|
||||||
|
getTx(txid).then(tx => {
|
||||||
|
if (!tx)
|
||||||
|
return reject("Transaction not found");
|
||||||
|
if (tx.confirmations)
|
||||||
|
return resolve(tx);
|
||||||
|
else if (max_retry === 0) //no more retries
|
||||||
|
return reject("Waiting timeout: tx still not confirmed");
|
||||||
|
else {
|
||||||
|
max_retry = max_retry < 0 ? -1 : max_retry - 1; //decrease retry count (unless infinite retries)
|
||||||
|
waitForConfirmation(txid, max_retry, retry_timeout)
|
||||||
|
.then(result => resolve(result))
|
||||||
|
.catch(error => reject(error))
|
||||||
|
}
|
||||||
|
}).catch(error => reject(error))
|
||||||
|
}, retry_timeout * 1000)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
//Read Txs of Address between from and to
|
||||||
|
const readTxs = floBlockchainAPI.readTxs = function (addr, options = {}) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
let api = `api/addrs/${addr}/txs`;
|
||||||
|
//API options
|
||||||
|
let query_params = {};
|
||||||
|
if (!isUndefined(options.after) || !isUndefined(options.before)) {
|
||||||
|
if (!isUndefined(options.after))
|
||||||
|
query_params.after = options.after;
|
||||||
|
if (!isUndefined(options.before))
|
||||||
|
query_params.before = options.before;
|
||||||
|
} else {
|
||||||
|
if (!isUndefined(options.from))
|
||||||
|
query_params.from = options.from;
|
||||||
|
if (!isUndefined(options.to))
|
||||||
|
query_params.to = options.to;
|
||||||
|
}
|
||||||
|
if (!isUndefined(options.latest))
|
||||||
|
query_params.latest = options.latest;
|
||||||
|
if (!isUndefined(options.mempool))
|
||||||
|
query_params.mempool = options.mempool;
|
||||||
|
promisedAPI(api, query_params)
|
||||||
.then(response => resolve(response))
|
.then(response => resolve(response))
|
||||||
.catch(error => reject(error))
|
.catch(error => reject(error))
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
//Read All Txs of Address (newest first)
|
//Read All Txs of Address (newest first)
|
||||||
floBlockchainAPI.readAllTxs = function (addr) {
|
const readAllTxs = floBlockchainAPI.readAllTxs = function (addr, options = {}) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
promisedAPI(`api/addrs/${addr}/txs?from=0&to=1`).then(response => {
|
readTxs(addr, options).then(response => {
|
||||||
promisedAPI(`api/addrs/${addr}/txs?from=0&to=${response.totalItems}0`)
|
if (response.incomplete) {
|
||||||
.then(response => resolve(response.items))
|
let next_options = Object.assign({}, options);
|
||||||
.catch(error => reject(error));
|
if (options.latest)
|
||||||
|
next_options.before = response.initItem; //update before for chain query (latest 1st)
|
||||||
|
else
|
||||||
|
next_options.after = response.lastItem; //update after for chain query (oldest 1st)
|
||||||
|
readAllTxs(addr, next_options).then(r => {
|
||||||
|
r.items = r.items.concat(response.items); //latest tx are 1st in array
|
||||||
|
resolve(r);
|
||||||
}).catch(error => reject(error))
|
}).catch(error => reject(error))
|
||||||
|
} else
|
||||||
|
resolve({
|
||||||
|
lastItem: response.lastItem || options.after,
|
||||||
|
items: response.items
|
||||||
|
});
|
||||||
|
})
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/*Read flo Data from txs of given Address
|
/*Read flo Data from txs of given Address
|
||||||
options can be used to filter data
|
options can be used to filter data
|
||||||
limit : maximum number of filtered data (default = 1000, negative = no limit)
|
after : query after the given txid
|
||||||
ignoreOld : ignore old txs (default = 0)
|
before : query before the given txid
|
||||||
|
mempool : query mempool tx or not (options same as readAllTx, DEFAULT=false: ignore unconfirmed tx)
|
||||||
|
ignoreOld : ignore old txs (deprecated: support for backward compatibility only, cannot be used with 'after')
|
||||||
sentOnly : filters only sent data
|
sentOnly : filters only sent data
|
||||||
receivedOnly: filters only received data
|
receivedOnly: filters only received data
|
||||||
pattern : filters data that with JSON pattern
|
pattern : filters data that with JSON pattern
|
||||||
@ -483,98 +890,149 @@
|
|||||||
receiver : flo-id(s) of receiver
|
receiver : flo-id(s) of receiver
|
||||||
*/
|
*/
|
||||||
floBlockchainAPI.readData = function (addr, options = {}) {
|
floBlockchainAPI.readData = function (addr, options = {}) {
|
||||||
options.limit = options.limit || 0;
|
return new Promise((resolve, reject) => {
|
||||||
options.ignoreOld = options.ignoreOld || 0;
|
|
||||||
|
//fetch options
|
||||||
|
let query_options = {};
|
||||||
|
query_options.mempool = isUndefined(options.mempool) ? false : options.mempool; //DEFAULT: ignore unconfirmed tx
|
||||||
|
if (!isUndefined(options.after) || !isUndefined(options.before)) {
|
||||||
|
if (!isUndefined(options.ignoreOld)) //Backward support
|
||||||
|
return reject("Invalid options: cannot use after/before and ignoreOld in same query");
|
||||||
|
//use passed after and/or before options (options remain undefined if not passed)
|
||||||
|
query_options.after = options.after;
|
||||||
|
query_options.before = options.before;
|
||||||
|
}
|
||||||
|
readAllTxs(addr, query_options).then(response => {
|
||||||
|
|
||||||
|
if (Number.isInteger(options.ignoreOld)) //backward support, cannot be used with options.after or options.before
|
||||||
|
response.items.splice(-options.ignoreOld); //negative to count from end of the array
|
||||||
|
|
||||||
if (typeof options.senders === "string") options.senders = [options.senders];
|
if (typeof options.senders === "string") options.senders = [options.senders];
|
||||||
if (typeof options.receivers === "string") options.receivers = [options.receivers];
|
if (typeof options.receivers === "string") options.receivers = [options.receivers];
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
promisedAPI(`api/addrs/${addr}/txs?from=0&to=1`).then(response => {
|
//filter the txs based on options
|
||||||
var newItems = response.totalItems - options.ignoreOld;
|
const filteredData = response.items.filter(tx => {
|
||||||
promisedAPI(`api/addrs/${addr}/txs?from=0&to=${newItems * 2}`).then(response => {
|
|
||||||
if (options.limit <= 0)
|
if (!tx.confirmations) //unconfirmed transactions: this should not happen as we send mempool=false in API query
|
||||||
options.limit = response.items.length;
|
return false;
|
||||||
var filteredData = [];
|
|
||||||
let numToRead = response.totalItems - options.ignoreOld,
|
if (options.sentOnly && !tx.vin.some(vin => vin.addr === addr))
|
||||||
unconfirmedCount = 0;
|
return false;
|
||||||
for (let i = 0; i < numToRead && filteredData.length < options.limit; i++) {
|
else if (Array.isArray(options.senders) && !tx.vin.some(vin => options.senders.includes(vin.addr)))
|
||||||
if (!response.items[i].confirmations) { //unconfirmed transactions
|
return false;
|
||||||
unconfirmedCount++;
|
|
||||||
if (numToRead < response.items[i].length)
|
if (options.receivedOnly && !tx.vout.some(vout => vout.scriptPubKey.addresses[0] === addr))
|
||||||
numToRead++;
|
return false;
|
||||||
continue;
|
else if (Array.isArray(options.receivers) && !tx.vout.some(vout => options.receivers.includes(vout.scriptPubKey.addresses[0])))
|
||||||
}
|
return false;
|
||||||
|
|
||||||
if (options.pattern) {
|
if (options.pattern) {
|
||||||
try {
|
try {
|
||||||
let jsonContent = JSON.parse(response.items[i].floData);
|
let jsonContent = JSON.parse(tx.floData);
|
||||||
if (!Object.keys(jsonContent).includes(options.pattern))
|
if (!Object.keys(jsonContent).includes(options.pattern))
|
||||||
continue;
|
return false;
|
||||||
} catch (error) {
|
} catch {
|
||||||
continue;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (options.sentOnly) {
|
|
||||||
let flag = false;
|
|
||||||
for (let vin of response.items[i].vin)
|
|
||||||
if (vin.addr === addr) {
|
|
||||||
flag = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (!flag) continue;
|
|
||||||
}
|
|
||||||
if (Array.isArray(options.senders)) {
|
|
||||||
let flag = false;
|
|
||||||
for (let vin of response.items[i].vin)
|
|
||||||
if (options.senders.includes(vin.addr)) {
|
|
||||||
flag = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (!flag) continue;
|
|
||||||
}
|
|
||||||
if (options.receivedOnly) {
|
|
||||||
let flag = false;
|
|
||||||
for (let vout of response.items[i].vout)
|
|
||||||
if (vout.scriptPubKey.addresses[0] === addr) {
|
|
||||||
flag = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (!flag) continue;
|
|
||||||
}
|
|
||||||
if (Array.isArray(options.receivers)) {
|
|
||||||
let flag = false;
|
|
||||||
for (let vout of response.items[i].vout)
|
|
||||||
if (options.receivers.includes(vout.scriptPubKey.addresses[0])) {
|
|
||||||
flag = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (!flag) continue;
|
|
||||||
}
|
|
||||||
if (options.filter && !options.filter(response.items[i].floData))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
|
if (options.filter && !options.filter(tx.floData))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}).map(tx => options.tx ? {
|
||||||
|
txid: tx.txid,
|
||||||
|
time: tx.time,
|
||||||
|
blockheight: tx.blockheight,
|
||||||
|
senders: new Set(tx.vin.map(v => v.addr)),
|
||||||
|
receivers: new Set(tx.vout.map(v => v.scriptPubKey.addresses[0])),
|
||||||
|
data: tx.floData
|
||||||
|
} : tx.floData);
|
||||||
|
|
||||||
|
const result = { lastItem: response.lastItem };
|
||||||
|
if (options.tx)
|
||||||
|
result.items = filteredData;
|
||||||
|
else
|
||||||
|
result.data = filteredData
|
||||||
|
resolve(result);
|
||||||
|
|
||||||
|
}).catch(error => reject(error))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/*Get the latest flo Data that match the caseFn from txs of given Address
|
||||||
|
caseFn: (function) flodata => return bool value
|
||||||
|
options can be used to filter data
|
||||||
|
after : query after the given txid
|
||||||
|
before : query before the given txid
|
||||||
|
mempool : query mempool tx or not (options same as readAllTx, DEFAULT=false: ignore unconfirmed tx)
|
||||||
|
sentOnly : filters only sent data
|
||||||
|
receivedOnly: filters only received data
|
||||||
|
tx : (boolean) resolve tx data or not (resolves an Array of Object with tx details)
|
||||||
|
sender : flo-id(s) of sender
|
||||||
|
receiver : flo-id(s) of receiver
|
||||||
|
*/
|
||||||
|
const getLatestData = floBlockchainAPI.getLatestData = function (addr, caseFn, options = {}) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
//fetch options
|
||||||
|
let query_options = { latest: true };
|
||||||
|
query_options.mempool = isUndefined(options.mempool) ? false : options.mempool; //DEFAULT: ignore unconfirmed tx
|
||||||
|
if (!isUndefined(options.after)) query_options.after = options.after;
|
||||||
|
if (!isUndefined(options.before)) query_options.before = options.before;
|
||||||
|
|
||||||
|
readTxs(addr, query_options).then(response => {
|
||||||
|
|
||||||
|
if (typeof options.senders === "string") options.senders = [options.senders];
|
||||||
|
if (typeof options.receivers === "string") options.receivers = [options.receivers];
|
||||||
|
|
||||||
|
var item = response.items.find(tx => {
|
||||||
|
if (!tx.confirmations) //unconfirmed transactions: this should not happen as we send mempool=false in API query
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (options.sentOnly && !tx.vin.some(vin => vin.addr === addr))
|
||||||
|
return false;
|
||||||
|
else if (Array.isArray(options.senders) && !tx.vin.some(vin => options.senders.includes(vin.addr)))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (options.receivedOnly && !tx.vout.some(vout => vout.scriptPubKey.addresses[0] === addr))
|
||||||
|
return false;
|
||||||
|
else if (Array.isArray(options.receivers) && !tx.vout.some(vout => options.receivers.includes(vout.scriptPubKey.addresses[0])))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return caseFn(tx.floData) ? true : false; //return only bool for find fn
|
||||||
|
});
|
||||||
|
|
||||||
|
//if item found, then resolve the result
|
||||||
|
if (!isUndefined(item)) {
|
||||||
|
const result = { lastItem: response.lastItem };
|
||||||
if (options.tx) {
|
if (options.tx) {
|
||||||
let d = {}
|
result.item = {
|
||||||
d.txid = response.items[i].txid;
|
txid: tx.txid,
|
||||||
d.time = response.items[i].time;
|
time: tx.time,
|
||||||
d.blockheight = response.items[i].blockheight;
|
blockheight: tx.blockheight,
|
||||||
d.senders = new Set(response.items[i].vin.map(v => v.addr));
|
senders: new Set(tx.vin.map(v => v.addr)),
|
||||||
d.receivers = new Set(response.items[i].vout.map(v => v.scriptPubKey.addresses[0]));
|
receivers: new Set(tx.vout.map(v => v.scriptPubKey.addresses[0])),
|
||||||
d.data = response.items[i].floData;
|
data: tx.floData
|
||||||
filteredData.push(d);
|
}
|
||||||
} else
|
} else
|
||||||
filteredData.push(response.items[i].floData);
|
result.data = tx.floData;
|
||||||
|
return resolve(result);
|
||||||
}
|
}
|
||||||
resolve({
|
//else if address needs chain query
|
||||||
totalTxs: response.totalItems - unconfirmedCount,
|
else if (response.incomplete) {
|
||||||
data: filteredData
|
let next_options = Object.assign({}, options);
|
||||||
});
|
options.before = response.initItem; //this fn uses latest option, so using before to chain query
|
||||||
}).catch(error => {
|
getLatestData(addr, caseFn, next_options).then(r => {
|
||||||
reject(error);
|
r.lastItem = response.lastItem; //update last key as it should be the newest tx
|
||||||
});
|
resolve(r);
|
||||||
}).catch(error => {
|
}).catch(error => reject(error))
|
||||||
reject(error);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
//no data match the caseFn, resolve just the lastItem
|
||||||
|
else
|
||||||
|
resolve({ lastItem: response.lastItem });
|
||||||
|
|
||||||
|
}).catch(error => reject(error))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
})('object' === typeof module ? module.exports : window.floBlockchainAPI = {});
|
})('object' === typeof module ? module.exports : window.floBlockchainAPI = {});
|
||||||
@ -1,4 +1,4 @@
|
|||||||
(function (EXPORTS) { //floCrypto v2.3.3e
|
(function (EXPORTS) { //floCrypto v2.3.6a
|
||||||
/* FLO Crypto Operators */
|
/* FLO Crypto Operators */
|
||||||
'use strict';
|
'use strict';
|
||||||
const floCrypto = EXPORTS;
|
const floCrypto = EXPORTS;
|
||||||
@ -152,6 +152,19 @@
|
|||||||
newID: {
|
newID: {
|
||||||
get: () => generateNewID()
|
get: () => generateNewID()
|
||||||
},
|
},
|
||||||
|
hashID: {
|
||||||
|
value: (str) => {
|
||||||
|
let bytes = ripemd160(Crypto.SHA256(str, { asBytes: true }), { asBytes: true });
|
||||||
|
bytes.unshift(bitjs.pub);
|
||||||
|
var hash = Crypto.SHA256(Crypto.SHA256(bytes, {
|
||||||
|
asBytes: true
|
||||||
|
}), {
|
||||||
|
asBytes: true
|
||||||
|
});
|
||||||
|
var checksum = hash.slice(0, 4);
|
||||||
|
return bitjs.Base58.encode(bytes.concat(checksum));
|
||||||
|
}
|
||||||
|
},
|
||||||
tmpID: {
|
tmpID: {
|
||||||
get: () => {
|
get: () => {
|
||||||
let bytes = Crypto.util.randomBytes(20);
|
let bytes = Crypto.util.randomBytes(20);
|
||||||
@ -222,7 +235,7 @@
|
|||||||
key.setCompressed(true);
|
key.setCompressed(true);
|
||||||
if (isfloID && pubKey_floID == key.getBitcoinAddress())
|
if (isfloID && pubKey_floID == key.getBitcoinAddress())
|
||||||
return true;
|
return true;
|
||||||
else if (!isfloID && pubKey_floID == key.getPubKeyHex())
|
else if (!isfloID && pubKey_floID.toUpperCase() == key.getPubKeyHex().toUpperCase())
|
||||||
return true;
|
return true;
|
||||||
else
|
else
|
||||||
return false;
|
return false;
|
||||||
@ -231,12 +244,36 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
floCrypto.getMultisigAddress = function (publicKeyList, requiredSignatures) {
|
||||||
|
if (!Array.isArray(publicKeyList) || !publicKeyList.length)
|
||||||
|
return null;
|
||||||
|
if (!Number.isInteger(requiredSignatures) || requiredSignatures < 1 || requiredSignatures > publicKeyList.length)
|
||||||
|
return null;
|
||||||
|
try {
|
||||||
|
var multisig = bitjs.pubkeys2multisig(publicKeyList, requiredSignatures);
|
||||||
|
return multisig;
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
floCrypto.decodeRedeemScript = function (redeemScript) {
|
||||||
|
try {
|
||||||
|
var decoded = bitjs.transaction().decodeRedeemScript(redeemScript);
|
||||||
|
return decoded;
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//Check if the given flo-id is valid or not
|
//Check if the given flo-id is valid or not
|
||||||
floCrypto.validateFloID = function (floID) {
|
floCrypto.validateFloID = function (floID, regularOnly = false) {
|
||||||
if (!floID)
|
if (!floID)
|
||||||
return false;
|
return false;
|
||||||
try {
|
try {
|
||||||
let addr = new Bitcoin.Address(floID);
|
let addr = new Bitcoin.Address(floID);
|
||||||
|
if (regularOnly && addr.version != Bitcoin.Address.standardVersion)
|
||||||
|
return false;
|
||||||
return true;
|
return true;
|
||||||
} catch {
|
} catch {
|
||||||
return false;
|
return false;
|
||||||
@ -266,13 +303,15 @@
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
//Check the public-key for the address (any blockchain)
|
//Check the public-key (or redeem-script) for the address (any blockchain)
|
||||||
floCrypto.verifyPubKey = function (pubKeyHex, address) {
|
floCrypto.verifyPubKey = function (pubKeyHex, address) {
|
||||||
let raw = decodeAddress(address),
|
let raw = decodeAddress(address);
|
||||||
pub_hash = Crypto.util.bytesToHex(ripemd160(Crypto.SHA256(Crypto.util.hexToBytes(pubKeyHex), {
|
if (!raw)
|
||||||
asBytes: true
|
return;
|
||||||
})));
|
let pub_hash = Crypto.util.bytesToHex(ripemd160(Crypto.SHA256(Crypto.util.hexToBytes(pubKeyHex), { asBytes: true })));
|
||||||
return raw ? pub_hash === raw.hex : false;
|
if (typeof raw.bech_version !== 'undefined' && raw.bytes.length == 32) //bech32-multisig
|
||||||
|
raw.hex = Crypto.util.bytesToHex(ripemd160(raw.bytes, { asBytes: true }));
|
||||||
|
return pub_hash === raw.hex;
|
||||||
}
|
}
|
||||||
|
|
||||||
//Convert the given address (any blockchain) to equivalent floID
|
//Convert the given address (any blockchain) to equivalent floID
|
||||||
@ -282,7 +321,7 @@
|
|||||||
let raw = decodeAddress(address);
|
let raw = decodeAddress(address);
|
||||||
if (!raw)
|
if (!raw)
|
||||||
return;
|
return;
|
||||||
else if (options) {
|
else if (options) { //if (optional) version check is passed
|
||||||
if (typeof raw.version !== 'undefined' && (!options.std || !options.std.includes(raw.version)))
|
if (typeof raw.version !== 'undefined' && (!options.std || !options.std.includes(raw.version)))
|
||||||
return;
|
return;
|
||||||
if (typeof raw.bech_version !== 'undefined' && (!options.bech || !options.bech.includes(raw.bech_version)))
|
if (typeof raw.bech_version !== 'undefined' && (!options.bech || !options.bech.includes(raw.bech_version)))
|
||||||
@ -297,6 +336,50 @@
|
|||||||
return bitjs.Base58.encode(raw.bytes.concat(hash.slice(0, 4)));
|
return bitjs.Base58.encode(raw.bytes.concat(hash.slice(0, 4)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Convert raw address bytes to floID
|
||||||
|
floCrypto.rawToFloID = function (raw_bytes) {
|
||||||
|
if (typeof raw_bytes === 'string')
|
||||||
|
raw_bytes = Crypto.util.hexToBytes(raw_bytes);
|
||||||
|
if (raw_bytes.length != 20)
|
||||||
|
return null;
|
||||||
|
raw_bytes.unshift(bitjs.pub);
|
||||||
|
let hash = Crypto.SHA256(Crypto.SHA256(raw_bytes, {
|
||||||
|
asBytes: true
|
||||||
|
}), {
|
||||||
|
asBytes: true
|
||||||
|
});
|
||||||
|
return bitjs.Base58.encode(raw_bytes.concat(hash.slice(0, 4)));
|
||||||
|
}
|
||||||
|
|
||||||
|
//Convert the given multisig address (any blockchain) to equivalent multisig floID
|
||||||
|
floCrypto.toMultisigFloID = function (address, options = null) {
|
||||||
|
if (!address)
|
||||||
|
return;
|
||||||
|
let raw = decodeAddress(address);
|
||||||
|
if (!raw)
|
||||||
|
return;
|
||||||
|
else if (options) { //if (optional) version check is passed
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
if (typeof raw.bech_version !== 'undefined') {
|
||||||
|
if (raw.bytes.length != 32) return; //multisig bech address have 32 bytes
|
||||||
|
//multisig-bech:hash=SHA256 whereas multisig:hash=r160(SHA265), thus ripemd160 the bytes from multisig-bech
|
||||||
|
raw.bytes = ripemd160(raw.bytes, {
|
||||||
|
asBytes: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
raw.bytes.unshift(bitjs.multisig);
|
||||||
|
let hash = Crypto.SHA256(Crypto.SHA256(raw.bytes, {
|
||||||
|
asBytes: true
|
||||||
|
}), {
|
||||||
|
asBytes: true
|
||||||
|
});
|
||||||
|
return bitjs.Base58.encode(raw.bytes.concat(hash.slice(0, 4)));
|
||||||
|
}
|
||||||
|
|
||||||
//Checks if the given addresses (any blockchain) are same (w.r.t keys)
|
//Checks if the given addresses (any blockchain) are same (w.r.t keys)
|
||||||
floCrypto.isSameAddr = function (addr1, addr2) {
|
floCrypto.isSameAddr = function (addr1, addr2) {
|
||||||
if (!addr1 || !addr2)
|
if (!addr1 || !addr2)
|
||||||
@ -305,9 +388,14 @@
|
|||||||
raw2 = decodeAddress(addr2);
|
raw2 = decodeAddress(addr2);
|
||||||
if (!raw1 || !raw2)
|
if (!raw1 || !raw2)
|
||||||
return false;
|
return false;
|
||||||
else
|
else {
|
||||||
|
if (typeof raw1.bech_version !== 'undefined' && raw1.bytes.length == 32) //bech32-multisig
|
||||||
|
raw1.hex = Crypto.util.bytesToHex(ripemd160(raw1.bytes, { asBytes: true }));
|
||||||
|
if (typeof raw2.bech_version !== 'undefined' && raw2.bytes.length == 32) //bech32-multisig
|
||||||
|
raw2.hex = Crypto.util.bytesToHex(ripemd160(raw2.bytes, { asBytes: true }));
|
||||||
return raw1.hex === raw2.hex;
|
return raw1.hex === raw2.hex;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const decodeAddress = floCrypto.decodeAddr = function (address) {
|
const decodeAddress = floCrypto.decodeAddr = function (address) {
|
||||||
if (!address)
|
if (!address)
|
||||||
@ -326,7 +414,7 @@
|
|||||||
hex: Crypto.util.bytesToHex(bytes),
|
hex: Crypto.util.bytesToHex(bytes),
|
||||||
bytes
|
bytes
|
||||||
}
|
}
|
||||||
} else if (address.length == 42) { //bech encoding
|
} else if (address.length == 42 || address.length == 62) { //bech encoding
|
||||||
let decode = coinjs.bech32_decode(address);
|
let decode = coinjs.bech32_decode(address);
|
||||||
if (decode) {
|
if (decode) {
|
||||||
let bytes = decode.data;
|
let bytes = decode.data;
|
||||||
|
|||||||
@ -1737,13 +1737,15 @@
|
|||||||
trusted = new Set();
|
trusted = new Set();
|
||||||
assets = new Set();
|
assets = new Set();
|
||||||
tags = new Set();
|
tags = new Set();
|
||||||
lastTx = 0;
|
lastTx = undefined;
|
||||||
}
|
}
|
||||||
floBlockchainAPI.readData(DEFAULT.marketID, {
|
|
||||||
ignoreOld: lastTx,
|
var query_options = { sentOnly: true, pattern: DEFAULT.marketApp };
|
||||||
sentOnly: true,
|
if (typeof lastTx == 'string' && /^[0-9a-f]{64}/i.test(lastTx))//lastTx is txid of last tx
|
||||||
pattern: DEFAULT.marketApp
|
query_options.after = lastTx;
|
||||||
}).then(result => {
|
else if (!isNaN(lastTx))//lastTx is tx count (*backward support)
|
||||||
|
query_options.ignoreOld = parseInt(lastTx);
|
||||||
|
floBlockchainAPI.readData(DEFAULT.marketID, query_options).then(result => {
|
||||||
result.data.reverse().forEach(data => {
|
result.data.reverse().forEach(data => {
|
||||||
var content = JSON.parse(data)[DEFAULT.marketApp];
|
var content = JSON.parse(data)[DEFAULT.marketApp];
|
||||||
//Node List
|
//Node List
|
||||||
|
|||||||
@ -1,11 +1,11 @@
|
|||||||
(function(EXPORTS) { //floTokenAPI v1.0.3c
|
(function (EXPORTS) { //floTokenAPI v1.0.4a
|
||||||
/* Token Operator to send/receive tokens via blockchain using API calls*/
|
/* Token Operator to send/receive tokens via blockchain using API calls*/
|
||||||
'use strict';
|
'use strict';
|
||||||
const tokenAPI = EXPORTS;
|
const tokenAPI = EXPORTS;
|
||||||
|
|
||||||
const DEFAULT = {
|
const DEFAULT = {
|
||||||
apiURL: floGlobals.tokenURL || "https://ranchimallflo.duckdns.org/",
|
apiURL: floGlobals.tokenURL || "https://ranchimallflo.duckdns.org/",
|
||||||
currency: "rupee"
|
currency: floGlobals.currency || "rupee"
|
||||||
}
|
}
|
||||||
|
|
||||||
Object.defineProperties(tokenAPI, {
|
Object.defineProperties(tokenAPI, {
|
||||||
@ -77,6 +77,70 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function sendTokens_raw(privKey, receiverID, token, amount, utxo, vout, scriptPubKey) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
var trx = bitjs.transaction();
|
||||||
|
trx.addinput(utxo, vout, scriptPubKey)
|
||||||
|
trx.addoutput(receiverID, floBlockchainAPI.sendAmt);
|
||||||
|
trx.addflodata(`send ${amount} ${token}#`);
|
||||||
|
var signedTxHash = trx.sign(privKey, 1);
|
||||||
|
floBlockchainAPI.broadcastTx(signedTxHash)
|
||||||
|
.then(txid => resolve([receiverID, txid]))
|
||||||
|
.catch(error => reject([receiverID, error]))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
//bulk transfer tokens
|
||||||
|
tokenAPI.bulkTransferTokens = function (sender, privKey, token, receivers) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if (typeof receivers !== 'object')
|
||||||
|
return reject("receivers must be object in format {receiver1: amount1, receiver2:amount2...}")
|
||||||
|
|
||||||
|
let receiver_list = Object.keys(receivers), amount_list = Object.values(receivers);
|
||||||
|
let invalidReceivers = receiver_list.filter(id => !floCrypto.validateFloID(id));
|
||||||
|
let invalidAmount = amount_list.filter(val => typeof val !== 'number' || val <= 0);
|
||||||
|
if (invalidReceivers.length)
|
||||||
|
return reject(`Invalid receivers: ${invalidReceivers}`);
|
||||||
|
else if (invalidAmount.length)
|
||||||
|
return reject(`Invalid amounts: ${invalidAmount}`);
|
||||||
|
|
||||||
|
if (receiver_list.length == 0)
|
||||||
|
return reject("Receivers cannot be empty");
|
||||||
|
|
||||||
|
if (receiver_list.length == 1) {
|
||||||
|
let receiver = receiver_list[0], amount = amount_list[0];
|
||||||
|
floTokenAPI.sendToken(privKey, amount, receiver, "", token)
|
||||||
|
.then(txid => resolve({ success: { [receiver]: txid } }))
|
||||||
|
.catch(error => reject(error))
|
||||||
|
} else {
|
||||||
|
//check for token balance
|
||||||
|
floTokenAPI.getBalance(sender, token).then(token_balance => {
|
||||||
|
let total_token_amout = amount_list.reduce((a, e) => a + e, 0);
|
||||||
|
if (total_token_amout > token_balance)
|
||||||
|
return reject(`Insufficient ${token}# balance`);
|
||||||
|
|
||||||
|
//split utxos
|
||||||
|
floBlockchainAPI.splitUTXOs(sender, privKey, receiver_list.length).then(split_txid => {
|
||||||
|
//wait for the split utxo to get confirmation
|
||||||
|
floBlockchainAPI.waitForConfirmation(split_txid).then(split_tx => {
|
||||||
|
//send tokens using the split-utxo
|
||||||
|
var scriptPubKey = split_tx.vout[0].scriptPubKey.hex;
|
||||||
|
let promises = [];
|
||||||
|
for (let i in receiver_list)
|
||||||
|
promises.push(sendTokens_raw(privKey, receiver_list[i], token, amount_list[i], split_txid, i, scriptPubKey));
|
||||||
|
Promise.allSettled(promises).then(results => {
|
||||||
|
let success = Object.fromEntries(results.filter(r => r.status == 'fulfilled').map(r => r.value));
|
||||||
|
let failed = Object.fromEntries(results.filter(r => r.status == 'rejected').map(r => r.reason));
|
||||||
|
resolve({ success, failed });
|
||||||
|
})
|
||||||
|
}).catch(error => reject(error))
|
||||||
|
}).catch(error => reject(error))
|
||||||
|
}).catch(error => reject(error))
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
tokenAPI.getAllTxs = function (floID, token = DEFAULT.currency) {
|
tokenAPI.getAllTxs = function (floID, token = DEFAULT.currency) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
fetch_api(`api/v1.0/getFloAddressTransactions?token=${token}&floAddress=${floID}`)
|
fetch_api(`api/v1.0/getFloAddressTransactions?token=${token}&floAddress=${floID}`)
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
(function(GLOBAL) { //lib v1.3.1
|
(function (GLOBAL) { //lib v1.4.2b
|
||||||
'use strict';
|
'use strict';
|
||||||
/* Utility Libraries required for Standard operations
|
/* Utility Libraries required for Standard operations
|
||||||
* All credits for these codes belong to their respective creators, moderators and owners.
|
* All credits for these codes belong to their respective creators, moderators and owners.
|
||||||
@ -4349,20 +4349,18 @@
|
|||||||
|
|
||||||
var bitjs = GLOBAL.bitjs = function () { };
|
var bitjs = GLOBAL.bitjs = function () { };
|
||||||
|
|
||||||
function ascii_to_hexa(str) {
|
|
||||||
var arr1 = [];
|
|
||||||
for (var n = 0, l = str.length; n < l; n++) {
|
|
||||||
var hex = Number(str.charCodeAt(n)).toString(16);
|
|
||||||
arr1.push(hex);
|
|
||||||
}
|
|
||||||
return arr1.join('');
|
|
||||||
}
|
|
||||||
|
|
||||||
/* public vars */
|
/* public vars */
|
||||||
bitjs.pub = 0x23; // flochange - changed the prefix to FLO Mainnet PublicKey Prefix 0x23
|
bitjs.pub = 0x23; // flochange - changed the prefix to FLO Mainnet PublicKey Prefix 0x23
|
||||||
bitjs.priv = 0xa3; //flochange - changed the prefix to FLO Mainnet Private key prefix 0xa3
|
bitjs.priv = 0xa3; //flochange - changed the prefix to FLO Mainnet Private key prefix 0xa3
|
||||||
|
bitjs.multisig = 0x5e; //flochange - prefix for FLO Mainnet Multisig 0x5e
|
||||||
bitjs.compressed = false;
|
bitjs.compressed = false;
|
||||||
|
|
||||||
|
if (GLOBAL.cryptocoin == 'FLO_TEST') {
|
||||||
|
bitjs.pub = 0x73; // flochange - changed the prefix to FLO TestNet PublicKey Prefix 0x73
|
||||||
|
bitjs.priv = 0xa3; //flochange - changed the prefix to FLO TestNet Private key prefix 0xa3
|
||||||
|
bitjs.multisig = 0xc6; //flochange - prefix for FLO TestNet Multisig 0xc6
|
||||||
|
}
|
||||||
|
|
||||||
/* provide a privkey and return an WIF */
|
/* provide a privkey and return an WIF */
|
||||||
bitjs.privkey2wif = function (h) {
|
bitjs.privkey2wif = function (h) {
|
||||||
var r = Crypto.util.hexToBytes(h);
|
var r = Crypto.util.hexToBytes(h);
|
||||||
@ -4461,7 +4459,46 @@
|
|||||||
return B58.encode(r.concat(checksum));
|
return B58.encode(r.concat(checksum));
|
||||||
}
|
}
|
||||||
|
|
||||||
bitjs.transaction = function() {
|
/* generate a multisig address from pubkeys and required signatures */
|
||||||
|
bitjs.pubkeys2multisig = function (pubkeys, required) {
|
||||||
|
var s = [];
|
||||||
|
s.push(80 + required); //OP_1
|
||||||
|
for (var i = 0; i < pubkeys.length; ++i) {
|
||||||
|
let bytes = Crypto.util.hexToBytes(pubkeys[i]);
|
||||||
|
s.push(bytes.length);
|
||||||
|
s = s.concat(bytes);
|
||||||
|
}
|
||||||
|
s.push(80 + pubkeys.length); //OP_1
|
||||||
|
s.push(174); //OP_CHECKMULTISIG
|
||||||
|
|
||||||
|
if (s.length > 520) { // too large
|
||||||
|
throw Error(`redeemScript size(=${s.length}) too large`)
|
||||||
|
}
|
||||||
|
|
||||||
|
var x = ripemd160(Crypto.SHA256(s, {
|
||||||
|
asBytes: true
|
||||||
|
}), {
|
||||||
|
asBytes: true
|
||||||
|
});
|
||||||
|
x.unshift(bitjs.multisig);
|
||||||
|
var r = x;
|
||||||
|
r = Crypto.SHA256(Crypto.SHA256(r, {
|
||||||
|
asBytes: true
|
||||||
|
}), {
|
||||||
|
asBytes: true
|
||||||
|
});
|
||||||
|
var checksum = r.slice(0, 4);
|
||||||
|
var redeemScript = Crypto.util.bytesToHex(s);
|
||||||
|
var address = B58.encode(x.concat(checksum));
|
||||||
|
|
||||||
|
return {
|
||||||
|
'address': address,
|
||||||
|
'redeemScript': redeemScript,
|
||||||
|
'size': s.length
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
bitjs.transaction = function (tx_data = undefined) {
|
||||||
var btrx = {};
|
var btrx = {};
|
||||||
btrx.version = 2; //flochange look at this version
|
btrx.version = 2; //flochange look at this version
|
||||||
btrx.inputs = [];
|
btrx.inputs = [];
|
||||||
@ -4476,7 +4513,6 @@
|
|||||||
'hash': txid,
|
'hash': txid,
|
||||||
'index': index
|
'index': index
|
||||||
};
|
};
|
||||||
//o.script = []; Signature and Public Key should be added after singning
|
|
||||||
o.script = Crypto.util.hexToBytes(scriptPubKey); //push previous output pubkey script
|
o.script = Crypto.util.hexToBytes(scriptPubKey); //push previous output pubkey script
|
||||||
o.sequence = sequence || ((btrx.locktime == 0) ? 4294967295 : 0);
|
o.sequence = sequence || ((btrx.locktime == 0) ? 4294967295 : 0);
|
||||||
return this.inputs.push(o);
|
return this.inputs.push(o);
|
||||||
@ -4485,26 +4521,41 @@
|
|||||||
btrx.addoutput = function (address, value) {
|
btrx.addoutput = function (address, value) {
|
||||||
var o = {};
|
var o = {};
|
||||||
var buf = [];
|
var buf = [];
|
||||||
var addrDecoded = btrx.addressDecode(address);
|
var addr = this.addressDecode(address);
|
||||||
o.value = new BigInteger('' + Math.round((value * 1) * 1e8), 10);
|
o.value = new BigInteger('' + Math.round((value * 1) * 1e8), 10);
|
||||||
|
|
||||||
|
if (addr.version === bitjs.pub) { // regular address
|
||||||
buf.push(118); //OP_DUP
|
buf.push(118); //OP_DUP
|
||||||
buf.push(169); //OP_HASH160
|
buf.push(169); //OP_HASH160
|
||||||
buf.push(addrDecoded.length);
|
buf = this.writeBytesToScriptBuffer(buf, addr.bytes);// address in bytes
|
||||||
buf = buf.concat(addrDecoded); // address in bytes
|
|
||||||
buf.push(136); //OP_EQUALVERIFY
|
buf.push(136); //OP_EQUALVERIFY
|
||||||
buf.push(172); //OP_CHECKSIG
|
buf.push(172); //OP_CHECKSIG
|
||||||
|
} else if (addr.version === bitjs.multisig) { // multisig address
|
||||||
|
buf.push(169); //OP_HASH160
|
||||||
|
buf = this.writeBytesToScriptBuffer(buf, addr.bytes);// address in bytes
|
||||||
|
buf.push(135); //OP_EQUAL
|
||||||
|
}
|
||||||
|
|
||||||
o.script = buf;
|
o.script = buf;
|
||||||
return this.outputs.push(o);
|
return this.outputs.push(o);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// flochange - Added fn to assign flodata to tx
|
||||||
|
btrx.addflodata = function (data) {
|
||||||
|
//checks for valid flo-data string
|
||||||
|
if (typeof data !== "string")
|
||||||
|
throw Error("floData should be String");
|
||||||
|
if (data.length > 1040)
|
||||||
|
throw Error("floData Character Limit Exceeded");
|
||||||
|
if (bitjs.strToBytes(data).some(c => c < 32 || c > 127))
|
||||||
|
throw Error("floData contains Invalid characters (only ASCII characters allowed");
|
||||||
|
|
||||||
btrx.addflodata = function(txcomments) { // flochange - this whole function needs to be done
|
this.floData = data;
|
||||||
this.floData = txcomments;
|
return this.floData;
|
||||||
return this.floData; //flochange .. returning the txcomments -- check if the function return will assign
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Only standard addresses
|
// Only standard addresses (standard multisig supported)
|
||||||
btrx.addressDecode = function (address) {
|
btrx.addressDecode = function (address) {
|
||||||
var bytes = B58.decode(address);
|
var bytes = B58.decode(address);
|
||||||
var front = bytes.slice(0, bytes.length - 4);
|
var front = bytes.slice(0, bytes.length - 4);
|
||||||
@ -4515,7 +4566,10 @@
|
|||||||
asBytes: true
|
asBytes: true
|
||||||
}).slice(0, 4);
|
}).slice(0, 4);
|
||||||
if (checksum + "" == back + "") {
|
if (checksum + "" == back + "") {
|
||||||
return front.slice(1);
|
return {
|
||||||
|
version: front[0],
|
||||||
|
bytes: front.slice(1)
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -4740,6 +4794,83 @@
|
|||||||
return KBigInt;
|
return KBigInt;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
btrx.writeBytesToScriptBuffer = function (buf, bytes) {
|
||||||
|
if (bytes.length < 76) { //OP_PUSHDATA1
|
||||||
|
buf.push(bytes.length);
|
||||||
|
} else if (bytes.length <= 0xff) {
|
||||||
|
buf.push(76); //OP_PUSHDATA1
|
||||||
|
buf.push(bytes.length);
|
||||||
|
} else if (bytes.length <= 0xffff) {
|
||||||
|
buf.push(77); //OP_PUSHDATA2
|
||||||
|
buf.push(bytes.length & 0xff);
|
||||||
|
buf.push((bytes.length >>> 8) & 0xff);
|
||||||
|
} else {
|
||||||
|
buf.push(78); //OP_PUSHDATA4
|
||||||
|
buf.push(bytes.length & 0xff);
|
||||||
|
buf.push((bytes.length >>> 8) & 0xff);
|
||||||
|
buf.push((bytes.length >>> 16) & 0xff);
|
||||||
|
buf.push((bytes.length >>> 24) & 0xff);
|
||||||
|
}
|
||||||
|
buf = buf.concat(bytes);
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
btrx.parseScript = function (script) {
|
||||||
|
|
||||||
|
var chunks = [];
|
||||||
|
var i = 0;
|
||||||
|
|
||||||
|
function readChunk(n) {
|
||||||
|
chunks.push(script.slice(i, i + n));
|
||||||
|
i += n;
|
||||||
|
};
|
||||||
|
|
||||||
|
while (i < script.length) {
|
||||||
|
var opcode = script[i++];
|
||||||
|
if (opcode >= 0xF0) {
|
||||||
|
opcode = (opcode << 8) | script[i++];
|
||||||
|
}
|
||||||
|
|
||||||
|
var len;
|
||||||
|
if (opcode > 0 && opcode < 76) { //OP_PUSHDATA1
|
||||||
|
readChunk(opcode);
|
||||||
|
} else if (opcode == 76) { //OP_PUSHDATA1
|
||||||
|
len = script[i++];
|
||||||
|
readChunk(len);
|
||||||
|
} else if (opcode == 77) { //OP_PUSHDATA2
|
||||||
|
len = (script[i++] << 8) | script[i++];
|
||||||
|
readChunk(len);
|
||||||
|
} else if (opcode == 78) { //OP_PUSHDATA4
|
||||||
|
len = (script[i++] << 24) | (script[i++] << 16) | (script[i++] << 8) | script[i++];
|
||||||
|
readChunk(len);
|
||||||
|
} else {
|
||||||
|
chunks.push(opcode);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i < 0x00) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return chunks;
|
||||||
|
}
|
||||||
|
|
||||||
|
btrx.decodeRedeemScript = function (rs) {
|
||||||
|
if (typeof rs == "string")
|
||||||
|
rs = Crypto.util.hexToBytes(rs);
|
||||||
|
var script = this.parseScript(rs);
|
||||||
|
if (!(script[0] > 80 && script[script.length - 2] > 80 && script[script.length - 1] == 174)) //OP_CHECKMULTISIG
|
||||||
|
throw "Invalid RedeemScript";
|
||||||
|
var r = {};
|
||||||
|
r.required = script[0] - 80;
|
||||||
|
r.pubkeys = [];
|
||||||
|
for (var i = 1; i < script.length - 2; i++)
|
||||||
|
r.pubkeys.push(Crypto.util.bytesToHex(script[i]));
|
||||||
|
r.address = bitjs.pubkeys2multisig(r.pubkeys, r.required).address;
|
||||||
|
r.redeemscript = Crypto.util.bytesToHex(rs);
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
/* sign a "standard" input */
|
/* sign a "standard" input */
|
||||||
btrx.signinput = function (index, wif, sigHashType) {
|
btrx.signinput = function (index, wif, sigHashType) {
|
||||||
var key = bitjs.wif2pubkey(wif);
|
var key = bitjs.wif2pubkey(wif);
|
||||||
@ -4747,8 +4878,7 @@
|
|||||||
var signature = this.transactionSig(index, wif, shType);
|
var signature = this.transactionSig(index, wif, shType);
|
||||||
var buf = [];
|
var buf = [];
|
||||||
var sigBytes = Crypto.util.hexToBytes(signature);
|
var sigBytes = Crypto.util.hexToBytes(signature);
|
||||||
buf.push(sigBytes.length);
|
buf = this.writeBytesToScriptBuffer(buf, sigBytes);
|
||||||
buf = buf.concat(sigBytes);
|
|
||||||
var pubKeyBytes = Crypto.util.hexToBytes(key['pubkey']);
|
var pubKeyBytes = Crypto.util.hexToBytes(key['pubkey']);
|
||||||
buf.push(pubKeyBytes.length);
|
buf.push(pubKeyBytes.length);
|
||||||
buf = buf.concat(pubKeyBytes);
|
buf = buf.concat(pubKeyBytes);
|
||||||
@ -4756,15 +4886,98 @@
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* sign a multisig input */
|
||||||
|
btrx.signmultisig = function (index, wif, sigHashType) {
|
||||||
|
|
||||||
|
var script = Array.from(this.inputs[index].script);
|
||||||
|
var redeemScript, sigsList = [];
|
||||||
|
|
||||||
|
if (script[0] == 0) { //script with signatures
|
||||||
|
script = this.parseScript(script);
|
||||||
|
for (var i = 0; i < script.length; i++) {
|
||||||
|
if (Array.isArray(script[i])) {
|
||||||
|
if (script[i][0] == 48) //0x30 DERSequence
|
||||||
|
sigsList.push(script[i]);
|
||||||
|
else if (script[i][0] >= 80 && script[i][script[i].length - 1] == 174) //OP_CHECKMULTISIG
|
||||||
|
redeemScript = script[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else { //script = redeemscript
|
||||||
|
redeemScript = script;
|
||||||
|
}
|
||||||
|
|
||||||
|
var pubkeyList = this.decodeRedeemScript(redeemScript).pubkeys;
|
||||||
|
var pubkey = bitjs.wif2pubkey(wif)['pubkey'];
|
||||||
|
if (!pubkeyList.includes(pubkey)) //wif not a part of this multisig
|
||||||
|
return false;
|
||||||
|
|
||||||
|
pubkeyList = pubkeyList.map(pub => Crypto.util.hexToBytes(bitjs.pubkeydecompress(pub))); //decompress pubkeys
|
||||||
|
|
||||||
|
var shType = sigHashType || 1;
|
||||||
|
this.inputs[index].script = redeemScript; //script to be signed is redeemscript
|
||||||
|
var signature = Crypto.util.hexToBytes(this.transactionSig(index, wif, shType));
|
||||||
|
sigsList.push(signature);
|
||||||
|
|
||||||
|
var buf = [];
|
||||||
|
buf.push(0);
|
||||||
|
|
||||||
|
//verify signatures and order them (also remove duplicate sigs)
|
||||||
|
for (let x in pubkeyList) {
|
||||||
|
for (let y in sigsList) {
|
||||||
|
var sighash = Crypto.util.hexToBytes(this.transactionHash(index, sigsList[y].slice(-1)[0] * 1));
|
||||||
|
if (bitjs.verifySignature(sighash, sigsList[y], pubkeyList[x])) {
|
||||||
|
buf = this.writeBytesToScriptBuffer(buf, sigsList[y]);
|
||||||
|
break; //ensures duplicate sigs from same pubkey are not added
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//append redeemscript
|
||||||
|
buf = this.writeBytesToScriptBuffer(buf, redeemScript);
|
||||||
|
|
||||||
|
this.inputs[index].script = buf;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/* sign inputs */
|
/* sign inputs */
|
||||||
btrx.sign = function (wif, sigHashType) {
|
btrx.sign = function (wif, sigHashType) {
|
||||||
var shType = sigHashType || 1;
|
var shType = sigHashType || 1;
|
||||||
for (var i = 0; i < this.inputs.length; i++) {
|
for (var i = 0; i < this.inputs.length; i++) {
|
||||||
|
|
||||||
|
var decodedScript = this.scriptDecode(i);
|
||||||
|
|
||||||
|
if (decodedScript.type == "scriptpubkey" && decodedScript.signed == false) { //regular
|
||||||
|
var addr = bitjs.wif2address(wif)["address"];;
|
||||||
|
if (decodedScript.pubhash == Crypto.util.bytesToHex(this.addressDecode(addr).bytes)) //input belongs to wif
|
||||||
this.signinput(i, wif, shType);
|
this.signinput(i, wif, shType);
|
||||||
|
} else if (decodedScript.type == "multisig") { //multisig
|
||||||
|
this.signmultisig(i, wif, shType);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return this.serialize();
|
return this.serialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// function to find type of the script in input
|
||||||
|
btrx.scriptDecode = function (index) {
|
||||||
|
var script = this.parseScript(this.inputs[index].script);
|
||||||
|
if (script.length == 5 && script[script.length - 1] == 172) {
|
||||||
|
//OP_DUP OP_HASH160 [address bytes] OP_EQUALVERIFY OP_CHECKSIG
|
||||||
|
// regular scriptPubkey (not signed)
|
||||||
|
return { type: 'scriptpubkey', signed: false, pubhash: Crypto.util.bytesToHex(script[2]) };
|
||||||
|
} else if (script.length == 2 && script[0][0] == 48) {
|
||||||
|
//[signature] [pubkey]
|
||||||
|
//(probably) regular signed
|
||||||
|
return { type: 'scriptpubkey', signed: true };
|
||||||
|
} else if (script[0] == 0 && script[script.length - 1][script[script.length - 1].length - 1] == 174) {
|
||||||
|
//0 [signatues] [redeemscript OP_CHECKMULTISIG]
|
||||||
|
// multisig with signature
|
||||||
|
return { type: 'multisig', rs: script[script.length - 1] };
|
||||||
|
} else if (script[0] >= 80 && script[script.length - 1] == 174) {
|
||||||
|
//redeemscript: 80+ [pubkeys] OP_CHECKMULTISIG
|
||||||
|
// multisig without signature
|
||||||
|
return { type: 'multisig', rs: Array.from(this.inputs[index].script) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* serialize a transaction */
|
/* serialize a transaction */
|
||||||
btrx.serialize = function () {
|
btrx.serialize = function () {
|
||||||
@ -4793,28 +5006,85 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
buffer = buffer.concat(bitjs.numToBytes(parseInt(this.locktime), 4));
|
buffer = buffer.concat(bitjs.numToBytes(parseInt(this.locktime), 4));
|
||||||
var flohex = ascii_to_hexa(this.floData);
|
|
||||||
var floDataCount = this.floData.length;
|
//flochange -- append floData field
|
||||||
var floDataCountString;
|
buffer = buffer.concat(bitjs.numToVarInt(this.floData.length));
|
||||||
//flochange -- creating unique data character count logic for floData. This string is prefixed before actual floData string in Raw Transaction
|
buffer = buffer.concat(bitjs.strToBytes(this.floData))
|
||||||
if (floDataCount < 16) {
|
|
||||||
floDataCountString = floDataCount.toString(16);
|
return Crypto.util.bytesToHex(buffer);
|
||||||
floDataCountString = "0" + floDataCountString;
|
|
||||||
} else if (floDataCount < 253) {
|
|
||||||
floDataCountString = floDataCount.toString(16);
|
|
||||||
} else if (floDataCount <= 1040) {
|
|
||||||
let floDataCountAdjusted = (floDataCount - 253) + parseInt("0xfd00fd");
|
|
||||||
let floDataCountStringAdjusted = floDataCountAdjusted.toString(16);
|
|
||||||
floDataCountString = floDataCountStringAdjusted.substr(0, 2) + floDataCountStringAdjusted.substr(4, 2) + floDataCountStringAdjusted.substr(2, 2);
|
|
||||||
} else {
|
|
||||||
floDataCountString = "Character Limit Exceeded";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* deserialize a transaction */
|
||||||
return Crypto.util.bytesToHex(buffer) + floDataCountString + flohex; // flochange -- Addition of floDataCountString and floData in serialization
|
function deserialize(buffer) {
|
||||||
|
if (typeof buffer == "string") {
|
||||||
|
buffer = Crypto.util.hexToBytes(buffer)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var pos = 0;
|
||||||
|
|
||||||
|
var readAsInt = function (bytes) {
|
||||||
|
if (bytes == 0) return 0;
|
||||||
|
pos++;
|
||||||
|
return buffer[pos - 1] + readAsInt(bytes - 1) * 256;
|
||||||
|
}
|
||||||
|
|
||||||
|
var readVarInt = function () {
|
||||||
|
pos++;
|
||||||
|
if (buffer[pos - 1] < 253) {
|
||||||
|
return buffer[pos - 1];
|
||||||
|
}
|
||||||
|
return readAsInt(buffer[pos - 1] - 251);
|
||||||
|
}
|
||||||
|
|
||||||
|
var readBytes = function (bytes) {
|
||||||
|
pos += bytes;
|
||||||
|
return buffer.slice(pos - bytes, pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
var readVarString = function () {
|
||||||
|
var size = readVarInt();
|
||||||
|
return readBytes(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
var bytesToStr = function (bytes) {
|
||||||
|
return bytes.map(b => String.fromCharCode(b)).join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
const self = btrx;
|
||||||
|
|
||||||
|
self.version = readAsInt(4);
|
||||||
|
|
||||||
|
var ins = readVarInt();
|
||||||
|
for (var i = 0; i < ins; i++) {
|
||||||
|
self.inputs.push({
|
||||||
|
outpoint: {
|
||||||
|
hash: Crypto.util.bytesToHex(readBytes(32).reverse()),
|
||||||
|
index: readAsInt(4)
|
||||||
|
},
|
||||||
|
script: readVarString(),
|
||||||
|
sequence: readAsInt(4)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var outs = readVarInt();
|
||||||
|
for (var i = 0; i < outs; i++) {
|
||||||
|
self.outputs.push({
|
||||||
|
value: bitjs.bytesToNum(readBytes(8)),
|
||||||
|
script: readVarString()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
self.lock_time = readAsInt(4);
|
||||||
|
|
||||||
|
//flochange - floData field
|
||||||
|
self.floData = bytesToStr(readVarString());
|
||||||
|
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
//deserialize the data if passed
|
||||||
|
if (tx_data)
|
||||||
|
deserialize(tx_data);
|
||||||
|
|
||||||
return btrx;
|
return btrx;
|
||||||
|
|
||||||
@ -4856,6 +5126,36 @@
|
|||||||
else return bytes[0] + 256 * bitjs.bytesToNum(bytes.slice(1));
|
else return bytes[0] + 256 * bitjs.bytesToNum(bytes.slice(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//flochange - adding fn to convert string (for flodata) to byte
|
||||||
|
bitjs.strToBytes = function (str) {
|
||||||
|
return str.split('').map(c => c.charCodeAt(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* decompress an compressed public key */
|
||||||
|
bitjs.pubkeydecompress = function (pubkey) {
|
||||||
|
if ((typeof (pubkey) == 'string') && pubkey.match(/^[a-f0-9]+$/i)) {
|
||||||
|
var curve = EllipticCurve.getSECCurveByName("secp256k1");
|
||||||
|
try {
|
||||||
|
var pt = curve.curve.decodePointHex(pubkey);
|
||||||
|
var x = pt.getX().toBigInteger();
|
||||||
|
var y = pt.getY().toBigInteger();
|
||||||
|
|
||||||
|
var publicKeyBytes = EllipticCurve.integerToBytes(x, 32);
|
||||||
|
publicKeyBytes = publicKeyBytes.concat(EllipticCurve.integerToBytes(y, 32));
|
||||||
|
publicKeyBytes.unshift(0x04);
|
||||||
|
return Crypto.util.bytesToHex(publicKeyBytes);
|
||||||
|
} catch (e) {
|
||||||
|
// console.log(e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bitjs.verifySignature = function (hash, sig, pubkey) {
|
||||||
|
return Bitcoin.ECDSA.verify(hash, sig, pubkey);
|
||||||
|
}
|
||||||
|
|
||||||
/* clone an object */
|
/* clone an object */
|
||||||
bitjs.clone = function (obj) {
|
bitjs.clone = function (obj) {
|
||||||
if (obj == null || typeof (obj) != 'object') return obj;
|
if (obj == null || typeof (obj) != 'object') return obj;
|
||||||
@ -5020,17 +5320,25 @@
|
|||||||
|
|
||||||
//https://raw.github.com/bitcoinjs/bitcoinjs-lib/09e8c6e184d6501a0c2c59d73ca64db5c0d3eb95/src/address.js
|
//https://raw.github.com/bitcoinjs/bitcoinjs-lib/09e8c6e184d6501a0c2c59d73ca64db5c0d3eb95/src/address.js
|
||||||
Bitcoin.Address = function (bytes) {
|
Bitcoin.Address = function (bytes) {
|
||||||
if (GLOBAL.cryptocoin == "FLO")
|
|
||||||
this.version = 0x23; // FLO mainnet public address
|
|
||||||
else if (GLOBAL.cryptocoin == "FLO_TEST")
|
|
||||||
this.version = 0x73; // FLO testnet public address
|
|
||||||
if ("string" == typeof bytes) {
|
if ("string" == typeof bytes) {
|
||||||
bytes = Bitcoin.Address.decodeString(bytes, this.version);
|
var d = Bitcoin.Address.decodeString(bytes);
|
||||||
|
bytes = d.hash;
|
||||||
|
if (d.version == Bitcoin.Address.standardVersion || d.version == Bitcoin.Address.multisigVersion)
|
||||||
|
this.version = d.version;
|
||||||
|
else throw "Version (prefix) " + d.version + " not supported!";
|
||||||
|
} else {
|
||||||
|
this.version = Bitcoin.Address.standardVersion;
|
||||||
}
|
}
|
||||||
this.hash = bytes;
|
this.hash = bytes;
|
||||||
};
|
};
|
||||||
|
|
||||||
Bitcoin.Address.networkVersion = 0x23; // (FLO mainnet 0x23, 35D), (Bitcoin Mainnet, 0x00, 0D) // *this has no effect *
|
Bitcoin.Address.standardVersion = 0x23; // (FLO mainnet 0x23, 35D), (Bitcoin Mainnet, 0x00, 0D)
|
||||||
|
Bitcoin.Address.multisigVersion = 0x5e; // (FLO multisig 0x5e, 94D)
|
||||||
|
|
||||||
|
if (GLOBAL.cryptocoin == "FLO_TEST") {
|
||||||
|
Bitcoin.Address.standardVersion = 0x73; // (FLO testnet 0x73, 115D), (Bitcoin Mainnet, 0x00, 0D)
|
||||||
|
Bitcoin.Address.multisigVersion = 0xc6; // (FLO testnet multisig 0xc6, 198D)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Serialize this object as a standard Bitcoin address.
|
* Serialize this object as a standard Bitcoin address.
|
||||||
@ -5059,7 +5367,7 @@
|
|||||||
/**
|
/**
|
||||||
* Parse a Bitcoin address contained in a string.
|
* Parse a Bitcoin address contained in a string.
|
||||||
*/
|
*/
|
||||||
Bitcoin.Address.decodeString = function(string, version) {
|
Bitcoin.Address.decodeString = function (string) {
|
||||||
var bytes = Bitcoin.Base58.decode(string);
|
var bytes = Bitcoin.Base58.decode(string);
|
||||||
var hash = bytes.slice(0, 21);
|
var hash = bytes.slice(0, 21);
|
||||||
var checksum = Crypto.SHA256(Crypto.SHA256(hash, {
|
var checksum = Crypto.SHA256(Crypto.SHA256(hash, {
|
||||||
@ -5075,11 +5383,12 @@
|
|||||||
throw "Checksum validation failed!";
|
throw "Checksum validation failed!";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (version != hash.shift()) {
|
/*if (version != hash.shift()) {
|
||||||
throw "Version " + hash.shift() + " not supported!";
|
throw "Version " + hash.shift() + " not supported!";
|
||||||
}
|
}*/
|
||||||
|
|
||||||
return hash;
|
var version = hash.shift();
|
||||||
|
return { version, hash };
|
||||||
};
|
};
|
||||||
//https://raw.github.com/bitcoinjs/bitcoinjs-lib/e90780d3d3b8fc0d027d2bcb38b80479902f223e/src/ecdsa.js
|
//https://raw.github.com/bitcoinjs/bitcoinjs-lib/e90780d3d3b8fc0d027d2bcb38b80479902f223e/src/ecdsa.js
|
||||||
Bitcoin.ECDSA = (function () {
|
Bitcoin.ECDSA = (function () {
|
||||||
@ -6478,6 +6787,21 @@
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Return a Bech32 address for the multisig. Format is same as above
|
||||||
|
coinjs.pubkeys2MultisigAddressBech32 = function (pubkeys, required) {
|
||||||
|
var r = coinjs.pubkeys2MultisigAddress(pubkeys, required);
|
||||||
|
var program = Crypto.SHA256(Crypto.util.hexToBytes(r.redeemScript), {
|
||||||
|
asBytes: true
|
||||||
|
});
|
||||||
|
var address = coinjs.bech32_encode(coinjs.bech32.hrp, [coinjs.bech32.version].concat(coinjs.bech32_convert(program, 8, 5, true)));
|
||||||
|
return {
|
||||||
|
'address': address,
|
||||||
|
'redeemScript': r.redeemScript,
|
||||||
|
'scripthash': Crypto.util.bytesToHex(program),
|
||||||
|
'size': r.size
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/* new time locked address, provide the pubkey and time necessary to unlock the funds.
|
/* new time locked address, provide the pubkey and time necessary to unlock the funds.
|
||||||
when time is greater than 500000000, it should be a unix timestamp (seconds since epoch),
|
when time is greater than 500000000, it should be a unix timestamp (seconds since epoch),
|
||||||
otherwise it should be the block height required before this transaction can be released.
|
otherwise it should be the block height required before this transaction can be released.
|
||||||
@ -6567,6 +6891,19 @@
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
coinjs.multisigBech32Address = function (redeemscript) {
|
||||||
|
var program = Crypto.SHA256(Crypto.util.hexToBytes(redeemscript), {
|
||||||
|
asBytes: true
|
||||||
|
});
|
||||||
|
var address = coinjs.bech32_encode(coinjs.bech32.hrp, [coinjs.bech32.version].concat(coinjs.bech32_convert(program, 8, 5, true)));
|
||||||
|
return {
|
||||||
|
'address': address,
|
||||||
|
'type': 'multisigBech32',
|
||||||
|
'redeemScript': redeemscript,
|
||||||
|
'scripthash': Crypto.util.bytesToHex(program)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/* extract the redeemscript from a bech32 address */
|
/* extract the redeemscript from a bech32 address */
|
||||||
coinjs.bech32redeemscript = function (address) {
|
coinjs.bech32redeemscript = function (address) {
|
||||||
var r = false;
|
var r = false;
|
||||||
@ -6658,6 +6995,9 @@
|
|||||||
} else if (o.version == coinjs.multisig) { // multisig address
|
} else if (o.version == coinjs.multisig) { // multisig address
|
||||||
o.type = 'multisig';
|
o.type = 'multisig';
|
||||||
|
|
||||||
|
} else if (o.version == coinjs.multisigBech32) { // multisigBech32 added
|
||||||
|
o.type = 'multisigBech32';
|
||||||
|
|
||||||
} else if (o.version == coinjs.priv) { // wifkey
|
} else if (o.version == coinjs.priv) { // wifkey
|
||||||
o.type = 'wifkey';
|
o.type = 'wifkey';
|
||||||
|
|
||||||
@ -6698,11 +7038,16 @@
|
|||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
let bech32rs = coinjs.bech32redeemscript(addr);
|
let bech32rs = coinjs.bech32redeemscript(addr);
|
||||||
if (bech32rs) {
|
if (bech32rs && bech32rs.length == 40) {
|
||||||
return {
|
return {
|
||||||
'type': 'bech32',
|
'type': 'bech32',
|
||||||
'redeemscript': bech32rs
|
'redeemscript': bech32rs
|
||||||
};
|
};
|
||||||
|
} else if (bech32rs && bech32rs.length == 64) {
|
||||||
|
return {
|
||||||
|
'type': 'multisigBech32',
|
||||||
|
'redeemscript': bech32rs
|
||||||
|
};
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -6711,7 +7056,7 @@
|
|||||||
|
|
||||||
/* retreive the balance from a given address */
|
/* retreive the balance from a given address */
|
||||||
coinjs.addressBalance = function (address, callback) {
|
coinjs.addressBalance = function (address, callback) {
|
||||||
coinjs.ajax(coinjs.host + '?uid=' + coinjs.uid + '&key=' + coinjs.key + '&setmodule=addresses&request=bal&address=' + address + '&r=' + securedMathRandom(), callback, "GET");
|
coinjs.ajax(coinjs.host + '?uid=' + coinjs.uid + '&key=' + coinjs.key + '&setmodule=addresses&request=bal&address=' + address + '&r=' + Math.random(), callback, "GET");
|
||||||
}
|
}
|
||||||
|
|
||||||
/* decompress an compressed public key */
|
/* decompress an compressed public key */
|
||||||
@ -7328,11 +7673,39 @@
|
|||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* decode the redeemscript of a multisignature transaction for Bech32*/
|
||||||
|
r.decodeRedeemScriptBech32 = function (script) {
|
||||||
|
var r = false;
|
||||||
|
try {
|
||||||
|
var s = coinjs.script(Crypto.util.hexToBytes(script));
|
||||||
|
if ((s.chunks.length >= 3) && s.chunks[s.chunks.length - 1] == 174) { //OP_CHECKMULTISIG
|
||||||
|
r = {};
|
||||||
|
r.signaturesRequired = s.chunks[0] - 80;
|
||||||
|
var pubkeys = [];
|
||||||
|
for (var i = 1; i < s.chunks.length - 2; i++) {
|
||||||
|
pubkeys.push(Crypto.util.bytesToHex(s.chunks[i]));
|
||||||
|
}
|
||||||
|
r.pubkeys = pubkeys;
|
||||||
|
var multi = coinjs.pubkeys2MultisigAddressBech32(pubkeys, r.signaturesRequired);
|
||||||
|
r.address = multi['address'];
|
||||||
|
r.type = 'multisig__'; // using __ for now to differentiat from the other object .type == "multisig"
|
||||||
|
var rs = Crypto.util.bytesToHex(s.buffer);
|
||||||
|
r.redeemscript = rs;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
// console.log(e);
|
||||||
|
r = false;
|
||||||
|
}
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
/* create output script to spend */
|
/* create output script to spend */
|
||||||
r.spendToScript = function (address) {
|
r.spendToScript = function (address) {
|
||||||
var addr = coinjs.addressDecode(address);
|
var addr = coinjs.addressDecode(address);
|
||||||
var s = coinjs.script();
|
var s = coinjs.script();
|
||||||
if (addr.type == "bech32") {
|
if (addr.type == "bech32" || addr.type == "multisigBech32") {
|
||||||
s.writeOp(0);
|
s.writeOp(0);
|
||||||
s.writeBytes(Crypto.util.hexToBytes(addr.redeemscript));
|
s.writeBytes(Crypto.util.hexToBytes(addr.redeemscript));
|
||||||
} else if (addr.version == coinjs.multisig) { // multisig address
|
} else if (addr.version == coinjs.multisig) { // multisig address
|
||||||
@ -7490,12 +7863,12 @@
|
|||||||
|
|
||||||
/* list unspent transactions */
|
/* list unspent transactions */
|
||||||
r.listUnspent = function (address, callback) {
|
r.listUnspent = function (address, callback) {
|
||||||
coinjs.ajax(coinjs.host + '?uid=' + coinjs.uid + '&key=' + coinjs.key + '&setmodule=addresses&request=unspent&address=' + address + '&r=' + securedMathRandom(), callback, "GET");
|
coinjs.ajax(coinjs.host + '?uid=' + coinjs.uid + '&key=' + coinjs.key + '&setmodule=addresses&request=unspent&address=' + address + '&r=' + Math.random(), callback, "GET");
|
||||||
}
|
}
|
||||||
|
|
||||||
/* list transaction data */
|
/* list transaction data */
|
||||||
r.getTransaction = function (txid, callback) {
|
r.getTransaction = function (txid, callback) {
|
||||||
coinjs.ajax(coinjs.host + '?uid=' + coinjs.uid + '&key=' + coinjs.key + '&setmodule=bitcoin&request=gettransaction&txid=' + txid + '&r=' + securedMathRandom(), callback, "GET");
|
coinjs.ajax(coinjs.host + '?uid=' + coinjs.uid + '&key=' + coinjs.key + '&setmodule=bitcoin&request=gettransaction&txid=' + txid + '&r=' + Math.random(), callback, "GET");
|
||||||
}
|
}
|
||||||
|
|
||||||
/* add unspent to transaction */
|
/* add unspent to transaction */
|
||||||
@ -7525,7 +7898,7 @@
|
|||||||
var n = u.getElementsByTagName("tx_output_n")[0].childNodes[0].nodeValue;
|
var n = u.getElementsByTagName("tx_output_n")[0].childNodes[0].nodeValue;
|
||||||
var scr = script || u.getElementsByTagName("script")[0].childNodes[0].nodeValue;
|
var scr = script || u.getElementsByTagName("script")[0].childNodes[0].nodeValue;
|
||||||
|
|
||||||
if (segwit) {
|
if (segwit) { //also for MULTISIG_BECH32 (p2wsh-multisig)(script = redeemscript; for p2wsh-multisig)
|
||||||
/* this is a small hack to include the value with the redeemscript to make the signing procedure smoother.
|
/* this is a small hack to include the value with the redeemscript to make the signing procedure smoother.
|
||||||
It is not standard and removed during the signing procedure. */
|
It is not standard and removed during the signing procedure. */
|
||||||
|
|
||||||
@ -7664,7 +8037,7 @@
|
|||||||
|
|
||||||
// start redeem script check
|
// start redeem script check
|
||||||
var extract = this.extractScriptKey(index);
|
var extract = this.extractScriptKey(index);
|
||||||
if (extract['type'] != 'segwit') {
|
if (extract['type'] != 'segwit' && extract['type'] != 'multisig_bech32') {
|
||||||
return {
|
return {
|
||||||
'result': 0,
|
'result': 0,
|
||||||
'fail': 'redeemscript',
|
'fail': 'redeemscript',
|
||||||
@ -7693,6 +8066,8 @@
|
|||||||
scriptcode = scriptcode.slice(1);
|
scriptcode = scriptcode.slice(1);
|
||||||
scriptcode.unshift(25, 118, 169);
|
scriptcode.unshift(25, 118, 169);
|
||||||
scriptcode.push(136, 172);
|
scriptcode.push(136, 172);
|
||||||
|
} else if (scriptcode[0] > 80) {
|
||||||
|
scriptcode.unshift(scriptcode.length)
|
||||||
}
|
}
|
||||||
|
|
||||||
var value = coinjs.numToBytes(extract['value'], 8);
|
var value = coinjs.numToBytes(extract['value'], 8);
|
||||||
@ -7855,11 +8230,26 @@
|
|||||||
'signatures': 0,
|
'signatures': 0,
|
||||||
'script': Crypto.util.bytesToHex(this.ins[index].script.buffer)
|
'script': Crypto.util.bytesToHex(this.ins[index].script.buffer)
|
||||||
};
|
};
|
||||||
|
} else if (this.ins[index].script.chunks.length == 3 && this.ins[index].script.chunks[0][0] >= 80 && this.ins[index].script.chunks[0][this.ins[index].script.chunks[0].length - 1] == 174 && this.ins[index].script.chunks[1] == 0) { //OP_CHECKMULTISIG_BECH32
|
||||||
|
// multisig bech32 script
|
||||||
|
let last_index = this.ins[index].script.chunks.length - 1;
|
||||||
|
var value = -1;
|
||||||
|
if (last_index >= 2 && this.ins[index].script.chunks[last_index].length == 8) {
|
||||||
|
value = coinjs.bytesToNum(this.ins[index].script.chunks[last_index]); // value found encoded in transaction (THIS IS NON STANDARD)
|
||||||
|
}
|
||||||
|
var sigcount = (!this.witness[index]) ? 0 : this.witness[index].length - 2;
|
||||||
|
return {
|
||||||
|
'type': 'multisig_bech32',
|
||||||
|
'signed': 'false',
|
||||||
|
'signatures': sigcount,
|
||||||
|
'script': Crypto.util.bytesToHex(this.ins[index].script.chunks[0]),
|
||||||
|
'value': value
|
||||||
|
};
|
||||||
} else if (this.ins[index].script.chunks.length == 0) {
|
} else if (this.ins[index].script.chunks.length == 0) {
|
||||||
// empty
|
// empty
|
||||||
//bech32 witness check
|
//bech32 witness check
|
||||||
var signed = ((this.witness[index]) && this.witness[index].length == 2) ? 'true' : 'false';
|
var signed = ((this.witness[index]) && this.witness[index].length >= 2) ? 'true' : 'false';
|
||||||
var sigs = (signed == 'true') ? 1 : 0;
|
var sigs = (signed == 'true') ? (!this.witness[index][0] ? this.witness[index].length - 2 : 1) : 0;
|
||||||
return {
|
return {
|
||||||
'type': 'empty',
|
'type': 'empty',
|
||||||
'signed': signed,
|
'signed': signed,
|
||||||
@ -8038,6 +8428,71 @@
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
r.signmultisig_bech32 = function (index, wif, sigHashType) {
|
||||||
|
|
||||||
|
function scriptListPubkey(redeemScript) {
|
||||||
|
var r = {};
|
||||||
|
for (var i = 1; i < redeemScript.chunks.length - 2; i++) {
|
||||||
|
r[i] = Crypto.util.hexToBytes(coinjs.pubkeydecompress(Crypto.util.bytesToHex(redeemScript.chunks[i])));
|
||||||
|
}
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
function scriptListSigs(sigList) {
|
||||||
|
let r = {};
|
||||||
|
var c = 0;
|
||||||
|
if (Array.isArray(sigList)) {
|
||||||
|
for (let i = 1; i < sigList.length - 1; i++) {
|
||||||
|
c++;
|
||||||
|
r[c] = Crypto.util.hexToBytes(sigList[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
var redeemScript = Crypto.util.bytesToHex(this.ins[index].script.chunks[0]); //redeemScript
|
||||||
|
|
||||||
|
if (!coinjs.isArray(this.witness)) {
|
||||||
|
this.witness = new Array(this.ins.length);
|
||||||
|
this.witness.fill([]);
|
||||||
|
}
|
||||||
|
|
||||||
|
var pubkeyList = scriptListPubkey(coinjs.script(redeemScript));
|
||||||
|
var sigsList = scriptListSigs(this.witness[index]);
|
||||||
|
let decode_rs = coinjs.script().decodeRedeemScriptBech32(redeemScript);
|
||||||
|
|
||||||
|
var shType = sigHashType || 1;
|
||||||
|
var txhash = this.transactionHashSegWitV0(index, shType);
|
||||||
|
|
||||||
|
if (txhash.result == 1 && decode_rs.pubkeys.includes(coinjs.wif2pubkey(wif)['pubkey'])) {
|
||||||
|
|
||||||
|
var segwitHash = Crypto.util.hexToBytes(txhash.hash);
|
||||||
|
var signature = Crypto.util.hexToBytes(this.transactionSig(index, wif, shType, segwitHash)); //CHECK THIS
|
||||||
|
|
||||||
|
sigsList[coinjs.countObject(sigsList) + 1] = signature;
|
||||||
|
|
||||||
|
var w = [];
|
||||||
|
|
||||||
|
for (let x in pubkeyList) {
|
||||||
|
for (let y in sigsList) {
|
||||||
|
var sighash = this.transactionHashSegWitV0(index, sigsList[y].slice(-1)[0] * 1).hash
|
||||||
|
sighash = Crypto.util.hexToBytes(sighash);
|
||||||
|
if (coinjs.verifySignature(sighash, sigsList[y], pubkeyList[x])) {
|
||||||
|
w.push((Crypto.util.bytesToHex(sigsList[y])))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// when enough signatures collected, remove any non standard data we store, i.e. input value
|
||||||
|
if (w.length >= decode_rs.signaturesRequired) {
|
||||||
|
this.ins[index].script = coinjs.script();
|
||||||
|
}
|
||||||
|
w.unshift(0);
|
||||||
|
w.push(redeemScript);
|
||||||
|
this.witness[index] = w;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* sign a multisig input */
|
/* sign a multisig input */
|
||||||
r.signmultisig = function (index, wif, sigHashType) {
|
r.signmultisig = function (index, wif, sigHashType) {
|
||||||
|
|
||||||
@ -8187,6 +8642,9 @@
|
|||||||
} else if (d['type'] == 'multisig') {
|
} else if (d['type'] == 'multisig') {
|
||||||
this.signmultisig(i, wif, shType);
|
this.signmultisig(i, wif, shType);
|
||||||
|
|
||||||
|
} else if (d['type'] == 'multisig_bech32' && d['signed'] == "false") {
|
||||||
|
this.signmultisig_bech32(i, wif, shType);
|
||||||
|
|
||||||
} else if (d['type'] == 'segwit') {
|
} else if (d['type'] == 'segwit') {
|
||||||
this.signsegwit(i, wif, shType);
|
this.signsegwit(i, wif, shType);
|
||||||
|
|
||||||
@ -8240,6 +8698,63 @@
|
|||||||
return Crypto.util.bytesToHex(buffer);
|
return Crypto.util.bytesToHex(buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Utility funtion added to directly compute signatures without transaction index
|
||||||
|
r.transactionSigNoIndex = function (wif, sigHashType, txhash) {
|
||||||
|
|
||||||
|
function serializeSig(r, s) {
|
||||||
|
var rBa = r.toByteArraySigned();
|
||||||
|
var sBa = s.toByteArraySigned();
|
||||||
|
|
||||||
|
var sequence = [];
|
||||||
|
sequence.push(0x02); // INTEGER
|
||||||
|
sequence.push(rBa.length);
|
||||||
|
sequence = sequence.concat(rBa);
|
||||||
|
|
||||||
|
sequence.push(0x02); // INTEGER
|
||||||
|
sequence.push(sBa.length);
|
||||||
|
sequence = sequence.concat(sBa);
|
||||||
|
|
||||||
|
sequence.unshift(sequence.length);
|
||||||
|
sequence.unshift(0x30); // SEQUENCE
|
||||||
|
|
||||||
|
return sequence;
|
||||||
|
}
|
||||||
|
|
||||||
|
var shType = sigHashType || 1;
|
||||||
|
var hash = Crypto.util.hexToBytes(txhash);
|
||||||
|
|
||||||
|
if (hash) {
|
||||||
|
var curve = EllipticCurve.getSECCurveByName("secp256k1");
|
||||||
|
var key = coinjs.wif2privkey(wif);
|
||||||
|
var priv = BigInteger.fromByteArrayUnsigned(Crypto.util.hexToBytes(key['privkey']));
|
||||||
|
var n = curve.getN();
|
||||||
|
var e = BigInteger.fromByteArrayUnsigned(hash);
|
||||||
|
|
||||||
|
var badrs = 0
|
||||||
|
do {
|
||||||
|
var k = this.deterministicK(wif, hash, badrs);
|
||||||
|
var G = curve.getG();
|
||||||
|
var Q = G.multiply(k);
|
||||||
|
var r = Q.getX().toBigInteger().mod(n);
|
||||||
|
var s = k.modInverse(n).multiply(e.add(priv.multiply(r))).mod(n);
|
||||||
|
badrs++
|
||||||
|
} while (r.compareTo(BigInteger.ZERO) <= 0 || s.compareTo(BigInteger.ZERO) <= 0);
|
||||||
|
|
||||||
|
// Force lower s values per BIP62
|
||||||
|
var halfn = n.shiftRight(1);
|
||||||
|
if (s.compareTo(halfn) > 0) {
|
||||||
|
s = n.subtract(s);
|
||||||
|
};
|
||||||
|
|
||||||
|
var sig = serializeSig(r, s);
|
||||||
|
sig.push(parseInt(shType, 10));
|
||||||
|
|
||||||
|
return Crypto.util.bytesToHex(sig);
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* deserialize a transaction */
|
/* deserialize a transaction */
|
||||||
r.deserialize = function (buffer) {
|
r.deserialize = function (buffer) {
|
||||||
if (typeof buffer == "string") {
|
if (typeof buffer == "string") {
|
||||||
@ -8582,12 +9097,116 @@
|
|||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Nine utility functions added for generating transaction hashes and verification of signatures
|
||||||
|
coinjs.changeEndianness = (string) => {
|
||||||
|
const result = [];
|
||||||
|
let len = string.length - 2;
|
||||||
|
while (len >= 0) {
|
||||||
|
result.push(string.substr(len, 2));
|
||||||
|
len -= 2;
|
||||||
|
}
|
||||||
|
return result.join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
coinjs.getTransactionHash = function (transaction_in_hex, changeOutputEndianess) {
|
||||||
|
var x1, x2, x3, x4, x5;
|
||||||
|
x1 = Crypto.util.hexToBytes(transaction_in_hex);
|
||||||
|
x2 = Crypto.SHA256(x1);
|
||||||
|
x3 = Crypto.util.hexToBytes(x2);
|
||||||
|
x4 = Crypto.SHA256(x3);
|
||||||
|
x5 = coinjs.changeEndianness(x4);
|
||||||
|
if (changeOutputEndianess == true) { x5 = x5 } else if ((typeof changeOutputEndianess == 'undefined') || (changeOutputEndianess == false)) { x5 = x4 };
|
||||||
|
return x5;
|
||||||
|
}
|
||||||
|
|
||||||
|
coinjs.compressedToUncompressed = function (compressed) {
|
||||||
|
var t1, t2;
|
||||||
|
var curve = EllipticCurve.getSECCurveByName("secp256k1");
|
||||||
|
t1 = curve.curve.decodePointHex(compressed);
|
||||||
|
t2 = curve.curve.encodePointHex(t1);
|
||||||
|
return t2;
|
||||||
|
}
|
||||||
|
|
||||||
|
coinjs.uncompressedToCompressed = function (uncompressed) {
|
||||||
|
var t1, t2, t3;
|
||||||
|
t1 = uncompressed.charAt(uncompressed.length - 1)
|
||||||
|
t2 = parseInt(t1, 10);
|
||||||
|
//Check if the last digit is odd
|
||||||
|
if (t2 % 2 == 1) { t3 = "03"; } else { t3 = "02" };
|
||||||
|
return t3 + uncompressed.substr(2, 64);
|
||||||
|
}
|
||||||
|
|
||||||
|
coinjs.verifySignatureHex = function (hashHex, sigHex, pubHexCompressed) {
|
||||||
|
var h1, s1, p1, p2;
|
||||||
|
h1 = Crypto.util.hexToBytes(hashHex);
|
||||||
|
s1 = Crypto.util.hexToBytes(sigHex);
|
||||||
|
p1 = coinjs.compressedToUncompressed(pubHexCompressed);
|
||||||
|
p2 = Crypto.util.hexToBytes(p1);
|
||||||
|
|
||||||
|
return coinjs.verifySignature(h1, s1, p2);
|
||||||
|
}
|
||||||
|
|
||||||
|
coinjs.generateBitcoinSignature = function (private_key, hash, sighash_type_int = 1) {
|
||||||
|
var wif, tx1;
|
||||||
|
if (private_key.length < 60) { wif = private_key } else { wif = coinjs.privkey2wif(private_key) };
|
||||||
|
tx1 = coinjs.transaction();
|
||||||
|
return tx1.transactionSigNoIndex(wif, sighash_type_int, hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
coinjs.dSHA256 = function (data) {
|
||||||
|
var t1, t2, t3;
|
||||||
|
t1 = Crypto.SHA256(Crypto.util.hexToBytes(data));
|
||||||
|
t2 = Crypto.util.hexToBytes(t1);
|
||||||
|
t3 = Crypto.SHA256(t2);
|
||||||
|
return t3;
|
||||||
|
}
|
||||||
|
|
||||||
|
coinjs.fromBitcoinAmountFormat = function (data) {
|
||||||
|
var x1, x2, x3;
|
||||||
|
x1 = coinjs.changeEndianness(data);
|
||||||
|
x2 = parseInt(x1, 16);
|
||||||
|
x3 = x2 / (10 ** 8);
|
||||||
|
return x3;
|
||||||
|
}
|
||||||
|
|
||||||
|
coinjs.toBitcoinAmountFormat = function (countBitcoin) {
|
||||||
|
var t2, t3, t4, t5;
|
||||||
|
t2 = countBitcoin * 10 ** 8;
|
||||||
|
t3 = t2.toString(16);
|
||||||
|
t4 = coinjs.changeEndianness(t3);
|
||||||
|
t5 = t4.padEnd(16, "0");
|
||||||
|
return t5;
|
||||||
|
}
|
||||||
|
|
||||||
|
coinjs.scriptcodeCreatorBasic = function (scriptpubkey) {
|
||||||
|
var t1, t2, t3, t4;
|
||||||
|
if (scriptpubkey.substr(0, 4) == "0014") {
|
||||||
|
//Scriptpubkey case
|
||||||
|
t1 = scriptpubkey.slice(2);
|
||||||
|
t2 = "1976a9" + t1 + "88ac";
|
||||||
|
} else {
|
||||||
|
//Redeemscript case
|
||||||
|
t3 = (scriptpubkey.length) / 2;
|
||||||
|
t4 = t3.toString(16);
|
||||||
|
t2 = t4 + scriptpubkey;
|
||||||
|
}
|
||||||
|
return t2;
|
||||||
|
}
|
||||||
|
|
||||||
|
coinjs.ripemd160sha256 = function (data) {
|
||||||
|
var t1, t2;
|
||||||
|
|
||||||
|
t1 = ripemd160(Crypto.SHA256(Crypto.util.hexToBytes(data), { asBytes: true }), { asBytes: true });
|
||||||
|
t2 = Crypto.util.bytesToHex(t1)
|
||||||
|
return t2;
|
||||||
|
}
|
||||||
|
|
||||||
coinjs.random = function (length) {
|
coinjs.random = function (length) {
|
||||||
var r = "";
|
var r = "";
|
||||||
var l = length || 25;
|
var l = length || 25;
|
||||||
var chars = "!$%^&*()_+{}:@~?><|\./;'#][=-abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
|
var chars = "!$%^&*()_+{}:@~?><|\./;'#][=-abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
|
||||||
for (let x = 0; x < l; x++) {
|
for (let x = 0; x < l; x++) {
|
||||||
r += chars.charAt(Math.floor(securedMathRandom() * 62));
|
r += chars.charAt(Math.floor(Math.random() * 62));
|
||||||
}
|
}
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -316,7 +316,7 @@ function deleteTableData(data) {
|
|||||||
data.forEach(r => r.t_name in delete_needed ? delete_needed[r.t_name].push(r.id) : delete_needed[r.t_name] = [r.id]);
|
data.forEach(r => r.t_name in delete_needed ? delete_needed[r.t_name].push(r.id) : delete_needed[r.t_name] = [r.id]);
|
||||||
let queries = [];
|
let queries = [];
|
||||||
for (let table in delete_needed)
|
for (let table in delete_needed)
|
||||||
queries.push("DELETE FROM ?? WHERE id IN (?)", [table, delete_needed[table]]);
|
queries.push(["DELETE FROM ?? WHERE id IN (?)", [table, delete_needed[table]]]);
|
||||||
DB.transaction(queries).then(_ => resolve(true)).catch(error => reject(error));
|
DB.transaction(queries).then(_ => resolve(true)).catch(error => reject(error));
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@ -139,7 +139,7 @@ function getTableHashes(table) {
|
|||||||
//columns
|
//columns
|
||||||
let columns = result.map(r => r["Field"]).sort();
|
let columns = result.map(r => r["Field"]).sort();
|
||||||
//select statement
|
//select statement
|
||||||
let statement = "SELECT CEIL(id/?) as group_id";
|
let statement = "SELECT CEIL(id/?) as group_id,";
|
||||||
let query_values = [HASH_N_ROW];
|
let query_values = [HASH_N_ROW];
|
||||||
//aggregate column values
|
//aggregate column values
|
||||||
let col_aggregate = columns.map(c => "IFNULL(CRC32(??), 0)").join('+');
|
let col_aggregate = columns.map(c => "IFNULL(CRC32(??), 0)").join('+');
|
||||||
|
|||||||
19
src/main.js
19
src/main.js
@ -40,13 +40,16 @@ function refreshData(startup = false) {
|
|||||||
|
|
||||||
function refreshDataFromBlockchain() {
|
function refreshDataFromBlockchain() {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
DB.query("SELECT num FROM LastTx WHERE floID=?", [floGlobals.adminID]).then(result => {
|
DB.query("SELECT txid FROM LastTx WHERE floID=?", [floGlobals.adminID]).then(result => {
|
||||||
let lastTx = result.length ? result[0].num : 0;
|
var query_options = { sentOnly: true, pattern: floGlobals.application };
|
||||||
floBlockchainAPI.readData(floGlobals.adminID, {
|
|
||||||
ignoreOld: lastTx,
|
let lastTx = result.length ? result[0].txid : undefined;
|
||||||
sentOnly: true,
|
if (typeof lastTx == 'string' && /^[0-9a-f]{64}/i.test(lastTx))//lastTx is txid of last tx
|
||||||
pattern: floGlobals.application
|
query_options.after = lastTx;
|
||||||
}).then(result => {
|
else if (!isNaN(lastTx))//lastTx is tx count (*backward support)
|
||||||
|
query_options.ignoreOld = parseInt(lastTx);
|
||||||
|
|
||||||
|
floBlockchainAPI.readData(floGlobals.adminID, query_options).then(result => {
|
||||||
let promises = [],
|
let promises = [],
|
||||||
nodes_change = false,
|
nodes_change = false,
|
||||||
assets_change = false,
|
assets_change = false,
|
||||||
@ -96,7 +99,7 @@ function refreshDataFromBlockchain() {
|
|||||||
promises.push(`UPDATE TagList WHERE tag=? SET ${a}=?`, [t, content.Tag.update[t][a]]);
|
promises.push(`UPDATE TagList WHERE tag=? SET ${a}=?`, [t, content.Tag.update[t][a]]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
promises.push(DB.query("INSERT INTO LastTx (floID, num) VALUE (?) ON DUPLICATE KEY UPDATE num=?", [[floGlobals.adminID, result.totalTxs], result.totalTxs]));
|
promises.push(DB.query("INSERT INTO LastTx (floID, txid) VALUE (?) ON DUPLICATE KEY UPDATE txid=?", [[floGlobals.adminID, result.lastItem], result.lastItem]));
|
||||||
//Check if all save process were successful
|
//Check if all save process were successful
|
||||||
Promise.allSettled(promises).then(results => {
|
Promise.allSettled(promises).then(results => {
|
||||||
//console.debug(results.filter(r => r.status === "rejected"));
|
//console.debug(results.filter(r => r.status === "rejected"));
|
||||||
|
|||||||
@ -203,16 +203,21 @@ bobsFund.config = {
|
|||||||
|
|
||||||
function refreshBlockchainData(nodeList = []) {
|
function refreshBlockchainData(nodeList = []) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
DB.query("SELECT num FROM LastTx WHERE floID=?", [bobsFund.config.adminID]).then(result => {
|
DB.query("SELECT txid FROM LastTx WHERE floID=?", [bobsFund.config.adminID]).then(result => {
|
||||||
let lastTx = result.length ? result[0].num : 0;
|
|
||||||
floBlockchainAPI.readData(bobsFund.config.adminID, {
|
var query_options = {
|
||||||
ignoreOld: lastTx,
|
senders: nodeList.concat(bobsFund.config.adminID),
|
||||||
senders: nodeList.concat(bobsFund.config.adminID), //sentOnly: true,
|
tx: true, filter: d => d.startsWith(bobsFund.productStr)
|
||||||
tx: true,
|
};
|
||||||
filter: d => d.startsWith(bobsFund.productStr)
|
let lastTx = result.length ? result[0].txid : undefined;
|
||||||
}).then(result => {
|
if (typeof lastTx == 'string' && /^[0-9a-f]{64}/i.test(lastTx))//lastTx is txid of last tx
|
||||||
|
query_options.after = lastTx;
|
||||||
|
else if (!isNaN(lastTx))//lastTx is tx count (*backward support)
|
||||||
|
query_options.ignoreOld = parseInt(lastTx);
|
||||||
|
|
||||||
|
floBlockchainAPI.readData(bobsFund.config.adminID, query_options).then(result => {
|
||||||
let txQueries = [];
|
let txQueries = [];
|
||||||
result.data.reverse().forEach(d => {
|
result.items.reverse().forEach(d => {
|
||||||
let fund = bobsFund.parse(d.data);
|
let fund = bobsFund.parse(d.data);
|
||||||
if (d.senders.has(bobsFund.config.adminID) && !/close:/.test(d.data)) {
|
if (d.senders.has(bobsFund.config.adminID) && !/close:/.test(d.data)) {
|
||||||
let fund_id = d.data.match(/continue: [a-z0-9]{64}\|/);
|
let fund_id = d.data.match(/continue: [a-z0-9]{64}\|/);
|
||||||
@ -240,10 +245,10 @@ function refreshBlockchainData(nodeList = []) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
txQueries.push(["INSERT INTO LastTx (floID, num) VALUE (?) ON DUPLICATE KEY UPDATE num=?",
|
txQueries.push(["INSERT INTO LastTx (floID, txid) VALUE (?) ON DUPLICATE KEY UPDATE txid=?",
|
||||||
[[bobsFund.config.adminID, result.totalTxs], result.totalTxs]])
|
[[bobsFund.config.adminID, result.lastItem], result.lastItem]])
|
||||||
DB.transaction(txQueries)
|
DB.transaction(txQueries)
|
||||||
.then(_ => resolve(result.totalTxs))
|
.then(_ => resolve(result.lastItem))
|
||||||
.catch(error => reject(["Bobs-Fund refresh data failed!", error]));
|
.catch(error => reject(["Bobs-Fund refresh data failed!", error]));
|
||||||
}).catch(error => reject(error))
|
}).catch(error => reject(error))
|
||||||
}).catch(error => reject(error))
|
}).catch(error => reject(error))
|
||||||
|
|||||||
@ -186,16 +186,21 @@ blockchainBond.config = {
|
|||||||
|
|
||||||
function refreshBlockchainData(nodeList = []) {
|
function refreshBlockchainData(nodeList = []) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
DB.query("SELECT num FROM LastTx WHERE floID=?", [blockchainBond.config.adminID]).then(result => {
|
DB.query("SELECT txid FROM LastTx WHERE floID=?", [blockchainBond.config.adminID]).then(result => {
|
||||||
let lastTx = result.length ? result[0].num : 0;
|
|
||||||
floBlockchainAPI.readData(blockchainBond.config.adminID, {
|
var query_options = {
|
||||||
ignoreOld: lastTx,
|
senders: nodeList.concat(blockchainBond.config.adminID),
|
||||||
senders: nodeList.concat(blockchainBond.config.adminID), //sentOnly: true,
|
tx: true, filter: d => d.startsWith(blockchainBond.productStr)
|
||||||
tx: true,
|
};
|
||||||
filter: d => d.startsWith(blockchainBond.productStr)
|
let lastTx = result.length ? result[0].txid : undefined;
|
||||||
}).then(result => {
|
if (typeof lastTx == 'string' && /^[0-9a-f]{64}/i.test(lastTx))//lastTx is txid of last tx
|
||||||
|
query_options.after = lastTx;
|
||||||
|
else if (!isNaN(lastTx))//lastTx is tx count (*backward support)
|
||||||
|
query_options.ignoreOld = parseInt(lastTx);
|
||||||
|
|
||||||
|
floBlockchainAPI.readData(blockchainBond.config.adminID, query_options).then(result => {
|
||||||
let txQueries = [];
|
let txQueries = [];
|
||||||
result.data.reverse().forEach(d => {
|
result.items.reverse().forEach(d => {
|
||||||
let bond = d.senders.has(blockchainBond.config.adminID) ? blockchainBond.parse.main(d.data) : null;
|
let bond = d.senders.has(blockchainBond.config.adminID) ? blockchainBond.parse.main(d.data) : null;
|
||||||
if (bond && bond.amount)
|
if (bond && bond.amount)
|
||||||
txQueries.push(["INSERT INTO BlockchainBonds(bond_id, floID, amount_in, begin_date, btc_base, usd_base, gain_cut, min_ipa, max_period, lockin_period) VALUE (?) ON DUPLICATE KEY UPDATE bond_id=bond_id",
|
txQueries.push(["INSERT INTO BlockchainBonds(bond_id, floID, amount_in, begin_date, btc_base, usd_base, gain_cut, min_ipa, max_period, lockin_period) VALUE (?) ON DUPLICATE KEY UPDATE bond_id=bond_id",
|
||||||
@ -207,10 +212,10 @@ function refreshBlockchainData(nodeList = []) {
|
|||||||
[d.txid, details.amountFinal, details.bondID]]);
|
[d.txid, details.amountFinal, details.bondID]]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
txQueries.push(["INSERT INTO LastTx (floID, num) VALUE (?) ON DUPLICATE KEY UPDATE num=?",
|
txQueries.push(["INSERT INTO LastTx (floID, txid) VALUE (?) ON DUPLICATE KEY UPDATE txid=?",
|
||||||
[[blockchainBond.config.adminID, result.totalTxs], result.totalTxs]])
|
[[blockchainBond.config.adminID, result.lastItem], result.lastItem]])
|
||||||
DB.transaction(txQueries)
|
DB.transaction(txQueries)
|
||||||
.then(_ => resolve(result.totalTxs))
|
.then(_ => resolve(result.lastItem))
|
||||||
.catch(error => reject(["Blockchain-bonds refresh data failed!", error]));
|
.catch(error => reject(["Blockchain-bonds refresh data failed!", error]));
|
||||||
}).catch(error => reject(error))
|
}).catch(error => reject(error))
|
||||||
}).catch(error => reject(error))
|
}).catch(error => reject(error))
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user