import { Injectable, Logger } from '@nestjs/common'; import { JsonRpcProvider, Contract } from 'ethers'; import { ChainConfigService } from '@/domain/services/chain-config.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 链的交互 */ @Injectable() export class EvmProviderAdapter { private readonly logger = new Logger(EvmProviderAdapter.name); private readonly providers: Map = new Map(); constructor(private readonly chainConfig: ChainConfigService) { this.initializeProviders(); } private initializeProviders(): void { for (const chainType of this.chainConfig.getSupportedChains()) { const config = this.chainConfig.getConfig(ChainType.fromEnum(chainType)); const provider = new JsonRpcProvider(config.rpcUrl, config.chainId); this.providers.set(chainType, provider); this.logger.log(`Initialized provider for ${chainType}: ${config.rpcUrl}`); } } private getProvider(chainType: ChainType): JsonRpcProvider { const provider = this.providers.get(chainType.value); if (!provider) { throw new Error(`No provider for chain: ${chainType.toString()}`); } return provider; } /** * 获取当前区块号 */ async getCurrentBlockNumber(chainType: ChainType): Promise { const provider = this.getProvider(chainType); const blockNumber = await provider.getBlockNumber(); return BlockNumber.create(blockNumber); } /** * 获取区块时间戳 */ async getBlockTimestamp(chainType: ChainType, blockNumber: BlockNumber): Promise { 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 { 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 { 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 { 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 { const provider = this.getProvider(chainType); const balance = await provider.getBalance(address); return TokenAmount.fromRaw(balance, 18); } /** * 广播签名交易 */ async broadcastTransaction(chainType: ChainType, signedTx: string): Promise { 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 { 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 { 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; } }