MetaMask / metamask-mobile

Mobile web browser providing access to websites that use the Ethereum blockchain
https://metamask.io
Other
2.13k stars 1.1k forks source link

Connect Metamask with a native mobile app built with Flutter #3735

Open 0xCaso opened 2 years ago

0xCaso commented 2 years ago

Hi everyone. I've been researching a lot online but I can't figure out how to perform a connection between a Mobile app (which we're building with Flutter) and Metamask.

Connecting with a blockchain isn't a problem, I just have to figure out how to take private keys from a Metamask account in order to interact with the chain (for know I'm using a private key imported manually, but would be great to have a "connect wallet" button which opens the Metamask Mobile app, asking for permissions).

I found about "deeplinking" and interacting with WalletConnect, but still can't find anything for Flutter Mobile, just Web versions (which is useless).

pozniejzmienie commented 2 years ago

Hi, I have the same problem. Stuck with researching more data for opening Matamusk app and auto sync wallets. Which packages did you use for blockchain connection inside Flutter Mobile? Have you thought about something like external_app_luncher https://pub.dev/packages/external_app_launcher?

0xCaso commented 2 years ago

Hi! Yesterday I managed to make the connection using https://pub.dev/documentation/walletconnect_dart/latest/. As they suggest, you should also use https://pub.dev/packages/url_launcher.

Basically you should launch the uri (in the format wc:00e46b69-d0cc-4b3e-b6a2-cee442f97188@1?bridge=https%3A%2F%2Fbridge.walletconnect.org&key=91303dedf64285cbbaf9120f6e9d160a5c8aa3deb67017a3874cd272323f48ae) when the user presses the button "Connect Wallet".

Check also this https://docs.walletconnect.com/mobile-linking

For blockchain connection I used https://pub.dev/packages/dart_web3

pozniejzmienie commented 2 years ago

Hi again Matteo, could you show me your logical steps you did, when implementing connections? I read what you have showed me and more, but can't put it together in a logical whole... :/

EDIT: should be like this?

  1. By deep linking create wallet connections beeten Fluter app and Metamask app
  2. Then connect to proper chain
  3. By using private keys I'll be able to do transactions directly from Flutter app as Metamask account

Is it correct way of thinking?

0xCaso commented 2 years ago

Basically you should merge these two examples:

  1. Create a file with your contract's ABI, contract.abi.json.

  2. From this you can generate contract.g.dart, with the command flutter pub run build_runner build.

  3. Then you'll need the class WalletConnectEthereumCredentials from https://github.com/RootSoft/walletconnect-dart-sdk/blob/master/example/mobile/lib/ethereum_transaction_tester.dart, in order to send transactions.

  4. Now you can write this function, maybe the code is not reliable but this is what I've written:

    _walletConnect() async {
    final connector = WalletConnect(
      bridge: 'https://bridge.walletconnect.org',
      clientMeta: const PeerMeta(
        name: 'WalletConnect',
        description: 'WalletConnect Developer App',
        url: 'https://walletconnect.org',
        icons: [
          'https://gblobscdn.gitbook.com/spaces%2F-LJJeCjcLrr53DcT1Ml7%2Favatar.png?alt=media'
        ],
      ),
    );
    // Subscribe to events
    connector.on('connect', (session) => print(session));
    connector.on('session_update', (payload) => print(payload));
    connector.on('disconnect', (session) => print(session));
    
    // Create a new session
    if (!connector.connected) {
      session = await connector.createSession(
          chainId: 43113,
          onDisplayUri: (uri) async => {print(uri), await launch(uri)});
    }
    
    setState(() {
      account = session.accounts[0];
    });
    
    if (account != null) {
      final client = Web3Client(rpcUrl, Client());
      EthereumWalletConnectProvider provider =
          EthereumWalletConnectProvider(connector);
      credentials = WalletConnectEthereumCredentials(provider: provider);
      yourContract = YourContract(address: contractAddr, client: client);
    }
    }

    This function has to be called when user presses your "Connect Wallet" button, something like this:

    Row(
      children: [
        ElevatedButton(
          style: ElevatedButton.styleFrom(
            primary: Colors.white24,
            padding: const EdgeInsets.all(16.0),
            textStyle: const TextStyle(fontSize: 22, fontFamily: 'Poppins'),
          ),
          onPressed: () async => _walletConnect(),
          child: const Text('Connect Wallet'),
        ),
      ],
      mainAxisAlignment: MainAxisAlignment.center,
    ) 

    Note you can use every EVM compatible chain: here I'm using Avalanche Fuji Testnet (chainId: 43113); Also I put session, account, credentials and yourContract as global variables cause you could need them in other functions. rpcUrl in my case is https://api.avax-test.network/ext/bc/C/rpc. await launch(uri) will let the user chose the wallet to connect, then it will open the wallet app, for example Metamask. Then user will allow (or not) the connection between app and Metamask.

  5. You can now interact with your smart contract. For getter methods (view-functions in Solidity), you won't need to build the object transaction, but if you have to "write" in the blockchain then you'll need it (here escrow is my smart contract variable). For example I wrote this:

    Future<void> _confirmOrder(String orderID) async {
    final transaction = Transaction(
      to: contractAddr,
      from: EthereumAddress.fromHex(account),
      value: EtherAmount.fromUnitAndValue(EtherUnit.finney, 0),
    );
    
    launch("https://metamask.app.link/");
    
    String returned = await escrow.confirmOrder(BigInt.parse(orderID),
        credentials: credentials, transaction: transaction);
    }

Let me know if you manage to do it :)

pozniejzmienie commented 2 years ago

Hi, thanks for extensive answer! :) I will try to implement it in my enviroment. I'll let you know my results

pozniejzmienie commented 2 years ago

Why do I need to create contract.abi.json?

Wont be enough to connect wallets Flutter app <=> Metamask accout and then proceed transactions?

I try do implement same logic like we have in OpenSea mobile app (when you press the button and integrate OpenSea account with Metamask)

0xCaso commented 2 years ago

Sorry, I took for granted you wanted to interact with a smart contract. If not, you won't need that.

pozniejzmienie commented 2 years ago

Have to say, it is quite complicated, but you showed me appropriate way! I am grateful. :)

pozniejzmienie commented 2 years ago

It works without contract. By pressing the button it open Metamask app and ask for connection. Great! Thank you! Now I have to menage transactions and onther autentication stuff.

0xCaso commented 2 years ago

Perfect! Good luck then :)

EBP20 commented 2 years ago

Thanks for the explanation. I had the same problem.

EBP20 commented 2 years ago

hi, When the Metamsk opens, cann't the user automatically return to the app? how can I handle this?

pozniejzmienie commented 2 years ago

Hi, Do you mean, can't go back when pressing 'connect' button on Metamusk? I haven't done this yet. But if you've found solution, lets us know :)

0xCaso commented 2 years ago

yea didn't think about it honestly, I think it's something Metamask app has to manage, but not sure about it

pozniejzmienie commented 2 years ago

@matteocasonato Have you menaged sending transactions via Metamusk?

0xCaso commented 2 years ago

yes, I showed it a bit in the example above.

EBP20 commented 2 years ago

The problem I have now is that when it connects to Metamsk, it doesn't show a connection page to the user and doesn't ask if it wants to connect but this screen is displayed to connect to the Trustwallet.

EBP20 commented 2 years ago

The code is executed up to this line: if (!connector.connected) { session = await connector.createSession( chainId: 43113, onDisplayUri: (uri) async => {print(uri), await launch(uri)}); } , when it enters Metamask, the rest of the line isn't executed: account is still null

pozniejzmienie commented 2 years ago

Your chain id should be validated with RPC server, check this case.

pon., 21 lut 2022, 17:28 użytkownik EBP20 @.***> napisał:

The code is executed up to this line: if (!connector.connected) { session = await connector.createSession( chainId: 43113, onDisplayUri: (uri) async => {print(uri), await launch(uri)}); } , when it enters Metamask, the rest of the line isn't executed: account is still null

— Reply to this email directly, view it on GitHub https://github.com/MetaMask/metamask-mobile/issues/3735#issuecomment-1047052052, or unsubscribe https://github.com/notifications/unsubscribe-auth/AXVFDX3OI5E3GILILTGAWI3U4JR37ANCNFSM5OG6KZ4A . You are receiving this because you commented.Message ID: @.***>

EBP20 commented 2 years ago

Your chain id should be validated with RPC server, check this case. pon., 21 lut 2022, 17:28 użytkownik EBP20 @.> napisał: The code is executed up to this line: if (!connector.connected) { session = await connector.createSession( chainId: 43113, onDisplayUri: (uri) async => {print(uri), await launch(uri)}); } , when it enters Metamask, the rest of the line isn't executed: account is still null — Reply to this email directly, view it on GitHub <#3735 (comment)>, or unsubscribe https://github.com/notifications/unsubscribe-auth/AXVFDX3OI5E3GILILTGAWI3U4JR37ANCNFSM5OG6KZ4A . You are receiving this because you commented.Message ID: @.>

I used rinkeby, chain id for that is 4 Do you think this code has a problem?

_walletConnect() async { final connector = WalletConnect( bridge: 'https://bridge.walletconnect.org', clientMeta: const PeerMeta( name: 'WalletConnect', description: 'WalletConnect Developer App', url: 'https://walletconnect.org', icons: [ 'https://gblobscdn.gitbook.com/spaces%2F-LJJeCjcLrr53DcT1Ml7%2Favatar.png?alt=media' ], ), ); // Subscribe to events connector.on('connect', (session) => print(session)); connector.on('session_update', (payload) => print(payload)); connector.on('disconnect', (session) => print(session));

// Create a new session
if (!connector.connected) {
  session = await connector.createSession(
      chainId: 4,
      onDisplayUri: (uri) async => {print(uri), await launch(uri)});
}

setState(() {
  account = session.accounts[0];
  print(account);
});

if (account != null) {
  print('account != null');
  final client = Web3Client(infura, Client());
  EthereumWalletConnectProvider provider =
      EthereumWalletConnectProvider(connector);
  credentials = WalletConnectEthereumCredentials(provider: provider);
  print('test_credentials${credentials}');
  yourContract = ethUtils.getDeployedContract(contractAddress!, client);
}

}

pozniejzmienie commented 2 years ago

Show me infura URL :P

I did the same. I also had to create project on infura dashboard. RPC Infura URL should also provide project id. Tomorow I will show you how infura URL looks like in my code.

My connection is ok, but I stucked on sending transactions :/

EBP20 commented 2 years ago

String infura = "https://rinkeby.infura.io/v3/" + dotenv.env['INFURA_PROJECT_ID']!; Why it doesn't work for me :( If everything is correct, when I return to the app from Metamsk, the account shouldn't be null, right?

pozniejzmienie commented 2 years ago

I have as fallow: final rpcUrl = 'https://rinkeby.infura.io/v3/$INFURA_PROJECT_ID';

just replace INFURA_PROJECT_ID with yours infura project id :)

Make sure your dontenv dependency incjections works well

EBP20 commented 2 years ago

I found this problem. What is it about?

E/flutter (20605): [ERROR:flutter/lib/ui/ui_dart_state.cc(209)] Unhandled Exception: PlatformException(ACTIVITY_NOT_FOUND, No Activity found to handle intent { wc:c33f9376-8b62-45c0-bf98-c1a6fca4645a@1?bridge=https%3A%2F%2Ff.bridge.walletconnect.org&key=491b6b728805351d3f0eb757e14666f6c29132c88c1e9450968a6207066b71c9 }, null, null)

pozniejzmienie commented 2 years ago

No idea.. But if it throw exception, go that way. Have you debuged line by line part of code where the exception was thrown?

EGMKVT commented 2 years ago

@matteocasonato Hello, sorry if you think this is rude, is it possible to look at your source code, it's interesting to see how everything is implemented for you, I would be sincerely grateful.

EBP20 commented 2 years ago

No idea.. But if it throw exception, go that way. Have you debuged line by line part of code where the exception was thrown?

hi. Could you please look at my code? I still couldn't find the problem and all the ways I found it are for web flutter :( I appreciate you very much

Pablo4Coding commented 2 years ago

@matteocasonato Thank you for your provided answer. Thanks to that I am now able to connect my app to Metamask, fetch the wallet's address and sign transactions. So far, so good.

The problem comes when trying to implement an asymemetric/plubic key encryption system for an Authentication workflow. In order to do that, I need to sign a message (a nonce) that comes from the backend. The class WalletConnectEthereumCredentials has a method called signToSignature but is marked as a TODO to implement.

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

I'm pretty sure that's the method I need to use to sign the message. Have you by any chance implemented this function in your application? Any guess on how could I?

Thank you so much!

akshay-bhimani-pedalsup commented 2 years ago

Hey, @Pablo4Coding I am also able to connect wallet and fetch wallet addresses but not able to sign transactions. How you have managed to do that? It would be better if you can give some hints, processes, or code snippet for signing transactions.

Thanks in advance.

Jens05 commented 2 years ago

Hi there! I want to connect metamask to my app to make sure users can buy items in a shop and make a transaction to my address. How can i achieve this?

Jens05 commented 2 years ago

@matteocasonato Thank you for your provided answer. Thanks to that I am now able to connect my app to Metamask, fetch the wallet's address and sign transactions. So far, so good.

The problem comes when trying to implement an asymemetric/plubic key encryption system for an Authentication workflow. In order to do that, I need to sign a message (a nonce) that comes from the backend. The class WalletConnectEthereumCredentials has a method called signToSignature but is marked as a TODO to implement.

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

I'm pretty sure that's the method I need to use to sign the message. Have you by any chance implemented this function in your application? Any guess on how could I?

Thank you so much!

Hi, how did you manage to sign transactions? I can't get it to work. Could you please help me out?

bobwith2bees commented 2 years ago

I have an example Flutter app that connects to Metamask mobile using a WalletConnect plugin (and handles some of the IOS intent trickiness.). It's not ready for wide sharing but if it helps - https://gitlab.com/graflr/flutter_rarible_example

That said - I am hitting this issue:

I do not see the issue with the Rainbow wallet or trust wallet. So if you get to the same point +1 my Metamask mobile bug :-)

ejioforaustine commented 2 years ago

Hello Guys i am also stuck at signtransaction, is this thread still on ? please someone should throw some more light.

jvinai commented 2 years ago

Same issue with the signTransaction.

ahmedalashii commented 2 years ago

How can I get the private key when clicking on the "Connect Wallet" Button? I have this code snippet. I want to replace EthPrivateKey tempCredentials with the private key or the credentials that I'll get from the metamask application when the connection is happening! so to pass it to ethClient!.sendTransaction method

Future<String> submit(String functionName, List<dynamic> args) async {
    EthPrivateKey tempCredentials = EthPrivateKey.fromHex(
        "c58e17d53714f56c3ded7e9eed18863301055b78e141dc4ccf2c9cd9c156a4b3");
    WalletConnectEthereumCredentials credentials = this.credentials;
    debugPrint("Credentials: ${credentials}");
    final ethFunction = deployedContract!.function(functionName);
    final result = await ethClient!.sendTransaction(
      tempCredentials ,
      Transaction.callContract(
        from: EthereumAddress.fromHex(account!),
        contract: deployedContract!,
        function: ethFunction,
        parameters: args,
        maxGas: 100000,
      ),
      fetchChainIdFromNetworkId: false,
      chainId: 3,
    );
    return result;
  }
jvinai commented 2 years ago

How can I get the private key when clicking on the "Connect Wallet" Button? I have this code snippet. I want to replace EthPrivateKey tempCredentials with the private key or the credentials that I'll get from the metamask application when the connection is happening! so to pass it to ethClient!.sendTransaction method

Future<String> submit(String functionName, List<dynamic> args) async {
  EthPrivateKey tempCredentials = EthPrivateKey.fromHex(
      "c58e17d53714f56c3ded7e9eed18863301055b78e141dc4ccf2c9cd9c156a4b3");
  WalletConnectEthereumCredentials credentials = this.credentials;
  debugPrint("Credentials: ${credentials}");
  final ethFunction = deployedContract!.function(functionName);
  final result = await ethClient!.sendTransaction(
    tempCredentials ,
    Transaction.callContract(
      from: EthereumAddress.fromHex(account!),
      contract: deployedContract!,
      function: ethFunction,
      parameters: args,
      maxGas: 100000,
    ),
    fetchChainIdFromNetworkId: false,
    chainId: 3,
  );
  return result;
}

You cannot get the private key out of Metamask vault by security design.

devsideal commented 2 years ago

@matteocasonato @Pablo4Coding I also stuck on "signToSignature" due to the method was not implemented,

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

I need to sign my transaction before send it, like:

final signedTrans = await ethClient.signTransaction(credentials, transaction);
final txnHash = await ethClient.sendRawTransaction(signedTrans);

Have you got any idea to implement it?

Colin-Stark commented 2 years ago

i dont know if this helps but this blog really explains things, cause i was going through the same thing too

https://dev.to/bhaskardutta/building-with-flutter-and-metamask-8h5

salahawk commented 2 years ago

Hello, folks. Sorry for this stupid question, but I'm new to Flutter mobile development and I now need to interact with Metamask on my app (no need to sign transactions, but need users the user select his/her Metamask to get the wallet address). I followed all the instructions you've described above and I'm wondering how to install Metamask Mobile App (external app).

I'm using MacOS (installed on VM running on Windows10) and using iPhone 13 Pro - iOS 15.5 as the Emulator.

Any advice/help is welcome! Thanks! 🙇‍♂️

Colin-Stark commented 2 years ago

Hello, folks. Sorry for this stupid question, but I'm new to Flutter mobile development and I now need to interact with Metamask on my app (no need to sign transactions, but need users the user select his/her Metamask to get the wallet address). I followed all the instructions you've described above and I'm wondering how to install Metamask Mobile App (external app).

I'm using MacOS (installed on VM running on Windows10) and using iPhone 13 Pro - iOS 15.5 as the Emulator.

  • First of all, on which platform (Emulator or MacOS) should I install Metamask?
  • How to install it? Apple Store shows an error.

Any advice/help is welcome! Thanks! 🙇‍♂️

Where are you trying to install the metamask app

salahawk commented 2 years ago

on the Emulator? I'm not sure where to instal it (anyways, I'm now trying to fork the Metamask repo & build locally & run to the Emulator - but fails)

The goal is: to allow my app to interact with Metamask (only need to get wallet address) on iOS (not web app) It's written in Flutter.

Can you please tell me how I can figure out this issue?

Really urgent. Still working on this issue. 😢

salahawk commented 2 years ago

i dont know if this helps but this blog really explains things, cause i was going through the same thing too

https://dev.to/bhaskardutta/building-with-flutter-and-metamask-8h5

I was following these steps, but failed with the following error message image

Colin-Stark commented 2 years ago

i dont know if this helps but this blog really explains things, cause i was going through the same thing too

https://dev.to/bhaskardutta/building-with-flutter-and-metamask-8h5

I was following these steps, but failed with the following error message image

Mine is on windows so i downloaded metamask on my phone(physical device) and then I used the information on the article I sent earlier

salahawk commented 2 years ago

Thanks! 🙇‍♂️

What do you think about this, @Colin-Stark @pozniejzmienie @0xCaos @EBP20 @Pablo4Coding @akshay-bhimani-pedalsup ? To summarize:

It's been about 1 week for me working on this issue, but not yet solved. Rock head mine is, lol 🤣

Can you tell me how I can solve this problem soon?

Colin-Stark commented 2 years ago

Thanks

https://dev.to/bhaskardutta/building-with-flutter-and-metamask-8h5

salahawk commented 2 years ago

Thanks

https://dev.to/bhaskardutta/building-with-flutter-and-metamask-8h5

When I follow those steps, I'm facing errors while installing Metamask. I cannot install from Apple Store for iOS. The only way is to

But this shows an error like the image I uploaded before.

Colin-Stark commented 2 years ago

Maybe you should look into installing apps in xcode first.

Like tackle one issue at a time

salahawk commented 2 years ago

@Colin-Stark Your advice helped me for sure and now Installed Metamask on my Simulator. Can you please share your code for the whole code? I've tried with several code snippets but all failed.

like: how to initiate connector and how to ...

Preferably screenshot of your code? 🙇‍♂️

salahawk commented 2 years ago

Now when click on button, Metamask pops up. But it's just opened, but no request to connect with my app. Is there something specific to set on parameters?

Screen Shot 2022-07-28 at 8 19 29 PM

Colin-Stark commented 2 years ago

Sorry bruh I was in hiatus but I'm back, can you share your source code so I can see what is going on