Introduction
Decentralized applications, or dApps, have taken the blockchain industry by storm, offering users decentralized, permissionless, and trustless services in a variety of areas, from finance to gaming and beyond. One key to building successful dApps is the use of oracle services, which bring external data into the blockchain environment. In this tutorial, we will explore how to integrate Celo oracles into your dApp to obtain current and historical price data for various assets.
Prerequisites
A proper understanding of Solidity and JavaScript is needed for this tutorial.
Before you continue, read this article for basic understanding on Celo Consensus Mechanism
Requirements
Before you get started, you’ll need the following:
- Node.js and Node Package Manager(NPM) installed on your machine.
- A Celo wallet with testnet faucets. Create your Celo wallet here and get your faucet here.
Step 1: Setting up our Integrated Development Environment
Firstly, you’ll use Hardhat as your development environment for Solidity contracts, you need to have it installed in your project.
Here’s how you can install Hardhat:
-
First, make sure you have Node.js (version 10.16.0 or later) and npm installed on your machine. You can download Node.js from the official website, and it comes with npm (node package manager) included.
-
Create a new directory for your project, navigate to it via the terminal, and then initialize a new Node.js project by running:
mkdir CeloOracle
cd CeloOracle
npm init -y
The -y
flag is used to accept all default configurations.
- After initializing your Node.js project, you can install Hardhat by running:
npm install --save-dev hardhat
This command will install Hardhat as a development dependency in your project.
- To create a new Hardhat project, run:
npx hardhat
You will be prompted to create a sample project. You will choose Create a JavaScript Project
, then for your add .gitignore
, choose y
.
- Then install ethers.js
npm install --save ethers
- We configure our
hardhat.config.js
to the Celo network. Hardhat’s configuration is done through thehardhat.config.js
file located in the root directory of your project.
// Include the Hardhat-Celo plugin which provides tasks, helpers and tools to work with the Celo network.
require("hardhat-celo");
networks: {
// Define the alfajores test network settings.
alfajores: {
// The URL of the RPC provider.
// Replace this with the RPC URL of your choice if not using the public alfajores testnet node.
url: "https://alfajores-forno.celo-testnet.org",
// List of accounts that hardhat will use.
// Replace "<YOUR_PRIVATE_KEY>" with your actual private key.
accounts: [
"<YOUR_PRIVATE_KEY>"
],
},
// Define the main Celo network settings.
celo: {
// The URL of the RPC provider.
// Replace this with the RPC URL of your choice if not using the public mainnet node.
url: "https://forno.celo.org",
// List of accounts that hardhat will use.
// Replace "<YOUR_PRIVATE_KEY>" with your actual private key.
accounts: [
"<YOUR_PRIVATE_KEY>"
],
}
},
// Define the etherscan configuration.
etherscan: {
// The API keys for etherscan (block explorer).
// Replace "<CELOSCAN_API_KEY>" with your actual API keys for each network.
apiKey: {
alfajores: "<CELOSCAN_API_KEY>",
celo: "<CELOSCAN_API_KEY>"
},
},
In this configuration:
Make sure to replace <YOUR_PRIVATE_KEY>
with your actual private key, or better yet, use an .env
file to keep your private key secure. Never expose your private key in your code or version control system.
Now, you should have a fully functional Hardhat setup ready to go!
Step 2: Setting up our Smart Contract
Then we’ll be creating a Solidity smart contract called CeloOracle.sol
in your contracts
folder. This interacts with the Celo oracle. In this contract, we’ll define a function to fetch the price of a particular asset from the oracle.
// SPDX-License-Identifier: MIT
// Specifies the license for the source code, which is MIT
pragma solidity ^0.8.0;
// This indicates the version of Solidity that the contract is written in.
import "@openzeppelin/contracts/access/Ownable.sol";
// This imports the Ownable contract from the OpenZeppelin library, which provides basic authorization control functions.
import "@celo/contractkit/contracts/stabletoken/IStableToken.sol";
// This imports the IStableToken interface from the Celo ContractKit library, which describes the functions of the Celo stable tokens.
contract CeloOracle is Ownable {
// Defines a new contract named CeloOracle, which inherits the functionality of the Ownable contract.
address public stableTokenAddress;
// This public variable stores the Ethereum address of the stable token.
constructor(address _stableTokenAddress) {
// The constructor function is called once when the contract is deployed.
// It takes the address of the stable token as an argument.
stableTokenAddress = _stableTokenAddress;
// Sets the stable token's address to the passed-in address.
}
function getAssetPrice() public view returns (uint256) {
// This is a public function that returns the price of the asset.
IStableToken stableToken = IStableToken(stableTokenAddress);
// Creates an instance of IStableToken for the stored stable token address.
return stableToken.inflationFactor();
// Returns the inflation factor of the stable token, which can be used as an indicator of its price.
}
}
This contract imports the OpenZeppelin Ownable
contract, which provides basic authorization control functions, and the Celo IStableToken
interface, which contains the inflationFactor
function that returns the current price of the token.
Step 3: Writing the JavaScript Code
Next, we’ll write the JavaScript code in a file called index.js
in your test
folder that interacts with our smart contract.
// Import the ethers.js library, a complete Ethereum wallet implementation and utilities in JavaScript and TypeScript.
const ethers = require("ethers");
// Import the ABI (Application Binary Interface) from the local CeloOracle contract artifact
// ABI is a JSON object that describes how to interact with the contract, including all of its functions and their parameters.
const contractABI = require("./artifacts/contracts/CeloOracle.sol/CeloOracle.json").abi;
// Set up the connection to the Celo testnet (Alfajores) using a JSON-RPC provider
// The provider allows us to interact and communicate with the blockchain.
const provider = new ethers.providers.JsonRpcProvider("https://alfajores-forno.celo-testnet.org");
// Set the contract address that we are going to interact with
// You should replace the "0x..." with your own contract's address.
const contractAddress = "0x..."; // Replace with your contract address
// Define an async function that fetches the asset price from the contract
async function fetchPrice() {
// Create a random wallet and connect it to the provider
// In a real-world situation, you would likely use a specific wallet instead of a random one.
const wallet = ethers.Wallet.createRandom().connect(provider);
// Instantiate a new ethers.js contract object that we can use to interact with our contract
// The contract object needs the contract address, the contract ABI, and a connected wallet to work.
const contract = new ethers.Contract(contractAddress, contractABI, wallet);
// Try to call the getAssetPrice function of the contract
try {
// Call the getAssetPrice() function of the contract, which is an async operation
const price = await contract.getAssetPrice();
// Log the received price to the console
console.log(`Price: ${price}`);
} catch (err) {
// If there was an error calling the function or processing the result, log the error to the console
console.error(err);
}
}
// Call the fetchPrice function to start the process
fetchPrice();
This code initializes a web3 instance with the Celo network configuration, retrieves the accounts from the provider, and then calls the fetchPrice
function from the smart contract.
Step 4: Deploying the Contract
We’ll write a deployment scripts for our contracts now.
First, install the Hardhat ethers plugin:
npm install --save-dev @nomiclabs/hardhat-ethers 'ethers@^5.0.0'
//You can check the latest command [here](https://hardhat.org/hardhat-runner/plugins/nomiclabs-hardhat-ethers)
Next, create a deployment deploy.js
file in the scripts directory:
// Define an async function named main
async function main() {
// Get the list of signers (accounts) from the ethers object and take the first one as the deployer
const [deployer] = await ethers.getSigners();
// Log the deployer's address
console.log(`Deploying contracts with the account: ${deployer.address}`);
// Get the contract factory for the CeloOracle contract
const CeloOracle = await ethers.getContractFactory("CeloOracle");
// Define the address for the stable token
const stableTokenAddress = "0x..."; // Replace with your stable token address
// Deploy the CeloOracle contract with the stable token address as a parameter
const oracle = await CeloOracle.deploy(stableTokenAddress);
// Log the address of the newly deployed CeloOracle contract
console.log(`CeloOracle contract address: ${oracle.address}`);
}
// Invoke the main function
main()
// If the main function executes successfully, then exit the process with status code 0 (no error)
.then(() => process.exit(0))
// If an error is caught during the execution of the main function, log the error and exit the process with status code 1 (indicates an error)
.catch((error) => {
console.error(error);
process.exit(1);
});
To deploy your contract using Hardhat, use the following command:
npx hardhat run --network celo scripts/deploy.js
Step 5: Testing the Contract
After deploying, you can run your JavaScript code to test the contract:
node script.js
If everything is set up correctly, you should see the current price of the asset printed to the console.
Conclusion
This tutorial provided a high-level overview of how to integrate Celo oracles into your dApp to fetch current and historical price data for various assets. This knowledge is a powerful tool for developers building dApps on the Celo platform, especially those dealing with financial transactions.
Remember that the use of oracles comes with trust assumptions, so it’s important to understand the risks and potential failure modes associated with using an oracle service. Always ensure that your dApp has mechanisms in place to handle potential oracle failures or manipulations.
Next Steps
By harnessing the power of Celo oracles, you can build more dynamic and responsive dApps with our tutorials here.
About the Author
Joshua Obafemi
I’m a Web3 Fullstack Developer and Technical Writer. You can connect with me on GitHub, Twitter, Linkedin.