Introduction
In this article, we will explore the process of building a tokenized farmer’s market on the Celo blockchain. We will leverage JavaScript, Hardhat, and Solidity to develop a smart contract that enables the creation and management of a decentralized marketplace for farmers to sell their produce. The currency used within this marketplace will be cUSD, which is the stablecoin on the Celo network.
Prerequisites:
Before we begin, make sure you have the following installed:
- Node.js: https://nodejs.org
- Hardhat: https://hardhat.org
Setting Up the Project:
To start, let’s set up our project by creating a new directory and initializing a new Node.js project. Open your terminal and execute the following commands:
mkdir tokenized-farmers-market
cd tokenized-farmers-market
code .
npm init -y
Next, let’s install the required dependencies:
npm install --save-dev hardhat
npm install --save-dev @nomicfoundation/hardhat-toolbox
npm install ethers@5
npx hardhat
Now, create a new file named hardhat.config.js
in the project root and add the following code:
require("@nomiclabs/hardhat-waffle");
require("@celo/hardhat-celo");
module.exports = {
solidity: "0.8.0",
networks: {
hardhat: {},
celo: {
url: "https://forno.celo.org",
accounts: ["MNEMONIC_KEY"],
},
},
};
Replace "MNEMONIC_KEY"
with your actual Celo mnemonic key. Make sure to keep this private key safe and never commit it to a public repository.
Creating the Smart Contract:
Let’s create a new Solidity file named FarmersMarket.sol
in the contracts
directory. Add the following code to define the smart contract:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
contract FarmersMarket {
struct Product {
uint256 id;
address seller;
string name;
uint256 price;
uint256 quantity;
bool sold;
}
mapping(uint256 => Product) public products;
uint256 public totalProducts;
address public admin;
IERC20 public cUSDToken;
event ProductAdded(uint256 id, address seller, string name, uint256 price, uint256 quantity);
event ProductSold(uint256 id, address buyer, uint256 quantity);
constructor(address _cUSDTokenAddress) {
admin = msg.sender;
cUSDToken = IERC20(_cUSDTokenAddress);
}
modifier onlyAdmin() {
require(msg.sender == admin, "Only admin can perform this action");
_;
}
function addProduct(string memory _name, uint256 _price, uint256 _quantity) external {
totalProducts++;
products[totalProducts] = Product(totalProducts, msg.sender, _name, _price, _quantity, false);
emit ProductAdded(totalProducts, msg.sender, _name, _price, _quantity);
}
function buyProduct(uint256 _id, uint256 _quantity) external {
require(_id <= totalProducts, "Invalid product ID");
Product storage product = products[_id];
require(!product.sold, "Product is already sold");
require(product.quantity >= _quantity, "Insufficient quantity");
uint256 totalPrice = product.price * _quantity;
require(cUSDToken.allowance(msg.sender, address(this)) >= totalPrice, "Insufficient allowance");
product.quantity -= _quantity;
if (product.quantity == 0) {
product.sold = true;
}
cUSDToken.transferFrom(msg.sender, product.seller, totalPrice);
emit ProductSold(_id, msg.sender, _quantity);
}
function updateProductPrice(uint256 _id, uint256 _newPrice) external onlyAdmin {
require(_id <= totalProducts, "Invalid product ID");
Product storage product = products[_id];
require(!product.sold, "Cannot update price for sold product");
product.price = _newPrice;
}
}
Let’s go through the important parts of the code:
- We define a
Product
struct to represent the details of a product. - The
products
mapping stores the products, indexed by their IDs. - The
totalProducts
variable keeps track of the total number of products. - The
admin
variable represents the address of the contract administrator. - The
cUSDToken
variable is an instance of theIERC20
interface, representing the cUSD token contract. - The
ProductAdded
andProductSold
events emit relevant information for external observers. - The
constructor
initializes thecUSDToken
instance with the provided token address. - The
onlyAdmin
modifier ensures that certain functions can only be called by the contract administrator. - The
addProduct
function allows sellers to add new products to the marketplace. - The
buyProduct
function allows buyers to purchase products from the marketplace using cUSD. - The
updateProductPrice
function enables the administrator to update the price of a product.
Compile and Deploy the Smart Contract:
To compile the smart contract, run the following command in the terminal:
npx hardhat compile
Now, let’s deploy the contract to the Celo network. Create a new file named deploy.js
in the project root and add the following code:
async function main() {
const FarmersMarket = await ethers.getContractFactory("FarmersMarket");
const cUSDTokenAddress = "CELO_CUSD_TOKEN_ADDRESS";
const farmersMarket = await FarmersMarket.deploy(cUSDTokenAddress);
await farmersMarket.deployed();
console.log("FarmersMarket contract deployed to:", farmersMarket.address);
}
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
Replace "CELO_CUSD_TOKEN_ADDRESS"
with the actual address of the cUSD token on the Celo network.
Run the deployment script:
npx hardhat run deploy.js --network celo
Conclusion:
In this article, we have learned how to build a tokenized farmer’s market on the Celo blockchain using JavaScript, Hardhat, and Solidity. We created a complex smart contract that enables the creation, listing, and purchase of products using cUSD. By leveraging the power of blockchain and decentralized finance, we can create transparent and efficient marketplaces that empower farmers and consumers alike.
In our next article, we’ll be building a frontend for our smart contract using Next.js, a React framework. You can check out the Part 2 here
About Us
Joel Obafemi
A marketer, copywriter, and collab manager for web3 brands. You can connect with me on LinkedIn.