Browse Source

switched utxo selector algo, interest calc

v0.25
pbca26 7 years ago
parent
commit
725cb6c528
  1. 784
      routes/shepherd.js

784
routes/shepherd.js

@ -23,6 +23,7 @@ const {execFile} = require('child_process');
const sha256 = require('sha256'); const sha256 = require('sha256');
const CoinKey = require('coinkey') const CoinKey = require('coinkey')
const bitcoinJS = require('bitcoinjs-lib'); const bitcoinJS = require('bitcoinjs-lib');
const coinSelect = require('coinselect');
const fixPath = require('fix-path'); const fixPath = require('fix-path');
var ps = require('ps-node'); var ps = require('ps-node');
@ -292,7 +293,7 @@ shepherd.get('/electrum/login', function(req, res, next) {
const successObj = { const successObj = {
msg: 'success', msg: 'success',
result: 'true' result: 'true',
}; };
res.end(JSON.stringify(successObj)); res.end(JSON.stringify(successObj));
@ -303,7 +304,7 @@ shepherd.get('/electrum/dev/logout', function(req, res, next) {
const successObj = { const successObj = {
msg: 'success', msg: 'success',
result: 'true' result: 'true',
}; };
res.end(JSON.stringify(successObj)); res.end(JSON.stringify(successObj));
@ -311,18 +312,18 @@ shepherd.get('/electrum/dev/logout', function(req, res, next) {
shepherd.get('/electrum/bip39/seed', function(req, res, next) { shepherd.get('/electrum/bip39/seed', function(req, res, next) {
// TODO // TODO
var bip39 = require('bip39'); // npm i -S bip39 const bip39 = require('bip39'); // npm i -S bip39
var crypto = require('crypto'); const crypto = require('crypto');
// what you describe as 'seed' // what you describe as 'seed'
var randomBytes = crypto.randomBytes(16); // 128 bits is enough const randomBytes = crypto.randomBytes(16); // 128 bits is enough
// your 12 word phrase // your 12 word phrase
var mnemonic = bip39.entropyToMnemonic(randomBytes.toString('hex')); const mnemonic = bip39.entropyToMnemonic(randomBytes.toString('hex'));
// what is accurately described as the wallet seed // what is accurately described as the wallet seed
// var seed = bip39.mnemonicToSeed(mnemonic) // you'll use this in #3 below // var seed = bip39.mnemonicToSeed(mnemonic) // you'll use this in #3 below
var seed = bip39.mnemonicToSeed(req.query.seed); const seed = bip39.mnemonicToSeed(req.query.seed);
console.log(seed); console.log(seed);
@ -336,17 +337,17 @@ shepherd.get('/electrum/bip39/seed', function(req, res, next) {
res.end(JSON.stringify(successObj)); res.end(JSON.stringify(successObj));
console.log(bitcoinJS.networks.komodo); console.log(bitcoinJS.networks.komodo);
var hdMaster = bitcoinJS.HDNode.fromSeedBuffer(seed, electrumJSNetworks.komodo); // seed from above const hdMaster = bitcoinJS.HDNode.fromSeedBuffer(seed, electrumJSNetworks.komodo); // seed from above
var key1 = hdMaster.derivePath('m/0'); const key1 = hdMaster.derivePath('m/0');
var key2 = hdMaster.derivePath('m/1'); const key2 = hdMaster.derivePath('m/1');
console.log(hdMaster); console.log(hdMaster);
console.log(key1.keyPair.toWIF()); console.log(key1.keyPair.toWIF());
console.log(key1.keyPair.getAddress()); console.log(key1.keyPair.getAddress());
console.log(key2.keyPair.toWIF()); console.log(key2.keyPair.toWIF());
var hdnode = bitcoinJS.HDNode.fromSeedBuffer(seed, electrumJSNetworks.komodo).deriveHardened(0).derive(0).derive(1); const hdnode = bitcoinJS.HDNode.fromSeedBuffer(seed, electrumJSNetworks.komodo).deriveHardened(0).derive(0).derive(1);
console.log('Address: ' + hdnode.getAddress()); console.log('Address: ' + hdnode.getAddress());
console.log('Private key (WIF): ' + hdnode.keyPair.toWIF()); console.log('Private key (WIF): ' + hdnode.keyPair.toWIF());
}); });
@ -412,6 +413,44 @@ shepherd.get('/electrum/coins', function(req, res, next) {
res.end(JSON.stringify(successObj)); res.end(JSON.stringify(successObj));
}); });
shepherd.kdmCalcInterest = function(locktime, value) { // value in sats
const timestampDiff = (Math.floor(Date.now() / 1000) - locktime - 777);
const currentTimeHours = Math.floor(timestampDiff / 3600);
const currentTimeMinutes = Math.floor((timestampDiff - (currentTimeHours * 3600)) / 60);
const currentTimeSeconds = timestampDiff - (currentTimeHours * 3600) - (currentTimeMinutes * 60);
let timestampDiffMinutes = timestampDiff / 60;
let interest = 0;
console.log(`locktime ${locktime}`);
console.log(`minutes converted ${timestampDiffMinutes}`);
console.log(`passed ${currentTimeHours}h ${currentTimeMinutes}m ${currentTimeSeconds}s`);
// calc interest
if (timestampDiffMinutes >= 60) {
if (timestampDiffMinutes > 365 * 24 * 60) {
timestampDiffMinutes = 365 * 24 * 60;
}
timestampDiffMinutes -= 59;
console.log(`minutes if statement ${timestampDiffMinutes}`);
let denominator = (365 * 24 * 60) / timestampDiffMinutes;
if (denominator === 0) {
denominator = 1; // max KOMODO_INTEREST per transfer, do it at least annually!
}
console.log(`denominator ${denominator}`);
// TODO: check if interest is > 5% yr
const numerator = Number(value) * 0.00000001 / 20; // assumes 5%!
interest = ((Number(value) * 0.00000001) / 10512000) * timestampDiffMinutes;
console.log(`interest ${interest}`);
}
return interest;
}
shepherd.get('/electrum/getbalance', function(req, res, next) { shepherd.get('/electrum/getbalance', function(req, res, next) {
const network = req.query.network || shepherd.findNetworkObj(req.query.coin); const network = req.query.network || shepherd.findNetworkObj(req.query.coin);
const ecl = new electrumJSCore(electrumServers[network].port, electrumServers[network].address, electrumServers[network].proto); // tcp or tls const ecl = new electrumJSCore(electrumServers[network].port, electrumServers[network].address, electrumServers[network].proto); // tcp or tls
@ -419,172 +458,126 @@ shepherd.get('/electrum/getbalance', function(req, res, next) {
ecl.connect(); ecl.connect();
ecl.blockchainAddressGetBalance(req.query.address) ecl.blockchainAddressGetBalance(req.query.address)
.then((json) => { .then((json) => {
ecl.close(); if (network === 'komodo') {
console.log('electrum getbalance ==>'); ecl.connect();
console.log(json); ecl.blockchainAddressListunspent(req.query.address)
.then((utxoList) => {
if (utxoList &&
utxoList.length) {
// filter out < 10 KMD amounts
let _utxo = [];
for (let i = 0; i < utxoList.length; i++) {
console.log(`utxo ${utxoList[i]['tx_hash']} sats ${utxoList[i].value} value ${Number(utxoList[i].value) * 0.00000001}`);
if (Number(utxoList[i].value) * 0.00000001 >= 10) {
_utxo.push(utxoList[i]);
}
}
const successObj = { console.log('filtered utxo list =>');
msg: 'success', console.log(_utxo);
result: {
balance: 0.00000001 * json.confirmed,
unconfirmed: json.unconfirmed,
sats: json.confirmed,
},
};
res.end(JSON.stringify(successObj)); if (_utxo &&
}); _utxo.length) {
}); let interestTotal = 0;
shepherd.sortTransactions = function(transactions) { for (let i = 0; i < _utxo.length; i++) {
return transactions.sort(function(b, a) { ecl.blockchainTransactionGet(_utxo[i]['tx_hash'])
if (a.height < b.height) { .then((_rawtxJSON) => {
return -1; console.log('electrum gettransaction ==>');
} console.log(i + ' | ' + (_rawtxJSON.length - 1));
console.log(_rawtxJSON);
if (a.height > b.height) { // decode tx
return 1; const _network = electrumJSNetworks[network];
} const decodedTx = electrumJSTxDecoder(_rawtxJSON, _network);
return 0; interestTotal += shepherd.kdmCalcInterest(decodedTx.format.locktime, _utxo[i].value);
}); console.log('decoded tx =>');
} console.log(decodedTx);
console.log(decodedTx.format.locktime);
if (i === _utxo.length -1) {
const successObj = {
msg: 'success',
result: {
balance: 0.00000001 * json.confirmed,
unconfirmed: json.unconfirmed,
sats: json.confirmed,
interest: interestTotal,
interestSats: Math.floor(interestTotal * 100000000),
total: (0.00000001 * json.confirmed) + interestTotal,
totalSats: json.confirmed + Math.floor(interestTotal * 100000000),
},
};
/*shepherd.get('/electrum/listtransactions', function(req, res, next) { res.end(JSON.stringify(successObj));
const network = req.query.network || shepherd.findNetworkObj(req.query.coin); }
const ecl = new electrumJSCore(electrumServers[network].port, electrumServers[network].address, electrumServers[network].proto); // tcp or tls });
}
} else {
const successObj = {
msg: 'success',
result: {
balance: 0.00000001 * json.confirmed,
unconfirmed: json.unconfirmed,
sats: json.confirmed,
interest: 0,
interestSats: 0,
total: 0.00000001 * json.confirmed,
totalSats: json.confirmed,
},
};
if (!req.query.full) { res.end(JSON.stringify(successObj));
ecl.connect(); }
ecl.blockchainAddressGetHistory(req.query.address) } else {
.then((json) => { const successObj = {
msg: 'success',
result: {
balance: 0.00000001 * json.confirmed,
unconfirmed: json.unconfirmed,
sats: json.confirmed,
interest: 0,
},
};
res.end(JSON.stringify(successObj));
}
});
} else {
ecl.close(); ecl.close();
console.log('electrum listtransactions ==>'); console.log('electrum getbalance ==>');
console.log(json); console.log(json);
json = shepherd.sortTransactions(json);
const successObj = { const successObj = {
msg: 'success', msg: 'success',
result: { result: {
listtransactions: json, balance: 0.00000001 * json.confirmed,
unconfirmed: json.unconfirmed,
sats: json.confirmed,
}, },
}; };
res.end(JSON.stringify(successObj)); res.end(JSON.stringify(successObj));
}); }
} else { });
// !expensive call! });
// TODO: limit e.g. 1-10, 10-20 etc
const MAX_TX = 10;
ecl.connect();
ecl.blockchainNumblocksSubscribe()
.then(function(currentHeight) {
// TODO: block time, confs
ecl.blockchainAddressGetHistory(req.query.address)
.then((json) => {
if (json &&
json.length) {
json = shepherd.sortTransactions(json);
json = json.slice(0, MAX_TX);
console.log(json.length);
let _rawtx = [];
// get raw tx
for (let i = 0; i < json.length; i++) {
ecl.blockchainTransactionGet(json[i]['tx_hash'])
.then((_json) => {
console.log('electrum gettransaction ==>');
console.log(i + ' | ' + (json.length - 1));
console.log(_json);
// decode tx
const _network = electrumJSNetworks[network];
const decodedTx = electrumJSTxDecoder(_json, _network);
// TODO: multi vin
if (decodedTx.inputs[0].txid === '0000000000000000000000000000000000000000000000000000000000000000') {
ecl.blockchainBlockGetHeader(json[i].height)
.then((blockInfo) => {
_rawtx.push({
network: decodedTx.network,
format: decodedTx.format,
inputs: decodedTx.inputs,
outputs: decodedTx.outputs,
height: json[i].height,
timestamp: blockInfo.timestamp,
confirmations: currentHeight - json[i].height,
miner: true,
});
if (i === json.length - 1) {
ecl.close();
console.log('electrum gettransaction array ==>');
console.log(_rawtx);
const successObj = {
msg: 'success',
result: {
listtransactions: _rawtx,
},
};
res.end(JSON.stringify(successObj)); shepherd.sortTransactions = function(transactions) {
} return transactions.sort(function(b, a) {
}); if (a.height < b.height) {
} else { return -1;
// get vin tx, decode }
ecl.blockchainBlockGetHeader(json[i].height)
.then((blockInfo) => {
ecl.blockchainTransactionGet(decodedTx.inputs[0].txid)
.then((__json) => {
console.log('electrum decoderawtx input tx ==>');
console.log(__json);
const decodedVin = electrumJSTxDecoder(__json, _network);
_rawtx.push({
network: decodedTx.network,
format: decodedTx.format,
inputs: decodedVin.outputs[decodedTx.inputs[0].n],
outputs: decodedTx.outputs,
height: json[i].height,
timestamp: blockInfo.timestamp,
confirmations: currentHeight - json[i].height,
});
if (i === json.length - 1) {
ecl.close();
console.log('electrum gettransaction array ==>');
console.log(_rawtx);
let result = [];
for (let i = 0; i < _rawtx.length; i++) {
result = result.concat(shepherd.parseTransactionAddresses(_rawtx[i], req.query.address));
}
const successObj = { if (a.height > b.height) {
msg: 'success', return 1;
result: { }
//listtransactions: _rawtx,
listtransactions: result,
},
};
res.end(JSON.stringify(successObj)); return 0;
} });
}); }
});
}
});
}
}
});
});
}
});*/
shepherd.get('/electrum/listtransactions', function(req, res, next) { shepherd.get('/electrum/listtransactions', function(req, res, next) {
const network = req.query.network || shepherd.findNetworkObj(req.query.coin); const network = req.query.network || shepherd.findNetworkObj(req.query.coin);
@ -651,7 +644,6 @@ shepherd.get('/electrum/listtransactions', function(req, res, next) {
ecl.blockchainTransactionGet(decodedTx.inputs[j].txid) ecl.blockchainTransactionGet(decodedTx.inputs[j].txid)
.then((rawInput) => { .then((rawInput) => {
console.log('electrum raw input tx ==>'); console.log('electrum raw input tx ==>');
//console.log
const decodedVinVout = electrumJSTxDecoder(rawInput, _network); const decodedVinVout = electrumJSTxDecoder(rawInput, _network);
console.log(decodedVinVout.outputs[decodedTx.inputs[j].n]); console.log(decodedVinVout.outputs[decodedTx.inputs[j].n]);
txInputs.push(decodedVinVout.outputs[decodedTx.inputs[j].n]); txInputs.push(decodedVinVout.outputs[decodedTx.inputs[j].n]);
@ -777,84 +769,7 @@ shepherd.get('/electrum/listtransactions', function(req, res, next) {
} }
}); });
// TODO: multi vin
/*if (decodedTx.inputs[0].txid === '0000000000000000000000000000000000000000000000000000000000000000') {
ecl.blockchainBlockGetHeader(json[i].height)
.then((blockInfo) => {
_rawtx.push({
network: decodedTx.network,
format: decodedTx.format,
inputs: decodedTx.inputs,
outputs: decodedTx.outputs,
height: json[i].height,
timestamp: blockInfo.timestamp,
confirmations: currentHeight - json[i].height,
miner: true,
});
if (i === json.length - 1) {
ecl.close();
console.log('electrum gettransaction array ==>');
console.log(_rawtx);
const successObj = {
msg: 'success',
result: {
listtransactions: _rawtx,
},
};
res.end(JSON.stringify(successObj));
}
});
} else {
// get vin tx, decode
ecl.blockchainBlockGetHeader(json[i].height)
.then((blockInfo) => {
ecl.blockchainTransactionGet(decodedTx.inputs[0].txid)
.then((__json) => {
console.log('electrum decoderawtx input tx ==>');
console.log(__json);
const decodedVin = electrumJSTxDecoder(__json, _network);
_rawtx.push({
network: decodedTx.network,
format: decodedTx.format,
inputs: decodedVin.outputs[decodedTx.inputs[0].n],
outputs: decodedTx.outputs,
height: json[i].height,
timestamp: blockInfo.timestamp,
confirmations: currentHeight - json[i].height,
});
if (i === json.length - 1) {
ecl.close();
console.log('electrum gettransaction array ==>');
console.log(_rawtx);
let result = [];
for (let i = 0; i < _rawtx.length; i++) {
result = result.concat(shepherd.parseTransactionAddresses(_rawtx[i], req.query.address));
}
const successObj = {
msg: 'success',
result: {
//listtransactions: _rawtx,
listtransactions: result,
},
};
res.end(JSON.stringify(successObj));
}
});
});
}*/
shepherd.get('/electrum/gettransaction', function(req, res, next) { shepherd.get('/electrum/gettransaction', function(req, res, next) {
// TODO: block time, confs, current height
const ecl = new electrumJSCore(electrumServers[req.query.network].port, electrumServers[req.query.network].address, electrumServers[req.query.network].proto); // tcp or tls const ecl = new electrumJSCore(electrumServers[req.query.network].port, electrumServers[req.query.network].address, electrumServers[req.query.network].proto); // tcp or tls
ecl.connect(); ecl.connect();
@ -995,29 +910,6 @@ shepherd.parseTransactionAddresses = function(tx, targetAddress) {
_parse[key][i].value) { _parse[key][i].value) {
_sum[key] += Number(_parse[key][i].value); _sum[key] += Number(_parse[key][i].value);
} }
/*if (key === 'outputs' ||
(key === 'inputs' && _parse[key][i].scriptPubKey && _parse[key][i].scriptPubKey.addresses && _parse[key][i].value)) {
if (!targetAddress || (targetAddress === _parse[key][i].scriptPubKey.addresses[0] && !addressFound)) {
let _type;
if (tx.miner) {
_type = 'miner';
} else {
_type = key === 'inputs' ? 'sent' : 'received';
}
result.push({
type: _type, // flip
amount: Number(_parse[key][i].value),
address: _parse[key][i].scriptPubKey.addresses[0],
timestamp: tx.timestamp,
txid: tx.format.txid,
confirmations: tx.confirmations,
});
addressFound = true;
}
}*/
} }
} }
@ -1203,63 +1095,8 @@ shepherd.get('/electrum/decoderawtx', function(req, res, next) {
} }
}); });
// simple case // deprecated, remove
shepherd.buildTestTx = function(sendTo, changeAddress, wif, network) {
// single utxo
const _utxo = {"height":400118,"value":20000,"tx_hash":"764cb67ef9d92942b0a3ef5f9c62ad2a552de07f575e1ab935c3cf45ace00cc8","tx_pos":0};
const _valToSpend = 10000;
var key = bitcoinJS.ECPair.fromWIF(wif, electrumJSNetworks['komodo']);
var tx = new bitcoinJS.TransactionBuilder(electrumJSNetworks[network]);
console.log(key);
console.log(key.getAddress().toString());
// electrumServers[].txfee
const _txSize = shepherd.estimateTxSize(1, 2);
const _feeEstimateByte = shepherd.estimateFee('small');
const _feeEstimateTx = Math.floor(_txSize * _feeEstimateByte);
const _change = _utxo.value - _valToSpend - _feeEstimateTx;
console.log('val to spend ' + _valToSpend);
console.log('change value ' + _change);
tx.addInput(_utxo['tx_hash'], _utxo['tx_pos']);
tx.addOutput(sendTo, _valToSpend);
tx.addOutput(changeAddress, _change);
console.log('estimate tx size ' + _txSize);
console.log('fee per byte ' + _feeEstimateByte);
console.log('tx fee ' + _feeEstimateTx);
console.log(tx);
// (in)15000 - (out)12000 = (fee)3000, this is the miner fee
tx.sign(0, key);
const rawtx = tx.build().toHex();
console.log(rawtx);
return rawtx;
}
shepherd.get('/electrum/txbuildtest', function(req, res, next) {
const rawtx = shepherd.buildTestTx(
'RDbGxL8QYdEp8sMULaVZS2E6XThcTKT9Jd',
'RDbGxL8QYdEp8sMULaVZS2E6XThcTKT9Jd',
'UrA1TCN2j9iMYKBLkKGMo9MbndBNYVW9nJV9RdViR9CoVK82ApFb',
'komodo'
);
const successObj = {
msg: 'success',
result: {
rawtx: rawtx,
},
};
res.end(JSON.stringify(successObj));
});
shepherd.findUtxoSet = function(utxoList, target) { shepherd.findUtxoSet = function(utxoList, target) {
// utxoList = [{"height":427959,"value":20000,"tx_hash":"3f7da2daef2ea59fd40eb28d2be04a17839f2073c16ea291b1b03a3d799fec3b","tx_pos":0},{"height":428139,"value":20000,"tx_hash":"aa4afdece59c46a6a1e34d6ae7bf2c46b282de0ce6ef1753e8766c08e87bdb36","tx_pos":0},{"height":428216,"value":20000,"tx_hash":"3ec5e982083ffc617aaa75e68e69f9da5f07faadcd870e144f6451c34936ee24","tx_pos":0},{"height":433459,"value":20000,"tx_hash":"b943330cb034f36c6d9f03c57a2e1f0a3970378825336b505d51d1f2561af3fe","tx_pos":0},{"height":459573,"value":100000,"tx_hash":"7fee95da75e9b77bf9cd03b460fcf04c31ce4dbe4bdfa1e66256c9beb1fe2308","tx_pos":0},{"height":505916,"value":20000,"tx_hash":"aa92df15b20a10543750db58fc6e885205d1eee3438ef79f01d3c4ec2616e7aa","tx_pos":0},{"height":505916,"value":90000,"tx_hash":"aa92df15b20a10543750db58fc6e885205d1eee3438ef79f01d3c4ec2616e7aa","tx_pos":1},{"height":505931,"value":20000,"tx_hash":"dd1ab56c0e3f63e3650aeca3e2aa4ecea54cca22e97932e48341e299c4640ef9","tx_pos":0},{"height":505931,"value":100000,"tx_hash":"dd1ab56c0e3f63e3650aeca3e2aa4ecea54cca22e97932e48341e299c4640ef9","tx_pos":1},{"height":505937,"value":20000,"tx_hash":"7bed6729de8be9b61b85716d8d1615d751c1dbb2bd2defd6f9c76354a591586c","tx_pos":0},{"height":505937,"value":70000,"tx_hash":"7bed6729de8be9b61b85716d8d1615d751c1dbb2bd2defd6f9c76354a591586c","tx_pos":1},{"height":506038,"value":20000,"tx_hash":"f47f4d5e2aaee19f98ebdf97deac3c471b919b71312a073d1b45dfc27fb64d24","tx_pos":0},{"height":506045,"value":20000,"tx_hash":"e8a92b174b768a050b4801ea497cdb9651d4647f20ec5a77b5af0d6558fdf468","tx_pos":0},{"height":506057,"value":20000,"tx_hash":"758ae45606ee43b55d72ec57284abac54c34b34badf4986b3d9e53e5aaf227d8","tx_pos":0},{"height":514498,"value":18902,"tx_hash":"b3918bc40a8ba4fb26383bdd54b8519d14200b7f2c700adb2ceea46d5ca36590","tx_pos":0},{"height":514722,"value":10000,"tx_hash":"a6c46b2168964f2e6f18a8cba40eac10a30a6bcc363b773696265f8450c5a881","tx_pos":0},{"height":514722,"value":4820,"tx_hash":"a6c46b2168964f2e6f18a8cba40eac10a30a6bcc363b773696265f8450c5a881","tx_pos":1}];
let result = []; let result = [];
let sum = 0; let sum = 0;
@ -1315,18 +1152,19 @@ shepherd.get('/electrum/subset', function(req, res, next) {
// single sig // single sig
shepherd.buildSignedTx = function(sendTo, changeAddress, wif, network, utxo, changeValue, spendValue) { shepherd.buildSignedTx = function(sendTo, changeAddress, wif, network, utxo, changeValue, spendValue) {
var key = bitcoinJS.ECPair.fromWIF(wif, electrumJSNetworks[network]); let key = bitcoinJS.ECPair.fromWIF(wif, electrumJSNetworks[network]);
var tx = new bitcoinJS.TransactionBuilder(electrumJSNetworks[network]); let tx = new bitcoinJS.TransactionBuilder(electrumJSNetworks[network]);
console.log('buildSignedTx priv key ' + wif); console.log('buildSignedTx priv key ' + wif);
console.log('buildSignedTx pub key ' + key.getAddress().toString()); console.log('buildSignedTx pub key ' + key.getAddress().toString());
console.log('buildSignedTx std tx fee ' + electrumServers[network].txfee); // console.log('buildSignedTx std tx fee ' + electrumServers[network].txfee);
for (let i = 0; i < utxo.length; i++) { for (let i = 0; i < utxo.length; i++) {
tx.addInput(utxo[i]['tx_hash'], utxo[i]['tx_pos']); tx.addInput(utxo[i].txid, utxo[i].vout);
} }
tx.addOutput(sendTo, Number(spendValue)); tx.addOutput(sendTo, Number(spendValue));
if (changeValue > 0) { if (changeValue > 0) {
tx.addOutput(changeAddress, Number(changeValue)); tx.addOutput(changeAddress, Number(changeValue));
} }
@ -1369,45 +1207,124 @@ shepherd.get('/electrum/createrawtx', function(req, res, next) {
const outputAddress = req.query.address; const outputAddress = req.query.address;
const changeAddress = req.query.change; const changeAddress = req.query.change;
let wif = req.query.wif; let wif = req.query.wif;
const value = req.query.value; const value = Number(req.query.value);
const push = req.query.push; const push = req.query.push;
const fee = electrumServers[network].txfee; const fee = electrumServers[network].txfee;
console.log(electrumCoins[req.query.coin]);
if (req.query.gui) { if (req.query.gui) {
wif = electrumCoins[req.query.coin].priv; wif = electrumCoins[req.query.coin].priv;
} }
ecl.connect(); ecl.connect();
ecl.blockchainAddressListunspent(changeAddress) //ecl.blockchainAddressListunspent(changeAddress)
shepherd.listunspent(ecl, changeAddress, network, network === 'komodo' ? true : false)
.then((utxoList) => { .then((utxoList) => {
ecl.close(); ecl.close();
let utxoListFormatted = [];
let totalInterest = 0;
let totalInterestUTXOCount = 0;
let interestClaimThreshold = 200;
for (let i = 0; i < utxoList.length; i++) {
if (network === 'komodo') {
utxoListFormatted.push({
txid: utxoList[i].txid,
vout: utxoList[i].vout,
value: Number(utxoList[i].amountSats),
interestSats: Number(utxoList[i].interestSats),
});
if (Number(utxoList[i].interestSats) > interestClaimThreshold) {
totalInterest += Number(utxoList[i].interestSats);
totalInterestUTXOCount++;
}
} else {
utxoListFormatted.push({
txid: utxoList[i]['tx_hash'],
vout: utxoList[i]['tx_pos'],
value: Number(utxoList[i].value),
height: utxoList[i].height,
});
}
}
console.log('electrum listunspent ==>'); console.log('electrum listunspent ==>');
console.log(utxoListFormatted);
let _value = Number(value) + Number(fee); let targets = [{
let _utxoSet = shepherd.findUtxoSet(utxoList, _value); // target + txfee address: outputAddress,
value: value > Number(shepherd.maxSpendBalance(utxoListFormatted)) ? Number(shepherd.maxSpendBalance(utxoListFormatted)) : value,
}];
console.log('targets =>');
console.log(targets);
const feeRate = 20; // sats/byte
// default coin selection algo blackjack with fallback to accumulative
// make a first run, calc approx tx fee
// if ins and outs are empty reduce max spend by txfee
let { inputs, outputs, fee } = coinSelect(utxoListFormatted, targets, feeRate);
console.log('coinselect res =>');
console.log('coinselect inputs =>');
console.log(inputs);
console.log('coinselect outputs =>');
console.log(outputs);
console.log('coinselect calculated fee =>');
console.log(fee);
if (!inputs &&
!outputs) {
targets[0].value = targets[0].value - electrumServers[network].txfee;
console.log('second run');
console.log('coinselect adjusted targets =>');
console.log(targets);
const secondRun = coinSelect(utxoListFormatted, targets, feeRate);
inputs = secondRun.inputs;
outputs = secondRun.outputs;
fee = secondRun.fee;
console.log('coinselect inputs =>');
console.log(inputs);
console.log('coinselect outputs =>');
console.log(outputs);
console.log('coinselect fee =>');
console.log(fee);
}
if (Number(value) >= shepherd.maxSpendBalance(utxoList, fee)) { let _change = 0;
_value = shepherd.maxSpendBalance(utxoList, fee);
_utxoSet = { if (outputs.length === 2) {
set: utxoList, _change = outputs[1].value;
change: 0,
};
} }
console.log('maxspend ' + shepherd.maxSpendBalance(utxoList, fee)); const _maxSpend = shepherd.maxSpendBalance(utxoListFormatted);
console.log('value ' + value);
console.log(`maxspend ${_maxSpend} (${_maxSpend * 0.00000001})`);
console.log(`value ${value}`);
console.log(`sendto ${outputAddress} amount ${value} (${value * 0.00000001})`);
console.log(`changeto ${changeAddress} amount ${_change} (${_change * 0.00000001})`);
const _rawtx = shepherd.buildSignedTx(outputAddress, changeAddress, wif, network, _utxoSet.set, _utxoSet.change, _utxoSet.change <= 0 ? _value : value); // account for KMD interest
if (network === 'komodo' &&
totalInterest > 0) {
const _feeOverhead = shepherd.estimateTxSize(0, 1) * 2;
console.log(`max interest to claim ${totalInterest} (${totalInterest * 0.00000001})`);
console.log('estimated fee overhead ' + _feeOverhead);
console.log(`current change amount ${_change} (${_change * 0.00000001}), boosted change amount ${_change + (totalInterest - _feeOverhead)} (${(_change + (totalInterest - _feeOverhead)) * 0.00000001})`);
_change = _change + (totalInterest - _feeOverhead);
}
const _rawtx = shepherd.buildSignedTx(outputAddress, changeAddress, wif, network, inputs, _change, value);
if (!push) { if (!push) {
const successObj = { const successObj = {
msg: 'success', msg: 'success',
result: { result: {
utxoSet: _utxoSet.set, utxoSet: inputs,
change: _utxoSet.change, change: _change,
// wif, // wif,
fee, fee,
value, value,
@ -1430,9 +1347,10 @@ shepherd.get('/electrum/createrawtx', function(req, res, next) {
const successObj = { const successObj = {
msg: 'success', msg: 'success',
result: { result: {
utxoSet: _utxoSet.set, utxoSet: inputs,
change: _utxoSet.change, change: _change,
wif, fee,
// wif,
value, value,
outputAddress, outputAddress,
changeAddress, changeAddress,
@ -1448,37 +1366,6 @@ shepherd.get('/electrum/createrawtx', function(req, res, next) {
}); });
}); });
/*shepherd.buildTestTx = function(address, wif, network) {
const _utxo = [{"value":20000,"tx_hash":"2bb1a6d807ddfd1614317028748a6feddc585255c105438e3fa7e6384e607944","height":358804,"tx_pos":0},{"value":20000,"tx_hash":"764cb67ef9d92942b0a3ef5f9c62ad2a552de07f575e1ab935c3cf45ace00cc8","height":400118,"tx_pos":0},{"value":20000,"tx_hash":"3f7da2daef2ea59fd40eb28d2be04a17839f2073c16ea291b1b03a3d799fec3b","height":427959,"tx_pos":0},{"value":20000,"tx_hash":"aa4afdece59c46a6a1e34d6ae7bf2c46b282de0ce6ef1753e8766c08e87bdb36","height":428139,"tx_pos":0},{"value":20000,"tx_hash":"3ec5e982083ffc617aaa75e68e69f9da5f07faadcd870e144f6451c34936ee24","height":428216,"tx_pos":0},{"value":20000,"tx_hash":"b943330cb034f36c6d9f03c57a2e1f0a3970378825336b505d51d1f2561af3fe","height":433459,"tx_pos":0},{"value":100000,"tx_hash":"7fee95da75e9b77bf9cd03b460fcf04c31ce4dbe4bdfa1e66256c9beb1fe2308","height":459573,"tx_pos":0},{"value":20000,"tx_hash":"aa92df15b20a10543750db58fc6e885205d1eee3438ef79f01d3c4ec2616e7aa","height":505916,"tx_pos":0},{"value":90000,"tx_hash":"aa92df15b20a10543750db58fc6e885205d1eee3438ef79f01d3c4ec2616e7aa","height":505916,"tx_pos":1},{"value":20000,"tx_hash":"dd1ab56c0e3f63e3650aeca3e2aa4ecea54cca22e97932e48341e299c4640ef9","height":505931,"tx_pos":0},{"value":100000,"tx_hash":"dd1ab56c0e3f63e3650aeca3e2aa4ecea54cca22e97932e48341e299c4640ef9","height":505931,"tx_pos":1},{"value":20000,"tx_hash":"7bed6729de8be9b61b85716d8d1615d751c1dbb2bd2defd6f9c76354a591586c","height":505937,"tx_pos":0},{"value":70000,"tx_hash":"7bed6729de8be9b61b85716d8d1615d751c1dbb2bd2defd6f9c76354a591586c","height":505937,"tx_pos":1},{"value":20000,"tx_hash":"f47f4d5e2aaee19f98ebdf97deac3c471b919b71312a073d1b45dfc27fb64d24","height":506038,"tx_pos":0},{"value":20000,"tx_hash":"e8a92b174b768a050b4801ea497cdb9651d4647f20ec5a77b5af0d6558fdf468","height":506045,"tx_pos":0},{"value":20000,"tx_hash":"758ae45606ee43b55d72ec57284abac54c34b34badf4986b3d9e53e5aaf227d8","height":506057,"tx_pos":0}];
var key = bitcoinJS.ECPair.fromWIF('UrA1TCN2j9iMYKBLkKGMo9MbndBNYVW9nJV9RdViR9CoVK82ApFb', electrumJSNetworks['komodo']);
var tx = new bitcoinJS.TransactionBuilder(electrumJSNetworks['komodo']);
console.log(key);
console.log(key.getAddress().toString());
// electrumServers[].txfee
const _txSize = shepherd.estimateTxSize(1, 1);
const _feeEstimateByte = shepherd.estimateFee('small');
const _feeEstimateTx = Math.floor(_txSize * _feeEstimateByte);
console.log('output val ' + (20000 - _feeEstimateTx));
// 20000
tx.addInput('2bb1a6d807ddfd1614317028748a6feddc585255c105438e3fa7e6384e607944', 0);
tx.addOutput('RDbGxL8QYdEp8sMULaVZS2E6XThcTKT9Jd', 20000 - _feeEstimateTx);
console.log('estimate tx size ' + _txSize);
console.log('fee per byte ' + _feeEstimateByte);
console.log('tx fee ' + _feeEstimateTx);
console.log(tx);
// (in)15000 - (out)12000 = (fee)3000, this is the miner fee
tx.sign(0, key);
const rawtx = tx.build().toHex();
console.log(rawtx);
return rawtx;
}*/
shepherd.get('/electrum/pushtx', function(req, res, next) { shepherd.get('/electrum/pushtx', function(req, res, next) {
const rawtx = req.query.rawtx; const rawtx = req.query.rawtx;
const ecl = new electrumJSCore(electrumServers[req.query.network].port, electrumServers[req.query.network].address, electrumServers[req.query.network].proto); // tcp or tls const ecl = new electrumJSCore(electrumServers[req.query.network].port, electrumServers[req.query.network].address, electrumServers[req.query.network].proto); // tcp or tls
@ -1501,24 +1388,141 @@ shepherd.get('/electrum/pushtx', function(req, res, next) {
}); });
}); });
shepherd.listunspent = function(ecl, address, network, full) {
if (full) {
return new Promise(function(resolve, reject) {
ecl.connect();
ecl.blockchainAddressListunspent(address)
.then((_utxo) => {
if (_utxo &&
_utxo.length) {
let formattedUtxoList = [];
ecl.blockchainNumblocksSubscribe()
.then(function(currentHeight) {
for (let i = 0; i < _utxo.length; i++) {
ecl.blockchainTransactionGet(_utxo[i]['tx_hash'])
.then((_rawtxJSON) => {
console.log('electrum gettransaction ==>');
console.log(i + ' | ' + (_rawtxJSON.length - 1));
console.log(_rawtxJSON);
// decode tx
const _network = electrumJSNetworks[network];
const decodedTx = electrumJSTxDecoder(_rawtxJSON, _network);
if (network === 'komodo') {
let interest = 0;
if (Number(_utxo[i].value) * 0.00000001 >= 10) {
interest = shepherd.kdmCalcInterest(decodedTx.format.locktime, _utxo[i].value);
}
formattedUtxoList.push({
txid: _utxo[i]['tx_hash'],
vout: _utxo[i]['tx_pos'],
address,
// "scriptPubKey": "76a9140e78f3f7daf736f58eee93ccd3bfaa7b2b35f49a88ac",
amount: Number(_utxo[i].value) * 0.00000001,
amountSats: _utxo[i].value,
interest: interest,
interestSats: Math.floor(interest * 100000000),
confirmations: currentHeight - _utxo[i].height,
spendable: true
});
} else {
formattedUtxoList.push({
txid: _utxo[i]['tx_hash'],
vout: _utxo[i]['tx_pos'],
address,
// "scriptPubKey": "76a9140e78f3f7daf736f58eee93ccd3bfaa7b2b35f49a88ac",
amount: Number(_utxo[i].value) * 0.00000001,
amountSats: _utxo[i].value,
confirmations: currentHeight - _utxo[i].height,
spendable: true
});
}
console.log('decoded tx =>');
console.log(decodedTx);
console.log(decodedTx.format.locktime);
if (i === _utxo.length - 1) {
ecl.close();
resolve(formattedUtxoList);
/*const successObj = {
msg: 'success',
result: {
listunspent: formattedUtxoList,
},
};
res.end(JSON.stringify(successObj));*/
}
});
}
});
} else {
ecl.close();
resolve([]);
/*const successObj = {
msg: 'success',
result: {
listunspent: [],
},
};
res.end(JSON.stringify(successObj));*/
}
});
});
} else {
return new Promise(function(resolve, reject) {
ecl.connect();
ecl.blockchainAddressListunspent(address)
.then((json) => {
ecl.close();
resolve(json);
});
});
}
}
shepherd.get('/electrum/listunspent', function(req, res, next) { shepherd.get('/electrum/listunspent', function(req, res, next) {
const ecl = new electrumJSCore(electrumServers[req.query.network].port, electrumServers[req.query.network].address, electrumServers[req.query.network].proto); // tcp or tls const network = req.query.network || shepherd.findNetworkObj(req.query.coin);
const ecl = new electrumJSCore(electrumServers[network].port, electrumServers[network].address, electrumServers[network].proto); // tcp or tls
ecl.connect(); if (req.query.full) {
ecl.blockchainAddressListunspent(req.query.address) shepherd.listunspent(ecl, req.query.address, network, true)
.then((json) => { .then((listunspent) => {
ecl.close(); console.log('electrum listunspent ==>');
console.log('electrum listunspent ==>');
const successObj = { const successObj = {
msg: 'success', msg: 'success',
result: { result: {
listunspent: json, listunspent,
}, },
}; };
res.end(JSON.stringify(successObj)); res.end(JSON.stringify(successObj));
}); });
} else {
shepherd.listunspent(ecl, req.query.address, network)
.then((json) => {
ecl.close();
console.log('electrum listunspent ==>');
const successObj = {
msg: 'success',
result: {
listunspent: json,
},
};
res.end(JSON.stringify(successObj));
});
}
}); });
shepherd.get('/electrum/estimatefee', function(req, res, next) { shepherd.get('/electrum/estimatefee', function(req, res, next) {
@ -1546,20 +1550,6 @@ shepherd.estimateTxSize = function(numVins, numOuts) {
return numVins * 180 + numOuts * 34 + 11; return numVins * 180 + numOuts * 34 + 11;
} }
shepherd.estimateFee = function(type) {
return 20;
if (type === 'small') {
return 0.00005 / 1024 * 100000000;
}
if (type === 'medium') {
return 0.0001 / 1024 * 100000000;
}
if (type === 'high') {
return 0.0003 / 1024 * 100000000;
}
}
/* /*
* list native coind * list native coind
* type: * type:

Loading…
Cancel
Save