WalletConnect / WalletConnectFlutterV2

WalletConnect v2 client made in Dart for Flutter.
https://pub.dev/packages/walletconnect_flutter_v2
Apache License 2.0
117 stars 61 forks source link

Credentials not getting - WalletConnectEthereumCredentials & Custom contract call #122

Closed jay-benzatine closed 1 year ago

jay-benzatine commented 1 year ago

Hello,

I have used this library https://pub.dev/packages/walletconnect_dart in my current project and it will not work anymore due to the wallet connect v2 version.

I have changed my code as given by the official doc. And I implemented this library https://pub.dev/packages/walletconnect_flutter_v2.

In my, there is functionality like a custom smart contract method call with dynamic parameters and open Metmask or any wallet for allowance or transaction. In library https://pub.dev/packages/walletconnect_flutter_v2 send transaction method is available but it will not support the custom contract call method how can I do that with this package?

So for that solution, I used this library https://pub.dev/packages/web3dart for custom contract calls.

But in this contract call method, he wants Credentials-WalletConnectEthereumCredentials to verify users.

In this https://pub.dev/packages/walletconnect_flutter_v2 library not getting WalletConnectEthereumCredentials like this lib https://pub.dev/packages/walletconnect_dart.

Here is my custom contract call function for Token Approval. But in that code, I have not gotten Credentials from walletconnect_flutter_v2 2.0.12 new library so how can I do that?


 Future<bool> getMCTApproval({required double price}) async {

    String? transactionId;
    try {

     /// THIS IS THE CUSTOM TRANSACTION USING"web3dart" 

      Transaction transaction = Transaction.callContract(
        from: EthereumAddress.fromHex(uData.walletAddress ?? ""),
        contract: contractService.mctContract,
        function:
            contractService.mctContract.function(ContractFunctionsName.approve),
        parameters: [
          EthereumAddress.fromHex(ContractAddressConstant.marketplaceAddress),
          BigInt.from((price) * pow(10, 18))
        ],
      );

      await _initGoToWallet();

     /// HERE IS THE PROBLEM I HAVE NOT GETTING **credential** FOR THE SEND TRANSACTION 

      transactionId = await client.sendTransaction(
        credential,
        chainId: chainId,
        transaction,
      );

      Debug.printLog("transactionId", transactionId.toString());
    } on Exception catch (_, e) {
      e.printError();
      Debug.printLog("Catch E", e.toString());
    }

    return (transactionId != null);
  }

Anyone, can you help me with this how can I do this? My project is stopped working. I want a solution urgently.

Jens05 commented 1 year ago

I have the exact same problem. Did you even manage to connect your wallet with the new package? Because my wallet is opening, but no request is shown to connect. Im really need a solution urgently, because by app relies on that code too...

Luzzotica commented 1 year ago

In the example dapp, this is how I send a transaction:

static Future<dynamic> ethSendTransaction({
    required Web3App web3App,
    required String topic,
    required String chainId,
    required EthereumTransaction transaction,
  }) async {
    return await web3App.request(
      topic: topic,
      chainId: chainId,
      request: SessionRequestParams(
        method: methods[EIP155Methods.ethSendTransaction]!,
        params: [transaction.toJson()],
      ),
    );
  }

And the EthereumTransaction class:

@JsonSerializable(includeIfNull: false)
class EthereumTransaction {
  final String from;
  final String to;
  final String value;
  final String? nonce;
  final String? gasPrice;
  final String? maxFeePerGas;
  final String? maxPriorityFeePerGas;
  final String? gas;
  final String? gasLimit;
  final String? data;

  EthereumTransaction({
    required this.from,
    required this.to,
    required this.value,
    this.nonce,
    this.gasPrice,
    this.maxFeePerGas,
    this.maxPriorityFeePerGas,
    this.gas,
    this.gasLimit,
    this.data,
  });

  factory EthereumTransaction.fromJson(Map<String, dynamic> json) =>
      _$EthereumTransactionFromJson(json);

  Map<String, dynamic> toJson() => _$EthereumTransactionToJson(this);

  @override
  String toString() {
    return 'WCEthereumTransaction(from: $from, to: $to, nonce: $nonce, gasPrice: $gasPrice, maxFeePerGas: $maxFeePerGas, maxPriorityFeePerGas: $maxPriorityFeePerGas, gas: $gas, gasLimit: $gasLimit, value: $value, data: $data)';
  }
}

I built the EthereumTransaction myself. You might be able to use Web3Dart library and serialize it to JSON that way.

bobwith2bees commented 1 year ago

I have the exact same problem. Did you even manage to connect your wallet with the new package? Because my wallet is opening, but no request is shown to connect. Im really need a solution urgently, because by app relies on that code too...

@Jens05 - if the wallet app opens but there is no request - double check you have encoded the URI. With V2 there are [] in the URI that need to be properly encoded.

There is code in this ticket https://github.com/WalletConnect/WalletConnectFlutterV2/issues/113#issuecomment-1614202796

Luzzotica commented 1 year ago

Perhaps you can use this repo:

https://pub.dev/packages/walletconnect_modal_flutter

And call the launchCurrentWallet function to open the currently connected wallet. Either way, you can use that repo as an example of how to launch the currently connected wallet.

Luzzotica commented 1 year ago

I understand opening the wallet is not the issue.

Your issue is that the URI is not encoded properly. I dealt with this while building this package https://pub.dev/packages/walletconnect_modal_flutter

Here is the code I use to build the proper URI:

@override
  Uri? formatNativeUrl(String? appUrl, String wcUri) {
    if (appUrl == null || appUrl.isEmpty) return null;

    if (isHttpUrl(appUrl)) {
      return formatUniversalUrl(appUrl, wcUri);
    }

    String safeAppUrl = appUrl;
    if (!safeAppUrl.contains('://')) {
      safeAppUrl = appUrl.replaceAll('/', '').replaceAll(':', '');
      safeAppUrl = '$safeAppUrl://';
    }

    String encodedWcUrl = Uri.encodeComponent(wcUri);
    LoggerUtil.logger.i('Encoded WC URL: $encodedWcUrl');

    return Uri.parse('${safeAppUrl}wc?uri=$encodedWcUrl');
  }

  @override
  Uri? formatUniversalUrl(String? appUrl, String wcUri) {
    if (appUrl == null || appUrl.isEmpty) return null;

    if (!isHttpUrl(appUrl)) {
      return formatNativeUrl(appUrl, wcUri);
    }
    String plainAppUrl = appUrl;
    if (appUrl.endsWith('/')) {
      plainAppUrl = appUrl.substring(0, appUrl.length - 1);
    }

    String encodedWcUrl = Uri.encodeComponent(wcUri);
    LoggerUtil.logger.i('Encoded WC URL: $encodedWcUrl');

    return Uri.parse('$plainAppUrl/wc?uri=$encodedWcUrl');
  }

Again, you can find this code and use it in the above repo.

Luzzotica commented 1 year ago

@Jens05

You have so many different issues going on dude.

Please open different issue requests for each one, stop hijacking other people's issues with your own stuff.

klepov commented 1 year ago

same trouble

Jens05 commented 1 year ago

In the example dapp, this is how I send a transaction:

static Future<dynamic> ethSendTransaction({
    required Web3App web3App,
    required String topic,
    required String chainId,
    required EthereumTransaction transaction,
  }) async {
    return await web3App.request(
      topic: topic,
      chainId: chainId,
      request: SessionRequestParams(
        method: methods[EIP155Methods.ethSendTransaction]!,
        params: [transaction.toJson()],
      ),
    );
  }

And the EthereumTransaction class:

@JsonSerializable(includeIfNull: false)
class EthereumTransaction {
  final String from;
  final String to;
  final String value;
  final String? nonce;
  final String? gasPrice;
  final String? maxFeePerGas;
  final String? maxPriorityFeePerGas;
  final String? gas;
  final String? gasLimit;
  final String? data;

  EthereumTransaction({
    required this.from,
    required this.to,
    required this.value,
    this.nonce,
    this.gasPrice,
    this.maxFeePerGas,
    this.maxPriorityFeePerGas,
    this.gas,
    this.gasLimit,
    this.data,
  });

  factory EthereumTransaction.fromJson(Map<String, dynamic> json) =>
      _$EthereumTransactionFromJson(json);

  Map<String, dynamic> toJson() => _$EthereumTransactionToJson(this);

  @override
  String toString() {
    return 'WCEthereumTransaction(from: $from, to: $to, nonce: $nonce, gasPrice: $gasPrice, maxFeePerGas: $maxFeePerGas, maxPriorityFeePerGas: $maxPriorityFeePerGas, gas: $gas, gasLimit: $gasLimit, value: $value, data: $data)';
  }
}

I built the EthereumTransaction myself. You might be able to use Web3Dart library and serialize it to JSON that way.

How can i make an interaction with a contract than using this package?

jay-benzatine commented 1 year ago

In the example dapp, this is how I send a transaction:

static Future<dynamic> ethSendTransaction({
    required Web3App web3App,
    required String topic,
    required String chainId,
    required EthereumTransaction transaction,
  }) async {
    return await web3App.request(
      topic: topic,
      chainId: chainId,
      request: SessionRequestParams(
        method: methods[EIP155Methods.ethSendTransaction]!,
        params: [transaction.toJson()],
      ),
    );
  }

And the EthereumTransaction class:

@JsonSerializable(includeIfNull: false)
class EthereumTransaction {
  final String from;
  final String to;
  final String value;
  final String? nonce;
  final String? gasPrice;
  final String? maxFeePerGas;
  final String? maxPriorityFeePerGas;
  final String? gas;
  final String? gasLimit;
  final String? data;

  EthereumTransaction({
    required this.from,
    required this.to,
    required this.value,
    this.nonce,
    this.gasPrice,
    this.maxFeePerGas,
    this.maxPriorityFeePerGas,
    this.gas,
    this.gasLimit,
    this.data,
  });

  factory EthereumTransaction.fromJson(Map<String, dynamic> json) =>
      _$EthereumTransactionFromJson(json);

  Map<String, dynamic> toJson() => _$EthereumTransactionToJson(this);

  @override
  String toString() {
    return 'WCEthereumTransaction(from: $from, to: $to, nonce: $nonce, gasPrice: $gasPrice, maxFeePerGas: $maxFeePerGas, maxPriorityFeePerGas: $maxPriorityFeePerGas, gas: $gas, gasLimit: $gasLimit, value: $value, data: $data)';
  }
}

I built the EthereumTransaction myself. You might be able to use the Web3Dart library and serialize it to JSON that way.

This transaction is working but this transaction method doesn't work with contract call, I mean how to integrate custom contract call with this method?

jay-benzatine commented 1 year ago

@Luzzotica Look here is old library "https://pub.dev/packages/walletconnect_dart" code how can do this in this library "https://pub.dev/packages/walletconnect_flutter_v2"?

https://github.com/RootSoft/walletconnect-dart-sdk/tree/master/lib

jay-benzatine commented 1 year ago

@Luzzotica I want WalletConnectEthereumCredentials Form connect session how can get that from the new library???

jay-benzatine commented 1 year ago

@Luzzotica Hello,

Can you please let me know how can I do transactions using a custom contract using https://pub.dev/packages/walletconnect_flutter_v2 this lib?

If you have a demo then let me know or send an example code for that.

jay-benzatine commented 1 year ago

Hello,

I have a solution for that.

You can call the custom contract call using the default web3App.request method in walletconnect_flutter_v2.

For the call custom contract, you need to use the library.

  1. walletconnect_flutter_v2 => For connect wallet
  2. web3dart => To make a transaction and send this transaction into the web3App.request method with encoding.

hex.encode(List<int>.from(transaction.data!)),

Here is my full example for the token approval...


  Future<bool> getMCTApproval({required double price}) async {
    String? transactionId;
    try {

    /// MAKE TRANSACTION USING web3dart

      Transaction transaction = Transaction.callContract(
        from: EthereumAddress.fromHex(uData.walletAddress ?? ""),
        contract: contractService.mctContract,
        function:
            contractService.mctContract.function(ContractFunctionsName.approve),
        parameters: [
          EthereumAddress.fromHex(ContractAddressConstant.marketplaceAddress),
          BigInt.from((price) * pow(10, 18))
        ],
      );

     /// MAKE ETERUM TRANSACTION USING THE walletconnect_flutter_v2

      EthereumTransaction ethereumTransaction = EthereumTransaction(
        from: uData.walletAddress ?? "",
        to: ContractAddressConstant.mctAddress,
        value: "0x0",
        data: hex.encode(List<int>.from(transaction.data!)), /// ENCODE TRANSACTION USING convert LIB
      );

      await _initGoToWallet();

     ///  REQUEST TO WALLET FOR TRANSACTION USING vwalletconnect_flutter_v2

      transactionId = await MyApp.walletConnectHelper.web3App?.request(
        topic: MyApp.walletConnectHelper.sessionData?.topic ?? "",
        chainId: MyApp.walletConnectHelper.chain.chainId,
        request: SessionRequestParams(
          method: EIP155.methods[EIP155Methods.ethSendTransaction] ?? "",
          params: [ethereumTransaction.toJson()],
        ),
      );

      Debug.printLog("TRANSACTION ID", transactionId.toString());
    } on Exception catch (_, e) {
      e.printError();
      Debug.printLog("Catch E", e.toString());
    }

    return transactionId != null;
  }
ContrastPro commented 1 year ago

Hello everyone! I will briefly describe how to send a transaction through Metamask.

1) Initialize WalletConnect

  static Web3App? _walletConnect;

  Future<void> _initWalletConnect() async {
    _walletConnect = await Web3App.createInstance(
      projectId: 'c4f79cc821944d9680842e34466bfb',
      metadata: const PairingMetadata(
        name: 'Flutter WalletConnect',
        description: 'Flutter WalletConnect Dapp Example',
        url: 'https://walletconnect.com/',
        icons: [
          'https://walletconnect.com/walletconnect-logo.png',
        ],
      ),
    );
  }

2) Create session with Metamask

  static const String launchError = 'Metamask wallet not installed';
  static const String kShortChainId = 'eip155';
  static const String kFullChainId = 'eip155:80001';

  static String? _url;
  static SessionData? _sessionData;

  String get deepLinkUrl => 'metamask://wc?uri=$_url';

  Future<String?> createSession() async {
    final bool isInstalled = await metamaskIsInstalled();

    if (!isInstalled) {
      return Future.error(launchError);
    }

    if (_walletConnect == null) {
      await _initWalletConnect();
    }

    final ConnectResponse connectResponse = await _walletConnect!.connect(
      requiredNamespaces: {
        kShortChainId: const RequiredNamespace(
          chains: [kFullChainId],
          methods: [
            'eth_sign',
            'eth_signTransaction',
            'eth_sendTransaction',
          ],
          events: [
            'chainChanged',
            'accountsChanged',
          ],
        ),
      },
    );

    final Uri? uri = connectResponse.uri;

    if (uri != null) {
      final String encodedUrl = Uri.encodeComponent('$uri');

      _url = encodedUrl;

      await launchUrlString(
        deepLinkUrl,
        mode: LaunchMode.externalApplication,
      );

      _sessionData = await connectResponse.session.future;

      final String account = NamespaceUtils.getAccount(
        _sessionData!.namespaces.values.first.accounts.first,
      );

      return account;
    }

    return null;
  }

3) Create custom EthereumTransaction model

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

  final String from;
  final String to;
  final String value;
  final String? data;

  Map<String, dynamic> toJson() => {
        'from': from,
        'to': to,
        'value': value,
        'data': data,
      };
}

4) Create instance of EthereumTransaction

  Future<EthereumTransaction> generateTransaction({
    required String userId,
    required String reseiverId,
  }) async {
    final EthereumTransaction transaction = EthereumTransaction(
      from: userId,
      to: reseiverId,
      value: '1',
    );

    return transaction;
  }

5) Sign and send transaction with Metamask

  Future<Stream<dynamic>?> sendTransaction({
    required EthereumTransaction transaction,
  }) async {
    await launchUrlString(
      deepLinkUrl,
      mode: LaunchMode.externalApplication,
    );

    final Future<dynamic> signResponse = _walletConnect!.request(
      topic: _sessionData!.topic,
      chainId: kFullChainId,
      request: SessionRequestParams(
        method: 'eth_sendTransaction',
        params: [transaction.toJson()],
      ),
    );

    return signResponse.asStream();
  }

6) You can check info from request() like this

      final Stream<dynamic>? stream = await sendTransaction(
        transaction: transaction,
      );

      if (stream != null) {
        stream.listen((event) {
          log('$event', name: 'Stream event');
        });
      }
jay-benzatine commented 1 year ago

@jay-benzatine From which library is this method?

hex.encode(List<int>.from(transaction.data!))

@ContrastPro

I used this lib for encoding transactions.

convert: ^3.1.1 (https://pub.dev/packages/convert)

4xMafole commented 1 year ago

I appreciate all the efforts used to answer this issue. But I can see most of you having different solutions which isn't bad. But I guess the solution I will provide is a close answer to what the issue was created for. I think this will be helpful for others coming on this issue to get a simple answer.

WalletConnectEthereumCredentials (Just copy this class):

// ignore: implementation_imports
import 'package:web3dart/crypto.dart';
import 'package:web3dart/src/crypto/secp256k1.dart';
import 'dart:typed_data';

import 'package:walletconnect_flutter_v2/walletconnect_flutter_v2.dart';
import 'package:web3dart/web3dart.dart';

class WalletConnectEthereumCredentials extends CustomTransactionSender {
  WalletConnectEthereumCredentials(
      {required this.wcClient, required this.session});

  final Web3App wcClient;
  final SessionData session;

  @override
  Future<String> sendTransaction(Transaction transaction) async {
    final from = await extractAddress();
    final signResponse = await wcClient.request(
      topic: session.topic,
      chainId: 'eip155:80001',
      request: SessionRequestParams(
        method: 'eth_sendTransaction',
        params: [
          {
            'from': from.hex,
            'to': transaction.to?.hex,
            'gas': '0x${transaction.maxGas!.toRadixString(16)}',
            'gasPrice':
                '0x${transaction.gasPrice?.getInWei.toRadixString(16) ?? '0'}',
            'value':
                '0x${transaction.value?.getInWei.toRadixString(16) ?? '0'}',
            'data':
                transaction.data != null ? bytesToHex(transaction.data!) : null,
            'nonce': transaction.nonce,
          }
        ],
      ),
    );

    return signResponse.toString();
  }

  @override
  EthereumAddress get address => EthereumAddress.fromHex(
      session.namespaces.values.first.accounts.first.split(':').last);

  @override
  Future<EthereumAddress> extractAddress() =>
      Future(() => EthereumAddress.fromHex(
          session.namespaces.values.first.accounts.first.split(':').last));

  @override
  MsgSignature signToEcSignature(Uint8List payload,
      {int? chainId, bool isEIP1559 = false}) {
    // TODO: implement signToEcSignature
    throw UnimplementedError();
  }

  @override
  Future<MsgSignature> signToSignature(Uint8List payload,
      {int? chainId, bool isEIP1559 = false}) {
    // TODO: implement signToSignature
    throw UnimplementedError();
  }
}

Example (Usage):

 ///Deploys a smart contract using mobile wallet
  Future<String> deployContract(
      {required Web3App walletConnect,
      required SessionData session,
      required Uri walletURI,
      ...
      }) async {
    //Getting credentials from the provider
    var credentials = WalletConnectEthereumCredentials(
      wcClient: walletConnect,
      session: session,
    );

    //Constructing web3 client
    var web3 = EthInitial();

    //Initializing web3 client
    await web3.initWeb3(ethereumCredentials: credentials, chainNetwork: chain);
    ....

    //Creating a transaction
    final Transaction transaction = Transaction(
       ...
      from: web3.credentials.address,
      ....
    );

    //Launching the mobile wallet (Metamask)
    launchUrl(walletURI, mode: LaunchMode.externalApplication);

    //Getting transaction hash
    final String transactionHash =
        await credentials.sendTransaction(transaction);

    //Getting a receipt through transaction hash
    final receipt = await web3.client.getTransactionReceipt(transactionHash);

  }

Example for Custom contract call:


 ///Mints NFT to the specified collection address
  Future<String> mintNFT({
    required String dataUri,
    required int royaltyPercentage,
    required Web3App walletConnector,
    required SessionData session,
    required Uri walletUri,
    required Chain chain,
    String? collectionAddress,
  }) async {
    //Getting credentials from the provider
    var credentials = WalletConnectEthereumCredentials(
      wcClient: walletConnector,
      session: session,
    );

    //Constructing a web3 client
    var web3 = EthInitial();

    //Initializing web3 client
    await web3.initWeb3(ethereumCredentials: credentials, chainNetwork: chain);

    //Getting the deployed contract using ABI and contract address
    final contract = DeployedContract(
      ContractAbi.fromJson(NFTContract.contractABI, ''),
      EthereumAddress.fromHex(collectionAddress!),
    );

    //Create a minting function
    final mintFunction = contract.function('mint');

    //Create a transaction from contract, mint function and data uri.
    var transaction = Transaction.callContract(
      contract: contract,
      function: mintFunction,
     .....
      parameters: [dataUri, BigInt.from(royaltyPercentage)],
    );

     ....

  }

Hope, this solves the issue. Thanks.

oyen-bright commented 1 year ago

Hello,

I have a solution for that.

You can call the custom contract call using the default web3App.request method in walletconnect_flutter_v2.

For the call custom contract, you need to use the library.

  1. walletconnect_flutter_v2 => For connect wallet
  2. web3dart => To make a transaction and send this transaction into the web3App.request method with encoding.

hex.encode(List<int>.from(transaction.data!)),

Here is my full example for the token approval...


  Future<bool> getMCTApproval({required double price}) async {
    String? transactionId;
    try {

    /// MAKE TRANSACTION USING web3dart

      Transaction transaction = Transaction.callContract(
        from: EthereumAddress.fromHex(uData.walletAddress ?? ""),
        contract: contractService.mctContract,
        function:
            contractService.mctContract.function(ContractFunctionsName.approve),
        parameters: [
          EthereumAddress.fromHex(ContractAddressConstant.marketplaceAddress),
          BigInt.from((price) * pow(10, 18))
        ],
      );

     /// MAKE ETERUM TRANSACTION USING THE walletconnect_flutter_v2

      EthereumTransaction ethereumTransaction = EthereumTransaction(
        from: uData.walletAddress ?? "",
        to: ContractAddressConstant.mctAddress,
        value: "0x0",
        data: hex.encode(List<int>.from(transaction.data!)), /// ENCODE TRANSACTION USING convert LIB
      );

      await _initGoToWallet();

     ///  REQUEST TO WALLET FOR TRANSACTION USING vwalletconnect_flutter_v2

      transactionId = await MyApp.walletConnectHelper.web3App?.request(
        topic: MyApp.walletConnectHelper.sessionData?.topic ?? "",
        chainId: MyApp.walletConnectHelper.chain.chainId,
        request: SessionRequestParams(
          method: EIP155.methods[EIP155Methods.ethSendTransaction] ?? "",
          params: [ethereumTransaction.toJson()],
        ),
      );

      Debug.printLog("TRANSACTION ID", transactionId.toString());
    } on Exception catch (_, e) {
      e.printError();
      Debug.printLog("Catch E", e.toString());
    }

    return transactionId != null;
  }

@jay-benzatine

  await _initGoToWallet();

How do i go to the wallet ? is it with the createInstance url ?