Skip to content

eth_sendTransaction issue #239

@muhammad-hassan-shakeel

Description

class WalletConnectServiceV2 extends WalletConnectService {
  WalletConnectServiceV2();

  late wc.Web3Wallet wcClient;

  late Web3Client ethClient;

  // final ethClient = Web3Client(
  //     'https://mainnet.infura.io/v3/51716d2096df4e73bec298680a51f0c5',
  //     Client());

  late Function(bool) _onConnectionStatus;
  late Function(SessionProposal p1) _onSessionProposal;
  late Function(String topic) _onSessionDelete;
  late Function(Session session) _onSessionSettle;
  late Future<String> Function(
      String to,
      String from,
      String amount,
      BigInt gasPriceInGwei,
      Uint8List data,
      int nounce) _onSignTransactionRequest;

  late String _privateAddress;
  // late String _rpcUrl;

  @override
  Future<void> approveRequest(String topic, String requestId, String result) {
    // TODO: implement approveRequest
    throw UnimplementedError();
  }

  @override
  Future<void> approveSession(SessionProposal session, String address) async {
    await wcClient.approveSession(
      id: int.parse(session.id),
      namespaces: session.namespaces.map(
        (key, value) => MapEntry(
          key,
          wc.Namespace(
            methods: value.methods,
            events: value.events,
            accounts: value.chains!,
          ),
        ),
      ),
    );
  }

  @override
  Future<void> connect() async {
    _onConnectionStatus(true);
  }

  @override
  Future<void> deleteSession(String topic) async {
    // await wcClient.core.pairing.disconnect(topic: topic);
    await wcClient.disconnectSession(
        topic: topic,
        reason: const wc.WalletConnectError(
            code: -1, message: 'Session deleted', data: ''));
  }

  @override
  Future<void> disconnect() async {}

  @override
  Future<void> disconnectWallet() async {}

  @override
  void dispose() {
    // TODO: implement dispose
  }

  @override
  Future<List<Session>> getActveSessions() async {
    final sessions = wcClient.getActiveSessions();
    return sessions.values
        .map((e) => Session(
            topic: e.pairingTopic,
            peer: AppMetadata(
                name: e.peer.metadata.name,
                url: e.peer.metadata.url,
                description: e.peer.metadata.description,
                icons: e.peer.metadata.icons),
            expiration: DateTime.fromMillisecondsSinceEpoch(e.expiry),
            namespaces: e.namespaces.map((key, value) => MapEntry(
                key,
                SessionNamespace(
                    accounts: value.accounts,
                    methods: value.methods,
                    events: value.events)))))
        .toList();
  }

  @override
  Future<void> initWallet(String publicAddress) async {
    wcClient = wc.Web3Wallet(
      core: wc.Core(
        projectId: PROJECT_ID,
        logLevel: wc.LogLevel.error,
      ),
      metadata: const wc.PairingMetadata(
        name: 'Example Wallet',
        description: 'Example Wallet',
        url: 'https://walletconnect.com/',
        icons: [
          'https://github.com/WalletConnect/Web3ModalFlutter/blob/master/assets/png/logo_wc.png'
        ],
        redirect: wc.Redirect(
          native: 'myflutterwallet://',
          universal: 'https://walletconnect.com',
        ),
      ),
    );

    wcClient.onSessionProposal.subscribe((sessionProposal) {
      debugPrint(':: onSessionProposal: $sessionProposal');
      final id = sessionProposal!.id.toString();
      final metadata = sessionProposal.params.proposer.metadata;
      final namespaces = sessionProposal.params.generatedNamespaces;

      _onSessionProposal(
        SessionProposal(
          id: id,
          proposer: AppMetadata(
            name: metadata.name,
            url: metadata.url,
            description: metadata.description,
            icons: metadata.icons,
          ),
          namespaces: namespaces!.map(
            (key, value) => MapEntry(
              key,
              ProposalNamespace(
                methods: value.methods,
                events: value.events,
                chains: value.accounts,
              ),
            ),
          ),
        ),
      );
    });

    for (final network in SupportedCovalentChain.supportedNetworks) {
      wcClient.registerEventEmitter(
        chainId: 'eip155:${network.chainId}',
        event: 'accountsChanged',
      );
      wcClient.registerEventEmitter(
        chainId: 'eip155:${network.chainId}',
        event: 'chainChanged',
      );

      wcClient.registerAccount(
        chainId: 'eip155:${network.chainId}',
        accountAddress: publicAddress,
      );

      Map<String, dynamic Function(String, dynamic)> methodsHandlers = {
        'personal_sign': (String topic, dynamic parameter) {
          debugPrint(':: personal_sign: $topic, $parameter');
        },
        'eth_sign': (String topic, dynamic parameter) {
          debugPrint(':: eth_sign: $topic, $parameter');
        },
        'eth_signTransaction': (String topic, dynamic parameter) async {
          debugPrint(':: eth_signTransaction: $topic, $parameter');
          final signedMessage = await ethSignTransaction(topic, parameter);
          return signedMessage;
        },
        'eth_signTypedData': (String topic, dynamic parameter) {
          debugPrint(':: eth_signTypedData: $topic, $parameter');
        },
        'eth_sendTransaction': (String topic, dynamic parameter) async {
          debugPrint(':: eth_sendTransaction: $topic, $parameter');

          final signedMessage = await ethSignTransaction(topic, parameter);
          return signedMessage;
          // final response = wc.JsonRpcResponse(result: signedMessage, id: 432);
          // try {
          //   await wcClient.respondSessionRequest(
          //       topic: topic, response: response);
          // } catch (e) {
          //   debugPrint(':: onSessionRequest error: $e');
          //   print(e);
          // }

          // return {"jsonrpc": "2.0", "id": 1, "result": signedMessage};
          // return signedMessage;
        },
        // add whatever method/handler you want to support
        // 'eth_signTypedData_v4': ethSignTypedDataV4,
      };

      for (var method in methodsHandlers.keys) {
        wcClient.registerRequestHandler(
          chainId: 'eip155:${network.chainId}',
          method: method,
          handler: methodsHandlers[method]!,
        );
      }

      wcClient.registerEventEmitter(
        chainId: 'eip155:${network.chainId}',
        event: 'chainChanged',
      );
    }

    wcClient.onSessionProposalError.subscribe((args) {
      debugPrint(':: onSessionProposalError: $args');
    });

    wcClient.core.pairing.onPairingInvalid.subscribe((args) {
      debugPrint(':: onPairingInvalid: $args');
    });
    wcClient.core.pairing.onPairingCreate.subscribe((args) {
      debugPrint(':: onPairingCreate: $args');
    });
    wcClient.pairings.onSync.subscribe((args) {
      debugPrint(':: onSync: $args');
    });
    wcClient.onSessionProposalError.subscribe((args) {
      debugPrint(':: onSessionProposalError: $args');
    });
    wcClient.onSessionConnect.subscribe((args) {
      debugPrint(':: onSessionConnect: $args');
      _onSessionSettle(
        Session(
          topic: args!.session.pairingTopic,
          peer: AppMetadata(
              name: args.session.peer.metadata.name,
              url: args.session.peer.metadata.url,
              description: args.session.peer.metadata.description,
              icons: args.session.peer.metadata.icons),
          expiration: DateTime.fromMillisecondsSinceEpoch(args.session.expiry),
          namespaces: args.session.namespaces.map(
            (key, value) => MapEntry(
              key,
              SessionNamespace(
                accounts: value.accounts,
                events: value.events,
                methods: value.methods,
              ),
            ),
          ),
        ),
      );
    });

    wcClient.onSessionRequest.subscribe((args) async {
      debugPrint(':: onSessionRequest: $args');

      // final id = args?.id;
      // final topic = args?.topic;
      // final params = args?.params;

      // final message = params[0]['data'];

      // final credentials = EthPrivateKey.fromHex(_privateAddress);
      // final String? signedMessage = await ethSignTransaction(topic!, params);
      // print(signedMessage);

      // final response = {'id': id, 'result': signedMessage, 'jsonrpc': '2.0'};
      // final r = wc.JsonRpcResponse.fromJson(response);
      // print(r);
      // try {
      //   await wcClient.respondSessionRequest(topic: topic, response: r);
      // } catch (e) {
      //   print(e);
      // }
    });

    wcClient.onAuthRequest.subscribe((args) {
      debugPrint(':: onAuthRequest: $args');
    });
    wcClient.core.relayClient.onRelayClientError
        .subscribe((args) => debugPrint(':: onRelayClientError: $args'));

    wcClient.core.pairing.onPairingDelete.subscribe((args) {
      debugPrint(':: onPairingDelete: $args');
    });

    wcClient.pairings.onDelete.subscribe((args) {
      debugPrint(':: onDelete: $args');
      _onSessionDelete(args!.value.topic);
    });

    await wcClient.init();
  }

  Future<String?> ethSignTransaction(String topic, dynamic parameters) async {
    final Credentials credentials = EthPrivateKey.fromHex(_privateAddress);

    EthereumTransaction ethTransaction =
        EthereumTransaction.fromJson(parameters[0]);
    final gasFee = ethTransaction.gas != null
        ? BigInt.tryParse(ethTransaction.gas!, radix: 16) ?? BigInt.zero
        : BigInt.zero;

    final transaction = Transaction(
      from: EthereumAddress.fromHex(ethTransaction.from),
      to: EthereumAddress.fromHex(ethTransaction.to),
      value: ethTransaction.value == null
          ? null
          : EtherAmount.fromBigInt(
              EtherUnit.wei,
              BigInt.parse(ethTransaction.value!.substring(2), radix: 16),
            ),
      gasPrice: ethTransaction.gas != null
          ? EtherAmount.fromBigInt(EtherUnit.wei, gasFee)
          : null,
      maxFeePerGas: EtherAmount.fromBigInt(
          EtherUnit.wei, gasFee + gasFee * BigInt.from(10)),
      data: (ethTransaction.data != null && ethTransaction.data != '0x')
          ? Uint8List.fromList(hex.decode(ethTransaction.data!.substring(2)))
          : null,
    );

    try {
      final Uint8List sig = await ethClient.signTransaction(
        credentials,
        transaction,
      );
      final String signedTx = hex.encode(sig);

      // zero hash
      // final String zerohash = '0x' + '0' * 64;
      // return zerohash;

      // Return the signed transaction as a hexadecimal string
      return '0x$signedTx';
    } catch (e) {
      print(e);
      return 'Failed';
    }

    // Sign the transaction

    // final tx = await _onSignTransactionRequest(
    //   ethTransaction.to,
    //   ethTransaction.from,
    //   amount.toString(),
    //   BigInt.two,
    //   ethTransaction.data != null && ethTransaction.data != '0x'
    //       ? Uint8List.fromList(hex.decode(ethTransaction.data!.substring(2)))
    //       : Uint8List(0),
    //   int.tryParse(ethTransaction.nonce ?? '') ?? 0,
    // );

    // return tx;

    // final transaction = Transaction(
    //   from: EthereumAddress.fromHex(ethTransaction.from),
    //   to: EthereumAddress.fromHex(ethTransaction.to),
    //   value: EtherAmount.fromBigInt(
    //     EtherUnit.wei,
    //     BigInt.tryParse(ethTransaction.value) ?? BigInt.zero,
    //   ),
    //   gasPrice: ethTransaction.gasPrice != null
    //       ? EtherAmount.fromBigInt(
    //           EtherUnit.wei,
    //           BigInt.tryParse(ethTransaction.gasPrice!) ?? BigInt.zero,
    //         )
    //       : null,
    //   maxFeePerGas: ethTransaction.maxFeePerGas != null
    //       ? EtherAmount.fromBigInt(
    //           EtherUnit.wei,
    //           BigInt.tryParse(ethTransaction.maxFeePerGas!) ?? BigInt.zero,
    //         )
    //       : null,
    //   maxPriorityFeePerGas: ethTransaction.maxPriorityFeePerGas != null
    //       ? EtherAmount.fromBigInt(
    //           EtherUnit.wei,
    //           BigInt.tryParse(ethTransaction.maxPriorityFeePerGas!) ??
    //               BigInt.zero,
    //         )
    //       : null,
    //   maxGas: int.tryParse(ethTransaction.gasLimit ?? ''),
    //   nonce: int.tryParse(ethTransaction.nonce ?? ''),
    //   data: (ethTransaction.data != null && ethTransaction.data != '0x')
    //       ? Uint8List.fromList(hex.decode(ethTransaction.data!))
    //       : null,
    // );

    // try {
    //   final Uint8List sig = await ethClient.signTransaction(
    //     credentials,
    //     transaction,
    //   );

    //   // Sign the transaction
    //   final String signedTx = hex.encode(sig);

    //   // Return the signed transaction as a hexadecimal string
    //   return '0x$signedTx';
    // } catch (e) {
    //   print(e);
    //   return 'Failed';
    // }
  }

  @override
  set onConnectionStatus(Function(bool p1) onConnectionStatus) {
    _onConnectionStatus = onConnectionStatus;
  }

  @override
  set onEventError(Function(String p1, String p2) onEventError) {
    // TODO: implement onEventError
  }

  @override
  set onSessionDelete(Function(String p1) onSessionDelete) {
    _onSessionDelete = onSessionDelete;
  }

  @override
  set onSessionProposal(Function(SessionProposal p1) onSessionProposal) {
    _onSessionProposal = onSessionProposal;
  }

  @override
  set onSessionRejection(Function(String p1) onSessionRejection) {
    // TODO: implement onSessionRejection
  }

  @override
  set onSessionRequest(Function(SessionRequest p1) onSessionRequest) {
    // TODO: implement onSessionRequest
  }

  @override
  set onSessionResponse(Function(SessionResponse p1) onSessionResponse) {
    // TODO: implement onSessionResponse
  }

  @override
  set onSessionSettle(Function(Session p1) onSessionSettle) {
    _onSessionSettle = onSessionSettle;
  }

  @override
  set onSessionUpdate(Function(String p1) onSessionUpdate) {
    // TODO: implement onSessionUpdate
  }

  @override
  Future<void> pair(String uri) async {
    Uri qrUri = Uri.parse(uri);
    final wc.PairingInfo pairing = await wcClient.pair(uri: qrUri);
  }

  @override
  Future<void> refreshToken() {
    // TODO: implement refreshToken
    throw UnimplementedError();
  }

  @override
  Future<void> rejectRequest(String topic, String requestId) {
    // TODO: implement rejectRequest
    throw UnimplementedError();
  }

  @override
  Future<void> rejectSession(String topic) {
    // TODO: implement rejectSession
    throw UnimplementedError();
  }

  @override
  void privateAddress(String privateAddress) {
    _privateAddress = privateAddress;
  }

  @override
  set onSignTransactionRequest(
      Future<String> Function(String to, String from, String amount,
              BigInt gasPriceInGwei, Uint8List data, int nounce)
          onSignTransactionRequest) {
    _onSignTransactionRequest = onSignTransactionRequest;
  }

  @override
  set rpcUrl(String rpcUrl) {
    // _rpcUrl = rpcUrl;
    ethClient = Web3Client(rpcUrl, Client());
  }
}

class EthUtils {
  static String getUtf8Message(String maybeHex) {
    if (maybeHex.startsWith('0x')) {
      final List<int> decoded = hex.decode(
        maybeHex.substring(2),
      );
      return utf8.decode(decoded);
    }

    return maybeHex;
  }
}

class EthereumTransaction {
  final String from;
  final String to;
  final String? gas;
  final String? data;
  final String? value;

  EthereumTransaction({
    required this.from,
    required this.to,
    this.gas,
    this.data,
    this.value,
  });

  factory EthereumTransaction.fromJson(Map<String, dynamic> json) {
    return EthereumTransaction(
      from: json['from'] as String,
      to: json['to'] as String,
      gas: json['gas'] as String?,
      data: json['data'] as String?,
      value: json['value'] as String?,
    );
  }
}

This is the service I have written.

Wallet connects with D-App successfully, but when I try to swap tokens using uniswap, "eth_sendTransaction" function gets called, where I am signing the transaction. I get a new hash in response of signing it, but after that it fails on uniswap app.

Can you tell me what am I doing wrong here?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions