mirror of https://github.com/lukechilds/Agama.git
siulynot
7 years ago
committed by
GitHub
49 changed files with 5374 additions and 2998 deletions
@ -1 +1 @@ |
|||||
Subproject commit 0092ac50871b6964e6462f3ba68ac7d1cd8c1421 |
Subproject commit 54f9dfc6e7b519a5a2295b49813cfd99be131cae |
@ -1,126 +0,0 @@ |
|||||
/* |
|
||||
* Copyright (c) 2015 Satinderjit Singh |
|
||||
* |
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy |
|
||||
* of this software and associated documentation files (the "Software"), to deal |
|
||||
* in the Software without restriction, including without limitation the rights |
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
|
||||
* copies of the Software, and to permit persons to whom the Software is |
|
||||
* furnished to do so, subject to the following conditions: |
|
||||
* |
|
||||
* The above copyright notice and this permission notice shall be included in all |
|
||||
* copies or substantial portions of the Software. |
|
||||
* |
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
|
||||
* SOFTWARE. |
|
||||
* |
|
||||
*/ |
|
||||
|
|
||||
/* |
|
||||
* Agama komodo-cli paths |
|
||||
* |
|
||||
*/ |
|
||||
|
|
||||
var child_process = require('child_process'), |
|
||||
path = require('path'), |
|
||||
os = require('os'); |
|
||||
|
|
||||
if (os.platform() === 'darwin') { |
|
||||
var komodocliBin = path.join(__dirname, '../assets/bin/osx/komodo-cli'), |
|
||||
zcashcliBin = '/Applications/ZCashSwingWalletUI.app/Contents/MacOS/zcash-cli'; |
|
||||
} |
|
||||
|
|
||||
if (os.platform() === 'linux') { |
|
||||
var komodocliBin = path.join(__dirname, '../assets/bin/linux64/komodo-cli'); |
|
||||
} |
|
||||
|
|
||||
if (os.platform() === 'win32') { |
|
||||
var komodocliBin = path.join(__dirname, '../assets/bin/win64/komodo-cli.exe'), |
|
||||
komodocliBin = path.normalize(komodocliBin); |
|
||||
} |
|
||||
|
|
||||
console.log(komodocliBin) |
|
||||
|
|
||||
/** |
|
||||
* The **komodo-cli** command is used to get komodo api calls answer. |
|
||||
* |
|
||||
* @private |
|
||||
* @category kmdcli |
|
||||
* |
|
||||
*/ |
|
||||
var kmdcli = module.exports = { |
|
||||
exec: child_process.exec, |
|
||||
command: command |
|
||||
}; |
|
||||
|
|
||||
/** |
|
||||
* Parses komodo-cli commands. |
|
||||
* |
|
||||
* @private |
|
||||
* @static |
|
||||
* @category kmdcli |
|
||||
* @param {function} callback The callback function. |
|
||||
* |
|
||||
*/ |
|
||||
function parse_kmdcli_commands(callback) { |
|
||||
return function(error, stdout, stderr) { |
|
||||
if (error) callback(error, stderr); |
|
||||
else callback(error, stdout); |
|
||||
//console.log(stdout)
|
|
||||
}; |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* Parses komodo-cli commands. |
|
||||
* |
|
||||
* @private |
|
||||
* @static |
|
||||
* @category kmdcli |
|
||||
* @param {function} callback The callback function. |
|
||||
* @example |
|
||||
* |
|
||||
* var kmdcli = require('./kmdcli'); |
|
||||
* |
|
||||
* kmdcli.command('getinfo', function(err, command) { |
|
||||
* console.log(command); |
|
||||
* }); |
|
||||
* |
|
||||
* // =>
|
|
||||
* { |
|
||||
* "version" : 1000550, |
|
||||
* "protocolversion" : 170002, |
|
||||
* "notarized" : 254740, |
|
||||
* "notarizedhash" : "01f4f1c46662ccca2e7fa9e7e38d4d2e4ced4402fa0f4fc116b8f004bb8cf272", |
|
||||
* "notarizedtxid" : "2b16e47a176f8c1886ca0268243f9b96f8b2db466ea26ae99873d5224bbf80b6", |
|
||||
* "walletversion" : 60000, |
|
||||
* "balance" : 32632.46167742, |
|
||||
* "interest" : 0.00478671, |
|
||||
* "blocks" : 254791, |
|
||||
* "longestchain" : 254791, |
|
||||
* "timeoffset" : 0, |
|
||||
* "tiptime" : 1490815616, |
|
||||
* "connections" : 8, |
|
||||
* "proxy" : "", |
|
||||
* "difficulty" : 707836.56791394, |
|
||||
* "testnet" : false, |
|
||||
* "keypoololdest" : 1482746526, |
|
||||
* "keypoolsize" : 101, |
|
||||
* "paytxfee" : 0.00000000, |
|
||||
* "relayfee" : 0.00001000, |
|
||||
* "errors" : "WARNING: check your network connection, 157 blocks received in the last 4 hours (240 expected)", |
|
||||
* "notaryid" : -1, |
|
||||
* "pubkey" : "000000000000000000000000000000000000000000000000000000000000000000" |
|
||||
* } |
|
||||
* |
|
||||
*/ |
|
||||
function command(kmd_command, callback) { |
|
||||
if (callback) { |
|
||||
return this.exec(komodocliBin + " " + kmd_command, |
|
||||
parse_kmdcli_commands(callback)); |
|
||||
} |
|
||||
} |
|
@ -0,0 +1,120 @@ |
|||||
|
/* |
||||
|
MIT License |
||||
|
|
||||
|
Copyright (c) 2017 Yuki Akiyama, SuperNET |
||||
|
|
||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy |
||||
|
of this software and associated documentation files (the "Software"), to deal |
||||
|
in the Software without restriction, including without limitation the rights |
||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
||||
|
copies of the Software, and to permit persons to whom the Software is |
||||
|
furnished to do so, subject to the following conditions: |
||||
|
|
||||
|
The above copyright notice and this permission notice shall be included in all |
||||
|
copies or substantial portions of the Software. |
||||
|
|
||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
||||
|
SOFTWARE. |
||||
|
*/ |
||||
|
|
||||
|
var bitcoin = require('bitcoinjs-lib-zcash'); |
||||
|
|
||||
|
var decodeFormat = function(tx) { |
||||
|
var result = { |
||||
|
txid: tx.getId(), |
||||
|
version: tx.version, |
||||
|
locktime: tx.locktime, |
||||
|
}; |
||||
|
|
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
var decodeInput = function(tx) { |
||||
|
var result = []; |
||||
|
|
||||
|
tx.ins.forEach(function(input, n) { |
||||
|
var vin = { |
||||
|
txid: input.hash.reverse().toString('hex'), |
||||
|
n: input.index, |
||||
|
script: bitcoin.script.toASM(input.script), |
||||
|
sequence: input.sequence, |
||||
|
}; |
||||
|
|
||||
|
result.push(vin); |
||||
|
}); |
||||
|
|
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
var decodeOutput = function(tx, network) { |
||||
|
var format = function(out, n, network) { |
||||
|
var vout = { |
||||
|
satoshi: out.value, |
||||
|
value: (1e-8 * out.value).toFixed(8), |
||||
|
n: n, |
||||
|
scriptPubKey: { |
||||
|
asm: bitcoin.script.toASM(out.script), |
||||
|
hex: out.script.toString('hex'), |
||||
|
type: bitcoin.script.classifyOutput(out.script), |
||||
|
addresses: [], |
||||
|
}, |
||||
|
}; |
||||
|
|
||||
|
switch(vout.scriptPubKey.type) { |
||||
|
case 'pubkeyhash': |
||||
|
vout.scriptPubKey.addresses.push(bitcoin.address.fromOutputScript(out.script, network)); |
||||
|
break; |
||||
|
case 'pubkey': |
||||
|
const pubKeyBuffer = new Buffer(vout.scriptPubKey.asm.split(' ')[0], 'hex'); |
||||
|
vout.scriptPubKey.addresses.push(bitcoin.ECPair.fromPublicKeyBuffer(pubKeyBuffer, network).getAddress()); |
||||
|
break; |
||||
|
case 'scripthash': |
||||
|
vout.scriptPubKey.addresses.push(bitcoin.address.fromOutputScript(out.script, network)); |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
return vout; |
||||
|
} |
||||
|
|
||||
|
var result = []; |
||||
|
|
||||
|
tx.outs.forEach(function(out, n) { |
||||
|
result.push(format(out, n, network)); |
||||
|
}); |
||||
|
|
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
var TxDecoder = module.exports = function(rawtx, network) { |
||||
|
try { |
||||
|
const _tx = bitcoin.Transaction.fromHex(rawtx); |
||||
|
|
||||
|
return { |
||||
|
tx: _tx, |
||||
|
network: network, |
||||
|
format: decodeFormat(_tx), |
||||
|
inputs: decodeInput(_tx), |
||||
|
outputs: decodeOutput(_tx, network), |
||||
|
}; |
||||
|
} catch (e) { |
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
TxDecoder.prototype.decode = function() { |
||||
|
var result = {}; |
||||
|
var self = this; |
||||
|
|
||||
|
Object.keys(self.format).forEach(function(key) { |
||||
|
result[key] = self.format[key]; |
||||
|
}); |
||||
|
|
||||
|
result.outputs = self.outputs; |
||||
|
|
||||
|
return result; |
||||
|
} |
@ -0,0 +1,123 @@ |
|||||
|
/* |
||||
|
MIT License |
||||
|
|
||||
|
Copyright (c) 2017 Yuki Akiyama, SuperNET |
||||
|
|
||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy |
||||
|
of this software and associated documentation files (the "Software"), to deal |
||||
|
in the Software without restriction, including without limitation the rights |
||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
||||
|
copies of the Software, and to permit persons to whom the Software is |
||||
|
furnished to do so, subject to the following conditions: |
||||
|
|
||||
|
The above copyright notice and this permission notice shall be included in all |
||||
|
copies or substantial portions of the Software. |
||||
|
|
||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
||||
|
SOFTWARE. |
||||
|
*/ |
||||
|
|
||||
|
var bitcoin = require('bitcoinjs-lib-pos'); |
||||
|
var script = require('bitcoinjs-lib-pos/src/script'); |
||||
|
var address = require('bitcoinjs-lib-pos/src/address'); |
||||
|
var bitcoinJS = require('bitcoinjs-lib'); |
||||
|
|
||||
|
var decodeFormat = function(tx) { |
||||
|
var result = { |
||||
|
txid: tx.getId(), |
||||
|
version: tx.version, |
||||
|
locktime: tx.locktime, |
||||
|
}; |
||||
|
|
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
var decodeInput = function(tx) { |
||||
|
var result = []; |
||||
|
|
||||
|
tx.ins.forEach(function(input, n) { |
||||
|
var vin = { |
||||
|
txid: input.hash.reverse().toString('hex'), |
||||
|
n: input.index, |
||||
|
script: script.fromHex(input.hash), |
||||
|
sequence: input.sequence, |
||||
|
}; |
||||
|
|
||||
|
result.push(vin); |
||||
|
}); |
||||
|
|
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
var decodeOutput = function(tx, network) { |
||||
|
var format = function(out, n, network) { |
||||
|
var vout = { |
||||
|
satoshi: out.value, |
||||
|
value: (1e-8 * out.value).toFixed(8), |
||||
|
n: n, |
||||
|
scriptPubKey: { |
||||
|
asm: bitcoinJS.script.toASM(out.script.chunks), |
||||
|
hex: out.script.toHex(), |
||||
|
type: bitcoin.scripts.classifyOutput(out.script), |
||||
|
addresses: [], |
||||
|
}, |
||||
|
}; |
||||
|
|
||||
|
switch(vout.scriptPubKey.type) { |
||||
|
case 'pubkeyhash': |
||||
|
vout.scriptPubKey.addresses.push(address.fromOutputScript(out.script, network)); |
||||
|
break; |
||||
|
case 'pubkey': |
||||
|
const pubKeyBuffer = new Buffer(vout.scriptPubKey.asm.split(' ')[0], 'hex'); |
||||
|
vout.scriptPubKey.addresses.push(bitcoin.ECPair.fromPublicKeyBuffer(pubKeyBuffer, network).getAddress()); |
||||
|
break; |
||||
|
case 'scripthash': |
||||
|
vout.scriptPubKey.addresses.push(address.fromOutputScript(out.script, network)); |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
return vout; |
||||
|
} |
||||
|
|
||||
|
var result = []; |
||||
|
|
||||
|
tx.outs.forEach(function(out, n) { |
||||
|
result.push(format(out, n, network)); |
||||
|
}); |
||||
|
|
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
var TxDecoder = module.exports = function(rawtx, network) { |
||||
|
try { |
||||
|
const _tx = bitcoin.Transaction.fromHex(rawtx, network); |
||||
|
|
||||
|
return { |
||||
|
tx: _tx, |
||||
|
network: network, |
||||
|
format: decodeFormat(_tx), |
||||
|
inputs: decodeInput(_tx), |
||||
|
outputs: decodeOutput(_tx, network), |
||||
|
}; |
||||
|
} catch (e) { |
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
TxDecoder.prototype.decode = function() { |
||||
|
var result = {}; |
||||
|
var self = this; |
||||
|
|
||||
|
Object.keys(self.format).forEach(function(key) { |
||||
|
result[key] = self.format[key]; |
||||
|
}); |
||||
|
|
||||
|
result.outputs = self.outputs; |
||||
|
|
||||
|
return result; |
||||
|
} |
@ -1,239 +0,0 @@ |
|||||
module.exports = (shepherd) => { |
|
||||
shepherd.testClearAll = () => { |
|
||||
return new shepherd.Promise((resolve, reject) => { |
|
||||
shepherd.fs.removeSync(`${iguanaTestDir}`); |
|
||||
resolve('done'); |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
shepherd.testBins = (daemonName) => { |
|
||||
return new shepherd.Promise((resolve, reject) => { |
|
||||
const _bins = { |
|
||||
komodod: shepherd.komododBin, |
|
||||
komodoCli: shepherd.komodocliBin, |
|
||||
}; |
|
||||
const _arg = null; |
|
||||
let _pid; |
|
||||
|
|
||||
shepherd.log('testBins exec ' + _bins[daemonName]); |
|
||||
|
|
||||
if (!shepherd.fs.existsSync(shepherd.agamaTestDir)) { |
|
||||
shepherd.fs.mkdirSync(shepherd.agamaTestDir); |
|
||||
} |
|
||||
|
|
||||
try { |
|
||||
shepherd._fs.access(`${shepherd.agamaTestDir}/${daemonName}Test.log`, shepherd.fs.constants.R_OK, (err) => { |
|
||||
if (!err) { |
|
||||
try { |
|
||||
shepherd._fs.unlinkSync(`${shepherd.agamaTestDir}/${daemonName}Test.log`); |
|
||||
} catch (e) {} |
|
||||
} else { |
|
||||
shepherd.log(`path ${shepherd.agamaTestDir}/${daemonName}Test.log doesnt exist`); |
|
||||
} |
|
||||
}); |
|
||||
} catch (e) {} |
|
||||
|
|
||||
if (daemonName === 'komodod') { |
|
||||
try { |
|
||||
shepherd._fs.access(`${iguanaTestDir}/debug.log`, shepherd.fs.constants.R_OK, (err) => { |
|
||||
if (!err) { |
|
||||
shepherd._fs.unlinkSync(`${iguanaTestDir}/db.log`); |
|
||||
shepherd._fs.unlinkSync(`${iguanaTestDir}/debug.log`); |
|
||||
shepherd._fs.unlinkSync(`${iguanaTestDir}/komodo.conf`); |
|
||||
shepherd._fs.unlinkSync(`${iguanaTestDir}/komodod.pid`); |
|
||||
shepherd._fs.unlinkSync(`${iguanaTestDir}/komodostate`); |
|
||||
shepherd._fs.unlinkSync(`${iguanaTestDir}/realtime`); |
|
||||
shepherd._fs.unlinkSync(`${iguanaTestDir}/wallet.dat`); |
|
||||
shepherd._fs.unlinkSync(`${iguanaTestDir}/.lock`); |
|
||||
shepherd.fs.removeSync(`${iguanaTestDir}/blocks`); |
|
||||
shepherd.fs.removeSync(`${iguanaTestDir}/chainstate`); |
|
||||
shepherd.fs.removeSync(`${iguanaTestDir}/database`); |
|
||||
execKomodod(); |
|
||||
} else { |
|
||||
shepherd.log(`test: nothing to remove in ${iguanaTestDir}`); |
|
||||
execKomodod(); |
|
||||
} |
|
||||
}); |
|
||||
} catch (e) {} |
|
||||
|
|
||||
const execKomodod = () => { |
|
||||
let _komododTest = { |
|
||||
port: 'unknown', |
|
||||
start: 'unknown', |
|
||||
getinfo: 'unknown', |
|
||||
errors: { |
|
||||
assertFailed: false, |
|
||||
zcashParams: false, |
|
||||
}, |
|
||||
}; |
|
||||
const _komodoConf = 'rpcuser=user83f3afba8d714993\n' + |
|
||||
'rpcpassword=0d4430ca1543833e35bce5a0cc9e16b3\n' + |
|
||||
'server=1\n' + |
|
||||
'addnode=78.47.196.146\n' + |
|
||||
'addnode=5.9.102.210\n' + |
|
||||
'addnode=178.63.69.164\n' + |
|
||||
'addnode=88.198.65.74\n' + |
|
||||
'addnode=5.9.122.241\n' + |
|
||||
'addnode=144.76.94.3\n' + |
|
||||
'addnode=144.76.94.38\n' + |
|
||||
'addnode=89.248.166.91\n' + |
|
||||
'addnode=148.251.57.148\n' + |
|
||||
'addnode=149.56.28.84\n' + |
|
||||
'addnode=176.9.26.39\n' + |
|
||||
'addnode=94.102.63.199\n' + |
|
||||
'addnode=94.102.63.200\n' + |
|
||||
'addnode=104.255.64.3\n' + |
|
||||
'addnode=221.121.144.140\n' + |
|
||||
'addnode=103.18.58.150\n' + |
|
||||
'addnode=103.18.58.146\n' + |
|
||||
'addnode=213.202.253.10\n' + |
|
||||
'addnode=185.106.121.32\n' + |
|
||||
'addnode=27.100.36.201\n'; |
|
||||
|
|
||||
shepherd.fs.writeFile(`${iguanaTestDir}/komodo.conf`, _komodoConf, (err) => { |
|
||||
if (err) { |
|
||||
shepherd.log(`test: error writing komodo conf in ${iguanaTestDir}`); |
|
||||
} |
|
||||
}); |
|
||||
|
|
||||
shepherd.portscanner.checkPortStatus('7771', '127.0.0.1', (error, status) => { |
|
||||
// Status is 'open' if currently in use or 'closed' if available
|
|
||||
if (status === 'closed') { |
|
||||
_komododTest.port = 'passed'; |
|
||||
} else { |
|
||||
_komododTest.port = 'failed'; |
|
||||
} |
|
||||
}); |
|
||||
|
|
||||
/*pm2.connect(true,function(err) { //start up pm2 god |
|
||||
if (err) { |
|
||||
shepherd.error(err); |
|
||||
process.exit(2); |
|
||||
} |
|
||||
|
|
||||
pm2.start({ |
|
||||
script: shepherd.komododBin, // path to binary
|
|
||||
name: 'komodod', |
|
||||
exec_mode : 'fork', |
|
||||
args: [ |
|
||||
'-daemon=0', |
|
||||
'-addnode=78.47.196.146', |
|
||||
`-datadir=${iguanaTestDir}/` |
|
||||
], |
|
||||
output: `${iguanaTestDir}/komododTest.log`, |
|
||||
mergeLogs: true, |
|
||||
}, function(err, apps) { |
|
||||
if (apps[0] && |
|
||||
apps[0].process && |
|
||||
apps[0].process.pid) { |
|
||||
_komododTest.start = 'success'; |
|
||||
shepherd.log(`test: got komodod instance pid = ${apps[0].process.pid}`); |
|
||||
shepherd.writeLog(`test: komodod started with pid ${apps[0].process.pid}`); |
|
||||
} else { |
|
||||
_komododTest.start = 'failed'; |
|
||||
shepherd.log(`unable to start komodod`); |
|
||||
} |
|
||||
|
|
||||
pm2.disconnect(); // Disconnect from PM2
|
|
||||
if (err) { |
|
||||
shepherd.writeLog(`test: error starting komodod`); |
|
||||
shepherd.log(`komodod fork err: ${err}`); |
|
||||
// throw err;
|
|
||||
} |
|
||||
}); |
|
||||
});*/ |
|
||||
|
|
||||
setTimeout(() => { |
|
||||
const options = { |
|
||||
url: `http://localhost:7771`, |
|
||||
method: 'POST', |
|
||||
auth: { |
|
||||
user: 'user83f3afba8d714993', |
|
||||
pass: '0d4430ca1543833e35bce5a0cc9e16b3', |
|
||||
}, |
|
||||
body: JSON.stringify({ |
|
||||
agent: 'bitcoinrpc', |
|
||||
method: 'getinfo', |
|
||||
}), |
|
||||
}; |
|
||||
|
|
||||
shepherd.request(options, (error, response, body) => { |
|
||||
if (response && |
|
||||
response.statusCode && |
|
||||
response.statusCode === 200) { |
|
||||
// res.end(body);
|
|
||||
shepherd.log(JSON.stringify(body, null, '\t')); |
|
||||
} else { |
|
||||
// res.end(body);
|
|
||||
shepherd.log(JSON.stringify(body, null, '\t')); |
|
||||
} |
|
||||
}); |
|
||||
}, 10000); |
|
||||
|
|
||||
setTimeout(() => { |
|
||||
pm2.delete('komodod'); |
|
||||
resolve(_komododTest); |
|
||||
}, 20000); |
|
||||
} |
|
||||
// komodod debug.log hooks
|
|
||||
|
|
||||
//"{\"result\":{\"version\":1000850,\"protocolversion\":170002,\"KMDversion\":\"0.1.1\",\"notarized\":0,\"notarizedhash\":\"0000000000000000000000000000000000000000000000000000000000000000\",\"notarizedtxid\":\"0000000000000000000000000000000000000000000000000000000000000000\",\"notarizedtxid_height\":\"mempool\",\"notarized_confirms\":0,\"walletversion\":60000,\"balance\":0.00000000,\"interest\":0.00000000,\"blocks\":128,\"longestchain\":472331,\"timeoffset\":0,\"tiptime\":1473827710,\"connections\":1,\"proxy\":\"\",\"difficulty\":1,\"testnet\":false,\"keypoololdest\":1504118047,\"keypoolsize\":101,\"paytxfee\":0.00000000,\"relayfee\":0.00000100,\"errors\":\"\"},\"error\":null,\"id\":null}\n"
|
|
||||
|
|
||||
//2017-08-30 17:51:33 Error: Cannot find the Zcash network parameters in the following directory:
|
|
||||
//"/home/pbca/.zcash-params"
|
|
||||
//Please run 'zcash-fetch-params' or './zcutil/fetch-params.sh' and then restart.
|
|
||||
//EXCEPTION: St13runtime_error
|
|
||||
//Assertion failed.
|
|
||||
//2017-08-30 17:51:14 Using config file /home/pbca/.iguana/test/komodo.conf
|
|
||||
//2017-08-30 18:23:43 UpdateTip: new best=0a47c1323f393650f7221c217d19d149d002d35444f47fde61be2dd90fbde8e6 height=1 log2_work=5.0874628 tx=2 date=2016-09-13 19:04:01 progress=0.000001 cache=0.0MiB(1tx)
|
|
||||
//2017-08-30 18:23:43 UpdateTip: new best=05076a4e1fc9af0f5fda690257b17ae20c12d4796dfba1624804d012c9ec00be height=2 log2_work=5.6724253 tx=3 date=2016-09-13 19:05:28 progress=0.000001 cache=0.0MiB(2tx)
|
|
||||
|
|
||||
/*shepherd.execFile(`${shepherd.komododBin}`, _arg, { |
|
||||
maxBuffer: 1024 * 10000 // 10 mb
|
|
||||
}, function(error, stdout, stderr) { |
|
||||
shepherd.writeLog(`stdout: ${stdout}`); |
|
||||
shepherd.writeLog(`stderr: ${stderr}`); |
|
||||
|
|
||||
if (error !== null) { |
|
||||
console.log(`exec error: ${error}`); |
|
||||
shepherd.writeLog(`exec error: ${error}`); |
|
||||
|
|
||||
if (error.toString().indexOf('using -reindex') > -1) { |
|
||||
shepherd.io.emit('service', { |
|
||||
komodod: { |
|
||||
error: 'run -reindex', |
|
||||
} |
|
||||
}); |
|
||||
} |
|
||||
} |
|
||||
});*/ |
|
||||
} |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
// komodod datadir location test
|
|
||||
shepherd.testLocation = (path) => { |
|
||||
return new shepherd.Promise((resolve, reject) => { |
|
||||
if (path.indexOf(' ') > -1) { |
|
||||
shepherd.log(`error testing path ${path}`); |
|
||||
resolve(-1); |
|
||||
} else { |
|
||||
shepherd.fs.lstat(path, (err, stats) => { |
|
||||
if (err) { |
|
||||
shepherd.log(`error testing path ${path}`); |
|
||||
resolve(-1); |
|
||||
} else { |
|
||||
if (stats.isDirectory()) { |
|
||||
resolve(true); |
|
||||
} else { |
|
||||
shepherd.log(`error testing path ${path} not a folder`); |
|
||||
resolve(false); |
|
||||
} |
|
||||
} |
|
||||
}); |
|
||||
} |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
return shepherd; |
|
||||
}; |
|
@ -0,0 +1,372 @@ |
|||||
|
const bs58check = require('bs58check'); |
||||
|
const bitcoin = require('bitcoinjs-lib'); |
||||
|
|
||||
|
module.exports = (shepherd) => { |
||||
|
shepherd.elections = {}; |
||||
|
|
||||
|
shepherd.hex2str = (hexx) => { |
||||
|
const hex = hexx.toString(); // force conversion
|
||||
|
let str = ''; |
||||
|
|
||||
|
for (let i = 0; i < hex.length; i += 2) { |
||||
|
str += String.fromCharCode(parseInt(hex.substr(i, 2), 16)); |
||||
|
} |
||||
|
|
||||
|
return str; |
||||
|
}; |
||||
|
|
||||
|
shepherd.post('/elections/status', (req, res, next) => { |
||||
|
if (shepherd.checkToken(req.body.token)) { |
||||
|
const successObj = { |
||||
|
msg: 'success', |
||||
|
result: shepherd.elections.pub ? shepherd.elections.pub : 'unauth', |
||||
|
}; |
||||
|
|
||||
|
res.end(JSON.stringify(successObj)); |
||||
|
} else { |
||||
|
const errorObj = { |
||||
|
msg: 'error', |
||||
|
result: 'unauthorized access', |
||||
|
}; |
||||
|
|
||||
|
res.end(JSON.stringify(errorObj)); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
shepherd.post('/elections/login', (req, res, next) => { |
||||
|
if (shepherd.checkToken(req.body.token)) { |
||||
|
const _seed = req.body.seed; |
||||
|
const _network = req.body.network; |
||||
|
let keys; |
||||
|
let isWif = false; |
||||
|
|
||||
|
if (_seed.match('^[a-zA-Z0-9]{34}$')) { |
||||
|
shepherd.log('watchonly elections pub addr'); |
||||
|
shepherd.elections = { |
||||
|
priv: _seed, |
||||
|
pub: _seed, |
||||
|
}; |
||||
|
} else { |
||||
|
try { |
||||
|
bs58check.decode(_seed); |
||||
|
isWif = true; |
||||
|
} catch (e) {} |
||||
|
|
||||
|
if (isWif) { |
||||
|
try { |
||||
|
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); |
||||
|
} |
||||
|
|
||||
|
shepherd.elections = { |
||||
|
priv: keys.priv, |
||||
|
pub: keys.pub, |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
const successObj = { |
||||
|
msg: 'success', |
||||
|
result: shepherd.elections.pub, |
||||
|
}; |
||||
|
|
||||
|
res.end(JSON.stringify(successObj)); |
||||
|
} else { |
||||
|
const errorObj = { |
||||
|
msg: 'error', |
||||
|
result: 'unauthorized access', |
||||
|
}; |
||||
|
|
||||
|
res.end(JSON.stringify(errorObj)); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
shepherd.post('/elections/logout', (req, res, next) => { |
||||
|
if (shepherd.checkToken(req.body.token)) { |
||||
|
shepherd.elections = {}; |
||||
|
|
||||
|
const successObj = { |
||||
|
msg: 'success', |
||||
|
result: true, |
||||
|
}; |
||||
|
|
||||
|
res.end(JSON.stringify(successObj)); |
||||
|
} else { |
||||
|
const errorObj = { |
||||
|
msg: 'error', |
||||
|
result: 'unauthorized access', |
||||
|
}; |
||||
|
|
||||
|
res.end(JSON.stringify(errorObj)); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
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) => { |
||||
|
if (shepherd.checkToken(req.query.token)) { |
||||
|
const network = req.query.network || shepherd.findNetworkObj(req.query.coin); |
||||
|
const ecl = new shepherd.electrumJSCore(shepherd.electrumServers[network].port, shepherd.electrumServers[network].address, shepherd.electrumServers[network].proto); // tcp or tls
|
||||
|
const type = req.query.type; |
||||
|
const _address = req.query.address; |
||||
|
|
||||
|
shepherd.log('electrum elections listtransactions ==>', true); |
||||
|
|
||||
|
const MAX_TX = req.query.maxlength || 10; |
||||
|
ecl.connect(); |
||||
|
|
||||
|
ecl.blockchainAddressGetHistory(_address) |
||||
|
.then((json) => { |
||||
|
if (json && |
||||
|
json.length) { |
||||
|
let _rawtx = []; |
||||
|
|
||||
|
json = shepherd.sortTransactions(json); |
||||
|
// json = json.length > MAX_TX ? json.slice(0, MAX_TX) : json;
|
||||
|
|
||||
|
shepherd.log(json.length, true); |
||||
|
|
||||
|
shepherd.Promise.all(json.map((transaction, index) => { |
||||
|
return new shepherd.Promise((resolve, reject) => { |
||||
|
ecl.blockchainBlockGetHeader(transaction.height) |
||||
|
.then((blockInfo) => { |
||||
|
if (blockInfo && |
||||
|
blockInfo.timestamp) { |
||||
|
ecl.blockchainTransactionGet(transaction['tx_hash']) |
||||
|
.then((_rawtxJSON) => { |
||||
|
//shepherd.log('electrum gettransaction ==>', true);
|
||||
|
//shepherd.log((index + ' | ' + (_rawtxJSON.length - 1)), true);
|
||||
|
//shepherd.log(_rawtxJSON, true);
|
||||
|
|
||||
|
// decode tx
|
||||
|
const _network = shepherd.getNetworkData(network); |
||||
|
const decodedTx = shepherd.electrumJSTxDecoder(_rawtxJSON, network, _network); |
||||
|
let _res = {}; |
||||
|
let _opreturnFound = false; |
||||
|
let _region; |
||||
|
|
||||
|
if (decodedTx && |
||||
|
decodedTx.outputs && |
||||
|
decodedTx.outputs.length) { |
||||
|
for (let i = 0; i < decodedTx.outputs.length; i++) { |
||||
|
if (decodedTx.outputs[i].scriptPubKey.asm.indexOf('OP_RETURN') > -1) { |
||||
|
_opreturnFound = true; |
||||
|
_region = shepherd.hex2str(decodedTx.outputs[i].scriptPubKey.hex.substr(4, decodedTx.outputs[i].scriptPubKey.hex.length)); |
||||
|
shepherd.log(`found opreturn tag ${_region}`); |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (_opreturnFound) { |
||||
|
let _candidate = {}; |
||||
|
|
||||
|
for (let i = 0; i < decodedTx.outputs.length; i++) { |
||||
|
if (type === 'voter' && |
||||
|
decodedTx.outputs[i].scriptPubKey.addresses && |
||||
|
decodedTx.outputs[i].scriptPubKey.addresses[0] && |
||||
|
decodedTx.outputs[i].scriptPubKey.addresses[0] !== _address) { |
||||
|
if (_region === 'ne2k18-na-1-eu-2-ae-3-sh-4') { |
||||
|
const _regionsLookup = [ |
||||
|
'ne2k18-na', |
||||
|
'ne2k18-eu', |
||||
|
'ne2k18-ae', |
||||
|
'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 (_region === 'ne2k18-na-1-eu-2-ae-3-sh-4') { |
||||
|
if (decodedTx.outputs[i].scriptPubKey.addresses[0] === _address && decodedTx.outputs[i].scriptPubKey.asm.indexOf('OP_RETURN') === -1) { |
||||
|
const _regionsLookup = [ |
||||
|
'ne2k18-na', |
||||
|
'ne2k18-eu', |
||||
|
'ne2k18-ae', |
||||
|
'ne2k18-sh' |
||||
|
]; |
||||
|
|
||||
|
shepherd.electionsDecodeTx(decodedTx, ecl, network, _network, transaction, blockInfo, _address) |
||||
|
.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 || 'self', |
||||
|
timestamp: _regularTx[type === 'voter' ? 0 : 1].timestamp, |
||||
|
amount: _regularTx[type === 'voter' ? 0 : 1].amount, |
||||
|
region: 'unknown', |
||||
|
regularTx: true, |
||||
|
hash: transaction['tx_hash'], |
||||
|
}); |
||||
|
} else { |
||||
|
if ((type === 'voter' && _regularTx.type !== 'received') && (type === 'candidate' && _regularTx.type !== 'sent')) { |
||||
|
_rawtx.push({ |
||||
|
address: _regularTx.address || 'self', |
||||
|
timestamp: _regularTx.timestamp, |
||||
|
amount: _regularTx.amount, |
||||
|
region: 'unknown', |
||||
|
regularTx: true, |
||||
|
hash: transaction['tx_hash'], |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
resolve(true); |
||||
|
}); |
||||
|
} |
||||
|
}); |
||||
|
} else { |
||||
|
_rawtx.push({ |
||||
|
address: 'unknown', |
||||
|
timestamp: 'cant get block info', |
||||
|
amount: 'unknown', |
||||
|
region: 'unknown', |
||||
|
regularTx: true, |
||||
|
}); |
||||
|
resolve(false); |
||||
|
} |
||||
|
}); |
||||
|
}); |
||||
|
})) |
||||
|
.then(promiseResult => { |
||||
|
ecl.close(); |
||||
|
|
||||
|
const successObj = { |
||||
|
msg: 'success', |
||||
|
result: _rawtx, |
||||
|
}; |
||||
|
|
||||
|
res.end(JSON.stringify(successObj)); |
||||
|
}); |
||||
|
} else { |
||||
|
const successObj = { |
||||
|
msg: 'success', |
||||
|
result: [], |
||||
|
}; |
||||
|
|
||||
|
res.end(JSON.stringify(successObj)); |
||||
|
} |
||||
|
}); |
||||
|
} else { |
||||
|
const errorObj = { |
||||
|
msg: 'error', |
||||
|
result: 'unauthorized access', |
||||
|
}; |
||||
|
|
||||
|
res.end(JSON.stringify(errorObj)); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
return shepherd; |
||||
|
} |
@ -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; |
||||
|
}; |
@ -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; |
||||
|
}; |
@ -1,3 +1,3 @@ |
|||||
version=0.2.0.25e |
version=0.2.0.29c |
||||
type=e-beta |
type=c-beta |
||||
minversion=0.2.0.2 |
minversion=0.2.0.29 |
@ -1 +1 @@ |
|||||
0.2.0.25e-beta |
0.2.0.29c-beta |
Loading…
Reference in new issue