diff --git a/README.md b/README.md index 5f1f1a3..e69de29 100644 --- a/README.md +++ b/README.md @@ -1,5 +0,0 @@ -# node-electrum-client - -## electrum protocol - -* http://docs.electrum.org/en/latest/protocol.html diff --git a/example/ex.js b/example/ex.js new file mode 100644 index 0000000..9ee12a8 --- /dev/null +++ b/example/ex.js @@ -0,0 +1,25 @@ +const Client = require("../lib/electrum_cli") + +const proc = async(cl) => { + try{ + const version = await cl.server_version("2.7.11", "1.0") + console.log(version) + const balance = await cl.blockchainAddress_getBalance("MS43dMzRKfEs99Q931zFECfUhdvtWmbsPt") + console.log(balance) + const utxo = await cl.blockchainAddress_listunspent("MS43dMzRKfEs99Q931zFECfUhdvtWmbsPt") + console.log(utxo) + }catch(e){ + console.log(e) + } +} + +const main = async(port, host) => { + const cl = new Client(port, host); + await cl.connect() + for(let i = 0; i<100; ++i){ + await proc(cl) + } + await cl.close() +} + +main(4444, "localhost") diff --git a/index.js b/index.js new file mode 100644 index 0000000..e1250b0 --- /dev/null +++ b/index.js @@ -0,0 +1 @@ +module.exports = require('./lib/client'); diff --git a/lib/client.js b/lib/client.js new file mode 100644 index 0000000..7334d65 --- /dev/null +++ b/lib/client.js @@ -0,0 +1,92 @@ +'use strict' +const net = require('net'); +const util = require('./util'); + +class MessageParser{ + constructor(callback){ + this.buffer = '' + this.callback = callback + } + run(chunk){ + this.buffer += chunk + while(true){ + const res = util.recursiveParser(0, this.buffer, this.callback) + this.buffer = res.buffer + if(res.code === 0){ + break; + } + } + } +} + +class Client{ + constructor(port, host){ + this.port = port + this.host = host + this.hostname = [host, port].join(':') + this.callback_message_queue = {} + this.id = 0; + + this.mp = new MessageParser((body, n) => { + this.onMessageRecv(JSON.parse(body)); + }); + + const conn = this.conn = new net.Socket() + conn.setEncoding('utf8') + conn.setKeepAlive(true, 0) + conn.setNoDelay(true) + conn.on('connect', () => { + }) + conn.on('close', () => { + Object.keys(this.callback_message_queue).forEach((key) => { + this.callback_message_queue[key](new Error('close connect')) + delete this.callback_message_queue[key] + }) + }) + conn.on('data', (chunk) => { + this.mp.run(chunk) + }) + conn.on('end', () => { + }) + } + + connect(){ + return new Promise((resolve, reject) => { + this.conn.connect(this.port, this.host, () => { + resolve() + }) + }) + } + + close(){ + this.conn.end(); + this.conn.destroy(); + } + + onMessageRecv(msg){ + if(msg instanceof Array){ + ; // don't support batch request + } else { + const callback = this.callback_message_queue[msg.id] + if(callback){ + delete this.callback_message_queue[msg.id] + if(msg.error){ + callback(msg.error) + }else{ + callback(null, msg.result) + } + } + } + } + + request(method, params){ + return new Promise((resolve, reject) => { + const id = ++this.id; + const content = util.makeRequest(method, params, id); + this.callback_message_queue[id] = util.createPromiseResult(resolve, reject); + this.conn.write(content + '\n'); + }) + } +} + +module.exports = Client diff --git a/lib/electrum_cli.js b/lib/electrum_cli.js new file mode 100644 index 0000000..f39f78e --- /dev/null +++ b/lib/electrum_cli.js @@ -0,0 +1,65 @@ +const Client = require("./client") +class ElectrumCli extends Client{ + constructor(port, host){ + super(port, host) + } + server_version(client_name, protocol_version){ + return this.request('server.version', [client_name, protocol_version]) + } + server_banner(){ + return this.request('server.banner', []) + } + serverDonation_address(){ + return this.request('server.donation_address', []) + } + serverPeers_subscribe(){ + return this.request('server.peers.subscribe', []) + } + blockchainAddress_getBalance(address){ + return this.request('blockchain.address.get_balance', [address]) + } + blockchainAddress_getHistory(address){ + return this.request('blockchain.address.get_history', [address]) + } + blockchainAddress_getMempool(address){ + return this.request('blockchain.address.get_mempool', [address]) + } + blockchainAddress_getProof(address){ + return this.request('blockchain.address.get_proof', [address]) + } + blockchainAddress_listunspent(address){ + return this.request('blockchain.address.listunspent', [address]) + } + blockchainBlock_getHeader(height){ + return this.request('blockchain.block.get_header', [height]) + } + blockchainBlock_getChunk(index){ + return this.request('blockchain.block.get_chunk', [index]) + } + blockchainEstimatefee(number){ + return this.request('blockchain.estimatefee', [number]) + } + blockchainHeaders_subscribe(){ + return this.request('blockchain.headers.subscribe', []) + } + blockchainNumblocks_subscribe(){ + return this.request('blockchain.numblocks.subscribe', []) + } + blockchain_relayfee(){ + return this.request('blockchain.relayfee', []) + } + blockchainTransaction_broadcast(rawtx){ + return this.request('blockchain.transaction.broadcast', [rawtx]) + } + blockchainTransaction_get(tx_hash, height){ + return this.request('blockchain.transaction.get', [tx_hash, height]) + } + blockchainTransaction_getMerkle(tx_hash, height){ + return this.request('blockchain.transaction.get_merkle', [tx_hash, height]) + } + blockchainUtxo_getAddress(tx_hash, index){ + return this.request('blockchain.utxo.get_address', [tx_hash, index]) + } +} + +module.exports = ElectrumCli diff --git a/lib/util.js b/lib/util.js new file mode 100644 index 0000000..cac3b83 --- /dev/null +++ b/lib/util.js @@ -0,0 +1,36 @@ +'use strict' + +const makeRequest = exports.makeRequest = (method, params, id) => { + return JSON.stringify({ + jsonrpc : "2.0", + method : method, + params : params, + id : id, + }) +} + +const recursiveParser = exports.recursiveParser = (n, buffer, callback) => { + const MAX_DEPTH = 20; + if(buffer.length === 0) { + return {code:0, buffer:buffer} + } + if(n > MAX_DEPTH) { + return {code:1, buffer:buffer} + } + const xs = buffer.split('\n') + if(xs.length === 1){ + return {code:0, buffer:buffer} + } + const content = xs.shift() + + callback(content, n) + return recursiveParser(n + 1, xs.join('\n'), callback) +} + +const createPromiseResult = exports.createPromiseResult = (resolve, reject) => { + return (err, result) => { + if(err) reject(err) + else resolve(result) + } +} + diff --git a/package.json b/package.json index 487dda7..48b7727 100644 --- a/package.json +++ b/package.json @@ -1,22 +1,13 @@ { "name": "electrum-client", - "version": "1.0.0", - "description": "## electrum protocol", + "version": "0.0.1", + "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, - "repository": { - "type": "git", - "url": "git+https://github.com/you21979/node-electrum-client.git" - }, "dependencies": { - "stratum": "" }, - "author": "", - "license": "MIT", - "bugs": { - "url": "https://github.com/you21979/node-electrum-client/issues" - }, - "homepage": "https://github.com/you21979/node-electrum-client#readme" + "author": "Yuki Akiyama", + "license": "MIT" }