Source: wallet/rpc.js

  1. /*!
  2. * rpc.js - bitcoind-compatible json rpc for bcoin.
  3. * Copyright (c) 2014-2017, Christopher Jeffrey (MIT License).
  4. * https://github.com/bcoin-org/bcoin
  5. */
  6. 'use strict';
  7. const assert = require('bsert');
  8. const {format} = require('util');
  9. const bweb = require('bweb');
  10. const {Lock} = require('bmutex');
  11. const fs = require('bfile');
  12. const Validator = require('bval');
  13. const {BufferMap, BufferSet} = require('buffer-map');
  14. const util = require('../utils/util');
  15. const messageUtil = require('../utils/message');
  16. const Amount = require('../btc/amount');
  17. const Script = require('../script/script');
  18. const Address = require('../primitives/address');
  19. const KeyRing = require('../primitives/keyring');
  20. const MerkleBlock = require('../primitives/merkleblock');
  21. const MTX = require('../primitives/mtx');
  22. const Outpoint = require('../primitives/outpoint');
  23. const Output = require('../primitives/output');
  24. const TX = require('../primitives/tx');
  25. const consensus = require('../protocol/consensus');
  26. const pkg = require('../pkg');
  27. const common = require('./common');
  28. const {BlockMeta} = require('./records');
  29. const RPCBase = bweb.RPC;
  30. const RPCError = bweb.RPCError;
  31. /*
  32. * Constants
  33. */
  34. const errs = {
  35. // Standard JSON-RPC 2.0 errors
  36. INVALID_REQUEST: bweb.errors.INVALID_REQUEST,
  37. METHOD_NOT_FOUND: bweb.errors.METHOD_NOT_FOUND,
  38. INVALID_PARAMS: bweb.errors.INVALID_PARAMS,
  39. INTERNAL_ERROR: bweb.errors.INTERNAL_ERROR,
  40. PARSE_ERROR: bweb.errors.PARSE_ERROR,
  41. // General application defined errors
  42. MISC_ERROR: -1,
  43. FORBIDDEN_BY_SAFE_MODE: -2,
  44. TYPE_ERROR: -3,
  45. INVALID_ADDRESS_OR_KEY: -5,
  46. OUT_OF_MEMORY: -7,
  47. INVALID_PARAMETER: -8,
  48. DATABASE_ERROR: -20,
  49. DESERIALIZATION_ERROR: -22,
  50. VERIFY_ERROR: -25,
  51. VERIFY_REJECTED: -26,
  52. VERIFY_ALREADY_IN_CHAIN: -27,
  53. IN_WARMUP: -28,
  54. // Wallet errors
  55. WALLET_ERROR: -4,
  56. WALLET_INSUFFICIENT_FUNDS: -6,
  57. WALLET_INVALID_ACCOUNT_NAME: -11,
  58. WALLET_KEYPOOL_RAN_OUT: -12,
  59. WALLET_UNLOCK_NEEDED: -13,
  60. WALLET_PASSPHRASE_INCORRECT: -14,
  61. WALLET_WRONG_ENC_STATE: -15,
  62. WALLET_ENCRYPTION_FAILED: -16,
  63. WALLET_ALREADY_UNLOCKED: -17
  64. };
  65. /**
  66. * Wallet RPC
  67. * @alias module:wallet.RPC
  68. * @extends bweb.RPC
  69. */
  70. class RPC extends RPCBase {
  71. /**
  72. * Create an RPC.
  73. * @param {WalletDB} wdb
  74. */
  75. constructor(node) {
  76. super();
  77. assert(node, 'RPC requires a WalletDB.');
  78. this.wdb = node.wdb;
  79. this.network = node.network;
  80. this.logger = node.logger.context('wallet-rpc');
  81. this.client = node.client;
  82. this.locker = new Lock();
  83. this.wallet = null;
  84. this.init();
  85. }
  86. getCode(err) {
  87. switch (err.type) {
  88. case 'RPCError':
  89. return err.code;
  90. case 'ValidationError':
  91. return errs.TYPE_ERROR;
  92. case 'EncodingError':
  93. return errs.DESERIALIZATION_ERROR;
  94. case 'FundingError':
  95. return errs.WALLET_INSUFFICIENT_FUNDS;
  96. default:
  97. return errs.INTERNAL_ERROR;
  98. }
  99. }
  100. handleCall(cmd, query) {
  101. this.logger.debug('Handling RPC call: %s.', cmd.method);
  102. }
  103. init() {
  104. this.add('help', this.help);
  105. this.add('stop', this.stop);
  106. this.add('fundrawtransaction', this.fundRawTransaction);
  107. this.add('resendwallettransactions', this.resendWalletTransactions);
  108. this.add('abandontransaction', this.abandonTransaction);
  109. this.add('addmultisigaddress', this.addMultisigAddress);
  110. this.add('addwitnessaddress', this.addWitnessAddress);
  111. this.add('backupwallet', this.backupWallet);
  112. this.add('dumpprivkey', this.dumpPrivKey);
  113. this.add('dumpwallet', this.dumpWallet);
  114. this.add('encryptwallet', this.encryptWallet);
  115. this.add('getaddressinfo', this.getAddressInfo);
  116. this.add('getaccountaddress', this.getAccountAddress);
  117. this.add('getaccount', this.getAccount);
  118. this.add('getaddressesbyaccount', this.getAddressesByAccount);
  119. this.add('getbalance', this.getBalance);
  120. this.add('getnewaddress', this.getNewAddress);
  121. this.add('getrawchangeaddress', this.getRawChangeAddress);
  122. this.add('getreceivedbyaccount', this.getReceivedByAccount);
  123. this.add('getreceivedbyaddress', this.getReceivedByAddress);
  124. this.add('gettransaction', this.getTransaction);
  125. this.add('getunconfirmedbalance', this.getUnconfirmedBalance);
  126. this.add('getwalletinfo', this.getWalletInfo);
  127. this.add('importprivkey', this.importPrivKey);
  128. this.add('importwallet', this.importWallet);
  129. this.add('importaddress', this.importAddress);
  130. this.add('importprunedfunds', this.importPrunedFunds);
  131. this.add('importpubkey', this.importPubkey);
  132. this.add('keypoolrefill', this.keyPoolRefill);
  133. this.add('listaccounts', this.listAccounts);
  134. this.add('listaddressgroupings', this.listAddressGroupings);
  135. this.add('listlockunspent', this.listLockUnspent);
  136. this.add('listreceivedbyaccount', this.listReceivedByAccount);
  137. this.add('listreceivedbyaddress', this.listReceivedByAddress);
  138. this.add('listsinceblock', this.listSinceBlock);
  139. this.add('listtransactions', this.listTransactions);
  140. this.add('listunspent', this.listUnspent);
  141. this.add('lockunspent', this.lockUnspent);
  142. this.add('move', this.move);
  143. this.add('sendfrom', this.sendFrom);
  144. this.add('sendmany', this.sendMany);
  145. this.add('sendtoaddress', this.sendToAddress);
  146. this.add('setaccount', this.setAccount);
  147. this.add('settxfee', this.setTXFee);
  148. this.add('signmessage', this.signMessage);
  149. this.add('walletlock', this.walletLock);
  150. this.add('walletpassphrasechange', this.walletPassphraseChange);
  151. this.add('walletpassphrase', this.walletPassphrase);
  152. this.add('removeprunedfunds', this.removePrunedFunds);
  153. this.add('selectwallet', this.selectWallet);
  154. this.add('getmemoryinfo', this.getMemoryInfo);
  155. this.add('setloglevel', this.setLogLevel);
  156. }
  157. async help(args, _help) {
  158. if (args.length === 0)
  159. return `Select a command:\n${Object.keys(this.calls).join('\n')}`;
  160. const json = {
  161. method: args[0],
  162. params: []
  163. };
  164. return await this.execute(json, true);
  165. }
  166. async stop(args, help) {
  167. if (help || args.length !== 0)
  168. throw new RPCError(errs.MISC_ERROR, 'stop');
  169. this.wdb.close();
  170. return 'Stopping.';
  171. }
  172. async fundRawTransaction(args, help) {
  173. if (help || args.length < 1 || args.length > 2) {
  174. throw new RPCError(errs.MISC_ERROR,
  175. 'fundrawtransaction "hexstring" ( options )');
  176. }
  177. const wallet = this.wallet;
  178. const valid = new Validator(args);
  179. const data = valid.buf(0);
  180. const options = valid.obj(1);
  181. if (!data)
  182. throw new RPCError(errs.TYPE_ERROR, 'Invalid hex string.');
  183. const tx = MTX.fromRaw(data);
  184. if (tx.outputs.length === 0) {
  185. throw new RPCError(errs.INVALID_PARAMETER,
  186. 'TX must have at least one output.');
  187. }
  188. let rate = null;
  189. let change = null;
  190. if (options) {
  191. const valid = new Validator(options);
  192. rate = valid.ufixed('feeRate', 8);
  193. change = valid.str('changeAddress');
  194. if (change)
  195. change = parseAddress(change, this.network);
  196. }
  197. await wallet.fund(tx, {
  198. rate: rate,
  199. changeAddress: change
  200. });
  201. return {
  202. hex: tx.toRaw().toString('hex'),
  203. changepos: tx.changeIndex,
  204. fee: Amount.btc(tx.getFee(), true)
  205. };
  206. }
  207. /*
  208. * Wallet
  209. */
  210. async resendWalletTransactions(args, help) {
  211. if (help || args.length !== 0)
  212. throw new RPCError(errs.MISC_ERROR, 'resendwallettransactions');
  213. const wallet = this.wallet;
  214. const txs = await wallet.resend();
  215. const hashes = [];
  216. for (const tx of txs)
  217. hashes.push(tx.txid());
  218. return hashes;
  219. }
  220. async addMultisigAddress(args, help) {
  221. // Impossible to implement in bcoin (no address book).
  222. throw new Error('Not implemented.');
  223. }
  224. async addWitnessAddress(args, help) {
  225. // Unlikely to be implemented.
  226. throw new Error('Not implemented.');
  227. }
  228. async backupWallet(args, help) {
  229. const valid = new Validator(args);
  230. const dest = valid.str(0);
  231. if (help || args.length !== 1 || !dest)
  232. throw new RPCError(errs.MISC_ERROR, 'backupwallet "destination"');
  233. await this.wdb.backup(dest);
  234. return null;
  235. }
  236. async dumpPrivKey(args, help) {
  237. if (help || args.length !== 1)
  238. throw new RPCError(errs.MISC_ERROR, 'dumpprivkey "bitcoinaddress"');
  239. const wallet = this.wallet;
  240. const valid = new Validator(args);
  241. const addr = valid.str(0, '');
  242. const hash = parseHash(addr, this.network);
  243. const ring = await wallet.getPrivateKey(hash);
  244. if (!ring)
  245. throw new RPCError(errs.MISC_ERROR, 'Key not found.');
  246. return ring.toSecret(this.network);
  247. }
  248. async dumpWallet(args, help) {
  249. if (help || args.length !== 1)
  250. throw new RPCError(errs.MISC_ERROR, 'dumpwallet "filename"');
  251. const wallet = this.wallet;
  252. const valid = new Validator(args);
  253. const file = valid.str(0);
  254. if (!file)
  255. throw new RPCError(errs.TYPE_ERROR, 'Invalid parameter.');
  256. const tip = await this.wdb.getTip();
  257. const time = util.date();
  258. const out = [
  259. format('# Wallet Dump created by Bcoin %s', pkg.version),
  260. format('# * Created on %s', time),
  261. format('# * Best block at time of backup was %d (%s).',
  262. tip.height, util.revHex(tip.hash)),
  263. format('# * File: %s', file),
  264. ''
  265. ];
  266. const hashes = await wallet.getAddressHashes();
  267. for (const hash of hashes) {
  268. const ring = await wallet.getPrivateKey(hash);
  269. if (!ring)
  270. continue;
  271. const addr = ring.getAddress('string', this.network);
  272. let fmt = '%s %s label= addr=%s';
  273. if (ring.branch === 1)
  274. fmt = '%s %s change=1 addr=%s';
  275. const str = format(fmt, ring.toSecret(this.network), time, addr);
  276. out.push(str);
  277. }
  278. out.push('');
  279. out.push('# End of dump');
  280. out.push('');
  281. const dump = out.join('\n');
  282. if (fs.unsupported)
  283. return dump;
  284. await fs.writeFile(file, dump, 'utf8');
  285. return null;
  286. }
  287. async encryptWallet(args, help) {
  288. const wallet = this.wallet;
  289. if (!wallet.master.encrypted && (help || args.length !== 1))
  290. throw new RPCError(errs.MISC_ERROR, 'encryptwallet "passphrase"');
  291. const valid = new Validator(args);
  292. const passphrase = valid.str(0, '');
  293. if (wallet.master.encrypted) {
  294. throw new RPCError(errs.WALLET_WRONG_ENC_STATE,
  295. 'Already running with an encrypted wallet.');
  296. }
  297. if (passphrase.length < 1)
  298. throw new RPCError(errs.MISC_ERROR, 'encryptwallet "passphrase"');
  299. try {
  300. await wallet.encrypt(passphrase);
  301. } catch (e) {
  302. throw new RPCError(errs.WALLET_ENCRYPTION_FAILED, 'Encryption failed.');
  303. }
  304. return 'wallet encrypted; we do not need to stop!';
  305. }
  306. async getAccountAddress(args, help) {
  307. if (help || args.length !== 1)
  308. throw new RPCError(errs.MISC_ERROR, 'getaccountaddress "account"');
  309. const wallet = this.wallet;
  310. const valid = new Validator(args);
  311. let name = valid.str(0, '');
  312. if (!name)
  313. name = 'default';
  314. const addr = await wallet.receiveAddress(name);
  315. if (!addr)
  316. return '';
  317. return addr.toString(this.network);
  318. }
  319. async getAccount(args, help) {
  320. if (help || args.length !== 1)
  321. throw new RPCError(errs.MISC_ERROR, 'getaccount "bitcoinaddress"');
  322. const wallet = this.wallet;
  323. const valid = new Validator(args);
  324. const addr = valid.str(0, '');
  325. const hash = parseHash(addr, this.network);
  326. const path = await wallet.getPath(hash);
  327. if (!path)
  328. return '';
  329. return path.name;
  330. }
  331. async getAddressesByAccount(args, help) {
  332. if (help || args.length !== 1)
  333. throw new RPCError(errs.MISC_ERROR, 'getaddressesbyaccount "account"');
  334. const wallet = this.wallet;
  335. const valid = new Validator(args);
  336. let name = valid.str(0, '');
  337. const addrs = [];
  338. if (name === '')
  339. name = 'default';
  340. let paths;
  341. try {
  342. paths = await wallet.getPaths(name);
  343. } catch (e) {
  344. if (e.message === 'Account not found.')
  345. return [];
  346. throw e;
  347. }
  348. for (const path of paths) {
  349. const addr = path.toAddress();
  350. addrs.push(addr.toString(this.network));
  351. }
  352. return addrs;
  353. }
  354. async getAddressInfo(args, help) {
  355. if (help || args.length !== 1)
  356. throw new RPCError(errs.MISC_ERROR, 'getaddressinfo "address"');
  357. const valid = new Validator(args);
  358. const addr = valid.str(0, '');
  359. const address = parseAddress(addr, this.network);
  360. const script = Script.fromAddress(address);
  361. const wallet = this.wallet.toJSON();
  362. const path = await this.wallet.getPath(address);
  363. const isScript = script.isScripthash() || script.isWitnessScripthash();
  364. const isWitness = address.isProgram();
  365. const result = {
  366. address: address.toString(this.network),
  367. scriptPubKey: script ? script.toJSON() : undefined,
  368. ismine: path != null,
  369. ischange: path ? path.branch === 1 : false,
  370. iswatchonly: wallet.watchOnly,
  371. isscript: isScript,
  372. iswitness: isWitness
  373. };
  374. if (isWitness) {
  375. result.witness_version = address.version;
  376. result.witness_program = address.hash.toString('hex');
  377. }
  378. return result;
  379. }
  380. async getBalance(args, help) {
  381. if (help || args.length > 3) {
  382. throw new RPCError(errs.MISC_ERROR,
  383. 'getbalance ( "account" minconf includeWatchonly )');
  384. }
  385. const wallet = this.wallet;
  386. const valid = new Validator(args);
  387. let name = valid.str(0);
  388. const minconf = valid.u32(1, 1);
  389. const watchOnly = valid.bool(2, false);
  390. if (name === '')
  391. name = 'default';
  392. if (name === '*')
  393. name = null;
  394. if (wallet.watchOnly !== watchOnly)
  395. return 0;
  396. const balance = await wallet.getBalance(name);
  397. let value;
  398. if (minconf > 0)
  399. value = balance.confirmed;
  400. else
  401. value = balance.unconfirmed;
  402. return Amount.btc(value, true);
  403. }
  404. async getNewAddress(args, help) {
  405. if (help || args.length > 1)
  406. throw new RPCError(errs.MISC_ERROR, 'getnewaddress ( "account" )');
  407. const wallet = this.wallet;
  408. const valid = new Validator(args);
  409. let name = valid.str(0);
  410. if (name === '' || args.length === 0)
  411. name = 'default';
  412. const addr = await wallet.createReceive(name);
  413. return addr.getAddress('string', this.network);
  414. }
  415. async getRawChangeAddress(args, help) {
  416. if (help || args.length !== 0)
  417. throw new RPCError(errs.MISC_ERROR, 'getrawchangeaddress');
  418. const wallet = this.wallet;
  419. const addr = await wallet.createChange();
  420. return addr.getAddress('string', this.network);
  421. }
  422. async getReceivedByAccount(args, help) {
  423. if (help || args.length < 1 || args.length > 2) {
  424. throw new RPCError(errs.MISC_ERROR,
  425. 'getreceivedbyaccount "account" ( minconf )');
  426. }
  427. const wallet = this.wallet;
  428. const valid = new Validator(args);
  429. let name = valid.str(0);
  430. const minconf = valid.u32(1, 0);
  431. const height = this.wdb.state.height;
  432. if (name === '')
  433. name = 'default';
  434. const paths = await wallet.getPaths(name);
  435. const filter = new BufferSet();
  436. for (const path of paths)
  437. filter.add(path.hash);
  438. const txs = await wallet.getHistory(name);
  439. let total = 0;
  440. let lastConf = -1;
  441. for (const wtx of txs) {
  442. const conf = wtx.getDepth(height);
  443. if (conf < minconf)
  444. continue;
  445. if (lastConf === -1 || conf < lastConf)
  446. lastConf = conf;
  447. for (const output of wtx.tx.outputs) {
  448. const hash = output.getHash();
  449. if (hash && filter.has(hash))
  450. total += output.value;
  451. }
  452. }
  453. return Amount.btc(total, true);
  454. }
  455. async getReceivedByAddress(args, help) {
  456. if (help || args.length < 1 || args.length > 2) {
  457. throw new RPCError(errs.MISC_ERROR,
  458. 'getreceivedbyaddress "bitcoinaddress" ( minconf )');
  459. }
  460. const wallet = this.wallet;
  461. const valid = new Validator(args);
  462. const addr = valid.str(0, '');
  463. const minconf = valid.u32(1, 0);
  464. const height = this.wdb.state.height;
  465. const hash = parseHash(addr, this.network);
  466. const txs = await wallet.getHistory();
  467. let total = 0;
  468. for (const wtx of txs) {
  469. if (wtx.getDepth(height) < minconf)
  470. continue;
  471. for (const output of wtx.tx.outputs) {
  472. if (output.getHash().equals(hash))
  473. total += output.value;
  474. }
  475. }
  476. return Amount.btc(total, true);
  477. }
  478. async _toWalletTX(wtx) {
  479. const wallet = this.wallet;
  480. const details = await wallet.toDetails(wtx);
  481. if (!details)
  482. throw new RPCError(errs.WALLET_ERROR, 'TX not found.');
  483. let receive = true;
  484. for (const member of details.inputs) {
  485. if (member.path) {
  486. receive = false;
  487. break;
  488. }
  489. }
  490. const det = [];
  491. let sent = 0;
  492. let received = 0;
  493. for (let i = 0; i < details.outputs.length; i++) {
  494. const member = details.outputs[i];
  495. if (member.path) {
  496. if (member.path.branch === 1)
  497. continue;
  498. det.push({
  499. account: member.path.name,
  500. address: member.address.toString(this.network),
  501. category: 'receive',
  502. amount: Amount.btc(member.value, true),
  503. label: member.path.name,
  504. vout: i
  505. });
  506. received += member.value;
  507. continue;
  508. }
  509. if (receive)
  510. continue;
  511. det.push({
  512. account: '',
  513. address: member.address
  514. ? member.address.toString(this.network)
  515. : null,
  516. category: 'send',
  517. amount: -(Amount.btc(member.value, true)),
  518. fee: -(Amount.btc(details.fee, true)),
  519. vout: i
  520. });
  521. sent += member.value;
  522. }
  523. return {
  524. amount: Amount.btc(receive ? received : -sent, true),
  525. confirmations: details.confirmations,
  526. blockhash: details.block ? util.revHex(details.block) : null,
  527. blockindex: details.index,
  528. blocktime: details.time,
  529. txid: util.revHex(details.hash),
  530. walletconflicts: [],
  531. time: details.mtime,
  532. timereceived: details.mtime,
  533. 'bip125-replaceable': 'no',
  534. details: det,
  535. hex: details.tx.toRaw().toString('hex')
  536. };
  537. }
  538. async getTransaction(args, help) {
  539. if (help || args.length < 1 || args.length > 2) {
  540. throw new RPCError(errs.MISC_ERROR,
  541. 'gettransaction "txid" ( includeWatchonly )');
  542. }
  543. const wallet = this.wallet;
  544. const valid = new Validator(args);
  545. const hash = valid.brhash(0);
  546. const watchOnly = valid.bool(1, false);
  547. if (!hash)
  548. throw new RPCError(errs.TYPE_ERROR, 'Invalid parameter');
  549. const wtx = await wallet.getTX(hash);
  550. if (!wtx)
  551. throw new RPCError(errs.WALLET_ERROR, 'TX not found.');
  552. return await this._toWalletTX(wtx, watchOnly);
  553. }
  554. async abandonTransaction(args, help) {
  555. if (help || args.length !== 1)
  556. throw new RPCError(errs.MISC_ERROR, 'abandontransaction "txid"');
  557. const wallet = this.wallet;
  558. const valid = new Validator(args);
  559. const hash = valid.brhash(0);
  560. if (!hash)
  561. throw new RPCError(errs.TYPE_ERROR, 'Invalid parameter.');
  562. const result = await wallet.abandon(hash);
  563. if (!result)
  564. throw new RPCError(errs.WALLET_ERROR, 'Transaction not in wallet.');
  565. return null;
  566. }
  567. async getUnconfirmedBalance(args, help) {
  568. if (help || args.length > 0)
  569. throw new RPCError(errs.MISC_ERROR, 'getunconfirmedbalance');
  570. const wallet = this.wallet;
  571. const balance = await wallet.getBalance();
  572. return Amount.btc(balance.unconfirmed, true);
  573. }
  574. async getWalletInfo(args, help) {
  575. if (help || args.length !== 0)
  576. throw new RPCError(errs.MISC_ERROR, 'getwalletinfo');
  577. const wallet = this.wallet;
  578. const balance = await wallet.getBalance();
  579. return {
  580. walletid: wallet.id,
  581. walletversion: 6,
  582. balance: Amount.btc(balance.unconfirmed, true),
  583. unconfirmed_balance: Amount.btc(balance.unconfirmed, true),
  584. txcount: balance.tx,
  585. keypoololdest: 0,
  586. keypoolsize: 0,
  587. unlocked_until: wallet.master.until,
  588. paytxfee: Amount.btc(this.wdb.feeRate, true)
  589. };
  590. }
  591. async importPrivKey(args, help) {
  592. if (help || args.length < 1 || args.length > 3) {
  593. throw new RPCError(errs.MISC_ERROR,
  594. 'importprivkey "bitcoinprivkey" ( "label" rescan )');
  595. }
  596. const wallet = this.wallet;
  597. const valid = new Validator(args);
  598. const secret = valid.str(0);
  599. const rescan = valid.bool(2, false);
  600. const key = parseSecret(secret, this.network);
  601. await wallet.importKey(0, key);
  602. if (rescan)
  603. await this.wdb.rescan(0);
  604. return null;
  605. }
  606. async importWallet(args, help) {
  607. if (help || args.length < 1 || args.length > 2)
  608. throw new RPCError(errs.MISC_ERROR, 'importwallet "filename" ( rescan )');
  609. const wallet = this.wallet;
  610. const valid = new Validator(args);
  611. const file = valid.str(0);
  612. const rescan = valid.bool(1, false);
  613. if (fs.unsupported)
  614. throw new RPCError(errs.INTERNAL_ERROR, 'FS not available.');
  615. let data;
  616. try {
  617. data = await fs.readFile(file, 'utf8');
  618. } catch (e) {
  619. throw new RPCError(errs.INTERNAL_ERROR, e.code || '');
  620. }
  621. const lines = data.split(/\n+/);
  622. const keys = [];
  623. for (let line of lines) {
  624. line = line.trim();
  625. if (line.length === 0)
  626. continue;
  627. if (/^\s*#/.test(line))
  628. continue;
  629. const parts = line.split(/\s+/);
  630. if (parts.length < 4)
  631. throw new RPCError(errs.DESERIALIZATION_ERROR, 'Malformed wallet.');
  632. const secret = parseSecret(parts[0], this.network);
  633. keys.push(secret);
  634. }
  635. for (const key of keys)
  636. await wallet.importKey(0, key);
  637. if (rescan)
  638. await this.wdb.rescan(0);
  639. return null;
  640. }
  641. async importAddress(args, help) {
  642. if (help || args.length < 1 || args.length > 4) {
  643. throw new RPCError(errs.MISC_ERROR,
  644. 'importaddress "address" ( "label" rescan p2sh )');
  645. }
  646. const wallet = this.wallet;
  647. const valid = new Validator(args);
  648. let addr = valid.str(0, '');
  649. const rescan = valid.bool(2, false);
  650. const p2sh = valid.bool(3, false);
  651. if (p2sh) {
  652. let script = valid.buf(0);
  653. if (!script)
  654. throw new RPCError(errs.TYPE_ERROR, 'Invalid parameters.');
  655. script = Script.fromRaw(script);
  656. script = Script.fromScripthash(script.hash160());
  657. addr = script.getAddress();
  658. } else {
  659. addr = parseAddress(addr, this.network);
  660. }
  661. try {
  662. await wallet.importAddress(0, addr);
  663. } catch (e) {
  664. if (e.message !== 'Address already exists.')
  665. throw e;
  666. }
  667. if (rescan)
  668. await this.wdb.rescan(0);
  669. return null;
  670. }
  671. async importPubkey(args, help) {
  672. if (help || args.length < 1 || args.length > 3) {
  673. throw new RPCError(errs.MISC_ERROR,
  674. 'importpubkey "pubkey" ( "label" rescan )');
  675. }
  676. const wallet = this.wallet;
  677. const valid = new Validator(args);
  678. const data = valid.buf(0);
  679. const rescan = valid.bool(2, false);
  680. if (!data)
  681. throw new RPCError(errs.TYPE_ERROR, 'Invalid parameter.');
  682. const key = KeyRing.fromPublic(data, this.network);
  683. await wallet.importKey(0, key);
  684. if (rescan)
  685. await this.wdb.rescan(0);
  686. return null;
  687. }
  688. async keyPoolRefill(args, help) {
  689. if (help || args.length > 1)
  690. throw new RPCError(errs.MISC_ERROR, 'keypoolrefill ( newsize )');
  691. return null;
  692. }
  693. async listAccounts(args, help) {
  694. if (help || args.length > 2) {
  695. throw new RPCError(errs.MISC_ERROR,
  696. 'listaccounts ( minconf includeWatchonly)');
  697. }
  698. const wallet = this.wallet;
  699. const valid = new Validator(args);
  700. const minconf = valid.u32(0, 0);
  701. const watchOnly = valid.bool(1, false);
  702. const accounts = await wallet.getAccounts();
  703. const map = {};
  704. for (const account of accounts) {
  705. const balance = await wallet.getBalance(account);
  706. let value = balance.unconfirmed;
  707. if (minconf > 0)
  708. value = balance.confirmed;
  709. if (wallet.watchOnly !== watchOnly)
  710. value = 0;
  711. map[account] = Amount.btc(value, true);
  712. }
  713. return map;
  714. }
  715. async listAddressGroupings(args, help) {
  716. throw new Error('Not implemented.');
  717. }
  718. async listLockUnspent(args, help) {
  719. if (help || args.length > 0)
  720. throw new RPCError(errs.MISC_ERROR, 'listlockunspent');
  721. const wallet = this.wallet;
  722. const outpoints = wallet.getLocked();
  723. const out = [];
  724. for (const outpoint of outpoints) {
  725. out.push({
  726. txid: outpoint.txid(),
  727. vout: outpoint.index
  728. });
  729. }
  730. return out;
  731. }
  732. async listReceivedByAccount(args, help) {
  733. if (help || args.length > 3) {
  734. throw new RPCError(errs.MISC_ERROR,
  735. 'listreceivedbyaccount ( minconf includeempty includeWatchonly )');
  736. }
  737. const valid = new Validator(args);
  738. const minconf = valid.u32(0, 0);
  739. const includeEmpty = valid.bool(1, false);
  740. const watchOnly = valid.bool(2, false);
  741. return await this._listReceived(minconf, includeEmpty, watchOnly, true);
  742. }
  743. async listReceivedByAddress(args, help) {
  744. if (help || args.length > 3) {
  745. throw new RPCError(errs.MISC_ERROR,
  746. 'listreceivedbyaddress ( minconf includeempty includeWatchonly )');
  747. }
  748. const valid = new Validator(args);
  749. const minconf = valid.u32(0, 0);
  750. const includeEmpty = valid.bool(1, false);
  751. const watchOnly = valid.bool(2, false);
  752. return await this._listReceived(minconf, includeEmpty, watchOnly, false);
  753. }
  754. async _listReceived(minconf, empty, watchOnly, account) {
  755. const wallet = this.wallet;
  756. const paths = await wallet.getPaths();
  757. const height = this.wdb.state.height;
  758. const map = new BufferMap();
  759. for (const path of paths) {
  760. const addr = path.toAddress();
  761. map.set(path.hash, {
  762. involvesWatchonly: wallet.watchOnly,
  763. address: addr.toString(this.network),
  764. account: path.name,
  765. amount: 0,
  766. confirmations: -1,
  767. label: ''
  768. });
  769. }
  770. const txs = await wallet.getHistory();
  771. for (const wtx of txs) {
  772. const conf = wtx.getDepth(height);
  773. if (conf < minconf)
  774. continue;
  775. for (const output of wtx.tx.outputs) {
  776. const addr = output.getAddress();
  777. if (!addr)
  778. continue;
  779. const hash = addr.getHash();
  780. const entry = map.get(hash);
  781. if (entry) {
  782. if (entry.confirmations === -1 || conf < entry.confirmations)
  783. entry.confirmations = conf;
  784. entry.address = addr.toString(this.network);
  785. entry.amount += output.value;
  786. }
  787. }
  788. }
  789. let out = [];
  790. for (const entry of map.values())
  791. out.push(entry);
  792. if (account) {
  793. const map = new Map();
  794. for (const entry of out) {
  795. const item = map.get(entry.account);
  796. if (!item) {
  797. map.set(entry.account, entry);
  798. entry.address = undefined;
  799. continue;
  800. }
  801. item.amount += entry.amount;
  802. }
  803. out = [];
  804. for (const entry of map.values())
  805. out.push(entry);
  806. }
  807. const result = [];
  808. for (const entry of out) {
  809. if (!empty && entry.amount === 0)
  810. continue;
  811. if (entry.confirmations === -1)
  812. entry.confirmations = 0;
  813. entry.amount = Amount.btc(entry.amount, true);
  814. result.push(entry);
  815. }
  816. return result;
  817. }
  818. async listSinceBlock(args, help) {
  819. if (help || args.length > 3) {
  820. throw new RPCError(errs.MISC_ERROR,
  821. 'listsinceblock ( "blockhash" target-confirmations includeWatchonly)');
  822. }
  823. const wallet = this.wallet;
  824. const chainHeight = this.wdb.state.height;
  825. const valid = new Validator(args);
  826. const block = valid.brhash(0);
  827. const minconf = valid.u32(1, 0);
  828. const watchOnly = valid.bool(2, false);
  829. if (wallet.watchOnly !== watchOnly)
  830. return [];
  831. let height = -1;
  832. if (block) {
  833. const entry = await this.client.getEntry(block);
  834. if (entry)
  835. height = entry.height;
  836. else
  837. throw new RPCError(errs.MISC_ERROR, 'Block not found.');
  838. }
  839. if (height === -1)
  840. height = chainHeight;
  841. const txs = await wallet.getHistory();
  842. const out = [];
  843. let highest = null;
  844. for (const wtx of txs) {
  845. if (wtx.height < height)
  846. continue;
  847. if (wtx.getDepth(chainHeight) < minconf)
  848. continue;
  849. if (!highest || wtx.height > highest)
  850. highest = wtx;
  851. const json = await this._toListTX(wtx);
  852. out.push(json);
  853. }
  854. return {
  855. transactions: out,
  856. lastblock: highest && highest.block
  857. ? util.revHex(highest.block)
  858. : util.revHex(consensus.ZERO_HASH)
  859. };
  860. }
  861. async _toListTX(wtx) {
  862. const wallet = this.wallet;
  863. const details = await wallet.toDetails(wtx);
  864. if (!details)
  865. throw new RPCError(errs.WALLET_ERROR, 'TX not found.');
  866. let receive = true;
  867. for (const member of details.inputs) {
  868. if (member.path) {
  869. receive = false;
  870. break;
  871. }
  872. }
  873. let sent = 0;
  874. let received = 0;
  875. let sendMember = null;
  876. let recMember = null;
  877. let sendIndex = -1;
  878. let recIndex = -1;
  879. for (let i = 0; i < details.outputs.length; i++) {
  880. const member = details.outputs[i];
  881. if (member.path) {
  882. if (member.path.branch === 1)
  883. continue;
  884. received += member.value;
  885. recMember = member;
  886. recIndex = i;
  887. continue;
  888. }
  889. sent += member.value;
  890. sendMember = member;
  891. sendIndex = i;
  892. }
  893. let member = null;
  894. let index = -1;
  895. if (receive) {
  896. assert(recMember);
  897. member = recMember;
  898. index = recIndex;
  899. } else {
  900. if (sendMember) {
  901. member = sendMember;
  902. index = sendIndex;
  903. } else {
  904. // In the odd case where we send to ourselves.
  905. receive = true;
  906. received = 0;
  907. member = recMember;
  908. index = recIndex;
  909. }
  910. }
  911. let rbf = false;
  912. if (wtx.height === -1 && wtx.tx.isRBF())
  913. rbf = true;
  914. return {
  915. account: member.path ? member.path.name : '',
  916. address: member.address
  917. ? member.address.toString(this.network)
  918. : null,
  919. category: receive ? 'receive' : 'send',
  920. amount: Amount.btc(receive ? received : -sent, true),
  921. label: member.path ? member.path.name : undefined,
  922. vout: index,
  923. confirmations: details.getDepth(this.wdb.height),
  924. blockhash: details.block ? util.revHex(details.block) : null,
  925. blockindex: -1,
  926. blocktime: details.time,
  927. blockheight: details.height,
  928. txid: util.revHex(details.hash),
  929. walletconflicts: [],
  930. time: details.mtime,
  931. timereceived: details.mtime,
  932. 'bip125-replaceable': rbf ? 'yes' : 'no'
  933. };
  934. }
  935. async listTransactions(args, help) {
  936. if (help || args.length > 4) {
  937. throw new RPCError(errs.MISC_ERROR,
  938. 'listtransactions ( "account" count from includeWatchonly)');
  939. }
  940. const wallet = this.wallet;
  941. const valid = new Validator(args);
  942. let name = valid.str(0);
  943. const count = valid.u32(1, 10);
  944. const from = valid.u32(2, 0);
  945. const watchOnly = valid.bool(3, false);
  946. if (wallet.watchOnly !== watchOnly)
  947. return [];
  948. if (name === '')
  949. name = 'default';
  950. const txs = await wallet.getHistory(name);
  951. common.sortTX(txs);
  952. const end = from + count;
  953. const to = Math.min(end, txs.length);
  954. const out = [];
  955. for (let i = from; i < to; i++) {
  956. const wtx = txs[i];
  957. const json = await this._toListTX(wtx);
  958. out.push(json);
  959. }
  960. return out;
  961. }
  962. async listUnspent(args, help) {
  963. if (help || args.length > 3) {
  964. throw new RPCError(errs.MISC_ERROR,
  965. 'listunspent ( minconf maxconf ["address",...] )');
  966. }
  967. const wallet = this.wallet;
  968. const valid = new Validator(args);
  969. const minDepth = valid.u32(0, 1);
  970. const maxDepth = valid.u32(1, 9999999);
  971. const addrs = valid.array(2);
  972. const height = this.wdb.state.height;
  973. const map = new BufferSet();
  974. if (addrs) {
  975. const valid = new Validator(addrs);
  976. for (let i = 0; i < addrs.length; i++) {
  977. const addr = valid.str(i, '');
  978. const hash = parseHash(addr, this.network);
  979. if (map.has(hash))
  980. throw new RPCError(errs.INVALID_PARAMETER, 'Duplicate address.');
  981. map.add(hash);
  982. }
  983. }
  984. const coins = await wallet.getCoins();
  985. common.sortCoins(coins);
  986. const out = [];
  987. for (const coin of coins) {
  988. const depth = coin.getDepth(height);
  989. if (depth < minDepth || depth > maxDepth)
  990. continue;
  991. const addr = coin.getAddress();
  992. if (!addr)
  993. continue;
  994. const hash = coin.getHash();
  995. if (addrs) {
  996. if (!hash || !map.has(hash))
  997. continue;
  998. }
  999. const ring = await wallet.getKey(hash);
  1000. out.push({
  1001. txid: coin.txid(),
  1002. vout: coin.index,
  1003. address: addr ? addr.toString(this.network) : null,
  1004. account: ring ? ring.name : undefined,
  1005. redeemScript: ring && ring.script
  1006. ? ring.script.toJSON()
  1007. : undefined,
  1008. scriptPubKey: coin.script.toJSON(),
  1009. amount: Amount.btc(coin.value, true),
  1010. confirmations: depth,
  1011. spendable: !wallet.isLocked(coin),
  1012. solvable: true
  1013. });
  1014. }
  1015. return out;
  1016. }
  1017. async lockUnspent(args, help) {
  1018. if (help || args.length < 1 || args.length > 2) {
  1019. throw new RPCError(errs.MISC_ERROR,
  1020. 'lockunspent unlock ([{"txid":"txid","vout":n},...])');
  1021. }
  1022. const wallet = this.wallet;
  1023. const valid = new Validator(args);
  1024. const unlock = valid.bool(0, false);
  1025. const outputs = valid.array(1);
  1026. if (args.length === 1) {
  1027. if (unlock)
  1028. wallet.unlockCoins();
  1029. return true;
  1030. }
  1031. if (!outputs)
  1032. throw new RPCError(errs.TYPE_ERROR, 'Invalid parameter.');
  1033. for (const output of outputs) {
  1034. const valid = new Validator(output);
  1035. const hash = valid.brhash('txid');
  1036. const index = valid.u32('vout');
  1037. if (hash == null || index == null)
  1038. throw new RPCError(errs.INVALID_PARAMETER, 'Invalid parameter.');
  1039. const outpoint = new Outpoint(hash, index);
  1040. if (unlock) {
  1041. wallet.unlockCoin(outpoint);
  1042. continue;
  1043. }
  1044. wallet.lockCoin(outpoint);
  1045. }
  1046. return true;
  1047. }
  1048. async move(args, help) {
  1049. // Not implementing: stupid and deprecated.
  1050. throw new Error('Not implemented.');
  1051. }
  1052. async sendFrom(args, help) {
  1053. if (help || args.length < 3 || args.length > 6) {
  1054. throw new RPCError(errs.MISC_ERROR,
  1055. 'sendfrom "fromaccount" "tobitcoinaddress"'
  1056. + ' amount ( minconf "comment" "comment-to" )');
  1057. }
  1058. const wallet = this.wallet;
  1059. const valid = new Validator(args);
  1060. let name = valid.str(0);
  1061. const str = valid.str(1);
  1062. const value = valid.ufixed(2, 8);
  1063. const minconf = valid.u32(3, 0);
  1064. const addr = parseAddress(str, this.network);
  1065. if (!addr || value == null)
  1066. throw new RPCError(errs.TYPE_ERROR, 'Invalid parameter.');
  1067. if (name === '')
  1068. name = 'default';
  1069. const options = {
  1070. account: name,
  1071. depth: minconf,
  1072. outputs: [{
  1073. address: addr,
  1074. value: value
  1075. }]
  1076. };
  1077. const tx = await wallet.send(options);
  1078. return tx.txid();
  1079. }
  1080. async sendMany(args, help) {
  1081. if (help || args.length < 2 || args.length > 5) {
  1082. throw new RPCError(errs.MISC_ERROR,
  1083. 'sendmany "fromaccount" {"address":amount,...}'
  1084. + ' ( minconf "comment" subtractfee )');
  1085. }
  1086. const wallet = this.wallet;
  1087. const valid = new Validator(args);
  1088. let name = valid.str(0);
  1089. const sendTo = valid.obj(1);
  1090. const minconf = valid.u32(2, 1);
  1091. const subtract = valid.bool(4, false);
  1092. if (name === '')
  1093. name = 'default';
  1094. if (!sendTo)
  1095. throw new RPCError(errs.TYPE_ERROR, 'Invalid send-to address.');
  1096. const to = new Validator(sendTo);
  1097. const uniq = new BufferSet();
  1098. const outputs = [];
  1099. for (const key of Object.keys(sendTo)) {
  1100. const value = to.ufixed(key, 8);
  1101. const addr = parseAddress(key, this.network);
  1102. const hash = addr.getHash();
  1103. if (value == null)
  1104. throw new RPCError(errs.INVALID_PARAMETER, 'Invalid amount.');
  1105. if (uniq.has(hash))
  1106. throw new RPCError(errs.INVALID_PARAMETER,
  1107. 'Each send-to address must be unique.');
  1108. uniq.add(hash);
  1109. const output = new Output();
  1110. output.value = value;
  1111. output.script.fromAddress(addr);
  1112. outputs.push(output);
  1113. }
  1114. const options = {
  1115. outputs: outputs,
  1116. subtractFee: subtract,
  1117. account: name,
  1118. depth: minconf
  1119. };
  1120. const tx = await wallet.send(options);
  1121. return tx.txid();
  1122. }
  1123. async sendToAddress(args, help) {
  1124. if (help || args.length < 2 || args.length > 5) {
  1125. throw new RPCError(errs.MISC_ERROR,
  1126. 'sendtoaddress "bitcoinaddress" amount'
  1127. + ' ( "comment" "comment-to" subtractfeefromamount )');
  1128. }
  1129. const wallet = this.wallet;
  1130. const valid = new Validator(args);
  1131. const str = valid.str(0);
  1132. const value = valid.ufixed(1, 8);
  1133. const subtract = valid.bool(4, false);
  1134. const addr = parseAddress(str, this.network);
  1135. if (!addr || value == null)
  1136. throw new RPCError(errs.TYPE_ERROR, 'Invalid parameter.');
  1137. const options = {
  1138. subtractFee: subtract,
  1139. outputs: [{
  1140. address: addr,
  1141. value: value
  1142. }]
  1143. };
  1144. const tx = await wallet.send(options);
  1145. return tx.txid();
  1146. }
  1147. async setAccount(args, help) {
  1148. // Impossible to implement in bcoin:
  1149. throw new Error('Not implemented.');
  1150. }
  1151. async setTXFee(args, help) {
  1152. const valid = new Validator(args);
  1153. const rate = valid.ufixed(0, 8);
  1154. if (help || args.length < 1 || args.length > 1)
  1155. throw new RPCError(errs.MISC_ERROR, 'settxfee amount');
  1156. if (rate == null)
  1157. throw new RPCError(errs.TYPE_ERROR, 'Invalid parameter.');
  1158. this.wdb.feeRate = rate;
  1159. return true;
  1160. }
  1161. async signMessage(args, help) {
  1162. if (help || args.length !== 2) {
  1163. throw new RPCError(errs.MISC_ERROR,
  1164. 'signmessage "bitcoinaddress" "message"');
  1165. }
  1166. const wallet = this.wallet;
  1167. const valid = new Validator(args);
  1168. const b58 = valid.str(0, '');
  1169. const str = valid.str(1, '');
  1170. const addr = parseHash(b58, this.network);
  1171. const ring = await wallet.getKey(addr);
  1172. if (!ring)
  1173. throw new RPCError(errs.WALLET_ERROR, 'Address not found.');
  1174. if (!wallet.master.key)
  1175. throw new RPCError(errs.WALLET_UNLOCK_NEEDED, 'Wallet is locked.');
  1176. const sig = messageUtil.sign(str, ring);
  1177. return sig.toString('base64');
  1178. }
  1179. async walletLock(args, help) {
  1180. const wallet = this.wallet;
  1181. if (help || (wallet.master.encrypted && args.length !== 0))
  1182. throw new RPCError(errs.MISC_ERROR, 'walletlock');
  1183. if (!wallet.master.encrypted) {
  1184. throw new RPCError(
  1185. errs.WALLET_WRONG_ENC_STATE,
  1186. 'Wallet is not encrypted.');
  1187. }
  1188. await wallet.lock();
  1189. return null;
  1190. }
  1191. async walletPassphraseChange(args, help) {
  1192. const wallet = this.wallet;
  1193. if (help || (wallet.master.encrypted && args.length !== 2)) {
  1194. throw new RPCError(errs.MISC_ERROR, 'walletpassphrasechange'
  1195. + ' "oldpassphrase" "newpassphrase"');
  1196. }
  1197. const valid = new Validator(args);
  1198. const old = valid.str(0, '');
  1199. const passphrase = valid.str(1, '');
  1200. if (!wallet.master.encrypted) {
  1201. throw new RPCError(
  1202. errs.WALLET_WRONG_ENC_STATE,
  1203. 'Wallet is not encrypted.');
  1204. }
  1205. if (old.length < 1 || passphrase.length < 1)
  1206. throw new RPCError(errs.INVALID_PARAMETER, 'Invalid parameter');
  1207. await wallet.setPassphrase(passphrase, old);
  1208. return null;
  1209. }
  1210. async walletPassphrase(args, help) {
  1211. const wallet = this.wallet;
  1212. const valid = new Validator(args);
  1213. const passphrase = valid.str(0, '');
  1214. const timeout = valid.u32(1);
  1215. if (help || (wallet.master.encrypted && args.length !== 2)) {
  1216. throw new RPCError(errs.MISC_ERROR,
  1217. 'walletpassphrase "passphrase" timeout');
  1218. }
  1219. if (!wallet.master.encrypted) {
  1220. throw new RPCError(
  1221. errs.WALLET_WRONG_ENC_STATE,
  1222. 'Wallet is not encrypted.');
  1223. }
  1224. if (passphrase.length < 1)
  1225. throw new RPCError(errs.INVALID_PARAMETER, 'Invalid parameter');
  1226. if (timeout == null)
  1227. throw new RPCError(errs.TYPE_ERROR, 'Invalid parameter');
  1228. await wallet.unlock(passphrase, timeout);
  1229. return null;
  1230. }
  1231. async importPrunedFunds(args, help) {
  1232. if (help || args.length !== 2) {
  1233. throw new RPCError(errs.MISC_ERROR,
  1234. 'importprunedfunds "rawtransaction" "txoutproof"');
  1235. }
  1236. const valid = new Validator(args);
  1237. const txRaw = valid.buf(0);
  1238. const blockRaw = valid.buf(1);
  1239. if (!txRaw || !blockRaw)
  1240. throw new RPCError(errs.TYPE_ERROR, 'Invalid parameter.');
  1241. const tx = TX.fromRaw(txRaw);
  1242. const block = MerkleBlock.fromRaw(blockRaw);
  1243. const hash = block.hash();
  1244. if (!block.verify())
  1245. throw new RPCError(errs.VERIFY_ERROR, 'Invalid proof.');
  1246. if (!block.hasTX(tx.hash()))
  1247. throw new RPCError(errs.VERIFY_ERROR, 'Invalid proof.');
  1248. const entry = await this.client.getEntry(hash);
  1249. if (!entry)
  1250. throw new RPCError(errs.VERIFY_ERROR, 'Invalid proof.');
  1251. const meta = BlockMeta.fromEntry(entry);
  1252. if (!await this.wdb.addTX(tx, meta))
  1253. throw new RPCError(errs.WALLET_ERROR,
  1254. 'Address for TX not in wallet, or TX already in wallet');
  1255. return null;
  1256. }
  1257. async removePrunedFunds(args, help) {
  1258. if (help || args.length !== 1)
  1259. throw new RPCError(errs.MISC_ERROR, 'removeprunedfunds "txid"');
  1260. const wallet = this.wallet;
  1261. const valid = new Validator(args);
  1262. const hash = valid.brhash(0);
  1263. if (!hash)
  1264. throw new RPCError(errs.TYPE_ERROR, 'Invalid parameter.');
  1265. if (!await wallet.remove(hash))
  1266. throw new RPCError(errs.WALLET_ERROR, 'Transaction not in wallet.');
  1267. return null;
  1268. }
  1269. async selectWallet(args, help) {
  1270. const valid = new Validator(args);
  1271. const id = valid.str(0);
  1272. if (help || args.length !== 1)
  1273. throw new RPCError(errs.MISC_ERROR, 'selectwallet "id"');
  1274. const wallet = await this.wdb.get(id);
  1275. if (!wallet)
  1276. throw new RPCError(errs.WALLET_ERROR, 'Wallet not found.');
  1277. this.wallet = wallet;
  1278. return null;
  1279. }
  1280. async getMemoryInfo(args, help) {
  1281. if (help || args.length !== 0)
  1282. throw new RPCError(errs.MISC_ERROR, 'getmemoryinfo');
  1283. return this.logger.memoryUsage();
  1284. }
  1285. async setLogLevel(args, help) {
  1286. if (help || args.length !== 1)
  1287. throw new RPCError(errs.MISC_ERROR, 'setloglevel "level"');
  1288. const valid = new Validator(args);
  1289. const level = valid.str(0, '');
  1290. this.logger.setLevel(level);
  1291. return null;
  1292. }
  1293. }
  1294. /*
  1295. * Helpers
  1296. */
  1297. function parseHash(raw, network) {
  1298. const addr = parseAddress(raw, network);
  1299. return addr.getHash();
  1300. }
  1301. function parseAddress(raw, network) {
  1302. try {
  1303. return Address.fromString(raw, network);
  1304. } catch (e) {
  1305. throw new RPCError(errs.INVALID_ADDRESS_OR_KEY, 'Invalid address.');
  1306. }
  1307. }
  1308. function parseSecret(raw, network) {
  1309. try {
  1310. return KeyRing.fromSecret(raw, network);
  1311. } catch (e) {
  1312. throw new RPCError(errs.INVALID_ADDRESS_OR_KEY, 'Invalid key.');
  1313. }
  1314. }
  1315. /*
  1316. * Expose
  1317. */
  1318. module.exports = RPC;