Ethereum Solidity Smart Contracts interaction with pure, vanilla JavaScript.
By: Dmytro Zharkov
Tags: Solidity, Ethereum, Rpc, JavaScript, Fetch

I love JavaScript, and I hope you do as well. One of the greatest things about it - is the community, libraries, and frameworks universe. As a developer, I can find a JS library literally for everything. Blockchain and Ethereum, in particular, is not an exception. Web3, EthersJS, Truffle contract are fantastic examples. They are easy to set up and use, providing me anything I need for developing decentralized applications.

You probably have done or seen something like this many many times:

Sweet! A read request to an Ethereum Solidity Smart Contact with just several lines of code. And all the data comes in a standard human-readable format.

Or this:

And wow, magic, we subscribed to a smart contract event, that signals when something has happened in the Contract, and we receive corresponding data.

Where am I leading with this? As software engineers, we need not only know how to use tools (like libraries above) but also understand how they work. And being able to answer questions like:

What happens underhood of those frameworks and libraries?

How is it possible to make requests to a Smart Contract using only native JavaScript, without any libraries?

How to construct those requests properly?

How to encode method parameters and decode response results?

How to subscribe and listen to Ethereum WebSocket events?

Are there challenges behind?

Those are the questions I asked my self after a rather long time working with Web3JS and Truffle contract. I decided to take a closer eye on the issue and share the results with you. Writing this, I assume that you have a basic understanding of Ethereum/Solidity/DApps world and of course, a bit of JavaScript.

To keep it short, easy to read, and perceive, I decided to split the article into three parts:

Part 1. Ethereum RPC read requests with pure, vanilla JavaScript.

Part 2. Ethereum WebSocket events with pure, vanilla JavaScript.

Part 3. Encoding/decoding Ethereum RPC parameters and results.

Part 1. Ethereum RPC read requests with pure, vanilla JavaScript

To my point of view, an example-based explanation is the most understandable one, so I created an Ethereum decentralized application to make code samples below meaningful and interesting.

The idea - is a review/feedback system for superheroes.

So the user can:

- Get all superheroes.

- Get a single superhero by an ID.

- Get superhero reviews by a hero ID.

- Give a review for a superhero by the ID.

- Nominate/add a superhero to the list.

A working example is available here.

And the GitHub page is here.

Ethereum network layer allows us to communicate with Solidity Smart Contracts from frontend JavaScript applications using JSON-RPC.

In two words, JSON-RPC - is a standard, that describes, how two remote programs can talk to one another without understanding the underlying network details. For JavaScript applications, it means sending specially formatted POST request over HTTP.

Here is the essence of Ethereum JSON-RPC.

The JSON-RPC call must contain four following properties:

1. method - tells the EVM what type of operation we want to execute.

For example:

eth_call - to make read requests.

eth_send - to send a transaction, perform a write operation on a blockchain.

eth_subscribe - subscribe to WebSocket event.

2. id - a random number set up by a client, basically any integer.

3. params - everything I need to pass as arguments. Those have to be encoded into Ethereum compatible hexadecimal format.

4. jsonrpc - “2.0”. A bonus one. It always stays the same.

Where do we get data for the request? The answer is - from the contract ABI file. As it’s pretty huge I’ll add here only relevant chunks of it. The full version is here.

We will focus on 2 properties, marked in orange below.

abi - an array that contains descriptions of the contract methods. networks, more specifically - the contract address on Ethereum network, in this example Ropsten test network.

Say, I want to get all heroes. For that, I’ll go into abi array property and expand the getSuperHeroes method description. I see there are no inputs because the method doesn’t require any parameters to return a full list of superheroes; this makes it simple. So, to make the request, I only need:

1. signature property, which is a hexadecimal-encoded unique ID of the method.

2. address property from networks array above.

Now I just pass the signature into the data property and the contract address into the to property.

As this is a simple read request, you can try this right in a new browser’s tab developer tools console (Ethereum is not the fastest blockchain out there, and the request might take some time, if you passed this into the console, wait for several seconds for the promise to resolve). Cool, this works! But returned data looks awkward and not readable. Something like this:

This is because it comes in the Ethereum hexadecimal format. Decoding it - is not a trivial task, and I’ll cover this in a third part of the article. For now, I just use the Web3JS decodeParameters method, I know, I’ve promised a vanilla JS, but bere with me.

The Web3JS decodeParameters method requires two parameters:

- a types array, basically the outputs property from the method ABI. To get it, I use the JS Array find method on the abi array property with getSuperHeroes as the method name I want to call.

- a hexadecimal result string I got from the request.

And that’s it, console.log above outputs the superheroes list in a human-readable format.

That was a request without any parameters. If not mentioning the decoding part I hope it’s pretty easy and clear.

So we have a superheroes list, which could be displayed to users of a resource. Its users might want to click on a superhero to check more details about him/her. For that, we have to make a request with a specific hero ID as a parameter. In this example, the method’s name is - getHero.

A request with parameters is going to be more complex as all the parameters have to be in the Ethereum hexadecimal format and properly concatenated together in one string. To find the required request parameters, let’s take a look at the ABI again.

From the method ABI above we need:

- a signature property value, just like in the previous example.

- an inputs property. Notice, that this time it’s not empty, like before, and you can see that the request should contain a hero ID, which is unsigned 256 bits integer.

A raw request for a hero with ID=1 looks like this:

Everything stays the same as in a previous example, besides the data property.

Now it’s rather a long string 0x21d801110000000000000000000000000000000000000000000000000000000000000001. At first look, it seems like nonsense, but a closer one reveals a simple pattern. It’s a concatenation of the method signature 0x21d80111 and a number 1, encoded into Ethereum hexadecimal unsigned 256 bits integer 0000000000000000000000000000000000000000000000000000000000000001.

We could encode it manually, but we’ll leave it for the future articles and use Web3JS encodeFunctionCall method. It requires an ABI method object and the values array, in this case just a number 1.

As in a previous example, we have to decode the data from hexadecimal format into normal ones.

Phew! That works! And enough for this part. Now we should understand how to:

- make an RPC call to Solidity Smart Contract without parameters.

- decode the results of a call from hexadecimal to human-readable format.

- make an RPC call to Solidity Smart Contract that requires parameters.

- encode parameters into the Ethereum hexadecimal format.

Those where read request, we haven’t sent any data yet (made transactions), as it requires an Ethereum account and has to be signed with a valid SSH key. This topic - is its own discussion and I’ll dive into it later.

I really hope this information was useful and interesting and I’ll post the next parts of the article series really soon.

P.S. Just as you, might have learned something new for yourself here, I’m learning by researching the topic and writing this article. I would be glad for any feedback (positive and constructive), suggestion, and a discussion below. If you have any ideas on how to improve the example application - just open an issue, or make a PR, any help is welcome.