Ape Curtis Testnet

Contract Diff Checker

Contract Name:
GobsDataSource

Contract Source Code:

File 1 of 1 : GobsDataSource

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

/**
 * @title GobsDataSource
 * @notice Stores RGBA data, dynamically generates SVGs, and supports metadata traits.
 */
contract GobsDataSource {
    address payable internal deployer;
    bool private contractSealed = false;

    struct Trait {
        string traitType;
        string value;
    }

    struct GobData {
        bytes imageData; // Encoded RGBA data in row-major order
        Trait[] traits;  // Array of traits for the token
    }

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

    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 token in row-major order.
     * @param tokenId The ID of the token.
     * @param imageData The RGBA data (row-major order, 29x29 pixels).
     */
    function storePixelData(uint256 tokenId, bytes memory imageData) external onlyDeployer unsealed {
        require(imageData.length == 29 * 29 * 4, "Invalid RGBA data length");
        gobData[tokenId].imageData = imageData;
    }

    /**
     * @notice Add traits for a token.
     * @param tokenId The ID of the token.
     * @param traits The traits to associate with the token.
     */
    function storeTraits(uint256 tokenId, Trait[] memory traits) external onlyDeployer unsealed {
        delete gobData[tokenId].traits; // Clear existing traits
        for (uint256 i = 0; i < traits.length; i++) {
            gobData[tokenId].traits.push(traits[i]);
        }
    }

    /**
     * @notice Retrieve RGBA pixel data for a token.
     * @param tokenId The ID of the token.
     * @return The RGBA pixel data.
     */
    function getPixelData(uint256 tokenId) external view returns (bytes memory) {
        require(gobData[tokenId].imageData.length > 0, "Image data not set");
        return gobData[tokenId].imageData;
    }

    /**
     * @notice Retrieve traits for a token.
     * @param tokenId The ID of the token.
     * @return The traits associated with the token.
     */
    function getTraits(uint256 tokenId) external view returns (Trait[] memory) {
        require(gobData[tokenId].traits.length > 0, "Traits not set");
        return gobData[tokenId].traits;
    }

    /**
     * @notice Dynamically generate SVG from RGBA data using bitwise operations.
     * @param tokenId The ID of the token.
     * @return The SVG representation of the token.
     */
    function getSVG(uint256 tokenId) external view returns (string memory) {
        bytes memory pixelData = gobData[tokenId].imageData;
        require(pixelData.length > 0, "Image data not set");

        string memory svgStart = '<svg xmlns="http://www.w3.org/2000/svg" width="29" height="29" viewBox="0 0 29 29">';
        string memory rects;

        uint256 index = 0;
        for (uint256 y = 0; y < 29; y++) {
            for (uint256 x = 0; x < 29; x++) {
                uint32 rgba = toUint32(pixelData, index);
                index += 4;

                if ((rgba & 0xFF000000) != 0) { // Check alpha channel
                    string memory color = toHexColor(rgba);
                    rects = string(
                        abi.encodePacked(
                            rects,
                            '<rect x="', uint2str(x), '" y="', uint2str(y),
                            '" width="1" height="1" fill="', color, '" shape-rendering="crispEdges"/>'
                        )
                    );
                }
            }
        }

        return string(abi.encodePacked(svgStart, rects, "</svg>"));
    }

    /**
     * @notice Convert a slice of bytes to a uint32.
     * @param data The byte array.
     * @param index The starting index.
     * @return The uint32 representation.
     */
    function toUint32(bytes memory data, uint256 index) internal pure returns (uint32) {
        require(index + 4 <= data.length, "Index out of bounds");
        return uint32(
            uint8(data[index]) << 24 |
            uint8(data[index + 1]) << 16 |
            uint8(data[index + 2]) << 8 |
            uint8(data[index + 3])
        );
    }

    /**
     * @notice Convert a uint32 RGBA value to a hex color string.
     * @param rgba The RGBA value.
     * @return The hex color string.
     */
    function toHexColor(uint32 rgba) internal pure returns (string memory) {
        bytes16 hexSymbols = "0123456789abcdef";
        bytes memory buffer = new bytes(9); // '#RRGGBBAA'

        buffer[0] = "#";
        for (uint256 i = 0; i < 4; i++) {
            uint8 value = uint8(rgba >> ((3 - i) * 8));
            buffer[1 + i * 2] = hexSymbols[value >> 4];
            buffer[2 + i * 2] = hexSymbols[value & 0xF];
        }

        return string(buffer);
    }

    /**
     * @notice Utility function: Convert uint256 to string.
     * @param value The value to convert.
     * @return The string representation of the value.
     */
    function uint2str(uint256 value) internal pure returns (string memory) {
        if (value == 0) return "0";
        uint256 temp = value;
        uint256 digits;
        while (temp != 0) {
            digits++;
            temp /= 10;
        }
        bytes memory buffer = new bytes(digits);
        while (value != 0) {
            digits -= 1;
            buffer[digits] = bytes1(uint8(48 + uint256(value % 10)));
            value /= 10;
        }
        return string(buffer);
    }
}

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

Context size (optional):