File size: 5,353 Bytes
9bf0b92 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 | // SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
/**
* @title FORGE ART — On-Chain SACM Agent Generative NFT
* @notice Each token is a unique piece of ASCII art generated by a SnapKitty
* SACM agent, sealed to the WORM chain, stored entirely on-chain.
* No IPFS. No external dependencies. Art lives on Base forever.
*
* @dev ERC-721 with fully on-chain SVG tokenURI.
* Mint is gated by MINTER_ROLE — only the FORGE settlement bridge mints.
* Each mint records: agent name, WORM chain tip, SVG art, token seed.
* Art is deterministically reproducible from (agent, wormTip, tokenId).
*
* @custom:deployed-by SnapKitty Collective LLC — EIN 41-5105572
* @custom:holding-entity Bel Esprit D'Accord Trust — EIN 41-6630640
* @custom:legal UTILITY COLLECTIBLE ONLY. Not a security or investment instrument.
*/
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Burnable.sol";
import "@openzeppelin/contracts/access/AccessControl.sol";
import "@openzeppelin/contracts/utils/Base64.sol";
import "@openzeppelin/contracts/utils/Strings.sol";
contract FORGEART is ERC721, ERC721Burnable, AccessControl {
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
bytes32 public constant ARCHITECT_ROLE = keccak256("ARCHITECT_ROLE");
uint256 public nextTokenId;
uint256 public constant MAX_SUPPLY = 2_100; // 1/10 of FORGE cap — scarce
struct ArtToken {
string agent; // NOVA | ENKI | CIPHER | VAULT
string wormTip; // WORM chain tip at mint time (provenance)
string tokenSeed; // deterministic seed for off-chain reproduction
string svgArt; // full SVG stored on-chain
uint256 mintedAt;
}
mapping(uint256 => ArtToken) public artTokens;
mapping(string => uint256) public agentMintCount;
event ArtMinted(
uint256 indexed tokenId,
address indexed recipient,
string agent,
string tokenSeed,
string wormTip
);
constructor(address architect, address minter) ERC721("FORGE ART", "FART") {
require(architect != address(0), "FORGEART: zero architect");
require(minter != address(0), "FORGEART: zero minter");
_grantRole(DEFAULT_ADMIN_ROLE, architect);
_grantRole(ARCHITECT_ROLE, architect);
_grantRole(MINTER_ROLE, minter);
}
/**
* @notice Mint a new FORGE ART token with fully on-chain SVG art.
* @param recipient Address receiving the NFT
* @param agent Agent name — "NOVA", "ENKI", "CIPHER", or "VAULT"
* @param wormTip WORM chain tip at time of sealing (provenance anchor)
* @param tokenSeed Deterministic seed — keccak256(agent:wormTip:tokenId)
* @param svgArt Complete SVG string to store on-chain
*/
function mintArt(
address recipient,
string calldata agent,
string calldata wormTip,
string calldata tokenSeed,
string calldata svgArt
)
external
onlyRole(MINTER_ROLE)
{
require(nextTokenId < MAX_SUPPLY, "FORGEART: max supply reached");
require(recipient != address(0), "FORGEART: zero recipient");
require(bytes(agent).length > 0, "FORGEART: empty agent");
require(bytes(svgArt).length > 0, "FORGEART: empty art");
uint256 tokenId = nextTokenId++;
artTokens[tokenId] = ArtToken({
agent: agent,
wormTip: wormTip,
tokenSeed: tokenSeed,
svgArt: svgArt,
mintedAt: block.timestamp
});
agentMintCount[agent]++;
_safeMint(recipient, tokenId);
emit ArtMinted(tokenId, recipient, agent, tokenSeed, wormTip);
}
/**
* @notice Returns fully on-chain tokenURI as a base64-encoded data URI.
*/
function tokenURI(uint256 tokenId)
public
view
override
returns (string memory)
{
_requireOwned(tokenId);
ArtToken storage art = artTokens[tokenId];
string memory svgBase64 = Base64.encode(bytes(art.svgArt));
string memory json = string(abi.encodePacked(
'{"name":"FORGE ART #', Strings.toString(tokenId), ' - ', art.agent,
'","description":"On-chain generative ASCII art sealed to the SnapKitty SACM WORM chain. Agent: ', art.agent,
'. Every pixel is a proof of productive work.","image":"data:image/svg+xml;base64,', svgBase64,
'","attributes":[',
'{"trait_type":"Agent","value":"', art.agent, '"},',
'{"trait_type":"WORM Tip","value":"', art.wormTip, '"},',
'{"trait_type":"Token Seed","value":"', art.tokenSeed, '"},',
'{"trait_type":"Minted At","value":', Strings.toString(art.mintedAt), '}',
']}'
)) ;
return string(abi.encodePacked(
'data:application/json;base64,',
Base64.encode(bytes(json))
));
}
function remainingSupply() external view returns (uint256) {
return MAX_SUPPLY - nextTokenId;
}
function supportsInterface(bytes4 interfaceId)
public view override(ERC721, AccessControl)
returns (bool)
{
return super.supportsInterface(interfaceId);
}
}
|