Description:
Multi-signature wallet contract requiring multiple confirmations for transaction execution.
Blockchain: Ethereum
Source Code: View Code On The Blockchain
Solidity Source Code:
/**
*Submitted for verification at Etherscan.io on 2025-08-31
*/
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.16;
interface IGASEVO {
function transferFrom(address from_, address to_, uint256 id_) external;
function balanceOf(address owner) external view returns (uint256);
function ownerOf(uint256 id_) external view returns (address);
function mintAsController(address to_, uint256 id_) external;
}
abstract contract Ownable {
address public owner;
/// @dev This emits when ownership of a contract changes.
event OwnershipTransferred(address indexed _previousOwner, address indexed _newOwner);
// constructor() { owner = msg.sender; }
modifier onlyOwner { require(owner == msg.sender, "Not Owner!"); _; }
function transferOwnership(address new_) external onlyOwner {
address oldOwner = owner;
owner = new_;
emit OwnershipTransferred(oldOwner, new_);
}
function mockTransferOwnership(address old_, address new_) external onlyOwner {
// only a mock transfer event
emit OwnershipTransferred(old_, new_);
}
}
abstract contract ERC721G {
// Standard ERC721 Events
event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
event Approval(address indexed owner, address indexed approved,
uint256 indexed tokenId);
event ApprovalForAll(address indexed owner, address indexed operator,
bool approved);
// // ERC721G Events
// event TokenStaked(uint256 indexed tokenId_, address indexed staker,
// uint256 timestamp_);
// event TokenUnstaked(uint256 indexed tokenid_, address indexed staker,
// uint256 timestamp_, uint256 totalTimeStaked_);
// Standard ERC721 Global Variables
string public name; // Token Name
string public symbol; // Token Symbol
// ERC721G Global Variables
uint256 public tokenIndex; // The running index for the next TokenId
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable
uint256 public immutable startTokenId; // Bytes Storage for the starting TokenId
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable
uint256 public immutable maxBatchSize;
// Staking Address supports Proxy
// address public immutable stakingAddress = address(this); // The staking address
function stakingAddress() public view returns (address) {
return address(this);
}
/** @dev instructions:
* name_ sets the token name
* symbol_ sets the token symbol
* startId_ sets the starting tokenId (recommended 0-1)
* maxBatchSize_ sets the maximum batch size for each mint (recommended 5-20)
*/
/// @custom:oz-upgrades-unsafe-allow constructor
constructor(
string memory name_, string memory symbol_,
uint256 startId_, uint256 maxBatchSize_) {
name = name_;
symbol = symbol_;
tokenIndex = startId_;
startTokenId = startId_;
maxBatchSize = maxBatchSize_;
}
// ERC721G Structs
struct OwnerStruct {
address owner; // stores owner address for OwnerOf
uint32 lastTransfer; // stores the last transfer of the token
uint32 stakeTimestamp; // stores the stake timestamp in _setStakeTimestamp()
uint32 totalTimeStaked; // stores the total time staked accumulated
}
struct BalanceStruct {
uint32 balance; // stores the token balance of the address
uint32 mintedAmount; // stores the minted amount of the address on mint
// GAS2 Upgrade
uint32 lastUnboxTimestamp; // stores the last unbox timestamp in compressed time
// 160 free bits -- 20 free bytes
}
// ERC721G Mappings
mapping(uint256 => OwnerStruct) public _tokenData; // ownerOf replacement
mapping(address => BalanceStruct) public _balanceData; // balanceOf replacement
mapping(uint256 => OwnerStruct) public mintIndex; // uninitialized ownerOf pointer
// ERC721 Mappings
mapping(uint256 => address) public getApproved; // for single token approvals
mapping(address => mapping(address => bool)) public isApprovedForAll; // approveall
// TIME by 0xInuarashi
function _getBlockTimestampCompressed() public virtual view returns (uint32) {
return uint32(block.timestamp / 10);
}
function _compressTimestamp(uint256 timestamp_) public virtual view
returns (uint32) {
return uint32(timestamp_ / 10);
}
function _expandTimestamp(uint32 timestamp_) public virtual view
returns (uint256) {
return uint256(timestamp_) * 10;
}
function getLastTransfer(uint256 tokenId_) public virtual view
returns (uint256) {
return _expandTimestamp(_getTokenDataOf(tokenId_).lastTransfer);
}
function getStakeTimestamp(uint256 tokenId_) public virtual view
returns (uint256) {
return _expandTimestamp(_getTokenDataOf(tokenId_).stakeTimestamp);
}
function getTotalTimeStaked(uint256 tokenId_) public virtual view
returns (uint256) {
return _expandTimestamp(_getTokenDataOf(tokenId_).totalTimeStaked);
}
///// ERC721G: ERC721-Like Simple Read Outputs /////
function totalSupply() public virtual view returns (uint256) {
return tokenIndex - startTokenId;
}
function balanceOf(address address_) public virtual view returns (uint256) {
return _balanceData[address_].balance;
}
///// ERC721G: Range-Based Logic /////
/** @dev explanation:
* _getTokenDataOf() finds and returns either the (and in priority)
* - the initialized storage pointer from _tokenData
* - the uninitialized storage pointer from mintIndex
*
* if the _tokenData storage slot is populated, return it
* otherwise, do a reverse-lookup to find the uninitialized pointer from mintIndex
*/
function _getTokenDataOf(uint256 tokenId_) public virtual view
returns (OwnerStruct memory) {
// The tokenId must be above startTokenId only
require(tokenId_ >= startTokenId, "TokenId below starting Id!");
// If the _tokenData is initialized (not 0x0), return the _tokenData
if (_tokenData[tokenId_].owner != address(0)
|| tokenId_ >= tokenIndex) {
return _tokenData[tokenId_];
}
// Else, do a reverse-lookup to find the corresponding uninitialized pointer
else { unchecked {
uint256 _lowerRange = tokenId_;
while (mintIndex[_lowerRange].owner == address(0)) { _lowerRange--; }
return mintIndex[_lowerRange];
}}
}
/** @dev explanation:
* ownerOf calls _getTokenDataOf() which returns either the initialized or
* uninitialized pointer.
* Then, it checks if the token is staked or not through stakeTimestamp.
* If the token is staked, return the stakingAddress, otherwise, return the owner.
*/
function ownerOf(uint256 tokenId_) public virtual view returns (address) {
OwnerStruct memory _OwnerStruct = _getTokenDataOf(tokenId_);
return _OwnerStruct.stakeTimestamp == 0 ? _OwnerStruct.owner : stakingAddress();
}
/** @dev explanation:
* _trueOwnerOf() calls _getTokenDataOf() which returns either the initialized or
* uninitialized pointer.
* It returns the owner directly without any checks.
* Used internally for proving the staker address on unstake.
*/
function _trueOwnerOf(uint256 tokenId_) public virtual view returns (address) {
return _getTokenDataOf(tokenId_).owner;
}
///// ERC721G: Internal Single-Contract Staking Logic /////
/** @dev explanation:
* _initializeTokenIf() is used as a beginning-hook to functions that require
* that the token is explicitly INITIALIZED before the function is able to be used.
* It will check if the _tokenData slot is initialized or not.
* If it is not, it will initialize it.
* Used internally for staking logic.
*/
function _initializeTokenIf(uint256 tokenId_, OwnerStruct memory _OwnerStruct)
internal virtual {
// If the target _tokenData is not initialized, initialize it.
if (_tokenData[tokenId_].owner == address(0)) {
_tokenData[tokenId_] = _OwnerStruct;
}
}
/** @dev explanation:
* _setStakeTimestamp() is our staking / unstaking logic.
* If timestamp_ is > 0, the action is "stake"
* If timestamp_ is == 0, the action is "unstake"
*
* We grab the tokenData using _getTokenDataOf and then read its values.
* As this function requires INITIALIZED tokens only, we call _initializeTokenIf()
* to initialize any token using this function first.
*
* Processing of the function is explained in in-line comments.
*/
function _setStakeTimestamp(uint256 tokenId_, uint256 timestamp_)
internal virtual returns (address) {
// First, call _getTokenDataOf and grab the relevant tokenData
OwnerStruct memory _OwnerStruct = _getTokenDataOf(tokenId_);
address _owner = _OwnerStruct.owner;
uint32 _stakeTimestamp = _OwnerStruct.stakeTimestamp;
// _setStakeTimestamp requires initialization
_initializeTokenIf(tokenId_, _OwnerStruct);
// Clear any token approvals
delete getApproved[tokenId_];
// if timestamp_ > 0, the action is "stake"
if (timestamp_ > 0) {
// Make sure that the token is not staked already
require(_stakeTimestamp == 0,
"ERC721G: _setStakeTimestamp() already staked");
// Callbrate balances between staker and stakingAddress
unchecked {
_balanceData[_owner].balance--;
_balanceData[stakingAddress()].balance++;
}
// Emit Transfer event from trueOwner
emit Transfer(_owner, stakingAddress(), tokenId_);
}
// if timestamp_ == 0, the action is "unstake"
else {
// Make sure the token is not staked
require(_stakeTimestamp != 0,
"ERC721G: _setStakeTimestamp() already unstaked");
// Callibrate balances between stakingAddress and staker
unchecked {
_balanceData[_owner].balance++;
_balanceData[stakingAddress()].balance--;
}
// we add total time staked to the token on unstake
uint32 _timeStaked = _getBlockTimestampCompressed() - _stakeTimestamp;
_tokenData[tokenId_].totalTimeStaked += _timeStaked;
// Emit Transfer event to trueOwner
emit Transfer(stakingAddress(), _owner, tokenId_);
}
// Set the stakeTimestamp to timestamp_
_tokenData[tokenId_].stakeTimestamp = _compressTimestamp(timestamp_);
// We save internal gas by returning the owner for a follow-up function
return _owner;
}
/** @dev explanation:
* _stake() works like an extended function of _setStakeTimestamp()
* where the logic of _setStakeTimestamp() runs and returns the _owner address
* afterwards, we do the post-hook required processing to finish the staking logic
* in this function.
*
* Processing logic explained in in-line comments.
*/
function _stake(uint256 tokenId_) internal virtual returns (address) {
// set the stakeTimestamp to block.timestamp and return the owner
return _setStakeTimestamp(tokenId_, block.timestamp);
}
/** @dev explanation:
* _unstake() works like an extended unction of _setStakeTimestamp()
* where the logic of _setStakeTimestamp() runs and returns the _owner address
* afterwards, we do the post-hook required processing to finish the unstaking logic
* in this function.
*
* Processing logic explained in in-line comments.
*/
function _unstake(uint256 tokenId_) internal virtual returns(address) {
// set the stakeTimestamp to 0 and return the owner
return _setStakeTimestamp(tokenId_, 0);
}
/** @dev explanation:
* _mintAndStakeInternal() is the internal mintAndStake function that is called
* to mintAndStake tokens to users.
*
* It populates mintIndex with the phantom-mint data (owner, lastTransferTime)
* as well as the phantom-stake data (stakeTimestamp)
*
* Then, it emits the necessary phantom events to replicate the behavior as canon.
*
* Further logic explained in in-line comments.
*/
function _mintAndStakeInternal(address to_, uint256 amount_) internal virtual {
// we cannot mint to 0x0
require(to_ != address(0), "ERC721G: _mintAndStakeInternal to 0x0");
// we limit max mints per SSTORE to prevent expensive gas lookup
require(amount_ <= maxBatchSize,
"ERC721G: _mintAndStakeInternal over maxBatchSize");
// process the required variables to write to mintIndex
uint256 _startId = tokenIndex;
uint256 _endId = _startId + amount_;
uint32 _currentTime = _getBlockTimestampCompressed();
// write to the mintIndex to store the OwnerStruct for uninitialized tokenData
mintIndex[_startId] = OwnerStruct(
to_, // the address the token is minted to
_currentTime, // the last transfer time
_currentTime, // the curent time of staking
0 // the accumulated time staked
);
unchecked {
// we add the balance to the stakingAddress through our staking logic
_balanceData[stakingAddress()].balance += uint32(amount_);
// we add the mintedAmount to the to_ through our minting logic
_balanceData[to_].mintedAmount += uint32(amount_);
// emit phantom mint to to_, then emit a staking transfer
do {
emit Transfer(address(0), to_, _startId);
emit Transfer(to_, stakingAddress(), _startId);
// /** @dev testing:
// * emitting a TokenStaked event for testing
// */
// emit TokenStaked(_startId, to_, _currentTime);
} while (++_startId < _endId);
}
// set the new tokenIndex to the _endId
tokenIndex = _endId;
}
/** @dev explanation:
* _mintAndStake() calls _mintAndStakeInternal() but calls it using a while-loop
* based on the required minting amount to stay within the bounds of
* max mints per batch (maxBatchSize)
*/
function _mintAndStake(address to_, uint256 amount_) internal virtual {
uint256 _amountToMint = amount_;
while (_amountToMint > maxBatchSize) {
_amountToMint -= maxBatchSize;
_mintAndStakeInternal(to_, maxBatchSize);
}
_mintAndStakeInternal(to_, _amountToMint);
}
///// ERC721G Range-Based Internal Minting Logic /////
/** @dev explanation:
* _mintInternal() is our internal batch minting logic.
* First, we store the uninitialized pointer at mintIndex of _startId
* Then, we process the balances changes
* Finally, we phantom-mint the tokens using Transfer events loop.
*/
function _mintInternal(address to_, uint256 amount_) internal virtual {
// cannot mint to 0x0
require(to_ != address(0), "ERC721G: _mintInternal to 0x0");
// we limit max mints to prevent expensive gas lookup
require(amount_ <= maxBatchSize,
"ERC721G: _mintInternal over maxBatchSize");
// process the token id data
uint256 _startId = tokenIndex;
uint256 _endId = _startId + amount_;
// push the required phantom mint data to mintIndex
mintIndex[_startId].owner = to_;
mintIndex[_startId].lastTransfer = _getBlockTimestampCompressed();
// process the balance changes and do a loop to phantom-mint the tokens to to_
unchecked {
_balanceData[to_].balance += uint32(amount_);
_balanceData[to_].mintedAmount += uint32(amount_);
do { emit Transfer(address(0), to_, _startId); } while (++_startId < _endId);
}
// set the new token index
tokenIndex = _endId;
}
/** @dev explanation:
* _mint() is the function that calls _mintInternal() using a while-loop
* based on the maximum batch size (maxBatchSize)
*/
function _mint(address to_, uint256 amount_) internal virtual {
uint256 _amountToMint = amount_;
while (_amountToMint > maxBatchSize) {
_amountToMint -= maxBatchSize;
_mintInternal(to_, maxBatchSize);
}
_mintInternal(to_, _amountToMint);
}
/** @dev explanation:
* _transfer() is the internal function that transfers the token from_ to to_
* it has ERC721-standard require checks
* and then uses solmate-style approval clearing
*
* afterwards, it sets the _tokenData to the data of the to_ (transferee) as well as
* set the balanceData.
*
* this results in INITIALIZATION of the token, if it has not been initialized yet.
*/
function _transfer(address from_, address to_, uint256 tokenId_) internal virtual {
// the from_ address must be the ownerOf
require(from_ == ownerOf(tokenId_), "ERC721G: _transfer != ownerOf");
// cannot transfer to 0x0
require(to_ != address(0), "ERC721G: _transfer to 0x0");
// delete any approvals
delete getApproved[tokenId_];
// set _tokenData to to_
_tokenData[tokenId_].owner = to_;
_tokenData[tokenId_].lastTransfer = _getBlockTimestampCompressed();
// update the balance data
unchecked {
_balanceData[from_].balance--;
_balanceData[to_].balance++;
}
// emit a standard Transfer
emit Transfer(from_, to_, tokenId_);
}
///// ERC721G: User-Enabled Out-of-the-box Staking Functionality /////
/** @dev clarification:
* As a developer, you DO NOT have to enable these functions, or use them
* in the way defined in this section.
*
* The functions in this section are just out-of-the-box plug-and-play staking
* which is enabled IMMEDIATELY.
* (As well as some useful view-functions)
*
* You can choose to call the internal staking functions yourself, to create
* custom staking logic based on the section (n-2) above.
*/
/** @dev explanation:
* this is a staking function that receives calldata tokenIds_ array
* and loops to call internal _stake in a gas-efficient way
* written in a shorthand-style syntax
*/
function stake(uint256[] calldata tokenIds_) public virtual {
uint256 i;
uint256 l = tokenIds_.length;
while (i < l) {
// stake and return the owner's address
address _owner = _stake(tokenIds_[i]);
// make sure the msg.sender is the owner
require(msg.sender == _owner, "You are not the owner!");
unchecked {++i;}
}
}
/** @dev explanation:
* this is an unstaking function that receives calldata tokenIds_ array
* and loops to call internal _unstake in a gas-efficient way
* written in a shorthand-style syntax
*/
function unstake(uint256[] calldata tokenIds_) public virtual {
uint256 i;
uint256 l = tokenIds_.length;
while (i < l) {
// unstake and return the owner's address
address _owner = _unstake(tokenIds_[i]);
// make sure the msg.sender is the owner
require(msg.sender == _owner, "You are not the owner!");
unchecked {++i;}
}
}
///// ERC721G: User-Enabled Out-of-the-box Staking View Functions /////
/** @dev explanation:
* balanceOfStaked loops through the entire tokens using
* startTokenId as the start pointer, and
* tokenIndex (current-next tokenId) as the end pointer
*
* it checks if the _trueOwnerOf() is the address_ or not
* and if the owner() is not the address, indicating the
* state that the token is staked.
*
* if so, it increases the balance. after the loop, it returns the balance.
*
* this is mainly for external view only.
* !! NOT TO BE INTERFACED WITH CONTRACT WRITE FUNCTIONS EVER.
*/
function balanceOfStaked(address address_) public virtual view
returns (uint256) {
uint256 _balance;
uint256 i = startTokenId;
uint256 max = tokenIndex;
while (i < max) {
if (ownerOf(i) != address_ && _trueOwnerOf(i) == address_) {
_balance++;
}
unchecked { ++i; }
}
return _balance;
}
/** @dev explanation:
* walletOfOwnerStaked calls balanceOfStaked to get the staked
* balance of a user. Afterwards, it runs staked-checking logic
* to figure out the tokenIds that the user has staked
* and then returns it in walletOfOwner fashion.
*
* this is mainly for external view only.
* !! NOT TO BE INTERFACED WITH CONTRACT WRITE FUNCTIONS EVER.
*/
function walletOfOwnerStaked(address address_) public virtual view
returns (uint256[] memory) {
uint256 _balance = balanceOfStaked(address_);
uint256[] memory _tokens = new uint256[] (_balance);
uint256 _currentIndex;
uint256 i = startTokenId;
while (_currentIndex < _balance) {
if (ownerOf(i) != address_ && _trueOwnerOf(i) == address_) {
_tokens[_currentIndex++] = i;
}
unchecked { ++i; }
}
return _tokens;
}
/** @dev explanation:
* balanceOf of the address returns UNSTAKED tokens only.
* to get the total balance of the user containing both STAKED and UNSTAKED tokens,
* we use this function.
*
* this is mainly for external view only.
* !! NOT TO BE INTERFACED WITH CONTRACT WRITE FUNCTIONS EVER.
*/
function totalBalanceOf(address address_) public virtual view returns (uint256) {
return balanceOf(address_) + balanceOfStaked(address_);
}
/** @dev explanation:
* totalTimeStakedOfToken returns the accumulative total time staked of a tokenId
* it reads from the totalTimeStaked of the tokenId_ and adds it with
* a calculation of pending time staked and returns the sum of both values.
*
* this is mainly for external view / use only.
* this function can be interfaced with contract writes.
*/
function totalTimeStakedOfToken(uint256 tokenId_) public virtual view
returns (uint256) {
OwnerStruct memory _OwnerStruct = _getTokenDataOf(tokenId_);
uint256 _totalTimeStakedOnToken = _expandTimestamp(_OwnerStruct.totalTimeStaked);
uint256 _totalTimeStakedPending =
_OwnerStruct.stakeTimestamp > 0 ?
_expandTimestamp(
_getBlockTimestampCompressed() - _OwnerStruct.stakeTimestamp) :
0;
return _totalTimeStakedOnToken + _totalTimeStakedPending;
}
/** @dev explanation:
* totalTimeStakedOfTokens just returns an array of totalTimeStakedOfToken
* based on tokenIds_ calldata.
*
* this is mainly for external view / use only.
* this function can be interfaced with contract writes... however
* BE CAREFUL and USE IT CORRECTLY.
* (dont pass in 5000 tokenIds_ in a write function)
*/
function totalTimeStakedOfTokens(uint256[] calldata tokenIds_) public
virtual view returns (uint256[] memory) {
uint256 i;
uint256 l = tokenIds_.length;
uint256[] memory _totalTimeStakeds = new uint256[] (l);
while (i < l) {
_totalTimeStakeds[i] = totalTimeStakedOfToken(tokenIds_[i]);
unchecked { ++i; }
}
return _totalTimeStakeds;
}
///// ERC721G: ERC721 Standard Logic /////
/** @dev clarification:
* no explanations here as these are standard ERC721 logics.
* the reason that we can use standard ERC721 logics is because
* the ERC721G logic is compartmentalized and supports internally
* these ERC721 logics without any need of modification.
*/
function _isApprovedOrOwner(address spender_, uint256 tokenId_) internal
view virtual returns (bool) {
address _owner = ownerOf(tokenId_);
return (
// "i am the owner of the token, and i am transferring it"
_owner == spender_
// "the token's approved spender is me"
|| getApproved[tokenId_] == spender_
// "the owner has approved me to spend all his tokens"
|| isApprovedForAll[_owner][spender_]);
}
/** @dev clarification:
* sets a specific address to be able to spend a specific token.
*/
function _approve(address to_, uint256 tokenId_) internal virtual {
getApproved[tokenId_] = to_;
emit Approval(ownerOf(tokenId_), to_, tokenId_);
}
function approve(address to_, uint256 tokenId_) public virtual {
address _owner = ownerOf(tokenId_);
require(
// "i am the owner, and i am approving this token."
_owner == msg.sender
// "i am isApprovedForAll, so i can approve this token too."
|| isApprovedForAll[_owner][msg.sender],
"ERC721G: approve not authorized");
_approve(to_, tokenId_);
}
function _setApprovalForAll(address owner_, address operator_, bool approved_)
internal virtual {
isApprovedForAll[owner_][operator_] = approved_;
emit ApprovalForAll(owner_, operator_, approved_);
}
function setApprovalForAll(address operator_, bool approved_) public virtual {
// this function can only be used as self-approvalforall for others.
_setApprovalForAll(msg.sender, operator_, approved_);
}
function _exists(uint256 tokenId_) internal virtual view returns (bool) {
return ownerOf(tokenId_) != address(0);
}
function transferFrom(address from_, address to_, uint256 tokenId_) public virtual {
require(_isApprovedOrOwner(msg.sender, tokenId_),
"ERC721G: transferFrom unauthorized");
_transfer(from_, to_, tokenId_);
}
function safeTransferFrom(address from_, address to_, uint256 tokenId_,
bytes memory data_) public virtual {
transferFrom(from_, to_, tokenId_);
if (to_.code.length != 0) {
(, bytes memory _returned) = to_.call(abi.encodeWithSelector(
0x150b7a02, msg.sender, from_, tokenId_, data_));
bytes4 _selector = abi.decode(_returned, (bytes4));
require(_selector == 0x150b7a02,
"ERC721G: safeTransferFrom to_ non-ERC721Receivable!");
}
}
function safeTransferFrom(address from_, address to_, uint256 tokenId_)
public virtual {
safeTransferFrom(from_, to_, tokenId_, "");
}
function supportsInterface(bytes4 iid_) public virtual view returns (bool) {
return iid_ == 0x01ffc9a7 || iid_ == 0x80ac58cd || iid_ == 0x5b5e139f || iid_ == 0x7f5828d0;
}
function walletOfOwner(address address_) public virtual view
returns (uint256[] memory) {
uint256 _balance = balanceOf(address_);
uint256[] memory _tokens = new uint256[] (_balance);
uint256 _currentIndex;
uint256 i = startTokenId;
while (_currentIndex < _balance) {
if (ownerOf(i) == address_) { _tokens[_currentIndex++] = i; }
unchecked { ++i; }
}
return _tokens;
}
function tokenURI(uint256 tokenId_) public virtual view returns (string memory) {}
// Proxy Padding
bytes32[50] private proxyPadding;
}
abstract contract Minterable is Ownable {
mapping(address => bool) public minters;
modifier onlyMinter { require(minters[msg.sender], "Not Minter!"); _; }
event MinterSet(address newMinter, bool status);
function setMinter(address address_, bool bool_) external onlyOwner {
minters[address_] = bool_;
emit MinterSet(address_, bool_);
}
}
contract GAS2P4_20251007_1 is ERC721G, Ownable, Minterable {
// Set the base ERC721G Constructor
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() ERC721G("Gangster All Star: Evolution", "GAS:EVO", 1, 20) {}
// Proxy Initializer Logic
bool proxyIsInitialized;
// GAS2P2_20241029_1 we no longer need to initialize
// function proxyInitialize(address newOwner) external {
// require(!proxyIsInitialized);
// proxyIsInitialized = true;
// // Hardcode
// owner = newOwner;
// name = "Gangster All Star: Evolution";
// symbol = "GAS:EVO";
// tokenIndex = 1;
// }
// GAS2P2_20241029_1 set new name and symbol for GAS2
function O_setNameAndSymbol(string calldata name_, string calldata symbol_)
external onlyOwner {
name = name_;
symbol = symbol_;
}
// On-Chain Generation Seed for Generative Art Generation
bytes32 public generationSeed;
function pullGenerationSeed() external onlyOwner {
generationSeed = keccak256(abi.encodePacked(
block.timestamp, block.number, block.difficulty,
block.coinbase, block.gaslimit, blockhash(block.number)
));
}
// Define the NFT Constant Params
uint256 public constant maxSupply = 17777; // Padding for boxes, actual supply is 7777
// Define NFT Global Params
bool public stakingIsEnabled;
bool public unstakingIsEnabled;
function O_setStakingIsEnabled(bool bool_) external onlyOwner {
stakingIsEnabled = bool_; }
function O_setUnstakingIsEnabled(bool bool_) external onlyOwner {
unstakingIsEnabled = bool_; }
// Internal Overrides
function _mint(address address_, uint256 amount_) internal override {
require(maxSupply >= (totalSupply() + amount_),
"ERC721G: _mint(): exceeds maxSupply");
ERC721G._mint(address_, amount_);
}
// Stake / Unstake Overrides for Future Compatibility
function stake(uint256[] calldata tokenIds_) public override {
require(stakingIsEnabled, "Staking functionality not enabled yet!");
ERC721G.stake(tokenIds_);
}
function unstake(uint256[] calldata tokenIds_) public override {
require(unstakingIsEnabled, "Unstaking functionality not enabled yet!");
ERC721G.unstake(tokenIds_);
}
// Internal Functions
function _mintMany(address[] memory addresses_, uint256[] memory amounts_)
internal {
require(addresses_.length == amounts_.length, "Array lengths mismatch!");
for (uint256 i = 0; i < addresses_.length;) {
_mint(addresses_[i], amounts_[i]);
unchecked { ++i; }
}
}
// Controllerable Minting
function mintAsController(address to_, uint256 amount_) external onlyMinter {
_mint(to_, amount_);
}
function mintAsControllerMany(address[] calldata tos_, uint256[] calldata amounts_)
external onlyMinter {
_mintMany(tos_, amounts_);
}
// Token URI Configurations
string internal baseURI;
string internal baseURI_EXT;
function O_setBaseURI(string calldata uri_) external onlyOwner {
baseURI = uri_;
}
function O_setBaseURI_EXT(string calldata ext_) external onlyOwner {
baseURI_EXT = ext_;
}
function _toString(uint256 value_) internal pure returns (string memory) {
if (value_ == 0) { return "0"; }
uint256 _iterate = value_; uint256 _digits;
while (_iterate != 0) { _digits++; _iterate /= 10; }
bytes memory _buffer = new bytes(_digits);
while (value_ != 0) { _digits--; _buffer[_digits] = bytes1(uint8(
48 + uint256(value_ % 10 ))); value_ /= 10; }
return string(_buffer);
}
function tokenURI(uint256 tokenId_) public view override returns (string memory) {
// PoS Merge-Safe
if (block.chainid != 1) return "";
return string(abi.encodePacked(baseURI, _toString(tokenId_), baseURI_EXT));
}
///// Additional Helper Functions /////
function isOwnerOfAll(address owner, uint256[] calldata tokenIds_)
external view returns (bool) {
// Patch 2.1
uint256 i;
uint256 l = tokenIds_.length;
unchecked { do {
if (ownerOf(tokenIds_[i]) != owner) return false;
} while (++i < l); }
return true;
}
function isTrueOwnerOfAll(address owner, uint256[] calldata tokenIds_)
external view returns (bool) {
// Patch 2.1
uint256 i;
uint256 l = tokenIds_.length;
unchecked { do {
if (_trueOwnerOf(tokenIds_[i]) != owner) return false;
} while (++i < l); }
return true;
}
////////////////////////////////////////////////////////
///// GAS2 GAS2 GAS2 GAS2 GAS2 GAS2 GAS2 GAS2 GAS2 /////
///// GAS2 GAS2 GAS2 GAS2 GAS2 GAS2 GAS2 GAS2 GAS2 /////
///// GAS2 GAS2 GAS2 GAS2 GAS2 GAS2 GAS2 GAS2 GAS2 /////
////////////////////////////////////////////////////////
////////////////////////////////////////////////////////
///// CLAIM CLAIM CLAIM CLAIM CLAIM CLAIM CLAIM /////
///// CLAIM CLAIM CLAIM CLAIM CLAIM CLAIM CLAIM /////
///// CLAIM CLAIM CLAIM CLAIM CLAIM CLAIM CLAIM /////
////////////////////////////////////////////////////////
// For path 2, we are reusing the SSTORE for all early 1-7777 and repacking
// them from box to GAS2 by using 0x...dead retransfer
// For this, we have the issue of users able to unbox/reveal and accept bids immediately
// For that, we will remedy it by locking user's transfers for 1h after revealing
// Also, we need to figure out a way to indicate the packed-token is a box or a GAS2
// Initial ideas include checking the GASEVO contract if the EVO counterpart has been minted
// Initialize the connection to GASEVO
IGASEVO public constant GASEVO = IGASEVO(0x0f926Df0DDB33A1dB95088964E09Fa8Fb47E490E); // GAS EVO
// Initialize a constant BURN_ADDRESS
address public constant BURN_ADDRESS = 0x000000000000000000000000000000000000dEaD;
// Packed ID claim
function _claimGAS2Internal(address user_, uint256[] memory tokenIds_) internal {
// All-or-nothing check
uint256 _amount = tokenIds_.length;
uint256 _balance = GASEVO.balanceOf(user_);
require(_amount == _balance, "INVALID_UPGRADE_AMOUNT");
// Resurrection Claim on packed 1-7777
for (uint256 i; i < _amount;) {
// Load current ID into memory
uint256 _currId = tokenIds_[i];
// Make sure the current ID is owned by the user
require(GASEVO.ownerOf(_currId) == user_, "NOT_OWNER_OF_TOKEN");
// Make sure that the token hasn't been claimed yet
// Note: there is a edge case where the user can transfer "burn" and reclaim
require(ownerOf(_currId) == BURN_ADDRESS, "TOKEN_EXISTS");
// If both conditions are met, packed-resurrect mint the GAS2
_transfer(BURN_ADDRESS, user_, _currId);
// Increment the loop tracker
unchecked { ++i; }
}
}
// Public msg.sender initiated claiming of GAS2
function claimGAS2(uint256[] calldata tokenIds_) external {
_claimGAS2Internal(msg.sender, tokenIds_);
}
// Priviledged claiming GAS2 for other users by onlyMinter
function C_claimGAS2For(address user_, uint256[] calldata tokenIds_) external
onlyMinter {
_claimGAS2Internal(user_, tokenIds_);
}
// This function is meant to be used in very specific cases only and should be removed after.
function C_bulkClaimFor(uint256[] calldata tokenIds_) external onlyMinter {
// Here, we prime a address-agnostic function and find the user address within
// the function itself. Additionally, we do bulk tokenIds so that we can
// unbox multiple tokens for people.
// Initiate loop length locally
uint256 _amount = tokenIds_.length;
// Do claim loop
for (uint256 i; i < _amount;) {
// Load current ID to in-loop scope
uint256 _currId = tokenIds_[i];
// Make sure that the token is in "burned" state (unclaimed)
require(ownerOf(_currId) == BURN_ADDRESS, "TOKEN_EXISTS");
// Find the owner of the GAS1 counterpart
address _owner = GASEVO.ownerOf(_currId);
// Transfer the token from BURN_ADDRESS to the _owner
_transfer(BURN_ADDRESS, _owner, _currId);
unchecked { ++i; }
}
}
////////////////////////////////////////////////////////
///// UNBOX UNBOX UNBOX UNBOX UNBOX UNBOX UNBOX /////
///// UNBOX UNBOX UNBOX UNBOX UNBOX UNBOX UNBOX /////
///// UNBOX UNBOX UNBOX UNBOX UNBOX UNBOX UNBOX /////
////////////////////////////////////////////////////////
// Now, we must create a function for the new reveal mechanic.
// Here, we will reuse the SSTORE of GASEVO to indicate revealed / non revealed
// The conditions are as following:
// GAS2 - MINTED && GASEVO - NOT_MINTED => BOX / EQUIPMENT
// GAS2 - MINTED && GASEVO - MINTED => CLAIMED & REVEALED
// GAS2 - BURNED && GASEVO - MINTED => UNCLAIMED & UNREVEALED
// GAS2 - BURNED && GASEVO - NOT_MINTED => EXCEPTION / ERROR
// Additionally, after revealing, we lock the user's tokens transfer for 2H
// GAS2P3_20250831_1 Update: Migrate Boxes for Unbox
function C_prepareGAS2UnboxPad() external onlyOwner {
require(tokenIndex == 7778, "PAD_ERROR");
// Mint tokens to burn address until 10001
for (uint256 i; i < 2220; i += 20) {
_mint(BURN_ADDRESS, 20);
}
_mint(BURN_ADDRESS, 3);
require(tokenIndex == 10001, "PAD_ERROR_END");
}
function C_migrateBoxesToPadded(uint256 amount_) external onlyOwner {
require(tokenIndex > 10000, "PAD_START_ERROR");
require(tokenIndex < 17778, "PAD_ENDED");
for (uint256 i; i < amount_; i++) {
// get current ID to process
uint256 _idToProcess = tokenIndex - 10000;
address _evoOwner = GASEVO.ownerOf(_idToProcess);
// EVO owner is addr0, so we will migrate it (it's a box)
if (_evoOwner == address(0)) {
address _gas2Owner = ownerOf(_idToProcess);
_transfer(_gas2Owner, BURN_ADDRESS, _idToProcess);
_mint(_gas2Owner, 1); // sequential mint lego blocks
}
// EVO owner isn't addr0, so we pad it to burn_address
else {
_mint(BURN_ADDRESS, 1);
}
}
require(tokenIndex <= 17778, "PAD_OVER_LIMIT");
}
function _unboxGAS2Internal(address user_, uint256[] memory tokenIds_) internal {
// assume boxes are > 10000
for (uint256 i; i < tokenIds_.length; i++) {
uint256 _boxId = tokenIds_[i];
uint256 _unboxedId = _boxId - 10000;
// Checks
require(_boxId > 10000, "NOT_BOX");
require(ownerOf(_boxId) == user_, "NOT_OWNER");
require(ownerOf(_unboxedId) == BURN_ADDRESS, "NOT_BURNED_EXCEPTION");
require(GASEVO.ownerOf(_unboxedId) == address(0), "MINTED_EXCEPTION");
// Effects
_transfer(user_, BURN_ADDRESS, _boxId);
_transfer(BURN_ADDRESS, user_, _unboxedId);
// Interactions
GASEVO.mintAsController(user_, _unboxedId);
}
}
// Priviledged GAS2 Unbox by onlyMinter
function C_unboxFor(address user_, uint256[] calldata tokenIds_) external onlyMinter {
_unboxGAS2Internal(user_, tokenIds_);
}
// GAS2P4_20251007_1 Update: enable Public msg.sender GAS2 Unbox
function unboxGAS2(uint256[] calldata tokenIds_) external {
_unboxGAS2Internal(msg.sender, tokenIds_);
}
// Proxy Padding
bytes32[50] private proxyPadding;
}
Submitted on: 2025-10-07 12:38:56
Comments
Log in to comment.
No comments yet.