// SPDX-License-Identifier: MIT
// BytesBuilder uses `mcopy`, so this file needs solc 0.8.24 / Cancun.
pragma solidity ^0.8.24 <0.9.0;
import "@franknft.eth/erc721-f/contracts/token/ERC721/extensions/ERC721FOnChain.sol";
import "@franknft.eth/erc721-f/contracts/utils/BytesBuilder.sol";
/**
* @title OnChain
*
* @dev Example implementation of [ERC721FOnChain] with overridden functions for custom SVG and URI creation
*/
contract OnChain is ERC721FOnChain {
string private constant SVG_HEAD =
'";
string[10] private pokemon = [
"Bulbasaur",
"Ivysaur",
"Venusaur",
"Charmander",
"Charmeleon",
"Charizard",
"Squirtle",
"Wartortle",
"Blastoise",
"Mew"
];
uint256 public constant MAX_TOKENS = 10000;
uint256 public constant MAX_PURCHASE = 31;
bool public saleIsActive;
error ContractsNotAllowed();
constructor()
ERC721FOnChain("OnChain", "OC", msg.sender, "Example OnChain Contract")
{}
/**
* Changes the state of saleIsActive from true to false and false to true
*/
function flipSaleState() external onlyOwner {
saleIsActive = !saleIsActive;
}
/**
* @notice Mints `numberOfTokens` tokens for `sender`
*/
function mint(uint256 numberOfTokens) external {
if (msg.sender.code.length != 0) revert ContractsNotAllowed();
require(saleIsActive, "Sale NOT active yet");
require(numberOfTokens > 0, "numberOfNfts cannot be 0");
require(
numberOfTokens < MAX_PURCHASE,
"Can only mint 30 tokens at a time"
);
uint256 supply = _totalMinted();
require(
supply + numberOfTokens <= MAX_TOKENS,
"Purchase would exceed max supply of Tokens"
);
unchecked {
for (uint256 i; i < numberOfTokens;) {
_mint(msg.sender, supply + i); // no need to use safeMint as we don't allow contracts.
i++;
}
}
}
/**
* @notice Overridden function to display description change in tokenURI
*/
function getDescription() public view override returns (string memory) {
return
string(
abi.encodePacked(
super.getDescription(),
" - Overwrote description"
)
);
}
/**
* @notice Overridden function which creates custom SVG image
* @dev `id` changes the displayed name in `parts[3]`, every 100th you get mew
*/
function renderTokenById(
uint256 id
) public view override returns (string memory) {
require(_exists(id), "Non-Existing token");
// Eight parts, not nine: the id slot is gone because appendNumber renders it
// straight into the buffer instead of allocating a string for it.
string[8] memory parts;
parts[0] = SVG_HEAD;
parts[1] = name();
parts[2] = '';
parts[3] =
id % 100 == 0 && id != 0
? pokemon[9]
: pokemon[id % (pokemon.length - 1)];
parts[4] = '';
parts[5] = getDescription();
parts[6] =
'';
parts[7] = SVG_FOOTER;
uint256 cap = 78; // widest uint256 the id can render to
for (uint256 i; i < 8; ++i) {
cap += bytes(parts[i]).length;
}
(bytes memory out, uint256 ptr) = BytesBuilder.start(cap);
for (uint256 i; i < 7; ++i) {
ptr = BytesBuilder.append(ptr, bytes(parts[i]));
}
ptr = BytesBuilder.appendNumber(ptr, id);
ptr = BytesBuilder.append(ptr, bytes(parts[7]));
BytesBuilder.finish(out, ptr);
return string(out);
}
/**
* @dev Creates two example traits, one static and one dynamic, override function for custom traits (leave empty when no traits are wanted)
*/
function getTraits(
uint256 id
) public view override returns (string memory) {
require(_exists(id), "Non-Existing token");
// The original built two trait strings and then concatenated them with
// brackets, so every literal was copied twice. Written straight into one
// buffer the literals collapse into a head and a tail around the id,
// and appendNumber renders the id in place instead of allocating a string.
bytes memory head = bytes(
'[{\n"trait_type": "TypeName",\n"value": "testValue"\n}\n'
',{\n"trait_type": "Id",\n"value": "'
);
bytes memory tail = bytes('"\n}\n]');
(bytes memory out, uint256 ptr) = BytesBuilder.start(
head.length + 78 + tail.length
);
ptr = BytesBuilder.append(ptr, head);
ptr = BytesBuilder.appendNumber(ptr, id);
ptr = BytesBuilder.append(ptr, tail);
BytesBuilder.finish(out, ptr);
return string(out);
}
}