ETH Price: $3,306.21 (-1.94%)

Contract

0x8DcC656FDb71Ffc9813f809FDbf4f29c1cED9f85
 

Overview

ETH Balance

0 ETH

Eth Value

$0.00

Multichain Info

No addresses found
Transaction Hash
Method
Block
From
To

There are no matching entries

Please try again later

View more zero value Internal Transactions in Advanced View mode

Advanced mode:
Loading...
Loading

Contract Source Code Verified (Exact Match)

Contract Name:
IntelliLinkerV2

Compiler Version
v0.8.7+commit.e28d00a7

Optimization Enabled:
Yes with 200 runs

Other Settings:
default evmVersion
File 1 of 14 : IntelliLinkerV2.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

import "../interfaces/ERC721Spec.sol";
import "./IntelligentNFTv2.sol";
import "../utils/UpgradeableAccessControl.sol";

/**
 * @title Intelligent Token Linker (iNFT Linker)
 *
 * @notice iNFT Linker is a helper smart contract responsible for managing iNFTs.
 *      It creates and destroys iNFTs, determines iNFT creation price and destruction fee.
 *
 * @dev Known limitations (to be resolved in the future releases):
 *      - doesn't check AI Personality / target NFT compatibility: any personality
 *        can be linked to any NFT (NFT contract must be whitelisted)
 *      - doesn't support unlinking + linking in a single transaction
 *      - doesn't support AI Personality smart contract upgrades: in case when new
 *        AI Personality contract is deployed, new iNFT Linker should also be deployed
 *
 * @dev V2 modification
 *      - supports two separate whitelists for linking and unlinking
 *      - is upgradeable
 *
 * @author Basil Gorin
 */
contract IntelliLinkerV2 is UpgradeableAccessControl {
	/**
	 * @dev iNFT Linker locks/unlocks ALI tokens defined by `aliContract` to mint/burn iNFT
	 */
	address public aliContract;

	/**
	 * @dev iNFT Linker locks/unlocks AI Personality defined by `personalityContract` to mint/burn iNFT
	 */
	address public personalityContract;

	/**
	 * @dev iNFT Linker mints/burns iNFTs defined by `iNftContract`
	 */
	address public iNftContract;

	/**
	 * @dev iNFTs may get created with the ALI tokens bound to them,
	 *      linking fee may get charged when creating an iNFT
	 *
	 * @dev Linking price, how much ALI tokens is charged upon iNFT creation;
	 *      `linkPrice - linkFee` is locked within the iNFT created
	 */
	uint96 public linkPrice;

	/**
	 * @dev iNFTs may get created with the ALI tokens bound to them,
	 *      linking fee may get charged when creating an iNFT
	 *
	 * @dev Linking fee, how much ALI tokens is sent into treasury `feeDestination`
	 *      upon iNFT creation
	 *
	 * @dev Both `linkFee` and `feeDestination` must be set for the fee to be charged;
	 *      both `linkFee` and `feeDestination` can be either set or unset
	 */
	uint96 public linkFee;

	/**
	 * @dev iNFTs may get created with the ALI tokens bound to them,
	 *      linking fee may get charged when creating an iNFT
	 *
	 * @dev Treasury `feeDestination` is an address to send linking fee to upon iNFT creation
	 *
	 * @dev Both `linkFee` and `feeDestination` must be set for the fee to be charged;
	 *      both `linkFee` and `feeDestination` can be either set or unset
	 */
	address public feeDestination;

	/**
	/**
	 * @dev Next iNFT ID to mint; initially this is the first "free" ID which can be minted;
	 *      at any point in time this should point to a free, mintable ID for iNFT
	 *
	 * @dev iNFT ID space up to 0xFFFF_FFFF (uint32 max) is reserved for the sales
	 */
	uint256 public nextId;

	/**
	 * @dev Target NFT Contracts allowed iNFT to be linked to;
	 *      is not taken into account if FEATURE_ALLOW_ANY_NFT_CONTRACT is enabled
	 * @dev Lowest bit (zero) defines if contract is allowed to be linked to;
	 *      Next bit (one) defines if contract is allowed to be unlinked from
	 */
	mapping(address => uint8) public whitelistedTargetContracts;

	/**
	 * @notice Enables iNFT linking (creation)
	 *
	 * @dev Feature FEATURE_LINKING must be enabled
	 *      as a prerequisite for `link()` function to succeed
	 */
	uint32 public constant FEATURE_LINKING = 0x0000_0001;

	/**
	 * @notice Enables iNFT unlinking (destruction)
	 *
	 * @dev Feature FEATURE_UNLINKING must be enabled
	 *      for the `unlink()` and `unlinkNFT()` functions to succeed
	 */
	uint32 public constant FEATURE_UNLINKING = 0x0000_0002;

	/**
	 * @notice Allows linker to link (mint) iNFT bound to any target NFT contract,
	 *      independently whether it was previously whitelisted or not
	 * @dev Feature FEATURE_ALLOW_ANY_NFT_CONTRACT allows linking (minting) iNFTs
	 *      bound to any target NFT contract, without a check if it's whitelisted in
	 *      `whitelistedTargetContracts` or not
	 */
	uint32 public constant FEATURE_ALLOW_ANY_NFT_CONTRACT = 0x0000_0004;

	/**
	 * @notice Enables depositing more ALI to already existing iNFTs
	 *
	 * @dev Feature FEATURE_DEPOSITS must be enabled
	 *      for the `deposit()` function to succeed
	 */
	uint32 public constant FEATURE_DEPOSITS = 0x0000_0008;

	/**
	 * @notice Enables ALI withdrawals from the iNFT (without destroying them)
	 *
	 * @dev Feature FEATURE_WITHDRAWALS must be enabled
	 *      for the `withdraw()` function to succeed
	 */
	uint32 public constant FEATURE_WITHDRAWALS = 0x0000_0010;

	/**
	 * @notice Link price manager is responsible for updating linking price
	 *
	 * @dev Role ROLE_LINK_PRICE_MANAGER allows `updateLinkPrice` execution,
	 *      and `linkPrice` modification
	 */
	uint32 public constant ROLE_LINK_PRICE_MANAGER = 0x0001_0000;

	/**
	 * @notice Next ID manager is responsible for updating `nextId` variable,
	 *      pointing to the next iNFT ID free slot
	 *
	 * @dev Role ROLE_NEXT_ID_MANAGER allows `updateNextId` execution,
	 *     and `nextId` modification
	 */
	uint32 public constant ROLE_NEXT_ID_MANAGER = 0x0002_0000;

	/**
	 * @notice Whitelist manager is responsible for managing the target NFT contracts
	 *     whitelist, which are the contracts iNFT is allowed to be bound to
	 *
	 * @dev Role ROLE_WHITELIST_MANAGER allows `whitelistTargetContract` execution,
	 *     and `whitelistedTargetContracts` mapping modification
	 */
	uint32 public constant ROLE_WHITELIST_MANAGER = 0x0004_0000;

	/**
	 * @dev Fired in link() when new iNFT is created
	 *
	 * @param _by an address which executed (and funded) the link function
	 * @param _iNftId ID of the iNFT minted
	 * @param _linkPrice amount of ALI tokens locked (transferred) to newly created iNFT
	 * @param _linkFee amount of ALI tokens charged as a fee and sent to the treasury
	 * @param _personalityContract AI Personality contract address
	 * @param _personalityId ID of the AI Personality locked (transferred) to newly created iNFT
	 * @param _targetContract target NFT smart contract
	 * @param _targetId target NFT ID (where this iNFT binds to and belongs to)
	 */
	event Linked(
		address indexed _by,
		uint256 _iNftId,
		uint96 _linkPrice,
		uint96 _linkFee,
		address indexed _personalityContract,
		uint96 indexed _personalityId,
		address _targetContract,
		uint256 _targetId
	);

	/**
	 * @dev Fired in unlink() when an existing iNFT gets destroyed
	 *
	 * @param _by an address which executed the unlink function
	 *      (and which received unlocked AI Personality and ALI tokens)
	 * @param _iNftId ID of the iNFT burnt
	 */
	event Unlinked(address indexed _by, uint256 indexed _iNftId);

	/**
	 * @dev Fired in deposit(), withdraw() when an iNFT ALI balance gets changed
	 *
	 * @param _by an address which executed the deposit/withdraw function
	 *      (in case of withdraw it received unlocked ALI tokens)
	 * @param _iNftId ID of the iNFT to update
	 * @param _aliDelta locked ALI tokens delta, positive for deposit, negative for withdraw
	 * @param _feeValue amount of ALI tokens charged as a fee
	 */
	event LinkUpdated(address indexed _by, uint256 indexed _iNftId, int128 _aliDelta, uint96 _feeValue);

	/**
	 * @dev Fired in updateLinkPrice()
	 *
	 * @param _by an address which executed the operation
	 * @param _linkPrice new linking price set
	 * @param _linkFee new linking fee set
	 * @param _feeDestination new treasury address set
	 */
	event LinkPriceChanged(address indexed _by, uint96 _linkPrice, uint96 _linkFee, address indexed _feeDestination);

	/**
	 * @dev Fired in updateNextId()
	 *
	 * @param _by an address which executed the operation
	 * @param _oldVal old nextId value
	 * @param _newVal new nextId value
	 */
	event NextIdChanged(address indexed _by, uint256 _oldVal, uint256 _newVal);

	/**
	 * @dev Fired in whitelistTargetContract()
	 *
	 * @param _by an address which executed the operation
	 * @param _targetContract target NFT contract address affected
	 * @param _oldVal old whitelisted raw value (contains both linking/unlinking flags)
	 * @param _newVal new whitelisted raw value (contains both linking/unlinking flags)
	 */
	event TargetContractWhitelisted(address indexed _by, address indexed _targetContract, uint8 _oldVal, uint8 _newVal);

	/**
	 * @dev "Constructor replacement" for upgradeable, must be execute immediately after deployment
	 *      see https://docs.openzeppelin.com/upgrades-plugins/1.x/writing-upgradeable#initializers
	 *
	 * @dev Binds an iNFT Linker instance to already deployed
	 *      iNFT, AI Personality and ALI Token instances
	 *
	 * @param _ali address of the deployed ALI ERC20 Token instance the iNFT Linker is bound to
	 * @param _personality address of the deployed AI Personality instance the iNFT Linker is bound to
	 * @param _iNft address of the deployed iNFT instance the iNFT Linker is bound to
	 */
	function postConstruct(address _ali, address _personality, address _iNft) public virtual initializer {
		// verify inputs are set
		require(_ali != address(0), "ALI Token addr is not set");
		require(_personality != address(0), "AI Personality addr is not set");
		require(_iNft != address(0), "iNFT addr is not set");

		// verify inputs are valid smart contracts of the expected interfaces
		require(ERC165(_ali).supportsInterface(type(ERC20).interfaceId), "unexpected ALI Token type");
		require(ERC165(_personality).supportsInterface(type(ERC721).interfaceId), "unexpected AI Personality type");
		require(ERC165(_iNft).supportsInterface(type(IntelligentNFTv2Spec).interfaceId), "unexpected iNFT type");

		// setup smart contract internal state
		aliContract = _ali;
		personalityContract = _personality;
		iNftContract = _iNft;

		// setup the defaults
		// linkPrice = 2_000 ether; // we use "ether" suffix instead of "e18"
		// iNFT ID space up to 0xFFFF_FFFF (uint32 max) is reserved for the sales
		// iNFT ID space up to 0x1_FFFF_FFFF is reserved for IntelliLinker (v1, non-upgradeable)
		nextId = 0x2_0000_0000;

		// execute all parent initializers in cascade
		UpgradeableAccessControl._postConstruct(msg.sender);
	}

	/**
	 * @notice Links given AI Personality with the given NFT and forms an iNFT.
	 *      AI Personality specified and `linkPrice` ALI are transferred into minted iNFT
	 *      and are effectively locked within an iNFT until it is destructed (burnt)
	 *
	 * @dev AI Personality and ALI tokens are transferred from the transaction sender account
	 *      to iNFT smart contract
	 * @dev Sender must approve both AI Personality and ALI tokens transfers to be
	 *      performed by the linker contract
	 *
	 * @param personalityId AI Personality ID to be locked into iNFT
	 * @param targetContract NFT address iNFT to be linked to
	 * @param targetId NFT ID iNFT to be linked to
	 */
	function link(uint96 personalityId, address targetContract, uint256 targetId) public virtual {
		// verify linking is enabled
		require(isFeatureEnabled(FEATURE_LINKING), "linking is disabled");

		// verify AI Personality belongs to transaction sender
		require(ERC721(personalityContract).ownerOf(personalityId) == msg.sender, "access denied");
		// verify NFT contract is either whitelisted or any NFT contract is allowed globally
		require(
			isAllowedForLinking(targetContract) || isFeatureEnabled(FEATURE_ALLOW_ANY_NFT_CONTRACT),
			"not a whitelisted NFT contract"
		);

		// if linking fee is set
		if(linkFee > 0) {
			// transfer ALI tokens to the treasury - `feeDestination`
			ERC20(aliContract).transferFrom(msg.sender, feeDestination, linkFee);
		}

		// if linking price is set
		if(linkPrice > 0) {
			// transfer ALI tokens to iNFT contract to be locked
			ERC20(aliContract).transferFrom(msg.sender, iNftContract, linkPrice - linkFee);
		}

		// transfer AI Personality to iNFT contract to be locked
		ERC721(personalityContract).transferFrom(msg.sender, iNftContract, personalityId);

		// mint the next iNFT, increment next iNFT ID to be minted
		IntelligentNFTv2(iNftContract).mint(nextId++, linkPrice - linkFee, personalityContract, personalityId, targetContract, targetId);

		// emit an event
		emit Linked(msg.sender, nextId - 1, linkPrice, linkFee, personalityContract, personalityId, targetContract, targetId);
	}

	/**
	 * @notice Destroys given iNFT, unlinking it from underlying NFT and unlocking
	 *      the AI Personality and ALI tokens locked in iNFT.
	 *      AI Personality and ALI tokens are transferred to the underlying NFT owner
	 *
	 * @dev Can be executed only by iNFT owner (effectively underlying NFT owner)
	 *
	 * @param iNftId ID of the iNFT to unlink
	 */
	function unlink(uint256 iNftId) public virtual {
		// verify unlinking is enabled
		require(isFeatureEnabled(FEATURE_UNLINKING), "unlinking is disabled");

		// get a link to an iNFT contract to perform several actions with it
		IntelligentNFTv2 iNFT = IntelligentNFTv2(iNftContract);

		// get target NFT contract address from the iNFT binding
		(,,,address targetContract,) = iNFT.bindings(iNftId);
		// verify NFT contract is either whitelisted or any NFT contract is allowed globally
		require(
			isAllowedForUnlinking(targetContract) || isFeatureEnabled(FEATURE_ALLOW_ANY_NFT_CONTRACT),
			"not a whitelisted NFT contract"
		);

		// verify the transaction is executed by iNFT owner (effectively by underlying NFT owner)
		require(iNFT.ownerOf(iNftId) == msg.sender, "not an iNFT owner");

		// burn the iNFT unlocking the AI Personality and ALI tokens - delegate to `IntelligentNFTv2.burn`
		iNFT.burn(iNftId);

		// emit an event
		emit Unlinked(msg.sender, iNftId);
	}

	/**
	 * @notice Unlinks given NFT by destroying iNFTs and unlocking
	 *      the AI Personality and ALI tokens locked in iNFTs.
	 *      AI Personality and ALI tokens are transferred to the underlying NFT owner
	 *
	 * @dev Can be executed only by NFT owner (effectively underlying NFT owner)
	 *
	 * @param nftContract NFT address iNFTs to be unlinked to
	 * @param nftId NFT ID iNFTs to be unlinked to
	 */
	function unlinkNFT(address nftContract, uint256 nftId) public virtual {
		// verify unlinking is enabled
		require(isFeatureEnabled(FEATURE_UNLINKING), "unlinking is disabled");

		// get a link to an iNFT contract to perform several actions with it
		IntelligentNFTv2 iNFT = IntelligentNFTv2(iNftContract);

		// verify the transaction is executed by NFT owner
		require(ERC721(nftContract).ownerOf(nftId) == msg.sender, "not an NFT owner");

		// get iNFT ID linked with given NFT
		uint256 iNftId = iNFT.reverseBindings(nftContract, nftId);

		// verify NFT contract is either whitelisted or any NFT contract is allowed globally
		require(
			isAllowedForUnlinking(nftContract) || isFeatureEnabled(FEATURE_ALLOW_ANY_NFT_CONTRACT),
			"not a whitelisted NFT contract"
		);

		// burn the iNFT unlocking the AI Personality and ALI tokens - delegate to `IntelligentNFTv2.burn`
		iNFT.burn(iNftId);

		// emit an event
		emit Unlinked(msg.sender, iNftId);
	}

	/**
	 * @notice Deposits additional ALI tokens into already existing iNFT
	 *
	 * @dev Can be executed only by NFT owner (effectively underlying NFT owner)
	 *
	 * @dev ALI tokens are transferred from the transaction sender account to iNFT smart contract
	 *      Sender must approve ALI tokens transfers to be performed by the linker contract
	 *
	 * @param iNftId ID of the iNFT to transfer (and lock) tokens to
	 * @param aliValue amount of ALI tokens to transfer (and lock)
	 */
	function deposit(uint256 iNftId, uint96 aliValue) public virtual {
		// verify deposits are enabled
		require(isFeatureEnabled(FEATURE_DEPOSITS), "deposits are disabled");

		// get a link to an iNFT contract to perform several actions with it
		IntelligentNFTv2 iNFT = IntelligentNFTv2(iNftContract);

		// verify the transaction is executed by iNFT owner (effectively by underlying NFT owner)
		require(iNFT.ownerOf(iNftId) == msg.sender, "not an iNFT owner");

		// effective ALI value locked in iNFT may get altered according to the linking fee set
		// init effective fee as if linking fee is not set
		uint96 _linkFee = 0;
		// init effective ALI value locked as if linking fee is not set
		uint96 _aliValue = aliValue;
		// in case when link price/fee are set (effectively meaning fee percent is set)
		if(linkPrice != 0 && linkFee != 0) {
			// we need to make sure the fee is charged from the value supplied
			// proportionally to the value supplied and fee percent
			_linkFee = uint96(uint256(_aliValue) * linkFee / linkPrice);

			// recalculate ALI value to be locked accordingly
			_aliValue = aliValue - _linkFee;

			// transfer ALI tokens to the treasury - `feeDestination`
			ERC20(aliContract).transferFrom(msg.sender, feeDestination, _linkFee);
		}

		// transfer ALI tokens to iNFT contract to be locked
		ERC20(aliContract).transferFrom(msg.sender, iNftContract, _aliValue);

		// update the iNFT record
		iNFT.increaseAli(iNftId, _aliValue);

		// emit an event
		emit LinkUpdated(msg.sender, iNftId, int128(uint128(_aliValue)), _linkFee);
	}

	/**
	 * @notice Withdraws some ALI tokens from already existing iNFT without destroying it
	 *
	 * @dev Can be executed only by NFT owner (effectively underlying NFT owner)
	 *
	 * @dev ALI tokens are transferred to the iNFT owner (transaction executor)
	 *
	 * @param iNftId ID of the iNFT to unlock tokens from
	 * @param aliValue amount of ALI tokens to unlock
	 */
	function withdraw(uint256 iNftId, uint96 aliValue) public virtual {
		// verify withdrawals are enabled
		require(isFeatureEnabled(FEATURE_WITHDRAWALS), "withdrawals are disabled");

		// get a link to an iNFT contract to perform several actions with it
		IntelligentNFTv2 iNFT = IntelligentNFTv2(iNftContract);

		// verify the transaction is executed by iNFT owner (effectively by underlying NFT owner)
		require(iNFT.ownerOf(iNftId) == msg.sender, "not an iNFT owner");

		// ensure iNFT locked balance doesn't go below `linkPrice - linkFee`
		require(iNFT.lockedValue(iNftId) >= aliValue + linkPrice, "deposit too low");

		// update the iNFT record and transfer tokens back to the iNFT owner
		iNFT.decreaseAli(iNftId, aliValue, msg.sender);

		// emit an event
		emit LinkUpdated(msg.sender, iNftId, -int128(uint128(aliValue)), 0);
	}

	/**
	 * @dev Restricted access function to modify
	 *      - linking price `linkPrice`,
	 *      - linking fee `linkFee`, and
	 *      - treasury address `feeDestination`
	 *
	 * @dev Requires executor to have ROLE_LINK_PRICE_MANAGER permission
	 * @dev Requires linking price to be either unset (zero), or not less than 1e12 (0.000001 ALI)
	 * @dev Requires both linking fee and treasury address to be either set or unset (zero);
	 *      if set, linking fee must not be less than 1e12 (0.000001 ALI);
	 *      if set, linking fee must not exceed linking price
	 *
	 * @param _linkPrice new linking price to be set
	 * @param _linkFee new linking fee to be set
	 * @param _feeDestination treasury address
	 */
	function updateLinkPrice(uint96 _linkPrice, uint96 _linkFee, address _feeDestination) public virtual {
		// verify the access permission
		require(isSenderInRole(ROLE_LINK_PRICE_MANAGER), "access denied");

		// verify the price is not too low if it's set
		require(_linkPrice == 0 || _linkPrice >= 1e12, "invalid price");

		// linking fee/treasury should be either both set or both unset
		// linking fee must not be too low if set
		require(_linkFee == 0 && _feeDestination == address(0) || _linkFee >= 1e12 && _feeDestination != address(0), "invalid linking fee/treasury");
		// linking fee must not exceed linking price
		require(_linkFee <= _linkPrice, "linking fee exceeds linking price");

		// update the linking price, fee, and treasury address
		linkPrice = _linkPrice;
		linkFee = _linkFee;
		feeDestination = _feeDestination;

		// emit an event
		emit LinkPriceChanged(msg.sender, _linkPrice, _linkFee, _feeDestination);
	}

	/**
	 * @dev Restricted access function to modify next iNFT ID `nextId`
	 *
	 * @param _nextId new next iNFT ID to be set
	 */
	function updateNextId(uint256 _nextId) public virtual {
		// verify the access permission
		require(isSenderInRole(ROLE_NEXT_ID_MANAGER), "access denied");

		// verify nextId is in safe bounds
		require(_nextId > 0xFFFF_FFFF, "value too low");

		// emit a event
		emit NextIdChanged(msg.sender, nextId, _nextId);

		// update next ID
		nextId = _nextId;
	}

	/**
	 * @dev Restricted access function to manage whitelisted NFT contracts mapping `whitelistedTargetContracts`
	 *
	 * @dev Requires executor to have ROLE_WHITELIST_MANAGER permission
	 *
	 * @param targetContract target NFT contract address to add/remove to/from the whitelist
	 * @param allowedForLinking true to add, false to remove to/from whitelist (allowed for linking)
	 * @param allowedForUnlinking true to add, false to remove to/from whitelist (allowed for unlinking)
	 */
	function whitelistTargetContract(
		address targetContract,
		bool allowedForLinking,
		bool allowedForUnlinking
	) public virtual {
		// verify the access permission
		require(isSenderInRole(ROLE_WHITELIST_MANAGER), "access denied");

		// verify the address is set
		require(targetContract != address(0), "zero address");

		// delisting is always possible, whitelisting - only for valid ERC721
		if(allowedForLinking) {
			// verify targetContract is a valid ERC721
			require(ERC165(targetContract).supportsInterface(type(ERC721).interfaceId), "target NFT is not ERC721");
		}

		// derive the uint8 value representing two boolean flags:
		// Lowest bit (zero) defines if contract is allowed to be linked to;
		// Next bit (one) defines if contract is allowed to be unlinked from
		uint8 newVal = (allowedForLinking? 0x1: 0x0) | (allowedForUnlinking? 0x2: 0x0);

		// emit an event
		emit TargetContractWhitelisted(msg.sender, targetContract, whitelistedTargetContracts[targetContract], newVal);

		// update the contract address in the whitelist
		whitelistedTargetContracts[targetContract] = newVal;
	}

	/**
	 * @notice Checks if specified target NFT contract is allowed to be linked to
	 *
	 * @dev Using this function can be more convenient than accessing the
	 *      `whitelistedTargetContracts` directly since the mapping contains linking/unlinking
	 *      flags packed into uint8
	 *
	 * @param targetContract target NFT contract address to query for
	 * @return true if target NFT contract is allowed to be linked to, false otherwise
	 */
	function isAllowedForLinking(address targetContract) public view virtual returns (bool) {
		// read the mapping and extract the lowest bit (zero) containing information required
		return whitelistedTargetContracts[targetContract] & 0x1 == 0x1;
	}

	/**
	 * @notice Checks if specified target NFT contract is allowed to be unlinked from
	 *
	 * @dev Using this function can be more convenient than accessing the
	 *      `whitelistedTargetContracts` directly since the mapping contains linking/unlinking
	 *      flags packed into uint8
	 *
	 * @param targetContract target NFT contract address to query for
	 * @return true if target NFT contract is allowed to be unlinked from, false otherwise
	 */
	function isAllowedForUnlinking(address targetContract) public view virtual returns (bool) {
		// read the mapping and extract the next bit (one) containing information required
		return whitelistedTargetContracts[targetContract] & 0x2 == 0x2;
	}
}

File 2 of 14 : ERC721Spec.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

import "./ERC165Spec.sol";

/**
 * @title ERC-721 Non-Fungible Token Standard
 *
 * @notice See https://eips.ethereum.org/EIPS/eip-721
 *
 * @dev Solidity issue #3412: The ERC721 interfaces include explicit mutability guarantees for each function.
 *      Mutability guarantees are, in order weak to strong: payable, implicit nonpayable, view, and pure.
 *      Implementation MUST meet the mutability guarantee in this interface and MAY meet a stronger guarantee.
 *      For example, a payable function in this interface may be implemented as nonpayable
 *      (no state mutability specified) in implementing contract.
 *      It is expected a later Solidity release will allow stricter contract to inherit from this interface,
 *      but current workaround is that we edit this interface to add stricter mutability before inheriting:
 *      we have removed all "payable" modifiers.
 *
 * @dev The ERC-165 identifier for this interface is 0x80ac58cd.
 *
 * @author William Entriken, Dieter Shirley, Jacob Evans, Nastassia Sachs
 */
interface ERC721 is ERC165 {
	/// @dev This emits when ownership of any NFT changes by any mechanism.
	///  This event emits when NFTs are created (`from` == 0) and destroyed
	///  (`to` == 0). Exception: during contract creation, any number of NFTs
	///  may be created and assigned without emitting Transfer. At the time of
	///  any transfer, the approved address for that NFT (if any) is reset to none.
	event Transfer(address indexed _from, address indexed _to, uint256 indexed _tokenId);

	/// @dev This emits when the approved address for an NFT is changed or
	///  reaffirmed. The zero address indicates there is no approved address.
	///  When a Transfer event emits, this also indicates that the approved
	///  address for that NFT (if any) is reset to none.
	event Approval(address indexed _owner, address indexed _approved, uint256 indexed _tokenId);

	/// @dev This emits when an operator is enabled or disabled for an owner.
	///  The operator can manage all NFTs of the owner.
	event ApprovalForAll(address indexed _owner, address indexed _operator, bool _approved);

	/// @notice Count all NFTs assigned to an owner
	/// @dev NFTs assigned to the zero address are considered invalid, and this
	///  function throws for queries about the zero address.
	/// @param _owner An address for whom to query the balance
	/// @return The number of NFTs owned by `_owner`, possibly zero
	function balanceOf(address _owner) external view returns (uint256);

	/// @notice Find the owner of an NFT
	/// @dev NFTs assigned to zero address are considered invalid, and queries
	///  about them do throw.
	/// @param _tokenId The identifier for an NFT
	/// @return The address of the owner of the NFT
	function ownerOf(uint256 _tokenId) external view returns (address);

	/// @notice Transfers the ownership of an NFT from one address to another address
	/// @dev Throws unless `msg.sender` is the current owner, an authorized
	///  operator, or the approved address for this NFT. Throws if `_from` is
	///  not the current owner. Throws if `_to` is the zero address. Throws if
	///  `_tokenId` is not a valid NFT. When transfer is complete, this function
	///  checks if `_to` is a smart contract (code size > 0). If so, it calls
	///  `onERC721Received` on `_to` and throws if the return value is not
	///  `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`.
	/// @param _from The current owner of the NFT
	/// @param _to The new owner
	/// @param _tokenId The NFT to transfer
	/// @param _data Additional data with no specified format, sent in call to `_to`
	function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes calldata _data) external /*payable*/;

	/// @notice Transfers the ownership of an NFT from one address to another address
	/// @dev This works identically to the other function with an extra data parameter,
	///  except this function just sets data to "".
	/// @param _from The current owner of the NFT
	/// @param _to The new owner
	/// @param _tokenId The NFT to transfer
	function safeTransferFrom(address _from, address _to, uint256 _tokenId) external /*payable*/;

	/// @notice Transfer ownership of an NFT -- THE CALLER IS RESPONSIBLE
	///  TO CONFIRM THAT `_to` IS CAPABLE OF RECEIVING NFTS OR ELSE
	///  THEY MAY BE PERMANENTLY LOST
	/// @dev Throws unless `msg.sender` is the current owner, an authorized
	///  operator, or the approved address for this NFT. Throws if `_from` is
	///  not the current owner. Throws if `_to` is the zero address. Throws if
	///  `_tokenId` is not a valid NFT.
	/// @param _from The current owner of the NFT
	/// @param _to The new owner
	/// @param _tokenId The NFT to transfer
	function transferFrom(address _from, address _to, uint256 _tokenId) external /*payable*/;

	/// @notice Change or reaffirm the approved address for an NFT
	/// @dev The zero address indicates there is no approved address.
	///  Throws unless `msg.sender` is the current NFT owner, or an authorized
	///  operator of the current owner.
	/// @param _approved The new approved NFT controller
	/// @param _tokenId The NFT to approve
	function approve(address _approved, uint256 _tokenId) external /*payable*/;

	/// @notice Enable or disable approval for a third party ("operator") to manage
	///  all of `msg.sender`'s assets
	/// @dev Emits the ApprovalForAll event. The contract MUST allow
	///  multiple operators per owner.
	/// @param _operator Address to add to the set of authorized operators
	/// @param _approved True if the operator is approved, false to revoke approval
	function setApprovalForAll(address _operator, bool _approved) external;

	/// @notice Get the approved address for a single NFT
	/// @dev Throws if `_tokenId` is not a valid NFT.
	/// @param _tokenId The NFT to find the approved address for
	/// @return The approved address for this NFT, or the zero address if there is none
	function getApproved(uint256 _tokenId) external view returns (address);

	/// @notice Query if an address is an authorized operator for another address
	/// @param _owner The address that owns the NFTs
	/// @param _operator The address that acts on behalf of the owner
	/// @return True if `_operator` is an approved operator for `_owner`, false otherwise
	function isApprovedForAll(address _owner, address _operator) external view returns (bool);
}

/// @dev Note: the ERC-165 identifier for this interface is 0x150b7a02.
interface ERC721TokenReceiver {
	/// @notice Handle the receipt of an NFT
	/// @dev The ERC721 smart contract calls this function on the recipient
	///  after a `transfer`. This function MAY throw to revert and reject the
	///  transfer. Return of other than the magic value MUST result in the
	///  transaction being reverted.
	///  Note: the contract address is always the message sender.
	/// @param _operator The address which called `safeTransferFrom` function
	/// @param _from The address which previously owned the token
	/// @param _tokenId The NFT identifier which is being transferred
	/// @param _data Additional data with no specified format
	/// @return `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`
	///  unless throwing
	function onERC721Received(address _operator, address _from, uint256 _tokenId, bytes calldata _data) external returns(bytes4);
}

/**
 * @title ERC-721 Non-Fungible Token Standard, optional metadata extension
 *
 * @notice See https://eips.ethereum.org/EIPS/eip-721
 *
 * @dev The ERC-165 identifier for this interface is 0x5b5e139f.
 *
 * @author William Entriken, Dieter Shirley, Jacob Evans, Nastassia Sachs
 */
interface ERC721Metadata is ERC721 {
	/// @notice A descriptive name for a collection of NFTs in this contract
	function name() external view returns (string memory _name);

	/// @notice An abbreviated name for NFTs in this contract
	function symbol() external view returns (string memory _symbol);

	/// @notice A distinct Uniform Resource Identifier (URI) for a given asset.
	/// @dev Throws if `_tokenId` is not a valid NFT. URIs are defined in RFC
	///  3986. The URI may point to a JSON file that conforms to the "ERC721
	///  Metadata JSON Schema".
	function tokenURI(uint256 _tokenId) external view returns (string memory);
}

/**
 * @title ERC-721 Non-Fungible Token Standard, optional enumeration extension
 *
 * @notice See https://eips.ethereum.org/EIPS/eip-721
 *
 * @dev The ERC-165 identifier for this interface is 0x780e9d63.
 *
 * @author William Entriken, Dieter Shirley, Jacob Evans, Nastassia Sachs
 */
interface ERC721Enumerable is ERC721 {
	/// @notice Count NFTs tracked by this contract
	/// @return A count of valid NFTs tracked by this contract, where each one of
	///  them has an assigned and queryable owner not equal to the zero address
	function totalSupply() external view returns (uint256);

	/// @notice Enumerate valid NFTs
	/// @dev Throws if `_index` >= `totalSupply()`.
	/// @param _index A counter less than `totalSupply()`
	/// @return The token identifier for the `_index`th NFT,
	///  (sort order not specified)
	function tokenByIndex(uint256 _index) external view returns (uint256);

	/// @notice Enumerate NFTs assigned to an owner
	/// @dev Throws if `_index` >= `balanceOf(_owner)` or if
	///  `_owner` is the zero address, representing invalid NFTs.
	/// @param _owner An address where we are interested in NFTs owned by them
	/// @param _index A counter less than `balanceOf(_owner)`
	/// @return The token identifier for the `_index`th NFT assigned to `_owner`,
	///   (sort order not specified)
	function tokenOfOwnerByIndex(address _owner, uint256 _index) external view returns (uint256);
}

File 3 of 14 : IntelligentNFTv2.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

import "../interfaces/ERC20Spec.sol";
import "../interfaces/ERC721Spec.sol";
import "../lib/StringUtils.sol";
import "../utils/AccessControl.sol";

/**
 * @title Intelligent NFT Interface
 *        Version 2
 *
 * @notice External interface of IntelligentNFTv2 declared to support ERC165 detection.
 *      Despite some similarity with ERC721 interfaces, iNFT is not ERC721, any similarity
 *      should be treated as coincidental. Client applications may benefit from this similarity
 *      to reuse some of the ERC721 client code for display/reading.
 *
 * @dev See Intelligent NFT documentation below.
 *
 * @author Basil Gorin
 */
interface IntelligentNFTv2Spec {
	/**
	 * @dev ERC20/ERC721 like name - Intelligent NFT
	 *
	 * @return "Intelligent NFT"
	 */
	function name() external view returns (string memory);

	/**
	 * @dev ERC20/ERC721 like symbol - iNFT
	 *
	 * @return "iNFT"
	 */
	function symbol() external view returns (string memory);

	/**
	 * @dev ERC721 like link to the iNFT metadata
	 *
	 * @param recordId iNFT ID to get metadata URI for
	 */
	function tokenURI(uint256 recordId) external view returns (string memory);

	/**
	 * @dev ERC20/ERC721 like counter of the iNFTs in existence (upper bound),
	 *      some (or all) of which may not exist due to target NFT destruction
	 *
	 * @return amount of iNFT tracked by this smart contract
	 */
	function totalSupply() external view returns (uint256);

	/**
	 * @dev Check if iNFT binding with the given ID exists
	 *
	 * @return true if iNFT binding exist, false otherwise
	 */
	function exists(uint256 recordId) external view returns (bool);

	/**
	 * @dev ERC721 like function to get owner of the iNFT, which is by definition
	 *      an owner of the underlying NFT
	 */
	function ownerOf(uint256 recordId) external view returns (address);
}

/**
 * @title Intelligent NFT (iNFT)
 *        Version 2
 *
 * @notice Intelligent NFT (iNFT) represents an enhancement to an existing NFT
 *      (we call it a "target" or "target NFT"), it binds a GPT-3 prompt (a "personality prompt",
 *      delivered as a Personality Pod ERC721 token bound to iNFT)
 *      to the target to embed intelligence, is controlled and belongs to the owner of the target.
 *
 * @notice iNFT stores AI Personality and some amount of ALI tokens locked, available for
 *      unlocking when iNFT is destroyed
 *
 * @notice iNFT is not an ERC721 token, but it has some very limited similarity to an ERC721:
 *      every record is identified by ID and this ID has an owner, which is effectively the target NFT owner;
 *      still, it doesn't store ownership information itself and fully relies on the target ownership instead
 *
 * @dev Internally iNFTs consist of:
 *      - target NFT - smart contract address and ID of the NFT the iNFT is bound to
 *      - AI Personality - smart contract address and ID of the AI Personality used to produce given iNFT,
 *        representing a "personality prompt", and locked within an iNFT
 *      - ALI tokens amount - amount of the ALI tokens used to produce given iNFT, also locked
 *
 * @dev iNFTs can be
 *      - created, this process requires an AI Personality and ALI tokens to be locked
 *      - destroyed, this process releases an AI Personality and ALI tokens previously locked
 *
 * @author Basil Gorin
 */
contract IntelligentNFTv2 is IntelligentNFTv2Spec, AccessControl, ERC165 {
	/**
	 * @inheritdoc IntelligentNFTv2Spec
	 */
	string public override name = "Intelligent NFT";

	/**
	 * @inheritdoc IntelligentNFTv2Spec
	 */
	string public override symbol = "iNFT";

	/**
	 * @dev Each intelligent token, represented by its unique ID, is bound to the target NFT,
	 *      defined by the pair of the target NFT smart contract address and unique token ID
	 *      within the target NFT smart contract
	 *
	 * @dev Effectively iNFT is owned by the target NFT owner
	 *
	 * @dev Additionally, each token holds an AI Personality and some amount of ALI tokens bound to it
	 *
	 * @dev `IntelliBinding` keeps all the binding information, including target NFT coordinates,
	 *      bound AI Personality ID, and amount of ALI ERC20 tokens bound to the iNFT
	 */
	struct IntelliBinding {
		// Note: structure members are reordered to fit into less memory slots, see EVM memory layout
		// ----- SLOT.1 (256/256)
		/**
		 * @dev Specific AI Personality is defined by the pair of AI Personality smart contract address
		 *       and AI Personality ID
		 *
		 * @dev Address of the AI Personality smart contract
		 */
		address personalityContract;

		/**
		 * @dev AI Personality ID within the AI Personality smart contract
		 */
		uint96 personalityId;

		// ----- SLOT.2 (256/256)
		/**
		 * @dev Amount of an ALI ERC20 tokens bound to (owned by) the iNFTs
		 *
		 * @dev ALI ERC20 smart contract address is defined globally as `aliContract` constant
		 */
		uint96 aliValue;

		/**
		 * @dev Address of the target NFT deployed smart contract,
		 *      this is a contract a particular iNFT is bound to
		 */
		address targetContract;

		// ----- SLOT.3 (256/256)
		/**
		 * @dev Target NFT ID within the target NFT smart contract,
		 *      effectively target NFT ID and contract address define the owner of an iNFT
		 */
		uint256 targetId;
	}

	/**
	 * @notice iNFT binding storage, stores binding information for each existing iNFT
	 * @dev Maps iNFT ID to its binding data, which includes underlying NFT data
	 */
	mapping(uint256 => IntelliBinding) public bindings;

	/**
	 * @notice Reverse iNFT binding allows to find iNFT bound to a particular NFT
	 * @dev Maps target NFT (smart contract address and unique token ID) to the iNFT ID:
	 *      NFT Contract => NFT ID => iNFT ID
	 */
	mapping(address => mapping(uint256 => uint256)) public reverseBindings;

	/**
	 * @notice Ai Personality to iNFT binding allows to find iNFT bound to a particular Ai Personality
	 * @dev Maps Ai Personality NFT (unique token ID) to the linked iNFT:
	 *      AI Personality Contract => AI Personality ID => iNFT ID
	 */
	mapping(address => mapping(uint256 => uint256)) public personalityBindings;

	/**
	 * @notice Total amount (maximum value estimate) of iNFT in existence.
	 *       This value can be higher than number of effectively accessible iNFTs
	 *       since when underlying NFT gets burned this value doesn't get updated.
	 */
	uint256 public override totalSupply;

	/**
	 * @notice Each iNFT holds some ALI tokens, which are tracked by the ALI token ERC20 smart contract defined here
	 */
	address public immutable aliContract;

	/**
	 * @notice ALI token balance the contract is aware of, cumulative ALI obligation,
	 *      i.e. sum of all iNFT locked ALI balances
	 *
	 * @dev Sum of all `IntelliBinding.aliValue` for each iNFT in existence
	 */
	uint256 public aliBalance;

	/**
	 * @dev Base URI is used to construct ERC721Metadata.tokenURI as
	 *      `base URI + token ID` if token URI is not set (not present in `_tokenURIs` mapping)
	 *
	 * @dev For example, if base URI is https://api.com/token/, then token #1
	 *      will have an URI https://api.com/token/1
	 *
	 * @dev If token URI is set with `setTokenURI()` it will be returned as is via `tokenURI()`
	 */
	string public baseURI = "";

	/**
	 * @dev Optional mapping for token URIs to be returned as is when `tokenURI()`
	 *      is called; if mapping doesn't exist for token, the URI is constructed
	 *      as `base URI + token ID`, where plus (+) denotes string concatenation
	 */
	mapping(uint256 => string) internal _tokenURIs;

	/**
	 * @notice Minter is responsible for creating (minting) iNFTs
	 *
	 * @dev Role ROLE_MINTER allows minting iNFTs (calling `mint` function)
	 */
	uint32 public constant ROLE_MINTER = 0x0001_0000;

	/**
	 * @notice Burner is responsible for destroying (burning) iNFTs
	 *
	 * @dev Role ROLE_BURNER allows burning iNFTs (calling `burn` function)
	 */
	uint32 public constant ROLE_BURNER = 0x0002_0000;

	/**
	 * @notice Editor is responsible for editing (updating) iNFT records in general,
	 *      adding/removing locked ALI tokens to/from iNFT in particular
	 *
	 * @dev Role ROLE_EDITOR allows editing iNFTs (calling `increaseAli`, `decreaseAli` functions)
	 */
	uint32 public constant ROLE_EDITOR = 0x0004_0000;

	/**
	 * @notice URI manager is responsible for managing base URI
	 *      part of the token URI ERC721Metadata interface
	 *
	 * @dev Role ROLE_URI_MANAGER allows updating the base URI
	 *      (executing `setBaseURI` function)
	 */
	uint32 public constant ROLE_URI_MANAGER = 0x0010_0000;

	/**
	 * @dev Fired in setBaseURI()
	 *
	 * @param _by an address which executed update
	 * @param _oldVal old _baseURI value
	 * @param _newVal new _baseURI value
	 */
	event BaseURIUpdated(address indexed _by, string _oldVal, string _newVal);

	/**
	 * @dev Fired in setTokenURI()
	 *
	 * @param _by an address which executed update
	 * @param _tokenId token ID which URI was updated
	 * @param _oldVal old _baseURI value
	 * @param _newVal new _baseURI value
	 */
	event TokenURIUpdated(address indexed _by, uint256 indexed _tokenId, string _oldVal, string _newVal);

	/**
	 * @dev Fired in mint() when new iNFT is created
	 *
	 * @param _by an address which executed the mint function
	 * @param _owner current owner of the NFT
	 * @param _recordId ID of the iNFT minted (created, bound)
	 * @param _aliValue amount of ALI tokens locked within newly created iNFT
	 * @param _personalityContract AI Personality smart contract address
	 * @param _personalityId ID of the AI Personality locked within newly created iNFT
	 * @param _targetContract target NFT smart contract address
	 * @param _targetId target NFT ID (where this iNFT binds to and belongs to)
	 */
	event Minted(
		address indexed _by,
		address indexed _owner,
		uint256 indexed _recordId,
		uint96 _aliValue,
		address _personalityContract,
		uint96 _personalityId,
		address _targetContract,
		uint256 _targetId
	);

	/**
	 * @dev Fired in increaseAli() and decreaseAli() when iNFT record is updated
	 *
	 * @param _by an address which executed the update
	 * @param _owner iNFT (target NFT) owner
	 * @param _recordId ID of the updated iNFT
	 * @param _oldAliValue amount of ALI tokens locked within iNFT before update
	 * @param _newAliValue amount of ALI tokens locked within iNFT after update
	 */
	event Updated(
		address indexed _by,
		address indexed _owner,
		uint256 indexed _recordId,
		uint96 _oldAliValue,
		uint96 _newAliValue
	);

	/**
	 * @dev Fired in burn() when an existing iNFT gets destroyed
	 *
	 * @param _by an address which executed the burn function
	 * @param _recordId ID of the iNFT burnt (destroyed, unbound)
	 * @param _recipient and address which received unlocked AI Personality and ALI tokens
	 * @param _aliValue amount of ALI tokens transferred from the destroyed iNFT
	 * @param _personalityContract AI Personality smart contract address
	 * @param _personalityId ID of the AI Personality transferred from the destroyed iNFT
	 * @param _targetContract target NFT smart contract
	 * @param _targetId target NFT ID (where this iNFT was bound to and belonged to)
	 */
	event Burnt(
		address indexed _by,
		uint256 indexed _recordId,
		address indexed _recipient,
		uint96 _aliValue,
		address _personalityContract,
		uint96 _personalityId,
		address _targetContract,
		uint256 _targetId
	);

	/**
	 * @dev Creates/deploys an iNFT instance bound to already ALI token instance
	 *
	 * @param _ali address of the deployed ALI ERC20 Token instance the iNFT is bound to
	 */
	constructor(address _ali) {
		// verify the inputs are set
		require(_ali != address(0), "ALI Token addr is not set");

		// verify _ali is a valid ERC20
		require(ERC165(_ali).supportsInterface(type(ERC20).interfaceId), "unexpected ALI Token type");

		// setup smart contract internal state
		aliContract = _ali;
	}

	/**
	 * @inheritdoc ERC165
	 */
	function supportsInterface(bytes4 interfaceId) public pure override returns (bool) {
		// reconstruct from current interface and super interface
		return interfaceId == type(IntelligentNFTv2Spec).interfaceId;
	}

	/**
	 * @dev Restricted access function which updates base URI used to construct
	 *      ERC721Metadata.tokenURI
	 *
	 * @param _baseURI new base URI to set
	 */
	function setBaseURI(string memory _baseURI) public virtual {
		// verify the access permission
		require(isSenderInRole(ROLE_URI_MANAGER), "access denied");

		// emit an event first - to log both old and new values
		emit BaseURIUpdated(msg.sender, baseURI, _baseURI);

		// and update base URI
		baseURI = _baseURI;
	}

	/**
	 * @dev Returns token URI if it was previously set with `setTokenURI`,
	 *      otherwise constructs it as base URI + token ID
	 *
	 * @param _recordId iNFT ID to query metadata link URI for
	 * @return URI link to fetch iNFT metadata from
	 */
	function tokenURI(uint256 _recordId) public view override returns (string memory) {
		// verify token exists
		require(exists(_recordId), "iNFT doesn't exist");

		// read the token URI for the token specified
		string memory _tokenURI = _tokenURIs[_recordId];

		// if token URI is set
		if(bytes(_tokenURI).length > 0) {
			// just return it
			return _tokenURI;
		}

		// if base URI is not set
		if(bytes(baseURI).length == 0) {
			// return an empty string
			return "";
		}

		// otherwise concatenate base URI + token ID
		return StringUtils.concat(baseURI, StringUtils.itoa(_recordId, 10));
	}

	/**
	 * @dev Sets the token URI for the token defined by its ID
	 *
	 * @param _tokenId an ID of the token to set URI for
	 * @param _tokenURI token URI to set
	 */
	function setTokenURI(uint256 _tokenId, string memory _tokenURI) public virtual {
		// verify the access permission
		require(isSenderInRole(ROLE_URI_MANAGER), "access denied");

		// we do not verify token existence: we want to be able to
		// preallocate token URIs before tokens are actually minted

		// emit an event first - to log both old and new values
		emit TokenURIUpdated(msg.sender, _tokenId, _tokenURIs[_tokenId], _tokenURI);

		// and update token URI
		_tokenURIs[_tokenId] = _tokenURI;
	}

	/**
	 * @notice Verifies if given iNFT exists
	 *
	 * @param recordId iNFT ID to verify existence of
	 * @return true if iNFT exists, false otherwise
	 */
	function exists(uint256 recordId) public view override returns (bool) {
		// verify if biding exists for that tokenId and return the result
		return bindings[recordId].targetContract != address(0);
	}

	/**
	 * @notice Returns an owner of the given iNFT.
	 *      By definition iNFT owner is an owner of the target NFT
	 *
	 * @param recordId iNFT ID to query ownership information for
	 * @return address of the given iNFT owner
	 */
	function ownerOf(uint256 recordId) public view override returns (address) {
		// get the link to the token binding (we need to access only one field)
		IntelliBinding storage binding = bindings[recordId];

		// verify the binding exists and throw standard Zeppelin message if not
		require(binding.targetContract != address(0), "iNFT doesn't exist");

		// delegate `ownerOf` call to the target NFT smart contract
		return ERC721(binding.targetContract).ownerOf(binding.targetId);
	}

	/**
	 * @dev Restricted access function which creates an iNFT, binding it to the specified
	 *      NFT, locking the AI Personality specified, and funded with the amount of ALI specified
	 *
	 * @dev Locks AI Personality defined by its ID within iNFT smart contract;
	 *      AI Personality must be transferred to the iNFT smart contract
	 *      prior to calling the `mint`, but in the same transaction with `mint`
	 *
	 * @dev Locks specified amount of ALI token within iNFT smart contract;
	 *      ALI token amount must be transferred to the iNFT smart contract
	 *      prior to calling the `mint`, but in the same transaction with `mint`
	 *
	 * @dev To summarize, minting transaction (a transaction which executes `mint`) must
	 *      1) transfer AI Personality
	 *      2) transfer ALI tokens if they are to be locked
	 *      3) mint iNFT
	 *      NOTE: breaking the items above into multiple transactions is not acceptable!
	 *            (results in a security risk)
	 *
	 * @dev The NFT to be linked to is not required to owned by the funder, but it must exist;
	 *      throws if target NFT doesn't exist
	 *
	 * @dev This is a restricted function which is accessed by iNFT Linker
	 *
	 * @param recordId ID of the iNFT to mint (create, bind)
	 * @param aliValue amount of ALI tokens to bind to newly created iNFT
	 * @param personalityContract AI Personality contract address
	 * @param personalityId ID of the AI Personality to bind to newly created iNFT
	 * @param targetContract target NFT smart contract
	 * @param targetId target NFT ID (where this iNFT binds to and belongs to)
	 */
	function mint(
		uint256 recordId,
		uint96 aliValue,
		address personalityContract,
		uint96 personalityId,
		address targetContract,
		uint256 targetId
	) public {
		// verify the access permission
		require(isSenderInRole(ROLE_MINTER), "access denied");

		// verify personalityContract is a valid ERC721
		require(ERC165(personalityContract).supportsInterface(type(ERC721).interfaceId), "personality is not ERC721");

		// verify targetContract is a valid ERC721
		require(ERC165(targetContract).supportsInterface(type(ERC721).interfaceId), "target NFT is not ERC721");

		// verify this iNFT is not yet minted
		require(!exists(recordId), "iNFT already exists");

		// verify target NFT is not yet bound to
		require(reverseBindings[targetContract][targetId] == 0, "NFT is already bound");

		// verify AI Personality is not yet locked
		require(personalityBindings[personalityContract][personalityId] == 0, "personality already linked");

		// verify if AI Personality is already transferred to iNFT
		require(ERC721(personalityContract).ownerOf(personalityId) == address(this), "personality is not yet transferred");

		// retrieve NFT owner and verify if target NFT exists
		address owner = ERC721(targetContract).ownerOf(targetId);
		// Note: we do not require funder to be NFT owner,
		// if required this constraint should be added by the caller (iNFT Linker)
		require(owner != address(0), "target NFT doesn't exist");

		// in case when ALI tokens are expected to be locked within iNFT
		if(aliValue > 0) {
			// verify ALI tokens are already transferred to iNFT
			require(aliBalance + aliValue <= ERC20(aliContract).balanceOf(address(this)), "ALI tokens not yet transferred");

			// update ALI balance on the contract
			aliBalance += aliValue;
		}

		// bind AI Personality transferred and ALI ERC20 value transferred to an NFT specified
		bindings[recordId] = IntelliBinding({
			personalityContract : personalityContract,
			personalityId : personalityId,
			aliValue : aliValue,
			targetContract : targetContract,
			targetId : targetId
		});

		// fill in the reverse binding
		reverseBindings[targetContract][targetId] = recordId;

		// fill in the AI Personality to iNFT binding
		personalityBindings[personalityContract][personalityId] = recordId;

		// increase total supply counter
		totalSupply++;

		// emit an event
		emit Minted(
			msg.sender,
			owner,
			recordId,
			aliValue,
			personalityContract,
			personalityId,
			targetContract,
			targetId
		);
	}

	/**
	 * @dev Restricted access function which creates several iNFTs, binding them to the specified
	 *      NFTs, locking the AI Personalities specified, each funded with the amount of ALI specified
	 *
	 * @dev Locks AI Personalities defined by their IDs within iNFT smart contract;
	 *      AI Personalities must be transferred to the iNFT smart contract
	 *      prior to calling the `mintBatch`, but in the same transaction with `mintBatch`
	 *
	 * @dev Locks specified amount of ALI token within iNFT smart contract for each iNFT minted;
	 *      ALI token amount must be transferred to the iNFT smart contract
	 *      prior to calling the `mintBatch`, but in the same transaction with `mintBatch`
	 *
	 * @dev To summarize, minting transaction (a transaction which executes `mintBatch`) must
	 *      1) transfer AI Personality
	 *      2) transfer ALI tokens if they are to be locked
	 *      3) mint iNFT
	 *      NOTE: breaking the items above into multiple transactions is not acceptable!
	 *            (results in a security risk)
	 *
	 * @dev The NFTs to be linked to are not required to owned by the funder, but they must exist;
	 *      throws if target NFTs don't exist
	 *
	 * @dev iNFT IDs to be minted: [recordId, recordId + n)
	 * @dev AI Personality IDs to be locked: [personalityId, personalityId + n)
	 * @dev NFT IDs to be bound to: [targetId, targetId + n)
	 *
	 * @dev n must be greater or equal 2: `n > 1`
	 *
	 * @dev This is a restricted function which is accessed by iNFT Linker
	 *
	 * @param recordId ID of the first iNFT to mint (create, bind)
	 * @param aliValue amount of ALI tokens to bind to each newly created iNFT
	 * @param personalityContract AI Personality contract address
	 * @param personalityId ID of the first AI Personality to bind to newly created iNFT
	 * @param targetContract target NFT smart contract
	 * @param targetId first target NFT ID (where this iNFT binds to and belongs to)
	 * @param n how many iNFTs to mint, sequentially increasing the recordId, personalityId, and targetId
	 */
	function mintBatch(
		uint256 recordId,
		uint96 aliValue,
		address personalityContract,
		uint96 personalityId,
		address targetContract,
		uint256 targetId,
		uint96 n
	) public {
		// verify the access permission
		require(isSenderInRole(ROLE_MINTER), "access denied");

		// verify n is set properly
		require(n > 1, "n is too small");

		// verify personalityContract is a valid ERC721
		require(ERC165(personalityContract).supportsInterface(type(ERC721).interfaceId), "personality is not ERC721");

		// verify targetContract is a valid ERC721
		require(ERC165(targetContract).supportsInterface(type(ERC721).interfaceId), "target NFT is not ERC721");

		// verifications: for each iNFT in a batch
		for(uint96 i = 0; i < n; i++) {
			// verify this token ID is not yet bound
			require(!exists(recordId + i), "iNFT already exists");

			// verify the AI Personality is not yet bound
			require(personalityBindings[personalityContract][personalityId + i] == 0, "personality already linked");

			// verify if AI Personality is already transferred to iNFT
			require(ERC721(personalityContract).ownerOf(personalityId + i) == address(this), "personality is not yet transferred");

			// retrieve NFT owner and verify if target NFT exists
			address owner = ERC721(targetContract).ownerOf(targetId + i);
			// Note: we do not require funder to be NFT owner,
			// if required this constraint should be added by the caller (iNFT Linker)
			require(owner != address(0), "target NFT doesn't exist");

			// emit an event - we log owner for each iNFT
			// and its convenient to do it here when we have the owner inline
			emit Minted(
				msg.sender,
				owner,
				recordId + i,
				aliValue,
				personalityContract,
				personalityId + i,
				targetContract,
				targetId + i
			);
		}

		// cumulative ALI value may overflow uint96, store it into uint256 on stack
		uint256 _aliValue = uint256(aliValue) * n;

		// in case when ALI tokens are expected to be locked within iNFT
		if(_aliValue > 0) {
			// verify ALI tokens are already transferred to iNFT
			require(aliBalance + _aliValue <= ERC20(aliContract).balanceOf(address(this)), "ALI tokens not yet transferred");
			// update ALI balance on the contract
			aliBalance += _aliValue;
		}

		// minting: for each iNFT in a batch
		for(uint96 i = 0; i < n; i++) {
			// bind AI Personality transferred and ALI ERC20 value transferred to an NFT specified
			bindings[recordId + i] = IntelliBinding({
				personalityContract : personalityContract,
				personalityId : personalityId + i,
				aliValue : aliValue,
				targetContract : targetContract,
				targetId : targetId + i
			});

			// fill in the AI Personality to iNFT binding
			personalityBindings[personalityContract][personalityId + i] = recordId + i;

			// fill in the reverse binding
			reverseBindings[targetContract][targetId + i] = recordId + i;
		}

		// increase total supply counter
		totalSupply += n;
	}

	/**
	 * @dev Restricted access function which destroys an iNFT, unbinding it from the
	 *      linked NFT, releasing an AI Personality, and ALI tokens locked in the iNFT
	 *
	 * @dev Transfers an AI Personality locked in iNFT to its owner via ERC721.safeTransferFrom;
	 *      owner must be an EOA or implement ERC721Receiver.onERC721Received properly
	 * @dev Transfers ALI tokens locked in iNFT to its owner
	 * @dev Since iNFT owner is determined as underlying NFT owner, this underlying NFT must
	 *      exist and its ownerOf function must not throw and must return non-zero owner address
	 *      for the underlying NFT ID
	 *
	 * @dev Doesn't verify if it's safe to send ALI tokens to the NFT owner, this check
	 *      must be handled by the transaction executor
	 *
	 * @dev This is a restricted function which is accessed by iNFT Linker
	 *
	 * @param recordId ID of the iNFT to burn (destroy, unbind)
	 */
	function burn(uint256 recordId) public {
		// verify the access permission
		require(isSenderInRole(ROLE_BURNER), "access denied");

		// decrease total supply counter
		totalSupply--;

		// read the token binding (we'll need to access all the fields)
		IntelliBinding memory binding = bindings[recordId];

		// verify binding exists
		require(binding.targetContract != address(0), "not bound");

		// destroy binding first to protect from any reentrancy possibility
		delete bindings[recordId];

		// free the reverse binding
		delete reverseBindings[binding.targetContract][binding.targetId];

		// free the AI Personality binding
		delete personalityBindings[binding.personalityContract][binding.personalityId];

		// determine an owner of the underlying NFT
		address owner = ERC721(binding.targetContract).ownerOf(binding.targetId);

		// verify that owner address is set (not a zero address)
		require(owner != address(0), "no such NFT");

		// transfer the AI Personality to the NFT owner
		// using safe transfer since we don't know if owner address can accept the AI Personality right now
		ERC721(binding.personalityContract).safeTransferFrom(address(this), owner, binding.personalityId);

		// in case when ALI tokens were locked within iNFT
		if(binding.aliValue > 0) {
			// update ALI balance on the contract prior to token transfer (reentrancy style)
			aliBalance -= binding.aliValue;

			// transfer the ALI tokens to the NFT owner
			ERC20(aliContract).transfer(owner, binding.aliValue);
		}

		// emit an event
		emit Burnt(
			msg.sender,
			recordId,
			owner,
			binding.aliValue,
			binding.personalityContract,
			binding.personalityId,
			binding.targetContract,
			binding.targetId
		);
	}

	/**
	 * @dev Restricted access function which updates iNFT record by increasing locked ALI tokens value,
	 *      effectively locking additional ALI tokens to the iNFT
	 *
	 * @dev Locks specified amount of ALI token within iNFT smart contract;
	 *      ALI token amount must be transferred to the iNFT smart contract
	 *      prior to calling the `increaseAli`, but in the same transaction with `increaseAli`
	 *
	 * @dev To summarize, update transaction (a transaction which executes `increaseAli`) must
	 *      1) transfer ALI tokens
	 *      2) update the iNFT
	 *      NOTE: breaking the items above into multiple transactions is not acceptable!
	 *            (results in a security risk)
	 *
	 * @dev This is a restricted function which is accessed by iNFT Linker
	 *
	 * @param recordId ID of the iNFT to update
	 * @param aliDelta amount of ALI tokens to lock
	 */
	function increaseAli(uint256 recordId, uint96 aliDelta) public {
		// verify the access permission
		require(isSenderInRole(ROLE_EDITOR), "access denied");

		// verify the inputs are set
		require(aliDelta != 0, "zero value");

		// get iNFT owner for logging (check iNFT record exists under the hood)
		address owner = ownerOf(recordId);

		// cache the ALI value of the record
		uint96 aliValue = bindings[recordId].aliValue;

		// verify ALI tokens are already transferred to iNFT
		require(aliBalance + aliDelta <= ERC20(aliContract).balanceOf(address(this)), "ALI tokens not yet transferred");

		// update ALI balance on the contract
		aliBalance += aliDelta;

		// update ALI balance on the binding
		bindings[recordId].aliValue = aliValue + aliDelta;

		// emit an event
		emit Updated(msg.sender, owner, recordId, aliValue, aliValue + aliDelta);
	}

	/**
	 * @dev Restricted access function which updates iNFT record by decreasing locked ALI tokens value,
	 *      effectively unlocking some or all ALI tokens from the iNFT
	 *
	 * @dev Unlocked tokens are sent to the recipient address specified
	 *
	 * @dev This is a restricted function which is accessed by iNFT Linker
	 *
	 * @param recordId ID of the iNFT to update
	 * @param aliDelta amount of ALI tokens to unlock
	 * @param recipient an address to send unlocked tokens to
	 */
	function decreaseAli(uint256 recordId, uint96 aliDelta, address recipient) public {
		// verify the access permission
		require(isSenderInRole(ROLE_EDITOR), "access denied");

		// verify the inputs are set
		require(aliDelta != 0, "zero value");
		require(recipient != address(0), "zero address");

		// get iNFT owner for logging (check iNFT record exists under the hood)
		address owner = ownerOf(recordId);

		// cache the ALI value of the record
		uint96 aliValue = bindings[recordId].aliValue;

		// positive or zero resulting balance check
		require(aliValue >= aliDelta, "not enough ALI");

		// update ALI balance on the contract
		aliBalance -= aliDelta;

		// update ALI balance on the binding
		bindings[recordId].aliValue = aliValue - aliDelta;

		// transfer the ALI tokens to the recipient
		ERC20(aliContract).transfer(recipient, aliDelta);

		// emit an event
		emit Updated(msg.sender, owner, recordId, aliValue, aliValue - aliDelta);
	}

	/**
	 * @notice Determines how many tokens are locked in a particular iNFT
	 *
	 * @dev A shortcut for bindings(recordId).aliValue
	 * @dev Throws if iNFT specified doesn't exist
	 *
	 * @param recordId iNFT ID to query locked tokens balance for
	 * @return locked tokens balance, bindings[recordId].aliValue
	 */
	function lockedValue(uint256 recordId) public view returns(uint96) {
		// ensure iNFT exists
		require(exists(recordId), "iNFT doesn't exist");

		// read and return ALI value locked in the binding
		return bindings[recordId].aliValue;
	}
}

File 4 of 14 : UpgradeableAccessControl.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

import "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";

/**
 * @title Upgradeable Access Control List // ERC1967Proxy
 *
 * @notice Access control smart contract provides an API to check
 *      if a specific operation is permitted globally and/or
 *      if a particular user has a permission to execute it.
 *
 * @notice It deals with two main entities: features and roles.
 *
 * @notice Features are designed to be used to enable/disable public functions
 *      of the smart contract (used by a wide audience).
 * @notice User roles are designed to control the access to restricted functions
 *      of the smart contract (used by a limited set of maintainers).
 *
 * @notice Terms "role", "permissions" and "set of permissions" have equal meaning
 *      in the documentation text and may be used interchangeably.
 * @notice Terms "permission", "single permission" implies only one permission bit set.
 *
 * @notice Access manager is a special role which allows to grant/revoke other roles.
 *      Access managers can only grant/revoke permissions which they have themselves.
 *      As an example, access manager with no other roles set can only grant/revoke its own
 *      access manager permission and nothing else.
 *
 * @notice Access manager permission should be treated carefully, as a super admin permission:
 *      Access manager with even no other permission can interfere with another account by
 *      granting own access manager permission to it and effectively creating more powerful
 *      permission set than its own.
 *
 * @dev Both current and OpenZeppelin AccessControl implementations feature a similar API
 *      to check/know "who is allowed to do this thing".
 * @dev Zeppelin implementation is more flexible:
 *      - it allows setting unlimited number of roles, while current is limited to 256 different roles
 *      - it allows setting an admin for each role, while current allows having only one global admin
 * @dev Current implementation is more lightweight:
 *      - it uses only 1 bit per role, while Zeppelin uses 256 bits
 *      - it allows setting up to 256 roles at once, in a single transaction, while Zeppelin allows
 *        setting only one role in a single transaction
 *
 * @dev This smart contract is designed to be inherited by other
 *      smart contracts which require access control management capabilities.
 *
 * @dev Access manager permission has a bit 255 set.
 *      This bit must not be used by inheriting contracts for any other permissions/features.
 *
 * @dev This is an upgradeable version of the ACL, based on Zeppelin implementation for ERC1967,
 *      see https://docs.openzeppelin.com/contracts/4.x/upgradeable
 *      see https://docs.openzeppelin.com/contracts/4.x/api/proxy#UUPSUpgradeable
 *      see https://forum.openzeppelin.com/t/uups-proxies-tutorial-solidity-javascript/7786
 *
 * @author Basil Gorin
 */
abstract contract UpgradeableAccessControl is UUPSUpgradeable {
	/**
	 * @notice Privileged addresses with defined roles/permissions
	 * @notice In the context of ERC20/ERC721 tokens these can be permissions to
	 *      allow minting or burning tokens, transferring on behalf and so on
	 *
	 * @dev Maps user address to the permissions bitmask (role), where each bit
	 *      represents a permission
	 * @dev Bitmask 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
	 *      represents all possible permissions
	 * @dev 'This' address mapping represents global features of the smart contract
	 */
	mapping(address => uint256) public userRoles;

	/**
	 * @dev Empty reserved space in storage. The size of the __gap array is calculated so that
	 *      the amount of storage used by a contract always adds up to the 50.
	 *      See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
	 */
	uint256[49] private __gap;

	/**
	 * @notice Access manager is responsible for assigning the roles to users,
	 *      enabling/disabling global features of the smart contract
	 * @notice Access manager can add, remove and update user roles,
	 *      remove and update global features
	 *
	 * @dev Role ROLE_ACCESS_MANAGER allows modifying user roles and global features
	 * @dev Role ROLE_ACCESS_MANAGER has single bit at position 255 enabled
	 */
	uint256 public constant ROLE_ACCESS_MANAGER = 0x8000000000000000000000000000000000000000000000000000000000000000;

	/**
	 * @notice Upgrade manager is responsible for smart contract upgrades,
	 *      see https://docs.openzeppelin.com/contracts/4.x/api/proxy#UUPSUpgradeable
	 *      see https://docs.openzeppelin.com/contracts/4.x/upgradeable
	 *
	 * @dev Role ROLE_UPGRADE_MANAGER allows passing the _authorizeUpgrade() check
	 * @dev Role ROLE_UPGRADE_MANAGER has single bit at position 254 enabled
	 */
	uint256 public constant ROLE_UPGRADE_MANAGER = 0x4000000000000000000000000000000000000000000000000000000000000000;

	/**
	 * @dev Bitmask representing all the possible permissions (super admin role)
	 * @dev Has all the bits are enabled (2^256 - 1 value)
	 */
	uint256 private constant FULL_PRIVILEGES_MASK = type(uint256).max; // before 0.8.0: uint256(-1) overflows to 0xFFFF...

	/**
	 * @dev Fired in updateRole() and updateFeatures()
	 *
	 * @param _by operator which called the function
	 * @param _to address which was granted/revoked permissions
	 * @param _requested permissions requested
	 * @param _actual permissions effectively set
	 */
	event RoleUpdated(address indexed _by, address indexed _to, uint256 _requested, uint256 _actual);

	/**
	 * @dev UUPS initializer, sets the contract owner to have full privileges
	 *
	 * @dev Can be executed only in constructor during deployment,
	 *      reverts when executed in already deployed contract
	 *
	 * @dev IMPORTANT:
	 *      this function MUST be executed during proxy deployment (in proxy constructor),
	 *      otherwise it renders useless and cannot be executed at all,
	 *      resulting in no admin control over the proxy and no possibility to do future upgrades
	 *
	 * @param _owner smart contract owner having full privileges
	 */
	function _postConstruct(address _owner) internal virtual initializer {
		// ensure this function is execute only in constructor
		require(!AddressUpgradeable.isContract(address(this)), "invalid context");

		// grant owner full privileges
		userRoles[_owner] = FULL_PRIVILEGES_MASK;

		// fire an event
		emit RoleUpdated(msg.sender, _owner, FULL_PRIVILEGES_MASK, FULL_PRIVILEGES_MASK);
	}

	/**
	 * @notice Returns an address of the implementation smart contract,
	 *      see ERC1967Upgrade._getImplementation()
	 *
	 * @return the current implementation address
	 */
	function getImplementation() public view virtual returns (address) {
		// delegate to `ERC1967Upgrade._getImplementation()`
		return _getImplementation();
	}

	/**
	 * @notice Retrieves globally set of features enabled
	 *
	 * @dev Effectively reads userRoles role for the contract itself
	 *
	 * @return 256-bit bitmask of the features enabled
	 */
	function features() public view returns (uint256) {
		// features are stored in 'this' address  mapping of `userRoles` structure
		return userRoles[address(this)];
	}

	/**
	 * @notice Updates set of the globally enabled features (`features`),
	 *      taking into account sender's permissions
	 *
	 * @dev Requires transaction sender to have `ROLE_ACCESS_MANAGER` permission
	 * @dev Function is left for backward compatibility with older versions
	 *
	 * @param _mask bitmask representing a set of features to enable/disable
	 */
	function updateFeatures(uint256 _mask) public {
		// delegate call to `updateRole`
		updateRole(address(this), _mask);
	}

	/**
	 * @notice Updates set of permissions (role) for a given user,
	 *      taking into account sender's permissions.
	 *
	 * @dev Setting role to zero is equivalent to removing an all permissions
	 * @dev Setting role to `FULL_PRIVILEGES_MASK` is equivalent to
	 *      copying senders' permissions (role) to the user
	 * @dev Requires transaction sender to have `ROLE_ACCESS_MANAGER` permission
	 *
	 * @param operator address of a user to alter permissions for or zero
	 *      to alter global features of the smart contract
	 * @param role bitmask representing a set of permissions to
	 *      enable/disable for a user specified
	 */
	function updateRole(address operator, uint256 role) public {
		// caller must have a permission to update user roles
		require(isSenderInRole(ROLE_ACCESS_MANAGER), "access denied");

		// evaluate the role and reassign it
		userRoles[operator] = evaluateBy(msg.sender, userRoles[operator], role);

		// fire an event
		emit RoleUpdated(msg.sender, operator, role, userRoles[operator]);
	}

	/**
	 * @notice Determines the permission bitmask an operator can set on the
	 *      target permission set
	 * @notice Used to calculate the permission bitmask to be set when requested
	 *     in `updateRole` and `updateFeatures` functions
	 *
	 * @dev Calculated based on:
	 *      1) operator's own permission set read from userRoles[operator]
	 *      2) target permission set - what is already set on the target
	 *      3) desired permission set - what do we want set target to
	 *
	 * @dev Corner cases:
	 *      1) Operator is super admin and its permission set is `FULL_PRIVILEGES_MASK`:
	 *        `desired` bitset is returned regardless of the `target` permission set value
	 *        (what operator sets is what they get)
	 *      2) Operator with no permissions (zero bitset):
	 *        `target` bitset is returned regardless of the `desired` value
	 *        (operator has no authority and cannot modify anything)
	 *
	 * @dev Example:
	 *      Consider an operator with the permissions bitmask     00001111
	 *      is about to modify the target permission set          01010101
	 *      Operator wants to set that permission set to          00110011
	 *      Based on their role, an operator has the permissions
	 *      to update only lowest 4 bits on the target, meaning that
	 *      high 4 bits of the target set in this example is left
	 *      unchanged and low 4 bits get changed as desired:      01010011
	 *
	 * @param operator address of the contract operator which is about to set the permissions
	 * @param target input set of permissions to operator is going to modify
	 * @param desired desired set of permissions operator would like to set
	 * @return resulting set of permissions given operator will set
	 */
	function evaluateBy(address operator, uint256 target, uint256 desired) public view returns (uint256) {
		// read operator's permissions
		uint256 p = userRoles[operator];

		// taking into account operator's permissions,
		// 1) enable the permissions desired on the `target`
		target |= p & desired;
		// 2) disable the permissions desired on the `target`
		target &= FULL_PRIVILEGES_MASK ^ (p & (FULL_PRIVILEGES_MASK ^ desired));

		// return calculated result
		return target;
	}

	/**
	 * @notice Checks if requested set of features is enabled globally on the contract
	 *
	 * @param required set of features to check against
	 * @return true if all the features requested are enabled, false otherwise
	 */
	function isFeatureEnabled(uint256 required) public view returns (bool) {
		// delegate call to `__hasRole`, passing `features` property
		return __hasRole(features(), required);
	}

	/**
	 * @notice Checks if transaction sender `msg.sender` has all the permissions required
	 *
	 * @param required set of permissions (role) to check against
	 * @return true if all the permissions requested are enabled, false otherwise
	 */
	function isSenderInRole(uint256 required) public view returns (bool) {
		// delegate call to `isOperatorInRole`, passing transaction sender
		return isOperatorInRole(msg.sender, required);
	}

	/**
	 * @notice Checks if operator has all the permissions (role) required
	 *
	 * @param operator address of the user to check role for
	 * @param required set of permissions (role) to check
	 * @return true if all the permissions requested are enabled, false otherwise
	 */
	function isOperatorInRole(address operator, uint256 required) public view returns (bool) {
		// delegate call to `__hasRole`, passing operator's permissions (role)
		return __hasRole(userRoles[operator], required);
	}

	/**
	 * @dev Checks if role `actual` contains all the permissions required `required`
	 *
	 * @param actual existent role
	 * @param required required role
	 * @return true if actual has required role (all permissions), false otherwise
	 */
	function __hasRole(uint256 actual, uint256 required) internal pure returns (bool) {
		// check the bitmask for the role required and return the result
		return actual & required == required;
	}

	/**
	 * @inheritdoc UUPSUpgradeable
	 */
	function _authorizeUpgrade(address) internal virtual override {
		// caller must have a permission to upgrade the contract
		require(isSenderInRole(ROLE_UPGRADE_MANAGER), "access denied");
	}
}

File 5 of 14 : ERC165Spec.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

/**
 * @title ERC-165 Standard Interface Detection
 *
 * @dev Interface of the ERC165 standard, as defined in the
 *       https://eips.ethereum.org/EIPS/eip-165[EIP].
 *
 * @dev Implementers can declare support of contract interfaces,
 *      which can then be queried by others.
 *
 * @author Christian Reitwießner, Nick Johnson, Fabian Vogelsteller, Jordi Baylina, Konrad Feldmeier, William Entriken
 */
interface ERC165 {
	/**
	 * @notice Query if a contract implements an interface
	 *
	 * @dev Interface identification is specified in ERC-165.
	 *      This function uses less than 30,000 gas.
	 *
	 * @param interfaceID The interface identifier, as specified in ERC-165
	 * @return `true` if the contract implements `interfaceID` and
	 *      `interfaceID` is not 0xffffffff, `false` otherwise
	 */
	function supportsInterface(bytes4 interfaceID) external view returns (bool);
}

File 6 of 14 : ERC20Spec.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

/**
 * @title EIP-20: ERC-20 Token Standard
 *
 * @notice The ERC-20 (Ethereum Request for Comments 20), proposed by Fabian Vogelsteller in November 2015,
 *      is a Token Standard that implements an API for tokens within Smart Contracts.
 *
 * @notice It provides functionalities like to transfer tokens from one account to another,
 *      to get the current token balance of an account and also the total supply of the token available on the network.
 *      Besides these it also has some other functionalities like to approve that an amount of
 *      token from an account can be spent by a third party account.
 *
 * @notice If a Smart Contract implements the following methods and events it can be called an ERC-20 Token
 *      Contract and, once deployed, it will be responsible to keep track of the created tokens on Ethereum.
 *
 * @notice See https://ethereum.org/en/developers/docs/standards/tokens/erc-20/
 * @notice See https://eips.ethereum.org/EIPS/eip-20
 */
interface ERC20 {
	/**
	 * @dev Fired in transfer(), transferFrom() to indicate that token transfer happened
	 *
	 * @param from an address tokens were consumed from
	 * @param to an address tokens were sent to
	 * @param value number of tokens transferred
	 */
	event Transfer(address indexed from, address indexed to, uint256 value);

	/**
	 * @dev Fired in approve() to indicate an approval event happened
	 *
	 * @param owner an address which granted a permission to transfer
	 *      tokens on its behalf
	 * @param spender an address which received a permission to transfer
	 *      tokens on behalf of the owner `_owner`
	 * @param value amount of tokens granted to transfer on behalf
	 */
	event Approval(address indexed owner, address indexed spender, uint256 value);

	/**
	 * @return name of the token (ex.: USD Coin)
	 */
	// OPTIONAL - This method can be used to improve usability,
	// but interfaces and other contracts MUST NOT expect these values to be present.
	// function name() external view returns (string memory);

	/**
	 * @return symbol of the token (ex.: USDC)
	 */
	// OPTIONAL - This method can be used to improve usability,
	// but interfaces and other contracts MUST NOT expect these values to be present.
	// function symbol() external view returns (string memory);

	/**
	 * @dev Returns the number of decimals used to get its user representation.
	 *      For example, if `decimals` equals `2`, a balance of `505` tokens should
	 *      be displayed to a user as `5,05` (`505 / 10 ** 2`).
	 *
	 * @dev Tokens usually opt for a value of 18, imitating the relationship between
	 *      Ether and Wei. This is the value {ERC20} uses, unless this function is
	 *      overridden;
	 *
	 * @dev NOTE: This information is only used for _display_ purposes: it in
	 *      no way affects any of the arithmetic of the contract, including
	 *      {IERC20-balanceOf} and {IERC20-transfer}.
	 *
	 * @return token decimals
	 */
	// OPTIONAL - This method can be used to improve usability,
	// but interfaces and other contracts MUST NOT expect these values to be present.
	// function decimals() external view returns (uint8);

	/**
	 * @return the amount of tokens in existence
	 */
	function totalSupply() external view returns (uint256);

	/**
	 * @notice Gets the balance of a particular address
	 *
	 * @param _owner the address to query the the balance for
	 * @return balance an amount of tokens owned by the address specified
	 */
	function balanceOf(address _owner) external view returns (uint256 balance);

	/**
	 * @notice Transfers some tokens to an external address or a smart contract
	 *
	 * @dev Called by token owner (an address which has a
	 *      positive token balance tracked by this smart contract)
	 * @dev Throws on any error like
	 *      * insufficient token balance or
	 *      * incorrect `_to` address:
	 *          * zero address or
	 *          * self address or
	 *          * smart contract which doesn't support ERC20
	 *
	 * @param _to an address to transfer tokens to,
	 *      must be either an external address or a smart contract,
	 *      compliant with the ERC20 standard
	 * @param _value amount of tokens to be transferred,, zero
	 *      value is allowed
	 * @return success true on success, throws otherwise
	 */
	function transfer(address _to, uint256 _value) external returns (bool success);

	/**
	 * @notice Transfers some tokens on behalf of address `_from' (token owner)
	 *      to some other address `_to`
	 *
	 * @dev Called by token owner on his own or approved address,
	 *      an address approved earlier by token owner to
	 *      transfer some amount of tokens on its behalf
	 * @dev Throws on any error like
	 *      * insufficient token balance or
	 *      * incorrect `_to` address:
	 *          * zero address or
	 *          * same as `_from` address (self transfer)
	 *          * smart contract which doesn't support ERC20
	 *
	 * @param _from token owner which approved caller (transaction sender)
	 *      to transfer `_value` of tokens on its behalf
	 * @param _to an address to transfer tokens to,
	 *      must be either an external address or a smart contract,
	 *      compliant with the ERC20 standard
	 * @param _value amount of tokens to be transferred,, zero
	 *      value is allowed
	 * @return success true on success, throws otherwise
	 */
	function transferFrom(address _from, address _to, uint256 _value) external returns (bool success);

	/**
	 * @notice Approves address called `_spender` to transfer some amount
	 *      of tokens on behalf of the owner (transaction sender)
	 *
	 * @dev Transaction sender must not necessarily own any tokens to grant the permission
	 *
	 * @param _spender an address approved by the caller (token owner)
	 *      to spend some tokens on its behalf
	 * @param _value an amount of tokens spender `_spender` is allowed to
	 *      transfer on behalf of the token owner
	 * @return success true on success, throws otherwise
	 */
	function approve(address _spender, uint256 _value) external returns (bool success);

	/**
	 * @notice Returns the amount which _spender is still allowed to withdraw from _owner.
	 *
	 * @dev A function to check an amount of tokens owner approved
	 *      to transfer on its behalf by some other address called "spender"
	 *
	 * @param _owner an address which approves transferring some tokens on its behalf
	 * @param _spender an address approved to transfer some tokens on behalf
	 * @return remaining an amount of tokens approved address `_spender` can transfer on behalf
	 *      of token owner `_owner`
	 */
	function allowance(address _owner, address _spender) external view returns (uint256 remaining);
}

File 7 of 14 : StringUtils.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

/**
 * @title String Utils Library
 *
 * @dev Library for working with strings, primarily converting
 *      between strings and integer types
 *
 * @author Basil Gorin
 */
library StringUtils {
	/**
	 * @dev Converts a string to unsigned integer using the specified `base`
	 * @dev Throws on invalid input
	 *      (wrong characters for a given `base`)
	 * @dev Throws if given `base` is not supported
	 * @param a string to convert
	 * @param base number base, one of 2, 8, 10, 16
	 * @return i a number representing given string
	 */
	function atoi(string memory a, uint8 base) internal pure returns (uint256 i) {
		// check if the base is valid
		require(base == 2 || base == 8 || base == 10 || base == 16);

		// convert string into bytes for convenient iteration
		bytes memory buf = bytes(a);

		// iterate over the string (bytes buffer)
		for(uint256 p = 0; p < buf.length; p++) {
			// extract the digit
			uint8 digit = uint8(buf[p]) - 0x30;

			// if digit is greater then 10 - mind the gap
			// see `itoa` function for more details
			if(digit > 10) {
				// remove the gap
				digit -= 7;
			}

			// check if digit meets the base
			require(digit < base);

			// move to the next digit slot
			i *= base;

			// add digit to the result
			i += digit;
		}

		// return the result
		return i;
	}

	/**
	 * @dev Converts a integer to a string using the specified `base`
	 * @dev Throws if given `base` is not supported
	 * @param i integer to convert
	 * @param base number base, one of 2, 8, 10, 16
	 * @return a a string representing given integer
	 */
	function itoa(uint256 i, uint8 base) internal pure returns (string memory a) {
		// check if the base is valid
		require(base == 2 || base == 8 || base == 10 || base == 16);

		// for zero input the result is "0" string for any base
		if(i == 0) {
			return "0";
		}

		// bytes buffer to put ASCII characters into
		bytes memory buf = new bytes(256);

		// position within a buffer to be used in cycle
		uint256 p = 0;

		// extract digits one by one in a cycle
		while(i > 0) {
			// extract current digit
			uint8 digit = uint8(i % base);

			// convert it to an ASCII code
			// 0x20 is " "
			// 0x30-0x39 is "0"-"9"
			// 0x41-0x5A is "A"-"Z"
			// 0x61-0x7A is "a"-"z" ("A"-"Z" XOR " ")
			uint8 ascii = digit + 0x30;

			// if digit is greater then 10,
			// fix the 0x3A-0x40 gap of punctuation marks
			// (7 characters in ASCII table)
			if(digit >= 10) {
				// jump through the gap
				ascii += 7;
			}

			// write character into the buffer
			buf[p++] = bytes1(ascii);

			// move to the next digit
			i /= base;
		}

		// `p` contains real length of the buffer now,
		// allocate the resulting buffer of that size
		bytes memory result = new bytes(p);

		// copy the buffer in the reversed order
		for(p = 0; p < result.length; p++) {
			// copy from the beginning of the original buffer
			// to the end of resulting smaller buffer
			result[result.length - p - 1] = buf[p];
		}

		// construct string and return
		return string(result);
	}

	/**
	 * @dev Concatenates two strings `s1` and `s2`, for example, if
	 *      `s1` == `foo` and `s2` == `bar`, the result `s` == `foobar`
	 * @param s1 first string
	 * @param s2 second string
	 * @return s concatenation result s1 + s2
	 */
	function concat(string memory s1, string memory s2) internal pure returns (string memory s) {
		// an old way of string concatenation (Solidity 0.4) is commented out
/*
		// convert s1 into buffer 1
		bytes memory buf1 = bytes(s1);
		// convert s2 into buffer 2
		bytes memory buf2 = bytes(s2);
		// create a buffer for concatenation result
		bytes memory buf = new bytes(buf1.length + buf2.length);

		// copy buffer 1 into buffer
		for(uint256 i = 0; i < buf1.length; i++) {
			buf[i] = buf1[i];
		}

		// copy buffer 2 into buffer
		for(uint256 j = buf1.length; j < buf2.length; j++) {
			buf[j] = buf2[j - buf1.length];
		}

		// construct string and return
		return string(buf);
*/

		// simply use built in function
		return string(abi.encodePacked(s1, s2));
	}
}

File 8 of 14 : AccessControl.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

/**
 * @title Access Control List
 *
 * @notice Access control smart contract provides an API to check
 *      if specific operation is permitted globally and/or
 *      if particular user has a permission to execute it.
 *
 * @notice It deals with two main entities: features and roles.
 *
 * @notice Features are designed to be used to enable/disable specific
 *      functions (public functions) of the smart contract for everyone.
 * @notice User roles are designed to restrict access to specific
 *      functions (restricted functions) of the smart contract to some users.
 *
 * @notice Terms "role", "permissions" and "set of permissions" have equal meaning
 *      in the documentation text and may be used interchangeably.
 * @notice Terms "permission", "single permission" implies only one permission bit set.
 *
 * @notice Access manager is a special role which allows to grant/revoke other roles.
 *      Access managers can only grant/revoke permissions which they have themselves.
 *      As an example, access manager with no other roles set can only grant/revoke its own
 *      access manager permission and nothing else.
 *
 * @notice Access manager permission should be treated carefully, as a super admin permission:
 *      Access manager with even no other permission can interfere with another account by
 *      granting own access manager permission to it and effectively creating more powerful
 *      permission set than its own.
 *
 * @dev Both current and OpenZeppelin AccessControl implementations feature a similar API
 *      to check/know "who is allowed to do this thing".
 * @dev Zeppelin implementation is more flexible:
 *      - it allows setting unlimited number of roles, while current is limited to 256 different roles
 *      - it allows setting an admin for each role, while current allows having only one global admin
 * @dev Current implementation is more lightweight:
 *      - it uses only 1 bit per role, while Zeppelin uses 256 bits
 *      - it allows setting up to 256 roles at once, in a single transaction, while Zeppelin allows
 *        setting only one role in a single transaction
 *
 * @dev This smart contract is designed to be inherited by other
 *      smart contracts which require access control management capabilities.
 *
 * @dev Access manager permission has a bit 255 set.
 *      This bit must not be used by inheriting contracts for any other permissions/features.
 *
 * @author Basil Gorin
 */
contract AccessControl {
	/**
	 * @notice Access manager is responsible for assigning the roles to users,
	 *      enabling/disabling global features of the smart contract
	 * @notice Access manager can add, remove and update user roles,
	 *      remove and update global features
	 *
	 * @dev Role ROLE_ACCESS_MANAGER allows modifying user roles and global features
	 * @dev Role ROLE_ACCESS_MANAGER has single bit at position 255 enabled
	 */
	uint256 public constant ROLE_ACCESS_MANAGER = 0x8000000000000000000000000000000000000000000000000000000000000000;

	/**
	 * @dev Bitmask representing all the possible permissions (super admin role)
	 * @dev Has all the bits are enabled (2^256 - 1 value)
	 */
	uint256 private constant FULL_PRIVILEGES_MASK = type(uint256).max; // before 0.8.0: uint256(-1) overflows to 0xFFFF...

	/**
	 * @notice Privileged addresses with defined roles/permissions
	 * @notice In the context of ERC20/ERC721 tokens these can be permissions to
	 *      allow minting or burning tokens, transferring on behalf and so on
	 *
	 * @dev Maps user address to the permissions bitmask (role), where each bit
	 *      represents a permission
	 * @dev Bitmask 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
	 *      represents all possible permissions
	 * @dev 'This' address mapping represents global features of the smart contract
	 */
	mapping(address => uint256) public userRoles;

	/**
	 * @dev Fired in updateRole() and updateFeatures()
	 *
	 * @param _by operator which called the function
	 * @param _to address which was granted/revoked permissions
	 * @param _requested permissions requested
	 * @param _actual permissions effectively set
	 */
	event RoleUpdated(address indexed _by, address indexed _to, uint256 _requested, uint256 _actual);

	/**
	 * @notice Creates an access control instance,
	 *      setting contract creator to have full privileges
	 */
	constructor() {
		// contract creator has full privileges
		userRoles[msg.sender] = FULL_PRIVILEGES_MASK;
	}

	/**
	 * @notice Retrieves globally set of features enabled
	 *
	 * @dev Effectively reads userRoles role for the contract itself
	 *
	 * @return 256-bit bitmask of the features enabled
	 */
	function features() public view returns(uint256) {
		// features are stored in 'this' address  mapping of `userRoles` structure
		return userRoles[address(this)];
	}

	/**
	 * @notice Updates set of the globally enabled features (`features`),
	 *      taking into account sender's permissions
	 *
	 * @dev Requires transaction sender to have `ROLE_ACCESS_MANAGER` permission
	 * @dev Function is left for backward compatibility with older versions
	 *
	 * @param _mask bitmask representing a set of features to enable/disable
	 */
	function updateFeatures(uint256 _mask) public {
		// delegate call to `updateRole`
		updateRole(address(this), _mask);
	}

	/**
	 * @notice Updates set of permissions (role) for a given user,
	 *      taking into account sender's permissions.
	 *
	 * @dev Setting role to zero is equivalent to removing an all permissions
	 * @dev Setting role to `FULL_PRIVILEGES_MASK` is equivalent to
	 *      copying senders' permissions (role) to the user
	 * @dev Requires transaction sender to have `ROLE_ACCESS_MANAGER` permission
	 *
	 * @param operator address of a user to alter permissions for or zero
	 *      to alter global features of the smart contract
	 * @param role bitmask representing a set of permissions to
	 *      enable/disable for a user specified
	 */
	function updateRole(address operator, uint256 role) public {
		// caller must have a permission to update user roles
		require(isSenderInRole(ROLE_ACCESS_MANAGER), "access denied");

		// evaluate the role and reassign it
		userRoles[operator] = evaluateBy(msg.sender, userRoles[operator], role);

		// fire an event
		emit RoleUpdated(msg.sender, operator, role, userRoles[operator]);
	}

	/**
	 * @notice Determines the permission bitmask an operator can set on the
	 *      target permission set
	 * @notice Used to calculate the permission bitmask to be set when requested
	 *     in `updateRole` and `updateFeatures` functions
	 *
	 * @dev Calculated based on:
	 *      1) operator's own permission set read from userRoles[operator]
	 *      2) target permission set - what is already set on the target
	 *      3) desired permission set - what do we want set target to
	 *
	 * @dev Corner cases:
	 *      1) Operator is super admin and its permission set is `FULL_PRIVILEGES_MASK`:
	 *        `desired` bitset is returned regardless of the `target` permission set value
	 *        (what operator sets is what they get)
	 *      2) Operator with no permissions (zero bitset):
	 *        `target` bitset is returned regardless of the `desired` value
	 *        (operator has no authority and cannot modify anything)
	 *
	 * @dev Example:
	 *      Consider an operator with the permissions bitmask     00001111
	 *      is about to modify the target permission set          01010101
	 *      Operator wants to set that permission set to          00110011
	 *      Based on their role, an operator has the permissions
	 *      to update only lowest 4 bits on the target, meaning that
	 *      high 4 bits of the target set in this example is left
	 *      unchanged and low 4 bits get changed as desired:      01010011
	 *
	 * @param operator address of the contract operator which is about to set the permissions
	 * @param target input set of permissions to operator is going to modify
	 * @param desired desired set of permissions operator would like to set
	 * @return resulting set of permissions given operator will set
	 */
	function evaluateBy(address operator, uint256 target, uint256 desired) public view returns(uint256) {
		// read operator's permissions
		uint256 p = userRoles[operator];

		// taking into account operator's permissions,
		// 1) enable the permissions desired on the `target`
		target |= p & desired;
		// 2) disable the permissions desired on the `target`
		target &= FULL_PRIVILEGES_MASK ^ (p & (FULL_PRIVILEGES_MASK ^ desired));

		// return calculated result
		return target;
	}

	/**
	 * @notice Checks if requested set of features is enabled globally on the contract
	 *
	 * @param required set of features to check against
	 * @return true if all the features requested are enabled, false otherwise
	 */
	function isFeatureEnabled(uint256 required) public view returns(bool) {
		// delegate call to `__hasRole`, passing `features` property
		return __hasRole(features(), required);
	}

	/**
	 * @notice Checks if transaction sender `msg.sender` has all the permissions required
	 *
	 * @param required set of permissions (role) to check against
	 * @return true if all the permissions requested are enabled, false otherwise
	 */
	function isSenderInRole(uint256 required) public view returns(bool) {
		// delegate call to `isOperatorInRole`, passing transaction sender
		return isOperatorInRole(msg.sender, required);
	}

	/**
	 * @notice Checks if operator has all the permissions (role) required
	 *
	 * @param operator address of the user to check role for
	 * @param required set of permissions (role) to check
	 * @return true if all the permissions requested are enabled, false otherwise
	 */
	function isOperatorInRole(address operator, uint256 required) public view returns(bool) {
		// delegate call to `__hasRole`, passing operator's permissions (role)
		return __hasRole(userRoles[operator], required);
	}

	/**
	 * @dev Checks if role `actual` contains all the permissions required `required`
	 *
	 * @param actual existent role
	 * @param required required role
	 * @return true if actual has required role (all permissions), false otherwise
	 */
	function __hasRole(uint256 actual, uint256 required) internal pure returns(bool) {
		// check the bitmask for the role required and return the result
		return actual & required == required;
	}
}

File 9 of 14 : AddressUpgradeable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/Address.sol)

pragma solidity ^0.8.0;

/**
 * @dev Collection of functions related to the address type
 */
library AddressUpgradeable {
    /**
     * @dev Returns true if `account` is a contract.
     *
     * [IMPORTANT]
     * ====
     * It is unsafe to assume that an address for which this function returns
     * false is an externally-owned account (EOA) and not a contract.
     *
     * Among others, `isContract` will return false for the following
     * types of addresses:
     *
     *  - an externally-owned account
     *  - a contract in construction
     *  - an address where a contract will be created
     *  - an address where a contract lived, but was destroyed
     * ====
     */
    function isContract(address account) internal view returns (bool) {
        // This method relies on extcodesize, which returns 0 for contracts in
        // construction, since the code is only stored at the end of the
        // constructor execution.

        uint256 size;
        assembly {
            size := extcodesize(account)
        }
        return size > 0;
    }

    /**
     * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
     * `recipient`, forwarding all available gas and reverting on errors.
     *
     * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
     * of certain opcodes, possibly making contracts go over the 2300 gas limit
     * imposed by `transfer`, making them unable to receive funds via
     * `transfer`. {sendValue} removes this limitation.
     *
     * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
     *
     * IMPORTANT: because control is transferred to `recipient`, care must be
     * taken to not create reentrancy vulnerabilities. Consider using
     * {ReentrancyGuard} or the
     * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
     */
    function sendValue(address payable recipient, uint256 amount) internal {
        require(address(this).balance >= amount, "Address: insufficient balance");

        (bool success, ) = recipient.call{value: amount}("");
        require(success, "Address: unable to send value, recipient may have reverted");
    }

    /**
     * @dev Performs a Solidity function call using a low level `call`. A
     * plain `call` is an unsafe replacement for a function call: use this
     * function instead.
     *
     * If `target` reverts with a revert reason, it is bubbled up by this
     * function (like regular Solidity function calls).
     *
     * Returns the raw returned data. To convert to the expected return value,
     * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
     *
     * Requirements:
     *
     * - `target` must be a contract.
     * - calling `target` with `data` must not revert.
     *
     * _Available since v3.1._
     */
    function functionCall(address target, bytes memory data) internal returns (bytes memory) {
        return functionCall(target, data, "Address: low-level call failed");
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
     * `errorMessage` as a fallback revert reason when `target` reverts.
     *
     * _Available since v3.1._
     */
    function functionCall(
        address target,
        bytes memory data,
        string memory errorMessage
    ) internal returns (bytes memory) {
        return functionCallWithValue(target, data, 0, errorMessage);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but also transferring `value` wei to `target`.
     *
     * Requirements:
     *
     * - the calling contract must have an ETH balance of at least `value`.
     * - the called Solidity function must be `payable`.
     *
     * _Available since v3.1._
     */
    function functionCallWithValue(
        address target,
        bytes memory data,
        uint256 value
    ) internal returns (bytes memory) {
        return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
    }

    /**
     * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
     * with `errorMessage` as a fallback revert reason when `target` reverts.
     *
     * _Available since v3.1._
     */
    function functionCallWithValue(
        address target,
        bytes memory data,
        uint256 value,
        string memory errorMessage
    ) internal returns (bytes memory) {
        require(address(this).balance >= value, "Address: insufficient balance for call");
        require(isContract(target), "Address: call to non-contract");

        (bool success, bytes memory returndata) = target.call{value: value}(data);
        return verifyCallResult(success, returndata, errorMessage);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but performing a static call.
     *
     * _Available since v3.3._
     */
    function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
        return functionStaticCall(target, data, "Address: low-level static call failed");
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
     * but performing a static call.
     *
     * _Available since v3.3._
     */
    function functionStaticCall(
        address target,
        bytes memory data,
        string memory errorMessage
    ) internal view returns (bytes memory) {
        require(isContract(target), "Address: static call to non-contract");

        (bool success, bytes memory returndata) = target.staticcall(data);
        return verifyCallResult(success, returndata, errorMessage);
    }

    /**
     * @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the
     * revert reason using the provided one.
     *
     * _Available since v4.3._
     */
    function verifyCallResult(
        bool success,
        bytes memory returndata,
        string memory errorMessage
    ) internal pure returns (bytes memory) {
        if (success) {
            return returndata;
        } else {
            // Look for revert reason and bubble it up if present
            if (returndata.length > 0) {
                // The easiest way to bubble the revert reason is using memory via assembly

                assembly {
                    let returndata_size := mload(returndata)
                    revert(add(32, returndata), returndata_size)
                }
            } else {
                revert(errorMessage);
            }
        }
    }
}

File 10 of 14 : UUPSUpgradeable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (proxy/utils/UUPSUpgradeable.sol)

pragma solidity ^0.8.0;

import "../ERC1967/ERC1967UpgradeUpgradeable.sol";
import "./Initializable.sol";

/**
 * @dev An upgradeability mechanism designed for UUPS proxies. The functions included here can perform an upgrade of an
 * {ERC1967Proxy}, when this contract is set as the implementation behind such a proxy.
 *
 * A security mechanism ensures that an upgrade does not turn off upgradeability accidentally, although this risk is
 * reinstated if the upgrade retains upgradeability but removes the security mechanism, e.g. by replacing
 * `UUPSUpgradeable` with a custom implementation of upgrades.
 *
 * The {_authorizeUpgrade} function must be overridden to include access restriction to the upgrade mechanism.
 *
 * _Available since v4.1._
 */
abstract contract UUPSUpgradeable is Initializable, ERC1967UpgradeUpgradeable {
    function __UUPSUpgradeable_init() internal onlyInitializing {
        __ERC1967Upgrade_init_unchained();
        __UUPSUpgradeable_init_unchained();
    }

    function __UUPSUpgradeable_init_unchained() internal onlyInitializing {
    }
    /// @custom:oz-upgrades-unsafe-allow state-variable-immutable state-variable-assignment
    address private immutable __self = address(this);

    /**
     * @dev Check that the execution is being performed through a delegatecall call and that the execution context is
     * a proxy contract with an implementation (as defined in ERC1967) pointing to self. This should only be the case
     * for UUPS and transparent proxies that are using the current contract as their implementation. Execution of a
     * function through ERC1167 minimal proxies (clones) would not normally pass this test, but is not guaranteed to
     * fail.
     */
    modifier onlyProxy() {
        require(address(this) != __self, "Function must be called through delegatecall");
        require(_getImplementation() == __self, "Function must be called through active proxy");
        _;
    }

    /**
     * @dev Upgrade the implementation of the proxy to `newImplementation`.
     *
     * Calls {_authorizeUpgrade}.
     *
     * Emits an {Upgraded} event.
     */
    function upgradeTo(address newImplementation) external virtual onlyProxy {
        _authorizeUpgrade(newImplementation);
        _upgradeToAndCallSecure(newImplementation, new bytes(0), false);
    }

    /**
     * @dev Upgrade the implementation of the proxy to `newImplementation`, and subsequently execute the function call
     * encoded in `data`.
     *
     * Calls {_authorizeUpgrade}.
     *
     * Emits an {Upgraded} event.
     */
    function upgradeToAndCall(address newImplementation, bytes memory data) external payable virtual onlyProxy {
        _authorizeUpgrade(newImplementation);
        _upgradeToAndCallSecure(newImplementation, data, true);
    }

    /**
     * @dev Function that should revert when `msg.sender` is not authorized to upgrade the contract. Called by
     * {upgradeTo} and {upgradeToAndCall}.
     *
     * Normally, this function will use an xref:access.adoc[access control] modifier such as {Ownable-onlyOwner}.
     *
     * ```solidity
     * function _authorizeUpgrade(address) internal override onlyOwner {}
     * ```
     */
    function _authorizeUpgrade(address newImplementation) internal virtual;
    uint256[50] private __gap;
}

File 11 of 14 : ERC1967UpgradeUpgradeable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (proxy/ERC1967/ERC1967Upgrade.sol)

pragma solidity ^0.8.2;

import "../beacon/IBeaconUpgradeable.sol";
import "../../utils/AddressUpgradeable.sol";
import "../../utils/StorageSlotUpgradeable.sol";
import "../utils/Initializable.sol";

/**
 * @dev This abstract contract provides getters and event emitting update functions for
 * https://eips.ethereum.org/EIPS/eip-1967[EIP1967] slots.
 *
 * _Available since v4.1._
 *
 * @custom:oz-upgrades-unsafe-allow delegatecall
 */
abstract contract ERC1967UpgradeUpgradeable is Initializable {
    function __ERC1967Upgrade_init() internal onlyInitializing {
        __ERC1967Upgrade_init_unchained();
    }

    function __ERC1967Upgrade_init_unchained() internal onlyInitializing {
    }
    // This is the keccak-256 hash of "eip1967.proxy.rollback" subtracted by 1
    bytes32 private constant _ROLLBACK_SLOT = 0x4910fdfa16fed3260ed0e7147f7cc6da11a60208b5b9406d12a635614ffd9143;

    /**
     * @dev Storage slot with the address of the current implementation.
     * This is the keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1, and is
     * validated in the constructor.
     */
    bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;

    /**
     * @dev Emitted when the implementation is upgraded.
     */
    event Upgraded(address indexed implementation);

    /**
     * @dev Returns the current implementation address.
     */
    function _getImplementation() internal view returns (address) {
        return StorageSlotUpgradeable.getAddressSlot(_IMPLEMENTATION_SLOT).value;
    }

    /**
     * @dev Stores a new address in the EIP1967 implementation slot.
     */
    function _setImplementation(address newImplementation) private {
        require(AddressUpgradeable.isContract(newImplementation), "ERC1967: new implementation is not a contract");
        StorageSlotUpgradeable.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation;
    }

    /**
     * @dev Perform implementation upgrade
     *
     * Emits an {Upgraded} event.
     */
    function _upgradeTo(address newImplementation) internal {
        _setImplementation(newImplementation);
        emit Upgraded(newImplementation);
    }

    /**
     * @dev Perform implementation upgrade with additional setup call.
     *
     * Emits an {Upgraded} event.
     */
    function _upgradeToAndCall(
        address newImplementation,
        bytes memory data,
        bool forceCall
    ) internal {
        _upgradeTo(newImplementation);
        if (data.length > 0 || forceCall) {
            _functionDelegateCall(newImplementation, data);
        }
    }

    /**
     * @dev Perform implementation upgrade with security checks for UUPS proxies, and additional setup call.
     *
     * Emits an {Upgraded} event.
     */
    function _upgradeToAndCallSecure(
        address newImplementation,
        bytes memory data,
        bool forceCall
    ) internal {
        address oldImplementation = _getImplementation();

        // Initial upgrade and setup call
        _setImplementation(newImplementation);
        if (data.length > 0 || forceCall) {
            _functionDelegateCall(newImplementation, data);
        }

        // Perform rollback test if not already in progress
        StorageSlotUpgradeable.BooleanSlot storage rollbackTesting = StorageSlotUpgradeable.getBooleanSlot(_ROLLBACK_SLOT);
        if (!rollbackTesting.value) {
            // Trigger rollback using upgradeTo from the new implementation
            rollbackTesting.value = true;
            _functionDelegateCall(
                newImplementation,
                abi.encodeWithSignature("upgradeTo(address)", oldImplementation)
            );
            rollbackTesting.value = false;
            // Check rollback was effective
            require(oldImplementation == _getImplementation(), "ERC1967Upgrade: upgrade breaks further upgrades");
            // Finally reset to the new implementation and log the upgrade
            _upgradeTo(newImplementation);
        }
    }

    /**
     * @dev Storage slot with the admin of the contract.
     * This is the keccak-256 hash of "eip1967.proxy.admin" subtracted by 1, and is
     * validated in the constructor.
     */
    bytes32 internal constant _ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103;

    /**
     * @dev Emitted when the admin account has changed.
     */
    event AdminChanged(address previousAdmin, address newAdmin);

    /**
     * @dev Returns the current admin.
     */
    function _getAdmin() internal view returns (address) {
        return StorageSlotUpgradeable.getAddressSlot(_ADMIN_SLOT).value;
    }

    /**
     * @dev Stores a new address in the EIP1967 admin slot.
     */
    function _setAdmin(address newAdmin) private {
        require(newAdmin != address(0), "ERC1967: new admin is the zero address");
        StorageSlotUpgradeable.getAddressSlot(_ADMIN_SLOT).value = newAdmin;
    }

    /**
     * @dev Changes the admin of the proxy.
     *
     * Emits an {AdminChanged} event.
     */
    function _changeAdmin(address newAdmin) internal {
        emit AdminChanged(_getAdmin(), newAdmin);
        _setAdmin(newAdmin);
    }

    /**
     * @dev The storage slot of the UpgradeableBeacon contract which defines the implementation for this proxy.
     * This is bytes32(uint256(keccak256('eip1967.proxy.beacon')) - 1)) and is validated in the constructor.
     */
    bytes32 internal constant _BEACON_SLOT = 0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50;

    /**
     * @dev Emitted when the beacon is upgraded.
     */
    event BeaconUpgraded(address indexed beacon);

    /**
     * @dev Returns the current beacon.
     */
    function _getBeacon() internal view returns (address) {
        return StorageSlotUpgradeable.getAddressSlot(_BEACON_SLOT).value;
    }

    /**
     * @dev Stores a new beacon in the EIP1967 beacon slot.
     */
    function _setBeacon(address newBeacon) private {
        require(AddressUpgradeable.isContract(newBeacon), "ERC1967: new beacon is not a contract");
        require(
            AddressUpgradeable.isContract(IBeaconUpgradeable(newBeacon).implementation()),
            "ERC1967: beacon implementation is not a contract"
        );
        StorageSlotUpgradeable.getAddressSlot(_BEACON_SLOT).value = newBeacon;
    }

    /**
     * @dev Perform beacon upgrade with additional setup call. Note: This upgrades the address of the beacon, it does
     * not upgrade the implementation contained in the beacon (see {UpgradeableBeacon-_setImplementation} for that).
     *
     * Emits a {BeaconUpgraded} event.
     */
    function _upgradeBeaconToAndCall(
        address newBeacon,
        bytes memory data,
        bool forceCall
    ) internal {
        _setBeacon(newBeacon);
        emit BeaconUpgraded(newBeacon);
        if (data.length > 0 || forceCall) {
            _functionDelegateCall(IBeaconUpgradeable(newBeacon).implementation(), data);
        }
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
     * but performing a delegate call.
     *
     * _Available since v3.4._
     */
    function _functionDelegateCall(address target, bytes memory data) private returns (bytes memory) {
        require(AddressUpgradeable.isContract(target), "Address: delegate call to non-contract");

        // solhint-disable-next-line avoid-low-level-calls
        (bool success, bytes memory returndata) = target.delegatecall(data);
        return AddressUpgradeable.verifyCallResult(success, returndata, "Address: low-level delegate call failed");
    }
    uint256[50] private __gap;
}

File 12 of 14 : Initializable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (proxy/utils/Initializable.sol)

pragma solidity ^0.8.0;

import "../../utils/AddressUpgradeable.sol";

/**
 * @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed
 * behind a proxy. Since a proxied contract can't have a constructor, it's common to move constructor logic to an
 * external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer
 * function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.
 *
 * TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as
 * possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}.
 *
 * CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure
 * that all initializers are idempotent. This is not verified automatically as constructors are by Solidity.
 *
 * [CAUTION]
 * ====
 * Avoid leaving a contract uninitialized.
 *
 * An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation
 * contract, which may impact the proxy. To initialize the implementation contract, you can either invoke the
 * initializer manually, or you can include a constructor to automatically mark it as initialized when it is deployed:
 *
 * [.hljs-theme-light.nopadding]
 * ```
 * /// @custom:oz-upgrades-unsafe-allow constructor
 * constructor() initializer {}
 * ```
 * ====
 */
abstract contract Initializable {
    /**
     * @dev Indicates that the contract has been initialized.
     */
    bool private _initialized;

    /**
     * @dev Indicates that the contract is in the process of being initialized.
     */
    bool private _initializing;

    /**
     * @dev Modifier to protect an initializer function from being invoked twice.
     */
    modifier initializer() {
        // If the contract is initializing we ignore whether _initialized is set in order to support multiple
        // inheritance patterns, but we only do this in the context of a constructor, because in other contexts the
        // contract may have been reentered.
        require(_initializing ? _isConstructor() : !_initialized, "Initializable: contract is already initialized");

        bool isTopLevelCall = !_initializing;
        if (isTopLevelCall) {
            _initializing = true;
            _initialized = true;
        }

        _;

        if (isTopLevelCall) {
            _initializing = false;
        }
    }

    /**
     * @dev Modifier to protect an initialization function so that it can only be invoked by functions with the
     * {initializer} modifier, directly or indirectly.
     */
    modifier onlyInitializing() {
        require(_initializing, "Initializable: contract is not initializing");
        _;
    }

    function _isConstructor() private view returns (bool) {
        return !AddressUpgradeable.isContract(address(this));
    }
}

File 13 of 14 : IBeaconUpgradeable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (proxy/beacon/IBeacon.sol)

pragma solidity ^0.8.0;

/**
 * @dev This is the interface that {BeaconProxy} expects of its beacon.
 */
interface IBeaconUpgradeable {
    /**
     * @dev Must return an address that can be used as a delegate call target.
     *
     * {BeaconProxy} will check that this address is a contract.
     */
    function implementation() external view returns (address);
}

File 14 of 14 : StorageSlotUpgradeable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/StorageSlot.sol)

pragma solidity ^0.8.0;

/**
 * @dev Library for reading and writing primitive types to specific storage slots.
 *
 * Storage slots are often used to avoid storage conflict when dealing with upgradeable contracts.
 * This library helps with reading and writing to such slots without the need for inline assembly.
 *
 * The functions in this library return Slot structs that contain a `value` member that can be used to read or write.
 *
 * Example usage to set ERC1967 implementation slot:
 * ```
 * contract ERC1967 {
 *     bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
 *
 *     function _getImplementation() internal view returns (address) {
 *         return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value;
 *     }
 *
 *     function _setImplementation(address newImplementation) internal {
 *         require(Address.isContract(newImplementation), "ERC1967: new implementation is not a contract");
 *         StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation;
 *     }
 * }
 * ```
 *
 * _Available since v4.1 for `address`, `bool`, `bytes32`, and `uint256`._
 */
library StorageSlotUpgradeable {
    struct AddressSlot {
        address value;
    }

    struct BooleanSlot {
        bool value;
    }

    struct Bytes32Slot {
        bytes32 value;
    }

    struct Uint256Slot {
        uint256 value;
    }

    /**
     * @dev Returns an `AddressSlot` with member `value` located at `slot`.
     */
    function getAddressSlot(bytes32 slot) internal pure returns (AddressSlot storage r) {
        assembly {
            r.slot := slot
        }
    }

    /**
     * @dev Returns an `BooleanSlot` with member `value` located at `slot`.
     */
    function getBooleanSlot(bytes32 slot) internal pure returns (BooleanSlot storage r) {
        assembly {
            r.slot := slot
        }
    }

    /**
     * @dev Returns an `Bytes32Slot` with member `value` located at `slot`.
     */
    function getBytes32Slot(bytes32 slot) internal pure returns (Bytes32Slot storage r) {
        assembly {
            r.slot := slot
        }
    }

    /**
     * @dev Returns an `Uint256Slot` with member `value` located at `slot`.
     */
    function getUint256Slot(bytes32 slot) internal pure returns (Uint256Slot storage r) {
        assembly {
            r.slot := slot
        }
    }
}

Settings
{
  "optimizer": {
    "enabled": true,
    "runs": 200
  },
  "outputSelection": {
    "*": {
      "*": [
        "evm.bytecode",
        "evm.deployedBytecode",
        "devdoc",
        "userdoc",
        "metadata",
        "abi"
      ]
    }
  },
  "libraries": {}
}

Contract Security Audit

Contract ABI

[{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"previousAdmin","type":"address"},{"indexed":false,"internalType":"address","name":"newAdmin","type":"address"}],"name":"AdminChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"beacon","type":"address"}],"name":"BeaconUpgraded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"_by","type":"address"},{"indexed":false,"internalType":"uint96","name":"_linkPrice","type":"uint96"},{"indexed":false,"internalType":"uint96","name":"_linkFee","type":"uint96"},{"indexed":true,"internalType":"address","name":"_feeDestination","type":"address"}],"name":"LinkPriceChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"_by","type":"address"},{"indexed":true,"internalType":"uint256","name":"_iNftId","type":"uint256"},{"indexed":false,"internalType":"int128","name":"_aliDelta","type":"int128"},{"indexed":false,"internalType":"uint96","name":"_feeValue","type":"uint96"}],"name":"LinkUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"_by","type":"address"},{"indexed":false,"internalType":"uint256","name":"_iNftId","type":"uint256"},{"indexed":false,"internalType":"uint96","name":"_linkPrice","type":"uint96"},{"indexed":false,"internalType":"uint96","name":"_linkFee","type":"uint96"},{"indexed":true,"internalType":"address","name":"_personalityContract","type":"address"},{"indexed":true,"internalType":"uint96","name":"_personalityId","type":"uint96"},{"indexed":false,"internalType":"address","name":"_targetContract","type":"address"},{"indexed":false,"internalType":"uint256","name":"_targetId","type":"uint256"}],"name":"Linked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"_by","type":"address"},{"indexed":false,"internalType":"uint256","name":"_oldVal","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"_newVal","type":"uint256"}],"name":"NextIdChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"_by","type":"address"},{"indexed":true,"internalType":"address","name":"_to","type":"address"},{"indexed":false,"internalType":"uint256","name":"_requested","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"_actual","type":"uint256"}],"name":"RoleUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"_by","type":"address"},{"indexed":true,"internalType":"address","name":"_targetContract","type":"address"},{"indexed":false,"internalType":"uint8","name":"_oldVal","type":"uint8"},{"indexed":false,"internalType":"uint8","name":"_newVal","type":"uint8"}],"name":"TargetContractWhitelisted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"_by","type":"address"},{"indexed":true,"internalType":"uint256","name":"_iNftId","type":"uint256"}],"name":"Unlinked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"implementation","type":"address"}],"name":"Upgraded","type":"event"},{"inputs":[],"name":"FEATURE_ALLOW_ANY_NFT_CONTRACT","outputs":[{"internalType":"uint32","name":"","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"FEATURE_DEPOSITS","outputs":[{"internalType":"uint32","name":"","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"FEATURE_LINKING","outputs":[{"internalType":"uint32","name":"","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"FEATURE_UNLINKING","outputs":[{"internalType":"uint32","name":"","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"FEATURE_WITHDRAWALS","outputs":[{"internalType":"uint32","name":"","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"ROLE_ACCESS_MANAGER","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"ROLE_LINK_PRICE_MANAGER","outputs":[{"internalType":"uint32","name":"","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"ROLE_NEXT_ID_MANAGER","outputs":[{"internalType":"uint32","name":"","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"ROLE_UPGRADE_MANAGER","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"ROLE_WHITELIST_MANAGER","outputs":[{"internalType":"uint32","name":"","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"aliContract","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"iNftId","type":"uint256"},{"internalType":"uint96","name":"aliValue","type":"uint96"}],"name":"deposit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"operator","type":"address"},{"internalType":"uint256","name":"target","type":"uint256"},{"internalType":"uint256","name":"desired","type":"uint256"}],"name":"evaluateBy","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"features","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"feeDestination","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getImplementation","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"iNftContract","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"targetContract","type":"address"}],"name":"isAllowedForLinking","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"targetContract","type":"address"}],"name":"isAllowedForUnlinking","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"required","type":"uint256"}],"name":"isFeatureEnabled","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"operator","type":"address"},{"internalType":"uint256","name":"required","type":"uint256"}],"name":"isOperatorInRole","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"required","type":"uint256"}],"name":"isSenderInRole","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint96","name":"personalityId","type":"uint96"},{"internalType":"address","name":"targetContract","type":"address"},{"internalType":"uint256","name":"targetId","type":"uint256"}],"name":"link","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"linkFee","outputs":[{"internalType":"uint96","name":"","type":"uint96"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"linkPrice","outputs":[{"internalType":"uint96","name":"","type":"uint96"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"nextId","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"personalityContract","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_ali","type":"address"},{"internalType":"address","name":"_personality","type":"address"},{"internalType":"address","name":"_iNft","type":"address"}],"name":"postConstruct","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"iNftId","type":"uint256"}],"name":"unlink","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"nftContract","type":"address"},{"internalType":"uint256","name":"nftId","type":"uint256"}],"name":"unlinkNFT","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_mask","type":"uint256"}],"name":"updateFeatures","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint96","name":"_linkPrice","type":"uint96"},{"internalType":"uint96","name":"_linkFee","type":"uint96"},{"internalType":"address","name":"_feeDestination","type":"address"}],"name":"updateLinkPrice","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_nextId","type":"uint256"}],"name":"updateNextId","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"operator","type":"address"},{"internalType":"uint256","name":"role","type":"uint256"}],"name":"updateRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newImplementation","type":"address"}],"name":"upgradeTo","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newImplementation","type":"address"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"upgradeToAndCall","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"userRoles","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"targetContract","type":"address"},{"internalType":"bool","name":"allowedForLinking","type":"bool"},{"internalType":"bool","name":"allowedForUnlinking","type":"bool"}],"name":"whitelistTargetContract","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"whitelistedTargetContracts","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"iNftId","type":"uint256"},{"internalType":"uint96","name":"aliValue","type":"uint96"}],"name":"withdraw","outputs":[],"stateMutability":"nonpayable","type":"function"}]

60a06040523060601b60805234801561001757600080fd5b5060805160601c612e5761004b60003960008181610c1901528181610c5901528181610ce20152610d220152612e576000f3fe60806040526004361061023b5760003560e01c8063aaf10f421161012e578063cfd9dadf116100ab578063e023fd421161006f578063e023fd4214610702578063eb31b05b14610722578063f1e023fd14610739578063f822d5aa14610759578063fcc2c0781461077957600080fd5b8063cfd9dadf14610653578063d542d5dc1461066a578063d5bb7f67146106a6578063d79c85e5146106c6578063de53fcd5146106db57600080fd5b8063c4257421116100f2578063c4257421146105ad578063c6179e58146105c2578063c688d693146105e2578063c960651b14610602578063cf1c28321461061757600080fd5b8063aaf10f4214610528578063ae5b102e1461053d578063ae60bda41461055d578063ae682e2e14610575578063b66c72ac1461058d57600080fd5b80635fa2ad4a116101bc57806383df7ad51161018057806383df7ad5146104935780638d936c1e146104b35780639cf1bc92146104d3578063a1c6ef93146104e8578063a841ee281461050857600080fd5b80635fa2ad4a146103eb57806361b8ce8c1461040b578063725f36261461042157806374d5e1001461045157806378a9e4041461047e57600080fd5b806327d5b0aa1161020357806327d5b0aa1461032a5780632b5214161461034a5780633659cfe6146103765780633e0f0fa4146103965780634f1ef286146103d857600080fd5b806301889611146102405780630b6f78131461027d5780630cf785351461029f5780630dc3d284146102c65780631ca4f8ee146102fe575b600080fd5b34801561024c57600080fd5b50609a54610260906001600160601b031681565b6040516001600160601b0390911681526020015b60405180910390f35b34801561028957600080fd5b5061029d6102983660046127ad565b610799565b005b3480156102ab57600080fd5b5060995461026090600160a01b90046001600160601b031681565b3480156102d257600080fd5b506099546102e6906001600160a01b031681565b6040516001600160a01b039091168152602001610274565b34801561030a57600080fd5b506103156204000081565b60405163ffffffff9091168152602001610274565b34801561033657600080fd5b5061029d6103453660046128b1565b610991565b34801561035657600080fd5b50306000908152606560205260409020545b604051908152602001610274565b34801561038257600080fd5b5061029d610391366004612728565b610c0e565b3480156103a257600080fd5b506103c66103b1366004612728565b609c6020526000908152604090205460ff1681565b60405160ff9091168152602001610274565b61029d6103e63660046127ed565b610cd7565b3480156103f757600080fd5b5061029d610406366004612a5a565b610d91565b34801561041757600080fd5b50610368609b5481565b34801561042d57600080fd5b5061044161043c36600461299a565b610f96565b6040519015158152602001610274565b34801561045d57600080fd5b5061036861046c366004612728565b60656020526000908152604090205481565b34801561048a57600080fd5b50610315600881565b34801561049f57600080fd5b506097546102e6906001600160a01b031681565b3480156104bf57600080fd5b5061029d6104ce366004612a19565b610fb1565b3480156104df57600080fd5b50610315600181565b3480156104f457600080fd5b5061029d61050336600461299a565b611443565b34801561051457600080fd5b5061029d6105233660046129cc565b6114f1565b34801561053457600080fd5b506102e66117a1565b34801561054957600080fd5b5061029d6105583660046128b1565b6117b0565b34801561056957600080fd5b50610368600160fe1b81565b34801561058157600080fd5b50610368600160ff1b81565b34801561059957600080fd5b5061029d6105a83660046129cc565b611863565b3480156105b957600080fd5b50610315601081565b3480156105ce57600080fd5b506098546102e6906001600160a01b031681565b3480156105ee57600080fd5b506104416105fd3660046128b1565b611ba7565b34801561060e57600080fd5b50610315600281565b34801561062357600080fd5b50610441610632366004612728565b6001600160a01b03166000908152609c602052604090205460029081161490565b34801561065f57600080fd5b506103156201000081565b34801561067657600080fd5b50610441610685366004612728565b6001600160a01b03166000908152609c602052604090205460019081161490565b3480156106b257600080fd5b5061029d6106c136600461299a565b611bcc565b3480156106d257600080fd5b50610315600481565b3480156106e757600080fd5b50609a546102e690600160601b90046001600160a01b031681565b34801561070e57600080fd5b5061029d61071d366004612762565b611bd6565b34801561072e57600080fd5b506103156202000081565b34801561074557600080fd5b5061029d61075436600461299a565b611fe8565b34801561076557600080fd5b506103686107743660046128dd565b61223a565b34801561078557600080fd5b5061044161079436600461299a565b612265565b6107a562040000612265565b6107ca5760405162461bcd60e51b81526004016107c190612c16565b60405180910390fd5b6001600160a01b03831661080f5760405162461bcd60e51b815260206004820152600c60248201526b7a65726f206164647265737360a01b60448201526064016107c1565b81156108df576040516301ffc9a760e01b81526380ac58cd60e01b60048201526001600160a01b038416906301ffc9a79060240160206040518083038186803b15801561085b57600080fd5b505afa15801561086f573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610893919061297d565b6108df5760405162461bcd60e51b815260206004820152601860248201527f746172676574204e4654206973206e6f7420455243373231000000000000000060448201526064016107c1565b6000816108ed5760006108f0565b60025b836108fc5760006108ff565b60015b6001600160a01b0386166000818152609c602090815260409182902054825160ff918216815294909517948516908401529293509133917f3a3ca66be8fde14aec70663d5c198392dc74a3ad641905f583393d5415178d2f910160405180910390a36001600160a01b03939093166000908152609c60205260409020805460ff191660ff909416939093179092555050565b61099b6002610f96565b6109df5760405162461bcd60e51b81526020600482015260156024820152741d5b9b1a5b9ada5b99c81a5cc8191a5cd8589b1959605a1b60448201526064016107c1565b6099546040516331a9108f60e11b8152600481018390526001600160a01b0391821691339190851690636352211e9060240160206040518083038186803b158015610a2957600080fd5b505afa158015610a3d573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610a619190612745565b6001600160a01b031614610aaa5760405162461bcd60e51b815260206004820152601060248201526f3737ba1030b71027232a1037bbb732b960811b60448201526064016107c1565b6040516303366f7f60e21b81526001600160a01b0384811660048301526024820184905260009190831690630cd9bdfc9060440160206040518083038186803b158015610af657600080fd5b505afa158015610b0a573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610b2e91906129b3565b9050610b55846001600160a01b03166000908152609c602052604090205460029081161490565b80610b655750610b656004610f96565b610b815760405162461bcd60e51b81526004016107c190612c3d565b604051630852cd8d60e31b8152600481018290526001600160a01b038316906342966c6890602401600060405180830381600087803b158015610bc357600080fd5b505af1158015610bd7573d6000803e3d6000fd5b50506040518392503391507fdfa02adc9cf1364277c3c57daa66f9e9d90d54e6816235d64c77f3fce73f17be90600090a350505050565b306001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000161415610c575760405162461bcd60e51b81526004016107c190612b30565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316610c89612271565b6001600160a01b031614610caf5760405162461bcd60e51b81526004016107c190612b7c565b610cb88161229f565b60408051600080825260208201909252610cd4918391906122c8565b50565b306001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000161415610d205760405162461bcd60e51b81526004016107c190612b30565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316610d52612271565b6001600160a01b031614610d785760405162461bcd60e51b81526004016107c190612b7c565b610d818261229f565b610d8d828260016122c8565b5050565b610d9d62010000612265565b610db95760405162461bcd60e51b81526004016107c190612c16565b6001600160601b0383161580610ddd575064e8d4a51000836001600160601b031610155b610e195760405162461bcd60e51b815260206004820152600d60248201526c696e76616c696420707269636560981b60448201526064016107c1565b6001600160601b038216158015610e3757506001600160a01b038116155b80610e63575064e8d4a51000826001600160601b031610158015610e6357506001600160a01b03811615155b610eaf5760405162461bcd60e51b815260206004820152601c60248201527f696e76616c6964206c696e6b696e67206665652f74726561737572790000000060448201526064016107c1565b826001600160601b0316826001600160601b03161115610f1b5760405162461bcd60e51b815260206004820152602160248201527f6c696e6b696e67206665652065786365656473206c696e6b696e6720707269636044820152606560f81b60648201526084016107c1565b609980546001600160601b03858116600160a01b81026001600160a01b0393841617909355908316600160601b8102918516918217609a556040805193845260208401929092529133917f5f0d10b02f0b30044bc718c9f66f21a6bdfd73fd50346fa8bdeeab9c20fb844391015b60405180910390a3505050565b30600090815260656020526040812054821682145b92915050565b610fbb6001610f96565b610ffd5760405162461bcd60e51b81526020600482015260136024820152721b1a5b9ada5b99c81a5cc8191a5cd8589b1959606a1b60448201526064016107c1565b6098546040516331a9108f60e11b81526001600160601b038516600482015233916001600160a01b031690636352211e9060240160206040518083038186803b15801561104957600080fd5b505afa15801561105d573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906110819190612745565b6001600160a01b0316146110a75760405162461bcd60e51b81526004016107c190612c16565b6001600160a01b0382166000908152609c6020526040902054600190811614806110d657506110d66004610f96565b6110f25760405162461bcd60e51b81526004016107c190612c3d565b609a546001600160601b03161561119d57609754609a546040516323b872dd60e01b81526001600160a01b03928316926323b872dd92611149923392600160601b830416916001600160601b031690600401612aa6565b602060405180830381600087803b15801561116357600080fd5b505af1158015611177573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061119b919061297d565b505b609954600160a01b90046001600160601b03161561126657609754609954609a546001600160a01b03928316926323b872dd923392918116916111f4916001600160601b0391821691600160a01b90910416612cf7565b6040518463ffffffff1660e01b815260040161121293929190612aa6565b602060405180830381600087803b15801561122c57600080fd5b505af1158015611240573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611264919061297d565b505b6098546099546040516323b872dd60e01b81526001600160a01b03928316926323b872dd9261129f923392909116908890600401612aa6565b600060405180830381600087803b1580156112b957600080fd5b505af11580156112cd573d6000803e3d6000fd5b5050609954609b80546001600160a01b039092169350632c8d3fa3925060006112f583612d4b565b90915550609a5460995461131c916001600160601b0390811691600160a01b900416612cf7565b60985460405160e085901b6001600160e01b031916815260048101939093526001600160601b0391821660248401526001600160a01b03908116604484015290871660648301528516608482015260a4810184905260c401600060405180830381600087803b15801561138e57600080fd5b505af11580156113a2573d6000803e3d6000fd5b5050609854609b546001600160601b03871693506001600160a01b03909116915033907f4c5f6243e66f868e375120e87ec9c0e34ad78379d66dca7921055094b6a7eacd906113f390600190612ce0565b609954609a5460408051938452600160a01b9092046001600160601b03908116602085015216908201526001600160a01b03871660608201526080810186905260a00160405180910390a4505050565b61144f62020000612265565b61146b5760405162461bcd60e51b81526004016107c190612c16565b63ffffffff81116114ae5760405162461bcd60e51b815260206004820152600d60248201526c76616c756520746f6f206c6f7760981b60448201526064016107c1565b609b54604080519182526020820183905233917f9e66ea12c52a59bf2f0b1a9e2552dc42a46982c7a2c17c098b82166866dd8450910160405180910390a2609b55565b6114fb6010610f96565b6115475760405162461bcd60e51b815260206004820152601860248201527f7769746864726177616c73206172652064697361626c6564000000000000000060448201526064016107c1565b6099546040516331a9108f60e11b8152600481018490526001600160a01b039091169033908290636352211e9060240160206040518083038186803b15801561158f57600080fd5b505afa1580156115a3573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906115c79190612745565b6001600160a01b0316146115ed5760405162461bcd60e51b81526004016107c190612b05565b60995461160a90600160a01b90046001600160601b031683612c74565b6001600160601b0316816001600160a01b031663d12b5320856040518263ffffffff1660e01b815260040161164191815260200190565b60206040518083038186803b15801561165957600080fd5b505afa15801561166d573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061169191906129fc565b6001600160601b031610156116da5760405162461bcd60e51b815260206004820152600f60248201526e6465706f73697420746f6f206c6f7760881b60448201526064016107c1565b604051634cc7f53f60e01b8152600481018490526001600160601b03831660248201523360448201526001600160a01b03821690634cc7f53f90606401600060405180830381600087803b15801561173157600080fd5b505af1158015611745573d6000803e3d6000fd5b5050505082336001600160a01b03167f493ef55f20ebab6167ee6aa104d68707ac6d1af20254df7b68d53e081c62973a846001600160601b031661178890612d66565b60408051600f9290920b82526000602083015201610f89565b60006117ab612271565b905090565b6117bd600160ff1b612265565b6117d95760405162461bcd60e51b81526004016107c190612c16565b6001600160a01b0382166000908152606560205260409020546117fe9033908361223a565b6001600160a01b0383166000818152606560205260409081902083905551909133917f5a10526456f5116c0b7b80582c217d666243fd51b6a2d92c8011e601c2462e5f9161185791869190918252602082015260400190565b60405180910390a35050565b61186d6008610f96565b6118b15760405162461bcd60e51b815260206004820152601560248201527419195c1bdcda5d1cc8185c9948191a5cd8589b1959605a1b60448201526064016107c1565b6099546040516331a9108f60e11b8152600481018490526001600160a01b039091169033908290636352211e9060240160206040518083038186803b1580156118f957600080fd5b505afa15801561190d573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906119319190612745565b6001600160a01b0316146119575760405162461bcd60e51b81526004016107c190612b05565b6099546000908390600160a01b90046001600160601b0316158015906119875750609a546001600160601b031615155b15611a6157609954609a546001600160601b03600160a01b9092048216916119b3918116908416612cc1565b6119bd9190612c9f565b91506119c98285612cf7565b609754609a546040516323b872dd60e01b81529293506001600160a01b03918216926323b872dd92611a0d923392600160601b909104909116908790600401612aa6565b602060405180830381600087803b158015611a2757600080fd5b505af1158015611a3b573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611a5f919061297d565b505b6097546099546040516323b872dd60e01b81526001600160a01b03928316926323b872dd92611a9a923392909116908690600401612aa6565b602060405180830381600087803b158015611ab457600080fd5b505af1158015611ac8573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611aec919061297d565b50604051634a35dcff60e01b8152600481018690526001600160601b03821660248201526001600160a01b03841690634a35dcff90604401600060405180830381600087803b158015611b3e57600080fd5b505af1158015611b52573d6000803e3d6000fd5b5050604080516001600160601b03808616600f0b8252861660208201528893503392507f493ef55f20ebab6167ee6aa104d68707ac6d1af20254df7b68d53e081c62973a910160405180910390a35050505050565b6001600160a01b038216600090815260656020526040812054821682145b9392505050565b610cd430826117b0565b600054610100900460ff16611bf15760005460ff1615611bf5565b303b155b611c115760405162461bcd60e51b81526004016107c190612bc8565b600054610100900460ff16158015611c33576000805461ffff19166101011790555b6001600160a01b038416611c895760405162461bcd60e51b815260206004820152601960248201527f414c4920546f6b656e2061646472206973206e6f74207365740000000000000060448201526064016107c1565b6001600160a01b038316611cdf5760405162461bcd60e51b815260206004820152601e60248201527f414920506572736f6e616c6974792061646472206973206e6f7420736574000060448201526064016107c1565b6001600160a01b038216611d2c5760405162461bcd60e51b81526020600482015260146024820152731a539195081859191c881a5cc81b9bdd081cd95d60621b60448201526064016107c1565b6040516301ffc9a760e01b81526336372b0760e01b60048201526001600160a01b038516906301ffc9a79060240160206040518083038186803b158015611d7257600080fd5b505afa158015611d86573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611daa919061297d565b611df65760405162461bcd60e51b815260206004820152601960248201527f756e657870656374656420414c4920546f6b656e20747970650000000000000060448201526064016107c1565b6040516301ffc9a760e01b81526380ac58cd60e01b60048201526001600160a01b038416906301ffc9a79060240160206040518083038186803b158015611e3c57600080fd5b505afa158015611e50573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611e74919061297d565b611ec05760405162461bcd60e51b815260206004820152601e60248201527f756e657870656374656420414920506572736f6e616c6974792074797065000060448201526064016107c1565b6040516301ffc9a760e01b8152636f4fb12560e01b60048201526001600160a01b038316906301ffc9a79060240160206040518083038186803b158015611f0657600080fd5b505afa158015611f1a573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611f3e919061297d565b611f815760405162461bcd60e51b8152602060048201526014602482015273756e657870656374656420694e4654207479706560601b60448201526064016107c1565b609780546001600160a01b038087166001600160a01b031992831617909255609880548684169083161790556099805492851692909116919091179055640200000000609b55611fd033612413565b8015611fe2576000805461ff00191690555b50505050565b611ff26002610f96565b6120365760405162461bcd60e51b81526020600482015260156024820152741d5b9b1a5b9ada5b99c81a5cc8191a5cd8589b1959605a1b60448201526064016107c1565b6099546040516399a2241f60e01b8152600481018390526001600160a01b039091169060009082906399a2241f9060240160a06040518083038186803b15801561207f57600080fd5b505afa158015612093573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906120b79190612912565b5093505050506120e2816001600160a01b03166000908152609c602052604090205460029081161490565b806120f257506120f26004610f96565b61210e5760405162461bcd60e51b81526004016107c190612c3d565b6040516331a9108f60e11b81526004810184905233906001600160a01b03841690636352211e9060240160206040518083038186803b15801561215057600080fd5b505afa158015612164573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906121889190612745565b6001600160a01b0316146121ae5760405162461bcd60e51b81526004016107c190612b05565b604051630852cd8d60e31b8152600481018490526001600160a01b038316906342966c6890602401600060405180830381600087803b1580156121f057600080fd5b505af1158015612204573d6000803e3d6000fd5b50506040518592503391507fdfa02adc9cf1364277c3c57daa66f9e9d90d54e6816235d64c77f3fce73f17be90600090a3505050565b6001600160a01b03929092166000908152606560205260409020546000198084188216189216171690565b6000610fab3383611ba7565b7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc546001600160a01b031690565b6122ac600160fe1b612265565b610cd45760405162461bcd60e51b81526004016107c190612c16565b60006122d2612271565b90506122dd8461251f565b6000835111806122ea5750815b156122fb576122f984846125c4565b505b7f4910fdfa16fed3260ed0e7147f7cc6da11a60208b5b9406d12a635614ffd9143805460ff1661240c57805460ff191660011781556040516001600160a01b038316602482015261237a90869060440160408051601f198184030181529190526020810180516001600160e01b0316631b2ce7f360e11b1790526125c4565b50805460ff1916815561238b612271565b6001600160a01b0316826001600160a01b0316146124035760405162461bcd60e51b815260206004820152602f60248201527f45524331393637557067726164653a207570677261646520627265616b73206660448201526e75727468657220757067726164657360881b60648201526084016107c1565b61240c856126af565b5050505050565b600054610100900460ff1661242e5760005460ff1615612432565b303b155b61244e5760405162461bcd60e51b81526004016107c190612bc8565b600054610100900460ff16158015612470576000805461ffff19166101011790555b303b156124b15760405162461bcd60e51b815260206004820152600f60248201526e1a5b9d985b1a590818dbdb9d195e1d608a1b60448201526064016107c1565b6001600160a01b0382166000818152606560209081526040918290206000199081905582518181529182015233917f5a10526456f5116c0b7b80582c217d666243fd51b6a2d92c8011e601c2462e5f910160405180910390a38015610d8d576000805461ff00191690555050565b803b6125835760405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b60648201526084016107c1565b7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc80546001600160a01b0319166001600160a01b0392909216919091179055565b6060823b6126235760405162461bcd60e51b815260206004820152602660248201527f416464726573733a2064656c65676174652063616c6c20746f206e6f6e2d636f6044820152651b9d1c9858dd60d21b60648201526084016107c1565b600080846001600160a01b03168460405161263e9190612a8a565b600060405180830381855af49150503d8060008114612679576040519150601f19603f3d011682016040523d82523d6000602084013e61267e565b606091505b50915091506126a68282604051806060016040528060278152602001612dfb602791396126ef565b95945050505050565b6126b88161251f565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b90600090a250565b606083156126fe575081611bc5565b82511561270e5782518084602001fd5b8160405162461bcd60e51b81526004016107c19190612ad2565b60006020828403121561273a57600080fd5b8135611bc581612dc2565b60006020828403121561275757600080fd5b8151611bc581612dc2565b60008060006060848603121561277757600080fd5b833561278281612dc2565b9250602084013561279281612dc2565b915060408401356127a281612dc2565b809150509250925092565b6000806000606084860312156127c257600080fd5b83356127cd81612dc2565b925060208401356127dd81612dd7565b915060408401356127a281612dd7565b6000806040838503121561280057600080fd5b823561280b81612dc2565b9150602083013567ffffffffffffffff8082111561282857600080fd5b818501915085601f83011261283c57600080fd5b81358181111561284e5761284e612dac565b604051601f8201601f19908116603f0116810190838211818310171561287657612876612dac565b8160405282815288602084870101111561288f57600080fd5b8260208601602083013760006020848301015280955050505050509250929050565b600080604083850312156128c457600080fd5b82356128cf81612dc2565b946020939093013593505050565b6000806000606084860312156128f257600080fd5b83356128fd81612dc2565b95602085013595506040909401359392505050565b600080600080600060a0868803121561292a57600080fd5b855161293581612dc2565b602087015190955061294681612de5565b604087015190945061295781612de5565b606087015190935061296881612dc2565b80925050608086015190509295509295909350565b60006020828403121561298f57600080fd5b8151611bc581612dd7565b6000602082840312156129ac57600080fd5b5035919050565b6000602082840312156129c557600080fd5b5051919050565b600080604083850312156129df57600080fd5b8235915060208301356129f181612de5565b809150509250929050565b600060208284031215612a0e57600080fd5b8151611bc581612de5565b600080600060608486031215612a2e57600080fd5b8335612a3981612de5565b92506020840135612a4981612dc2565b929592945050506040919091013590565b600080600060608486031215612a6f57600080fd5b8335612a7a81612de5565b9250602084013561279281612de5565b60008251612a9c818460208701612d1f565b9190910192915050565b6001600160a01b0393841681529190921660208201526001600160601b03909116604082015260600190565b6020815260008251806020840152612af1816040850160208701612d1f565b601f01601f19169190910160400192915050565b6020808252601190820152703737ba1030b71034a7232a1037bbb732b960791b604082015260600190565b6020808252602c908201527f46756e6374696f6e206d7573742062652063616c6c6564207468726f7567682060408201526b19195b1959d85d1958d85b1b60a21b606082015260800190565b6020808252602c908201527f46756e6374696f6e206d7573742062652063616c6c6564207468726f7567682060408201526b6163746976652070726f787960a01b606082015260800190565b6020808252602e908201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160408201526d191e481a5b9a5d1a585b1a5e995960921b606082015260800190565b6020808252600d908201526c1858d8d95cdcc819195b9a5959609a1b604082015260600190565b6020808252601e908201527f6e6f7420612077686974656c6973746564204e465420636f6e74726163740000604082015260600190565b60006001600160601b03808316818516808303821115612c9657612c96612d96565b01949350505050565b600082612cbc57634e487b7160e01b600052601260045260246000fd5b500490565b6000816000190483118215151615612cdb57612cdb612d96565b500290565b600082821015612cf257612cf2612d96565b500390565b60006001600160601b0383811690831681811015612d1757612d17612d96565b039392505050565b60005b83811015612d3a578181015183820152602001612d22565b83811115611fe25750506000910152565b6000600019821415612d5f57612d5f612d96565b5060010190565b600081600f0b6f7fffffffffffffffffffffffffffffff19811415612d8d57612d8d612d96565b60000392915050565b634e487b7160e01b600052601160045260246000fd5b634e487b7160e01b600052604160045260246000fd5b6001600160a01b0381168114610cd457600080fd5b8015158114610cd457600080fd5b6001600160601b0381168114610cd457600080fdfe416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a2646970667358221220b211463bf7bfd4076cfc901b4eb0862bba32ac054ac70cde25cb304defe8b3dd64736f6c63430008070033

Deployed Bytecode

0x60806040526004361061023b5760003560e01c8063aaf10f421161012e578063cfd9dadf116100ab578063e023fd421161006f578063e023fd4214610702578063eb31b05b14610722578063f1e023fd14610739578063f822d5aa14610759578063fcc2c0781461077957600080fd5b8063cfd9dadf14610653578063d542d5dc1461066a578063d5bb7f67146106a6578063d79c85e5146106c6578063de53fcd5146106db57600080fd5b8063c4257421116100f2578063c4257421146105ad578063c6179e58146105c2578063c688d693146105e2578063c960651b14610602578063cf1c28321461061757600080fd5b8063aaf10f4214610528578063ae5b102e1461053d578063ae60bda41461055d578063ae682e2e14610575578063b66c72ac1461058d57600080fd5b80635fa2ad4a116101bc57806383df7ad51161018057806383df7ad5146104935780638d936c1e146104b35780639cf1bc92146104d3578063a1c6ef93146104e8578063a841ee281461050857600080fd5b80635fa2ad4a146103eb57806361b8ce8c1461040b578063725f36261461042157806374d5e1001461045157806378a9e4041461047e57600080fd5b806327d5b0aa1161020357806327d5b0aa1461032a5780632b5214161461034a5780633659cfe6146103765780633e0f0fa4146103965780634f1ef286146103d857600080fd5b806301889611146102405780630b6f78131461027d5780630cf785351461029f5780630dc3d284146102c65780631ca4f8ee146102fe575b600080fd5b34801561024c57600080fd5b50609a54610260906001600160601b031681565b6040516001600160601b0390911681526020015b60405180910390f35b34801561028957600080fd5b5061029d6102983660046127ad565b610799565b005b3480156102ab57600080fd5b5060995461026090600160a01b90046001600160601b031681565b3480156102d257600080fd5b506099546102e6906001600160a01b031681565b6040516001600160a01b039091168152602001610274565b34801561030a57600080fd5b506103156204000081565b60405163ffffffff9091168152602001610274565b34801561033657600080fd5b5061029d6103453660046128b1565b610991565b34801561035657600080fd5b50306000908152606560205260409020545b604051908152602001610274565b34801561038257600080fd5b5061029d610391366004612728565b610c0e565b3480156103a257600080fd5b506103c66103b1366004612728565b609c6020526000908152604090205460ff1681565b60405160ff9091168152602001610274565b61029d6103e63660046127ed565b610cd7565b3480156103f757600080fd5b5061029d610406366004612a5a565b610d91565b34801561041757600080fd5b50610368609b5481565b34801561042d57600080fd5b5061044161043c36600461299a565b610f96565b6040519015158152602001610274565b34801561045d57600080fd5b5061036861046c366004612728565b60656020526000908152604090205481565b34801561048a57600080fd5b50610315600881565b34801561049f57600080fd5b506097546102e6906001600160a01b031681565b3480156104bf57600080fd5b5061029d6104ce366004612a19565b610fb1565b3480156104df57600080fd5b50610315600181565b3480156104f457600080fd5b5061029d61050336600461299a565b611443565b34801561051457600080fd5b5061029d6105233660046129cc565b6114f1565b34801561053457600080fd5b506102e66117a1565b34801561054957600080fd5b5061029d6105583660046128b1565b6117b0565b34801561056957600080fd5b50610368600160fe1b81565b34801561058157600080fd5b50610368600160ff1b81565b34801561059957600080fd5b5061029d6105a83660046129cc565b611863565b3480156105b957600080fd5b50610315601081565b3480156105ce57600080fd5b506098546102e6906001600160a01b031681565b3480156105ee57600080fd5b506104416105fd3660046128b1565b611ba7565b34801561060e57600080fd5b50610315600281565b34801561062357600080fd5b50610441610632366004612728565b6001600160a01b03166000908152609c602052604090205460029081161490565b34801561065f57600080fd5b506103156201000081565b34801561067657600080fd5b50610441610685366004612728565b6001600160a01b03166000908152609c602052604090205460019081161490565b3480156106b257600080fd5b5061029d6106c136600461299a565b611bcc565b3480156106d257600080fd5b50610315600481565b3480156106e757600080fd5b50609a546102e690600160601b90046001600160a01b031681565b34801561070e57600080fd5b5061029d61071d366004612762565b611bd6565b34801561072e57600080fd5b506103156202000081565b34801561074557600080fd5b5061029d61075436600461299a565b611fe8565b34801561076557600080fd5b506103686107743660046128dd565b61223a565b34801561078557600080fd5b5061044161079436600461299a565b612265565b6107a562040000612265565b6107ca5760405162461bcd60e51b81526004016107c190612c16565b60405180910390fd5b6001600160a01b03831661080f5760405162461bcd60e51b815260206004820152600c60248201526b7a65726f206164647265737360a01b60448201526064016107c1565b81156108df576040516301ffc9a760e01b81526380ac58cd60e01b60048201526001600160a01b038416906301ffc9a79060240160206040518083038186803b15801561085b57600080fd5b505afa15801561086f573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610893919061297d565b6108df5760405162461bcd60e51b815260206004820152601860248201527f746172676574204e4654206973206e6f7420455243373231000000000000000060448201526064016107c1565b6000816108ed5760006108f0565b60025b836108fc5760006108ff565b60015b6001600160a01b0386166000818152609c602090815260409182902054825160ff918216815294909517948516908401529293509133917f3a3ca66be8fde14aec70663d5c198392dc74a3ad641905f583393d5415178d2f910160405180910390a36001600160a01b03939093166000908152609c60205260409020805460ff191660ff909416939093179092555050565b61099b6002610f96565b6109df5760405162461bcd60e51b81526020600482015260156024820152741d5b9b1a5b9ada5b99c81a5cc8191a5cd8589b1959605a1b60448201526064016107c1565b6099546040516331a9108f60e11b8152600481018390526001600160a01b0391821691339190851690636352211e9060240160206040518083038186803b158015610a2957600080fd5b505afa158015610a3d573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610a619190612745565b6001600160a01b031614610aaa5760405162461bcd60e51b815260206004820152601060248201526f3737ba1030b71027232a1037bbb732b960811b60448201526064016107c1565b6040516303366f7f60e21b81526001600160a01b0384811660048301526024820184905260009190831690630cd9bdfc9060440160206040518083038186803b158015610af657600080fd5b505afa158015610b0a573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610b2e91906129b3565b9050610b55846001600160a01b03166000908152609c602052604090205460029081161490565b80610b655750610b656004610f96565b610b815760405162461bcd60e51b81526004016107c190612c3d565b604051630852cd8d60e31b8152600481018290526001600160a01b038316906342966c6890602401600060405180830381600087803b158015610bc357600080fd5b505af1158015610bd7573d6000803e3d6000fd5b50506040518392503391507fdfa02adc9cf1364277c3c57daa66f9e9d90d54e6816235d64c77f3fce73f17be90600090a350505050565b306001600160a01b037f0000000000000000000000008dcc656fdb71ffc9813f809fdbf4f29c1ced9f85161415610c575760405162461bcd60e51b81526004016107c190612b30565b7f0000000000000000000000008dcc656fdb71ffc9813f809fdbf4f29c1ced9f856001600160a01b0316610c89612271565b6001600160a01b031614610caf5760405162461bcd60e51b81526004016107c190612b7c565b610cb88161229f565b60408051600080825260208201909252610cd4918391906122c8565b50565b306001600160a01b037f0000000000000000000000008dcc656fdb71ffc9813f809fdbf4f29c1ced9f85161415610d205760405162461bcd60e51b81526004016107c190612b30565b7f0000000000000000000000008dcc656fdb71ffc9813f809fdbf4f29c1ced9f856001600160a01b0316610d52612271565b6001600160a01b031614610d785760405162461bcd60e51b81526004016107c190612b7c565b610d818261229f565b610d8d828260016122c8565b5050565b610d9d62010000612265565b610db95760405162461bcd60e51b81526004016107c190612c16565b6001600160601b0383161580610ddd575064e8d4a51000836001600160601b031610155b610e195760405162461bcd60e51b815260206004820152600d60248201526c696e76616c696420707269636560981b60448201526064016107c1565b6001600160601b038216158015610e3757506001600160a01b038116155b80610e63575064e8d4a51000826001600160601b031610158015610e6357506001600160a01b03811615155b610eaf5760405162461bcd60e51b815260206004820152601c60248201527f696e76616c6964206c696e6b696e67206665652f74726561737572790000000060448201526064016107c1565b826001600160601b0316826001600160601b03161115610f1b5760405162461bcd60e51b815260206004820152602160248201527f6c696e6b696e67206665652065786365656473206c696e6b696e6720707269636044820152606560f81b60648201526084016107c1565b609980546001600160601b03858116600160a01b81026001600160a01b0393841617909355908316600160601b8102918516918217609a556040805193845260208401929092529133917f5f0d10b02f0b30044bc718c9f66f21a6bdfd73fd50346fa8bdeeab9c20fb844391015b60405180910390a3505050565b30600090815260656020526040812054821682145b92915050565b610fbb6001610f96565b610ffd5760405162461bcd60e51b81526020600482015260136024820152721b1a5b9ada5b99c81a5cc8191a5cd8589b1959606a1b60448201526064016107c1565b6098546040516331a9108f60e11b81526001600160601b038516600482015233916001600160a01b031690636352211e9060240160206040518083038186803b15801561104957600080fd5b505afa15801561105d573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906110819190612745565b6001600160a01b0316146110a75760405162461bcd60e51b81526004016107c190612c16565b6001600160a01b0382166000908152609c6020526040902054600190811614806110d657506110d66004610f96565b6110f25760405162461bcd60e51b81526004016107c190612c3d565b609a546001600160601b03161561119d57609754609a546040516323b872dd60e01b81526001600160a01b03928316926323b872dd92611149923392600160601b830416916001600160601b031690600401612aa6565b602060405180830381600087803b15801561116357600080fd5b505af1158015611177573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061119b919061297d565b505b609954600160a01b90046001600160601b03161561126657609754609954609a546001600160a01b03928316926323b872dd923392918116916111f4916001600160601b0391821691600160a01b90910416612cf7565b6040518463ffffffff1660e01b815260040161121293929190612aa6565b602060405180830381600087803b15801561122c57600080fd5b505af1158015611240573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611264919061297d565b505b6098546099546040516323b872dd60e01b81526001600160a01b03928316926323b872dd9261129f923392909116908890600401612aa6565b600060405180830381600087803b1580156112b957600080fd5b505af11580156112cd573d6000803e3d6000fd5b5050609954609b80546001600160a01b039092169350632c8d3fa3925060006112f583612d4b565b90915550609a5460995461131c916001600160601b0390811691600160a01b900416612cf7565b60985460405160e085901b6001600160e01b031916815260048101939093526001600160601b0391821660248401526001600160a01b03908116604484015290871660648301528516608482015260a4810184905260c401600060405180830381600087803b15801561138e57600080fd5b505af11580156113a2573d6000803e3d6000fd5b5050609854609b546001600160601b03871693506001600160a01b03909116915033907f4c5f6243e66f868e375120e87ec9c0e34ad78379d66dca7921055094b6a7eacd906113f390600190612ce0565b609954609a5460408051938452600160a01b9092046001600160601b03908116602085015216908201526001600160a01b03871660608201526080810186905260a00160405180910390a4505050565b61144f62020000612265565b61146b5760405162461bcd60e51b81526004016107c190612c16565b63ffffffff81116114ae5760405162461bcd60e51b815260206004820152600d60248201526c76616c756520746f6f206c6f7760981b60448201526064016107c1565b609b54604080519182526020820183905233917f9e66ea12c52a59bf2f0b1a9e2552dc42a46982c7a2c17c098b82166866dd8450910160405180910390a2609b55565b6114fb6010610f96565b6115475760405162461bcd60e51b815260206004820152601860248201527f7769746864726177616c73206172652064697361626c6564000000000000000060448201526064016107c1565b6099546040516331a9108f60e11b8152600481018490526001600160a01b039091169033908290636352211e9060240160206040518083038186803b15801561158f57600080fd5b505afa1580156115a3573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906115c79190612745565b6001600160a01b0316146115ed5760405162461bcd60e51b81526004016107c190612b05565b60995461160a90600160a01b90046001600160601b031683612c74565b6001600160601b0316816001600160a01b031663d12b5320856040518263ffffffff1660e01b815260040161164191815260200190565b60206040518083038186803b15801561165957600080fd5b505afa15801561166d573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061169191906129fc565b6001600160601b031610156116da5760405162461bcd60e51b815260206004820152600f60248201526e6465706f73697420746f6f206c6f7760881b60448201526064016107c1565b604051634cc7f53f60e01b8152600481018490526001600160601b03831660248201523360448201526001600160a01b03821690634cc7f53f90606401600060405180830381600087803b15801561173157600080fd5b505af1158015611745573d6000803e3d6000fd5b5050505082336001600160a01b03167f493ef55f20ebab6167ee6aa104d68707ac6d1af20254df7b68d53e081c62973a846001600160601b031661178890612d66565b60408051600f9290920b82526000602083015201610f89565b60006117ab612271565b905090565b6117bd600160ff1b612265565b6117d95760405162461bcd60e51b81526004016107c190612c16565b6001600160a01b0382166000908152606560205260409020546117fe9033908361223a565b6001600160a01b0383166000818152606560205260409081902083905551909133917f5a10526456f5116c0b7b80582c217d666243fd51b6a2d92c8011e601c2462e5f9161185791869190918252602082015260400190565b60405180910390a35050565b61186d6008610f96565b6118b15760405162461bcd60e51b815260206004820152601560248201527419195c1bdcda5d1cc8185c9948191a5cd8589b1959605a1b60448201526064016107c1565b6099546040516331a9108f60e11b8152600481018490526001600160a01b039091169033908290636352211e9060240160206040518083038186803b1580156118f957600080fd5b505afa15801561190d573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906119319190612745565b6001600160a01b0316146119575760405162461bcd60e51b81526004016107c190612b05565b6099546000908390600160a01b90046001600160601b0316158015906119875750609a546001600160601b031615155b15611a6157609954609a546001600160601b03600160a01b9092048216916119b3918116908416612cc1565b6119bd9190612c9f565b91506119c98285612cf7565b609754609a546040516323b872dd60e01b81529293506001600160a01b03918216926323b872dd92611a0d923392600160601b909104909116908790600401612aa6565b602060405180830381600087803b158015611a2757600080fd5b505af1158015611a3b573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611a5f919061297d565b505b6097546099546040516323b872dd60e01b81526001600160a01b03928316926323b872dd92611a9a923392909116908690600401612aa6565b602060405180830381600087803b158015611ab457600080fd5b505af1158015611ac8573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611aec919061297d565b50604051634a35dcff60e01b8152600481018690526001600160601b03821660248201526001600160a01b03841690634a35dcff90604401600060405180830381600087803b158015611b3e57600080fd5b505af1158015611b52573d6000803e3d6000fd5b5050604080516001600160601b03808616600f0b8252861660208201528893503392507f493ef55f20ebab6167ee6aa104d68707ac6d1af20254df7b68d53e081c62973a910160405180910390a35050505050565b6001600160a01b038216600090815260656020526040812054821682145b9392505050565b610cd430826117b0565b600054610100900460ff16611bf15760005460ff1615611bf5565b303b155b611c115760405162461bcd60e51b81526004016107c190612bc8565b600054610100900460ff16158015611c33576000805461ffff19166101011790555b6001600160a01b038416611c895760405162461bcd60e51b815260206004820152601960248201527f414c4920546f6b656e2061646472206973206e6f74207365740000000000000060448201526064016107c1565b6001600160a01b038316611cdf5760405162461bcd60e51b815260206004820152601e60248201527f414920506572736f6e616c6974792061646472206973206e6f7420736574000060448201526064016107c1565b6001600160a01b038216611d2c5760405162461bcd60e51b81526020600482015260146024820152731a539195081859191c881a5cc81b9bdd081cd95d60621b60448201526064016107c1565b6040516301ffc9a760e01b81526336372b0760e01b60048201526001600160a01b038516906301ffc9a79060240160206040518083038186803b158015611d7257600080fd5b505afa158015611d86573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611daa919061297d565b611df65760405162461bcd60e51b815260206004820152601960248201527f756e657870656374656420414c4920546f6b656e20747970650000000000000060448201526064016107c1565b6040516301ffc9a760e01b81526380ac58cd60e01b60048201526001600160a01b038416906301ffc9a79060240160206040518083038186803b158015611e3c57600080fd5b505afa158015611e50573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611e74919061297d565b611ec05760405162461bcd60e51b815260206004820152601e60248201527f756e657870656374656420414920506572736f6e616c6974792074797065000060448201526064016107c1565b6040516301ffc9a760e01b8152636f4fb12560e01b60048201526001600160a01b038316906301ffc9a79060240160206040518083038186803b158015611f0657600080fd5b505afa158015611f1a573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611f3e919061297d565b611f815760405162461bcd60e51b8152602060048201526014602482015273756e657870656374656420694e4654207479706560601b60448201526064016107c1565b609780546001600160a01b038087166001600160a01b031992831617909255609880548684169083161790556099805492851692909116919091179055640200000000609b55611fd033612413565b8015611fe2576000805461ff00191690555b50505050565b611ff26002610f96565b6120365760405162461bcd60e51b81526020600482015260156024820152741d5b9b1a5b9ada5b99c81a5cc8191a5cd8589b1959605a1b60448201526064016107c1565b6099546040516399a2241f60e01b8152600481018390526001600160a01b039091169060009082906399a2241f9060240160a06040518083038186803b15801561207f57600080fd5b505afa158015612093573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906120b79190612912565b5093505050506120e2816001600160a01b03166000908152609c602052604090205460029081161490565b806120f257506120f26004610f96565b61210e5760405162461bcd60e51b81526004016107c190612c3d565b6040516331a9108f60e11b81526004810184905233906001600160a01b03841690636352211e9060240160206040518083038186803b15801561215057600080fd5b505afa158015612164573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906121889190612745565b6001600160a01b0316146121ae5760405162461bcd60e51b81526004016107c190612b05565b604051630852cd8d60e31b8152600481018490526001600160a01b038316906342966c6890602401600060405180830381600087803b1580156121f057600080fd5b505af1158015612204573d6000803e3d6000fd5b50506040518592503391507fdfa02adc9cf1364277c3c57daa66f9e9d90d54e6816235d64c77f3fce73f17be90600090a3505050565b6001600160a01b03929092166000908152606560205260409020546000198084188216189216171690565b6000610fab3383611ba7565b7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc546001600160a01b031690565b6122ac600160fe1b612265565b610cd45760405162461bcd60e51b81526004016107c190612c16565b60006122d2612271565b90506122dd8461251f565b6000835111806122ea5750815b156122fb576122f984846125c4565b505b7f4910fdfa16fed3260ed0e7147f7cc6da11a60208b5b9406d12a635614ffd9143805460ff1661240c57805460ff191660011781556040516001600160a01b038316602482015261237a90869060440160408051601f198184030181529190526020810180516001600160e01b0316631b2ce7f360e11b1790526125c4565b50805460ff1916815561238b612271565b6001600160a01b0316826001600160a01b0316146124035760405162461bcd60e51b815260206004820152602f60248201527f45524331393637557067726164653a207570677261646520627265616b73206660448201526e75727468657220757067726164657360881b60648201526084016107c1565b61240c856126af565b5050505050565b600054610100900460ff1661242e5760005460ff1615612432565b303b155b61244e5760405162461bcd60e51b81526004016107c190612bc8565b600054610100900460ff16158015612470576000805461ffff19166101011790555b303b156124b15760405162461bcd60e51b815260206004820152600f60248201526e1a5b9d985b1a590818dbdb9d195e1d608a1b60448201526064016107c1565b6001600160a01b0382166000818152606560209081526040918290206000199081905582518181529182015233917f5a10526456f5116c0b7b80582c217d666243fd51b6a2d92c8011e601c2462e5f910160405180910390a38015610d8d576000805461ff00191690555050565b803b6125835760405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b60648201526084016107c1565b7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc80546001600160a01b0319166001600160a01b0392909216919091179055565b6060823b6126235760405162461bcd60e51b815260206004820152602660248201527f416464726573733a2064656c65676174652063616c6c20746f206e6f6e2d636f6044820152651b9d1c9858dd60d21b60648201526084016107c1565b600080846001600160a01b03168460405161263e9190612a8a565b600060405180830381855af49150503d8060008114612679576040519150601f19603f3d011682016040523d82523d6000602084013e61267e565b606091505b50915091506126a68282604051806060016040528060278152602001612dfb602791396126ef565b95945050505050565b6126b88161251f565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b90600090a250565b606083156126fe575081611bc5565b82511561270e5782518084602001fd5b8160405162461bcd60e51b81526004016107c19190612ad2565b60006020828403121561273a57600080fd5b8135611bc581612dc2565b60006020828403121561275757600080fd5b8151611bc581612dc2565b60008060006060848603121561277757600080fd5b833561278281612dc2565b9250602084013561279281612dc2565b915060408401356127a281612dc2565b809150509250925092565b6000806000606084860312156127c257600080fd5b83356127cd81612dc2565b925060208401356127dd81612dd7565b915060408401356127a281612dd7565b6000806040838503121561280057600080fd5b823561280b81612dc2565b9150602083013567ffffffffffffffff8082111561282857600080fd5b818501915085601f83011261283c57600080fd5b81358181111561284e5761284e612dac565b604051601f8201601f19908116603f0116810190838211818310171561287657612876612dac565b8160405282815288602084870101111561288f57600080fd5b8260208601602083013760006020848301015280955050505050509250929050565b600080604083850312156128c457600080fd5b82356128cf81612dc2565b946020939093013593505050565b6000806000606084860312156128f257600080fd5b83356128fd81612dc2565b95602085013595506040909401359392505050565b600080600080600060a0868803121561292a57600080fd5b855161293581612dc2565b602087015190955061294681612de5565b604087015190945061295781612de5565b606087015190935061296881612dc2565b80925050608086015190509295509295909350565b60006020828403121561298f57600080fd5b8151611bc581612dd7565b6000602082840312156129ac57600080fd5b5035919050565b6000602082840312156129c557600080fd5b5051919050565b600080604083850312156129df57600080fd5b8235915060208301356129f181612de5565b809150509250929050565b600060208284031215612a0e57600080fd5b8151611bc581612de5565b600080600060608486031215612a2e57600080fd5b8335612a3981612de5565b92506020840135612a4981612dc2565b929592945050506040919091013590565b600080600060608486031215612a6f57600080fd5b8335612a7a81612de5565b9250602084013561279281612de5565b60008251612a9c818460208701612d1f565b9190910192915050565b6001600160a01b0393841681529190921660208201526001600160601b03909116604082015260600190565b6020815260008251806020840152612af1816040850160208701612d1f565b601f01601f19169190910160400192915050565b6020808252601190820152703737ba1030b71034a7232a1037bbb732b960791b604082015260600190565b6020808252602c908201527f46756e6374696f6e206d7573742062652063616c6c6564207468726f7567682060408201526b19195b1959d85d1958d85b1b60a21b606082015260800190565b6020808252602c908201527f46756e6374696f6e206d7573742062652063616c6c6564207468726f7567682060408201526b6163746976652070726f787960a01b606082015260800190565b6020808252602e908201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160408201526d191e481a5b9a5d1a585b1a5e995960921b606082015260800190565b6020808252600d908201526c1858d8d95cdcc819195b9a5959609a1b604082015260600190565b6020808252601e908201527f6e6f7420612077686974656c6973746564204e465420636f6e74726163740000604082015260600190565b60006001600160601b03808316818516808303821115612c9657612c96612d96565b01949350505050565b600082612cbc57634e487b7160e01b600052601260045260246000fd5b500490565b6000816000190483118215151615612cdb57612cdb612d96565b500290565b600082821015612cf257612cf2612d96565b500390565b60006001600160601b0383811690831681811015612d1757612d17612d96565b039392505050565b60005b83811015612d3a578181015183820152602001612d22565b83811115611fe25750506000910152565b6000600019821415612d5f57612d5f612d96565b5060010190565b600081600f0b6f7fffffffffffffffffffffffffffffff19811415612d8d57612d8d612d96565b60000392915050565b634e487b7160e01b600052601160045260246000fd5b634e487b7160e01b600052604160045260246000fd5b6001600160a01b0381168114610cd457600080fd5b8015158114610cd457600080fd5b6001600160601b0381168114610cd457600080fdfe416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a2646970667358221220b211463bf7bfd4076cfc901b4eb0862bba32ac054ac70cde25cb304defe8b3dd64736f6c63430008070033

Block Transaction Difficulty Gas Used Reward
View All Blocks Produced

Block Uncle Number Difficulty Gas Used Reward
View All Uncles
Loading...
Loading
Loading...
Loading

Validator Index Block Amount
View All Withdrawals

Transaction Hash Block Value Eth2 PubKey Valid
View All Deposits
Loading...
Loading

A contract address hosts a smart contract, which is a set of code stored on the blockchain that runs when predetermined conditions are met. Learn more about addresses in our Knowledge Base.