TypeScript~封装通用合约调用工具

4 天前(已编辑)
/
4
1
摘要
项目中包含大量合约交互,推荐使用次方法,封装通用的合约调用,避免重复造轮子。

TypeScript~封装通用合约调用工具

前言

在 Web3 开发中,与智能合约的交互是核心功能。但直接使用 ethers.js 或 web3.js 进行合约调用时,我们常常会遇到这些问题:

开发痛点:

  • 🔴 代码重复:每次调用都要重复创建 Provider、Signer、Contract 实例
  • 🔴 错误处理混乱:各种异常分散在业务代码中,难以统一管理
  • 🔴 性能低下:频繁创建连接对象,缺少复用机制
  • 🔴 网络不稳定:区块链 RPC 节点经常超时,缺少重试机制
  • 🔴 维护困难:业务逻辑和技术细节耦合,改一处动全身

本文目标:

通过生产环境实战经验,教你构建一个分层清晰、功能完善、工程化的合约调用工具库,让你的 Web3 代码更优雅、更可靠。

你将学到:

  • ✅ 四层架构设计思路
  • ✅ Provider/Signer 连接池管理
  • ✅ 超时重试机制实现
  • ✅ Gas 自动估算与降级
  • ✅ 5 种设计模式的实战应用
  • ✅ 生产环境最佳实践

架构设计

整体架构

我们采用四层架构设计,自底向上分别是:

┌─────────────────────────────────────┐
│   Business Layer (业务封装层)        │  ← 面向具体业务的方法
│   ContractCall                      │
├─────────────────────────────────────┤
│   Service Layer (服务抽象层)         │  ← 通用的读写方法
│   ContractService                   │
├─────────────────────────────────────┤
│   Provider Layer (连接管理层)        │  ← Provider/Signer 管理
│   ProviderManager                   │
├─────────────────────────────────────┤
│   Type Layer (类型定义层)            │  ← 接口和类型定义
│   Interfaces & Types                │
└─────────────────────────────────────┘

设计原则

  1. 单一职责:每层只负责自己的功能
  2. 依赖倒置:上层依赖下层接口,而非具体实现
  3. 开闭原则:对扩展开放,对修改封闭
  4. DRY 原则:消除代码重复

类型定义层

首先定义统一的类型接口,确保类型安全。

// src/contract/type.ts

/**
 * 合约配置接口
 */
export interface ContractConfig {
    address: string;      // 合约地址
    abi: any[];          // 合约 ABI
    bytecode?: string;   // 合约字节码(部署时需要)
}

/**
 * 交易选项
 */
export interface TransactionOptions {
    gasLimit?: string;
    gasPrice?: string;
    maxFeePerGas?: string;
    maxPriorityFeePerGas?: string;
    value?: string;
}

/**
 * 统一返回结果
 */
export interface ContractCallResult {
    success: boolean;           // 是否成功
    result?: any;              // 返回结果
    error?: string;            // 错误信息
    transactionHash?: string;  // 交易哈希
    gasUsed?: string;          // Gas 消耗
}

💡 设计亮点

  • 统一返回格式:所有方法都返回 ContractCallResult,便于统一处理成功和失败情况
  • 可选字段:使用 ? 标记可选字段,提高灵活性
  • 类型安全:充分利用 TypeScript 的类型检查

Provider 管理层

Provider 管理层负责创建和管理与区块链的连接,是整个架构的基础。

核心功能

  1. 连接池管理:缓存 Provider 和 Signer 实例
  2. 超时控制:多种方式配置请求超时
  3. 重试机制:指数退避的自动重试
  4. 网络信息查询:获取链信息、区块、余额等

完整实现

// src/contract/provider.ts

import { environment } from '../config/environment';
import { ethers } from 'ethers';

export class ProviderManager {
    // 使用 Map 缓存 Provider 和 Signer
    private providers: Map<string, ethers.JsonRpcProvider> = new Map();
    private signers: Map<string, ethers.Wallet> = new Map();

    private defaultTimeout = 30000;        // 30 秒
    private defaultPollingInterval = 4000; // 4 秒

    constructor(
        private privateKey?: string,
        private timeout: number = 30000,
        private pollingInterval: number = 4000
    ) {
        this.defaultTimeout = timeout;
        this.defaultPollingInterval = pollingInterval;
    }

    /**
     * 获取 Provider(带缓存)
     */
    getProvider(networkName: string): ethers.JsonRpcProvider {
        if (!this.providers.has(networkName)) {
            const network = environment.networks[networkName];
            if (!network) {
                throw new Error(`Network ${networkName} not found`);
            }

            // 方法1: 构造函数配置
            const provider = new ethers.JsonRpcProvider(
                network.rpc,
                undefined,
                {
                    staticNetwork: true,
                    pollingInterval: this.defaultPollingInterval,
                    cacheTimeout: this.defaultTimeout
                }
            );

            // 方法2: 全局超时配置
            provider._getConnection().timeout = this.defaultTimeout;

            // 方法3: 轮询间隔配置
            provider.pollingInterval = this.defaultPollingInterval;

            this.providers.set(networkName, provider);
        }

        return this.providers.get(networkName)!;
    }

    /**
     * 获取 Signer(带缓存)
     */
    getSigner(networkName: string): ethers.Wallet {
        if (!this.privateKey) {
            throw new Error('Private key not provided');
        }

        if (!this.signers.has(networkName)) {
            const provider = this.getProvider(networkName);
            const signer = new ethers.Wallet(this.privateKey, provider);
            this.signers.set(networkName, signer);
        }

        return this.signers.get(networkName)!;
    }

    /**
     * 获取网络信息(带重试)
     */
    async getNetworkInfo(networkName: string, retries: number = 3): Promise<any> {
        const provider = this.getProvider(networkName);

        for (let attempt = 1; attempt <= retries; attempt++) {
            try {
                // 使用 Promise.race 实现超时控制
                const networkPromise = Promise.race([
                    provider.getNetwork(),
                    this.timeoutPromise(this.defaultTimeout, 'Network info request timed out')
                ]);

                const blockNumberPromise = Promise.race([
                    provider.getBlockNumber(),
                    this.timeoutPromise(this.defaultTimeout, 'Block number request timed out')
                ]);

                const gasPricePromise = Promise.race([
                    provider.getFeeData(),
                    this.timeoutPromise(this.defaultTimeout, 'Gas price request timed out')
                ]);

                // 并行请求,提高效率
                const [network, blockNumber, gasPrice] = await Promise.all([
                    networkPromise,
                    blockNumberPromise,
                    gasPricePromise
                ]);

                return {
                    name: network.name,
                    chainId: network.chainId,
                    blockNumber,
                    gasPrice: gasPrice.gasPrice?.toString(),
                    maxFeePerGas: gasPrice.maxFeePerGas?.toString(),
                    maxPriorityFeePerGas: gasPrice.maxPriorityFeePerGas?.toString()
                };
            } catch (error) {
                console.warn(`Attempt ${attempt} failed:`, error);

                if (attempt === retries) {
                    throw new Error(`Failed to get network info after ${retries} attempts: ${error}`);
                }

                // 指数退避:1s, 2s, 3s...
                await new Promise(resolve => setTimeout(resolve, attempt * 1000));
            }
        }
    }

    /**
     * 获取地址余额(带重试)
     */
    async getBalance(address: string, networkName: string, retries: number = 3): Promise<string> {
        const provider = this.getProvider(networkName);

        for (let attempt = 1; attempt <= retries; attempt++) {
            try {
                const balancePromise = Promise.race([
                    provider.getBalance(address),
                    this.timeoutPromise(this.defaultTimeout, 'Balance request timed out')
                ]);

                const balance = await balancePromise;
                return ethers.formatEther(balance);
            } catch (error) {
                console.warn(`Balance request attempt ${attempt} failed:`, error);

                if (attempt === retries) {
                    throw new Error(`Failed to get balance after ${retries} attempts: ${error}`);
                }

                await new Promise(resolve => setTimeout(resolve, attempt * 1000));
            }
        }

        throw new Error('This should never be reached');
    }

    /**
     * 超时 Promise 辅助方法
     */
    private timeoutPromise(ms: number, message: string): Promise<never> {
        return new Promise((_, reject) => {
            setTimeout(() => reject(new Error(message)), ms);
        });
    }
}

💡 核心技巧

1. 缓存机制

private providers: Map<string, ethers.JsonRpcProvider> = new Map();

// 检查缓存,避免重复创建
if (!this.providers.has(networkName)) {
    // 创建并缓存
    this.providers.set(networkName, provider);
}

优势

  • 减少重复创建开销
  • 复用 TCP 连接
  • 提高响应速度

2. 超时控制三板斧

// 方法1: 构造函数配置
const provider = new ethers.JsonRpcProvider(rpc, undefined, {
    cacheTimeout: 30000
});

// 方法2: 全局配置
provider._getConnection().timeout = 30000;

// 方法3: Promise.race 实现精确超时
Promise.race([
    provider.getNetwork(),
    this.timeoutPromise(30000, 'Timeout')
]);

3. 指数退避重试

for (let attempt = 1; attempt <= retries; attempt++) {
    try {
        return await operation();
    } catch (error) {
        if (attempt === retries) throw error;
        
        // 等待时间随重试次数增加:1s, 2s, 3s...
        await new Promise(resolve => setTimeout(resolve, attempt * 1000));
    }
}

为什么用指数退避?

  • 避免对服务器造成压力
  • 给网络恢复时间
  • 提高成功率

服务抽象层

服务层提供通用的合约调用方法,屏蔽底层细节。

核心功能

  1. 合约实例管理:缓存合约实例
  2. 读写分离:区分只读和写入操作
  3. 自动 Gas 估算:智能估算 Gas,失败时降级
  4. 交易日志:详细记录交易状态
  5. 统一错误处理:返回标准化结果

完整实现

// src/contract/service.ts

import { ethers } from 'ethers';
import { ProviderManager } from './provider';
import { ContractConfig, ContractCallResult, TransactionOptions } from './type';

export class ContractService {
    private contracts: Map<string, ethers.Contract> = new Map();
    
    constructor(private providerManager: ProviderManager) {}

    /**
     * 获取合约实例(带缓存)
     * @param withSigner - 是否需要 Signer(写入操作需要)
     */
    getContract(
        contractConfig: ContractConfig,
        networkName: string,
        withSigner: boolean = false
    ): ethers.Contract {
        // 使用组合键:地址_网络_是否签名
        const key = `${contractConfig.address}_${networkName}_${withSigner}`;
        
        if (!this.contracts.has(key)) {
            let providerOrSigner;

            if (withSigner) {
                providerOrSigner = this.providerManager.getSigner(networkName);
            } else {
                providerOrSigner = this.providerManager.getProvider(networkName);
            }

            const contract = new ethers.Contract(
                contractConfig.address,
                contractConfig.abi,
                providerOrSigner
            );

            this.contracts.set(key, contract);
        }

        return this.contracts.get(key)!;
    }

    /**
     * 调用只读函数
     */
    async callReadFunction(
        contractConfig: ContractConfig,
        networkName: string,
        functionName: string,
        params: any[] = []
    ): Promise<ContractCallResult> {
        try {
            const contract = this.getContract(contractConfig, networkName, false);
            const result = await contract[functionName](...params);

            return {
                success: true,
                result: result
            };
        } catch (error) {
            return {
                success: false,
                error: error instanceof Error ? error.message : 'Unknown error'
            };
        }
    }

    /**
     * 调用写入函数
     */
    async callWriteFunction(
        contractConfig: ContractConfig,
        networkName: string,
        functionName: string,
        params: any[] = [],
        options: TransactionOptions = {}
    ): Promise<ContractCallResult> {
        try {
            const contract = this.getContract(contractConfig, networkName, true);

            // 构建交易选项
            const txOptions: any = {};
            if (options.gasLimit) txOptions.gasLimit = options.gasLimit;
            if (options.gasPrice) txOptions.gasPrice = options.gasPrice;
            if (options.maxFeePerGas) txOptions.maxFeePerGas = options.maxFeePerGas;
            if (options.maxPriorityFeePerGas) txOptions.maxPriorityFeePerGas = options.maxPriorityFeePerGas;
            if (options.value) txOptions.value = options.value;

            // 🔥 自动 Gas 估算(带降级)
            let estimatedGas: bigint;
            try {
                estimatedGas = await contract[functionName].estimateGas(...params, txOptions);
                console.log(`Estimated gas: ${estimatedGas.toString()}`);
            } catch (error) {
                // 估算失败,使用默认值
                estimatedGas = BigInt(500_000);
                console.warn('Gas estimation failed, using default:', estimatedGas);
            }

            // 发送交易
            const tx = await contract[functionName](...params, {
                ...txOptions,
                gasLimit: txOptions.gasLimit || estimatedGas
            });

            console.log(`Transaction sent: ${tx.hash}`);
            console.log(`Network: ${networkName}`);
            console.log(`Contract: ${contractConfig.address}`);

            // 等待确认
            const receipt = await tx.wait();
            console.log(`Confirmed in block: ${receipt.blockNumber}`);

            return {
                success: true,
                result: receipt,
                transactionHash: tx.hash,
                gasUsed: receipt.gasUsed.toString()
            };
        } catch (error) {
            return {
                success: false,
                error: error instanceof Error ? error.message : 'Unknown error'
            };
        }
    }

    /**
     * 查询区块时间戳
     */
    async getBlockTimestamp(
        networkName: string,
        blockNumber?: number | string
    ): Promise<ContractCallResult> {
        try {
            const provider = this.providerManager.getProvider(networkName);
            const blockTag = blockNumber || 0;
            const block = await provider.getBlock(blockTag);

            if (!block) {
                return {
                    success: false,
                    error: `Block ${blockTag} not found`
                };
            }

            const result = {
                blockNumber: block.number,
                timestamp: block.timestamp,
                hash: block.hash,
                date: new Date(block.timestamp * 1000).toISOString()
            };

            return {
                success: true,
                result: result
            };
        } catch (error) {
            return {
                success: false,
                error: error instanceof Error ? error.message : 'Unknown error'
            };
        }
    }
}

💡 核心技巧

1. 智能缓存键设计

const key = `${contractConfig.address}_${networkName}_${withSigner}`;

为什么需要三个维度?

  • address:同一网络可能有多个合约
  • networkName:同一合约可能部署在多个网络
  • withSigner:同一合约可能需要只读和可写两种实例

2. Gas 估算的容错处理

try {
    estimatedGas = await contract.estimateGas(...params);
} catch (error) {
    // 降级方案:使用合理的默认值
    estimatedGas = BigInt(500_000);
}

为什么需要降级?

  • 某些复杂合约 Gas 估算可能失败
  • 有些链的 Gas 估算不准确
  • 保证交易能继续执行

3. 详细的日志记录

console.log(`Transaction sent: ${tx.hash}`);
console.log(`Network: ${networkName}`);
console.log(`Contract: ${contractConfig.address}`);
console.log(`Confirmed in block: ${receipt.blockNumber}`);

优势

  • 便于追踪交易状态
  • 方便问题排查和调试
  • 可扩展接入日志系统或通知服务

业务封装层

业务层提供面向具体业务场景的高级 API,将底层的通用调用封装成语义化的业务方法。

完整实现

// src/contract/index.ts

import { environment } from "../config/environment";
import { ProviderManager } from "./provider";
import { ContractService } from "./service";
import erc20Abi from "../abis/erc20.json";
import tokenSaleAbi from "../abis/tokenSale.json";
import { ContractCallResult } from "./type";

export class ContractManager {
    private contractService: ContractService;
    private providerManager: ProviderManager;

    constructor() {
        this.providerManager = new ProviderManager(environment.privateKey);
        this.contractService = new ContractService(this.providerManager);
    }

    /**
     * 查询 ERC20 代币余额
     */
    async getTokenBalance(
        networkName: string,
        tokenAddress: string,
        accountAddress: string
    ): Promise<ContractCallResult> {
        const contractConfig = {
            address: tokenAddress,
            abi: erc20Abi
        };

        return await this.contractService.callReadFunction(
            contractConfig,
            networkName,
            "balanceOf",
            [accountAddress]
        );
    }

    /**
     * 查询代币总供应量
     */
    async getTotalSupply(
        networkName: string,
        tokenAddress: string
    ): Promise<ContractCallResult> {
        const contractConfig = {
            address: tokenAddress,
            abi: erc20Abi
        };

        return await this.contractService.callReadFunction(
            contractConfig,
            networkName,
            "totalSupply"
        );
    }

    /**
     * 授权代币(复合调用示例)
     */
    async approveToken(
        networkName: string,
        tokenAddress: string,
        spenderAddress: string,
        amount: string
    ): Promise<ContractCallResult> {
        const contractConfig = {
            address: tokenAddress,
            abi: erc20Abi
        };

        // 步骤1: 检查当前授权额度
        const allowanceResult = await this.contractService.callReadFunction(
            contractConfig,
            networkName,
            "allowance",
            [environment.walletAddress, spenderAddress]
        );

        if (!allowanceResult.success) {
            return allowanceResult;
        }

        // 步骤2: 如果授权额度不足,进行授权
        if (BigInt(allowanceResult.result) < BigInt(amount)) {
            return await this.contractService.callWriteFunction(
                contractConfig,
                networkName,
                "approve",
                [spenderAddress, amount]
            );
        }

        return {
            success: true,
            result: "Already approved"
        };
    }

    /**
     * 转账代币
     */
    async transferToken(
        networkName: string,
        tokenAddress: string,
        toAddress: string,
        amount: string
    ): Promise<ContractCallResult> {
        const contractConfig = {
            address: tokenAddress,
            abi: erc20Abi
        };

        return await this.contractService.callWriteFunction(
            contractConfig,
            networkName,
            "transfer",
            [toAddress, amount]
        );
    }

    /**
     * 检查合约是否暂停
     */
    async isPaused(
        networkName: string,
        contractAddress: string,
        abi: any[]
    ): Promise<ContractCallResult> {
        const contractConfig = {
            address: contractAddress,
            abi: abi
        };

        return await this.contractService.callReadFunction(
            contractConfig,
            networkName,
            "paused"
        );
    }

    /**
     * 查询区块时间戳
     */
    async getBlockTimestamp(
        networkName: string,
        blockNumber: number
    ): Promise<number> {
        try {
            const res = await this.contractService.getBlockTimestamp(
                networkName,
                blockNumber
            );
            return res.result?.timestamp ?? 0;
        } catch (error) {
            console.error(`getBlockTimestamp error: ${error}`);
            return 0;
        }
    }
}

💡 设计亮点

1. 复合调用

async approveToken(networkName, tokenAddress, spenderAddress, amount) {
    // 步骤1: 检查当前授权额度
    const allowanceResult = await this.callReadFunction(..., "allowance", [...]);
    
    // 步骤2: 如果授权额度不足,进行授权
    if (BigInt(allowanceResult.result) < BigInt(amount)) {
        return await this.callWriteFunction(..., "approve", [spenderAddress, amount]);
    }
    
    return { success: true, result: "Already approved" };
}

优势

  • 封装多步骤调用逻辑
  • 智能判断,避免不必要的操作
  • 对外提供简洁的 API

2. 语义化方法名

// ❌ 不好的命名
async call(contract, method) { ... }
async query(address, func) { ... }

// ✅ 好的命名
async getTokenBalance() { ... }
async transferToken() { ... }
async isPaused() { ... }

优势

  • 见名知义,一眼看懂功能
  • 减少文档需求
  • 提高代码可读性

3. 统一的错误处理

try {
    const res = await this.contractService.getBlockTimestamp(...);
    return res.result?.timestamp ?? 0;
} catch (error) {
    console.error(`getBlockTimestamp error: ${error}`);
    return 0; // 返回默认值,保证程序继续运行
}

设计模式应用

1. 单例模式(Singleton)

应用场景:Provider 和 Signer 的缓存管理

private providers: Map<string, ethers.JsonRpcProvider> = new Map();

getProvider(networkName: string): ethers.JsonRpcProvider {
    if (!this.providers.has(networkName)) {
        this.providers.set(networkName, new ethers.JsonRpcProvider(...));
    }
    return this.providers.get(networkName)!;
}

优势

  • 避免重复创建实例
  • 节省资源
  • 提高性能

2. 工厂模式(Factory)

应用场景:Contract 实例的创建

getContract(config: ContractConfig, network: string, withSigner: boolean): Contract {
    const key = `${config.address}_${network}_${withSigner}`;
    
    if (!this.contracts.has(key)) {
        const providerOrSigner = withSigner 
            ? this.providerManager.getSigner(network)
            : this.providerManager.getProvider(network);
        
        this.contracts.set(key, new ethers.Contract(
            config.address,
            config.abi,
            providerOrSigner
        ));
    }
    
    return this.contracts.get(key)!;
}

优势

  • 统一创建逻辑
  • 便于扩展
  • 支持缓存

3. 策略模式(Strategy)

应用场景:读写分离

// 策略1: 只读操作(使用 Provider)
async callReadFunction(...) {
    const contract = this.getContract(config, network, false); // Provider
    return await contract[functionName](...params);
}

// 策略2: 写入操作(使用 Signer)
async callWriteFunction(...) {
    const contract = this.getContract(config, network, true); // Signer
    return await contract[functionName](...params);
}

优势

  • 根据场景选择不同策略
  • 代码清晰易懂
  • 便于维护

4. 门面模式(Facade)

应用场景:业务封装层

export class ContractCall {
    // 隐藏复杂的底层调用
    async getLatestPricerInfo(networkName: string, contractAddress: string) {
        // 内部处理多步骤调用
        const priceIdRes = await this.getLatestPriceID(...);
        return await this.contractService.callReadFunction(...);
    }
}

优势

  • 简化接口
  • 降低使用难度
  • 提高可读性

5. 缓存模式(Cache)

应用场景:贯穿各层的实例缓存

// Provider 层
private providers: Map<string, ethers.JsonRpcProvider> = new Map();

// Service 层
private contracts: Map<string, ethers.Contract> = new Map();

优势

  • 减少对象创建开销
  • 复用连接和实例
  • 显著提升性能

最佳实践

1. 错误处理

✅ 统一的返回格式

interface ContractCallResult {
    success: boolean;
    result?: any;
    error?: string;
}

// 使用示例
const result = await contractCall.getVaultPrice(network, address);
if (result.success) {
    console.log('Price:', result.result);
} else {
    console.error('Error:', result.error);
}

✅ 优雅的降级策略

try {
    estimatedGas = await contract.estimateGas(...);
} catch (error) {
    // 降级:使用默认值
    estimatedGas = BigInt(500_000);
}

2. 性能优化

✅ 并行请求

const [network, blockNumber, gasPrice] = await Promise.all([
    provider.getNetwork(),
    provider.getBlockNumber(),
    provider.getFeeData()
]);

✅ 实例缓存

private providers: Map<string, Provider> = new Map();
private signers: Map<string, Signer> = new Map();
private contracts: Map<string, Contract> = new Map();

3. 可观测性

✅ 日志记录

console.log(`Transaction sent: ${tx.hash}`);
console.log(`Confirmed in block: ${receipt.blockNumber}`);
console.error(`Error: ${error.message}`);

扩展建议

  • 接入 Winston/Bunyan 等专业日志库
  • 集成 Sentry 进行错误追踪
  • 添加通知服务(Telegram/Slack/Email)

4. 可配置性

✅ 构造函数注入

constructor(
    private privateKey?: string,
    private timeout: number = 30000,
    private pollingInterval: number = 4000
) { }

✅ 可选参数

async callWriteFunction(
    contractConfig: ContractConfig,
    networkName: string,
    functionName: string,
    params: any[] = [],
    options: TransactionOptions = {}  // 可选配置
) { }

5. 类型安全

✅ 接口定义

interface ContractConfig {
    address: string;
    abi: any[];
    bytecode?: string;
}

✅ 泛型使用

private providers: Map<string, ethers.JsonRpcProvider> = new Map();

实战案例

案例1: 代币批量转账

import { ContractManager } from './contract';
import { ethers } from 'ethers';

class TokenTransfer {
    private contractManager: ContractManager;

    constructor() {
        this.contractManager = new ContractManager();
    }

    async batchTransfer(recipients: string[], amounts: string[]) {
        const network = 'mainnet';
        const tokenAddress = '0x...'; // 你的代币地址

        console.log('Starting batch transfer...');

        for (let i = 0; i < recipients.length; i++) {
            // 步骤1: 查询余额
            const balanceResult = await this.contractManager.getTokenBalance(
                network,
                tokenAddress,
                recipients[i]
            );

            console.log(`Recipient ${i + 1} balance:`, 
                ethers.formatEther(balanceResult.result || 0)
            );

            // 步骤2: 转账
            const transferResult = await this.contractManager.transferToken(
                network,
                tokenAddress,
                recipients[i],
                amounts[i]
            );

            if (transferResult.success) {
                console.log(`✅ Transfer ${i + 1} successful!`);
                console.log(`   TX: ${transferResult.transactionHash}`);
                console.log(`   Gas: ${transferResult.gasUsed}`);
            } else {
                console.error(`❌ Transfer ${i + 1} failed:`, transferResult.error);
            }

            // 等待1秒,避免 nonce 冲突
            await new Promise(resolve => setTimeout(resolve, 1000));
        }
    }
}

// 使用示例
const transfer = new TokenTransfer();
transfer.batchTransfer(
    ['0xRecipient1...', '0xRecipient2...'],
    [ethers.parseEther('10').toString(), ethers.parseEther('20').toString()]
);

案例2: 多链代币余额查询

async function checkMultiChainBalance(address: string) {
    const contractManager = new ContractManager();
    const tokenAddress = '0x...'; // USDT/USDC 等跨链代币地址

    const networks = ['mainnet', 'polygon', 'bsc', 'arbitrum'];

    // 并行查询所有链上的余额
    const results = await Promise.all(
        networks.map(network =>
            contractManager.getTokenBalance(network, tokenAddress, address)
        )
    );

    console.log('\n=== Multi-Chain Balance ===');
    
    let totalBalance = 0n;
    
    results.forEach((result, index) => {
        const network = networks[index];
        if (result.success) {
            const balance = result.result;
            totalBalance += BigInt(balance);
            console.log(`${network.padEnd(10)}: ${ethers.formatEther(balance)} tokens`);
        } else {
            console.log(`${network.padEnd(10)}: Failed - ${result.error}`);
        }
    });

    console.log(`${'TOTAL'.padEnd(10)}: ${ethers.formatEther(totalBalance)} tokens`);
    console.log('===========================\n');
}

// 使用示例
checkMultiChainBalance('0xYourAddress...');

案例3: Token Sale 合约交互

import { ContractManager } from './contract';
import tokenSaleAbi from './abis/tokenSale.json';

class TokenSaleBot {
    private contractManager: ContractManager;
    private saleAddress: string;

    constructor(saleAddress: string) {
        this.contractManager = new ContractManager();
        this.saleAddress = saleAddress;
    }

    async participateInSale(tokenAddress: string, amount: string) {
        const network = 'mainnet';

        // 步骤1: 检查 Sale 是否暂停
        const pausedResult = await this.contractManager.isPaused(
            network,
            this.saleAddress,
            tokenSaleAbi
        );

        if (pausedResult.result) {
            console.log('❌ Token sale is paused');
            return;
        }

        // 步骤2: 授权代币
        console.log('📝 Approving tokens...');
        const approveResult = await this.contractManager.approveToken(
            network,
            tokenAddress,
            this.saleAddress,
            amount
        );

        if (!approveResult.success) {
            console.error('❌ Approval failed:', approveResult.error);
            return;
        }

        console.log('✅ Tokens approved');

        // 步骤3: 购买代币
        console.log('💰 Purchasing tokens...');
        const buyResult = await this.contractManager.contractService.callWriteFunction(
            { address: this.saleAddress, abi: tokenSaleAbi },
            network,
            'buyTokens',
            [amount]
        );

        if (buyResult.success) {
            console.log('✅ Purchase successful!');
            console.log(`   TX: ${buyResult.transactionHash}`);
            console.log(`   Gas: ${buyResult.gasUsed}`);
        } else {
            console.error('❌ Purchase failed:', buyResult.error);
        }
    }
}

// 使用示例
const bot = new TokenSaleBot('0xSaleContract...');
bot.participateInSale(
    '0xUSDTAddress...',
    ethers.parseUnits('1000', 6).toString() // 1000 USDT
);

总结

核心优势

通过这套架构设计,我们实现了:

特性 说明 带来的价值
🏗️ 分层架构 四层架构,职责清晰分离 代码易维护、易扩展、易测试
🔒 类型安全 TypeScript 类型系统保障 编译时发现错误,减少 bug
🚀 性能优化 三级缓存 + 并行请求 响应速度快,资源消耗低
🛡️ 容错能力 重试、超时、降级策略 生产环境稳定可靠
📊 可观测性 完善的日志记录机制 便于监控和问题排查
🎯 易用性 语义化 API,封装完善 降低使用门槛,提高开发效率

适用场景

适合使用的场景

  • DApp 后端服务:为前端提供稳定的合约调用接口
  • 链上数据监控:定时查询链上数据,触发告警
  • 自动化交易机器人:执行复杂的交易策略
  • 合约管理工具:批量操作多个合约
  • 区块链中间件:构建通用的区块链服务层
  • 多链应用:同时与多条链交互的应用

不适合的场景

  • 简单的一次性脚本(过度设计)
  • 前端钱包应用(需要用户主动签名)
  • 对实时性要求极高的场景(毫秒级响应)

使用社交账号登录

  • Loading...
  • Loading...
  • Loading...
  • Loading...
  • Loading...