You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

132 lines
5.6 KiB
Solidity

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
import {ERC721} from "solmate/tokens/ERC721.sol";
import {Owned} from "solmate/auth/Owned.sol";
import {LibString} from "solmate/utils/LibString.sol";
import {Main} from "./Main.sol";
/**
@title Unaboomer
@author 0xgrimey.eth
@notice This contract contains ERC-721 Unaboomer tokens (BOOMR) which are the profile
picture and membership tokens for the Unaboomer NFT project and chain based game.
Each Unaboomer is a unique, dynamically generated pixel avatar in the likeness
of the real-life Unabomber, Theodore Kaczynski. Unaboomers can be "killed" by
other players by "sending" (burning) mailbombs. When Unaboomers are killed their
corresponding image is replaced with an explosion, rendering it worthless as any
rarity associated with it ceases to exist. The game stops when SURVIVOR_COUNT
threshold is breached. The surviving players (any address which holds an "alive"
Unaboomer) will advance to the next round of gameplay.
@dev All contract functions regarding token burning and minting are limited to
the Main interface where the logic and validation resides.
*/
contract Unaboomer is ERC721, Owned {
using LibString for uint256;
/// Track if a BOOMR token is toggled as alive or dead
mapping(uint256 => bool) public tokenDead;
/// Maximum supply of BOOMR tokens
uint256 public constant MAX_SUPPLY = 10000;
/// Maximum amount of survivors remaining to advance to the next round
uint256 public constant SURVIVOR_COUNT = 1000;
/// The total amount of Unaboomers who have been killed during the game
uint256 public totalKillCount;
/// Number of tokens minted (total supply)
uint256 public minted;
/// Base URI for living Unaboomers - original pixelated avatars
string public aliveURI;
/// Base URI for dead Unaboomers - pixelated explosion
string public deadURI;
/// Contract address of the deployed Main contract interface to the game
Main public main;
constructor() ERC721("Unaboomer", "BOOMR") Owned(msg.sender) {}
// =========================================================================
// Admin
// =========================================================================
/// Withdraw funds to contract owner
function withdraw() external onlyOwner {
uint256 balance = address(this).balance;
payable(msg.sender).transfer(balance);
}
/// Set metadata URI for living Unaboomer tokens
/// @param _baseURI IPFS hash or URL to retrieve JSON metadata for living Unaboomer tokens
function setAliveURI(string calldata _baseURI) external onlyOwner {
aliveURI = _baseURI;
}
/// Set metadata URI for dead Unaboomer tokens
/// @param _baseURI IPFS hash or URL to retrieve JSON metadata for dead Unaboomer tokens
function setDeadURI(string calldata _baseURI) external onlyOwner {
deadURI = _baseURI;
}
/// Set main contract address for executing functions
/// @param _address Contract address of the deployed Main contract
function setMainContract(address _address) external onlyOwner {
main = Main(_address);
}
// =========================================================================
// Modifiers
// =========================================================================
/// Limit function execution to deployed Main contract
modifier onlyMain {
require(msg.sender == address(main), "invalid msg sender");
_;
}
// =========================================================================
// Tokens
// =========================================================================
/// Helper function to get supply minted
/// @return supply Number of Unaboomers radicalized in totality (minted)
function totalSupply() public view returns (uint256) {
return minted;
}
/// Mint tokens from main contract
/// @param _to Address to mint BOOMR tokens to
/// @param _amount Amount of BOOMR tokens to mint
function radicalize(address _to, uint256 _amount) external payable onlyMain {
require(totalSupply() + _amount <= MAX_SUPPLY, "supply reached");
for (uint256 i; i < _amount; i++) {
_safeMint(_to, minted);
minted++;
}
}
/// Toggle token state from living to dead
/// @param tokenId Token ID of BOOMR to toggle living -> dead and increment kill count
function die(uint256 tokenId) external onlyMain {
require(tokenId < totalSupply(), "invalid token id");
if (tokenDead[tokenId] == false) {
totalKillCount++;
tokenDead[tokenId] = true;
}
}
// Return URI to retrieve JSON metadata from - points to images and descriptions
/// @param _tokenId Token ID of BOOMR to fetch URI for
/// @return string IPFS or HTTP URI to retrieve JSON metadata from
function tokenURI(uint256 _tokenId) public view override returns (string memory) {
if (tokenDead[_tokenId] == true) {
return string(abi.encodePacked(deadURI, _tokenId.toString(), ".json"));
} else {
return string(abi.encodePacked(aliveURI, _tokenId.toString(), ".json"));
}
}
/// Checks if contract supports a given interface
/// @param interfaceId The interface ID to check if contract supports
/// @return bool Boolean value if contract supports interface ID or not
function supportsInterface(bytes4 interfaceId) public view virtual override (ERC721) returns (bool) {
return super.supportsInterface(interfaceId);
}
}