import 'dart:convert'; import 'package:reown_appkit/modal/i_appkit_modal_impl.dart'; import 'package:reown_appkit/reown_appkit.dart'; import '../config/app_config.dart'; import '../../data/models/asset/recharge_order.dart'; import 'tron_fullnode_client.dart'; import 'tron_recharge.dart'; import 'wallet_connect_recharge_helper.dart'; /// 使用 Reown AppKit 的 Tron 命名空间签名 + FullNode 广播(与 Web `useWalletConnectTronDeposit.ts` 对齐)。 class WalletConnectTronRecharge { WalletConnectTronRecharge._(); static Map _deepMap(Object raw) { return jsonDecode(jsonEncode(raw)) as Map; } static Map _prepareUnsigned(Map tx) { final o = _deepMap(tx); o.remove('signature'); final rd = o['raw_data']; if (rd is Map) { final contracts = rd['contract']; if (contracts is List) { for (final c in contracts) { if (c is Map && !c.containsKey('Permission_id')) { c['Permission_id'] = 0; } } } } return o; } static Map _cloneWithoutTxId(Map tx) { final c = _deepMap(tx); c.remove('txID'); return c; } /// triggersmartcontract 完整响应的多种形态(与官方示例及常见钱包兼容)。 static List> _triggerEnvelopesForWallet( Map full, ) { final out = >[_deepMap(full)]; final inner = full['transaction']; if (inner is Map) { final innerMap = Map.from(inner); final preparedInner = _prepareUnsigned(innerMap); final wrapped = _deepMap(full); wrapped['transaction'] = preparedInner; out.add(wrapped); final noId = _deepMap(full); noId['transaction'] = _cloneWithoutTxId(preparedInner); out.add(noId); } return out; } static String _formatSignFailure(Object e) { if (e is ReownSignError) { final d = e.data; final extra = (d != null && d.isNotEmpty) ? ' data=$d' : ''; return 'ReownSignError(code=${e.code}, message=${e.message})$extra'; } return e.toString(); } static Map _normalizeSigned(dynamic raw) { dynamic cur = raw; if (cur is String) { try { cur = jsonDecode(cur) as Object?; } catch (_) { throw StateError('钱包返回的签名格式无效'); } } if (cur is! Map) { throw StateError('钱包未返回签名数据'); } final m = Map.from(cur); if (m['result'] != null && m['result'] is Map) { return _normalizeSigned(m['result']); } if (m['signature'] is List && (m['signature'] as List).isNotEmpty && m['raw_data'] is Map) { return m; } for (final k in ['transaction', 'signedTransaction', 'tx']) { final inner = m[k]; if (inner is Map) { try { return _normalizeSigned(inner); } catch (_) { // 尝试下一包裹字段 } } } throw StateError('钱包未返回有效签名(signature)'); } static bool _tronSessionUsesSignV1(ReownAppKitModalSession? session) { final v = session?.sessionProperties['tron_method_version']; return v == 'v1'; } static String _tronSessionAccountString( ReownAppKitModalSession session, String base58, ) { final raw = session.getAccounts(namespace: 'tron') ?? []; for (final a in raw) { if (parseTronCaip10Address(a) == base58) { return a; } } return tronMainnetCaip10Account(base58); } static Future> _requestTronSignTransaction({ required IReownAppKitModal modal, required String topic, required ReownAppKitModalSession session, required String fromBase58, required Map unsigned, Map? triggerSmartContractBody, }) async { final prepared = _prepareUnsigned(unsigned); final noTxId = _cloneWithoutTxId(prepared); final sessionAcct = _tronSessionAccountString(session, fromBase58); final useV1 = _tronSessionUsesSignV1(session); final rawVariants = >[]; // 顺序:优先 Reown 官方示例(完整 triggersmartcontract 响应 + Base58 地址), // 其次 CAIP-10 账户字符串;内层仅 tx 时先扁平 transaction 再嵌套;全部 Map 试完后再试 JSON-RPC 风格的 [params]。 if (triggerSmartContractBody != null) { for (final env in _triggerEnvelopesForWallet(triggerSmartContractBody)) { rawVariants.add({'address': fromBase58, 'transaction': env}); rawVariants.add({'address': sessionAcct, 'transaction': env}); } } if (useV1) { rawVariants.add({'address': fromBase58, 'transaction': prepared}); rawVariants.add({'address': sessionAcct, 'transaction': prepared}); rawVariants.add({ 'address': fromBase58, 'transaction': {'transaction': prepared}, }); rawVariants.add({'transaction': prepared}); rawVariants.add({'address': fromBase58, 'transaction': noTxId}); rawVariants.add({'address': sessionAcct, 'transaction': noTxId}); rawVariants.add({'transaction': noTxId}); } else { rawVariants.add({'address': fromBase58, 'transaction': prepared}); rawVariants.add({'address': sessionAcct, 'transaction': prepared}); rawVariants.add({ 'address': fromBase58, 'transaction': {'transaction': prepared}, }); rawVariants.add({ 'address': sessionAcct, 'transaction': {'transaction': prepared}, }); rawVariants.add({'transaction': prepared}); rawVariants.add({'transaction': {'transaction': prepared}}); rawVariants.add({'address': fromBase58, 'transaction': noTxId}); rawVariants.add({'address': sessionAcct, 'transaction': noTxId}); rawVariants.add({ 'address': fromBase58, 'transaction': {'transaction': noTxId}, }); rawVariants.add({ 'address': sessionAcct, 'transaction': {'transaction': noTxId}, }); rawVariants.add({'transaction': noTxId}); rawVariants.add({'transaction': {'transaction': noTxId}}); } final variants = [ ...rawVariants, ...rawVariants.map((m) => [m]), ]; Object? lastErr; for (var i = 0; i < variants.length; i++) { try { final out = await modal.request( topic: topic, chainId: kTronMainnetCaip2, request: SessionRequestParams( method: 'tron_signTransaction', params: variants[i], ), ); return _normalizeSigned(out); } catch (e) { lastErr = e; } } final err = lastErr; if (err == null) { throw StateError('tron_signTransaction 失败:未知错误'); } throw StateError( 'tron_signTransaction 失败(已尝试多种参数格式)。最后错误:${_formatSignFailure(err)}', ); } /// 检查当前会话是否含 `tron_signTransaction`(不抛异常,仅返回布尔值)。 static bool _sessionHasTronSign(ReownAppKitModalSession? session) { final ns = session?.namespaces; if (ns == null || ns.isEmpty) { return false; } final methods = NamespaceUtils.getNamespacesMethodsForChainId( chainId: kTronMainnetCaip2, namespaces: ns, ); return methods.contains('tron_signTransaction'); } static Future _ensureTronSession(IReownAppKitModal modal) async { await WalletConnectRechargeHelper.ensureConnected(modal); // 旧会话(在 optionalNamespaces 修复前握手建立)可能不含 tron 签名方法,需强制重连。 var addr = modal.session?.getAddress('tron'); final needsReconnect = addr == null || addr.isEmpty || !_sessionHasTronSign(modal.session); if (needsReconnect) { await modal.disconnect(disconnectAllSessions: true); await WalletConnectRechargeHelper.ensureConnected(modal); addr = modal.session?.getAddress('tron'); } if (addr == null || !addr.startsWith('T')) { throw StateError('请使用支持波场(Tron)的钱包连接后重试'); } if (!_sessionHasTronSign(modal.session)) { throw StateError( '钱包未授权 tron_signTransaction,请在钱包端确认 Tron 命名空间后重试', ); } } static Future _ensureTronChain(IReownAppKitModal modal) async { final net = ReownAppKitModalNetworks.getNetworkInfo('tron', kTronMainnetCaip2); if (net == null) { throw StateError('App 未注册 Tron 网络'); } await modal.selectChain(net, switchChain: true); } /// 返回 Tron 交易 txid(非 0x 以太坊 hash)。 static Future connectAndPayTron({ required IReownAppKitModal appKitModal, required RechargeFlatNetworkOption network, required RechargeOrderDetail order, }) async { if (!order.isPendingPayment) { throw StateError('订单状态不可支付'); } if (!isTronDepositNetwork(network.protocol, network.networkName)) { throw StateError('当前网络不支持波场 WalletConnect'); } await _ensureTronSession(appKitModal); await _ensureTronChain(appKitModal); final session = appKitModal.session; if (session == null) { throw StateError('会话无效'); } final topic = session.topic; if (topic == null || topic.isEmpty) { throw StateError('会话无效'); } final from = session.getAddress('tron'); if (from == null || !from.startsWith('T')) { throw StateError('未获取到波场钱包地址'); } final to = order.rechargeAddress.trim(); if (!to.startsWith('T')) { throw StateError('收款地址不是有效的波场地址'); } final chainNet = ReownAppKitModalNetworks.getNetworkInfo('tron', kTronMainnetCaip2); if (chainNet == null) { throw StateError('Tron 网络未配置'); } var rpc = chainNet.rpcUrl.trim(); if (rpc.isEmpty) { rpc = AppConfig.tronFullHost; } final amountStr = order.amount.trim(); var contract = network.contractAddress.trim(); if (contract.isEmpty && (isTronRechargeUsdtSymbol(network.coinName) || isTronRechargeUsdtSymbol(order.coinName))) { contract = kTrc20UsdtOfficialMainnet; } final Map unsigned; final Map? triggerBody; if (contract.isNotEmpty) { final minUnits = BigInt.parse( trc20AmountToMinUnits(amountStr, kTrc20UsdtDecimals), ); final paramHex = tronTransferTrc20ParameterHex(to, minUnits); triggerBody = await TronFullNodeClient.triggerSmartContract( rpcBase: rpc, ownerAddressBase58: from, contractAddressBase58: contract, functionSelector: 'transfer(address,uint256)', parameterHex: paramHex, ); final inner = triggerBody['transaction']; if (inner is! Map) { throw StateError('TRC20 构造响应缺少 transaction'); } unsigned = Map.from(inner); } else { triggerBody = null; final sun = trxToSunAmount(amountStr); unsigned = await TronFullNodeClient.createTrxTransaction( rpcBase: rpc, ownerAddressBase58: from, toAddressBase58: to, amountSun: sun, ); } final signed = await _requestTronSignTransaction( modal: appKitModal, topic: topic, session: session, fromBase58: from, unsigned: unsigned, triggerSmartContractBody: triggerBody, ); return TronFullNodeClient.broadcastTransaction( rpcBase: rpc, signed: signed, ); } }