Description:
Multi-signature wallet contract requiring multiple confirmations for transaction execution.
Blockchain: Ethereum
Source Code: View Code On The Blockchain
Solidity Source Code:
{{
"language": "Solidity",
"sources": {
"GalactigonSoulNFT.sol": {
"content": "// SPDX-License-Identifier: MIT\r
pragma solidity ^0.8.20;\r
\r
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";\r
import "@openzeppelin/contracts/access/Ownable.sol";\r
\r
// ==================== INTERFACES FOR ALL SMART CONTRACTS ====================\r
\r
interface IPepeManticToken {\r
function balanceOf(address account) external view returns (uint256);\r
function totalSupply() external view returns (uint256);\r
function transfer(address to, uint256 amount) external returns (bool);\r
}\r
\r
interface IPepeManticBattleNFT {\r
function balanceOf(address owner) external view returns (uint256);\r
function ownerOf(uint256 tokenId) external view returns (address);\r
}\r
\r
interface IPresaleModule {\r
function contributions(address user) external view returns (uint256);\r
function claimed(address user) external view returns (bool);\r
}\r
\r
interface IStakingModule {\r
function getUserStake(address user) external view returns (uint256);\r
function getUserTier(address user) external view returns (uint8);\r
function boostStake(address user, uint256 amount) external;\r
}\r
\r
interface IDividendModule {\r
function viewDividends(address user) external view returns (uint256);\r
function hasClaimed(address user) external view returns (bool);\r
function grantBonus(address user, uint256 amount) external;\r
}\r
\r
interface IStakingBoostAdapter {\r
function getBoostMultiplier(address user) external view returns (uint256);\r
}\r
\r
interface IRewardOracleModule {\r
function getUserBoost(address user) external view returns (uint256);\r
function updateUserBoost(address user, uint256 newBoost) external;\r
}\r
\r
interface IClaimAndStakeRouter {\r
function hasClaimedAndStaked(address user) external view returns (bool);\r
}\r
\r
interface IMultiCurrencyHandler {\r
function getSupportedCurrencies() external view returns (address[] memory);\r
function getExchangeRate(address currency) external view returns (uint256);\r
}\r
\r
interface ITreasuryManager {\r
function recordSoulMint(address user, uint256 soulScore) external;\r
function getTreasuryBalance() external view returns (uint256);\r
}\r
\r
interface IPepeManticMarketplace {\r
function getUserListings(address user) external view returns (uint256);\r
}\r
\r
interface IPepeManticRapBattleArena {\r
function getUserBattles(address user) external view returns (uint256);\r
function getUserWins(address user) external view returns (uint256);\r
}\r
\r
interface IAIOracleRequestManager {\r
function requestCount() external view returns (uint256);\r
}\r
\r
interface IVestingWallet {\r
function getVestingSchedule(address user) external view returns (uint256, uint256);\r
}\r
\r
interface IVestingManager {\r
function getTotalVested(address user) external view returns (uint256);\r
}\r
\r
interface IMainController {\r
function notifySoulAlignment(address user) external;\r
function isControllerActive() external view returns (bool);\r
}\r
\r
interface IFiatClaimRegistry {\r
function hasClaimed(address user) external view returns (bool);\r
function getTotalFiatClaims() external view returns (uint256);\r
}\r
\r
interface ISystemModeController {\r
function isMainnetMode() external view returns (bool);\r
function isMaintenanceMode() external view returns (bool);\r
}\r
\r
// ==================== GALACTIGON SOUL NFT - COMPLETE ====================\r
\r
/// @title Galactigon Soul Alignment NFT\r
/// @notice Eternal proof of a wallet's soul alignment to Galactigon\r
/// @dev Complete integration with all PepeMantic ecosystem contracts\r
contract GalactigonSoulNFT is ERC721URIStorage, Ownable {\r
\r
// ==================== STATE VARIABLES ====================\r
\r
uint256 public nextTokenId;\r
mapping(address => bool) public hasAligned;\r
mapping(uint256 => string) public soulHashes;\r
mapping(address => uint256) public soulScores;\r
mapping(address => uint8) public soulTiers;\r
\r
// ==================== CONTRACT REFERENCES ====================\r
\r
IPepeManticToken public pepeManticToken;\r
IPepeManticBattleNFT public battleNFT;\r
IPresaleModule public presale;\r
IStakingModule public staking;\r
IDividendModule public dividends;\r
IStakingBoostAdapter public stakingBoost;\r
IRewardOracleModule public oracle;\r
IClaimAndStakeRouter public claimRouter;\r
IMultiCurrencyHandler public multiCurrency;\r
ITreasuryManager public treasury;\r
IPepeManticMarketplace public marketplace;\r
IPepeManticRapBattleArena public battleArena;\r
IAIOracleRequestManager public aiOracle;\r
IVestingWallet public vestingWallet;\r
IVestingManager public vestingManager;\r
IMainController public controller;\r
IFiatClaimRegistry public fiat;\r
ISystemModeController public systemMode;\r
\r
// ==================== EVENTS ====================\r
\r
event SoulAligned(address indexed wallet, uint256 tokenId, string hash);\r
event SoulScoreUpdated(address indexed user, uint256 newScore);\r
event SoulTierSet(address indexed user, uint8 tier);\r
event ContractUpdated(string contractName, address newAddress);\r
\r
// ==================== CONSTRUCTOR ====================\r
\r
constructor(\r
address initialOwner,\r
address presaleAddr,\r
address stakingAddr,\r
address dividendAddr,\r
address oracleAddr,\r
address fiatAddr,\r
address treasuryAddr,\r
address controllerAddr\r
) ERC721("Galactigon Soul Alignment", "SOUL") Ownable(initialOwner) {\r
presale = IPresaleModule(presaleAddr);\r
staking = IStakingModule(stakingAddr);\r
dividends = IDividendModule(dividendAddr);\r
oracle = IRewardOracleModule(oracleAddr);\r
fiat = IFiatClaimRegistry(fiatAddr);\r
treasury = ITreasuryManager(treasuryAddr);\r
controller = IMainController(controllerAddr);\r
}\r
\r
// ==================== SETTER FUNCTIONS - ALL CONTRACTS ====================\r
\r
function setPepeManticToken(address addr) external onlyOwner { \r
pepeManticToken = IPepeManticToken(addr); \r
emit ContractUpdated("PepeManticToken", addr);\r
}\r
\r
function setBattleNFT(address addr) external onlyOwner { \r
battleNFT = IPepeManticBattleNFT(addr); \r
emit ContractUpdated("BattleNFT", addr);\r
}\r
\r
function setPresaleModule(address addr) external onlyOwner { \r
presale = IPresaleModule(addr); \r
emit ContractUpdated("PresaleModule", addr);\r
}\r
\r
function setStakingModule(address addr) external onlyOwner { \r
staking = IStakingModule(addr); \r
emit ContractUpdated("StakingModule", addr);\r
}\r
\r
function setDividendModule(address addr) external onlyOwner { \r
dividends = IDividendModule(addr); \r
emit ContractUpdated("DividendModule", addr);\r
}\r
\r
function setStakingBoostAdapter(address addr) external onlyOwner { \r
stakingBoost = IStakingBoostAdapter(addr); \r
emit ContractUpdated("StakingBoostAdapter", addr);\r
}\r
\r
function setOracleModule(address addr) external onlyOwner { \r
oracle = IRewardOracleModule(addr); \r
emit ContractUpdated("OracleModule", addr);\r
}\r
\r
function setClaimAndStakeRouter(address addr) external onlyOwner { \r
claimRouter = IClaimAndStakeRouter(addr); \r
emit ContractUpdated("ClaimAndStakeRouter", addr);\r
}\r
\r
function setMultiCurrencyHandler(address addr) external onlyOwner { \r
multiCurrency = IMultiCurrencyHandler(addr); \r
emit ContractUpdated("MultiCurrencyHandler", addr);\r
}\r
\r
function setTreasury(address addr) external onlyOwner { \r
treasury = ITreasuryManager(addr); \r
emit ContractUpdated("TreasuryManager", addr);\r
}\r
\r
function setMarketplace(address addr) external onlyOwner { \r
marketplace = IPepeManticMarketplace(addr); \r
emit ContractUpdated("Marketplace", addr);\r
}\r
\r
function setBattleArena(address addr) external onlyOwner { \r
battleArena = IPepeManticRapBattleArena(addr); \r
emit ContractUpdated("BattleArena", addr);\r
}\r
\r
function setAIOracle(address addr) external onlyOwner { \r
aiOracle = IAIOracleRequestManager(addr); \r
emit ContractUpdated("AIOracleRequestManager", addr);\r
}\r
\r
function setVestingWallet(address addr) external onlyOwner { \r
vestingWallet = IVestingWallet(addr); \r
emit ContractUpdated("VestingWallet", addr);\r
}\r
\r
function setVestingManager(address addr) external onlyOwner { \r
vestingManager = IVestingManager(addr); \r
emit ContractUpdated("VestingManager", addr);\r
}\r
\r
function setMainController(address addr) external onlyOwner { \r
controller = IMainController(addr); \r
emit ContractUpdated("MainController", addr);\r
}\r
\r
function setFiatRegistry(address addr) external onlyOwner { \r
fiat = IFiatClaimRegistry(addr); \r
emit ContractUpdated("FiatClaimRegistry", addr);\r
}\r
\r
function setSystemModeController(address addr) external onlyOwner { \r
systemMode = ISystemModeController(addr); \r
emit ContractUpdated("SystemModeController", addr);\r
}\r
\r
// ==================== CORE SOUL ALIGNMENT FUNCTION ====================\r
\r
function mintSoulAlignment(string memory tokenUri, string memory hash) external {\r
require(!hasAligned[msg.sender], "Your soul is already aligned");\r
\r
uint256 tokenId = nextTokenId;\r
_safeMint(msg.sender, tokenId);\r
_setTokenURI(tokenId, tokenUri);\r
\r
hasAligned[msg.sender] = true;\r
soulHashes[tokenId] = hash;\r
\r
// ???? Auto-generate soul score based on wallet activity\r
uint256 presaleScore = presale.contributions(msg.sender) / 1e16; // 0.01 ETH = 1pt\r
uint256 stakeScore = staking.getUserStake(msg.sender) / 1e18; // 1 token = 1pt\r
uint256 dividendScore = dividends.viewDividends(msg.sender) / 1e18;\r
uint256 totalScore = presaleScore + stakeScore + dividendScore;\r
\r
soulScores[msg.sender] = totalScore;\r
emit SoulScoreUpdated(msg.sender, totalScore);\r
\r
// ????️ Set tier based on score\r
uint8 tier = 0;\r
if (totalScore >= 5000) tier = 5;\r
else if (totalScore >= 2500) tier = 4;\r
else if (totalScore >= 1000) tier = 3;\r
else if (totalScore >= 500) tier = 2;\r
else if (totalScore >= 100) tier = 1;\r
\r
soulTiers[msg.sender] = tier;\r
emit SoulTierSet(msg.sender, tier);\r
\r
// ???? Apply soul multiplier to oracle boost\r
uint256 currentBoost = oracle.getUserBoost(msg.sender);\r
uint256 newBoost = currentBoost + (tier * 10); // e.g. Tier 3 = +30 boost\r
oracle.updateUserBoost(msg.sender, newBoost);\r
\r
treasury.recordSoulMint(msg.sender, totalScore);\r
controller.notifySoulAlignment(msg.sender);\r
\r
emit SoulAligned(msg.sender, tokenId, hash);\r
nextTokenId++;\r
}\r
\r
// ==================== VIEW FUNCTIONS - BASIC ====================\r
\r
function isAligned(address wallet) public view returns (bool) {\r
return hasAligned[wallet];\r
}\r
\r
function getSoulHash(uint256 tokenId) public view returns (string memory) {\r
return soulHashes[tokenId];\r
}\r
\r
function getSoulProfile(address user) external view returns (uint256 score, uint8 tier, bool aligned) {\r
score = soulScores[user];\r
tier = soulTiers[user];\r
aligned = hasAligned[user];\r
}\r
\r
// ==================== VIEW FUNCTIONS - TOKEN DATA ====================\r
\r
function getTokenData(address user) external view returns (uint256 balance, uint256 totalSupply) {\r
if (address(pepeManticToken) != address(0)) {\r
balance = pepeManticToken.balanceOf(user);\r
totalSupply = pepeManticToken.totalSupply();\r
}\r
}\r
\r
function getBattleNFTData(address user) external view returns (uint256 nftBalance) {\r
if (address(battleNFT) != address(0)) {\r
nftBalance = battleNFT.balanceOf(user);\r
}\r
}\r
\r
// ==================== VIEW FUNCTIONS - PRESALE & STAKING ====================\r
\r
function getPresaleData(address user) external view returns (uint256 contribution, bool hasClaimed) {\r
contribution = presale.contributions(user);\r
hasClaimed = presale.claimed(user);\r
}\r
\r
function getStakingData(address user) external view returns (uint256 stakedAmount, uint8 tier) {\r
stakedAmount = staking.getUserStake(user);\r
tier = staking.getUserTier(user);\r
}\r
\r
function getStakingBoostData(address user) external view returns (uint256 boostMultiplier) {\r
if (address(stakingBoost) != address(0)) {\r
boostMultiplier = stakingBoost.getBoostMultiplier(user);\r
}\r
}\r
\r
// ==================== VIEW FUNCTIONS - DIVIDENDS & ORACLE ====================\r
\r
function getDividendData(address user) external view returns (uint256 unclaimedDividends, bool claimed) {\r
unclaimedDividends = dividends.viewDividends(user);\r
claimed = dividends.hasClaimed(user);\r
}\r
\r
function getUserBoost(address user) external view returns (uint256) {\r
return oracle.getUserBoost(user);\r
}\r
\r
// ==================== VIEW FUNCTIONS - CLAIMS & ROUTING ====================\r
\r
function getClaimRouterStatus(address user) external view returns (bool hasClaimedAndStaked) {\r
if (address(claimRouter) != address(0)) {\r
hasClaimedAndStaked = claimRouter.hasClaimedAndStaked(user);\r
}\r
}\r
\r
function getFiatClaimStatus(address user) external view returns (bool) {\r
return fiat.hasClaimed(user);\r
}\r
\r
function getFiatClaimData() external view returns (uint256 totalClaims) {\r
if (address(fiat) != address(0)) {\r
totalClaims = fiat.getTotalFiatClaims();\r
}\r
}\r
\r
// ==================== VIEW FUNCTIONS - MARKETPLACE & BATTLE ====================\r
\r
function getMarketplaceData(address user) external view returns (uint256 listings) {\r
if (address(marketplace) != address(0)) {\r
listings = marketplace.getUserListings(user);\r
}\r
}\r
\r
function getBattleArenaData(address user) external view returns (uint256 battles, uint256 wins) {\r
if (address(battleArena) != address(0)) {\r
battles = battleArena.getUserBattles(user);\r
wins = battleArena.getUserWins(user);\r
}\r
}\r
\r
// ==================== VIEW FUNCTIONS - AI ORACLE ====================\r
\r
function getAIOracleData() external view returns (uint256 requestCount) {\r
if (address(aiOracle) != address(0)) {\r
requestCount = aiOracle.requestCount();\r
}\r
}\r
\r
// ==================== VIEW FUNCTIONS - VESTING ====================\r
\r
function getVestingWalletData(address user) external view returns (uint256 vestingStart, uint256 vestingDuration) {\r
if (address(vestingWallet) != address(0)) {\r
(vestingStart, vestingDuration) = vestingWallet.getVestingSchedule(user);\r
}\r
}\r
\r
function getVestingManagerData(address user) external view returns (uint256 totalVested) {\r
if (address(vestingManager) != address(0)) {\r
totalVested = vestingManager.getTotalVested(user);\r
}\r
}\r
\r
// ==================== VIEW FUNCTIONS - SYSTEM STATUS ====================\r
\r
function getControllerStatus() external view returns (bool isActive) {\r
if (address(controller) != address(0)) {\r
isActive = controller.isControllerActive();\r
}\r
}\r
\r
function getSystemModeStatus() external view returns (bool mainnet, bool maintenance) {\r
if (address(systemMode) != address(0)) {\r
mainnet = systemMode.isMainnetMode();\r
maintenance = systemMode.isMaintenanceMode();\r
}\r
}\r
\r
function getTreasuryBalance() external view returns (uint256) {\r
if (address(treasury) != address(0)) {\r
return treasury.getTreasuryBalance();\r
}\r
return 0;\r
}\r
\r
function getMultiCurrencyData() external view returns (address[] memory currencies) {\r
if (address(multiCurrency) != address(0)) {\r
currencies = multiCurrency.getSupportedCurrencies();\r
}\r
}\r
\r
// ==================== WRITE INTERACTIONS - OWNER ONLY ====================\r
\r
function boostUserOracle(address user, uint256 newBoost) external onlyOwner {\r
oracle.updateUserBoost(user, newBoost);\r
}\r
\r
function bonusDividends(address user, uint256 amount) external onlyOwner {\r
dividends.grantBonus(user, amount);\r
}\r
\r
function soulStakingBoost(address user, uint256 extraAmount) external onlyOwner {\r
staking.boostStake(user, extraAmount);\r
}\r
\r
function updateSoulScore(address user, uint256 score) external onlyOwner {\r
soulScores[user] = score;\r
emit SoulScoreUpdated(user, score);\r
}\r
\r
function setSoulTier(address user, uint8 tier) external onlyOwner {\r
soulTiers[user] = tier;\r
emit SoulTierSet(user, tier);\r
}\r
\r
// ==================== COMPREHENSIVE SOUL DATA ====================\r
\r
/// @notice Get complete ecosystem data for a user\r
/// @param user The address to query\r
/// @return soulScore The user's calculated soul score\r
/// @return soulTier The user's soul tier (0-5)\r
/// @return soulAligned Whether user has minted a soul NFT\r
/// @return tokenBalance User's MANTIC token balance\r
/// @return stakedAmount Amount of tokens staked\r
/// @return unclaimedDividends Unclaimed dividend amount\r
/// @return oracleBoost Current oracle boost multiplier\r
/// @return battles Total battles participated in\r
/// @return wins Total battle wins\r
/// @return fiatClaimed Whether user claimed fiat allocation\r
function getComprehensiveSoulData(address user) external view returns (\r
uint256 soulScore,\r
uint8 soulTier,\r
bool soulAligned,\r
uint256 tokenBalance,\r
uint256 stakedAmount,\r
uint256 unclaimedDividends,\r
uint256 oracleBoost,\r
uint256 battles,\r
uint256 wins,\r
bool fiatClaimed\r
) {\r
soulScore = soulScores[user];\r
soulTier = soulTiers[user];\r
soulAligned = hasAligned[user];\r
\r
if (address(pepeManticToken) != address(0)) {\r
tokenBalance = pepeManticToken.balanceOf(user);\r
}\r
\r
stakedAmount = staking.getUserStake(user);\r
(unclaimedDividends, ) = this.getDividendData(user);\r
oracleBoost = oracle.getUserBoost(user);\r
\r
if (address(battleArena) != address(0)) {\r
battles = battleArena.getUserBattles(user);\r
wins = battleArena.getUserWins(user);\r
}\r
\r
fiatClaimed = fiat.hasClaimed(user);\r
}\r
}\r
"
},
"@openzeppelin/contracts/access/Ownable.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)
pragma solidity ^0.8.20;
import {Context} from "../utils/Context.sol";
/**
* @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.
*
* The initial owner is set to the address provided by the deployer. 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;
/**
* @dev The caller account is not authorized to perform an operation.
*/
error OwnableUnauthorizedAccount(address account);
/**
* @dev The owner is not a valid owner account. (eg. `address(0)`)
*/
error OwnableInvalidOwner(address owner);
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the address provided by the deployer as the initial owner.
*/
constructor(address initialOwner) {
if (initialOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(initialOwner);
}
/**
* @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 {
if (owner() != _msgSender()) {
revert OwnableUnauthorizedAccount(_msgSender());
}
}
/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby disabling 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 {
if (newOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_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);
}
}
"
},
"@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (token/ERC721/extensions/ERC721URIStorage.sol)
pragma solidity ^0.8.20;
import {ERC721} from "../ERC721.sol";
import {IERC721Metadata} from "./IERC721Metadata.sol";
import {Strings} from "../../../utils/Strings.sol";
import {IERC4906} from "../../../interfaces/IERC4906.sol";
import {IERC165} from "../../../interfaces/IERC165.sol";
/**
* @dev ERC-721 token with storage based token URI management.
*/
abstract contract ERC721URIStorage is IERC4906, ERC721 {
using Strings for uint256;
// Interface ID as defined in ERC-4906. This does not correspond to a traditional interface ID as ERC-4906 only
// defines events and does not include any external function.
bytes4 private constant ERC4906_INTERFACE_ID = bytes4(0x49064906);
// Optional mapping for token URIs
mapping(uint256 tokenId => string) private _tokenURIs;
/// @inheritdoc IERC165
function supportsInterface(bytes4 interfaceId) public view virtual override(ERC721, IERC165) returns (bool) {
return interfaceId == ERC4906_INTERFACE_ID || super.supportsInterface(interfaceId);
}
/// @inheritdoc IERC721Metadata
function tokenURI(uint256 tokenId) public view virtual override returns (string memory) {
_requireOwned(tokenId);
string memory _tokenURI = _tokenURIs[tokenId];
string memory base = _baseURI();
// If there is no base URI, return the token URI.
if (bytes(base).length == 0) {
return _tokenURI;
}
// If both are set, concatenate the baseURI and tokenURI (via string.concat).
if (bytes(_tokenURI).length > 0) {
return string.concat(base, _tokenURI);
}
return super.tokenURI(tokenId);
}
/**
* @dev Sets `_tokenURI` as the tokenURI of `tokenId`.
*
* Emits {IERC4906-MetadataUpdate}.
*/
function _setTokenURI(uint256 tokenId, string memory _tokenURI) internal virtual {
_tokenURIs[tokenId] = _tokenURI;
emit MetadataUpdate(tokenId);
}
}
"
},
"@openzeppelin/contracts/interfaces/IERC165.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (interfaces/IERC165.sol)
pragma solidity >=0.4.16;
import {IERC165} from "../utils/introspection/IERC165.sol";
"
},
"@openzeppelin/contracts/interfaces/IERC4906.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (interfaces/IERC4906.sol)
pragma solidity >=0.6.2;
import {IERC165} from "./IERC165.sol";
import {IERC721} from "./IERC721.sol";
/// @title ERC-721 Metadata Update Extension
interface IERC4906 is IERC165, IERC721 {
/// @dev This event emits when the metadata of a token is changed.
/// So that the third-party platforms such as NFT market could
/// timely update the images and related attributes of the NFT.
event MetadataUpdate(uint256 _tokenId);
/// @dev This event emits when the metadata of a range of tokens is changed.
/// So that the third-party platforms such as NFT market could
/// timely update the images and related attributes of the NFTs.
event BatchMetadataUpdate(uint256 _fromTokenId, uint256 _toTokenId);
}
"
},
"@openzeppelin/contracts/utils/Strings.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (utils/Strings.sol)
pragma solidity ^0.8.20;
import {Math} from "./math/Math.sol";
import {SafeCast} from "./math/SafeCast.sol";
import {SignedMath} from "./math/SignedMath.sol";
/**
* @dev String operations.
*/
library Strings {
using SafeCast for *;
bytes16 private constant HEX_DIGITS = "0123456789abcdef";
uint8 private constant ADDRESS_LENGTH = 20;
uint256 private constant SPECIAL_CHARS_LOOKUP =
(1 << 0x08) | // backspace
(1 << 0x09) | // tab
(1 << 0x0a) | // newline
(1 << 0x0c) | // form feed
(1 << 0x0d) | // carriage return
(1 << 0x22) | // double quote
(1 << 0x5c); // backslash
/**
* @dev The `value` string doesn't fit in the specified `length`.
*/
error StringsInsufficientHexLength(uint256 value, uint256 length);
/**
* @dev The string being parsed contains characters that are not in scope of the given base.
*/
error StringsInvalidChar();
/**
* @dev The string being parsed is not a properly formatted address.
*/
error StringsInvalidAddressFormat();
/**
* @dev Converts a `uint256` to its ASCII `string` decimal representation.
*/
function toString(uint256 value) internal pure returns (string memory) {
unchecked {
uint256 length = Math.log10(value) + 1;
string memory buffer = new string(length);
uint256 ptr;
assembly ("memory-safe") {
ptr := add(add(buffer, 0x20), length)
}
while (true) {
ptr--;
assembly ("memory-safe") {
mstore8(ptr, byte(mod(value, 10), HEX_DIGITS))
}
value /= 10;
if (value == 0) break;
}
return buffer;
}
}
/**
* @dev Converts a `int256` to its ASCII `string` decimal representation.
*/
function toStringSigned(int256 value) internal pure returns (string memory) {
return string.concat(value < 0 ? "-" : "", toString(SignedMath.abs(value)));
}
/**
* @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
*/
function toHexString(uint256 value) internal pure returns (string memory) {
unchecked {
return toHexString(value, Math.log256(value) + 1);
}
}
/**
* @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
*/
function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
uint256 localValue = value;
bytes memory buffer = new bytes(2 * length + 2);
buffer[0] = "0";
buffer[1] = "x";
for (uint256 i = 2 * length + 1; i > 1; --i) {
buffer[i] = HEX_DIGITS[localValue & 0xf];
localValue >>= 4;
}
if (localValue != 0) {
revert StringsInsufficientHexLength(value, length);
}
return string(buffer);
}
/**
* @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal
* representation.
*/
function toHexString(address addr) internal pure returns (string memory) {
return toHexString(uint256(uint160(addr)), ADDRESS_LENGTH);
}
/**
* @dev Converts an `address` with fixed length of 20 bytes to its checksummed ASCII `string` hexadecimal
* representation, according to EIP-55.
*/
function toChecksumHexString(address addr) internal pure returns (string memory) {
bytes memory buffer = bytes(toHexString(addr));
// hash the hex part of buffer (skip length + 2 bytes, length 40)
uint256 hashValue;
assembly ("memory-safe") {
hashValue := shr(96, keccak256(add(buffer, 0x22), 40))
}
for (uint256 i = 41; i > 1; --i) {
// possible values for buffer[i] are 48 (0) to 57 (9) and 97 (a) to 102 (f)
if (hashValue & 0xf > 7 && uint8(buffer[i]) > 96) {
// case shift by xoring with 0x20
buffer[i] ^= 0x20;
}
hashValue >>= 4;
}
return string(buffer);
}
/**
* @dev Returns true if the two strings are equal.
*/
function equal(string memory a, string memory b) internal pure returns (bool) {
return bytes(a).length == bytes(b).length && keccak256(bytes(a)) == keccak256(bytes(b));
}
/**
* @dev Parse a decimal string and returns the value as a `uint256`.
*
* Requirements:
* - The string must be formatted as `[0-9]*`
* - The result must fit into an `uint256` type
*/
function parseUint(string memory input) internal pure returns (uint256) {
return parseUint(input, 0, bytes(input).length);
}
/**
* @dev Variant of {parseUint-string} that parses a substring of `input` located between position `begin` (included) and
* `end` (excluded).
*
* Requirements:
* - The substring must be formatted as `[0-9]*`
* - The result must fit into an `uint256` type
*/
function parseUint(string memory input, uint256 begin, uint256 end) internal pure returns (uint256) {
(bool success, uint256 value) = tryParseUint(input, begin, end);
if (!success) revert StringsInvalidChar();
return value;
}
/**
* @dev Variant of {parseUint-string} that returns false if the parsing fails because of an invalid character.
*
* NOTE: This function will revert if the result does not fit in a `uint256`.
*/
function tryParseUint(string memory input) internal pure returns (bool success, uint256 value) {
return _tryParseUintUncheckedBounds(input, 0, bytes(input).length);
}
/**
* @dev Variant of {parseUint-string-uint256-uint256} that returns false if the parsing fails because of an invalid
* character.
*
* NOTE: This function will revert if the result does not fit in a `uint256`.
*/
function tryParseUint(
string memory input,
uint256 begin,
uint256 end
) internal pure returns (bool success, uint256 value) {
if (end > bytes(input).length || begin > end) return (false, 0);
return _tryParseUintUncheckedBounds(input, begin, end);
}
/**
* @dev Implementation of {tryParseUint-string-uint256-uint256} that does not check bounds. Caller should make sure that
* `begin <= end <= input.length`. Other inputs would result in undefined behavior.
*/
function _tryParseUintUncheckedBounds(
string memory input,
uint256 begin,
uint256 end
) private pure returns (bool success, uint256 value) {
bytes memory buffer = bytes(input);
uint256 result = 0;
for (uint256 i = begin; i < end; ++i) {
uint8 chr = _tryParseChr(bytes1(_unsafeReadBytesOffset(buffer, i)));
if (chr > 9) return (false, 0);
result *= 10;
result += chr;
}
return (true, result);
}
/**
* @dev Parse a decimal string and returns the value as a `int256`.
*
* Requirements:
* - The string must be formatted as `[-+]?[0-9]*`
* - The result must fit in an `int256` type.
*/
function parseInt(string memory input) internal pure returns (int256) {
return parseInt(input, 0, bytes(input).length);
}
/**
* @dev Variant of {parseInt-string} that parses a substring of `input` located between position `begin` (included) and
* `end` (excluded).
*
* Requirements:
* - The substring must be formatted as `[-+]?[0-9]*`
* - The result must fit in an `int256` type.
*/
function parseInt(string memory input, uint256 begin, uint256 end) internal pure returns (int256) {
(bool success, int256 value) = tryParseInt(input, begin, end);
if (!success) revert StringsInvalidChar();
return value;
}
/**
* @dev Variant of {parseInt-string} that returns false if the parsing fails because of an invalid character or if
* the result does not fit in a `int256`.
*
* NOTE: This function will revert if the absolute value of the result does not fit in a `uint256`.
*/
function tryParseInt(string memory input) internal pure returns (bool success, int256 value) {
return _tryParseIntUncheckedBounds(input, 0, bytes(input).length);
}
uint256 private constant ABS_MIN_INT256 = 2 ** 255;
/**
* @dev Variant of {parseInt-string-uint256-uint256} that returns false if the parsing fails because of an invalid
* character or if the result does not fit in a `int256`.
*
* NOTE: This function will revert if the absolute value of the result does not fit in a `uint256`.
*/
function tryParseInt(
string memory input,
uint256 begin,
uint256 end
) internal pure returns (bool success, int256 value) {
if (end > bytes(input).length || begin > end) return (false, 0);
return _tryParseIntUncheckedBounds(input, begin, end);
}
/**
* @dev Implementation of {tryParseInt-string-uint256-uint256} that does not check bounds. Caller should make sure that
* `begin <= end <= input.length`. Other inputs would result in undefined behavior.
*/
function _tryParseIntUncheckedBounds(
string memory input,
uint256 begin,
uint256 end
) private pure returns (bool success, int256 value) {
bytes memory buffer = bytes(input);
// Check presence of a negative sign.
bytes1 sign = begin == end ? bytes1(0) : bytes1(_unsafeReadBytesOffset(buffer, begin)); // don't do out-of-bound (possibly unsafe) read if sub-string is empty
bool positiveSign = sign == bytes1("+");
bool negativeSign = sign == bytes1("-");
uint256 offset = (positiveSign || negativeSign).toUint();
(bool absSuccess, uint256 absValue) = tryParseUint(input, begin + offset, end);
if (absSuccess && absValue < ABS_MIN_INT256) {
return (true, negativeSign ? -int256(absValue) : int256(absValue));
} else if (absSuccess && negativeSign && absValue == ABS_MIN_INT256) {
return (true, type(int256).min);
} else return (false, 0);
}
/**
* @dev Parse a hexadecimal string (with or without "0x" prefix), and returns the value as a `uint256`.
*
* Requirements:
* - The string must be formatted as `(0x)?[0-9a-fA-F]*`
* - The result must fit in an `uint256` type.
*/
function parseHexUint(string memory input) internal pure returns (uint256) {
return parseHexUint(input, 0, bytes(input).length);
}
/**
* @dev Variant of {parseHexUint-string} that parses a substring of `input` located between position `begin` (included) and
* `end` (excluded).
*
* Requirements:
* - The substring must be formatted as `(0x)?[0-9a-fA-F]*`
* - The result must fit in an `uint256` type.
*/
function parseHexUint(string memory input, uint256 begin, uint256 end) internal pure returns (uint256) {
(bool success, uint256 value) = tryParseHexUint(input, begin, end);
if (!success) revert StringsInvalidChar();
return value;
}
/**
* @dev Variant of {parseHexUint-string} that returns false if the parsing fails because of an invalid character.
*
* NOTE: This function will revert if the result does not fit in a `uint256`.
*/
function tryParseHexUint(string memory input) internal pure returns (bool success, uint256 value) {
return _tryParseHexUintUncheckedBounds(input, 0, bytes(input).length);
}
/**
* @dev Variant of {parseHexUint-string-uint256-uint256} that returns false if the parsing fails because of an
* invalid character.
*
* NOTE: This function will revert if the result does not fit in a `uint256`.
*/
function tryParseHexUint(
string memory input,
uint256 begin,
uint256 end
) internal pure returns (bool success, uint256 value) {
if (end > bytes(input).length || begin > end) return (false, 0);
return _tryParseHexUintUncheckedBounds(input, begin, end);
}
/**
* @dev Implementation of {tryParseHexUint-string-uint256-uint256} that does not check bounds. Caller should make sure that
* `begin <= end <= input.length`. Other inputs would result in undefined behavior.
*/
function _tryParseHexUintUncheckedBounds(
string memory input,
uint256 begin,
uint256 end
) private pure returns (bool success, uint256 value) {
bytes memory buffer = bytes(input);
// skip 0x prefix if present
bool hasPrefix = (end > begin + 1) && bytes2(_unsafeReadBytesOffset(buffer, begin)) == bytes2("0x"); // don't do out-of-bound (possibly unsafe) read if sub-string is empty
uint256 offset = hasPrefix.toUint() * 2;
uint256 result = 0;
for (uint256 i = begin + offset; i < end; ++i) {
uint8 chr = _tryParseChr(bytes1(_unsafeReadBytesOffset(buffer, i)));
if (chr > 15) return (false, 0);
result *= 16;
unchecked {
// Multiplying by 16 is equivalent to a shift of 4 bits (with additional overflow check).
// This guarantees that adding a value < 16 will not cause an overflow, hence the unchecked.
result += chr;
}
}
return (true, result);
}
/**
* @dev Parse a hexadecimal string (with or without "0x" prefix), and returns the value as an `address`.
*
* Requirements:
* - The string must be formatted as `(0x)?[0-9a-fA-F]{40}`
*/
function parseAddress(string memory input) internal pure returns (address) {
return parseAddress(input, 0, bytes(input).length);
}
/**
* @dev Variant of {parseAddress-string} that parses a substring of `input` located between position `begin` (included) and
* `end` (excluded).
*
* Requirements:
* - The substring must be formatted as `(0x)?[0-9a-fA-F]{40}`
*/
function parseAddress(string memory input, uint256 begin, uint256 end) internal pure returns (address) {
(bool success, address value) = tryParseAddress(input, begin, end);
if (!success) revert StringsInvalidAddressFormat();
return value;
}
/**
* @dev Variant of {parseAddress-string} that returns false if the parsing fails because the input is not a properly
* formatted address. See {parseAddress-string} requirements.
*/
function tryParseAddress(string memory input) internal pure returns (bool success, address value) {
return tryParseAddress(input, 0, bytes(input).length);
}
/**
* @dev Variant of {parseAddress-string-uint256-uint256} that returns false if the parsing fails because input is not a properly
* formatted address. See {parseAddress-string-uint256-uint256} requirements.
*/
function tryParseAddress(
string memory input,
uint256 begin,
uint256 end
) internal pure returns (bool success, address value) {
if (end > bytes(input).length || begin > end) return (false, address(0));
bool hasPrefix = (end > begin + 1) && bytes2(_unsafeReadBytesOffset(bytes(input), begin)) == bytes2("0x"); // don't do out-of-bound (possibly unsafe) read if sub-string is empty
uint256 expectedLength = 40 + hasPrefix.toUint() * 2;
// check that input is the correct length
if (end - begin == expectedLength) {
// length guarantees that this does not overflow, and value is at most type(uint160).max
(bool s, uint256 v) = _tryParseHexUintUncheckedBounds(input, begin, end);
return (s, address(uint160(v)));
} else {
return (false, address(0));
}
}
function _tryParseChr(bytes1 chr) private pure returns (uint8) {
uint8 value = uint8(chr);
// Try to parse `chr`:
// - Case 1: [0-9]
// - Case 2: [a-f]
// - Case 3: [A-F]
// - otherwise not supported
unchecked {
if (value > 47 && value < 58) value -= 48;
else if (value > 96 && value < 103) value -= 87;
else if (value > 64 && value < 71) value -= 55;
else return type(uint8).max;
}
return value;
}
/**
* @dev Escape special characters in JSON strings. This can be useful to prevent JSON injection in NFT metadata.
*
* WARNING: This function should only be used in double quoted JSON strings. Single quotes are not escaped.
*
* NOTE: This function escapes all unicode characters, and not just the ones in ranges defined in section 2.5 of
* RFC-4627 (U+0000 to U+001F, U+0022 and U+005C). ECMAScript's `JSON.parse` does recover escaped unicode
* characters that are not in this range, but other tooling may provide different results.
*/
function escapeJSON(string memory input) internal pure returns (string memory) {
bytes memory buffer = bytes(input);
bytes memory output = new bytes(2 * buffer.length); // worst case scenario
uint256 outputLength = 0;
for (uint256 i; i < buffer.length; ++i) {
bytes1 char = bytes1(_unsafeReadBytesOffset(buffer, i));
if (((SPECIAL_CHARS_LOOKUP & (1 << uint8(char))) != 0)) {
output[outputLength++] = "\\";
if (char == 0x08) output[outputLength++] = "b";
else if (char == 0x09) output[outputLength++] = "t";
else if (char == 0x0a) output[outputLength++] = "n";
else if (char == 0x0c) output[outputLength++] = "f";
else if (char == 0x0d) output[outputLength++] = "r";
else if (char == 0x5c) output[outputLength++] = "\\";
else if (char == 0x22) {
// solhint-disable-next-line quotes
output[outputLength++] = '"';
}
} else {
output[outputLength++] = char;
}
}
// write the actual length and deallocate unused memory
assembly ("memory-safe") {
mstore(output, outputLength)
mstore(0x40, add(output, shl(5, shr(5, add(outputLength, 63)))))
}
return string(output);
}
/**
* @dev Reads a bytes32 from a bytes array without bounds checking.
*
* NOTE: making this function internal would mean it could be used with memory unsafe offset, and marking the
* assembly block as such would prevent some optimizations.
*/
function _unsafeReadBytesOffset(bytes memory buffer, uint256 offset) private pure returns (bytes32 value) {
// This is not memory safe in the general case, but all calls to this private function are within bounds.
assembly ("memory-safe") {
value := mload(add(add(buffer, 0x20), offset))
}
}
}
"
},
"@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (token/ERC721/extensions/IERC721Metadata.sol)
pragma solidity >=0.6.2;
import {IERC721} from "../IERC721.sol";
/**
* @title ERC-721 Non-Fungible Token Standard, optional metadata extension
* @dev See https://eips.ethereum.org/EIPS/eip-721
*/
interface IERC721Metadata is IERC721 {
/**
* @dev Returns the token collection name.
*/
function name() external view returns (string memory);
/**
* @dev Returns the token collection symbol.
*/
function symbol() external view returns (string memory);
/**
* @dev Returns the Uniform Resource Identifier (URI) for `tokenId` token.
*/
function tokenURI(uint256 tokenId) external view returns (string memory);
}
"
},
"@openzeppelin/contracts/token/ERC721/ERC721.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (token/ERC721/ERC721.sol)
pragma solidity ^0.8.20;
import {IERC721} from "./IERC721.sol";
import {IERC721Metadata} from "./extensions/IERC721Metadata.sol";
import {ERC721Utils} from "./utils/ERC721Utils.sol";
import {Context} from "../../utils/Context.sol";
import {Strings} from "../../utils/Strings.sol";
import {IERC165, ERC165} from "../../utils/introspection/ERC165.sol";
import {IERC721Errors} from "../../interfaces/draft-IERC6093.sol";
/**
* @dev Implementation of https://eips.ethereum.org/EIPS/eip-721[ERC-721] Non-Fungible Token Standard, including
* the Metadata extension, but not including the Enumerable extension, which is available separately as
* {ERC721Enumerable}.
*/
abstract contract ERC721 is Context, ERC165, IERC721, IERC721Metadata, IERC721Errors {
using Strings for uint256;
// Token name
string private _name;
// Token symbol
string private _symbol;
mapping(uint256 tokenId => address) private _owners;
mapping(address owner => uint256) private _balances;
mapping(uint256 tokenId => address) private _tokenApprovals;
mapping(address owner => mapping(address operator => bool)) private _operatorApprovals;
/**
* @dev Initializes the contract by setting a `name` and a `symbol` to the token collection.
*/
constructor(string memory name_, string memory symbol_) {
_name = name_;
_symbol = symbol_;
}
/// @inheritdoc IERC165
function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) {
return
interfaceId == type(IERC721).interfaceId ||
interfaceId == type(IERC721Metadata).interfaceId ||
super.supportsInterface(interfaceId);
}
/// @inheritdoc IERC721
function balanceOf(address owner) public view virtual returns (uint256) {
if (owner == address(0)) {
revert ERC721InvalidOwner(address(0));
}
return _balances[owner];
}
/// @inheritdoc IERC721
function ownerOf(uint256 tokenId) public view virtual returns (address) {
return _requireOwned(tokenId);
}
/// @inheritdoc IERC721Metadata
function name() public view virtual returns (string memory) {
return _name;
}
/// @inheritdoc IERC721Metadata
function symbol() public view virtual returns (string memory) {
return _symbol;
}
/// @inheritdoc IERC721Metadata
function tokenURI(uint256 tokenId) public view virtual returns (string memory) {
_requireOwned(tokenId);
string memory baseURI = _baseURI();
return bytes(baseURI).length > 0 ? string.concat(baseURI, tokenId.toString()) : "";
}
/**
* @dev Base URI for computing {tokenURI}. If set, the resulting URI for each
* token will be the concatenation of the `baseURI` and the `tokenId`. Empty
* by default, can be overridden in child contracts.
*/
function _baseURI() internal view virtual returns (string memory) {
return "";
}
/// @inheritdoc IERC721
function approve(address to, uint256 tokenId) public virtual {
_approve(to, tokenId, _msgSender());
}
/// @inheritdoc IERC721
function getApproved(uint256 tokenId) public view virtual returns (address) {
_requireOwned(tokenId);
return _getApproved(tokenId);
}
/// @inheritdoc IERC721
function setApprovalForAll(address operator, bool approved) public virtual {
_setApprovalForAll(_msgSender(), operator, approved);
}
/// @inheritdoc IERC721
function isApprovedForAll(address owner, address operator) public view virtual returns (bool) {
return _operatorApprovals[owner][operator];
}
/// @inheritdoc IERC721
function transferFrom(address from, address to, uint256 tokenId) public virtual {
if (to == address(0)) {
revert ERC721InvalidReceiver(address(0));
}
// Setting an "auth" arguments enables the `_isAuthorized` check which verifies that the token exists
// (from != 0). Therefore, it is not needed to verify that the return value is not 0 here.
address previousOwner = _update(to, tokenId, _msgSender());
if (previousOwner != from) {
revert ERC721IncorrectOwner(from, tokenId, previousOwner);
}
}
/// @inheritdoc IERC721
function safeTransferFrom(address from, address to, uint256 tokenId) public {
safeTransferFrom(from, to, tokenId, "");
}
/// @inheritdoc IERC721
function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory data) public virtual {
transferFrom(from, to, tokenId);
ERC721Utils.checkOnERC721Received(_msgSender(), from, to, tokenId, data);
}
/**
* @dev Returns the owner of the `tokenId`. Does NOT revert if token doesn't exist
*
* IMPORTANT: Any overrides to this function that add ownership of tokens not tracked by the
* core ERC-721 logic MUST be matched with the use of {_increaseBalance} to keep balances
* consistent with ownership. The invariant to preserve is that for any address `a` the value returned by
* `balanceOf(a)` must be equal to the number of tokens such that `_ownerOf(tokenId)` is `a`.
*/
function _ownerOf(uint256 tokenId) internal view virtual returns (address) {
return _owners[tokenId];
}
/**
* @dev Returns the approved address for `tokenId`. Returns 0 if `tokenId` is not minted.
*/
function _getApproved(uint256 tokenId) internal view virtual returns (address) {
return _tokenApprovals[tokenId];
}
/**
* @dev Returns whether `spender` is allowed to manage `owner`'s tokens, or `tokenId` in
* particular (ignoring whether it is owned by `owner`).
*
* WARNING: This function assumes that `owner` is the actual owner of `tokenId` and does not verify this
* assumption.
*/
function _isAuthorized(address owner, address spender, uint256 tokenId) internal view virtual returns (bool) {
return
spender != address(0) &&
(owner == spender || isApprovedForAll(owner, spender) || _getApproved(tokenId) == spender);
}
/**
* @dev Checks if `spender` can operate on `tokenId`, assuming the provided `owner` is the actual owner.
* Reverts if:
* - `spender` does not have approval from `owner` for `tokenId`.
* - `spender` does not have approval to manage all of `owner`'s assets.
*
* WARNING: This function assumes that `owner` is the actual owner of `tokenId` and does not verify this
* assumption.
*/
function _checkAuthorized(address owner, address spender, uint256 tokenId) internal view virtual {
if (!_isAuthorized(owner, spender, tokenId)) {
if (owner == address(0)) {
revert ERC721NonexistentToken(tokenId);
} else {
revert ERC721InsufficientApproval(spender, tokenId);
}
}
}
/**
* @dev Unsafe write access to the balances, used by extensions that "mint" tokens using an {ownerOf} override.
*
* NOTE: the value is limited to type(uint128).max. This protect against _balance overflow. It is unrealistic that
* a uint256 would ever overflow from increments when these increments are bounded to uint128 values.
*
* WARNING: Increasing an account's balance using this function tends to be paired with an override of the
* {_ownerOf} function to resolve the ownership of the corresponding tokens so that balances and ownership
* remain consistent with one another.
*/
function _increaseBalance(address account, uint128 value) internal virtual {
unchecked {
_balances[account] += value;
}
}
/**
* @dev Transfers `tokenId` from its current owner to `to`, or alternatively mints (or burns) if the current owner
* (or `to`) is the zero address. Returns the owner of the `tokenId` before the update.
*
* The `auth` argument is optional. If the value passed is non 0, then this function will check that
* `auth` is either the owner of the token, or approved to operate on the token (by the owner).
*
* Emits a {Transfer} event.
*
* NOTE: If overriding this function in a way that tracks balances, see also {_increaseBalance}.
*/
function _update(address to, uint256 tokenId, address auth) internal virtual returns (address) {
address from = _ownerOf(tokenId);
// Perform (optional) operator check
if (auth != address(0)) {
_checkAuthorized(from, auth, tokenId);
}
// Execute the update
if (from != address(0)) {
// Clear approval. No need to re-authorize or emit the Approval event
_approve(address(0), tokenId, address(0), false);
unchecked {
_balances[from] -= 1;
}
}
if (to != address(0)) {
unchecked {
_balances[to] += 1;
}
}
_owners[tokenId] = to;
emit Transfer(from, to, tokenId);
return from;
}
/**
* @dev Mints `tokenId` and transfers it to `to`.
*
* WARNING: Usage of this method is discouraged, use {_safeMint} whenever possible
*
* Requirements:
*
* - `tokenId` must not exist.
* - `to` cannot be the zero address.
*
* Emits a {Transfer} event.
*/
function _mint(address to, uint256 tokenId) internal {
if (to == address(0)) {
revert ERC721InvalidReceiver(address(0));
}
address previousOwner = _update(to, tokenId, address(0));
if (previousOwner != address(0)) {
revert ERC721InvalidSender(address(0));
}
}
/**
* @dev Mints `tokenId`, transfers it to `to` and checks for `to` acceptance.
*
* Requirements:
*
* - `tokenId` must not exist.
* - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
*
* Emits a {Transfer} event.
*/
function _safeMint(address to, uint256 tokenId) internal {
_safeMint(to, tokenId, "");
}
/**
* @dev Same as {xref-ERC721-_safeMint-address-uint256-}[`_safeMint`], with an additional `data` parameter which is
* forwarded in {IERC721Receiver-onERC721Received} to contract recipients.
*/
function _safeMint(address to, uint256 tokenId, bytes memory data) internal virtual {
_mint(to, tokenId);
ERC721Utils.checkOnERC721Received(_msgSender(), address(0), to, tokenId, data);
}
/**
* @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.
*
* Requirements:
*
* - `tokenId` must exist.
*
* Emits a {Transfer} event.
*/
function _burn(uint256 tokenId) internal {
address previousOwner = _update(address(0), tokenId, address(0));
if (previousOwner == address(0)) {
revert ERC721NonexistentToken(tokenId);
}
}
/**
* @dev Transfers `tokenId` from `from` to `to`.
* As opposed to {transferFrom}, this imposes no restrictions on msg.sender.
*
* Requirements:
*
* - `to` cannot be the zero address.
* - `tokenId` token must be owned by `from`.
*
* Emits a {Transfer} event.
*/
function _transfer(address from, address to, uint256 tokenId) internal {
if (to == address(0)) {
revert ERC721InvalidReceiver(address(0));
}
address previousOwner = _update(to, tokenId, address(0));
if (previousOwner == address(0)) {
revert ERC721NonexistentToken(tokenId);
} else if (previousOwner != from) {
revert ERC721IncorrectOwner(from, tokenId, previousOwner);
}
}
/**
* @dev Safely transfers `tokenId` token from `from` to `to`, checking that contract recipients
* are aware of the ERC-721 standard to prevent tokens from being forever locked.
*
* `data` is additional data, it has no specified format and it is sent in call to `to`.
*
* This internal function is like {safeTransferFrom} in the sense that it invokes
* {IERC721Receiver-onERC721Received} on the receiver, and can be used to e.g.
* implement alternative mechanisms to perform token transfer, such as signature-based.
*
* Requirements:
*
* - `tokenId` token must exist and be owned by `from`.
* - `to` cannot be the zero address.
* - `from` cannot be the zero address.
* - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
*
* Emits a {Transfer} event.
*/
function _safeTransfer(address from, address to, uint256 tokenId) internal {
_safeTransfer(from, to, tokenId, "");
}
/**
* @dev Same as {xref-ERC721-_safeTransfer-address-address-uint256-}[`_safeTransfer`], with an additional `data` parameter which is
* forwarded in {IERC721Receiver-onERC721Received} to contract recipients.
*/
function _safeTransfer(address from, address to, uint256 tokenId, bytes memory data) internal virtual {
_transfer(from, to, tokenId);
ERC721Utils.checkOnERC721Received(_msgSender(), from, to, tokenId, data);
}
/**
* @dev Approve `to` to operate on `tokenId`
*
* The `auth` argument is optional. If the value passed is non 0, then this function will check that `auth` is
* either the owner of the token, or approved to operate on all tokens held by this owner.
*
* Emits an {Approval} event.
*
* Overrides to this logic should be done to the variant with an additional `bool emitEvent` argument.
*/
function _approve(address to, uint256 tokenId, address auth) internal {
_approve(to, tokenId, auth, true);
}
/**
* @dev Variant of `_approve` with an optional flag to enable or disable the {Approval} event. The event is not
* emitted in the context of transfers.
*/
function _approve(address to, uint256 tokenId, address auth, bool emitEvent) internal virtual {
// Avoid reading the owner unless necessary
if (emitEvent || auth != address(0)) {
address owner = _requireOwned(tokenId);
// We do not use _isAuthorized because single-token approvals should not be able to call approve
if (auth != address(0) && owner != auth && !isApprovedForAll(owner, auth)) {
revert ERC721InvalidApprover(auth);
}
if (emitEvent) {
emit Approval(owner, to, tokenId);
}
}
_tokenApprovals[tokenId] = to;
}
/**
* @dev Approve `operator` to operate on all of `owner` tokens
*
* Requirements:
* - operator can't be the address zero.
*
* Emits an {ApprovalForAll} event.
*/
function _setApprovalForAll(address owner, address operator, bool approved) internal virtual {
if (operator == address(0)) {
revert ERC721InvalidOperator(operator);
}
_operatorApprovals[owner][operator] = approved;
emit ApprovalForAll(owner, operator, approved);
}
/**
* @dev Reverts if the `tokenId` doesn't have a current owner (it hasn't been minted, or it has been burned).
* Returns the owner.
*
* Overrides to ownership logic should be done to {_ownerOf}.
*/
function _requireOwned(uint256 tokenId) internal view returns (address) {
address owner = _ownerOf(tokenId);
if (owner == address(0)) {
revert ERC721NonexistentToken(tokenId);
}
return owner;
}
}
"
},
"@openzeppelin/contracts/utils/Context.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)
pragma solidity ^0.8.20;
/**
* @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 _
Submitted on: 2025-10-31 18:05:31
Comments
Log in to comment.
No comments yet.