ox
Version:
Ethereum Standard Library
726 lines (659 loc) • 20.1 kB
text/typescript
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",
]
`)
})