Supply Chain Traceability Contract

What it does:
Tracks products and materials throughout the supply chain, recording each event, transfer, or checkpoint on-chain for full traceability.

Why it matters:
Enhances transparency, prevents fraud, ensures compliance with regulations, and allows all stakeholders to verify the origin and journey of products.

How it works:

  • Suppliers, manufacturers, and distributors register on-chain

  • Each product or batch is assigned a unique ID or NFT

  • Events such as shipment, receipt, processing, or inspection are logged on-chain

  • Ownership and custody transfers are recorded immutably

  • Smart contracts can trigger alerts for delays, deviations, or unauthorized handling

  • Public dashboards allow verification of supply chain integrity

  • Integrates with marketplaces, compliance audits, and product authenticity contracts

      // SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "@openzeppelin/contracts/access/Ownable.sol";

/**
 * @title SupplyChainTraceability
 * @author Nam
 * @notice Tracks product batches and supply chain events on-chain
 */
contract SupplyChainTraceability is Ownable {

    struct Batch {
        string metadataURI; // batch details
        address currentOwner;
        uint256 creationTimestamp;
        bool exists;
    }

    struct Event {
        uint256 timestamp;
        string description;
        address performedBy;
    }

    mapping(uint256 => Batch) public batches;
    mapping(uint256 => Event[]) public batchEvents;
    uint256 public batchCount;

    mapping(address => bool) public registeredParticipants;

    // -------------------- EVENTS --------------------

    event ParticipantRegistered(address indexed participant);
    event BatchCreated(uint256 indexed batchId, address owner);
    event EventLogged(uint256 indexed batchId, string description, address indexed performer);
    event BatchTransferred(uint256 indexed batchId, address from, address to);

    // -------------------- PARTICIPANT MANAGEMENT --------------------

    function registerParticipant(address _participant) external onlyOwner {
        registeredParticipants[_participant] = true;
        emit ParticipantRegistered(_participant);
    }

    function isParticipant(address _participant) public view returns (bool) {
        return registeredParticipants[_participant];
    }

    // -------------------- BATCH MANAGEMENT --------------------

    function createBatch(string calldata _metadataURI) external {
        require(isParticipant(msg.sender), "Not a registered participant");

        batchCount += 1;
        batches[batchCount] = Batch({
            metadataURI: _metadataURI,
            currentOwner: msg.sender,
            creationTimestamp: block.timestamp,
            exists: true
        });

        emit BatchCreated(batchCount, msg.sender);
    }

    function logEvent(uint256 _batchId, string calldata _description) external {
        require(isParticipant(msg.sender), "Not a registered participant");
        require(batches[_batchId].exists, "Batch does not exist");

        batchEvents[_batchId].push(Event({
            timestamp: block.timestamp,
            description: _description,
            performedBy: msg.sender
        }));

        emit EventLogged(_batchId, _description, msg.sender);
    }

    function transferBatch(uint256 _batchId, address _to) external {
        require(isParticipant(msg.sender), "Not a registered participant");
        require(batches[_batchId].currentOwner == msg.sender, "Not batch owner");
        require(isParticipant(_to), "Recipient not registered");

        address from = batches[_batchId].currentOwner;
        batches[_batchId].currentOwner = _to;

        emit BatchTransferred(_batchId, from, _to);
    }

    // -------------------- VIEW FUNCTIONS --------------------

    function getBatchEvents(uint256 _batchId) external view returns (Event[] memory) {
        return batchEvents[_batchId];
    }

    function getBatchOwner(uint256 _batchId) external view returns (address) {
        return batches[_batchId].currentOwner;
    }
}