/**`
*Submitted for verification at polygonscan.com on 2023-04-26
*/
// SPDX-License-Identifier: MIT
// File: Forge Token/Forge_ERC721.sol
// Contract based on https://docs.openzeppelin.com/contracts/4.x/erc721
pragma solidity ^0.8.4;
interface IERC721Receiver {
function onERC721Received(
address _operator,
address _from,
uint256 _tokenId,
bytes memory _data
) external returns(bytes4);
}
//=============================================================================
/// @title Forge_ERC721
/// @dev Implementation of the ERC-721 Non-Fungible Token Standard.
/// - This implementation was created to support giving tokens attribute data
/// without increasing the gas cost of minting.
/// - This implementation also enables tokenOfOwnerByIndex and totalSupply.
/// @author Forge Inc.
//=============================================================================
abstract contract Forge_ERC721 {
//-------------------------------------------------------------------------
/// @title TokenData
/// @dev A data structure containing information about a specific token ID.
/// - Initializing TokenData costs the same amount of gas as initializing
/// _owners for the token ID in the OpenZeppelin ERC721 implementation.
//-------------------------------------------------------------------------
struct TokenData {
// The owner of this token. An address takes up 20 bytes.
address tokenOwner;
// The index of this token in OwnerData's tokensOwned array. Max value 65536.
uint16 tokenIndex;
// Leftover to fill with arbitrary data by the implementation contract.
uint80 extraData;
}
//-------------------------------------------------------------------------
/// @title OwnerData
/// @dev A data structure containing information about a specific address.
/// - Initialized the first time an owner either receives a token or sets
/// an address approved operator. The expected-case is that OwnerData is
/// initialized when an owner mints their first token.
/// - Initializing OwnerData costs the same amount of gas as initializing
/// _balances for the owner in the OpenZeppelin ERC721 implementation.
//-------------------------------------------------------------------------
struct OwnerData {
// An array containing the token ids of all tokens owned by this owner
uint24[] tokensOwned;
// A mapping of addresses to if they are approved operators for this owner
mapping (address=>bool) operatorApprovals;
}
// Token name
string public name;
// Token symbol
string public symbol;
// An array containing TokenData for all tokens ever minted.
// A token's identifier is used to index directly to its data, so the
// order of _tokenData must never be modified.
TokenData[] internal _tokenData;
// A mapping containing OwnerData for all addresses
mapping (address=>OwnerData) internal _ownerData;
// A mapping containing approved addresses for each token ID
mapping (uint=>address) private _tokenApprovals;
// Number of burned tokens, used to calculate total supply
uint internal _burnedTokens;
//-------------------------------------------------------------------------
/// @dev This emits when ownership of any NFT changes by any mechanism.
/// This event also emits when NFTs are created (`from` == 0) and
/// destroyed (`to` == 0). Also indicates that the approved address for
/// the NFT is reset to none.
//-------------------------------------------------------------------------
event Transfer(
address indexed _from,
address indexed _to,
uint256 indexed _tokenId
);
//-------------------------------------------------------------------------
/// @dev This emits when the approved address for an NFT is changed or
/// reaffirmed. The zero address indicates there is no approved address.
//-------------------------------------------------------------------------
event Approval(
address indexed _owner,
address indexed _approved,
uint256 indexed _tokenId
);
//-------------------------------------------------------------------------
/// @dev This emits when an operator is enabled or disabled for an owner.
/// The operator can transfer all NFTs of the owner.
//-------------------------------------------------------------------------
event ApprovalForAll(
address indexed _owner,
address indexed _operator,
bool _approved
);
//-------------------------------------------------------------------------
/// @dev Throws if a token does not exist or has been burned
//-------------------------------------------------------------------------
modifier exists(uint _tokenId) {
require(
_tokenId < _tokenData.length &&
_tokenData[_tokenId].tokenOwner != address(0),
"ERC721: Token with this ID does not exist"
);
_;
}
//-------------------------------------------------------------------------
/// @dev Initializes the contract, setting the token collection's `name`
/// and `symbol`.
//-------------------------------------------------------------------------
constructor(string memory _name, string memory _symbol) {
_tokenData.push(TokenData(address(0),0,0));
name = _name;
symbol = _symbol;
}
//=========================================================================
// PUBLIC FUNCTIONS
//=========================================================================
//-------------------------------------------------------------------------
/// @notice Transfers the ownership of an NFT from one address to another
/// -- THE CALLER IS RESPONSIBLE TO CONFIRM THAT `_to` IS CAPABLE OF
/// RECEIVING NFTS OR ELSE THEY MAY BE PERMANENTLY LOST
/// @dev Throws unless `msg.sender` is the current owner, an authorized
/// operator, or the approved address for this NFT. Throws if `_from` is
/// not the current owner. Throws if `_to` is the zero address. Throws if
/// `_tokenId` is not a valid NFT.
/// @param _from The current owner of the NFT
/// @param _to The new owner
/// @param _tokenId The NFT to transfer
//-------------------------------------------------------------------------
function transferFrom(address _from, address _to, uint _tokenId)
public
exists(_tokenId)
{
address tokenOwner = _tokenData[_tokenId].tokenOwner;
require(
msg.sender == tokenOwner ||
msg.sender == _tokenApprovals[_tokenId] ||
_ownerData[tokenOwner].operatorApprovals[msg.sender],
"ERC721: Sender not owner or approved operator for this token"
);
require(
_from == tokenOwner,
"ERC721: _from parameter is not the owner of this token"
);
_transfer(_to, _tokenId);
}
function safeTransferFrom(address _from, address _to, uint _tokenId)
external
{
safeTransferFrom(_from, _to, _tokenId, "");
}
//-------------------------------------------------------------------------
/// @notice Transfers the ownership of an NFT from one address to another
/// @dev Throws unless `msg.sender` is the current owner, an authorized
/// operator, or the approved address for this NFT. Throws if `_from` is
/// not the current owner. Throws if `_to` is the zero address. Throws if
/// `_tokenId` is not a valid NFT. When transfer is complete, this function
/// checks if `_to` is a smart contract (code size > 0). If so, it calls
/// `onERC721Received` on `_to` and throws if the return value is not
/// `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`.
/// @param _from The current owner of the NFT
/// @param _to The new owner
/// @param _tokenId The NFT to transfer
/// @param _data Additional data with no specified format, sent to `_to`
//-------------------------------------------------------------------------
function safeTransferFrom(
address _from,
address _to,
uint _tokenId,
bytes memory _data
)
public
exists(_tokenId)
{
address tokenOwner = _tokenData[_tokenId].tokenOwner;
require(
msg.sender == tokenOwner ||
msg.sender == _tokenApprovals[_tokenId] ||
_ownerData[tokenOwner].operatorApprovals[msg.sender],
"ERC721: Sender not owner or approved operator for this token"
);
require(
_from == tokenOwner,
"ERC721: _from parameter is not the owner of this token"
);
_transfer(_to, _tokenId);
require(
_checkOnERC721Received(address(0), _to, _tokenId, _data),
"ERC721: transfer to non ERC721Receiver implementer"
);
}
//-------------------------------------------------------------------------
/// @notice Change or reaffirm the approved address for an NFT
/// @dev The zero address indicates there is no approved address.
/// Throws unless `msg.sender` is the current NFT owner, or an
/// authorized operator of the current owner.
/// @param _approved The new approved NFT controller
/// @param _tokenId The NFT to approve
//-------------------------------------------------------------------------
function approve(address _approved, uint256 _tokenId)
public
exists(_tokenId)
{
address tokenOwner = _tokenData[_tokenId].tokenOwner;
require(
msg.sender == tokenOwner ||
_ownerData[tokenOwner].operatorApprovals[msg.sender],
"ERC721: Sender does not own this token"
);
_tokenApprovals[_tokenId] = _approved;
emit Approval(_tokenData[_tokenId].tokenOwner, _approved, _tokenId);
}
//-------------------------------------------------------------------------
/// @notice Enable or disable approval for a third party ("operator") to
/// manage all of `msg.sender`'s tokens
/// @dev Emits the ApprovalForAll event. The contract MUST allow
/// multiple operators per owner.
/// @param _operator Address to add to the set of authorized operators
/// @param _approved True if the operator is approved, false to revoke approval
//-------------------------------------------------------------------------
function setApprovalForAll(address _operator, bool _approved) external {
_ownerData[msg.sender].operatorApprovals[_operator] = _approved;
emit ApprovalForAll(msg.sender, _operator, _approved);
}
//-------------------------------------------------------------------------
/// @notice Query if a contract implements an interface
/// @param interfaceID The interface identifier, as specified in ERC-165
/// @dev Interface identification is specified in ERC-165. This function
/// uses less than 30,000 gas.
/// @return bool True if the contract implements `interfaceID` and
/// `interfaceID` is not 0xffffffff
//-------------------------------------------------------------------------
function supportsInterface(bytes4 interfaceID) external pure returns (bool) {
return (
interfaceID == 0x01ffc9a7 || // ERC165
interfaceID == 0x80ac58cd || // ERC721
interfaceID == 0x5b5e139f // ERC721Metadata
);
}
//=========================================================================
// PUBLIC VIEW FUNCTIONS
//=========================================================================
//-------------------------------------------------------------------------
/// @notice Get the number of valid NFTs tracked by this contract, where
/// each one of them has an assigned and queryable owner not equal to
/// the zero address.
/// @return uint Total number of valid NFTs tracked by this contract.
//-------------------------------------------------------------------------
function totalSupply() external view returns (uint) {
return _tokenData.length - _burnedTokens - 1;
}
//-------------------------------------------------------------------------
/// @notice Find the owner of an NFT
/// @dev NFTs assigned to zero address are considered invalid, and queries
/// about them do throw.
/// @param _tokenId The identifier for an NFT
/// @return address The address of the owner of the NFT
//-------------------------------------------------------------------------
function ownerOf(uint _tokenId)
external
view
exists(_tokenId)
returns (address)
{
return _tokenData[_tokenId].tokenOwner;
}
//-------------------------------------------------------------------------
/// @notice Count all NFTs assigned to an owner
/// @dev NFTs assigned to the zero address are considered invalid, and
/// this function throws for queries about the zero address.
/// @param _owner The address of the owner to query
/// @return uint The number of NFTs owned by `_owner`, possibly zero
//-------------------------------------------------------------------------
function balanceOf(address _owner) external view returns (uint) {
require (_owner != address(0), "Invalid balance query");
return _ownerData[_owner].tokensOwned.length;
}
//-------------------------------------------------------------------------
/// @notice Enumerate NFTs assigned to an owner
/// @dev Throws if `_index` >= `balanceOf(_owner)` or if
/// `_owner` is the zero address, representing invalid NFTs.
/// @param _owner The address of the owner to query
/// @param _index A counter less than `balanceOf(_owner)`
/// @return The token identifier for the `_index`th NFT assigned to
/// `_owner`, (sort order not specified)
//-------------------------------------------------------------------------
function tokenOfOwnerByIndex(address _owner, uint256 _index)
external
view
returns (uint24)
{
require (
_owner != address(0),
"ERC721Enumerable: Invalid owner address"
);
require (
_index < _ownerData[_owner].tokensOwned.length,
"ERC721Enumerable: Invalid index"
);
return _ownerData[_owner].tokensOwned[_index];
}
//-------------------------------------------------------------------------
/// @notice Enumerate NFTs assigned to an owner
/// @dev Throws if `_index` >= `balanceOf(_owner)` or if
/// `_owner` is the zero address, representing invalid NFTs.
/// @param _owner The address of the owner to query
/// @return uint24[] The token identifiers for the tokens owned by `_owner`,
/// (sort order not specified)
//-------------------------------------------------------------------------
function tokensOfOwner(address _owner)
external
view
returns (uint24[] memory)
{
require (
_owner != address(0),
"ERC721Enumerable: Invalid owner address"
);
return _ownerData[_owner].tokensOwned;
}
//-------------------------------------------------------------------------
/// @notice Get the approved address for a single NFT
/// @dev Throws if `_tokenId` is not a valid NFT.
/// @param _tokenId The NFT to find the approved address for
/// @return address The approved address for this NFT, or the zero address
/// if there is none
//-------------------------------------------------------------------------
function getApproved(uint _tokenId)
external
view
exists(_tokenId)
returns (address)
{
return _tokenApprovals[_tokenId];
}
//-------------------------------------------------------------------------
/// @notice Query if an address is an authorized operator for another address
/// @param _owner The address that owns the NFTs
/// @param _operator The address that acts on behalf of the owner
/// @return bool True if `_operator` is an approved operator for `_owner`
//-------------------------------------------------------------------------
function isApprovedForAll(address _owner, address _operator)
external
view
returns (bool)
{
return _ownerData[_owner].operatorApprovals[_operator];
}
//-------------------------------------------------------------------------
/// @notice A distinct Uniform Resource Identifier (URI) for a given token.
/// @dev Throws if `_tokenId` is not a valid NFT
/// @param _tokenId The identifier for an NFT
/// @return string The URI of the specified token
//-------------------------------------------------------------------------
function tokenURI(uint256 _tokenId)
public
view
virtual
returns (string memory);
//=========================================================================
// INTERNAL / PRIVATE FUNCTIONS
//=========================================================================
function _transfer(address _to, uint _tokenId) private {
TokenData storage tokenData = _tokenData[_tokenId];
address from = tokenData.tokenOwner;
// modify `from` owner data
OwnerData storage ownerData = _ownerData[from];
uint numberOfTokensOwned = ownerData.tokensOwned.length;
if (tokenData.tokenIndex < numberOfTokensOwned - 1) {
uint24 lastOwnedToken = ownerData.tokensOwned[numberOfTokensOwned - 1];
// swap token to transfer with last owned token in ownerData array
ownerData.tokensOwned[tokenData.tokenIndex] = lastOwnedToken;
// set index of last owned token to the swapped index
_tokenData[lastOwnedToken].tokenIndex = tokenData.tokenIndex;
}
// pop the owned token array
ownerData.tokensOwned.pop();
// modify `to` owner data
if (_to != address(0)) {
// set token index to the new owner's token position
tokenData.tokenIndex = uint16(_ownerData[_to].tokensOwned.length);
// add token to new owner's owned token array
_ownerData[_to].tokensOwned.push(uint24(_tokenId));
}
// set the ownership of the token
tokenData.tokenOwner = _to;
// reset approval
_tokenApprovals[_tokenId] = address(0);
// emit transfer event
emit Transfer(from, _to, _tokenId);
}
//-------------------------------------------------------------------------
/// @dev Safely mints a new token and transfers it to `to`. If `to` refers
/// to a smart contract, it must implement {IERC721Receiver-onERC721Received},
/// which is called upon a safe transfer. Emits a {Transfer} event.
/// @param _to target address that will receive the token
/// @param _extraData arbitrary data to be handled by the
/// implementation contract.
/// @param _data arbitrary data to be handled by the receiver.
//-------------------------------------------------------------------------
function _safeMint(address _to, uint80 _extraData, bytes memory _data)
internal
{
uint24 tokenId = uint24(_tokenData.length);
uint16 tokenIndex = uint16(_ownerData[_to].tokensOwned.length);
_tokenData.push(TokenData(_to, tokenIndex, _extraData));
// add token to new owner's owned token array
_ownerData[_to].tokensOwned.push(tokenId);
// emit transfer event
emit Transfer(address(0), _to, tokenId);
require(
_checkOnERC721Received(address(0), _to, tokenId, _data),
"ERC721: transfer to non ERC721Receiver implementer"
);
}
//-------------------------------------------------------------------------
/// @dev Equivalent to `_safeMint(to, quantity, '')`.
//-------------------------------------------------------------------------
function _safeMint(address _to, uint80 _extraData) internal {
_safeMint(_to, _extraData, "");
}
//-------------------------------------------------------------------------
/// @dev Destroys `tokenId`. The approval is cleared when the token is
/// burned. This is an internal function that does not check if the sender
/// is authorized to operate on the token. Throws if `tokenId` does not
/// exist. Emits a {Transfer} event.
/// @param _tokenId The token to burn
//-------------------------------------------------------------------------
function _burn(uint _tokenId) internal exists(_tokenId) {
_tokenApprovals[_tokenId] = address(0);
_transfer(address(0), _tokenId);
++_burnedTokens;
}
//-------------------------------------------------------------------------
/// @dev Internal function to invoke {IERC721Receiver-onERC721Received} on
/// a target address.
/// @param _from address representing the previous owner of the given token ID
/// @param _to target address that will receive the token
/// @param _tokenId uint256 ID of the token to be transferred
/// @param _data bytes optional data to send along with the call
/// @return bool whether the call correctly returned the expected magic value
//-------------------------------------------------------------------------
function _checkOnERC721Received(
address _from,
address _to,
uint256 _tokenId,
bytes memory _data
) private returns (bool) {
if (_to.code.length > 0) {
try IERC721Receiver(_to).onERC721Received(msg.sender, _from, _tokenId, _data) returns (bytes4 retval) {
return retval == IERC721Receiver.onERC721Received.selector;
} catch (bytes memory reason) {
if (reason.length == 0) {
revert("ERC721: transfer to non ERC721Receiver implementer");
} else {
/// @solidity memory-safe-assembly
assembly {
revert(add(32, reason), mload(reason))
}
}
}
} else {
return true;
}
}
}
// File: @openzeppelin/contracts/utils/Context.sol
// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)
pragma solidity ^0.8.0;
/**
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
}
// File: @openzeppelin/contracts/access/Ownable.sol
// OpenZeppelin Contracts (last updated v4.7.0) (access/Ownable.sol)
pragma solidity ^0.8.0;
/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* By default, the owner account will be the one that deploys the contract. This
* can later be changed with {transferOwnership}.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be applied to your functions to restrict their use to
* the owner.
*/
abstract contract Ownable is Context {
address private _owner;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the deployer as the initial owner.
*/
constructor() {
_transferOwnership(_msgSender());
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
_checkOwner();
_;
}
/**
* @dev Returns the address of the current owner.
*/
function owner() public view virtual returns (address) {
return _owner;
}
/**
* @dev Throws if the sender is not the owner.
*/
function _checkOwner() internal view virtual {
require(owner() == _msgSender(), "Ownable: caller is not the owner");
}
/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions anymore. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby removing any functionality that is only available to the owner.
*/
function renounceOwnership() public virtual onlyOwner {
_transferOwnership(address(0));
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual onlyOwner {
require(newOwner != address(0), "Ownable: new owner is the zero address");
_transferOwnership(newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Internal function without access restriction.
*/
function _transferOwnership(address newOwner) internal virtual {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}
// File: Forge Token/ForgeToken.sol
// Contract based on https://docs.openzeppelin.com/contracts/4.x/erc721
pragma solidity ^0.8.4;
interface Forge_Metadata {
function tokenURI(uint _tokenId) external view returns(string memory);
}
interface IRandomizer {
function getRandomSeed(
address userAddress,
uint256 nonce,
uint256 lastExtradata,
bytes calldata signature
) external view returns (bytes32);
}
//-----------------------------------------------------------------------------
/// @title FORGE LAUNCH TOKEN
//-----------------------------------------------------------------------------
contract ForgeToken is Forge_ERC721, Ownable {
struct TokenAttributes {
uint16 xp;
bool isSpecialVariant;
}
uint private constant _NUM_XP_VALUES = 10;
// [0064 = 100, 01F4 = 500, 03E8 = 1000, 1388 = 5000, 1D4C = 7500, 2710 = 10000, 3A98 = 15000, 4E20 = 20000, 61A8 = 25000, 7FF8 = 32760]
bytes32 private constant _XP_VALUES = 0x006401F403E813881D4C27103A984E2061A87FF8000000000000000000000000;
// 02 = ~1%, 05 = ~2%, 0D = ~5%, 1A = ~10%, 33 = ~20%
bytes32 private constant _XP_OCCURRENCE_RATES = 0x1A3333331A1A0D05050200000000000000000000000000000000000000000000;
// 13/256 = ~5%
uint8 private constant _SPECIAL_VARIANT_RATE = 13;
Forge_Metadata public metadataContract = Forge_Metadata(0x31d64a222ad81004613C4Bb830733438877D8E21);
address public minterAddress = 0x86FeD702bCAF26f0b41476a58d4E88242654146E;
// Oct 16 2023 at 12:30:00 UTC
uint256 public dropStartTime = 1697459400;
// Approx 6 months
uint256 public dropDuration = 4380 hours;
uint256 public supplyLimit = 3333;
uint256 public reservedMints = 433;
// In order to ensure each owner only mints once per signature, each mint
// is assigned a unique nonce. Subsequent mint attempts with the same
// nonce will fail.
mapping(uint256 => bool) public usedNonces;
// Randomizer contract
IRandomizer private _randomizerContract;
uint256 private _specialVariants = 0;
//-------------------------------------------------------------------------
/// @dev This emits when XP is burned from a Forge Token
//-------------------------------------------------------------------------
event BurnXp(uint _tokenId, uint16 _amountBurned);
constructor() Forge_ERC721("ForgeToken", "FORGE") {}
//-------------------------------------------------------------------------
/// @notice Sets a new address to be the Minter
/// @dev Throws if sender is not the contract owner
/// @param _newMinter The new address to be the Minter
//-------------------------------------------------------------------------
function setMinterAddress(address _newMinter) external onlyOwner {
minterAddress = _newMinter;
}
//-------------------------------------------------------------------------
/// @notice Replaces the current metadata contract reference with a new one
/// @dev Throws if sender is not the contract owner
/// @param _contractAddress The address of the metadata contract
//-------------------------------------------------------------------------
function setMetadataContract(address _contractAddress) external onlyOwner {
metadataContract = Forge_Metadata(_contractAddress);
}
//-------------------------------------------------------------------------
/// @notice Sets the randomizer contract address
/// @dev Throws if sender is not the contract owner
/// @param _contractAddress The address of the randomizer contract
//-------------------------------------------------------------------------
function setRandomizerContract(address _contractAddress) external onlyOwner {
_randomizerContract = IRandomizer(_contractAddress);
}
//-------------------------------------------------------------------------
/// @notice Sets the start time where minting is allowed
/// @dev Throws if sender is not the contract owner
/// @param _dropStartTime The new timestamp in seconds
//-------------------------------------------------------------------------
function setDropStartTime(uint256 _dropStartTime) external onlyOwner {
require(
_dropStartTime >= block.timestamp,
"Drop start time must be later than now"
);
dropStartTime = _dropStartTime;
}
//-------------------------------------------------------------------------
/// @notice Sets the duration where minting is allowed
/// @dev Throws if sender is not the contract owner
/// @param _dropDuration The new duration in seconds
//-------------------------------------------------------------------------
function setDropDuration(uint256 _dropDuration) external onlyOwner {
require (_dropDuration > 0, "Drop duration must be greater than zero");
dropDuration = _dropDuration;
}
//-------------------------------------------------------------------------
/// @notice Sets the supply limit to a higher number
/// @dev Throws if `_newSupplyLimit` is less than current supply limit.
/// Throws if sender is not the contract owner.
/// @param _newSupplyLimit The new supply limit
//-------------------------------------------------------------------------
function increaseSupplyLimit(uint256 _newSupplyLimit) external onlyOwner {
require (
block.timestamp <= dropStartTime + dropDuration,
"Drop ended, increasing supply limit no longer allowed"
);
require (
_newSupplyLimit > supplyLimit,
"New supply limit must be greater than previous supply limit"
);
supplyLimit = _newSupplyLimit;
}
//-------------------------------------------------------------------------
/// @notice Mints a new Forge Badge for the sender.
/// Validates using a signature signed by the minter
/// The token ID of the new badge is determined by the totalSupply when
/// minted. Randomly rolls XP and specialVariant value.
/// @dev Throws if there's a signer address mismatch
/// @param _nonce A unique ID used to guard against double-mints
/// @param _sig The signature of the recipient of the minted NFT
//-------------------------------------------------------------------------
function safeMint(uint256 _nonce, bytes memory _sig) external {
require(block.timestamp >= dropStartTime, "Drop not started");
require(block.timestamp <= dropStartTime + dropDuration, "Drop ended");
require(tx.origin == msg.sender, "Transaction origin not from sender");
require(
_tokenData.length - 1 < supplyLimit - reservedMints,
"Collection has minted out"
);
require(!usedNonces[_nonce], "Nonce already used");
// validate message
bytes32 message = _generateMessage(msg.sender, _nonce, address(this));
require(
_recoverSigner(message, _sig) == minterAddress,
"Signer address mismatch"
);
// Generate random seed
bytes32 randomSeed = _randomizerContract.getRandomSeed(
msg.sender,
_nonce,
uint256(_tokenData[_tokenData.length - 1].extraData),
_sig
);
// Roll attributes
uint16 xpValue = _calculateXpValue(uint8(randomSeed[0]));
// Also check if used special variants are less than 5% of supply limit
bool isSpecialVariant = (
uint8(randomSeed[1]) < _SPECIAL_VARIANT_RATE &&
_specialVariants < supplyLimit * 13 / 256
);
if (isSpecialVariant) {
++_specialVariants;
}
uint40 packedAttributes = (uint24(xpValue) << 8) + (isSpecialVariant ? 1 : 0);
// mint the new token
usedNonces[_nonce] = true;
_safeMint(msg.sender, packedAttributes);
}
//-------------------------------------------------------------------------
/// @notice Mints a new Forge Badge for a given address only by the minter.
/// The token ID of the new badge is determined by the totalSupply when
/// minted. Randomly rolls XP and specialVariant value.
/// @dev Throws if there's a signer address mismatch
/// @param _nonce A unique ID used to guard against double-mints
/// @param _to address of the recipient of the minted NFT
//-------------------------------------------------------------------------
function safeMint(uint256 _nonce, address _to) external {
require(msg.sender == minterAddress, "Only minterAddress can mint");
require(reservedMints > 0, "No more tokens in reserve");
require(!usedNonces[_nonce], "Nonce already used");
// Generate random seed
bytes32 randomSeed = keccak256(
abi.encodePacked(
block.timestamp,
_to,
_tokenData.length
)
);
// Roll attributes
uint16 xpValue = _calculateXpValue(uint8(randomSeed[0]));
bool isSpecialVariant = uint8(randomSeed[1]) < _SPECIAL_VARIANT_RATE;
uint40 packedAttributes = (uint24(xpValue) << 8) + (isSpecialVariant ? 1 : 0);
// mint the new token
usedNonces[_nonce] = true;
_safeMint(_to, packedAttributes);
--reservedMints;
}
//-------------------------------------------------------------------------
/// @notice Burns an existing Forge Badge. Its XP value will be emitted as
/// an event.
/// @dev Throws if sender is not the owner of the specified token.
/// @param _tokenId The ID of the token to burn
//-------------------------------------------------------------------------
function burn(uint _tokenId) external {
require (
msg.sender == _tokenData[_tokenId].tokenOwner,
"Sender must own token to burn"
);
TokenAttributes memory attributes = tokenAttributes(_tokenId);
uint16 amountBurned = attributes.xp;
_burn(_tokenId);
emit BurnXp(_tokenId, amountBurned);
}
//-------------------------------------------------------------------------
/// @notice Gets the attributes of a given token
/// @param _tokenId The ID of the token to query
//-------------------------------------------------------------------------
function tokenAttributes(uint _tokenId)
public
view
exists(_tokenId)
returns(TokenAttributes memory attributes)
{
uint tokenData = _tokenData[_tokenId].extraData;
// extraData ends in 1 if the token is a special variant
attributes.isSpecialVariant = tokenData % 2 == 1;
attributes.xp = uint16(tokenData >> 8);
}
//-------------------------------------------------------------------------
/// @dev delegates tokenURI to external smart contract
//-------------------------------------------------------------------------
function tokenURI(uint _tokenId)
public
view
override
returns (string memory)
{
return metadataContract.tokenURI(_tokenId);
}
//-------------------------------------------------------------------------
// Takes a random number from 0 to 255 and returns an xp value
//-------------------------------------------------------------------------
function _calculateXpValue(uint8 _rand)
private
pure
returns (uint16)
{
// get index from occurrence rates
uint index;
for (uint i = 0; i < _NUM_XP_VALUES; ++i) {
if (_rand < uint8(_XP_OCCURRENCE_RATES[i])) {
index = i;
break;
}
_rand -= uint8(_XP_OCCURRENCE_RATES[i]);
}
// convert index into a bytes16 value
uint16 xpValue = uint16(uint8(_XP_VALUES[index * 2])) << 8;
xpValue += uint8(_XP_VALUES[index * 2 + 1]);
return xpValue;
}
//-------------------------------------------------------------------------
// Takes a message and signature and retrieves the signer address
//-------------------------------------------------------------------------
function _recoverSigner(bytes32 _message, bytes memory _sig)
private
pure
returns (address)
{
require(_sig.length == 65, "Length of signature must be 65");
// split signature
uint8 v;
bytes32 r;
bytes32 s;
assembly {
// first 32 bytes, after the length prefix
r := mload(add(_sig, 32))
// second 32 bytes
s := mload(add(_sig, 64))
// final byte (first byte of the next 32 bytes)
v := byte(0, mload(add(_sig, 96)))
}
// return the address from the given signature by calculating a
// recovery function of ECDSA
return ecrecover(_message, v, r, s);
}
//-------------------------------------------------------------------------
// Builds an encoded message with a prefixed hash to mimic the behavior
// of eth_sign.
//-------------------------------------------------------------------------
function _generateMessage(address _sender, uint _nonce, address _contract)
private
pure
returns (bytes32 message)
{
message = keccak256(abi.encodePacked(_sender, _nonce, _contract));
message = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", message));
}
}
{
"compilationTarget": {
"ForgeToken.sol": "ForgeToken"
},
"evmVersion": "paris",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": true,
"runs": 200
},
"remappings": []
}
[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"_owner","type":"address"},{"indexed":true,"internalType":"address","name":"_approved","type":"address"},{"indexed":true,"internalType":"uint256","name":"_tokenId","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"_owner","type":"address"},{"indexed":true,"internalType":"address","name":"_operator","type":"address"},{"indexed":false,"internalType":"bool","name":"_approved","type":"bool"}],"name":"ApprovalForAll","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"_tokenId","type":"uint256"},{"indexed":false,"internalType":"uint16","name":"_amountBurned","type":"uint16"}],"name":"BurnXp","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"_from","type":"address"},{"indexed":true,"internalType":"address","name":"_to","type":"address"},{"indexed":true,"internalType":"uint256","name":"_tokenId","type":"uint256"}],"name":"Transfer","type":"event"},{"inputs":[{"internalType":"address","name":"_approved","type":"address"},{"internalType":"uint256","name":"_tokenId","type":"uint256"}],"name":"approve","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_owner","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_tokenId","type":"uint256"}],"name":"burn","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"dropDuration","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"dropStartTime","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_tokenId","type":"uint256"}],"name":"getApproved","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_newSupplyLimit","type":"uint256"}],"name":"increaseSupplyLimit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_owner","type":"address"},{"internalType":"address","name":"_operator","type":"address"}],"name":"isApprovedForAll","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"metadataContract","outputs":[{"internalType":"contract Forge_Metadata","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"minterAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_tokenId","type":"uint256"}],"name":"ownerOf","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"reservedMints","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_nonce","type":"uint256"},{"internalType":"address","name":"_to","type":"address"}],"name":"safeMint","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_nonce","type":"uint256"},{"internalType":"bytes","name":"_sig","type":"bytes"}],"name":"safeMint","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_from","type":"address"},{"internalType":"address","name":"_to","type":"address"},{"internalType":"uint256","name":"_tokenId","type":"uint256"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_from","type":"address"},{"internalType":"address","name":"_to","type":"address"},{"internalType":"uint256","name":"_tokenId","type":"uint256"},{"internalType":"bytes","name":"_data","type":"bytes"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_operator","type":"address"},{"internalType":"bool","name":"_approved","type":"bool"}],"name":"setApprovalForAll","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_dropDuration","type":"uint256"}],"name":"setDropDuration","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_dropStartTime","type":"uint256"}],"name":"setDropStartTime","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_contractAddress","type":"address"}],"name":"setMetadataContract","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_newMinter","type":"address"}],"name":"setMinterAddress","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_contractAddress","type":"address"}],"name":"setRandomizerContract","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"supplyLimit","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceID","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_tokenId","type":"uint256"}],"name":"tokenAttributes","outputs":[{"components":[{"internalType":"uint16","name":"xp","type":"uint16"},{"internalType":"bool","name":"isSpecialVariant","type":"bool"}],"internalType":"struct ForgeToken.TokenAttributes","name":"attributes","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_owner","type":"address"},{"internalType":"uint256","name":"_index","type":"uint256"}],"name":"tokenOfOwnerByIndex","outputs":[{"internalType":"uint24","name":"","type":"uint24"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_tokenId","type":"uint256"}],"name":"tokenURI","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_owner","type":"address"}],"name":"tokensOfOwner","outputs":[{"internalType":"uint24[]","name":"","type":"uint24[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_from","type":"address"},{"internalType":"address","name":"_to","type":"address"},{"internalType":"uint256","name":"_tokenId","type":"uint256"}],"name":"transferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"usedNonces","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"}]