Contract Name:
GoobalooData
Contract Source Code:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;
/**
* @title GobsDataSource
* @notice Stores SVGs for traits and generates SVGs for on-chain Goblin images.
*/
contract GoobalooData {
address payable internal deployer;
bool private contractSealed = false;
mapping(string => mapping(string => string)) private traitSVGs; // traitType => value => SVG
mapping(uint256 => Trait[]) private gobTraits; // Simplified storage for traits
string private hoodieOverlaySVG;
struct Trait {
string traitType;
string value;
}
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 SVG data for a trait value
* @param traitType The type of trait (e.g., "Background", "Head", etc.)
* @param value The trait value (e.g., "Red", "Blue", etc.)
* @param svg The SVG string for this trait
*/
function storeTraitSVG(
string memory traitType,
string memory value,
string memory svg
) external onlyDeployer unsealed {
traitSVGs[traitType][value] = svg;
}
/**
* @notice Store multiple trait SVGs at once
*/
function batchStoreTraitSVGs(
string[] memory traitTypes,
string[] memory values,
string[] memory svgs
) external onlyDeployer unsealed {
require(
traitTypes.length == values.length && values.length == svgs.length,
"Mismatched arrays"
);
for (uint256 i = 0; i < traitTypes.length; i++) {
traitSVGs[traitTypes[i]][values[i]] = svgs[i];
}
}
/**
* @notice Store traits for a token
* @param tokenId The token ID to store traits for
* @param traitTypes Array of trait types
* @param values Array of values corresponding to trait types
*/
function storeTraits(
uint256 tokenId,
string[] memory traitTypes,
string[] memory values
) external onlyDeployer unsealed {
require(tokenId < 2222, "Invalid token ID");
require(traitTypes.length > 0, "No traits provided");
require(
traitTypes.length == values.length,
string.concat(
"Length mismatch: traitTypes(",
toString(traitTypes.length),
") != values(",
toString(values.length),
")"
)
);
// Clear existing traits
delete gobTraits[tokenId];
// Store new traits
for (uint256 i = 0; i < traitTypes.length; i++) {
require(bytes(traitTypes[i]).length > 0, "Empty trait type");
require(bytes(values[i]).length > 0, "Empty trait value");
gobTraits[tokenId].push(
Trait({traitType: traitTypes[i], value: values[i]})
);
}
}
/**
* @notice Retrieve traits for a token
* @param tokenId The ID of the token
* @return The traits for the token
*/
function getTraits(uint256 tokenId) external view returns (string memory) {
require(gobTraits[tokenId].length > 0, "Traits not set");
string memory jsonTraits = "[";
for (uint256 i = 0; i < gobTraits[tokenId].length; i++) {
jsonTraits = string.concat(
jsonTraits,
'{"trait_type": "',
gobTraits[tokenId][i].traitType,
'", "value": "',
gobTraits[tokenId][i].value,
'"}'
);
if (i < gobTraits[tokenId].length - 1) {
jsonTraits = string.concat(jsonTraits, ",");
}
}
jsonTraits = string.concat(jsonTraits, "]");
return jsonTraits;
}
/**
* @notice Retrieve SVG for a Goblin
* @param tokenId The ID of the Goblin
* @return The SVG for the Goblin
*/
function getGoobalooSVG(
uint256 tokenId
) external view returns (string memory) {
require(tokenId < 2222, "Invalid tokenId");
require(gobTraits[tokenId].length > 0, "Traits not set");
string memory svg = "";
bool hasHoodie = false;
for (uint256 i = 0; i < gobTraits[tokenId].length; i++) {
string memory traitType = gobTraits[tokenId][i].traitType;
string memory value = gobTraits[tokenId][i].value;
// Check if this goblin has hoodie
if (
keccak256(abi.encodePacked(traitType)) ==
keccak256(abi.encodePacked("clothes")) &&
keccak256(abi.encodePacked(value)) ==
keccak256(abi.encodePacked("hoodie"))
) {
hasHoodie = true;
}
string memory traitSVG = traitSVGs[traitType][value];
require(bytes(traitSVG).length > 0, "SVG not found for trait");
svg = string.concat(svg, "<g>", traitSVG, "</g>");
}
// Add hoodie overlay at the end if this goblin has hoodie
if (hasHoodie) {
require(
bytes(hoodieOverlaySVG).length > 0,
"Hoodie overlay not set"
);
svg = string.concat(svg, "<g>", hoodieOverlaySVG, "</g>");
}
return svg;
}
/**
* @notice Store the hoodie overlay SVG that goes on top of other traits
*/
function storeHoodieOverlay(
string memory svg
) external onlyDeployer unsealed {
hoodieOverlaySVG = svg;
}
/**
* @notice Store traits for multiple Goblins in batch
* @param tokenIds Array of token IDs
* @param traitTypes Array of trait types for each trait
* @param values Array of values for each trait
*/
function batchStoreTraits(
uint256[] memory tokenIds,
string[] memory traitTypes,
string[] memory values
) external onlyDeployer unsealed {
require(
traitTypes.length == values.length,
"Length mismatch: traitTypes and values arrays"
);
require(tokenIds.length > 0, "Empty arrays provided");
require(
tokenIds.length == traitTypes.length,
string.concat(
"Length mismatch: tokenIds(",
toString(tokenIds.length),
") != traitTypes(",
toString(traitTypes.length),
")"
)
);
require(
tokenIds[0] < 2222,
string.concat("Invalid token ID: ", toString(tokenIds[0]))
);
uint256 currentTokenId = tokenIds[0];
delete gobTraits[currentTokenId];
for (uint256 i = 0; i < tokenIds.length; i++) {
require(
tokenIds[i] < 2222,
string.concat(
"Invalid token ID at index ",
toString(i),
": ",
toString(tokenIds[i])
)
);
require(
bytes(traitTypes[i]).length > 0,
string.concat("Empty trait type at index ", toString(i))
);
require(
bytes(values[i]).length > 0,
string.concat("Empty trait value at index ", toString(i))
);
if (currentTokenId != tokenIds[i]) {
currentTokenId = tokenIds[i];
delete gobTraits[currentTokenId];
}
Trait memory trait = Trait({
traitType: traitTypes[i],
value: values[i]
});
gobTraits[tokenIds[i]].push(trait);
}
}
// Helper function to convert uint to string
function toString(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);
}
}