simolus3 / web3dart

Ethereum library, written in Dart.
MIT License
443 stars 273 forks source link

Is it possible to connect to MetaMask with web3dart? #79

Open timmornYE opened 4 years ago

timmornYE commented 4 years ago

I use web3dart with angulardart and found no information on how to connect to MetaMask. Is this possible?

simolus3 commented 4 years ago

At the moment, there's no integration for MetaMask. You could probably implement your own Credentials subclass with it if you need it. That would allow you to re-use most web3dart apis.

timmornYE commented 4 years ago

Do you have a suggestion... what system would you use so that users can make a transaction (store data in a smart contract) from an angulardart app?

simolus3 commented 4 years ago

I haven't used web3dart in a production web project yet, so I don't have any suggestions. Using MetaMask would probably be a good option - I can help write an integration for it if you like.

jmank88 commented 4 years ago

Has there been any work done on this? I am interested in seeing examples and/or helping out with a standard implementation.

simolus3 commented 4 years ago

I haven't done any work on this yet, but I'll start working on it now. I'll let you know when I have a basic integration that can be used to sign raw transactions. From that point on it should be easier to add more functionality, for which I'd appreciate all kinds of help or PRs of course :)

jmank88 commented 4 years ago

Great! I'm happy to test something hacky if you push a branch or open a draft.

simolus3 commented 4 years ago

I've published my initial version to the metamask branch. There's an example in example/metamask.

Currently, web3dart assumes that it's able to sign arbitrary data. I thought that we could do that with MetaMask as well, but I looks like I was wrong. This means that some endpoints, in particular sendTransaction and signTransaction would have to be changed to no longer use the eth_*rawTransaction rpc calls (or only use them when we have the private key). I'll think of a good api to support both approaches here.

jmank88 commented 4 years ago

Cool, I'll take a look. AFAIK you should still be able to construct a raw signed tx via sign, though it may be more manual. Letting metamask forward automatically might be more appropriate in some cases though.

treeder commented 4 years ago

Hi @simolus3, would be great to get this stuff into a release, thanks for looking at it.

I had one comment, on it: I don't think it should have any metamask specific names or anything, most wallets (DApp browsers) support the same interface and as a developer, you don't need to know what wallet a user is using.

davidkohsea commented 4 years ago

Yes, Metamask capability would be absolutely amazing to have. Thanks so much for this project!

treeder commented 4 years ago

BTW, I made a library over here to work with metamask and other DApp browsers: https://pub.dev/packages/flutter_web3_provider

s-udhaya commented 3 years ago

may i know the status of this issue? I think this library will have more usage if metamask wallet connect is supported.

simolus3 commented 3 years ago

I've just added support for MetaMask on the master branch in this repo.

Here's an example on how MetaMask support would look like: https://github.com/simolus3/web3dart/blob/96f94fd6e850383adde799e3adab154aeb1c300a/example/metamask/web/main.dart#L8-L24

There are still some pending todos:

naezith commented 3 years ago

Hi @simolus3 , I confirm that sendTransaction works with MetaMask.

However this line fails with the MetaMask RPC client:

_web3client.call(contract: _deployedContract, function: _ctTaskCount, params: []);

Everything else works the same until that line.

Isn't it supported at the moment? If it isn't, when would it be, approximately? Is there a workaround or a better solution?

paste

simolus3 commented 3 years ago

That might be another bug, can you share the abi of the method you're calling and which response value you're getting that couldn't be parsed?

naezith commented 3 years ago

That might be another bug, can you share the abi of the method you're calling and which response value you're getting that couldn't be parsed?

Thanks for the response, here: @simolus3

pragma solidity ^0.5.16;

contract TodoList {
    uint public taskCount;

    struct Task {
        string taskName;
        bool isCompleted;
    }

    mapping(uint => Task) public todos;

    event TaskCreated(string taskName, uint taskNumber);

    constructor() public {
        taskCount = 0;
    }

    function createTask(string memory _taskName) public {
        todos[taskCount] = Task(_taskName, false);

        emit TaskCreated(_taskName, taskCount);

        ++taskCount;
    }
}

Here is the line I call it:

    _deployedContract = DeployedContract(contractAbi, _contractAddress);
    _ctTaskCount = _deployedContract.function("taskCount");

    print('Requesting task count...');
    List response = await _web3client.call(contract: _deployedContract, function: _ctTaskCount, params: []);

    print('Extracting task count from the response...');

That last print() does not print so it fails at that .call I assume.

simolus3 commented 3 years ago

I actually got invalid data back after calling that method. My local node only returns 0x which is obviously not the uint256 we're expecting here.

Can you reproduce the same thing with the JS client?

const Contracts = require('web3-eth-contract');

Contracts.setProvider('http://localhost:8545');

let contract = new Contracts(
    [
        {
            "constant": true,
            "inputs": [],
            "name": "taskCount",
            "outputs": [
                {
                    "internalType": "uint256",
                    "name": "",
                    "type": "uint256"
                }
            ],
            "payable": false,
            "stateMutability": "view",
            "type": "function"
        },
    ],
    yourContractAddress,
);

contract.methods.taskCount().call(function (err, result) {
    console.log(err);
    console.log(result);
});
naezith commented 3 years ago

@simolus3 It prints correctly:

$ node index.js
null
1
simolus3 commented 3 years ago

Do you have the contract on ropsten or something? I tried to deploy it to a local ganache-cli node which didn't work as expected.

naezith commented 3 years ago

Do you have the contract on ropsten or something? I tried to deploy it to a local ganache-cli node which didn't work as expected.

Here is the truffle-config.js. Maybe the issue is here?

module.exports = {
  networks: {
    development: {
      host: "127.0.0.1",
      port: 7545,
      network_id: "*", // Match any network id
    },
    advanced: {
      websockets: true, // Enable EventEmitter interface for web3 (default: false)
    },
  },
  contracts_build_directory: "./src/abis/",
  compilers: {
    solc: {
      optimizer: {
        enabled: true,
        runs: 200,
      },
    },
  },
};

I added that config to Ganache in Truffle Projects section on Workspace.

I also call truffle migrate.

s-udhaya commented 3 years ago

I've just added support for MetaMask on the master branch in this repo.

Here's an example on how MetaMask support would look like: https://github.com/simolus3/web3dart/blob/96f94fd6e850383adde799e3adab154aeb1c300a/example/metamask/web/main.dart#L8-L24

There are still some pending todos:

  • ~transactions can't be signed yet~ Done, sendTransaction works.
  • real time updates for events are not yet supported

Many thanks for the support. I am planning to test this today/tomorrow , Is there any ETA for real time event support?

simolus3 commented 3 years ago

So far, couldn't figure out how to use events with MetaMask unfortunately. My understanding was that we could create subscriptions with eth_subscribe and then get events through window.ethereum.on('message', ...), but that doesn't appear to work for me.

RickVM commented 3 years ago

Hi there, nice work so far! @simolus3

I'll likely start work on some blockchain dapps soon, and am in the process of evaluating flutter vs some arbritary javascript libraries. Can you give me a rough estimation on the amount of time it took you so far, and might still take to have metamask and some of the otther most popular wallets reasonably usable in web3dart and therefore Flutter? I would really prefer to use flutter and dont mind contributing, but I do need to know roughly what i'm getting into.

Also, I believe web3dart and the currently wip metamask integration work in both Flutter web and mobile, correct?

simolus3 commented 3 years ago

So basically there are two ways to use web3dart:

There are some small remaining issues with the web implementation like real-time events not working, but in general both implementations should work. If you can contribute smaller fixes and improvements then I think it should work.

hishboy commented 3 years ago

@simolus3 I noticed an issue with getting Erc20 balance in web3 (currently using MetaMask). It seems like RequestArguments is not getting passed to Ethereum.request. See screenshot.

The code works fine when running on non-web platforms. I'm guessing because the non-web codepath use the JsonRpc implementation but web is using MetaMask rpc impl. Any idea what's going on?

image

hishboy commented 3 years ago

this seems to fix the issue. I converted map to a JS object and list to JsArray in dart_wrappers.dart

import 'package:js/js_util.dart' as js;

Object _mapToJsObject(Map<String, dynamic> map) {
  final object = js.newObject();
  map.forEach((k, v) => js.setProperty(object as Object, k, v));
  return object as Object;
}

Object _listToJsArray(List<dynamic> list) {
  final array = JsArray.from(list);
  return array;
}

then in DartEthereum extension updated the following:

  Future<dynamic> rawRequest(String method, {Object? params}) {
    if (params != null && params is List) {
      for (var i = 0; i < params.length; i++) {
        if (params[i] is Map) {
          params[i] = _mapToJsObject(params[i] as Map<String, dynamic>);
        } else if (params[i] is List) {
          params[i] = _listToJsArray(params[i] as List<dynamic>);
        }
      }
    }

    // No, this can't be simplified. Metamask wants `params` to be undefined.
    final args = params == null
        ? RequestArguments(method: method)
        : RequestArguments(method: method, params: params);
    return promiseToFuture(request(args));
  }
simolus3 commented 3 years ago

Thanks! I can also take care of it, but a PR for this would be really appreciated.

li8607 commented 2 years ago

cool