// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/release-v4.9/contracts/token/ERC20/IERC20.sol";
import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/release-v4.9/contracts/token/ERC721/ERC721.sol";
import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/release-v4.9/contracts/access/Ownable.sol";
import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/release-v4.9/contracts/security/Pausable.sol";
import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/release-v4.9/contracts/security/ReentrancyGuard.sol";
contract AlkimiValidator is ERC721, Ownable, ReentrancyGuard, Pausable {
using Address for address;
// ------------ Varaibles {{{
uint8 private constant TotalAdmins = 2;
uint32 public validatorVersionCount = 0;
uint256 private totalMinted = 0;
uint256 public whitelistDefaultValidatorVersionIdx = type(uint256).max;
uint256 public approvalDefaultValidatorVersionIdx = type(uint256).max;
// Fixed delay period for approvals (in seconds)
uint256 public approvalDelayPeriod;
// Fixed delay period for reclaims (in seconds)
uint256 public reclaimDelayPeriod;
string public defaultUri = "";
string public approvalPendingDefaultUri = "";
address private treasuryWallet; // Address of the treasury wallet
address[] private admins; // Array to store admin addresses
address[] public mintWhitelistAddressArray; // Array to store all whitelisted addresses
// Validator status enum
enum ValidatorStatus {Invalid, Inactive, Active}
// Request approval status
enum RequestStatus { Invalid, Pending, Approved, Rejected }
enum ReclaimRequestStatus { Invalid, Pending, Approved, Rejected }
struct ValidatorVersion {
string version;
address underlying;
uint256 collateral;
uint256 ts;
uint256 minted;
}
struct ApprovalRequest {
RequestStatus status;
uint256 requestTime;
uint256 validatorVersionIdx;
uint256 nftTokenId;
string reason; // rejection reason
}
struct ReclaimRequest {
address user;
ReclaimRequestStatus status;
uint256 requestTime;
string reason; // rejection reason
}
mapping(uint256 => ValidatorStatus) public nftValidatorStatus; // tokenId -> ValidatorStatus
mapping(uint256 => ValidatorVersion) public validatorVersions; // Index -> validatorVersions
mapping(uint256 => string) private tokenURIs; // Mapping to store individual NFT URIs
mapping(address => bool) private isAdmin;
mapping(uint256 => bool) public reclaimable; // Mapping to store reclaimable flag for NFTs
mapping(uint256 => uint256) public nftToValidatorVersion; // tokenId -> Validatorversion
// Mapping to store the whitelist addresses and their remaining mint allowance
mapping(address => uint256) public mintWhitelistAllowance; // mapping address -> allowance
mapping(address => ApprovalRequest) public approvalRequests;
mapping(uint256 => ReclaimRequest) public reclaimRequests; // Mapping between tokenId and struct
// ------------ Varaibles }}}
// ------------ Modifiers {{{
modifier tokenIDExists(uint256 _tokenId) {
require(_exists(_tokenId), "invalid-tokenID");
_;
}
modifier onlyAdmin() {
require(isAdmin[msg.sender] || owner() == msg.sender, "unauthorised admin");
_;
}
// ------------ Modifiers }}}
// ------------ EVENTS {{{
event AdminAdded(address indexed operator, address indexed added);
event AdminRemoved(address indexed operator, address indexed removed);
event NFTMinted(address indexed minter, uint256 tokenId, uint256 validatorVersion);
event NFTBurned(address indexed burner, uint256 tokenId, uint256 collateral);
event TreasuryWalletSet(address indexed newTreasuryWallet);
event ValidatorVersionAdded(address indexed caller, string version, address underlying, uint256 collateral, uint256 ts);
event ValidatorVersionUpdated(address indexed caller, uint256 validatorVersion, uint256 newTs);
event WhitelistAddressAdded(address indexed caller, address indexed user, uint256 allowance);
event WhitelistAddressRemoved(address indexed caller, address indexed user);
event ValidatorNodeRequested(address indexed caller, uint256 validatorVersion);
event ValidatorNodeApproved(address indexed admin, address indexed user, uint256 validatorVersion);
event ValidatorNodeRejected(address indexed admin, address indexed user, uint256 validatorVersion);
event ReclaimRequested(address indexed caller, uint256 _tokenId, uint256 validatorVersion);
event ReclaimApproved(address indexed admin, uint256 _tokenId, uint256 validatorVersion);
event ReclaimRejected(address indexed admin, uint256 _tokenId, uint256 validatorVersion);
// ------------ EVENTS }}}
constructor() ERC721("AlkimiValidator", "ALV") {}
function exists(uint256 _tokenId) external view returns (bool) {
return _exists(_tokenId);
}
function totalSupply() public view returns (uint256) {
return totalMinted;
}
function addAdmin(address _admin) external onlyOwner {
require(admins.length < TotalAdmins, "Max admins reached");
require(!isAdmin[_admin], "admin already added");
require(_admin != address(0), "invalid address");
require(_admin != owner(), "owner address");
admins.push(_admin); // Add admin address to the admins array
isAdmin[_admin] = true;
emit AdminAdded(msg.sender, _admin);
}
function removeAdmin(address _admin) external onlyOwner {
require(isAdmin[_admin], "not an admin");
uint256 index = (admins[0] == _admin) ? 0 : 1;
admins[index] = admins[admins.length - 1]; // Replace removed admin with the last element
admins.pop(); // Remove the last element (replaced element)
isAdmin[_admin] = false;
emit AdminRemoved(msg.sender, _admin);
}
function getAdmins() external view onlyOwner returns (address[] memory) {
return admins; // Simply return the admins array
}
function addValidatorVersion(
string memory _version,
address _underlying,
uint256 _collateral,
uint256 _ts
) external onlyAdmin {
validatorVersions[validatorVersionCount].version = _version;
validatorVersions[validatorVersionCount].underlying = _underlying;
validatorVersions[validatorVersionCount].collateral = _collateral;
validatorVersions[validatorVersionCount].ts = _ts;
validatorVersions[validatorVersionCount].minted = 0;
validatorVersionCount += 1;
emit ValidatorVersionAdded(msg.sender, _version, _underlying, _collateral, _ts);
}
function updateTotalSupplyForValidatorVersion(uint256 _validatorVersionIdx, uint256 _newTs) external onlyAdmin {
require(validatorVersions[_validatorVersionIdx].minted <= _newTs, "TotaSupply less than minted");
// NOTE: ONLY TS can be changed. For others, Admin should create a new validator version
validatorVersions[_validatorVersionIdx].ts = _newTs;
emit ValidatorVersionUpdated(msg.sender, _validatorVersionIdx, _newTs);
}
function getAllValidatorVersions() external view returns (ValidatorVersion[] memory) {
ValidatorVersion[] memory versions = new ValidatorVersion[](validatorVersionCount);
for (uint256 i = 0; i < validatorVersionCount; i++) {
versions[i] = validatorVersions[i];
}
return versions;
}
// NOTE : No set providied for nftToValidatorVersion. A NFT minted cannot be changed to a new validaor version
function getValidatorVersionDetailsForNFT(uint256 _tokenId) external view tokenIDExists(_tokenId) returns (ValidatorVersion memory) {
// uint256 versionId = nftToValidatorVersion[_tokenId];
require(nftToValidatorVersion[_tokenId] < validatorVersionCount, "invalid version mapping");
return validatorVersions[nftToValidatorVersion[_tokenId]];
}
// Set function to update the status of an NFT validator
function setNFTValidatorStatus(uint256 _tokenId, uint256 _status) external tokenIDExists(_tokenId) onlyAdmin {
require(_status <= uint256(ValidatorStatus.Active), "Invalid status");
nftValidatorStatus[_tokenId] = ValidatorStatus(_status);
}
// Mint function - requires ADS tokens and sets NFT as valid
function adminMint(uint256 _validatorVersion, address _user, string memory _uri) external nonReentrant onlyAdmin {
require(internalMint(_validatorVersion, _user, _uri, ValidatorStatus.Active), "minting failed");
}
function internalMint(uint256 _validatorVersionIdx, address _user, string memory _uri, ValidatorStatus _status) private returns (bool) {
if (_validatorVersionIdx >= validatorVersionCount) {
// Invalid validator version
return false;
}
if (validatorVersions[_validatorVersionIdx].minted >= validatorVersions[_validatorVersionIdx].ts) {
// Cannot mint more than ts
return false;
}
// Get the user's balance before mint
uint256 previousBalance = balanceOf(_user);
// Attempt to mint the NFT
_safeMint(_user, totalMinted);
// Get the user's balance after mint
// uint256 currentBalance = balanceOf(_user);
// Validate successful mint
if (balanceOf(_user) != previousBalance + 1) {
return false;
}
validatorVersions[_validatorVersionIdx].minted++;
tokenURIs[totalMinted] = _uri;
reclaimable[totalMinted] = true;
nftToValidatorVersion[totalMinted] = _validatorVersionIdx;
nftValidatorStatus[totalMinted] = _status;
emit NFTMinted(_user, totalMinted, _validatorVersionIdx);
totalMinted++;
return true;
}
function _beforeTokenTransfer(address from, address to, uint256, uint256) pure override internal {
require(from == address(0) || to == address(0), "This a Soulbound token. It cannot be transferred.");
}
// Set treasury wallet address
function setTreasuryWallet(address _treasuryWallet) external onlyOwner {
require(_treasuryWallet != address(0), "Invalid address");
treasuryWallet = _treasuryWallet;
emit TreasuryWalletSet(_treasuryWallet);
}
// Set URI for a specific NFT
function setTokenURI(uint256 _tokenId, string memory _tokenURI) external tokenIDExists(_tokenId) onlyAdmin {
tokenURIs[_tokenId] = _tokenURI;
}
function setDefaultURI(string memory _uri) external onlyAdmin {
defaultUri = _uri;
}
// Override tokenURI to return the URI from mapping (if not set)
function tokenURI(uint256 _tokenId) public view virtual override returns (string memory) {
require(_exists(_tokenId), "ERC721: URI query for nonexistent token");
if (bytes(tokenURIs[_tokenId]).length > 0) {
return tokenURIs[_tokenId];
} else {
return defaultUri;
}
}
// ----------------- WHITELIST {{{
// default validator version for whitelist
function setWhitelistDefaultValidatorVersion(uint256 _versionId) external onlyAdmin {
require(_versionId < validatorVersionCount, "invalid validator version");
whitelistDefaultValidatorVersionIdx = _versionId;
}
// Function to add an address to the whitelist and set their mint allowance
function addToWhitelist(address _user, uint256 _mintAllowance) external onlyAdmin {
require(_user != address(0), "Invalid address");
require(mintWhitelistAllowance[_user] == 0, "Address already whitelisted");
mintWhitelistAllowance[_user] = _mintAllowance;
mintWhitelistAddressArray.push(_user);
emit WhitelistAddressAdded(msg.sender, _user, _mintAllowance);
}
// Function to remove an address from the whitelist
function removeFromWhitelist(address _user) external onlyAdmin {
require(mintWhitelistAllowance[_user] > 0, "Address not whitelisted");
removeFromWhitelistInternal(_user);
}
// Function to remove an address from the whitelist
function removeFromWhitelistInternal(address _user) private {
uint256 index = 0;
for (uint256 i = 0; i < mintWhitelistAddressArray.length; i++) {
if (mintWhitelistAddressArray[i] == _user) {
index = i;
break;
}
}
mintWhitelistAddressArray[index] = mintWhitelistAddressArray[mintWhitelistAddressArray.length - 1];
mintWhitelistAddressArray.pop();
delete mintWhitelistAllowance[_user];
emit WhitelistAddressRemoved(msg.sender, _user);
}
// Function to return all values in mintWhitelistAllowance
function getAllWhitelistAddresses() external view returns (address[] memory, uint256) {
address[] memory addresses = new address[](mintWhitelistAddressArray.length);
for (uint256 i = 0; i < mintWhitelistAddressArray.length; i++) {
addresses[i] = mintWhitelistAddressArray[i];
}
return (addresses, mintWhitelistAddressArray.length);
}
// Whitelist NFT minting function
function whitelistMintNFT() external nonReentrant whenNotPaused onlyIfStage2Open {
require(treasuryWallet != address(0), "Treasury wallet not set");
require(mintWhitelistAllowance[msg.sender] > 0, "Address not whitelisted");
require(whitelistDefaultValidatorVersionIdx < validatorVersionCount, "invalid version id");
// address underlyingToken = validatorVersions[whitelistDefaultValidatorVersionIdx].underlying;
uint256 collateral = (validatorVersions[whitelistDefaultValidatorVersionIdx].collateral * 1e18);
// Transfer tokens from the user to the treasury wallet
require(IERC20(validatorVersions[whitelistDefaultValidatorVersionIdx].underlying).allowance(msg.sender, address(this)) >= collateral,"allowance failed");
bool transferSuccess = IERC20(validatorVersions[whitelistDefaultValidatorVersionIdx].underlying).transferFrom(msg.sender, treasuryWallet, collateral);
// Perform minting
bool mintSuccess = internalMint(whitelistDefaultValidatorVersionIdx, msg.sender, defaultUri, ValidatorStatus.Active);
// Check if both operations were successful
if (!transferSuccess || !mintSuccess) {
revert("Transaction failed: Transfer or minting unsuccessful");
}
mintWhitelistAllowance[msg.sender]--; // Decrement remaining mint allowance
if (mintWhitelistAllowance[msg.sender] == 0) {
removeFromWhitelistInternal(msg.sender); // Remove from whitelist if mint allowance is exhausted
}
}
// ----------------- WHITELIST }}}
// ----------------- APPROVAL {{{
// default validator version for Approval Process
function setApprovalDefaultValidatorVersion(uint256 _versionId) external onlyAdmin {
require(_versionId < validatorVersionCount, "invalid validator version");
approvalDefaultValidatorVersionIdx = _versionId;
}
// Function to set the fixed delay period (in seconds)
function setApprovalDelayPeriod(uint256 _delayPeriod) external onlyAdmin {
approvalDelayPeriod = _delayPeriod;
}
function setapprovalPendingDefaultURI(string memory _uri) external onlyAdmin {
approvalPendingDefaultUri = _uri;
}
// Function for users to request a validator node
function requestValidatorNode() external nonReentrant whenNotPaused onlyIfStage3Open {
require(approvalRequests[msg.sender].status != RequestStatus.Pending, "Request already in Pending state");
require(approvalRequests[msg.sender].status != RequestStatus.Rejected, "Request already in Rejected state");
require(treasuryWallet != address(0), "Treasury wallet not set");
require(approvalDefaultValidatorVersionIdx < validatorVersionCount, "invalid validator version");
require(bytes(approvalPendingDefaultUri).length > 0, "approvalPendingDefaultUri not set");
// address underlyingToken = validatorVersions[approvalDefaultValidatorVersionIdx].underlying;
uint256 collateral = (validatorVersions[approvalDefaultValidatorVersionIdx].collateral * 1e18);
// Transfer tokens from the user to the treasury wallet
require(IERC20(validatorVersions[approvalDefaultValidatorVersionIdx].underlying).allowance(msg.sender, address(this)) >= collateral,"allowance failed");
bool transferSuccess = IERC20(validatorVersions[approvalDefaultValidatorVersionIdx].underlying).transferFrom(msg.sender, treasuryWallet, collateral);
// Perform minting
bool mintSuccess = internalMint(approvalDefaultValidatorVersionIdx, msg.sender, approvalPendingDefaultUri, ValidatorStatus.Inactive);
// Check if both operations were successful
if (!transferSuccess || !mintSuccess) {
revert("Transaction failed: Transfer or minting unsuccessful");
}
approvalRequests[msg.sender].status = RequestStatus.Pending;
approvalRequests[msg.sender].requestTime = block.timestamp;
approvalRequests[msg.sender].validatorVersionIdx = approvalDefaultValidatorVersionIdx;
approvalRequests[msg.sender].nftTokenId = totalMinted - 1;
emit ValidatorNodeRequested(msg.sender, approvalDefaultValidatorVersionIdx);
}
// Function for admin to approve a request
function approveRequest(address _user, string memory _tokenURI) external onlyAdmin {
require(approvalRequests[_user].status == RequestStatus.Pending, "Request not pending");
require(block.timestamp >= approvalRequests[_user].requestTime + approvalDelayPeriod, "Delay period not yet passed");
tokenURIs[approvalRequests[_user].nftTokenId] = _tokenURI;
// Set the nft status to active here
nftValidatorStatus[approvalRequests[_user].nftTokenId] = ValidatorStatus.Active;
emit ValidatorNodeApproved(msg.sender, _user, approvalRequests[_user].validatorVersionIdx);
delete approvalRequests[_user];
}
// Function for admin to reject a request
function rejectRequest(address _user, string memory _reason) external onlyAdmin {
require(approvalRequests[_user].status == RequestStatus.Pending, "Request not pending");
approvalRequests[_user].status = RequestStatus.Rejected;
approvalRequests[_user].reason = _reason; // Set the rejection reason
approvalRequests[_user].requestTime = block.timestamp;
emit ValidatorNodeRejected(msg.sender, _user, approvalRequests[_user].validatorVersionIdx);
}
// Function for users to check their request status
function getApprovalRequestStatus(address _user) external view returns (RequestStatus, string memory) {
return (approvalRequests[_user].status, approvalRequests[_user].reason);
}
// Function for users to reclaim collateral
function reclaimRejectedApprovalCollateral() external nonReentrant whenNotPaused onlyIfStage3Open {
// uint256 tokenId = approvalRequests[msg.sender].nftTokenId;
require(approvalRequests[msg.sender].status == RequestStatus.Rejected, "Approval not in rejected state");
require(ownerOf(approvalRequests[msg.sender].nftTokenId) == msg.sender, "Not owner of the NFT");
require(reclaimable[approvalRequests[msg.sender].nftTokenId] == true, "Not Reclaimable");
require(block.timestamp >= approvalRequests[msg.sender].requestTime + reclaimDelayPeriod, "Reclaim delay period not yet passed");
// Burn the NFT before transferring collateral
_burn(approvalRequests[msg.sender].nftTokenId);
// Transfer collateral back from treasury to user
// address underlyingToken = validatorVersions[approvalRequests[msg.sender].validatorVersionIdx].underlying;
uint256 collateral = (validatorVersions[approvalRequests[msg.sender].validatorVersionIdx].collateral * 1e18);
require(IERC20(validatorVersions[approvalRequests[msg.sender].validatorVersionIdx].underlying).transfer(msg.sender, collateral), "Collateral transfer failed");
// Reset values
reclaimable[approvalRequests[msg.sender].nftTokenId] = false;
nftToValidatorVersion[approvalRequests[msg.sender].nftTokenId] = type(uint256).max;
nftValidatorStatus[approvalRequests[msg.sender].nftTokenId] = ValidatorStatus.Invalid;
emit NFTBurned(msg.sender, approvalRequests[msg.sender].nftTokenId, collateral);
delete approvalRequests[msg.sender];
}
// ----------------- APPROVAL }}}
// ----------------- RECALIM {{{
// Function to set the fixed delay period for reclaims (in seconds)
function setReclaimDelayPeriod(uint256 _delayPeriod) external onlyAdmin {
reclaimDelayPeriod = _delayPeriod;
}
function setReclaimable(uint256 _tokenId, bool _flag) external tokenIDExists(_tokenId) onlyAdmin {
reclaimable[_tokenId] = _flag;
}
// Function for users to request reclaim of collateral
function requestReclaimCollateral(uint256 _tokenId) external whenNotPaused tokenIDExists(_tokenId) {
require(ownerOf(_tokenId) == msg.sender, "Not owner of the NFT");
require(reclaimable[_tokenId] == true, "Not reclaimable");
require(reclaimRequests[_tokenId].status != ReclaimRequestStatus.Pending, "Reclaim already in Pending state");
require(reclaimRequests[_tokenId].status != ReclaimRequestStatus.Approved, "Reclaim already in Approved state");
// NOTE: If the earlier request is in Rejected state, the use must be able to go thorugh the reclaim again
reclaimRequests[_tokenId].user = msg.sender;
reclaimRequests[_tokenId].status = ReclaimRequestStatus.Pending;
reclaimRequests[_tokenId].requestTime = block.timestamp;
reclaimRequests[_tokenId].reason = "";
emit ReclaimRequested(msg.sender, _tokenId, nftToValidatorVersion[_tokenId]);
}
// Function for admin to approve reclaim request
function approveReclaimRequest(uint256 _tokenId) external tokenIDExists(_tokenId) onlyAdmin {
require(reclaimRequests[_tokenId].status == ReclaimRequestStatus.Pending, "Request not pending");
reclaimRequests[_tokenId].status = ReclaimRequestStatus.Approved; // Update request status
emit ReclaimApproved(msg.sender, _tokenId, nftToValidatorVersion[_tokenId]);
}
// Function for users to reclaim collateral
function reclaimCollateral(uint256 _tokenId) external nonReentrant whenNotPaused tokenIDExists(_tokenId) {
require(ownerOf(_tokenId) == msg.sender, "Not owner of the NFT");
require(reclaimable[_tokenId] == true, "Not Reclaimable");
require(reclaimRequests[_tokenId].status == ReclaimRequestStatus.Approved, "Reclaim request not approved");
require(block.timestamp >= reclaimRequests[_tokenId].requestTime + reclaimDelayPeriod, "Reclaim delay period not yet passed");
// Burn the NFT before transferring collateral (assuming your NFT contract has a burn function)
_burn(_tokenId);
// Get the validator version for the NFT
// uint256 validatorIdx = nftToValidatorVersion[_tokenId];
// Transfer collateral back from treasury to user
address underlyingToken = validatorVersions[nftToValidatorVersion[_tokenId]].underlying;
uint256 collateral = (validatorVersions[nftToValidatorVersion[_tokenId]].collateral * 1e18);
require(IERC20(underlyingToken).transfer(msg.sender, collateral), "Collateral transfer failed");
// Reset values
reclaimable[_tokenId] = false;
nftToValidatorVersion[_tokenId] = type(uint256).max;
nftValidatorStatus[_tokenId] = ValidatorStatus.Invalid;
emit NFTBurned(msg.sender, _tokenId, collateral);
// delete the record once everything is settled
delete reclaimRequests[_tokenId];
}
// Function to reject reclaim request (implementation detail)
function rejectReclaimRequest(uint256 _tokenId, string memory _reason) external tokenIDExists(_tokenId) onlyAdmin {
require(reclaimRequests[_tokenId].status == ReclaimRequestStatus.Pending, "Request not pending");
reclaimRequests[_tokenId].status = ReclaimRequestStatus.Rejected;
reclaimRequests[_tokenId].reason = _reason; // Set the rejection reason
// NOTE: The request is rejected but the reclaimable flag is not set to False. This has to be done manually by Admin if required
// raise event
emit ReclaimRejected(msg.sender, _tokenId, nftToValidatorVersion[_tokenId]);
}
function getReclaimRequestStatus(uint256 _tokenId) external view returns (ReclaimRequestStatus, string memory) {
return (reclaimRequests[_tokenId].status, reclaimRequests[_tokenId].reason);
}
// ----------------- RECALIM }}}
// ----------------- RESCUE {{{
// withdraw accidentally sent native currency
function rescue(address _to) public nonReentrant onlyAdmin {
// uint256 amount = address(this).balance;
(bool success, ) = payable(_to).call{value: address(this).balance}("");
require(success, "ether transfer failed");
}
// withdraw accidentally sent erc20 tokens
function rescueToken(address _token, address _to) public nonReentrant onlyAdmin {
require(IERC20(_token).transfer(_to, IERC20(_token).balanceOf(address(this))));
}
// withdraw accidentally sent nft
function rescueNFT(address _receiver, address _nft, uint256 _id) public nonReentrant onlyAdmin {
require(IERC721(_nft).ownerOf(_id) == address(this),"rescue/invalid-id");
IERC721(_nft).transferFrom(address(this), _receiver, _id);
}
// ----------------- RESCUE }}}
// ----------------- PAUSE {{{
// pause, unpause the contract
function pause() public onlyAdmin {
_pause();
}
function unPause() public onlyAdmin {
_unpause();
}
// ----------------- PAUSE }}}
// ----------------- Stages control {{{
bool private isStage2Open = false;
bool private isStage3Open = false;
modifier onlyIfStage2Open() {
require(isStage2Open, "Whitelist is not open");
_;
}
modifier onlyIfStage3Open() {
require(isStage3Open, "Stage3 is not open");
_;
}
function setIsStage2Open(bool _isOpen) external onlyOwner {
isStage2Open = _isOpen;
}
function setIsStage3Open(bool _isOpen) external onlyOwner {
isStage3Open = _isOpen;
}
// ----------------- Stages control }}}
}
{
"compilationTarget": {
"contracts/AlkimiValidator.sol": "AlkimiValidator"
},
"evmVersion": "shanghai",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": true,
"runs": 200
},
"remappings": []
}
[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"operator","type":"address"},{"indexed":true,"internalType":"address","name":"added","type":"address"}],"name":"AdminAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"operator","type":"address"},{"indexed":true,"internalType":"address","name":"removed","type":"address"}],"name":"AdminRemoved","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"approved","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"operator","type":"address"},{"indexed":false,"internalType":"bool","name":"approved","type":"bool"}],"name":"ApprovalForAll","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"burner","type":"address"},{"indexed":false,"internalType":"uint256","name":"tokenId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"collateral","type":"uint256"}],"name":"NFTBurned","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"minter","type":"address"},{"indexed":false,"internalType":"uint256","name":"tokenId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"validatorVersion","type":"uint256"}],"name":"NFTMinted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"}],"name":"Paused","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"admin","type":"address"},{"indexed":false,"internalType":"uint256","name":"_tokenId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"validatorVersion","type":"uint256"}],"name":"ReclaimApproved","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"admin","type":"address"},{"indexed":false,"internalType":"uint256","name":"_tokenId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"validatorVersion","type":"uint256"}],"name":"ReclaimRejected","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"caller","type":"address"},{"indexed":false,"internalType":"uint256","name":"_tokenId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"validatorVersion","type":"uint256"}],"name":"ReclaimRequested","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"newTreasuryWallet","type":"address"}],"name":"TreasuryWalletSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"}],"name":"Unpaused","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"admin","type":"address"},{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":false,"internalType":"uint256","name":"validatorVersion","type":"uint256"}],"name":"ValidatorNodeApproved","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"admin","type":"address"},{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":false,"internalType":"uint256","name":"validatorVersion","type":"uint256"}],"name":"ValidatorNodeRejected","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"caller","type":"address"},{"indexed":false,"internalType":"uint256","name":"validatorVersion","type":"uint256"}],"name":"ValidatorNodeRequested","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"caller","type":"address"},{"indexed":false,"internalType":"string","name":"version","type":"string"},{"indexed":false,"internalType":"address","name":"underlying","type":"address"},{"indexed":false,"internalType":"uint256","name":"collateral","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"ts","type":"uint256"}],"name":"ValidatorVersionAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"caller","type":"address"},{"indexed":false,"internalType":"uint256","name":"validatorVersion","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"newTs","type":"uint256"}],"name":"ValidatorVersionUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"caller","type":"address"},{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":false,"internalType":"uint256","name":"allowance","type":"uint256"}],"name":"WhitelistAddressAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"caller","type":"address"},{"indexed":true,"internalType":"address","name":"user","type":"address"}],"name":"WhitelistAddressRemoved","type":"event"},{"inputs":[{"internalType":"address","name":"_admin","type":"address"}],"name":"addAdmin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_user","type":"address"},{"internalType":"uint256","name":"_mintAllowance","type":"uint256"}],"name":"addToWhitelist","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"_version","type":"string"},{"internalType":"address","name":"_underlying","type":"address"},{"internalType":"uint256","name":"_collateral","type":"uint256"},{"internalType":"uint256","name":"_ts","type":"uint256"}],"name":"addValidatorVersion","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_validatorVersion","type":"uint256"},{"internalType":"address","name":"_user","type":"address"},{"internalType":"string","name":"_uri","type":"string"}],"name":"adminMint","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"approvalDefaultValidatorVersionIdx","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"approvalDelayPeriod","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"approvalPendingDefaultUri","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"approvalRequests","outputs":[{"internalType":"enum AlkimiValidator.RequestStatus","name":"status","type":"uint8"},{"internalType":"uint256","name":"requestTime","type":"uint256"},{"internalType":"uint256","name":"validatorVersionIdx","type":"uint256"},{"internalType":"uint256","name":"nftTokenId","type":"uint256"},{"internalType":"string","name":"reason","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"approve","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_tokenId","type":"uint256"}],"name":"approveReclaimRequest","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_user","type":"address"},{"internalType":"string","name":"_tokenURI","type":"string"}],"name":"approveRequest","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"defaultUri","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_tokenId","type":"uint256"}],"name":"exists","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getAdmins","outputs":[{"internalType":"address[]","name":"","type":"address[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getAllValidatorVersions","outputs":[{"components":[{"internalType":"string","name":"version","type":"string"},{"internalType":"address","name":"underlying","type":"address"},{"internalType":"uint256","name":"collateral","type":"uint256"},{"internalType":"uint256","name":"ts","type":"uint256"},{"internalType":"uint256","name":"minted","type":"uint256"}],"internalType":"struct AlkimiValidator.ValidatorVersion[]","name":"","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getAllWhitelistAddresses","outputs":[{"internalType":"address[]","name":"","type":"address[]"},{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_user","type":"address"}],"name":"getApprovalRequestStatus","outputs":[{"internalType":"enum AlkimiValidator.RequestStatus","name":"","type":"uint8"},{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"getApproved","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_tokenId","type":"uint256"}],"name":"getReclaimRequestStatus","outputs":[{"internalType":"enum AlkimiValidator.ReclaimRequestStatus","name":"","type":"uint8"},{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_tokenId","type":"uint256"}],"name":"getValidatorVersionDetailsForNFT","outputs":[{"components":[{"internalType":"string","name":"version","type":"string"},{"internalType":"address","name":"underlying","type":"address"},{"internalType":"uint256","name":"collateral","type":"uint256"},{"internalType":"uint256","name":"ts","type":"uint256"},{"internalType":"uint256","name":"minted","type":"uint256"}],"internalType":"struct AlkimiValidator.ValidatorVersion","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"operator","type":"address"}],"name":"isApprovedForAll","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"mintWhitelistAddressArray","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"mintWhitelistAllowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"nftToValidatorVersion","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"nftValidatorStatus","outputs":[{"internalType":"enum AlkimiValidator.ValidatorStatus","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"ownerOf","outputs":[{"internalType":"address","name":"","type":"address"}],"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":"uint256","name":"_tokenId","type":"uint256"}],"name":"reclaimCollateral","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"reclaimDelayPeriod","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"reclaimRejectedApprovalCollateral","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"reclaimRequests","outputs":[{"internalType":"address","name":"user","type":"address"},{"internalType":"enum AlkimiValidator.ReclaimRequestStatus","name":"status","type":"uint8"},{"internalType":"uint256","name":"requestTime","type":"uint256"},{"internalType":"string","name":"reason","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"reclaimable","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_tokenId","type":"uint256"},{"internalType":"string","name":"_reason","type":"string"}],"name":"rejectReclaimRequest","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_user","type":"address"},{"internalType":"string","name":"_reason","type":"string"}],"name":"rejectRequest","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_admin","type":"address"}],"name":"removeAdmin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_user","type":"address"}],"name":"removeFromWhitelist","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_tokenId","type":"uint256"}],"name":"requestReclaimCollateral","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"requestValidatorNode","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_to","type":"address"}],"name":"rescue","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_receiver","type":"address"},{"internalType":"address","name":"_nft","type":"address"},{"internalType":"uint256","name":"_id","type":"uint256"}],"name":"rescueNFT","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_token","type":"address"},{"internalType":"address","name":"_to","type":"address"}],"name":"rescueToken","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_versionId","type":"uint256"}],"name":"setApprovalDefaultValidatorVersion","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_delayPeriod","type":"uint256"}],"name":"setApprovalDelayPeriod","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"operator","type":"address"},{"internalType":"bool","name":"approved","type":"bool"}],"name":"setApprovalForAll","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"_uri","type":"string"}],"name":"setDefaultURI","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bool","name":"_isOpen","type":"bool"}],"name":"setIsStage2Open","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bool","name":"_isOpen","type":"bool"}],"name":"setIsStage3Open","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_tokenId","type":"uint256"},{"internalType":"uint256","name":"_status","type":"uint256"}],"name":"setNFTValidatorStatus","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_delayPeriod","type":"uint256"}],"name":"setReclaimDelayPeriod","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_tokenId","type":"uint256"},{"internalType":"bool","name":"_flag","type":"bool"}],"name":"setReclaimable","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_tokenId","type":"uint256"},{"internalType":"string","name":"_tokenURI","type":"string"}],"name":"setTokenURI","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_treasuryWallet","type":"address"}],"name":"setTreasuryWallet","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_versionId","type":"uint256"}],"name":"setWhitelistDefaultValidatorVersion","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"_uri","type":"string"}],"name":"setapprovalPendingDefaultURI","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_tokenId","type":"uint256"}],"name":"tokenURI","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"transferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"unPause","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_validatorVersionIdx","type":"uint256"},{"internalType":"uint256","name":"_newTs","type":"uint256"}],"name":"updateTotalSupplyForValidatorVersion","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"validatorVersionCount","outputs":[{"internalType":"uint32","name":"","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"validatorVersions","outputs":[{"internalType":"string","name":"version","type":"string"},{"internalType":"address","name":"underlying","type":"address"},{"internalType":"uint256","name":"collateral","type":"uint256"},{"internalType":"uint256","name":"ts","type":"uint256"},{"internalType":"uint256","name":"minted","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"whitelistDefaultValidatorVersionIdx","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"whitelistMintNFT","outputs":[],"stateMutability":"nonpayable","type":"function"}]