// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC165 standard, as defined in the
* https://eips.ethereum.org/EIPS/eip-165[EIP].
*
* Implementers can declare support of contract interfaces, which can then be
* queried by others ({ERC165Checker}).
*
* For an implementation, see {ERC165}.
*/
interface IERC165 {
/**
* @dev Returns true if this contract implements the interface defined by
* `interfaceId`. See the corresponding
* https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
* to learn more about how these ids are created.
*
* This function call must use less than 30 000 gas.
*/
function supportsInterface(bytes4 interfaceId) external view returns (bool);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC721/IERC721.sol)
pragma solidity ^0.8.0;
import "../../utils/introspection/IERC165.sol";
/**
* @dev Required interface of an ERC721 compliant contract.
*/
interface IERC721 is IERC165 {
/**
* @dev Emitted when `tokenId` token is transferred from `from` to `to`.
*/
event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
/**
* @dev Emitted when `owner` enables `approved` to manage the `tokenId` token.
*/
event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
/**
* @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets.
*/
event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
/**
* @dev Returns the number of tokens in ``owner``'s account.
*/
function balanceOf(address owner) external view returns (uint256 balance);
/**
* @dev Returns the owner of the `tokenId` token.
*
* Requirements:
*
* - `tokenId` must exist.
*/
function ownerOf(uint256 tokenId) external view returns (address owner);
/**
* @dev Safely transfers `tokenId` token from `from` to `to`.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `tokenId` token must exist and be owned by `from`.
* - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
* - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
*
* Emits a {Transfer} event.
*/
function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external;
/**
* @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients
* are aware of the ERC721 protocol to prevent tokens from being forever locked.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `tokenId` token must exist and be owned by `from`.
* - If the caller is not `from`, it must have been allowed to move this token by either {approve} or {setApprovalForAll}.
* - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
*
* Emits a {Transfer} event.
*/
function safeTransferFrom(address from, address to, uint256 tokenId) external;
/**
* @dev Transfers `tokenId` token from `from` to `to`.
*
* WARNING: Note that the caller is responsible to confirm that the recipient is capable of receiving ERC721
* or else they may be permanently lost. Usage of {safeTransferFrom} prevents loss, though the caller must
* understand this adds an external call which potentially creates a reentrancy vulnerability.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `tokenId` token must be owned by `from`.
* - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
*
* Emits a {Transfer} event.
*/
function transferFrom(address from, address to, uint256 tokenId) external;
/**
* @dev Gives permission to `to` to transfer `tokenId` token to another account.
* The approval is cleared when the token is transferred.
*
* Only a single account can be approved at a time, so approving the zero address clears previous approvals.
*
* Requirements:
*
* - The caller must own the token or be an approved operator.
* - `tokenId` must exist.
*
* Emits an {Approval} event.
*/
function approve(address to, uint256 tokenId) external;
/**
* @dev Approve or remove `operator` as an operator for the caller.
* Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller.
*
* Requirements:
*
* - The `operator` cannot be the caller.
*
* Emits an {ApprovalForAll} event.
*/
function setApprovalForAll(address operator, bool approved) external;
/**
* @dev Returns the account approved for `tokenId` token.
*
* Requirements:
*
* - `tokenId` must exist.
*/
function getApproved(uint256 tokenId) external view returns (address operator);
/**
* @dev Returns if the `operator` is allowed to manage all of the assets of `owner`.
*
* See {setApprovalForAll}
*/
function isApprovedForAll(address owner, address operator) external view returns (bool);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
/*
_ __
_| | ____ / /__
_| | | / __ \/ //_/
| | | | / /_/ / ,< __
| | | | __ \____/_/|_| ____ ___ ____ __ __/ /____ __________
| | | |/ \ / ___/ __ \/ __ `__ \/ __ \/ / / / __/ _ \/ ___/ ___/
| /\ \ / /__/ /_/ / / / / / / /_/ / /_/ / /_/ __/ / (__ )
| / \/ \___/\____/_/ /_/ /_/ .___/\__,_/\__/\___/_/ /____/
| \ /\ /_/
| \/ /
\ / onchain communication and storage module
| / - by @dailofrog
| |
*/
import "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import {Ownable} from "solady/src/auth/Ownable.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
interface IOKComputerDataStoreCallback {
function onRegister(uint256 tokenId, bytes32 key, address sender) external; // tokenId = 0 for global
function onDisconnect(uint256 tokenId, bytes32 key, address sender) external;
function onDataStored(uint256 tokenId, bytes32 key, bytes calldata data, address sender) external;
function onDataRemoved(uint256 tokenId, bytes32 key, bytes calldata data, address sender) external;
}
interface IOKComputerMessageCallback {
function onRegister(uint256 tokenId, bytes32 key, address sender) external; // tokenId = 0 for global
function onDisconnect(uint256 tokenId, bytes32 key, address sender) external;
function onMessageSubmitted(uint256 tokenId, bytes32 key, string calldata message, uint256 metadata, address sender) external;
}
contract OKComputerStore is Ownable, ReentrancyGuard {
uint256 public constant MAX_SUPPLY = 5000;
IERC721 public immutable okComputerNFT;
// Mapping from tokenId to arbitrary data
mapping(uint256 => mapping(bytes32 => bytes)) private tokenData;
// keys that only admin can write to
mapping(bytes32 => bool) private adminWriteOnlyKeys;
uint256 defaultDataMaxLength = 1024;
uint256 defaultSubmitCooldown = 0 days;
bool public paused = false;
bool public allowTokenDataStoreCallback = false;
// Mapping for key-specific data max length and submit cooldown
mapping(bytes32 => uint256) private dataMaxLengthForKey;
mapping(bytes32 => uint256) private dataSubmitCooldownForKey;
// Events
event DataStored(uint256 indexed tokenId, bytes32 indexed key, address indexed sender, bytes data);
event DataRemoved(uint256 indexed tokenId, bytes32 indexed key, address indexed sender);
event MessageSubmitted(uint256 indexed tokenId, bytes32 indexed key, string message, uint256 metadata, address indexed sender);
event CallbackFailure(bytes32 indexed key, string functionName, address indexed callback);
// Events (Admin)
event AdminTokenIdBlocked(uint256 indexed tokenId);
event AdminTokenIdUnblocked(uint256 indexed tokenId);
event AdminWriteOnlyKeyAdded(bytes32 indexed key);
event AdminWriteOnlyKeyRemoved(bytes32 indexed key);
// Follower system events
event Follow(uint256 indexed followerTokenId, uint256 indexed followedTokenId);
event Unfollow(uint256 indexed followerTokenId, uint256 indexed unfollowedTokenId);
event MutualFriendStart(uint256 indexed tokenId1, uint256 indexed tokenId2);
event MutualFriendEnd(uint256 indexed tokenId1, uint256 indexed tokenId2);
struct Message {
// key name - identifies name of the key of messages via a getKey(str)
bytes32 key;
// ok computer token id
uint256 tokenId;
// metadata
uint256 timestamp;
address sender;
uint256 metadata; // (optional)
/// msg
string message;
}
// Mapping from key => array of messages
mapping(bytes32 => Message[]) private bucketMessages;
// Mapping to track the last message timestamp for each tokenId and key
mapping(uint256 => mapping(bytes32 => uint256)) private lastMessageTimestamp;
// Mapping to track blocked tokenIds
mapping(uint256 => bool) private blockedTokenIds;
// Follower system mappings
mapping(uint256 => mapping(uint256 => bool)) public isFollowing;
mapping(uint256 => uint256) public followingCount;
mapping(uint256 => uint256) public followerCount;
mapping(bytes32 => IOKComputerMessageCallback) public globalMessageCallback;
mapping(bytes32 => IOKComputerDataStoreCallback) public globalDataStoreCallback;
mapping(uint256 => mapping(bytes32 => IOKComputerDataStoreCallback)) public tokenDataStoreCallback;
event MessageCallbackRegistered(bytes32 key, address callback);
event DataStoreCallbackRegistered(uint256 tokenId, bytes32 key, address callback);
event TokenDataStoreCallbackRegistered(uint256 tokenId, bytes32 key, address callback);
constructor(address _okComputerNFT) {
okComputerNFT = IERC721(_okComputerNFT);
_initializeOwner(msg.sender);
string[] memory keys = new string[](3);
keys[0] = "html_allow_inline_scripts";
keys[1] = "html_allow_ext_scripts";
keys[2] = "html_allow_ext_images";
adminAddWriteOnlyKeys(keys);
setDataMaxLengthForKey(getKey("email"), 512);
setDataMaxLengthForKey(getKey("page"), 51200); // restrict to 50kb (for now)
}
modifier whenNotPaused() {
require(!paused, "Contract is paused");
_;
}
function getKey(string memory key) public pure returns (bytes32) {
return keccak256(abi.encodePacked(key));
}
function getDataLengthMax(bytes32 key) public view returns (uint256) {
uint256 maxLength = dataMaxLengthForKey[key];
return maxLength > 0 ? maxLength : defaultDataMaxLength;
}
function getDataSubmitCooldown(bytes32 key) public view returns (uint256) {
uint256 cooldown = dataSubmitCooldownForKey[key];
return cooldown > 0 ? cooldown : defaultSubmitCooldown;
}
//////////////////////////////////////////////
// CALLBACKS
//////////////////////////////////////////////
// only admin can set message callbacks
function registerGlobalMessageCallback(bytes32 key, address callback) public onlyOwner nonReentrant {
// disconnect existing callback
IOKComputerMessageCallback existingCallback = globalMessageCallback[key];
if (address(existingCallback) != address(0)) {
try existingCallback.onDisconnect(0, key, msg.sender) {
} catch {
emit CallbackFailure(key, "onDisconnect", address(existingCallback));
}
}
// set new callback
globalMessageCallback[key] = IOKComputerMessageCallback(callback);
if (callback != address(0)) {
try IOKComputerMessageCallback(callback).onRegister(0, key, msg.sender) {
} catch {
emit CallbackFailure(key, "onRegister", callback);
}
}
emit MessageCallbackRegistered(key, callback);
}
function registerGlobalDataStoreCallback(bytes32 key, address callback) public onlyOwner nonReentrant {
IOKComputerDataStoreCallback existingCallback = globalDataStoreCallback[key];
if (address(existingCallback) != address(0)) {
try existingCallback.onDisconnect(0, key, msg.sender) {
} catch {
emit CallbackFailure(key, "onDisconnect", address(existingCallback));
}
}
globalDataStoreCallback[key] = IOKComputerDataStoreCallback(callback);
if (callback != address(0)) {
try IOKComputerDataStoreCallback(callback).onRegister(0, key, msg.sender) {
} catch {
emit CallbackFailure(key, "onRegister", callback);
}
}
emit DataStoreCallbackRegistered(0, key, callback);
}
// because ownerOf(tokenId) can fail if owner is address(0)
function isTokenOwner(uint256 tokenId, address account) internal view returns (bool) {
try okComputerNFT.ownerOf(tokenId) returns (address owner) {
return owner == account;
} catch {
return false;
}
}
function registerTokenDataStoreCallback(uint256 tokenId, bytes32 key, address callback) external nonReentrant {
require(msg.sender == owner() || isTokenOwner(tokenId, msg.sender), "Not token owner");
require(msg.sender == owner() || allowTokenDataStoreCallback, "Token data store callback not allowed");
IOKComputerDataStoreCallback existingCallback = tokenDataStoreCallback[tokenId][key];
if (address(existingCallback) != address(0)) {
try existingCallback.onDisconnect(tokenId, key, msg.sender) {
} catch {
emit CallbackFailure(key, "onDisconnect", address(existingCallback));
}
}
tokenDataStoreCallback[tokenId][key] = IOKComputerDataStoreCallback(callback);
if (callback != address(0)) {
try IOKComputerDataStoreCallback(callback).onRegister(tokenId, key, msg.sender) {
} catch {
emit CallbackFailure(key, "onRegister", callback);
}
}
emit TokenDataStoreCallbackRegistered(tokenId, key, callback);
}
//////////////////////////////////////////////
// ADMIN
//////////////////////////////////////////////
function setDefaultDataMaxLength(uint256 _defaultDataMaxLength) external onlyOwner {
defaultDataMaxLength = _defaultDataMaxLength;
}
function setDefaultSubmitCooldown(uint256 _defaultSubmitCooldown) external onlyOwner {
defaultSubmitCooldown = _defaultSubmitCooldown;
}
function setDataMaxLengthForKey(bytes32 key, uint256 maxLength) public onlyOwner {
dataMaxLengthForKey[key] = maxLength;
}
function setDataSubmitCooldownForKey(bytes32 key, uint256 cooldown) public onlyOwner {
dataSubmitCooldownForKey[key] = cooldown;
}
function setDataMaxLengthForKeys(bytes32[] memory keys, uint256[] memory maxLengths) public onlyOwner {
require(keys.length == maxLengths.length, "Keys and maxLengths arrays must have the same length");
for (uint256 i = 0; i < keys.length; ++i) {
dataMaxLengthForKey[keys[i]] = maxLengths[i];
}
}
function setDataSubmitCooldownForKeys(bytes32[] memory keys, uint256[] memory cooldowns) public onlyOwner {
require(keys.length == cooldowns.length, "Keys and cooldowns arrays must have the same length");
for (uint256 i = 0; i < keys.length; ++i) {
dataSubmitCooldownForKey[keys[i]] = cooldowns[i];
}
}
function blockTokenIds(uint256[] calldata tokenIds) external onlyOwner {
for (uint256 i = 0; i < tokenIds.length; ++i) {
uint256 tokenId = tokenIds[i];
require(tokenId > 0 && tokenId <= MAX_SUPPLY, "Invalid token ID");
blockedTokenIds[tokenId] = true;
emit AdminTokenIdBlocked(tokenId);
}
}
function unblockTokenIds(uint256[] calldata tokenIds) external onlyOwner {
for (uint256 i = 0; i < tokenIds.length; ++i) {
uint256 tokenId = tokenIds[i];
require(tokenId > 0 && tokenId <= MAX_SUPPLY, "Invalid token ID");
blockedTokenIds[tokenId] = false;
emit AdminTokenIdUnblocked(tokenId);
}
}
function adminAddWriteOnlyKeys(string[] memory keys) public onlyOwner {
for (uint256 i = 0; i < keys.length; ++i) {
bytes32 keyHash = getKey(keys[i]);
adminWriteOnlyKeys[keyHash] = true;
emit AdminWriteOnlyKeyAdded(keyHash);
}
}
function adminRemoveWriteOnlyKeys(string[] memory keys) public onlyOwner {
for (uint256 i = 0; i < keys.length; ++i) {
bytes32 keyHash = getKey(keys[i]);
adminWriteOnlyKeys[keyHash] = false;
emit AdminWriteOnlyKeyRemoved(keyHash);
}
}
function setPause(bool _paused) external onlyOwner {
paused = _paused;
}
function setAllowTokenDataStoreCallback(bool _allow) external onlyOwner {
allowTokenDataStoreCallback = _allow;
}
//////////////////////////////////////////////
// MESSAGES
//////////////////////////////////////////////
function submitMessages(uint256[] calldata tokenIds, bytes32[] calldata keys, string[] calldata messages, uint256[] calldata metadata) external {
require(tokenIds.length == keys.length && keys.length == messages.length && messages.length == metadata.length, "Array length mismatch");
for (uint256 i = 0; i < tokenIds.length; ++i) {
submitMessage(tokenIds[i], keys[i], messages[i], metadata[i]);
}
}
function submitMessage(uint256 tokenId, bytes32 key, string memory text, uint256 metadata) public whenNotPaused nonReentrant {
bool isAdmin = msg.sender == owner();
require(isAdmin || tokenId > 0 && tokenId <= MAX_SUPPLY, "Invalid token ID");
require(isAdmin || !blockedTokenIds[tokenId], "Token ID is blocked");
// check data length
uint256 maxLength = getDataLengthMax(key);
require(isAdmin || maxLength == 0 || bytes(text).length <= maxLength, "Message too long");
// check keys
require(isAdmin || isTokenOwner(tokenId, msg.sender), "Not token owner");
require(isAdmin || !adminWriteOnlyKeys[key], "Key is admin write-only");
// check cooldowns
uint256 cooldown = getDataSubmitCooldown(key);
require(isAdmin || block.timestamp >= lastMessageTimestamp[tokenId][key] + cooldown, "Submit cooldown not met");
// Update state before external call to prevent reentrancy
bucketMessages[key].push(Message(key, tokenId, block.timestamp, msg.sender, metadata, text));
lastMessageTimestamp[tokenId][key] = block.timestamp;
emit MessageSubmitted(tokenId, key, text, metadata, msg.sender);
IOKComputerMessageCallback callback = globalMessageCallback[key];
if (callback != IOKComputerMessageCallback(address(0))) {
try callback.onMessageSubmitted(tokenId, key, text, metadata, msg.sender) {
} catch {
emit CallbackFailure(key, "onMessageSubmitted", address(callback));
}
}
}
function getMessages(bytes32 key) external view returns (Message[] memory) {
return bucketMessages[key];
}
function getMessageCount(bytes32 key) external view returns (uint256) {
return bucketMessages[key].length;
}
function getMessage(bytes32 key, uint256 index) external view returns (Message memory) {
return bucketMessages[key][index];
}
//////////////////////////////////////////////
// STORE DATA
//////////////////////////////////////////////
function storeDataMany(uint256[] calldata tokenIds, bytes32[] calldata keys, bytes[] calldata data) external {
require(tokenIds.length == keys.length && keys.length == data.length, "Array length mismatch");
for (uint256 i = 0; i < tokenIds.length; ++i) {
storeData(tokenIds[i], keys[i], data[i]);
}
}
function storeData(uint256 tokenId, bytes32 key, bytes memory data) public whenNotPaused nonReentrant {
bool isAdmin = msg.sender == owner();
require(isAdmin || tokenId > 0 && tokenId <= MAX_SUPPLY, "Invalid token ID");
require(isAdmin || !blockedTokenIds[tokenId], "Token ID is blocked");
require(isAdmin || isTokenOwner(tokenId, msg.sender), "Not token owner");
require(isAdmin || !adminWriteOnlyKeys[key], "Key is admin write-only");
// check datasize
uint256 maxLength = getDataLengthMax(key);
require(isAdmin || maxLength == 0 || data.length <= maxLength, "Data size exceeds limit");
// check cooldown timestamp too
uint256 cooldown = getDataSubmitCooldown(key);
require(isAdmin || block.timestamp >= lastMessageTimestamp[tokenId][key] + cooldown, "Submit cooldown not met");
// Update state before external call to prevent reentrancy
lastMessageTimestamp[tokenId][key] = block.timestamp;
tokenData[tokenId][key] = data;
emit DataStored(tokenId, key, msg.sender, data);
IOKComputerDataStoreCallback globalCallback = globalDataStoreCallback[key];
if (globalCallback != IOKComputerDataStoreCallback(address(0))) {
try globalCallback.onDataStored(tokenId, key, data, msg.sender) {
} catch {
emit CallbackFailure(key, "onDataStored", address(globalCallback));
}
}
IOKComputerDataStoreCallback tokenCallback = tokenDataStoreCallback[tokenId][key];
if (tokenCallback != IOKComputerDataStoreCallback(address(0))) {
try tokenCallback.onDataStored(tokenId, key, data, msg.sender) {
} catch {
emit CallbackFailure(key, "onDataStored", address(tokenCallback));
}
}
}
function storeString(uint256 tokenId, bytes32 key, string memory data) external {
storeData(tokenId, key, abi.encode(data));
}
// Store boolean data for a token
function storeBool(uint256 tokenId, bytes32 key, bool data) external {
storeData(tokenId, key, abi.encode(data));
}
// Store integer data for a token
function storeInt(uint256 tokenId, bytes32 key, int256 data) external {
storeData(tokenId, key, abi.encode(data));
}
//////////////////////////////////////////////
// GET DATA
//////////////////////////////////////////////
function getData(uint256 tokenId, bytes32 key) public view returns (bytes memory) {
require(tokenId > 0 && tokenId <= MAX_SUPPLY, "Invalid token ID");
return tokenData[tokenId][key];
}
function getString(uint256 tokenId, bytes32 key) external view returns (string memory) {
bytes memory data = getData(tokenId, key);
require(data.length > 0, "No data found");
return abi.decode(data, (string));
}
function getBool(uint256 tokenId, bytes32 key) external view returns (bool) {
bytes memory data = getData(tokenId, key);
require(data.length > 0, "No data found");
return abi.decode(data, (bool));
}
function getInt(uint256 tokenId, bytes32 key) external view returns (int256) {
bytes memory data = getData(tokenId, key);
require(data.length > 0, "No data found");
return abi.decode(data, (int256));
}
function getStringOrDefault(uint256 tokenId, bytes32 key, string memory defaultValue) external view returns (string memory) {
bytes memory data = getData(tokenId, key);
if (data.length == 0) {
return defaultValue;
}
return abi.decode(data, (string));
}
function getBoolOrDefault(uint256 tokenId, bytes32 key, bool defaultValue) external view returns (bool) {
bytes memory data = getData(tokenId, key);
if (data.length == 0) {
return defaultValue;
}
return abi.decode(data, (bool));
}
function getIntOrDefault(uint256 tokenId, bytes32 key, int256 defaultValue) external view returns (int256) {
bytes memory data = getData(tokenId, key);
if (data.length == 0) {
return defaultValue;
}
return abi.decode(data, (int256));
}
//////////////////////////////////////////////
// REMOVE DATA
//////////////////////////////////////////////
function removeData(uint256 tokenId, bytes32 key) public whenNotPaused nonReentrant {
bool isAdmin = msg.sender == owner();
require(isAdmin || tokenId > 0 && tokenId <= MAX_SUPPLY, "Invalid token ID");
require(isAdmin || !blockedTokenIds[tokenId], "Token ID is blocked");
require(isAdmin || isTokenOwner(tokenId, msg.sender), "Not token owner");
require(isAdmin || !adminWriteOnlyKeys[key], "Key is admin write-only");
bytes memory data = tokenData[tokenId][key];
delete tokenData[tokenId][key];
emit DataRemoved(tokenId, key, msg.sender);
try globalDataStoreCallback[key].onDataRemoved(tokenId, key, data, msg.sender) {
} catch {
emit CallbackFailure(key, "onDataRemoved", address(globalDataStoreCallback[key]));
}
try tokenDataStoreCallback[tokenId][key].onDataRemoved(tokenId, key, data, msg.sender) {
} catch {
emit CallbackFailure(key, "onDataRemoved", address(tokenDataStoreCallback[tokenId][key]));
}
}
function removeDataMany(uint256[] memory tokenIds, bytes32[] memory keys) public whenNotPaused {
require(tokenIds.length == keys.length, "Token IDs and keys length mismatch");
for (uint256 i = 0; i < tokenIds.length; ++i) {
removeData(tokenIds[i], keys[i]);
}
}
// Check if data exists for a token
function hasData(uint256 tokenId, bytes32 key) external view returns (bool) {
require(tokenId > 0 && tokenId <= MAX_SUPPLY, "Invalid token ID");
return tokenData[tokenId][key].length > 0;
}
// Check if a tokenId is blocked
function isAdminTokenIdBlocked(uint256 tokenId) external view returns (bool) {
require(tokenId > 0 && tokenId <= MAX_SUPPLY, "Invalid token ID");
return blockedTokenIds[tokenId];
}
//////////////////////////////////////////////
// FOLLOWER SYSTEM
//////////////////////////////////////////////
function follow(uint256 followerTokenId, uint256 followedTokenId) external whenNotPaused nonReentrant {
require(followerTokenId != followedTokenId, "Cannot follow self");
require(isTokenOwner(followerTokenId, msg.sender), "Not token owner");
require(!isFollowing[followerTokenId][followedTokenId], "Already following");
isFollowing[followerTokenId][followedTokenId] = true;
followingCount[followerTokenId]++;
followerCount[followedTokenId]++;
emit Follow(followerTokenId, followedTokenId);
if (isFollowing[followedTokenId][followerTokenId]) {
emit MutualFriendStart(followerTokenId, followedTokenId);
}
}
function unfollow(uint256 followerTokenId, uint256 followedTokenId) external whenNotPaused nonReentrant {
require(followerTokenId != followedTokenId, "Cannot unfollow self");
require(isTokenOwner(followerTokenId, msg.sender), "Not token owner");
require(isFollowing[followerTokenId][followedTokenId], "Not following");
isFollowing[followerTokenId][followedTokenId] = false;
followingCount[followerTokenId]--;
followerCount[followedTokenId]--;
emit Unfollow(followerTokenId, followedTokenId);
if (isFollowing[followedTokenId][followerTokenId]) {
emit MutualFriendEnd(followerTokenId, followedTokenId);
}
}
function isMutualFriends(uint256 tokenId1, uint256 tokenId2) external view returns (bool) {
return isFollowing[tokenId1][tokenId2] && isFollowing[tokenId2][tokenId1];
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
/// @notice Simple single owner authorization mixin.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/auth/Ownable.sol)
///
/// @dev Note:
/// This implementation does NOT auto-initialize the owner to `msg.sender`.
/// You MUST call the `_initializeOwner` in the constructor / initializer.
///
/// While the ownable portion follows
/// [EIP-173](https://eips.ethereum.org/EIPS/eip-173) for compatibility,
/// the nomenclature for the 2-step ownership handover may be unique to this codebase.
abstract contract Ownable {
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CUSTOM ERRORS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev The caller is not authorized to call the function.
error Unauthorized();
/// @dev The `newOwner` cannot be the zero address.
error NewOwnerIsZeroAddress();
/// @dev The `pendingOwner` does not have a valid handover request.
error NoHandoverRequest();
/// @dev Cannot double-initialize.
error AlreadyInitialized();
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* EVENTS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev The ownership is transferred from `oldOwner` to `newOwner`.
/// This event is intentionally kept the same as OpenZeppelin's Ownable to be
/// compatible with indexers and [EIP-173](https://eips.ethereum.org/EIPS/eip-173),
/// despite it not being as lightweight as a single argument event.
event OwnershipTransferred(address indexed oldOwner, address indexed newOwner);
/// @dev An ownership handover to `pendingOwner` has been requested.
event OwnershipHandoverRequested(address indexed pendingOwner);
/// @dev The ownership handover to `pendingOwner` has been canceled.
event OwnershipHandoverCanceled(address indexed pendingOwner);
/// @dev `keccak256(bytes("OwnershipTransferred(address,address)"))`.
uint256 private constant _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE =
0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0;
/// @dev `keccak256(bytes("OwnershipHandoverRequested(address)"))`.
uint256 private constant _OWNERSHIP_HANDOVER_REQUESTED_EVENT_SIGNATURE =
0xdbf36a107da19e49527a7176a1babf963b4b0ff8cde35ee35d6cd8f1f9ac7e1d;
/// @dev `keccak256(bytes("OwnershipHandoverCanceled(address)"))`.
uint256 private constant _OWNERSHIP_HANDOVER_CANCELED_EVENT_SIGNATURE =
0xfa7b8eab7da67f412cc9575ed43464468f9bfbae89d1675917346ca6d8fe3c92;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* STORAGE */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev The owner slot is given by:
/// `bytes32(~uint256(uint32(bytes4(keccak256("_OWNER_SLOT_NOT")))))`.
/// It is intentionally chosen to be a high value
/// to avoid collision with lower slots.
/// The choice of manual storage layout is to enable compatibility
/// with both regular and upgradeable contracts.
bytes32 internal constant _OWNER_SLOT =
0xffffffffffffffffffffffffffffffffffffffffffffffffffffffff74873927;
/// The ownership handover slot of `newOwner` is given by:
/// ```
/// mstore(0x00, or(shl(96, user), _HANDOVER_SLOT_SEED))
/// let handoverSlot := keccak256(0x00, 0x20)
/// ```
/// It stores the expiry timestamp of the two-step ownership handover.
uint256 private constant _HANDOVER_SLOT_SEED = 0x389a75e1;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* INTERNAL FUNCTIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Override to return true to make `_initializeOwner` prevent double-initialization.
function _guardInitializeOwner() internal pure virtual returns (bool guard) {}
/// @dev Initializes the owner directly without authorization guard.
/// This function must be called upon initialization,
/// regardless of whether the contract is upgradeable or not.
/// This is to enable generalization to both regular and upgradeable contracts,
/// and to save gas in case the initial owner is not the caller.
/// For performance reasons, this function will not check if there
/// is an existing owner.
function _initializeOwner(address newOwner) internal virtual {
if (_guardInitializeOwner()) {
/// @solidity memory-safe-assembly
assembly {
let ownerSlot := _OWNER_SLOT
if sload(ownerSlot) {
mstore(0x00, 0x0dc149f0) // `AlreadyInitialized()`.
revert(0x1c, 0x04)
}
// Clean the upper 96 bits.
newOwner := shr(96, shl(96, newOwner))
// Store the new value.
sstore(ownerSlot, or(newOwner, shl(255, iszero(newOwner))))
// Emit the {OwnershipTransferred} event.
log3(0, 0, _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE, 0, newOwner)
}
} else {
/// @solidity memory-safe-assembly
assembly {
// Clean the upper 96 bits.
newOwner := shr(96, shl(96, newOwner))
// Store the new value.
sstore(_OWNER_SLOT, newOwner)
// Emit the {OwnershipTransferred} event.
log3(0, 0, _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE, 0, newOwner)
}
}
}
/// @dev Sets the owner directly without authorization guard.
function _setOwner(address newOwner) internal virtual {
if (_guardInitializeOwner()) {
/// @solidity memory-safe-assembly
assembly {
let ownerSlot := _OWNER_SLOT
// Clean the upper 96 bits.
newOwner := shr(96, shl(96, newOwner))
// Emit the {OwnershipTransferred} event.
log3(0, 0, _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE, sload(ownerSlot), newOwner)
// Store the new value.
sstore(ownerSlot, or(newOwner, shl(255, iszero(newOwner))))
}
} else {
/// @solidity memory-safe-assembly
assembly {
let ownerSlot := _OWNER_SLOT
// Clean the upper 96 bits.
newOwner := shr(96, shl(96, newOwner))
// Emit the {OwnershipTransferred} event.
log3(0, 0, _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE, sload(ownerSlot), newOwner)
// Store the new value.
sstore(ownerSlot, newOwner)
}
}
}
/// @dev Throws if the sender is not the owner.
function _checkOwner() internal view virtual {
/// @solidity memory-safe-assembly
assembly {
// If the caller is not the stored owner, revert.
if iszero(eq(caller(), sload(_OWNER_SLOT))) {
mstore(0x00, 0x82b42900) // `Unauthorized()`.
revert(0x1c, 0x04)
}
}
}
/// @dev Returns how long a two-step ownership handover is valid for in seconds.
/// Override to return a different value if needed.
/// Made internal to conserve bytecode. Wrap it in a public function if needed.
function _ownershipHandoverValidFor() internal view virtual returns (uint64) {
return 48 * 3600;
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* PUBLIC UPDATE FUNCTIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Allows the owner to transfer the ownership to `newOwner`.
function transferOwnership(address newOwner) public payable virtual onlyOwner {
/// @solidity memory-safe-assembly
assembly {
if iszero(shl(96, newOwner)) {
mstore(0x00, 0x7448fbae) // `NewOwnerIsZeroAddress()`.
revert(0x1c, 0x04)
}
}
_setOwner(newOwner);
}
/// @dev Allows the owner to renounce their ownership.
function renounceOwnership() public payable virtual onlyOwner {
_setOwner(address(0));
}
/// @dev Request a two-step ownership handover to the caller.
/// The request will automatically expire in 48 hours (172800 seconds) by default.
function requestOwnershipHandover() public payable virtual {
unchecked {
uint256 expires = block.timestamp + _ownershipHandoverValidFor();
/// @solidity memory-safe-assembly
assembly {
// Compute and set the handover slot to `expires`.
mstore(0x0c, _HANDOVER_SLOT_SEED)
mstore(0x00, caller())
sstore(keccak256(0x0c, 0x20), expires)
// Emit the {OwnershipHandoverRequested} event.
log2(0, 0, _OWNERSHIP_HANDOVER_REQUESTED_EVENT_SIGNATURE, caller())
}
}
}
/// @dev Cancels the two-step ownership handover to the caller, if any.
function cancelOwnershipHandover() public payable virtual {
/// @solidity memory-safe-assembly
assembly {
// Compute and set the handover slot to 0.
mstore(0x0c, _HANDOVER_SLOT_SEED)
mstore(0x00, caller())
sstore(keccak256(0x0c, 0x20), 0)
// Emit the {OwnershipHandoverCanceled} event.
log2(0, 0, _OWNERSHIP_HANDOVER_CANCELED_EVENT_SIGNATURE, caller())
}
}
/// @dev Allows the owner to complete the two-step ownership handover to `pendingOwner`.
/// Reverts if there is no existing ownership handover requested by `pendingOwner`.
function completeOwnershipHandover(address pendingOwner) public payable virtual onlyOwner {
/// @solidity memory-safe-assembly
assembly {
// Compute and set the handover slot to 0.
mstore(0x0c, _HANDOVER_SLOT_SEED)
mstore(0x00, pendingOwner)
let handoverSlot := keccak256(0x0c, 0x20)
// If the handover does not exist, or has expired.
if gt(timestamp(), sload(handoverSlot)) {
mstore(0x00, 0x6f5e8818) // `NoHandoverRequest()`.
revert(0x1c, 0x04)
}
// Set the handover slot to 0.
sstore(handoverSlot, 0)
}
_setOwner(pendingOwner);
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* PUBLIC READ FUNCTIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Returns the owner of the contract.
function owner() public view virtual returns (address result) {
/// @solidity memory-safe-assembly
assembly {
result := sload(_OWNER_SLOT)
}
}
/// @dev Returns the expiry timestamp for the two-step ownership handover to `pendingOwner`.
function ownershipHandoverExpiresAt(address pendingOwner)
public
view
virtual
returns (uint256 result)
{
/// @solidity memory-safe-assembly
assembly {
// Compute the handover slot.
mstore(0x0c, _HANDOVER_SLOT_SEED)
mstore(0x00, pendingOwner)
// Load the handover slot.
result := sload(keccak256(0x0c, 0x20))
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* MODIFIERS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Marks a function as only callable by the owner.
modifier onlyOwner() virtual {
_checkOwner();
_;
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (security/ReentrancyGuard.sol)
pragma solidity ^0.8.0;
/**
* @dev Contract module that helps prevent reentrant calls to a function.
*
* Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
* available, which can be applied to functions to make sure there are no nested
* (reentrant) calls to them.
*
* Note that because there is a single `nonReentrant` guard, functions marked as
* `nonReentrant` may not call one another. This can be worked around by making
* those functions `private`, and then adding `external` `nonReentrant` entry
* points to them.
*
* TIP: If you would like to learn more about reentrancy and alternative ways
* to protect against it, check out our blog post
* https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
*/
abstract contract ReentrancyGuard {
// Booleans are more expensive than uint256 or any type that takes up a full
// word because each write operation emits an extra SLOAD to first read the
// slot's contents, replace the bits taken up by the boolean, and then write
// back. This is the compiler's defense against contract upgrades and
// pointer aliasing, and it cannot be disabled.
// The values being non-zero value makes deployment a bit more expensive,
// but in exchange the refund on every call to nonReentrant will be lower in
// amount. Since refunds are capped to a percentage of the total
// transaction's gas, it is best to keep them low in cases like this one, to
// increase the likelihood of the full refund coming into effect.
uint256 private constant _NOT_ENTERED = 1;
uint256 private constant _ENTERED = 2;
uint256 private _status;
constructor() {
_status = _NOT_ENTERED;
}
/**
* @dev Prevents a contract from calling itself, directly or indirectly.
* Calling a `nonReentrant` function from another `nonReentrant`
* function is not supported. It is possible to prevent this from happening
* by making the `nonReentrant` function external, and making it call a
* `private` function that does the actual work.
*/
modifier nonReentrant() {
_nonReentrantBefore();
_;
_nonReentrantAfter();
}
function _nonReentrantBefore() private {
// On the first call to nonReentrant, _status will be _NOT_ENTERED
require(_status != _ENTERED, "ReentrancyGuard: reentrant call");
// Any calls to nonReentrant after this point will fail
_status = _ENTERED;
}
function _nonReentrantAfter() private {
// By storing the original value once again, a refund is triggered (see
// https://eips.ethereum.org/EIPS/eip-2200)
_status = _NOT_ENTERED;
}
/**
* @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a
* `nonReentrant` function in the call stack.
*/
function _reentrancyGuardEntered() internal view returns (bool) {
return _status == _ENTERED;
}
}
{
"compilationTarget": {
"contracts/OKComputerStore.sol": "OKComputerStore"
},
"evmVersion": "paris",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": true,
"runs": 200
},
"remappings": []
}
[{"inputs":[{"internalType":"address","name":"_okComputerNFT","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"AlreadyInitialized","type":"error"},{"inputs":[],"name":"NewOwnerIsZeroAddress","type":"error"},{"inputs":[],"name":"NoHandoverRequest","type":"error"},{"inputs":[],"name":"Unauthorized","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"AdminTokenIdBlocked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"AdminTokenIdUnblocked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"key","type":"bytes32"}],"name":"AdminWriteOnlyKeyAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"key","type":"bytes32"}],"name":"AdminWriteOnlyKeyRemoved","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"key","type":"bytes32"},{"indexed":false,"internalType":"string","name":"functionName","type":"string"},{"indexed":true,"internalType":"address","name":"callback","type":"address"}],"name":"CallbackFailure","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"},{"indexed":true,"internalType":"bytes32","name":"key","type":"bytes32"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"DataRemoved","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"tokenId","type":"uint256"},{"indexed":false,"internalType":"bytes32","name":"key","type":"bytes32"},{"indexed":false,"internalType":"address","name":"callback","type":"address"}],"name":"DataStoreCallbackRegistered","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"},{"indexed":true,"internalType":"bytes32","name":"key","type":"bytes32"},{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":false,"internalType":"bytes","name":"data","type":"bytes"}],"name":"DataStored","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"followerTokenId","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"followedTokenId","type":"uint256"}],"name":"Follow","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bytes32","name":"key","type":"bytes32"},{"indexed":false,"internalType":"address","name":"callback","type":"address"}],"name":"MessageCallbackRegistered","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"},{"indexed":true,"internalType":"bytes32","name":"key","type":"bytes32"},{"indexed":false,"internalType":"string","name":"message","type":"string"},{"indexed":false,"internalType":"uint256","name":"metadata","type":"uint256"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"MessageSubmitted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"tokenId1","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"tokenId2","type":"uint256"}],"name":"MutualFriendEnd","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"tokenId1","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"tokenId2","type":"uint256"}],"name":"MutualFriendStart","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"pendingOwner","type":"address"}],"name":"OwnershipHandoverCanceled","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"pendingOwner","type":"address"}],"name":"OwnershipHandoverRequested","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"oldOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"tokenId","type":"uint256"},{"indexed":false,"internalType":"bytes32","name":"key","type":"bytes32"},{"indexed":false,"internalType":"address","name":"callback","type":"address"}],"name":"TokenDataStoreCallbackRegistered","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"followerTokenId","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"unfollowedTokenId","type":"uint256"}],"name":"Unfollow","type":"event"},{"inputs":[],"name":"MAX_SUPPLY","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string[]","name":"keys","type":"string[]"}],"name":"adminAddWriteOnlyKeys","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string[]","name":"keys","type":"string[]"}],"name":"adminRemoveWriteOnlyKeys","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"allowTokenDataStoreCallback","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"tokenIds","type":"uint256[]"}],"name":"blockTokenIds","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"cancelOwnershipHandover","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"pendingOwner","type":"address"}],"name":"completeOwnershipHandover","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"followerTokenId","type":"uint256"},{"internalType":"uint256","name":"followedTokenId","type":"uint256"}],"name":"follow","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"followerCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"followingCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"bytes32","name":"key","type":"bytes32"}],"name":"getBool","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"bytes32","name":"key","type":"bytes32"},{"internalType":"bool","name":"defaultValue","type":"bool"}],"name":"getBoolOrDefault","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"bytes32","name":"key","type":"bytes32"}],"name":"getData","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"key","type":"bytes32"}],"name":"getDataLengthMax","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"key","type":"bytes32"}],"name":"getDataSubmitCooldown","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"bytes32","name":"key","type":"bytes32"}],"name":"getInt","outputs":[{"internalType":"int256","name":"","type":"int256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"bytes32","name":"key","type":"bytes32"},{"internalType":"int256","name":"defaultValue","type":"int256"}],"name":"getIntOrDefault","outputs":[{"internalType":"int256","name":"","type":"int256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"key","type":"string"}],"name":"getKey","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes32","name":"key","type":"bytes32"},{"internalType":"uint256","name":"index","type":"uint256"}],"name":"getMessage","outputs":[{"components":[{"internalType":"bytes32","name":"key","type":"bytes32"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint256","name":"timestamp","type":"uint256"},{"internalType":"address","name":"sender","type":"address"},{"internalType":"uint256","name":"metadata","type":"uint256"},{"internalType":"string","name":"message","type":"string"}],"internalType":"struct OKComputerStore.Message","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"key","type":"bytes32"}],"name":"getMessageCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"key","type":"bytes32"}],"name":"getMessages","outputs":[{"components":[{"internalType":"bytes32","name":"key","type":"bytes32"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint256","name":"timestamp","type":"uint256"},{"internalType":"address","name":"sender","type":"address"},{"internalType":"uint256","name":"metadata","type":"uint256"},{"internalType":"string","name":"message","type":"string"}],"internalType":"struct OKComputerStore.Message[]","name":"","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"bytes32","name":"key","type":"bytes32"}],"name":"getString","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"bytes32","name":"key","type":"bytes32"},{"internalType":"string","name":"defaultValue","type":"string"}],"name":"getStringOrDefault","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"globalDataStoreCallback","outputs":[{"internalType":"contract IOKComputerDataStoreCallback","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"globalMessageCallback","outputs":[{"internalType":"contract IOKComputerMessageCallback","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"bytes32","name":"key","type":"bytes32"}],"name":"hasData","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"isAdminTokenIdBlocked","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"isFollowing","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId1","type":"uint256"},{"internalType":"uint256","name":"tokenId2","type":"uint256"}],"name":"isMutualFriends","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"okComputerNFT","outputs":[{"internalType":"contract IERC721","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"result","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"pendingOwner","type":"address"}],"name":"ownershipHandoverExpiresAt","outputs":[{"internalType":"uint256","name":"result","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"paused","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"key","type":"bytes32"},{"internalType":"address","name":"callback","type":"address"}],"name":"registerGlobalDataStoreCallback","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"key","type":"bytes32"},{"internalType":"address","name":"callback","type":"address"}],"name":"registerGlobalMessageCallback","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"bytes32","name":"key","type":"bytes32"},{"internalType":"address","name":"callback","type":"address"}],"name":"registerTokenDataStoreCallback","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"bytes32","name":"key","type":"bytes32"}],"name":"removeData","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"tokenIds","type":"uint256[]"},{"internalType":"bytes32[]","name":"keys","type":"bytes32[]"}],"name":"removeDataMany","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"requestOwnershipHandover","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"bool","name":"_allow","type":"bool"}],"name":"setAllowTokenDataStoreCallback","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"key","type":"bytes32"},{"internalType":"uint256","name":"maxLength","type":"uint256"}],"name":"setDataMaxLengthForKey","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32[]","name":"keys","type":"bytes32[]"},{"internalType":"uint256[]","name":"maxLengths","type":"uint256[]"}],"name":"setDataMaxLengthForKeys","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"key","type":"bytes32"},{"internalType":"uint256","name":"cooldown","type":"uint256"}],"name":"setDataSubmitCooldownForKey","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32[]","name":"keys","type":"bytes32[]"},{"internalType":"uint256[]","name":"cooldowns","type":"uint256[]"}],"name":"setDataSubmitCooldownForKeys","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_defaultDataMaxLength","type":"uint256"}],"name":"setDefaultDataMaxLength","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_defaultSubmitCooldown","type":"uint256"}],"name":"setDefaultSubmitCooldown","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bool","name":"_paused","type":"bool"}],"name":"setPause","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"bytes32","name":"key","type":"bytes32"},{"internalType":"bool","name":"data","type":"bool"}],"name":"storeBool","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"bytes32","name":"key","type":"bytes32"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"storeData","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"tokenIds","type":"uint256[]"},{"internalType":"bytes32[]","name":"keys","type":"bytes32[]"},{"internalType":"bytes[]","name":"data","type":"bytes[]"}],"name":"storeDataMany","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"bytes32","name":"key","type":"bytes32"},{"internalType":"int256","name":"data","type":"int256"}],"name":"storeInt","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"bytes32","name":"key","type":"bytes32"},{"internalType":"string","name":"data","type":"string"}],"name":"storeString","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"bytes32","name":"key","type":"bytes32"},{"internalType":"string","name":"text","type":"string"},{"internalType":"uint256","name":"metadata","type":"uint256"}],"name":"submitMessage","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"tokenIds","type":"uint256[]"},{"internalType":"bytes32[]","name":"keys","type":"bytes32[]"},{"internalType":"string[]","name":"messages","type":"string[]"},{"internalType":"uint256[]","name":"metadata","type":"uint256[]"}],"name":"submitMessages","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"tokenDataStoreCallback","outputs":[{"internalType":"contract IOKComputerDataStoreCallback","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"tokenIds","type":"uint256[]"}],"name":"unblockTokenIds","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"followerTokenId","type":"uint256"},{"internalType":"uint256","name":"followedTokenId","type":"uint256"}],"name":"unfollow","outputs":[],"stateMutability":"nonpayable","type":"function"}]