More Info
Private Name Tags
ContractCreator
TokenTracker
Latest 25 from a total of 2,669 transactions
| Transaction Hash |
|
Block
|
From
|
To
|
|||||
|---|---|---|---|---|---|---|---|---|---|
| Sell | 24650370 | 130 days ago | IN | 0 ETH | 0 | ||||
| Sell | 10567605 | 456 days ago | IN | 0 ETH | 0.0002306 | ||||
| Sell | 10567288 | 456 days ago | IN | 0 ETH | 0.0002305 | ||||
| Sell | 10517021 | 457 days ago | IN | 0 ETH | 0.00023068 | ||||
| Sell | 9633844 | 478 days ago | IN | 0 ETH | 0.00027355 | ||||
| Sell | 9629211 | 478 days ago | IN | 0 ETH | 0.00023054 | ||||
| Sell | 9213428 | 487 days ago | IN | 0 ETH | 0.00023046 | ||||
| Sell | 8906504 | 494 days ago | IN | 0 ETH | 0.0002304 | ||||
| Sell | 8251973 | 510 days ago | IN | 0 ETH | 0.00023015 | ||||
| Sell | 7999754 | 515 days ago | IN | 0 ETH | 0.00023432 | ||||
| Sell | 7290997 | 532 days ago | IN | 0 ETH | 0.00023034 | ||||
| Sell | 6911602 | 541 days ago | IN | 0 ETH | 0.00024221 | ||||
| Sell | 6911538 | 541 days ago | IN | 0 ETH | 0.00023074 | ||||
| Sell | 6911393 | 541 days ago | IN | 0 ETH | 0.00024223 | ||||
| Sell | 6911365 | 541 days ago | IN | 0 ETH | 0.00024225 | ||||
| Sell | 6911329 | 541 days ago | IN | 0 ETH | 0.00024226 | ||||
| Sell | 6911308 | 541 days ago | IN | 0 ETH | 0.00023082 | ||||
| Claim All Yield | 6499549 | 550 days ago | IN | 0 ETH | 0 | ||||
| Claim All Gas | 6499541 | 550 days ago | IN | 0 ETH | 0 | ||||
| Sell | 6470726 | 551 days ago | IN | 0 ETH | 0.00023025 | ||||
| Claim All Gas | 6456058 | 551 days ago | IN | 0 ETH | 0.00000007 | ||||
| Claim All Yield | 6456051 | 551 days ago | IN | 0 ETH | 0.0000001 | ||||
| Sell | 6384749 | 553 days ago | IN | 0 ETH | 0.00023016 | ||||
| Buy | 6329704 | 554 days ago | IN | 0.00406494 ETH | 0.00020628 | ||||
| Sell | 6325356 | 554 days ago | IN | 0 ETH | 0.00023015 |
Latest 25 internal transactions (View All)
Advanced mode:
| Parent Transaction Hash | Block | From | To | |||
|---|---|---|---|---|---|---|
| 24650370 | 130 days ago | 0.00340057 ETH | ||||
| 24650370 | 130 days ago | 0.00017992 ETH | ||||
| 24650370 | 130 days ago | 0.00001799 ETH | ||||
| 10567605 | 456 days ago | 0.00341285 ETH | ||||
| 10567605 | 456 days ago | 0.00018057 ETH | ||||
| 10567605 | 456 days ago | 0.00001805 ETH | ||||
| 10567288 | 456 days ago | 0.00342518 ETH | ||||
| 10567288 | 456 days ago | 0.00018122 ETH | ||||
| 10567288 | 456 days ago | 0.00001812 ETH | ||||
| 10517021 | 457 days ago | 0.00343755 ETH | ||||
| 10517021 | 457 days ago | 0.00018188 ETH | ||||
| 10517021 | 457 days ago | 0.00001818 ETH | ||||
| 9633844 | 478 days ago | 0.00691238 ETH | ||||
| 9633844 | 478 days ago | 0.00036573 ETH | ||||
| 9633844 | 478 days ago | 0.00003657 ETH | ||||
| 9629211 | 478 days ago | 0.00347491 ETH | ||||
| 9629211 | 478 days ago | 0.00018385 ETH | ||||
| 9629211 | 478 days ago | 0.00001838 ETH | ||||
| 9213428 | 487 days ago | 0.00348744 ETH | ||||
| 9213428 | 487 days ago | 0.00018452 ETH | ||||
| 9213428 | 487 days ago | 0.00001845 ETH | ||||
| 8906504 | 494 days ago | 0.00350002 ETH | ||||
| 8906504 | 494 days ago | 0.00018518 ETH | ||||
| 8906504 | 494 days ago | 0.00001851 ETH | ||||
| 8251973 | 510 days ago | 0.00351264 ETH |
Cross-Chain Transactions
Loading...
Loading
Minimal Proxy Contract for 0x2fcbf4504add3dec2361a820b798d37ec67cf402
Contract Name:
CollectionImplementation
Compiler Version
v0.8.20+commit.a1b79de6
Optimization Enabled:
Yes with 200 runs
Other Settings:
paris EvmVersion
Contract Source Code (Solidity Standard Json-Input format)
// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;
// OpenZeppelin
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol";
import "@openzeppelin/contracts/utils/Address.sol";
import "@openzeppelin/contracts/utils/Strings.sol";
// DN404
import "../DN404/DN404.sol";
// Own Interfaces
import "./interfaces/ICollectionImpl.sol";
import "./interfaces/ITokenUri.sol";
import "./interfaces/IRouter.sol";
// Blast
import "../Blast/BlastBaseUpgradeable.sol";
contract CollectionImplementation is
Initializable,
DN404,
OwnableUpgradeable,
ReentrancyGuardUpgradeable,
BlastBaseUpgradeable,
ICollectionImpl
{
using Strings for uint256;
/************************
* Events *
***********************/
event PaymentReceived(address from, uint256 amount);
event Buy(
address indexed buyer,
uint256 amount,
uint256 price,
uint256 protocolFee,
uint256 creatorFee
);
event Sell(
address indexed seller,
uint256 amount,
uint256 price,
uint256 protocolFee,
uint256 creatorFee
);
event DeflationarySell(
address indexed seller,
uint256 amount,
uint256 price,
uint256 protocolFee,
uint256 creatorFee
);
event CreatorFeePercentageChanged(uint256 newCreatorFeePercentage);
event CreatorFeeFrozen();
event BaseUriChanged(string newBaseUri);
event TokenUriContractChanged(address newTokenUriContract);
event MetadataFrozen();
event WhitelistDisabled();
event WhitelistModified(address[] addresses, bool isWhitelisted);
event WhitelistMintLimitChanged(uint256 newWhitelistMintLimit);
/***********************
* Constants *
***********************/
uint256 public constant MAX_NAME_LENGTH = 30;
uint256 public constant MAX_SYMBOL_LENGTH = 6;
// limited by dn404
uint256 public constant MAX_NFT_SUPPLY = 2 ** 32 - 2;
uint256 public constant MAX_UNITS = 100_000 ether;
/***********************
* Immutable State (set at init)*
***********************/
/* Bonding Curve */
BondingCurveSpecs public bondingCurveSpecs;
/* Shared Ownership */
address payable public creatorVault;
/* Roles */
address public router;
/* DN404 */
uint256 public units;
/***********************
* Mutable State *
***********************/
string private _name;
string private _symbol;
/* ERC721 */
string public baseUri;
address public tokenUriContract;
address public tokenUriFallbackContract;
// supply limit. MAX_NFT_SUPPLY * _uint() is the maximum value for maxSupply
uint256 public maxSupply;
/* Whitelist */
// if enabled, only whitelisted addresses can mint. Must be set at init
bool public isWhitelistEnabled;
mapping(address => bool) public whitelist;
uint256 public whitelistMintLimit;
mapping(address => uint256) public whitelistMinted;
/**
* @dev We track how much supply is on the bonding curve for a given collection.
* This is not always equal to the total supply of the collection,
* because some tokens may have been burned.
*/
uint256 public bondingCurveSupply;
bool public metadataFreeze;
bool public bondingCurveFreeze;
uint256 public creatorFeePercentage; // 1e18 = 100%
// deflationary bonding curve
bool public isDeflationary;
uint256 public deflationBurnCount;
/*********************************
* Constructur & Initializer *
********************************/
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers();
}
function initialize(
InitializeParams memory params
) public override initializer {
uint256 nameLength = bytes(params.collectionSpecs.name_).length;
require(
nameLength > 0 && nameLength <= MAX_NAME_LENGTH,
"Invalid name length"
);
uint256 symbolLength = bytes(params.collectionSpecs.symbol_).length;
require(
symbolLength > 0 && symbolLength <= MAX_SYMBOL_LENGTH,
"Invalid symbol length"
);
/* name & symbol */
_name = params.collectionSpecs.name_;
_symbol = params.collectionSpecs.symbol_;
/* DN404 */
require(
params.collectionSpecs.units_ > 0 &&
params.collectionSpecs.units_ <= MAX_UNITS,
"CollectionImpl: invalid units"
);
units = params.collectionSpecs.units_;
/* Other */
__Ownable_init(params.creator);
__BlastBase_init(
params.gasClaimer,
params.creator,
params.blastPoints,
params.pointsOperator
);
__ReentrancyGuard_init();
/* DN404 */
_initializeDN404(0, address(0), params.dn404Mirror);
/* erc721 */
baseUri = params.collectionSpecs.baseUri_;
tokenUriContract = params.collectionSpecs.tokenUriContract_;
tokenUriFallbackContract = params.tokenUriFallbackContract;
/* bonding curve */
bondingCurveSpecs = params.bondingCurveSpecs;
/* shared ownership */
creatorVault = payable(params.creatorVault);
/* roles */
router = params.router;
creatorFeePercentage = 0.05 * 1e18; // 5%
if (params.collectionSpecs.maxNftSupply_ > 0) {
require(
params.collectionSpecs.maxNftSupply_ <= MAX_NFT_SUPPLY,
"CollectionImpl: maxSupply too high"
);
maxSupply = params.collectionSpecs.maxNftSupply_ * _unit();
} else {
maxSupply = MAX_NFT_SUPPLY * _unit();
}
isWhitelistEnabled = params.collectionSpecs.useWhitelist_;
whitelistMintLimit = params.collectionSpecs.whitelistMintLimit_;
isDeflationary = params.collectionSpecs.isDeflationary_;
}
/*************************
* Mint & Burn Functions *
*************************/
function _mintMultipleTo(address to, uint256 nftAmount) internal {
uint256 amount = nftAmount * _unit();
require(
totalSupply() + deflationBurnCount * _unit() + amount <= maxSupply,
"CollectionImpl: max supply reached"
);
_mint(to, amount);
}
function _bondingCurveBurnMultiple(uint256[] memory tokenIds_) internal {
for (uint256 i = 0; i < tokenIds_.length; i++) {
uint256 tokenId = tokenIds_[i];
address owner = _ownerOf(tokenIds_[i]);
// to burn specific NFTs, we need to transfer them to this contract first (due to dn404)
_initiateTransferFromNFT(owner, address(this), tokenId, msg.sender);
}
_burn(address(this), tokenIds_.length * _unit());
}
/***********************
* Owner Functions *
***********************/
/**
* @dev Sets the creator fee that is charged on every buy and sell. The fee is scaled to 1e18 = 100%.
* Has to be smaller than 10%
*/
function setCreatorFee(uint256 creatorFeePercentage_) external onlyOwner {
require(
creatorFeePercentage_ <= 10 * 1e18,
"CollectionImpl: creator fee percentage must be <= 10%"
);
// if frozen, the creator fee can only be lowered
if (bondingCurveFreeze) {
require(
creatorFeePercentage_ <= creatorFeePercentage,
"CollectionImpl: creator fee can only be lowered"
);
}
creatorFeePercentage = creatorFeePercentage_;
emit CreatorFeePercentageChanged(creatorFeePercentage_);
}
function freezeCreatorFee() external onlyOwner {
bondingCurveFreeze = true;
emit CreatorFeeFrozen();
}
function setBaseUri(string memory baseUri_) external onlyOwner {
if (metadataFreeze) {
revert("CollectionImpl: metadata freeze");
}
baseUri = baseUri_;
emit BaseUriChanged(baseUri_);
}
function setTokenUriContract(address tokenUriContract_) external onlyOwner {
if (metadataFreeze) {
revert("CollectionImpl: metadata freeze");
}
tokenUriContract = tokenUriContract_;
emit TokenUriContractChanged(tokenUriContract_);
}
function freezeMetadata() external onlyOwner {
metadataFreeze = true;
emit MetadataFrozen();
}
function disableWhitelist() external onlyOwner {
isWhitelistEnabled = false;
emit WhitelistDisabled();
}
function modifyWhitelist(
address[] memory addresses_,
bool isWhitelisted_
) external onlyOwner {
require(isWhitelistEnabled, "CollectionImpl: whitelist is not enabled");
for (uint256 i = 0; i < addresses_.length; ) {
whitelist[addresses_[i]] = isWhitelisted_;
unchecked {
i++;
}
}
emit WhitelistModified(addresses_, isWhitelisted_);
}
function setWhitelistMintLimit(
uint256 whitelistMintLimit_
) external onlyOwner {
require(isWhitelistEnabled, "CollectionImpl: whitelist is not enabled");
whitelistMintLimit = whitelistMintLimit_;
emit WhitelistMintLimitChanged(whitelistMintLimit_);
}
/************************
* Buy and Sell *
***********************/
function buy(
uint256 nftAmount,
uint256 deadline
) external payable nonReentrant {
require(nftAmount > 0, "CollectionImpl: amount must be > 0");
if (isWhitelistEnabled) {
require(whitelist[msg.sender], "CollectionImpl: not whitelisted");
if (whitelistMintLimit > 0) {
require(
whitelistMinted[msg.sender] + nftAmount <=
whitelistMintLimit,
"CollectionImpl: whitelist mint limit reached"
);
whitelistMinted[msg.sender] += nftAmount;
}
}
require(block.timestamp <= deadline, "CollectionImpl: deadline passed");
// get the price and fees
uint256 price = getPrice(bondingCurveSupply, nftAmount);
ProtocolFeeSpecs memory protocolFeeSpecs = _getProtocolFeeSpecs();
uint256 protocolFee = (price * protocolFeeSpecs.protocolFeePercentage) /
1e18;
uint256 creatorFee = (price * creatorFeePercentage) / 1e18;
// check if the user sent enough eth incl fees
require(
msg.value >= price + protocolFee + creatorFee,
"CollectionImpl: not enough eth sent"
);
bondingCurveSupply += nftAmount;
// mint the nfts to the user
_mintMultipleTo(msg.sender, nftAmount);
// send out the fees
Address.sendValue(
payable(protocolFeeSpecs.protocolFeeCollector),
protocolFee
);
Address.sendValue(creatorVault, creatorFee);
// send the dust back to the sender
uint256 dust = msg.value - price - protocolFee - creatorFee;
if (dust > 0) {
Address.sendValue(payable(msg.sender), dust);
}
emit Buy(msg.sender, nftAmount, price, protocolFee, creatorFee);
}
function sell(
uint256[] memory tokenIds_,
uint256 minPrice_, // min total eth the user wants to receive
uint256 deadline_
) external nonReentrant {
uint256 amount = tokenIds_.length;
require(amount > 0, "CollectionImpl: no tokenIds");
require(amount <= 100, "CollectionImpl: max 100 tokens");
require(
block.timestamp <= deadline_,
"CollectionImpl: deadline passed"
);
// get the price and fees
uint256 price = getPrice(bondingCurveSupply - amount, amount);
ProtocolFeeSpecs memory protocolFeeSpecs = _getProtocolFeeSpecs();
uint256 protocolFee = (price * protocolFeeSpecs.protocolFeePercentage) /
1e18;
uint256 creatorFee = (price * creatorFeePercentage) / 1e18;
require(
price - protocolFee - creatorFee >= minPrice_,
"CollectionImpl: price too low"
);
bondingCurveSupply -= amount;
// burn the NFTs from the owner
_bondingCurveBurnMultiple(tokenIds_);
// send out the fees
Address.sendValue(
payable(protocolFeeSpecs.protocolFeeCollector),
protocolFee
);
Address.sendValue(creatorVault, creatorFee);
// send the price minus the fees back
Address.sendValue(
payable(msg.sender),
price - protocolFee - creatorFee
);
emit Sell(msg.sender, amount, price, protocolFee, creatorFee);
}
function deflationarySell(
uint256[] memory tokenIds_
) external nonReentrant {
require(isDeflationary, "CollectionImpl: not deflationary");
uint256 amount = tokenIds_.length;
require(amount > 0, "CollectionImpl: no tokenIds");
require(amount <= 100, "CollectionImpl: max 100 tokens");
// get the price and fees
uint256 price = getPrice(deflationBurnCount, amount);
ProtocolFeeSpecs memory protocolFeeSpecs = _getProtocolFeeSpecs();
uint256 protocolFee = (price * protocolFeeSpecs.protocolFeePercentage) /
1e18;
uint256 creatorFee = (price * creatorFeePercentage) / 1e18;
// burn the NFTs from the owner
_bondingCurveBurnMultiple(tokenIds_);
deflationBurnCount += tokenIds_.length;
// send out the fees
Address.sendValue(
payable(protocolFeeSpecs.protocolFeeCollector),
protocolFee
);
Address.sendValue(creatorVault, creatorFee);
// send the price minus the fees back
Address.sendValue(
payable(msg.sender),
price - protocolFee - creatorFee
);
emit DeflationarySell(
msg.sender,
amount,
price,
protocolFee,
creatorFee
);
}
/********************************
* Price Related calculations *
*******************************/
// returns the price for a given supply and purchase-amount
function getPrice(
uint256 supply,
uint256 amount
) public view returns (uint256) {
// integral(supply): supply * ((factor * supply ** exponent) / (exponent + 1) + c)
// price: integral(supply + amount) - integral(supply)
// This formula is derived from the above calculation.
uint256 ePlusOne = bondingCurveSpecs.exponent + 1;
// prettier-ignore
return ((supply + amount) ** ePlusOne - supply ** ePlusOne)
* bondingCurveSpecs.factor / ePlusOne
+ bondingCurveSpecs.c * amount;
}
function getBuyPriceExclusiveFees(
uint256 amount_
) public view returns (uint256) {
uint256 supply = bondingCurveSupply;
uint256 price = getPrice(supply, amount_);
return price;
}
function getSellPriceExclusiveFees(
uint256 amount_
) public view returns (uint256) {
uint256 supply = bondingCurveSupply;
uint256 price = getPrice(supply - amount_, amount_);
uint256 protocolFee = (price * getProtocolFeePercentage()) / 1e18;
uint256 creatorFee = (price * creatorFeePercentage) / 1e18;
return price - protocolFee - creatorFee;
}
function getBuyPriceInclFees(
uint256 amount_
) public view returns (uint256) {
uint256 supply = bondingCurveSupply;
uint256 price = getPrice(supply, amount_);
uint256 protocolFee = (price * getProtocolFeePercentage()) / 1e18;
uint256 creatorFee = (price * creatorFeePercentage) / 1e18;
return price + protocolFee + creatorFee;
}
function getSellPriceInclFees(
uint256 amount_
) public view returns (uint256) {
uint256 supply = bondingCurveSupply;
uint256 price = getPrice(supply - amount_, amount_);
return price;
}
function getProtocolFeePercentage() public view returns (uint256) {
return _getProtocolFeeSpecs().protocolFeePercentage;
}
/************************
* Internal
***********************/
function _getProtocolFeeSpecs()
internal
view
returns (ProtocolFeeSpecs memory)
{
return IRouter(router).protocolFeeSpecs();
}
/************************
* Receive Function *
***********************/
receive() external payable override {
emit PaymentReceived(msg.sender, msg.value);
}
/************************
* Other *
***********************/
function name() public view virtual override returns (string memory) {
return _name;
}
function symbol() public view virtual override returns (string memory) {
return _symbol;
}
function _tokenURI(
uint256 id
) internal view virtual override returns (string memory) {
if (tokenUriContract != address(0)) {
return ITokenUri(tokenUriContract).tokenURI(id, address(this));
} else if (bytes(baseUri).length > 0) {
return string.concat(baseUri, id.toString());
} else {
return
ITokenUri(tokenUriFallbackContract).tokenURI(id, address(this));
}
}
/// @dev Amount of token balance that is equal to one NFT.
function _unit() internal view virtual override returns (uint256) {
return units;
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)
pragma solidity ^0.8.20;
import {ContextUpgradeable} from "../utils/ContextUpgradeable.sol";
import {Initializable} from "../proxy/utils/Initializable.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 OwnableUpgradeable is Initializable, ContextUpgradeable {
/// @custom:storage-location erc7201:openzeppelin.storage.Ownable
struct OwnableStorage {
address _owner;
}
// keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.Ownable")) - 1)) & ~bytes32(uint256(0xff))
bytes32 private constant OwnableStorageLocation = 0x9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c199300;
function _getOwnableStorage() private pure returns (OwnableStorage storage $) {
assembly {
$.slot := OwnableStorageLocation
}
}
/**
* @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.
*/
function __Ownable_init(address initialOwner) internal onlyInitializing {
__Ownable_init_unchained(initialOwner);
}
function __Ownable_init_unchained(address initialOwner) internal onlyInitializing {
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) {
OwnableStorage storage $ = _getOwnableStorage();
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 {
OwnableStorage storage $ = _getOwnableStorage();
address oldOwner = $._owner;
$._owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (proxy/utils/Initializable.sol)
pragma solidity ^0.8.20;
/**
* @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed
* behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an
* external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer
* function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.
*
* The initialization functions use a version number. Once a version number is used, it is consumed and cannot be
* reused. This mechanism prevents re-execution of each "step" but allows the creation of new initialization steps in
* case an upgrade adds a module that needs to be initialized.
*
* For example:
*
* [.hljs-theme-light.nopadding]
* ```solidity
* contract MyToken is ERC20Upgradeable {
* function initialize() initializer public {
* __ERC20_init("MyToken", "MTK");
* }
* }
*
* contract MyTokenV2 is MyToken, ERC20PermitUpgradeable {
* function initializeV2() reinitializer(2) public {
* __ERC20Permit_init("MyToken");
* }
* }
* ```
*
* TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as
* possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}.
*
* CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure
* that all initializers are idempotent. This is not verified automatically as constructors are by Solidity.
*
* [CAUTION]
* ====
* Avoid leaving a contract uninitialized.
*
* An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation
* contract, which may impact the proxy. To prevent the implementation contract from being used, you should invoke
* the {_disableInitializers} function in the constructor to automatically lock it when it is deployed:
*
* [.hljs-theme-light.nopadding]
* ```
* /// @custom:oz-upgrades-unsafe-allow constructor
* constructor() {
* _disableInitializers();
* }
* ```
* ====
*/
abstract contract Initializable {
/**
* @dev Storage of the initializable contract.
*
* It's implemented on a custom ERC-7201 namespace to reduce the risk of storage collisions
* when using with upgradeable contracts.
*
* @custom:storage-location erc7201:openzeppelin.storage.Initializable
*/
struct InitializableStorage {
/**
* @dev Indicates that the contract has been initialized.
*/
uint64 _initialized;
/**
* @dev Indicates that the contract is in the process of being initialized.
*/
bool _initializing;
}
// keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.Initializable")) - 1)) & ~bytes32(uint256(0xff))
bytes32 private constant INITIALIZABLE_STORAGE = 0xf0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00;
/**
* @dev The contract is already initialized.
*/
error InvalidInitialization();
/**
* @dev The contract is not initializing.
*/
error NotInitializing();
/**
* @dev Triggered when the contract has been initialized or reinitialized.
*/
event Initialized(uint64 version);
/**
* @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope,
* `onlyInitializing` functions can be used to initialize parent contracts.
*
* Similar to `reinitializer(1)`, except that in the context of a constructor an `initializer` may be invoked any
* number of times. This behavior in the constructor can be useful during testing and is not expected to be used in
* production.
*
* Emits an {Initialized} event.
*/
modifier initializer() {
// solhint-disable-next-line var-name-mixedcase
InitializableStorage storage $ = _getInitializableStorage();
// Cache values to avoid duplicated sloads
bool isTopLevelCall = !$._initializing;
uint64 initialized = $._initialized;
// Allowed calls:
// - initialSetup: the contract is not in the initializing state and no previous version was
// initialized
// - construction: the contract is initialized at version 1 (no reininitialization) and the
// current contract is just being deployed
bool initialSetup = initialized == 0 && isTopLevelCall;
bool construction = initialized == 1 && address(this).code.length == 0;
if (!initialSetup && !construction) {
revert InvalidInitialization();
}
$._initialized = 1;
if (isTopLevelCall) {
$._initializing = true;
}
_;
if (isTopLevelCall) {
$._initializing = false;
emit Initialized(1);
}
}
/**
* @dev A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the
* contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be
* used to initialize parent contracts.
*
* A reinitializer may be used after the original initialization step. This is essential to configure modules that
* are added through upgrades and that require initialization.
*
* When `version` is 1, this modifier is similar to `initializer`, except that functions marked with `reinitializer`
* cannot be nested. If one is invoked in the context of another, execution will revert.
*
* Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in
* a contract, executing them in the right order is up to the developer or operator.
*
* WARNING: Setting the version to 2**64 - 1 will prevent any future reinitialization.
*
* Emits an {Initialized} event.
*/
modifier reinitializer(uint64 version) {
// solhint-disable-next-line var-name-mixedcase
InitializableStorage storage $ = _getInitializableStorage();
if ($._initializing || $._initialized >= version) {
revert InvalidInitialization();
}
$._initialized = version;
$._initializing = true;
_;
$._initializing = false;
emit Initialized(version);
}
/**
* @dev Modifier to protect an initialization function so that it can only be invoked by functions with the
* {initializer} and {reinitializer} modifiers, directly or indirectly.
*/
modifier onlyInitializing() {
_checkInitializing();
_;
}
/**
* @dev Reverts if the contract is not in an initializing state. See {onlyInitializing}.
*/
function _checkInitializing() internal view virtual {
if (!_isInitializing()) {
revert NotInitializing();
}
}
/**
* @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.
* Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized
* to any version. It is recommended to use this to lock implementation contracts that are designed to be called
* through proxies.
*
* Emits an {Initialized} event the first time it is successfully executed.
*/
function _disableInitializers() internal virtual {
// solhint-disable-next-line var-name-mixedcase
InitializableStorage storage $ = _getInitializableStorage();
if ($._initializing) {
revert InvalidInitialization();
}
if ($._initialized != type(uint64).max) {
$._initialized = type(uint64).max;
emit Initialized(type(uint64).max);
}
}
/**
* @dev Returns the highest version that has been initialized. See {reinitializer}.
*/
function _getInitializedVersion() internal view returns (uint64) {
return _getInitializableStorage()._initialized;
}
/**
* @dev Returns `true` if the contract is currently initializing. See {onlyInitializing}.
*/
function _isInitializing() internal view returns (bool) {
return _getInitializableStorage()._initializing;
}
/**
* @dev Returns a pointer to the storage namespace.
*/
// solhint-disable-next-line var-name-mixedcase
function _getInitializableStorage() private pure returns (InitializableStorage storage $) {
assembly {
$.slot := INITIALIZABLE_STORAGE
}
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)
pragma solidity ^0.8.20;
import {Initializable} from "../proxy/utils/Initializable.sol";
/**
* @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 ContextUpgradeable is Initializable {
function __Context_init() internal onlyInitializing {
}
function __Context_init_unchained() internal onlyInitializing {
}
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
function _contextSuffixLength() internal view virtual returns (uint256) {
return 0;
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/ReentrancyGuard.sol)
pragma solidity ^0.8.20;
import {Initializable} from "../proxy/utils/Initializable.sol";
/**
* @dev Contract module that helps prevent reentrant calls to a function.
*
* Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
* available, which can be applied to functions to make sure there are no nested
* (reentrant) calls to them.
*
* Note that because there is a single `nonReentrant` guard, functions marked as
* `nonReentrant` may not call one another. This can be worked around by making
* those functions `private`, and then adding `external` `nonReentrant` entry
* points to them.
*
* TIP: If you would like to learn more about reentrancy and alternative ways
* to protect against it, check out our blog post
* https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
*/
abstract contract ReentrancyGuardUpgradeable is Initializable {
// Booleans are more expensive than uint256 or any type that takes up a full
// word because each write operation emits an extra SLOAD to first read the
// slot's contents, replace the bits taken up by the boolean, and then write
// back. This is the compiler's defense against contract upgrades and
// pointer aliasing, and it cannot be disabled.
// The values being non-zero value makes deployment a bit more expensive,
// but in exchange the refund on every call to nonReentrant will be lower in
// amount. Since refunds are capped to a percentage of the total
// transaction's gas, it is best to keep them low in cases like this one, to
// increase the likelihood of the full refund coming into effect.
uint256 private constant NOT_ENTERED = 1;
uint256 private constant ENTERED = 2;
/// @custom:storage-location erc7201:openzeppelin.storage.ReentrancyGuard
struct ReentrancyGuardStorage {
uint256 _status;
}
// keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.ReentrancyGuard")) - 1)) & ~bytes32(uint256(0xff))
bytes32 private constant ReentrancyGuardStorageLocation = 0x9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f00;
function _getReentrancyGuardStorage() private pure returns (ReentrancyGuardStorage storage $) {
assembly {
$.slot := ReentrancyGuardStorageLocation
}
}
/**
* @dev Unauthorized reentrant call.
*/
error ReentrancyGuardReentrantCall();
function __ReentrancyGuard_init() internal onlyInitializing {
__ReentrancyGuard_init_unchained();
}
function __ReentrancyGuard_init_unchained() internal onlyInitializing {
ReentrancyGuardStorage storage $ = _getReentrancyGuardStorage();
$._status = NOT_ENTERED;
}
/**
* @dev Prevents a contract from calling itself, directly or indirectly.
* Calling a `nonReentrant` function from another `nonReentrant`
* function is not supported. It is possible to prevent this from happening
* by making the `nonReentrant` function external, and making it call a
* `private` function that does the actual work.
*/
modifier nonReentrant() {
_nonReentrantBefore();
_;
_nonReentrantAfter();
}
function _nonReentrantBefore() private {
ReentrancyGuardStorage storage $ = _getReentrancyGuardStorage();
// On the first call to nonReentrant, _status will be NOT_ENTERED
if ($._status == ENTERED) {
revert ReentrancyGuardReentrantCall();
}
// Any calls to nonReentrant after this point will fail
$._status = ENTERED;
}
function _nonReentrantAfter() private {
ReentrancyGuardStorage storage $ = _getReentrancyGuardStorage();
// By storing the original value once again, a refund is triggered (see
// https://eips.ethereum.org/EIPS/eip-2200)
$._status = NOT_ENTERED;
}
/**
* @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a
* `nonReentrant` function in the call stack.
*/
function _reentrancyGuardEntered() internal view returns (bool) {
ReentrancyGuardStorage storage $ = _getReentrancyGuardStorage();
return $._status == ENTERED;
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/Address.sol)
pragma solidity ^0.8.20;
/**
* @dev Collection of functions related to the address type
*/
library Address {
/**
* @dev The ETH balance of the account is not enough to perform the operation.
*/
error AddressInsufficientBalance(address account);
/**
* @dev There's no code at `target` (it is not a contract).
*/
error AddressEmptyCode(address target);
/**
* @dev A call to an address target failed. The target may have reverted.
*/
error FailedInnerCall();
/**
* @dev Replacement for Solidity's `transfer`: sends `amount` wei to
* `recipient`, forwarding all available gas and reverting on errors.
*
* https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
* of certain opcodes, possibly making contracts go over the 2300 gas limit
* imposed by `transfer`, making them unable to receive funds via
* `transfer`. {sendValue} removes this limitation.
*
* https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].
*
* IMPORTANT: because control is transferred to `recipient`, care must be
* taken to not create reentrancy vulnerabilities. Consider using
* {ReentrancyGuard} or the
* https://solidity.readthedocs.io/en/v0.8.20/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
*/
function sendValue(address payable recipient, uint256 amount) internal {
if (address(this).balance < amount) {
revert AddressInsufficientBalance(address(this));
}
(bool success, ) = recipient.call{value: amount}("");
if (!success) {
revert FailedInnerCall();
}
}
/**
* @dev Performs a Solidity function call using a low level `call`. A
* plain `call` is an unsafe replacement for a function call: use this
* function instead.
*
* If `target` reverts with a revert reason or custom error, it is bubbled
* up by this function (like regular Solidity function calls). However, if
* the call reverted with no returned reason, this function reverts with a
* {FailedInnerCall} error.
*
* Returns the raw returned data. To convert to the expected return value,
* use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
*
* Requirements:
*
* - `target` must be a contract.
* - calling `target` with `data` must not revert.
*/
function functionCall(address target, bytes memory data) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but also transferring `value` wei to `target`.
*
* Requirements:
*
* - the calling contract must have an ETH balance of at least `value`.
* - the called Solidity function must be `payable`.
*/
function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
if (address(this).balance < value) {
revert AddressInsufficientBalance(address(this));
}
(bool success, bytes memory returndata) = target.call{value: value}(data);
return verifyCallResultFromTarget(target, success, returndata);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a static call.
*/
function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
(bool success, bytes memory returndata) = target.staticcall(data);
return verifyCallResultFromTarget(target, success, returndata);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a delegate call.
*/
function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
(bool success, bytes memory returndata) = target.delegatecall(data);
return verifyCallResultFromTarget(target, success, returndata);
}
/**
* @dev Tool to verify that a low level call to smart-contract was successful, and reverts if the target
* was not a contract or bubbling up the revert reason (falling back to {FailedInnerCall}) in case of an
* unsuccessful call.
*/
function verifyCallResultFromTarget(
address target,
bool success,
bytes memory returndata
) internal view returns (bytes memory) {
if (!success) {
_revert(returndata);
} else {
// only check if target is a contract if the call was successful and the return data is empty
// otherwise we already know that it was a contract
if (returndata.length == 0 && target.code.length == 0) {
revert AddressEmptyCode(target);
}
return returndata;
}
}
/**
* @dev Tool to verify that a low level call was successful, and reverts if it wasn't, either by bubbling the
* revert reason or with a default {FailedInnerCall} error.
*/
function verifyCallResult(bool success, bytes memory returndata) internal pure returns (bytes memory) {
if (!success) {
_revert(returndata);
} else {
return returndata;
}
}
/**
* @dev Reverts with returndata if present. Otherwise reverts with {FailedInnerCall}.
*/
function _revert(bytes memory returndata) private pure {
// Look for revert reason and bubble it up if present
if (returndata.length > 0) {
// The easiest way to bubble the revert reason is using memory via assembly
/// @solidity memory-safe-assembly
assembly {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert FailedInnerCall();
}
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/math/Math.sol)
pragma solidity ^0.8.20;
/**
* @dev Standard math utilities missing in the Solidity language.
*/
library Math {
/**
* @dev Muldiv operation overflow.
*/
error MathOverflowedMulDiv();
enum Rounding {
Floor, // Toward negative infinity
Ceil, // Toward positive infinity
Trunc, // Toward zero
Expand // Away from zero
}
/**
* @dev Returns the addition of two unsigned integers, with an overflow flag.
*/
function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) {
unchecked {
uint256 c = a + b;
if (c < a) return (false, 0);
return (true, c);
}
}
/**
* @dev Returns the subtraction of two unsigned integers, with an overflow flag.
*/
function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) {
unchecked {
if (b > a) return (false, 0);
return (true, a - b);
}
}
/**
* @dev Returns the multiplication of two unsigned integers, with an overflow flag.
*/
function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) {
unchecked {
// Gas optimization: this is cheaper than requiring 'a' not being zero, but the
// benefit is lost if 'b' is also tested.
// See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
if (a == 0) return (true, 0);
uint256 c = a * b;
if (c / a != b) return (false, 0);
return (true, c);
}
}
/**
* @dev Returns the division of two unsigned integers, with a division by zero flag.
*/
function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) {
unchecked {
if (b == 0) return (false, 0);
return (true, a / b);
}
}
/**
* @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag.
*/
function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) {
unchecked {
if (b == 0) return (false, 0);
return (true, a % b);
}
}
/**
* @dev Returns the largest of two numbers.
*/
function max(uint256 a, uint256 b) internal pure returns (uint256) {
return a > b ? a : b;
}
/**
* @dev Returns the smallest of two numbers.
*/
function min(uint256 a, uint256 b) internal pure returns (uint256) {
return a < b ? a : b;
}
/**
* @dev Returns the average of two numbers. The result is rounded towards
* zero.
*/
function average(uint256 a, uint256 b) internal pure returns (uint256) {
// (a + b) / 2 can overflow.
return (a & b) + (a ^ b) / 2;
}
/**
* @dev Returns the ceiling of the division of two numbers.
*
* This differs from standard division with `/` in that it rounds towards infinity instead
* of rounding towards zero.
*/
function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
if (b == 0) {
// Guarantee the same behavior as in a regular Solidity division.
return a / b;
}
// (a + b - 1) / b can overflow on addition, so we distribute.
return a == 0 ? 0 : (a - 1) / b + 1;
}
/**
* @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or
* denominator == 0.
* @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv) with further edits by
* Uniswap Labs also under MIT license.
*/
function mulDiv(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) {
unchecked {
// 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use
// use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256
// variables such that product = prod1 * 2^256 + prod0.
uint256 prod0 = x * y; // Least significant 256 bits of the product
uint256 prod1; // Most significant 256 bits of the product
assembly {
let mm := mulmod(x, y, not(0))
prod1 := sub(sub(mm, prod0), lt(mm, prod0))
}
// Handle non-overflow cases, 256 by 256 division.
if (prod1 == 0) {
// Solidity will revert if denominator == 0, unlike the div opcode on its own.
// The surrounding unchecked block does not change this fact.
// See https://docs.soliditylang.org/en/latest/control-structures.html#checked-or-unchecked-arithmetic.
return prod0 / denominator;
}
// Make sure the result is less than 2^256. Also prevents denominator == 0.
if (denominator <= prod1) {
revert MathOverflowedMulDiv();
}
///////////////////////////////////////////////
// 512 by 256 division.
///////////////////////////////////////////////
// Make division exact by subtracting the remainder from [prod1 prod0].
uint256 remainder;
assembly {
// Compute remainder using mulmod.
remainder := mulmod(x, y, denominator)
// Subtract 256 bit number from 512 bit number.
prod1 := sub(prod1, gt(remainder, prod0))
prod0 := sub(prod0, remainder)
}
// Factor powers of two out of denominator and compute largest power of two divisor of denominator.
// Always >= 1. See https://cs.stackexchange.com/q/138556/92363.
uint256 twos = denominator & (0 - denominator);
assembly {
// Divide denominator by twos.
denominator := div(denominator, twos)
// Divide [prod1 prod0] by twos.
prod0 := div(prod0, twos)
// Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one.
twos := add(div(sub(0, twos), twos), 1)
}
// Shift in bits from prod1 into prod0.
prod0 |= prod1 * twos;
// Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such
// that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for
// four bits. That is, denominator * inv = 1 mod 2^4.
uint256 inverse = (3 * denominator) ^ 2;
// Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also
// works in modular arithmetic, doubling the correct bits in each step.
inverse *= 2 - denominator * inverse; // inverse mod 2^8
inverse *= 2 - denominator * inverse; // inverse mod 2^16
inverse *= 2 - denominator * inverse; // inverse mod 2^32
inverse *= 2 - denominator * inverse; // inverse mod 2^64
inverse *= 2 - denominator * inverse; // inverse mod 2^128
inverse *= 2 - denominator * inverse; // inverse mod 2^256
// Because the division is now exact we can divide by multiplying with the modular inverse of denominator.
// This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is
// less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1
// is no longer required.
result = prod0 * inverse;
return result;
}
}
/**
* @notice Calculates x * y / denominator with full precision, following the selected rounding direction.
*/
function mulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internal pure returns (uint256) {
uint256 result = mulDiv(x, y, denominator);
if (unsignedRoundsUp(rounding) && mulmod(x, y, denominator) > 0) {
result += 1;
}
return result;
}
/**
* @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded
* towards zero.
*
* Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11).
*/
function sqrt(uint256 a) internal pure returns (uint256) {
if (a == 0) {
return 0;
}
// For our first guess, we get the biggest power of 2 which is smaller than the square root of the target.
//
// We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have
// `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`.
//
// This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)`
// → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))`
// → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)`
//
// Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit.
uint256 result = 1 << (log2(a) >> 1);
// At this point `result` is an estimation with one bit of precision. We know the true value is a uint128,
// since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at
// every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision
// into the expected uint128 result.
unchecked {
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
return min(result, a / result);
}
}
/**
* @notice Calculates sqrt(a), following the selected rounding direction.
*/
function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = sqrt(a);
return result + (unsignedRoundsUp(rounding) && result * result < a ? 1 : 0);
}
}
/**
* @dev Return the log in base 2 of a positive value rounded towards zero.
* Returns 0 if given 0.
*/
function log2(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >> 128 > 0) {
value >>= 128;
result += 128;
}
if (value >> 64 > 0) {
value >>= 64;
result += 64;
}
if (value >> 32 > 0) {
value >>= 32;
result += 32;
}
if (value >> 16 > 0) {
value >>= 16;
result += 16;
}
if (value >> 8 > 0) {
value >>= 8;
result += 8;
}
if (value >> 4 > 0) {
value >>= 4;
result += 4;
}
if (value >> 2 > 0) {
value >>= 2;
result += 2;
}
if (value >> 1 > 0) {
result += 1;
}
}
return result;
}
/**
* @dev Return the log in base 2, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/
function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log2(value);
return result + (unsignedRoundsUp(rounding) && 1 << result < value ? 1 : 0);
}
}
/**
* @dev Return the log in base 10 of a positive value rounded towards zero.
* Returns 0 if given 0.
*/
function log10(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >= 10 ** 64) {
value /= 10 ** 64;
result += 64;
}
if (value >= 10 ** 32) {
value /= 10 ** 32;
result += 32;
}
if (value >= 10 ** 16) {
value /= 10 ** 16;
result += 16;
}
if (value >= 10 ** 8) {
value /= 10 ** 8;
result += 8;
}
if (value >= 10 ** 4) {
value /= 10 ** 4;
result += 4;
}
if (value >= 10 ** 2) {
value /= 10 ** 2;
result += 2;
}
if (value >= 10 ** 1) {
result += 1;
}
}
return result;
}
/**
* @dev Return the log in base 10, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/
function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log10(value);
return result + (unsignedRoundsUp(rounding) && 10 ** result < value ? 1 : 0);
}
}
/**
* @dev Return the log in base 256 of a positive value rounded towards zero.
* Returns 0 if given 0.
*
* Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.
*/
function log256(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >> 128 > 0) {
value >>= 128;
result += 16;
}
if (value >> 64 > 0) {
value >>= 64;
result += 8;
}
if (value >> 32 > 0) {
value >>= 32;
result += 4;
}
if (value >> 16 > 0) {
value >>= 16;
result += 2;
}
if (value >> 8 > 0) {
result += 1;
}
}
return result;
}
/**
* @dev Return the log in base 256, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/
function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log256(value);
return result + (unsignedRoundsUp(rounding) && 1 << (result << 3) < value ? 1 : 0);
}
}
/**
* @dev Returns whether a provided rounding mode is considered rounding up for unsigned integers.
*/
function unsignedRoundsUp(Rounding rounding) internal pure returns (bool) {
return uint8(rounding) % 2 == 1;
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/math/SignedMath.sol)
pragma solidity ^0.8.20;
/**
* @dev Standard signed math utilities missing in the Solidity language.
*/
library SignedMath {
/**
* @dev Returns the largest of two signed numbers.
*/
function max(int256 a, int256 b) internal pure returns (int256) {
return a > b ? a : b;
}
/**
* @dev Returns the smallest of two signed numbers.
*/
function min(int256 a, int256 b) internal pure returns (int256) {
return a < b ? a : b;
}
/**
* @dev Returns the average of two signed numbers without overflow.
* The result is rounded towards zero.
*/
function average(int256 a, int256 b) internal pure returns (int256) {
// Formula from the book "Hacker's Delight"
int256 x = (a & b) + ((a ^ b) >> 1);
return x + (int256(uint256(x) >> 255) & (a ^ b));
}
/**
* @dev Returns the absolute unsigned value of a signed value.
*/
function abs(int256 n) internal pure returns (uint256) {
unchecked {
// must be unchecked in order to support `n = type(int256).min`
return uint256(n >= 0 ? n : -n);
}
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/Strings.sol)
pragma solidity ^0.8.20;
import {Math} from "./math/Math.sol";
import {SignedMath} from "./math/SignedMath.sol";
/**
* @dev String operations.
*/
library Strings {
bytes16 private constant HEX_DIGITS = "0123456789abcdef";
uint8 private constant ADDRESS_LENGTH = 20;
/**
* @dev The `value` string doesn't fit in the specified `length`.
*/
error StringsInsufficientHexLength(uint256 value, uint256 length);
/**
* @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;
/// @solidity memory-safe-assembly
assembly {
ptr := add(buffer, add(32, length))
}
while (true) {
ptr--;
/// @solidity memory-safe-assembly
assembly {
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 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));
}
}// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import {IBlast} from "./IBlast.sol";
import {IBlastPoints} from "./IBlastPoints.sol";
/**
* @title BlastBaseUpgradeable
* @notice Blast documentation:
* Gas fees:
* https://docs.blast.io/building/guides/gas-fees
* Yield:
* https://docs.blast.io/building/guides/eth-yield
* Points:
* https://docs.blast.io/airdrop/api
*/
contract BlastBaseUpgradeable is Initializable {
IBlast public constant BLAST =
IBlast(0x4300000000000000000000000000000000000002);
address public gasClaimer;
address public yieldClaimer;
IBlastPoints public blastPoints;
address public pointsOperator;
modifier onlyGasClaimer() {
require(
msg.sender == gasClaimer,
"BlastBaseUpgradeable: caller is not the gasClaimer"
);
_;
}
modifier onlyYieldClaimer() {
require(
msg.sender == yieldClaimer,
"BlastBaseUpgradeable: caller is not the yieldClaimer"
);
_;
}
modifier onlyPointsOperator() {
require(
msg.sender == pointsOperator,
"BlastBaseUpgradeable: caller is not the pointsOperator"
);
_;
}
function __BlastBase_init(
address _gasClaimer,
address _yieldClaimer,
IBlastPoints _blastPoints,
address _pointsOperator
) internal onlyInitializing {
gasClaimer = _gasClaimer;
yieldClaimer = _yieldClaimer;
BLAST.configureClaimableGas();
BLAST.configureClaimableYield();
blastPoints = _blastPoints;
pointsOperator = _pointsOperator;
blastPoints.configurePointsOperator(_pointsOperator);
}
/**********************************
* Gas *
*********************************/
function claimAllGas(
address recipientOfGas
) external onlyGasClaimer returns (uint256) {
return BLAST.claimAllGas(address(this), recipientOfGas);
}
function claimGasAtMinClaimRate(
address recipientOfGas,
uint256 minClaimRateBips
) external onlyGasClaimer returns (uint256) {
return
BLAST.claimGasAtMinClaimRate(
address(this),
recipientOfGas,
minClaimRateBips
);
}
function claimMaxGas(
address recipientOfGas
) external onlyGasClaimer returns (uint256) {
return BLAST.claimMaxGas(address(this), recipientOfGas);
}
function claimGas(
address recipientOfGas,
uint256 gasToClaim,
uint256 gasSecondsToConsume
) external onlyGasClaimer returns (uint256) {
return
BLAST.claimGas(
address(this),
recipientOfGas,
gasToClaim,
gasSecondsToConsume
);
}
function setGasClaimer(address gasClaimer_) external onlyGasClaimer {
gasClaimer = gasClaimer_;
}
/**********************************
* Yield *
*********************************/
function claimAllYield(
address recipientOfYield
) external onlyYieldClaimer returns (uint256) {
return BLAST.claimAllYield(address(this), recipientOfYield);
}
function claimYield(
address recipientOfYield,
uint256 amount
) external onlyYieldClaimer returns (uint256) {
return BLAST.claimYield(address(this), recipientOfYield, amount);
}
function setYieldClaimer(address yieldClaimer_) external onlyYieldClaimer {
yieldClaimer = yieldClaimer_;
}
/**********************************
* Points *
*********************************/
/// @dev The current points operator must set the new points operator on the blastPoints contract directly with the
/// function onfigurePointsOperatorOnBehalf(address contractAddress, address operatorAddress).
function setPointsOperator(
address pointsOperator_
) external onlyPointsOperator {
pointsOperator = pointsOperator_;
}
function setBlastPoints(
IBlastPoints blastPoints_
) external onlyPointsOperator {
blastPoints = blastPoints_;
blastPoints.configurePointsOperator(pointsOperator);
}
}// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;
enum YieldMode {
AUTOMATIC,
VOID,
CLAIMABLE
}
enum GasMode {
VOID,
CLAIMABLE
}
interface IBlast {
// configure
function configureContract(
address contractAddress,
YieldMode _yield,
GasMode gasMode,
address governor
) external;
function configure(
YieldMode _yield,
GasMode gasMode,
address governor
) external;
// base configuration options
function configureClaimableYield() external;
function configureClaimableYieldOnBehalf(address contractAddress) external;
function configureAutomaticYield() external;
function configureAutomaticYieldOnBehalf(address contractAddress) external;
function configureVoidYield() external;
function configureVoidYieldOnBehalf(address contractAddress) external;
function configureClaimableGas() external;
function configureClaimableGasOnBehalf(address contractAddress) external;
function configureVoidGas() external;
function configureVoidGasOnBehalf(address contractAddress) external;
function configureGovernor(address _governor) external;
function configureGovernorOnBehalf(
address _newGovernor,
address contractAddress
) external;
// claim yield
function claimYield(
address contractAddress,
address recipientOfYield,
uint256 amount
) external returns (uint256);
function claimAllYield(
address contractAddress,
address recipientOfYield
) external returns (uint256);
// claim gas
function claimAllGas(
address contractAddress,
address recipientOfGas
) external returns (uint256);
function claimGasAtMinClaimRate(
address contractAddress,
address recipientOfGas,
uint256 minClaimRateBips
) external returns (uint256);
function claimMaxGas(
address contractAddress,
address recipientOfGas
) external returns (uint256);
function claimGas(
address contractAddress,
address recipientOfGas,
uint256 gasToClaim,
uint256 gasSecondsToConsume
) external returns (uint256);
// read functions
function readClaimableYield(
address contractAddress
) external view returns (uint256);
function readYieldConfiguration(
address contractAddress
) external view returns (uint8);
function readGasParams(
address contractAddress
)
external
view
returns (
uint256 etherSeconds,
uint256 etherBalance,
uint256 lastUpdated,
GasMode
);
function configurePointsOperator(address operator) external;
}// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;
interface IBlastPoints {
function configurePointsOperator(address operator) external;
function configurePointsOperatorOnBehalf(
address contractAddress,
address operator
) external;
}// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;
import "../../Blast/IBlastPoints.sol";
import "./Structs.sol";
interface ICollectionImpl {
/***********************
* Structs *
***********************/
struct InitializeParams {
/* erc721 */
CollectionSpecs collectionSpecs;
address tokenUriFallbackContract;
/* bonding curve */
BondingCurveSpecs bondingCurveSpecs;
/* shared ownership */
address creatorVault;
/* roles */
address creator;
address router;
/* blast */
address gasClaimer;
address yieldClaimer;
address pointsOperator;
IBlastPoints blastPoints;
/* dn404 */
address dn404Mirror;
}
function initialize(InitializeParams memory params) external;
}// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;
// Own Interfaces
import "../interfaces/Structs.sol";
interface IRouter {
function protocolFeeSpecs() external view returns (ProtocolFeeSpecs memory);
}// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;
interface ITokenUri {
function tokenURI(
uint256 tokenId,
address collection
) external view returns (string memory);
}// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;
struct ProtocolFeeSpecs {
uint256 protocolFeePercentage; // 1e18 = 100%
address protocolFeeCollector;
}
struct BondingCurveSpecs {
uint256 factor; // with precision of 1e18
uint256 exponent;
uint256 c; // constant; with precision of 1e18
}
struct CollectionSpecs {
string name_;
string symbol_;
uint256 units_; // the amount of tokens that represent a single NFT
string baseUri_; // leave blank if tokenURI is provided by a contract. If tokenUriProvider is provided, it will override this tokenUri
address tokenUriContract_; // if provided, overrides the tokenUri
uint256 maxNftSupply_; // the max supply in NFTs. 0 defaults to the max (CollectionImplementation::MAX_NFT_SUPPLY)
bool useWhitelist_; // if enabled, only whitelisted addresses can mint. Must be set at init
uint256 whitelistMintLimit_; // 0 for unlimited minting.
bool isDeflationary_; // allows to burn from the bottom of the curve
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
/// @title DN404
/// @notice DN404 is a hybrid ERC20 and ERC721 implementation that mints
/// and burns NFTs based on an account's ERC20 token balance.
///
/// @author vectorized.eth (@optimizoor)
/// @author Quit (@0xQuit)
/// @author Michael Amadi (@AmadiMichaels)
/// @author cygaar (@0xCygaar)
/// @author Thomas (@0xjustadev)
/// @author Harrison (@PopPunkOnChain)
///
/// @dev Note:
/// - The ERC721 data is stored in this base DN404 contract, however a
/// DN404Mirror contract ***MUST*** be deployed and linked during
/// initialization.
abstract contract DN404 {
/*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/
/* EVENTS */
/*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/
/// @dev Emitted when `amount` tokens is transferred from `from` to `to`.
event Transfer(address indexed from, address indexed to, uint256 amount);
/// @dev Emitted when `amount` tokens is approved by `owner` to be used by `spender`.
event Approval(address indexed owner, address indexed spender, uint256 amount);
/// @dev Emitted when `owner` sets their skipNFT flag to `status`.
event SkipNFTSet(address indexed owner, bool status);
/// @dev `keccak256(bytes("Transfer(address,address,uint256)"))`.
uint256 private constant _TRANSFER_EVENT_SIGNATURE =
0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef;
/// @dev `keccak256(bytes("Approval(address,address,uint256)"))`.
uint256 private constant _APPROVAL_EVENT_SIGNATURE =
0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925;
/// @dev `keccak256(bytes("SkipNFTSet(address,bool)"))`.
uint256 private constant _SKIP_NFT_SET_EVENT_SIGNATURE =
0xb5a1de456fff688115a4f75380060c23c8532d14ff85f687cc871456d6420393;
/*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/
/* CUSTOM ERRORS */
/*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/
/// @dev Thrown when attempting to double-initialize the contract.
error DNAlreadyInitialized();
/// @dev The function can only be called after the contract has been initialized.
error DNNotInitialized();
/// @dev Thrown when attempting to transfer or burn more tokens than sender's balance.
error InsufficientBalance();
/// @dev Thrown when a spender attempts to transfer tokens with an insufficient allowance.
error InsufficientAllowance();
/// @dev Thrown when minting an amount of tokens that would overflow the max tokens.
error TotalSupplyOverflow();
/// @dev The unit must be greater than zero and less than `2**96`.
error InvalidUnit();
/// @dev Thrown when the caller for a fallback NFT function is not the mirror contract.
error SenderNotMirror();
/// @dev Thrown when attempting to transfer tokens to the zero address.
error TransferToZeroAddress();
/// @dev Thrown when the mirror address provided for initialization is the zero address.
error MirrorAddressIsZero();
/// @dev Thrown when the link call to the mirror contract reverts.
error LinkMirrorContractFailed();
/// @dev Thrown when setting an NFT token approval
/// and the caller is not the owner or an approved operator.
error ApprovalCallerNotOwnerNorApproved();
/// @dev Thrown when transferring an NFT
/// and the caller is not the owner or an approved operator.
error TransferCallerNotOwnerNorApproved();
/// @dev Thrown when transferring an NFT and the from address is not the current owner.
error TransferFromIncorrectOwner();
/// @dev Thrown when checking the owner or approved address for a non-existent NFT.
error TokenDoesNotExist();
/// @dev The function selector is not recognized.
error FnSelectorNotRecognized();
/*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/
/* CONSTANTS */
/*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/
/// @dev The flag to denote that the address data is initialized.
uint8 internal constant _ADDRESS_DATA_INITIALIZED_FLAG = 1 << 0;
/// @dev The flag to denote that the address should skip NFTs.
uint8 internal constant _ADDRESS_DATA_SKIP_NFT_FLAG = 1 << 1;
/// @dev The flag to denote that the address has overridden the default Permit2 allowance.
uint8 internal constant _ADDRESS_DATA_OVERRIDE_PERMIT2_FLAG = 1 << 2;
/// @dev The canonical Permit2 address.
/// For signature-based allowance granting for single transaction ERC20 `transferFrom`.
/// To enable, override `_givePermit2DefaultInfiniteAllowance()`.
/// [Github](https://github.com/Uniswap/permit2)
/// [Etherscan](https://etherscan.io/address/0x000000000022D473030F116dDEE9F6B43aC78BA3)
address internal constant _PERMIT2 = 0x000000000022D473030F116dDEE9F6B43aC78BA3;
/*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/
/* STORAGE */
/*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/
/// @dev Struct containing an address's token data and settings.
struct AddressData {
// Auxiliary data.
uint88 aux;
// Flags for `initialized` and `skipNFT`.
uint8 flags;
// The alias for the address. Zero means absence of an alias.
uint32 addressAlias;
// The number of NFT tokens.
uint32 ownedLength;
// The token balance in wei.
uint96 balance;
}
/// @dev A uint32 map in storage.
struct Uint32Map {
uint256 spacer;
}
/// @dev A bitmap in storage.
struct Bitmap {
uint256 spacer;
}
/// @dev A struct to wrap a uint256 in storage.
struct Uint256Ref {
uint256 value;
}
/// @dev A mapping of an address pair to a Uint256Ref.
struct AddressPairToUint256RefMap {
uint256 spacer;
}
/// @dev Struct containing the base token contract storage.
struct DN404Storage {
// Current number of address aliases assigned.
uint32 numAliases;
// Next NFT ID to assign for a mint.
uint32 nextTokenId;
// The head of the burned pool.
uint32 burnedPoolHead;
// The tail of the burned pool.
uint32 burnedPoolTail;
// Total number of NFTs in existence.
uint32 totalNFTSupply;
// Total supply of tokens.
uint96 totalSupply;
// Address of the NFT mirror contract.
address mirrorERC721;
// Mapping of a user alias number to their address.
mapping(uint32 => address) aliasToAddress;
// Mapping of user operator approvals for NFTs.
AddressPairToUint256RefMap operatorApprovals;
// Mapping of NFT approvals to approved operators.
mapping(uint256 => address) nftApprovals;
// Bitmap of whether an non-zero NFT approval may exist.
Bitmap mayHaveNFTApproval;
// Bitmap of whether a NFT ID exists. Ignored if `_useExistsLookup()` returns false.
Bitmap exists;
// Mapping of user allowances for ERC20 spenders.
AddressPairToUint256RefMap allowance;
// Mapping of NFT IDs owned by an address.
mapping(address => Uint32Map) owned;
// The pool of burned NFT IDs.
Uint32Map burnedPool;
// Even indices: owner aliases. Odd indices: owned indices.
Uint32Map oo;
// Mapping of user account AddressData.
mapping(address => AddressData) addressData;
}
/// @dev Returns a storage pointer for DN404Storage.
function _getDN404Storage() internal pure virtual returns (DN404Storage storage $) {
/// @solidity memory-safe-assembly
assembly {
// `uint72(bytes9(keccak256("DN404_STORAGE")))`.
$.slot := 0xa20d6e21d0e5255308 // Truncate to 9 bytes to reduce bytecode size.
}
}
/*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/
/* INITIALIZER */
/*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/
/// @dev Initializes the DN404 contract with an
/// `initialTokenSupply`, `initialTokenOwner` and `mirror` NFT contract address.
function _initializeDN404(
uint256 initialTokenSupply,
address initialSupplyOwner,
address mirror
) internal virtual {
DN404Storage storage $ = _getDN404Storage();
unchecked {
if (_unit() - 1 >= 2 ** 96 - 1) revert InvalidUnit();
}
if ($.mirrorERC721 != address(0)) revert DNAlreadyInitialized();
if (mirror == address(0)) revert MirrorAddressIsZero();
/// @solidity memory-safe-assembly
assembly {
// Make the call to link the mirror contract.
mstore(0x00, 0x0f4599e5) // `linkMirrorContract(address)`.
mstore(0x20, caller())
if iszero(and(eq(mload(0x00), 1), call(gas(), mirror, 0, 0x1c, 0x24, 0x00, 0x20))) {
mstore(0x00, 0xd125259c) // `LinkMirrorContractFailed()`.
revert(0x1c, 0x04)
}
}
$.nextTokenId = 1;
$.mirrorERC721 = mirror;
if (initialTokenSupply != 0) {
if (initialSupplyOwner == address(0)) revert TransferToZeroAddress();
if (_totalSupplyOverflows(initialTokenSupply)) revert TotalSupplyOverflow();
$.totalSupply = uint96(initialTokenSupply);
AddressData storage initialOwnerAddressData = _addressData(initialSupplyOwner);
initialOwnerAddressData.balance = uint96(initialTokenSupply);
/// @solidity memory-safe-assembly
assembly {
// Emit the {Transfer} event.
mstore(0x00, initialTokenSupply)
log3(0x00, 0x20, _TRANSFER_EVENT_SIGNATURE, 0, shr(96, shl(96, initialSupplyOwner)))
}
_setSkipNFT(initialSupplyOwner, true);
}
}
/*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/
/* BASE UNIT FUNCTION TO OVERRIDE */
/*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/
/// @dev Amount of token balance that is equal to one NFT.
function _unit() internal view virtual returns (uint256) {
return 10 ** 18;
}
/*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/
/* METADATA FUNCTIONS TO OVERRIDE */
/*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/
/// @dev Returns the name of the token.
function name() public view virtual returns (string memory);
/// @dev Returns the symbol of the token.
function symbol() public view virtual returns (string memory);
/// @dev Returns the Uniform Resource Identifier (URI) for token `id`.
function _tokenURI(uint256 id) internal view virtual returns (string memory);
/*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/
/* CONFIGURABLES */
/*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/
/// @dev Returns if direct NFT transfers should be used during ERC20 transfers
/// whenever possible, instead of burning and re-minting.
function _useDirectTransfersIfPossible() internal view virtual returns (bool) {
return true;
}
/// @dev Returns if burns should be added to the burn pool.
/// This returns false by default, which means the NFT IDs are re-minted in a cycle.
function _addToBurnedPool(uint256 totalNFTSupplyAfterBurn, uint256 totalSupplyAfterBurn)
internal
view
virtual
returns (bool)
{
// Silence unused variable compiler warning.
totalSupplyAfterBurn = totalNFTSupplyAfterBurn;
return false;
}
/// @dev Returns whether to use the exists bitmap for more efficient
/// scanning of an empty token ID slot.
/// Recommended for collections that do not use the burn pool,
/// and are expected to have nearly all possible NFTs materialized.
///
/// Note: The returned value must be constant after initialization.
function _useExistsLookup() internal view virtual returns (bool) {
return true;
}
/// @dev Hook that is called after any NFT token transfers, including minting and burning.
function _afterNFTTransfer(address from, address to, uint256 id) internal virtual {}
/*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/
/* ERC20 OPERATIONS */
/*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/
/// @dev Returns the decimals places of the token. Always 18.
function decimals() public pure returns (uint8) {
return 18;
}
/// @dev Returns the amount of tokens in existence.
function totalSupply() public view virtual returns (uint256) {
return uint256(_getDN404Storage().totalSupply);
}
/// @dev Returns the amount of tokens owned by `owner`.
function balanceOf(address owner) public view virtual returns (uint256) {
return _getDN404Storage().addressData[owner].balance;
}
/// @dev Returns the amount of tokens that `spender` can spend on behalf of `owner`.
function allowance(address owner, address spender) public view returns (uint256) {
if (_givePermit2DefaultInfiniteAllowance() && spender == _PERMIT2) {
uint8 flags = _getDN404Storage().addressData[owner].flags;
if (_isZero(flags & _ADDRESS_DATA_OVERRIDE_PERMIT2_FLAG)) return type(uint256).max;
}
return _ref(_getDN404Storage().allowance, owner, spender).value;
}
/// @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
///
/// Emits a {Approval} event.
function approve(address spender, uint256 amount) public virtual returns (bool) {
_approve(msg.sender, spender, amount);
return true;
}
/// @dev Transfer `amount` tokens from the caller to `to`.
///
/// Will burn sender NFTs if balance after transfer is less than
/// the amount required to support the current NFT balance.
///
/// Will mint NFTs to `to` if the recipient's new balance supports
/// additional NFTs ***AND*** the `to` address's skipNFT flag is
/// set to false.
///
/// Requirements:
/// - `from` must at least have `amount`.
///
/// Emits a {Transfer} event.
function transfer(address to, uint256 amount) public virtual returns (bool) {
_transfer(msg.sender, to, amount);
return true;
}
/// @dev Transfers `amount` tokens from `from` to `to`.
///
/// Note: Does not update the allowance if it is the maximum uint256 value.
///
/// Will burn sender NFTs if balance after transfer is less than
/// the amount required to support the current NFT balance.
///
/// Will mint NFTs to `to` if the recipient's new balance supports
/// additional NFTs ***AND*** the `to` address's skipNFT flag is
/// set to false.
///
/// Requirements:
/// - `from` must at least have `amount`.
/// - The caller must have at least `amount` of allowance to transfer the tokens of `from`.
///
/// Emits a {Transfer} event.
function transferFrom(address from, address to, uint256 amount) public virtual returns (bool) {
Uint256Ref storage a = _ref(_getDN404Storage().allowance, from, msg.sender);
uint256 allowed = _givePermit2DefaultInfiniteAllowance() && msg.sender == _PERMIT2
&& _isZero(_getDN404Storage().addressData[from].flags & _ADDRESS_DATA_OVERRIDE_PERMIT2_FLAG)
? type(uint256).max
: a.value;
if (allowed != type(uint256).max) {
if (amount > allowed) revert InsufficientAllowance();
unchecked {
a.value = allowed - amount;
}
}
_transfer(from, to, amount);
return true;
}
/*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/
/* PERMIT2 */
/*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/
/// @dev Whether Permit2 has infinite allowances by default for all owners.
/// For signature-based allowance granting for single transaction ERC20 `transferFrom`.
/// To enable, override this function to return true.
function _givePermit2DefaultInfiniteAllowance() internal view virtual returns (bool) {
return false;
}
/*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/
/* INTERNAL MINT FUNCTIONS */
/*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/
/// @dev Mints `amount` tokens to `to`, increasing the total supply.
///
/// Will mint NFTs to `to` if the recipient's new balance supports
/// additional NFTs ***AND*** the `to` address's skipNFT flag is set to false.
///
/// Emits a {Transfer} event.
function _mint(address to, uint256 amount) internal virtual {
if (to == address(0)) revert TransferToZeroAddress();
AddressData storage toAddressData = _addressData(to);
DN404Storage storage $ = _getDN404Storage();
if ($.mirrorERC721 == address(0)) revert DNNotInitialized();
_DNMintTemps memory t;
unchecked {
uint256 toBalance = uint256(toAddressData.balance) + amount;
toAddressData.balance = uint96(toBalance);
t.toEnd = toBalance / _unit();
}
uint256 maxId;
unchecked {
uint256 totalSupply_ = uint256($.totalSupply) + amount;
$.totalSupply = uint96(totalSupply_);
uint256 overflows = _toUint(_totalSupplyOverflows(totalSupply_));
if (overflows | _toUint(totalSupply_ < amount) != 0) revert TotalSupplyOverflow();
maxId = totalSupply_ / _unit();
}
unchecked {
if (_isZero(toAddressData.flags & _ADDRESS_DATA_SKIP_NFT_FLAG)) {
Uint32Map storage toOwned = $.owned[to];
Uint32Map storage oo = $.oo;
uint256 toIndex = toAddressData.ownedLength;
_DNPackedLogs memory packedLogs = _packedLogsMalloc(_zeroFloorSub(t.toEnd, toIndex));
if (packedLogs.logs.length != 0) {
_packedLogsSet(packedLogs, to, 0);
$.totalNFTSupply += uint32(packedLogs.logs.length);
toAddressData.ownedLength = uint32(t.toEnd);
t.toAlias = _registerAndResolveAlias(toAddressData, to);
uint32 burnedPoolHead = $.burnedPoolHead;
t.burnedPoolTail = $.burnedPoolTail;
t.nextTokenId = _wrapNFTId($.nextTokenId, maxId);
// Mint loop.
do {
uint256 id;
if (burnedPoolHead != t.burnedPoolTail) {
id = _get($.burnedPool, burnedPoolHead++);
} else {
id = t.nextTokenId;
while (_get(oo, _ownershipIndex(id)) != 0) {
id = _useExistsLookup()
? _wrapNFTId(_findFirstUnset($.exists, id + 1, maxId), maxId)
: _wrapNFTId(id + 1, maxId);
}
t.nextTokenId = _wrapNFTId(id + 1, maxId);
}
if (_useExistsLookup()) _set($.exists, id, true);
_set(toOwned, toIndex, uint32(id));
_setOwnerAliasAndOwnedIndex(oo, id, t.toAlias, uint32(toIndex++));
_packedLogsAppend(packedLogs, id);
_afterNFTTransfer(address(0), to, id);
} while (toIndex != t.toEnd);
$.nextTokenId = uint32(t.nextTokenId);
$.burnedPoolHead = burnedPoolHead;
_packedLogsSend(packedLogs, $.mirrorERC721);
}
}
}
/// @solidity memory-safe-assembly
assembly {
// Emit the {Transfer} event.
mstore(0x00, amount)
log3(0x00, 0x20, _TRANSFER_EVENT_SIGNATURE, 0, shr(96, shl(96, to)))
}
}
/// @dev Mints `amount` tokens to `to`, increasing the total supply.
/// This variant mints NFT tokens starting from ID `preTotalSupply / _unit() + 1`.
/// This variant will not touch the `burnedPool` and `nextTokenId`.
///
/// Will mint NFTs to `to` if the recipient's new balance supports
/// additional NFTs ***AND*** the `to` address's skipNFT flag is set to false.
///
/// Emits a {Transfer} event.
function _mintNext(address to, uint256 amount) internal virtual {
if (to == address(0)) revert TransferToZeroAddress();
AddressData storage toAddressData = _addressData(to);
DN404Storage storage $ = _getDN404Storage();
if ($.mirrorERC721 == address(0)) revert DNNotInitialized();
_DNMintTemps memory t;
unchecked {
uint256 toBalance = uint256(toAddressData.balance) + amount;
toAddressData.balance = uint96(toBalance);
t.toEnd = toBalance / _unit();
}
uint256 startId;
uint256 maxId;
unchecked {
uint256 preTotalSupply = uint256($.totalSupply);
startId = preTotalSupply / _unit() + 1;
uint256 totalSupply_ = uint256(preTotalSupply) + amount;
$.totalSupply = uint96(totalSupply_);
uint256 overflows = _toUint(_totalSupplyOverflows(totalSupply_));
if (overflows | _toUint(totalSupply_ < amount) != 0) revert TotalSupplyOverflow();
maxId = totalSupply_ / _unit();
}
unchecked {
if (_isZero(toAddressData.flags & _ADDRESS_DATA_SKIP_NFT_FLAG)) {
Uint32Map storage toOwned = $.owned[to];
Uint32Map storage oo = $.oo;
uint256 toIndex = toAddressData.ownedLength;
_DNPackedLogs memory packedLogs = _packedLogsMalloc(_zeroFloorSub(t.toEnd, toIndex));
if (packedLogs.logs.length != 0) {
_packedLogsSet(packedLogs, to, 0);
$.totalNFTSupply += uint32(packedLogs.logs.length);
toAddressData.ownedLength = uint32(t.toEnd);
t.toAlias = _registerAndResolveAlias(toAddressData, to);
// Mint loop.
do {
uint256 id = startId;
while (_get(oo, _ownershipIndex(id)) != 0) {
id = _useExistsLookup()
? _wrapNFTId(_findFirstUnset($.exists, id + 1, maxId), maxId)
: _wrapNFTId(id + 1, maxId);
}
startId = _wrapNFTId(id + 1, maxId);
if (_useExistsLookup()) _set($.exists, id, true);
_set(toOwned, toIndex, uint32(id));
_setOwnerAliasAndOwnedIndex(oo, id, t.toAlias, uint32(toIndex++));
_packedLogsAppend(packedLogs, id);
_afterNFTTransfer(address(0), to, id);
} while (toIndex != t.toEnd);
_packedLogsSend(packedLogs, $.mirrorERC721);
}
}
}
/// @solidity memory-safe-assembly
assembly {
// Emit the {Transfer} event.
mstore(0x00, amount)
log3(0x00, 0x20, _TRANSFER_EVENT_SIGNATURE, 0, shr(96, shl(96, to)))
}
}
/*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/
/* INTERNAL BURN FUNCTIONS */
/*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/
/// @dev Burns `amount` tokens from `from`, reducing the total supply.
///
/// Will burn sender NFTs if balance after transfer is less than
/// the amount required to support the current NFT balance.
///
/// Emits a {Transfer} event.
function _burn(address from, uint256 amount) internal virtual {
DN404Storage storage $ = _getDN404Storage();
if ($.mirrorERC721 == address(0)) revert DNNotInitialized();
AddressData storage fromAddressData = $.addressData[from];
uint256 fromBalance = fromAddressData.balance;
if (amount > fromBalance) revert InsufficientBalance();
unchecked {
fromAddressData.balance = uint96(fromBalance -= amount);
uint256 totalSupply_ = uint256($.totalSupply) - amount;
$.totalSupply = uint96(totalSupply_);
Uint32Map storage fromOwned = $.owned[from];
uint256 fromIndex = fromAddressData.ownedLength;
uint256 numNFTBurns = _zeroFloorSub(fromIndex, fromBalance / _unit());
if (numNFTBurns != 0) {
_DNPackedLogs memory packedLogs = _packedLogsMalloc(numNFTBurns);
_packedLogsSet(packedLogs, from, 1);
bool addToBurnedPool;
{
uint256 totalNFTSupply = uint256($.totalNFTSupply) - numNFTBurns;
$.totalNFTSupply = uint32(totalNFTSupply);
addToBurnedPool = _addToBurnedPool(totalNFTSupply, totalSupply_);
}
Uint32Map storage oo = $.oo;
uint256 fromEnd = fromIndex - numNFTBurns;
fromAddressData.ownedLength = uint32(fromEnd);
uint32 burnedPoolTail = $.burnedPoolTail;
// Burn loop.
do {
uint256 id = _get(fromOwned, --fromIndex);
_setOwnerAliasAndOwnedIndex(oo, id, 0, 0);
_packedLogsAppend(packedLogs, id);
if (_useExistsLookup()) _set($.exists, id, false);
if (addToBurnedPool) _set($.burnedPool, burnedPoolTail++, uint32(id));
if (_get($.mayHaveNFTApproval, id)) {
_set($.mayHaveNFTApproval, id, false);
delete $.nftApprovals[id];
}
_afterNFTTransfer(from, address(0), id);
} while (fromIndex != fromEnd);
if (addToBurnedPool) $.burnedPoolTail = burnedPoolTail;
_packedLogsSend(packedLogs, $.mirrorERC721);
}
}
/// @solidity memory-safe-assembly
assembly {
// Emit the {Transfer} event.
mstore(0x00, amount)
log3(0x00, 0x20, _TRANSFER_EVENT_SIGNATURE, shr(96, shl(96, from)), 0)
}
}
/*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/
/* INTERNAL TRANSFER FUNCTIONS */
/*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/
/// @dev Moves `amount` of tokens from `from` to `to`.
///
/// Will burn sender NFTs if balance after transfer is less than
/// the amount required to support the current NFT balance.
///
/// Will mint NFTs to `to` if the recipient's new balance supports
/// additional NFTs ***AND*** the `to` address's skipNFT flag is
/// set to false.
///
/// Emits a {Transfer} event.
function _transfer(address from, address to, uint256 amount) internal virtual {
if (to == address(0)) revert TransferToZeroAddress();
DN404Storage storage $ = _getDN404Storage();
AddressData storage fromAddressData = $.addressData[from];
AddressData storage toAddressData = _addressData(to);
if ($.mirrorERC721 == address(0)) revert DNNotInitialized();
_DNTransferTemps memory t;
t.fromOwnedLength = fromAddressData.ownedLength;
t.toOwnedLength = toAddressData.ownedLength;
unchecked {
{
uint256 fromBalance = fromAddressData.balance;
if (amount > fromBalance) revert InsufficientBalance();
fromAddressData.balance = uint96(fromBalance -= amount);
uint256 toBalance = uint256(toAddressData.balance) + amount;
toAddressData.balance = uint96(toBalance);
t.numNFTBurns = _zeroFloorSub(t.fromOwnedLength, fromBalance / _unit());
if (_isZero(toAddressData.flags & _ADDRESS_DATA_SKIP_NFT_FLAG)) {
if (from == to) t.toOwnedLength = t.fromOwnedLength - t.numNFTBurns;
t.numNFTMints = _zeroFloorSub(toBalance / _unit(), t.toOwnedLength);
}
}
while (_useDirectTransfersIfPossible()) {
uint256 n = _min(t.fromOwnedLength, _min(t.numNFTBurns, t.numNFTMints));
if (_isZero(n)) break;
t.numNFTBurns -= n;
t.numNFTMints -= n;
if (from == to) {
t.toOwnedLength += n;
break;
}
_DNDirectLogs memory directLogs = _directLogsMalloc(n, from, to);
Uint32Map storage fromOwned = $.owned[from];
Uint32Map storage toOwned = $.owned[to];
t.toAlias = _registerAndResolveAlias(toAddressData, to);
uint256 toIndex = t.toOwnedLength;
n = toIndex + n;
// Direct transfer loop.
do {
uint256 id = _get(fromOwned, --t.fromOwnedLength);
_set(toOwned, toIndex, uint32(id));
_setOwnerAliasAndOwnedIndex($.oo, id, t.toAlias, uint32(toIndex));
_directLogsAppend(directLogs, id);
if (_get($.mayHaveNFTApproval, id)) {
_set($.mayHaveNFTApproval, id, false);
delete $.nftApprovals[id];
}
_afterNFTTransfer(from, to, id);
} while (++toIndex != n);
toAddressData.ownedLength = uint32(t.toOwnedLength = toIndex);
fromAddressData.ownedLength = uint32(t.fromOwnedLength);
_directLogsSend(directLogs, $.mirrorERC721);
break;
}
t.totalNFTSupply = uint256($.totalNFTSupply) + t.numNFTMints - t.numNFTBurns;
$.totalNFTSupply = uint32(t.totalNFTSupply);
Uint32Map storage oo = $.oo;
_DNPackedLogs memory packedLogs = _packedLogsMalloc(t.numNFTBurns + t.numNFTMints);
t.burnedPoolTail = $.burnedPoolTail;
if (t.numNFTBurns != 0) {
_packedLogsSet(packedLogs, from, 1);
bool addToBurnedPool = _addToBurnedPool(t.totalNFTSupply, $.totalSupply);
Uint32Map storage fromOwned = $.owned[from];
uint256 fromIndex = t.fromOwnedLength;
fromAddressData.ownedLength = uint32(t.fromEnd = fromIndex - t.numNFTBurns);
uint32 burnedPoolTail = t.burnedPoolTail;
// Burn loop.
do {
uint256 id = _get(fromOwned, --fromIndex);
_setOwnerAliasAndOwnedIndex(oo, id, 0, 0);
_packedLogsAppend(packedLogs, id);
if (_useExistsLookup()) _set($.exists, id, false);
if (addToBurnedPool) _set($.burnedPool, burnedPoolTail++, uint32(id));
if (_get($.mayHaveNFTApproval, id)) {
_set($.mayHaveNFTApproval, id, false);
delete $.nftApprovals[id];
}
_afterNFTTransfer(from, address(0), id);
} while (fromIndex != t.fromEnd);
if (addToBurnedPool) $.burnedPoolTail = (t.burnedPoolTail = burnedPoolTail);
}
if (t.numNFTMints != 0) {
_packedLogsSet(packedLogs, to, 0);
Uint32Map storage toOwned = $.owned[to];
t.toAlias = _registerAndResolveAlias(toAddressData, to);
uint256 maxId = $.totalSupply / _unit();
t.nextTokenId = _wrapNFTId($.nextTokenId, maxId);
uint256 toIndex = t.toOwnedLength;
toAddressData.ownedLength = uint32(t.toEnd = toIndex + t.numNFTMints);
uint32 burnedPoolHead = $.burnedPoolHead;
// Mint loop.
do {
uint256 id;
if (burnedPoolHead != t.burnedPoolTail) {
id = _get($.burnedPool, burnedPoolHead++);
} else {
id = t.nextTokenId;
while (_get(oo, _ownershipIndex(id)) != 0) {
id = _useExistsLookup()
? _wrapNFTId(_findFirstUnset($.exists, id + 1, maxId), maxId)
: _wrapNFTId(id + 1, maxId);
}
t.nextTokenId = _wrapNFTId(id + 1, maxId);
}
if (_useExistsLookup()) _set($.exists, id, true);
_set(toOwned, toIndex, uint32(id));
_setOwnerAliasAndOwnedIndex(oo, id, t.toAlias, uint32(toIndex++));
_packedLogsAppend(packedLogs, id);
_afterNFTTransfer(address(0), to, id);
} while (toIndex != t.toEnd);
$.burnedPoolHead = burnedPoolHead;
$.nextTokenId = uint32(t.nextTokenId);
}
if (packedLogs.logs.length != 0) _packedLogsSend(packedLogs, $.mirrorERC721);
}
/// @solidity memory-safe-assembly
assembly {
// Emit the {Transfer} event.
mstore(0x00, amount)
// forgefmt: disable-next-item
log3(0x00, 0x20, _TRANSFER_EVENT_SIGNATURE, shr(96, shl(96, from)), shr(96, shl(96, to)))
}
}
/// @dev Transfers token `id` from `from` to `to`. Also emits ERC721 Transfer event.
///
/// Requirements:
///
/// - Token `id` must exist.
/// - `from` must be the owner of the token.
/// - `to` cannot be the zero address.
/// `msgSender` must be the owner of the token, or be approved to manage the token.
///
/// Emits a {Transfer} event.
function _initiateTransferFromNFT(address from, address to, uint256 id, address msgSender)
internal
virtual
{
_transferFromNFT(from, to, id, msgSender);
// Emit ERC721 Transfer
DN404Storage storage $ = _getDN404Storage();
_DNDirectLogs memory directLogs = _directLogsMalloc(1, from, to);
_directLogsAppend(directLogs, id);
_directLogsSend(directLogs, $.mirrorERC721);
}
/// @dev Transfers token `id` from `from` to `to`.
///
/// Requirements:
///
/// - Call must originate from the mirror contract.
/// - Token `id` must exist.
/// - `from` must be the owner of the token.
/// - `to` cannot be the zero address.
/// `msgSender` must be the owner of the token, or be approved to manage the token.
///
/// Emits a {Transfer} event.
function _transferFromNFT(address from, address to, uint256 id, address msgSender)
internal
virtual
{
if (to == address(0)) revert TransferToZeroAddress();
DN404Storage storage $ = _getDN404Storage();
if ($.mirrorERC721 == address(0)) revert DNNotInitialized();
Uint32Map storage oo = $.oo;
if (from != $.aliasToAddress[_get(oo, _ownershipIndex(_restrictNFTId(id)))]) {
revert TransferFromIncorrectOwner();
}
if (msgSender != from) {
if (!_isApprovedForAll(from, msgSender)) {
if (_getApproved(id) != msgSender) {
revert TransferCallerNotOwnerNorApproved();
}
}
}
AddressData storage fromAddressData = $.addressData[from];
AddressData storage toAddressData = $.addressData[to];
uint256 unit = _unit();
mapping(address => Uint32Map) storage owned = $.owned;
unchecked {
uint256 fromBalance = fromAddressData.balance;
if (unit > fromBalance) revert InsufficientBalance();
fromAddressData.balance = uint96(fromBalance - unit);
toAddressData.balance += uint96(unit);
}
if (_get($.mayHaveNFTApproval, id)) {
_set($.mayHaveNFTApproval, id, false);
delete $.nftApprovals[id];
}
unchecked {
Uint32Map storage fromOwned = owned[from];
uint32 updatedId = _get(fromOwned, --fromAddressData.ownedLength);
uint32 i = _get(oo, _ownedIndex(id));
_set(fromOwned, i, updatedId);
_set(oo, _ownedIndex(updatedId), i);
}
unchecked {
uint32 n = toAddressData.ownedLength++;
_set(owned[to], n, uint32(id));
_setOwnerAliasAndOwnedIndex(oo, id, _registerAndResolveAlias(toAddressData, to), n);
}
_afterNFTTransfer(from, to, id);
/// @solidity memory-safe-assembly
assembly {
// Emit the {Transfer} event.
mstore(0x00, unit)
// forgefmt: disable-next-item
log3(0x00, 0x20, _TRANSFER_EVENT_SIGNATURE, shr(96, shl(96, from)), shr(96, shl(96, to)))
}
}
/*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/
/* INTERNAL APPROVE FUNCTIONS */
/*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/
/// @dev Sets `amount` as the allowance of `spender` over the tokens of `owner`.
///
/// Emits a {Approval} event.
function _approve(address owner, address spender, uint256 amount) internal virtual {
if (_givePermit2DefaultInfiniteAllowance() && spender == _PERMIT2) {
_getDN404Storage().addressData[owner].flags |= _ADDRESS_DATA_OVERRIDE_PERMIT2_FLAG;
}
_ref(_getDN404Storage().allowance, owner, spender).value = amount;
/// @solidity memory-safe-assembly
assembly {
// Emit the {Approval} event.
mstore(0x00, amount)
// forgefmt: disable-next-item
log3(0x00, 0x20, _APPROVAL_EVENT_SIGNATURE, shr(96, shl(96, owner)), shr(96, shl(96, spender)))
}
}
/*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/
/* DATA HITCHHIKING FUNCTIONS */
/*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/
/// @dev Returns the auxiliary data for `owner`.
/// Minting, transferring, burning the tokens of `owner` will not change the auxiliary data.
/// Auxiliary data can be set for any address, even if it does not have any tokens.
function _getAux(address owner) internal view virtual returns (uint88) {
return _getDN404Storage().addressData[owner].aux;
}
/// @dev Set the auxiliary data for `owner` to `value`.
/// Minting, transferring, burning the tokens of `owner` will not change the auxiliary data.
/// Auxiliary data can be set for any address, even if it does not have any tokens.
function _setAux(address owner, uint88 value) internal virtual {
_getDN404Storage().addressData[owner].aux = value;
}
/*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/
/* SKIP NFT FUNCTIONS */
/*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/
/// @dev Returns true if minting and transferring ERC20s to `owner` will skip minting NFTs.
/// Returns false otherwise.
function getSkipNFT(address owner) public view virtual returns (bool) {
AddressData storage d = _getDN404Storage().addressData[owner];
if (_isZero(d.flags & _ADDRESS_DATA_INITIALIZED_FLAG)) return _hasCode(owner);
return d.flags & _ADDRESS_DATA_SKIP_NFT_FLAG != 0;
}
/// @dev Sets the caller's skipNFT flag to `skipNFT`. Returns true.
///
/// Emits a {SkipNFTSet} event.
function setSkipNFT(bool skipNFT) public virtual returns (bool) {
_setSkipNFT(msg.sender, skipNFT);
return true;
}
/// @dev Internal function to set account `owner` skipNFT flag to `state`
///
/// Initializes account `owner` AddressData if it is not currently initialized.
///
/// Emits a {SkipNFTSet} event.
function _setSkipNFT(address owner, bool state) internal virtual {
AddressData storage d = _addressData(owner);
if ((d.flags & _ADDRESS_DATA_SKIP_NFT_FLAG != 0) != state) {
d.flags ^= _ADDRESS_DATA_SKIP_NFT_FLAG;
}
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, iszero(iszero(state)))
log2(0x00, 0x20, _SKIP_NFT_SET_EVENT_SIGNATURE, shr(96, shl(96, owner)))
}
}
/// @dev Returns a storage data pointer for account `owner` AddressData
///
/// Initializes account `owner` AddressData if it is not currently initialized.
function _addressData(address owner) internal virtual returns (AddressData storage d) {
d = _getDN404Storage().addressData[owner];
unchecked {
if (_isZero(d.flags & _ADDRESS_DATA_INITIALIZED_FLAG)) {
uint256 skipNFT = _toUint(_hasCode(owner)) * _ADDRESS_DATA_SKIP_NFT_FLAG;
d.flags = uint8(skipNFT | _ADDRESS_DATA_INITIALIZED_FLAG);
}
}
}
/// @dev Returns the `addressAlias` of account `to`.
///
/// Assigns and registers the next alias if `to` alias was not previously registered.
function _registerAndResolveAlias(AddressData storage toAddressData, address to)
internal
virtual
returns (uint32 addressAlias)
{
DN404Storage storage $ = _getDN404Storage();
addressAlias = toAddressData.addressAlias;
if (_isZero(addressAlias)) {
unchecked {
addressAlias = ++$.numAliases;
}
toAddressData.addressAlias = addressAlias;
$.aliasToAddress[addressAlias] = to;
if (_isZero(addressAlias)) revert(); // Overflow.
}
}
/*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/
/* MIRROR OPERATIONS */
/*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/
/// @dev Returns the address of the mirror NFT contract.
function mirrorERC721() public view virtual returns (address) {
return _getDN404Storage().mirrorERC721;
}
/// @dev Returns the total NFT supply.
function _totalNFTSupply() internal view virtual returns (uint256) {
return _getDN404Storage().totalNFTSupply;
}
/// @dev Returns `owner` NFT balance.
function _balanceOfNFT(address owner) internal view virtual returns (uint256) {
return _getDN404Storage().addressData[owner].ownedLength;
}
/// @dev Returns the owner of token `id`.
/// Returns the zero address instead of reverting if the token does not exist.
function _ownerAt(uint256 id) internal view virtual returns (address) {
DN404Storage storage $ = _getDN404Storage();
return $.aliasToAddress[_get($.oo, _ownershipIndex(_restrictNFTId(id)))];
}
/// @dev Returns the owner of token `id`.
///
/// Requirements:
/// - Token `id` must exist.
function _ownerOf(uint256 id) internal view virtual returns (address) {
if (!_exists(id)) revert TokenDoesNotExist();
return _ownerAt(id);
}
/// @dev Returns whether `operator` is approved to manage the NFT tokens of `owner`.
function _isApprovedForAll(address owner, address operator)
internal
view
virtual
returns (bool)
{
return !_isZero(_ref(_getDN404Storage().operatorApprovals, owner, operator).value);
}
/// @dev Returns if token `id` exists.
function _exists(uint256 id) internal view virtual returns (bool) {
return _ownerAt(id) != address(0);
}
/// @dev Returns the account approved to manage token `id`.
///
/// Requirements:
/// - Token `id` must exist.
function _getApproved(uint256 id) internal view virtual returns (address) {
if (!_exists(id)) revert TokenDoesNotExist();
return _getDN404Storage().nftApprovals[id];
}
/// @dev Sets `spender` as the approved account to manage token `id`, using `msgSender`.
///
/// Requirements:
/// - `msgSender` must be the owner or an approved operator for the token owner.
function _approveNFT(address spender, uint256 id, address msgSender)
internal
virtual
returns (address owner)
{
DN404Storage storage $ = _getDN404Storage();
owner = $.aliasToAddress[_get($.oo, _ownershipIndex(_restrictNFTId(id)))];
if (msgSender != owner) {
if (!_isApprovedForAll(owner, msgSender)) {
revert ApprovalCallerNotOwnerNorApproved();
}
}
$.nftApprovals[id] = spender;
_set($.mayHaveNFTApproval, id, spender != address(0));
}
/// @dev Approve or remove the `operator` as an operator for `msgSender`,
/// without authorization checks.
function _setApprovalForAll(address operator, bool approved, address msgSender)
internal
virtual
{
_ref(_getDN404Storage().operatorApprovals, msgSender, operator).value = _toUint(approved);
}
/// @dev Returns the NFT IDs of `owner` in the range `[begin..end)` (exclusive of `end`).
/// `begin` and `end` are indices in the owner's token ID array, not the entire token range.
/// Optimized for smaller bytecode size, as this function is intended for off-chain calling.
function _ownedIds(address owner, uint256 begin, uint256 end)
internal
view
virtual
returns (uint256[] memory ids)
{
DN404Storage storage $ = _getDN404Storage();
Uint32Map storage owned = $.owned[owner];
end = _min($.addressData[owner].ownedLength, end);
/// @solidity memory-safe-assembly
assembly {
ids := mload(0x40)
let i := begin
for {} lt(i, end) { i := add(i, 1) } {
let s := add(shl(96, owned.slot), shr(3, i)) // Storage slot.
let id := and(0xffffffff, shr(shl(5, and(i, 7)), sload(s)))
mstore(add(add(ids, 0x20), shl(5, sub(i, begin))), id) // Append to.
}
mstore(ids, sub(i, begin)) // Store the length.
mstore(0x40, add(add(ids, 0x20), shl(5, sub(i, begin)))) // Allocate memory.
}
}
/// @dev Fallback modifier to dispatch calls from the mirror NFT contract
/// to internal functions in this contract.
modifier dn404Fallback() virtual {
DN404Storage storage $ = _getDN404Storage();
uint256 fnSelector = _calldataload(0x00) >> 224;
// `transferFromNFT(address,address,uint256,address)`.
if (fnSelector == 0xe5eb36c8) {
if (msg.sender != $.mirrorERC721) revert SenderNotMirror();
_transferFromNFT(
address(uint160(_calldataload(0x04))), // `from`.
address(uint160(_calldataload(0x24))), // `to`.
_calldataload(0x44), // `id`.
address(uint160(_calldataload(0x64))) // `msgSender`.
);
_return(1);
}
// `setApprovalForAll(address,bool,address)`.
if (fnSelector == 0x813500fc) {
if (msg.sender != $.mirrorERC721) revert SenderNotMirror();
_setApprovalForAll(
address(uint160(_calldataload(0x04))), // `spender`.
_calldataload(0x24) != 0, // `status`.
address(uint160(_calldataload(0x44))) // `msgSender`.
);
_return(1);
}
// `isApprovedForAll(address,address)`.
if (fnSelector == 0xe985e9c5) {
bool result = _isApprovedForAll(
address(uint160(_calldataload(0x04))), // `owner`.
address(uint160(_calldataload(0x24))) // `operator`.
);
_return(_toUint(result));
}
// `ownerOf(uint256)`.
if (fnSelector == 0x6352211e) {
_return(uint160(_ownerOf(_calldataload(0x04))));
}
// `ownerAt(uint256)`.
if (fnSelector == 0x24359879) {
_return(uint160(_ownerAt(_calldataload(0x04))));
}
// `approveNFT(address,uint256,address)`.
if (fnSelector == 0xd10b6e0c) {
if (msg.sender != $.mirrorERC721) revert SenderNotMirror();
address owner = _approveNFT(
address(uint160(_calldataload(0x04))), // `spender`.
_calldataload(0x24), // `id`.
address(uint160(_calldataload(0x44))) // `msgSender`.
);
_return(uint160(owner));
}
// `getApproved(uint256)`.
if (fnSelector == 0x081812fc) {
_return(uint160(_getApproved(_calldataload(0x04))));
}
// `balanceOfNFT(address)`.
if (fnSelector == 0xf5b100ea) {
_return(_balanceOfNFT(address(uint160(_calldataload(0x04)))));
}
// `totalNFTSupply()`.
if (fnSelector == 0xe2c79281) {
_return(_totalNFTSupply());
}
// `tokenURI(uint256)`.
if (fnSelector == 0xc87b56dd) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x40, add(mload(0x40), 0x20))
}
string memory uri = _tokenURI(_calldataload(0x04));
/// @solidity memory-safe-assembly
assembly {
// Memory safe, as we've advanced the free memory pointer by a word.
let o := sub(uri, 0x20)
mstore(o, 0x20) // Store the offset of `uri`.
return(o, add(0x60, mload(uri)))
}
}
// `implementsDN404()`.
if (fnSelector == 0xb7a94eb8) {
_return(1);
}
_;
}
/// @dev Fallback function for calls from mirror NFT contract.
/// Override this if you need to implement your custom
/// fallback with utilities like Solady's `LibZip.cdFallback()`.
/// And always remember to always wrap the fallback with `dn404Fallback`.
fallback() external payable virtual dn404Fallback {
revert FnSelectorNotRecognized(); // Not mandatory. Just for quality of life.
}
/// @dev This is to silence the compiler warning.
/// Override and remove the revert if you want your contract to receive ETH via receive.
receive() external payable virtual {
if (msg.value != 0) revert();
}
/*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/
/* INTERNAL / PRIVATE HELPERS */
/*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/
/// @dev Returns `(i - 1) << 1`.
function _ownershipIndex(uint256 i) internal pure returns (uint256) {
unchecked {
return (i - 1) << 1; // Minus 1 as token IDs start from 1.
}
}
/// @dev Returns `((i - 1) << 1) + 1`.
function _ownedIndex(uint256 i) internal pure returns (uint256) {
unchecked {
return ((i - 1) << 1) + 1; // Minus 1 as token IDs start from 1.
}
}
/// @dev Returns the uint32 value at `index` in `map`.
function _get(Uint32Map storage map, uint256 index) internal view returns (uint32 result) {
/// @solidity memory-safe-assembly
assembly {
let s := add(shl(96, map.slot), shr(3, index)) // Storage slot.
result := and(0xffffffff, shr(shl(5, and(index, 7)), sload(s)))
}
}
/// @dev Updates the uint32 value at `index` in `map`.
function _set(Uint32Map storage map, uint256 index, uint32 value) internal {
/// @solidity memory-safe-assembly
assembly {
let s := add(shl(96, map.slot), shr(3, index)) // Storage slot.
let o := shl(5, and(index, 7)) // Storage slot offset (bits).
let v := sload(s) // Storage slot value.
sstore(s, xor(v, shl(o, and(0xffffffff, xor(value, shr(o, v))))))
}
}
/// @dev Sets the owner alias and the owned index together.
function _setOwnerAliasAndOwnedIndex(
Uint32Map storage map,
uint256 id,
uint32 ownership,
uint32 ownedIndex
) internal {
/// @solidity memory-safe-assembly
assembly {
let i := sub(id, 1) // Index of the uint64 combined value.
let s := add(shl(96, map.slot), shr(2, i)) // Storage slot.
let v := sload(s) // Storage slot value.
let o := shl(6, and(i, 3)) // Storage slot offset (bits).
let combined := or(shl(32, ownedIndex), and(0xffffffff, ownership))
sstore(s, xor(v, shl(o, and(0xffffffffffffffff, xor(shr(o, v), combined)))))
}
}
/// @dev Returns the boolean value of the bit at `index` in `bitmap`.
function _get(Bitmap storage bitmap, uint256 index) internal view returns (bool result) {
/// @solidity memory-safe-assembly
assembly {
let s := add(shl(96, bitmap.slot), shr(8, index)) // Storage slot.
result := and(1, shr(and(0xff, index), sload(s)))
}
}
/// @dev Updates the bit at `index` in `bitmap` to `value`.
function _set(Bitmap storage bitmap, uint256 index, bool value) internal {
/// @solidity memory-safe-assembly
assembly {
let s := add(shl(96, bitmap.slot), shr(8, index)) // Storage slot.
let o := and(0xff, index) // Storage slot offset (bits).
sstore(s, or(and(sload(s), not(shl(o, 1))), shl(o, iszero(iszero(value)))))
}
}
/// @dev Returns the index of the least significant unset bit in `[begin..upTo]`.
/// If no set bit is found, returns `type(uint256).max`.
function _findFirstUnset(Bitmap storage bitmap, uint256 begin, uint256 upTo)
internal
view
returns (uint256 unsetBitIndex)
{
/// @solidity memory-safe-assembly
assembly {
unsetBitIndex := not(0) // Initialize to `type(uint256).max`.
let s := shl(96, bitmap.slot) // Storage offset of the bitmap.
let bucket := add(s, shr(8, begin))
let negBits := shl(and(0xff, begin), shr(and(0xff, begin), not(sload(bucket))))
if iszero(negBits) {
let lastBucket := add(s, shr(8, upTo))
for {} 1 {} {
bucket := add(bucket, 1)
negBits := not(sload(bucket))
if or(negBits, gt(bucket, lastBucket)) { break }
}
if gt(bucket, lastBucket) {
negBits := shr(and(0xff, not(upTo)), shl(and(0xff, not(upTo)), negBits))
}
}
if negBits {
// Find-first-set routine.
let b := and(negBits, add(not(negBits), 1)) // Isolate the least significant bit.
let r := shl(7, lt(0xffffffffffffffffffffffffffffffff, b))
r := or(r, shl(6, lt(0xffffffffffffffff, shr(r, b))))
r := or(r, shl(5, lt(0xffffffff, shr(r, b))))
// For the remaining 32 bits, use a De Bruijn lookup.
// forgefmt: disable-next-item
r := or(r, byte(and(div(0xd76453e0, shr(r, b)), 0x1f),
0x001f0d1e100c1d070f090b19131c1706010e11080a1a141802121b1503160405))
r := or(shl(8, sub(bucket, s)), r)
unsetBitIndex := or(r, sub(0, or(gt(r, upTo), lt(r, begin))))
}
}
}
/// @dev Returns a storage reference to the value at (`a0`, `a1`) in `map`.
function _ref(AddressPairToUint256RefMap storage map, address a0, address a1)
internal
pure
returns (Uint256Ref storage ref)
{
/// @solidity memory-safe-assembly
assembly {
mstore(0x28, a1)
mstore(0x14, a0)
mstore(0x00, map.slot)
ref.slot := keccak256(0x00, 0x48)
// Clear the part of the free memory pointer that was overwritten.
mstore(0x28, 0x00)
}
}
/// @dev Wraps the NFT ID.
function _wrapNFTId(uint256 id, uint256 maxId) internal pure returns (uint256 result) {
/// @solidity memory-safe-assembly
assembly {
result := or(mul(iszero(gt(id, maxId)), id), gt(id, maxId))
}
}
/// @dev Returns `id > type(uint32).max ? 0 : id`.
function _restrictNFTId(uint256 id) internal pure returns (uint256 result) {
/// @solidity memory-safe-assembly
assembly {
result := mul(id, lt(id, 0x100000000))
}
}
/// @dev Returns whether `amount` is a valid `totalSupply`.
function _totalSupplyOverflows(uint256 amount) internal view returns (bool result) {
uint256 unit = _unit();
/// @solidity memory-safe-assembly
assembly {
result := iszero(iszero(or(shr(96, amount), lt(0xfffffffe, div(amount, unit)))))
}
}
/// @dev Returns `max(0, x - y)`.
function _zeroFloorSub(uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
z := mul(gt(x, y), sub(x, y))
}
}
/// @dev Returns `x < y ? x : y`.
function _min(uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
z := xor(x, mul(xor(x, y), lt(y, x)))
}
}
/// @dev Returns `b ? 1 : 0`.
function _toUint(bool b) internal pure returns (uint256 result) {
/// @solidity memory-safe-assembly
assembly {
result := iszero(iszero(b))
}
}
/// @dev Returns `b == 0`. This is because solc is sometimes dumb.
function _isZero(uint256 x) internal pure returns (bool result) {
/// @solidity memory-safe-assembly
assembly {
result := iszero(x)
}
}
/// @dev Struct containing direct transfer log data for {Transfer} events to be
/// emitted by the mirror NFT contract.
struct _DNDirectLogs {
uint256 offset;
uint256[] logs;
}
/// @dev Initiates memory allocation for direct logs with `n` log items.
function _directLogsMalloc(uint256 n, address from, address to)
private
pure
returns (_DNDirectLogs memory p)
{
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40)
mstore(m, 0x144027d3) // `logDirectTransfer(address,address,uint256[])`.
mstore(add(m, 0x20), shr(96, shl(96, from)))
mstore(add(m, 0x40), shr(96, shl(96, to)))
mstore(add(m, 0x60), 0x60) // Offset of `logs` in the calldata to send.
// Skip 4 words: `fnSelector`, `from`, `to`, `calldataLogsOffset`.
let logs := add(0x80, m)
mstore(logs, n) // Store the length.
let offset := add(0x20, logs) // Skip the word for `p.logs.length`.
mstore(0x40, add(offset, shl(5, n))) // Allocate memory.
mstore(add(0x20, p), logs) // Set `p.logs`.
mstore(p, offset) // Set `p.offset`.
}
}
/// @dev Adds a direct log item to `p` with token `id`.
function _directLogsAppend(_DNDirectLogs memory p, uint256 id) private pure {
/// @solidity memory-safe-assembly
assembly {
let offset := mload(p)
mstore(offset, id)
mstore(p, add(offset, 0x20))
}
}
/// @dev Calls the `mirror` NFT contract to emit {Transfer} events for packed logs `p`.
function _directLogsSend(_DNDirectLogs memory p, address mirror) private {
/// @solidity memory-safe-assembly
assembly {
let logs := mload(add(p, 0x20))
let n := add(0x84, shl(5, mload(logs))) // Length of calldata to send.
let o := sub(logs, 0x80) // Start of calldata to send.
if iszero(and(eq(mload(o), 1), call(gas(), mirror, 0, add(o, 0x1c), n, o, 0x20))) {
revert(o, 0x00)
}
}
}
/// @dev Struct containing packed log data for {Transfer} events to be
/// emitted by the mirror NFT contract.
struct _DNPackedLogs {
uint256 offset;
uint256 addressAndBit;
uint256[] logs;
}
/// @dev Initiates memory allocation for packed logs with `n` log items.
function _packedLogsMalloc(uint256 n) private pure returns (_DNPackedLogs memory p) {
/// @solidity memory-safe-assembly
assembly {
// Note that `p` implicitly allocates and advances the free memory pointer by
// 3 words, which we can safely mutate in `_packedLogsSend`.
let logs := mload(0x40)
mstore(logs, n) // Store the length.
let offset := add(0x20, logs) // Skip the word for `p.logs.length`.
mstore(0x40, add(offset, shl(5, n))) // Allocate memory.
mstore(add(0x40, p), logs) // Set `p.logs`.
mstore(p, offset) // Set `p.offset`.
}
}
/// @dev Set the current address and the burn bit.
function _packedLogsSet(_DNPackedLogs memory p, address a, uint256 burnBit) private pure {
/// @solidity memory-safe-assembly
assembly {
mstore(add(p, 0x20), or(shl(96, a), burnBit)) // Set `p.addressAndBit`.
}
}
/// @dev Adds a packed log item to `p` with token `id`.
function _packedLogsAppend(_DNPackedLogs memory p, uint256 id) private pure {
/// @solidity memory-safe-assembly
assembly {
let offset := mload(p)
mstore(offset, or(mload(add(p, 0x20)), shl(8, id))) // `p.addressAndBit | (id << 8)`.
mstore(p, add(offset, 0x20))
}
}
/// @dev Calls the `mirror` NFT contract to emit {Transfer} events for packed logs `p`.
function _packedLogsSend(_DNPackedLogs memory p, address mirror) private {
/// @solidity memory-safe-assembly
assembly {
let logs := mload(add(p, 0x40))
let o := sub(logs, 0x40) // Start of calldata to send.
mstore(o, 0x263c69d6) // `logTransfer(uint256[])`.
mstore(add(o, 0x20), 0x20) // Offset of `logs` in the calldata to send.
let n := add(0x44, shl(5, mload(logs))) // Length of calldata to send.
if iszero(and(eq(mload(o), 1), call(gas(), mirror, 0, add(o, 0x1c), n, o, 0x20))) {
revert(o, 0x00)
}
}
}
/// @dev Struct of temporary variables for transfers.
struct _DNTransferTemps {
uint256 numNFTBurns;
uint256 numNFTMints;
uint256 fromOwnedLength;
uint256 toOwnedLength;
uint256 totalNFTSupply;
uint256 fromEnd;
uint256 toEnd;
uint32 toAlias;
uint256 nextTokenId;
uint32 burnedPoolTail;
}
/// @dev Struct of temporary variables for mints.
struct _DNMintTemps {
uint256 nextTokenId;
uint32 burnedPoolTail;
uint256 toEnd;
uint32 toAlias;
}
/// @dev Returns if `a` has bytecode of non-zero length.
function _hasCode(address a) private view returns (bool result) {
/// @solidity memory-safe-assembly
assembly {
result := extcodesize(a) // Can handle dirty upper bits.
}
}
/// @dev Returns the calldata value at `offset`.
function _calldataload(uint256 offset) private pure returns (uint256 value) {
/// @solidity memory-safe-assembly
assembly {
value := calldataload(offset)
}
}
/// @dev Executes a return opcode to return `x` and end the current call frame.
function _return(uint256 x) private pure {
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, x)
return(0x00, 0x20)
}
}
}{
"optimizer": {
"enabled": true,
"runs": 200
},
"evmVersion": "paris",
"outputSelection": {
"*": {
"*": [
"evm.bytecode",
"evm.deployedBytecode",
"devdoc",
"userdoc",
"metadata",
"abi"
]
}
},
"libraries": {}
}Contract ABI
API[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"AddressInsufficientBalance","type":"error"},{"inputs":[],"name":"ApprovalCallerNotOwnerNorApproved","type":"error"},{"inputs":[],"name":"DNAlreadyInitialized","type":"error"},{"inputs":[],"name":"DNNotInitialized","type":"error"},{"inputs":[],"name":"FailedInnerCall","type":"error"},{"inputs":[],"name":"FnSelectorNotRecognized","type":"error"},{"inputs":[],"name":"InsufficientAllowance","type":"error"},{"inputs":[],"name":"InsufficientBalance","type":"error"},{"inputs":[],"name":"InvalidInitialization","type":"error"},{"inputs":[],"name":"InvalidUnit","type":"error"},{"inputs":[],"name":"LinkMirrorContractFailed","type":"error"},{"inputs":[],"name":"MirrorAddressIsZero","type":"error"},{"inputs":[],"name":"NotInitializing","type":"error"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"OwnableInvalidOwner","type":"error"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"OwnableUnauthorizedAccount","type":"error"},{"inputs":[],"name":"ReentrancyGuardReentrantCall","type":"error"},{"inputs":[],"name":"SenderNotMirror","type":"error"},{"inputs":[],"name":"TokenDoesNotExist","type":"error"},{"inputs":[],"name":"TotalSupplyOverflow","type":"error"},{"inputs":[],"name":"TransferCallerNotOwnerNorApproved","type":"error"},{"inputs":[],"name":"TransferFromIncorrectOwner","type":"error"},{"inputs":[],"name":"TransferToZeroAddress","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"string","name":"newBaseUri","type":"string"}],"name":"BaseUriChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"buyer","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"price","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"protocolFee","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"creatorFee","type":"uint256"}],"name":"Buy","type":"event"},{"anonymous":false,"inputs":[],"name":"CreatorFeeFrozen","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"newCreatorFeePercentage","type":"uint256"}],"name":"CreatorFeePercentageChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"seller","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"price","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"protocolFee","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"creatorFee","type":"uint256"}],"name":"DeflationarySell","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint64","name":"version","type":"uint64"}],"name":"Initialized","type":"event"},{"anonymous":false,"inputs":[],"name":"MetadataFrozen","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":false,"internalType":"address","name":"from","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"PaymentReceived","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"seller","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"price","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"protocolFee","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"creatorFee","type":"uint256"}],"name":"Sell","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":false,"internalType":"bool","name":"status","type":"bool"}],"name":"SkipNFTSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"newTokenUriContract","type":"address"}],"name":"TokenUriContractChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[],"name":"WhitelistDisabled","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"newWhitelistMintLimit","type":"uint256"}],"name":"WhitelistMintLimitChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address[]","name":"addresses","type":"address[]"},{"indexed":false,"internalType":"bool","name":"isWhitelisted","type":"bool"}],"name":"WhitelistModified","type":"event"},{"stateMutability":"payable","type":"fallback"},{"inputs":[],"name":"BLAST","outputs":[{"internalType":"contract IBlast","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_NAME_LENGTH","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_NFT_SUPPLY","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_SYMBOL_LENGTH","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_UNITS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"baseUri","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"blastPoints","outputs":[{"internalType":"contract IBlastPoints","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"bondingCurveFreeze","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"bondingCurveSpecs","outputs":[{"internalType":"uint256","name":"factor","type":"uint256"},{"internalType":"uint256","name":"exponent","type":"uint256"},{"internalType":"uint256","name":"c","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"bondingCurveSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"nftAmount","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"buy","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"recipientOfGas","type":"address"}],"name":"claimAllGas","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"recipientOfYield","type":"address"}],"name":"claimAllYield","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"recipientOfGas","type":"address"},{"internalType":"uint256","name":"gasToClaim","type":"uint256"},{"internalType":"uint256","name":"gasSecondsToConsume","type":"uint256"}],"name":"claimGas","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"recipientOfGas","type":"address"},{"internalType":"uint256","name":"minClaimRateBips","type":"uint256"}],"name":"claimGasAtMinClaimRate","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"recipientOfGas","type":"address"}],"name":"claimMaxGas","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"recipientOfYield","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"claimYield","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"creatorFeePercentage","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"creatorVault","outputs":[{"internalType":"address payable","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"deflationBurnCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"tokenIds_","type":"uint256[]"}],"name":"deflationarySell","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"disableWhitelist","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"freezeCreatorFee","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"freezeMetadata","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"gasClaimer","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount_","type":"uint256"}],"name":"getBuyPriceExclusiveFees","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount_","type":"uint256"}],"name":"getBuyPriceInclFees","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"supply","type":"uint256"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"getPrice","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getProtocolFeePercentage","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount_","type":"uint256"}],"name":"getSellPriceExclusiveFees","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount_","type":"uint256"}],"name":"getSellPriceInclFees","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"getSkipNFT","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"components":[{"internalType":"string","name":"name_","type":"string"},{"internalType":"string","name":"symbol_","type":"string"},{"internalType":"uint256","name":"units_","type":"uint256"},{"internalType":"string","name":"baseUri_","type":"string"},{"internalType":"address","name":"tokenUriContract_","type":"address"},{"internalType":"uint256","name":"maxNftSupply_","type":"uint256"},{"internalType":"bool","name":"useWhitelist_","type":"bool"},{"internalType":"uint256","name":"whitelistMintLimit_","type":"uint256"},{"internalType":"bool","name":"isDeflationary_","type":"bool"}],"internalType":"struct CollectionSpecs","name":"collectionSpecs","type":"tuple"},{"internalType":"address","name":"tokenUriFallbackContract","type":"address"},{"components":[{"internalType":"uint256","name":"factor","type":"uint256"},{"internalType":"uint256","name":"exponent","type":"uint256"},{"internalType":"uint256","name":"c","type":"uint256"}],"internalType":"struct BondingCurveSpecs","name":"bondingCurveSpecs","type":"tuple"},{"internalType":"address","name":"creatorVault","type":"address"},{"internalType":"address","name":"creator","type":"address"},{"internalType":"address","name":"router","type":"address"},{"internalType":"address","name":"gasClaimer","type":"address"},{"internalType":"address","name":"yieldClaimer","type":"address"},{"internalType":"address","name":"pointsOperator","type":"address"},{"internalType":"contract IBlastPoints","name":"blastPoints","type":"address"},{"internalType":"address","name":"dn404Mirror","type":"address"}],"internalType":"struct ICollectionImpl.InitializeParams","name":"params","type":"tuple"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"isDeflationary","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"isWhitelistEnabled","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"maxSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"metadataFreeze","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"mirrorERC721","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address[]","name":"addresses_","type":"address[]"},{"internalType":"bool","name":"isWhitelisted_","type":"bool"}],"name":"modifyWhitelist","outputs":[],"stateMutability":"nonpayable","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":[],"name":"pointsOperator","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"router","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"tokenIds_","type":"uint256[]"},{"internalType":"uint256","name":"minPrice_","type":"uint256"},{"internalType":"uint256","name":"deadline_","type":"uint256"}],"name":"sell","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"baseUri_","type":"string"}],"name":"setBaseUri","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract IBlastPoints","name":"blastPoints_","type":"address"}],"name":"setBlastPoints","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"creatorFeePercentage_","type":"uint256"}],"name":"setCreatorFee","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"gasClaimer_","type":"address"}],"name":"setGasClaimer","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"pointsOperator_","type":"address"}],"name":"setPointsOperator","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bool","name":"skipNFT","type":"bool"}],"name":"setSkipNFT","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"tokenUriContract_","type":"address"}],"name":"setTokenUriContract","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"whitelistMintLimit_","type":"uint256"}],"name":"setWhitelistMintLimit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"yieldClaimer_","type":"address"}],"name":"setYieldClaimer","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"tokenUriContract","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"tokenUriFallbackContract","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"units","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"whitelist","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"whitelistMintLimit","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"whitelistMinted","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"yieldClaimer","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"stateMutability":"payable","type":"receive"}]Loading...
Loading
Loading...
Loading
Loading...
Loading
Net Worth in USD
$2,784.84
Net Worth in ETH
0.9888
Token Allocations
ETH
100.00%
Multichain Portfolio | 35 Chains
| Chain | Token | Portfolio % | Price | Amount | Value |
|---|---|---|---|---|---|
| BLAST | 100.00% | $2,816.39 | 0.9888 | $2,784.84 |
Loading...
Loading
Loading...
Loading
Loading...
Loading
[ Download: CSV Export ]
[ Download: CSV Export ]
A contract address hosts a smart contract, which is a set of code stored on the blockchain that runs when predetermined conditions are met. Learn more about addresses in our Knowledge Base.