├── .github └── FUNDING.yml ├── .gitignore ├── LICENSE ├── ReadMe.md ├── examples.js ├── lib └── index.js └── package.json /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | # GitHub Sponsor 4 | github: BitDesert 5 | 6 | # donate NANO 7 | custom: https://mynano.ninja/account/my-nano-ninja/send 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.log 3 | *.swp -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright © 2017 BitDesert 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the “Software”), to deal in 7 | the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is furnished 10 | to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 18 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 19 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 20 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 21 | DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /ReadMe.md: -------------------------------------------------------------------------------- 1 | # 🔗 Nano Node RPC Client 2 | 3 | [![npm version](https://nodei.co/npm/nano-node-rpc.png)](https://www.npmjs.com/package/nano-node-rpc) 4 | 5 | Nano RPC client written in Javascript with no external dependencies. 6 | It produces JSON objects or strings as output, wrapped in native promises. 7 | 8 | All RPC calls are defined in the [Nano.org Docs](https://docs.nano.org/commands/rpc-protocol/). 9 | 10 | ## Getting Started 11 | 12 | ### Install 13 | 14 | `npm install nano-node-rpc` 15 | 16 | #### My Nano Ninja Node API 17 | 18 | ```js 19 | const NanoClient = require('nano-node-rpc'); 20 | const client = new NanoClient({apiKey: process.env.NINJA_API_KEY}) 21 | ``` 22 | 23 | #### Your own Nano RPC server 24 | 25 | ```js 26 | const NanoClient = require('nano-node-rpc'); 27 | const client = new NanoClient({url: 'http://localhost:7076'}) 28 | ``` 29 | 30 | ### Use methods attached to `client` to send RPC calls 31 | 32 | #### Examples 33 | 34 | Head to the [`examples.js`](examples.js) file for even more! 35 | 36 | ```js 37 | const client = NanoClient({url: 'http://localhost:7076'}) 38 | 39 | // Some methods do not require arguments: 40 | client 41 | .block_count() 42 | .then(count => { 43 | console.log(count); 44 | /** 45 | * { 46 | * "count": "1826834", 47 | * "unchecked": "3385205" 48 | * } 49 | */ 50 | }) 51 | .catch(e => { 52 | // Deal with your errors here. 53 | }); 54 | 55 | // Some methods require arguments: 56 | client 57 | .account_balance("nano_1ninja7rh37ehfp9utkor5ixmxyg8kme8fnzc4zty145ibch8kf5jwpnzr3r") 58 | .then(balance => { 59 | console.log(balance); 60 | /** 61 | * { 62 | * "balance": "325586539664609129644855132177", 63 | * "pending": "2309370929000000000000000000000000" 64 | * } 65 | */ 66 | }) 67 | .catch(e => { 68 | // Deal with your errors here. 69 | }); 70 | ``` 71 | 72 | ### Promise-wrapped responses 73 | 74 | All method calls return native NodeJS promises. You need to use the 75 | `then()` / `catch()` pattern shown above. If the call was succesful, 76 | the data will be passed to `then()`, otherwise the error will be passed 77 | to `catch()`. 78 | 79 | ### Methods Names 80 | 81 | The method calls are the same as the original RPC actions defined 82 | on the [Nano.org Docs](https://docs.nano.org/commands/rpc-protocol/). 83 | 84 | Example1: on the Nano wiki `account_balance` is called with `account`. 85 | For the NodeJS client, the method is `account_balance` and the argument is the account string. 86 | 87 | If a method is not available with a method you can use the `_send` method like this: 88 | 89 | ```js 90 | client._send('block_info', { 91 | "json_block": true, 92 | "hash": "87434F8041869A01C8F6F263B87972D7BA443A72E0A97D7A3FD0CCC2358FD6F9" 93 | }).then(block_info => { 94 | console.log(block_info); 95 | /** 96 | * { 97 | * "block_account": "nano_1ipx847tk8o46pwxt5qjdbncjqcbwcc1rrmqnkztrfjy5k7z4imsrata9est", 98 | * "amount": "30000000000000000000000000000000000", 99 | * "balance": "5606157000000000000000000000000000000", 100 | * "height": "58", 101 | * "local_timestamp": "0", 102 | * "confirmed": "true", 103 | * "contents": { 104 | * ... 105 | * }, 106 | * "subtype": "send" 107 | * } 108 | */ 109 | }) 110 | .catch(e => { 111 | // Deal with your errors here. 112 | }); 113 | ``` 114 | -------------------------------------------------------------------------------- /examples.js: -------------------------------------------------------------------------------- 1 | const NanoClient = require("./lib"); 2 | 3 | // with your own node 4 | const client = new NanoClient({ 5 | url: 'http://localhost:7076' 6 | }); 7 | 8 | // with https://mynano.ninja/api 9 | /* 10 | const client = new NanoClient({ 11 | apiKey: 'yourapikey' 12 | }); 13 | */ 14 | 15 | async function queryDemo() { 16 | try { 17 | // query the account balance 18 | var account = await client.account_balance('nano_1ninja7rh37ehfp9utkor5ixmxyg8kme8fnzc4zty145ibch8kf5jwpnzr3r'); 19 | 20 | // query the current block count 21 | var count = await client.block_count(); 22 | 23 | // custom RPC command (e.g. block_info) 24 | var block_info = await client._send('block_info', { 25 | "json_block": true, 26 | "hash": "87434F8041869A01C8F6F263B87972D7BA443A72E0A97D7A3FD0CCC2358FD6F9" 27 | }); 28 | 29 | } catch (error) { 30 | console.error("\nError: " + error.message); 31 | return 32 | } 33 | 34 | console.log("Account balance:", account); 35 | console.log("Block count:", count); 36 | console.log("Block info:", block_info.block_account); 37 | } 38 | 39 | queryDemo(); -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | const net = require("net"); 2 | const { 3 | URL 4 | } = require("url"); 5 | const http = require("http"); 6 | const https = require("https"); 7 | 8 | /** 9 | * @class NanoClient 10 | * @description An RPC Client for RaiBlocks. The official RPC API is here: 11 | * https://github.com/clemahieu/raiblocks/wiki/RPC-protocol 12 | */ 13 | class NanoClient { 14 | 15 | /* 16 | * @function constructor 17 | * @description Build an instance of `NanoClient` 18 | * @param {Object} options - The options with either the node URL or API key 19 | */ 20 | constructor(options) { 21 | if (options.url) { 22 | this.nodeAddress = options.url; 23 | } else if (options.apiKey) { 24 | this.nodeAddress = "https://mynano.ninja/api/node"; 25 | this.headers = { 26 | 'Authorization': options.apiKey, 27 | 'content-type': 'application/json' 28 | } 29 | } 30 | } 31 | 32 | /** 33 | * @function _send 34 | * @private 35 | * @description Send the request to the daemon 36 | * @param {string} method - the name of the RPC method 37 | * @param {Object|Array} params - Parameters to be passed to the RPC method 38 | * @return {Promise} - A Promise which is resolved if the request succesfully 39 | * fetch the data, and rejected otherwise. Failure can happen 40 | * either because of a problem of the request, or before the 41 | * request happen, when `JSON.stringify` fails 42 | */ 43 | _send(method, params = undefined) { 44 | return new Promise((resolve, reject) => { 45 | var req = {}; 46 | try { 47 | req = this._buildRPCReq(method, params); 48 | } catch (err) { 49 | return reject(err); 50 | } 51 | 52 | // Take HTTPS urls in consideration. 53 | const url = new URL(req.url); 54 | const lib = url.protocol === "https:" ? https : http; 55 | const requestOptions = { 56 | hostname: url.hostname, 57 | port: url.port, 58 | method: "POST", 59 | path: url.pathname, 60 | headers: this.headers 61 | }; 62 | 63 | const request = lib.request(requestOptions, response => { 64 | if (response.statusCode < 200 || response.statusCode > 299) { 65 | reject( 66 | new Error( 67 | "Failed to fetch URL. Status code: " + response.statusCode 68 | ) 69 | ); 70 | } 71 | 72 | const body = []; 73 | 74 | response.on("data", chunk => { 75 | body.push(chunk); 76 | }); 77 | 78 | response.on("end", () => { 79 | const data = body.join(""); 80 | 81 | try { 82 | return resolve(JSON.parse(data)); 83 | } catch (e) { 84 | reject(e); 85 | } 86 | }); 87 | }); 88 | 89 | request.on("error", e => { 90 | reject(e); 91 | }); 92 | 93 | request.write(req.body); 94 | request.end(); 95 | }); 96 | } 97 | 98 | /** 99 | * @function _buildRPCReq 100 | * @private 101 | * @description Create an RPC request object to be later used by `#_send`. 102 | * @param {string} action - A given RPC action. 103 | * @param {Object|Array} params - Parameters to be passed to the RPC daemon 104 | * @return {Object} Returns an object containing the request (url, body). 105 | */ 106 | _buildRPCReq(action, params) { 107 | const req = {}; 108 | const payload = null; 109 | 110 | req.url = this.nodeAddress; 111 | 112 | try { 113 | if (typeof params === "undefined") { 114 | req.body = JSON.stringify({ 115 | action: action 116 | }); 117 | } else { 118 | req.body = JSON.stringify({ 119 | action: action, 120 | ...params 121 | }); 122 | } 123 | return req; 124 | } catch (e) { 125 | throw new Error(e); 126 | } 127 | } 128 | 129 | /** 130 | * Returns how many RAW is owned and how many have not yet been received by account. 131 | * 132 | * @param {string} account - The XRB account address. 133 | */ 134 | account_balance(account) { 135 | return this._send("account_balance", { 136 | account 137 | }); 138 | } 139 | 140 | /** 141 | * Get number of blocks for a specific account 142 | * @param {string} account - The XRB account address. 143 | */ 144 | account_block_count(account) { 145 | return this._send("account_block_count", { 146 | account 147 | }); 148 | } 149 | 150 | /** 151 | * Returns frontier, open block, change representative block, balance, 152 | * last modified timestamp from local database & block count for account 153 | * @param {string} account - The XRB account address. 154 | * @param {boolean} representative - Additionally returns representative for account (v8.1+) 155 | * @param {boolean} weight - Additionally returns voting weight for account (v8.1+) 156 | * @param {boolean} pending - Additionally returns pending balance for account (v8.1+) 157 | */ 158 | account_info( 159 | account, 160 | representative = false, 161 | weight = false, 162 | pending = false 163 | ) { 164 | return this._send("account_info", { 165 | account, 166 | representative, 167 | weight, 168 | pending 169 | }); 170 | } 171 | 172 | /** 173 | * Get account number for the public key 174 | * @param {string} key - An XRB public key. 175 | */ 176 | account_get(key) { 177 | return this._send("account_get", { 178 | key 179 | }); 180 | } 181 | 182 | /** 183 | * Reports send/receive information for a account 184 | * @param {string} account - The XRB account address. 185 | * @param {Number} count - Response length (default 1) 186 | */ 187 | account_history(account, count = 1) { 188 | return this._send("account_history", { 189 | account, 190 | count 191 | }); 192 | } 193 | 194 | /** 195 | * Get the public key for account 196 | * @param {string} account - AAn XRB account. 197 | */ 198 | account_key(account) { 199 | return this._send("account_key", { 200 | account 201 | }); 202 | } 203 | 204 | /** 205 | * Returns the representative for account 206 | * @param {string} account - The XRB account address. 207 | */ 208 | account_representative(account) { 209 | return this._send("account_representative", { 210 | account 211 | }); 212 | } 213 | 214 | /** 215 | * Returns the voting weight for account 216 | * @param {string} account - The XRB account address. 217 | */ 218 | account_weight(account) { 219 | return this._send("account_weight", { 220 | account 221 | }); 222 | } 223 | 224 | /** 225 | * Returns how many rai are in the public supply 226 | */ 227 | available_supply() { 228 | return this._send("available_supply"); 229 | } 230 | 231 | /** 232 | * Retrieves a json representation of block 233 | * @param {string} hash - A block hash. 234 | */ 235 | block(hash) { 236 | return this._send("block", { 237 | hash 238 | }); 239 | } 240 | 241 | /** 242 | * Retrieves a json representations of blocks 243 | * @param {Array} hashes - A list of block hashes. 244 | */ 245 | blocks(hashes) { 246 | return this._send("blocks", { 247 | hashes 248 | }); 249 | } 250 | 251 | /** 252 | * Retrieves a json representations of blocks with transaction amount & block account 253 | * @param {Array} hashes - A list of block hashes. 254 | */ 255 | blocks_info(hashes, source = false, pending = false) { 256 | return this._send("blocks_info", { 257 | hashes, 258 | source, 259 | pending 260 | }); 261 | } 262 | 263 | /** 264 | * Returns the account containing block 265 | * @param {string} hash - A block hash. 266 | */ 267 | block_account(hash) { 268 | return this._send("block_account", { 269 | hash 270 | }); 271 | } 272 | 273 | /** 274 | * Reports the number of blocks in the ledger and unchecked synchronizing blocks 275 | */ 276 | block_count() { 277 | return this._send("block_count"); 278 | } 279 | 280 | /** 281 | * Reports the number of blocks in the ledger by type (send, receive, open, change) 282 | */ 283 | block_count_type() { 284 | return this._send("block_count_type"); 285 | } 286 | 287 | /** 288 | * Returns a list of block hashes in the account chain starting at block up to count 289 | * @param {string} block - A block hash. 290 | * @param {Number} count - Max count of items to return. 291 | */ 292 | chain(block, count = 1) { 293 | return this._send("chain", { 294 | block, 295 | count 296 | }); 297 | } 298 | 299 | /** 300 | * Returns a list of pairs of account and block hash representing the head block starting at account up to count 301 | * @param {string} account - The XRB account address. 302 | * @param {Number} count - How much items to get from the list. (defaults to 1) 303 | */ 304 | frontiers(account, count = 1) { 305 | return this._send("frontiers", { 306 | account, 307 | count 308 | }); 309 | } 310 | 311 | /** 312 | * Reports the number of accounts in the ledger 313 | */ 314 | frontiers_count() { 315 | return this._send("frontiers_count"); 316 | } 317 | 318 | /** 319 | * Reports send/receive information for a chain of blocks 320 | * @param {string} hash - A block hash. 321 | * @param {Number} count - How much items to get from the list. (defaults to 1) 322 | */ 323 | history(hash, count = 1) { 324 | return this._send("history", { 325 | hash, 326 | count 327 | }); 328 | } 329 | 330 | /** 331 | * Divide a raw amount down by the Mrai ratio. 332 | * @param {string} amount - An amount to be converted. 333 | */ 334 | mrai_from_raw(amount) { 335 | return this._send("mrai_from_raw", { 336 | amount 337 | }); 338 | } 339 | 340 | /** 341 | * Multiply an Mrai amount by the Mrai ratio. 342 | * @param {string} amount - An amount to be converted. 343 | */ 344 | mrai_to_raw(amount) { 345 | return this._send("mrai_to_raw", { 346 | amount 347 | }); 348 | } 349 | 350 | /** 351 | * Divide a raw amount down by the krai ratio. 352 | * @param {string} amount - An amount to be converted. 353 | */ 354 | krai_from_raw(amount) { 355 | return this._send("krai_from_raw", { 356 | amount 357 | }); 358 | } 359 | 360 | /** 361 | * Multiply an krai amount by the krai ratio. 362 | * @param {string} amount - An amount to be converted. 363 | */ 364 | krai_to_raw(amount) { 365 | return this._send("krai_to_raw", { 366 | amount 367 | }); 368 | } 369 | 370 | /** 371 | * Divide a raw amount down by the rai ratio. 372 | * @param {string} amount - An amount to be converted. 373 | */ 374 | rai_from_raw(amount) { 375 | return this._send("rai_from_raw", { 376 | amount 377 | }); 378 | } 379 | 380 | /** 381 | * Multiply an rai amount by the rai ratio. 382 | * @param {string} amount - An amount to be converted. 383 | */ 384 | rai_to_raw(amount) { 385 | return this._send("rai_to_raw", { 386 | amount 387 | }); 388 | } 389 | 390 | /** 391 | * Returns frontier, open block, change representative block, balance, 392 | * last modified timestamp from local database & block count starting at account up to count 393 | * @enable_control required, version 8.1+ 394 | * 395 | * @param {string} account - The XRB account address. 396 | * @param {Number} count - Defines from where results are returned. 397 | * @param {boolean} representative - Additionally returns representative for each account. 398 | * @param {boolean} weight - Additionally returns voting weight for each account. 399 | * @param {boolean} pending - Additionally returns pending balance for each account. 400 | * @param {boolean} sorting - Sort the results by DESC. 401 | */ 402 | ledger( 403 | account, 404 | count = 1, 405 | representative = false, 406 | weight = false, 407 | pending = false, 408 | sorting = false 409 | ) { 410 | return this._send("ledger", { 411 | account, 412 | count, 413 | representative, 414 | weight, 415 | pending, 416 | sorting 417 | }); 418 | } 419 | 420 | /** 421 | * Creates a json representations of new block based on input data & signed with private key or account in wallet 422 | * @enable_control required, version 8.1+ 423 | * 424 | * @param {string} type - The block type. 425 | * @param {string} key - The block signing key. 426 | * @param {string} account - An XRB account. 427 | * @param {string} representative - An XRB representative account. 428 | * @param {string} source - A block source. 429 | */ 430 | block_create(type, key, account, representative, source) { 431 | return this._send("block_create", { 432 | type, 433 | key, 434 | account, 435 | representative, 436 | source 437 | }); 438 | } 439 | 440 | /** 441 | * Publish block to the network. 442 | * @param {Object} block - A block to process. Format: 443 | * https://github.com/clemahieu/raiblocks/wiki/RPC-protocol#process-block 444 | */ 445 | process(block) { 446 | return this._send("process", { 447 | block 448 | }); 449 | } 450 | 451 | /** 452 | * Returns a list of pairs of representative and its voting weight 453 | * @param {Number} count - Count of items to return. (Defaults to 1) 454 | * @param {boolean} sorting - Sort the returned results by DESC. 455 | */ 456 | representatives(count = 1, sorting = false) { 457 | return this._send("representatives"); 458 | } 459 | } 460 | 461 | module.exports = NanoClient; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nano-node-rpc", 3 | "version": "0.3.1", 4 | "description": "A NodeJS client for the Nano' RPC API", 5 | "main": "lib/index.js", 6 | "directories": { 7 | "lib": "lib" 8 | }, 9 | "scripts": { 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git+https://github.com/BitDesert/nano-node-rpc.git" 14 | }, 15 | "keywords": ["nano", "crypto", "json", "rpc", "api", "client"], 16 | "author": "BitDesert ", 17 | "license": "MIT", 18 | "bugs": { 19 | "url": "https://github.com/BitDesert/nano-node-rpc/issues" 20 | }, 21 | "homepage": "https://github.com/BitDesert/nano-node-rpc#readme", 22 | "devDependencies": {}, 23 | "dependencies": {} 24 | } 25 | --------------------------------------------------------------------------------