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 │
└─────────────────────────────────────┘
设计原则
- 单一职责:每层只负责自己的功能
- 依赖倒置:上层依赖下层接口,而非具体实现
- 开闭原则:对扩展开放,对修改封闭
- 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 管理层负责创建和管理与区块链的连接,是整个架构的基础。
核心功能
- ✅ 连接池管理:缓存 Provider 和 Signer 实例
- ✅ 超时控制:多种方式配置请求超时
- ✅ 重试机制:指数退避的自动重试
- ✅ 网络信息查询:获取链信息、区块、余额等
完整实现
// 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));
}
}
为什么用指数退避?
- 避免对服务器造成压力
- 给网络恢复时间
- 提高成功率
服务抽象层
服务层提供通用的合约调用方法,屏蔽底层细节。
核心功能
- ✅ 合约实例管理:缓存合约实例
- ✅ 读写分离:区分只读和写入操作
- ✅ 自动 Gas 估算:智能估算 Gas,失败时降级
- ✅ 交易日志:详细记录交易状态
- ✅ 统一错误处理:返回标准化结果
完整实现
// 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 后端服务:为前端提供稳定的合约调用接口
- 链上数据监控:定时查询链上数据,触发告警
- 自动化交易机器人:执行复杂的交易策略
- 合约管理工具:批量操作多个合约
- 区块链中间件:构建通用的区块链服务层
- 多链应用:同时与多条链交互的应用
❌ 不适合的场景:
- 简单的一次性脚本(过度设计)
- 前端钱包应用(需要用户主动签名)
- 对实时性要求极高的场景(毫秒级响应)