UNPKG

ox

Version:

Ethereum Standard Library

726 lines (659 loc) 20.1 kB
import { type Hex, RpcRequest, RpcResponse } from 'ox' import { assertType, describe, expect, test } from 'vp/test' import { anvilMainnet } from '../../../test/prool.js' describe('from', () => { test('default', async () => { const response = RpcResponse.from({ id: 0, jsonrpc: '2.0', result: '0xdeadbeef', }) expect(response).toMatchInlineSnapshot(` { "id": 0, "jsonrpc": "2.0", "result": "0xdeadbeef", } `) }) test('behavior: with request', () => { const request = RpcRequest.from({ id: 0, method: 'eth_blockNumber' }) const response = RpcResponse.from( { id: 0, jsonrpc: '2.0', result: '0xdeadbeef', }, { request }, ) expect(response).toMatchInlineSnapshot(` { "id": 0, "jsonrpc": "2.0", "result": "0xdeadbeef", } `) }) test('behavior: missing request and missing jsonrpc throws ParseError', () => { expect(() => RpcResponse.from({ id: 0, result: '0x1' } as never), ).toThrowErrorMatchingInlineSnapshot( '[RpcResponse.ParseError: Invalid JSON-RPC response.]', ) }) test('behavior: missing request and missing id throws ParseError', () => { expect(() => RpcResponse.from({ jsonrpc: '2.0', result: '0x1' } as never), ).toThrowErrorMatchingInlineSnapshot( '[RpcResponse.ParseError: Invalid JSON-RPC response.]', ) }) }) describe('parse', () => { test('default', async () => { const request = RpcRequest.from({ method: 'eth_estimateGas', params: [ { from: '0xd8da6bf26964af9d7eed9e03e53415d37aa96045', to: '0x0000000000000000000000000000000000000000', }, ], id: 0, }) const raw = await fetch(anvilMainnet.rpcUrl, { body: JSON.stringify(request), headers: { 'Content-Type': 'application/json', }, method: 'POST', }).then((res) => res.json()) const gas = RpcResponse.parse(raw) assertType<unknown>(gas) expect(gas).toMatchInlineSnapshot(`"0x5208"`) }) test('error', async () => { const request = RpcRequest.from({ method: 'eth_sendTransaction', params: [ { from: '0xd8da6bf26964af9d7eed9e03e53415d37aa96045', to: '0x0000000000000000000000000000000000000000', }, ], id: 0, }) const raw = await fetch(anvilMainnet.rpcUrl, { body: JSON.stringify(request), headers: { 'Content-Type': 'application/json', }, method: 'POST', }).then((res) => res.json()) expect(() => RpcResponse.parse(raw)).toThrowErrorMatchingInlineSnapshot( '[RpcResponse.InvalidParamsError: No Signer available]', ) }) test('options: request', async () => { const request = RpcRequest.from({ method: 'eth_estimateGas', params: [ { from: '0xd8da6bf26964af9d7eed9e03e53415d37aa96045', to: '0x0000000000000000000000000000000000000000', }, ], id: 0, }) const raw = await fetch(anvilMainnet.rpcUrl, { body: JSON.stringify(request), headers: { 'Content-Type': 'application/json', }, method: 'POST', }).then((res) => res.json()) const gas = RpcResponse.parse(raw, { request, }) assertType<Hex.Hex>(gas) expect(gas).toMatchInlineSnapshot(`"0x5208"`) }) test('options: safe', async () => { const request = RpcRequest.from({ method: 'eth_estimateGas', params: [ { from: '0xd8da6bf26964af9d7eed9e03e53415d37aa96045', to: '0x0000000000000000000000000000000000000000', }, ], id: 0, }) const json = await fetch(anvilMainnet.rpcUrl, { body: JSON.stringify(request), headers: { 'Content-Type': 'application/json', }, method: 'POST', }).then((res) => res.json()) { const response = RpcResponse.parse(json, { request, raw: true, }) assertType<RpcResponse.RpcResponse<Hex.Hex>>(response) expect(response).toMatchInlineSnapshot(` { "id": 0, "jsonrpc": "2.0", "result": "0x5208", } `) } { const response = RpcResponse.parse(json, { raw: true, }) assertType<RpcResponse.RpcResponse<unknown>>(response) } }) test('behavior: throws JsonRpc.InvalidInputError', () => { expect(() => RpcResponse.parse({ jsonrpc: '2.0', id: 0, error: { code: -32000, message: 'Internal error', }, }), ).toThrowErrorMatchingInlineSnapshot( '[RpcResponse.InvalidInputError: Internal error]', ) }) test('behavior: throws JsonRpc.ResourceNotFoundError', () => { expect(() => RpcResponse.parse({ jsonrpc: '2.0', id: 0, error: { code: -32001, message: 'Resource not found', }, }), ).toThrowErrorMatchingInlineSnapshot( '[RpcResponse.ResourceNotFoundError: Resource not found]', ) }) test('behavior: throws JsonRpc.ResourceUnavailableError', () => { expect(() => RpcResponse.parse({ jsonrpc: '2.0', id: 0, error: { code: -32002, message: 'Resource unavailable', }, }), ).toThrowErrorMatchingInlineSnapshot( '[RpcResponse.ResourceUnavailableError: Resource unavailable]', ) }) test('behavior: throws JsonRpc.TransactionRejectedError', () => { expect(() => RpcResponse.parse({ jsonrpc: '2.0', id: 0, error: { code: -32003, message: 'Transaction rejected', }, }), ).toThrowErrorMatchingInlineSnapshot( '[RpcResponse.TransactionRejectedError: Transaction rejected]', ) }) test('behavior: throws JsonRpc.MethodNotSupportedError', () => { expect(() => RpcResponse.parse({ jsonrpc: '2.0', id: 0, error: { code: -32004, message: 'Method not supported', }, }), ).toThrowErrorMatchingInlineSnapshot( '[RpcResponse.MethodNotSupportedError: Method not supported]', ) }) test('behavior: throws JsonRpc.LimitExceededError', () => { expect(() => RpcResponse.parse({ jsonrpc: '2.0', id: 0, error: { code: -32005, message: 'Limit exceeded', }, }), ).toThrowErrorMatchingInlineSnapshot( '[RpcResponse.LimitExceededError: Limit exceeded]', ) }) test('behavior: throws JsonRpc.VersionNotSupportedError', () => { expect(() => RpcResponse.parse({ jsonrpc: '2.0', id: 0, error: { code: -32006, message: 'Version not supported', }, }), ).toThrowErrorMatchingInlineSnapshot( '[RpcResponse.VersionNotSupportedError: Version not supported]', ) }) test('behavior: throws JsonRpc.InvalidRequestError', () => { expect(() => RpcResponse.parse({ jsonrpc: '2.0', id: 0, error: { code: -32600, message: 'Invalid Request', }, }), ).toThrowErrorMatchingInlineSnapshot( '[RpcResponse.InvalidRequestError: Invalid Request]', ) }) test('behavior: throws JsonRpc.MethodNotFoundError', () => { expect(() => RpcResponse.parse({ jsonrpc: '2.0', id: 0, error: { code: -32601, message: 'Method not found', }, }), ).toThrowErrorMatchingInlineSnapshot( '[RpcResponse.MethodNotFoundError: Method not found]', ) }) test('behavior: throws JsonRpc.InvalidParamsError', () => { expect(() => RpcResponse.parse({ jsonrpc: '2.0', id: 0, error: { code: -32602, message: 'Method not found', }, }), ).toThrowErrorMatchingInlineSnapshot( '[RpcResponse.InvalidParamsError: Method not found]', ) }) test('behavior: throws JsonRpc.InternalError', () => { expect(() => RpcResponse.parse({ jsonrpc: '2.0', id: 0, error: { code: -32603, message: 'Internal JSON-RPC error.', }, }), ).toThrowErrorMatchingInlineSnapshot( '[RpcResponse.InternalError: Internal JSON-RPC error.]', ) }) test('behavior: throws JsonRpc.ParseError', () => { expect(() => RpcResponse.parse({ jsonrpc: '2.0', id: 0, error: { code: -32700, message: 'Method not found', }, }), ).toThrowErrorMatchingInlineSnapshot( '[RpcResponse.ParseError: Method not found]', ) }) test('behavior: throws JsonRpc.Error', () => { expect(() => RpcResponse.parse({ jsonrpc: '2.0', id: 0, error: { code: 69420, message: 'oh no', }, }), ).toThrowErrorMatchingInlineSnapshot('[RpcResponse.InternalError: oh no]') }) test('behavior: rejects empty object envelope', () => { expect(() => RpcResponse.parse({})).toThrowErrorMatchingInlineSnapshot( '[RpcResponse.ParseError: Invalid JSON-RPC response.]', ) }) test('behavior: rejects null envelope', () => { expect(() => RpcResponse.parse(null as never), ).toThrowErrorMatchingInlineSnapshot( '[RpcResponse.ParseError: Invalid JSON-RPC response.]', ) }) test('behavior: rejects envelope without result or error', () => { expect(() => RpcResponse.parse({ jsonrpc: '2.0', id: 0 } as never), ).toThrowErrorMatchingInlineSnapshot( '[RpcResponse.ParseError: Invalid JSON-RPC response.]', ) }) test('behavior: rejects envelope with wrong jsonrpc', () => { expect(() => RpcResponse.parse({ jsonrpc: '1.0', id: 0, result: '0x1' } as never), ).toThrowErrorMatchingInlineSnapshot( '[RpcResponse.ParseError: Invalid JSON-RPC response.]', ) }) }) describe('parseError', () => { test('InternalError', () => { const error = RpcResponse.parseError({ code: -32603, message: 'foo', }) expect(error).toMatchInlineSnapshot('[RpcResponse.InternalError: foo]') }) test('InternalError (cause)', () => { const error = RpcResponse.parseError(new Error('lol')) expect(error).toMatchInlineSnapshot('[RpcResponse.InternalError: lol]') }) test('InvalidInputError', () => { const error = RpcResponse.parseError({ code: -32000, message: 'foo', }) expect(error).toMatchInlineSnapshot('[RpcResponse.InvalidInputError: foo]') }) test('InvalidParamsError', () => { const error = RpcResponse.parseError({ code: -32602, message: 'foo', }) expect(error).toMatchInlineSnapshot('[RpcResponse.InvalidParamsError: foo]') }) test('InvalidRequestError', () => { const error = RpcResponse.parseError({ code: -32600, message: 'foo', }) expect(error).toMatchInlineSnapshot( '[RpcResponse.InvalidRequestError: foo]', ) }) test('LimitExceededError', () => { const error = RpcResponse.parseError({ code: -32005, message: 'foo', }) expect(error).toMatchInlineSnapshot('[RpcResponse.LimitExceededError: foo]') }) test('MethodNotFoundError', () => { const error = RpcResponse.parseError({ code: -32601, message: 'foo', }) expect(error).toMatchInlineSnapshot( '[RpcResponse.MethodNotFoundError: foo]', ) }) test('MethodNotSupportedError', () => { const error = RpcResponse.parseError({ code: -32004, message: 'foo', }) expect(error).toMatchInlineSnapshot( '[RpcResponse.MethodNotSupportedError: foo]', ) }) test('ParseError', () => { const error = RpcResponse.parseError({ code: -32700, message: 'foo', }) expect(error).toMatchInlineSnapshot('[RpcResponse.ParseError: foo]') }) test('ResourceNotFoundError', () => { const error = RpcResponse.parseError({ code: -32001, message: 'foo', }) expect(error).toMatchInlineSnapshot( '[RpcResponse.ResourceNotFoundError: foo]', ) }) test('ResourceUnavailableError', () => { const error = RpcResponse.parseError({ code: -32002, message: 'foo', }) expect(error).toMatchInlineSnapshot( '[RpcResponse.ResourceUnavailableError: foo]', ) }) test('TransactionRejectedError', () => { const error = RpcResponse.parseError({ code: -32003, message: 'foo', }) expect(error).toMatchInlineSnapshot( '[RpcResponse.TransactionRejectedError: foo]', ) }) test('VersionNotSupportedError', () => { const error = RpcResponse.parseError({ code: -32006, message: 'foo', }) expect(error).toMatchInlineSnapshot( '[RpcResponse.VersionNotSupportedError: foo]', ) }) test('BaseError', () => { const error = RpcResponse.parseError({ code: -69420, message: 'foo', }) const { code, message } = error.data as RpcResponse.ErrorObject expect(code).toBe(-69420) expect(message).toBe('foo') expect(error).toMatchInlineSnapshot('[RpcResponse.InternalError: foo]') }) test('behavior: preserves existing BaseError instance', () => { const original = new RpcResponse.InvalidInputError({ message: 'foo' }) const error = RpcResponse.parseError(original) expect(error).toBe(original) }) }) test('InvalidInputError', () => { expect(new RpcResponse.InvalidInputError()).toMatchInlineSnapshot( '[RpcResponse.InvalidInputError: Missing or invalid parameters.]', ) expect( new RpcResponse.InvalidInputError({ message: 'foo' }), ).toMatchInlineSnapshot('[RpcResponse.InvalidInputError: foo]') expect( JSON.stringify(new RpcResponse.InvalidInputError()), ).toMatchInlineSnapshot( `"{"stack":"","name":"RpcResponse.InvalidInputError","code":-32000}"`, ) }) test('ResourceNotFoundError', () => { expect(new RpcResponse.ResourceNotFoundError()).toMatchInlineSnapshot( '[RpcResponse.ResourceNotFoundError: Requested resource not found.]', ) expect( new RpcResponse.ResourceNotFoundError({ message: 'foo' }), ).toMatchInlineSnapshot('[RpcResponse.ResourceNotFoundError: foo]') expect( JSON.stringify(new RpcResponse.ResourceNotFoundError()), ).toMatchInlineSnapshot( `"{"stack":"","name":"RpcResponse.ResourceNotFoundError","code":-32001}"`, ) }) test('ResourceUnavailableError', () => { expect(new RpcResponse.ResourceUnavailableError()).toMatchInlineSnapshot( '[RpcResponse.ResourceUnavailableError: Requested resource not available.]', ) expect( new RpcResponse.ResourceUnavailableError({ message: 'foo' }), ).toMatchInlineSnapshot('[RpcResponse.ResourceUnavailableError: foo]') expect( JSON.stringify(new RpcResponse.ResourceUnavailableError()), ).toMatchInlineSnapshot( `"{"stack":"","name":"RpcResponse.ResourceUnavailableError","code":-32002}"`, ) }) test('TransactionRejectedError', () => { expect(new RpcResponse.TransactionRejectedError()).toMatchInlineSnapshot( '[RpcResponse.TransactionRejectedError: Transaction creation failed.]', ) expect( new RpcResponse.TransactionRejectedError({ message: 'foo' }), ).toMatchInlineSnapshot('[RpcResponse.TransactionRejectedError: foo]') expect( JSON.stringify(new RpcResponse.TransactionRejectedError()), ).toMatchInlineSnapshot( `"{"stack":"","name":"RpcResponse.TransactionRejectedError","code":-32003}"`, ) }) test('MethodNotSupportedError', () => { expect(new RpcResponse.MethodNotSupportedError()).toMatchInlineSnapshot( '[RpcResponse.MethodNotSupportedError: Method is not implemented.]', ) expect( new RpcResponse.MethodNotSupportedError({ message: 'foo' }), ).toMatchInlineSnapshot('[RpcResponse.MethodNotSupportedError: foo]') expect( JSON.stringify(new RpcResponse.MethodNotSupportedError()), ).toMatchInlineSnapshot( `"{"stack":"","name":"RpcResponse.MethodNotSupportedError","code":-32004}"`, ) }) test('LimitExceededError', () => { expect(new RpcResponse.LimitExceededError()).toMatchInlineSnapshot( '[RpcResponse.LimitExceededError: Rate limit exceeded.]', ) expect( new RpcResponse.LimitExceededError({ message: 'foo' }), ).toMatchInlineSnapshot('[RpcResponse.LimitExceededError: foo]') expect( JSON.stringify(new RpcResponse.LimitExceededError()), ).toMatchInlineSnapshot( `"{"stack":"","name":"RpcResponse.LimitExceededError","code":-32005}"`, ) }) test('VersionNotSupportedError', () => { expect(new RpcResponse.VersionNotSupportedError()).toMatchInlineSnapshot( '[RpcResponse.VersionNotSupportedError: JSON-RPC version not supported.]', ) expect( new RpcResponse.VersionNotSupportedError({ message: 'foo' }), ).toMatchInlineSnapshot('[RpcResponse.VersionNotSupportedError: foo]') expect( JSON.stringify(new RpcResponse.VersionNotSupportedError()), ).toMatchInlineSnapshot( `"{"stack":"","name":"RpcResponse.VersionNotSupportedError","code":-32006}"`, ) }) test('InvalidRequestError', () => { expect(new RpcResponse.InvalidRequestError()).toMatchInlineSnapshot( '[RpcResponse.InvalidRequestError: Input is not a valid JSON-RPC request.]', ) expect( new RpcResponse.InvalidRequestError({ message: 'foo' }), ).toMatchInlineSnapshot('[RpcResponse.InvalidRequestError: foo]') expect( JSON.stringify(new RpcResponse.InvalidRequestError()), ).toMatchInlineSnapshot( `"{"stack":"","name":"RpcResponse.InvalidRequestError","code":-32600}"`, ) }) test('MethodNotFoundError', () => { expect(new RpcResponse.MethodNotFoundError()).toMatchInlineSnapshot( '[RpcResponse.MethodNotFoundError: Method does not exist.]', ) expect( new RpcResponse.MethodNotFoundError({ message: 'foo' }), ).toMatchInlineSnapshot('[RpcResponse.MethodNotFoundError: foo]') expect( JSON.stringify(new RpcResponse.MethodNotFoundError()), ).toMatchInlineSnapshot( `"{"stack":"","name":"RpcResponse.MethodNotFoundError","code":-32601}"`, ) }) test('InvalidParamsError', () => { expect(new RpcResponse.InvalidParamsError()).toMatchInlineSnapshot( '[RpcResponse.InvalidParamsError: Invalid method parameters.]', ) expect( new RpcResponse.InvalidParamsError({ message: 'foo' }), ).toMatchInlineSnapshot('[RpcResponse.InvalidParamsError: foo]') expect( JSON.stringify(new RpcResponse.InvalidParamsError()), ).toMatchInlineSnapshot( `"{"stack":"","name":"RpcResponse.InvalidParamsError","code":-32602}"`, ) }) test('InternalError', () => { expect(new RpcResponse.InternalError()).toMatchInlineSnapshot( '[RpcResponse.InternalError: Internal JSON-RPC error.]', ) expect( new RpcResponse.InternalError({ message: 'foo' }), ).toMatchInlineSnapshot('[RpcResponse.InternalError: foo]') expect(JSON.stringify(new RpcResponse.InternalError())).toMatchInlineSnapshot( `"{"stack":"","name":"RpcResponse.InternalError","code":-32603}"`, ) }) test('ParseError', () => { expect(new RpcResponse.ParseError()).toMatchInlineSnapshot( '[RpcResponse.ParseError: Failed to parse JSON-RPC response.]', ) expect(new RpcResponse.ParseError({ message: 'foo' })).toMatchInlineSnapshot( '[RpcResponse.ParseError: foo]', ) expect(JSON.stringify(new RpcResponse.ParseError())).toMatchInlineSnapshot( `"{"stack":"","name":"RpcResponse.ParseError","code":-32700}"`, ) }) test('exports', () => { expect(Object.keys(RpcResponse)).toMatchInlineSnapshot(` [ "from", "parse", "parseError", "BaseError", "InvalidInputError", "ResourceNotFoundError", "ResourceUnavailableError", "TransactionRejectedError", "MethodNotSupportedError", "LimitExceededError", "VersionNotSupportedError", "InvalidRequestError", "MethodNotFoundError", "InvalidParamsError", "InternalError", "ParseError", ] `) })