ETH Price: $2,517.11 (-0.15%)

Transaction Decoder

Block:
11510147 at Dec-23-2020 01:38:27 PM +UTC
Transaction Fee:
0.02000602113587514 ETH $50.36
Gas Used:
209,268 Gas / 95.600001605 Gwei

Emitted Events:

211 GenArt721Core.Transfer( from=0xe7925D19...B9389Cc2b, to=0x80acb3b55132c4fe408f8bcda583aaf98824e206, tokenId=5000060 )
212 0x55945d98a5d37552f1f8163262c275a8512bc20f.0x30d0b1544cabbf52b74d6df1eb5af510e230111d857b36e36cb89a29766419d2( 0x30d0b1544cabbf52b74d6df1eb5af510e230111d857b36e36cb89a29766419d2, 0x00000000000000000000000081bb11c41efc6cbf5710b2d6d47a7b43a9c28df8, 0x000000000000000000000000a7d8d9ef8d8ce8992df33d8b8cf4aebabd5bd270, 0000000000000000000000000000000000000000000000000005af3107a40000, 00000000000000000000000000000000000000000000000000000000004c4b7c, 00000000000000000000000080acb3b55132c4fe408f8bcda583aaf98824e206 )

Account State Difference:

  Address   Before After State Difference Code
(zhizhu.top)
1,761.098111214464402054 Eth1,761.118117235600277194 Eth0.02000602113587514
0x2C5641c0...A5a53Cea1
1.415857089879860481 Eth
Nonce: 2661
1.405851068743985341 Eth
Nonce: 2662
0.01000602113587514
0x55945d98...8512bc20f 0.7888 Eth0.7772 Eth0.0116
0x80Acb3b5...98824e206
0 Eth
Nonce: 0
0.0016 Eth
Nonce: 0
0.0016From: 0 To: 0
0xa7d8d9ef...abd5bD270

Execution Trace

LinkdropFactory.claimERC721( _weiAmount=1600000000000000, _nftAddress=0xa7d8d9ef8D8Ce8992Df33D8b8CF4Aebabd5bD270, _tokenId=5000060, _expiration=1900000000000, _linkId=0x81BB11C41Efc6cbf5710B2d6D47a7b43a9C28Df8, _linkdropMaster=0xe7925D190aea9279400cD9a005E33CEB9389Cc2b, _campaignId=1, _linkdropSignerSignature=0x04252B5E3C57A1F57D418732C87FF6DBC93147DE3D2403F3A878F1BDAFB29003734B1160C0ED53011D97206BBFC537FBF5615F881797EAEDEEA35CFBB2B5BB581B, _receiver=0x80Acb3b55132C4fe408f8BCDa583AAF98824e206, _receiverSignature=0xD33155B65C278F375B703D5B23EA7FCFBA7DB2C51D337BECFA248DCA3C0B4B471C9B0F183C4BC4FB8DD4B956859C846D17FC6A8ED6A9962DBE86C7BCF003B97D1B ) => ( True )
  • 0x55945d98a5d37552f1f8163262c275a8512bc20f.db7b363c( )
    • 0xc737976bf98acedb0e4211813cf98cb6de85e304.db7b363c( )
      • GenArt721Core.ownerOf( tokenId=5000060 ) => ( 0xe7925D190aea9279400cD9a005E33CEB9389Cc2b )
      • Null: 0x000...001.03ebd617( )
      • Null: 0x000...001.ac9b7922( )
      • ETH 0.01 0x2c5641c0075b7e9d25c5f597b4d80b7a5a53cea1.CALL( )
      • ETH 0.0016 0x80acb3b55132c4fe408f8bcda583aaf98824e206.CALL( )
      • GenArt721Core.transferFrom( from=0xe7925D190aea9279400cD9a005E33CEB9389Cc2b, to=0x80Acb3b55132C4fe408f8BCDa583AAF98824e206, tokenId=5000060 )
        File 1 of 2: LinkdropFactory
        pragma solidity ^0.5.6;
        
        interface ILinkdropERC20 {
        
            function verifyLinkdropSignerSignature
            (
                uint _weiAmount,
                address _tokenAddress,
                uint _tokenAmount,
                uint _expiration,
                address _linkId,
                bytes calldata _signature
            )
            external view returns (bool);
        
            function verifyReceiverSignature
            (
                address _linkId,
        	    address _receiver,
        		bytes calldata _signature
            )
            external view returns (bool);
        
            function checkClaimParams
            (
                uint _weiAmount,
                address _tokenAddress,
                uint _tokenAmount,
                uint _expiration,
                address _linkId,
                bytes calldata _linkdropSignerSignature,
                address _receiver,
                bytes calldata _receiverSignature,
                uint _fee
            )
            external view returns (bool);
        
            function claim
            (
                uint _weiAmount,
                address _tokenAddress,
                uint _tokenAmount,
                uint _expiration,
                address _linkId,
                bytes calldata _linkdropSignerSignature,
                address payable _receiver,
                bytes calldata _receiverSignature,
                address payable _feeReceiver,
                uint _fee
            )
            external returns (bool);
        
        }
        
        interface ILinkdropFactoryERC20 {
        
            function checkClaimParams
            (
                uint _weiAmount,
                address _tokenAddress,
                uint _tokenAmount,
                uint _expiration,
                address _linkId,
                address payable _linkdropMaster,
                uint _campaignId,
                bytes calldata _linkdropSignerSignature,
                address _receiver,
                bytes calldata _receiverSignature
            )
            external view
            returns (bool);
        
            function claim
            (
                uint _weiAmount,
                address _tokenAddress,
                uint _tokenAmount,
                uint _expiration,
                address _linkId,
                address payable _linkdropMaster,
                uint _campaignId,
                bytes calldata _linkdropSignerSignature,
                address payable _receiver,
                bytes calldata _receiverSignature
            )
            external
            returns (bool);
        
        }
        
        /**
         * @dev Interface of the ERC20 standard as defined in the EIP. Does not include
         * the optional functions; to access them see `ERC20Detailed`.
         */
        interface IERC20 {
            /**
             * @dev Returns the amount of tokens in existence.
             */
            function totalSupply() external view returns (uint256);
        
            /**
             * @dev Returns the amount of tokens owned by `account`.
             */
            function balanceOf(address account) external view returns (uint256);
        
            /**
             * @dev Moves `amount` tokens from the caller's account to `recipient`.
             *
             * Returns a boolean value indicating whether the operation succeeded.
             *
             * Emits a `Transfer` event.
             */
            function transfer(address recipient, uint256 amount) external returns (bool);
        
            /**
             * @dev Returns the remaining number of tokens that `spender` will be
             * allowed to spend on behalf of `owner` through `transferFrom`. This is
             * zero by default.
             *
             * This value changes when `approve` or `transferFrom` are called.
             */
            function allowance(address owner, address spender) external view returns (uint256);
        
            /**
             * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
             *
             * Returns a boolean value indicating whether the operation succeeded.
             *
             * > Beware that changing an allowance with this method brings the risk
             * that someone may use both the old and the new allowance by unfortunate
             * transaction ordering. One possible solution to mitigate this race
             * condition is to first reduce the spender's allowance to 0 and set the
             * desired value afterwards:
             * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
             *
             * Emits an `Approval` event.
             */
            function approve(address spender, uint256 amount) external returns (bool);
        
            /**
             * @dev Moves `amount` tokens from `sender` to `recipient` using the
             * allowance mechanism. `amount` is then deducted from the caller's
             * allowance.
             *
             * Returns a boolean value indicating whether the operation succeeded.
             *
             * Emits a `Transfer` event.
             */
            function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
        
            /**
             * @dev Emitted when `value` tokens are moved from one account (`from`) to
             * another (`to`).
             *
             * Note that `value` may be zero.
             */
            event Transfer(address indexed from, address indexed to, uint256 value);
        
            /**
             * @dev Emitted when the allowance of a `spender` for an `owner` is set by
             * a call to `approve`. `value` is the new allowance.
             */
            event Approval(address indexed owner, address indexed spender, uint256 value);
        }
        
        
        interface ILinkdropERC721 {
        
            function verifyLinkdropSignerSignatureERC721
            (
                uint _weiAmount,
                address _nftAddress,
                uint _tokenId,
                uint _expiration,
                address _linkId,
                bytes calldata _signature
            )
            external view returns (bool);
        
            function verifyReceiverSignatureERC721
            (
                address _linkId,
        	    address _receiver,
        		bytes calldata _signature
            )
            external view returns (bool);
        
            function checkClaimParamsERC721
            (
                uint _weiAmount,
                address _nftAddress,
                uint _tokenId,
                uint _expiration,
                address _linkId,
                bytes calldata _linkdropSignerSignature,
                address _receiver,
                bytes calldata _receiverSignature,
                uint _fee
            )
            external view returns (bool);
        
            function claimERC721
            (
                uint _weiAmount,
                address _nftAddress,
                uint _tokenId,
                uint _expiration,
                address _linkId,
                bytes calldata _linkdropSignerSignature,
                address payable _receiver,
                bytes calldata _receiverSignature,
                address payable _feeReceiver,
                uint _fee
            )
            external returns (bool);
        
        }
        
        
        interface ILinkdropFactoryERC721 {
        
            function checkClaimParamsERC721
            (
                uint _weiAmount,
                address _nftAddress,
                uint _tokenId,
                uint _expiration,
                address _linkId,
                address payable _linkdropMaster,
                uint _campaignId,
                bytes calldata _linkdropSignerSignature,
                address _receiver,
                bytes calldata _receiverSignature
            )
            external view
            returns (bool);
        
            function claimERC721
            (
                uint _weiAmount,
                address _nftAddress,
                uint _tokenId,
                uint _expiration,
                address _linkId,
                address payable _linkdropMaster,
                uint _campaignId,
                bytes calldata _linkdropSignerSignature,
                address payable _receiver,
                bytes calldata _receiverSignature
            )
            external
            returns (bool);
        
        }
        
        interface ILinkdropCommon {
        
            function initialize
            (
                address _owner,
                address payable _linkdropMaster,
                uint _version,
                uint _chainId
            )
            external returns (bool);
        
            function isClaimedLink(address _linkId) external view returns (bool);
            function isCanceledLink(address _linkId) external view returns (bool);
            function paused() external view returns (bool);
            function cancel(address _linkId) external  returns (bool);
            function withdraw() external returns (bool);
            function pause() external returns (bool);
            function unpause() external returns (bool);
            function addSigner(address _linkdropSigner) external payable returns (bool);
            function removeSigner(address _linkdropSigner) external returns (bool);
            function destroy() external;
            function getMasterCopyVersion() external view returns (uint);
            function () external payable;
        
        }
        
        /**
         * @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations.
         *
         * These functions can be used to verify that a message was signed by the holder
         * of the private keys of a given address.
         */
        library ECDSA {
            /**
             * @dev Returns the address that signed a hashed message (`hash`) with
             * `signature`. This address can then be used for verification purposes.
             *
             * The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:
             * this function rejects them by requiring the `s` value to be in the lower
             * half order, and the `v` value to be either 27 or 28.
             *
             * (.note) This call _does not revert_ if the signature is invalid, or
             * if the signer is otherwise unable to be retrieved. In those scenarios,
             * the zero address is returned.
             *
             * (.warning) `hash` _must_ be the result of a hash operation for the
             * verification to be secure: it is possible to craft signatures that
             * recover to arbitrary addresses for non-hashed data. A safe way to ensure
             * this is by receiving a hash of the original message (which may otherwise)
             * be too long), and then calling `toEthSignedMessageHash` on it.
             */
            function recover(bytes32 hash, bytes memory signature) internal pure returns (address) {
                // Check the signature length
                if (signature.length != 65) {
                    return (address(0));
                }
        
                // Divide the signature in r, s and v variables
                bytes32 r;
                bytes32 s;
                uint8 v;
        
                // ecrecover takes the signature parameters, and the only way to get them
                // currently is to use assembly.
                // solhint-disable-next-line no-inline-assembly
                assembly {
                    r := mload(add(signature, 0x20))
                    s := mload(add(signature, 0x40))
                    v := byte(0, mload(add(signature, 0x60)))
                }
        
                // EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature
                // unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines
                // the valid range for s in (281): 0 < s < secp256k1n ÷ 2 + 1, and for v in (282): v ∈ {27, 28}. Most
                // signatures from current libraries generate a unique signature with an s-value in the lower half order.
                //
                // If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value
                // with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or
                // vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept
                // these malleable signatures as well.
                if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) {
                    return address(0);
                }
        
                if (v != 27 && v != 28) {
                    return address(0);
                }
        
                // If the signature is valid (and not malleable), return the signer address
                return ecrecover(hash, v, r, s);
            }
        
            /**
             * @dev Returns an Ethereum Signed Message, created from a `hash`. This
             * replicates the behavior of the
             * [`eth_sign`](https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_sign)
             * JSON-RPC method.
             *
             * See `recover`.
             */
            function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32) {
                // 32 is the length in bytes of hash,
                // enforced by the type signature above
                return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", hash));
            }
        }
        
        
        /**
         * @dev Wrappers over Solidity's arithmetic operations with added overflow
         * checks.
         *
         * Arithmetic operations in Solidity wrap on overflow. This can easily result
         * in bugs, because programmers usually assume that an overflow raises an
         * error, which is the standard behavior in high level programming languages.
         * `SafeMath` restores this intuition by reverting the transaction when an
         * operation overflows.
         *
         * Using this library instead of the unchecked operations eliminates an entire
         * class of bugs, so it's recommended to use it always.
         */
        library SafeMath {
            /**
             * @dev Returns the addition of two unsigned integers, reverting on
             * overflow.
             *
             * Counterpart to Solidity's `+` operator.
             *
             * Requirements:
             * - Addition cannot overflow.
             */
            function add(uint256 a, uint256 b) internal pure returns (uint256) {
                uint256 c = a + b;
                require(c >= a, "SafeMath: addition overflow");
        
                return c;
            }
        
            /**
             * @dev Returns the subtraction of two unsigned integers, reverting on
             * overflow (when the result is negative).
             *
             * Counterpart to Solidity's `-` operator.
             *
             * Requirements:
             * - Subtraction cannot overflow.
             */
            function sub(uint256 a, uint256 b) internal pure returns (uint256) {
                require(b <= a, "SafeMath: subtraction overflow");
                uint256 c = a - b;
        
                return c;
            }
        
            /**
             * @dev Returns the multiplication of two unsigned integers, reverting on
             * overflow.
             *
             * Counterpart to Solidity's `*` operator.
             *
             * Requirements:
             * - Multiplication cannot overflow.
             */
            function mul(uint256 a, uint256 b) internal pure returns (uint256) {
                // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
                // benefit is lost if 'b' is also tested.
                // See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522
                if (a == 0) {
                    return 0;
                }
        
                uint256 c = a * b;
                require(c / a == b, "SafeMath: multiplication overflow");
        
                return c;
            }
        
            /**
             * @dev Returns the integer division of two unsigned integers. Reverts on
             * division by zero. The result is rounded towards zero.
             *
             * Counterpart to Solidity's `/` operator. Note: this function uses a
             * `revert` opcode (which leaves remaining gas untouched) while Solidity
             * uses an invalid opcode to revert (consuming all remaining gas).
             *
             * Requirements:
             * - The divisor cannot be zero.
             */
            function div(uint256 a, uint256 b) internal pure returns (uint256) {
                // Solidity only automatically asserts when dividing by 0
                require(b > 0, "SafeMath: division by zero");
                uint256 c = a / b;
                // assert(a == b * c + a % b); // There is no case in which this doesn't hold
        
                return c;
            }
        
            /**
             * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
             * Reverts when dividing by zero.
             *
             * Counterpart to Solidity's `%` operator. This function uses a `revert`
             * opcode (which leaves remaining gas untouched) while Solidity uses an
             * invalid opcode to revert (consuming all remaining gas).
             *
             * Requirements:
             * - The divisor cannot be zero.
             */
            function mod(uint256 a, uint256 b) internal pure returns (uint256) {
                require(b != 0, "SafeMath: modulo by zero");
                return a % b;
            }
        }
        
        
        /**
         * @dev Interface of the ERC165 standard, as defined in the
         * [EIP](https://eips.ethereum.org/EIPS/eip-165).
         *
         * Implementers can declare support of contract interfaces, which can then be
         * queried by others (`ERC165Checker`).
         *
         * For an implementation, see `ERC165`.
         */
        interface IERC165 {
            /**
             * @dev Returns true if this contract implements the interface defined by
             * `interfaceId`. See the corresponding
             * [EIP section](https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified)
             * to learn more about how these ids are created.
             *
             * This function call must use less than 30 000 gas.
             */
            function supportsInterface(bytes4 interfaceId) external view returns (bool);
        }
        
        
        /**
         * @dev Contract module which provides a basic access control mechanism, where
         * there is an account (an owner) that can be granted exclusive access to
         * specific functions.
         *
         * This module is used through inheritance. It will make available the modifier
         * `onlyOwner`, which can be aplied to your functions to restrict their use to
         * the owner.
         */
        contract Ownable {
            address private _owner;
        
            event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
        
            /**
             * @dev Initializes the contract setting the deployer as the initial owner.
             */
            constructor () internal {
                _owner = msg.sender;
                emit OwnershipTransferred(address(0), _owner);
            }
        
            /**
             * @dev Returns the address of the current owner.
             */
            function owner() public view returns (address) {
                return _owner;
            }
        
            /**
             * @dev Throws if called by any account other than the owner.
             */
            modifier onlyOwner() {
                require(isOwner(), "Ownable: caller is not the owner");
                _;
            }
        
            /**
             * @dev Returns true if the caller is the current owner.
             */
            function isOwner() public view returns (bool) {
                return msg.sender == _owner;
            }
        
            /**
             * @dev Leaves the contract without owner. It will not be possible to call
             * `onlyOwner` functions anymore. Can only be called by the current owner.
             *
             * > Note: Renouncing ownership will leave the contract without an owner,
             * thereby removing any functionality that is only available to the owner.
             */
            function renounceOwnership() public onlyOwner {
                emit OwnershipTransferred(_owner, address(0));
                _owner = address(0);
            }
        
            /**
             * @dev Transfers ownership of the contract to a new account (`newOwner`).
             * Can only be called by the current owner.
             */
            function transferOwnership(address newOwner) public onlyOwner {
                _transferOwnership(newOwner);
            }
        
            /**
             * @dev Transfers ownership of the contract to a new account (`newOwner`).
             */
            function _transferOwnership(address newOwner) internal {
                require(newOwner != address(0), "Ownable: new owner is the zero address");
                emit OwnershipTransferred(_owner, newOwner);
                _owner = newOwner;
            }
        }
        
        
        /**
         * @dev Required interface of an ERC721 compliant contract.
         */
        contract IERC721 is IERC165 {
            event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
            event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
            event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
        
            /**
             * @dev Returns the number of NFTs in `owner`'s account.
             */
            function balanceOf(address owner) public view returns (uint256 balance);
        
            /**
             * @dev Returns the owner of the NFT specified by `tokenId`.
             */
            function ownerOf(uint256 tokenId) public view returns (address owner);
        
            /**
             * @dev Transfers a specific NFT (`tokenId`) from one account (`from`) to
             * another (`to`).
             *
             * 
             *
             * Requirements:
             * - `from`, `to` cannot be zero.
             * - `tokenId` must be owned by `from`.
             * - If the caller is not `from`, it must be have been allowed to move this
             * NFT by either `approve` or `setApproveForAll`.
             */
            function safeTransferFrom(address from, address to, uint256 tokenId) public;
            /**
             * @dev Transfers a specific NFT (`tokenId`) from one account (`from`) to
             * another (`to`).
             *
             * Requirements:
             * - If the caller is not `from`, it must be approved to move this NFT by
             * either `approve` or `setApproveForAll`.
             */
            function transferFrom(address from, address to, uint256 tokenId) public;
            function approve(address to, uint256 tokenId) public;
            function getApproved(uint256 tokenId) public view returns (address operator);
        
            function setApprovalForAll(address operator, bool _approved) public;
            function isApprovedForAll(address owner, address operator) public view returns (bool);
        
        
            function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory data) public;
        }
        
        
        contract LinkdropFactoryStorage is Ownable {
        
            // Current version of mastercopy contract
            uint public masterCopyVersion;
        
            // Contract bytecode to be installed when deploying proxy
            bytes internal _bytecode;
        
            // Bootstrap initcode to fetch the actual contract bytecode. Used to generate repeatable contract addresses
            bytes internal _initcode;
        
            // Network id
            uint public chainId;
        
            // Maps hash(sender address, campaign id) to its corresponding proxy address
            mapping (bytes32 => address) public deployed;
        
            // Events
            event Deployed(address payable indexed owner, uint campaignId, address payable proxy, bytes32 salt);
            event Destroyed(address payable owner, address payable proxy);
            event SetMasterCopy(address masterCopy, uint version);
        
        }
        
        contract FeeManager is Ownable {
        
            event FeeChanged(address proxy, uint fee);
        
            mapping (address => uint) fees;
        
            uint public standardFee = 0.002 ether;
        
            function setFee(address _proxy, uint _fee) external onlyOwner returns (bool) {
                _setFee(_proxy, _fee);
                return true;
            }
        
            function _setFee(address _proxy, uint _fee) internal {
                if (fees[_proxy] != 0) {
                    require(_fee < fees[_proxy], "CANNOT_INCREASE_FEE");
                }
                fees[_proxy] = _fee;
                emit FeeChanged(_proxy, _fee);
            }
        
            function setStandardFee(uint _fee) external onlyOwner {
                standardFee = _fee;
            }
        
        }
        
        contract RelayerManager is Ownable {
        
            mapping (address => bool) public isRelayer;
        
            event RelayerAdded(address indexed relayer);
        
            event RelayerRemoved(address indexed relayer);
        
            function addRelayer(address _relayer) external onlyOwner returns (bool) {
                require(_relayer != address(0) && !isRelayer[_relayer], "INVALID_RELAYER_ADDRESS");
                isRelayer[_relayer] = true;
                emit RelayerAdded(_relayer);
                return true;
            }
        
            function removeRelayer(address _relayer) external onlyOwner returns (bool) {
                require(isRelayer[_relayer], "INVALID_RELAYER_ADDRESS");
                isRelayer[_relayer] = false;
                emit RelayerRemoved(_relayer);
                return true;
            }
        
        }
        
        
        
        
        
        
        contract LinkdropFactoryCommon is LinkdropFactoryStorage, FeeManager, RelayerManager {
            using SafeMath for uint;
        
            /**
            * @dev Indicates whether a proxy contract for linkdrop master is deployed or not
            * @param _linkdropMaster Address of linkdrop master
            * @param _campaignId Campaign id
            * @return True if deployed
            */
            function isDeployed(address _linkdropMaster, uint _campaignId) public view returns (bool) {
                return (deployed[salt(_linkdropMaster, _campaignId)] != address(0));
            }
        
            /**
            * @dev Indicates whether a link is claimed or not
            * @param _linkdropMaster Address of lindkrop master
            * @param _campaignId Campaign id
            * @param _linkId Address corresponding to link key
            * @return True if claimed
            */
            function isClaimedLink(address payable _linkdropMaster, uint _campaignId, address _linkId) public view returns (bool) {
        
                if (!isDeployed(_linkdropMaster, _campaignId)) {
                    return false;
                }
                else {
                    address payable proxy = address(uint160(deployed[salt(_linkdropMaster, _campaignId)]));
                    return ILinkdropCommon(proxy).isClaimedLink(_linkId);
                }
        
            }
        
            /**
            * @dev Function to deploy a proxy contract for msg.sender
            * @param _campaignId Campaign id
            * @return Proxy contract address
            */
            function deployProxy(uint _campaignId)
            public
            payable
            returns (address payable proxy)
            {
                proxy = _deployProxy(msg.sender, _campaignId);
            }
        
            /**
            * @dev Function to deploy a proxy contract for msg.sender and add a new signing key
            * @param _campaignId Campaign id
            * @param _signer Address corresponding to signing key
            * @return Proxy contract address
            */
            function deployProxyWithSigner(uint _campaignId, address _signer)
            public
            payable
            returns (address payable proxy)
            {
                proxy = deployProxy(_campaignId);
                ILinkdropCommon(proxy).addSigner(_signer);
            }
        
            /**
            * @dev Internal function to deploy a proxy contract for linkdrop master
            * @param _linkdropMaster Address of linkdrop master
            * @param _campaignId Campaign id
            * @return Proxy contract address
            */
            function _deployProxy(address payable _linkdropMaster, uint _campaignId)
            internal
            returns (address payable proxy)
            {
        
                require(!isDeployed(_linkdropMaster, _campaignId), "LINKDROP_PROXY_CONTRACT_ALREADY_DEPLOYED");
                require(_linkdropMaster != address(0), "INVALID_LINKDROP_MASTER_ADDRESS");
        
                bytes32 salt = salt(_linkdropMaster, _campaignId);
                bytes memory initcode = getInitcode();
        
                assembly {
                    proxy := create2(0, add(initcode, 0x20), mload(initcode), salt)
                    if iszero(extcodesize(proxy)) { revert(0, 0) }
                }
        
                deployed[salt] = proxy;
        
                // Initialize owner address, linkdrop master address master copy version in proxy contract
                require
                (
                    ILinkdropCommon(proxy).initialize
                    (
                        address(this), // Owner address
                        _linkdropMaster, // Linkdrop master address
                        masterCopyVersion,
                        chainId
                    ),
                    "INITIALIZATION_FAILED"
                );
        
                // Send funds attached to proxy contract
                proxy.transfer(msg.value);
        
                // Set standard fee for the proxy
                _setFee(proxy, standardFee);
        
                emit Deployed(_linkdropMaster, _campaignId, proxy, salt);
                return proxy;
            }
        
            /**
            * @dev Function to destroy proxy contract, called by proxy owner
            * @param _campaignId Campaign id
            * @return True if destroyed successfully
            */
            function destroyProxy(uint _campaignId)
            public
            returns (bool)
            {
                require(isDeployed(msg.sender, _campaignId), "LINKDROP_PROXY_CONTRACT_NOT_DEPLOYED");
                address payable proxy = address(uint160(deployed[salt(msg.sender, _campaignId)]));
                ILinkdropCommon(proxy).destroy();
                delete deployed[salt(msg.sender, _campaignId)];
                delete fees[proxy];
                emit Destroyed(msg.sender, proxy);
                return true;
            }
        
            /**
            * @dev Function to get bootstrap initcode for generating repeatable contract addresses
            * @return Static bootstrap initcode
            */
            function getInitcode()
            public view
            returns (bytes memory)
            {
                return _initcode;
            }
        
            /**
            * @dev Function to fetch the actual contract bytecode to install. Called by proxy when executing initcode
            * @return Contract bytecode to install
            */
            function getBytecode()
            public view
            returns (bytes memory)
            {
                return _bytecode;
            }
        
            /**
            * @dev Function to set new master copy and update contract bytecode to install. Can only be called by factory owner
            * @param _masterCopy Address of linkdrop mastercopy contract to calculate bytecode from
            * @return True if updated successfully
            */
            function setMasterCopy(address payable _masterCopy)
            public onlyOwner
            returns (bool)
            {
                require(_masterCopy != address(0), "INVALID_MASTER_COPY_ADDRESS");
                masterCopyVersion = masterCopyVersion.add(1);
        
                require
                (
                    ILinkdropCommon(_masterCopy).initialize
                    (
                        address(0), // Owner address
                        address(0), // Linkdrop master address
                        masterCopyVersion,
                        chainId
                    ),
                    "INITIALIZATION_FAILED"
                );
        
                bytes memory bytecode = abi.encodePacked
                (
                    hex"363d3d373d3d3d363d73",
                    _masterCopy,
                    hex"5af43d82803e903d91602b57fd5bf3"
                );
        
                _bytecode = bytecode;
        
                emit SetMasterCopy(_masterCopy, masterCopyVersion);
                return true;
            }
        
            /**
            * @dev Function to fetch the master copy version installed (or to be installed) to proxy
            * @param _linkdropMaster Address of linkdrop master
            * @param _campaignId Campaign id
            * @return Master copy version
            */
            function getProxyMasterCopyVersion(address _linkdropMaster, uint _campaignId) external view returns (uint) {
        
                if (!isDeployed(_linkdropMaster, _campaignId)) {
                    return masterCopyVersion;
                }
                else {
                    address payable proxy = address(uint160(deployed[salt(_linkdropMaster, _campaignId)]));
                    return ILinkdropCommon(proxy).getMasterCopyVersion();
                }
            }
        
            /**
             * @dev Function to hash `_linkdropMaster` and `_campaignId` params. Used as salt when deploying with create2
             * @param _linkdropMaster Address of linkdrop master
             * @param _campaignId Campaign id
             * @return Hash of passed arguments
             */
            function salt(address _linkdropMaster, uint _campaignId) public pure returns (bytes32) {
                return keccak256(abi.encodePacked(_linkdropMaster, _campaignId));
            }
        
        }
        
        
        
        
        contract LinkdropFactoryERC20 is ILinkdropFactoryERC20, LinkdropFactoryCommon {
        
            /**
            * @dev Function to verify claim params, make sure the link is not claimed or canceled and proxy has sufficient balance
            * @param _weiAmount Amount of wei to be claimed
            * @param _tokenAddress Token address
            * @param _tokenAmount Amount of tokens to be claimed (in atomic value)
            * @param _expiration Unix timestamp of link expiration time
            * @param _linkId Address corresponding to link key
            * @param _linkdropMaster Address corresponding to linkdrop master key
            * @param _campaignId Campaign id
            * @param _linkdropSignerSignature ECDSA signature of linkdrop signer
            * @param _receiver Address of linkdrop receiver
            * @param _receiverSignature ECDSA signature of linkdrop receiver
            * @return True if success
            */
            function checkClaimParams
            (
                uint _weiAmount,
                address _tokenAddress,
                uint _tokenAmount,
                uint _expiration,
                address _linkId,
                address payable _linkdropMaster,
                uint _campaignId,
                bytes memory _linkdropSignerSignature,
                address _receiver,
                bytes memory _receiverSignature
            )
            public view
            returns (bool)
            {
                // Make sure proxy contract is deployed
                require(isDeployed(_linkdropMaster, _campaignId), "LINKDROP_PROXY_CONTRACT_NOT_DEPLOYED");
        
                uint fee = fees[deployed[salt(_linkdropMaster, _campaignId)]];
        
                return ILinkdropERC20(deployed[salt(_linkdropMaster, _campaignId)]).checkClaimParams
                (
                    _weiAmount,
                    _tokenAddress,
                    _tokenAmount,
                    _expiration,
                    _linkId,
                    _linkdropSignerSignature,
                    _receiver,
                    _receiverSignature,
                    fee
                );
            }
        
            /**
            * @dev Function to claim ETH and/or ERC20 tokens
            * @param _weiAmount Amount of wei to be claimed
            * @param _tokenAddress Token address
            * @param _tokenAmount Amount of tokens to be claimed (in atomic value)
            * @param _expiration Unix timestamp of link expiration time
            * @param _linkId Address corresponding to link key
            * @param _linkdropMaster Address corresponding to linkdrop master key
            * @param _campaignId Campaign id
            * @param _linkdropSignerSignature ECDSA signature of linkdrop signer
            * @param _receiver Address of linkdrop receiver
            * @param _receiverSignature ECDSA signature of linkdrop receiver
            * @return True if success
            */
            function claim
            (
                uint _weiAmount,
                address _tokenAddress,
                uint _tokenAmount,
                uint _expiration,
                address _linkId,
                address payable _linkdropMaster,
                uint _campaignId,
                bytes calldata _linkdropSignerSignature,
                address payable _receiver,
                bytes calldata _receiverSignature
            )
            external
            returns (bool)
            {
                // Make sure proxy contract is deployed
                require(isDeployed(_linkdropMaster, _campaignId), "LINKDROP_PROXY_CONTRACT_NOT_DEPLOYED");
        
                // Make sure only whitelisted relayer calls this function
                require(isRelayer[msg.sender], "ONLY_RELAYER");
        
                uint fee = fees[deployed[salt(_linkdropMaster, _campaignId)]];
        
                // Call claim function in the context of proxy contract
                ILinkdropERC20(deployed[salt(_linkdropMaster, _campaignId)]).claim
                (
                    _weiAmount,
                    _tokenAddress,
                    _tokenAmount,
                    _expiration,
                    _linkId,
                    _linkdropSignerSignature,
                    _receiver,
                    _receiverSignature,
                    msg.sender, // Fee receiver
                    fee
                );
        
                return true;
            }
        
        }
        
        
        
        
        contract LinkdropFactoryERC721 is ILinkdropFactoryERC721, LinkdropFactoryCommon {
        
            /**
            * @dev Function to verify claim params, make sure the link is not claimed or canceled and proxy is allowed to spend token
            * @param _weiAmount Amount of wei to be claimed
            * @param _nftAddress NFT address
            * @param _tokenId Token id to be claimed
            * @param _expiration Unix timestamp of link expiration time
            * @param _linkId Address corresponding to link key
            * @param _linkdropMaster Address corresponding to linkdrop master key
            * @param _campaignId Campaign id
            * @param _linkdropSignerSignature ECDSA signature of linkdrop signer
            * @param _receiver Address of linkdrop receiver
            * @param _receiverSignature ECDSA signature of linkdrop receiver
            * @return True if success
            */
            function checkClaimParamsERC721
            (
                uint _weiAmount,
                address _nftAddress,
                uint _tokenId,
                uint _expiration,
                address _linkId,
                address payable _linkdropMaster,
                uint _campaignId,
                bytes memory _linkdropSignerSignature,
                address _receiver,
                bytes memory _receiverSignature
            )
            public view
            returns (bool)
            {
                // Make sure proxy contract is deployed
                require(isDeployed(_linkdropMaster, _campaignId), "LINKDROP_PROXY_CONTRACT_NOT_DEPLOYED");
        
                uint fee = fees[deployed[salt(_linkdropMaster, _campaignId)]];
        
                return ILinkdropERC721(deployed[salt(_linkdropMaster, _campaignId)]).checkClaimParamsERC721
                (
                    _weiAmount,
                    _nftAddress,
                    _tokenId,
                    _expiration,
                    _linkId,
                    _linkdropSignerSignature,
                    _receiver,
                    _receiverSignature,
                    fee
                );
            }
        
            /**
            * @dev Function to claim ETH and/or ERC721 token
            * @param _weiAmount Amount of wei to be claimed
            * @param _nftAddress NFT address
            * @param _tokenId Token id to be claimed
            * @param _expiration Unix timestamp of link expiration time
            * @param _linkId Address corresponding to link key
            * @param _linkdropMaster Address corresponding to linkdrop master key
            * @param _campaignId Campaign id
            * @param _linkdropSignerSignature ECDSA signature of linkdrop signer
            * @param _receiver Address of linkdrop receiver
            * @param _receiverSignature ECDSA signature of linkdrop receiver
            * @return True if success
            */
            function claimERC721
            (
                uint _weiAmount,
                address _nftAddress,
                uint _tokenId,
                uint _expiration,
                address _linkId,
                address payable _linkdropMaster,
                uint _campaignId,
                bytes calldata _linkdropSignerSignature,
                address payable _receiver,
                bytes calldata _receiverSignature
            )
            external
            returns (bool)
            {
                // Make sure proxy contract is deployed
                require(isDeployed(_linkdropMaster, _campaignId), "LINKDROP_PROXY_CONTRACT_NOT_DEPLOYED");
        
                // Make sure only whitelisted relayer calls this function
                require(isRelayer[msg.sender], "ONLY_RELAYER");
        
                uint fee = fees[deployed[salt(_linkdropMaster, _campaignId)]];
        
                // Call claim function in the context of proxy contract
                ILinkdropERC721(deployed[salt(_linkdropMaster, _campaignId)]).claimERC721
                (
                    _weiAmount,
                    _nftAddress,
                    _tokenId,
                    _expiration,
                    _linkId,
                    _linkdropSignerSignature,
                    _receiver,
                    _receiverSignature,
                    msg.sender, // Fee receiver
                    fee
                );
        
                return true;
            }
        
        }
        
        
        contract LinkdropFactory is LinkdropFactoryERC20, LinkdropFactoryERC721 {
        
            /**
            * @dev Constructor that sets bootstap initcode, factory owner, chainId and master copy
            * @param _masterCopy Linkdrop mastercopy contract address to calculate bytecode from
            * @param _chainId Chain id
            */
            constructor(address payable _masterCopy, uint _chainId) public {
                _initcode = (hex"6352c7420d6000526103ff60206004601c335afa6040516060f3");
                chainId = _chainId;
                setMasterCopy(_masterCopy);
            }
        
        }

        File 2 of 2: GenArt721Core
        // File contracts/libs/IERC165.sol
        
        // File: openzeppelin-solidity/contracts/introspection/IERC165.sol
        pragma solidity ^0.5.0;
        
        /**
         * @dev Interface of the ERC165 standard, as defined in the
         * [EIP](https://eips.ethereum.org/EIPS/eip-165).
         *
         * Implementers can declare support of contract interfaces, which can then be
         * queried by others (`ERC165Checker`).
         *
         * For an implementation, see `ERC165`.
         */
        interface IERC165 {
            /**
             * @dev Returns true if this contract implements the interface defined by
             * `interfaceId`. See the corresponding
             * [EIP section](https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified)
             * to learn more about how these ids are created.
             *
             * This function call must use less than 30 000 gas.
             */
            function supportsInterface(bytes4 interfaceId) external view returns (bool);
        }
        
        
        // File contracts/libs/ERC165.sol
        
        // File: openzeppelin-solidity/contracts/introspection/ERC165.sol
        
        pragma solidity ^0.5.0;
        
        
        
        /**
         * @dev Implementation of the `IERC165` interface.
         *
         * Contracts may inherit from this and call `_registerInterface` to declare
         * their support of an interface.
         */
        contract ERC165 is IERC165 {
            /*
             * bytes4(keccak256('supportsInterface(bytes4)')) == 0x01ffc9a7
             */
            bytes4 private constant _INTERFACE_ID_ERC165 = 0x01ffc9a7;
        
            /**
             * @dev Mapping of interface ids to whether or not it's supported.
             */
            mapping(bytes4 => bool) private _supportedInterfaces;
        
            constructor () internal {
                // Derived contracts need only register support for their own interfaces,
                // we register support for ERC165 itself here
                _registerInterface(_INTERFACE_ID_ERC165);
            }
        
            /**
             * @dev See `IERC165.supportsInterface`.
             *
             * Time complexity O(1), guaranteed to always use less than 30 000 gas.
             */
            function supportsInterface(bytes4 interfaceId) external view returns (bool) {
                return _supportedInterfaces[interfaceId];
            }
        
            /**
             * @dev Registers the contract as an implementer of the interface defined by
             * `interfaceId`. Support of the actual ERC165 interface is automatic and
             * registering its interface id is not required.
             *
             * See `IERC165.supportsInterface`.
             *
             * Requirements:
             *
             * - `interfaceId` cannot be the ERC165 invalid interface (`0xffffffff`).
             */
            function _registerInterface(bytes4 interfaceId) internal {
                require(interfaceId != 0xffffffff, "ERC165: invalid interface id");
                _supportedInterfaces[interfaceId] = true;
            }
        }
        
        
        // File contracts/libs/IERC721.sol
        
        // File: openzeppelin-solidity/contracts/token/ERC721/IERC721.sol
        
        pragma solidity ^0.5.0;
        
        
        
        /**
         * @dev Required interface of an ERC721 compliant contract.
         */
        contract IERC721 is IERC165 {
            event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
            event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
            event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
        
            /**
             * @dev Returns the number of NFTs in `owner`'s account.
             */
            function balanceOf(address owner) public view returns (uint256 balance);
        
            /**
             * @dev Returns the owner of the NFT specified by `tokenId`.
             */
            function ownerOf(uint256 tokenId) public view returns (address owner);
        
            /**
             * @dev Transfers a specific NFT (`tokenId`) from one account (`from`) to
             * another (`to`).
             *
             *
             *
             * Requirements:
             * - `from`, `to` cannot be zero.
             * - `tokenId` must be owned by `from`.
             * - If the caller is not `from`, it must be have been allowed to move this
             * NFT by either `approve` or `setApproveForAll`.
             */
            function safeTransferFrom(address from, address to, uint256 tokenId) public;
            /**
             * @dev Transfers a specific NFT (`tokenId`) from one account (`from`) to
             * another (`to`).
             *
             * Requirements:
             * - If the caller is not `from`, it must be approved to move this NFT by
             * either `approve` or `setApproveForAll`.
             */
            function transferFrom(address from, address to, uint256 tokenId) public;
            function approve(address to, uint256 tokenId) public;
            function getApproved(uint256 tokenId) public view returns (address operator);
        
            function setApprovalForAll(address operator, bool _approved) public;
            function isApprovedForAll(address owner, address operator) public view returns (bool);
        
        
            function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory data) public;
        }
        
        
        // File contracts/libs/SafeMath.sol
        
        // File: openzeppelin-solidity/contracts/math/SafeMath.sol
        
        pragma solidity ^0.5.0;
        
        /**
         * @dev Wrappers over Solidity's arithmetic operations with added overflow
         * checks.
         *
         * Arithmetic operations in Solidity wrap on overflow. This can easily result
         * in bugs, because programmers usually assume that an overflow raises an
         * error, which is the standard behavior in high level programming languages.
         * `SafeMath` restores this intuition by reverting the transaction when an
         * operation overflows.
         *
         * Using this library instead of the unchecked operations eliminates an entire
         * class of bugs, so it's recommended to use it always.
         */
        library SafeMath {
            /**
             * @dev Returns the addition of two unsigned integers, reverting on
             * overflow.
             *
             * Counterpart to Solidity's `+` operator.
             *
             * Requirements:
             * - Addition cannot overflow.
             */
            function add(uint256 a, uint256 b) internal pure returns (uint256) {
                uint256 c = a + b;
                require(c >= a, "SafeMath: addition overflow");
        
                return c;
            }
        
            /**
             * @dev Returns the subtraction of two unsigned integers, reverting on
             * overflow (when the result is negative).
             *
             * Counterpart to Solidity's `-` operator.
             *
             * Requirements:
             * - Subtraction cannot overflow.
             */
            function sub(uint256 a, uint256 b) internal pure returns (uint256) {
                require(b <= a, "SafeMath: subtraction overflow");
                uint256 c = a - b;
        
                return c;
            }
        
            /**
             * @dev Returns the multiplication of two unsigned integers, reverting on
             * overflow.
             *
             * Counterpart to Solidity's `*` operator.
             *
             * Requirements:
             * - Multiplication cannot overflow.
             */
            function mul(uint256 a, uint256 b) internal pure returns (uint256) {
                // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
                // benefit is lost if 'b' is also tested.
                // See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522
                if (a == 0) {
                    return 0;
                }
        
                uint256 c = a * b;
                require(c / a == b, "SafeMath: multiplication overflow");
        
                return c;
            }
        
            /**
             * @dev Returns the integer division of two unsigned integers. Reverts on
             * division by zero. The result is rounded towards zero.
             *
             * Counterpart to Solidity's `/` operator. Note: this function uses a
             * `revert` opcode (which leaves remaining gas untouched) while Solidity
             * uses an invalid opcode to revert (consuming all remaining gas).
             *
             * Requirements:
             * - The divisor cannot be zero.
             */
            function div(uint256 a, uint256 b) internal pure returns (uint256) {
                // Solidity only automatically asserts when dividing by 0
                require(b > 0, "SafeMath: division by zero");
                uint256 c = a / b;
                // assert(a == b * c + a % b); // There is no case in which this doesn't hold
        
                return c;
            }
        }
        
        
        // File contracts/libs/Address.sol
        
        // File: openzeppelin-solidity/contracts/utils/Address.sol
        
        pragma solidity ^0.5.0;
        
        /**
         * @dev Collection of functions related to the address type,
         */
        library Address {
            /**
             * @dev Returns true if `account` is a contract.
             *
             * This test is non-exhaustive, and there may be false-negatives: during the
             * execution of a contract's constructor, its address will be reported as
             * not containing a contract.
             *
             * > It is unsafe to assume that an address for which this function returns
             * false is an externally-owned account (EOA) and not a contract.
             */
            function isContract(address account) internal view returns (bool) {
                // This method relies in extcodesize, which returns 0 for contracts in
                // construction, since the code is only stored at the end of the
                // constructor execution.
        
                uint256 size;
                // solhint-disable-next-line no-inline-assembly
                assembly { size := extcodesize(account) }
                return size > 0;
            }
        }
        
        
        // File contracts/libs/Counters.sol
        
        // File: openzeppelin-solidity/contracts/drafts/Counters.sol
        
        pragma solidity ^0.5.0;
        
        
        
        /**
         * @title Counters
         * @author Matt Condon (@shrugs)
         * @dev Provides counters that can only be incremented or decremented by one. This can be used e.g. to track the number
         * of elements in a mapping, issuing ERC721 ids, or counting request ids.
         *
         * Include with `using Counters for Counters.Counter;`
         * Since it is not possible to overflow a 256 bit integer with increments of one, `increment` can skip the SafeMath
         * overflow check, thereby saving gas. This does assume however correct usage, in that the underlying `_value` is never
         * directly accessed.
         */
        library Counters {
            using SafeMath for uint256;
        
            struct Counter {
                // This variable should never be directly accessed by users of the library: interactions must be restricted to
                // the library's function. As of Solidity v0.5.2, this cannot be enforced, though there is a proposal to add
                // this feature: see https://github.com/ethereum/solidity/issues/4637
                uint256 _value; // default: 0
            }
        
            function current(Counter storage counter) internal view returns (uint256) {
                return counter._value;
            }
        
            function increment(Counter storage counter) internal {
                counter._value += 1;
            }
        
            function decrement(Counter storage counter) internal {
                counter._value = counter._value.sub(1);
            }
        }
        
        
        // File contracts/libs/IERC721Receiver.sol
        
        // File: openzeppelin-solidity/contracts/token/ERC721/IERC721Receiver.sol
        
        pragma solidity ^0.5.0;
        
        /**
         * @title ERC721 token receiver interface
         * @dev Interface for any contract that wants to support safeTransfers
         * from ERC721 asset contracts.
         */
        contract IERC721Receiver {
            function onERC721Received(address operator, address from, uint256 tokenId, bytes memory data)
            public returns (bytes4);
        }
        
        
        // File contracts/libs/ERC721.sol
        
        // File: openzeppelin-solidity/contracts/token/ERC721/ERC721.sol
        
        pragma solidity ^0.5.0;
        
        
        
        
        
        
        
        /**
         * @title ERC721 Non-Fungible Token Standard basic implementation
         * @dev see https://eips.ethereum.org/EIPS/eip-721
         */
        contract ERC721 is ERC165, IERC721 {
            using SafeMath for uint256;
            using Address for address;
            using Counters for Counters.Counter;
        
            // Equals to `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`
            // which can be also obtained as `IERC721Receiver(0).onERC721Received.selector`
            bytes4 private constant _ERC721_RECEIVED = 0x150b7a02;
        
            // Mapping from token ID to owner
            mapping (uint256 => address) private _tokenOwner;
        
            // Mapping from token ID to approved address
            mapping (uint256 => address) private _tokenApprovals;
        
            // Mapping from owner to number of owned token
            mapping (address => Counters.Counter) private _ownedTokensCount;
        
            // Mapping from owner to operator approvals
            mapping (address => mapping (address => bool)) private _operatorApprovals;
            
            bytes4 private constant _INTERFACE_ID_ERC721 = 0x80ac58cd;
        
            constructor () public {
                // register the supported interfaces to conform to ERC721 via ERC165
                _registerInterface(_INTERFACE_ID_ERC721);
            }
        
        
            function balanceOf(address owner) public view returns (uint256) {
                require(owner != address(0), "ERC721: balance query for the zero address");
        
                return _ownedTokensCount[owner].current();
            }
        
            function ownerOf(uint256 tokenId) public view returns (address) {
                address owner = _tokenOwner[tokenId];
                require(owner != address(0), "ERC721: owner query for nonexistent token");
        
                return owner;
            }
        
            function approve(address to, uint256 tokenId) public {
                address owner = ownerOf(tokenId);
                require(to != owner, "ERC721: approval to current owner");
        
                require(msg.sender == owner || isApprovedForAll(owner, msg.sender),
                    "ERC721: approve caller is not owner nor approved for all"
                );
        
                _tokenApprovals[tokenId] = to;
                emit Approval(owner, to, tokenId);
            }
        
            function getApproved(uint256 tokenId) public view returns (address) {
                require(_exists(tokenId), "ERC721: approved query for nonexistent token");
        
                return _tokenApprovals[tokenId];
            }
        
            function setApprovalForAll(address to, bool approved) public {
                require(to != msg.sender, "ERC721: approve to caller");
        
                _operatorApprovals[msg.sender][to] = approved;
                emit ApprovalForAll(msg.sender, to, approved);
            }
        
            function isApprovedForAll(address owner, address operator) public view returns (bool) {
                return _operatorApprovals[owner][operator];
            }
        
            function transferFrom(address from, address to, uint256 tokenId) public {
                //solhint-disable-next-line max-line-length
                require(_isApprovedOrOwner(msg.sender, tokenId), "ERC721: transfer caller is not owner nor approved");
        
                _transferFrom(from, to, tokenId);
            }
        
            function safeTransferFrom(address from, address to, uint256 tokenId) public {
                safeTransferFrom(from, to, tokenId, "");
            }
        
            function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory _data) public {
                transferFrom(from, to, tokenId);
                require(_checkOnERC721Received(from, to, tokenId, _data), "ERC721: transfer to non ERC721Receiver implementer");
            }
        
            function _exists(uint256 tokenId) internal view returns (bool) {
                address owner = _tokenOwner[tokenId];
                return owner != address(0);
            }
        
            function _isApprovedOrOwner(address spender, uint256 tokenId) internal view returns (bool) {
                require(_exists(tokenId), "ERC721: operator query for nonexistent token");
                address owner = ownerOf(tokenId);
                return (spender == owner || getApproved(tokenId) == spender || isApprovedForAll(owner, spender));
            }
        
            function _mint(address to, uint256 tokenId) internal {
                require(to != address(0), "ERC721: mint to the zero address");
                require(!_exists(tokenId), "ERC721: token already minted");
        
                _tokenOwner[tokenId] = to;
                _ownedTokensCount[to].increment();
        
                emit Transfer(address(0), to, tokenId);
            }
        
            function _burn(address owner, uint256 tokenId) internal {
                require(ownerOf(tokenId) == owner, "ERC721: burn of token that is not own");
        
                _clearApproval(tokenId);
        
                _ownedTokensCount[owner].decrement();
                _tokenOwner[tokenId] = address(0);
        
                emit Transfer(owner, address(0), tokenId);
            }
        
            function _burn(uint256 tokenId) internal {
                _burn(ownerOf(tokenId), tokenId);
            }
        
            function _transferFrom(address from, address to, uint256 tokenId) internal {
                require(ownerOf(tokenId) == from, "ERC721: transfer of token that is not own");
                require(to != address(0), "ERC721: transfer to the zero address");
        
                _clearApproval(tokenId);
        
                _ownedTokensCount[from].decrement();
                _ownedTokensCount[to].increment();
        
                _tokenOwner[tokenId] = to;
        
                emit Transfer(from, to, tokenId);
            }
        
            function _checkOnERC721Received(address from, address to, uint256 tokenId, bytes memory _data)
            internal returns (bool)
            {
                if (!to.isContract()) {
                    return true;
                }
        
                bytes4 retval = IERC721Receiver(to).onERC721Received(msg.sender, from, tokenId, _data);
                return (retval == _ERC721_RECEIVED);
            }
        
            function _clearApproval(uint256 tokenId) private {
                if (_tokenApprovals[tokenId] != address(0)) {
                    _tokenApprovals[tokenId] = address(0);
                }
            }
        }
        
        
        // File contracts/libs/IERC721Enumerable.sol
        
        // File: openzeppelin-solidity/contracts/token/ERC721/IERC721Enumerable.sol
        
        pragma solidity ^0.5.0;
        
        
        
        /**
         * @title ERC-721 Non-Fungible Token Standard, optional enumeration extension
         * @dev See https://eips.ethereum.org/EIPS/eip-721
         */
        contract IERC721Enumerable is IERC721 {
            function totalSupply() public view returns (uint256);
            function tokenOfOwnerByIndex(address owner, uint256 index) public view returns (uint256 tokenId);
        
            function tokenByIndex(uint256 index) public view returns (uint256);
        }
        
        
        // File contracts/libs/ERC721Enumerable.sol
        
        // File: openzeppelin-solidity/contracts/token/ERC721/ERC721Enumerable.sol
        
        pragma solidity ^0.5.0;
        
        
        
        
        
        
        
        /**
         * @title ERC-721 Non-Fungible Token with optional enumeration extension logic
         * @dev See https://eips.ethereum.org/EIPS/eip-721
         */
        contract ERC721Enumerable is ERC165, ERC721, IERC721Enumerable {
            // Mapping from owner to list of owned token IDs
            mapping(address => uint256[]) private _ownedTokens;
        
            // Mapping from token ID to index of the owner tokens list
            mapping(uint256 => uint256) private _ownedTokensIndex;
        
            // Array with all token ids, used for enumeration
            uint256[] private _allTokens;
        
            // Mapping from token id to position in the allTokens array
            mapping(uint256 => uint256) private _allTokensIndex;
        
            /*
             *     bytes4(keccak256('totalSupply()')) == 0x18160ddd
             *     bytes4(keccak256('tokenOfOwnerByIndex(address,uint256)')) == 0x2f745c59
             *     bytes4(keccak256('tokenByIndex(uint256)')) == 0x4f6ccce7
             *
             *     => 0x18160ddd ^ 0x2f745c59 ^ 0x4f6ccce7 == 0x780e9d63
             */
            bytes4 private constant _INTERFACE_ID_ERC721_ENUMERABLE = 0x780e9d63;
        
            /**
             * @dev Constructor function.
             */
            constructor () public {
                // register the supported interface to conform to ERC721Enumerable via ERC165
                _registerInterface(_INTERFACE_ID_ERC721_ENUMERABLE);
            }
        
            /**
             * @dev Gets the token ID at a given index of the tokens list of the requested owner.
             * @param owner address owning the tokens list to be accessed
             * @param index uint256 representing the index to be accessed of the requested tokens list
             * @return uint256 token ID at the given index of the tokens list owned by the requested address
             */
            function tokenOfOwnerByIndex(address owner, uint256 index) public view returns (uint256) {
                require(index < balanceOf(owner), "ERC721Enumerable: owner index out of bounds");
                return _ownedTokens[owner][index];
            }
        
            /**
             * @dev Gets the total amount of tokens stored by the contract.
             * @return uint256 representing the total amount of tokens
             */
            function totalSupply() public view returns (uint256) {
                return _allTokens.length;
            }
        
            /**
             * @dev Gets the token ID at a given index of all the tokens in this contract
             * Reverts if the index is greater or equal to the total number of tokens.
             * @param index uint256 representing the index to be accessed of the tokens list
             * @return uint256 token ID at the given index of the tokens list
             */
            function tokenByIndex(uint256 index) public view returns (uint256) {
                require(index < totalSupply(), "ERC721Enumerable: global index out of bounds");
                return _allTokens[index];
            }
        
            /**
             * @dev Internal function to transfer ownership of a given token ID to another address.
             * As opposed to transferFrom, this imposes no restrictions on msg.sender.
             * @param from current owner of the token
             * @param to address to receive the ownership of the given token ID
             * @param tokenId uint256 ID of the token to be transferred
             */
            function _transferFrom(address from, address to, uint256 tokenId) internal {
                super._transferFrom(from, to, tokenId);
        
                _removeTokenFromOwnerEnumeration(from, tokenId);
        
                _addTokenToOwnerEnumeration(to, tokenId);
            }
        
            /**
             * @dev Internal function to mint a new token.
             * Reverts if the given token ID already exists.
             * @param to address the beneficiary that will own the minted token
             * @param tokenId uint256 ID of the token to be minted
             */
            function _mint(address to, uint256 tokenId) internal {
                super._mint(to, tokenId);
        
                _addTokenToOwnerEnumeration(to, tokenId);
        
                _addTokenToAllTokensEnumeration(tokenId);
            }
        
            /**
             * @dev Internal function to burn a specific token.
             * Reverts if the token does not exist.
             * Deprecated, use _burn(uint256) instead.
             * @param owner owner of the token to burn
             * @param tokenId uint256 ID of the token being burned
             */
            function _burn(address owner, uint256 tokenId) internal {
                super._burn(owner, tokenId);
        
                _removeTokenFromOwnerEnumeration(owner, tokenId);
                // Since tokenId will be deleted, we can clear its slot in _ownedTokensIndex to trigger a gas refund
                _ownedTokensIndex[tokenId] = 0;
        
                _removeTokenFromAllTokensEnumeration(tokenId);
            }
        
            /**
             * @dev Gets the list of token IDs of the requested owner.
             * @param owner address owning the tokens
             * @return uint256[] List of token IDs owned by the requested address
             */
            function _tokensOfOwner(address owner) internal view returns (uint256[] storage) {
                return _ownedTokens[owner];
            }
        
            /**
             * @dev Private function to add a token to this extension's ownership-tracking data structures.
             * @param to address representing the new owner of the given token ID
             * @param tokenId uint256 ID of the token to be added to the tokens list of the given address
             */
            function _addTokenToOwnerEnumeration(address to, uint256 tokenId) private {
                _ownedTokensIndex[tokenId] = _ownedTokens[to].length;
                _ownedTokens[to].push(tokenId);
            }
        
            /**
             * @dev Private function to add a token to this extension's token tracking data structures.
             * @param tokenId uint256 ID of the token to be added to the tokens list
             */
            function _addTokenToAllTokensEnumeration(uint256 tokenId) private {
                _allTokensIndex[tokenId] = _allTokens.length;
                _allTokens.push(tokenId);
            }
        
            /**
             * @dev Private function to remove a token from this extension's ownership-tracking data structures. Note that
             * while the token is not assigned a new owner, the _ownedTokensIndex mapping is _not_ updated: this allows for
             * gas optimizations e.g. when performing a transfer operation (avoiding double writes).
             * This has O(1) time complexity, but alters the order of the _ownedTokens array.
             * @param from address representing the previous owner of the given token ID
             * @param tokenId uint256 ID of the token to be removed from the tokens list of the given address
             */
            function _removeTokenFromOwnerEnumeration(address from, uint256 tokenId) private {
                // To prevent a gap in from's tokens array, we store the last token in the index of the token to delete, and
                // then delete the last slot (swap and pop).
        
                uint256 lastTokenIndex = _ownedTokens[from].length.sub(1);
                uint256 tokenIndex = _ownedTokensIndex[tokenId];
        
                // When the token to delete is the last token, the swap operation is unnecessary
                if (tokenIndex != lastTokenIndex) {
                    uint256 lastTokenId = _ownedTokens[from][lastTokenIndex];
        
                    _ownedTokens[from][tokenIndex] = lastTokenId; // Move the last token to the slot of the to-delete token
                    _ownedTokensIndex[lastTokenId] = tokenIndex; // Update the moved token's index
                }
        
                // This also deletes the contents at the last position of the array
                _ownedTokens[from].length--;
        
                // Note that _ownedTokensIndex[tokenId] hasn't been cleared: it still points to the old slot (now occupied by
                // lastTokenId, or just over the end of the array if the token was the last one).
            }
        
            /**
             * @dev Private function to remove a token from this extension's token tracking data structures.
             * This has O(1) time complexity, but alters the order of the _allTokens array.
             * @param tokenId uint256 ID of the token to be removed from the tokens list
             */
            function _removeTokenFromAllTokensEnumeration(uint256 tokenId) private {
                // To prevent a gap in the tokens array, we store the last token in the index of the token to delete, and
                // then delete the last slot (swap and pop).
        
                uint256 lastTokenIndex = _allTokens.length.sub(1);
                uint256 tokenIndex = _allTokensIndex[tokenId];
        
                // When the token to delete is the last token, the swap operation is unnecessary. However, since this occurs so
                // rarely (when the last minted token is burnt) that we still do the swap here to avoid the gas cost of adding
                // an 'if' statement (like in _removeTokenFromOwnerEnumeration)
                uint256 lastTokenId = _allTokens[lastTokenIndex];
        
                _allTokens[tokenIndex] = lastTokenId; // Move the last token to the slot of the to-delete token
                _allTokensIndex[lastTokenId] = tokenIndex; // Update the moved token's index
        
                // This also deletes the contents at the last position of the array
                _allTokens.length--;
                _allTokensIndex[tokenId] = 0;
            }
        }
        
        
        // File contracts/libs/CustomERC721Metadata.sol
        
        // File: contracts/CustomERC721Metadata.sol
        
        pragma solidity ^0.5.0;
        
        
        
        
        
        
        /**
         * ERC721 base contract without the concept of tokenUri as this is managed by the parent
         */
        contract CustomERC721Metadata is ERC165, ERC721, ERC721Enumerable {
        
            // Token name
            string private _name;
        
            // Token symbol
            string private _symbol;
        
            bytes4 private constant _INTERFACE_ID_ERC721_METADATA = 0x5b5e139f;
        
            /**
             * @dev Constructor function
             */
            constructor (string memory name, string memory symbol) public {
                _name = name;
                _symbol = symbol;
        
                // register the supported interfaces to conform to ERC721 via ERC165
                _registerInterface(_INTERFACE_ID_ERC721_METADATA);
            }
        
            /**
             * @dev Gets the token name
             * @return string representing the token name
             */
            function name() external view returns (string memory) {
                return _name;
            }
        
            /**
             * @dev Gets the token symbol
             * @return string representing the token symbol
             */
            function symbol() external view returns (string memory) {
                return _symbol;
            }
        
        }
        
        
        // File contracts/libs/Strings.sol
        
        // File: contracts/Strings.sol
        
        pragma solidity ^0.5.0;
        
        //https://github.com/oraclize/ethereum-api/blob/master/oraclizeAPI_0.5.sol
        library Strings {
        
            function strConcat(string memory _a, string memory _b) internal pure returns (string memory _concatenatedString) {
                return strConcat(_a, _b, "", "", "");
            }
        
            function strConcat(string memory _a, string memory _b, string memory _c) internal pure returns (string memory _concatenatedString) {
                return strConcat(_a, _b, _c, "", "");
            }
        
            function strConcat(string memory _a, string memory _b, string memory _c, string memory _d) internal pure returns (string memory _concatenatedString) {
                return strConcat(_a, _b, _c, _d, "");
            }
        
            function strConcat(string memory _a, string memory _b, string memory _c, string memory _d, string memory _e) internal pure returns (string memory _concatenatedString) {
                bytes memory _ba = bytes(_a);
                bytes memory _bb = bytes(_b);
                bytes memory _bc = bytes(_c);
                bytes memory _bd = bytes(_d);
                bytes memory _be = bytes(_e);
                string memory abcde = new string(_ba.length + _bb.length + _bc.length + _bd.length + _be.length);
                bytes memory babcde = bytes(abcde);
                uint k = 0;
                uint i = 0;
                for (i = 0; i < _ba.length; i++) {
                    babcde[k++] = _ba[i];
                }
                for (i = 0; i < _bb.length; i++) {
                    babcde[k++] = _bb[i];
                }
                for (i = 0; i < _bc.length; i++) {
                    babcde[k++] = _bc[i];
                }
                for (i = 0; i < _bd.length; i++) {
                    babcde[k++] = _bd[i];
                }
                for (i = 0; i < _be.length; i++) {
                    babcde[k++] = _be[i];
                }
                return string(babcde);
            }
        
            function uint2str(uint _i) internal pure returns (string memory _uintAsString) {
                if (_i == 0) {
                    return "0";
                }
                uint j = _i;
                uint len;
                while (j != 0) {
                    len++;
                    j /= 10;
                }
                bytes memory bstr = new bytes(len);
                uint k = len - 1;
                while (_i != 0) {
                    bstr[k--] = byte(uint8(48 + _i % 10));
                    _i /= 10;
                }
                return string(bstr);
            }
        }
        
        
        // File contracts/GenArt721Core.sol
        
        // File: contracts/GenArt721Core.sol
        
        //0x1454EFCa69FA654e5A7d83CB61c1aD81790c44B7
        
        //https://oneclickdapp.com/radar-valery/
        
        pragma solidity ^0.5.0;
        
        
        
        
        interface Randomizer {
           function returnValue() external view returns(bytes32);
        }
        
        contract GenArt721Core is CustomERC721Metadata {
            using SafeMath for uint256;
        
            event Mint(
                address indexed _to,
                uint256 indexed _tokenId,
                uint256 indexed _projectId
        
            );
        
            Randomizer public randomizerContract;
        
            struct Project {
                string name;
                string artist;
                string description;
                string website;
                string license;
                bool dynamic;
                string projectBaseURI;
                string projectBaseIpfsURI;
                uint256 invocations;
                uint256 maxInvocations;
                string scriptJSON;
                mapping(uint256 => string) scripts;
                uint scriptCount;
                string ipfsHash;
                bool useHashString;
                bool useIpfs;
                bool active;
                bool locked;
                bool paused;
        
            }
        
            uint256 constant ONE_MILLION = 1_000_000;
            mapping(uint256 => Project) projects;
        
            //All financial functions are stripped from struct for visibility
            mapping(uint256 => address) public projectIdToArtistAddress;
            mapping(uint256 => string) public projectIdToCurrencySymbol;
            mapping(uint256 => address) public projectIdToCurrencyAddress;
            mapping(uint256 => uint256) public projectIdToPricePerTokenInWei;
            mapping(uint256 => address) public projectIdToAdditionalPayee;
            mapping(uint256 => uint256) public projectIdToAdditionalPayeePercentage;
            mapping(uint256 => uint256) public projectIdToSecondaryMarketRoyaltyPercentage;
        
            address public artblocksAddress;
            uint256 public artblocksPercentage = 10;
        
            mapping(uint256 => string) public staticIpfsImageLink;
            mapping(uint256 => uint256) public tokenIdToProjectId;
            mapping(uint256 => uint256[]) internal projectIdToTokenIds;
            mapping(uint256 => bytes32) public tokenIdToHash;
            mapping(bytes32 => uint256) public hashToTokenId;
        
            address public admin;
            mapping(address => bool) public isWhitelisted;
            mapping(address => bool) public isMintWhitelisted;
        
            uint256 public nextProjectId = 3;
        
            modifier onlyValidTokenId(uint256 _tokenId) {
                require(_exists(_tokenId), "Token ID does not exist");
                _;
            }
        
            modifier onlyUnlocked(uint256 _projectId) {
                require(!projects[_projectId].locked, "Only if unlocked");
                _;
            }
        
            modifier onlyArtist(uint256 _projectId) {
                require(msg.sender == projectIdToArtistAddress[_projectId], "Only artist");
                _;
            }
        
            modifier onlyAdmin() {
                require(msg.sender == admin, "Only admin");
                _;
            }
        
            modifier onlyWhitelisted() {
                require(isWhitelisted[msg.sender], "Only whitelisted");
                _;
            }
        
            modifier onlyArtistOrWhitelisted(uint256 _projectId) {
                require(isWhitelisted[msg.sender] || msg.sender == projectIdToArtistAddress[_projectId], "Only artist or whitelisted");
                _;
            }
        
            constructor(string memory _tokenName, string memory _tokenSymbol, address _randomizerContract) CustomERC721Metadata(_tokenName, _tokenSymbol) public {
                admin = msg.sender;
                isWhitelisted[msg.sender] = true;
                artblocksAddress = msg.sender;
                randomizerContract = Randomizer(_randomizerContract);
        
            }
        
            function mint(address _to, uint256 _projectId, address _by) external returns (uint256 _tokenId) {
                require(isMintWhitelisted[msg.sender], "Must mint from whitelisted minter contract.");
                require(projects[_projectId].invocations.add(1) <= projects[_projectId].maxInvocations, "Must not exceed max invocations");
                require(projects[_projectId].active || _by == projectIdToArtistAddress[_projectId], "Project must exist and be active");
                require(!projects[_projectId].paused || _by == projectIdToArtistAddress[_projectId], "Purchases are paused.");
        
        
                uint256 tokenId = _mintToken(_to, _projectId);
        
                return tokenId;
            }
        
            function _mintToken(address _to, uint256 _projectId) internal returns (uint256 _tokenId) {
        
                uint256 tokenIdToBe = (_projectId * ONE_MILLION) + projects[_projectId].invocations;
        
                projects[_projectId].invocations = projects[_projectId].invocations.add(1);
        
        
                    bytes32 hash = keccak256(abi.encodePacked(projects[_projectId].invocations, block.number, blockhash(block.number - 1), msg.sender, randomizerContract.returnValue()));
                    tokenIdToHash[tokenIdToBe]=hash;
                    hashToTokenId[hash] = tokenIdToBe;
        
        
                _mint(_to, tokenIdToBe);
        
                tokenIdToProjectId[tokenIdToBe] = _projectId;
                projectIdToTokenIds[_projectId].push(tokenIdToBe);
        
                emit Mint(_to, tokenIdToBe, _projectId);
        
                return tokenIdToBe;
            }
            function updateArtblocksAddress(address _artblocksAddress) public onlyAdmin {
                artblocksAddress = _artblocksAddress;
            }
        
            function updateArtblocksPercentage(uint256 _artblocksPercentage) public onlyAdmin {
                require(_artblocksPercentage <= 25, "Max of 25%");
                artblocksPercentage = _artblocksPercentage;
            }
        
            function addWhitelisted(address _address) public onlyAdmin {
                isWhitelisted[_address] = true;
            }
        
            function removeWhitelisted(address _address) public onlyAdmin {
                isWhitelisted[_address] = false;
            }
        
            function addMintWhitelisted(address _address) public onlyAdmin {
                isMintWhitelisted[_address] = true;
            }
        
            function removeMintWhitelisted(address _address) public onlyAdmin {
                isMintWhitelisted[_address] = false;
            }
        
            function updateRandomizerAddress(address _randomizerAddress) public onlyWhitelisted {
              randomizerContract = Randomizer(_randomizerAddress);
            }
            function toggleProjectIsLocked(uint256 _projectId) public onlyWhitelisted onlyUnlocked(_projectId) {
                projects[_projectId].locked = true;
            }
        
            function toggleProjectIsActive(uint256 _projectId) public onlyWhitelisted {
                projects[_projectId].active = !projects[_projectId].active;
            }
        
            function updateProjectArtistAddress(uint256 _projectId, address _artistAddress) public onlyArtistOrWhitelisted(_projectId) {
                projectIdToArtistAddress[_projectId] = _artistAddress;
            }
        
            function toggleProjectIsPaused(uint256 _projectId) public onlyArtist(_projectId) {
                projects[_projectId].paused = !projects[_projectId].paused;
            }
        
            function addProject(string memory _projectName, address _artistAddress, uint256 _pricePerTokenInWei, bool _dynamic) public onlyWhitelisted {
        
                uint256 projectId = nextProjectId;
                projectIdToArtistAddress[projectId] = _artistAddress;
                projects[projectId].name = _projectName;
                projectIdToCurrencySymbol[projectId] = "ETH";
                projectIdToPricePerTokenInWei[projectId] = _pricePerTokenInWei;
                projects[projectId].paused=true;
                projects[projectId].dynamic=_dynamic;
                projects[projectId].maxInvocations = ONE_MILLION;
                if (!_dynamic) {
                    projects[projectId].useHashString = false;
                } else {
                    projects[projectId].useHashString = true;
                }
                nextProjectId = nextProjectId.add(1);
            }
        
            function updateProjectCurrencyInfo(uint256 _projectId, string memory _currencySymbol, address _currencyAddress) onlyArtist(_projectId) public {
                projectIdToCurrencySymbol[_projectId] = _currencySymbol;
                projectIdToCurrencyAddress[_projectId] = _currencyAddress;
            }
        
            function updateProjectPricePerTokenInWei(uint256 _projectId, uint256 _pricePerTokenInWei) onlyArtist(_projectId) public {
                projectIdToPricePerTokenInWei[_projectId] = _pricePerTokenInWei;
            }
        
            function updateProjectName(uint256 _projectId, string memory _projectName) onlyUnlocked(_projectId) onlyArtistOrWhitelisted(_projectId) public {
                projects[_projectId].name = _projectName;
            }
        
            function updateProjectArtistName(uint256 _projectId, string memory _projectArtistName) onlyUnlocked(_projectId) onlyArtistOrWhitelisted(_projectId) public {
                projects[_projectId].artist = _projectArtistName;
            }
        
            function updateProjectAdditionalPayeeInfo(uint256 _projectId, address _additionalPayee, uint256 _additionalPayeePercentage) onlyArtist(_projectId) public {
                require(_additionalPayeePercentage <= 100, "Max of 100%");
                projectIdToAdditionalPayee[_projectId] = _additionalPayee;
                projectIdToAdditionalPayeePercentage[_projectId] = _additionalPayeePercentage;
            }
        
            function updateProjectSecondaryMarketRoyaltyPercentage(uint256 _projectId, uint256 _secondMarketRoyalty) onlyArtist(_projectId) public {
                require(_secondMarketRoyalty <= 100, "Max of 100%");
                projectIdToSecondaryMarketRoyaltyPercentage[_projectId] = _secondMarketRoyalty;
            }
        
            function updateProjectDescription(uint256 _projectId, string memory _projectDescription) onlyArtist(_projectId) public {
                projects[_projectId].description = _projectDescription;
            }
        
            function updateProjectWebsite(uint256 _projectId, string memory _projectWebsite) onlyArtist(_projectId) public {
                projects[_projectId].website = _projectWebsite;
            }
        
            function updateProjectLicense(uint256 _projectId, string memory _projectLicense) onlyUnlocked(_projectId) onlyArtistOrWhitelisted(_projectId) public {
                projects[_projectId].license = _projectLicense;
            }
        
            function updateProjectMaxInvocations(uint256 _projectId, uint256 _maxInvocations) onlyArtist(_projectId) public {
                require((!projects[_projectId].locked || _maxInvocations<projects[_projectId].maxInvocations), "Only if unlocked");
                require(_maxInvocations > projects[_projectId].invocations, "You must set max invocations greater than current invocations");
                require(_maxInvocations <= ONE_MILLION, "Cannot exceed 1,000,000");
                projects[_projectId].maxInvocations = _maxInvocations;
            }
        
            function toggleProjectUseHashString(uint256 _projectId) onlyUnlocked(_projectId) onlyArtistOrWhitelisted(_projectId) public {
              require(projects[_projectId].invocations == 0, "Cannot modify after a token is minted.");
              projects[_projectId].useHashString = !projects[_projectId].useHashString;
            }
        
            function addProjectScript(uint256 _projectId, string memory _script) onlyUnlocked(_projectId) onlyArtistOrWhitelisted(_projectId) public {
                projects[_projectId].scripts[projects[_projectId].scriptCount] = _script;
                projects[_projectId].scriptCount = projects[_projectId].scriptCount.add(1);
            }
        
            function updateProjectScript(uint256 _projectId, uint256 _scriptId, string memory _script) onlyUnlocked(_projectId) onlyArtistOrWhitelisted(_projectId) public {
                require(_scriptId < projects[_projectId].scriptCount, "scriptId out of range");
                projects[_projectId].scripts[_scriptId] = _script;
            }
        
            function removeProjectLastScript(uint256 _projectId) onlyUnlocked(_projectId) onlyArtistOrWhitelisted(_projectId) public {
                require(projects[_projectId].scriptCount > 0, "there are no scripts to remove");
                delete projects[_projectId].scripts[projects[_projectId].scriptCount - 1];
                projects[_projectId].scriptCount = projects[_projectId].scriptCount.sub(1);
            }
        
            function updateProjectScriptJSON(uint256 _projectId, string memory _projectScriptJSON) onlyUnlocked(_projectId) onlyArtistOrWhitelisted(_projectId) public {
                projects[_projectId].scriptJSON = _projectScriptJSON;
            }
        
            function updateProjectIpfsHash(uint256 _projectId, string memory _ipfsHash) onlyUnlocked(_projectId) onlyArtistOrWhitelisted(_projectId) public {
                projects[_projectId].ipfsHash = _ipfsHash;
            }
        
            function updateProjectBaseURI(uint256 _projectId, string memory _newBaseURI) onlyArtist(_projectId) public {
                projects[_projectId].projectBaseURI = _newBaseURI;
            }
        
            function updateProjectBaseIpfsURI(uint256 _projectId, string memory _projectBaseIpfsURI) onlyArtist(_projectId) public {
                projects[_projectId].projectBaseIpfsURI = _projectBaseIpfsURI;
            }
        
            function toggleProjectUseIpfsForStatic(uint256 _projectId) onlyArtist(_projectId) public {
                require(!projects[_projectId].dynamic, "can only set static IPFS hash for static projects");
                projects[_projectId].useIpfs = !projects[_projectId].useIpfs;
            }
        
            function toggleProjectIsDynamic(uint256 _projectId) onlyUnlocked(_projectId) onlyArtistOrWhitelisted(_projectId) public {
              require(projects[_projectId].invocations == 0, "Can not switch after a token is minted.");
                if (projects[_projectId].dynamic) {
                    projects[_projectId].useHashString = false;
                } else {
                    projects[_projectId].useHashString = true;
                }
                projects[_projectId].dynamic = !projects[_projectId].dynamic;
            }
        
            function overrideTokenDynamicImageWithIpfsLink(uint256 _tokenId, string memory _ipfsHash) onlyArtist(tokenIdToProjectId[_tokenId]) public {
                staticIpfsImageLink[_tokenId] = _ipfsHash;
            }
        
            function clearTokenIpfsImageUri(uint256 _tokenId) onlyArtist(tokenIdToProjectId[_tokenId]) public {
                delete staticIpfsImageLink[tokenIdToProjectId[_tokenId]];
            }
        
            function projectDetails(uint256 _projectId) view public returns (string memory projectName, string memory artist, string memory description, string memory website, string memory license, bool dynamic) {
                projectName = projects[_projectId].name;
                artist = projects[_projectId].artist;
                description = projects[_projectId].description;
                website = projects[_projectId].website;
                license = projects[_projectId].license;
                dynamic = projects[_projectId].dynamic;
            }
        
            function projectTokenInfo(uint256 _projectId) view public returns (address artistAddress, uint256 pricePerTokenInWei, uint256 invocations, uint256 maxInvocations, bool active, address additionalPayee, uint256 additionalPayeePercentage ,string memory currency, address currencyAddress) {
                artistAddress = projectIdToArtistAddress[_projectId];
                pricePerTokenInWei = projectIdToPricePerTokenInWei[_projectId];
                invocations = projects[_projectId].invocations;
                maxInvocations = projects[_projectId].maxInvocations;
                active = projects[_projectId].active;
                additionalPayee = projectIdToAdditionalPayee[_projectId];
                additionalPayeePercentage = projectIdToAdditionalPayeePercentage[_projectId];
                currency = projectIdToCurrencySymbol[_projectId];
                currencyAddress = projectIdToCurrencyAddress[_projectId];
            }
        
            function projectScriptInfo(uint256 _projectId) view public returns (string memory scriptJSON, uint256 scriptCount, bool useHashString, string memory ipfsHash, bool locked, bool paused) {
                scriptJSON = projects[_projectId].scriptJSON;
                scriptCount = projects[_projectId].scriptCount;
                useHashString = projects[_projectId].useHashString;
                ipfsHash = projects[_projectId].ipfsHash;
                locked = projects[_projectId].locked;
                paused = projects[_projectId].paused;
            }
        
            function projectScriptByIndex(uint256 _projectId, uint256 _index) view public returns (string memory){
                return projects[_projectId].scripts[_index];
            }
        
            function projectURIInfo(uint256 _projectId) view public returns (string memory projectBaseURI, string memory projectBaseIpfsURI, bool useIpfs) {
                projectBaseURI = projects[_projectId].projectBaseURI;
                projectBaseIpfsURI = projects[_projectId].projectBaseIpfsURI;
                useIpfs = projects[_projectId].useIpfs;
            }
        
            function projectShowAllTokens(uint _projectId) public view returns (uint256[] memory){
                return projectIdToTokenIds[_projectId];
            }
        
            function tokensOfOwner(address owner) external view returns (uint256[] memory) {
                return _tokensOfOwner(owner);
            }
        
            function getRoyaltyData(uint256 _tokenId) public view returns (address artistAddress, address additionalPayee, uint256 additionalPayeePercentage, uint256 royaltyFeeByID) {
                artistAddress = projectIdToArtistAddress[tokenIdToProjectId[_tokenId]];
                additionalPayee = projectIdToAdditionalPayee[tokenIdToProjectId[_tokenId]];
                additionalPayeePercentage = projectIdToAdditionalPayeePercentage[tokenIdToProjectId[_tokenId]];
                royaltyFeeByID = projectIdToSecondaryMarketRoyaltyPercentage[tokenIdToProjectId[_tokenId]];
            }
        
            function tokenURI(uint256 _tokenId) external view onlyValidTokenId(_tokenId) returns (string memory) {
                if (bytes(staticIpfsImageLink[_tokenId]).length > 0) {
                    return Strings.strConcat(projects[tokenIdToProjectId[_tokenId]].projectBaseIpfsURI, staticIpfsImageLink[_tokenId]);
                }
        
                if (!projects[tokenIdToProjectId[_tokenId]].dynamic && projects[tokenIdToProjectId[_tokenId]].useIpfs) {
                    return Strings.strConcat(projects[tokenIdToProjectId[_tokenId]].projectBaseIpfsURI, projects[tokenIdToProjectId[_tokenId]].ipfsHash);
                }
        
                return Strings.strConcat(projects[tokenIdToProjectId[_tokenId]].projectBaseURI, Strings.uint2str(_tokenId));
            }
        }