Building an ICP Dapp
This is a step-by-step tutorial on how to build an ICP dapp and connect with Earth Wallet
We are going to create a very simple dapp where users can come and store a value on-chain and get that value by clicking a button. Before starting you can go to https://jlbak-jiaaa-aaaak-aclsa-cai.ic0.app/ to see the live version of the dapp that we are going to create.
Prerequisites
You should have earth wallet installed, if not, download from here
You should have DFX SDK installed on your machine, if not, run this command to install it:
sh -ci "$(curl -fsSL https://smartcontracts.org/install.sh)"
Or follow the detailed instructions here
Developing the dapp
- Start by creating a new project called
hello
, to do this, navigate to the directory in which you want to create the new project and run this command:
dfx new hello
- This will create a new basic project, which is consisted of two cannisters, one for the frontend and one for the backend.
- In your terminal, cd into your project, running:
cd hello
- Next we are going to run this project, for running it, open 2 terminals and in the first terminal run this command:
dfx start
This starts a local version of the ICP blockchain.
- In your second terminal run this command:
dfx deploy
- Copy the link of the local website printed on the terminal and open that link in your browser to see your site running.
Now let's make some changes to the code in order to make it compatible with Earth Wallet and also change the code to store a value in the backend cannister.
Replace the code in
src/hello_backend/main.mo
with:
actor {
stable var value = "";
public func getVal() : async Text {
return value;
};
public func setVal(val : Text) : async Text {
value := val;
return value;
};
};
Here we are writing the motoko code to write a backend cannister where we are making a state variable to store a value on-chain and there are two functions called getVal()
and setVal()
which can be used to get and set the value of that state variable.
- Replace the code in your
src/frontend/src/index.html
with this code:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width" />
<title>hello</title>
<base href="/" />
<link rel="icon" href="favicon.ico" />
<link type="text/css" rel="stylesheet" href="main.css" />
</head>
<body>
<main>
<img src="logo2.svg" alt="DFINITY logo" />
<br />
<br />
<section id="form">
<label for="name">Enter some value to store on-chain: </label>
<input id="name" alt="Name" type="text" />
<div>
<button id="setVal">Set Value</button>
<button id="getVal">Get Value</button>
</div>
</section>
<section id="greeting"></section>
</main>
</body>
</html>
- Replace the code in your
src/hello_frontend/src/index.js
with this code:
import { hello_backend } from "../../declarations/hello_backend";
const setButton = document.getElementById("setVal");
const getButton = document.getElementById("getVal");
setButton.addEventListener("click", async (e) => {
const name = document.getElementById("name").value.toString();
setButton.setAttribute("disabled", true);
// To connect with Earth Wallet if not connected already
await window.earth.connect();
// To call the setVal function of our backend cannister
const response = await window.earth.sign({
canisterId: 'j6grh-iaaaa-aaaak-aclrq-cai',
method: 'setVal',
args: name
});
console.log(response);
setButton.removeAttribute("disabled");
window.alert("Value set โ
")
});
getButton.addEventListener("click", async () => {
getButton.setAttribute("disabled", true);
const greeting = await hello_backend.getVal();
document.getElementById("greeting").innerText = greeting;
getButton.removeAttribute("disabled");
});
The main piece of code here is:
// To connect with Earth Wallet if not connected already
await window.earth.connect();
// To call the setVal function of our backend cannister
const response = await window.earth.sign({
canisterId: 'j6grh-iaaaa-aaaak-aclrq-cai',
method: 'setVal',
args: name
});
So let's break it down:
await window.earth.connect();
Here we are calling the window.earth.connect()
method to connect the user's Earth Wallet with the dapp if it's not already connected.
const response = await window.earth.sign({
canisterId: 'j6grh-iaaaa-aaaak-aclrq-cai',
method: 'setVal',
args: name
});
Here we are calling the window.earth.sign
method to call the setVal
function of our backend cannister, for this we are specifying the function name in method
and giving the arguments to the function in args
field, we also have to provide a cannisterId
for our backend cannister, but since our backend cannister isn't deployed yet, we can put any random id here for now, we will replace it later.
- Replace the code in your
src/hello_frontend/assets/main.css
with this code:
body {
font-family: sans-serif;
font-size: 1.5rem;
}
img {
max-width: 50vw;
max-height: 25vw;
display: block;
margin: auto;
}
#form {
display: flex;
justify-content: center;
gap: 0.5em;
flex-flow: row wrap;
max-width: 40vw;
margin: auto;
align-items: baseline;
}
button {
padding: 5px 20px;
margin: 10px auto;
float: right;
margin: 2rem;
}
#greeting {
margin: 10px auto;
padding: 10px 60px;
border: 1px solid #222;
}
#greeting:empty {
display: none;
}
Here we are just adding some styles to make our components not look ugly.
- At this point we are ready to deploy the dapp, so let's go ahead and do that.
Deploying the dapp
- Open up a new terminal in your
hello
directory and run this command:
dfx ledger account-id
- This will print your account id, transfer some ICP tokens to this account id ($3-$5 worth should be enough), which we will use to convert to cycles, in order to deploy our dapp.
tip
ICP tokens can be used to convert into cycles, cycles can be used to deploy and keep running the cannisters on chain.
- After that, run this command:
dfx identity get-principal
This will give you your principal id, which we will use to create a cycles wallet.
- Now run this command:
dfx ledger --network ic create-canister <your-principal-identifier> --amount <icp-tokens>
Replace the <your-principal-identifier>
with your principal id and <icp-tokens>
with the amount of tokens that you want to use in order to convert to cycles and deploy the dapp. For example I ran:
dfx ledger --network ic create-canister kuhyk-5lsc5-pfyme-zum5h-ltkn3-bnnqg-vk61x-acvt3-mvetn-h274t-ug --amount 0.5
This command will take some time and output the cannister id of the newly created cannister.
- Now that the canister is created, you can install the wallet code using this command:
dfx identity --network ic deploy-wallet <canister-identifer>
Here, you have to substitute the canister identifier using the id you received in the output of the previous command. So, in the example this would look like this:
dfx identity --network ic deploy-wallet jzhxt-fyaaa-aaaak-aclra-cai
- To see the cycles balance of your wallet run this command:
dfx wallet --network ic balance
This will print the number of cycles your wallet has, which were converted from the ICP tokens.
- Now let's deploy the cannisters by running this command:
dfx deploy --network ic --with-cycles 1000000000000
Here we are deploying our cannisters using 1000000000000
cycles, you can change this number if you want.
This will deploy both the cannisters and print your dapp's live url on the terminal which you can open in the browser to see your app running live.
The deployment command creates a new file called
cannister_ids.json
in the root of your project, copy the id ofhello_backend
from there and replace withcannisterId
in thesrc/hello_frontend/src/index.js
file, this ensures that your frontend cannister is only calling your backend cannister.Now, since we have made the changes, we have to update our dapp as well by running this command:
dfx deploy --network ic
This will update our dapp and it's live to share with the world.
Our version of the live dapp is here: https://jlbak-jiaaa-aaaak-aclsa-cai.ic0.app/
This is a very simple dapp where anyone in the world can set the value of a variable stored in the smart contract using Earth Wallet to anything and anyone in the world can also get that value at any time.
Anything unclear or issue in this docs? Please connect at Discord!