Skip to content

Commit 395d149

Browse files
committed
move to custom error types in rpc
1 parent 63dc452 commit 395d149

3 files changed

Lines changed: 73 additions & 14 deletions

File tree

packages/types/src/errors.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ export enum ErrorCode {
66
PARSE_ERROR = "PARSE_ERROR",
77
INVALID_RESPONSE = "INVALID_RESPONSE",
88
SUBSCRIPTION_ERROR = "SUBSCRIPTION_ERROR",
9-
PROTOCOL_ERROR = "PROTOCOL_ERROR"
9+
PROTOCOL_ERROR = "PROTOCOL_ERROR",
10+
ABCI_ERROR = "ABCI_ERROR"
1011
}
1112

1213
export enum ErrorCategory {
@@ -62,4 +63,24 @@ export class SubscriptionError extends QueryClientError {
6263
export class ProtocolError extends QueryClientError {
6364
readonly code = ErrorCode.PROTOCOL_ERROR;
6465
readonly category = ErrorCategory.PROTOCOL;
66+
}
67+
68+
/**
69+
* Error thrown when an ABCI query returns a non-zero response code.
70+
* This indicates the chain application rejected the query (e.g., simulation failure,
71+
* invalid transaction, out of gas during execution).
72+
*/
73+
export class AbciError extends QueryClientError {
74+
readonly code = ErrorCode.ABCI_ERROR;
75+
readonly category = ErrorCategory.SERVER;
76+
77+
constructor(
78+
message: string,
79+
/** The ABCI response code (non-zero indicates error) */
80+
public readonly abciCode: number,
81+
/** The log message from the ABCI response */
82+
public readonly log: string
83+
) {
84+
super(message);
85+
}
6586
}

packages/utils/src/rpc.spec.ts

Lines changed: 42 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { abciQuery } from './rpc';
2+
import { ProtocolError, AbciError } from '@interchainjs/types';
23

34
// Mock fetch globally
45
const mockFetch = jest.fn();
@@ -13,17 +14,18 @@ describe('abciQuery', () => {
1314
mockFetch.mockReset();
1415
});
1516

16-
it('should throw on JSON-RPC error', async () => {
17+
it('should throw ProtocolError on JSON-RPC error', async () => {
1718
mockFetch.mockResolvedValue({
1819
json: () => Promise.resolve({
19-
error: 'Internal error',
20+
error: { code: -32600, message: 'Invalid Request' },
2021
}),
2122
});
2223

23-
await expect(abciQuery(endpoint, path, data)).rejects.toThrow('Request Error: Internal error');
24+
await expect(abciQuery(endpoint, path, data)).rejects.toThrow(ProtocolError);
25+
await expect(abciQuery(endpoint, path, data)).rejects.toThrow('JSON-RPC error');
2426
});
2527

26-
it('should throw on non-zero ABCI response code', async () => {
28+
it('should throw AbciError on non-zero ABCI response code', async () => {
2729
mockFetch.mockResolvedValue({
2830
json: () => Promise.resolve({
2931
result: {
@@ -36,12 +38,19 @@ describe('abciQuery', () => {
3638
}),
3739
});
3840

39-
await expect(abciQuery(endpoint, path, data)).rejects.toThrow(
40-
'ABCI Error (code 11): out of gas in location: ReadFlat; gasWanted: 0, gasUsed: 1000'
41-
);
41+
await expect(abciQuery(endpoint, path, data)).rejects.toThrow(AbciError);
42+
43+
try {
44+
await abciQuery(endpoint, path, data);
45+
} catch (e) {
46+
expect(e).toBeInstanceOf(AbciError);
47+
const abciError = e as AbciError;
48+
expect(abciError.abciCode).toBe(11);
49+
expect(abciError.log).toBe('out of gas in location: ReadFlat; gasWanted: 0, gasUsed: 1000');
50+
}
4251
});
4352

44-
it('should throw with generic message when ABCI error has no log', async () => {
53+
it('should throw AbciError with generic message when ABCI error has no log', async () => {
4554
mockFetch.mockResolvedValue({
4655
json: () => Promise.resolve({
4756
result: {
@@ -54,7 +63,15 @@ describe('abciQuery', () => {
5463
}),
5564
});
5665

57-
await expect(abciQuery(endpoint, path, data)).rejects.toThrow('ABCI Error (code 5): Unknown error');
66+
await expect(abciQuery(endpoint, path, data)).rejects.toThrow(AbciError);
67+
68+
try {
69+
await abciQuery(endpoint, path, data);
70+
} catch (e) {
71+
const abciError = e as AbciError;
72+
expect(abciError.abciCode).toBe(5);
73+
expect(abciError.log).toBe('Unknown error');
74+
}
5875
});
5976

6077
it('should return decoded value on success (code 0)', async () => {
@@ -75,4 +92,20 @@ describe('abciQuery', () => {
7592
const result = await abciQuery(endpoint, path, data);
7693
expect(result).toEqual(new Uint8Array([4, 5, 6]));
7794
});
95+
96+
it('should preserve instanceof Error for backward compatibility', async () => {
97+
mockFetch.mockResolvedValue({
98+
json: () => Promise.resolve({
99+
result: {
100+
response: {
101+
code: 11,
102+
log: 'test error',
103+
value: '',
104+
},
105+
},
106+
}),
107+
});
108+
109+
await expect(abciQuery(endpoint, path, data)).rejects.toThrow(Error);
110+
});
78111
});

packages/utils/src/rpc.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { HttpEndpoint, Rpc } from '@interchainjs/types';
1+
import { HttpEndpoint, Rpc, ProtocolError, AbciError, ParseError } from '@interchainjs/types';
22
import { toHttpEndpoint } from './endpoint';
33
import { fromBase64, toBase64, toHex } from './encoding';
44
import { randomId } from './random';
@@ -48,20 +48,25 @@ export async function abciQuery(
4848
const resp = await fetch(endpoint.url, req);
4949
const json = await resp.json();
5050
if (json['error'] != void 0) {
51-
throw new Error(`Request Error: ${json['error']}`);
51+
throw new ProtocolError(`JSON-RPC error: ${JSON.stringify(json['error'])}`);
5252
}
5353
const response = json['result']['response'];
5454

5555
// Check ABCI response code - non-zero indicates an error
5656
if (response['code'] !== 0) {
57-
throw new Error(`ABCI Error (code ${response['code']}): ${response['log'] || 'Unknown error'}`);
57+
const log = response['log'] || 'Unknown error';
58+
throw new AbciError(
59+
`ABCI query failed (code ${response['code']}): ${log}`,
60+
response['code'],
61+
log
62+
);
5863
}
5964

6065
try {
6166
const result = fromBase64(response['value']);
6267
return result;
6368
} catch (error) {
64-
throw new Error(`Request Error: ${response['log'] || 'Failed to decode response'}`);
69+
throw new ParseError(`Failed to decode ABCI response: ${response['log'] || 'invalid base64'}`);
6570
}
6671
}
6772

0 commit comments

Comments
 (0)