import 'dart:async'; import 'models/coupon_detail.dart'; import 'models/coupon_holding.dart'; import 'models/block_info.dart'; import 'models/transaction_info.dart'; import 'models/chain_stats.dart'; import 'models/chain_event.dart'; import 'models/event_filter.dart'; import 'models/address_balance.dart'; import 'rpc/json_rpc_client.dart'; import 'rpc/websocket_client.dart'; import 'contracts/contract_abis.dart'; import 'contracts/contract_addresses.dart'; import 'utils/formatters.dart'; /// Genex Chain SDK 主客户端 class GenexClient { final String rpcUrl; final int chainId; final JsonRpcClient _rpc; GenexWebSocketClient? _ws; GenexClient({ required this.rpcUrl, this.chainId = 8888, }) : _rpc = JsonRpcClient(rpcUrl); // ─── 区块查询 ────────────────────────────────────────── /// 获取指定高度区块 Future getBlock(int height) async { final result = await _rpc.call( 'eth_getBlockByNumber', [Formatters.toHex(height), false], ); return BlockInfo.fromRpcJson(result as Map); } /// 获取最新区块 Future getLatestBlock() async { final result = await _rpc.call( 'eth_getBlockByNumber', ['latest', false], ); return BlockInfo.fromRpcJson(result as Map); } /// 获取当前区块高度 Future getBlockHeight() async { final result = await _rpc.call('eth_blockNumber', []); return Formatters.hexToInt(result as String); } // ─── 交易查询 ────────────────────────────────────────── /// 获取交易详情 Future getTransaction(String txHash) async { final txResult = await _rpc.call('eth_getTransactionByHash', [txHash]); final receiptResult = await _rpc.call('eth_getTransactionReceipt', [txHash]); return TransactionInfo.fromRpcJson( txResult as Map, receiptResult as Map, ); } // ─── 地址查询 ────────────────────────────────────────── /// 查询地址余额 Future getBalance(String address) async { final balanceHex = await _rpc.call('eth_getBalance', [address, 'latest']); final nonce = await _rpc.call('eth_getTransactionCount', [address, 'latest']); return AddressBalance( address: address, balance: Formatters.hexToBigInt(balanceHex as String), nonce: Formatters.hexToInt(nonce as String), ); } // ─── 券查询 ──────────────────────────────────────────── /// 查询券详情(调用 Coupon.getConfig) Future getCouponDetail(BigInt tokenId) async { final data = ContractAbis.encodeCouponGetConfig(tokenId); final result = await _rpc.call('eth_call', [ { 'to': ContractAddresses.coupon, 'data': data, }, 'latest', ]); return CouponDetail.fromAbiResult(result as String, tokenId); } /// 查询地址持有的券 NFT Future> getCouponHoldings(String address) async { // balanceOf final balanceData = ContractAbis.encodeBalanceOf(address); final balanceHex = await _rpc.call('eth_call', [ {'to': ContractAddresses.coupon, 'data': balanceData}, 'latest', ]); final balance = Formatters.hexToInt(balanceHex as String); final holdings = []; for (var i = 0; i < balance; i++) { final tokenData = ContractAbis.encodeTokenOfOwnerByIndex(address, i); final tokenIdHex = await _rpc.call('eth_call', [ {'to': ContractAddresses.coupon, 'data': tokenData}, 'latest', ]); final tokenId = Formatters.hexToBigInt(tokenIdHex as String); holdings.add(CouponHolding(tokenId: tokenId, index: i)); } return holdings; } // ─── 链统计 ──────────────────────────────────────────── /// 获取链统计信息 Future getStats() async { final blockHex = await _rpc.call('eth_blockNumber', []); final peerCount = await _rpc.call('net_peerCount', []); return ChainStats( blockHeight: Formatters.hexToInt(blockHex as String), peerCount: Formatters.hexToInt(peerCount as String), chainId: chainId, ); } // ─── 事件订阅 ────────────────────────────────────────── GenexWebSocketClient _ensureWs() { _ws ??= GenexWebSocketClient(rpcUrl.replaceFirst('http', 'ws')); return _ws!; } /// 统一事件订阅接口(匹配指南 §8.4 subscribeEvents 规范) /// /// [filter] 事件过滤器: /// - type='newHeads' 订阅新区块 /// - type='logs' 订阅合约事件,可指定 address / topics Stream subscribeEvents(EventFilter filter) { return _ensureWs().subscribe(filter.type, filter.params); } /// 连接 WebSocket 并订阅新区块头 Stream subscribeNewHeads() { return _ensureWs().subscribe('newHeads', {}); } /// 订阅合约事件日志 Stream subscribeLogs({ String? address, List? topics, }) { final params = {}; if (address != null) params['address'] = address; if (topics != null) params['topics'] = topics; return _ensureWs().subscribe('logs', params); } // ─── 生命周期 ────────────────────────────────────────── /// 关闭客户端连接 void close() { _rpc.close(); _ws?.close(); } }