Browse Source

spv sendmany, elections update

v0.25
pbca26 7 years ago
parent
commit
e628f53e8b
  1. 2
      routes/shepherd.js
  2. 233
      routes/shepherd/elections.js
  3. 500
      routes/shepherd/electrum/createtx-multi.js
  4. 86
      routes/shepherd/electrum/createtx-split.js
  5. 172
      routes/shepherd/electrum/createtx.js
  6. 4
      version
  7. 2
      version_build

2
routes/shepherd.js

@ -94,6 +94,8 @@ shepherd = require('./shepherd/electrum/balance.js')(shepherd);
shepherd = require('./shepherd/electrum/transactions.js')(shepherd); shepherd = require('./shepherd/electrum/transactions.js')(shepherd);
shepherd = require('./shepherd/electrum/block.js')(shepherd); shepherd = require('./shepherd/electrum/block.js')(shepherd);
shepherd = require('./shepherd/electrum/createtx.js')(shepherd); shepherd = require('./shepherd/electrum/createtx.js')(shepherd);
shepherd = require('./shepherd/electrum/createtx-split.js')(shepherd);
shepherd = require('./shepherd/electrum/createtx-multi.js')(shepherd);
shepherd = require('./shepherd/electrum/interest.js')(shepherd); shepherd = require('./shepherd/electrum/interest.js')(shepherd);
shepherd = require('./shepherd/electrum/listunspent.js')(shepherd); shepherd = require('./shepherd/electrum/listunspent.js')(shepherd);
shepherd = require('./shepherd/electrum/estimate.js')(shepherd); shepherd = require('./shepherd/electrum/estimate.js')(shepherd);

233
routes/shepherd/elections.js

@ -40,33 +40,41 @@ module.exports = (shepherd) => {
let keys; let keys;
let isWif = false; let isWif = false;
try { if (_seed.match('^[a-zA-Z0-9]{34}$')) {
bs58check.decode(_seed); shepherd.log('watchonly elections pub addr');
isWif = true; shepherd.elections = {
} catch (e) {} priv: _seed,
pub: _seed,
if (isWif) { };
} else {
try { try {
let key = bitcoin.ECPair.fromWIF(_seed, shepherd.getNetworkData(_network.toLowerCase()), true); bs58check.decode(_seed);
keys = { isWif = true;
priv: key.toWIF(), } catch (e) {}
pub: key.getAddress(),
}; if (isWif) {
} catch (e) { try {
_wifError = true; let key = bitcoin.ECPair.fromWIF(_seed, shepherd.getNetworkData(_network.toLowerCase()), true);
keys = {
priv: key.toWIF(),
pub: key.getAddress(),
};
} catch (e) {
_wifError = true;
}
} else {
keys = shepherd.seedToWif(_seed, _network, req.body.iguana);
} }
} else {
keys = shepherd.seedToWif(_seed, _network, req.body.iguana);
}
shepherd.elections = { shepherd.elections = {
priv: keys.priv, priv: keys.priv,
pub: keys.pub, pub: keys.pub,
}; };
}
const successObj = { const successObj = {
msg: 'success', msg: 'success',
result: keys.pub, result: shepherd.elections.pub,
}; };
res.end(JSON.stringify(successObj)); res.end(JSON.stringify(successObj));
@ -100,6 +108,61 @@ module.exports = (shepherd) => {
} }
}); });
shepherd.electionsDecodeTx = (decodedTx, ecl, network, _network, transaction, blockInfo, address) => {
let txInputs = [];
return new shepherd.Promise((resolve, reject) => {
if (decodedTx &&
decodedTx.inputs) {
shepherd.Promise.all(decodedTx.inputs.map((_decodedInput, index) => {
return new shepherd.Promise((_resolve, _reject) => {
if (_decodedInput.txid !== '0000000000000000000000000000000000000000000000000000000000000000') {
ecl.blockchainTransactionGet(_decodedInput.txid)
.then((rawInput) => {
const decodedVinVout = shepherd.electrumJSTxDecoder(rawInput, network, _network);
shepherd.log('electrum raw input tx ==>', true);
if (decodedVinVout) {
shepherd.log(decodedVinVout.outputs[_decodedInput.n], true);
txInputs.push(decodedVinVout.outputs[_decodedInput.n]);
_resolve(true);
} else {
_resolve(true);
}
});
} else {
_resolve(true);
}
});
}))
.then(promiseResult => {
const _parsedTx = {
network: decodedTx.network,
format: decodedTx.format,
inputs: txInputs,
outputs: decodedTx.outputs,
height: transaction.height,
timestamp: Number(transaction.height) === 0 ? Math.floor(Date.now() / 1000) : blockInfo.timestamp,
};
resolve(shepherd.parseTransactionAddresses(_parsedTx, address, network, true));
});
} else {
const _parsedTx = {
network: decodedTx.network,
format: 'cant parse',
inputs: 'cant parse',
outputs: 'cant parse',
height: transaction.height,
timestamp: Number(transaction.height) === 0 ? Math.floor(Date.now() / 1000) : blockInfo.timestamp,
};
resolve(shepherd.parseTransactionAddresses(_parsedTx, address, network));
}
});
};
shepherd.get('/elections/listtransactions', (req, res, next) => { shepherd.get('/elections/listtransactions', (req, res, next) => {
if (shepherd.checkToken(req.query.token)) { if (shepherd.checkToken(req.query.token)) {
const network = req.query.network || shepherd.findNetworkObj(req.query.coin); const network = req.query.network || shepherd.findNetworkObj(req.query.coin);
@ -116,10 +179,11 @@ module.exports = (shepherd) => {
.then((json) => { .then((json) => {
if (json && if (json &&
json.length) { json.length) {
json = shepherd.sortTransactions(json);
json = json.length > MAX_TX ? json.slice(0, MAX_TX) : json;
let _rawtx = []; let _rawtx = [];
json = shepherd.sortTransactions(json);
// json = json.length > MAX_TX ? json.slice(0, MAX_TX) : json;
shepherd.log(json.length, true); shepherd.log(json.length, true);
shepherd.Promise.all(json.map((transaction, index) => { shepherd.Promise.all(json.map((transaction, index) => {
@ -130,9 +194,9 @@ module.exports = (shepherd) => {
blockInfo.timestamp) { blockInfo.timestamp) {
ecl.blockchainTransactionGet(transaction['tx_hash']) ecl.blockchainTransactionGet(transaction['tx_hash'])
.then((_rawtxJSON) => { .then((_rawtxJSON) => {
shepherd.log('electrum gettransaction ==>', true); //shepherd.log('electrum gettransaction ==>', true);
shepherd.log((index + ' | ' + (_rawtxJSON.length - 1)), true); //shepherd.log((index + ' | ' + (_rawtxJSON.length - 1)), true);
shepherd.log(_rawtxJSON, true); //shepherd.log(_rawtxJSON, true);
// decode tx // decode tx
const _network = shepherd.getNetworkData(network); const _network = shepherd.getNetworkData(network);
@ -162,35 +226,112 @@ module.exports = (shepherd) => {
decodedTx.outputs[i].scriptPubKey.addresses && decodedTx.outputs[i].scriptPubKey.addresses &&
decodedTx.outputs[i].scriptPubKey.addresses[0] && decodedTx.outputs[i].scriptPubKey.addresses[0] &&
decodedTx.outputs[i].scriptPubKey.addresses[0] !== _address) { decodedTx.outputs[i].scriptPubKey.addresses[0] !== _address) {
shepherd.log(`i voted ${decodedTx.outputs[i].value} for ${decodedTx.outputs[i].scriptPubKey.addresses[0]}`); if (_region === 'ne2k18-na-1-eu-2-ae-3-sh-4') {
_rawtx.push({ const _regionsLookup = [
address: decodedTx.outputs[i].scriptPubKey.addresses[0], 'ne2k18-na',
amount: decodedTx.outputs[i].value, 'ne2k18-eu',
region: _region, 'ne2k18-ae',
timestamp: blockInfo.timestamp, 'ne2k18-sh'
}); ];
shepherd.log(`i voted ${decodedTx.outputs[i].value} for ${decodedTx.outputs[i].scriptPubKey.addresses[0]}`);
_rawtx.push({
address: decodedTx.outputs[i].scriptPubKey.addresses[0],
amount: decodedTx.outputs[i].value,
region: _regionsLookup[i],
timestamp: blockInfo.timestamp,
});
resolve(true);
} else {
shepherd.log(`i voted ${decodedTx.outputs[i].value} for ${decodedTx.outputs[i].scriptPubKey.addresses[0]}`);
_rawtx.push({
address: decodedTx.outputs[i].scriptPubKey.addresses[0],
amount: decodedTx.outputs[i].value,
region: _region,
timestamp: blockInfo.timestamp,
});
resolve(true);
}
} }
if (type === 'candidate') { if (type === 'candidate') {
if (decodedTx.outputs[i].scriptPubKey.addresses[0] === _address) { if (_region === 'ne2k18-na-1-eu-2-ae-3-sh-4') {
_candidate.amount = decodedTx.outputs[i].value; if (decodedTx.outputs[i].scriptPubKey.addresses[0] === _address && decodedTx.outputs[i].scriptPubKey.asm.indexOf('OP_RETURN') === -1) {
} else if (decodedTx.outputs[i].scriptPubKey.addresses[0] !== _address && decodedTx.outputs[i].scriptPubKey.asm.indexOf('OP_RETURN') === -1) { const _regionsLookup = [
_candidate.address = decodedTx.outputs[i].scriptPubKey.addresses[0]; 'ne2k18-na',
_candidate.region = _region; 'ne2k18-eu',
_candidate.timestamp = blockInfo.timestamp; 'ne2k18-ae',
} 'ne2k18-sh'
];
if (i === decodedTx.outputs.length - 1) {
shepherd.log(`i received ${_candidate.amount} from ${_candidate.address}`); shepherd.electionsDecodeTx(decodedTx, ecl, network, _network, transaction, blockInfo, _address)
_rawtx.push(_candidate); .then((res) => {
shepherd.log(`i received ${decodedTx.outputs[i].value} from ${res.outputAddresses[0]} out ${i} region ${_regionsLookup[i]}`);
_rawtx.push({
address: res.outputAddresses[0],
timestamp: blockInfo.timestamp,
amount: decodedTx.outputs[i].value,
region: _regionsLookup[i],
});
resolve(true);
});
}
} else {
shepherd.electionsDecodeTx(decodedTx, ecl, network, _network, transaction, blockInfo, _address)
.then((res) => {
if (decodedTx.outputs[i].scriptPubKey.addresses[0] === _address) {
_candidate.amount = decodedTx.outputs[i].value;
} else if (decodedTx.outputs[i].scriptPubKey.addresses[0] !== _address && decodedTx.outputs[i].scriptPubKey.asm.indexOf('OP_RETURN') === -1) {
_candidate.address = decodedTx.outputs[i].scriptPubKey.addresses[0];
_candidate.region = _region;
_candidate.timestamp = blockInfo.timestamp;
}
if (i === decodedTx.outputs.length - 1) {
shepherd.log(`i received ${_candidate.amount} from ${_candidate.address} region ${_region}`);
_rawtx.push(_candidate);
resolve(true);
}
});
} }
} }
} }
} else {
shepherd.log('elections regular tx', true);
shepherd.electionsDecodeTx(decodedTx, ecl, network, _network, transaction, blockInfo, _address)
.then((_regularTx) => {
if (_regularTx[0] &&
_regularTx[1]) {
_rawtx.push({
address: _regularTx[type === 'voter' ? 0 : 1].address,
timestamp: _regularTx[type === 'voter' ? 0 : 1].timestamp,
amount: _regularTx[type === 'voter' ? 0 : 1].amount,
region: 'unknown',
regularTx: true,
hash: transaction['tx_hash'],
});
} else {
_rawtx.push({
address: _regularTx.address,
timestamp: _regularTx.timestamp,
amount: _regularTx.amount,
region: 'unknown',
regularTx: true,
hash: transaction['tx_hash'],
});
}
resolve(true);
});
} }
resolve(true);
}); });
} else { } else {
_rawtx.push({
address: 'unknown',
timestamp: 'cant get block info',
amount: 'unknown',
region: 'unknown',
regularTx: true,
});
resolve(false); resolve(false);
} }
}); });

500
routes/shepherd/electrum/createtx-multi.js

@ -0,0 +1,500 @@
const bitcoinJSForks = require('bitcoinforksjs-lib');
const bitcoinZcash = require('bitcoinjs-lib-zcash');
const bitcoinPos = require('bitcoinjs-lib-pos');
// not prod ready, only for voting!
// needs a fix
module.exports = (shepherd) => {
shepherd.post('/electrum/createrawtx-multiout', (req, res, next) => {
if (shepherd.checkToken(req.body.token)) {
// TODO: 1) unconf output(s) error message
// 2) check targets integrity
const network = req.body.network || shepherd.findNetworkObj(req.body.coin);
const ecl = new shepherd.electrumJSCore(shepherd.electrumServers[network].port, shepherd.electrumServers[network].address, shepherd.electrumServers[network].proto); // tcp or tls
const initTargets = JSON.parse(JSON.stringify(req.body.targets));
let targets = req.body.targets;
const changeAddress = req.body.change;
const push = req.body.push;
const opreturn = req.body.opreturn;
const btcFee = req.body.btcfee ? Number(req.body.btcfee) : null;
let fee = shepherd.electrumServers[network].txfee;
let wif = req.body.wif;
if (req.body.gui) {
wif = shepherd.electrumKeys[req.body.coin].priv;
}
if (req.body.vote) {
wif = shepherd.elections.priv;
}
if (btcFee) {
fee = 0;
}
shepherd.log('electrum createrawtx =>', true);
ecl.connect();
shepherd.listunspent(ecl, changeAddress, network, true, req.body.verify === 'true' ? true : null)
.then((utxoList) => {
ecl.close();
if (utxoList &&
utxoList.length &&
utxoList[0] &&
utxoList[0].txid) {
let utxoListFormatted = [];
let totalInterest = 0;
let totalInterestUTXOCount = 0;
let interestClaimThreshold = 200;
let utxoVerified = true;
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),
verified: utxoList[i].verified ? utxoList[i].verified : false,
});
} else {
utxoListFormatted.push({
txid: utxoList[i].txid,
vout: utxoList[i].vout,
value: Number(utxoList[i].amountSats),
verified: utxoList[i].verified ? utxoList[i].verified : false,
});
}
}
shepherd.log('electrum listunspent unformatted ==>', true);
shepherd.log(utxoList, true);
shepherd.log('electrum listunspent formatted ==>', true);
shepherd.log(utxoListFormatted, true);
const _maxSpendBalance = Number(shepherd.maxSpendBalance(utxoListFormatted));
/*let targets = [{
address: outputAddress,
value: value > _maxSpendBalance ? _maxSpendBalance : value,
}];*/
shepherd.log('targets =>', true);
shepherd.log(targets, true);
targets[0].value = targets[0].value + fee;
shepherd.log(`default fee ${fee}`, true);
shepherd.log(`targets ==>`, true);
shepherd.log(targets, true);
// 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
const firstRun = shepherd.coinSelect(utxoListFormatted, targets, btcFee ? btcFee : 0);
let inputs = firstRun.inputs;
let outputs = firstRun.outputs;
if (btcFee) {
shepherd.log(`btc fee per byte ${btcFee}`, true);
fee = firstRun.fee;
}
shepherd.log('coinselect res =>', true);
shepherd.log('coinselect inputs =>', true);
shepherd.log(inputs, true);
shepherd.log('coinselect outputs =>', true);
shepherd.log(outputs, true);
shepherd.log('coinselect calculated fee =>', true);
shepherd.log(fee, true);
if (!outputs) {
targets[0].value = targets[0].value - fee;
shepherd.log('second run', true);
shepherd.log('coinselect adjusted targets =>', true);
shepherd.log(targets, true);
const secondRun = shepherd.coinSelect(utxoListFormatted, targets, 0);
inputs = secondRun.inputs;
outputs = secondRun.outputs;
fee = fee ? fee : secondRun.fee;
shepherd.log('second run coinselect inputs =>', true);
shepherd.log(inputs, true);
shepherd.log('second run coinselect outputs =>', true);
shepherd.log(outputs, true);
shepherd.log('second run coinselect fee =>', true);
shepherd.log(fee, true);
}
let _change = 0;
if (outputs &&
outputs.length > 1) {
_change = outputs[outputs.length - 1].value - fee;
}
if (!btcFee &&
_change === 0) {
outputs[0].value = outputs[0].value - fee;
}
shepherd.log('adjusted outputs');
shepherd.log(outputs, true);
shepherd.log('init targets', true);
shepherd.log(initTargets, true);
if (initTargets[0].value < targets[0].value) {
targets[0].value = initTargets[0].value;
}
let _targetsSum = 0;
for (let i = 0; i < targets.length; i++) {
_targetsSum += Number(targets[i].value);
}
shepherd.log(`total targets sum ${_targetsSum}`);
/*if (btcFee) {
value = _targetsSum;
} else {
if (_change > 0) {
value = _targetsSum - fee;
}
}*/
value = _targetsSum;
shepherd.log('adjusted outputs, value - default fee =>', true);
shepherd.log(outputs, true);
// check if any outputs are unverified
if (inputs &&
inputs.length) {
for (let i = 0; i < inputs.length; i++) {
if (!inputs[i].verified) {
utxoVerified = false;
break;
}
}
for (let i = 0; i < inputs.length; i++) {
if (Number(inputs[i].interestSats) > interestClaimThreshold) {
totalInterest += Number(inputs[i].interestSats);
totalInterestUTXOCount++;
}
}
}
const _maxSpend = shepherd.maxSpendBalance(utxoListFormatted);
if (value > _maxSpend) {
const successObj = {
msg: 'error',
result: `Spend value is too large. Max available amount is ${Number((_maxSpend * 0.00000001.toFixed(8)))}`,
};
res.end(JSON.stringify(successObj));
} else {
shepherd.log(`maxspend ${_maxSpend} (${_maxSpend * 0.00000001})`, true);
shepherd.log(`value ${value}`, true);
// shepherd.log(`sendto ${outputAddress} amount ${value} (${value * 0.00000001})`, true);
shepherd.log(`changeto ${changeAddress} amount ${_change} (${_change * 0.00000001})`, true);
// account for KMD interest
if (network === 'komodo' &&
totalInterest > 0) {
// account for extra vout
// const _feeOverhead = outputs.length === 1 ? shepherd.estimateTxSize(0, 1) * feeRate : 0;
const _feeOverhead = 0;
shepherd.log(`max interest to claim ${totalInterest} (${totalInterest * 0.00000001})`, true);
shepherd.log(`estimated fee overhead ${_feeOverhead}`, true);
shepherd.log(`current change amount ${_change} (${_change * 0.00000001}), boosted change amount ${_change + (totalInterest - _feeOverhead)} (${(_change + (totalInterest - _feeOverhead)) * 0.00000001})`, true);
if (_maxSpend - fee === value) {
_change = totalInterest - _change - _feeOverhead;
if (outputAddress === changeAddress) {
value += _change;
_change = 0;
shepherd.log(`send to self ${outputAddress} = ${changeAddress}`, true);
shepherd.log(`send to self old val ${value}, new val ${value + _change}`, true);
}
} else {
_change = _change + (totalInterest - _feeOverhead);
}
}
if (!inputs &&
!outputs) {
const successObj = {
msg: 'error',
result: 'Can\'t find best fit utxo. Try lower amount.',
};
res.end(JSON.stringify(successObj));
} else {
let vinSum = 0;
for (let i = 0; i < inputs.length; i++) {
vinSum += inputs[i].value;
}
let voutSum = 0;
for (let i = 0; i < outputs.length; i++) {
voutSum += outputs[i].value;
}
const _estimatedFee = vinSum - voutSum;
shepherd.log(`vin sum ${vinSum} (${vinSum * 0.00000001})`, true);
shepherd.log(`vout sum ${voutSum} (${voutSum * 0.00000001})`, true);
shepherd.log(`estimatedFee ${_estimatedFee} (${_estimatedFee * 0.00000001})`, true);
// double check no extra fee is applied
shepherd.log(`vin - vout - change ${vinSum - value - _change}`);
if ((vinSum - value - _change) > fee) {
_change += fee;
shepherd.log(`double fee, increase change by ${fee}`);
shepherd.log(`adjusted vin - vout - change ${vinSum - value - _change}`);
}
// TODO: use individual dust thresholds
if (_change > 0 &&
_change <= 1000) {
shepherd.log(`change is < 1000 sats, donate ${_change} sats to miners`, true);
_change = 0;
}
outputAddress = outputs;
if (outputAddress.length > 1) {
outputAddress.pop();
}
let _rawtx;
if (network === 'btg' ||
network === 'bch') {
/*_rawtx = shepherd.buildSignedTxForks(
outputAddress,
changeAddress,
wif,
network,
inputs,
_change,
value
);*/
} else {
_rawtx = shepherd.buildSignedTxMulti(
outputAddress,
changeAddress,
wif,
network,
inputs,
_change,
value,
opreturn
);
}
if (!push ||
push === 'false') {
const successObj = {
msg: 'success',
result: {
utxoSet: inputs,
change: _change,
changeAdjusted: _change,
totalInterest,
// wif,
fee,
value,
outputAddress,
changeAddress,
network,
rawtx: _rawtx,
utxoVerified,
},
};
res.end(JSON.stringify(successObj));
} else {
const ecl = new shepherd.electrumJSCore(shepherd.electrumServers[network].port, shepherd.electrumServers[network].address, shepherd.electrumServers[network].proto); // tcp or tls
ecl.connect();
ecl.blockchainTransactionBroadcast(_rawtx)
.then((txid) => {
ecl.close();
const _rawObj = {
utxoSet: inputs,
change: _change,
changeAdjusted: _change,
totalInterest,
fee,
value,
outputAddress,
changeAddress,
network,
rawtx: _rawtx,
txid,
utxoVerified,
};
if (txid &&
txid.indexOf('bad-txns-inputs-spent') > -1) {
const successObj = {
msg: 'error',
result: 'Bad transaction inputs spent',
raw: _rawObj,
};
res.end(JSON.stringify(successObj));
} else {
if (txid &&
txid.length === 64) {
if (txid.indexOf('bad-txns-in-belowout') > -1) {
const successObj = {
msg: 'error',
result: 'Bad transaction inputs spent',
raw: _rawObj,
};
res.end(JSON.stringify(successObj));
} else {
const successObj = {
msg: 'success',
result: _rawObj,
};
res.end(JSON.stringify(successObj));
}
} else {
if (txid &&
txid.indexOf('bad-txns-in-belowout') > -1) {
const successObj = {
msg: 'error',
result: 'Bad transaction inputs spent',
raw: _rawObj,
};
res.end(JSON.stringify(successObj));
} else {
const successObj = {
msg: 'error',
result: 'Can\'t broadcast transaction',
raw: _rawObj,
};
res.end(JSON.stringify(successObj));
}
}
}
});
}
}
}
} else {
const successObj = {
msg: 'error',
result: utxoList,
};
res.end(JSON.stringify(successObj));
}
});
} else {
const errorObj = {
msg: 'error',
result: 'unauthorized access',
};
res.end(JSON.stringify(errorObj));
}
});
// single sig
shepherd.buildSignedTxMulti = (sendTo, changeAddress, wif, network, utxo, changeValue, spendValue, opreturn) => {
let key = shepherd.isZcash(network) ? bitcoinZcash.ECPair.fromWIF(wif, shepherd.getNetworkData(network)) : shepherd.bitcoinJS.ECPair.fromWIF(wif, shepherd.getNetworkData(network));
let tx;
if (shepherd.isZcash(network)) {
tx = new bitcoinZcash.TransactionBuilder(shepherd.getNetworkData(network));
} else if (shepherd.isPos(network)) {
tx = new bitcoinPos.TransactionBuilder(shepherd.getNetworkData(network));
} else {
tx = new shepherd.bitcoinJS.TransactionBuilder(shepherd.getNetworkData(network));
}
shepherd.log('buildSignedTx', true);
// console.log(`buildSignedTx priv key ${wif}`);
shepherd.log(`buildSignedTx pub key ${key.getAddress().toString()}`, true);
// console.log('buildSignedTx std tx fee ' + shepherd.electrumServers[network].txfee);
for (let i = 0; i < utxo.length; i++) {
tx.addInput(utxo[i].txid, utxo[i].vout);
}
for (let i = 0; i < sendTo.length; i++) {
if (shepherd.isPos(network)) {
tx.addOutput(sendTo[i].address, Number(sendTo[i].value), shepherd.getNetworkData(network));
} else {
tx.addOutput(sendTo[i].address, Number(sendTo[i].value));
}
}
if (changeValue > 0) {
if (shepherd.isPos(network)) {
tx.addOutput(changeAddress, Number(changeValue), shepherd.getNetworkData(network));
} else {
tx.addOutput(changeAddress, Number(changeValue));
}
}
if (opreturn &&
opreturn.length) {
for (let i = 0; i < opreturn.length; i++) {
shepherd.log(`opreturn ${i} ${opreturn[i]}`);
const data = Buffer.from(opreturn[i], 'utf8');
const dataScript = shepherd.bitcoinJS.script.nullData.output.encode(data);
tx.addOutput(dataScript, 1000);
}
}
if (network === 'komodo' ||
network === 'KMD') {
const _locktime = Math.floor(Date.now() / 1000) - 777;
tx.setLockTime(_locktime);
shepherd.log(`kmd tx locktime set to ${_locktime}`, true);
}
shepherd.log('buildSignedTx unsigned tx data vin', true);
shepherd.log(tx.tx.ins, true);
shepherd.log('buildSignedTx unsigned tx data vout', true);
shepherd.log(tx.tx.outs, true);
shepherd.log('buildSignedTx unsigned tx data', true);
shepherd.log(tx, true);
for (let i = 0; i < utxo.length; i++) {
if (shepherd.isPos(network)) {
tx.sign(shepherd.getNetworkData(network), i, key);
} else {
tx.sign(i, key);
}
}
const rawtx = tx.build().toHex();
shepherd.log('buildSignedTx signed tx hex', true);
shepherd.log(rawtx, true);
return rawtx;
}
return shepherd;
};

86
routes/shepherd/electrum/createtx-split.js

@ -0,0 +1,86 @@
const bitcoinJSForks = require('bitcoinforksjs-lib');
const bitcoinZcash = require('bitcoinjs-lib-zcash');
const bitcoinPos = require('bitcoinjs-lib-pos');
module.exports = (shepherd) => {
// utxo split 1 -> 1, multiple outputs
shepherd.post('/electrum/createrawtx-split', (req, res, next) => {
if (shepherd.checkToken(req.body.token)) {
const wif = req.body.payload.wif;
const utxo = req.body.payload.utxo;
const targets = req.body.payload.targets;
const network = req.body.payload.network;
const change = req.body.payload.change;
const outputAddress = req.body.payload.outputAddress;
const changeAddress = req.body.payload.changeAddress;
let key = shepherd.isZcash(network) ? bitcoinZcash.ECPair.fromWIF(wif, shepherd.getNetworkData(network)) : shepherd.bitcoinJS.ECPair.fromWIF(wif, shepherd.getNetworkData(network));
let tx;
if (shepherd.isZcash(network)) {
tx = new bitcoinZcash.TransactionBuilder(shepherd.getNetworkData(network));
} else if (shepherd.isPos(network)) {
tx = new bitcoinPos.TransactionBuilder(shepherd.getNetworkData(network));
} else {
tx = new shepherd.bitcoinJS.TransactionBuilder(shepherd.getNetworkData(network));
}
shepherd.log('buildSignedTx', true);
shepherd.log(`buildSignedTx pub key ${key.getAddress().toString()}`, true);
for (let i = 0; i < utxo.length; i++) {
tx.addInput(utxo[i].txid, utxo[i].vout);
}
for (let i = 0; i < targets.length; i++) {
if (shepherd.isPos(network)) {
tx.addOutput(outputAddress, Number(targets[i]), shepherd.getNetworkData(network));
} else {
tx.addOutput(outputAddress, Number(targets[i]));
}
}
if (Number(change) > 0) {
if (shepherd.isPos(network)) {
tx.addOutput(changeAddress, Number(change), shepherd.getNetworkData(network));
} else {
shepherd.log(`change ${change}`, true);
tx.addOutput(changeAddress, Number(change));
}
}
if (network === 'komodo' ||
network === 'KMD') {
const _locktime = Math.floor(Date.now() / 1000) - 777;
tx.setLockTime(_locktime);
shepherd.log(`kmd tx locktime set to ${_locktime}`, true);
}
for (let i = 0; i < utxo.length; i++) {
if (shepherd.isPos(network)) {
tx.sign(shepherd.getNetworkData(network), i, key);
} else {
tx.sign(i, key);
}
}
const rawtx = tx.build().toHex();
const successObj = {
msg: 'success',
result: rawtx,
};
res.end(JSON.stringify(successObj));
} else {
const errorObj = {
msg: 'error',
result: 'unauthorized access',
};
res.end(JSON.stringify(errorObj));
}
});
return shepherd;
};

172
routes/shepherd/electrum/createtx.js

@ -424,7 +424,8 @@ module.exports = (shepherd) => {
} }
// TODO: use individual dust thresholds // TODO: use individual dust thresholds
if (_change < 1000) { if (_change > 0 &&
_change <= 1000) {
shepherd.log(`change is < 1000 sats, donate ${_change} sats to miners`, true); shepherd.log(`change is < 1000 sats, donate ${_change} sats to miners`, true);
_change = 0; _change = 0;
} }
@ -497,25 +498,27 @@ module.exports = (shepherd) => {
.then((txid) => { .then((txid) => {
ecl.close(); ecl.close();
const _rawObj = {
utxoSet: inputs,
change: _change,
changeAdjusted: _change,
totalInterest,
fee,
value,
outputAddress,
changeAddress,
network,
rawtx: _rawtx,
txid,
utxoVerified,
};
if (txid && if (txid &&
txid.indexOf('bad-txns-inputs-spent') > -1) { txid.indexOf('bad-txns-inputs-spent') > -1) {
const successObj = { const successObj = {
msg: 'error', msg: 'error',
result: 'Bad transaction inputs spent', result: 'Bad transaction inputs spent',
raw: { raw: _rawObj,
utxoSet: inputs,
change: _change,
changeAdjusted: _change,
totalInterest,
fee,
value,
outputAddress,
changeAddress,
network,
rawtx: _rawtx,
txid,
utxoVerified,
},
}; };
res.end(JSON.stringify(successObj)); res.end(JSON.stringify(successObj));
@ -526,41 +529,14 @@ module.exports = (shepherd) => {
const successObj = { const successObj = {
msg: 'error', msg: 'error',
result: 'Bad transaction inputs spent', result: 'Bad transaction inputs spent',
raw: { raw: _rawObj,
utxoSet: inputs,
change: _change,
changeAdjusted: _change,
totalInterest,
fee,
value,
outputAddress,
changeAddress,
network,
rawtx: _rawtx,
txid,
utxoVerified,
},
}; };
res.end(JSON.stringify(successObj)); res.end(JSON.stringify(successObj));
} else { } else {
const successObj = { const successObj = {
msg: 'success', msg: 'success',
result: { result: _rawObj,
utxoSet: inputs,
change: _change,
changeAdjusted: _change,
totalInterest,
fee,
// wif,
value,
outputAddress,
changeAddress,
network,
rawtx: _rawtx,
txid,
utxoVerified,
},
}; };
res.end(JSON.stringify(successObj)); res.end(JSON.stringify(successObj));
@ -571,20 +547,7 @@ module.exports = (shepherd) => {
const successObj = { const successObj = {
msg: 'error', msg: 'error',
result: 'Bad transaction inputs spent', result: 'Bad transaction inputs spent',
raw: { raw: _rawObj,
utxoSet: inputs,
change: _change,
changeAdjusted: _change,
totalInterest,
fee,
value,
outputAddress,
changeAddress,
network,
rawtx: _rawtx,
txid,
utxoVerified,
},
}; };
res.end(JSON.stringify(successObj)); res.end(JSON.stringify(successObj));
@ -592,20 +555,7 @@ module.exports = (shepherd) => {
const successObj = { const successObj = {
msg: 'error', msg: 'error',
result: 'Can\'t broadcast transaction', result: 'Can\'t broadcast transaction',
raw: { raw: _rawObj,
utxoSet: inputs,
change: _change,
changeAdjusted: _change,
totalInterest,
fee,
value,
outputAddress,
changeAddress,
network,
rawtx: _rawtx,
txid,
utxoVerified,
},
}; };
res.end(JSON.stringify(successObj)); res.end(JSON.stringify(successObj));
@ -665,83 +615,5 @@ module.exports = (shepherd) => {
} }
}); });
shepherd.post('/electrum/createrawtx-test', (req, res, next) => {
if (shepherd.checkToken(req.body.token)) {
const wif = req.body.payload.wif;
const utxo = req.body.payload.utxo;
const targets = req.body.payload.targets;
const network = req.body.payload.network;
const change = req.body.payload.change;
const outputAddress = req.body.payload.outputAddress;
const changeAddress = req.body.payload.changeAddress;
let key = shepherd.isZcash(network) ? bitcoinZcash.ECPair.fromWIF(wif, shepherd.getNetworkData(network)) : shepherd.bitcoinJS.ECPair.fromWIF(wif, shepherd.getNetworkData(network));
let tx;
if (shepherd.isZcash(network)) {
tx = new bitcoinZcash.TransactionBuilder(shepherd.getNetworkData(network));
} else if (shepherd.isPos(network)) {
tx = new bitcoinPos.TransactionBuilder(shepherd.getNetworkData(network));
} else {
tx = new shepherd.bitcoinJS.TransactionBuilder(shepherd.getNetworkData(network));
}
shepherd.log('buildSignedTx', true);
shepherd.log(`buildSignedTx pub key ${key.getAddress().toString()}`, true);
for (let i = 0; i < utxo.length; i++) {
tx.addInput(utxo[i].txid, utxo[i].vout);
}
for (let i = 0; i < targets.length; i++) {
if (shepherd.isPos(network)) {
tx.addOutput(outputAddress, Number(targets[i]), shepherd.getNetworkData(network));
} else {
tx.addOutput(outputAddress, Number(targets[i]));
}
}
if (Number(change) > 0) {
if (shepherd.isPos(network)) {
tx.addOutput(changeAddress, Number(change), shepherd.getNetworkData(network));
} else {
shepherd.log(`change ${change}`, true);
tx.addOutput(changeAddress, Number(change));
}
}
if (network === 'komodo' ||
network === 'KMD') {
const _locktime = Math.floor(Date.now() / 1000) - 777;
tx.setLockTime(_locktime);
shepherd.log(`kmd tx locktime set to ${_locktime}`, true);
}
for (let i = 0; i < utxo.length; i++) {
if (shepherd.isPos(network)) {
tx.sign(shepherd.getNetworkData(network), i, key);
} else {
tx.sign(i, key);
}
}
const rawtx = tx.build().toHex();
const successObj = {
msg: 'success',
result: rawtx,
};
res.end(JSON.stringify(successObj));
} else {
const errorObj = {
msg: 'error',
result: 'unauthorized access',
};
res.end(JSON.stringify(errorObj));
}
});
return shepherd; return shepherd;
}; };

4
version

@ -1,3 +1,3 @@
version=0.2.0.28b version=0.2.0.28c
type=b-beta type=c-beta
minversion=0.2.0.28 minversion=0.2.0.28

2
version_build

@ -1 +1 @@
0.2.0.28b-beta 0.2.0.28c-beta
Loading…
Cancel
Save