IssueTokenContentManager

Description:

Multi-signature wallet contract requiring multiple confirmations for transaction execution.

Blockchain: Ethereum

Source Code: View Code On The Blockchain

Solidity Source Code:

{{
  "language": "Solidity",
  "sources": {
    "@openzeppelin/contracts/token/ERC721/IERC721.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC721/IERC721.sol)

pragma solidity ^0.8.0;

import "../../utils/introspection/IERC165.sol";

/**
 * @dev Required interface of an ERC721 compliant contract.
 */
interface IERC721 is IERC165 {
    /**
     * @dev Emitted when `tokenId` token is transferred from `from` to `to`.
     */
    event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);

    /**
     * @dev Emitted when `owner` enables `approved` to manage the `tokenId` token.
     */
    event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);

    /**
     * @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets.
     */
    event ApprovalForAll(address indexed owner, address indexed operator, bool approved);

    /**
     * @dev Returns the number of tokens in ``owner``'s account.
     */
    function balanceOf(address owner) external view returns (uint256 balance);

    /**
     * @dev Returns the owner of the `tokenId` token.
     *
     * Requirements:
     *
     * - `tokenId` must exist.
     */
    function ownerOf(uint256 tokenId) external view returns (address owner);

    /**
     * @dev Safely transfers `tokenId` token from `from` to `to`.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `tokenId` token must exist and be owned by `from`.
     * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
     * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
     *
     * Emits a {Transfer} event.
     */
    function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external;

    /**
     * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients
     * are aware of the ERC721 protocol to prevent tokens from being forever locked.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `tokenId` token must exist and be owned by `from`.
     * - If the caller is not `from`, it must have been allowed to move this token by either {approve} or {setApprovalForAll}.
     * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
     *
     * Emits a {Transfer} event.
     */
    function safeTransferFrom(address from, address to, uint256 tokenId) external;

    /**
     * @dev Transfers `tokenId` token from `from` to `to`.
     *
     * WARNING: Note that the caller is responsible to confirm that the recipient is capable of receiving ERC721
     * or else they may be permanently lost. Usage of {safeTransferFrom} prevents loss, though the caller must
     * understand this adds an external call which potentially creates a reentrancy vulnerability.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `tokenId` token must be owned by `from`.
     * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
     *
     * Emits a {Transfer} event.
     */
    function transferFrom(address from, address to, uint256 tokenId) external;

    /**
     * @dev Gives permission to `to` to transfer `tokenId` token to another account.
     * The approval is cleared when the token is transferred.
     *
     * Only a single account can be approved at a time, so approving the zero address clears previous approvals.
     *
     * Requirements:
     *
     * - The caller must own the token or be an approved operator.
     * - `tokenId` must exist.
     *
     * Emits an {Approval} event.
     */
    function approve(address to, uint256 tokenId) external;

    /**
     * @dev Approve or remove `operator` as an operator for the caller.
     * Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller.
     *
     * Requirements:
     *
     * - The `operator` cannot be the caller.
     *
     * Emits an {ApprovalForAll} event.
     */
    function setApprovalForAll(address operator, bool approved) external;

    /**
     * @dev Returns the account approved for `tokenId` token.
     *
     * Requirements:
     *
     * - `tokenId` must exist.
     */
    function getApproved(uint256 tokenId) external view returns (address operator);

    /**
     * @dev Returns if the `operator` is allowed to manage all of the assets of `owner`.
     *
     * See {setApprovalForAll}
     */
    function isApprovedForAll(address owner, address operator) external view returns (bool);
}
"
    },
    "@openzeppelin/contracts/token/ERC721/extensions/IERC721Enumerable.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.5.0) (token/ERC721/extensions/IERC721Enumerable.sol)

pragma solidity ^0.8.0;

import "../IERC721.sol";

/**
 * @title ERC-721 Non-Fungible Token Standard, optional enumeration extension
 * @dev See https://eips.ethereum.org/EIPS/eip-721
 */
interface IERC721Enumerable is IERC721 {
    /**
     * @dev Returns the total amount of tokens stored by the contract.
     */
    function totalSupply() external view returns (uint256);

    /**
     * @dev Returns a token ID owned by `owner` at a given `index` of its token list.
     * Use along with {balanceOf} to enumerate all of ``owner``'s tokens.
     */
    function tokenOfOwnerByIndex(address owner, uint256 index) external view returns (uint256);

    /**
     * @dev Returns a token ID at a given `index` of all the tokens stored by the contract.
     * Use along with {totalSupply} to enumerate all tokens.
     */
    function tokenByIndex(uint256 index) external view returns (uint256);
}
"
    },
    "@openzeppelin/contracts/utils/introspection/IERC165.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol)

pragma solidity ^0.8.0;

/**
 * @dev Interface of the ERC165 standard, as defined in the
 * https://eips.ethereum.org/EIPS/eip-165[EIP].
 *
 * Implementers can declare support of contract interfaces, which can then be
 * queried by others ({ERC165Checker}).
 *
 * For an implementation, see {ERC165}.
 */
interface IERC165 {
    /**
     * @dev Returns true if this contract implements the interface defined by
     * `interfaceId`. See the corresponding
     * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
     * to learn more about how these ids are created.
     *
     * This function call must use less than 30 000 gas.
     */
    function supportsInterface(bytes4 interfaceId) external view returns (bool);
}
"
    },
    "interfaces/IController.sol": {
      "content": "// SPDX-License-Identifier: UNLICENSED
// Copyright 2025 US Fintech LLC and DelNorte Holdings.
// 
// Permission to use, copy, modify, or distribute this software is strictly prohibited
// without prior written consent from both copyright holders.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
// CLAIM, DAMAGES, OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT, OR OTHERWISE,
// ARISING FROM, OUT OF, OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// OFFICIAL DEL NORTE NETWORK COMPONENT 
// Provides immediate membership access to platform at different levels. 
// Required Non US or accredited US registration to swap for DTV token. Registration available within 180 days per terms.delnorte.io . 
// Minimally tesed Conroller Tree for world-wide government administration of, well, anything, including property ownership.
// Designed by Ken Silverman as part of his ElasticTreasury (HUB and SPOKE), PeerTreasury and Controller model. 
// @author Ken Silverman
// This deployment is for Del Norte Holdings, Delaware and US Fintech, LLC NY.  
// Permission to change metadata stored on blockchain explorers and elsewhere granted to:
// Del Norte Holdings, DE only and/or US Fintech, LLC NY independently
pragma solidity 0.8.30;

interface IController {
    struct OfficialEntityStruct {
        string fullNameOfEntityOrLabel;
        string nationalIdOfEntity;
        address pubAddress;
        uint256 blockNumber;
        uint256 blockTimestamp;
        bool active;
        uint256 value;  // Associated value (0-1,000,000 for absolute, or basis points for %, type(uint256).max = no value)
    }

    struct ChainedEntityStruct {
        address entityAddress;
        address parent;
        uint8 type1;     // 0 = 'V' (value/absolute), 1 = 'P' (percentage in basis points)
        uint256 val1;    // Value for type1 (absolute amount or percentage in basis points)
        uint8 type2;     // 0 = 'V' (value/absolute), 1 = 'P' (percentage in basis points)
        uint256 val2;    // Value for type2 (absolute amount or percentage in basis points)
        bool active;
        uint256 blockNumber;
        uint256 blockTimestamp;
        string entityName;  // Human-readable name
        string entityID;    // Additional identifier (tax ID, registration #, etc)
    }

    struct CalculatedEntityAmount {
        address entityAddress;
        uint256 type1Amount;  // Calculated amount for type1 (e.g., transfer fee)
        uint256 type2Amount;  // Calculated amount for type2 (e.g., activation fee)
    }

    // Official Entity Functions
    function addOfficialEntity(string memory, address, string memory, string memory) external returns (bool);
    function addOfficialEntityWithValue(string memory, address, string memory, string memory, uint256) external returns (bool);
    function removeOfficialEntity(string memory, address) external returns (bool);
    function isOfficialEntity(string memory, address) external view returns (bool);
    function isOfficialEntityFast(bytes32, address) external view returns (bool);
    function isOfficialDoubleEntityFast(bytes32, address, bytes32, address, bool) external view returns (bool);
    function isOfficialTripleEntityFast(bytes32, address, bytes32, address, bytes32, address, bool) external view returns (bool);
    function isOfficialQuadrupleEntityFast(bytes32, address,  bytes32, address, bytes32, address, bytes32, address, bool) external view returns (bool);
    function getOfficialEntity(string calldata, address) external view returns (OfficialEntityStruct memory);
    function getAllOfficialEntities(string calldata, bool) external view returns (OfficialEntityStruct[] memory);
    function init(address, string calldata) external;
    
    // Chained Entity Functions
    function addChainedEntity(string memory, address, address, uint8, uint256, uint8, uint256, string memory, string memory) external returns (bool);
    function removeChainedEntity(string calldata, address, bool) external returns (bool);
    function isChainedEntity(string calldata, address) external view returns (bool);
    function isChainedEntityFast(bytes32, address) external view returns (bool);
    function getChainedEntity(string calldata, address) external view returns (ChainedEntityStruct memory);
    function getActiveChainedEntities(string calldata) external view returns (ChainedEntityStruct[] memory);
    function calculateChainedAmounts(string calldata, uint256, bool, uint256, uint8) external view returns (CalculatedEntityAmount[] memory, uint256, uint256);
    
    // Constants
    function BASIS_POINTS_DIVISOR() external view returns (uint256);
}
"
    },
    "contracts/IssueTokenContentManager.sol": {
      "content": "// SPDX-License-Identifier: UNLICENSED\r
// Copyright 2025 US Fintech LLC\r
// \r
// Permission to use, copy, modify, or distribute this software is strictly prohibited\r
// without prior written consent from either copyright holder.\r
// \r
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\r
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR\r
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY\r
// CLAIM, DAMAGES, OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT, OR OTHERWISE,\r
// ARISING FROM, OUT OF, OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\r
\r
pragma solidity 0.8.30;\r
\r
import "../interfaces/IController.sol";\r
import "@openzeppelin/contracts/token/ERC721/IERC721.sol";\r
import "@openzeppelin/contracts/token/ERC721/extensions/IERC721Enumerable.sol";\r
import "@openzeppelin/contracts/utils/introspection/IERC165.sol";\r
\r
/// @title IssueTokenContentManager\r
/// @notice Manages NFT pointers, document hashes, and official strings for IssueToken\r
/// @dev This contract is permanently tied to a single IssueToken instance\r
/// @author Ken Silverman\r
contract IssueTokenContentManager {\r
    \r
    /* ========== IMMUTABLES ========== */\r
    \r
    /// @notice The Controller for entity authorization\r
    address public immutable controller;\r
    \r
    /* ========== CONSTANTS ========== */\r
    \r
    uint256 public constant MAX_STRING_LENGTH = 1024; // Max chars for official strings (gas limit)\r
    \r
    // NFT ownership status constants\r
    uint8 constant NFT_OWNED_NO = 0;\r
    uint8 constant NFT_OWNED_YES = 1;\r
    uint8 constant NFT_OWNED_UNCHECKED = 2;\r
    \r
    bytes32 public constant KECCAK_TOKEN_ADMIN = keccak256(bytes("TokenAdmin"));\r
    bytes32 public constant KECCAK_TREASURY_ADMIN = keccak256(bytes("TreasuryAdmin"));\r
    \r
    /* ========== STRUCTS ========== */\r
    \r
    /// @notice Structure for token info URL history with content hashes\r
    struct TokenInfoHistory {\r
        string baseUrl;\r
        bytes32 contentHash;\r
        uint256 timestamp;\r
    }\r
    \r
    /// @notice Structure for NFT transaction records\r
    struct NFTTransaction {\r
        address nftAddress;\r
        uint256 tokenId;\r
        uint8 transactionType;\r
        uint8 isOwned;        // 0=NO, 1=YES, 2=UNCHECKED\r
        bool isActive;\r
        uint256 blockNumber;\r
        uint256 blockTimestamp;\r
    }\r
    \r
    /// @notice Structure for NFT record with full history\r
    struct NFTRecord {\r
        address nftAddress;\r
        bool everAdded;\r
        NFTTransaction[] transactions;\r
    }\r
    \r
    /// @notice Structure for official strings (emails, notes, etc.)\r
    struct OfficialString {\r
        string content;\r
        bytes32 contentHash;\r
        uint256 timestamp;\r
        bool isActive;\r
    }\r
    \r
    /* ========== STORAGE ========== */\r
    \r
    // Token Info URL & Hash Tracking\r
    string public tokenInfoUrl;\r
    TokenInfoHistory[] public tokenInfoHistory;\r
    mapping(bytes32 => uint256) private contentHashToIndex;\r
    \r
    // NFT Tracking\r
    mapping(address => NFTRecord) private nftRecords;\r
    address[] private allNFTAddresses;\r
    \r
    // Official Strings\r
    mapping(bytes32 => OfficialString) private officialStrings;\r
    bytes32[] private officialStringHashes;\r
    \r
    /* ========== EVENTS ========== */\r
    \r
    // Token Info Events\r
    event TokenInfoUrlUpdated(string newUrl, bytes32 indexed contentHash, address indexed updatedBy, uint256 timestamp);\r
    \r
    // NFT Events\r
    event NFTTransactionAdded(address indexed nftAddress, uint256 indexed tokenId, uint8 transactionType, uint8 isOwned, bool isActive, uint256 blockNumber, uint256 blockTimestamp);\r
    event NFTOwnershipUpdated(address indexed nftAddress, uint256 indexed tokenId, uint8 oldIsOwned, uint8 newIsOwned);\r
    \r
    // Official String Events\r
    event OfficialStringAdded(bytes32 indexed contentHash, string content, uint256 timestamp);\r
    event OfficialStringDeactivated(bytes32 indexed contentHash, uint256 timestamp);\r
    \r
    /* ========== MODIFIERS ========== */\r
    \r
    modifier onlyTokenAdmin() {\r
        require(IController(controller).isOfficialEntityFast(KECCAK_TOKEN_ADMIN, msg.sender), "Not TokenAdmin");\r
        _;\r
    }\r
    \r
    modifier onlyTreasuryAdmin() {\r
        require(IController(controller).isOfficialEntityFast(KECCAK_TREASURY_ADMIN, msg.sender), "Not TreasuryAdmin");\r
        _;\r
    }\r
    \r
    /* ========== CONSTRUCTOR ========== */\r
    \r
    /**\r
     * @notice Creates a content manager for an IssueToken\r
     * @param _controller The Controller address for authorization\r
     * @param _tokenInfoBaseUrl Initial token info base URL\r
     * @param _tokenInfoHash Initial content hash (SHA-256 of document at URL)\r
     */\r
    constructor(\r
        address _controller,\r
        string memory _tokenInfoBaseUrl,\r
        bytes32 _tokenInfoHash\r
    ) {\r
        require(_controller != address(0), "Invalid controller address");\r
        require(bytes(_tokenInfoBaseUrl).length > 0, "URL cannot be empty");\r
        require(_tokenInfoHash != bytes32(0), "Hash cannot be zero");\r
        \r
        controller = _controller;\r
        tokenInfoUrl = _tokenInfoBaseUrl;\r
        \r
        // Initialize history with first entry\r
        tokenInfoHistory.push(TokenInfoHistory({\r
            baseUrl: _tokenInfoBaseUrl,\r
            contentHash: _tokenInfoHash,\r
            timestamp: block.timestamp\r
        }));\r
        \r
        contentHashToIndex[_tokenInfoHash] = 0;\r
        \r
        emit TokenInfoUrlUpdated(_tokenInfoBaseUrl, _tokenInfoHash, msg.sender, block.timestamp);\r
    }\r
    \r
    /* ========== TOKEN INFO URL & HASH FUNCTIONS ========== */\r
    \r
    /**\r
     * @notice Updates the token info base URL and adds to immutable history\r
     * @param _newTokenInfoBaseUrl New base URL (document must be uploaded first)\r
     * @param _newContentHash SHA-256 hash of document at new URL\r
     */\r
    function setOfficialTokenInfoBaseUrl(string memory _newTokenInfoBaseUrl, bytes32 _newContentHash) external onlyTreasuryAdmin {\r
        require(bytes(_newTokenInfoBaseUrl).length > 0, "URL cannot be empty");\r
        require(_newContentHash != bytes32(0), "Hash cannot be zero");\r
        \r
        tokenInfoUrl = _newTokenInfoBaseUrl;\r
        \r
        // Add to history\r
        uint256 newIndex = tokenInfoHistory.length;\r
        tokenInfoHistory.push(TokenInfoHistory({\r
            baseUrl: _newTokenInfoBaseUrl,\r
            contentHash: _newContentHash,\r
            timestamp: block.timestamp\r
        }));\r
        \r
        contentHashToIndex[_newContentHash] = newIndex;\r
        \r
        emit TokenInfoUrlUpdated(_newTokenInfoBaseUrl, _newContentHash, msg.sender, block.timestamp);\r
    }\r
    \r
    /**\r
     * @notice Returns full history of token info URLs and hashes\r
     * @return Array of all historical token info entries\r
     */\r
    function getBaseUrlHistory() external view returns (TokenInfoHistory[] memory) {\r
        return tokenInfoHistory;\r
    }\r
    \r
    /**\r
     * @notice Confirms if a content hash exists in history\r
     * @param hash The content hash to verify\r
     * @return trailNumber Index in history array\r
     * @return timeStamp When this hash was added\r
     */\r
    function confirmHash(bytes32 hash) external view returns (uint256 trailNumber, uint256 timeStamp) {\r
        require(contentHashToIndex[hash] < tokenInfoHistory.length || \r
                (contentHashToIndex[hash] == 0 && tokenInfoHistory.length > 0 && tokenInfoHistory[0].contentHash == hash),\r
                "Hash not found");\r
        \r
        uint256 index = contentHashToIndex[hash];\r
        TokenInfoHistory memory entry = tokenInfoHistory[index];\r
        \r
        return (index, entry.timestamp);\r
    }\r
    \r
    /**\r
     * @notice Returns the official token information URL with controller address\r
     * @dev Constructs full URL as: https://[tokenInfoUrl]/[controller_address]\r
     *      Handles trailing slashes automatically to prevent double slashes\r
     * @return Full URL string including protocol, domain, and the Controller's address\r
     * \r
     * ⚠️ SECURITY PROTOCOL:\r
     * - This URL is the ONLY official source for token offering documents\r
     * - Document hash MUST match on-chain hash (verify via confirmHash)\r
     * - Domain trustworthiness is buyer's responsibility\r
     * - No content at URL = token is NOT VALID\r
     * - Content present = domain owner is FULLY responsible\r
     */\r
    function getOfficialTokenInfoURL() external view returns (string memory) {\r
        bytes memory baseUrlBytes = bytes(tokenInfoUrl);\r
        \r
        // Check if baseUrl ends with '/' (0x2f in ASCII)\r
        bool hasTrailingSlash = baseUrlBytes.length > 0 && baseUrlBytes[baseUrlBytes.length - 1] == 0x2f;\r
        \r
        if (hasTrailingSlash) {\r
            // Base URL already has trailing slash, don't add another\r
            return string(abi.encodePacked("https://", tokenInfoUrl, _toHexString(controller)));\r
        } else {\r
            // Add slash before controller address\r
            return string(abi.encodePacked("https://", tokenInfoUrl, "/", _toHexString(controller)));\r
        }\r
    }\r
    \r
    /// @notice Internal helper to convert address to hex string (lowercase with 0x prefix)\r
    function _toHexString(address addr) internal pure returns (string memory) {\r
        bytes memory hexChars = "0123456789abcdef";\r
        bytes memory result = new bytes(42);\r
        result[0] = '0';\r
        result[1] = 'x';\r
        for (uint256 i = 0; i < 20; i++) {\r
            uint8 b = uint8(uint160(addr) >> (8 * (19 - i)));\r
            result[2 + i * 2] = hexChars[b >> 4];\r
            result[3 + i * 2] = hexChars[b & 0x0f];\r
        }\r
        return string(result);\r
    }\r
    \r
    /* ========== NFT MANAGEMENT FUNCTIONS ========== */\r
    \r
    /**\r
     * @notice Add an NFT transaction record\r
     * @param nftAddress Address of the NFT contract (must implement ERC721)\r
     * @param tokenId Specific token ID (0 = unknown or not applicable)\r
     * @param transactionType Transaction type (0-10 defined, 11-255 reserved)\r
     * @param isActive Whether this transaction makes the NFT relationship active\r
     */\r
    function addNFTTransaction(address nftAddress, uint256 tokenId, uint8 transactionType, bool isActive) external onlyTokenAdmin {\r
        require(nftAddress != address(0), "Invalid NFT address");\r
        \r
        // Verify ERC721 interface\r
        try IERC165(nftAddress).supportsInterface(type(IERC721).interfaceId) returns (bool supported) {\r
            require(supported, "Not ERC721");\r
        } catch {\r
            revert("Invalid ERC721 contract");\r
        }\r
        \r
        NFTRecord storage record = nftRecords[nftAddress];\r
        \r
        if (!record.everAdded) {\r
            record.nftAddress = nftAddress;\r
            record.everAdded = true;\r
            allNFTAddresses.push(nftAddress);\r
        }\r
        \r
        record.transactions.push(NFTTransaction({\r
            nftAddress: nftAddress,\r
            tokenId: tokenId,\r
            transactionType: transactionType,\r
            isOwned: NFT_OWNED_UNCHECKED,\r
            isActive: isActive,\r
            blockNumber: block.number,\r
            blockTimestamp: block.timestamp\r
        }));\r
        \r
        emit NFTTransactionAdded(nftAddress, tokenId, transactionType, NFT_OWNED_UNCHECKED, isActive, block.number, block.timestamp);\r
    }\r
    \r
    /**\r
     * @notice Get all NFT addresses associated with this token\r
     */\r
    function getAllNFTAddresses() external view returns (address[] memory) {\r
        return allNFTAddresses;\r
    }\r
    \r
    /**\r
     * @notice Get transaction history for a specific NFT\r
     */\r
    function getNFTTransactions(address nftAddress) external view returns (NFTTransaction[] memory) {\r
        return nftRecords[nftAddress].transactions;\r
    }\r
    \r
    /**\r
     * @notice Get current active status of an NFT\r
     */\r
    function getNFTStatus(address nftAddress) external view returns (bool isActive, uint8 lastTransactionType) {\r
        NFTRecord storage record = nftRecords[nftAddress];\r
        if (record.transactions.length == 0) {\r
            return (false, 0);\r
        }\r
        NFTTransaction storage lastTx = record.transactions[record.transactions.length - 1];\r
        return (lastTx.isActive, lastTx.transactionType);\r
    }\r
    \r
    /**\r
     * @notice Get all currently active NFTs\r
     */\r
    function getActiveNFTs() external view returns (address[] memory) {\r
        uint256 activeCount = 0;\r
        for (uint256 i = 0; i < allNFTAddresses.length; i++) {\r
            NFTRecord storage record = nftRecords[allNFTAddresses[i]];\r
            if (record.transactions.length > 0) {\r
                NFTTransaction storage lastTx = record.transactions[record.transactions.length - 1];\r
                if (lastTx.isActive) {\r
                    activeCount++;\r
                }\r
            }\r
        }\r
        \r
        address[] memory activeNFTs = new address[](activeCount);\r
        uint256 index = 0;\r
        for (uint256 i = 0; i < allNFTAddresses.length; i++) {\r
            NFTRecord storage record = nftRecords[allNFTAddresses[i]];\r
            if (record.transactions.length > 0) {\r
                NFTTransaction storage lastTx = record.transactions[record.transactions.length - 1];\r
                if (lastTx.isActive) {\r
                    activeNFTs[index++] = allNFTAddresses[i];\r
                }\r
            }\r
        }\r
        \r
        return activeNFTs;\r
    }\r
    \r
    /**\r
     * @notice Get all NFTs that a token contract actually owns on-chain\r
     * @param tokenAddress The token contract to check ownership for\r
     * @param updateOwnershipStatus If true, updates all records with actual ownership\r
     */\r
    function getOfficiallyOwnedNFTs(address tokenAddress, bool updateOwnershipStatus) external returns (NFTTransaction[] memory ownedNFTs) {\r
        require(tokenAddress != address(0), "Invalid token address");\r
        if (updateOwnershipStatus) {\r
            for (uint256 i = 0; i < allNFTAddresses.length; i++) {\r
                address nftContract = allNFTAddresses[i];\r
                NFTRecord storage record = nftRecords[nftContract];\r
                \r
                try IERC721(nftContract).balanceOf(tokenAddress) returns (uint256 balance) {\r
                    bool actuallyOwned = (balance > 0);\r
                    \r
                    for (uint256 j = 0; j < record.transactions.length; j++) {\r
                        NFTTransaction storage nftTx = record.transactions[j];\r
                        uint8 oldIsOwned = nftTx.isOwned;\r
                        uint8 newIsOwned = actuallyOwned ? NFT_OWNED_YES : NFT_OWNED_NO;\r
                        \r
                        if (oldIsOwned != newIsOwned) {\r
                            nftTx.isOwned = newIsOwned;\r
                            emit NFTOwnershipUpdated(nftContract, nftTx.tokenId, oldIsOwned, newIsOwned);\r
                        }\r
                        \r
                        if (actuallyOwned && nftTx.tokenId == 0) {\r
                            try IERC165(nftContract).supportsInterface(type(IERC721Enumerable).interfaceId) returns (bool supportsEnumerable) {\r
                                if (supportsEnumerable && balance > 0) {\r
                                    try IERC721Enumerable(nftContract).tokenOfOwnerByIndex(tokenAddress, 0) returns (uint256 tokenId) {\r
                                        nftTx.tokenId = tokenId;\r
                                    } catch {}\r
                                }\r
                            } catch {}\r
                        }\r
                    }\r
                } catch {\r
                    for (uint256 j = 0; j < record.transactions.length; j++) {\r
                        NFTTransaction storage nftTx = record.transactions[j];\r
                        if (nftTx.isOwned != NFT_OWNED_NO) {\r
                            uint8 oldIsOwned = nftTx.isOwned;\r
                            nftTx.isOwned = NFT_OWNED_NO;\r
                            emit NFTOwnershipUpdated(nftContract, nftTx.tokenId, oldIsOwned, NFT_OWNED_NO);\r
                        }\r
                    }\r
                }\r
            }\r
        }\r
        \r
        // Count owned\r
        uint256 ownedCount = 0;\r
        for (uint256 i = 0; i < allNFTAddresses.length; i++) {\r
            NFTRecord storage record = nftRecords[allNFTAddresses[i]];\r
            for (uint256 j = 0; j < record.transactions.length; j++) {\r
                if (record.transactions[j].isOwned == NFT_OWNED_YES) {\r
                    ownedCount++;\r
                }\r
            }\r
        }\r
        \r
        // Build array\r
        ownedNFTs = new NFTTransaction[](ownedCount);\r
        uint256 index = 0;\r
        for (uint256 i = 0; i < allNFTAddresses.length; i++) {\r
            NFTRecord storage record = nftRecords[allNFTAddresses[i]];\r
            for (uint256 j = 0; j < record.transactions.length; j++) {\r
                if (record.transactions[j].isOwned == NFT_OWNED_YES) {\r
                    ownedNFTs[index] = record.transactions[j];\r
                    index++;\r
                }\r
            }\r
        }\r
        \r
        return ownedNFTs;\r
    }\r
    \r
    /* ========== OFFICIAL STRING FUNCTIONS ========== */\r
    \r
    /**\r
     * @notice Store an official string (email confirmation, note, etc.)\r
     * @param content The string to store (max 1024 chars to limit gas)\r
     * @dev Gas cost: ~21K base + ~100 gas/char. 1024 chars ≈ 121K gas total\r
     */\r
    function addOfficialString(string memory content) external onlyTokenAdmin {\r
        require(bytes(content).length > 0 && bytes(content).length <= MAX_STRING_LENGTH, \r
                "String must be 1-1024 characters");\r
        \r
        bytes32 hash = keccak256(bytes(content));\r
        require(officialStrings[hash].timestamp == 0, "String already exists");\r
        \r
        officialStrings[hash] = OfficialString({\r
            content: content,\r
            contentHash: hash,\r
            timestamp: block.timestamp,\r
            isActive: true\r
        });\r
        \r
        officialStringHashes.push(hash);\r
        emit OfficialStringAdded(hash, content, block.timestamp);\r
    }\r
    \r
    /**\r
     * @notice Verify if a string is official and return its details\r
     * @param content The string to verify\r
     * @return The OfficialString struct (reverts if not found)\r
     */\r
    function isOfficialString(string memory content) external view returns (OfficialString memory) {\r
        bytes32 hash = keccak256(bytes(content));\r
        require(officialStrings[hash].timestamp != 0, "String not found");\r
        return officialStrings[hash];\r
    }\r
    \r
    /**\r
     * @notice Deactivate an official string (keeps proof it existed)\r
     * @param content The string to deactivate\r
     */\r
    function makeOfficialStringInactive(string memory content) external onlyTokenAdmin {\r
        bytes32 hash = keccak256(bytes(content));\r
        require(officialStrings[hash].timestamp != 0, "String not found");\r
        officialStrings[hash].isActive = false;\r
        emit OfficialStringDeactivated(hash, block.timestamp);\r
    }\r
    \r
    /**\r
     * @notice Get all official string hashes\r
     */\r
    function getAllOfficialStringHashes() external view returns (bytes32[] memory) {\r
        return officialStringHashes;\r
    }\r
}\r
\r
"
    }
  },
  "settings": {
    "optimizer": {
      "enabled": true,
      "runs": 10000
    },
    "viaIR": true,
    "outputSelection": {
      "*": {
        "*": [
          "evm.bytecode",
          "evm.deployedBytecode",
          "devdoc",
          "userdoc",
          "metadata",
          "abi"
        ]
      }
    }
  }
}}

Tags:
ERC721, ERC165, Multisig, Non-Fungible, Multi-Signature, Factory|addr:0x1af2fcc2fc4b6bbd0f747017158e55dcdbdac4b3|verified:true|block:23721884|tx:0x8fdedb6455e9bf2a2e6ad47219f175b8c9700465f5a57ae792b3f35ab0dd5836|first_check:1762247178

Submitted on: 2025-11-04 10:06:20

Comments

Log in to comment.

No comments yet.