Skip to content

BatchToken.sol (ERC-1155)

The central token of the AsiliChain protocol. Each BatchToken ID represents one weighed, graded coffee batch. Minted at DELIVERED stage. Burns at SETTLED stage. The primary collateral unit for LendingVault.

ERC-1155 is chosen over ERC-721 because:

  • Semi-fungible: Batches of the same grade, cooperative, and season are interchangeable for collateral purposes
  • Batch operations: A single transaction can mint multiple BatchTokens for multiple farmers in a cooperative delivery
  • Gas efficiency: 30–40% cheaper than equivalent ERC-721 operations at scale
struct BatchData {
string batchId; // e.g. "BATCH-2026-004821"
address farmerWallet; // Primary key mapped to FarmerRegistry
address cooperativeWallet; // Aggregating cooperative (or INDEPENDENT_AGGREGATOR)
uint256 weightKg; // Scaled ×10 (e.g. 675 = 67.5 kg)
string grade; // "screen18", "screen15", "FAQ"
uint256 moisturePct; // Scaled ×10 (e.g. 112 = 11.2%)
bytes32 collectionPointHash; // GPS hash of collection point
bytes32 weightSlipIpfsCid; // IPFS CID of weight slip photo
uint256 mintTimestamp;
bool loanActive; // Prevents double-collateralisation
}
mapping(uint256 => BatchData) public batchData;
uint256 public nextTokenId;
// Mint a new BatchToken (AGENT_ROLE required, farmer must be registered)
// Token is minted to the cooperativeWallet (not the farmer)
function mintBatch(
string calldata batchId,
address cooperativeWallet,
address farmerWallet,
uint256 weightKg,
string calldata grade,
uint256 moisturePct,
bytes32 collectionPointHash,
bytes32 weightSlipIpfsCid
) external onlyRole(AGENT_ROLE) returns (uint256 tokenId);
// Get batch data (read by LendingVault for collateral valuation)
function getBatchData(uint256 tokenId) external view returns (BatchData memory);
// NEW: Get the farmer wallet for a batch token
// Used by TraceLog to check if a batch belongs to an independent farmer
function getFarmerWallet(uint256 tokenId) external view returns (address);
// Check if batch has active loan (prevents double-collateralisation)
function hasActiveLoan(uint256 tokenId) external view returns (bool);
// Lock batch as collateral (called by LendingVault on loan origination)
function lockAsCollateral(uint256 tokenId) external onlyRole(VAULT_ROLE);
// Unlock collateral (called by LendingVault on repayment or liquidation)
function unlockCollateral(uint256 tokenId) external onlyRole(VAULT_ROLE);
// Burn on SETTLED (loan repaid and export settled)
function burnSettled(uint256 tokenId) external onlyRole(VAULT_ROLE);

When LendingVault originates a loan, it calls lockAsCollateral() which sets loanActive = true. This blocks safeTransferFrom (Invariant #7) — the batch cannot be transferred while locked. On settlement, unlockCollateral() is called, then burnSettled() destroys the token.

checkExists(tokenId) is used by TraceLog and PurchaseOrder to validate token existence before stage updates or PO creation. It reverts if the token doesn’t exist.

RoleCan
AGENT_ROLEMint new batches
VAULT_ROLELock/unlock collateral, burn settled batches
DEFAULT_ADMIN_ROLEUpgrade contract