PurchaseOrder.sol
Records a buyer’s committed purchase of a specific BatchToken. Confirming a PurchaseOrder upgrades the BatchToken’s collateral tier from WAREHOUSED (70% LTV) to COMMITTED (80% LTV) and advances TraceLog to the COMMITTED stage.
What a PurchaseOrder Does
Section titled “What a PurchaseOrder Does”Before PO: BatchToken at WAREHOUSED → LTV 70% → max loan $315 on 67.5kg batchAfter PO: BatchToken at COMMITTED → LTV 80% → max loan $360 on 67.5kg batchThe PurchaseOrder is the on-chain proof that a buyer has committed. This commitment is what justifies the higher LTV — the coffee is no longer speculative inventory, it is contracted inventory.
Key Data Structure
Section titled “Key Data Structure”struct PurchaseOrder { uint256 orderId; // Monotonically increasing from 1 uint256 batchTokenId; address buyerWallet; // EU buyer's wallet (or buyer portal hot wallet) string buyerOrganisation; // e.g. "Sucafina SA" uint256 agreedPriceUsdc; // Total USDC agreed for the batch uint256 createdTimestamp; uint256 confirmedTimestamp; POStatus status; // PENDING | CONFIRMED | CANCELLED}
enum POStatus { PENDING, CONFIRMED, CANCELLED }
mapping(uint256 => PurchaseOrder) public orders; // orderId → POmapping(uint256 => uint256) public batchToActiveOrder; // batchTokenId → active orderId. 0 = noneInterface
Section titled “Interface”// Create a PO (BUYER_ROLE — buyer portal hot wallet)function createPurchaseOrder( uint256 batchTokenId, address buyerWallet, // ← passed at creation time string calldata buyerOrganisation, uint256 agreedPriceUsdc) external onlyRole(BUYER_ROLE) returns (uint256 orderId);
// Confirm a PO (COOP_ROLE — cooperative accepts the order)// Calls traceLog.updateStage(batchTokenId, 4) for COMMITTEDfunction confirmPurchaseOrder(uint256 orderId) external onlyRole(COOP_ROLE);
// Cancel a PO (BUYER_ROLE or COOP_ROLE — within 48h of creation)function cancelPurchaseOrder(uint256 orderId) external;
// View a POfunction getOrder(uint256 orderId) external view returns (PurchaseOrder memory);Confirmation Side Effects
Section titled “Confirmation Side Effects”When confirmPurchaseOrder is called:
PurchaseOrder.status→CONFIRMED,confirmedTimestampset toblock.timestampbatchToActiveOrder[batchTokenId]deleted (PO is no longer “active”)traceLog.updateStage(batchTokenId, 4)— stageCOMMITTED(4 = TraceLog.Stage.COMMITTED)BatchTokencollateral tier recalculated to COMMITTED LTV (80%)LendingVaultnotified — existing loan can be topped up to new LTV if requested- HCS event written:
COMMITTEDwith PO reference and agreed price
Guards (createPurchaseOrder)
Section titled “Guards (createPurchaseOrder)”| Check | Revert if violated |
|---|---|
buyerWallet != address(0) | "PurchaseOrder: zero buyer wallet" |
bytes(buyerOrganisation).length > 0 | "PurchaseOrder: empty buyer organisation" |
agreedPriceUsdc > 0 | "PurchaseOrder: price must be greater than zero" |
batchToken.checkExists(batchTokenId) | BatchToken revert |
!batchToken.hasActiveLoan(batchTokenId) | "PurchaseOrder: batch is locked as collateral" |
batchToActiveOrder[batchTokenId] == 0 | "PurchaseOrder: batch already has an active order" |
Cancellation Rules
Section titled “Cancellation Rules”- Only
BUYER_ROLEorCOOP_ROLEcan cancel. - Must be within 48 hours of
createdTimestamp. - Only
PENDINGorders can be cancelled. - Deletes
batchToActiveOrdermapping entry.
Events
Section titled “Events”event PurchaseOrderCreated( uint256 indexed orderId, uint256 indexed batchTokenId, address indexed buyerWallet);
event PurchaseOrderConfirmed( uint256 indexed orderId, uint256 indexed batchTokenId, address indexed buyerWallet);
event PurchaseOrderCancelled( uint256 indexed orderId, uint256 indexed batchTokenId, address indexed cancelledBy);Buyer Portal Integration
Section titled “Buyer Portal Integration”Commodity traders (Sucafina, Olam, Kawacom) access a buyer portal dashboard. From the portal:
- Browse available COMMITTED or WAREHOUSED BatchTokens by cooperative, grade, and weight
- View DDS eligibility status per batch
- Create a PurchaseOrder with agreed USDC price
- Receive automatic notification when cooperative confirms
The buyer portal calls PurchaseOrder.createPurchaseOrder() via AsiliChain API on behalf of the trader.