Skip to content

Latest commit

 

History

History
451 lines (369 loc) · 11 KB

File metadata and controls

451 lines (369 loc) · 11 KB

Refactoring Quick Start Guide

Problem Statement

Your codebase has 200+ lines of duplicate code and 6 major patterns that repeat across files. This guide shows exactly how to refactor starting with the highest-impact item.


Example 1: TransactionPoller (HIGH PRIORITY)

Current State (Duplicated in 5+ files)

// In web3_instance.ts, transfer_for_gas.ts, bitcoin_raw_signer.ts, etc.
while (
  currentStatus !== TransactionStatus.COMPLETED &&
  currentStatus !== TransactionStatus.FAILED &&
  currentStatus !== TransactionStatus.BLOCKED &&
  currentStatus !== TransactionStatus.CANCELLED
) {
  try {
    console.log(`Polling for tx ${txid}; status: ${currentStatus}`);
    txInfo = await fireblocksApiClient.getTransactionById(txid);
    currentStatus = txInfo.status;
  } catch (err) {
    console.error("Error while polling transaction:", err);
  }
  await new Promise(resolve => setTimeout(resolve, 1000));
}

if (currentStatus === TransactionStatus.FAILED || 
    currentStatus === TransactionStatus.BLOCKED ||
    currentStatus === TransactionStatus.REJECTED) {
  await fireblocksApiClient.cancelTransactionById(txid);
}

Solution: Create Utility Class

File: src/utils/TransactionPoller.ts

import {
  FireblocksSDK,
  TransactionStatus,
  FireblocksTransaction,
} from "fireblocks-sdk";

export interface PollingOptions {
  maxAttempts?: number;
  intervalMs?: number;
  autoCancel?: boolean;
}

export class TransactionPoller {
  private static readonly DEFAULT_INTERVAL_MS = 1000;
  private static readonly TERMINAL_STATUSES = [
    TransactionStatus.COMPLETED,
    TransactionStatus.FAILED,
    TransactionStatus.BLOCKED,
    TransactionStatus.CANCELLED,
  ];

  private static readonly CANCELABLE_STATUSES = [
    TransactionStatus.FAILED,
    TransactionStatus.BLOCKED,
    TransactionStatus.REJECTED,
  ];

  constructor(
    private fireblocksApi: FireblocksSDK,
    private readonly options: PollingOptions = {}
  ) {}

  async poll(
    transactionId: string,
    onStatusChange?: (status: string) => void
  ): Promise<FireblocksTransaction> {
    const maxAttempts = this.options.maxAttempts ?? Infinity;
    const intervalMs = this.options.intervalMs ?? TransactionPoller.DEFAULT_INTERVAL_MS;
    const autoCancel = this.options.autoCancel !== false;

    let currentStatus: string | undefined;
    let txInfo: FireblocksTransaction | undefined;
    let attempts = 0;

    while (
      attempts < maxAttempts &&
      !TransactionPoller.isTerminalStatus(currentStatus)
    ) {
      try {
        txInfo = await this.fireblocksApi.getTransactionById(transactionId);
        currentStatus = txInfo.status;
        
        if (onStatusChange) {
          onStatusChange(currentStatus);
        }
      } catch (error) {
        console.error(`Error polling transaction ${transactionId}:`, error);
        throw error;
      }

      if (!TransactionPoller.isTerminalStatus(currentStatus)) {
        await new Promise(resolve => setTimeout(resolve, intervalMs));
      }

      attempts++;
    }

    if (!txInfo) {
      throw new Error(`Failed to retrieve transaction ${transactionId}`);
    }

    // Auto-cancel failed transactions if enabled
    if (autoCancel && TransactionPoller.isCancelableStatus(currentStatus)) {
      await this.cancelTransaction(transactionId);
    }

    return txInfo;
  }

  private async cancelTransaction(transactionId: string): Promise<void> {
    try {
      await this.fireblocksApi.cancelTransactionById(transactionId);
      console.log(`Cancelled transaction ${transactionId}`);
    } catch (error) {
      console.error(`Failed to cancel transaction ${transactionId}:`, error);
    }
  }

  private static isTerminalStatus(status?: string): boolean {
    return status ? this.TERMINAL_STATUSES.includes(status as TransactionStatus) : false;
  }

  private static isCancelableStatus(status?: string): boolean {
    return status ? this.CANCELABLE_STATUSES.includes(status as TransactionStatus) : false;
  }
}

How to Use

Before (consolidate_unsupported_assets_with_raw_transactions.ts):

while (currentStatus !== TransactionStatus.COMPLETED && ...) {
  try {
    const txInfo = await fireblocksApiClient.getTransactionById(txid);
    currentStatus = txInfo.status;
  } catch (err) {
    console.error("Error while polling transaction:", err);
  }
  await new Promise(resolve => setTimeout(resolve, 1000));
}

if (currentStatus === TransactionStatus.FAILED || ...) {
  await fireblocksApiClient.cancelTransactionById(txid);
}

After:

import { TransactionPoller } from "../utils/TransactionPoller";

const poller = new TransactionPoller(fireblocksApiClient, {
  maxAttempts: 60,
  intervalMs: 1000,
  autoCancel: true,
});

const txInfo = await poller.poll(txid, (status) => {
  console.log(`Transaction status: ${status}`);
});

Files to Update

  • EVM/web3_instance.ts - Replace lines 87-117
  • EVM/transfer_for_gas.ts - Replace lines 24-60
  • EVM/deposit_eth_gas_to_vault.ts - Replace lines 22-61
  • EVM/deposit_gas_to_many_specified_tokens_and_chains.ts - Replace lines 59-94
  • Bitcoin/bitcoin_raw_signer.ts - Replace lines 75-100

Impact: Removes 100+ lines of duplicate code, makes polling consistent across codebase


Example 2: Magic Numbers to Constants

Create: src/constants.ts

// Gas and Transaction Configuration
export const GAS_CONFIG = {
  NATIVE_TRANSFER_LIMIT: 21000,
  ESTIMATION_BUFFER: 1.2,
  POLLING_INTERVAL_MS: 1000,
};

// Balance Thresholds
export const BALANCE_THRESHOLDS = {
  MIN_GAS_BALANCE_ETH: 0.0005,
  MIN_BALANCE_CHECK_ETH: 0.0001,
  MIN_BALANCE_SEARCH_ETH: 0.0009,
};

// RPC Endpoints
export const RPC_ENDPOINTS = {
  ETHEREUM_FLASHBOTS: 'https://rpc.flashbots.net',
  BSC: 'https://bsc-dataseed1.bnbchain.org',
};

// Color Codes (ANSI)
export const COLOR_CODES = {
  RED: '31',
  GREEN: '32',
  YELLOW: '33',
  MAGENTA: '35',
  CYAN: '36',
};

// Asset Configurations
export const ASSET_IDS = {
  ETH: 'ETH',
  ETH_TEST3: 'ETH_TEST3',
  MATIC: 'MATIC',
  MATIC_POLYGON: 'MATIC_POLYGON',
} as const;

// Peer Types
export const PEER_TYPES = {
  VAULT_ACCOUNT: 'VAULT_ACCOUNT',
  ONE_TIME_ADDRESS: 'ONE_TIME_ADDRESS',
} as const;

Usage Example

Before:

const gasLimit = Math.floor(estimatedGas * 1.2); // What is 1.2?
const minimumGasBalance = 0.0005; // Magic number
const httpProviderURL = 'https://rpc.flashbots.net'; // Hardcoded
console.log(colorLog("message", "32")); // What is "32"?

After:

import { GAS_CONFIG, BALANCE_THRESHOLDS, RPC_ENDPOINTS, COLOR_CODES } from "../constants";

const gasLimit = Math.floor(estimatedGas * GAS_CONFIG.ESTIMATION_BUFFER);
const minimumGasBalance = BALANCE_THRESHOLDS.MIN_GAS_BALANCE_ETH;
const httpProviderURL = RPC_ENDPOINTS.ETHEREUM_FLASHBOTS;
console.log(colorLog("message", COLOR_CODES.GREEN));

Example 3: Missing Type Definitions

Create: src/types.ts

import { FireblocksSDK } from "fireblocks-sdk";
import Web3 from "web3";

// Fireblocks-related types
export interface FireblocksConfig {
  apiSecret: string;
  apiKey: string;
}

export interface DepositAddress {
  address: string;
  tag?: string;
  type?: string;
}

// Transaction-related types
export interface TransactionOptions {
  existingTransactionId?: string;
  autoCancel?: boolean;
  maxAttempts?: number;
}

export interface TransferRequest {
  fireblocksApiClient: FireblocksSDK;
  ethereumProviderUrl: string;
  sourceVaultAccountId: string | number;
  recipientAddress: string;
  assetIdentifier: string;
  assetSymbol: string;
  transferAmount: number;
  erc20ContractAddress?: string;
  transactionFilename?: string;
  existingTransactionId?: string;
  destinationVault?: number;
}

// Balance-related types
export interface BalanceCheckResult {
  address: string;
  balance: string;
}

export interface TokenBalance {
  tokenBalance: string;
  nativeBalance: string;
}

// CSV-related types
export interface CryptoData {
  Coin: string;
  Network: string;
  Vaults: string;
  RowNumber: number;
  Balance: string;
}

export interface ContractData {
  Coin: string;
  "Token Name": string;
  Contract: string;
}

export interface ChainData {
  Network: string;
  RPC: string;
}

export interface VaultData {
  Vault: string;
  NativeToken: string;
  Address: string;
  Coin: string;
}

Update Function Signatures

Before:

export async function transfer(
  fireblocksApiClient,
  ethereumProviderUrl,
  sourceVaultAccountId,
  recipientAddress,
  assetIdentifier,
  assetSymbol,
  transferAmount = 0,
  erc20ContractAddress?,
  transactionFilename?,
  existingTransactionId?,
  destinationVault = 0
) {
  // ...
}

After:

import { TransferRequest } from "../types";

export async function transfer(request: TransferRequest): Promise<void> {
  const {
    fireblocksApiClient,
    ethereumProviderUrl,
    sourceVaultAccountId,
    recipientAddress,
    assetIdentifier,
    assetSymbol,
    transferAmount = 0,
    erc20ContractAddress,
    transactionFilename,
    existingTransactionId,
    destinationVault = 0,
  } = request;
  
  // Implementation
}

Step-by-Step Implementation Plan

Week 1: Critical Fixes

  1. Day 1: Fix undefined variable bug in bitcoin_raw_signer.ts:90
  2. Day 2: Create TransactionPoller utility class
  3. Day 3: Create constants.ts file
  4. Day 4: Create types.ts file
  5. Day 5: Update 6 files to use TransactionPoller

Week 2: Consolidation

  1. Day 6-7: Remove duplicate Balance Checker class
  2. Day 8: Extract CLI Argument Parser
  3. Day 9-10: Add return types to public functions

Week 3-4: Enhancement

  1. Extract ERC20 Transfer Handler
  2. Refactor initWeb3Instance()
  3. Create Error Handling utilities

Testing Checklist

Before and after each refactoring:

  • Code compiles without errors
  • No new TypeScript warnings
  • Existing tests still pass (if any)
  • Manual testing on test network
  • Verify transaction polling works as expected
  • Check colored console output displays correctly
  • Confirm error handling catches exceptions properly

Rollback Strategy

If something breaks:

  1. Commit working version before refactoring
  2. Keep old code in separate branch
  3. Test each change independently
  4. Use git bisect to find problematic commit
# Create backup branch
git checkout -b refactoring-backup

# Make changes on main branch
git checkout main

# If issue found, compare with backup
git diff refactoring-backup

Expected Results

Code Quality Metrics

  • Reduce duplicate code by 75% (200 → 50 lines)
  • Reduce magic numbers from 40+ to 5
  • Increase type coverage from 30% to 95%
  • Reduce average function length from 35 to 25 lines
  • Improve error handling coverage from 40% to 75%

Maintainability Improvements

  • Changes to polling logic only needed in 1 place instead of 6
  • New developers can understand patterns more easily
  • IDE can provide better autocomplete with proper types
  • Tests become easier to write with clear interfaces

See REFACTORING_ANALYSIS.md for complete details on all findings.