Smart Contract for MultiversX

github twitter github

Let's play with a Smart Contract for MultiversX Blockchain executed on the WASM Virtual Machine.

But before that, I have to drop a disclaimer here. At the moment of writing this, I am in a stage where I am learning Rust and the tech around Smart Contracts. So if you are also like me learning and would like to see some code and see what I've learned, keep reading. Otherwise, please back in a couple of weeks ;)

For this article, I chose MultiversX blockchain. For a couple of reasons. The first one is that I had some knowledge already, and the most important one is that they have a lot of examples of Smart Contracts written in Rust. There is also pretty nice tooling, Command-line tools and even VS Code plugin which provides a whole customized IDE for writing Smart Contracts. It looks fantastic for newcomers. The only drawback for me was not complete documentation. But I hope the MultiversX team will improve this very soon. They have a lot of essential things on their hands now (xExchange).

I know that MultiversX blockchain isn't so popular, and we even don't know if it will be still around in 2-3 years (I hope so because it looks very promising). Anyway, let's see what we will be able to build.

Rust is treated as the primary language of Smart Contracts in many different projects lately. For example, Polkadot and its Substrate framework. Solana and Terra are also excellent examples. Many less-known ecosystems also bet on Rust.

In this article, I will focus only on the Smart Contract part. Then in the following articles, I will jump into JavaScript tools to interact with the Smart Contract, and I would also like to take a look at the testing approach in the MultiversX blockchain.

Worth mentioning is that every blockchain will have a little bit different approach. So using Rust isn't a guarantee that we will write Smart Contracts in the same way. It depends on tooling, library, dependencies, and overall architecture. But in general, knowing the basics of Rust should be enough to jump into the codebase quickly.

What am I building?

I wanted it to be something simple, so I choose to write a PiggyBank Smart Contract. For now, the full functionality will be basic. So the user will be able to create a Piggy and deposit some xEGLD tokens (MultiversX devnet/testnet uses xEGLD). For now, I will leave all nice features like paying for the Piggy, penalties for early withdrawal of money, etc. I will try to add them later and write the second part of the article.

Ok, let's jump into the actual code and configuration.

In this article, I won't show any user interface tools. We will use mxpy, which is MultiversX's command-line tool and not only because you can use it as Python SDK. With mxpy, you can build, deploy, and interact with Smart Contracts in the MultiversX ecosystem. In the following articles, we will create some UI for that.

For simplicity, I will show you how to generate a pem key file. This way, it will be simpler to sign transactions. Of course, you can also use the JSON file and password if you want. For such a case, check the mxpy contract deploy --help.

So we will deploy and interact with the Smart Contract using mxpy, and then I will write some words about coding in Rust using MultiversX's libraries.

Let's start by installing the mxpy

You can work with the tool on macOS and Linux. Here are the detailed guides on how to install it: https://docs.multiversx.com/sdk-and-tools/sdk-py/installing-mxpy.

Installation with mxpy-up should be straightforward. I had no problems with Linux. For macOS, I had to add a configuration line in my zshrc file. You'll find how to do this in MultiversX's docs. You can always check the walkthrough video (it uses the testnet and older function names).

Building the Smart Contract

After we have our mxpy installed already, you can clone the PiggyBank Smart Contract repo: https://github.com/xdevguild/multiversx-simple-sc, and in its directory run:

mxpy contract build

Worth mentioning that mxpy allows creating a new Smart Contract using predefined templates. But for simplicity, we will work on my GitHub repository, especially since some templates were also outdated. The ecosystem is very dynamic. So I don't blame it.

The first mxpy build will also handle all dependencies and will prepare the whole environment for us. So it will be a little bit slower than the next ones.

MultiversX wallets

First of all, we need to have devnet wallets. You can create them here: https://devnet-wallet.multiversx.com/create. Create two, write down secret phrase words in the correct order, then download the JSON files. We will need them to be able to sign transactions. You'll read about it later. Also, save passwords. Check the walkthrough video if you don't know how to do that here (it uses the testnet and older function names).

For a better demo effect, we will use two wallets. The first one will be the Smart Contract owner, and the second one will interact with it.

Both wallets should have pem files derived from seed phrases. You'll see how to do this soon.

Important! You can reconfigure all and use the testnet instead of the devnet. Switch to --chain="T" and --proxy="https://testnet-gateway.multiversx.com" in mxpy commands. Then You would also use the testnet wallets. The devnet is recommended for testing.

Fund your devnet wallets

What is important is that we need to have some xELGD on these wallets for transaction fees. Otherwise, we won't be able to do anything. Because this is a devnet, we can get the tokens using a faucet. The only one which I could find is this one: https://r3d4.fr/faucet. So let me know if there are any others.

Paste your wallet address there and define how many tokens you want to claim. Be aware of the limits there.

Important! The faucet mentioned here isn't an official tool, you can also use the official faucet built into the devnet's web wallet.

Signing transactions

To sign transactions with mxpy, you will need to provide a pem file, or if you want, you can also pass the JSON file with a password file which is more complicated, so let's see how to derive our pem file. Remember that this file should be private, don't publish it anywhere, especially if this is a pem file of a mainnet wallet.

To derive your pem file, you would need to call mxpy:

mxpy --verbose wallet derive ./walletKey.pem --mnemonic

Where ./walletKey.pem is the path and name of your generated pem file. After running this command, you would need to provide all secret words in exact order. You can check the video attached below if you are not sure what to do.

Smart Contract deployment

The first of our wallet addresses will be the owner of the Smart Contract. So we need to get its pem file and deploy the contract. We can do this using:

mxpy --verbose contract deploy --chain="D" --project=multiversx-simple-sc --pem="wallets/walletKey.pem" --gas-limit=80000000 --proxy="https://devnet-gateway.multiversx.com" --recall-nonce --send

We assumed that our PiggyBank Smart Contract is cloned to the multiversx-simple-sc directory, and our pem file is located in wallets/walletKey.pem.

After that, in the terminal, we will get our Smart Contract's address. We will need it later. Please note it down. It should be located at the end of the relatively big response object, under the address key.

Interacting with the Smart Contract

At the current state of the Smart Contract, we can add xEGLD tokens, we can also check how much we have, and we can also do payout if, of course, we will be able to do it. We assume that when creating a Piggy, we will configure the lock time before which we can't withdraw our tokens.

Let's create our Piggy. We can do this using:

mxpy --verbose contract call <smart_contract_address_here> --chain="D" --pem="wallets/walletKey2.pem" --gas-limit=5000000 --function="createPiggy" --arguments 1628619457 --proxy="https://devnet-gateway.multiversx.com" --recall-nonce --send

We are sending a transaction that will call our createPiggy function. You'll read more about it and others in the following sections of the article. For now, let's focus on interaction with the PiggyBank Smart Contract.

As you can see, we have the <smart_contract_address_here> variable. Please replace it with the previously saved Smart Contract address. We also have an argument which is a Unix Timestamp in the future. It is lock time for our PiggyBank. You also probably noted that now we use walletKey2.pem because we use another wallet to interact with our Smart Contract. With this wallet, I am not an owner of the Smart Contract anymore.

When our Piggy is ready, we want to lock some tokens there. You can do this using:

mxpy --verbose contract call <smart_contract_address_here> --chain="D" --pem="wallets/walletKey2.pem" --gas-limit=5000000 --function="addAmount" --value=1000000000000000000 --proxy="https://devnet-gateway.multiversx.com" --recall-nonce --send

As you can see, we pass 1000000000000000000 to our addAmount function. The big number corresponds to 1 xEGLD. The denomination for MultiversX's native tokens is 18. We also need to pass the Smart Contract's address and use the second wallet's pem file.

After every transaction, you can check the results in the devnet explorer here. You'll find all mxpy commands in the readme of PiggyBank SC repo. Remember that all is also presented in the walkthrough video (uses the testnet and older function names).

Ok, let's check what we already have there in our PiggyBank:

mxpy --verbose contract call <smart_contract_address_here> --chain="D" --pem="wallets/walletKey2.pem" --gas-limit=5000000 --function="amount" --proxy="https://devnet-gateway.multiversx.com" --recall-nonce --send

The response from the Smart Contract will be in HEX format. You can find many hex-to-decimal tools on the net to convert it and check that we have the same amount as we sent. So 1000000000000000000.

Suppose you would like to claim back all your tokens. You can do this using:

mxpy --verbose contract call <smart_contract_address_here> --chain="D" --pem="wallets/walletKey2.pem" --gas-limit=5000000 --function="payOut" --proxy="https://devnet-gateway.multiversx.com" --recall-nonce --send

When we call our payOut function before our lock time, we will get a failure for the transaction. We will get the information that we can't withdraw it yet.

That's all methods on which our PiggyBank Smart Contract reacts.

Hopefully, you are not lost yet. There was a lot of information here. Please watch the walkthrough video if you need it. The following section will be more about the code.

Important! The video is older than the current state of the smart contract. It uses the testnet and older names of the functions. Compare it with the docs here and in the repo.

A lot of information, and we even didn't look into the code.

Mainly because we don't have any UI yet, we could hide all that operations under a friendly user interface. But it is good to know how it works under the hood first. It is why I use mxpy here.

I will try to prepare the UI, which will handle all the interactions. It will also be fun to explore MultiversX's JavaScript tooling.

Ok, let's see what we have in the repository.

The repository contains a Rust project, which relies on MultiversX's dependencies, so writing Smart Contracts is relatively easy, even for someone who doesn't know Rust much.

The main dependency is MultiversX Rust SDK, and this is the whole framework. Unfortunately, I wasn't able to find good docs on that part. The only docs which are great, by the way, are about annotations here. But many examples and the possibility of going through the MultiversX Rust SDK library's code were enough for me to understand basics concepts. So that's good because it shows that even without good documentation, the overall codebase is in good shape.

Main code concepts in the PiggyBank Smart Contract

There are two main concepts:

  • Each MultiversX Smart Contract has access to the on chain storage. I use these to store the information about the amount, owner, and lock time for all users.
  • I use #[endpoint] annotation for my functions to expose them to the public. Endpoints are the public methods of contracts, called using transaction calls the same as described above.

Lets take a look at one of the functions to demonstrate the main idea:

#[endpoint(createPiggy)]
fn create_piggy(&self, lock_time: u64) {
  let caller = &self.blockchain().get_caller();
  require!(
    self.lock_time(&caller).is_empty() == true,
    "You already have one piggy"
  );
  require!(
    lock_time > self.get_current_time(),
    "Lock time should be in the future!"
  );
  self.lock_time(&caller).set(&lock_time);
}

Storage is defined here like:

#[storage_mapper("owner")]
fn owner(&self) -> SingleValueMapper<Address>;

#[view(getLockedAmount)]
#[storage_mapper("lockedAmount")]
fn locked_amount(&self, piggy_owner: &Address) -> SingleValueMapper<BigUint>;

#[view(getLockTime)]
#[storage_mapper("lockTime")]
fn lock_time(&self, piggy_owner: &Address) -> SingleValueMapper<u64>;

As you can see, with annotations delivered by MultiversX Rust SDK, it is simple to define methods and storage. Besides annotations, there is a lot of helpful stuff, for example, on the self.blockchain().

All other functions are very similar, so I won't paste them here, but you can always study the code.

What is worth mentioning is that we have endpoint and view annotations, as you can see. Endpoints are the public methods of contracts, which can be called through transactions. Views should be pure read-only functions which are usually used for querying values stored within Smart Contracts. In our example, it is information about the locked value and lock time.

Summary

MultiversX blockchain gives a lot of great tools. We can use mxpy to build, deploy and interact with Smart Contracts. And this is just one of many tools, a way to start learning.

Of course, there are also things about which I am not sure yet. For example, gas prices calculation. I am not sure how to automate gas price predictions. There is some info in the docs, but this topic is still hard to figure out, at least for me. I will take a closer look later.

The PiggyBank Smart Contract is simple. There is a lot of other exciting stuff, like events and callbacks. I need to learn more about the topic.

Next, I would also want to check the whole testing approach for writing Smart Contracts. It is also an essential part, and it seems that MultiversX has it sorted out in a friendly way.

I hope that his article will be helpful.

Let me know if you have any questions, and please follow me on Twitter and Github to be up to date with following articles around the topic.