From 9037bd3db92efc04d2fcfcbe503dc6c54b5f987d Mon Sep 17 00:00:00 2001 From: Grid Cat Date: Sun, 9 Dec 2018 22:58:54 +0100 Subject: [PATCH] feat(*): add rest of the calls to the main client --- .prettierrc | 6 + package-lock.json | 72 ++--- package.json | 4 +- src/Errors/Authentication.ts | 20 ++ src/GridcoinRPC.ts | 583 ++++++++++++++++++++++++++++++++++- src/JsonRPC.ts | 168 ++++++++++ test/JsonRPC.spec.ts | 18 ++ 7 files changed, 809 insertions(+), 62 deletions(-) create mode 100644 .prettierrc create mode 100644 src/Errors/Authentication.ts create mode 100644 src/JsonRPC.ts create mode 100644 test/JsonRPC.spec.ts diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..cf77643 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,6 @@ +{ + "printWidth": 120, + "trailingComma": "all", + "singleQuote": true, + "semi": true +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 0e7446c..54802bf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -568,7 +568,8 @@ "@types/node": { "version": "10.12.7", "resolved": "https://registry.npmjs.org/@types/node/-/node-10.12.7.tgz", - "integrity": "sha512-Zh5Z4kACfbeE8aAOYh9mqotRxaZMro8MbBQtR8vEXOMiZo2rGEh2LayJijKdlu48YnS6y2EFU/oo2NCe5P6jGw==" + "integrity": "sha512-Zh5Z4kACfbeE8aAOYh9mqotRxaZMro8MbBQtR8vEXOMiZo2rGEh2LayJijKdlu48YnS6y2EFU/oo2NCe5P6jGw==", + "dev": true }, "@types/shelljs": { "version": "0.8.0", @@ -584,6 +585,7 @@ "version": "1.3.5", "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", + "dev": true, "requires": { "jsonparse": "^1.2.0", "through": ">=2.2.7 <3" @@ -1889,7 +1891,8 @@ "commander": { "version": "2.17.1", "resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz", - "integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==" + "integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==", + "dev": true }, "commitizen": { "version": "3.0.4", @@ -2522,12 +2525,14 @@ "es6-promise": { "version": "4.2.5", "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.5.tgz", - "integrity": "sha512-n6wvpdE43VFtJq+lUDYDBFUwV8TZbuGXLV4D6wKafg13ldznKsyEvatubnmUe31zcvelSzOHF+XbaT+Bl9ObDg==" + "integrity": "sha512-n6wvpdE43VFtJq+lUDYDBFUwV8TZbuGXLV4D6wKafg13ldznKsyEvatubnmUe31zcvelSzOHF+XbaT+Bl9ObDg==", + "dev": true }, "es6-promisify": { "version": "5.0.0", "resolved": "http://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", + "dev": true, "requires": { "es6-promise": "^4.0.3" } @@ -2723,7 +2728,8 @@ "eyes": { "version": "0.1.8", "resolved": "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz", - "integrity": "sha1-Ys8SAjTGg3hdkCNIqADvPgzCC8A=" + "integrity": "sha1-Ys8SAjTGg3hdkCNIqADvPgzCC8A=", + "dev": true }, "fast-deep-equal": { "version": "2.0.1", @@ -3292,14 +3298,12 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, - "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -3314,20 +3318,17 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "core-util-is": { "version": "1.0.2", @@ -3444,8 +3445,7 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "ini": { "version": "1.3.5", @@ -3457,7 +3457,6 @@ "version": "1.0.0", "bundled": true, "dev": true, - "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -3472,7 +3471,6 @@ "version": "3.0.4", "bundled": true, "dev": true, - "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -3480,14 +3478,12 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "minipass": { "version": "2.2.4", "bundled": true, "dev": true, - "optional": true, "requires": { "safe-buffer": "^5.1.1", "yallist": "^3.0.0" @@ -3506,7 +3502,6 @@ "version": "0.5.1", "bundled": true, "dev": true, - "optional": true, "requires": { "minimist": "0.0.8" } @@ -3587,8 +3582,7 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "object-assign": { "version": "4.1.1", @@ -3600,7 +3594,6 @@ "version": "1.4.0", "bundled": true, "dev": true, - "optional": true, "requires": { "wrappy": "1" } @@ -3722,7 +3715,6 @@ "version": "1.0.2", "bundled": true, "dev": true, - "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -4966,21 +4958,6 @@ "integrity": "sha512-CpKJh9VRNhS+XqZtg1UMejETGEiqwCGDC/uwPEEQwc2nfdbSm73SIE29TplG2gLYuBOOTNDqxzG6A9NtEPLt0w==", "dev": true }, - "jayson": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/jayson/-/jayson-2.1.0.tgz", - "integrity": "sha512-WQhCph4BgSDbRUPdZYqGMojKMxjzPqCCKmWYMsRWX/Bvh1oP+Irs2upeEJy8flU3ZAZzm68TjuL1X8u9Rt4wWQ==", - "requires": { - "@types/node": "^10.3.5", - "JSONStream": "^1.3.1", - "commander": "^2.12.2", - "es6-promisify": "^5.0.0", - "eyes": "^0.1.8", - "json-stringify-safe": "^5.0.1", - "lodash": "^4.17.10", - "uuid": "^3.2.1" - } - }, "jest": { "version": "23.6.0", "resolved": "https://registry.npmjs.org/jest/-/jest-23.6.0.tgz", @@ -5493,7 +5470,8 @@ "json-stringify-safe": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", + "dev": true }, "json5": { "version": "0.5.1", @@ -5513,7 +5491,8 @@ "jsonparse": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", - "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=" + "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=", + "dev": true }, "jsprim": { "version": "1.4.1", @@ -6194,7 +6173,8 @@ "lodash": { "version": "4.17.11", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", - "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==" + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", + "dev": true }, "lodash._reinterpolate": { "version": "3.0.0", @@ -12383,7 +12363,8 @@ "through": { "version": "2.3.8", "resolved": "http://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "dev": true }, "through2": { "version": "2.0.5", @@ -12924,7 +12905,8 @@ "uuid": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", - "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==", + "dev": true }, "validate-npm-package-license": { "version": "3.0.4", diff --git a/package.json b/package.json index e4d25a8..06da742 100644 --- a/package.json +++ b/package.json @@ -118,7 +118,5 @@ "typedoc": "^0.12.0", "typescript": "^3.0.3" }, - "dependencies": { - "jayson": "^2.1.0" - } + "dependencies": {} } diff --git a/src/Errors/Authentication.ts b/src/Errors/Authentication.ts new file mode 100644 index 0000000..0bacec3 --- /dev/null +++ b/src/Errors/Authentication.ts @@ -0,0 +1,20 @@ +/** + * Authentication Error + * + * @export + * @class AuthenticationError + * @extends {Error} + */ +export default class AuthenticationError extends Error { + /** + * Creates an instance of AuthenticationError. + * @param {string} [message=Authentication Error] - Error message + * @memberof AuthenticationError + */ + constructor(message?: string) { + if (!message) { + message = 'Authentication Error' + } + super(message) + } +} diff --git a/src/GridcoinRPC.ts b/src/GridcoinRPC.ts index fae0864..514bd14 100644 --- a/src/GridcoinRPC.ts +++ b/src/GridcoinRPC.ts @@ -1,5 +1,4 @@ -const jayson = require('jayson/promise') -import IConfig from './contracts/config' +import JsonRPC, { IParameters } from './JsonRPC' import IDifficulty from './contracts/difficulty' import ICpid from './contracts/cpid' import IBeaconStatus from './contracts/beaconStatus' @@ -26,11 +25,17 @@ import IInput from './contracts/input' import IOutput from './contracts/output' import ITransaction from './contracts/transaction' import IScript from './contracts/script' - -// square brackets [optional option] -// angle brackets -// curly braces {default values} -// parenthesis (miscellaneous info) +import IWalletInfo from './contracts/walletInfo' +import IReceivement from './contracts/receivement' +import IAddress from './contracts/address' +import IUnspent from './contracts/unspent' +import ITransactionShort from './contracts/transactionShort' +import IListSinceBlock from './contracts/listSinceBolck' +import ITransactionUnspent from './contracts/transactionUnspent' +import IKeysPair from './contracts/keysPair' +import IBurnAddress from './contracts/burnAddress' +import IReserve from './contracts/reserve' +import IRain from './contracts/rain' function filterParameters(parameters: Array): Array { return parameters.filter(element => element !== undefined) @@ -39,10 +44,10 @@ function filterParameters(parameters: Array): Array { type callParameters = string | number | boolean | undefined | Array class GridcoinRPC { - private readonly client: any + private readonly client: JsonRPC - constructor(config: IConfig) { - this.client = new jayson.client.http(config) + constructor(config: IParameters) { + this.client = new JsonRPC(config) } /** @@ -105,7 +110,7 @@ class GridcoinRPC { * @memberof GridcoinRPC */ public backupWallet(destination?: string): Promise { - return this.call('backupwallet') + return this.call('backupwallet', destination) } /** @@ -208,10 +213,559 @@ class GridcoinRPC { return this.call('dumpwallet', filename) } + /** + * @todo later + * + * @param {string} walletPassPhrase + * @returns + * @memberof GridcoinRPC + */ public encrypt(walletPassPhrase: string) { return this.call('encrypt', walletPassPhrase) } + /** + * @todo later + * + * @param {string} walletPassPhrase + * @returns + * @memberof GridcoinRPC + */ + public encryptWaller(walletPassPhrase: string) { + return this.call('encryptwaller', walletPassPhrase) + } + + /** + * Returns the account associated with the given address. + * + * @param {string} gridcoinAddress + * @returns {Promise} - an account name + * @memberof GridcoinRPC + */ + public getAccount(gridcoinAddress: string): Promise { + return this.call('getaccount', gridcoinAddress) + } + + /** + * Returns the current Gridcoin address for receiving payments to this account. + * @description + * If does not exist, it will be created along with an associated new address that will be returned. + * + * @param {string} account - an account name + * @returns {Promise} - GRC address + * @memberof GridcoinRPC + */ + public getAccountAddress(account: string): Promise { + return this.call('getaccountaddress', account) + } + + /** + * Returns the list of addresses for the given account. + * + * @param {string} account - the account name + * @returns {Promise>} - a list of addresses + * @memberof GridcoinRPC + */ + public getAddressesByAccount(account: string): Promise> { + return this.call('getaddressesbyaccount', account) + } + + /** + * If [account] is not specified, returns the server's total available balance. + * If [account] is specified, returns the balance in the account. + * + * @param {string} [account] + * @param {number} [minConf] + * @param {boolean} [includeWatchonly] + * @returns {Promise} + * @memberof GridcoinRPC + */ + public getBalance( + account?: string, + minConf?: number, + includeWatchonly?: boolean + ): Promise { + return this.call('getbalance', account, minConf, includeWatchonly) + } + + /** + * Returns a new Gridcoin address for receiving payments. + * @description + * If [account] is specified payments received with the address will be credited to [account]. + * + * @param {string} [account] + * @returns {Promise} + * @memberof GridcoinRPC + */ + public getNewAddress(account?: string): Promise { + return this.call('getnewaddress', account) + } + + /** + * @todo clarify + * + * @param {string} [account] + * @returns {Promise} + * @memberof GridcoinRPC + */ + public getNewPubkey(account?: string): Promise { + return this.call('getnewpubkey', account) + } + + /** + * Returns raw transaction representation for given transaction id. + * + * @param {string} txid + * @param {boolean} [verbose=false] + * @returns {(Promise)} + * @memberof GridcoinRPC + */ + public getRawTransaction(txid: string, verbose: boolean = false): Promise { + return this.call('getrawtransaction', txid, verbose) + } + + /** + * Returns the total amount received by addresses with [account] in transactions with at least [minconf] confirmations. + * If [account] not provided return will include all transactions to all accounts. + * + * @param {string} account - the account name + * @param {number} [minconf=1] - the minimum number of confirmations + * @returns {Promise} - the number of coins received + * @memberof GridcoinRPC + */ + public getReceivedByAccount(account: string, minconf: number = 1): Promise { + return this.call('getreceivedbyaccount', account, minconf) + } + + /** + * Returns the amount received by in transactions with at least [minconf] confirmations. + * @description + * It correctly handles the case where someone has sent to the address in multiple transactions. + * Keep in mind that addresses are only ever used for receiving transactions. + * Works only for addresses in the local wallet, external addresses will always show 0. + * + * @param {string} gridcoinAddress - the address + * @param {number} [minconf=1] - the minimum number of confirmations + * @returns {Promise} - the number of coins received + * @memberof GridcoinRPC + */ + public getReceivedByAddress(gridcoinAddress: string, minconf: number = 1): Promise { + return this.call('getreceivedbyaddress', gridcoinAddress, minconf) + } + + /** + * Adds signatures to a raw transaction and returns the resulting raw transaction. + * + * @param {string} txid - transaction identifier (TXID) + * @returns {Promise} - description of the transaction + * @memberof GridcoinRPC + */ + public getTransaction(txid: string): Promise { + return this.call('gettransaction', txid) + } + + /** + * Returns useful information about current wallet state. + * + * @returns {Promise} + * @memberof GridcoinRPC + */ + public getWalletInfo(): Promise { + return this.call('getwalletinfo') + } + + /** + * @todo check + * + * @param {string} privKey + * @param {string} [label] + * @param {boolean} [rescan] + * @returns + * @memberof GridcoinRPC + */ + public importPrivKey(privKey: string, label?: string, rescan?: boolean) { + return this.call('importprivkey', privKey, label, rescan) + } + + /** + * @todo test + * + * @param {string} filename + * @returns + * @memberof GridcoinRPC + */ + public importWallet(filename: string) { + return this.call('importwallet', filename) + } + + /** + * @todo check + * Fills the keypool, requires wallet passphrase to be set. + * + * @param {*} newSize + * @returns + * @memberof GridcoinRPC + */ + public keyPoolRefill(newSize: number) { + return this.call('keypoolrefill', newSize) + } + + /** + * @todo check + * + * @returns + * @memberof GridcoinRPC + */ + public listAccounts() { + return this.call('listaccounts') + } + + /** + * Returns all addresses in the wallet and info used for coincontrol. + * + * @returns {(Promise>>)} + * @memberof GridcoinRPC + */ + public listAddressGroupings(): Promise>> { + return this.call('listaddressgroupings') + } + + /** + * @todo check + * + * @param {number} [minconf=1] + * @param {boolean} [includeEmpty=false] + * @returns + * @memberof GridcoinRPC + */ + public listReceivedByAccount(minconf: number = 1, includeEmpty: boolean = false) { + return this.call('listreceivedbyaccount', minconf, includeEmpty) + } + + /** + * Returns an array of objects containing: address, account, amount, confirmations. + * @description + * To get a list of accounts on the system, gridcoind listreceivedbyaddress 0 true. + * + * @param {number} [minConf=1] - The minimum number of confirmations before payments are included. + * @param {boolean} [includeEmpty=false] - Whether to include addresses that haven't received any payments. + * @returns {Promise} + * @memberof GridcoinRPC + */ + public listReceivedByAddress( + minConf: number = 1, + includeEmpty: boolean = false + ): Promise { + return this.call('listreceivedbyaddress', minConf, includeEmpty) + } + + /** + * Get all transactions in blocks since block [blockhash], or all transactions if omitted + * + * @param {string} [blockHash] + * @param {number} [targetConfirmations] + * @param {boolean} [includeWatchonly] + * @returns {Promise} + * @memberof GridcoinRPC + */ + public listSinceBlock( + blockHash?: string, + targetConfirmations?: number, + includeWatchonly?: boolean + ): Promise { + return this.call('listsinceblock', blockHash, targetConfirmations, includeWatchonly) + } + + /** + * returns the most recent transactions that affect the wallet + * + * @param {string} [account] + * @param {*} [count] + * @param {*} [from] + * @param {boolean} [includeWatchonly] + * @returns {Promise} + * @memberof GridcoinRPC + */ + public listTransactions( + account?: string, + count?: any, + from?: any, + includeWatchonly?: boolean + ): Promise> { + return this.call('listtransactions', account, count, from, includeWatchonly) + } + + /** + * + * + * @param {number} [minConf] + * @param {number} [maxConf] + * @param {...Array} addresses + * @returns {Promise>} + * @memberof GridcoinRPC + */ + public listUnspent( + minConf?: number, + maxConf?: number, + ...addresses: Array + ): Promise> { + return this.call('listunspent', minConf, maxConf, ...addresses) + } + + /** + * Make a public/private key pair. + * + * @param {string} [prefix] + * @returns {Promise} + * @memberof GridcoinRPC + */ + public makeKeyPair(prefix?: string): Promise { + return this.call('makekeypair', prefix) + } + + /** + * @deprecated + * + * @param {number} fromAccount + * @param {number} toAccount + * @param {number} amount + * @param {number} [minConf] + * @param {string} [comment] + * @returns + * @memberof GridcoinRPC + */ + public move( + fromAccount: number, + toAccount: number, + amount: number, + minConf?: number, + comment?: string + ) { + return this.call('move', fromAccount, toAccount, amount, minConf, comment) + } + + /** + * @todo document + * + * @param {string} [burntemplate] + * @returns {Promise} + * @memberof GridcoinRPC + */ + public newBurnAddress(burntemplate?: string): Promise { + return this.call('newburnaddress', burntemplate) + } + + /** + * Rain on specific addresses with specific amounts. + * @todo: Failing + * Requires unlocked wallet + * + * @param {IRain} targets + * @returns + * @memberof GridcoinRPC + */ + public rain(targets: IRain) { + return this.call('rain', JSON.stringify(targets)) + } + + /** + * Check wallet.dat for missing coins. If any are found, attempt recovery. + * + * @returns {Promise} + * @memberof GridcoinRPC + */ + public repairWallet(): Promise { + return this.call('repairwallet') + } + + /** + * Resend any failed or unsent transactions. + * Requires unlocked wallet + * + * @returns {Promise} + * @memberof GridcoinRPC + */ + public resendTx(): Promise { + return this.call('resendtx') + } + + /** + * Reserve an amount of coins that do not participate in staking. + * + * @param {boolean} reserve + * @param {number} [amount] + * @returns {Promise} + * @memberof GridcoinRPC + */ + public reserveBalance(reserve: boolean, amount?: number): Promise { + return this.call('reservebalance', reserve, amount) + } + + public sendFrom( + fromAccount: string, + toGridcoinaddress: string, + amount: number, + minConf?: number, + comment?: string, + commentTo?: string + ) { + return this.call( + 'sendfrom', + fromAccount, + toGridcoinaddress, + amount, + minConf, + comment, + commentTo + ) + } + + // sendmany {address:amount,...} [minconf=1] [comment] + public sendMany(fromAccount: string) { + return this.call('sendmany') + } + + /** + * Submits raw transaction (serialized, hex-encoded) to local node and network. + * + * @param {string} hex - serialized transaction to broadcast + * @returns {Promise} - TXID or error message + * @memberof GridcoinRPC + */ + public sendRawTransaction(hex: string): Promise { + return this.call('sendrawtransaction', hex) + } + + /** + * Spend an amount to a given address. + * Requires unlocked wallet + * + * @param {string} gridcoinAddress - target gridcoin address + * @param {number} amount - amount to spend, real number and is rounded to 8 decimal places. + * @param {string} [comment] - comment + * @param {string} [commentTo] - comment about who the payment was sent to + * @returns {Promise} - TXID of the sent transaction + * @memberof GridcoinRPC + */ + public sendToAddress( + gridcoinAddress: string, + amount: number, + comment?: string, + commentTo?: string + ): Promise { + return this.call('sendtoaddress', gridcoinAddress, amount, comment, commentTo) + } + + /** + * Sets the account associated with the given address. + * Assigning address that is already assigned to the same account will create a new address associated with that account. + * + * @param {string} gridcoinAddress + * @param {string} account + * @returns {Promise} + * @memberof GridcoinRPC + */ + public setAccount(gridcoinAddress: string, account: string): Promise { + return this.call('setaccount', gridcoinAddress, account) + } + + // signrawtransaction [{"txid":txid,"vout":n,"scriptPubKey":hex},...] [,...] [sighashtype="ALL"] + // Adds signatures to a raw transaction and returns the resulting raw transaction. + /** + * @todo + * + * @param {string} hexString + * @returns + * @memberof GridcoinRPC + */ + public signRawTransaction(hexString: string) { + return this.call('signrawtransaction') + } + + /** + * Set the transaction fee per kilobyte paid by transactions created by this wallet. + * + * @param {number} amount - is a real and is rounded to the nearest 0.00000001 + * @returns {Promise} + * @memberof GridcoinRPC + */ + public setTxFee(amount: number): Promise { + return this.call('settxfee', amount) + } + + /** + * Display a report on unspent coins in the wallet. + * + * @returns {Promise>} + * @memberof GridcoinRPC + */ + public unspentReport(): Promise> { + return this.call('unspentreport') + } + + /** + * Return information about . + * @description + * The validateaddress RPC accepts a block + * verifies it is a valid addition to the block chain + * and broadcasts it to the network. + * + * @param {string} gridcoinAddress + * @returns {Promise} + * @memberof GridcoinRPC + */ + public validateAddress(gridcoinAddress: string): Promise { + return this.call('validateaddress', gridcoinAddress) + } + + /** + * Return information about . + * + * @param {string} gridcoinPubkey + * @returns {Promise} + * @memberof GridcoinRPC + */ + public validatePubkey(gridcoinPubkey: string): Promise { + return this.call('validatepubkey', gridcoinPubkey) + } + + /** + * Sign a message with the private key of an address. + * Requires unlocked wallet + * + * @param {string} gridcoinAddress - the address corresponding to the private key to sign with + * @param {string} message - the message to sign + * @returns {Promise} - message signature + * @memberof GridcoinRPC + */ + public signMessage(gridcoinAddress: string, message: string): Promise { + return this.call('signmessage', gridcoinAddress, message) + } + + /** + * Verify a signed message. + * @description + * The P2PKH address corresponding to the private key which made the signature. + * A P2PKH address is a hash of the public key corresponding to the private key which made the signature. + * When the ECDSA signature is checked, up to four possible ECDSA public keys will be reconstructed from the signature; + * each key will be hashed and compared against the P2PKH address provided to see if any of them match. + * If there are no matches, signature validation will fail. + * + * @param {string} gridcoinAddress - The gridcoin address to use for the signature. + * @param {string} signature - The signature provided by the signer in base 64 encoding. + * @param {string} message - The message that was signed. + * @returns {Promise} + * @memberof GridcoinRPC + * @see signmessage + */ + public verifyMessage( + gridcoinAddress: string, + signature: string, + message: string + ): Promise { + return this.call('verifymessage', gridcoinAddress, signature, message) + } + /******************************************** * * NETWORK @@ -243,6 +797,7 @@ class GridcoinRPC { * Requires unlocked wallet * @todo implement */ + // tslint:disable-next-line public addPoll() {} /** @@ -339,8 +894,8 @@ class GridcoinRPC { * @returns {Promise} * @memberof GridcoinRPC */ - public getBlockByNumber(number: number, txinfo?: boolean): Promise { - return this.call('getblockbynumber', number, txinfo) + public getBlockByNumber(blockNumber: number, txinfo?: boolean): Promise { + return this.call('getblockbynumber', blockNumber, txinfo) } /** @@ -828,4 +1383,4 @@ class GridcoinRPC { } } -export default GridcoinRPC +export { GridcoinRPC } diff --git a/src/JsonRPC.ts b/src/JsonRPC.ts new file mode 100644 index 0000000..837b876 --- /dev/null +++ b/src/JsonRPC.ts @@ -0,0 +1,168 @@ +import http from 'http' +import https from 'https' +import AuthenticationError from './Errors/Authentication' + +/** + * Command interface + * + * @interface IRequestJSON + */ +interface IRequestJSON { + id: number + method: string + params: any +} + +interface IProviders { + http: any + https: any +} + +/** + * Class constructor parameters + * + * @interface IParameters + */ +export interface IParameters { + /** + * Gridcoin RPC server (wallet) host + * + * @type {string} + * @memberof IParameters + */ + host: string + /** + * Gridcoin RPC server (wallet) port + * + * @type {number} + * @memberof IParameters + */ + port: number + path?: string + /** + * Username (if specified) + * + * @type {string} + * @memberof IParameters + */ + username?: string + /** + * Password (if specified) + * + * @type {string} + * @memberof IParameters + */ + password?: string + ca?: string | Buffer | Array + ssl?: boolean + sslStrict?: boolean + providers?: IProviders +} + +/** + * + * + * @class JsonRPC + */ +class JsonRPC { + private host: string + private port: number + private path: string + private auth?: string + private ssl: boolean + private sslStrict: boolean + private ca?: string | Buffer | Array + private provider: any + + /** + * Creates an instance of JsonRPC. + * @param {IParameters} parameters + * @memberof JsonRPC + */ + constructor(parameters: IParameters) { + this.host = parameters.host + this.path = parameters.path || '/' + this.port = parameters.port + if (parameters.username && parameters.password) { + this.auth = `${parameters.username}:${parameters.password}` + } + this.ca = parameters.ca + this.ssl = !!parameters.ssl + this.sslStrict = !parameters.sslStrict + const providers: IProviders = parameters.providers || { http, https } + if (this.ssl) { + this.provider = providers.https.request + } else { + this.provider = providers.http.request + } + } + + /** + * Make actual request + * + * @param {string} command + * @param {*} parameters + * @returns {Promise} + * @memberof JsonRPC + */ + request(command: string, parameters: Array): Promise { + const request: IRequestJSON = { + id: Date.now(), + method: command, + params: parameters + } + const strigifyRequest: string = JSON.stringify(request) + + const requestOptions: http.ClientRequestArgs | https.RequestOptions = { + host: this.host, + port: this.port, + method: 'POST', + path: this.path, + headers: { + Host: this.host, + 'Content-Length': strigifyRequest.length + }, + agent: false, + rejectUnauthorized: this.ssl && this.sslStrict !== false + } + + if (this.auth) { + requestOptions.auth = this.auth + } + + if (this.ssl) { + requestOptions.ca = this.ca + } + + // Send request + return new Promise((resolve: any, reject: any) => { + const request = this.provider(requestOptions) + + request.end(strigifyRequest) + + request.on('error', reject) + + request.on('response', (response: any) => { + let buffer: string = '' + if (response.statusCode === 401) { + reject(new AuthenticationError()) + return + } + // console.log(response.statusCode); + response.on('data', (chunk: string) => { + buffer += chunk + }) + response.on('end', () => { + try { + const decoded: Object = JSON.parse(buffer) + resolve(decoded) + } catch (e) { + reject(e) + } + }) + }) + }) + } +} + +export default JsonRPC diff --git a/test/JsonRPC.spec.ts b/test/JsonRPC.spec.ts new file mode 100644 index 0000000..4893668 --- /dev/null +++ b/test/JsonRPC.spec.ts @@ -0,0 +1,18 @@ +import JsonRpc from '../src/JsonRPC' + +const httpMock = { + request: () => {} +} +const httpsMock = { + request: () => {} +} + +describe('JsonRpc', () => { + it('should create rpc object', () => { + const jsonRpc = new JsonRpc({ + host: 'localhost', + port: 12345 + }) + expect(jsonRpc).toBeInstanceOf(JsonRpc) + }) +})