ZeroCollection
0x7ead084aef032e3d2e97144c4b42e2a508aa2b5c
Verification
Verified
v0.8.24+commit.e11b9ed9
Type
Contract
11,263 bytes
ABI entries
41
22 read · 12 write
License
none
Contract information
- Address
- 0x7ead084aef032e3d2e97144c4b42e2a508aa2b5c
- Chain
- Robinhood Chain (4663)
- Compiler
- v0.8.24+commit.e11b9ed9
- Optimization
- Enabled
- Creator
- 0xbAe532dDF3…6efd602C40
- Creation tx
- 0x752d283fce…3d7693fe7b
Token
- Name
- ZERO
- Symbol
- ZERO
- Decimals
- —
- Holders
- 6,459
- Market
- View token page →
Read contract (22)
FREE_MINTS_PER_WALLET() → uint256
MAX_PER_WALLET() → uint256
MAX_SUPPLY() → uint256
balanceOf(address) → uint256
contractURI() → string
freeMintsRemaining(address) → uint256
getApproved(uint256) → address
isApprovedForAll(address, address) → bool
mintPaused() → bool
mintPrice() → uint256
mintedByWallet(address) → uint256
name() → string
owner() → address
ownerOf(uint256) → address
remainingSupply() → uint256
royaltyFeeBps() → uint256
royaltyInfo(uint256, uint256) → address, uint256
royaltyReceiver() → address
supportsInterface(bytes4) → bool
symbol() → string
tokenURI(uint256) → string
totalSupply() → uint256
Events (6)
ApprovalApprovalForAllMintStateChangedMintedPriceUpdatedTransfer
ABI
[
{
"inputs": [
{
"internalType": "string",
"name": "_name",
"type": "string"
},
{
"internalType": "string",
"name": "_symbol",
"type": "string"
},
{
"internalType": "string",
"name": "_contractURI",
"type": "string"
},
{
"internalType": "uint256",
"name": "_mintPrice",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "_royaltyFeeBps",
"type": "uint256"
},
{
"internalType": "address",
"name": "_royaltyReceiver",
"type": "address"
},
{
"internalType": "address",
"name": "_owner",
"type": "address"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "owner",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "approved",
"type": "address"
},
{
"indexed": true,
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
}
],
"name": "Approval",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "owner",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "operator",
"type": "address"
},
{
"indexed": false,
"internalType": "bool",
"name": "approved",
"type": "bool"
}
],
"name": "ApprovalForAll",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "bool",
"name": "paused",
"type": "bool"
}
],
"name": "MintStateChanged",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "minter",
"type": "address"
},
{
"indexed": true,
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "quantity",
"type": "uint256"
}
],
"name": "Minted",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint256",
"name": "oldPrice",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "newPrice",
"type": "uint256"
}
],
"name": "PriceUpdated",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "from",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "to",
"type": "address"
},
{
"indexed": true,
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
}
],
"name": "Transfer",
"type": "event"
},
{
"inputs": [],
"name": "FREE_MINTS_PER_WALLET",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "MAX_PER_WALLET",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "MAX_SUPPLY",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "approved",
"type": "address"
},
{
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
}
],
"name": "approve",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "account",
"type": "address"
}
],
"name": "balanceOf",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "contractURI",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "account",
"type": "address"
}
],
"name": "freeMintsRemaining",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
}
],
"name": "getApproved",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "tokenOwner",
"type": "address"
},
{
"internalType": "address",
"name": "operator",
"type": "address"
}
],
"name": "isApprovedForAll",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "quantity",
"type": "uint256"
}
],
"name": "mint",
"outputs": [
{
"internalType": "uint256[]",
"name": "",
"type": "uint256[]"
}
],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [],
"name": "mintPaused",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "mintPrice",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "account",
"type": "address"
}
],
"name": "mintedByWallet",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "name",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "owner",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
}
],
"name": "ownerOf",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "remainingSupply",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "royaltyFeeBps",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "salePrice",
"type": "uint256"
}
],
"name": "royaltyInfo",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
},
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "royaltyReceiver",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "from",
"type": "address"
},
{
"internalType": "address",
"name": "to",
"type": "address"
},
{
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
}
],
"name": "safeTransferFrom",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "from",
"type": "address"
},
{
"internalType": "address",
"name": "to",
"type": "address"
},
{
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
},
{
"internalType": "bytes",
"name": "data",
"type": "bytes"
}
],
"name": "safeTransferFrom",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "operator",
"type": "address"
},
{
"internalType": "bool",
"name": "approved",
"type": "bool"
}
],
"name": "setApprovalForAll",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "string",
"name": "_contractURI",
"type": "string"
}
],
"name": "setContractURI",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "bool",
"name": "_paused",
"type": "bool"
}
],
"name": "setMintPaused",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "_price",
"type": "uint256"
}
],
"name": "setMintPrice",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "_feeBps",
"type": "uint256"
},
{
"internalType": "address",
"name": "_receiver",
"type": "address"
}
],
"name": "setRoyalty",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes4",
"name": "interfaceId",
"type": "bytes4"
}
],
"name": "supportsInterface",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "pure",
"type": "function"
},
{
"inputs": [],
"name": "symbol",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
}
],
"name": "tokenURI",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "totalSupply",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "from",
"type": "address"
},
{
"internalType": "address",
"name": "to",
"type": "address"
},
{
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
}
],
"name": "transferFrom",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "newOwner",
"type": "address"
}
],
"name": "transferOwnership",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "withdraw",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
}
]Source code
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import {IERC2981} from "./interfaces/IERC2981.sol";
/// @title ZeroCollection
/// @notice Generative art NFT — 10,000 unique pieces, public mint at 0.00066 ETH
/// @dev ERC-721 + EIP-2981 royalty. Onchain SVG art generation.
contract ZeroCollection is IERC2981 {
// ──────────────────────────────────────────────
// Events
// ──────────────────────────────────────────────
event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
event Minted(address indexed minter, uint256 indexed tokenId, uint256 quantity);
event PriceUpdated(uint256 oldPrice, uint256 newPrice);
event MintStateChanged(bool paused);
// ──────────────────────────────────────────────
// Constants
// ──────────────────────────────────────────────
uint256 public constant MAX_SUPPLY = 10_000;
uint256 public constant MAX_PER_WALLET = 10;
uint256 public constant FREE_MINTS_PER_WALLET = 1; // First mint is free
// ──────────────────────────────────────────────
// Storage
// ──────────────────────────────────────────────
string public name;
string public symbol;
string public contractURI;
uint256 private _nextTokenId;
uint256 public royaltyFeeBps; // 250 = 2.5%
address public royaltyReceiver;
address public owner;
uint256 public mintPrice; // in wei
bool public mintPaused;
mapping(uint256 => address) private _owners;
mapping(address => uint256) private _balances;
mapping(uint256 => address) private _tokenApprovals;
mapping(address => mapping(address => bool)) private _operatorApprovals;
mapping(address => uint256) private _mintedByWallet;
// ──────────────────────────────────────────────
// Modifiers
// ──────────────────────────────────────────────
modifier onlyOwner() {
require(msg.sender == owner, "Not owner");
_;
}
modifier onlyAuthorized(uint256 tokenId) {
require(
msg.sender == _owners[tokenId] || _operatorApprovals[_owners[tokenId]][msg.sender],
"Not authorized"
);
_;
}
modifier mintActive() {
require(!mintPaused, "Mint paused");
_;
}
// ──────────────────────────────────────────────
// Constructor
// ──────────────────────────────────────────────
/// @param _mintPrice Mint price in wei (e.g. 0.00066 ETH)
/// @param _royaltyReceiver Address to receive royalties
/// @param _owner Collection owner (can pause, withdraw, set price)
constructor(
string memory _name,
string memory _symbol,
string memory _contractURI,
uint256 _mintPrice,
uint256 _royaltyFeeBps,
address _royaltyReceiver,
address _owner
) {
name = _name;
symbol = _symbol;
contractURI = _contractURI;
mintPrice = _mintPrice;
royaltyFeeBps = _royaltyFeeBps;
royaltyReceiver = _royaltyReceiver;
owner = _owner;
_nextTokenId = 1;
}
// ──────────────────────────────────────────────
// Public Mint
// ──────────────────────────────────────────────
/// @notice Mint one or more ZERO pieces. First mint per wallet is FREE, subsequent mints at mintPrice.
/// @param quantity Number of pieces to mint (1–10 per tx, 10 per wallet total)
function mint(uint256 quantity) external payable mintActive returns (uint256[] memory) {
require(quantity > 0, "Zero quantity");
require(_nextTokenId + quantity - 1 <= MAX_SUPPLY, "Exceeds max supply");
require(_mintedByWallet[msg.sender] + quantity <= MAX_PER_WALLET, "Exceeds per-wallet limit");
// Calculate price: first FREE_MINTS_PER_WALLET mints are free, rest at mintPrice
uint256 alreadyMinted = _mintedByWallet[msg.sender];
uint256 freeRemaining = alreadyMinted < FREE_MINTS_PER_WALLET ? FREE_MINTS_PER_WALLET - alreadyMinted : 0;
uint256 paidQty = quantity > freeRemaining ? quantity - freeRemaining : 0;
uint256 requiredValue = mintPrice * paidQty;
require(msg.value == requiredValue, "Incorrect ETH value");
uint256[] memory tokenIds = new uint256[](quantity);
for (uint256 i; i < quantity; i++) {
uint256 tokenId = _nextTokenId++;
_owners[tokenId] = msg.sender;
_balances[msg.sender]++;
tokenIds[i] = tokenId;
emit Transfer(address(0), msg.sender, tokenId);
}
_mintedByWallet[msg.sender] += quantity;
emit Minted(msg.sender, tokenIds[0], quantity);
return tokenIds;
}
/// @notice How many pieces a wallet has minted
function mintedByWallet(address account) external view returns (uint256) {
return _mintedByWallet[account];
}
/// @notice How many free mints a wallet has left (1 per wallet)
function freeMintsRemaining(address account) external view returns (uint256) {
return _mintedByWallet[account] < FREE_MINTS_PER_WALLET ? FREE_MINTS_PER_WALLET - _mintedByWallet[account] : 0;
}
/// @notice Total minted so far
function totalSupply() external view returns (uint256) {
return _nextTokenId - 1;
}
/// @notice Remaining supply
function remainingSupply() external view returns (uint256) {
return MAX_SUPPLY - (_nextTokenId - 1);
}
// ──────────────────────────────────────────────
// ERC-721 Core
// ──────────────────────────────────────────────
function balanceOf(address account) public view returns (uint256) {
require(account != address(0), "Zero address");
return _balances[account];
}
function ownerOf(uint256 tokenId) public view returns (address) {
address tokenOwner = _owners[tokenId];
require(tokenOwner != address(0), "Nonexistent token");
return tokenOwner;
}
function transferFrom(address from, address to, uint256 tokenId) public {
require(_isAuthorized(msg.sender, tokenId), "Not authorized");
_transfer(from, to, tokenId);
}
function safeTransferFrom(address from, address to, uint256 tokenId) public {
transferFrom(from, to, tokenId);
require(
to.code.length == 0 || _onERC721Received(from, to, tokenId, "") == 0x150b7a02,
"Unsafe recipient"
);
}
function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory data) public {
transferFrom(from, to, tokenId);
require(
to.code.length == 0 || _onERC721Received(from, to, tokenId, data) == 0x150b7a02,
"Unsafe recipient"
);
}
function approve(address approved, uint256 tokenId) public onlyAuthorized(tokenId) {
_tokenApprovals[tokenId] = approved;
emit Approval(_owners[tokenId], approved, tokenId);
}
function setApprovalForAll(address operator, bool approved) public {
_operatorApprovals[msg.sender][operator] = approved;
emit ApprovalForAll(msg.sender, operator, approved);
}
function getApproved(uint256 tokenId) public view returns (address) {
require(_owners[tokenId] != address(0), "Nonexistent token");
return _tokenApprovals[tokenId];
}
function isApprovedForAll(address tokenOwner, address operator) public view returns (bool) {
return _operatorApprovals[tokenOwner][operator];
}
// ──────────────────────────────────────────────
// Metadata (onchain generation — no storage)
// ──────────────────────────────────────────────
function tokenURI(uint256 tokenId) public view returns (string memory) {
require(_owners[tokenId] != address(0), "Nonexistent token");
return _generateTokenURI(tokenId);
}
/// @notice Generate full onchain SVG + JSON metadata as a data URI
/// @dev Deterministic from tokenId. Matches frontend ZeroArtwork generator.
/// No storage — computed on-the-fly in this view-derived pure function.
function _generateTokenURI(uint256 tokenId) internal pure returns (string memory) {
bytes memory svg = _buildSVG(tokenId);
string memory svgB64 = _base64Encode(svg);
bytes memory traits = _buildTraits(tokenId);
bytes memory json = abi.encodePacked(
'{"name":"ZERO #', _toString(tokenId), '",',
'"description":"Generative art onchain. 10,000 unique pieces.",',
'"image":"data:image/svg+xml;base64,', svgB64, '",',
'"external_url":"https://assetra.fun",',
'"attributes":', traits, '}'
);
return string(abi.encodePacked(
"data:application/json;base64,",
_base64Encode(json)
));
}
// ──────────────────────────────────────────────
// EIP-2981 Royalty
// ──────────────────────────────────────────────
function royaltyInfo(uint256, uint256 salePrice) external view override returns (address, uint256) {
return (royaltyReceiver, (salePrice * royaltyFeeBps) / 10000);
}
// ──────────────────────────────────────────────
// Admin
// ──────────────────────────────────────────────
function setMintPrice(uint256 _price) external onlyOwner {
emit PriceUpdated(mintPrice, _price);
mintPrice = _price;
}
function setMintPaused(bool _paused) external onlyOwner {
mintPaused = _paused;
emit MintStateChanged(_paused);
}
function setRoyalty(uint256 _feeBps, address _receiver) public onlyOwner {
require(_feeBps <= 1000, "Max 10% royalty");
royaltyFeeBps = _feeBps;
royaltyReceiver = _receiver;
}
function setContractURI(string memory _contractURI) public onlyOwner {
contractURI = _contractURI;
}
function transferOwnership(address newOwner) public onlyOwner {
require(newOwner != address(0), "Zero address");
owner = newOwner;
}
/// @notice Withdraw mint proceeds to owner
function withdraw() external onlyOwner {
uint256 balance = address(this).balance;
require(balance > 0, "Nothing to withdraw");
(bool success,) = payable(owner).call{value: balance}("");
require(success, "Withdraw failed");
}
function supportsInterface(bytes4 interfaceId) public pure returns (bool) {
return interfaceId == 0x80ac58cd // ERC-721
|| interfaceId == 0x5b5e139f // ERC-721 Metadata
|| interfaceId == 0x2a55205a; // EIP-2981
}
// ──────────────────────────────────────────────
// Internal
// ──────────────────────────────────────────────
function _isAuthorized(address spender, uint256 tokenId) internal view returns (bool) {
address tokenOwner = _owners[tokenId];
return spender == tokenOwner || _tokenApprovals[tokenId] == spender
|| _operatorApprovals[tokenOwner][spender];
}
function _transfer(address from, address to, uint256 tokenId) internal {
require(_owners[tokenId] == from, "Wrong from");
require(to != address(0), "Zero address");
_balances[from]--;
_balances[to]++;
_owners[tokenId] = to;
_tokenApprovals[tokenId] = address(0);
emit Transfer(from, to, tokenId);
emit Approval(from, address(0), tokenId);
}
function _onERC721Received(address from, address to, uint256 tokenId, bytes memory data)
internal
returns (bytes4)
{
(bool success, bytes memory returndata) = to.call(
abi.encodeWithSelector(0x150b7a02, msg.sender, from, tokenId, data)
);
if (success && returndata.length >= 32) {
return bytes4(returndata);
}
return 0x00000000;
}
// ──────────────────────────────────────────────
// Onchain SVG Art Generation
// ──────────────────────────────────────────────
struct ArtParams {
uint256 ringCount;
uint256 thickness; // 100ths: 100=1.0, 50=0.5, 200=2.0
uint256 rotation; // degrees: 0,45,90,...,315
uint256 dotDensity; // 0=sparse, 1=medium, 2=dense
uint256 dotCount; // 8, 20, or 40
uint256 centerForm; // 0=empty, 1=solid, 2=cross, 3=diamond
uint256 noiseOp; // 100ths: 4=0.04, 2=0.02
uint256 seed; // seed after main params, for dot generation
}
/// @dev LCG matching frontend seededRandom: s = (s*9301 + 49297) % 233280
function _nextRand(uint256 seed) internal pure returns (uint256 newSeed, uint256 value) {
newSeed = (seed * 9301 + 49297) % 233280;
value = newSeed;
}
/// @dev Compute all art parameters from tokenId (deterministic)
function _computeArtParams(uint256 tokenId) internal pure returns (ArtParams memory p) {
uint256 seed = tokenId;
uint256 r;
// Ring count: 3-9
(seed, r) = _nextRand(seed);
p.ringCount = 3 + (r * 7 / 233280);
// Ring thickness (1-2 PRNG calls, matching frontend)
(seed, r) = _nextRand(seed);
if (r > 116640) {
p.thickness = 100; // 1.0
} else {
(seed, r) = _nextRand(seed);
p.thickness = r > 116640 ? 50 : 200; // 0.5 or 2.0
}
// Rotation: 0, 45, 90, ..., 315
(seed, r) = _nextRand(seed);
p.rotation = (r * 360 / 233280) / 45 * 45;
// Dot density → dot count
(seed, r) = _nextRand(seed);
p.dotDensity = r * 3 / 233280;
p.dotCount = p.dotDensity == 0 ? 8 : p.dotDensity == 1 ? 20 : 40;
// Center form: 0-3
(seed, r) = _nextRand(seed);
p.centerForm = r * 4 / 233280;
// Noise opacity
(seed, r) = _nextRand(seed);
p.noiseOp = r > 163296 ? 4 : 2; // 0.04 or 0.02
p.seed = seed;
}
/// @dev Build SVG string from art parameters
function _buildSVG(uint256 tokenId) internal pure returns (bytes memory) {
ArtParams memory p = _computeArtParams(tokenId);
uint256 seed = p.seed;
uint256 r;
uint256 maxR = 168; // 400 * 0.42
bytes memory svg = abi.encodePacked(
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 400">',
'<rect width="400" height="400" fill="#000000"/>'
);
// Concentric rings
svg = abi.encodePacked(svg, '<g transform="rotate(', _toString(p.rotation), ' 200 200)">');
for (uint256 i; i < p.ringCount; i++) {
uint256 radius = maxR * (i + 1) / p.ringCount;
uint256 opacity = 90 - (i * 60) / p.ringCount;
svg = abi.encodePacked(svg,
'<circle cx="200" cy="200" r="', _toString(radius),
'" stroke="#FFFFFF" stroke-width="', _fmtDec(p.thickness),
'" fill="none" opacity="', _fmtDec(opacity), '"/>'
);
}
svg = abi.encodePacked(svg, '</g>');
// Scattered dots (4 PRNG calls per dot, matching frontend sequence)
for (uint256 i; i < p.dotCount; i++) {
(seed, r) = _nextRand(seed);
uint256 x = 40 + r * 320 / 233280; // 40-360
(seed, r) = _nextRand(seed);
uint256 y = 40 + r * 320 / 233280; // 40-360
(seed, r) = _nextRand(seed);
uint256 dotR = r > 186624 ? 250 : 150; // 2.5 or 1.5
(seed, r) = _nextRand(seed);
uint256 dotOp = 30 + r * 50 / 233280; // 0.30-0.80
svg = abi.encodePacked(svg,
'<circle cx="', _toString(x),
'" cy="', _toString(y),
'" r="', _fmtDec(dotR),
'" fill="#FFFFFF" opacity="', _fmtDec(dotOp), '"/>'
);
}
// Center form
if (p.centerForm == 1) {
svg = abi.encodePacked(svg, '<circle cx="200" cy="200" r="12" fill="#FFFFFF" opacity="0.9"/>');
} else if (p.centerForm == 2) {
svg = abi.encodePacked(svg,
'<g stroke="#FFFFFF" stroke-width="2" opacity="0.8">',
'<line x1="184" y1="200" x2="216" y2="200"/>',
'<line x1="200" y1="184" x2="200" y2="216"/>',
'</g>'
);
} else if (p.centerForm == 3) {
svg = abi.encodePacked(svg,
'<g transform="rotate(45 200 200)" stroke="#FFFFFF" stroke-width="1.5" fill="none" opacity="0.8">',
'<rect x="186" y="186" width="28" height="28"/>',
'</g>'
);
}
// Noise overlay
svg = abi.encodePacked(svg,
'<rect width="400" height="400" fill="#FFFFFF" opacity="', _fmtDec(p.noiseOp), '"/>'
);
return abi.encodePacked(svg, '</svg>');
}
/// @dev Build JSON traits array from art parameters
function _buildTraits(uint256 tokenId) internal pure returns (bytes memory) {
ArtParams memory p = _computeArtParams(tokenId);
// Determine rarity
string memory rarity;
if (p.centerForm == 3 && p.ringCount >= 8) {
rarity = "Legendary";
} else if (p.centerForm >= 2 && p.ringCount >= 7) {
rarity = "Epic";
} else if (p.centerForm >= 1 || p.ringCount >= 7) {
rarity = "Rare";
} else {
rarity = "Common";
}
string memory densityStr = p.dotDensity == 0 ? "Sparse" : p.dotDensity == 1 ? "Medium" : "Dense";
string memory centerStr = p.centerForm == 0 ? "Empty" : p.centerForm == 1 ? "Solid" : p.centerForm == 2 ? "Cross" : "Diamond";
return abi.encodePacked(
'[',
'{"trait_type":"Ring Count","value":', _toString(p.ringCount), '},',
'{"trait_type":"Rotation","value":"', _toString(p.rotation), 'deg"},',
'{"trait_type":"Dot Density","value":"', densityStr, '"},',
'{"trait_type":"Center Form","value":"', centerStr, '"},',
'{"trait_type":"Rarity","value":"', rarity, '"}',
']'
);
}
/// @dev Format value in 100ths as decimal string: 90→"0.90", 4→"0.04", 200→"2.00"
function _fmtDec(uint256 val) internal pure returns (string memory) {
uint256 intPart = val / 100;
uint256 fracPart = val % 100;
if (fracPart < 10) {
return string(abi.encodePacked(_toString(intPart), ".0", _toString(fracPart)));
}
return string(abi.encodePacked(_toString(intPart), ".", _toString(fracPart)));
}
// ──────────────────────────────────────────────
// Utilities
// ──────────────────────────────────────────────
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);
}
function _base64Encode(bytes memory data) internal pure returns (string memory) {
if (data.length == 0) return "";
// Base64 encoding table
bytes memory table = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
uint256 encodedLen = 4 * ((data.length + 2) / 3);
bytes memory result = new bytes(encodedLen);
assembly {
let tablePtr := add(table, 1)
let resultPtr := add(result, 32)
for {
let dataPtr := data
let endPtr := add(data, mload(data))
} lt(dataPtr, endPtr) {} {
dataPtr := add(dataPtr, 3)
let input := mload(dataPtr)
mstore8(resultPtr, mload(add(tablePtr, and(shr(18, input), 0x3F))))
resultPtr := add(resultPtr, 1)
mstore8(resultPtr, mload(add(tablePtr, and(shr(12, input), 0x3F))))
resultPtr := add(resultPtr, 1)
mstore8(resultPtr, mload(add(tablePtr, and(shr(6, input), 0x3F))))
resultPtr := add(resultPtr, 1)
mstore8(resultPtr, mload(add(tablePtr, and(input, 0x3F))))
resultPtr := add(resultPtr, 1)
}
switch mod(mload(data), 3)
case 1 {
mstore8(sub(resultPtr, 1), 0x3d)
mstore8(sub(resultPtr, 2), 0x3d)
}
case 2 {
mstore8(sub(resultPtr, 1), 0x3d)
}
}
return string(result);
}
}
Chain explorer2209msChain node80ms