import 'dart:async'; import 'package:reown_appkit/modal/i_appkit_modal_impl.dart'; import 'package:reown_appkit/reown_appkit.dart'; import 'evm_recharge.dart'; import '../../data/models/asset/recharge_order.dart'; /// 使用 Reown AppKit 连接钱包并向订单收款地址发起转账(与 Web `useWalletConnectDeposit` 对齐)。 class WalletConnectRechargeHelper { WalletConnectRechargeHelper._(); static const _erc20Abi = ''' [{"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transfer","outputs":[{"name":"","type":"bool"}],"type":"function"}] '''; static BigInt _parseUnits(String amount, int decimals) { final s = amount.trim(); if (s.isEmpty) { throw FormatException('empty amount'); } var normalized = s.replaceAll(',', ''); final neg = normalized.startsWith('-'); if (neg) { normalized = normalized.substring(1); } final parts = normalized.split('.'); final intPart = parts[0].isEmpty ? '0' : parts[0]; var frac = parts.length > 1 ? parts[1] : ''; if (frac.length > decimals) { frac = frac.substring(0, decimals); } else { frac = frac.padRight(decimals, '0'); } final combined = intPart + frac; var bi = BigInt.parse(combined); if (neg) { bi = -bi; } return bi; } static Future ensureConnected(IReownAppKitModal modal) async { if (modal.isConnected) { return; } final completer = Completer(); void onConnect(ModalConnect _) { if (!completer.isCompleted) { completer.complete(); } } modal.onModalConnect.subscribe(onConnect); try { // 与「先 await openModalView 再 await connect」不同:用户关掉弹窗未连接时, // openModalView 会结束而 onConnect 永不触发,会导致外层 loading 卡死到超时。 final modalClosed = modal.openModalView(); await Future.any([ completer.future, modalClosed, ]).timeout( const Duration(minutes: 3), onTimeout: () { throw TimeoutException('WalletConnect 连接超时'); }, ); if (!modal.isConnected) { throw StateError('WalletConnect 已取消或未连接'); } } finally { modal.onModalConnect.unsubscribe(onConnect); } } static Future ensureEvmChain( IReownAppKitModal modal, int chainId, ) async { final net = ReownAppKitModalNetworks.getNetworkInfo( 'eip155', chainIdToCaip2(chainId), ); if (net == null) { throw StateError('不支持的链 chainId=$chainId'); } await modal.selectChain(net, switchChain: true); } /// 返回交易 hash(0x…)。 static Future connectAndPay({ required IReownAppKitModal appKitModal, required RechargeFlatNetworkOption network, required RechargeOrderDetail order, }) async { final chainId = resolveEvmChainId(network.protocol, network.networkName); if (chainId == null) { throw StateError('当前网络不支持 WalletConnect,请使用钱包手动转账'); } await ensureConnected(appKitModal); await ensureEvmChain(appKitModal, chainId); final session = appKitModal.session; if (session == null) { throw StateError('会话无效'); } final fromRaw = session.getAddress('eip155'); if (fromRaw == null || fromRaw.isEmpty) { throw StateError('未获取到钱包地址'); } final from = EthereumAddress.fromHex(fromRaw); final to = EthereumAddress.fromHex(order.rechargeAddress.trim()); final caip2 = chainIdToCaip2(chainId); final topic = session.topic; final amountStr = order.amount.trim(); if (network.hasTokenContract) { final token = EthereumAddress.fromHex(network.contractAddress.trim()); final decimals = erc20DecimalsForChain(chainId); final amountWei = _parseUnits(amountStr, decimals); final abi = ContractAbi.fromJson(_erc20Abi, 'ERC20'); final deployed = DeployedContract(abi, token); final hash = await appKitModal.requestWriteContract( topic: topic, chainId: caip2, deployedContract: deployed, functionName: 'transfer', transaction: Transaction(from: from), parameters: [to, amountWei], ); return hash.toString(); } else { final valueWei = _parseUnits(amountStr, 18); final trx = Transaction( from: from, to: to, value: EtherAmount.fromBigInt(EtherUnit.wei, valueWei), ); final hash = await appKitModal.request( topic: topic, chainId: caip2, request: SessionRequestParams( method: MethodsConstants.ethSendTransaction, params: [trx.toJson()], ), ); return hash.toString(); } } }