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

Interact with Smart Contrat #246

Closed david-dyn closed 10 months ago

david-dyn commented 10 months ago

Hello

I need to send transaction with currency other then default in network For example Pri in Binance Smart Chain I need to send 0.001 PRI not 0.001 BNB IMG_20240110_103401

sargiskocharyan commented 10 months ago

will follow up

quetool commented 10 months ago

Hello! Tokens are smart contracts so in order to do what you are asking for you should leverage the transfer/transferFrom functions from Privateum smart contract https://bscscan.com/address/0xb10be3f4c7e1f870d86ed6cfd412fed6615feb6f#writeContract

As per how to interact with smart contracts, there's no direct way on our SDK but you can do it through web3dart package, which is a dependency of our SDK.

Here it is an example directly from web3dart repo https://github.com/xclud/web3dart/blob/10151b108fde49dfa7f4f1a5452e38837639df5f/example/contracts.dart

Here it is an example from Celo academy https://celo.academy/t/interact-with-celo-blockchain-using-web3dart/183

And here it is a really simple example on how to read from a Contract using web3dart inside of our Web3Modal package https://docs.walletconnect.com/web3modal/flutter/actions#how-to-read-a-contract

I hope this helps for now. We are working on an more user friendly interaction with contracts from our own SDKs!

david-dyn commented 10 months ago

Thank you for your response and solution that you suggested., But the solution uses private key, it can cause security issues.

How can we avoid using private key? Thanks in advance.

quetool commented 10 months ago

Hello @david-dyn! As of today, the only way to interact with a Smart Contract is through web3dart, which indeed uses private key. We will start soon to work on our own way to interact with Smart Contract without relying on web3dart.

In the meantime you can refer to this Bob's answer here as well https://github.com/WalletConnect/WalletConnectFlutterV2/issues/219#issuecomment-1805780710

Mash-Woo commented 10 months ago

Hi, I'll just leave it here

  static Future<dynamic> transfer({
    required W3MService w3mService,
    required EthereumAddress contractAddress,
    required String tokenName,
    required EthereumAddress to,
    required BigInt amount,
  }) async {

    final ethClient = Web3Client(
      W3MChainPresets.chains[w3mService.session?.chainId]!.rpcUrl,
      http.Client(),
    );

    final contract = await ContractDetails.getContract(address: contractAddress, name: tokenName);
    final transfer = contract.function('transfer');

    Credentials credentials = WalletConnectEip155Credentials(
        signEngine: w3mService.web3App!.signEngine,
        sessionTopic: w3mService.session!.topic!,
        chainId: w3mService.selectedChain!.namespace,
        credentialAddress:
            EthereumAddress.fromHex(w3mService.session!.address!));

    final transaction = Transaction.callContract(
      contract: contract,
      function: transfer,
      parameters: [
        to,
        amount,
      ],
    );

    final result = ethClient.sendTransaction(
      credentials,
      transaction,
      chainId: int.parse(w3mService.session!.chainId)
    );

    w3mService.launchConnectedWallet();

    return result;
  }

We were helped by Bob's https://gitlab.com/graflr/flutter_web3_demo/-/blob/main/lib/model/wallet_connect_eip155_credential.dart?ref_type=heads

So sorry, but why I can't user WalletConnectEip155Credentials? Can I know what your package is?

Rudo-dyn commented 10 months ago

I was integrated WalletConnectEip155Credentials in wallet connect example project, and it worked perfectly, web3dart package.

Mash-Woo commented 10 months ago

I tried, but it does not direct me to metamask with w3mService.launchConnectedWallet();

I was integrated WalletConnectEip155Credentials in wallet connect example project, and it worked perfectly, web3dart package.

Rudo-dyn commented 10 months ago

Before transaction you need to connect wallet to your dApp, you can check example project.

quetool commented 10 months ago

I don't see the comment quoted by @Mash-Woo anymore, wasn't it good?

Rudo-dyn commented 10 months ago

I was delete it, tomorrow I will refactor it, that everyone can understand it 🙃

Mash-Woo commented 10 months ago

Before transaction you need to connect the wallet to your dApp, you can check example project.

Thank you. Now, I fixed a bit and it worked with my code, but do you know how to let user automatically change the chain to the chain that deployed the contract. My contract is on Arbitrum but when I click send transaction it often direct user to Ethereum chain

Mash-Woo commented 10 months ago

Hello @david-dyn! As of today, the only way to interact with a Smart Contract is through web3dart, which indeed uses private key. We will start soon to work on our own way to interact with Smart Contract without relying on web3dart.

In the meantime you can refer to this Bob's answer here as well #219 (comment)

Hi @quetool , I tried many different ways, but if I use web3dart to send transactions, how can I get user's private key with web3modal, I am seeing that I can't get the user's private key if they use a decentralized wallet such as Metamask

Rudo-dyn commented 10 months ago
class CustomCredentialsSender extends CustomTransactionSender {
  CustomCredentialsSender({
    required this.signingEngine,
    required this.sessionTopic,
    required this.chainId,
    required this.credentialsAddress,
  });

  final ISignEngine signingEngine;
  final String sessionTopic;
  final String chainId;
  final EthereumAddress credentialsAddress;

  @override
  EthereumAddress get address => credentialsAddress;

  @override
  Future<String> sendTransaction(Transaction transaction) async {
    if (kDebugMode) {
      print(
          'CustomCredentialsSender: sendTransaction - transaction: ${transaction.prettyPrint}');
    }

    if (!signingEngine.getActiveSessions().keys.contains(sessionTopic)) {
      if (kDebugMode) {
        print(
            'sendTransaction - called with invalid sessionTopic: $sessionTopic');
      }
      return 'Internal Error - sendTransaction - called with invalid sessionTopic';
    }

    SessionRequestParams sessionRequestParams = SessionRequestParams(
      method: 'eth_sendTransaction',
      params: [
        {
          'from': transaction.from?.hex ?? credentialsAddress.hex,
          'to': transaction.to?.hex,
          'data':
              (transaction.data != null) ? bytesToHex(transaction.data!) : null,
          if (transaction.value != null)
            'value':
                '0x${transaction.value?.getInWei.toRadixString(16) ?? '0'}',
          if (transaction.maxGas != null)
            'gas': '0x${transaction.maxGas?.toRadixString(16)}',
          if (transaction.gasPrice != null)
            'gasPrice': '0x${transaction.gasPrice?.getInWei.toRadixString(16)}',
          if (transaction.nonce != null) 'nonce': transaction.nonce,
        }
      ],
    );

    if (kDebugMode) {
      print(
          'CustomCredentialsSender: sendTransaction - blockchain $chainId, sessionRequestParams: ${sessionRequestParams.toJson()}');
    }

    final hash = await signingEngine.request(
      topic: sessionTopic,
      chainId: chainId,
      request: sessionRequestParams,
    );
    return hash;
  }

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

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

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

You can use this custom credentials class instead of using private key, you need to create this class object in your transfer function like this:

Credentials credentials = CustomCredentialsSender(
  signingEngine: w3mService.web3App!.signEngine,
  sessionTopic: w3mService.session!.topic!,
  chainId: w3mService.selectedChain!.namespace,
  credentialsAddress: EthereumAddress.fromHex(w3mService.session!.address!),
);

After it you can use this credentials in your sendTransaction method.

Mash-Woo commented 10 months ago

Credentials credentials = CustomCredentialsSender( signEngine: w3mService.web3App!.signEngine, sessionTopic: w3mService.session!.topic!, chainId: w3mService.selectedChain!.namespace, credentialAddress: EthereumAddress.fromHex(w3mService.session!.address!));

Thank you, I tested it, and it can be sent. However, instead of sending my token according to my contract, it sends Eth. And besides that, if the user rejects it, it will cause bugs. Exception has occurred. _$JsonRpcErrorImpl (JsonRpcError(code: 5000, message: User rejected the transaction))

Rudo-dyn commented 10 months ago

Are you use contract abi, and your custom token contract address? Can you share your transfer method code here?

Mash-Woo commented 10 months ago

Are you use contract abi and your custom token contract address? Can you share your transfer method code here?

Yes, I tried to use contract details like you, but it does not work for me, so now I am using this

final scAddress = EthereumAddress.fromHex(dotenv.env['WEB3_SC_ADDRESS']!);

Future<void> transferWalletConnect() async {
  String abi = await rootBundle.loadString('backend/contract/namecontract.abi.json');

  final contract =
      DeployedContract(ContractAbi.fromJson(abi, 'nameToken'), scAddress);
  final transfer = contract.function('safeTransferFromFee'); //function from the contract
  log('Check contract ${contract.address}');

  Credentials credentials = CustomCredentialsSender(
      signEngine: w3mService.web3App!.signEngine,
      sessionTopic: w3mService.session!.topic!,
      chainId: w3mService.selectedChain!.namespace,
      credentialAddress:
          EthereumAddress.fromHex(w3mService.address!));

  final transaction = Transaction.callContract(
      contract: contract,
      function: transfer,
      parameters: [
        EthereumAddress.fromHex('$myaddress'),
        EthereumAddress.fromHex('$receiveraddress'),
        BigInt.parse(authProvider.userData!.profile!.mintId!.toString()),
        BigInt.one,
        Uint8List.fromList([])
      ]);
  log('Check credentials $credentials');
  log('Check transaction $transaction');
  final result = ethClient.sendTransaction(credentials, transaction,
      chainId: int.parse(w3mService.selectedChain!.chainId));
  w3mService.launchConnectedWallet();
  w3mService.addListener(() {
    result;
  });
  w3mService.notifyListeners();
}
Rudo-dyn commented 10 months ago

if your abi is json, use

final contract =
    DeployedContract(ContractAbi.fromJson(jsonEncode(abi), 'nameToken'), scAddress);

And try to use w3mService.session!.chainId instead of w3mService.selectedChain!.chainId

if these steps don't help you, please share your contract address of token I will try reproduce it.

Mash-Woo commented 10 months ago

if your abi is json, use

final contract =
    DeployedContract(ContractAbi.fromJson(jsonEncode(abi), 'nameToken'), scAddress);

And try to use w3mService.session!.chainId instead of w3mService.selectedChain!.chainId

if these steps don't help you, please share your contract address of token I will try reproduce it.

Somehow my package only can use w3mService.selectedChain!.chainId or maybe I will set a String such as '42161' the chain id of Arbitrum

Rudo-dyn commented 10 months ago

if your abi is json, use

final contract =
    DeployedContract(ContractAbi.fromJson(jsonEncode(abi), 'nameToken'), scAddress);

And try to use w3mService.session!.chainId instead of w3mService.selectedChain!.chainId if these steps don't help you, please share your contract address of token I will try reproduce it.

Somehow my package only can use w3mService.selectedChain!.chainId or maybe I will set a String such as '42161' the chain id of Arbitrum

Where is your ethClient init? And can you share rpcUrl that you use?

Mash-Woo commented 10 months ago

if your abi is json, use

final contract =
    DeployedContract(ContractAbi.fromJson(jsonEncode(abi), 'nameToken'), scAddress);

And try to use w3mService.session!.chainId instead of w3mService.selectedChain!.chainId if these steps don't help you, please share your contract address of token I will try reproduce it.

Somehow my package only can use w3mService.selectedChain!.chainId or maybe I will set a String such as '42161' the chain id of Arbitrum

Where is your ethClient init? And can you share rpcUrl that you use?

I put it in main, my rpcUrl, I am facing this Error

Exception has occurred. _$WalletConnectErrorImpl (WalletConnectError(code: 5100, message: Unsupported chains. The chain eip155:42161 is not supported, data: null))

Rudo-dyn commented 10 months ago

if your abi is json, use

final contract =
    DeployedContract(ContractAbi.fromJson(jsonEncode(abi), 'nameToken'), scAddress);

And try to use w3mService.session!.chainId instead of w3mService.selectedChain!.chainId if these steps don't help you, please share your contract address of token I will try reproduce it.

Somehow my package only can use w3mService.selectedChain!.chainId or maybe I will set a String such as '42161' the chain id of Arbitrum

Where is your ethClient init? And can you share rpcUrl that you use?

I put it in main, my rpcUrl, I am facing this Error

Exception has occurred. _$WalletConnectErrorImpl (WalletConnectError(code: 5100, message: Unsupported chains. The chain eip155:42161 is not supported, data: null))

Your chain id is 42161, use it instead of eip155:42161

Mash-Woo commented 9 months ago

if your abi is json, use

final contract =
    DeployedContract(ContractAbi.fromJson(jsonEncode(abi), 'nameToken'), scAddress);

And try to use w3mService.session!.chainId instead of w3mService.selectedChain!.chainId if these steps don't help you, please share your contract address of token I will try reproduce it.

Somehow my package only can use w3mService.selectedChain!.chainId or maybe I will set a String such as '42161' the chain id of Arbitrum

Where is your ethClient init? And can you share rpcUrl that you use?

I put it in main, my rpcUrl, I am facing this Error Exception has occurred. _$WalletConnectErrorImpl (WalletConnectError(code: 5100, message: Unsupported chains. The chain eip155:42161 is not supported, data: null))

Your chain id is 42161, use it instead of eip155:42161

I changed but still facing this problem

quetool commented 9 months ago

@Mash-Woo could you share the contract address and your code where you send the transaction?

Mash-Woo commented 9 months ago

@Mash-Woo could you share the contract address and your code where you send the transaction?

This is my set-up config

void initializedW3MService() async {
  W3MChainPresets.chains.putIfAbsent(
    'eip155:${dotenv.get('CHAIN_ID')}',
    () => W3MChainInfo(
      chainName: 'Arbitrum Sepolia Testnet',
      chainId: dotenv.get('CHAIN_ID'),
      namespace: 'eip155:${dotenv.get('CHAIN_ID')}',
      tokenName: 'ETH',
      rpcUrl: 'https://sepolia-rollup.arbitrum.io/rpc',
      blockExplorer: W3MBlockExplorer(
        name: 'Arbitrum Sepolia Testnet',
        url: 'https://sepolia.arbiscan.io/',
      ),
    ),
  );
  await w3mService.init();
  w3mService.notifyListeners();
}

I just want to add this config and use it, it can work on metamask but not on trust wallet

Mash-Woo commented 9 months ago

In Metamask, I am facing this #176

quetool commented 9 months ago

I meant a whole reproducible code, including contract Abi, contract address, and everything that could be useful to test. it's pretty difficult otherwise.

quetool commented 9 months ago

Also, is your custom chain (Arbitrum Sepolia) added in your Wallet? If not, are you requesting wallet_addEthereumChain?

Mash-Woo commented 9 months ago

Also, is your custom chain (Arbitrum Sepolia) added in your Wallet? If not, are you requesting wallet_addEthereumChain?

I added it to my wallet

Mash-Woo commented 9 months ago

I meant a whole reproducible code, including contract Abi, contract address, and everything that could be useful to test. it's pretty difficult otherwise.

Hmm, actually I completed solving this, and now my bug is Exception has occurred. _$WalletConnectErrorImpl (WalletConnectError(code: 5100, message: Unsupported chains. The chain eip155:421614 is not supported, data: null))

It cause when I work with Trust Wallet, except for Metamask

quetool commented 9 months ago

It means Trust wallet does not support that chain. Try a different wallet or a different chain on Trust.

Mash-Woo commented 9 months ago

It means Trust wallet does not support that chain. Try a different wallet or a different chain on Trust.

Thank you, I changed to another network and it worked, but sometime, if user rejected transaction, I will meet this problem Exception has occurred. _$JsonRpcErrorImpl (JsonRpcError(code: 5001, message: User disapproved requested methods))

quetool commented 9 months ago

Indeed that means the user rejected the request

Mash-Woo commented 9 months ago

Indeed that means the user rejected the request

Would you happen to have any solution, such as if the user rejects the request, it will redirect to my app?

quetool commented 9 months ago

It's up to the wallet to redirect to your dapp. You can't do anything from your dapp to get back to your dapp from the wallet. All you can do is to catch the error and do whatever you need to do.

In any case I just realized you are implementing Web3Modal and this is WalletConnectFlutterV2 repo so please if you have any other issue open a new one in the proper repository. Thanks!

quetool commented 9 months ago

Hello all! Glad to announce that a new beta is out for WalletConnectFlutterV2 with Smart Contract interaction based on the proposed solution from @bobwith2bees. Thank you Bob! And thank you @Rudo-dyn for bringing this to my attention!

For any issue with this beta please follow up on this new thread

In the PR description you will find how to interact with the new functionality, hopefully it's easy enough.

For the time being, if you are willing to try this with Web3Modal (such as your case @Mash-Woo), you'll need to override walletconnect_flutter_v2 dependency as follows:

dependency_overrides:
  walletconnect_flutter_v2: ^2.2.0-beta01

And then make your requests leveraging web3app instance of w3mService

return w3mService.web3App!.requestWriteContract(.....)
// OR
return w3mService.web3App!.requestReadContract(.....)

Bear in mind that this is a beta release and things might slightly change in the near future. Thanks!