Revert Decoder
Overview
When contract calls fail, they return revert data containing encoded error information. The decodeRevertData() function transforms this raw binary data into human-readable error messages, enabling applications to provide meaningful feedback about why an operation failed. This utility is essential for debugging contract interactions and delivering clear error explanations to users.
Decode Revert Data
Using decodeRevertData()
The decodeRevertData() function accepts raw revert data as a Uint8Array and returns a decoded error message string. The function first checks if the data begins with a standard error selector, then extracts and decodes the remaining bytes as UTF-8 text to produce a readable message.
If the revert data does not match the expected error format, the function returns a hexadecimal representation prefixed with "Unknown Revert". This fallback ensures that all revert data can be surfaced for debugging, even when the encoding is non-standard or custom.
Method Signature
function decodeRevertData(
revertDataBytes: Uint8Array
): stringBasic Usage
Transaction objects provide both pre-decoded and raw revert data. The revert property contains the already-decoded message, while rawRevert holds the original bytes for manual decoding or custom processing using decodeRevertData().
import { decodeRevertData } from 'opnet';
// From transaction receipt
const tx = await provider.getTransaction(txHash);
if (tx.revert) {
console.log('Transaction reverted:', tx.revert);
}
// Manual decoding
if (tx.rawRevert) {
const message = decodeRevertData(tx.rawRevert);
console.log('Decoded revert:', message);
}Custom Error Handling
A centralized error handler provides consistent transformation of technical revert messages into user-friendly text. By registering pattern-based handlers, applications can map common revert reasons to clear explanations while preserving the original message for unrecognized errors.
Log Reverts for Debugging
Logging revert information aids in debugging and monitoring application behavior. Capturing both the decoded message and raw revert bytes ensures that sufficient detail is available for diagnosing issues, whether from simulation results or confirmed transactions.
Common Revert Messages
| Revert Message | Cause |
|---|---|
| Insufficient balance | Sender doesn't have enough tokens. |
| Insufficient allowance | Spender not approved for amount. |
| Invalid address | Zero address or invalid format. |
| Overflow | Amount exceeds maximum. |
| Paused | Contract is paused. |
| Not authorized | Caller lacks permission. |
| Invalid amount | Amount is zero or invalid. |
Using a Custom Error Handler
class RevertHandler {
private handlers: Map<string, (msg: string) => string> = new Map();
registerHandler(
pattern: string,
handler: (msg: string) => string
): void {
this.handlers.set(pattern.toLowerCase(), handler);
}
handle(revertMessage: string): string {
const lowerMessage = revertMessage.toLowerCase();
for (const [pattern, handler] of this.handlers) {
if (lowerMessage.includes(pattern)) {
return handler(revertMessage);
}
}
return revertMessage;
}
}
// Usage
const handler = new RevertHandler();
handler.registerHandler('insufficient balance', () =>
'Not enough tokens in your wallet'
);
handler.registerHandler('insufficient allowance', () =>
'Please approve tokens first'
);
handler.registerHandler('unauthorized', () =>
'Permission denied'
);
// Apply to revert
const userFriendly = handler.handle(result.revert);
console.log(userFriendly);Complete Revert Service
class RevertService {
private errorMappings: Map<string, string> = new Map();
constructor() {
this.initDefaultMappings();
}
private initDefaultMappings(): void {
this.errorMappings.set('insufficient balance', 'Insufficient balance');
this.errorMappings.set('insufficient allowance', 'Insufficient allowance');
this.errorMappings.set('unauthorized', 'Unauthorized access');
this.errorMappings.set('paused', 'Contract is paused');
this.errorMappings.set('zero address', 'Invalid zero address');
}
addMapping(pattern: string, message: string): void {
this.errorMappings.set(pattern.toLowerCase(), message);
}
decode(rawRevert: Uint8Array): string {
return decodeRevertData(rawRevert);
}
getUserFriendlyMessage(revert: string): string {
const lower = revert.toLowerCase();
for (const [pattern, message] of this.errorMappings) {
if (lower.includes(pattern)) {
return message;
}
}
return revert;
}
isKnownError(revert: string): boolean {
const lower = revert.toLowerCase();
for (const pattern of this.errorMappings.keys()) {
if (lower.includes(pattern)) {
return true;
}
}
return false;
}
extractFromCallResult(result: { revert?: string }): string | null {
if (result.revert) {
return this.getUserFriendlyMessage(result.revert);
}
return null;
}
extractFromTransaction(tx: { revert?: string; rawRevert?: Uint8Array }): string | null {
if (tx.revert) {
return this.getUserFriendlyMessage(tx.revert);
}
if (tx.rawRevert) {
const decoded = this.decode(tx.rawRevert);
return this.getUserFriendlyMessage(decoded);
}
return null;
}
}
// Usage
const revertService = new RevertService();
// Add custom mapping
revertService.addMapping('already claimed', 'You have already claimed');
// Use with contract call result
const result = await contract.transfer(recipient, amount, new Uint8Array(0));
const error = revertService.extractFromCallResult(result);
if (error) {
console.log('Error:', error);
}