Ape Curtis Testnet

Contract Diff Checker

Contract Name:
GobsDataSource

Contract Source Code:

File 1 of 1 : GobsDataSource

/**
 *Submitted for verification at curtis.apescan.io on 2024-11-21
*/

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

/**
 * @title GobsDataSource
 * @notice Stores RGBA pixel data and generates SVGs for on-chain Goblin images.
 */
contract GobsDataSource {
    address payable internal deployer;
    bool private contractSealed = false;
    string internal constant SVG_HEADER = 'data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" version="1.2" viewBox="0 0 29 29"><rect width="100%" height="100%" fill="#0047B1"/>';
    string internal constant SVG_FOOTER = '</svg>';
    bytes16 private constant _HEX_SYMBOLS = "0123456789abcdef";


    struct Trait {
        string traitType;
        string value;
    }

    struct GobData {
        bytes pixelData; // Row-major RGBA data (29x29 pixels, 3364 bytes)
        Trait[] traits;  // Array of traits for the token
    }

    

    mapping(uint256 => GobData) private gobData; // Stores data for each Goblin

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

    modifier unsealed() {
        require(!contractSealed, "Contract is sealed.");
        _;
    }

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

    /**
     * @notice Seal the contract to prevent further modifications.
     */
    function sealContract() external onlyDeployer unsealed {
        contractSealed = true;
    }

    /**
     * @notice Store RGBA pixel data for a Goblin.
     * @param tokenId The ID of the Goblin (0 <= tokenId < 2222).
     * @param pixelData The RGBA data (row-major order, 29x29 pixels, 3364 bytes).
     */
    function storePixelData(uint256 tokenId, bytes memory pixelData) external onlyDeployer unsealed {
        require(tokenId < 2222, "Invalid tokenId");
        require(pixelData.length == 29 * 29 * 3, "Invalid pixel data length");
        gobData[tokenId].pixelData = pixelData;
    }

   function storeTraits(uint256 tokenId, Trait[] memory traits) external onlyDeployer {
        delete gobData[tokenId].traits; // Clear existing traits
        for (uint256 i = 0; i < traits.length; i++) {
            gobData[tokenId].traits.push(traits[i]);
        }
    }


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

        string memory jsonTraits = "[";

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

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

        jsonTraits = string.concat(jsonTraits, "]");
        return jsonTraits;
    }
    /**
     * @notice Retrieve RGB pixel data for a Goblin.
     * @param tokenId The ID of the Goblin.
     * @return The RGB pixel data.
     */
    function getGobData(uint256 tokenId) external view returns (bytes memory) {
        require(tokenId < 2222, "Invalid tokenId");
        require(gobData[tokenId].pixelData.length == 29 * 29 * 3, "Pixel data not set");
        return gobData[tokenId].pixelData;
    }

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

    svg = "";
    require(pixels.length > 0, "Pixel data not set");

    // Pre-allocate space for the SVG (estimate size to reduce reallocations)
    // string memory svg = string(abi.encodePacked(SVG_HEADER));
    bytes memory buffer = new bytes(7);

    for (uint y = 0; y < 29; y++) {
        for (uint x = 0; x < 29; x++) {
            uint p = (y * 29 + x) * 3;
            if(pixels[p] == 0x00 && pixels[p + 1] == 0x47 && pixels[p + 2] == 0xb1){
                continue;
            }
            // Convert RGB values to hex
            buffer[0] = "#";
            for (uint 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 the rect element
            svg = string.concat(svg, 
                '<rect x="', toString(x),
                '" y="', toString(y),
                '" width="1" height="1" shape-rendering="crispEdges" fill="', string(buffer), '"/>'); 
        }
    }

    svg = string.concat(SVG_HEADER,svg,SVG_FOOTER);
}


/// @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) // Tsk.
            // We write the string from rightmost digit to leftmost digit.
            // The following is essentially a do-while loop that also handles the zero case.
            for { let temp := value } 1 {} {
                str := add(str, w) // `sub(str, 1)`.
                // Write the character to the pointer.
                // The ASCII index of the '0' character 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):