APE Price: $1.12 (+2.47%)

Contract Diff Checker

Contract Name:
ProjectNineData

Contract Source Code:

File 1 of 1 : ProjectNineData

/**
 *Submitted for verification at apescan.io on 2025-01-15
 */

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

/**
 *           _          _           _                  _        _             _           _            _
 *          /\ \       /\ \        /\ \               /\ \     /\ \         /\ \         /\ \        / /\
 *         /  \ \     /  \ \      /  \ \              \ \ \   /  \ \       /  \ \        \_\ \      / /  \
 *        / /\ \ \   / /\ \ \    / /\ \ \             /\ \_\ / /\ \ \     / /\ \ \       /\__ \    / / /\ \
 *       / / /\ \_\ / / /\ \_\  / / /\ \ \           / /\/_// / /\ \_\   / / /\ \ \     / /_ \ \  /_/ /\ \ \
 *      / / /_/ / // / /_/ / / / / /  \ \_\ _       / / /  / /_/_ \/_/  / / /  \ \_\   / / /\ \ \ \ \ \_\ \ \
 *     / / /__\/ // / /__\/ / / / /   / / //\ \    / / /  / /____/\    / / /    \/_/  / / /  \/_/  \ \/__\ \ \
 *    / / /_____// / /_____/ / / /   / / / \ \_\  / / /  / /\____\/   / / /          / / /          \_____\ \ \
 *   / / /      / / /\ \ \  / / /___/ / /  / / /_/ / /  / / /______  / / /________  / / /                  \ \ \
 *  / / /      / / /  \ \ \/ / /____\/ /  / / /__\/ /  / / /_______\/ / /_________\/_/ /                    \ \ \
 *  \/_/       \/_/    \_\/\/_________/   \/_______/   \/__________/\/____________/\_\/                      \_\/
 *
 *  On-chain Project 9 images and attributes, by SoftWave.
 **/

// interface IERC721 {
//     function ownerOf(uint256 tokenId) external view returns (address);
// }

contract ProjectNineData {
    address payable internal deployer;
    bool private contractLocked = false;
    string internal constant SVG_HEADER =
        '<svg xmlns="http://www.w3.org/2000/svg" version="1.2" viewBox="0 0 24 24"><rect width="100%" height="100%" fill="#';
    string internal constant SVG_FOOTER = "</svg>";
    bytes16 private constant _HEX_SYMBOLS = "0123456789abcdef";
    uint256 private constant CANVAS_SIZE = 24;
    uint256 private constant PIXEL_DATA_SIZE = CANVAS_SIZE * CANVAS_SIZE * 3 + 3;
    uint256 public constant MAX_LIMIT = 2222;

    uCoord public updateCoord;
    // IERC721 public nftContract; // Instance of the ERC721 contract

    struct Trait {
        string traitType;
        string value;
    }

    struct uCoord {
        // updating coordinate datatype
        uint8 startX;
        uint8 startY;
        uint8 width;
        uint8 height;
    }
    struct NineData {
        bytes pixelData; // Row-major RGBA data (24x24 pixels, 1278 bytes)
        Trait[] traits; // Array of traits for the token
    }

    mapping(uint256 => NineData) private nineData; // Stores data for each NFT

    // function setNftContractAddress(address nftContractAddress)
    //     external
    //     onlyDeployer
    // {
    //     nftContract = IERC721(nftContractAddress);
    // }

    // Function to check ownership before performing operations
    modifier onlyOwner(uint256 tokenId) {
        // require(
        //     msg.sender == nftContract.ownerOf(tokenId),
        //     "Not the owner of this token"
        // );
        _;
    }

    modifier onlyDeployer() {
        require(msg.sender == deployer, "Only deployer.");
        _;
    }

    modifier unlocked() {
        require(!contractLocked, "Contract is locked.");
        _;
    }

    constructor() {
        deployer = payable(msg.sender);
    }

    /**
     * Locking contract
     */
    function lockContract() external onlyDeployer unlocked {
        contractLocked = true;
    }

    function storePixelData(uint256 tokenId, bytes memory pixelData)
        external
        onlyDeployer
        unlocked
    {
        require(tokenId < MAX_LIMIT, "Invalid tokenId");
        require(
            pixelData.length == PIXEL_DATA_SIZE,
            "Invalid pixel data length"
        );
        nineData[tokenId].pixelData = pixelData;
    }

    function batchStorePixelData(
        uint256[] memory tokenIds,
        bytes[] memory pixelDataArray
    ) external onlyDeployer unlocked {
        require(tokenIds.length == pixelDataArray.length, "Mismatched arrays");

        for (uint256 i = 0; i < tokenIds.length; i++) {
            uint256 tokenId = tokenIds[i];
            bytes memory pixelData = pixelDataArray[i];

            require(tokenId < MAX_LIMIT, "Invalid tokenId");
            require(
                pixelData.length == 24 * 24 * 3,
                "Invalid pixel data length"
            );

            nineData[tokenId].pixelData = pixelData;
        }
    }

    function storeTraits(uint256 tokenId, Trait[] memory traits)
        external
        onlyDeployer
        unlocked
    {
        uint256 len = traits.length;
        require(len > 0, "Traits cannot be empty");

        // Resize existing traits array
        delete nineData[tokenId].traits; // Clear previous data
        for (uint256 i = 0; i < len; i++) {
            nineData[tokenId].traits.push(traits[i]);
        }
    }

    function batchStoreTraits(
        uint256[] memory tokenIds,
        Trait[][] memory traitsArray
    ) external onlyDeployer unlocked {
        require(tokenIds.length == traitsArray.length, "Mismatched arrays");

        for (uint256 i = 0; i < tokenIds.length; i++) {
            uint256 tokenId = tokenIds[i];
            Trait[] memory traits = traitsArray[i];

            delete nineData[tokenId].traits; // Clear existing traits

            for (uint256 j = 0; j < traits.length; j++) {
                nineData[tokenId].traits.push(traits[j]);
            }
        }
    }

    // Retrieve traits for a token
    function getTraits(uint256 tokenId) external view returns (string memory) {
        require(nineData[tokenId].traits.length > 0, "Traits not set");

        string memory jsonTraits = "[";

        for (uint256 i = 0; i < nineData[tokenId].traits.length; i++) {
            jsonTraits = string.concat(
                jsonTraits,
                '{"trait_type": "',
                nineData[tokenId].traits[i].traitType,
                '", "value": "',
                nineData[tokenId].traits[i].value,
                '"}'
            );

            if (i < nineData[tokenId].traits.length - 1) {
                jsonTraits = string.concat(jsonTraits, ",");
            }
        }

        jsonTraits = string.concat(jsonTraits, "]");
        return jsonTraits;
    }

    /**
     * @notice Retrieve RGB pixel data for a NFT.
     * @param tokenId The ID of the NFT.
     * @return The RGB pixel data.
     */
    function getNineData(uint256 tokenId) external view returns (bytes memory) {
        require(tokenId < MAX_LIMIT, "Invalid tokenId");
        require(
            nineData[tokenId].pixelData.length == PIXEL_DATA_SIZE,
            "Pixel data not set"
        );
        return nineData[tokenId].pixelData;
    }

    /**
     * @notice Generate the SVG for a NFT from its pixel data.
     * @param tokenId The ID of the NFT.
     */
    function getNineSVG(uint256 tokenId)
    external
    view
    returns (string memory svg)
{
    require(tokenId < MAX_LIMIT, "Invalid tokenId");
    bytes memory pixels = nineData[tokenId].pixelData;

    require(pixels.length > 6, "Pixel data not set or invalid");

    // Extract background color from the first 6 bytes
    uint8[3] memory bg = [
        uint8(pixels[0]),
        uint8(pixels[1]),
        uint8(pixels[2])
    ];

    // Start SVG with dynamic background color
    string memory bgHex = string(
        abi.encodePacked(
            _HEX_SYMBOLS[bg[0] >> 4],
            _HEX_SYMBOLS[bg[0] & 0xf],
            _HEX_SYMBOLS[bg[1] >> 4],
            _HEX_SYMBOLS[bg[1] & 0xf],
            _HEX_SYMBOLS[bg[2] >> 4],
            _HEX_SYMBOLS[bg[2] & 0xf]
        )
    );

    bytes memory result = abi.encodePacked(
        SVG_HEADER,
        bgHex,
        '"/>'
    );

    bytes memory buffer = new bytes(7); // Buffer for color hex

    // Iterate through pixel data, starting after the first 6 bytes
   
    for (uint256 y = 0; y < CANVAS_SIZE; y++) {
        for (uint256 x = 0; x < CANVAS_SIZE; x++) {
            uint256 p = 6 + (y * CANVAS_SIZE + x) * 3; // Start after the first 6 bytes
            if (
                uint8(pixels[p]) == bg[0] &&
                uint8(pixels[p + 1]) == bg[1] &&
                uint8(pixels[p + 2]) == bg[2]
            ) {
                continue; // Skip background pixels
            }

            // Convert RGB to hex
            buffer[0] = "#";
            for (uint256 i = 0; i < 3; i++) {
                uint8 value = uint8(pixels[p + i]);
                buffer[1 + i * 2] = _HEX_SYMBOLS[value >> 4];
                buffer[2 + i * 2] = _HEX_SYMBOLS[value & 0xf];
            }

            // Append rect to the SVG result
            result = abi.encodePacked(
                result,
                '<rect x="',
                toString(x),
                '" y="',
                toString(y),
                '" width="1" height="1" shape-rendering="crispEdges" fill="',
                string(buffer),
                '"/>'
            );
        }
    }

    // Close the SVG tag
    svg = string(abi.encodePacked(result, "</svg>"));
}

    function updatePixelData(uint256 tokenId, bytes memory newPixelData)
        external
        onlyOwner(tokenId)
    {
        require(
            newPixelData.length == updateCoord.width * updateCoord.height * 3,
            "Invalid pixel data length ! "
        );

        // Calculate the starting position in the pixel data array
        uint256 startPos = (updateCoord.startY * 24 + updateCoord.startX) * 3; // Assuming 24x24 pixel data with 3 bytes per pixel (RGB)

        // Ensure the update is within bounds of the 24x24 grid
        require(
            updateCoord.startX + updateCoord.width <= 24 &&
                updateCoord.startY + updateCoord.height <= 24,
            "Update out of bounds ! "
        );

        // Loop through the new pixel data and update the corresponding segment
        for (uint256 y = 0; y < updateCoord.height; y++) {
            for (uint256 x = 0; x < updateCoord.width; x++) {
                uint256 pos = startPos + ((y * 24 + x) * 3);
                for (uint256 i = 0; i < 3; i++) {
                    // RGB
                    nineData[tokenId].pixelData[pos + i] = newPixelData[
                        (y * updateCoord.width + x) * 3 + i
                    ];
                }
            }
        }
    }

    // for update coordinates
    function updateCoordinates(uCoord memory newCoordinate)
        external
        onlyDeployer
    {
        updateCoord = newCoordinate;
    }

    /// @dev Returns the base 10 decimal representation of `value`.
    function toString(uint256 value) internal pure returns (string memory str) {
        /// @solidity memory-safe-assembly
        assembly {
            // The maximum value of a uint256 contains 78 digits (1 byte per digit), but
            // we allocate 0xa0 bytes to keep the free memory pointer 32-byte word aligned.
            // We will need 1 word for the trailing zeros padding, 1 word for the length,
            // and 3 words for a maximum of 78 digits.
            str := add(mload(0x40), 0x80)
            // Update the free memory pointer to allocate.
            mstore(0x40, add(str, 0x20))
            // Zeroize the slot after the string.
            mstore(str, 0)

            // Cache the end of the memory to calculate the length later.
            let end := str

            let w := not(0)
            for {
                let temp := value
            } 1 {

            } {
                str := add(str, w) // `sub(str, 1)`.
                // Write the character to the pointer.
                // The ASCII index of the '0' cxwharacter is 48.
                mstore8(str, add(48, mod(temp, 10)))
                // Keep dividing `temp` until zero.
                temp := div(temp, 10)
                if iszero(temp) {
                    break
                }
            }

            let length := sub(end, str)
            // Move the pointer 32 bytes leftwards to make room for the length.
            str := sub(str, 0x20)
            // Store the length.
            mstore(str, length)
        }
    }
}

Please enter a contract address above to load the contract details and source code.

Context size (optional):