Contract Name:
ProjectNineData
Contract Source Code:
File 1 of 1 : ProjectNineData
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/**
* _ _ _ _ _ _ _ _
* /\ \ /\ \ /\ \ /\ \ /\ \ /\ \ /\ \ / /\
* / \ \ / \ \ / \ \ \ \ \ / \ \ / \ \ \_\ \ / / \
* / /\ \ \ / /\ \ \ / /\ \ \ /\ \_\ / /\ \ \ / /\ \ \ /\__ \ / / /\ \
* / / /\ \_\ / / /\ \_\ / / /\ \ \ / /\/_// / /\ \_\ / / /\ \ \ / /_ \ \ /_/ /\ \ \
* / / /_/ / // / /_/ / / / / / \ \_\ _ / / / / /_/_ \/_/ / / / \ \_\ / / /\ \ \ \ \ \_\ \ \
* / / /__\/ // / /__\/ / / / / / / //\ \ / / / / /____/\ / / / \/_/ / / / \/_/ \ \/__\ \ \
* / / /_____// / /_____/ / / / / / / \ \_\ / / / / /\____\/ / / / / / / \_____\ \ \
* / / / / / /\ \ \ / / /___/ / / / / /_/ / / / / /______ / / /________ / / / \ \ \
* / / / / / / \ \ \/ / /____\/ / / / /__\/ / / / /_______\/ / /_________\/_/ / \ \ \
* \/_/ \/_/ \_\/\/_________/ \/_______/ \/__________/\/____________/\_\/ \_\/
*
* On-chain Project 9 images and attributes, by SoftWave.
**/
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="#E73879"/>';
string internal constant SVG_FOOTER = "</svg>";
bytes16 private constant _HEX_SYMBOLS = "0123456789abcdef";
uint256 private constant PIXEL_DATA_SIZE = 24 * 24 * 3;
uint public constant MAX_LIMIT = 2222;
struct Trait {
string traitType;
string value;
}
struct NineData {
bytes pixelData; // Row-major RGBA data (3x3 pixels, 3364 bytes)
Trait[] traits; // Array of traits for the token
}
mapping(uint256 => NineData) private nineData; // Stores data for each NFT
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 > 0, "Pixel data not set");
bytes memory result = abi.encodePacked(SVG_HEADER); // Start with the header
bytes memory buffer = new bytes(7); // Buffer for color hex
for (uint256 y = 0; y < 24; y++) {
for (uint256 x = 0; x < 24; x++) {
uint256 p = (y * 24 + x) * 3;
if (
pixels[p] == 0xe7 &&
pixels[p + 1] == 0x38 &&
pixels[p + 2] == 0x79
) {
continue; // Skip background pixels #E73879
}
// 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 result
result = abi.encodePacked(
result,
'<rect x="',
toString(x),
'" y="',
toString(y),
'" width="1" height="1" shape-rendering="crispEdges" fill="',
string(buffer),
'"/>'
);
}
}
svg = string(abi.encodePacked(result, SVG_FOOTER)); // Append 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)
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)
}
}
}