DEV Community

Gabriel Temsten
Gabriel Temsten

Posted on

Building a Basic Smart Contract and dApp on Celo

Writing a smart contract can be a daunting task for beginners, but with the right tools and guidance, it can be made simple. In this tutorial, we will walk through how to write a basic smart contract, deploy it on the Celo testnet, and create a decentralized application (dApp) to interact with the contract.

Image description

Prerequisites:

Basic programming concepts and JavaScript proficiency
Node.js installed on your computer
Command line experience
Celo wallet set up to obtain testnet tokens
Familiarity with Vue.js and TypeScript

Step 1: Initialize and Install Hardhat Project

The first step is to initialize and install a Hardhat project, which is a development environment for building and testing smart contracts on Ethereum and other EVM-compatible blockchains. To do this, you can follow the official Hardhat documentation to install it on your machine.

Step 2: Write the Smart Contract Once you have set up your Hardhat project, you can start writing your smart contract. For this tutorial, we will create a basic contract called "HelloWorld.sol" that has two public variables: text and name. The user can set the text they want and also set their name on the contract.

To create the contract, you can create a new file called "HelloWorld.sol" in the contracts folder of your Hardhat project and add the following code:

pragma solidity ^0.8.0;

contract HelloWorld {
  string public text;
  string public name;

  function setText(string memory _text) public {
    text = _text;
  }

  function setName(string memory _name) public {
    name = _name;
  }
}
Enter fullscreen mode Exit fullscreen mode

This contract has two public variables, text and name, and two public functions, setText and setName, which allow the user to set the values of the variables.

Step 3: Deploy the Contract on the Celo Testnet

After writing the smart contract, the next step is to deploy it on the Celo testnet. To do this, you can follow the official Celo documentation to set up your Celo account and connect it to your Hardhat project:
hardhatconfig.ts

mport { HardhatUserConfig } from "hardhat/config";
import "@nomicfoundation/hardhat-toolbox";
import * as dotenv from 'dotenv'
dotenv.config();

const PRIVATE_KEY = process.env.PRIVATE_KEY

const config: HardhatUserConfig = {
  solidity: "0.8.17",
  networks:{
    alfajores: {
      url: `https://alfajores-forno.celo-testnet.org`,
      accounts: [PRIVATE_KEY]
    },
  }
};

export default config;

Enter fullscreen mode Exit fullscreen mode

Once you have connected your Celo account to your Hardhat project, you can deploy your contract to the Alfojores Celo network by running the following command in your terminal:

npx hardhat run --network alfajores scripts/deploy.js
Enter fullscreen mode Exit fullscreen mode

This command will deploy your contract to the Alfojores Celo network and give you the address of the deployed contract(make sure to copy and save the contract address).

**Step 4: **Create the dApp for Contract Interaction

The final step is to create a dApp for interacting with the deployed smart contract. For this tutorial, we will create a simple dApp using Vue 3 and TypeScript.

To create the dApp, you can follow the official Vue documentation to set up a new Vue project with TypeScript.
Use this command to Quick start a Vue 3 Appliction

npm create vue@latest
//select Typescript--useRouter--piniaStore--Eslint--Preetier
npm install bootstrap ethers
Enter fullscreen mode Exit fullscreen mode

Check the source code here

After Initiating you Vue application, navigate to src/main.ts to add bootstrap

import 'bootstrap/dist/css/bootstrap.min.css'

Enter fullscreen mode Exit fullscreen mode

*Step 5: * Navigate to src/views/HomeView.vue replace the code below:

<script setup lang="ts">
import { onMounted, reactive, ref, watchEffect } from 'vue'
import { ethers } from 'ethers'
import * as HelloWorldABI from '../contract/HelloWorld.json'

const { ethereum } = window
const contractAddress = '0xF112ba99A3586Ac4b5dA4148c0a03ADD2C6BFA0a'

const setAddress = reactive({
  address: ''
})
const setTexts = reactive({
  setText: '',
  setName: ''
})

const getTexts = reactive({
  getText: '',
  getName: ''
})

const networName = reactive({
  network: ''
})

let connected = ref(false)
onMounted(() => {
  if (!ethereum) {
    connected.value = false
    alert('Make sure you have MetaMask Connected!')
    return
  }
  watchEffect(() => {
    getCurrentText()
    getCurrentName()
  })
})

//connect your Metamask wallet on connect button click
const connectWallet = async () => {
  try {
    // Check if MetaMask is installed
    if (!ethereum) {
      alert('Make sure you have MetaMask Connected!')
      return
    }

    // Get user Metamask Ethereum wallet address
    const accounts = await ethereum.request({
      method: 'eth_requestAccounts'
    })
    setAddress.address = accounts[0]
    const provider = new ethers.providers.Web3Provider(ethereum)
    const network = await provider.getNetwork()
    networName.network = network.name

    console.log(accounts[0])
    connected.value = true
  } catch (error) {
    console.log(error)
  }
}
const getCurrentText = async () => {
  try {
    // Check if User already connected a wallet
    if (ethereum) {
      const provider = new ethers.providers.Web3Provider(ethereum)
      const signer = provider.getSigner()

      // Create a contract object
      const contractInstance = new ethers.Contract(contractAddress, HelloWorldABI.abi, signer)

      let text = await contractInstance.getText()
      getTexts.getText = text
    }
  } catch (error) {
    console.log(error)
  }
}
const getCurrentName = async () => {
  try {
    // Check if User already connected a wallet
    if (ethereum) {
      const provider = new ethers.providers.Web3Provider(ethereum)
      const signer = provider.getSigner()
      // Create a contract object
      const contractInstance = new ethers.Contract(contractAddress, HelloWorldABI.abi, signer)
      let text = await contractInstance.getName()
      getTexts.getName = text
    }
  } catch (error) {
    console.log(error)
  }
}
const setText = async () => {
  try {
    // Check if User already connected a wallet
    if (ethereum) {
      const provider = new ethers.providers.Web3Provider(ethereum)
      const signer = provider.getSigner()

      // Create a contract object
      const contractInstance = new ethers.Contract(contractAddress, HelloWorldABI.abi, signer)

      const setText = await contractInstance.setText(setTexts.setText)
      // Wait for the transaction to be mined
      await setText.wait()
      watchEffect(() => {
        getCurrentText()
        getCurrentName()
      })
      // Display a success message to the user
      alert('Txn successful!')
    }
  } catch (error) {
    console.log(error)
  }
}

const setName = async () => {
  try {
    // Check if User already connected a wallet
    if (ethereum) {
      const provider = new ethers.providers.Web3Provider(ethereum)
      const signer = provider.getSigner()

      // Create a contract object
      const contractInstance = new ethers.Contract(contractAddress, HelloWorldABI.abi, signer)

      const setName = await contractInstance.setName(setTexts.setName)
      // Wait for the transaction to be mined
      await setName.wait()
      // Display a success message to the user
      alert('Txn successful!')
      watchEffect(() => {
        getCurrentText()
        getCurrentName()
      })
    }
  } catch (error) {
    console.log(error)
  }
}
</script>

<template>
  <main>
    <div class="text-center">
      <img class="text-center img-thumbnail" src="../assets/celo.png" alt="" />
    </div>
    <div class="text-success textcenter">
      NetWork: <strong>{{ networName.network }}</strong>
    </div>
    <nav class="navbar navbar-expand-lg navbar-dark bg-dark mt-5 pt-1 mb-1">
      <div class="container-fluid">
        <a class="navbar-brand" href="#"></a>
        <button
          class="navbar-toggler"
          type="button"
          data-bs-toggle="collapse"
          data-bs-target="#navbarSupportedContent"
          aria-controls="navbarSupportedContent"
          aria-expanded="false"
          aria-label="Toggle navigation"
        >
          <span class="navbar-toggler-icon"></span>
        </button>
        <div class="collapse navbar-collapse" id="navbarSupportedContent">
          <ul class="navbar-nav me-auto mb-2 mb-lg-0">
            <li class="nav-item">
              <a class="nav-link active" aria-current="page" href="#">Home</a>
            </li>
          </ul>

          <button v-if="!connected" @click="connectWallet" class="btn btn-outline-success">
            Connect Wallet
          </button>
          <button v-else class="btn btn-success">{{ setAddress.address }}</button>
        </div>
      </div>
    </nav>
  </main>

  <div class="py-5">
    <div class="card">
      <label class="card-body" for="">BLOCKCHAIN-DATA: </label>
      <p class="card-body">
        Current Text: <strong> {{ getTexts.getText }}</strong>
      </p>
      <p class="card-body">
        Current Name: <strong> {{ getTexts.getName }}</strong>
      </p>
    </div>

    <div class="py-5 global-container">
      <div class="card login-form">
        <div class="card-body">
          <h3 class="card-title text-center">Contract Interaction</h3>
          <div class="card-text">
            <form>
              <div class="form-group">
                <label for="exampleInputEmail1">Set Text</label>
                <input
                  v-model="setTexts.setText"
                  type="text"
                  class="form-control form-control-sm"
                />
                <button @click.prevent="setText" class="btn btn-dark py-1 mt-2">Call</button>
              </div>
              <div class="form-group">
                <label for="exampleInputEmail1">Set Name</label>
                <input
                  v-model="setTexts.setName"
                  type="text"
                  class="form-control form-control-sm"
                />
                <button @click.prevent="setName" class="btn btn-dark py-1 mt-2">Call</button>
              </div>
            </form>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>
<style>
.global-container {
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  background-color: #141313;
}

.form {
  padding-top: 10px;
  font-size: 14px;
  margin-top: 30px;
}

.card-title {
  font-weight: 300;
}

.login-form {
  width: 330px;
  margin: 20px;
}

.sign-up {
  text-align: center;
  padding: 20px 0 0;
}

.alert {
  margin-bottom: -30px;
  font-size: 13px;
  margin-top: 20px;
}
</style>

Enter fullscreen mode Exit fullscreen mode

Edit the styling to the best of your Taste(src/assets/main.ts).
*Note: *
Change the smartContract address in src/views/HomeView.vue with the contract address you got from deploying the HelloWorld contract.

Copy the contract ABI(helloWorld.json) from the contract folder in step 1. then create a folder in your vue application(src/) named contract then paste the json file so you can import it to HomeView.vue for contract interaction.

*Summary of the functions: *
OnMounted() : To initiate a function call when the page reloads.
connectWallet(): To connect a user, using MetaMask.
getCurrentText(): To get the updated text from the contract we deployed earlier.
getCurrentName(): To get the name from the contract earlier deployed.
setText(): To send the transaction to the blockchain(celo Alfajores network) containing the text gotten from the form as an argument.
setName(): To send the transaction to the blockchain(celo Alfajores network) containig the name gotten from the form as an argument.
View the live project here

Top comments (0)