Files
smom-dbis-138/contracts/nft/GRUFormulasNFT.sol
2026-03-02 12:14:09 -08:00

113 lines
5.7 KiB
Solidity
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts/access/AccessControl.sol";
import "@openzeppelin/contracts/utils/Base64.sol";
/**
* @title GRUFormulasNFT
* @notice ERC-721 NFT depicting the three GRU-related monetary formulas as on-chain SVG graphics
* @dev Token IDs: 0 = Money Supply (GRU M00/M0/M1), 1 = Money Velocity (M×V=P×Y), 2 = Money Multiplier (m=1.0)
*/
contract GRUFormulasNFT is ERC721, ERC721URIStorage, AccessControl {
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
uint256 public constant TOKEN_ID_MONEY_SUPPLY = 0;
uint256 public constant TOKEN_ID_MONEY_VELOCITY = 1;
uint256 public constant TOKEN_ID_MONEY_MULTIPLIER = 2;
uint256 public constant MAX_TOKEN_ID = 2;
constructor(address admin) ERC721("GRU Formulas", "GRUF") {
_grantRole(DEFAULT_ADMIN_ROLE, admin);
_grantRole(MINTER_ROLE, admin);
}
/**
* @notice Mint one of the three formula NFTs (tokenId 0, 1, or 2)
* @param to Recipient
* @param tokenId 0 = Money Supply, 1 = Money Velocity, 2 = Money Multiplier
*/
function mint(address to, uint256 tokenId) external onlyRole(MINTER_ROLE) {
require(tokenId <= MAX_TOKEN_ID, "GRUFormulasNFT: invalid tokenId");
_safeMint(to, tokenId);
}
function _baseURI() internal pure override returns (string memory) {
return "";
}
/**
* @notice Returns metadata URI with on-chain SVG image for the formula
* @param tokenId 0 = Money Supply, 1 = Money Velocity, 2 = Money Multiplier
*/
function tokenURI(uint256 tokenId) public view override(ERC721, ERC721URIStorage) returns (string memory) {
require(_ownerOf(tokenId) != address(0), "ERC721: invalid token ID");
(string memory name, string memory description, string memory svg) = _formulaData(tokenId);
string memory imageData = string.concat("data:image/svg+xml;base64,", Base64.encode(bytes(svg)));
string memory json = string.concat(
'{"name":"', name,
'","description":"', description,
'","image":"', imageData,
'"}'
);
return string.concat("data:application/json;base64,", Base64.encode(bytes(json)));
}
function _formulaData(uint256 tokenId) internal pure returns (string memory name, string memory description, string memory svg) {
if (tokenId == TOKEN_ID_MONEY_SUPPLY) {
name = "GRU Money Supply (M)";
description = "GRU monetary layers: 1 M00 = 5 M0 = 25 M1 (base, collateral, credit). Non-ISO synthetic unit of account.";
svg = _svgMoneySupply();
} else if (tokenId == TOKEN_ID_MONEY_VELOCITY) {
name = "Money Velocity (V)";
description = "Equation of exchange: M x V = P x Y (money supply, velocity, price level, output).";
svg = _svgMoneyVelocity();
} else if (tokenId == TOKEN_ID_MONEY_MULTIPLIER) {
name = "Money Multiplier (m)";
description = "m = Reserve / Supply; GRU and ISO4217W enforce m = 1.0 (no fractional reserve).";
svg = _svgMoneyMultiplier();
} else {
revert("GRUFormulasNFT: invalid tokenId");
}
}
function _svgMoneySupply() internal pure returns (string memory) {
return string.concat(
"<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 320 120' width='320' height='120'>",
"<rect width='320' height='120' fill='#1a1a2e' rx='8'/>",
"<text x='160' y='42' text-anchor='middle' fill='#eaeaea' font-family='sans-serif' font-size='14'>Money Supply (M) - GRU layers</text>",
"<text x='160' y='68' text-anchor='middle' fill='#a0e0a0' font-family='monospace' font-size='16'>1 M00 = 5 M0 = 25 M1</text>",
"<text x='160' y='92' text-anchor='middle' fill='#888' font-family='sans-serif' font-size='11'>M00 base | M0 collateral | M1 credit</text>",
"</svg>"
);
}
function _svgMoneyVelocity() internal pure returns (string memory) {
return string.concat(
"<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 320 120' width='320' height='120'>",
"<rect width='320' height='120' fill='#1a1a2e' rx='8'/>",
"<text x='160' y='42' text-anchor='middle' fill='#eaeaea' font-family='sans-serif' font-size='14'>Money Velocity (V)</text>",
"<text x='160' y='72' text-anchor='middle' fill='#a0c0ff' font-family='serif' font-size='20'>M * V = P * Y</text>",
"<text x='160' y='98' text-anchor='middle' fill='#888' font-family='sans-serif' font-size='10'>Equation of exchange</text>",
"</svg>"
);
}
function _svgMoneyMultiplier() internal pure returns (string memory) {
return string.concat(
"<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 320 120' width='320' height='120'>",
"<rect width='320' height='120' fill='#1a1a2e' rx='8'/>",
"<text x='160' y='42' text-anchor='middle' fill='#eaeaea' font-family='sans-serif' font-size='14'>Money Multiplier (m)</text>",
"<text x='160' y='72' text-anchor='middle' fill='#ffc0a0' font-family='serif' font-size='18'>m = Reserve / Supply = 1.0</text>",
"<text x='160' y='98' text-anchor='middle' fill='#888' font-family='sans-serif' font-size='10'>No fractional reserve (GRU / ISO4217W)</text>",
"</svg>"
);
}
function supportsInterface(bytes4 interfaceId) public view override(ERC721, ERC721URIStorage, AccessControl) returns (bool) {
return super.supportsInterface(interfaceId) || AccessControl.supportsInterface(interfaceId);
}
}