import { Injectable, Logger } from '@nestjs/common'; import { JsonRpcProvider, Contract } from 'ethers'; import { RpcProviderManager } from '@/domain/services/rpc-provider-manager.service'; import { ChainType, BlockNumber, TokenAmount } from '@/domain/value-objects'; import { ChainTypeEnum } from '@/domain/enums'; // ERC20 Transfer 事件 ABI const ERC20_TRANSFER_EVENT_ABI = [ 'event Transfer(address indexed from, address indexed to, uint256 value)', ]; // ERC20 balanceOf ABI const ERC20_BALANCE_ABI = [ 'function balanceOf(address owner) view returns (uint256)', 'function decimals() view returns (uint8)', ]; export interface TransferEvent { txHash: string; logIndex: number; blockNumber: bigint; blockTimestamp: Date; from: string; to: string; value: bigint; tokenContract: string; } /** * EVM 区块链提供者适配器 * * 封装与 EVM 链的交互。通过 RpcProviderManager 获取 provider, * 自动上报 RPC 调用的成功/失败状态,实现故障转移。 */ @Injectable() export class EvmProviderAdapter { private readonly logger = new Logger(EvmProviderAdapter.name); constructor(private readonly rpcProviderManager: RpcProviderManager) {} /** * 获取某条链当前活跃的 provider(由 RpcProviderManager 统一管理) */ private getProvider(chainType: ChainType): JsonRpcProvider { return this.rpcProviderManager.getProvider(chainType.value); } /** * 执行 RPC 调用并自动上报成功/失败状态 * * 所有公开方法通过此辅助方法包裹 RPC 调用: * - 成功时调用 reportSuccess() 重置失败计时 * - 失败时调用 reportFailure() 记录失败(超过 3 分钟自动切换端点) * - 错误会 re-throw,不影响调用方的错误处理逻辑 */ private async executeWithFailover( chainType: ChainType, operation: () => Promise, ): Promise { try { const result = await operation(); this.rpcProviderManager.reportSuccess(chainType.value); return result; } catch (error) { this.rpcProviderManager.reportFailure( chainType.value, error instanceof Error ? error : new Error(String(error)), ); throw error; } } /** * 获取当前区块号 */ async getCurrentBlockNumber(chainType: ChainType): Promise { return this.executeWithFailover(chainType, async () => { const provider = this.getProvider(chainType); const blockNumber = await provider.getBlockNumber(); return BlockNumber.create(blockNumber); }); } /** * 获取区块时间戳 */ async getBlockTimestamp(chainType: ChainType, blockNumber: BlockNumber): Promise { return this.executeWithFailover(chainType, async () => { const provider = this.getProvider(chainType); const block = await provider.getBlock(blockNumber.asNumber); if (!block) { throw new Error(`Block not found: ${blockNumber.toString()}`); } return new Date(block.timestamp * 1000); }); } /** * 扫描指定区块范围内的 ERC20 Transfer 事件 */ async scanTransferEvents( chainType: ChainType, fromBlock: BlockNumber, toBlock: BlockNumber, tokenContract: string, ): Promise { return this.executeWithFailover(chainType, async () => { const provider = this.getProvider(chainType); const contract = new Contract(tokenContract, ERC20_TRANSFER_EVENT_ABI, provider); const filter = contract.filters.Transfer(); const logs = await contract.queryFilter(filter, fromBlock.asNumber, toBlock.asNumber); const events: TransferEvent[] = []; for (const log of logs) { const block = await provider.getBlock(log.blockNumber); if (!block) continue; const parsedLog = contract.interface.parseLog({ topics: log.topics as string[], data: log.data, }); if (parsedLog) { events.push({ txHash: log.transactionHash, logIndex: log.index, blockNumber: BigInt(log.blockNumber), blockTimestamp: new Date(block.timestamp * 1000), from: parsedLog.args[0], to: parsedLog.args[1], value: parsedLog.args[2], tokenContract, }); } } return events; }); } /** * 查询 ERC20 代币余额 */ async getTokenBalance( chainType: ChainType, tokenContract: string, address: string, ): Promise { return this.executeWithFailover(chainType, async () => { const provider = this.getProvider(chainType); const contract = new Contract(tokenContract, ERC20_BALANCE_ABI, provider); const [balance, decimals] = await Promise.all([ contract.balanceOf(address), contract.decimals(), ]); return TokenAmount.fromRaw(balance, Number(decimals)); }); } /** * 查询 ERC20 代币的 decimals */ async getTokenDecimals(chainType: ChainType, tokenContract: string): Promise { return this.executeWithFailover(chainType, async () => { const provider = this.getProvider(chainType); const contract = new Contract(tokenContract, ERC20_BALANCE_ABI, provider); const decimals = await contract.decimals(); return Number(decimals); }); } /** * 查询原生代币余额 */ async getNativeBalance(chainType: ChainType, address: string): Promise { return this.executeWithFailover(chainType, async () => { const provider = this.getProvider(chainType); const balance = await provider.getBalance(address); return TokenAmount.fromRaw(balance, 18); }); } /** * 广播签名交易 */ async broadcastTransaction(chainType: ChainType, signedTx: string): Promise { return this.executeWithFailover(chainType, async () => { const provider = this.getProvider(chainType); const txResponse = await provider.broadcastTransaction(signedTx); this.logger.log(`Transaction broadcasted: ${txResponse.hash}`); return txResponse.hash; }); } /** * 等待交易确认 */ async waitForTransaction( chainType: ChainType, txHash: string, confirmations: number = 1, ): Promise { return this.executeWithFailover(chainType, async () => { const provider = this.getProvider(chainType); const receipt = await provider.waitForTransaction(txHash, confirmations); return receipt !== null && receipt.status === 1; }); } /** * 检查交易是否确认 */ async isTransactionConfirmed( chainType: ChainType, txHash: string, requiredConfirmations: number, ): Promise { return this.executeWithFailover(chainType, async () => { const provider = this.getProvider(chainType); const receipt = await provider.getTransactionReceipt(txHash); if (!receipt) return false; const currentBlock = await provider.getBlockNumber(); const confirmations = currentBlock - receipt.blockNumber; return confirmations >= requiredConfirmations; }); } }