FarmerRegistry.sol
The first contract deployed. Stores verified farmer identities, Uganda NIN (National ID), phone numbers, MAAIF government IDs, GPS farm data references, and cooperative memberships. All other contracts depend on it.
:::important Contract Status
✅ Implemented — UUPS upgradeable. Sprint 1 added nationalId, farmerName, phoneNumber fields and cooperative self-management of agents. Sprint 3 added on-chain agent cap enforcement: max(3, ceil(totalFarmers/50)) enforced in _grantRole override.
:::
Responsibilities
Section titled “Responsibilities”- Stores the canonical list of verified farmers keyed by deterministic NIN-derived address
- Links Uganda NIN to wallet address (one-to-one, enforced via
maaifToWalletreverse lookup) - Stores farmer name and phone number required for Fonbnk MTN MoMo payouts
- References GPS polygon IPFS CIDs (not raw coordinates on-chain)
- Cooperatives self-manage agents via
_setRoleAdmin(AGENT_ROLE, COOP_ROLE)— no protocol admin bottleneck - Agent cap enforced on-chain: a cooperative can have at most
max(3, ceil(totalFarmers/50))active agents. The_grantRoleoverride reverts if the cap is exceeded._revokeRoledecrements the counter. - GFW deforestation check is recorded at registration
Key Data Structure
Section titled “Key Data Structure”struct Farmer { // ─── Original fields ─── string maaifFarmerId; // MAAIF National Traceability System ID address cooperativeWallet; // Cooperative wallet — or INDEPENDENT_AGGREGATOR bytes32 farmBoundaryIpfsCid; // IPFS CID of GeoJSON polygon (EUDR DDS) uint256 farmAreaHectares; // Farm area × 100 (e.g. 250 = 2.50 ha) bool gfwDeforestationFree; // Global Forest Watch check result bool active; // false = deactivated (soft delete) uint256 registrationTimestamp; // ─── Sprint 1 fields (append only) ─── string nationalId; // Uganda NIN — primary on-chain identity string farmerName; // Human-readable name (Fonbnk recipientName) string phoneNumber; // MTN phone (+2567XXXXXXXX) — Fonbnk recipientPhone}
mapping(address => Farmer) public farmers; // wallet → Farmermapping(string => address) public maaifToWallet; // MAAIF ID / NIN → wallet (uniqueness guard)address public INDEPENDENT_AGGREGATOR; // Set post-deploy via setIndependentAggregator()Interface
Section titled “Interface”// Register a new farmer — AGENT_ROLE required// Wallet is derived deterministically from NIN (not passed by caller)function registerFarmer( address farmerWallet, string calldata maaifFarmerId, address cooperativeWallet, bytes32 farmBoundaryIpfsCid, uint256 farmAreaHectares, bool gfwDeforestationFree, string calldata nationalId, // NEW: Uganda NIN string calldata farmerName, // NEW: farmer display name string calldata phoneNumber // NEW: MTN phone for payouts) external onlyRole(AGENT_ROLE);
// Returns true if farmer is registered and activefunction isRegistered(address farmerWallet) external view returns (bool);
// Returns full Farmer struct. Reverts only if never registered.function getFarmer(address farmerWallet) external view returns (Farmer memory);
// Returns true if farmer's cooperativeWallet == INDEPENDENT_AGGREGATOR.function isIndependent(address farmerWallet) external view returns (bool);
// NEW: Returns the number of farmers registered under a cooperative wallet.// Used by API to enforce agent caps: max(3, ceil(farmerCount / 50))function getFarmerCount(address cooperativeWallet) external view returns (uint256);
// Update GFW deforestation status — AGENT_ROLEfunction verifyFarmer(address farmerWallet, bool gfwDeforestationFree) external;
// Deactivate a farmer — AGENT_ROLE or COOP_ROLEfunction deactivateFarmer(address farmerWallet) external;
// Set or update the INDEPENDENT_AGGREGATOR — DEFAULT_ADMIN_ROLEfunction setIndependentAggregator(address aggregator) external;
// Migrate an independent farmer to a named cooperative — DEFAULT_ADMIN_ROLEfunction migrateFarmer(address farmerWallet, address newCooperativeWallet) external;Role Management
Section titled “Role Management”bytes32 public constant AGENT_ROLE = keccak256("AGENT_ROLE");bytes32 public constant COOP_ROLE = keccak256("COOP_ROLE");Sprint 1: Cooperatives self-manage their agents:
_setRoleAdmin(AGENT_ROLE, COOP_ROLE);This means:
- Protocol admin grants
COOP_ROLEonce (cooperative wallet) - Cooperative manager grants
AGENT_ROLEto field agents directly - Cooperative manager revokes
AGENT_ROLEwhen an agent leaves - No protocol admin involvement in day-to-day agent management
Sprint 3 — Agent cap enforcement:
The contract tracks totalFarmers and agentCount counters. The _grantRole and _revokeRole functions are overridden to enforce:
function _computeMaxAgents() internal view returns (uint256) { uint256 base = totalFarmers / 50; // 1 agent per 50 farmers if (totalFarmers % 50 != 0) base += 1; // ceil division return base < 3 ? 3 : base; // minimum 3 agents}The cap applies globally (not per-cooperative). A cooperative cannot have more agents than max(3, ceil(totalFarmers / 50)) regardless of how many farmers they manage.
Wallet Derivation
Section titled “Wallet Derivation”Farmer wallet addresses are not generated — they are derived deterministically from NIN:
function addressFromNin(nin: string): `0x${string}` { const hash = keccak256(toBytes(`asilichain:${nin}`)); return `0x${hash.slice(26)}`;}The farmer never signs transactions and does not need a private key. The address is purely an on-chain identifier. For future self-custody, migrateFarmer() reassigns to a farmer-controlled wallet.
INDEPENDENT_AGGREGATOR
Section titled “INDEPENDENT_AGGREGATOR”~55-77% of Uganda farmers operate outside formal cooperatives. These farmers are registered with cooperativeWallet = INDEPENDENT_AGGREGATOR, a protocol-managed multisig set post-deploy.
isIndependent(farmerWallet)— checks if farmer uses INDEPENDENT_AGGREGATORmigrateFarmer(farmerWallet, newCooperative)— moves farmer to a cooperative- Independent farmer batches can have stages advanced by
AGENT_ROLE(see TraceLog changes)
Access Control
Section titled “Access Control”| Role | Default Holder | Can |
|---|---|---|
DEFAULT_ADMIN_ROLE | AsiliChain deployer multisig | Grant/revoke all roles; setIndependentAggregator(); migrateFarmer(); upgrade contract |
COOP_ROLE | Cooperative wallet | Grant/revoke AGENT_ROLE; deactivate farmers |
AGENT_ROLE | Field agent wallets | Register farmers, verify GFW status, deactivate farmers |