ListingOracle
0x64e7d62c20c5310361fa56050d7a2600f236e1b6
Verification
Verified
v0.8.28+commit.7893614a
Type
Contract
2,959 bytes
ABI entries
32
10 read · 8 write
License
none
Contract information
- Address
- 0x64e7d62c20c5310361fa56050d7a2600f236e1b6
- Chain
- Robinhood Chain (4663)
- Compiler
- v0.8.28+commit.7893614a
- Optimization
- Enabled
- Creator
- 0x6fef3335F4…14e42C2128
- Creation tx
- 0xdf1acd3f9f…9d6d9e3f11
Token
Not a token
This contract does not expose ERC-20 metadata.
Read contract (10)
DEFAULT_ADMIN_ROLE() → bytes32
PAUSER_ROLE() → bytes32
REPORTER_ROLE() → bytes32
getRoleAdmin(bytes32) → bytes32
hasRole(bytes32, address) → bool
isListed(uint256) → bool
lastUpdated(uint256) → uint64
paused() → bool
staking() → address
supportsInterface(bytes4) → bool
Events (7)
ListingUpdatedPausedRoleAdminChangedRoleGrantedRoleRevokedStakingSetUnpaused
ABI
[
{
"inputs": [
{
"internalType": "address",
"name": "admin",
"type": "address"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"inputs": [],
"name": "AccessControlBadConfirmation",
"type": "error"
},
{
"inputs": [
{
"internalType": "address",
"name": "account",
"type": "address"
},
{
"internalType": "bytes32",
"name": "neededRole",
"type": "bytes32"
}
],
"name": "AccessControlUnauthorizedAccount",
"type": "error"
},
{
"inputs": [],
"name": "EnforcedPause",
"type": "error"
},
{
"inputs": [],
"name": "ExpectedPause",
"type": "error"
},
{
"inputs": [],
"name": "LengthMismatch",
"type": "error"
},
{
"inputs": [],
"name": "ZeroAddress",
"type": "error"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
},
{
"indexed": false,
"internalType": "bool",
"name": "listed",
"type": "bool"
},
{
"indexed": false,
"internalType": "uint64",
"name": "updatedAt",
"type": "uint64"
}
],
"name": "ListingUpdated",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "address",
"name": "account",
"type": "address"
}
],
"name": "Paused",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "bytes32",
"name": "role",
"type": "bytes32"
},
{
"indexed": true,
"internalType": "bytes32",
"name": "previousAdminRole",
"type": "bytes32"
},
{
"indexed": true,
"internalType": "bytes32",
"name": "newAdminRole",
"type": "bytes32"
}
],
"name": "RoleAdminChanged",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "bytes32",
"name": "role",
"type": "bytes32"
},
{
"indexed": true,
"internalType": "address",
"name": "account",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "sender",
"type": "address"
}
],
"name": "RoleGranted",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "bytes32",
"name": "role",
"type": "bytes32"
},
{
"indexed": true,
"internalType": "address",
"name": "account",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "sender",
"type": "address"
}
],
"name": "RoleRevoked",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "staking",
"type": "address"
}
],
"name": "StakingSet",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "address",
"name": "account",
"type": "address"
}
],
"name": "Unpaused",
"type": "event"
},
{
"inputs": [],
"name": "DEFAULT_ADMIN_ROLE",
"outputs": [
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "PAUSER_ROLE",
"outputs": [
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "REPORTER_ROLE",
"outputs": [
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "role",
"type": "bytes32"
}
],
"name": "getRoleAdmin",
"outputs": [
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "role",
"type": "bytes32"
},
{
"internalType": "address",
"name": "account",
"type": "address"
}
],
"name": "grantRole",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "role",
"type": "bytes32"
},
{
"internalType": "address",
"name": "account",
"type": "address"
}
],
"name": "hasRole",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"name": "isListed",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"name": "lastUpdated",
"outputs": [
{
"internalType": "uint64",
"name": "",
"type": "uint64"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "pause",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "paused",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "role",
"type": "bytes32"
},
{
"internalType": "address",
"name": "callerConfirmation",
"type": "address"
}
],
"name": "renounceRole",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "role",
"type": "bytes32"
},
{
"internalType": "address",
"name": "account",
"type": "address"
}
],
"name": "revokeRole",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
},
{
"internalType": "bool",
"name": "listed",
"type": "bool"
}
],
"name": "setListed",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256[]",
"name": "tokenIds",
"type": "uint256[]"
},
{
"internalType": "bool[]",
"name": "listed",
"type": "bool[]"
}
],
"name": "setListedBatch",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "staking_",
"type": "address"
}
],
"name": "setStaking",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "staking",
"outputs": [
{
"internalType": "contract IHoodilioStaking",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes4",
"name": "interfaceId",
"type": "bytes4"
}
],
"name": "supportsInterface",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "unpause",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
}
]Source code
// SPDX-License-Identifier: MIT
pragma solidity 0.8.28;
import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol";
import {Pausable} from "@openzeppelin/contracts/utils/Pausable.sol";
import {IHoodilioStaking} from "./interfaces/IHoodilioStaking.sol";
/**
* @title ListingOracle
* @notice On-chain mirror of OpenSea listing state, updated by an off-chain reporter bot.
*/
contract ListingOracle is AccessControl, Pausable {
bytes32 public constant REPORTER_ROLE = keccak256("REPORTER_ROLE");
bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");
IHoodilioStaking public staking;
mapping(uint256 => bool) public isListed;
mapping(uint256 => uint64) public lastUpdated;
event StakingSet(address indexed staking);
event ListingUpdated(uint256 indexed tokenId, bool listed, uint64 updatedAt);
error LengthMismatch();
error ZeroAddress();
constructor(address admin) {
if (admin == address(0)) revert ZeroAddress();
_grantRole(DEFAULT_ADMIN_ROLE, admin);
_grantRole(PAUSER_ROLE, admin);
}
function setStaking(address staking_) external onlyRole(DEFAULT_ADMIN_ROLE) {
if (staking_ == address(0)) revert ZeroAddress();
staking = IHoodilioStaking(staking_);
emit StakingSet(staking_);
}
function setListed(uint256 tokenId, bool listed) external onlyRole(REPORTER_ROLE) whenNotPaused {
_applyListing(tokenId, listed);
}
function setListedBatch(uint256[] calldata tokenIds, bool[] calldata listed)
external
onlyRole(REPORTER_ROLE)
whenNotPaused
{
uint256 len = tokenIds.length;
if (len != listed.length) revert LengthMismatch();
for (uint256 i = 0; i < len; ) {
_applyListing(tokenIds[i], listed[i]);
unchecked {
++i;
}
}
}
function pause() external onlyRole(PAUSER_ROLE) {
_pause();
}
function unpause() external onlyRole(PAUSER_ROLE) {
_unpause();
}
function _applyListing(uint256 tokenId, bool listed) internal {
lastUpdated[tokenId] = uint64(block.timestamp);
if (isListed[tokenId] == listed) {
return;
}
if (listed) {
// Notify staking while still unlisted so pending points sync first.
if (address(staking) != address(0)) {
staking.onListingStatusChanged(tokenId, true);
}
isListed[tokenId] = true;
} else {
isListed[tokenId] = false;
if (address(staking) != address(0)) {
staking.onListingStatusChanged(tokenId, false);
}
}
emit ListingUpdated(tokenId, listed, lastUpdated[tokenId]);
}
}
Chain explorer2208msChain node81ms