π§ Introduction
Farmers depend on rain β but nature isnβt always kind.
Today, weβll use blockchain + oracles to bring weather-based crop insurance on-chain π¦οΈ
This smart contract automatically pays farmers if rainfall drops below a set threshold.
Weβll use Chainlink Oracles for real-world weather data, build & test it with Foundry, and interact with it via React + Ethers.js.
π§ What Youβll Learn
- How to integrate Chainlink Oracles for real-world data
- How to build automated insurance on-chain
- How to test smart contracts locally using Foundry
- How to connect a React frontend with Ethers.js
π Project Structure
Hereβs the project layout for your reference:
day18-crop-insurance/
β
βββ src/
β βββ CropInsurance.sol
β βββ MockOracle.sol
β
βββ test/
β βββ CropInsurance.t.sol
β
βββ script/
β βββ DeployCropInsurance.s.sol
β
βββ frontend/
β βββ package.json
β βββ vite.config.js
β βββ src/
β β βββ App.jsx
β β βββ App.css
β βββ public/
β
βββ foundry.toml
ποΈ Smart Contracts
π CropInsurance.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
interface IOracle {
function requestRainfallData(uint256 latE6, uint256 lonE6, uint256 startTs, uint256 endTs)
external
returns (bytes32 requestId);
}
contract CropInsurance {
struct Policy {
address payable farmer;
uint256 premium;
uint256 payout;
uint256 latE6;
uint256 lonE6;
uint256 startTs;
uint256 endTs;
uint256 rainfallThresholdMM;
bool active;
bool paid;
bytes32 requestId;
}
mapping(uint256 => Policy) public policies;
uint256 public nextPolicyId;
address public owner;
IOracle public oracle;
event PolicyCreated(uint256 indexed id, address farmer);
event PolicyEvaluated(uint256 indexed id, uint256 rainfall);
event PolicyPaid(uint256 indexed id, address farmer, uint256 amount);
modifier onlyOwner() {
require(msg.sender == owner, "Not authorized");
_;
}
constructor(address _oracle) {
owner = msg.sender;
oracle = IOracle(_oracle);
}
function createPolicy(
address payable farmer,
uint256 premium,
uint256 payout,
uint256 latE6,
uint256 lonE6,
uint256 startTs,
uint256 endTs,
uint256 rainfallThresholdMM
) external onlyOwner {
require(farmer != address(0), "Invalid farmer");
require(startTs < endTs, "Invalid timestamps");
uint256 id = nextPolicyId++;
policies[id] = Policy({
farmer: farmer,
premium: premium,
payout: payout,
latE6: latE6,
lonE6: lonE6,
startTs: startTs,
endTs: endTs,
rainfallThresholdMM: rainfallThresholdMM,
active: true,
paid: false,
requestId: bytes32(0)
});
emit PolicyCreated(id, farmer);
}
function evaluatePolicy(uint256 policyId) external {
Policy storage p = policies[policyId];
require(block.timestamp > p.endTs, "Season not ended");
require(p.active, "Not active");
bytes32 requestId =
oracle.requestRainfallData(p.latE6, p.lonE6, p.startTs, p.endTs);
p.requestId = requestId;
}
function fulfill(bytes32 requestId, uint256 rainfallMM) external {
for (uint256 i = 0; i < nextPolicyId; i++) {
Policy storage p = policies[i];
if (p.requestId == requestId && p.active) {
p.active = false;
emit PolicyEvaluated(i, rainfallMM);
if (rainfallMM < p.rainfallThresholdMM && !p.paid) {
p.paid = true;
(bool ok, ) = p.farmer.call{value: p.payout}("");
require(ok, "Transfer failed");
emit PolicyPaid(i, p.farmer, p.payout);
}
break;
}
}
}
receive() external payable {}
}
π§© MockOracle.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "./CropInsurance.sol";
contract MockOracle is IOracle {
bytes32 public lastRequestId;
function requestRainfallData(uint256, uint256, uint256, uint256)
external
override
returns (bytes32 requestId)
{
requestId = keccak256(abi.encodePacked(block.timestamp, msg.sender));
lastRequestId = requestId;
return requestId;
}
function fulfillRequest(address cropInsurance, bytes32 requestId, uint256 rainfallMM) external {
CropInsurance(cropInsurance).fulfill(requestId, rainfallMM);
}
}
π§ͺ Foundry Test β CropInsurance.t.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "forge-std/Test.sol";
import "../src/CropInsurance.sol";
import "../src/MockOracle.sol";
contract CropInsuranceTest is Test {
CropInsurance crop;
MockOracle oracle;
address farmer = address(0xBEEF);
function setUp() public {
oracle = new MockOracle();
crop = new CropInsurance(address(oracle));
vm.deal(address(crop), 10 ether);
}
function testPayoutBelowThreshold() public {
crop.createPolicy(payable(farmer), 0, 1 ether, 12345678, 87654321, block.timestamp, block.timestamp + 5, 50);
vm.warp(block.timestamp + 6);
crop.evaluatePolicy(0);
bytes32 requestId = oracle.lastRequestId();
oracle.fulfillRequest(address(crop), requestId, 10); // rainfall = 10mm
assertEq(farmer.balance, 1 ether);
}
}
π§° Run Locally with Foundry
forge build
forge test -vv
β Tests show automatic payouts when rainfall < threshold.
βοΈ Frontend Integration (React + Ethers.js)
πͺ Setup Frontend
cd ..
mkdir frontend && cd frontend
npm create vite@latest crop-insurance-dapp -- --template react
cd crop-insurance-dapp
npm install ethers
βοΈ App.jsx
import { useState } from "react";
import { ethers } from "ethers";
import "./App.css";
const contractAddress = "YOUR_DEPLOYED_CONTRACT_ADDRESS";
const contractABI = [
"function createPolicy(address farmer,uint256,uint256,uint256,uint256,uint256,uint256,uint256)",
"function evaluatePolicy(uint256 policyId)",
"event PolicyCreated(uint256 indexed id,address farmer)",
"event PolicyPaid(uint256 indexed id,address farmer,uint256 amount)"
];
function App() {
const [farmer, setFarmer] = useState("");
const [status, setStatus] = useState("");
async function createPolicy() {
if (!window.ethereum) return alert("MetaMask not found");
const provider = new ethers.BrowserProvider(window.ethereum);
const signer = await provider.getSigner();
const contract = new ethers.Contract(contractAddress, contractABI, signer);
setStatus("Creating policy...");
const tx = await contract.createPolicy(
farmer,
0,
ethers.parseEther("1"),
12345678,
87654321,
Math.floor(Date.now() / 1000),
Math.floor(Date.now() / 1000) + 60,
50
);
await tx.wait();
setStatus("β
Policy Created!");
}
async function evaluatePolicy() {
const provider = new ethers.BrowserProvider(window.ethereum);
const signer = await provider.getSigner();
const contract = new ethers.Contract(contractAddress, contractABI, signer);
const tx = await contract.evaluatePolicy(0);
await tx.wait();
setStatus("π¦οΈ Policy evaluated");
}
return (
<div className="app">
<h1>πΎ Crop Insurance DApp</h1>
<input
type="text"
placeholder="Farmer address"
value={farmer}
onChange={(e) => setFarmer(e.target.value)}
/>
<button onClick={createPolicy}>Create Policy</button>
<button onClick={evaluatePolicy}>Evaluate Policy</button>
<p>{status}</p>
</div>
);
}
export default App;
π¨ App.css
.app {
text-align: center;
margin-top: 5rem;
}
input {
padding: 8px;
width: 280px;
margin-right: 10px;
border-radius: 6px;
}
button {
margin: 10px;
padding: 10px 16px;
border-radius: 8px;
cursor: pointer;
background-color: #2b9348;
color: white;
border: none;
}
π§ͺ Run Frontend
npm run dev
Open http://localhost:5173
Now you can create and evaluate insurance policies using MetaMask! π
π Final Thoughts
You just built a real-world DeFi insurance use case that connects blockchain with external data through Chainlink oracles.
From Solidity β Foundry β React β Ethers.js β youβve completed the full stack of Web3 integration.
π Resources
π¬ Next: Day 19 β Build a tokenized weather reward system βοΈπ§
π Follow Me
- π§βπ» Saurav Kumar
- π¬ Twitter/X: @_SauravCodes
- πΌ LinkedIn: Saurav Kumar
Top comments (0)