Start Building

Build your first dApp in 30 minutes

Create the next generation of Web3 applications. Now is our time. There are no shortcuts, and no excuses.

Create a dApp in 30 minutes

This video is a great start for anyone looking to develop decentralized applications (dApps) on the MultiversX blockchain.

We'll start with the smart contract section, where our first step is installing mxpy. This will set us up for building and deploying our smart contract.

Next, we'll turn our attention to the frontend, the main focus of this video. Starting with the React JS sdk-dapp template, which is built with TypeScript and Tailwind, we aim to craft a simple yet effective interface for interacting with our smart contract.

Through this interface, users will be able to authenticate and sign transactions using popular wallets such as xPortal, the Web Wallet, the Wallet Browser Extension, and the Ledger hardware wallet. Additionally, we introduce xAlias as a straightforward way to onboard Web2 users into your dApp with Google Login, enhancing accessibility for a broader audience.

Today's development will take place on the devnet, a testing environment that mirrors the mainnet. The devnet enables us to test our smart contracts and dApps or simply get a better understanding of the MultiversX tools.

On the devnet, we'll be using xEGLD tokens instead of EGLD (eGold). It's important to note that xEGLD tokens are solely for testing and hold no real-world value.

1. Preparations

You can follow along this tutorial on any operating system, be it Windows, Linux or Mac.

A prerequisite of this tutorial is that you have some experiece with React and TypeScript. For most of the rest you can get by by just following along the commands and use them as they are.

If you’re using Windows however, you will need to have the Windows Subsystem for Linux (WSL) installed to be able to run some of the mxpy subcomponents. We will go into more details on this later.

It is also expected that you have the git command available and a IDE like VS Code installed.

Video Note: Install GIT on Windows

Ensure the git command is installed & available by running

git --version

1.1. Project folder

Create a folder that will hold all that's required for our tutorial today.

We'll create the folder mx-tutorial.

1.2. Wallet

Before starting, we will need to also create a new wallet at devnet-wallet.multiversx.com.

On the web wallet homepage choose the option to create a new wallet.

Go through the stepts, backup your words in a secure location, even if you're only going to be using this wallet for testing purposes, set a secure password, and save the the JSON wallet file (keystore) in our mx-tutorial folder.

Access the wallet using the keystore file and the password you have set and check out your wallet's dashboard.

While still in the web wallet, you should also use the faucet option on the left hand side, to request some xEGLD. Some funds will be required in order to send transactions on the blockchain.

1.3. Repositories

There will be two repositories that we will also need to clone, the smart contract one, and the React, frontend one.

For the contract one, we'll go into the mx-tutorial folder and run

git clone https://github.com/multiversx/mx-defi-tutorial-rs.git

While still in the same folder, clone the frontend repo, which is really just a bare clone of the mx-template-dapp which is a sample application based on sdk-dapp.

This repo is where we'll spend most of our time.

git clone https://github.com/multiversx/mx-defi-tutorial-dapp.git

At this point, the folder that we've created should look like something like this. The frontend repo, the contract one and the wallet keystore file.

mx-tutorial/
├── mx-defi-tutorial-dapp
├── mx-defi-tutorial-rs
└── erd1avuz8x…jcqs43fwmq.json

2. Smart Contract

You can follow along this tutorial on any operating system, be it Windows, Linux or Mac.

A prerequisite of this tutorial is that you have some experiece with React and TypeScript. For most of the rest you can get by by just following along the commands and use them as they are.

If you’re using Windows however, you will need to have the Windows Subsystem for Linux (WSL) installed to be able to run some of the mxpy subcomponents. We will go into more details on this later.

It is also expected that you have the git command available and a IDE like VS Code installed.

Video Note: Install GIT on Windows

2.1. Install pipx

Before proceeding we'll need to install pipx, which is the recommended way to install mxpy. key Ubuntu & Windows WSL

sudo apt update
sudo apt install pipx
pipx ensurepath

Mac

brew install pipx
pipx ensurepath

Make sure that pipx is correctly installed & available by running.

pipx --version

2.2. Install mxpy

The first step is to install the mxpy CLI, the swiss army knife of interracting with the MultiverX blockchain from the command line. Among other tools, this also installs sc-meta which is a is MultiversX universal smart contract management tool

Video Note: Encountering issues? Troubleshoot Rust Install

Install mxpy by running the following command.

pipx install multiversx-sdk-cli --force

The most straightforward way to install Rust if by using mxpy. If you've not used your system for compiling applications before you will need to run the following.

Ubuntu & Windows WSL

sudo apt install build-essential pkg-config libssl-dev

Mac

xcode-select --install

Now for the actual install run the following command:

mxpy deps install rust --overwrite

Confirm that the mxpy and Rust installation was successful that the CLI command is available by running:

mxpy --version
rustup show

2.3. Build the contract

Now we're finally ready to build our smart contract.

In order to get the correct version of our smart contract, the one commited after the first development iteration, we'll need to switch the git branch to the one called step-1.

From your terminal, navigate to the mx-defi-tutorial-rs folder and switch to the correct branch.

cd ~/mx-tutorial/mx-defi-tutorial-rs
git checkout step-1

Run the sc-meta build command:

sc-meta all build

After successfully building the contract an output folder was created which should look something like this.

output/
├── escrow.abi.json
├── escrow.imports.json
├── escrow.mxsc.json
└── escrow.wasm

What we have managed to do by running this is to compile the smart contract from it's source code into the WASM bytecode that we will deploy to the blockchain next, the escrow.wasm file, and, among other files, escrow.abi.json, the ABI.

A Smart Contract ABI (Application Binary Interface) file defines how to interact with a smart contract on the blockchain, specifying the methods and their parameters in a JSON format.

This will allow us to call the contract's functions from the frontend.

2.4 Deploy the contract

We will now proceed to finally d eploy the contract by running the following command from within the same folder. You will need to replace the wallet file name with your own.

mxpy --verbose contract deploy --recall-nonce --bytecode="./output/escrow.wasm" --keyfile="../erd1avuz8x…jcqs43fwmq.json" --gas-limit=100000000 --proxy="https://devnet-gateway.multiversx.com" --chain="D" --send

The contract deploy command is interractively asking for our password two times.

⚖️ Congratulations!

You've just deployed your first smart contract!

Take note of the address of the contract we've just deployed as this is the contract we will write the frontend integration for.

Here, behind the scenes, mxpy is crafting, signing and sending to the blockchain a special smart contrat deploy transaction. Identify this transaction in you address' list of transaction to learn more about what happened in the background.

3. Frontend

For the frontend part, we're fortunate because we don't need to build everything from scratch. Instead, we'll start with a fresh clone of the sdk-dapp template.

This serves as a straightforward example of how the sdk-dapp NPM package can be utilized to implement all the fundamental features required for creating a dApp. These include connecting with a wallet, setting up pages that are either publicly accessible or protected, and most importantly, signing and sending transactions.

3.1. The sdk-dapp Template

Before we dive into VS Code, there are a couple more steps we can take care of directly in the terminal.

First, let's install our project's dependencies using Yarn. Open the project folder in the terminal and execute the following command:

cd ~/mx-tutorial/mx-defi-tutorial-dapp
yarn

While we're here, let's also copy our ABI file from the smart contract's output folder to the appropriate location within our React project.

cp ../mx-defi-tutorial-rs/output/escrow.abi.json ./src/contracts/

Let's now attempt to launch our project and verify that we're on the right track.

yarn start:devnet

Afterwards, we should be able to access our dApp by navigating to https://localhost:3000 in our web browser.

Because we're using HTTPS on localhost we will need to confirm the browser's security exception.

3.2. The Dashboard

At this stage, you can connect to our locally running dApp by clicking the 'Connect' button and choosing the 'Web Wallet' option. Within the Web Wallet interface, select the 'Keystore' option and proceed to log in using the JSON keystore file you created earlier in this tutorial. Enter the password you established for it and connect.

After logging in, you'll be redirected back to our locally running dApp, landing on the dashboard - a protected page that wouldn't have been accessible if you weren't connected.

On the dashboard, you'll notice a series of widgets, each illustrating a different feature. For example, some widgets may display the current user's address and balance, while others showcase various approaches of implementing a feature, such as the different ways to invoke the Ping and Pong endpoints in the example ping-pong contract.

This section provides examples of three ways for interacting with a smart contract: sending transactions manually formulated, using the ABI (which we'll focus on now), or using a backend service.

3.3. The Plan

Our goal is to develop a simple and intuitive UI that enables users to interact with the escrow smart contract deployed in the previous section.

The Escrow smart contract features two primary actions:

  1. Deposit: This action is triggered by sending a non-zero xEGLD transaction to the contract. The transaction must include the word deposit in the data field to invoke the deposit logic within our contract.
  2. Withdraw: This action involves sending a zero xEGLD transaction to the contract with withdraw included in the data field, thereby invoking the withdrawal logic. The contract checks for any successful deposits from our address and, if found, returns the xEGLD to us upon transaction processing.

To accomplish this, we need to:

  • Remove the unused widgets
  • Configure the contract address and ABI
  • Create the structure for the Escrow Widget
  • Implement the Deposit Hook
  • Implement the Withdraw Hook
  • Develop the Query Contract Hook

3.4. Remove Unused Widgets

We will now open our frontend project code. Here, we will begin by deleting some some code from the project that we won't be using.

We will begin with the /src/pages/Dashboard/widgets/ folder. In this folder, we'll keep the Account and Transactions widgets since they provide features useful for any dApp.

Additionally, we'll keep the PingPongAbi folder, however, we will rename this folder to EscrowAbi.

We chose to start from the ABI example as using the ABI is the preferred method for interacting with a contract when a backend is not available.

Within the newly renamed EscrowAbi folder, we will have the following structure:

EscrowAbi/
├── hooks/
├── EscrowAbi.tsx
└── index.ts

The file src/pages/Dashboard/widgets/EscrowAbi/EscrowAbi.tsx will be updated as follows:

...

export const EscrowAbi = ({ callbackRoute }: WidgetProps) => {
...

The updated src/pages/Dashboard/widgets/EscrowAbi/index.ts file will be as follows:

export * from './EscrowAbi';

We will remove all other component folders: BatchTransactions, NativeAuth, PingPongRaw, PingPongService, and SignMessage.

After these deletions, our src/pages/Dashboard/widgets/ folder will have the following structure:

widgets/
├── Account/
├── EscrowAbi/
├── Transactions/
└── index.ts

The src/pages/Dashboard/widgets/index.ts file will be updated to look as follows:

export * from './Account';
export * from './EscrowAbi';
export * from './Transactions';

Lastly, we'll make updates to src/pages/Dashboard/Dashboard.tsx to align with our new widget structure.

At the beginning of the file, we'll adjust the widget imports as follows:

...
import {
  Account,
  EscrowAbi,
  Transactions
} from './widgets';
...
...
const WIDGETS: WidgetType[] = [
  {
    title: 'Account',
    widget: Account,
    description: 'Connected account details',
    reference: 'https://docs.multiversx.com/sdk-and-tools/sdk-dapp/#account'
  },
  {
    title: 'Escrow (ABI)',
    widget: EscrowAbi,
    description:
      'Smart Contract interactions using the ABI-generated transactions',
    reference:
      'https://docs.multiversx.com/sdk-and-tools/sdk-js/sdk-js-cookbook/#using-interaction-when-the-abi-is-available',
  },
  {
    title: 'Transactions (Escrow)',
    widget: Transactions,
    props: { receiver: contractAddress },
    description: 'List transactions filtered for a given Smart Contract',
    reference:
      'https://api.elrond.com/#/accounts/AccountController_getAccountTransactions'
  }
];
...

At this point, our Dashboard component file will look like this:

import { contractAddress } from 'config';
import { AuthRedirectWrapper } from 'wrappers';
import {
  Account,
  EscrowAbi,
  Transactions
} from './widgets';
import { useScrollToElement } from 'hooks';
import { Widget } from './components';
import { WidgetType } from 'types/widget.types';
const WIDGETS: WidgetType[] = [
  {
    title: 'Account',
    widget: Account,
    description: 'Connected account details',
    reference: 'https://docs.multiversx.com/sdk-and-tools/sdk-dapp/#account'
  },
  {
    title: 'Escrow (ABI)',
    widget: EscrowAbi,
    description:
      'Smart Contract interactions using the ABI-generated transactions',
    reference:
      'https://docs.multiversx.com/sdk-and-tools/sdk-js/sdk-js-cookbook/#using-interaction-when-the-abi-is-available',
  },
  {
    title: 'Transactions (Escrow)',
    widget: Transactions,
    props: { receiver: contractAddress },
    description: 'List transactions filtered for a given Smart Contract',
    reference:
      'https://api.elrond.com/#/accounts/AccountController_getAccountTransactions'
  }
];
export const Dashboard = () => {
  useScrollToElement();
  return (
    <AuthRedirectWrapper>
      <div className='flex flex-col gap-6 max-w-3xl w-full'>
        {WIDGETS.map((element) => (
          <Widget key={element.title} {...element} />
        ))}
      </div>
    </AuthRedirectWrapper>
  );
};

The most important change is our widgets object, which dictates the layout of our dashboard page.

This update involved removing all references to the deleted widgets and updating the remaining widgets names.

After completing these cleanup tasks and updates, it's an opportune time to verify that the dApp is still operational. The Ping and Pong actions should remain functional, though they will still reference the previous ping-pong contract.

3.5. Configure Contract Address and ABI

We are now prepared to update our dApp's configurations.

Firstly, it's a good time to double-check that the escrow.abi.json file is present in the src/contracts/ folder.

If you don't have it here, you can copy it from the contract's output folder or from here.

While in that directory, we should also remove the obsolete ping-pong.abi.json file, as it is no longer needed.

As a result, the src/contracts/ folder will contain only the new ABI file.

contracts
└── escrow.abi.json

We will also need to update the import of the ABI file in src/utils/smartContract.ts.

The updated import will appear as follows:

...
import json from 'contracts/escrow.abi.json';
...

Next, we need to configure our dApp to use the ABI and address of our contract. This could be the address you've deployed or the address of the existing contract (on Devnet), which is erd1qqqqqqqqqqqqqpgq6583tkxk5d2f27lyp4s82fkrp8knq403jcqsktkwt3.

Update the file src/config/config.devnet.ts to reference the correct smart contract address in this manner:

...
export const contractAddress = 'erd1qqqqqqqqqqqqqpgq6583tkxk5d2f27lyp4s82fkrp8knq403jcqsktkwt3';
...

After making the update, stop our React app and restart it. This ensures that the new version of the config file is also updated in the src/config/index.ts at runtime.

At this stage, the dApp should still be running, but you might notice numerous errors appearing in the browser's console. We'll tackle these issues next.

3.6. Create the Escrow Widget Structure

In our Escrow widget, references to features from the original template still exist. We will proceed to remove these and establish the structure for two new actions: Deposit and Withdraw.

We're about to perform a thorough cleanup of all the code in the EscrowAbi widget that is no longer in use.

We will start by removing all the previous business logic from the EscrowAbi component.

In our new component, we will only define the following event handlers:

...
const { network } = useGetNetworkConfig();
const sendDeposit = useSendDeposit();
const sendWithdraw = useSendWithdraw();

const onDeposit = async () => {
  if (
    window.confirm(`Are you sure you want to deposit 1 ${network.egldLabel}?`)
  ) {
    await sendDeposit();
  }
};

const onWithdraw = async () => {
  if (window.confirm('Are you sure you want to withdraw everything?')) {
    await sendWithdraw();
  }
};
...

Moving on, we will also update the markup of our component to reflect the new action names and connect the buttons to the new logic. Additionally, we will clean up the code a bit here by removing unnecessary React props.

...
return (
  <div className='flex flex-col gap-6'>
    <div className='flex flex-col gap-2'>
      <div className='flex justify-start gap-2'>
        <Button
          onClick={onDeposit}
          className='inline-block rounded-lg px-3 py-2 text-center hover:no-underline my-0 bg-blue-600 text-white hover:bg-blue-700 mr-0 disabled:bg-gray-200 disabled:text-black disabled:cursor-not-allowed'
        >
          <FontAwesomeIcon icon={faArrowUp} className='mr-1' />
          Deposit
        </Button>

        <Button
          onClick={onWithdraw}
          className='inline-block rounded-lg px-3 py-2 text-center hover:no-underline my-0 bg-blue-600 text-white hover:bg-blue-700 mr-0 disabled:bg-gray-200 disabled:text-black disabled:cursor-not-allowed'
        >
          <FontAwesomeIcon icon={faArrowDown} className='mr-1' />
          Withdraw
        </Button>
      </div>
    </div>

    <OutputContainer>
      <ContractAddress />
    </OutputContainer>
  </div>
);
...

At this point, our src/pages/Dashboard/widgets/EscrowAbi/EscrowAbi.tsx file looks like this:

import { faArrowDown, faArrowUp } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { TokenTransfer } from '@multiversx/sdk-core';
import { Button } from 'components/Button';
import { ContractAddress } from 'components/ContractAddress';
import { OutputContainer } from 'components/OutputContainer';
import { useSendDeposit, useSendWithdraw, useGetNetworkConfig } from 'hooks';

export const EscrowAbi = () => {
  const { network } = useGetNetworkConfig();
  const sendDeposit = useSendDeposit();
  const sendWithdraw = useSendWithdraw();

  const onDeposit = async () => {
    if (window.confirm(`Are you sure you want to deposit 1 ${network.egldLabel}?`)) {
      await sendDeposit();
    }
  };

  const onWithdraw = async () => {
    if (window.confirm('Are you sure you want to withdraw everything?'))
      await sendWithdraw();
  };

  return (
    <div className='flex flex-col gap-6'>
      <div className='flex flex-col gap-2'>
        <div className='flex justify-start gap-2'>
          <Button
            onClick={onDeposit}
            className='inline-block rounded-lg px-3 py-2 text-center hover:no-underline my-0 bg-blue-600 text-white hover:bg-blue-700 mr-0 disabled:bg-gray-200 disabled:text-black disabled:cursor-not-allowed'
          >
            <FontAwesomeIcon icon={faArrowUp} className='mr-1' />
            Deposit
          </Button>

          <Button
            onClick={onWithdraw}
            className='inline-block rounded-lg px-3 py-2 text-center hover:no-underline my-0 bg-blue-600 text-white hover:bg-blue-700 mr-0 disabled:bg-gray-200 disabled:text-black disabled:cursor-not-allowed'
          >
            <FontAwesomeIcon icon={faArrowDown} className='mr-1' />
            Withdraw
          </Button>
        </div>
      </div>

      <OutputContainer>
        <ContractAddress />
      </OutputContainer>
    </div>
  );
};

As you can see, the useSendDeposit and the useSendWithdraw don't work yet, but we'll take care of those in a bit.

The useGetNetworkConfig React hook lets us access the current network, in our case devnet. It allows us to access network parameters such as the chainID or the webWallet URL address. In this file, we'll be using the field egldLabel.

The onDeposit and the onWithdraw functions are respectively click event handlers for our two placeholder hooks, useSendDeposit and useSendWithdraw. We will elaborate on this further in a bit.

The markup resembles the initial version, from the template, with the names of the two actions being changed to Deposit and Withdraw and with the two new click hanlers set onClick.

Additionally, we will delete the outdated src/hooks/transactions/useSendPingPongTransaction.ts hooks.

We will replace the content for the new placeholder hooks like so:

Here is the placeholder for src/hooks/transactions/useSendDeposit.ts:

export const useSendDeposit = () => {
    return async () => {
        console.log('useSendDeposit');
    }
}

Next, here's the placeholder for src/hooks/transactions/useSendWithdraw.ts:

export const useSendWithdraw = () => {
    return async () => {
        console.log('useSendWithdraw');
    }
}

And the placeholder for src/hooks/transactions/useGetBalance.ts

export const useGetBalance = () => {
    return async () => {
        console.log('useGetBalance');
    }
}

We will also update the src/hooks/transactions/index.ts exports to reflect these changes.

export * from './useGetBalance';
export * from './useSendDeposit';
export * from './useSendWithdraw';

With all the changes, the src/hooks/transactions/ folder will have the following structure:

widgets/
├── index.ts
├── useGetBalance.ts
├── useSendDeposit.ts
└── useSendWithdraw.ts

The three new hooks, useSendDeposit, useSendDeposit and useSendWithdraw are meant to be temporary placeholders and follow a similar structure.

As seen here, the hooks do nothing else but write a message to the browser's console.

It is now a good time to ensure that the project still runs smoothly. We need to verify that the Deposit and Withdraw confirmation logic functions correctly and that the placeholder hooks output the expected messages in the browser's console.

With these tasks completed, the general structure of our widget is established, and we can proceed to address each of the individual hooks.

3.7. Create the Deposit Hook

Let's now start with the src/hooks/transactions/useSendDeposit.ts deposit hook.

We'll replace the placeholder with this:

import { Address } from '@multiversx/sdk-core';
import { smartContract } from 'utils/smartContract';
import { getChainId } from 'utils/getChainId';
import { refreshAccount, sendTransactions } from 'helpers/sdkDappHelpers';
import { RouteNamesEnum } from 'localConstants';
import { useGetAccount } from 'hooks/sdkDappHooks';

export const useSendDeposit = () => {
  const { address } = useGetAccount();

  return async (amount: string) => {
    const depositTransaction = smartContract.methodsExplicit
      .deposit()
      .withValue(amount)
      .withGasLimit(60000000)
      .withSender(new Address(address))
      .withChainID(getChainId())
      .buildTransaction();

    await refreshAccount();

    await sendTransactions({
      transactions: [depositTransaction],
      transactionsDisplayInfo: {
        processingMessage: 'Processing Deposit transaction',
        errorMessage: 'An error has occured during Ping',
        successMessage: 'Deposit successful'
      },
      redirectAfterSign: false,
      callbackRoute: RouteNamesEnum.dashboard
    });
  };
};

In the src/pages/Dashboard/widgets/EscrowAbi/EscrowAbi.tsx widget we will also need to specify the amount.

For this we will use the TokenTransfer method egldFromAmount.

...
import { TokenTransfer } from '@multiversx/sdk-core/out';
await sendDeposit(TokenTransfer.egldFromAmount('1').toString());
...

The blockchain expresses amounts in whole big numbers and accounts for 18 possible digits. 1 EGLD is the expressed by a 1 followed by 18 zeros. This method helps us with that transformation.

3.8. Create the Withdraw Hook

For the src/hooks/transactions/useSendWithdraw.ts withdraw hook we will use:

import { Address } from '@multiversx/sdk-core';
import { smartContract } from 'utils/smartContract';
import { getChainId } from 'utils/getChainId';
import { refreshAccount, sendTransactions } from 'helpers/sdkDappHelpers';
import { RouteNamesEnum } from 'localConstants';
import { useGetAccount } from 'hooks/sdkDappHooks';

export const useSendWithdraw = () => {
  const { address } = useGetAccount();

  return async () => {
    const withdrawTransaction = smartContract.methods
      .withdraw()
      .withGasLimit(60000000)
      .withSender(new Address(address))
      .withChainID(getChainId())
      .buildTransaction();

    await refreshAccount();

    await sendTransactions({
      transactions: [withdrawTransaction],
      transactionsDisplayInfo: {
        processingMessage: 'Processing withdraw transaction',
        errorMessage: 'An error has occured during withdrawal',
        successMessage: 'Withdrawal successful'
      },
      redirectAfterSign: false,
      callbackRoute: RouteNamesEnum.dashboard
    });
  };
};

Let's explain the main concepts used in the useSendDeposit hook.

The first one is the useGetAccount hook that gives us access to the user's address.

Next, we are returning a function, which does three basic things: builds a transaction, refreshes the user's account in order to get the latest nonce and calls the sendTransaction helper configured with the following.

The transaction that we want to sign and send, a configuration for the toast that will track our transaction and a return route, required when users are using the web wallet.

The useSendWithdraw hook, follows a similar structure to the useSendDeposit one.

We are again grabbing the user's address, building the transaction, refreshing the nonce, and the calling the sendTransaction helper.

With the useSendDeposit and the useSendWithdraw hooks in place you should now be able to test that the actual transactions to the contract work.

🏆 This is a huge achievement!

You have now implemented a full, albeit simple, smart contract interaction. But this is really what is the foundation of everything that's required to create dApps with smart contract interactions.

3.9. Create the Query Contract Hook

Things are now getting really interesting, as, what we have until now is only pusing information and funds to the smart via transactions. With the useGetBalance hook we will also be quering the information from contract's state (by also leveraging the ABI) in order to update the dApp UI with that information.

Replace the src/hooks/transactions/useGetBalance.ts like so:

import {
  Address,
  AddressValue,
  ContractFunction,
  ResultsParser
} from 'utils/sdkDappCore';
import { smartContract } from 'utils/smartContract';
import { useGetAccount, useGetNetworkConfig } from 'hooks/sdkDappHooks';
import { ProxyNetworkProvider } from '@multiversx/sdk-network-providers/out';

const resultsParser = new ResultsParser();

const GET_USER_BALANCE = 'getUserBalance';

export const useGetBalance = () => {
  const { address } = useGetAccount();
  const { network } = useGetNetworkConfig();

  return async () => {
    try {
      const query = smartContract.createQuery({
        func: GET_USER_BALANCE,
        args: [new AddressValue(new Address(address))]
      });
      const provider = new ProxyNetworkProvider(network.apiAddress);
      const queryResponse = await provider.queryContract(query);
      const endpointDefinition = smartContract.getEndpoint(GET_USER_BALANCE);
      const { firstValue } = resultsParser.parseQueryResponse(
        queryResponse,
        endpointDefinition
      );
      const userBalance: string = firstValue?.valueOf()?.toString();
      return userBalance;
    } catch (err) {
      console.error(`Unable to call ${GET_USER_BALANCE}`, err);
    }
  };
};

Let's explain the main contepts used in the useGetBalance hook. First we're fetching the user's address and the network from the two sdk-dapp hooks, useGetAccount and useGetNetworkConfig.

We are then creating a smart contract query object. We are then sending this query to the blockchain.

We are then using the ABI to get the endpoint definition to be used for parsing the response. And finaly, we are parsing the response based on this definition.

In order to also make use of the knowlege of the user's Escrow contract balance in the UI we will need to set some states inside our EscrowAbi widget.

We will also fix the imports as follows.

The src/pages/Dashboard/widgets/EscrowAbi/EscrowAbi.tsx will look like this:

import { useEffect, useState } from 'react';
import { faArrowDown, faArrowUp } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { TokenTransfer } from '@multiversx/sdk-core';
import { Button } from 'components/Button';
import { ContractAddress } from 'components/ContractAddress';
import { OutputContainer } from 'components/OutputContainer';
import {
  useGetAccountInfo,
  useSendDeposit,
  useSendWithdraw,
  useGetBalance,
  useGetNetworkConfig
} from 'hooks';
import { FormatAmount, Label } from 'components';
import { formatAmount } from '@multiversx/sdk-dapp/utils/operations/formatAmount';

export const EscrowAbi = () => {
  const { network } = useGetNetworkConfig();
  const sendDeposit = useSendDeposit();
  const sendWithdraw = useSendWithdraw();
  const getBalance = useGetBalance();
  const { websocketEvent } = useGetAccountInfo();
  const [balance, setBalance] = useState<string>();

  const fetchBalance = async () => {
    const balance = await getBalance();
    setBalance(balance);
  };

  useEffect(() => {
    fetchBalance();
  }, [websocketEvent]);

  const onDeposit = async () => {
    if (
      window.confirm(`Are you sure you want to deposit 1 ${network.egldLabel}`)
    ) {
      await sendDeposit(TokenTransfer.egldFromAmount('1').toString());
    }
  };

  const onWithdraw = async () => {
    const amount = balance ? formatAmount({ input: balance }) : '…';
    if (
      window.confirm(
        `Are you sure you want to withdraw ${amount} ${network.egldLabel}?`
      )
    ) {
      await sendWithdraw();
    }
  };

  return (
    <div className='flex flex-col gap-6'>
      <div className='flex flex-col gap-2'>
        <div className='flex justify-start gap-2'>
          <Button
            onClick={onDeposit}
            className='inline-block rounded-lg px-3 py-2 text-center hover:no-underline my-0 bg-blue-600 text-white hover:bg-blue-700 mr-0 disabled:bg-gray-200 disabled:text-black disabled:cursor-not-allowed'
          >
            <FontAwesomeIcon icon={faArrowUp} className='mr-1' />
            Deposit
          </Button>

          <Button
            disabled={balance === '0'}
            onClick={onWithdraw}
            className='inline-block rounded-lg px-3 py-2 text-center hover:no-underline my-0 bg-blue-600 text-white hover:bg-blue-700 mr-0 disabled:bg-gray-200 disabled:text-black disabled:cursor-not-allowed'
          >
            <FontAwesomeIcon icon={faArrowDown} className='mr-1' />
            Withdraw
          </Button>
        </div>
      </div>

      <OutputContainer>
        <ContractAddress />
        <p>
          <Label>Escrow Balance:</Label>{' '}
          {balance ? (
            <FormatAmount value={balance} egldLabel={network.egldLabel} />
          ) : (
            'Loading…'
          )}
        </p>
      </OutputContainer>
    </div>
  );
};

The most important thing here is to import the useGetBalance hook and call it inside a use effect. As an argument for this effect, we will pass in a websocket event that gets triggered every time an action happens on the users account on the API.

We gain access to this websocket event from useGetAccountInfo provided by sdk-dapp. The data which comes from getBalance will be saved in a balance state variable.

We will now make use of the balance state to disable the Wihdraw button when there is nothing to withdraw.

The last step here is to display the formatted balance by using the <FormatAmount> component.

Go ahead and test the Deposit and Withdraw flows once again.

Now, you will see that if no balance is present in the Escrow contract for a user, the Withdraw button will be disabled.

🔥 We've come full circle!

We're now able to set new states in a smart contract via transactions on one hand. And we are able to retrieve information back from the smart contract by invoking contract view functions on the other hand.

4. Conclusions

In this tutorial, we've covered quite a bit of ground.

First off, we tackled smart contracts. We got our development environment all set up, grabbed a copy of the contract, compiled it, and then deployed it to the blockchain.

Next, we switched gears to the dApp side of things. We started with the sdk-dapp template and created up a simple yet fully functional dApp. This demonstrates how straightforward it is to develop a dApp that enables users to connect and sign transactions in order to interact with a smart contract.

Keep in mind that all the code is available on GitHub and you can reference it at any time.

Until the next time,
Happy Hacking!

Photo of the crowed at xDays 2023Photo of the crowed at xDays 2023Photo of the crowed at xDays 2023Photo of the crowed at xDays 2023

2,6k

xDay 2023 attendees
Photo of the crowed at xDays 2023Photo of the crowed at xDays 2023Photo of the crowed at xDays 2023Photo of the crowed at xDays 2023Photo of the crowed at xDays 2023
Photo of the crowed at xDays 2023Photo of the crowed at xDays 2023Photo of the crowed at xDays 2023Photo of the crowed at xDays 2023

2,6k

xDay 2023 attendees
Photo of the crowed at xDays 2023Photo of the crowed at xDays 2023Photo of the crowed at xDays 2023Photo of the crowed at xDays 2023Photo of the crowed at xDays 2023
Photo of the crowed at xDays 2023Photo of the crowed at xDays 2023Photo of the crowed at xDays 2023Photo of the crowed at xDays 2023Photo of the crowed at xDays 2023

39k

On Discord and Telegram

Photo of the crowed at xDays 2023

1k+

participants in the xDay 2023 hackathon

Photo of the crowed at xDays 2023Photo of the crowed at xDays 2023
Photo of the crowed at xDays 2023Photo of the crowed at xDays 2023Photo of the crowed at xDays 2023Photo of the crowed at xDays 2023Photo of the crowed at xDays 2023

39k

On Discord and Telegram

Photo of the crowed at xDays 2023

1k+

participants in the xDay 2023 hackathon

Photo of the crowed at xDays 2023Photo of the crowed at xDays 2023

Alone, we can do so little,
together, we can move mountains.