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; |
|||
} |
@ -1,64 +1,117 @@ |
|||
const bs58check = require('bs58check'); |
|||
const bitcoinZcash = require('bitcoinjs-lib-zcash'); |
|||
const bitcoin = require('bitcoinjs-lib'); |
|||
|
|||
module.exports = (shepherd) => { |
|||
shepherd.post('/electrum/login', (req, res, next) => { |
|||
for (let key in shepherd.electrumServers) { |
|||
const _abbr = shepherd.electrumServers[key].abbr; |
|||
const _seed = req.body.seed; |
|||
let keys; |
|||
|
|||
if ((_seed.length === 51 || _seed.length === 52) && |
|||
_seed.indexOf(' ') === -1 && |
|||
_seed.match(/^[a-zA-Z0-9]*$/)) { |
|||
let key = shepherd.bitcoinJS.ECPair.fromWIF(_seed, shepherd.electrumJSNetworks.komodo); |
|||
keys = { |
|||
priv: key.toWIF(), |
|||
pub: key.getAddress(), |
|||
}; |
|||
} else { |
|||
keys = shepherd.seedToWif(_seed, shepherd.findNetworkObj(_abbr), req.body.iguana); |
|||
if (shepherd.checkToken(req.body.token)) { |
|||
let _wifError = false; |
|||
|
|||
for (let key in shepherd.electrumCoins) { |
|||
if (key !== 'auth') { |
|||
const _abbr = key; |
|||
const _seed = req.body.seed; |
|||
let keys; |
|||
let isWif = false; |
|||
|
|||
if (req.body.seed.match('^[a-zA-Z0-9]{34}$') && |
|||
shepherd.appConfig.experimentalFeatures) { |
|||
shepherd.log('watchonly pub addr'); |
|||
shepherd.electrumKeys[_abbr] = { |
|||
priv: req.body.seed, |
|||
pub: req.body.seed, |
|||
}; |
|||
} else { |
|||
try { |
|||
bs58check.decode(_seed); |
|||
isWif = true; |
|||
} catch (e) {} |
|||
|
|||
if (isWif) { |
|||
try { |
|||
let key = shepherd.isZcash(_abbr.toLowerCase()) ? bitcoinZcash.ECPair.fromWIF(_seed, shepherd.getNetworkData(_abbr.toLowerCase()), true) : bitcoin.ECPair.fromWIF(_seed, shepherd.getNetworkData(_abbr.toLowerCase()), true); |
|||
keys = { |
|||
priv: key.toWIF(), |
|||
pub: key.getAddress(), |
|||
}; |
|||
} catch (e) { |
|||
_wifError = true; |
|||
break; |
|||
} |
|||
} else { |
|||
keys = shepherd.seedToWif(_seed, shepherd.findNetworkObj(_abbr), req.body.iguana); |
|||
} |
|||
|
|||
shepherd.electrumKeys[_abbr] = { |
|||
priv: keys.priv, |
|||
pub: keys.pub, |
|||
}; |
|||
} |
|||
} |
|||
} |
|||
|
|||
shepherd.electrumKeys[_abbr] = { |
|||
priv: keys.priv, |
|||
pub: keys.pub, |
|||
}; |
|||
} |
|||
shepherd.electrumCoins.auth = true; |
|||
|
|||
shepherd.electrumCoins.auth = true; |
|||
// shepherd.log(JSON.stringify(shepherd.electrumKeys, null, '\t'), true);
|
|||
|
|||
// shepherd.log(JSON.stringify(shepherd.electrumKeys, null, '\t'), true);
|
|||
const successObj = { |
|||
msg: _wifError ? 'error' : 'success', |
|||
result: 'true', |
|||
}; |
|||
|
|||
const successObj = { |
|||
msg: 'success', |
|||
result: 'true', |
|||
}; |
|||
res.end(JSON.stringify(successObj)); |
|||
} else { |
|||
const errorObj = { |
|||
msg: 'error', |
|||
result: 'unauthorized access', |
|||
}; |
|||
|
|||
res.end(JSON.stringify(successObj)); |
|||
res.end(JSON.stringify(errorObj)); |
|||
} |
|||
}); |
|||
|
|||
shepherd.post('/electrum/lock', (req, res, next) => { |
|||
shepherd.electrumCoins.auth = false; |
|||
shepherd.electrumKeys = {}; |
|||
if (shepherd.checkToken(req.body.token)) { |
|||
shepherd.electrumCoins.auth = false; |
|||
shepherd.electrumKeys = {}; |
|||
|
|||
const successObj = { |
|||
msg: 'success', |
|||
result: 'true', |
|||
}; |
|||
const successObj = { |
|||
msg: 'success', |
|||
result: 'true', |
|||
}; |
|||
|
|||
res.end(JSON.stringify(successObj)); |
|||
} else { |
|||
const errorObj = { |
|||
msg: 'error', |
|||
result: 'unauthorized access', |
|||
}; |
|||
|
|||
res.end(JSON.stringify(successObj)); |
|||
res.end(JSON.stringify(errorObj)); |
|||
} |
|||
}); |
|||
|
|||
shepherd.post('/electrum/logout', (req, res, next) => { |
|||
shepherd.electrumCoins = { |
|||
auth: false, |
|||
}; |
|||
shepherd.electrumKeys = {}; |
|||
if (shepherd.checkToken(req.body.token)) { |
|||
shepherd.electrumCoins = { |
|||
auth: false, |
|||
}; |
|||
shepherd.electrumKeys = {}; |
|||
|
|||
const obj = { |
|||
msg: 'success', |
|||
result: 'result', |
|||
}; |
|||
const obj = { |
|||
msg: 'success', |
|||
result: 'result', |
|||
}; |
|||
|
|||
res.end(JSON.stringify(obj)); |
|||
} else { |
|||
const errorObj = { |
|||
msg: 'error', |
|||
result: 'unauthorized access', |
|||
}; |
|||
|
|||
res.end(JSON.stringify(obj)); |
|||
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 |
|||
type=e-beta |
|||
minversion=0.2.0.2 |
|||
version=0.2.0.29c |
|||
type=c-beta |
|||
minversion=0.2.0.29 |
@ -1 +1 @@ |
|||
0.2.0.25e-beta |
|||
0.2.0.29c-beta |
Loading…
Reference in new issue