Building a Crop Insurance Platform Smart Contract using Solidity and JavaScript on Celo

Introduction

Agricultural insurance platforms play a vital role in aiding farmers manage risks associated with adverse weather conditions, pest infestations, and other unforeseen circumstances.

This tutorial will walk you through creating a decentralized crop insurance platform on the Celo blockchain, using Solidity, Hardhat, and JavaScript. This platform will use the Celo Dollar (cUSD) as the medium of exchange, and only authorized insurance officials can insure farmers.

Prerequisites

Before we get started, ensure you have the following installed:

Node.js and npm
Hardhat
Solidity version 0.8.0 or higher
A Celo Wallet

Setting Up the Development Environment

Create a new directory and initialize npm:

$ mkdir cropInsurance && cd cropInsurance
$ npm init -y

Next, install Hardhat and other necessary dependencies:

$ npm install --save-dev hardhat
$ npm install --save-dev @nomiclabs/hardhat-waffle ethereum-waffle chai @nomiclabs/hardhat-ethers ethers @openzeppelin/contracts
$ npm install dotenv --save

Initialize Hardhat:

$ npx hardhat

Follow the prompts to create a basic sample project.

Writing the Smart Contract

Create a new file in the contracts folder, name it CropInsurance.sol, and add the following code:

// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;

import "@openzeppelin/contracts/utils/math/SafeMath.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/interfaces/IERC20.sol";

contract Insurance is ERC20 {
    using SafeMath for uint256;
    IERC20 public immutable cUSD;

    uint256 constant minimumPremium = 50 ether;
    uint256 constant minimumClaimInsurance = 1000 ether;
    uint256 constant tokenRation = 20;

    struct Members {
        uint256 usdValue; 
        uint256 citValue;
        uint256 premi;
        uint256 registerDate;
        uint256 lastPayment;
        uint256 nextPayment;
    }

    mapping(address => Members) private _addressToMembers;
    mapping(address => bool) private _addressStatus;
    mapping (address => uint256[]) private _addressClaimHistory;
    mapping (address => uint256[]) private _addressPaymentHistory;

    event NewRegistration(address indexed members, uint256 timestamp);

    constructor(address _cUSD)ERC20("CropInsuranceToken", "CIT") {
        cUSD = IERC20(_cUSD);
    }

    function register(uint256 _usdAmount) external {
        require(_usdAmount >= minimumPremium, "Insurance: Minimum premium is 50 cUSD");
        require(!_addressStatus[msg.sender], "Insurance: You are already registered");
        cUSD.transferFrom(msg.sender, address(this), _usdAmount);
        uint256 _citAmount = _usdAmount.mul(tokenRation);
        _addressToMembers[msg.sender] = Members(
            _usdAmount,
            _citAmount,
            _usdAmount,
            block.timestamp,
            block.timestamp,
            block.timestamp.add(30 days)
        );
        _addressStatus[msg.sender] = true;
        _addressPaymentHistory[msg.sender].push(block.timestamp);
        _mint(msg.sender, _citAmount);
        emit NewRegistration(msg.sender, block.timestamp);
    }

    function claim(uint256 _citAmount) external {
        require(_citAmount >= minimumClaimInsurance, "Insurance: Minimum claim is 1000 CIT");
        require(_addressStatus[msg.sender], "Insurance: You are not registered");
        uint256 _usdAmount = _citAmount.div(tokenRation);
        Members storage members = _addressToMembers[msg.sender];
        members.usdValue = members.usdValue.sub(_usdAmount);
        members.citValue = members.citValue.sub(_citAmount);
        _addressClaimHistory[msg.sender].push(block.timestamp);
        _burn(msg.sender, _citAmount);
        cUSD.transfer(msg.sender, _usdAmount);
    }

    function pay(uint256 _usdAmount) external {
        require(_addressStatus[msg.sender], "Insurance: You are not registered");
        Members storage members = _addressToMembers[msg.sender];
        require(_usdAmount >= members.premi, "Insurance: Payment amount is less than the premium");
        require(block.timestamp >= members.nextPayment, "Insurance: Payment is not due");
        cUSD.transferFrom(msg.sender, address(this), _usdAmount);
        uint256 _citAmount = _usdAmount.mul(tokenRation);
        members.usdValue = members.usdValue.add(_usdAmount);
        members.citValue = members.citValue.add(_citAmount);
        members.lastPayment = block.timestamp;
        members.nextPayment = block.timestamp.add(30 days);
        _addressPaymentHistory[msg.sender].push(block.timestamp);
        _mint(msg.sender, _citAmount);
    }

    function getInsurance() external view returns (Members memory) {
        return _addressToMembers[msg.sender];
    }
}

This is a Solidity contract called Insurance for an insurance system where users can register, make claims and payments in a token called cUSD (presumably a stablecoin pegged to the US dollar), and get tokens called CropInsuranceToken (CIT) in return. This system uses the ERC20 standard for the CropInsuranceToken.

Let’s go over each section:

  1. Pragma and Imports:
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;

import "@openzeppelin/contracts/utils/math/SafeMath.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/interfaces/IERC20.sol";

This section defines the version of Solidity compiler to be used (0.8.15) and imports several libraries from OpenZeppelin: SafeMath for safe mathematical operations, ERC20 for the base functionality of the ERC20 standard, and IERC20 which is the interface for the ERC20 standard.

  1. Contract declaration:
contract Insurance is ERC20 {

This line declares the Insurance contract which extends the ERC20 contract. This gives Insurance all the functionality of an ERC20 token.

  1. Global Variables:
using SafeMath for uint256;
IERC20 public immutable cUSD;

uint256 constant minimumPremium = 50 ether;
uint256 constant minimumClaimInsurance = 1000 ether;
uint256 constant tokenRation = 20;

The SafeMath library is used to perform mathematical operations safely (avoiding overflows and underflows). cUSD is a public, immutable instance of an IERC20 token, which will be initialized in the constructor and cannot be changed afterwards. The contract also defines three constant variables: minimumPremium (50 ether), minimumClaimInsurance (1000 ether), and tokenRation (20).

  1. Structs and Mappings:
struct Members {
    uint256 usdValue; 
    uint256 citValue;
    uint256 premi;
    uint256 registerDate;
    uint256 lastPayment;
    uint256 nextPayment;
}

mapping(address => Members) private _addressToMembers;
mapping(address => bool) private _addressStatus;
mapping (address => uint256[]) private _addressClaimHistory;
mapping (address => uint256[]) private _addressPaymentHistory;

The Members struct stores information about each insured person including the amount of cUSD they have contributed (usdValue), the amount of CIT they have received (citValue), the amount of premium they are to pay (premi), the date of registration (registerDate), the time of their last payment (lastPayment), and the time of their next payment (nextPayment).

Four mappings are declared: _addressToMembers maps an address to a Members struct, _addressStatus maps an address to a bool indicating whether the address is registered or not, _addressClaimHistory maps an address to an array of timestamps indicating when they made claims, and _addressPaymentHistory maps an address to an array of timestamps indicating when they made payments.

  1. Events:
event NewRegistration(address indexed members, uint256 timestamp);

This event is emitted whenever a new registration occurs, providing the member’s address and the timestamp of the event.

  1. Constructor:
constructor(address _cUSD)ERC20("CropInsuranceToken", "CIT") {
    cUSD = IERC20(_cUSD);
}

The constructor accepts an address _cUSD as a parameter and assigns it to cUSD (which is an IERC20 token). This address is the contract address of the cUSD token. The contract also calls the constructor of the ERC20 contract, setting the name of the token to “CropInsuranceToken” and the symbol to “CIT”.

  1. Register Function:
function register(uint256 _usdAmount) external {
    // ... omitted for brevity
}

This function allows a user to register for insurance by sending _usdAmount of cUSD to the contract. The user gets _usdAmount times tokenRation of CIT tokens in return. Several conditions must be met for the transaction to succeed: the amount sent must be equal or higher than the minimum premium, and the user must not be already registered.

  1. Claim Function:
function claim(uint256 _citAmount) external {
    // ... omitted for brevity
}

This function allows a registered user to make a claim, burning their CIT tokens and receiving cUSD in return. The claim amount in CIT must be equal or higher than the minimumClaimInsurance and the user must be registered.

  1. Pay Function:
function pay(uint256 _usdAmount) external {
    // ... omitted for brevity
}

This function allows a registered user to make a payment in cUSD, which increases their usdValue and citValue by the appropriate amounts. The payment amount must be at least the value of premi and the payment must be due (i.e., current timestamp should be greater or equal to nextPayment).

  1. getInsurance Function:
function getInsurance() external view returns (Members memory) {
    return _addressToMembers[msg.sender];
}

This function allows a user to view their insurance details by returning the Members struct associated with their address.

Please note that all the functions in this contract are external, which means they can only be called from outside the contract. Also, this contract assumes that the approval for token transfers has been done outside this contract, i.e., users have approved this contract to spend their cUSD tokens.

Compiling the Smart Contract

Compile the contract using Hardhat:

$ npx hardhat compile

Writing Tests

Next, write tests for the contract. Create a new file in the test directory named CropInsuranceTest.js, and add the following code:

const { expect } = require("chai");
const { ethers } = require("hardhat");
const {
    time,
    loadFixture
} = require("@nomicfoundation/hardhat-network-helpers");


describe("Insurance", function(){
    async function setup() {
        const [ deployer, otherAccount ] = await ethers.getSigners();

        // Load contract
        const Insurance = await ethers.getContractFactory("Insurance");
        const Token = await ethers.getContractFactory("TestToken");

        // Deploy contract
        const cUSD = await Token.deploy();
        const insurance = await Insurance.deploy(await cUSD.getAddress());

        // OtherAccount request faucet
        await cUSD.connect(otherAccount).faucet();

        return { deployer, otherAccount, cUSD, insurance };
    }

    it("Register", async function(){
        const { otherAccount, cUSD, insurance } = await loadFixture(setup);
        const amount = ethers.parseEther("60");

        // Register insurance
        await cUSD.connect(otherAccount).approve(await insurance.getAddress(), amount);
        await insurance.connect(otherAccount).register(amount);

        // Check data
        const insuranceData = await insurance.connect(otherAccount).getInsurance();
        expect(insuranceData[0]).to.be.equal(amount);
        expect(insuranceData[1]).to.be.equal(ethers.parseEther("1200"));
        expect(insuranceData[2]).to.be.equal(amount);
        expect(await insurance.balanceOf(otherAccount.address)).to.be.equal(ethers.parseEther("1200"));
        expect(await cUSD.balanceOf(await insurance.getAddress())).to.be.equal(ethers.parseEther("60"));
    });

    it("Claim", async function(){
        const { otherAccount, cUSD, insurance } = await loadFixture(setup);
        const amount = ethers.parseEther("60");

        // Register insurance
        await cUSD.connect(otherAccount).approve(await insurance.getAddress(), amount);
        await insurance.connect(otherAccount).register(amount);

        // Get insurance data
        const citAmount = await insurance.balanceOf(otherAccount.address);
        const insuranceData = await insurance.connect(otherAccount).getInsurance();

        // Increase time
        time.increaseTo(insuranceData[5]);

        // Claim
        await insurance.connect(otherAccount).claim(citAmount);
        const newInsuranceData = await insurance.connect(otherAccount).getInsurance();
        expect(newInsuranceData[0]).to.be.equal(ethers.parseEther("0"));
        expect(newInsuranceData[1]).to.be.equal(ethers.parseEther("0"));
        expect(await insurance.balanceOf(otherAccount.address)).to.be.equal(ethers.parseEther("0"));
        expect(await cUSD.balanceOf(await otherAccount.address)).to.be.equal(ethers.parseEther("200"));
    });

    it("Pay Insurance", async function(){
        const { otherAccount, cUSD, insurance } = await loadFixture(setup);
        const amount = ethers.parseEther("60");

        // Register insurance
        await cUSD.connect(otherAccount).approve(await insurance.getAddress(), amount);
        await insurance.connect(otherAccount).register(amount);

        // Get insurance data
        const citAmount = await insurance.balanceOf(otherAccount.address);
        const insuranceData = await insurance.connect(otherAccount).getInsurance();

        // Increase time
        time.increaseTo(insuranceData[5]);

        // Pay insurance
        await cUSD.connect(otherAccount).approve(await insurance.getAddress(), amount);
        await insurance.connect(otherAccount).pay(amount);
        const newInsuranceData = await insurance.connect(otherAccount).getInsurance();

        expect(newInsuranceData[0]).to.be.equal(ethers.parseEther("120"));
        expect(newInsuranceData[1]).to.be.equal(ethers.parseEther("2400"));
        expect(await cUSD.balanceOf(await insurance.getAddress())).to.be.equal(ethers.parseEther("120"));
        expect(await insurance.balanceOf(otherAccount.address)).to.be.equal(ethers.parseEther("2400"));
    })
})

This test suite checks if the contract correctly insures a farmer and allows them to claim insurance. Replace “0xcUSDCoinAddress” with the cUSD token contract address and “0xYourAddress” with a real address.

You can run these tests using Hardhat:

$ npx hardhat test

Deploying the Smart Contract

After testing, deploy the contract on the Celo network. You need to set up a network in the Hardhat config and fund your account with cUSD.

In the hardhat.config.js file, add:

require("@nomicfoundation/hardhat-toolbox");
require("dotenv").config({ path: ".env" });

/** @type import('hardhat/config').HardhatUserConfig */
module.exports = {
    solidity: {
        version: "0.8.15",
        settings: {
            optimizer: {
                enabled: true,
                runs: 200,
            },
        },
    },
	networks:{
		alfajores:{
			url: process.env.RPC_URL,
			chainId: 44787,
			accounts: {
				mnemonic: process.env.MNEMONIC,
				path: "m/44'/60'/0'/0",
			}
		}
	}
};
Finally, create a deploy script at scripts/deploy.js:

const hre = require("hardhat")

async function main() {
  const CropInsurance = await hre.ethers.getContractFactory("CropInsurance");
  const cropInsurance = await CropInsurance.deploy();
  await cropInsurance.deployed("<CUSD_ADDRESS>");
  console.log("CropInsurance deployed to:", cropInsurance.address);
}

main()
  .then(() => process.exit(0))
  .catch(error => {
      console.error(error);
      process.exit(1);
  });

Run the command below to deploy the contract

npx hardhat run --network celo scripts/deploy.js

Now, you have a crop insurance platform on the Celo blockchain. This platform allows a designated official to insure farmers’ crops in return for a premium paid in cUSD. Farmers can claim their insurance in case of loss, with all transactions transparently recorded on the blockchain.

Future enhancements can include links to a weather oracle for automatic claims based on adverse weather conditions.

About the Author

Aborode Olusegun Isaac is a Growth Marketer and Data Analyst. He’s got a huge interest in Web3 and the decentralisation that it offers. Twitter

Reference

Source Code

5 Likes

Fantastic news! Your proposal has landed in this week’s top voted list. As you begin your project journey, remember to align with our community and technical guidelines, ensuring a high quality platform for our developers. Congratulations! :mortar_board: :seedling:

Note: It is not clear what it is what you’ll be showing developers how to build in this tutorial. This is approved assuming that you will create a technical article showing developers the code necessary to integrate a payments application with a SQL databases like MySQL and a NoSQL database like MongoDB. If your intention is to create a high level overview I will be unable to provide a reward.

2 Likes

I will do a review for you

1 Like

Thank you. Look forward to your review and correction

2 Likes

Please check source code too

2 Likes

Okay, yesterday I helped him to finish this article, it seems we forgot to add a few things. Soon we will fix it

2 Likes