// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol"; /** * @title ISO20022IntakeGateway * @notice On-chain entry for canonical ISO 20022 / MT-103 mapped instructions (idempotent by instructionId). * @dev Full MX/MT payloads remain off-chain; keyed by payloadHash. See SMART_CONTRACTS_ISO20022_FIN_METHODOLOGY.md */ contract ISO20022IntakeGateway is AccessControl { bytes32 public constant RELAYER_ROLE = keccak256("RELAYER_ROLE"); struct CanonicalMessage { bytes32 instructionId; bytes32 endToEndIdHash; bytes32 uetr; bytes32 payloadHash; bytes32 debtorRefHash; bytes32 creditorRefHash; bytes32 purposeHash; uint8 msgTypeCode; address token; uint256 amount; bytes3 currencyCode; } mapping(bytes32 => bool) public processedInstructions; mapping(bytes32 => CanonicalMessage) public inboundMessages; event InboundAccepted( bytes32 indexed instructionId, bytes32 indexed uetr, bytes32 payloadHash, uint8 msgTypeCode, address token, uint256 amount, bytes3 currencyCode, address indexed relayer ); event CCIPRouterUpdated(address router); address public ccipRouter; constructor(address admin) { _grantRole(DEFAULT_ADMIN_ROLE, admin); _grantRole(RELAYER_ROLE, admin); } function setCCIPRouter(address router) external onlyRole(DEFAULT_ADMIN_ROLE) { ccipRouter = router; emit CCIPRouterUpdated(router); } function submitInbound(CanonicalMessage calldata m) external onlyRole(RELAYER_ROLE) { require(m.instructionId != bytes32(0), "instructionId"); require(m.payloadHash != bytes32(0), "payloadHash"); require(!processedInstructions[m.instructionId], "duplicate instruction"); processedInstructions[m.instructionId] = true; inboundMessages[m.instructionId] = m; emit InboundAccepted( m.instructionId, m.uetr, m.payloadHash, m.msgTypeCode, m.token, m.amount, m.currencyCode, msg.sender ); } }