mirror of https://github.com/lukechilds/Agama.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
401 lines
15 KiB
401 lines
15 KiB
module.exports = (shepherd) => {
|
|
shepherd.sortTransactions = (transactions) => {
|
|
return transactions.sort((b, a) => {
|
|
if (a.height < b.height) {
|
|
return -1;
|
|
}
|
|
|
|
if (a.height > b.height) {
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
});
|
|
}
|
|
|
|
shepherd.get('/electrum/listtransactions', (req, res, next) => {
|
|
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
|
|
|
|
shepherd.log('electrum listtransactions ==>', true);
|
|
|
|
if (!req.query.full) {
|
|
ecl.connect();
|
|
ecl.blockchainAddressGetHistory(req.query.address)
|
|
.then((json) => {
|
|
ecl.close();
|
|
shepherd.log(json, true);
|
|
|
|
json = shepherd.sortTransactions(json);
|
|
|
|
const successObj = {
|
|
msg: 'success',
|
|
result: json,
|
|
};
|
|
|
|
res.end(JSON.stringify(successObj));
|
|
});
|
|
} else {
|
|
// !expensive call!
|
|
// TODO: limit e.g. 1-10, 10-20 etc
|
|
const MAX_TX = req.query.maxlength || 10;
|
|
ecl.connect();
|
|
|
|
ecl.blockchainNumblocksSubscribe()
|
|
.then((currentHeight) => {
|
|
if (currentHeight &&
|
|
Number(currentHeight) > 0) {
|
|
ecl.blockchainAddressGetHistory(req.query.address)
|
|
.then((json) => {
|
|
if (json &&
|
|
json.length) {
|
|
json = shepherd.sortTransactions(json);
|
|
json = json.length > MAX_TX ? json.slice(0, MAX_TX) : json;
|
|
let _rawtx = [];
|
|
|
|
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 txInputs = [];
|
|
shepherd.log(`decodedtx network ${network}`, true);
|
|
|
|
shepherd.log('decodedtx =>', true);
|
|
shepherd.log(decodedTx.outputs, true);
|
|
|
|
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,
|
|
confirmations: Number(transaction.height) === 0 ? 0 : currentHeight - transaction.height,
|
|
};
|
|
|
|
const formattedTx = shepherd.parseTransactionAddresses(_parsedTx, req.query.address, network);
|
|
|
|
if (formattedTx.type) {
|
|
formattedTx.height = transaction.height;
|
|
formattedTx.blocktime = blockInfo.timestamp;
|
|
formattedTx.timereceived = blockInfo.timereceived;
|
|
formattedTx.hex = _rawtxJSON;
|
|
formattedTx.inputs = decodedTx.inputs;
|
|
formattedTx.outputs = decodedTx.outputs;
|
|
formattedTx.locktime = decodedTx.format.locktime;
|
|
_rawtx.push(formattedTx);
|
|
} else {
|
|
formattedTx[0].height = transaction.height;
|
|
formattedTx[0].blocktime = blockInfo.timestamp;
|
|
formattedTx[0].timereceived = blockInfo.timereceived;
|
|
formattedTx[0].hex = _rawtxJSON;
|
|
formattedTx[0].inputs = decodedTx.inputs;
|
|
formattedTx[0].outputs = decodedTx.outputs;
|
|
formattedTx[0].locktime = decodedTx.format.locktime;
|
|
formattedTx[1].height = transaction.height;
|
|
formattedTx[1].blocktime = blockInfo.timestamp;
|
|
formattedTx[1].timereceived = blockInfo.timereceived;
|
|
formattedTx[1].hex = _rawtxJSON;
|
|
formattedTx[1].inputs = decodedTx.inputs;
|
|
formattedTx[1].outputs = decodedTx.outputs;
|
|
formattedTx[1].locktime = decodedTx.format.locktime;
|
|
_rawtx.push(formattedTx[0]);
|
|
_rawtx.push(formattedTx[1]);
|
|
}
|
|
resolve(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,
|
|
confirmations: Number(transaction.height) === 0 ? 0 : currentHeight - transaction.height,
|
|
};
|
|
|
|
const formattedTx = shepherd.parseTransactionAddresses(_parsedTx, req.query.address, network);
|
|
_rawtx.push(formattedTx);
|
|
resolve(true);
|
|
}
|
|
});
|
|
} else {
|
|
const _parsedTx = {
|
|
network: 'cant parse',
|
|
format: 'cant parse',
|
|
inputs: 'cant parse',
|
|
outputs: 'cant parse',
|
|
height: transaction.height,
|
|
timestamp: 'cant get block info',
|
|
confirmations: Number(transaction.height) === 0 ? 0 : currentHeight - transaction.height,
|
|
};
|
|
const formattedTx = shepherd.parseTransactionAddresses(_parsedTx, req.query.address, network);
|
|
_rawtx.push(formattedTx);
|
|
resolve(true);
|
|
}
|
|
});
|
|
});
|
|
}))
|
|
.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 successObj = {
|
|
msg: 'error',
|
|
result: 'cant get current height',
|
|
};
|
|
|
|
res.end(JSON.stringify(successObj));
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
shepherd.get('/electrum/gettransaction', (req, res, next) => {
|
|
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
|
|
|
|
shepherd.log('electrum gettransaction =>', true);
|
|
|
|
ecl.connect();
|
|
ecl.blockchainTransactionGet(req.query.txid)
|
|
.then((json) => {
|
|
ecl.close();
|
|
shepherd.log(json, true);
|
|
|
|
const successObj = {
|
|
msg: 'success',
|
|
result: json,
|
|
};
|
|
|
|
res.end(JSON.stringify(successObj));
|
|
});
|
|
});
|
|
|
|
shepherd.parseTransactionAddresses = (tx, targetAddress, network) => {
|
|
// TODO: - sum vins / sum vouts to the same address
|
|
// - multi vin multi vout
|
|
// - detect change address
|
|
let result = [];
|
|
let _parse = {
|
|
inputs: {},
|
|
outputs: {},
|
|
};
|
|
let _sum = {
|
|
inputs: 0,
|
|
outputs: 0,
|
|
};
|
|
let _total = {
|
|
inputs: 0,
|
|
outputs: 0,
|
|
};
|
|
|
|
shepherd.log('parseTransactionAddresses result ==>', true);
|
|
|
|
if (tx.format === 'cant parse') {
|
|
return {
|
|
type: 'unknown',
|
|
amount: 'unknown',
|
|
address: targetAddress,
|
|
timestamp: tx.timestamp,
|
|
txid: tx.format.txid,
|
|
confirmations: tx.confirmations,
|
|
}
|
|
}
|
|
|
|
for (let key in _parse) {
|
|
if (!tx[key].length) {
|
|
_parse[key] = [];
|
|
_parse[key].push(tx[key]);
|
|
} else {
|
|
_parse[key] = tx[key];
|
|
}
|
|
|
|
for (let i = 0; i < _parse[key].length; i++) {
|
|
shepherd.log(`key ==>`, true);
|
|
shepherd.log(_parse[key][i], true);
|
|
shepherd.log(Number(_parse[key][i].value), true);
|
|
|
|
_total[key] += Number(_parse[key][i].value);
|
|
|
|
if (_parse[key][i].scriptPubKey &&
|
|
_parse[key][i].scriptPubKey.addresses &&
|
|
_parse[key][i].scriptPubKey.addresses[0] === targetAddress &&
|
|
_parse[key][i].value) {
|
|
_sum[key] += Number(_parse[key][i].value);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (_sum.inputs > 0 &&
|
|
_sum.outputs > 0) {
|
|
// vin + change, break into two tx
|
|
result = [{ // reorder since tx sort by default is from newest to oldest
|
|
type: 'sent',
|
|
amount: Number(_sum.inputs.toFixed(8)),
|
|
address: targetAddress,
|
|
timestamp: tx.timestamp,
|
|
txid: tx.format.txid,
|
|
confirmations: tx.confirmations,
|
|
}, {
|
|
type: 'received',
|
|
amount: Number(_sum.outputs.toFixed(8)),
|
|
address: targetAddress,
|
|
timestamp: tx.timestamp,
|
|
txid: tx.format.txid,
|
|
confirmations: tx.confirmations,
|
|
}];
|
|
|
|
if (network === 'komodo') { // calc claimed interest amount
|
|
const vinVoutDiff = _total.inputs - _total.outputs;
|
|
|
|
if (vinVoutDiff < 0) {
|
|
result[1].interest = Number(vinVoutDiff.toFixed(8));
|
|
}
|
|
}
|
|
} else if (_sum.inputs === 0 && _sum.outputs > 0) {
|
|
result = {
|
|
type: 'received',
|
|
amount: Number(_sum.outputs.toFixed(8)),
|
|
address: targetAddress,
|
|
timestamp: tx.timestamp,
|
|
txid: tx.format.txid,
|
|
confirmations: tx.confirmations,
|
|
};
|
|
} else if (_sum.inputs > 0 && _sum.outputs === 0) {
|
|
result = {
|
|
type: 'sent',
|
|
amount: Number(_sum.inputs.toFixed(8)),
|
|
address: targetAddress,
|
|
timestamp: tx.timestamp,
|
|
txid: tx.format.txid,
|
|
confirmations: tx.confirmations,
|
|
};
|
|
} else {
|
|
// (?)
|
|
result = {
|
|
type: 'other',
|
|
amount: 'unknown',
|
|
address: targetAddress,
|
|
timestamp: tx.timestamp,
|
|
txid: tx.format.txid,
|
|
confirmations: tx.confirmations,
|
|
};
|
|
}
|
|
|
|
shepherd.log(_sum, true);
|
|
shepherd.log(result, true);
|
|
|
|
return result;
|
|
}
|
|
|
|
shepherd.get('/electrum/decoderawtx', (req, res, next) => {
|
|
const _network = shepherd.getNetworkData(req.query.network);
|
|
const _rawtx = req.query.rawtx;
|
|
console.log(_network);
|
|
//const _rawtx = '010000006f2c395a02d81487fc7f9d1be3ea900316730133c044af70cd76d21e988e71de0e9e85918f010000006a47304402202097acd391e1d0eaaf91844bd596e918fb71320e3e0c51554acb71a39e4ee98b0220548fd61d4ae77a08d70b01bf5340983a1ba63f6b71ad71d478af77011f96fd510121031ffc010d8abc4180b4c1a13962bf9153a78082e7f2ac18f7d14cb6a6634ca218feffffff2b31f6c9a7916f7cf128cae94b3fc10e4c74ca3a740e1a7a6fd6624e4e9a5c8b010000006a473044022063f014c5fbaa7614732e0ae486179a854215fc32c02230e13f69b7e81fa000e50220236a2ba6373b1854aafc59c5391ab7505062067f3d293c016cbb5d252b35a56a012102f307f17d282fc0eabf99227c2e0f3122ae9ecd7da0de099f0c6007d4c941b57bfeffffff021b797ad7120000001976a914c7a7142d743b3e6eebe76923f43bae477d3ce31a88acff086d66000000001976a91463800ff36b9c52b2ffe5564af1c2a38df4f0126788ac16381d00';
|
|
const decodedTx = shepherd.electrumJSTxDecoder(_rawtx, req.query.network, _network);
|
|
|
|
shepherd.log('electrum decoderawtx input tx ==>', true);
|
|
|
|
if (req.query.parseonly ||
|
|
decodedTx.inputs[0].txid === '0000000000000000000000000000000000000000000000000000000000000000') {
|
|
const successObj = {
|
|
msg: 'success',
|
|
result: {
|
|
network: decodedTx.network,
|
|
format: decodedTx.format,
|
|
inputs: decodedTx.inputs,
|
|
outputs: decodedTx.outputs,
|
|
},
|
|
};
|
|
|
|
shepherd.log(successObj.result, true);
|
|
|
|
res.end(JSON.stringify(successObj));
|
|
} else {
|
|
const ecl = new shepherd.electrumJSCore(shepherd.electrumServers[req.query.network].port, shepherd.electrumServers[req.query.network].address, shepherd.electrumServers[req.query.network].proto); // tcp or tls
|
|
|
|
ecl.connect();
|
|
shepherd.log(decodedTx.inputs[0]);
|
|
shepherd.log(decodedTx.inputs[0].txid);
|
|
ecl.blockchainTransactionGet(decodedTx.inputs[0].txid)
|
|
.then((json) => {
|
|
ecl.close();
|
|
shepherd.log(json, true);
|
|
|
|
const decodedVin = shepherd.electrumJSTxDecoder(json, req.query.network, _network);
|
|
|
|
const successObj = {
|
|
msg: 'success',
|
|
result: {
|
|
network: decodedTx.network,
|
|
format: decodedTx.format,
|
|
inputs: decodedVin.outputs[decodedTx.inputs[0].n],
|
|
outputs: decodedTx.outputs,
|
|
},
|
|
};
|
|
|
|
res.end(JSON.stringify(successObj));
|
|
});
|
|
}
|
|
});
|
|
|
|
return shepherd;
|
|
};
|