diff --git a/README.md b/README.md index f10b85a6..93a5e95b 100644 --- a/README.md +++ b/README.md @@ -135,9 +135,55 @@ After that you can use the `ckb` object to generate addresses, send requests, et # RPC -## Default RPC +## Basic RPC -Please see [Default RPC](https://github.com/nervosnetwork/ckb-sdk-js/blob/develop/packages/ckb-sdk-rpc/src/defaultRPC.ts#L188) +Please see [Basic RPC](https://github.com/nervosnetwork/ckb-sdk-js/blob/develop/packages/ckb-sdk-rpc/src/Base.ts#L161) + +## Batch Request + +```javascript +/** + * The following batch includes two requests + * 1. getBlock('0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee') + * 2. getTransactionsByLockHash('0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee', '0x0', '0x1) + */ +const batch = rpc.createBatchRequest([ + ['getBlock', '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee'], + ['getTransactionsByLockHash', '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee', '0x0', '0x1'], +]) + +/** + * Add a request and the batch should include three requests + * 1. getBlock('0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee') + * 2. getTransactionsByLockHash('0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee', '0x0', '0x1) + * 3. getTransactionsByLockHash('0xcccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc', '0x0', '0x1) + */ +batch.add( + 'getTransactionsByLockHash', + '0xcccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc', + '0x0', + '0x1', +) + +/** + * Remove a request by index and the batch should include two requests + * 1. getBlock('0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee') + * 2. getTransactionsByLockHash('0xcccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc', '0x0', '0x1) + */ +batch.remove(1) + +/** + * Send the batch request + */ +batch.exec().then(console.log) +// [ +// { "header": { }, "uncles": [], "transactions": [], "proposals": [] }, +// [ { "consumedBy": { }, "createdBy": { } } ] +// ] + +// chaning usage +batch.add().remove().exec() +``` ## Persistent Connection diff --git a/codecov.yaml b/codecov.yaml index 9e8743e6..809d048b 100644 --- a/codecov.yaml +++ b/codecov.yaml @@ -4,22 +4,17 @@ codecov: coverage: precision: 2 round: down - range: '70...100' + range: '90...100' status: project: default: target: auto - threshold: 20 + threshold: 50 base: auto - patch: - default: - target: auto - threshold: 20 - base: auto + patch: off changes: yes - parsers: gcov: branch_detection: @@ -32,3 +27,7 @@ comment: layout: 'reach,diff,flags,tree' behavior: default require_changes: no + +ignore: + # Tested but not recognized by codecov + - 'packages/ckb-sdk-rpc/src/index.ts' diff --git a/packages/ckb-sdk-core/__tests__/signWitnessGroup/fixtures.json b/packages/ckb-sdk-core/__tests__/signWitnessGroup/fixtures.json new file mode 100644 index 00000000..65229224 --- /dev/null +++ b/packages/ckb-sdk-core/__tests__/signWitnessGroup/fixtures.json @@ -0,0 +1,45 @@ +{ + "empty witnesses": { + "privateKey": "0xdcec27d0d975b0378471183a03f7071dea8532aaf968be796719ecd20af6988f", + "transactionHash": "0x4a4bcfef1b7448e27edf533df2f1de9f56be05eba645fb83f42d55816797ad2a", + "witnesses": [], + "exception": "WitnessGroup cannot be empty" + }, + "first one is witness args": { + "privateKey": "0xdcec27d0d975b0378471183a03f7071dea8532aaf968be796719ecd20af6988f", + "transactionHash": "0x4a4bcfef1b7448e27edf533df2f1de9f56be05eba645fb83f42d55816797ad2a", + "witnesses": [ + "0x", + { + "lock": "", + "inputType": "", + "outputType": "" + } + ], + "exception": "The first witness in the group should be type of WitnessArgs" + }, + "Should pass": { + "privateKey": "0xdcec27d0d975b0378471183a03f7071dea8532aaf968be796719ecd20af6988f", + "transactionHash": "0x4a4bcfef1b7448e27edf533df2f1de9f56be05eba645fb83f42d55816797ad2a", + "witnesses": [ + { + "lock": "", + "inputType": "", + "outputType": "" + }, + { + "lock": "", + "inputType": "", + "outputType": "" + } + ], + "expected": [ + "0x55000000100000005500000055000000410000005bc073bf55db333d5680ddf36e4814b9ce2118cfe4504f95c7d3e9a7548e16886cf1a1481fd80ce70d5e19108a43fd17fa32aad0d46c30c3410001ed2934ad2f00", + { + "lock": "", + "inputType": "", + "outputType": "" + } + ] + } +} diff --git a/packages/ckb-sdk-core/__tests__/signWitnessGroup/index.test.js b/packages/ckb-sdk-core/__tests__/signWitnessGroup/index.test.js new file mode 100644 index 00000000..7c489b0e --- /dev/null +++ b/packages/ckb-sdk-core/__tests__/signWitnessGroup/index.test.js @@ -0,0 +1,25 @@ +const signWitnessGroup = require('../../lib/signWitnessGroup').default +const fixtures = require('./fixtures.json') + +describe('test sign witness group', () => { + const fixtureTable = Object.entries( + fixtures, + ).map(([title, { privateKey, transactionHash, witnesses, expected, exception }]) => [ + title, + privateKey, + transactionHash, + witnesses, + exception, + expected, + ]) + + test.each(fixtureTable)('%s', (_title, privateKey, transactionHash, witnesses, exception, expected) => { + expect.assertions(1) + if (exception !== undefined) { + expect(() => signWitnessGroup(privateKey, transactionHash, witnesses)).toThrowError(exception) + } else if (privateKey !== undefined) { + const signedWitnessGroup = signWitnessGroup(privateKey, transactionHash, witnesses) + expect(signedWitnessGroup).toEqual(expected) + } + }) +}) diff --git a/packages/ckb-sdk-rpc/__tests__/ckb-rpc-helpers.js b/packages/ckb-sdk-rpc/__tests__/ckb-rpc-helpers.js index dd5db6e1..eb9e423d 100644 --- a/packages/ckb-sdk-rpc/__tests__/ckb-rpc-helpers.js +++ b/packages/ckb-sdk-rpc/__tests__/ckb-rpc-helpers.js @@ -32,8 +32,8 @@ describe('ckb-rpc settings and helpers', () => { expect(rpc.node.httpsAgent).toBeDefined() }) - it('has 31 default rpc', () => { - expect(rpc.methods.length).toBe(31) + it('has 31 basic rpc', () => { + expect(Object.values(rpc)).toHaveLength(31) }) it('set node url to http://test.localhost:8114', () => { diff --git a/packages/ckb-sdk-rpc/__tests__/ckb-rpc.test.js b/packages/ckb-sdk-rpc/__tests__/ckb-rpc.test.js index afe756f4..26c66a87 100644 --- a/packages/ckb-sdk-rpc/__tests__/ckb-rpc.test.js +++ b/packages/ckb-sdk-rpc/__tests__/ckb-rpc.test.js @@ -6,7 +6,7 @@ const axiosMock = require('axios') const CKBRPC = require('../lib').default describe('Test with mock', () => { - const rpc = new CKBRPC() + const rpc = new CKBRPC('http://localhost:8114') const ranNum = 1 const id = Math.round(ranNum * 10000) @@ -18,6 +18,12 @@ describe('Test with mock', () => { jest.restoreAllMocks() }) + describe('properties', () => { + expect(rpc.paramsFormatter).not.toBeUndefined() + expect(rpc.resultFormatter).not.toBeUndefined() + expect(rpc.node.url).toBe('http://localhost:8114') + }) + describe('ckb-rpc success', () => { afterEach(() => { jest.clearAllMocks() @@ -1269,11 +1275,225 @@ describe('Test with mock', () => { }) expect(res).toEqual('0xa0ef4eb5f4ceeb08a4c8524d84c5da95dce2f608e0ca2ec8091191b0f330c6e3') }) + + describe('batch request', () => { + const batch = rpc.createBatchRequest([ + ['getBlock', '0xffd50ddb91a842234ff8f0871b941a739928c2f4a6b5cfc39de96a3f87c2413e'], + ]) + + it('should has init request', () => { + expect(batch).toEqual([['getBlock', '0xffd50ddb91a842234ff8f0871b941a739928c2f4a6b5cfc39de96a3f87c2413e']]) + }) + + it('should add a new request', () => { + batch.add('getTipHeader') + expect(batch).toEqual([ + ['getBlock', '0xffd50ddb91a842234ff8f0871b941a739928c2f4a6b5cfc39de96a3f87c2413e'], + ['getTipHeader'], + ]) + }) + + it('should remove a request', () => { + batch.add('localNodeInfo') + expect(batch).toEqual([ + ['getBlock', '0xffd50ddb91a842234ff8f0871b941a739928c2f4a6b5cfc39de96a3f87c2413e'], + ['getTipHeader'], + ['localNodeInfo'], + ]) + batch.remove(1) + expect(batch).toEqual([ + ['getBlock', '0xffd50ddb91a842234ff8f0871b941a739928c2f4a6b5cfc39de96a3f87c2413e'], + ['localNodeInfo'], + ]) + }) + + it('should accept params in list', () => { + const multiParamBatch = rpc.createBatchRequest([['getCellsByLockHash', '0x0', '0x1', '0x2'], ['getPeers']]) + expect(multiParamBatch).toEqual([['getCellsByLockHash', '0x0', '0x1', '0x2'], ['getPeers']]) + multiParamBatch.add('getCellsByLockHash', '0x0', '0x1', '0x2') + expect(multiParamBatch).toEqual([ + ['getCellsByLockHash', '0x0', '0x1', '0x2'], + ['getPeers'], + ['getCellsByLockHash', '0x0', '0x1', '0x2'], + ]) + }) + + it('should parse request and response correctly', async () => { + axiosMock.mockResolvedValue({ + data: [ + { + jsonrpc: '2.0', + result: { + header: { + compact_target: '0x20010000', + dao: '0x1d78d68e4363a12ee3e511f1fa862300f091bde0110f00000053322801fbfe06', + epoch: '0xa0002000000', + hash: '0xffd50ddb91a842234ff8f0871b941a739928c2f4a6b5cfc39de96a3f87c2413e', + nonce: '0x4e51c6b50fd5a1af81c1d0c770a23c93', + number: '0x2', + parent_hash: '0x4aa1bf4930b2fbcebf70bd0b6cc63a19ae8554d6c7e89a666433040300641db9', + proposals_hash: '0x0000000000000000000000000000000000000000000000000000000000000000', + timestamp: '0x1725940cb91', + transactions_root: '0xcd95e31e21734fb796de0070407c1d4f91ec00d699f840e5ad9aa293443744e6', + uncles_hash: '0x0000000000000000000000000000000000000000000000000000000000000000', + version: '0x0', + }, + proposals: [], + transactions: [ + { + cell_deps: [], + hash: '0xdb9e84bc7bf583f0d0f2dcd82a41229bf52cfa45edbedfb7a4d0d3120b8e4066', + header_deps: [], + inputs: [ + { + previous_output: { + index: '0xffffffff', + tx_hash: '0x0000000000000000000000000000000000000000000000000000000000000000', + }, + since: '0x2', + }, + ], + outputs: [], + outputs_data: [], + version: '0x0', + witnesses: [ + '0x5a0000000c00000055000000490000001000000030000000310000009bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce80114000000e2fa82e70b062c8644b80ad7ecf6e015e5f352f60100000000', + ], + }, + ], + uncles: [], + }, + id, + }, + { + jsonrpc: '2.0', + result: { + addresses: [ + { + address: '/ip4/0.0.0.0/tcp/8115/p2p/eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee', + score: '0x1', + }, + ], + is_outbound: null, + node_id: 'eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee', + version: '0.32.0 (248aa88 2020-05-22)', + }, + id, + }, + ], + }) + const res = await batch.exec() + expect(axiosMock.mock.calls[0][0].data).toEqual([ + { + id, + jsonrpc: '2.0', + method: 'get_block', + params: ['0xffd50ddb91a842234ff8f0871b941a739928c2f4a6b5cfc39de96a3f87c2413e'], + }, + { id, jsonrpc: '2.0', method: 'local_node_info', params: [] }, + ]) + expect(res).toEqual([ + { + header: { + compactTarget: '0x20010000', + dao: '0x1d78d68e4363a12ee3e511f1fa862300f091bde0110f00000053322801fbfe06', + epoch: '0xa0002000000', + hash: '0xffd50ddb91a842234ff8f0871b941a739928c2f4a6b5cfc39de96a3f87c2413e', + nonce: '0x4e51c6b50fd5a1af81c1d0c770a23c93', + number: '0x2', + parentHash: '0x4aa1bf4930b2fbcebf70bd0b6cc63a19ae8554d6c7e89a666433040300641db9', + proposalsHash: '0x0000000000000000000000000000000000000000000000000000000000000000', + timestamp: '0x1725940cb91', + transactionsRoot: '0xcd95e31e21734fb796de0070407c1d4f91ec00d699f840e5ad9aa293443744e6', + unclesHash: '0x0000000000000000000000000000000000000000000000000000000000000000', + version: '0x0', + }, + proposals: [], + transactions: [ + { + cellDeps: [], + hash: '0xdb9e84bc7bf583f0d0f2dcd82a41229bf52cfa45edbedfb7a4d0d3120b8e4066', + headerDeps: [], + inputs: [ + { + previousOutput: { + index: '0xffffffff', + txHash: '0x0000000000000000000000000000000000000000000000000000000000000000', + }, + since: '0x2', + }, + ], + outputs: [], + outputsData: [], + version: '0x0', + witnesses: [ + '0x5a0000000c00000055000000490000001000000030000000310000009bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce80114000000e2fa82e70b062c8644b80ad7ecf6e015e5f352f60100000000', + ], + }, + ], + uncles: [], + }, + { + addresses: [ + { + address: '/ip4/0.0.0.0/tcp/8115/p2p/eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee', + score: '0x1', + }, + ], + isOutbound: null, + nodeId: 'eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee', + version: '0.32.0 (248aa88 2020-05-22)', + }, + ]) + }) + }) }) describe('ckb-rpc errors', () => { it('throw raw error', async () => { expect(() => rpc.getBlock(0)).toThrow('Hash 0 should be type of string') }) + + describe('batch request', () => { + it('should throw method not found error', () => { + expect(() => rpc.createBatchRequest([['Unknown', []]])).toThrow('[Batch Request]: Method Unknown is not found') + }) + + describe('should throw errors with index', () => { + it('should throw an error of validation', () => { + expect.assertions(1) + const batch = rpc.createBatchRequest([['getBlock', [0]]]) + batch + .exec() + .catch(err => expect(err).toEqual(new Error(`[Batch Request 0]: Hash 0 should be type of string`))) + }) + + it('should return an error of mismatched json rpc id', async () => { + expect.assertions(1) + const batch = rpc.createBatchRequest([['localNodeInfo']]) + axiosMock.mockResolvedValue({ + data: [ + { + id: id + 1, + jsonrpc: '2.0', + result: { + addresses: [ + { + address: '/ip4/0.0.0.0/tcp/8115/p2p/eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee', + score: '0x1', + }, + ], + is_outbound: null, + node_id: 'eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee', + version: '0.32.0 (248aa88 2020-05-22)', + }, + }, + ], + }) + const res = await batch.exec() + expect(res[0]).toEqual(new Error(`[Batch Request 0]: JSONRPC id not matched`)) + }) + }) + }) }) }) diff --git a/packages/ckb-sdk-rpc/__tests__/formatters/params.fixtures.json b/packages/ckb-sdk-rpc/__tests__/formatters/params.fixtures.json index 3d316cdd..f60ee4ae 100644 --- a/packages/ckb-sdk-rpc/__tests__/formatters/params.fixtures.json +++ b/packages/ckb-sdk-rpc/__tests__/formatters/params.fixtures.json @@ -370,6 +370,10 @@ } ], "toOutputsValidator": [ + { + "params": null, + "expected": "undefined" + }, { "param": "default", "expected": "default" diff --git a/packages/ckb-sdk-rpc/__tests__/formatters/result.fixtures.json b/packages/ckb-sdk-rpc/__tests__/formatters/result.fixtures.json index 87a237d7..c3e51bf5 100644 --- a/packages/ckb-sdk-rpc/__tests__/formatters/result.fixtures.json +++ b/packages/ckb-sdk-rpc/__tests__/formatters/result.fixtures.json @@ -1303,6 +1303,10 @@ } ], "toCapacityByLockHash": [ + { + "result": null, + "expected": null + }, { "result": { "block_number": "0x400", diff --git a/packages/ckb-sdk-rpc/__tests__/method.test.js b/packages/ckb-sdk-rpc/__tests__/method.test.js new file mode 100644 index 00000000..0786366c --- /dev/null +++ b/packages/ckb-sdk-rpc/__tests__/method.test.js @@ -0,0 +1,54 @@ +jest.mock('axios') +const axiosMock = require('axios') +const Method = require('../lib/method').default + +describe('Test Method', () => { + const ranNum = 1 + const id = Math.round(ranNum * 10000) + const NODE = { url: 'http://localhost:8114' } + const PROPERTIES = { + name: 'method name', + method: 'raw_method', + paramsFormatters: [], + } + const method = new Method(NODE, PROPERTIES) + + beforeAll(() => { + jest.spyOn(global.Math, 'random').mockReturnValue(ranNum) + }) + + afterAll(() => { + jest.restoreAllMocks() + }) + afterEach(() => { + axiosMock.mockClear() + }) + + it('has properties', () => { + expect(method.name).toBe(PROPERTIES.name) + }) + + it('jsonrpc id mismatched', async () => { + expect.assertions(1) + axiosMock.mockResolvedValue({ + data: { + id: id + 1, + jsonrpc: '2.0', + result: null, + }, + }) + await method.call().catch(err => expect(err).toEqual(new Error(`JSONRPC id not matched`))) + }) + + it('returns with error', async () => { + expect.assertions(1) + axiosMock.mockResolvedValue({ + data: { + id, + jsonrpc: '2.0', + error: 'mock error', + }, + }) + await method.call().catch(err => expect(err).toEqual(new Error('"mock error"'))) + }) +}) diff --git a/packages/ckb-sdk-rpc/src/Base.ts b/packages/ckb-sdk-rpc/src/Base.ts index 7fa7c7f2..6b861e5d 100644 --- a/packages/ckb-sdk-rpc/src/Base.ts +++ b/packages/ckb-sdk-rpc/src/Base.ts @@ -1,7 +1,7 @@ import paramsFmts from './paramsFormatter' import resultFmts from './resultFormatter' -interface RpcPropertes { +export interface RpcPropertes { [name: string]: Omit } @@ -18,7 +18,7 @@ export const rpcProperties: RpcPropertes = { }, getTransaction: { method: 'get_transaction', - paramsFormatters: [paramsFmts.toHash, paramsFmts.toNumber, paramsFmts.toNumber], + paramsFormatters: [paramsFmts.toHash], resultFormatters: resultFmts.toTransactionWithStatus, }, getBlockHash: { @@ -478,7 +478,11 @@ export interface Base { } export class Base { - protected rpcProperties = rpcProperties + #rpcProperties = rpcProperties + + get rpcProperties() { + return this.#rpcProperties + } } export default Base diff --git a/packages/ckb-sdk-rpc/src/index.ts b/packages/ckb-sdk-rpc/src/index.ts index 932501e5..b04bdaef 100644 --- a/packages/ckb-sdk-rpc/src/index.ts +++ b/packages/ckb-sdk-rpc/src/index.ts @@ -1,5 +1,6 @@ /// +import axios from 'axios' import Base from './Base' import Method from './method' @@ -7,21 +8,36 @@ import paramsFormatter from './paramsFormatter' import resultFormatter from './resultFormatter' class CKBRPC extends Base { - public node: CKBComponents.Node = { + #node: CKBComponents.Node = { url: '', } - public methods: Method[] = [] + get node() { + return this.#node + } + + #paramsFormatter = paramsFormatter + + get paramsFormatter() { + return this.#paramsFormatter + } - public paramsFormatter = paramsFormatter + #resultFormatter = resultFormatter - public resultFormatter = resultFormatter + get resultFormatter() { + return this.#resultFormatter + } constructor(url: string) { super() - this.setNode({ - url, + this.setNode({ url }) + + Object.defineProperties(this, { + addMethod: { value: this.addMethod, enumerable: false, writable: false, configurable: false }, + setNode: { value: this.setNode, enumerable: false, writable: false, configurable: false }, + createBatchRequest: { value: this.createBatchRequest, enumerable: false, writable: false, configurable: false }, }) + Object.keys(this.rpcProperties).forEach(name => { this.addMethod({ name, ...this.rpcProperties[name] }) }) @@ -34,13 +50,82 @@ class CKBRPC extends Base { public addMethod = (options: CKBComponents.Method) => { const method = new Method(this.node, options) - this.methods.push(method) Object.defineProperty(this, options.name, { value: method.call, enumerable: true, }) } + + public createBatchRequest = ( + params: [N, P][] = [], + ) => { + const ctx = this + const ERROR_LABEL = 'Batch Request' + + const proxied: [N, P][] = new Proxy([], { + set(...p) { + const methods = Object.keys(ctx) + if (p[1] !== 'length') { + const name = p?.[2]?.[0] + if (methods.indexOf(name) === -1) { + throw new Error(`[${ERROR_LABEL}]: Method ${name} is not found`) + } + } + return Reflect.set(...p) + }, + }) + + Object.defineProperties(proxied, { + add: { + value(...args: any) { + this.push(args) + return this + }, + }, + remove: { + value(i: number) { + this.splice(i, 1) + return this + }, + }, + exec: { + async value() { + const payload = proxied.map(([f, ...p], i) => { + try { + const method = new Method(ctx.node, { ...ctx.rpcProperties[f], name: f }) + return method.getPayload(...p) + } catch (err) { + throw new Error(`[${ERROR_LABEL} ${i}]: ${err.message}`) + } + }) + + const batchRes = await axios({ + method: 'POST', + headers: { 'content-type': 'application/json' }, + data: payload, + url: ctx.#node.url, + httpAgent: ctx.#node.httpAgent, + httpsAgent: ctx.#node.httpsAgent, + }) + + return batchRes.data.map((res: any, i: number) => { + if (res.id !== payload[i].id) { + return new Error(`[${ERROR_LABEL} ${i}]: JSONRPC id not matched`) + } + return ctx.rpcProperties[proxied[i][0]].resultFormatters?.(res.result) ?? res.result + }) + }, + }, + }) + params.forEach(p => proxied.push(p)) + + return proxied as typeof proxied & { + add: (n: N, ...p: P) => typeof proxied + remove: (index: number) => typeof proxied + exec: () => Promise + } + } } export default CKBRPC diff --git a/packages/ckb-sdk-rpc/src/method.ts b/packages/ckb-sdk-rpc/src/method.ts index 79fd68d0..7c0e0ed6 100644 --- a/packages/ckb-sdk-rpc/src/method.ts +++ b/packages/ckb-sdk-rpc/src/method.ts @@ -1,48 +1,61 @@ import axios from 'axios' class Method { - private options: CKBComponents.Method = { + #name: string + + get name() { + return this.#name + } + + #options: CKBComponents.Method = { name: '', method: '', paramsFormatters: [], resultFormatters: undefined, } - private node: CKBComponents.Node + #node: CKBComponents.Node constructor(node: CKBComponents.Node, options: CKBComponents.Method) { - this.node = node - this.options = options + this.#node = node + this.#options = options + this.#name = options.name + Object.defineProperty(this.call, 'name', { value: options.name, configurable: false, writable: false }) } - public call = (...params: (string | number)[]) => { - const data = params.map((p, i) => (this.options.paramsFormatters[i] && this.options.paramsFormatters[i](p)) || p) - const id = Math.round(Math.random() * 10000) - const payload = { - id, - method: this.options.method, - params: data, - jsonrpc: '2.0', - } + public call = (...params: (string | number | object)[]) => { + const payload = this.getPayload(...params) return axios({ method: 'POST', headers: { 'content-type': 'application/json', }, data: payload, - url: this.node.url, - httpAgent: this.node.httpAgent, - httpsAgent: this.node.httpsAgent, + url: this.#node.url, + httpAgent: this.#node.httpAgent, + httpsAgent: this.#node.httpsAgent, }).then(res => { - if (res.data.id !== id) { - throw new Error('JSONRPC id not match') + if (res.data.id !== payload.id) { + throw new Error('JSONRPC id not matched') } if (res.data.error) { throw new Error(JSON.stringify(res.data.error)) } - return this.options.resultFormatters ? this.options.resultFormatters(res.data.result) : res.data.result + return this.#options.resultFormatters?.(res.data.result) ?? res.data.result }) } + + public getPayload = (...params: (string | number | object)[]) => { + const data = params.map((p, i) => (this.#options.paramsFormatters[i] && this.#options.paramsFormatters[i](p)) || p) + const id = Math.round(Math.random() * 10000) + const payload = { + id, + method: this.#options.method, + params: data, + jsonrpc: '2.0', + } + return payload + } } export default Method