| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356 |
- 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<String, dynamic> _deepMap(Object raw) {
- return jsonDecode(jsonEncode(raw)) as Map<String, dynamic>;
- }
- static Map<String, dynamic> _prepareUnsigned(Map<String, dynamic> 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<String, dynamic> _cloneWithoutTxId(Map<String, dynamic> tx) {
- final c = _deepMap(tx);
- c.remove('txID');
- return c;
- }
- /// triggersmartcontract 完整响应的多种形态(与官方示例及常见钱包兼容)。
- static List<Map<String, dynamic>> _triggerEnvelopesForWallet(
- Map<String, dynamic> full,
- ) {
- final out = <Map<String, dynamic>>[_deepMap(full)];
- final inner = full['transaction'];
- if (inner is Map) {
- final innerMap = Map<String, dynamic>.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<String, dynamic> _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<String, dynamic>.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<Map<String, dynamic>> _requestTronSignTransaction({
- required IReownAppKitModal modal,
- required String topic,
- required ReownAppKitModalSession session,
- required String fromBase58,
- required Map<String, dynamic> unsigned,
- Map<String, dynamic>? triggerSmartContractBody,
- }) async {
- final prepared = _prepareUnsigned(unsigned);
- final noTxId = _cloneWithoutTxId(prepared);
- final sessionAcct = _tronSessionAccountString(session, fromBase58);
- final useV1 = _tronSessionUsesSignV1(session);
- final rawVariants = <Map<String, dynamic>>[];
- // 顺序:优先 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 = <dynamic>[
- ...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<void> _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<void> _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<String> 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<String, dynamic> unsigned;
- final Map<String, dynamic>? 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<String, dynamic>.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,
- );
- }
- }
|