// SPDX-License-Identifier: MIT pragma solidity ^0.8.19; /** * @title MainnetTether * @notice Anchors Chain-138 state proofs to Ethereum Mainnet (Kaleido-style) * @dev Stores signed state proofs from Chain-138 validators, creating an immutable * and verifiable record of Chain-138's state on Mainnet */ contract MainnetTether { address public admin; bool public paused; // Chain-138 chain ID uint64 public constant CHAIN_138 = 138; // State proof structure struct StateProof { uint256 blockNumber; // Chain-138 block number bytes32 blockHash; // Chain-138 block hash bytes32 stateRoot; // Chain-138 state root bytes32 previousBlockHash; // Previous block hash uint256 timestamp; // Block timestamp bytes signatures; // Collective signatures from validators uint256 validatorCount; // Number of validators that signed bytes32 proofHash; // Hash of the proof (for indexing) } // Mapping: blockNumber => StateProof mapping(uint256 => StateProof) public stateProofs; // Array of all anchored block numbers (for iteration) uint256[] public anchoredBlocks; // Mapping: proofHash => bool (replay protection) mapping(bytes32 => bool) public processed; // Events event AdminChanged(address indexed newAdmin); event Paused(); event Unpaused(); event StateProofAnchored( uint256 indexed blockNumber, bytes32 indexed blockHash, bytes32 indexed stateRoot, uint256 timestamp, uint256 validatorCount ); modifier onlyAdmin() { require(msg.sender == admin, "only admin"); _; } modifier whenNotPaused() { require(!paused, "paused"); _; } constructor(address _admin) { require(_admin != address(0), "zero admin"); admin = _admin; } /** * @notice Anchor a state proof from Chain-138 * @param blockNumber Chain-138 block number * @param blockHash Chain-138 block hash * @param stateRoot Chain-138 state root * @param previousBlockHash Previous block hash * @param timestamp Block timestamp * @param signatures Collective signatures from validators * @param validatorCount Number of validators that signed */ function anchorStateProof( uint256 blockNumber, bytes32 blockHash, bytes32 stateRoot, bytes32 previousBlockHash, uint256 timestamp, bytes calldata signatures, uint256 validatorCount ) external onlyAdmin whenNotPaused { require(blockNumber > 0, "invalid block"); require(blockHash != bytes32(0), "invalid hash"); require(stateRoot != bytes32(0), "invalid state root"); require(validatorCount > 0, "no validators"); require(signatures.length > 0, "no signatures"); // Calculate proof hash for replay protection bytes32 proofHash = keccak256( abi.encodePacked( blockNumber, blockHash, stateRoot, previousBlockHash, timestamp, signatures ) ); require(!processed[proofHash], "already processed"); require(stateProofs[blockNumber].blockNumber == 0, "block already anchored"); // Store state proof stateProofs[blockNumber] = StateProof({ blockNumber: blockNumber, blockHash: blockHash, stateRoot: stateRoot, previousBlockHash: previousBlockHash, timestamp: timestamp, signatures: signatures, validatorCount: validatorCount, proofHash: proofHash }); anchoredBlocks.push(blockNumber); processed[proofHash] = true; emit StateProofAnchored( blockNumber, blockHash, stateRoot, timestamp, validatorCount ); } /** * @notice Get state proof for a specific block * @param blockNumber Chain-138 block number * @return proof State proof structure */ function getStateProof(uint256 blockNumber) external view returns (StateProof memory proof) { require(stateProofs[blockNumber].blockNumber != 0, "not anchored"); return stateProofs[blockNumber]; } /** * @notice Check if a block is anchored * @param blockNumber Chain-138 block number * @return true if anchored */ function isAnchored(uint256 blockNumber) external view returns (bool) { return stateProofs[blockNumber].blockNumber != 0; } /** * @notice Get total number of anchored blocks * @return count Number of anchored blocks */ function getAnchoredBlockCount() external view returns (uint256) { return anchoredBlocks.length; } /** * @notice Get anchored block number at index * @param index Index in anchoredBlocks array * @return blockNumber Block number */ function getAnchoredBlock(uint256 index) external view returns (uint256) { require(index < anchoredBlocks.length, "out of bounds"); return anchoredBlocks[index]; } /** * @notice Admin functions */ 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(); } }