Skip to content

Commit 7b190e2

Browse files
committed
Implement viem error decoding in revert reason handler and add corresponding tests
1 parent a80ddd6 commit 7b190e2

2 files changed

Lines changed: 100 additions & 2 deletions

File tree

packages/smart-accounts-kit/src/decodeRevertReason.ts

Lines changed: 61 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import * as delegationAbis from '@metamask/delegation-abis';
2-
import { isHex } from 'viem';
3-
import type { Abi, AbiItem, Hex } from 'viem';
2+
import { BaseError, ContractFunctionRevertedError, isHex } from 'viem';
3+
import type { Abi, AbiItem, DecodeErrorResultReturnType, Hex } from 'viem';
44
import { decodeErrorResult, formatAbiItemWithArgs } from 'viem/utils';
55

66
const knownRevertAbis = Object.values(delegationAbis) as readonly Abi[];
@@ -32,6 +32,12 @@ export type DecodedRevertReason = {
3232
export function decodeRevertReason(
3333
error: unknown,
3434
): DecodedRevertReason | undefined {
35+
const decodedViemError = decodeViemContractRevert(error);
36+
37+
if (decodedViemError) {
38+
return decodedViemError;
39+
}
40+
3541
for (const rawData of getRevertDataCandidates(error)) {
3642
const decoded = decodeRevertData(rawData);
3743

@@ -43,6 +49,39 @@ export function decodeRevertReason(
4349
return undefined;
4450
}
4551

52+
/**
53+
* Extracts revert information that viem has already identified in its error
54+
* chain before falling back to provider-specific string/object shapes.
55+
*
56+
* @param error - The original error thrown by viem.
57+
* @returns A decoded revert reason, if viem exposed enough revert data.
58+
*/
59+
function decodeViemContractRevert(
60+
error: unknown,
61+
): DecodedRevertReason | undefined {
62+
if (!(error instanceof BaseError)) {
63+
return undefined;
64+
}
65+
66+
const revertError = error.walk(
67+
(cause) => cause instanceof ContractFunctionRevertedError,
68+
);
69+
70+
if (!(revertError instanceof ContractFunctionRevertedError)) {
71+
return undefined;
72+
}
73+
74+
if (!revertError.raw) {
75+
return undefined;
76+
}
77+
78+
if (revertError.data) {
79+
return formatDecodeErrorResult(revertError.data, revertError.raw);
80+
}
81+
82+
return decodeRevertData(revertError.raw);
83+
}
84+
4685
/**
4786
* Decodes raw revert data against standard Solidity errors and known SDK ABIs.
4887
*
@@ -84,6 +123,26 @@ export function decodeRevertData(
84123
return undefined;
85124
}
86125

126+
/**
127+
* Formats viem's decoded error result into the helper's return shape.
128+
*
129+
* @param decodedData - The decoded error result from viem.
130+
* @param rawData - The ABI-encoded revert data.
131+
* @returns Human-readable revert text.
132+
*/
133+
function formatDecodeErrorResult(
134+
decodedData: DecodeErrorResultReturnType,
135+
rawData: Hex,
136+
): DecodedRevertReason {
137+
const { abiItem, args, errorName } = decodedData;
138+
139+
return {
140+
errorName,
141+
message: formatDecodedError(errorName, args, abiItem),
142+
rawData,
143+
};
144+
}
145+
87146
/**
88147
* Formats a decoded error into compact user-facing text.
89148
*
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import {
2+
BaseError,
3+
ContractFunctionRevertedError,
4+
encodeErrorResult,
5+
} from 'viem';
6+
import { describe, expect, it } from 'vitest';
7+
8+
import { decodeRevertReason } from '../src/decodeRevertReason';
9+
10+
describe('decodeRevertReason', () => {
11+
it('should decode viem contract function reverted errors', () => {
12+
const abi = [
13+
{
14+
type: 'error',
15+
name: 'Error',
16+
inputs: [{ name: 'message', type: 'string' }],
17+
},
18+
] as const;
19+
const rawData = encodeErrorResult({
20+
abi,
21+
errorName: 'Error',
22+
args: ['AllowedMethodsEnforcer:method-not-allowed'],
23+
});
24+
const revertError = new ContractFunctionRevertedError({
25+
abi,
26+
data: rawData,
27+
functionName: 'redeemDelegations',
28+
});
29+
const executionError = new BaseError('Transaction simulation failed.', {
30+
cause: revertError,
31+
});
32+
33+
expect(decodeRevertReason(executionError)).toStrictEqual({
34+
errorName: 'Error',
35+
message: 'AllowedMethodsEnforcer:method-not-allowed',
36+
rawData,
37+
});
38+
});
39+
});

0 commit comments

Comments
 (0)