forge-token / contracts /FORGEART.sol
SNAPKITTYWEST's picture
October 2026 main drop: mirror from GitHub
9bf0b92 verified
Raw History Blame Contribute Delete
5.35 kB
// 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);
}
}