// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; /** * @title AddressActivityRegistry * @notice Emits Etherscan-indexed Chain 138 payment activity tied to participant addresses (ETH wei + USD e8). * @dev Called by checkpoint aggregator after each batch submit and optional v1 TransactionMirror dual-write. * Does not move mainnet ETH — attestation / visibility only. */ contract AddressActivityRegistry { uint64 public constant CHAIN_ID = 138; uint8 public constant USD_DECIMALS = 8; address public admin; bool public paused; struct ActivityRecord { bytes32 txHash; address from; address to; uint256 valueWei; uint256 blockNumber138; uint64 blockTimestamp138; uint64 valueUsdE8; uint32 logCount; bytes32 receiptHash; } mapping(bytes32 => bool) public recorded; mapping(address => bytes32[]) public participantTxHashes; mapping(bytes32 => ActivityRecord) public activities; uint256 public totalRecorded; event AdminChanged(address indexed newAdmin); event Paused(); event Unpaused(); /// @notice Indexed for Etherscan lookup by participant address (topic2 = participant). /// @param chain138TxHash Chain 138 transaction hash (view at explorer.d-bis.org/tx/0x…) event ParticipantDebited( bytes32 indexed chain138TxHash, address indexed counterparty, address indexed participant, uint64 batchId, uint256 valueWei, uint64 valueUsdE8, uint32 logCount, bytes32 receiptHash, uint256 blockNumber138 ); /// @param chain138TxHash Chain 138 transaction hash (view at explorer.d-bis.org/tx/0x…) event ParticipantCredited( bytes32 indexed chain138TxHash, address indexed participant, address indexed counterparty, uint64 batchId, uint256 valueWei, uint64 valueUsdE8, uint32 logCount, bytes32 receiptHash, uint256 blockNumber138 ); event BatchActivityRecorded(uint64 indexed batchId, uint256 count, uint256 totalValueWei, uint64 totalUsdE8); 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(); } /** * @notice Record Chain 138 activity for a checkpoint batch (one mainnet tx per batch). * @param batchId Checkpoint hub batch id * @param records Parallel activity rows (from 138 txs + off-chain USD enrichment) */ function recordBatch(uint64 batchId, ActivityRecord[] calldata records) external onlyAdmin whenNotPaused { uint256 batchValueWei; uint64 batchUsdE8; uint256 n = records.length; for (uint256 i = 0; i < n; i++) { ActivityRecord calldata r = records[i]; require(r.txHash != bytes32(0), "invalid hash"); require(!recorded[r.txHash], "already recorded"); recorded[r.txHash] = true; activities[r.txHash] = r; totalRecorded++; batchValueWei += r.valueWei; batchUsdE8 += r.valueUsdE8; participantTxHashes[r.from].push(r.txHash); participantTxHashes[r.to].push(r.txHash); emit ParticipantDebited( r.txHash, r.to, r.from, batchId, r.valueWei, r.valueUsdE8, r.logCount, r.receiptHash, r.blockNumber138 ); emit ParticipantCredited( r.txHash, r.to, r.from, batchId, r.valueWei, r.valueUsdE8, r.logCount, r.receiptHash, r.blockNumber138 ); } emit BatchActivityRecorded(batchId, n, batchValueWei, batchUsdE8); } function getParticipantTxCount(address participant) external view returns (uint256) { return participantTxHashes[participant].length; } function getParticipantTxHash(address participant, uint256 index) external view returns (bytes32) { return participantTxHashes[participant][index]; } function getActivity(bytes32 txHash) external view returns (ActivityRecord memory) { return activities[txHash]; } }