How to wrap a JavaScript module (Web3)?

First, some of my own background, which might help explain my thought processes: I am relatively new to Elm, but I have been developing with JavaScript, Haskell and Elixir for a long time.

So, I am in a very interesting situation. I am in the progress of building a so-called ‘Decentralized Application’, which is a fancy term of stating that the application will be a HTML+CSS+(Something compiled to)JavaScript-application that interfaces with the (in this case) Ethereum Blockchain.

This application might end up running in different contexts: It will probably run inside a web-browser(-like) environment, but the method that it will have to use to interact with the Blockchain Environment is through the so-called Web3 Provider.

Now, depending on what context the application is executed in, one of the following things needs to happen:

  • If executed in the Mist Etereum wallet, use the provider that Mist injects into its browser window (web3 = new Web3(window.web3.currentProvider)).
  • If executed in a web-browser when MetaMask is installed, use the provider that MetaMask injects into the browser window (web3 = new Web3(window.web3.currentProvider))
  • If executed in a web-browser when no external tools are installed, Web3 needs to be set up with a web3 = new Web3(web3.providers.HttpProvider(some_url)) that links to an RPC-exposing Ethereum Client endpoint.

All of this is only tangentially related to Elm, of course, but it might give some background about this problem, at least showing that it is infeasible to re-write this logic in Elm because in certain contexts we need to depend on injected JavaScript calls.
Side note: There is the elm-ethereum package, which is able to create Tasks by using the Http-library directly, but it does not support other wallets except when the user themselves sets up a lot of ports.

So, now I am looking into ways to make Elm interact with it.

I know of the following approaches:

  1. Use a Cmd Port to send requests to JavaScript, and send the responses back using a Subscription Port.
  2. Create a special ‘program’ wrapper (Akin to Html.program, Navigation.program and similar) that uses (1) under the hood but hides most of the implementation details.
  3. Create a Native/Kernel module.

Here’s my thoughts to each of these methods so far.

Ports

It’s impossible to create a library out of ports + JavaScript, because there is a lot of plumbing that needs to be put in place in the application programmer’s code to ensure that things work.

Also, it is very common for multiple (asynchronous!) calls to Web3 to need to happen after one-another before we have arrived at the final interesting result. Ports create Commands, and Commands do not seem that composable.

Custom Program

This has the same advantages and disatvantages as the former, except that some of the plumbing can be hidden because we can wrap the port back-and-forth in our own program wrapper.

However, the result will still be calls that are not that composable.

Native/Kernel code

It seems like writing native code would be the only way to create e.g. Tasks, which seem a lot more composable than Commands.

However, writing Native code has been discouraged for a long time and in 0.19 this will no longer be possible at all.


Neither of these approaches seem satisfying.
What am I missing?

Help is greatly appreciated!

Just released an accompanying npm package for elm-ethereum. Helps greatly reduce the boilerplate for sending transactions, and monitoring account/network changes from the wallet. See elm-ethereum-ports example

Regarding the title of this thread. If you look at the 0.20 and 1.0 branch of elm-ethereum you’ll see how I was originally wrapping the JS library with native code and effect managers. Having spent a long time going down this road, my advice is: it’s not worth it.

I spent months experimenting wrapping web3.js and each attempt was quite unsatisfactory. It was always highly coupled to web3.js, was finicky when dealing with stateful modules (events, wallet), and hard to reason about the code which dealt with a variety of function calls in the native code (asynchronous vs synchronous functions, vs object getters).

The lesson that’s come from all this, avoid at all cost using elm to wrap JS libraries. When possible, take the time to figure out a proper Elm implementation.

I feel the latest implementation of elm-ethereum (which currently lives here until a 1.0 release) is a decent pure Elm approach. The small amount of boiler plate, along with helper tools like elm-ethereum-ports and elm-ethereum-generator helps provide a pleasant developer experience.

Cheers

While this topic has been reasonably quiet, I did have some interesting conversations on the Elm Slack group.

Currently, I am writing my own wrapper for Ethereum’s JSON-RPC JS client. Where my approach differs from @cmditch 's, is that his library requires to connect to a remote RPC node for most ethereum-calls (‘get’-requests); and only uses the local node for ethereum-transactions (‘mutations’).

While this approach meant that elm-ethereum is able to piggyback on libraries like Http to use their provided functionality, some of which is very difficult to implement without your own Native module in Elm (like andThen to chain multiple requests), it does create a very odd impedance mismatch in my own opinion.

That said, he does have some very interesting features in his library (albeit some of them are still lacking documentation because the library is not yet finished).


The library I am building that I hope to be able to call elm-web3 (which as of yet is not released but will be in the coming couple of weeks; I’m about 33% done with wrapping all calls of the Ethereum JSON-RPC) instead uses the Porter library to provide request<->response-chains that always talk to the one node you’ve set up in JS-land.

JS-boilerplate will also be minimal in this library, and I do plan to release a package that sets up this boilerplate for you as well.

Huge kudos to both @cmditch as well as Peter Szerzo (his name on this forum might be something else?), the author of Porter, with whom I’ve had a couple of interesting discussions about Porter and how I am attempting to use it. As a result of these discussions, there will soon be released a v. 2 of Porter that allows simple chaining using andThen, and we’re currently discussing on a Porter spin-off Porter.Multi that allows each Porter request to have a different return type (and chain these as well as mapping over the return types, including short-circuiting Result values, which is very common in these contexts.

I’ll keep you posted. It’s a rollercoaster to start out in Elm!

2 Likes

Very excited to see what you come up with! :smile:

I agree, the impedance mismatch in elm-ethereum between wallet and node is an odd one to deal with indeed.

PS - elm-ethereum was originally called elm-web3 :stuck_out_tongue:
Even had some t-shirts made https://imgur.com/a/NxuNvvv

1 Like

Oh man, that T-shirt is amazing. I need one! :heart_eyes:

I’ll keep you updated closely on progress! Currently a PR is open on the Porter repo that adds specialized return type requests, and besides that I’ve done some work writing the deserializer to properly read a Block Info structure.

This topic was automatically closed 10 days after the last reply. New replies are no longer allowed.