TraceLog.sol
Records the seven-stage custody journey of every BatchToken from DELIVERED to SETTLED. Each stage update emits an event read by the API layer, which writes to Hedera HCS for an immutable audit trail.
Stage Enum
Section titled “Stage Enum”enum Stage { DELIVERED, // 0 — BatchToken minted, coffee weighed at collection GRADED, // 1 — Quality assessment passed MILLED, // 2 — Processing complete WAREHOUSED, // 3 — Physical storage confirmed COMMITTED, // 4 — PurchaseOrder confirmed by buyer EXPORTED, // 5 — Export licence confirmed — triggers auto-repayment SETTLED // 6 — Buyer USDC paid, loan repaid, net disbursed}Stage Transition Rules
Section titled “Stage Transition Rules”Stages must advance in order. No skipping. No reversal.
| From | To | Who can trigger | Notes |
|---|---|---|---|
| — | DELIVERED | AGENT_ROLE | Auto-initializes on first updateStage() call |
| DELIVERED | GRADED | COOP_ROLE or AGENT_ROLE (if independent farmer) | Quality assessment |
| GRADED | MILLED | COOP_ROLE or AGENT_ROLE (if independent) | Processing |
| MILLED | WAREHOUSED | COOP_ROLE or AGENT_ROLE (if independent) | Storage |
| WAREHOUSED | COMMITTED | PURCHASE_ORDER_ROLE (PurchaseOrder.sol) | On PO confirmation |
| COMMITTED | EXPORTED | COOP_ROLE or AGENT_ROLE (if independent) | Export permit |
| EXPORTED | SETTLED | VAULT_ROLE (LendingVault) | On buyer USDC receipt |
Independent Farmer Exception (Sprint 1)
Section titled “Independent Farmer Exception (Sprint 1)”Independent farmers (cooperativeWallet == INDEPENDENT_AGGREGATOR) have no cooperative manager. To prevent their batches from getting stuck:
AGENT_ROLEcan advance stages GRADED, MILLED, WAREHOUSED, and EXPORTED for independent farmer batches- Cooperative farmer batches still require
COOP_ROLEfor these stages - The check is done inside
_checkStageRole()by reading the farmer wallet from BatchToken and checkingFarmerRegistry.isIndependent()
Interface
Section titled “Interface”// Update stage (role-gated, enforces sequential order)function updateStage(uint256 tokenId, Stage newStage) external;
// Get current stagefunction getCurrentStage(uint256 tokenId) external view returns (Stage);
// Initializationfunction initialize(address defaultAdmin, address batchTokenAddress) public initializer;function initializeV2(address farmerRegistryAddress) external reinitializer(2); // Sprint 1| Role | Purpose |
|---|---|
AGENT_ROLE | Record DELIVERED; all stages for independent farmers |
COOP_ROLE | Record GRADED through EXPORTED for cooperative farmers |
PURCHASE_ORDER_ROLE | Record COMMITTED (PurchaseOrder.sol) |
VAULT_ROLE | Record SETTLED (LendingVault.sol) |
DEFAULT_ADMIN_ROLE | Upgrade contract |
Auto-Initialisation
Section titled “Auto-Initialisation”The first updateStage() call for any token must pass Stage.DELIVERED. The contract tracks initialization via a private _initialized mapping to distinguish uninitialised tokens (Stage(0)) from tokens actually at DELIVERED (also Stage(0) in the enum).
Dependencies
Section titled “Dependencies”- BatchToken.sol — reads
getFarmerWallet()to determine batch ownership - FarmerRegistry.sol (Sprint 1) — reads
isIndependent()for role gating