Skip to content

Instantly share code, notes, and snippets.

@sreyemnayr
Last active September 19, 2022 21:11
Show Gist options
  • Save sreyemnayr/d36747b19799257588bd4a1d31075a72 to your computer and use it in GitHub Desktop.
Save sreyemnayr/d36747b19799257588bd4a1d31075a72 to your computer and use it in GitHub Desktop.
// SPDX-License-Identifier: MIT
//
// ╓ ▄▄
// ╫█ ██▄
// ██ ╫▌▀▌ █▌
// ▓▌█┐ █¡╨█ █╫▌
// █µ▀▌ ╚▓:└▀▌ █░╠▓
// "█¡╙█▄ ╫▌⌐:│▀▌ ╚▓^┘▓▄
// ╙█¡"╨▓▄ █▄::"┘▀▓▄█▌::╨▓┐
// ╙█¡┌:╨▀▓█µ┌::┌:│▀▀:┌::╨▓▄
// ╙█µ┌┌:\╙!:⌐┌┌::»;╓▄▄▄▄▓██▓▄,
// █Q╓▄▄▄▄▌▓▓▓▓▓▓▀▀▀▀▀▀▀▓██▌▀▓█▌▄
// ▓█▀▀╨╙┘⌐~' '╥▓█▓▀╡█▌┐┐┐│╨▀▓▓▄▄
// ╒█╨ '╓▓█▀╠░░░▒█▌▄▄░┐┐┐┐\│╨▀▓▓▄▄
// ╓█╙' ''▄▓█╬░░▄▓▀▀╢█▌╨╟▀▓▌µ¡┐¡┐!╫▓└╨▀▓▓▄▄
// ╓█╛' ▄█▓▒░Ü▓██▓▓▓▓█████▓█▓Ö┐┐┐▐█ ╙██▓▄▄
// ╒█╙ '╥█▓╡░░░░╡┤┤▀▀▀░█▌└╨╨┐¡┐┐┐┐¡▓▌ ╨▀▀└╨▀▓▓▄╖
// ┌█╨ '.▓█╟░░░░░░░░░░░░░█▌┐┐┐┐┐┐┐┐┐¡█▓▌▌▄▄▄▄▄µ,, └╨▀▓▓▄┐
// █▀ .╓█▓│░░░░░░░░░░░░░░█▌┐┐┐┐┐┐┐┐¡┐▓▌ └┴╙╟╫▓▓███████▓▓▓▌▄▌▓██▓▄┐
// █▌' '▄█▀░░░░░░░░░░░░░░░░█▌┐┐┐┐┐;(Q▄▄▓█▓▓▀▀▀╙╙╙└└└ └└└╙╙╙╙▀▀▀▓▀
// █▓'' ╫█▒░░░░░░░░░░░░░░░░░█▌¡p▄▓▓▓▀╙└
// ╫█ ' '╫█▒░░░░░░░░░░░░░░░░░▒██▓▀└
// ╔█─. ╟█▒░░░░░░░░░░░░░░░▄▓█▀█▌
// █╝' ╥█▀░░░░░░░░░░░░░▄▓█▀┤┌⌐█▌
// █▌ .█▓░░░░░░░░░░░░▄█▓╙┌┌┌┌⌐█▌
// ╫█. '╫█░░░░░░░░░░░▄█▀┘┌┌:┌┌:⌐█▌
// ┌█b. .█▌░░░░░░░░░▄█▀└:┌┌┌┌┌┌┌⌐█▌
// ╙▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀¬
//
// The Stoics by Gabe Weis
// Phoenix Burn Token
// thestoics.art
//
// Assisted by Merlyn Labs
//
// Smart Contract v0 by Ryan Meyers
//
// Generosity attracts generosity
// The world will be saved by beauty
//
//
pragma solidity ^0.8.16;
import "chiru-labs/[email protected]/contracts/extensions/ERC721AQueryable.sol";
import "OpenZeppelin/[email protected]/contracts/access/Ownable.sol";
import "OpenZeppelin/[email protected]/contracts/token/common/ERC2981.sol";
import "OpenZeppelin/[email protected]/contracts/utils/cryptography/draft-EIP712.sol";
import "OpenZeppelin/[email protected]/contracts/utils/cryptography/ECDSA.sol";
import "OpenZeppelin/[email protected]/contracts/utils/Base64.sol";
import "OpenZeppelin/[email protected]/contracts/utils/Strings.sol";
interface ITheStoics is IERC721AQueryable {}
contract PhoenixBurn is ERC2981, EIP712, ERC721AQueryable, Ownable {
error ExceedsMaxSupply();
error InsufficientPhoenixSouls();
error NotAllowed();
error BadTiming();
error InsufficientStoics();
error BadMintKey();
event RedeemedPhoenixSouls(address holder, uint howMany, string perkName);
struct MintKey {
address wallet;
uint8 generation;
}
bytes32 private constant MINTKEY_TYPE_HASH = keccak256("MintKey(address wallet,uint8 generation)");
address private _signer;
uint8 public MAX_SUPPLY = 222;
uint8 public GENERATION = 0;
bool public REBIRTH_CYCLE = false;
uint public CURRENT_PERK_COST = 5;
string public CURRENT_PERK = "Inactive";
address public STOICS;
string public imageURI;
constructor(
string memory name,
string memory symbol,
address signer,
address receiver,
address stoics
)
ERC721A(name, symbol)
EIP712(name, "1")
{
setSigner(signer);
setStoics(stoics);
_setDefaultRoyalty(receiver, 1000);
}
// If REBIRTH_CYCLE, minting is open and burning is closed; if not vice-versa
function startRebirthCycle() public onlyOwner {
REBIRTH_CYCLE = true;
}
function endRebirthCycle() public onlyOwner {
REBIRTH_CYCLE = false;
GENERATION += 1;
}
// Send a list of addresses one PBT each
function riseAndFlyTo(address[] calldata targets) public onlyOwner {
if (totalSupply() + targets.length > MAX_SUPPLY) revert ExceedsMaxSupply();
for (uint i = 0; i < targets.length; i++) {
_mint(targets[i], 1);
}
}
// Mint PBTs
// Requirements:
// Must be in REBIRTH_CYCLE
// Must have a valid mint key (by wallet/generation)
// Must not exceed 222 total
// Must have at least the number of Stoics as PBTs
//
// When the total supply reaches 222, it automatically switches to a new generation and toggles REBIRTH_CYCLE
function riseFromTheAshes(bytes calldata signature, uint howMany) public {
if (!REBIRTH_CYCLE) revert BadTiming();
if (!verify(signature)) revert BadMintKey();
if (totalSupply() + howMany > MAX_SUPPLY) revert ExceedsMaxSupply();
if (IERC721A(STOICS).balanceOf(msg.sender) - balanceOf(msg.sender) < howMany) revert InsufficientStoics();
_mint(msg.sender, howMany);
if(totalSupply() == MAX_SUPPLY){
REBIRTH_CYCLE = false;
GENERATION += 1;
}
}
// Burn a PBT
// May only be called by the owner of the token OR the owner of the contract
// When burned, credits are given to that wallet address and the token goes away
function returnToDust(uint[] calldata tokens) public {
if (REBIRTH_CYCLE) revert BadTiming();
for (uint i = 0; i < tokens.length; i++){
_burn(tokens[i], msg.sender != owner());
}
}
// How many phoenix souls are held by the wallet?
// = number burned - number spent
function phoenixSoulBalance(address holder) public view returns (uint256){
return _numberBurned(holder) - _getAux(holder);
}
// Redeem souls for a perk
function redeemPhoenixSouls(address holder, uint64 howMany) public {
if (howMany != CURRENT_PERK_COST) revert InsufficientPhoenixSouls();
if(holder != msg.sender){
if (msg.sender != owner()){
revert NotAllowed();
}
}
if (phoenixSoulBalance(holder) < howMany) revert InsufficientPhoenixSouls();
_setAux(holder, _getAux(holder) + howMany);
emit RedeemedPhoenixSouls(holder, howMany, CURRENT_PERK);
}
// Setter methods
function setSigner(address signer) public onlyOwner {
_signer = signer;
}
function setStoics(address stoics) public onlyOwner {
STOICS = stoics;
}
function setImageURI(string memory uri) public onlyOwner {
imageURI = uri;
}
function setRoyalty(address receiver, uint96 feeNumerator) public onlyOwner {
_setDefaultRoyalty(receiver, feeNumerator);
}
function setCurrentPerk(string memory perk, uint perk_cost) public onlyOwner {
CURRENT_PERK = perk;
CURRENT_PERK_COST = perk_cost;
}
// Public getter methods
function BURN_CYCLE() public view returns (bool) {
return !REBIRTH_CYCLE;
}
function verify(bytes calldata signature) public view returns (bool) {
bytes32 digest = _hashTypedDataV4(
keccak256(
abi.encode(
MINTKEY_TYPE_HASH,
msg.sender,
GENERATION
)
)
);
return ECDSA.recover(digest, signature) == _signer;
}
/**
* @dev Returns the Uniform Resource Identifier (URI) for `tokenId` token.
*/
function tokenURI(uint256 tokenId) public view virtual override(IERC721A, ERC721A) returns (string memory) {
if (!_exists(tokenId)) revert URIQueryForNonexistentToken();
string memory baseURI = "data:application/json;base64,";
string memory json = string(
abi.encodePacked(
'{"name": "Phoenix Burn Token #',
Strings.toString(tokenId),
'", "description": "222 Phoenixes to Burn and Revive", "image":"',
imageURI,
'", "attributes": [{"trait_type": "Generation", "value": ',
Strings.toString(_ownershipOf(tokenId).extraData),
'}, { "display_type": "date", "trait_type": "Rebirth", "value": ',
Strings.toString(_ownershipOf(tokenId).startTimestamp),
'}]}'
)
);
string memory jsonBase64Encoded = Base64.encode(bytes(json));
return string(abi.encodePacked(baseURI, jsonBase64Encoded));
}
function _afterTokenTransfers(
address from,
address to,
uint256 startTokenId,
uint256 quantity
) internal virtual override(ERC721A) {
if(REBIRTH_CYCLE){
if(IERC721A(STOICS).balanceOf(to) < 1) {
revert InsufficientStoics();
}
}
if(from == address(0)){
for(uint i=startTokenId; i<quantity; i++){
_setExtraDataAt(i, GENERATION);
}
}
}
function _extraData(
address from,
address to,
uint24 previousExtraData
) internal pure override(ERC721A) returns (uint24) {
return previousExtraData;
}
// Override to support royalties via ERC2981
function supportsInterface(
bytes4 interfaceId
) public view virtual override(IERC721A, ERC721A, ERC2981) returns (bool) {
// Supports the following `interfaceId`s:
// - IERC165: 0x01ffc9a7
// - IERC721: 0x80ac58cd
// - IERC721Metadata: 0x5b5e139f
// - IERC2981: 0x2a55205a
return
ERC721A.supportsInterface(interfaceId) ||
ERC2981.supportsInterface(interfaceId);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment