gcx/blockchain/genex-sdk-dart/lib/src/client.dart

175 lines
5.9 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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<BlockInfo> getBlock(int height) async {
final result = await _rpc.call(
'eth_getBlockByNumber',
[Formatters.toHex(height), false],
);
return BlockInfo.fromRpcJson(result as Map<String, dynamic>);
}
/// 获取最新区块
Future<BlockInfo> getLatestBlock() async {
final result = await _rpc.call(
'eth_getBlockByNumber',
['latest', false],
);
return BlockInfo.fromRpcJson(result as Map<String, dynamic>);
}
/// 获取当前区块高度
Future<int> getBlockHeight() async {
final result = await _rpc.call('eth_blockNumber', []);
return Formatters.hexToInt(result as String);
}
// ─── 交易查询 ──────────────────────────────────────────
/// 获取交易详情
Future<TransactionInfo> 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<String, dynamic>,
receiptResult as Map<String, dynamic>,
);
}
// ─── 地址查询 ──────────────────────────────────────────
/// 查询地址余额
Future<AddressBalance> 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<CouponDetail> 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<List<CouponHolding>> 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 = <CouponHolding>[];
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<ChainStats> 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<ChainEvent> subscribeEvents(EventFilter filter) {
return _ensureWs().subscribe(filter.type, filter.params);
}
/// 连接 WebSocket 并订阅新区块头
Stream<ChainEvent> subscribeNewHeads() {
return _ensureWs().subscribe('newHeads', {});
}
/// 订阅合约事件日志
Stream<ChainEvent> subscribeLogs({
String? address,
List<String>? topics,
}) {
final params = <String, dynamic>{};
if (address != null) params['address'] = address;
if (topics != null) params['topics'] = topics;
return _ensureWs().subscribe('logs', params);
}
// ─── 生命周期 ──────────────────────────────────────────
/// 关闭客户端连接
void close() {
_rpc.close();
_ws?.close();
}
}