// SPDX-License-Identifier: MIT pragma solidity ^0.8.19; import "@openzeppelin/contracts/access/AccessControl.sol"; import "./interfaces/IPacketRegistry.sol"; /** * @title PacketRegistry * @notice Records packet lifecycle events for non-scheme participants * @dev Tracks packet generation, dispatch, and acknowledgment linked to ChainID 138 triggers * Provides tamper-evident audit trail for instruction packets sent via secure email, AS4, or PDF */ contract PacketRegistry is IPacketRegistry, AccessControl { bytes32 public constant PACKET_OPERATOR_ROLE = keccak256("PACKET_OPERATOR_ROLE"); // triggerId => latest packet info mapping(uint256 => PacketInfo) private _packets; struct PacketInfo { bytes32 payloadHash; bytes32 mode; bytes32 channel; bytes32 messageRef; bytes32 receiptRef; bytes32 status; bool generated; bool dispatched; bool acknowledged; } /** * @notice Initializes the registry with an admin address * @param admin Address that will receive DEFAULT_ADMIN_ROLE */ constructor(address admin) { _grantRole(DEFAULT_ADMIN_ROLE, admin); } /** * @notice Records that a packet has been generated * @dev Requires PACKET_OPERATOR_ROLE * @param triggerId The trigger ID from RailTriggerRegistry * @param payloadHash SHA-256 hash of the packet payload * @param mode Transmission mode (e.g., "PDF", "EMAIL", "AS4") */ function recordGenerated( uint256 triggerId, bytes32 payloadHash, bytes32 mode ) external override onlyRole(PACKET_OPERATOR_ROLE) { require(triggerId > 0, "PacketRegistry: zero triggerId"); require(payloadHash != bytes32(0), "PacketRegistry: zero payloadHash"); require(mode != bytes32(0), "PacketRegistry: zero mode"); require(!_packets[triggerId].generated, "PacketRegistry: already generated"); _packets[triggerId].payloadHash = payloadHash; _packets[triggerId].mode = mode; _packets[triggerId].generated = true; emit PacketGenerated(triggerId, payloadHash, mode); } /** * @notice Records that a packet has been dispatched via a channel * @dev Requires PACKET_OPERATOR_ROLE. Packet must have been generated first. * @param triggerId The trigger ID from RailTriggerRegistry * @param channel The dispatch channel (e.g., "EMAIL", "AS4", "PORTAL") * @param messageRef The message reference ID from the transport layer */ function recordDispatched( uint256 triggerId, bytes32 channel, bytes32 messageRef ) external override onlyRole(PACKET_OPERATOR_ROLE) { require(triggerId > 0, "PacketRegistry: zero triggerId"); require(channel != bytes32(0), "PacketRegistry: zero channel"); require(messageRef != bytes32(0), "PacketRegistry: zero messageRef"); require(_packets[triggerId].generated, "PacketRegistry: not generated"); require(!_packets[triggerId].dispatched, "PacketRegistry: already dispatched"); _packets[triggerId].channel = channel; _packets[triggerId].messageRef = messageRef; _packets[triggerId].dispatched = true; emit PacketDispatched(triggerId, channel, messageRef); } /** * @notice Records that a packet has been acknowledged by the recipient * @dev Requires PACKET_OPERATOR_ROLE. Packet must have been dispatched first. * @param triggerId The trigger ID from RailTriggerRegistry * @param receiptRef The receipt reference ID from the recipient * @param status The acknowledgment status (e.g., "RECEIVED", "ACCEPTED", "REJECTED") */ function recordAcknowledged( uint256 triggerId, bytes32 receiptRef, bytes32 status ) external override onlyRole(PACKET_OPERATOR_ROLE) { require(triggerId > 0, "PacketRegistry: zero triggerId"); require(receiptRef != bytes32(0), "PacketRegistry: zero receiptRef"); require(status != bytes32(0), "PacketRegistry: zero status"); require(_packets[triggerId].dispatched, "PacketRegistry: not dispatched"); require(!_packets[triggerId].acknowledged, "PacketRegistry: already acknowledged"); _packets[triggerId].receiptRef = receiptRef; _packets[triggerId].status = status; _packets[triggerId].acknowledged = true; emit PacketAcknowledged(triggerId, receiptRef, status); } /** * @notice Returns packet information for a trigger * @param triggerId The trigger ID * @return payloadHash The payload hash * @return mode The transmission mode * @return channel The dispatch channel * @return messageRef The message reference * @return receiptRef The receipt reference * @return status The acknowledgment status * @return generated Whether packet was generated * @return dispatched Whether packet was dispatched * @return acknowledged Whether packet was acknowledged */ function getPacketInfo( uint256 triggerId ) external view returns ( bytes32 payloadHash, bytes32 mode, bytes32 channel, bytes32 messageRef, bytes32 receiptRef, bytes32 status, bool generated, bool dispatched, bool acknowledged ) { PacketInfo memory info = _packets[triggerId]; return ( info.payloadHash, info.mode, info.channel, info.messageRef, info.receiptRef, info.status, info.generated, info.dispatched, info.acknowledged ); } }