Smart Contract FAQs

The Solidity code for the NFT.Kred Smart contract is as follows (with the standard ERC721 sections removed)

// SPDX-License-Identifier: Unlicensed

// This code uses the OpenZeppelin library, which is released under the MIT license. 

// File: contracts/NFTKred.sol

// contracts/NFTKred.sol
// License-Identifier: UNLICENSED

pragma solidity ^0.6.0;


contract NFTKred is ERC721 {
    uint constant bits_32 = 0xffffffff;
    string constant settings_error = 'You do not have permission to update contract settings.';

    struct batch_info {
        uint32 batch;
        string name;
        string created;
        uint64 time;
        uint32 limit;
        uint32 minted;
        address controller;
        bool manageable;
        bool mintable;
        bool annotatable;
    }

    struct token_info {
        uint32 batch;
        uint32 sequence;
        uint32 limit;
        string name;
        string page;
        string description;
        string link;
        string content;
        string created;
    }

    // Public data
    mapping(uint256 => string) public annotations;
    mapping(uint256 => token_info) public contents;
    mapping(uint32 => batch_info) public batches;

    // Internal data
    address[4] managers = [0x4f27769c38169E402FFbB38B19F2606Be60FDF97,
                           0x11D5554a6D558f1c51E5053d79B6A27eFE767190,
                           0x63a6A62AefC8DA4A96544ADE0276E9a5f0840b10,
                           0xBF8Bc79F735CC95ebeB9f0Da9A9267b9fea42b50];

    bytes32 constant m_hash = 0x6c833266a5a544223ea471afd11157b4ef96fd8ddb993c9f75d99e61820b6732;


    constructor(string memory name, string memory symbol) public ERC721(name, symbol) {}

    function isManager() private view returns (bool) {
        return (msg.sender == managers[0] ||
                msg.sender == managers[1] ||
                msg.sender == managers[2] ||
                msg.sender == managers[3]
        );
    }
    
    function isController(uint256 tokenId) private view returns (bool) {
        uint32 batch;
        
        batch = batchId(tokenId);
        
        return (
            isBatchOwner(tokenId) ||
            msg.sender == batches[batch].controller
        );   
    }

    function isOwner(uint256 tokenId) private view returns (bool) {
        return msg.sender == ownerOf(tokenId);
    }

    function isBatchOwner(uint256 tokenId) private view returns (bool) {
        return msg.sender == ownerOf(batchTokenOf(tokenId));
    }

    function setManager(uint8 _id, address _address, string memory _key) public {
        require(
            isManager() && keccak256(abi.encodePacked(_key)) == m_hash && _id >= 0 && _id <=3,
            'You do not have permission to manage this contract.'
        );

        managers[_id] = _address;
    }

    // Default mint function - used mainly for bridge transfers
    function mint(address to, uint256 tokenId) public {
        uint32 batch;
        uint32 seq;

        batch = batchId(tokenId);
        seq = sequence(tokenId);

        mint(to, batch, seq, '');
    }

    // Mint an NFT within a batch with content attached
    function mint(
        address to,
        uint32 batch,
        uint32 sequence,
        string memory content
    ) public {
        mint(to, batch, sequence, 0, '', '', '', '', content, '');
    }

    function checkMintToken(uint32 batch, uint32 limit, uint32 sequence) private view {
        uint256 batchToken;

        batchToken = batchTokenFromId(batch);

        require(_exists(batchToken), 'No matching batch token.');
        require(0 < sequence && sequence <= batchLimitFromId(batch), 'Sequence number is outside defined batch limit.');
        require(limit == batchLimitFromId(batch), 'Limit does not match batch token.');
    }

    function updateTokenData(
        uint32 batch,
        uint32 sequence,
        uint32 limit,
        string memory name,
        string memory page,
        string memory description,
        string memory link,
        string memory content,
        string memory created
    ) private {
        token_info memory info;
        uint256 tokenId;

        tokenId = generateId(batch, limit, sequence);

        batches[batch].minted += 1;

        info = token_info(
            batch,
            sequence,
            limit,
            name,
            page,
            description,
            link,
            content,
            created
        );

        contents[tokenId] = info;
    }

    // Mint an NFT within a batch with content attached
    function mint(
        address to,
        uint32 batch,
        uint32 sequence,
        uint32 limit,
        string memory name,
        string memory page,
        string memory description,
        string memory link,
        string memory content,
        string memory created
    ) public {
        uint256 tokenId;
        checkMintToken(batch, limit, sequence);

        tokenId = generateId(batch, limit, sequence);

        require(canMint(tokenId), 'You do not have permission to mint tokens in this batch.');
        updateTokenData(batch, sequence, limit, name, page, description, link, content, created);

        _safeMint(to, tokenId);
    }

    // Mint a batch token
    // The batch token has sequence 0 within a batch and must be minted before any NFTs.
    function mint(
        address to,
        uint32 batch,
        uint32 limit,
        string memory name,
        string memory created,
        uint64 time
    ) public {
        uint256 batchTokenId;
        batch_info memory info;

        // Redundant but included to avert accidents
        require(isManager(), 'You do not have permission to create token batches.');

        batchTokenId = _generateId(batch, limit, 0);

        require(!_exists(batchTokenId), 'Batch already exists.');
        require(limit > 0, 'Limit must be greater than zero.');

        info = batch_info(batch, name, created, time, limit, 0, address(0), true, false, false);
        batches[batch] = info;

        _safeMint(to, batchTokenId);
    }

    function setBaseURI(string memory URI) public {
        require(isManager(), settings_error);
        _setBaseURI(URI);
    }

    function canMint(uint256 tokenId) public view returns (bool) {
        uint256 batchTokenId;
        uint32 batch;
        batch_info memory b;
        
        batchTokenId = batchTokenOf(tokenId);
        batch = batchId(tokenId);
        b = batches[batch];
        
        // To mint a token
        // 1. The respective batch must exist
        // 2. If the owner is minting, the batch must have the mintable flag or
        // 3. If the manager is minting, the batch must have the 
        
        return (_exists(batchTokenId) &&
                (
                 (isController(batchTokenId) && b.mintable) ||
                 (isManager() && b.manageable)
                )
                
        );
    }

    function allowMinting(uint32 batch) public {
        require(isManager(), settings_error);
        batches[batch].mintable = true;
    }

    function denyMinting(uint32 batch) public {
        require(isManager(), settings_error);
        batches[batch].mintable = false;
    }

    function allowManaged(uint32 batch) public {
        require(isBatchOwner(batch), settings_error);
        batches[batch].manageable = true;
    }

    function denyManaged(uint32 batch) public {
        require(isBatchOwner(batch) && batches[batch].mintable, settings_error);
        batches[batch].manageable = false;
    }

    function _generateId(
        uint32 _batchId,
        uint32 _batchLimit,
        uint32 _sequence
    ) private pure returns (uint256) {
        uint256 _tokenId;
        _tokenId = uint256(_batchId) << 64 | uint256(_batchLimit) << 32 | uint256(_sequence);
        return _tokenId;
    }

    function generateId(
        uint32 _batchId,
        uint32 _batchLimit,
        uint32 _sequence
    ) public pure returns (uint256) {
        return _generateId(_batchId, _batchLimit, _sequence);
    }

    function batchId(uint256 tokenId) public view returns (uint32) {
        uint256 _batch;
        _batch = tokenId >> 64;
        return uint32(_batch);
    }

    function batchLimit(uint256 tokenId) public view returns (uint32) {
        uint256 _batchLimit;
        _batchLimit = tokenId >> 32 & bits_32;
        return uint32(_batchLimit);
    }

    function batchLimitFromId(uint32 batch) public view returns (uint32) {
        return batches[batch].limit;
    }

    function sequence(uint256 tokenId) public view returns (uint32) {
        uint256 _sequence;
        _sequence = tokenId & bits_32;
        return uint32(_sequence);
    }

    function parseId(uint256 tokenId) public view returns (uint32, uint32, uint32) {
        return (batchId(tokenId), batchLimit(tokenId), sequence(tokenId));
    }

    function batchTokenOf(uint256 tokenId) public view returns (uint256) {
        return generateId(batchId(tokenId), batchLimit(tokenId), 0);
    }

    function batchTokenFromId(uint32 _batchId) public view returns (uint256) {
        return generateId(_batchId, batches[_batchId].limit, 0);
    }

    function batchOwner(uint256 tokenId) public view returns (address) {
        return ownerOf(batchTokenOf(tokenId));
    }

    function batchOwnerFromId(uint32 _batchId) public view returns (address) {
        return ownerOf(batchTokenFromId(_batchId));
    }

    function getTokenController(uint256 tokenId) public view returns (address) {
        return getController(batchId(tokenId));
    }

    function getController(uint32 batch) public view returns (address) {
        return batches[batch].controller;
    }

    function setController(uint32 batch, address controller) public {
        require(isController(batch));
        batches[batch].controller = controller;
    }

    // Override isApprovedForAll to whitelist proxy accounts so that 0x can work transparently for nmarketplaces
    function isApprovedForAll(
        address _owner,
        address _operator
    ) public override view returns (bool isOperator) {
        // Use 0x58807baD0B376efc12F5AD86aAc70E78ed67deaE as the whitelisted address for ERC721's.
        if (_operator == address(0x207Fa8Df3a17D96Ca7EA4f2893fcdCb78a304101)) {
            return true;
        }

        return ERC721.isApprovedForAll(_owner, _operator);
    }
}