Linking Your Smart Contracts to Frontend Applications — Blockchain

Ali Murtaza Memon
Coinmonks
Published in
11 min readFeb 11, 2023

--

“Bridging the Gap between Blockchain and User Interfaces”

Cover Image

Decentralized applications (dApps) can be made more user-friendly and accessible by combining the functionality of Frontend applications with smart contracts. A smart contract holds the logic and regulations of a dApp on the blockchain, but it lacks the capability of direct interaction with users. The frontend application serves as the interface between the user and the smart contract, enabling them to carry out transactions and view information stored on the blockchain.

The integration of frontend and smart contracts results in a comprehensive dApp that can be utilized by a broad user base. The Frontend enhances the overall user experience and makes complex transactions simpler for users to understand. This can drive the adoption and utilization of dApps and create robust, user-friendly decentralized solutions.

In this article, we will explore the process of connecting a SimpleStorage smart contract, previously discussed in my other articles, to a Frontend ReactJS Application. We will demonstrate how to read and write data to the smart contract using the Frontend for a seamless user experience.

If you have not yet reviewed my previous articles, then you can read them by visiting these links:

  1. Learn to Deploy Smart Contracts more Professionally with Hardhat
  2. Verify your Smart Contracts on Etherscan Programmatically using Hardhat

Setup Frontend Project

Create your React JavaScript or TypeScript projects using these commands;

JavaScript

npx create-react-app my-app
cd my-app
npm start

TypeScript

npx create-react-app my-app --template typescript
cd my-app
npm start

NOTE: Make sure to rename my-app with your project name. I am going to name it simple-storage-frontend .

It is necessary to clear out any previously established styling and components. This can be done by removing or updating the current code within the files index.css, App.css, and either App.jsx or App.tsx . The specific steps to accomplish this will depend on the framework and programming language being used. However, the ultimate goal is to create a clean slate for new styling and components to be added or modified as needed.

After removing your App.jsx or App.tsx file should look like this;

import "./App.css";

function App() {
return <div>Hello World</div>;
}

export default App;

Get the ABI and Smart Contract Address

In order to effectively interact with the data stored in the smart contract, it is necessary to obtain the Application Binary Interface (ABI) and the address of the deployed smart contract. These two components are critical for facilitating read and write operations on the smart contract.

NOTE: I am leaving this to you to figure out why we need these two things(if you do not know).

To set up the necessary infrastructure, create a constants folder within the src folder. Then, obtain the SimpleStorage.json file from the path artifacts/contracts/SimpleStorage.sol in your Hardhat SimpleStorage project. Finally, create a contractAddresses.json file. (It should be noted that if a different smart contract is being utilized, the steps outlined here can still be followed).

ABI and Contract Address JSON files

Having obtained the Application Binary Interface (ABI) of the smart contract, the next step is to retrieve the deployed address of the smart contract.

For further information on acquiring the deployed address of the smart contract, it is recommended to refer to a previously written comprehensive article on deploying smart contracts. The relevant link has been provided above.

Once the deployed address of the smart contract has been obtained, it should be inserted into the contractAddresses.json file as follows:

{"31337": ["0x5FbDB2315678afecb367f032d93F642f64180aa3"]}

By storing the smart contract addresses in relation to the specific blockchain network Id it has been deployed on, we can dynamically retrieve the appropriate address for the frontend, based on the network selected by the user in MetaMask.

Interacting with the Smart Contract using ReactJS

To interact with the smart contract, we will use the react-moralis npm package offered by Moralis. react-moralis is a convenient React wrapper for Moralis features, enabling straightforward read and write operations on the data stored in the smart contract.

To install it run these commands based on the package manager you are using;

npm install moralis-v1 react-moralis

or

yarn add moralis-v1 react-moralis

After successfully installing the package, it is necessary to wrap the App component with the Moralis Provider in order to gain access to the React Hooks offered by the react-moralis package.

Import this inside index.jsxor index.tsxfile;

import { MoralisProvider } from "react-moralis";

And wrap the App;

<MoralisProvider initializeOnMount={false}>
<App />
</MoralisProvider>

It is essential to include the initializeOnMount=false attribute since it tells the Moralis package that using a server is not preferred. In the event that this is not done, a serverUrl and appId must be given.

Having completed the aforementioned steps, all of the hooks provided by the react-moralis package can now be utilized throughout the App.jsx or App.tsx component and its associated child components.

Prior to utilizing web3 features, it is important to verify their availability. This requires the presence and activation of web3-enabled wallet extensions, such as MetaMask or WalletConnect, within the browser. This allows the frontend applications to perform web3 calls and interact with the blockchain.

To determine web3 availability, the useMoralis hook from the react-moralis the package provides a variable called isWeb3Enabled. This variable can be used for verification purposes.

In the App.jsxor App.tsx import the useMoralis hook;

import { useMoralis } from "react-moralis";

And get the isWeb3Enabled variable inside the App component;

const { isWeb3Enabled } = useMoralis();

In the return value of the App component, add this condition;

return isWeb3Enabled ? <div>Hello Wrold</div> : <h1>Make Sure MetaMask is installed and Account is Connected.</h1>;

Connect Frontend with Wallet

The react-moralis package also provides an enableWeb3 function for enabling web3 in the frontend, however, the process is relatively lengthy. An alternative solution is to utilize the Web3 UI package, which provides lightweight and reusable web3 components for decentralized applications. One of the components within this package, the ConnectButton can be used to seamlessly connect the frontend to the user’s wallet.

Install the web3uikit package by running this command;

npm install @web3uikit/core @web3uikit/web3 @web3uikit/icons

or

yarn add  @web3uikit/core @web3uikit/web3 @web3uikit/icons

Import this inside App.jsx or App.tsx file;

import { ConnectButton } from "@web3uikit/web3";

And return the div containing the ConnectButton ;

return (
<div>
<ConnectButton />
{isWeb3Enabled ? (
<div>Hello Wrold</div>
) : (
<h1>
Make Sure MetaMask is installed and Account is Connected.
</h1>
)}
</div>
);

The user interface will look like this after the ConnectButton component is added.

UI with ConnectButton

With the addition of the ConnectButton, your user interface will display a button for connecting to web3-enabled wallets. Upon clicking the connect button, a pop-up window will appear presenting various wallet options. Selecting the MetaMask option will open the MetaMask wallet and prompt the user to grant permission to connect their wallet to the website.

Wallets Popup
Account Option
Connect Permission
Connected Button

It is recommended to create a React useEffect hook that rerenders the user interface every time the state of the isWeb3Enabled variable updates. This will ensure that the updated information regarding the web3 connection status is reflected in the user interface.

useEffect(() => {}, [isWeb3Enabled]);

In addition to this, to ensure compatibility with the selected chain, it’s important to maintain a list of supported chain IDs. The useMoralis hook provides access to the chainId variable, which can be used to determine if the selected chain is supported by the dApp. If the selected chain is not supported, the dApp can display a message to the user, informing them of the incompatibility.

To do this first get the chainId variable from the useMoralis ;

// JavaScript
const { isWeb3Enabled, chainId: chainIdHex } = useMoralis();
const chainId = parseInt(chainIdHex).toString();

// TypeScript
const { isWeb3Enabled, chainId: chainIdHex } = useMoralis();
const chainId: string = parseInt(chainIdHex!).toString();

Since the useMoralis hook returns the chain IDs in hexadecimal form, we are converting it to the first integer representation and then converting it back to its string representation.

After this create a list of supported chain IDs above the App.jsx or App.tsx component; Here for the example, we are only using the Hardhat chain.

// JavaScript
const supportedChains = ["31337"];

// TypeScript
const supportedChains: string[] = ["31337"];

Add the condition when the isWeb3Enabled;

return (
<div>
<ConnectButton />
{isWeb3Enabled ? (
supportedChains.includes(chainId) ? (
<div>Hello World</div>
) : (
<p>{`Please switch to a supported chainId. The supported Chain Ids are: ${supportedChains}`}</p>
)
) : (
<h1>
Make Sure MetaMask is installed and Account is Connected.
</h1>
)}
</div>
);

It’s worth mentioning that instead of displaying a message to the user regarding supported chains, many dApps opt to use a “Switch Chain” button. This button prompts MetaMask to open and, with the user’s permission, switch to a different chain. This topic will be covered in a separate article, as it expands beyond the scope of this one. Keep an eye out for it!

Read the Smart Contract Data

To retrieve the data from the smart contract, we will utilize the useWeb3Contract hook provided by the react-moralis package.

const { runContractFunction: get } = useWeb3Contract({
abi: SimpleStorage.abi,
contractAddress: simpleStorageContractAddress!,
functionName: "get",
params: {},
});

The useWeb3Contract hook takes the abi, contractAddress, functionName, and optional params and msgValue as key/values, and returns the runContractFunction, which can be renamed to match the name of the smart contract's function (such as we renamed it to get).

Import the ABI and Contract Addresses to resolve the values errors;

import contractAddresses from "./constants/contractAddresses.json";
import SimpleStorage from "./constants/SimpleStorage.json";

Obtain the initial contract address from the contractAddresses list based on the chainId.

// JavaScript
const simpleStorageContractAddress =
chainId in contractAddresses
? contractAddresses[chainId][0]
: null;

// TypeScript
// add this above the App component
interface contractsAddressesInterface {
[key: string]: string[];
}

// add these inside the App component
const simpleStorageAddresses: contractsAddressesInterface =
contractAddresses;

const simpleStorageContractAddress =
chainId in simpleStorageAddresses
? simpleStorageAddresses[chainId][0]
: null;

To persist the data retrieved from the smart contract, we will create a React useEffect hook that will store the value returned by the get function of the smart contract.

Inside the App component create this;

// JavaScript
const [data, setData] = useState("");

// TypeScript
const [data, setData] = useState<string>("");

Within the App component, create the updateUI function, which will retrieve the value from the smart contract’s get function and utilize the setData function to set the data.

async function updateUI() {
const data = (await get()) as string;
if (data != undefined) {
setData(data.toString());
}
}

Invoke the updateUI function within the previously established useEffect hook that was created for the isWeb3Enabled state. This will trigger the function as soon as web3 has been enabled, allowing the retrieval and display of data to the user.

useEffect(() => {
updateUI();
}, [isWeb3Enabled]);

Finally, replace the Hello World text container with a Heading 1 element that displays the data retrieved from the smart contract.

supportedChains.includes(chainId) ? (
<h1>{data}</h1>
) : (
<p>{`Please switch to a supported chainId. The supported Chain Ids are: ${supportedChains}`}</p>
)

Finally, the value returned by the get function will be displayed. As the get function returns a uint256 value, the default 0 value returned from the get function will be displayed.

Default Value Returned by get Function

Write to Smart Contract

With the ABI and smart contract address already established, invoking other functions within the smart contract is as straightforward as calling functions in other programming languages.

The set function, another function of our smart contract, accepts a numerical argument and stores it in the storedData variable.

function set(uint x) public {
storedData = x;
}

In order to provide users with the ability to input a value they wish to store through the set function, we will create an input field and a corresponding button.

To start, we will set up a state hook to store the value that the user will enter into the input field.

// JavaScript
const [input, setInput] = useState("");

// TypeScript
const [input, setInput] = useState<string>("");

Replace the heading 1 element that displays the data fetched from the smart contract with a div element that contains both the input and button elements.

<div>
<h1>{data}</h1>
<br />
<input
type="text"
value={input}
onChange={(event) => setInput(event.target.value)}
/>
<br />
<button onClick={handleStoreOnClick}>Store</button>
</div>

With the input field and button in place, we can proceed to invoke the set function within the handleStoreOnClick function. However, prior to that, we must first declare the runContractFunction for the set function.

const { runContractFunction: set } = useWeb3Contract({
abi: SimpleStorage.abi,
contractAddress: simpleStorageContractAddress!,
functionName: "set",
params: {
x: input,
},
});

It should be noted that the set function requires a parameter named x. Therefore, we pass a key with the same name x and its corresponding value input by the user.

Now we can call the set function inside the handleStoreOnClick function.

async function handleStoreOnClick() {
await set({
onSuccess: (tx) => handleSetOnSuccess(tx),
onError: (error) => console.log(error)
});
}

async function handleSetOnSuccess(tx: any) {
await tx.wait(1);

updateUI();
setInput("");
}

The renamed runContractFunction, set, provides two functions, onSuccess and onError, to handle the outcome of the call to the smart contract function. Depending on the outcome, either a successful or an error message can be displayed to the user.

Update Result

That’s it, by implementing the steps outlined above, we have successfully provided the user with a simple interface to input and store a value on the smart contract. Upon pressing the store button, MetaMask will prompt the user for permission, and once confirmed, the latest value will be updated on the smart contract and retrieved in the updateUI function, to be displayed to the user.

Note: Error handling in the event of exceptions thrown by the smart contract was not demonstrated in this article. However, this topic will be covered in a future piece, as this article has already reached a substantial length.

Conclusion

In conclusion, linking smart contracts with frontend applications is an essential step to bring your decentralized application to life. It allows users to interact with your smart contracts and perform various actions such as fetching data and executing functions. In this article, we have demonstrated how to connect a smart contract with a frontend application using react-moralis and React.js. We have covered the basics of react-moralis and showed how to get started with the integration process. Additionally, we have discussed the different steps involved in linking a smart contract with a frontend application, including setting up a React project, connecting to a blockchain network, and calling smart contract functions. With the steps outlined in this article, you should now have a good understanding of how to integrate your smart contracts with frontend applications.

This comprehensive guide aims to provide assistance to developers of all skill levels and is intended to be beneficial for a wide range of developers. Despite its length, the information provided is intended to be easily understandable and actionable for all readers.

If you found this content helpful, please consider giving it a clap and leaving any feedback for future improvements. Your suggestions and comments are greatly appreciated and will help make these articles even more valuable for you and other readers.

Be sure to follow me to receive updates on my future articles and stay informed of new content.

Thank you

Ali Murtaza Memon

New to trading? Try crypto trading bots or copy trading on best crypto exchanges

--

--