Last active
September 3, 2022 13:36
-
-
Save wilsoncusack/1f42782fef5469874722a76761c788ec to your computer and use it in GitHub Desktop.
Anonymice Contract
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// from https://etherscan.io/token/0xbad6186E92002E312078b5a1dAfd5ddf63d3f731#code | |
// contracts/Anonymice.sol | |
// SPDX-License-Identifier: MIT | |
pragma solidity ^0.8.0; | |
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol"; | |
import "./ICheeth.sol"; | |
import "./AnonymiceLibrary.sol"; | |
contract Anonymice is ERC721Enumerable { | |
/* | |
___ _ | |
/ _ \ (_) | |
/ /_\ \_ __ ___ _ __ _ _ _ __ ___ _ ___ ___ | |
| _ | '_ \ / _ \| '_ \| | | | '_ ` _ \| |/ __/ _ \ | |
| | | | | | | (_) | | | | |_| | | | | | | | (_| __/ | |
\_| |_/_| |_|\___/|_| |_|\__, |_| |_| |_|_|\___\___| | |
__/ | | |
|___/ | |
*/ | |
using AnonymiceLibrary for uint8; | |
struct Trait { | |
string traitName; | |
string traitType; | |
string pixels; | |
uint256 pixelCount; | |
} | |
//Mappings | |
mapping(uint256 => Trait[]) public traitTypes; | |
mapping(string => bool) hashToMinted; | |
mapping(uint256 => string) internal tokenIdToHash; | |
//uint256s | |
uint256 MAX_SUPPLY = 10000; | |
uint256 MINTS_PER_TIER = 2000; | |
uint256 SEED_NONCE = 0; | |
//string arrays | |
string[] LETTERS = [ | |
"a", | |
"b", | |
"c", | |
"d", | |
"e", | |
"f", | |
"g", | |
"h", | |
"i", | |
"j", | |
"k", | |
"l", | |
"m", | |
"n", | |
"o", | |
"p", | |
"q", | |
"r", | |
"s", | |
"t", | |
"u", | |
"v", | |
"w", | |
"x", | |
"y", | |
"z" | |
]; | |
//uint arrays | |
uint16[][8] TIERS; | |
//address | |
address cheethAddress; | |
address _owner; | |
constructor() ERC721("Anonymice", "MICE") { | |
_owner = msg.sender; | |
//Declare all the rarity tiers | |
//Hat | |
TIERS[0] = [50, 150, 200, 300, 400, 500, 600, 900, 1200, 5700]; | |
//whiskers | |
TIERS[1] = [200, 800, 1000, 3000, 5000]; | |
//Neck | |
TIERS[2] = [300, 800, 900, 1000, 7000]; | |
//Earrings | |
TIERS[3] = [50, 200, 300, 300, 9150]; | |
//Eyes | |
TIERS[4] = [50, 100, 400, 450, 500, 700, 1800, 2000, 2000, 2000]; | |
//Mouth | |
TIERS[5] = [1428, 1428, 1428, 1429, 1429, 1429, 1429]; | |
//Nose | |
TIERS[6] = [2000, 2000, 2000, 2000, 2000]; | |
//Character | |
TIERS[7] = [20, 70, 721, 1000, 1155, 1200, 1300, 1434, 1541, 1559]; | |
} | |
/* | |
__ __ _ _ _ ___ _ _ | |
| \/ (_)_ _| |_(_)_ _ __ _ | __| _ _ _ __| |_(_)___ _ _ ___ | |
| |\/| | | ' \ _| | ' \/ _` | | _| || | ' \/ _| _| / _ \ ' \(_-< | |
|_| |_|_|_||_\__|_|_||_\__, | |_| \_,_|_||_\__|\__|_\___/_||_/__/ | |
|___/ | |
*/ | |
/** | |
* @dev Converts a digit from 0 - 10000 into its corresponding rarity based on the given rarity tier. | |
* @param _randinput The input from 0 - 10000 to use for rarity gen. | |
* @param _rarityTier The tier to use. | |
*/ | |
function rarityGen(uint256 _randinput, uint8 _rarityTier) | |
internal | |
view | |
returns (string memory) | |
{ | |
uint16 currentLowerBound = 0; | |
for (uint8 i = 0; i < TIERS[_rarityTier].length; i++) { | |
uint16 thisPercentage = TIERS[_rarityTier][i]; | |
if ( | |
_randinput >= currentLowerBound && | |
_randinput < currentLowerBound + thisPercentage | |
) return i.toString(); | |
currentLowerBound = currentLowerBound + thisPercentage; | |
} | |
revert(); | |
} | |
/** | |
* @dev Generates a 9 digit hash from a tokenId, address, and random number. | |
* @param _t The token id to be used within the hash. | |
* @param _a The address to be used within the hash. | |
* @param _c The custom nonce to be used within the hash. | |
*/ | |
function hash( | |
uint256 _t, | |
address _a, | |
uint256 _c | |
) internal returns (string memory) { | |
require(_c < 10); | |
// This will generate a 9 character string. | |
//The last 8 digits are random, the first is 0, due to the mouse not being burned. | |
string memory currentHash = "0"; | |
for (uint8 i = 0; i < 8; i++) { | |
SEED_NONCE++; | |
uint16 _randinput = uint16( | |
uint256( | |
keccak256( | |
abi.encodePacked( | |
block.timestamp, | |
block.difficulty, | |
_t, | |
_a, | |
_c, | |
SEED_NONCE | |
) | |
) | |
) % 10000 | |
); | |
currentHash = string( | |
abi.encodePacked(currentHash, rarityGen(_randinput, i)) | |
); | |
} | |
if (hashToMinted[currentHash]) return hash(_t, _a, _c + 1); | |
return currentHash; | |
} | |
/** | |
* @dev Returns the current cheeth cost of minting. | |
*/ | |
function currentCheethCost() public view returns (uint256) { | |
uint256 _totalSupply = totalSupply(); | |
if (_totalSupply <= 2000) return 0; | |
if (_totalSupply > 2000 && _totalSupply <= 4000) | |
return 1000000000000000000; | |
if (_totalSupply > 4000 && _totalSupply <= 6000) | |
return 2000000000000000000; | |
if (_totalSupply > 6000 && _totalSupply <= 8000) | |
return 3000000000000000000; | |
if (_totalSupply > 8000 && _totalSupply <= 10000) | |
return 4000000000000000000; | |
revert(); | |
} | |
/** | |
* @dev Mint internal, this is to avoid code duplication. | |
*/ | |
function mintInternal() internal { | |
uint256 _totalSupply = totalSupply(); | |
require(_totalSupply < MAX_SUPPLY); | |
require(!AnonymiceLibrary.isContract(msg.sender)); | |
uint256 thisTokenId = _totalSupply; | |
tokenIdToHash[thisTokenId] = hash(thisTokenId, msg.sender, 0); | |
hashToMinted[tokenIdToHash[thisTokenId]] = true; | |
_mint(msg.sender, thisTokenId); | |
} | |
/** | |
* @dev Mints new tokens. | |
*/ | |
function mintMouse() public { | |
if (totalSupply() < MINTS_PER_TIER) return mintInternal(); | |
//Burn this much cheeth | |
ICheeth(cheethAddress).burnFrom(msg.sender, currentCheethCost()); | |
return mintInternal(); | |
} | |
/** | |
* @dev Burns and mints new. | |
* @param _tokenId The token to burn. | |
*/ | |
function burnForMint(uint256 _tokenId) public { | |
require(ownerOf(_tokenId) == msg.sender); | |
//Burn token | |
_transfer( | |
msg.sender, | |
0x000000000000000000000000000000000000dEaD, | |
_tokenId | |
); | |
mintInternal(); | |
} | |
/* | |
____ ___ ____ ___ _____ __ __ ____ __ ______ ____ ___ ____ _____ | |
| \ / _] / || \ | || | || \ / ] || |/ \ | \ / ___/ | |
| D ) / [_ | o || \ | __|| | || _ | / /| | | || || _ ( \_ | |
| / | _]| || D | | |_ | | || | |/ / |_| |_| | || O || | |\__ | | |
| \ | [_ | _ || | | _] | : || | / \_ | | | || || | |/ \ | | |
| . \| || | || | | | | || | \ | | | | || || | |\ | | |
|__|\_||_____||__|__||_____| |__| \__,_||__|__|\____| |__| |____|\___/ |__|__| \___| | |
*/ | |
/** | |
* @dev Helper function to reduce pixel size within contract | |
*/ | |
function letterToNumber(string memory _inputLetter) | |
internal | |
view | |
returns (uint8) | |
{ | |
for (uint8 i = 0; i < LETTERS.length; i++) { | |
if ( | |
keccak256(abi.encodePacked((LETTERS[i]))) == | |
keccak256(abi.encodePacked((_inputLetter))) | |
) return (i + 1); | |
} | |
revert(); | |
} | |
/** | |
* @dev Hash to SVG function | |
*/ | |
function hashToSVG(string memory _hash) | |
public | |
view | |
returns (string memory) | |
{ | |
string memory svgString; | |
bool[24][24] memory placedPixels; | |
for (uint8 i = 0; i < 9; i++) { | |
uint8 thisTraitIndex = AnonymiceLibrary.parseInt( | |
AnonymiceLibrary.substring(_hash, i, i + 1) | |
); | |
for ( | |
uint16 j = 0; | |
j < traitTypes[i][thisTraitIndex].pixelCount; | |
j++ | |
) { | |
string memory thisPixel = AnonymiceLibrary.substring( | |
traitTypes[i][thisTraitIndex].pixels, | |
j * 4, | |
j * 4 + 4 | |
); | |
uint8 x = letterToNumber( | |
AnonymiceLibrary.substring(thisPixel, 0, 1) | |
); | |
uint8 y = letterToNumber( | |
AnonymiceLibrary.substring(thisPixel, 1, 2) | |
); | |
if (placedPixels[x][y]) continue; | |
svgString = string( | |
abi.encodePacked( | |
svgString, | |
"<rect class='c", | |
AnonymiceLibrary.substring(thisPixel, 2, 4), | |
"' x='", | |
x.toString(), | |
"' y='", | |
y.toString(), | |
"'/>" | |
) | |
); | |
placedPixels[x][y] = true; | |
} | |
} | |
svgString = string( | |
abi.encodePacked( | |
'<svg id="mouse-svg" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMinYMin meet" viewBox="0 0 24 24"> ', | |
svgString, | |
"<style>rect{width:1px;height:1px;} #mouse-svg{shape-rendering: crispedges;} .c00{fill:#000000}.c01{fill:#B1ADAC}.c02{fill:#D7D7D7}.c03{fill:#FFA6A6}.c04{fill:#FFD4D5}.c05{fill:#B9AD95}.c06{fill:#E2D6BE}.c07{fill:#7F625A}.c08{fill:#A58F82}.c09{fill:#4B1E0B}.c10{fill:#6D2C10}.c11{fill:#D8D8D8}.c12{fill:#F5F5F5}.c13{fill:#433D4B}.c14{fill:#8D949C}.c15{fill:#05FF00}.c16{fill:#01C700}.c17{fill:#0B8F08}.c18{fill:#421C13}.c19{fill:#6B392A}.c20{fill:#A35E40}.c21{fill:#DCBD91}.c22{fill:#777777}.c23{fill:#848484}.c24{fill:#ABABAB}.c25{fill:#BABABA}.c26{fill:#C7C7C7}.c27{fill:#EAEAEA}.c28{fill:#0C76AA}.c29{fill:#0E97DB}.c30{fill:#10A4EC}.c31{fill:#13B0FF}.c32{fill:#2EB9FE}.c33{fill:#54CCFF}.c34{fill:#50C0F2}.c35{fill:#54CCFF}.c36{fill:#72DAFF}.c37{fill:#B6EAFF}.c38{fill:#FFFFFF}.c39{fill:#954546}.c40{fill:#0B87F7}.c41{fill:#FF2626}.c42{fill:#180F02}.c43{fill:#2B2319}.c44{fill:#FBDD4B}.c45{fill:#F5B923}.c46{fill:#CC8A18}.c47{fill:#3C2203}.c48{fill:#53320B}.c49{fill:#7B501D}.c50{fill:#FFE646}.c51{fill:#FFD627}.c52{fill:#F5B700}.c53{fill:#242424}.c54{fill:#4A4A4A}.c55{fill:#676767}.c56{fill:#F08306}.c57{fill:#FCA30E}.c58{fill:#FEBC0E}.c59{fill:#FBEC1C}.c60{fill:#14242F}.c61{fill:#B06837}.c62{fill:#8F4B0E}.c63{fill:#D88227}.c64{fill:#B06837}</style></svg>" | |
) | |
); | |
return svgString; | |
} | |
/** | |
* @dev Hash to metadata function | |
*/ | |
function hashToMetadata(string memory _hash) | |
public | |
view | |
returns (string memory) | |
{ | |
string memory metadataString; | |
for (uint8 i = 0; i < 9; i++) { | |
uint8 thisTraitIndex = AnonymiceLibrary.parseInt( | |
AnonymiceLibrary.substring(_hash, i, i + 1) | |
); | |
metadataString = string( | |
abi.encodePacked( | |
metadataString, | |
'{"trait_type":"', | |
traitTypes[i][thisTraitIndex].traitType, | |
'","value":"', | |
traitTypes[i][thisTraitIndex].traitName, | |
'"}' | |
) | |
); | |
if (i != 8) | |
metadataString = string(abi.encodePacked(metadataString, ",")); | |
} | |
return string(abi.encodePacked("[", metadataString, "]")); | |
} | |
/** | |
* @dev Returns the SVG and metadata for a token Id | |
* @param _tokenId The tokenId to return the SVG and metadata for. | |
*/ | |
function tokenURI(uint256 _tokenId) | |
public | |
view | |
override | |
returns (string memory) | |
{ | |
require(_exists(_tokenId)); | |
string memory tokenHash = _tokenIdToHash(_tokenId); | |
return | |
string( | |
abi.encodePacked( | |
"data:application/json;base64,", | |
AnonymiceLibrary.encode( | |
bytes( | |
string( | |
abi.encodePacked( | |
'{"name": "Anonymice #', | |
AnonymiceLibrary.toString(_tokenId), | |
'", "description": "Anonymice is a collection of 10,000 unique mice. All the metadata and images are generated and stored 100% on-chain. No IPFS, no API. Just the Ethereum blockchain.", "image": "data:image/svg+xml;base64,', | |
AnonymiceLibrary.encode( | |
bytes(hashToSVG(tokenHash)) | |
), | |
'","attributes":', | |
hashToMetadata(tokenHash), | |
"}" | |
) | |
) | |
) | |
) | |
) | |
); | |
} | |
/** | |
* @dev Returns a hash for a given tokenId | |
* @param _tokenId The tokenId to return the hash for. | |
*/ | |
function _tokenIdToHash(uint256 _tokenId) | |
public | |
view | |
returns (string memory) | |
{ | |
string memory tokenHash = tokenIdToHash[_tokenId]; | |
//If this is a burned token, override the previous hash | |
if (ownerOf(_tokenId) == 0x000000000000000000000000000000000000dEaD) { | |
tokenHash = string( | |
abi.encodePacked( | |
"1", | |
AnonymiceLibrary.substring(tokenHash, 1, 9) | |
) | |
); | |
} | |
return tokenHash; | |
} | |
/** | |
* @dev Returns the wallet of a given wallet. Mainly for ease for frontend devs. | |
* @param _wallet The wallet to get the tokens of. | |
*/ | |
function walletOfOwner(address _wallet) | |
public | |
view | |
returns (uint256[] memory) | |
{ | |
uint256 tokenCount = balanceOf(_wallet); | |
uint256[] memory tokensId = new uint256[](tokenCount); | |
for (uint256 i; i < tokenCount; i++) { | |
tokensId[i] = tokenOfOwnerByIndex(_wallet, i); | |
} | |
return tokensId; | |
} | |
/* | |
___ __ __ ____ ___ ____ _____ __ __ ____ __ ______ ____ ___ ____ _____ | |
/ \ | |__| || \ / _]| \ | || | || \ / ] || |/ \ | \ / ___/ | |
| || | | || _ | / [_ | D ) | __|| | || _ | / /| | | || || _ ( \_ | |
| O || | | || | || _]| / | |_ | | || | |/ / |_| |_| | || O || | |\__ | | |
| || ` ' || | || [_ | \ | _] | : || | / \_ | | | || || | |/ \ | | |
| | \ / | | || || . \ | | | || | \ | | | | || || | |\ | | |
\___/ \_/\_/ |__|__||_____||__|\_| |__| \__,_||__|__|\____| |__| |____|\___/ |__|__| \___| | |
*/ | |
/** | |
* @dev Clears the traits. | |
*/ | |
function clearTraits() public onlyOwner { | |
for (uint256 i = 0; i < 9; i++) { | |
delete traitTypes[i]; | |
} | |
} | |
/** | |
* @dev Add a trait type | |
* @param _traitTypeIndex The trait type index | |
* @param traits Array of traits to add | |
*/ | |
function addTraitType(uint256 _traitTypeIndex, Trait[] memory traits) | |
public | |
onlyOwner | |
{ | |
for (uint256 i = 0; i < traits.length; i++) { | |
traitTypes[_traitTypeIndex].push( | |
Trait( | |
traits[i].traitName, | |
traits[i].traitType, | |
traits[i].pixels, | |
traits[i].pixelCount | |
) | |
); | |
} | |
return; | |
} | |
/** | |
* @dev Sets the cheeth ERC20 address | |
* @param _cheethAddress The cheeth address | |
*/ | |
function setCheethAddress(address _cheethAddress) public onlyOwner { | |
cheethAddress = _cheethAddress; | |
} | |
/** | |
* @dev Transfers ownership | |
* @param _newOwner The new owner | |
*/ | |
function transferOwnership(address _newOwner) public onlyOwner { | |
_owner = _newOwner; | |
} | |
/** | |
* @dev Modifier to only allow owner to call functions | |
*/ | |
modifier onlyOwner() { | |
require(_owner == msg.sender); | |
_; | |
} | |
} | |
// SPDX-License-Identifier: MIT | |
pragma solidity ^0.8.0; | |
library AnonymiceLibrary { | |
string internal constant TABLE = | |
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; | |
function encode(bytes memory data) internal pure returns (string memory) { | |
if (data.length == 0) return ""; | |
// load the table into memory | |
string memory table = TABLE; | |
// multiply by 4/3 rounded up | |
uint256 encodedLen = 4 * ((data.length + 2) / 3); | |
// add some extra buffer at the end required for the writing | |
string memory result = new string(encodedLen + 32); | |
assembly { | |
// set the actual output length | |
mstore(result, encodedLen) | |
// prepare the lookup table | |
let tablePtr := add(table, 1) | |
// input ptr | |
let dataPtr := data | |
let endPtr := add(dataPtr, mload(data)) | |
// result ptr, jump over length | |
let resultPtr := add(result, 32) | |
// run over the input, 3 bytes at a time | |
for { | |
} lt(dataPtr, endPtr) { | |
} { | |
dataPtr := add(dataPtr, 3) | |
// read 3 bytes | |
let input := mload(dataPtr) | |
// write 4 characters | |
mstore( | |
resultPtr, | |
shl(248, mload(add(tablePtr, and(shr(18, input), 0x3F)))) | |
) | |
resultPtr := add(resultPtr, 1) | |
mstore( | |
resultPtr, | |
shl(248, mload(add(tablePtr, and(shr(12, input), 0x3F)))) | |
) | |
resultPtr := add(resultPtr, 1) | |
mstore( | |
resultPtr, | |
shl(248, mload(add(tablePtr, and(shr(6, input), 0x3F)))) | |
) | |
resultPtr := add(resultPtr, 1) | |
mstore( | |
resultPtr, | |
shl(248, mload(add(tablePtr, and(input, 0x3F)))) | |
) | |
resultPtr := add(resultPtr, 1) | |
} | |
// padding with '=' | |
switch mod(mload(data), 3) | |
case 1 { | |
mstore(sub(resultPtr, 2), shl(240, 0x3d3d)) | |
} | |
case 2 { | |
mstore(sub(resultPtr, 1), shl(248, 0x3d)) | |
} | |
} | |
return result; | |
} | |
/** | |
* @dev Converts a `uint256` to its ASCII `string` decimal representation. | |
*/ | |
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 parseInt(string memory _a) | |
internal | |
pure | |
returns (uint8 _parsedInt) | |
{ | |
bytes memory bresult = bytes(_a); | |
uint8 mint = 0; | |
for (uint8 i = 0; i < bresult.length; i++) { | |
if ( | |
(uint8(uint8(bresult[i])) >= 48) && | |
(uint8(uint8(bresult[i])) <= 57) | |
) { | |
mint *= 10; | |
mint += uint8(bresult[i]) - 48; | |
} | |
} | |
return mint; | |
} | |
function substring( | |
string memory str, | |
uint256 startIndex, | |
uint256 endIndex | |
) internal pure returns (string memory) { | |
bytes memory strBytes = bytes(str); | |
bytes memory result = new bytes(endIndex - startIndex); | |
for (uint256 i = startIndex; i < endIndex; i++) { | |
result[i - startIndex] = strBytes[i]; | |
} | |
return string(result); | |
} | |
function isContract(address account) internal view returns (bool) { | |
// This method relies on extcodesize, which returns 0 for contracts in | |
// construction, since the code is only stored at the end of the | |
// constructor execution. | |
uint256 size; | |
assembly { | |
size := extcodesize(account) | |
} | |
return size > 0; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment