mirror of https://github.com/lukechilds/Agama.git
pbca26
7 years ago
5 changed files with 1511 additions and 76 deletions
@ -0,0 +1,355 @@ |
|||
/* |
|||
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. |
|||
*/ |
|||
|
|||
const tls = require('tls'); |
|||
const net = require('net'); |
|||
const EventEmitter = require('events').EventEmitter; |
|||
|
|||
const makeRequest = function(method, params, id) { |
|||
return JSON.stringify({ |
|||
jsonrpc : '2.0', |
|||
method : method, |
|||
params : params, |
|||
id : id, |
|||
}); |
|||
} |
|||
|
|||
const createRecursiveParser = function(maxDepth, delimiter) { |
|||
const MAX_DEPTH = maxDepth; |
|||
const DELIMITER = delimiter; |
|||
const recursiveParser = function(n, buffer, callback) { |
|||
if (buffer.length === 0) { |
|||
return { |
|||
code: 0, |
|||
buffer: buffer, |
|||
}; |
|||
} |
|||
|
|||
if (n > MAX_DEPTH) { |
|||
return { |
|||
code: 1, |
|||
buffer: buffer, |
|||
}; |
|||
} |
|||
|
|||
const xs = buffer.split(DELIMITER); |
|||
|
|||
if (xs.length === 1) { |
|||
return { |
|||
code: 0, |
|||
buffer: buffer, |
|||
}; |
|||
} |
|||
|
|||
callback(xs.shift(), n); |
|||
|
|||
return recursiveParser(n + 1, xs.join(DELIMITER), callback); |
|||
} |
|||
|
|||
return recursiveParser; |
|||
} |
|||
|
|||
|
|||
const createPromiseResult = function(resolve, reject) { |
|||
return (err, result) => { |
|||
if (err) { |
|||
console.log('electrum error:'); |
|||
console.log(err); |
|||
reject(err); |
|||
} else { |
|||
resolve(result); |
|||
} |
|||
} |
|||
} |
|||
|
|||
class MessageParser { |
|||
constructor(callback) { |
|||
this.buffer = ''; |
|||
this.callback = callback; |
|||
this.recursiveParser = createRecursiveParser(20, '\n'); |
|||
} |
|||
|
|||
run(chunk) { |
|||
this.buffer += chunk; |
|||
|
|||
while (true) { |
|||
const res = this.recursiveParser(0, this.buffer, this.callback); |
|||
|
|||
this.buffer = res.buffer; |
|||
|
|||
if(res.code === 0) { |
|||
break; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
const util = { |
|||
makeRequest, |
|||
createRecursiveParser, |
|||
createPromiseResult, |
|||
MessageParser, |
|||
}; |
|||
|
|||
const getSocket = function(protocol, options) { |
|||
switch (protocol) { |
|||
case 'tcp': |
|||
return new net.Socket(); |
|||
case 'tls': |
|||
// todo
|
|||
case 'ssl': |
|||
return new tls.TLSSocket(options); |
|||
} |
|||
|
|||
throw new Error('unknown protocol'); |
|||
} |
|||
|
|||
const initSocket = function(self, protocol, options) { |
|||
const conn = getSocket(protocol, options); |
|||
|
|||
conn.setEncoding('utf8'); |
|||
conn.setKeepAlive(true, 0); |
|||
conn.setNoDelay(true); |
|||
conn.on('connect', () => { |
|||
self.onConnect(); |
|||
}); |
|||
conn.on('close', (e) => { |
|||
self.onClose(e); |
|||
}); |
|||
conn.on('data', (chunk) => { |
|||
self.onReceive(chunk); |
|||
}); |
|||
conn.on('end', (e) => { |
|||
self.onEnd(e); |
|||
}); |
|||
conn.on('error', (e) => { |
|||
self.onError(e); |
|||
}); |
|||
|
|||
return conn; |
|||
} |
|||
|
|||
class Client { |
|||
constructor(port, host, protocol = 'tcp', options = void 0) { |
|||
this.id = 0; |
|||
this.port = port; |
|||
this.host = host; |
|||
this.callbackMessageQueue = {}; |
|||
this.subscribe = new EventEmitter(); |
|||
this.conn = initSocket(this, protocol, options); |
|||
this.mp = new util.MessageParser((body, n) => { |
|||
this.onMessage(body, n); |
|||
}); |
|||
this.status = 0; |
|||
} |
|||
|
|||
connect() { |
|||
if (this.status) { |
|||
return Promise.resolve(); |
|||
} |
|||
|
|||
this.status = 1; |
|||
|
|||
return new Promise((resolve, reject) => { |
|||
const errorHandler = (e) => reject(e) |
|||
|
|||
this.conn.connect(this.port, this.host, () => { |
|||
this.conn.removeListener('error', errorHandler); |
|||
resolve(); |
|||
}); |
|||
this.conn.on('error', errorHandler); |
|||
}); |
|||
} |
|||
|
|||
close() { |
|||
if (!this.status) { |
|||
return |
|||
} |
|||
|
|||
this.conn.end(); |
|||
this.conn.destroy(); |
|||
this.status = 0; |
|||
} |
|||
|
|||
request(method, params) { |
|||
if (!this.status) { |
|||
return Promise.reject(new Error('ESOCKET')); |
|||
} |
|||
|
|||
return new Promise((resolve, reject) => { |
|||
const id = ++this.id; |
|||
const content = util.makeRequest(method, params, id); |
|||
|
|||
this.callbackMessageQueue[id] = util.createPromiseResult(resolve, reject); |
|||
this.conn.write(`${content}\n`); |
|||
}); |
|||
} |
|||
|
|||
response(msg) { |
|||
const callback = this.callbackMessageQueue[msg.id]; |
|||
|
|||
if (callback) { |
|||
delete this.callbackMessageQueue[msg.id]; |
|||
|
|||
if (msg.error) { |
|||
callback(msg.error); |
|||
} else { |
|||
callback(null, msg.result); |
|||
} |
|||
} else { |
|||
// can't get callback
|
|||
} |
|||
} |
|||
|
|||
onMessage(body, n) { |
|||
const msg = JSON.parse(body); |
|||
|
|||
if (msg instanceof Array) { |
|||
// don't support batch request
|
|||
} else { |
|||
if (msg.id !== void 0) { |
|||
this.response(msg); |
|||
} else { |
|||
this.subscribe.emit(msg.method, msg.params); |
|||
} |
|||
} |
|||
} |
|||
|
|||
onConnect() { |
|||
} |
|||
|
|||
onClose() { |
|||
Object.keys(this.callbackMessageQueue).forEach((key) => { |
|||
this.callbackMessageQueue[key](new Error('close connect')); |
|||
delete this.callbackMessageQueue[key]; |
|||
}); |
|||
} |
|||
|
|||
onReceive(chunk) { |
|||
this.mp.run(chunk); |
|||
} |
|||
|
|||
onEnd() { |
|||
} |
|||
|
|||
onError(e) { |
|||
} |
|||
} |
|||
|
|||
class ElectrumJSCore extends Client { |
|||
constructor(protocol, port, host, options) { |
|||
super(protocol, port, host, options); |
|||
} |
|||
|
|||
onClose() { |
|||
super.onClose(); |
|||
const list = [ |
|||
'server.peers.subscribe', |
|||
'blockchain.numblocks.subscribe', |
|||
'blockchain.headers.subscribe', |
|||
'blockchain.address.subscribe' |
|||
]; |
|||
|
|||
list.forEach(event => this.subscribe.removeAllListeners(event)); |
|||
} |
|||
|
|||
// ref: http://docs.electrum.org/en/latest/protocol.html
|
|||
serverVersion(client_name, protocol_version) { |
|||
return this.request('server.version', [client_name, protocol_version]); |
|||
} |
|||
|
|||
serverBanner() { |
|||
return this.request('server.banner', []); |
|||
} |
|||
|
|||
serverDonationAddress() { |
|||
return this.request('server.donation_address', []); |
|||
} |
|||
|
|||
serverPeersSubscribe() { |
|||
return this.request('server.peers.subscribe', []); |
|||
} |
|||
|
|||
blockchainAddressGetBalance(address) { |
|||
return this.request('blockchain.address.get_balance', [address]); |
|||
} |
|||
|
|||
blockchainAddressGetHistory(address) { |
|||
return this.request('blockchain.address.get_history', [address]); |
|||
} |
|||
|
|||
blockchainAddressGetMempool(address) { |
|||
return this.request('blockchain.address.get_mempool', [address]); |
|||
} |
|||
|
|||
blockchainAddressGetProof(address) { |
|||
return this.request('blockchain.address.get_proof', [address]); |
|||
} |
|||
|
|||
blockchainAddressListunspent(address) { |
|||
return this.request('blockchain.address.listunspent', [address]); |
|||
} |
|||
|
|||
blockchainBlockGetHeader(height) { |
|||
return this.request('blockchain.block.get_header', [height]); |
|||
} |
|||
|
|||
blockchainBlockGetChunk(index) { |
|||
return this.request('blockchain.block.get_chunk', [index]); |
|||
} |
|||
|
|||
blockchainEstimatefee(number) { |
|||
return this.request('blockchain.estimatefee', [number]); |
|||
} |
|||
|
|||
blockchainHeadersSubscribe() { |
|||
return this.request('blockchain.headers.subscribe', []); |
|||
} |
|||
|
|||
blockchainNumblocksSubscribe() { |
|||
return this.request('blockchain.numblocks.subscribe', []); |
|||
} |
|||
|
|||
blockchainRelayfee() { |
|||
return this.request('blockchain.relayfee', []); |
|||
} |
|||
|
|||
blockchainTransactionBroadcast(rawtx) { |
|||
return this.request('blockchain.transaction.broadcast', [rawtx]); |
|||
} |
|||
|
|||
blockchainTransactionGet(tx_hash, height) { |
|||
return this.request('blockchain.transaction.get', [tx_hash, height]); |
|||
} |
|||
|
|||
blockchainTransactionGetMerkle(tx_hash, height) { |
|||
return this.request('blockchain.transaction.get_merkle', [tx_hash, height]); |
|||
} |
|||
|
|||
blockchainUtxoGetAddress(tx_hash, index) { |
|||
return this.request('blockchain.utxo.get_address', [tx_hash, index]); |
|||
} |
|||
} |
|||
|
|||
module.exports = ElectrumJSCore; |
@ -0,0 +1,205 @@ |
|||
'use strict' |
|||
var bitcoin = require('bitcoinjs-lib'); |
|||
|
|||
var networks = exports; |
|||
Object.keys(bitcoin.networks).forEach(function(key){ |
|||
networks[key] = bitcoin.networks[key] |
|||
}); |
|||
|
|||
networks.litecoin = { |
|||
messagePrefix: '\x19Litecoin Signed Message:\n', |
|||
bip32: { |
|||
public: 0x019da462, |
|||
private: 0x019d9cfe |
|||
}, |
|||
pubKeyHash: 0x30, |
|||
scriptHash: 0x32, |
|||
wif: 0xb0, |
|||
dustThreshold: 0, // https://github.com/litecoin-project/litecoin/blob/v0.8.7.2/src/main.cpp#L360-L365
|
|||
} |
|||
|
|||
networks.dogecoin = { |
|||
messagePrefix: '\x19Dogecoin Signed Message:\n', |
|||
bip32: { |
|||
public: 0x02facafd, |
|||
private: 0x02fac398, |
|||
}, |
|||
pubKeyHash: 0x1e, |
|||
scriptHash: 0x16, |
|||
wif: 0x9e, |
|||
dustThreshold: 0 // https://github.com/dogecoin/dogecoin/blob/v1.7.1/src/core.h#L155-L160
|
|||
}; |
|||
|
|||
// https://github.com/monacoinproject/monacoin/blob/master-0.10/src/chainparams.cpp#L161
|
|||
networks.monacoin = { |
|||
messagePrefix: '\x19Monacoin Signed Message:\n', |
|||
bip32: { |
|||
public: 0x0488b21e, |
|||
private: 0x0488ade4, |
|||
}, |
|||
pubKeyHash: 0x32, |
|||
scriptHash: 0x05, |
|||
wif: 0xB2, |
|||
dustThreshold: 546, // https://github.com/bitcoin/bitcoin/blob/v0.9.2/src/core.h#L151-L162
|
|||
}; |
|||
|
|||
|
|||
// https://github.com/gamecredits-project/GameCredits/blob/master/src/chainparams.cpp#L136
|
|||
networks.game = { |
|||
messagePrefix: '\x19GameCredits Signed Message:\n', |
|||
bip32: { |
|||
public: 0x043587cf, |
|||
private: 0x04358394, |
|||
}, |
|||
pubKeyHash: 0x6f, |
|||
scriptHash: 0xc4, |
|||
wif: 0xef, |
|||
dustThreshold: 546, // https://github.com/bitcoin/bitcoin/blob/v0.9.2/src/core.h#L151-L162
|
|||
}; |
|||
|
|||
// https://github.com/dashpay/dash/blob/master/src/chainparams.cpp#L171
|
|||
networks.dash = { |
|||
messagePrefix: '\x19DarkCoin Signed Message:\n', |
|||
bip32: { |
|||
public: 0x02fe52f8, |
|||
private: 0x02fe52cc, |
|||
}, |
|||
pubKeyHash: 0x4c, |
|||
scriptHash: 0x10, |
|||
wif: 0xcc, |
|||
dustThreshold: 5460, // https://github.com/dashpay/dash/blob/v0.12.0.x/src/primitives/transaction.h#L144-L155
|
|||
}; |
|||
|
|||
// https://github.com/zcoinofficial/zcoin/blob/c93eccb39b07a6132cb3d787ac18be406b24c3fa/src/base58.h#L275
|
|||
networks.zcoin = { |
|||
messagePrefix: '\x19ZCoin Signed Message:\n', |
|||
bip32: { |
|||
public: 0x0488b21e, // todo
|
|||
private: 0x0488ade4, // todo
|
|||
}, |
|||
pubKeyHash: 0x52, |
|||
scriptHash: 0x07, |
|||
wif: 0x52 + 128, |
|||
dustThreshold: 1000, // https://github.com/zcoinofficial/zcoin/blob/f755f95a036eedfef7c96bcfb6769cb79278939f/src/main.h#L59
|
|||
}; |
|||
|
|||
// https://raw.githubusercontent.com/jl777/komodo/beta/src/chainparams.cpp
|
|||
networks.komodo = { |
|||
messagePrefix: '\x19Komodo Signed Message:\n', |
|||
bip32: { |
|||
public: 0x0488b21e, |
|||
private: 0x0488ade4, |
|||
}, |
|||
pubKeyHash: 0x3c, |
|||
scriptHash: 0x55, |
|||
wif: 0xbc, |
|||
dustThreshold: 1000, |
|||
}; |
|||
|
|||
networks.viacoin = { |
|||
messagePrefix: '\x19Viacoin Signed Message:\n', |
|||
bip32: { |
|||
public: 0x0488b21e, |
|||
private: 0x0488ade4, |
|||
}, |
|||
pubKeyHash: 0x47, |
|||
scriptHash: 0x21, |
|||
wif: 0xc7, |
|||
dustThreshold: 1000, |
|||
}; |
|||
|
|||
networks.vertcoin = { |
|||
messagePrefix: '\x19Vertcoin Signed Message:\n', |
|||
bip32: { |
|||
public: 0x0488b21e, |
|||
private: 0x0488ade4, |
|||
}, |
|||
pubKeyHash: 0x47, |
|||
scriptHash: 0x5, |
|||
wif: 0x80, |
|||
dustThreshold: 1000, |
|||
}; |
|||
|
|||
networks.namecoin = { |
|||
messagePrefix: '\x19Namecoin Signed Message:\n', |
|||
bip32: { |
|||
public: 0x0488b21e, |
|||
private: 0x0488ade4, |
|||
}, |
|||
pubKeyHash: 0x34, |
|||
scriptHash: 0xd, |
|||
wif: 0xb4, |
|||
dustThreshold: 1000, |
|||
}; |
|||
|
|||
networks.faircoin = { |
|||
messagePrefix: '\x19Faircoin Signed Message:\n', |
|||
bip32: { |
|||
public: 0x0488b21e, |
|||
private: 0x0488ade4, |
|||
}, |
|||
pubKeyHash: 0x5f, |
|||
scriptHash: 0x24, |
|||
wif: 0xdf, |
|||
dustThreshold: 1000, |
|||
}; |
|||
|
|||
networks.digibyte = { |
|||
messagePrefix: '\x19Digibyte Signed Message:\n', |
|||
bip32: { |
|||
public: 0x0488b21e, |
|||
private: 0x0488ade4, |
|||
}, |
|||
pubKeyHash: 0x1e, |
|||
scriptHash: 0x5, |
|||
wif: 0x80, |
|||
dustThreshold: 1000, |
|||
}; |
|||
|
|||
networks.crown = { |
|||
messagePrefix: '\x19Crown Signed Message:\n', |
|||
bip32: { |
|||
public: 0x0488b21e, |
|||
private: 0x0488ade4, |
|||
}, |
|||
pubKeyHash: 0x0, |
|||
scriptHash: 0x1c, |
|||
wif: 0x80, |
|||
dustThreshold: 1000, |
|||
}; |
|||
|
|||
networks.argentum = { |
|||
messagePrefix: '\x19Argentum Signed Message:\n', |
|||
bip32: { |
|||
public: 0x0488b21e, |
|||
private: 0x0488ade4, |
|||
}, |
|||
pubKeyHash: 0x17, |
|||
scriptHash: 0x5, |
|||
wif: 0x97, |
|||
dustThreshold: 1000, |
|||
}; |
|||
|
|||
networks.chips = { |
|||
messagePrefix: '\x19Chips Signed Message:\n', |
|||
bip32: { |
|||
public: 0x0488b21e, |
|||
private: 0x0488ade4, |
|||
}, |
|||
pubKeyHash: 0x3c, |
|||
scriptHash: 0x55, |
|||
wif: 0xbc, |
|||
dustThreshold: 1000, |
|||
}; |
|||
|
|||
/*networks.zcash = { |
|||
messagePrefix: '\x19Zcash Signed Message:\n', |
|||
bip32: { |
|||
public: 0x0488b21e, |
|||
private: 0x0488ade4, |
|||
}, |
|||
pubKeyHash: 0x1cb8, |
|||
scriptHash: 0x1cbd, |
|||
wif: 0x80, |
|||
dustThreshold: 1000, |
|||
};*/ |
@ -0,0 +1,110 @@ |
|||
/* |
|||
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'); |
|||
|
|||
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': |
|||
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) { |
|||
const _tx = bitcoin.Transaction.fromHex(rawtx); |
|||
|
|||
return { |
|||
tx: _tx, |
|||
network: network, |
|||
format: decodeFormat(_tx), |
|||
inputs: decodeInput(_tx), |
|||
outputs: decodeOutput(_tx, network), |
|||
}; |
|||
} |
|||
|
|||
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; |
|||
} |
File diff suppressed because one or more lines are too long
Loading…
Reference in new issue