Build A Decentralized Chat Using JavaScript & Rust (WebAssembly)

Create and deploy your own decentralized chat in just a hundred lines of code using Wavelet.

Creating a Dapp (decentralized app) takes a lot of research and implementation effort.

Countless hours have to be spent worrying about malicious users, secure p2p networking, security, and even governance when it comes towards building a Dapp.

To combat this, we at Perlin have created and open-sourced a blockchain called Wavelet that makes creating secure, resilient Dapps surprisingly easy.

By easy, I mean Firebase level of easy. About a hundred lines of straightforward code level of easy. Don’t believe me?

Well, let’s create a decentralized chat :).

What We’re Going To Be Building

Like any great tutorial, it would be nice to see a live demo of what exactly you’re going to be building ahead of time.

Head on over to https://perlin-network.github.io/decentralized-chat, and you should see this.

This is what we’re going to be building.

Enter your private key to your Wavelet wallet into the [secret] textbox, and make sure you have a minimum of 250,000 PERLs.

Press Connect, then Load Contract, and you should see messages made by other people.

Feel free to then fill in the message textbox with whatever message you want to send. Then simply press Send Message, pay some PERLs, and that’s pretty much it 😃.

If you don’t have a private key, a Wavelet wallet, or 250,000 PERLs, venture on forward as we will teach you what is a private key, how you can register your own decentralized Wavelet wallet, and how you can populate it with 250,000 PERLs.

The Backend

To start off, we need to have a place to store our chat messages. In a normal app you’d just host up a MySQL/Postgres/MongoDB database and make a NodeJS server to collect chat messages and stuff them in your database.

The pros? It works. The cons? With just a couple of dollars, someone could DDoS your database server down and keep your chat service down for ages.

Bob made a chat service using MySQL and NodeJS. Bob’s chat got DDoSed by hackers. Bob is sad.

Dapps use a blockchain to store data. That data gets spread throughout potentially hundreds or thousands of nodes in a mesh network, so that even if a hacker DDoS’ the majority of your blockchains network, your app would still remain secure and available.

The backend API that queries/stores/modifies data to your blockchain is what we call a smart contract.

So, the equivalent of a NodeJS HTTP API server and a MySQL database in this tutorial would be a Rust smart contract and Wavelet (our blockchain).

Let’s get started building a smart contract for our chat Dapp. If you haven’t already, you need to install Rust, alongside a WebAssembly compiler backend for Rust which may be installed by executing the following command:

❯ rustup target add wasm32-unknown-unknown

Right after, proceed to create your first Rust project by executing the following command, and take a bit of time searching up on how to install 3rd-party dependencies/libraries into your Rust project.

❯ cargo new --lib chat; cd chat

The dependencies for building Wavelet smart contracts are available in Rust’s package repository crates.io here and here.

You should have your project configuration file Cargo.toml look something like this:

Now that we are all setup, let me just give it to you straight: here’s what the source code of our decentralized chat smart contract looks like.

73 lines of code. That’s pretty much it (could be made smaller tbh). To explain a little bit what’s going on here:

The smart contract exposes two functions that may be called by our frontend: get_messages(), and send_message(msg: String).

The String param for send_message(…) is read using the param.read() function when declaring the entry variable. For more details on how inputs are specified into smart contracts, click here.

Chat logs are stored in a VecDeque (a double-ended queue where you can push items to the front of the queue, or to the end of the queue), which is initialized in init().

Each chat log contains a sender ID and a message. The sender ID is a cryptographic public key of the person who sent the chat message.

You call send_message(…) to place a chat log into the logs queue. Empty chat messages are not allowed, and chat messages may at most be 240 characters.

The logs queue has a capacity of at most 50 chat log entries. Should the queue be full when a new chat log is to be inserted into the queue, the queue removes the oldest message using logs.pop_front(). This logic is handled in the prune_old_messages() function.

You may call get_messages() to get a human-friendly readable version of a chatlog. Sender ID’s get printed out as a hex string, and are concatenated with the senders message contents.

Then a log!() macro is used to have the smart contract provide to your frontend all available chat messages from get_messages().

Let’s now build our smart contract using Rust’s Cargo tool:

❯ cargo build --release --target wasm32-unknown-unknown

… and if everything goes well, in the target/wasm32-unknown-unknown/release folder, you will see a chat.wasm file.

That is your smart contract, ready to be deployed on Wavelet’s blockchain.

Deploying The Backend

We need access to a Wavelet blockchain network to deploy our chat smart contract to.

Fear not, we hosted one up for you to test your smart contracts out with. Head on over to https://lens.perlin.net/, and you’ll be faced with this login screen.

Generate a new private key, save your private key, load a previously-saved private key; then proceed to login.

In case you have no knowledge of what a private key is, think of it as your password. Associated to your private key is a public key, which you can think of as your username.

In a blockchain, there exists the concept of accounts. You register an account by generating a new private key which is uniquely associated to a public key (which we alternatively also call a wallet address).

So long as you don’t give away your private key, it’s gonna be very hard for a hacker to steal your account. So keep your private key safe. It is what gives you access to what you can consider your public identity/username.

Proceed to Login, and you’ll be logged into your Wavelet account. Take note of the Wallet Address on the top left, being your public key.

Congrats! You just registered your first Wavelet wallet/account.

For a bit of background:

Each account holds PERLs, or what some of you might know as a cryptocurrency/virtual currency.

So you can think of your account as a wallet holding PERLs (hence why your public key is what we call a wallet address).

The reason we have this currency is because there is no free lunch: the nodes supporting the Wavelet network (in this case, the one we hosted) are willing to host/keep your smart contract alive in exchange for some PERLs.

The way this works is that every time anyone call send_message(…) in your contract, they need to pay some PERLs to the Wavelet network.

Thankfully, we provided an easy way for you or your friends to get some testing PERLs on the network we’ve hosted up so that you can test your chat app.

To get some PERLs, we have setup a Discord server which you can join here. In the server in the #wavelet-faucet channel, type:

$claim [your public key/wallet address here]

A bot would then send 300,000 PERLs to your provided wallet address. Type the message a couple more times, until you have about 500,000 PERLs, and you should be good to go.

Head to the Developer page on the screen, press 75% button in the Gas Limit box, and upload your chat.wasm file there.

After clicking the ‘75%’ button after attaining 500,000 PERLs from Discord.

Wait a few seconds and you should see your smart contract ID. Make sure to jot it down/keep it for later, because we will need it to interact with our smart contract in our frontend.

Make sure to jot down your smart contract ID.

And so with the push of a button, our smart contract is now live. Let’s now work on a front-end to spruce up our decentralized chat app.

The Frontend

I won’t recommend any particular frameworks like React or Vue or Svelte or teach you how to style your app; it’s entirely up to you. In this section, I’ll go over the core knowledge you need to hook up your website design to Wavelet.

To start off, each Wavelet node in the network hosts a HTTP API which we may connect to for interacting with/querying info from the ledger.

The API endpoint in particular for the network we hosted up for you all, which you used to register your account and deploy your smart contract in, is the following link: https://testnet.perlin.net

We provided a nice and simple JavaScript client to interact with a Wavelet nodes HTTP API, that works on both the web and in NodeJS.

On npm it’s wavelet-client , with the source code for it being just a single file located on Github here: https://github.com/perlin-network/wavelet-client-js

Now, assuming you have async/await support set up, we can setup wavelet-client as follows:

Simply, what this code above does is let us:

connect to the Wavelet network, load our wallet by giving our private key so that we may pay for the fees to use our smart contract, and construct a Contract instance which provides methods to help us with interacting with our smart contract.

Calling get_messages()

Before continuing any further, one thing to note is that out of the two smart contract methods we defined:

only one of them modifies the blockchain to push a new chat log, while the other only simply queries the blockchain to retrieve all chat logs made thus far.

It is good to know that:

The only smart contract methods which you have to pay nodes PERLs for to execute are ones that modify the blockchain.

So, as an example, the get_messages() function can be called without incurring any cost of PERLs to your account. You can call it like so:

From the code above, you might immediately be wondering: what is this additional JSBI.BigInt(0) parameter provided?

When calling a smart contract, apart from passing in input parameters, you may additionally simultaneously send the contract some of your PERLs.

A reason why you would want this functionality is because, for example, you might make it that certain smart contract methods execute only should 500 PERLs be sent to the contract beforehand.

In this case we don’t want to send our smart contract any PERLs (we’re just making a chat app 😅) upon invoking get_messages(); so we simply set the amount of PERLs to send to the contract to be zero.

Now, after executing the code above, you should see the following in your browsers console: {"result": null, "logs": []}

To explain the console results, what the log!(…) macro does, which we used in building our smart contract, is place logs as strings in the “logs” array.

On the other hand, the “result” object is set to be the returned result/error from your smart contract function.

Hence, since we didn’t send any chat messages yet, the “logs” array is empty, and the “result” object is null.

Only after we call send_message a couple of times and re-call get_messages will we then see our messages in the “logs” array.

Digging a little further, should you be interested in knowing how contract.test works, what it does behind the scenes is:

download your smart contracts latest memory from the node you are connected to, load the smart contracts code and memory into your browsers virtual machine, and locally executes the get_messages() function and returns all printed logs and results.

Calling send_message(msg: String)

Now, let’s talk about calling the send_message(msg: String) function. As you might guess, calling this function modifies the blockchain by putting your desired message on the blockchain, and thus costs PERLs.

The way it may be called with the Wavelet JS client is actually pretty similar to the code for get_messages():

Everything is the same, except you pass in your wallet as the first parameter, and you specify your message as as an input parameter encoded as an object like so:

{type: "string", value: "Your chat message here!"}

Just to put it out there, should you ever make a smart contract function outside of the scope of this tutorial that requests multiple parameters, you may pass multiple input parameters as a variadic list to the contract.call function like so:

Now, much like the odd JSBI.BigInt(0) passed in, you might be wondering what the second JSBI.BigInt(250000) passed in is for.

In order to make sure you do not spend too much PERLs accidentally executing your function, you can set a limit to how much PERLs may attempt to be spent on executing your smart contract function.

In this case, what the code above denotes is that your call to send_message() would at most expend 250,000 PERLs.

The explicit PERL fee you pay for executing a modifying smart contract clal is computed based on how many instructions your smart contract has to execute to fully execute the smart contract method you desire.

As a pro-tip, I intentionally provided the fee limit to be 250,000 because it seems like the upper-bound fee for sending messages which I noticed while I was testing is 250,000 PERLs 😉.

More details on the fee structure when it comes to executing smart contract methods may be found here.

Listening for changes to the blockchain

The one final bit perhaps that would be great to have is to automatically refresh the chat logs when new chat logs are appended to the blockchain.

Updates are batched and then published together inside Wavelet into what is called a “consensus round”. So everytime the blockchain gets modified, after a second or two, a “consensus round” ends/passes.

We can use the Wavelet JS client to listen for when a round ends, download the latest updated smart contract memory, and re-execute get_messages() to collect the latest chat logs which we may update our frontend with like so:

In the code above, what is happening is that the fetchAndPopulateMemoryPages() function is called every single time a consensus round ends. The function downloads your smart contracts latest memory data.

What then follows is a single call to get_messages(), providing you with the latest logs and results being printed straight to your browsers console.

Conclusion

So with all of these different components, you now have a full-fledged decentralized chat app with a splendid backend and (hopefully splendid) frontend.

Hopefully, after a bit of cleanup, it looks a little something like this:

Enter your chat smasrt contracts ID into you made into UI we made for you to chat with your friends at https://perlin-network.github.io/decentralized-chat :).

If it doesn’t, either way:

Congratulations, you just made your very first, scalable, secure Wavelet Dapp!

Zero configuration, zero API keys, zero deployment headaches, zero devops, and zero worries on having to manage a scalable, expensive, resilient data layer.

There is a lot more to explore, such as a plethora of additional functionalities provided by the Wavelet JS client, alongside an extensive set of documentation for Wavelet located at https://wavelet.perlin.net.

Now, for the skeptical reader, you might point out: all this time, we were working on top of a Wavelet network which we at Perlin have hosted for the community.

In response, you can always roll your own personal Wavelet network to privately host your own slew of Dapps, and even get your friends sparing some extra computers they have lying around so that they can contribute to hosting your Dapps in exchange for some PERLs.

However, we will be hosting up a public, open network once Wavelet is fully stabilized which will be fully decentralized. More news will be coming on this in the future.

As a side note, what you see in terms of the fees for invoking smart contracts racking up to 250,000 PERLs is in no way representative of how expensive smart contracts will really be when Wavelet is fully decentralized and publicly open.

Our idea is to have smart contract method invocation fees ballpark around the range of less than a US cents worth (with respect to some amount of PERLs) when Wavelet is publicly open.

The ridiculous fees you see for the time being is in no way representative of how fees really will be like when developing with Wavelet once it is fully publicly open and decentralized.

Footnotes

To leave off with some notes, what I personally find the most exciting is that in spite of this decentralized chat we made being incredibly simple, it holds useful and meaningful implications given the numerous political agendas present in our world today.

Extending this tutorial a little further, it is fairly straightforward for one to create their own decentralized Twitter, social network, retail store, container scheduler, key-value database, Redis. Realistically, whatever service or platform you have in mind , you could imagine being made decentralized.

All of this only having been made possible because of Wavelet.

If you need any help, are interested to know more, or are stuck at any bits throughout the tutorial, feel free to reach me and our Perlin team over on our Discord here.

Source code for the entire tutorial is available here: https://github.com/perlin-network/decentralized-chat

’Til next time,

Kenta Iwasaki