// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; /** * @title AddressActivityRegistryV2 * @notice MT-103 / pacs.008-equivalent attestation on mainnet (indexed instructionId + UETR + payloadHash). * @dev Complements V1 registry; does not move mainnet ETH. */ contract AddressActivityRegistryV2 { uint64 public constant CHAIN_ID = 138; uint8 public constant MSG_MT103 = 1; uint8 public constant MSG_PACS008 = 2; uint8 public constant MSG_PAIN001 = 3; uint8 public constant MSG_CHAIN138_SYNTH = 4; address public admin; bool public paused; struct IsoAttestationRecord { bytes32 txHash; address from; address to; uint256 valueWei; uint256 blockNumber138; uint64 blockTimestamp138; uint64 valueUsdE8; uint32 logCount; bytes32 receiptHash; bytes32 instructionId; bytes32 endToEndIdHash; bytes32 uetr; bytes32 payloadHash; uint8 msgTypeCode; bytes32 debtorRefHash; bytes32 creditorRefHash; bytes32 purposeHash; } mapping(bytes32 => bool) public recorded; mapping(bytes32 => IsoAttestationRecord) public attestations; mapping(address => bytes32[]) public participantTxHashes; mapping(bytes32 => bool) public processedInstructions; uint256 public totalRecorded; event AdminChanged(address indexed newAdmin); event Paused(); event Unpaused(); /// @param chain138TxHash Chain 138 tx (topic1 on Etherscan) /// @param instructionId MT :20 / pacs.008 InstrId (hashed if needed) /// @param uetr SWIFT UETR (bytes32; zero if N/A) event PaymentAttested( bytes32 indexed chain138TxHash, bytes32 indexed instructionId, address indexed creditor, address debtor, bytes32 uetr, uint64 batchId, uint8 msgTypeCode, uint256 valueWei, uint64 valueUsdE8, bytes32 payloadHash, bytes32 endToEndIdHash, bytes32 debtorRefHash, bytes32 creditorRefHash, bytes32 purposeHash, bytes32 receiptHash, uint256 blockNumber138 ); event BatchIsoAttestationRecorded(uint64 indexed batchId, uint256 count, bytes32 batchPayloadRoot); modifier onlyAdmin() { require(msg.sender == admin, "only admin"); _; } modifier whenNotPaused() { require(!paused, "paused"); _; } constructor(address _admin) { require(_admin != address(0), "zero admin"); admin = _admin; } function setAdmin(address newAdmin) external onlyAdmin { require(newAdmin != address(0), "zero admin"); admin = newAdmin; emit AdminChanged(newAdmin); } function pause() external onlyAdmin { paused = true; emit Paused(); } function unpause() external onlyAdmin { paused = false; emit Unpaused(); } function recordBatch(uint64 batchId, IsoAttestationRecord[] calldata records) external onlyAdmin whenNotPaused { uint256 n = records.length; bytes32 batchRoot; for (uint256 i = 0; i < n; i++) { IsoAttestationRecord calldata r = records[i]; require(r.txHash != bytes32(0), "invalid hash"); require(!recorded[r.txHash], "already recorded"); require(!processedInstructions[r.instructionId], "instruction used"); recorded[r.txHash] = true; processedInstructions[r.instructionId] = true; attestations[r.txHash] = r; totalRecorded++; participantTxHashes[r.from].push(r.txHash); participantTxHashes[r.to].push(r.txHash); batchRoot = keccak256(abi.encodePacked(batchRoot, r.payloadHash)); emit PaymentAttested( r.txHash, r.instructionId, r.to, r.from, r.uetr, batchId, r.msgTypeCode, r.valueWei, r.valueUsdE8, r.payloadHash, r.endToEndIdHash, r.debtorRefHash, r.creditorRefHash, r.purposeHash, r.receiptHash, r.blockNumber138 ); } emit BatchIsoAttestationRecorded(batchId, n, batchRoot); } function getAttestation(bytes32 txHash) external view returns (IsoAttestationRecord memory) { return attestations[txHash]; } }