Introduction
In this article, we will explore how to interact with Celo’s native stablecoins, cUSD and cEUR, using Hardhat and a simple smart contract. We will also develop a decentralized application (dApp) to illustrate the principles involved.
Prerequisites
- Basic understanding of blockchain and smart contracts.
- Familiarity with JavaScript and Solidity languages.
- Node.js and npm installed on your development environment.
- Metamask extension installed on your browser for interacting with the Celo network.
- Celo extension wallet installed on your browser.
Setting Up the Celo Development Environment
To start, create a new directory and navigate into it:
mkdir celo-dapp
cd celo-dapp
Next, initiate a new Node.js project:
npm init -y
Then, install Hardhat and other dependencies:
npm init --yes
npm install --save-dev hardhat
npm install --save-dev @nomicfoundation/hardhat-toolbox @nomiclabs/hardhat-waffle ethereum-waffle chai @nomiclabs/hardhat-ethers ethers
npm install express
npm install ejs
Next, create a Hardhat project:
npx hardhat
npm install @openzeppelin/contracts
Select “Create a JavaScript project” when prompted.
Smart Contract Development
We will create a simple contract named StablecoinWallet
that interacts with the cUSD and cEUR stablecoin contracts on Celo.
Create a new file contracts/StablecoinWallet.sol
and add the following:
//SPDX-License-Identifier: MIT
pragma solidity ^0.8.0; // Specifies the compiler version for Solidity
import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; // Imports the IERC20 interface from OpenZeppelin
// This is a contract for a StablecoinWallet that accepts Celo's cUSD and cEUR
contract StablecoinWallet {
// Public address variables for the cUSD and cEUR tokens
address public cUSDAddress;
address public cEURAddress;
// Constructor that sets the addresses of cUSD and cEUR when the contract is deployed
constructor(address _cUSDAddress, address _cEURAddress) {
cUSDAddress = _cUSDAddress;
cEURAddress = _cEURAddress;
}
// Function to deposit either cUSD or cEUR into the wallet
function deposit(address token, uint amount) external {
// Checks if the token is either cUSD or cEUR, else it throws an "Invalid token" error
require(token == cUSDAddress || token == cEURAddress, "Invalid token");
// Attempts to transfer the tokens from the sender to this contract. If the transfer fails, it throws a "Transfer failed" error
require(IERC20(token).transferFrom(msg.sender, address(this), amount), "Transfer failed");
}
// Function to check the balance of either cUSD or cEUR in the wallet
function balanceOf(address token) external view returns (uint) {
// Returns the balance of the specified token in the wallet
return IERC20(token).balanceOf(address(this));
}
}
This contract has two functions: deposit()
and balanceOf()
. The deposit()
function allows users to deposit cUSD or cEUR into the contract. The balanceOf()
function allows anyone to check the balance of cUSD or cEUR that the contract holds.
Compile your contract
npx hardhat compile
Deploying the Smart Contract
Before deploying the contract, we need to set up Hardhat for Celo. In the hardhat.config.js
file, add the following:
require("@nomiclabs/hardhat-waffle");
// Add the alfajores network to the configuration
module.exports = {
solidity: "0.8.17",
networks: {
alfajores: {
url: "https://alfajores-forno.celo-testnet.org",
accounts: {
mnemonic: "taxi expire enforce produce expire second pottery come knee fold slab yard",
path: "m/44'/52752'/0'/0",
},
chainId: 44787,
},
},
};
To deploy the contract, create a new script scripts/deploy.js
:
// Declare an asynchronous function named 'main'.
async function main() {
// Get the account that will deploy the contracts, called the 'deployer'.
const [deployer] = await ethers.getSigners();
// Log the Ethereum address of the deployer account to the console.
console.log(
"Deploying contracts with the account:",
deployer.address
);
// Get the contract factory for the 'StablecoinWallet' contract.
const StablecoinWallet = await ethers.getContractFactory("StablecoinWallet");
// Deploy a new instance of the 'StablecoinWallet' contract, initializing it with the
// addresses of the Celo Dollar and Celo Euro contracts.
const stablecoinWallet = await StablecoinWallet.deploy(
"0xYourCeloDollarContractAddress",
"0xYourCeloEuroContractAddress"
);
// Log the Ethereum address of the newly deployed 'StablecoinWallet' contract to the console.
console.log("StablecoinWallet contract address:", stablecoinWallet.address);
}
// Call the main function. If the promise it returns is fulfilled, then exit the process with a
// status code of 0, indicating success. If the promise is rejected (i.e., an error occurs), then
// log the error to the console and exit the process with a status code of 1, indicating failure.
main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});
You can now deploy your contract with the command:
npx hardhat run scripts/deploy.js --network alfajores
Creating a dApp
Now, we will create a dApp that interacts with our deployed contract and Celo’s cUSD and cEUR. For simplicity, this dApp will display the balance of cUSD and cEUR in our contract.
First, create a new index.ejs
file in the views
directory with the following content:
<!DOCTYPE html>
<html>
<head>
<title>Celo Stablecoin Balances</title>
<style>
body {
font-family: Arial, sans-serif;
background-color: #F3F3F3;
padding: 50px;
color: #333;
}
h1 {
text-align: center;
color: #007BFF;
}
h2 {
text-align: center;
margin-top: 40px;
}
</style>
</head>
<body>
<div class="container">
<h1>Celo Stablecoin Balances</h1>
<p id="cUSDBalance">cUSD Balance: <%= cUSD %></p>
<p id="cEURBalance">cEUR Balance: <%= cEUR %></p>
</div>
</body>
</html>
Next, create a new index.js
file in the project root directory with the following content:
const express = require('express');
const app = express();
const path = require('path');
// Set the view engine to EJS
app.set('view engine', 'ejs');
app.use(express.static(path.join(__dirname, 'public')));
app.get('/', async function(req, res) {
// Retrieve the balances from your contract (replace with your own logic)
const cUSDBalance = '1000 cUSD';
const cEURBalance = '500 cEUR';
// Render the EJS template with the balance data
res.render('index', { cUSD: cUSDBalance, cEUR: cEURBalance });
});
app.listen(3001, function() {
console.log('Server started on port 3001');
});
Finally, in the terminal, navigate to your project directory and run the following command to start the server:
node index.js
Now, you can access your dApp by opening your browser and navigating to http://localhost:3001
.
Remember to replace '0xYourCeloDollarContractAddress'
and '0xYourCeloEuroContractAddress'
with the actual addresses of your deployed cUSD and cEUR contracts.
Conclusion
In this article, we’ve explored how to interact with Celo’s native stablecoins, cUSD and cEUR, within a dApp and a smart contract using Hardhat. We’ve covered setting up a Celo development environment, creating a smart contract that interacts with cUSD and cEUR, deploying the contract, and developing a simple dApp using EJS to display the balances.
About the Author
I’m a Web3 fullstack developer and technical writer. You can connect with me on GitHub, Twitter, Linkedin.