Skip to content

A TypeScript library for decoding Sui blockchain transaction errors, including Move abort errors and custom error codes. Simplifies error handling for Sui dApps and smart contracts with a robust, extensible error decoder.

License

Notifications You must be signed in to change notification settings

Signor1/suiClient-Error-Decoder

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

19 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

SuiClient Error Decoder

npm version License: MIT Sui Blockchain Move TypeScript

SuiClient Error Decoder is a robust error decoding toolkit for Sui blockchain developers. Intelligently parses Move abort codes, system errors, and transaction failures with zero dependencies. Features customizable error mappings, automatic error categorization, and Sui-specific pattern recognition to simplify debugging and improve user feedback in dApps.

npmjs.com/package/suiclient-error-decoder

Features

  • 🔍 Comprehensive Error Parsing: Handles Move abort codes, named errors, and system errors
  • 🎯 Custom Error Codes: Add your own project-specific error codes
  • 📊 Error Categorization: Categorizes errors into move_abort, transaction, sui_system, or unknown
  • 🔄 Updatable Defaults: Built-in Sui error codes that can be updated
  • đź’ˇ Zero Dependencies: Lightweight and easy to integrate

Installation

npm install suiclient-error-decoder
# or
yarn add suiclient-error-decoder
# or
pnpm add suiclient-error-decoder

Quick Start

import { decodeSuiError, SuiClientErrorDecoder } from 'suiclient-error-decoder';

// Quick one-liner decoding
try {
  // Your Sui transaction code here
  await suiClient.executeTransactionBlock(/* ... */);
} catch (error) {
  const humanReadableError = decodeSuiError(error);
  console.error(humanReadableError);
  // Output: "Error Code 1001: Index out of bounds"
}

Usage Examples

1. Basic Setup and Usage

import { SuiClient } from "@mysten/sui/client";
import { Transaction } from "@mysten/sui/transactions";
import { SuiClientErrorDecoder } from 'suiclient-error-decoder';

// Initialize the decoder
const errorDecoder = new SuiClientErrorDecoder();
const suiClient = new SuiClient({ url: getFullnodeUrl("testnet") });

// Example: Decoding a transaction error
async function transferSui(recipientAddress: string, amount: number) {
  try {
    const txb = new Transaction();
    const [coin] = txb.splitCoins(txb.gas, [amount]);
    txb.transferObjects([coin], recipientAddress);
    
    const result = await suiClient.executeTransactionBlock({
      transactionBlock: txb,
      signer: keypair, // your keypair
    });
    
    console.log('Transfer successful:', result.digest);
  } catch (error) {
    // Raw error might be: "MoveAbort(0x2::coin, 0) at instruction 15"
    const decodedError = errorDecoder.parseError(error);
    
    console.error('Transaction failed:');
    console.error('- Code:', decodedError.code); // 0
    console.error('- Message:', decodedError.message); // "Error Code 0: Insufficient balance"
    console.error('- Category:', decodedError.category); // "move_abort"
    console.error('- Known Error:', decodedError.isKnownError); // true
  }
}

2. DeFi Pool Interaction with Custom Errors

// ...other imports
import { Transaction } from "@mysten/sui/transactions";
import { SuiClientErrorDecoder } from 'suiclient-error-decoder';

// Setup decoder with DeFi-specific error codes
const defiDecoder = new SuiClientErrorDecoder({
  customErrorCodes: {
    // Pool-specific errors
    100: "Pool does not exist",
    101: "Insufficient liquidity in pool",
    102: "Slippage tolerance exceeded",
    103: "Pool is paused for maintenance",
    104: "Invalid token pair",
    
    // Staking errors
    200: "Staking period not yet ended",
    201: "Rewards already claimed",
    202: "Minimum stake amount not met",
    203: "Unstaking cooldown period active",
  }
});

async function swapTokens(tokenA: string, tokenB: string, amountIn: number) {
  try {
    const txb = new Transaction();
    
    // Add your DeFi swap logic here
    txb.moveCall({
      target: '0x123::dex::swap',
      arguments: [
        txb.pure(tokenA),
        txb.pure(tokenB),
        txb.pure(amountIn)
      ],
      typeArguments: ['0x2::sui::SUI', '0x456::usdc::USDC']
    });
    
    const result = await suiClient.signAndExecuteTransactionBlock({
      transactionBlock: txb,
      signer: keypair,
      options: { showEffects: true }
    });
    
    return result;
  } catch (error) {
    const decoded = defiDecoder.parseError(error);
    
    // Handle specific DeFi errors
    if (decoded.code === 101) {
      throw new Error('Not enough liquidity in the pool. Try a smaller amount.');
    } else if (decoded.code === 102) {
      throw new Error('Price moved too much. Increase slippage tolerance.');
    } else {
      throw new Error(`Swap failed: ${decoded.message}`);
    }
  }
}

3. React Hook Integration

import { useState } from 'react';
import { Transaction } from "@mysten/sui/transactions";
import { useSignAndExecuteTransaction } from '@mysten/dapp-kit';
import { SuiClientErrorDecoder } from 'suiclient-error-decoder';

// Custom hook for error handling
function useErrorDecoder() {
  const decoder = new SuiClientErrorDecoder({
    customErrorCodes: {
      404: "NFT not found",
      405: "NFT already minted",
      406: "Mint limit exceeded",
    }
  });
  
  return decoder;
}

function MintNFTComponent() {
  const [isLoading, setIsLoading] = useState(false);
  const [error, setError] = useState<string>('');
  const { mutateAsync: signAndExecute } = useSignAndExecuteTransaction();
  const errorDecoder = useErrorDecoder();

  const mintNFT = async () => {
    setIsLoading(true);
    setError('');
    
    try {
      const txb = new Transaction();
      txb.moveCall({
        target: '0x123::nft::mint',
        arguments: [txb.pure('My NFT Name')]
      });
      
      const result = await signAndExecute({
        transactionBlock: txb,
        options: { showEffects: true }
      });
      
      console.log('NFT minted successfully:', result.digest);
    } catch (rawError) {
      const decoded = errorDecoder.parseError(rawError);
      
      // Set user-friendly error message
      setError(decoded.message);
      
      // Log detailed error for debugging
      console.error('Mint failed:', {
        code: decoded.code,
        category: decoded.category,
        isKnown: decoded.isKnownError,
        original: decoded.originalError
      });
    } finally {
      setIsLoading(false);
    }
  };

  return (
    <div>
      <button onClick={mintNFT} disabled={isLoading}>
        {isLoading ? 'Minting...' : 'Mint NFT'}
      </button>
      {error && <div className="error">{error}</div>}
    </div>
  );
}

4. Advanced Error Handling with Categorization

// ...imports
import { Transaction } from "@mysten/sui/transactions";
import { SuiClientErrorDecoder } from 'suiclient-error-decoder';

const decoder = new SuiClientErrorDecoder();

async function handleComplexTransaction() {
  try {
    // Your complex transaction logic
    const txb = new Transaction();
    // ... transaction setup
    
    const result = await suiClient.executeTransactionBlock({
      transactionBlock: txb,
      signer: keypair
    });
    
    return result;
  } catch (error) {
    const decoded = decoder.parseError(error);
    
    // Handle different error categories
    switch (decoded.category) {
      case 'move_abort':
        console.error('Smart contract error:', decoded.message);
        // Maybe retry with different parameters
        break;
        
      case 'transaction':
        console.error('Transaction processing error:', decoded.message);
        // Maybe increase gas or check network
        break;
        
      case 'sui_system':
        console.error('Sui system error:', decoded.message);
        // Maybe retry after some time
        break;
        
      default:
        console.error('Unknown error occurred:', decoded.message);
        // Log for investigation
        break;
    }
    
    // Re-throw with user-friendly message
    throw new Error(decoded.message);
  }
}

5. Batch Transaction Error Handling

async function processBatchTransactions(transactions: TransactionBlock[]) {
  const decoder = new SuiClientErrorDecoder();
  const results = [];
  const errors = [];

  for (let i = 0; i < transactions.length; i++) {
    try {
      const result = await suiClient.executeTransactionBlock({
        transactionBlock: transactions[i],
        signer: keypair
      });
      
      results.push({ index: i, success: true, result });
    } catch (error) {
      const decoded = decoder.parseError(error);
      
      errors.push({
        index: i,
        success: false,
        error: {
          code: decoded.code,
          message: decoded.message,
          category: decoded.category,
          isKnown: decoded.isKnownError
        }
      });
      
      // Continue with next transaction instead of stopping
      console.warn(`Transaction ${i} failed: ${decoded.message}`);
    }
  }

  return { results, errors };
}

6. Error Monitoring and Analytics

import { SuiClientErrorDecoder } from 'suiclient-error-decoder';

class ErrorAnalytics {
  private decoder: SuiClientErrorDecoder;
  private errorCounts: Map<string, number> = new Map();

  constructor() {
    this.decoder = new SuiClientErrorDecoder();
  }

  async executeWithAnalytics(transactionFn: () => Promise<any>) {
    try {
      return await transactionFn();
    } catch (error) {
      const decoded = this.decoder.parseError(error);
      
      // Track error frequency
      const errorKey = `${decoded.category}:${decoded.code || 'unknown'}`;
      this.errorCounts.set(errorKey, (this.errorCounts.get(errorKey) || 0) + 1);
      
      // Send to analytics service
      this.sendToAnalytics({
        timestamp: Date.now(),
        errorCode: decoded.code,
        errorMessage: decoded.message,
        category: decoded.category,
        isKnownError: decoded.isKnownError
      });
      
      throw error; // Re-throw the original error
    }
  }

  private async sendToAnalytics(errorData: any) {
    // Send to your analytics service
    console.log('Error Analytics:', errorData);
  }

  getErrorStats() {
    return Object.fromEntries(this.errorCounts);
  }
}

// Usage
const analytics = new ErrorAnalytics();

await analytics.executeWithAnalytics(async () => {
  // Your transaction code
  return await performSuiTransaction();
});

console.log('Error statistics:', analytics.getErrorStats());

7. Testing Error Scenarios

import { SuiClientErrorDecoder } from 'suiclient-error-decoder';

describe('Error Handling Tests', () => {
  const decoder = new SuiClientErrorDecoder({
    customErrorCodes: {
      999: "Test error for unit testing"
    }
  });

  test('should handle insufficient gas error', () => {
    const mockError = new Error('InsufficientGas: Transaction needs more gas');
    const decoded = decoder.parseError(mockError);
    
    expect(decoded.category).toBe('sui_system');
    expect(decoded.isKnownError).toBe(true);
    expect(decoded.message).toContain('gas');
  });

  test('should handle custom error codes', () => {
    const mockError = new Error('MoveAbort(0x123, 999)');
    const decoded = decoder.parseError(mockError);
    
    expect(decoded.code).toBe(999);
    expect(decoded.message).toContain('Test error for unit testing');
    expect(decoded.isKnownError).toBe(true);
  });

  test('should handle unknown errors gracefully', () => {
    const mockError = new Error('Some unknown error');
    const decoded = decoder.parseError(mockError);
    
    expect(decoded.category).toBe('unknown');
    expect(decoded.isKnownError).toBe(false);
    expect(typeof decoded.message).toBe('string');
  });
});

API Reference

SuiClientErrorDecoder

Constructor

new SuiClientErrorDecoder(options?: {
  customErrorCodes?: Record<number, string>;
  customTransactionErrors?: Record<string, string>;
  includeDefaults?: boolean; // Default: true
})

Methods

  • parseError(error: any): ParsedError - The core method. Parses and categorizes an error into a ParsedError object.
  • decodeError(error: any): string - A convenience method that returns only the human-readable error message string.
  • addErrorCodes(codes: Record<number, string>): void - Adds or updates custom numeric error codes at runtime.
  • addTransactionErrors(errors: Record<string, string>): void - Adds or updates custom string-based transaction errors at runtime.
  • updateDefaultErrorCodes(defaultCodes: Record<number, string>): void - Replaces the built-in default codes, preserving any custom codes you've added.
  • updateDefaultTransactionErrors(defaultErrors: Record<string, string>): void - Replaces the built-in default transaction errors, preserving custom ones.
  • getErrorCodes(): Record<number, string> - Returns a copy of all numeric error codes currently in use (defaults + custom).
  • getTransactionErrors(): Record<string, string> - Returns a copy of all transaction errors currently in use.
  • getErrorMessage(code: number): string | null - Gets the message for a specific numeric error code.
  • getTransactionErrorMessage(errorType: string): string | null - Gets the message for a specific transaction error type.
  • isKnownErrorCode(code: number): boolean - Checks if a numeric error code is in the map.
  • isKnownTransactionError(errorType: string): boolean - Checks if a transaction error type is in the map.

ParsedError Object

interface ParsedError {
  code?: number;                    // Numeric error code (if available)
  errorType?: string;              // Transaction error type (if available)
  message: string;                 // Human-readable error message
  isKnownError: boolean;          // Whether error is recognized
  category: 'move_abort' | 'transaction' | 'sui_system' | 'unknown';
  originalError: any;             // Original error object for debugging
}

Utility Functions

// Quick error decoding without creating decoder instance
decodeSuiError(error: any, customCodes?: Record<number, string>, customTransactionErrors?: Record<string, string>): string

// Default decoder instance
import { defaultDecoder } from 'suiclient-error-decoder';

Error Categories

Category Description Example Error Codes
move_abort Errors from Move smart contracts 1000-1999
sui_system Sui node/system-level errors 2000-2999
transaction Transaction processing errors String-based
unknown Unrecognized error patterns N/A

Best Practices

  1. Always handle errors: Wrap your Sui transactions in try-catch blocks
  2. Use specific error codes: Define custom error codes for your smart contracts
  3. Categorize handling: Handle different error categories appropriately
  4. Log for debugging: Keep original error objects for development debugging
  5. User-friendly messages: Show decoded messages to users, not raw errors

Contributing

Contributions are welcome! Please open an issue or submit a PR:

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/AmazingFeature)
  3. Commit your changes (git commit -m 'Add some amazing feature')
  4. Push to the branch (git push origin feature/AmazingFeature)
  5. Open a pull request

Support

For support, please open an issue on our GitHub repository

About

A TypeScript library for decoding Sui blockchain transaction errors, including Move abort errors and custom error codes. Simplifies error handling for Sui dApps and smart contracts with a robust, extensible error decoder.

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published