// SPDX-License-Identifier: MIT pragma solidity 0.8.23; import "@openzeppelin/contracts/access/Ownable.sol"; /** * @title ChainalysisOracleWrapper — read-only proxy to Chainalysis's free * on-chain sanctions oracle * @notice Chainalysis publishes a free, public sanctions oracle on Ethereum * mainnet (and other major chains) exposing `isSanctioned(address)`. * AERE wraps that oracle on Chain 2800 by holding the deployed * address as an immutable, providing the same interface, and * letting AERE dApps + institutional integrators query a SINGLE * endpoint without having to know the upstream provider's address. * * This contract makes a forwarding STATICCALL to the upstream * oracle. If the upstream rejects (e.g. paused, migrated, doesn't * exist on AERE yet), this contract returns false rather than * reverting — failing closed but not bricking the dApp. * * @dev Phase 1 (Q3 2026 ship): * - UPSTREAM_ORACLE is settable ONCE by Foundation owner via * setUpstream() and immutable after `lockUpstream()`. * - Foundation MUST call lockUpstream() before declaring the * registry "production-ready". Pre-lock, the contract is * considered configurable. * * Phase 2 (Q1 2027): * - Multiple upstream oracles polled, response aggregated via * "ANY-true" rule (sanctioned by any source ⇒ sanctioned). * - Per-source attestation events for transparency. * * FORWARD INTERFACE: bool isSanctioned(address) * (matches Chainalysis canonical SanctionsList contract) */ interface IChainalysisUpstream { function isSanctioned(address account) external view returns (bool); } contract ChainalysisOracleWrapper is Ownable { address public upstreamOracle; bool public locked; event UpstreamSet(address oldOracle, address newOracle); event UpstreamLocked(address finalOracle); error AlreadyLocked(); error ZeroAddress(); function setUpstream(address newOracle) external onlyOwner { if (locked) revert AlreadyLocked(); if (newOracle == address(0)) revert ZeroAddress(); emit UpstreamSet(upstreamOracle, newOracle); upstreamOracle = newOracle; } function lockUpstream() external onlyOwner { if (locked) revert AlreadyLocked(); if (upstreamOracle == address(0)) revert ZeroAddress(); locked = true; emit UpstreamLocked(upstreamOracle); } /// @notice Returns true if the upstream Chainalysis oracle reports /// `account` as sanctioned. Returns false on any upstream /// error (failing closed but never reverting the consumer). function isSanctioned(address account) external view returns (bool sanctioned) { address up = upstreamOracle; if (up == address(0)) return false; try IChainalysisUpstream(up).isSanctioned(account) returns (bool s) { return s; } catch { return false; } } }