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 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">';
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 (Trait[] memory) {
require(gobData[tokenId].traits.length > 0, "Traits not set");
return gobData[tokenId].traits;
}
/**
* @notice Retrieve RGBA pixel data for a Goblin.
* @param tokenId The ID of the Goblin.
* @return The RGBA pixel data.
*/
function getGobData(uint256 tokenId) external view returns (bytes memory) {
require(tokenId < 2222, "Invalid tokenId");
require(gobData[tokenId].pixelData.length > 0, "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;
require(pixels.length > 0, "Pixel data not set");
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;
for (uint i = 0; i < 3; i++) {
uint8 value = uint8(pixels[p + i]);
buffer[i * 2 + 1] = _HEX_SYMBOLS[value & 0xf];
value >>= 3;
buffer[i * 2] = _HEX_SYMBOLS[value & 0xf];
}
svg = string(abi.encodePacked(svg,
'<rect x="', toString(x), '" y="', toString(y),
'" width="1" height="1" shape-rendering="crispEdges" fill="#', string(buffer), '"/>'));
}
}
svg = string(abi.encodePacked(svg, SVG_FOOTER));
}
/**
* @notice Convert a slice of bytes to a uint32.
* @param data The byte array.
* @param index The starting index.
* @return The uint32 representation of the RGBA value.
*/
function toUint32(bytes memory data, uint256 index) internal pure returns (uint32) {
return uint32(
uint8(data[index]) << 24 |
uint8(data[index + 1]) << 16 |
uint8(data[index + 2]) << 8 |
uint8(data[index + 3])
);
}
function toString(uint256 value) internal pure returns (string memory) {
// Inspired by OraclizeAPI's implementation - MIT licence
// https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol
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);
}
}