Description:
Multi-signature wallet contract requiring multiple confirmations for transaction execution.
Blockchain: Ethereum
Source Code: View Code On The Blockchain
Solidity Source Code:
{{
"language": "Solidity",
"sources": {
"contracts/Glyfoc721Poly.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
import {ERC721} from "solady/src/tokens/ERC721.sol";
import {Ownable} from "solady/src/auth/Ownable.sol";
import {SSTORE2} from "solady/src/utils/SSTORE2.sol";
import {Base64} from "solady/src/utils/Base64.sol";
import {LibString} from "solady/src/utils/LibString.sol";
import {ReentrancyGuard} from "solady/src/utils/ReentrancyGuard.sol";
interface IERC2981 {
function royaltyInfo(uint256, uint256) external view returns (address, uint256);
}
contract Glyfoc721Poly is ERC721, Ownable, ReentrancyGuard {
using LibString for uint256;
address public immutable OFFICIAL_FACTORY;
bool public initialized;
uint256 public MAX_SUPPLY;
uint256 public mintPriceWei;
uint256 public totalMinted;
uint256 public maxPerTx;
address public platformFeeRecipient;
uint96 public platformFeeBps;
uint16 public fontSize;
uint16 public lineGap;
uint16 public viewBoxW;
uint16 public viewBoxH;
bytes32 public salt;
address public layersPtr;
address public orderPtr;
address public palettePtr;
string public bgColor;
string internal _nameHuman;
string internal _symbolHuman;
string internal _description;
bool public royaltyEnabled;
address public royaltyReceiver;
uint96 public royaltyBps;
mapping(bytes32 => bool) public comboUsed;
event SupplyFinalized(uint256 newMaxSupply);
event RoyaltySet(address indexed receiver, uint96 bps);
struct DeployParams {
string name;
string symbol;
string description;
uint256 maxSupply;
uint256 mintPriceWei;
uint256 maxPerTx;
uint16 fontSize;
uint16 lineGap;
uint16 viewBoxW;
uint16 viewBoxH;
bytes32 salt;
address owner;
bool royaltyEnabled;
address royaltyReceiver;
uint96 royaltyBps;
address layersPtr;
address orderPtr;
string bgColor;
address palettePtr;
}
modifier onlyFactory() {
require(msg.sender == OFFICIAL_FACTORY, "factory");
_;
}
constructor(address officialFactory) {
require(officialFactory != address(0), "factory=0");
OFFICIAL_FACTORY = officialFactory;
initialized = true;
_initializeOwner(address(this));
}
function initialize(
DeployParams calldata p,
address _platformFeeRecipient,
uint96 _platformFeeBps
) external onlyFactory {
require(!initialized, "init");
initialized = true;
require(p.layersPtr != address(0) && p.orderPtr != address(0), "data");
require(_platformFeeBps <= 10_000, "fee");
_nameHuman = p.name;
_symbolHuman = p.symbol;
_description = p.description;
MAX_SUPPLY = p.maxSupply;
mintPriceWei = p.mintPriceWei;
maxPerTx = p.maxPerTx == 0 ? 20 : p.maxPerTx;
fontSize = p.fontSize == 0 ? 64 : p.fontSize;
lineGap = p.lineGap == 0 ? 80 : p.lineGap;
viewBoxW = p.viewBoxW == 0 ? 800 : p.viewBoxW;
viewBoxH = p.viewBoxH == 0 ? 600 : p.viewBoxH;
salt = p.salt;
layersPtr = p.layersPtr;
orderPtr = p.orderPtr;
bgColor = bytes(p.bgColor).length == 0 ? "#0b0b10" : p.bgColor;
require(_isHexColor(bgColor), "bgColor");
palettePtr = p.palettePtr;
platformFeeRecipient = _platformFeeRecipient;
platformFeeBps = _platformFeeBps;
royaltyEnabled = p.royaltyEnabled;
royaltyReceiver= p.royaltyReceiver;
royaltyBps = p.royaltyBps;
_initializeOwner(p.owner == address(0) ? tx.origin : p.owner);
}
function mint(uint256 quantity) external payable nonReentrant {
require(quantity > 0 && quantity <= maxPerTx, "qty");
require(totalMinted + quantity <= MAX_SUPPLY, "soldout");
require(msg.value / quantity == mintPriceWei && msg.value % quantity == 0, "value");
uint256 platform = (msg.value * platformFeeBps) / 10_000;
uint256 creator = msg.value - platform;
for (uint256 i = 0; i < quantity; ++i) {
unchecked { ++totalMinted; }
uint256 tokenId = totalMinted;
bytes32 fp = _fingerprint(tokenId);
require(!comboUsed[fp], "duplicate");
comboUsed[fp] = true;
_safeMint(msg.sender, tokenId);
}
_payout(platformFeeRecipient, platform);
_payout(owner(), creator);
}
function airdrop(address[] calldata to) external onlyOwner {
uint256 n = to.length;
require(totalMinted + n <= MAX_SUPPLY, "soldout");
for (uint256 i = 0; i < n; ++i) {
unchecked { ++totalMinted; }
uint256 tokenId = totalMinted;
bytes32 fp = _fingerprint(tokenId);
require(!comboUsed[fp], "duplicate");
comboUsed[fp] = true;
_mint(to[i], tokenId);
}
}
function withdraw(address payable to) external onlyOwner nonReentrant {
require(to != address(0), "zero");
_payout(to, address(this).balance);
}
function finishMint() external onlyOwner {
require(initialized, "Not initialized");
require(MAX_SUPPLY >= totalMinted, "Invariant");
if (MAX_SUPPLY != totalMinted) {
MAX_SUPPLY = totalMinted;
}
emit SupplyFinalized(MAX_SUPPLY);
}
function name() public view override returns (string memory) { return _nameHuman; }
function symbol() public view override returns (string memory) { return _symbolHuman; }
function tokenURI(uint256 tokenId) public view override returns (string memory) {
require(_exists(tokenId), "id");
bytes32 h = _hash(tokenId);
(string[] memory layerNames, uint16[] memory variantIdx, string memory art) = _assemble(tokenId, h);
string memory svg = _buildSVG_perRowColors(art, h);
(string[] memory colors, uint16 paletteSize) = _paletteStats(art, h);
string memory attrs = _attributesWithPalette(layerNames, variantIdx, "Poly Palette", colors, paletteSize);
string memory title = string.concat(_nameHuman, " #", tokenId.toString());
bytes memory json = abi.encodePacked(
'{"name":"', title,
'","description":"', _description,
'","attributes":', attrs,
',"image":"data:image/svg+xml;base64,', Base64.encode(bytes(svg)),
'"}'
);
return string(abi.encodePacked("data:application/json;base64,", Base64.encode(json)));
}
function setRoyalty(address receiver, uint96 bps) external onlyOwner {
require(bps <= 10_000, "royalty bps");
royaltyEnabled = true;
royaltyReceiver = receiver;
royaltyBps = bps;
emit RoyaltySet(receiver, bps);
}
function royaltyInfo(uint256, uint256 salePrice) public view returns (address, uint256) {
if (!royaltyEnabled) return (address(0), 0);
address recv = royaltyReceiver == address(0) ? owner() : royaltyReceiver;
return (recv, (salePrice * royaltyBps) / 10_000);
}
function supportsInterface(bytes4 iid) public view virtual override returns (bool) {
bytes4 ERC2981ID = 0x2a55205a;
return super.supportsInterface(iid) || (royaltyEnabled && iid == ERC2981ID);
}
function _assemble(uint256 tokenId, bytes32) internal view returns (string[] memory layerNames, uint16[] memory variantIdx, string memory art) {
uint16[] memory order = _readOrder();
(uint16 L,,) = _layersCounts();
require(order.length == L, "order");
layerNames = new string[](L);
variantIdx = new uint16[](L);
string[] memory lines = new string[](L);
bytes memory b = SSTORE2.read(layersPtr);
uint256 off = 2;
uint256[] memory layerStart = new uint256[](L);
uint16[] memory vCount = new uint16[](L);
for (uint16 i = 0; i < L; ++i) {
layerStart[i] = off;
uint16 nameLen = _u16(b, off); off += 2;
layerNames[i] = string(_slice(b, off, nameLen)); off += nameLen;
uint16 V = _u16(b, off); off += 2;
vCount[i] = V;
for (uint16 j = 0; j < V; ++j) {
uint16 len = _u16(b, off); off += 2 + len;
}
}
uint256 M = 1;
for (uint16 pos = 0; pos < L; ++pos) {
uint16 V = vCount[order[pos]];
M *= (V == 0 ? 1 : V);
}
uint256 u = _comboIndex(tokenId, M);
for (uint16 pos = 0; pos < L; ++pos) {
uint16 li = order[pos];
uint16 V = vCount[li];
uint256 o = layerStart[li];
uint16 nameLen2 = _u16(b, o); o += 2 + nameLen2;
uint16 V2 = _u16(b, o); o += 2;
require(V2 == V, "v");
uint16 pick;
if (V == 0) {
pick = 0;
} else {
pick = uint16(u % V);
u /= V;
}
for (uint16 j = 0; j < V; ++j) {
uint16 len = _u16(b, o); o += 2;
bytes memory ln = _slice(b, o, len); o += len;
if (j == pick) lines[pos] = string(ln);
}
variantIdx[pos] = pick;
}
art = _joinLines(lines);
}
function _fingerprint(uint256 tokenId) internal view returns (bytes32) {
(uint16 L,,) = _layersCounts();
uint16[] memory order = _readOrder();
bytes memory b = SSTORE2.read(layersPtr);
uint256 off = 2;
uint16[] memory vCounts = new uint16[](L);
for (uint16 i = 0; i < L; ++i) {
uint16 nameLen = _u16(b, off); off += 2 + nameLen;
uint16 V = _u16(b, off); off += 2; vCounts[i] = V;
for (uint16 j = 0; j < V; ++j) {
uint16 len = _u16(b, off); off += 2 + len;
}
}
uint256 M = 1;
uint16[] memory bases = new uint16[](order.length);
for (uint16 k = 0; k < order.length; ++k) {
uint16 li = order[k];
uint16 base = (vCounts[li] == 0) ? 1 : vCounts[li];
bases[k] = base;
M *= uint256(base);
}
uint256 u = _comboIndex(tokenId, M);
bytes memory buf = new bytes(order.length * 2);
for (uint16 k = 0; k < order.length; ++k) {
uint16 base = bases[k];
uint16 pick = (base == 1) ? 0 : uint16(u % base);
_setU16(buf, k * 2, pick);
u /= base;
}
return keccak256(buf);
}
function _isHexColor(string memory s) internal pure returns (bool) {
bytes memory b = bytes(s);
if (b.length != 4 && b.length != 7 && b.length != 9) return false;
if (b[0] != "#") return false;
for (uint i = 1; i < b.length; ++i) {
bytes1 c = b[i];
bool ok = (c >= "0" && c <= "9") || (c >= "a" && c <= "f") || (c >= "A" && c <= "F");
if (!ok) return false;
}
return true;
}
function _palettePick(bytes32 h, uint256 rowIndex) internal view returns (string memory fg) {
if (palettePtr == address(0)) return "#ffffff";
bytes memory t = SSTORE2.read(palettePtr);
if (t.length < 2) return "#ffffff";
uint16 C = _u16(t, 0);
if (C == 0) return "#ffffff";
uint256 off = 2;
uint256 totalW;
uint16[] memory weights = new uint16[](C);
uint256[] memory colorOff = new uint256[](C);
uint16[] memory colorLen = new uint16[](C);
for (uint16 i = 0; i < C; ++i) {
uint16 w = _u16(t, off); off += 2;
weights[i] = w;
totalW += w;
uint16 len = _u16(t, off); off += 2;
colorLen[i] = len;
colorOff[i] = off;
off += len;
}
if (totalW == 0) {
uint256 idx0 = uint256(_r(h, 5000 + rowIndex)) % C;
string memory c0 = string(_slice(t, colorOff[uint16(idx0)], colorLen[uint16(idx0)]));
return _isHexColor(c0) ? c0 : "#ffffff";
}
uint256 r = uint256(_r(h, 6000 + rowIndex)) % totalW;
uint256 acc;
for (uint16 i = 0; i < C; ++i) {
acc += weights[i];
if (r < acc) {
string memory cx = string(_slice(t, colorOff[i], colorLen[i]));
return _isHexColor(cx) ? cx : "#ffffff";
}
}
string memory cl = string(_slice(t, colorOff[C - 1], colorLen[C - 1]));
return _isHexColor(cl) ? cl : "#ffffff";
}
function _buildSVG_perRowColors(string memory multiline, bytes32 h) internal view returns (string memory) {
string memory style = string.concat(
"text{font-family:ui-monospace,Menlo,Consolas,monospace;font-size:",
uint256(fontSize).toString(),
"px;white-space:pre;text-anchor:middle;dominant-baseline:central}"
);
bytes memory b = bytes(_xmlEscape(multiline));
uint256 rows;
for (uint256 k = 0; k <= b.length; ) {
bool endLine = (k == b.length) || (b[k] == 0x0a);
if (endLine) { unchecked { ++rows; } }
if (k == b.length) break;
unchecked { ++k; }
}
if (rows == 0) rows = 1;
uint256 startY = (viewBoxH / 2) - (((rows - 1) * lineGap) / 2);
uint256 i; uint256 lineStart; uint256 row;
bytes memory tspans;
while (i <= b.length) {
bool endLine = (i == b.length) || (b[i] == 0x0a);
if (endLine) {
bytes memory slice = new bytes(i - lineStart);
for (uint256 j = 0; j < slice.length; ++j) {
slice[j] = b[lineStart + j];
}
string memory fill = _palettePick(h, row);
tspans = abi.encodePacked(
tspans,
'<tspan x="', uint256(viewBoxW / 2).toString(),
'" dy="', row == 0 ? "0" : uint256(lineGap).toString(),
'" fill="', fill, '">', string(slice), "</tspan>"
);
lineStart = i + 1;
unchecked { ++row; }
}
unchecked { ++i; }
}
return string(
abi.encodePacked(
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ',
uint256(viewBoxW).toString(),' ',uint256(viewBoxH).toString(),
'"><rect width="100%" height="100%" fill="', bgColor, '"/>',
"<style>", style, "</style>",
'<text x="', uint256(viewBoxW / 2).toString(),
'" y="', startY.toString(), '">',
string(tspans),
"</text></svg>"
)
);
}
function _attributes(string[] memory layerNames, uint16[] memory variantIdx, string memory themeName)
internal
pure
returns (string memory)
{
bytes memory out = abi.encodePacked('[');
for (uint256 i = 0; i < layerNames.length; ++i) {
if (i != 0) out = abi.encodePacked(out, ',');
out = abi.encodePacked(
out,
'{"trait_type":"', layerNames[i],
'","value":"#', LibString.toString(uint256(variantIdx[i] + 1)), '"}'
);
}
out = abi.encodePacked(out, ',{"trait_type":"Theme","value":"', themeName, '"}]');
return string(out);
}
function _paletteStats(string memory multiline, bytes32 h)
internal
view
returns (string[] memory uniqColors, uint16 uniqCount)
{
bytes memory b = bytes(multiline);
uint16 rows;
for (uint256 i = 0; i < b.length; ++i) {
if (b[i] == 0x0a) rows++;
}
if (b.length != 0 && (b[b.length - 1] != 0x0a)) {
rows++;
}
string[] memory colors = new string[](rows);
bytes32[] memory seen = new bytes32[](rows);
uint16 count;
for (uint16 r = 0; r < rows; ++r) {
string memory c = _palettePick(h, r);
bytes32 hc = keccak256(bytes(c));
bool exists;
for (uint16 k = 0; k < count; ++k) {
if (seen[k] == hc) { exists = true; break; }
}
if (!exists) {
seen[count] = hc;
colors[count] = c;
count++;
}
}
uniqColors = new string[](count);
for (uint16 i = 0; i < count; ++i) {
uniqColors[i] = colors[i];
}
uniqCount = count;
}
function _attributesWithPalette(
string[] memory layerNames,
uint16[] memory variantIdx,
string memory themeName,
string[] memory colors,
uint16 paletteSize
)
internal
pure
returns (string memory)
{
bytes memory out = abi.encodePacked('[');
for (uint256 i = 0; i < layerNames.length; ++i) {
if (i != 0) out = abi.encodePacked(out, ',');
out = abi.encodePacked(
out,
'{"trait_type":"', layerNames[i],
'","value":"#', LibString.toString(uint256(variantIdx[i] + 1)), '"}'
);
}
out = abi.encodePacked(out, ',{"trait_type":"Theme","value":"', themeName, '"}');
out = abi.encodePacked(out, ',{"trait_type":"Palette Size","value":"', LibString.toString(uint256(paletteSize)), '"}');
for (uint256 i = 0; i < colors.length; ++i) {
out = abi.encodePacked(out, ',{"trait_type":"Color","value":"', colors[i], '"}');
}
out = abi.encodePacked(out, ']');
return string(out);
}
function _hash(uint256 tokenId) internal view returns (bytes32) {
return keccak256(abi.encodePacked(salt, tokenId));
}
function _r(bytes32 h, uint256 n) internal pure returns (bytes32) {
return keccak256(abi.encodePacked(h, n));
}
function _payout(address to, uint256 amount) internal {
if (amount == 0) return;
(bool ok,) = to.call{value: amount}("");
require(ok, "pay");
}
function _layersCounts() internal view returns (uint16 L, uint32 totalVariants, uint16 maxV) {
bytes memory b = SSTORE2.read(layersPtr);
L = _u16(b, 0);
uint256 off = 2;
for (uint16 i = 0; i < L; ++i) {
uint16 nameLen = _u16(b, off); off += 2 + nameLen;
uint16 V = _u16(b, off); off += 2;
if (V > maxV) maxV = V;
totalVariants += V;
for (uint16 j = 0; j < V; ++j) {
uint16 len = _u16(b, off); off += 2 + len;
}
}
}
function _readOrder() internal view returns (uint16[] memory order) {
bytes memory b = SSTORE2.read(orderPtr);
uint16 N = _u16(b, 0);
order = new uint16[](N);
uint256 off = 2;
for (uint16 i = 0; i < N; ++i) { order[i] = _u16(b, off); off += 2; }
}
function _gcd(uint256 x, uint256 y) internal pure returns (uint256) {
while (y != 0) { (x, y) = (y, x % y); }
return x;
}
function _deriveAffine(bytes32 s, uint256 M) internal pure returns (uint256 _a, uint256 _b) {
if (M <= 1) { return (1, 0); }
_a = 1 + (uint256(keccak256(abi.encode(s, "A"))) % (M - 1));
while (_gcd(_a, M) != 1) {
unchecked { _a++; if (_a >= M) _a = 1; }
}
_b = uint256(keccak256(abi.encode(s, "B"))) % M;
}
function _comboIndex(uint256 tokenId, uint256 M) internal view returns (uint256) {
if (M == 0) return 0;
uint256 x = tokenId - 1;
(uint256 a, uint256 b) = _deriveAffine(salt, M);
unchecked { return (a * x + b) % M; }
}
function _u16(bytes memory b, uint256 off) internal pure returns (uint16 v) {
v = (uint16(uint8(b[off])) << 8) | uint16(uint8(b[off + 1]));
}
function _setU16(bytes memory b, uint256 off, uint16 v) internal pure {
b[off] = bytes1(uint8(v >> 8));
b[off + 1] = bytes1(uint8(v));
}
function _slice(bytes memory b, uint256 off, uint256 len) internal pure returns (bytes memory out) {
out = new bytes(len);
for (uint256 i = 0; i < len; ++i) { out[i] = b[off + i]; }
}
function _joinLines(string[] memory lines) internal pure returns (string memory out) {
for (uint256 i = 0; i < lines.length; ++i) {
if (i != 0) out = string(abi.encodePacked(out, "\
", lines[i]));
else out = lines[i];
}
}
function _xmlEscape(string memory s) internal pure returns (string memory) {
bytes memory b = bytes(s);
bytes memory tmp = new bytes(b.length * 5);
uint256 k;
for (uint256 i = 0; i < b.length; ++i) {
bytes1 c = b[i];
if (c == 0x26) { tmp[k++] = 0x26; tmp[k++] = 0x61; tmp[k++] = 0x6D; tmp[k++] = 0x70; tmp[k++] = 0x3B; }
else if (c == 0x3C) { tmp[k++] = 0x26; tmp[k++] = 0x6C; tmp[k++] = 0x74; tmp[k++] = 0x3B; }
else if (c == 0x3E) { tmp[k++] = 0x26; tmp[k++] = 0x67; tmp[k++] = 0x74; tmp[k++] = 0x3B; }
else { tmp[k++] = c; }
}
bytes memory out = new bytes(k);
for (uint256 i = 0; i < k; ++i) out[i] = tmp[i];
return string(out);
}
function _baseURI() internal view virtual returns (string memory) { return ""; }
}
"
},
"solady/src/auth/Ownable.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
/// @notice Simple single owner authorization mixin.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/auth/Ownable.sol)
///
/// @dev Note:
/// This implementation does NOT auto-initialize the owner to `msg.sender`.
/// You MUST call the `_initializeOwner` in the constructor / initializer.
///
/// While the ownable portion follows
/// [EIP-173](https://eips.ethereum.org/EIPS/eip-173) for compatibility,
/// the nomenclature for the 2-step ownership handover may be unique to this codebase.
abstract contract Ownable {
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CUSTOM ERRORS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev The caller is not authorized to call the function.
error Unauthorized();
/// @dev The `newOwner` cannot be the zero address.
error NewOwnerIsZeroAddress();
/// @dev The `pendingOwner` does not have a valid handover request.
error NoHandoverRequest();
/// @dev Cannot double-initialize.
error AlreadyInitialized();
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* EVENTS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev The ownership is transferred from `oldOwner` to `newOwner`.
/// This event is intentionally kept the same as OpenZeppelin's Ownable to be
/// compatible with indexers and [EIP-173](https://eips.ethereum.org/EIPS/eip-173),
/// despite it not being as lightweight as a single argument event.
event OwnershipTransferred(address indexed oldOwner, address indexed newOwner);
/// @dev An ownership handover to `pendingOwner` has been requested.
event OwnershipHandoverRequested(address indexed pendingOwner);
/// @dev The ownership handover to `pendingOwner` has been canceled.
event OwnershipHandoverCanceled(address indexed pendingOwner);
/// @dev `keccak256(bytes("OwnershipTransferred(address,address)"))`.
uint256 private constant _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE =
0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0;
/// @dev `keccak256(bytes("OwnershipHandoverRequested(address)"))`.
uint256 private constant _OWNERSHIP_HANDOVER_REQUESTED_EVENT_SIGNATURE =
0xdbf36a107da19e49527a7176a1babf963b4b0ff8cde35ee35d6cd8f1f9ac7e1d;
/// @dev `keccak256(bytes("OwnershipHandoverCanceled(address)"))`.
uint256 private constant _OWNERSHIP_HANDOVER_CANCELED_EVENT_SIGNATURE =
0xfa7b8eab7da67f412cc9575ed43464468f9bfbae89d1675917346ca6d8fe3c92;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* STORAGE */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev The owner slot is given by:
/// `bytes32(~uint256(uint32(bytes4(keccak256("_OWNER_SLOT_NOT")))))`.
/// It is intentionally chosen to be a high value
/// to avoid collision with lower slots.
/// The choice of manual storage layout is to enable compatibility
/// with both regular and upgradeable contracts.
bytes32 internal constant _OWNER_SLOT =
0xffffffffffffffffffffffffffffffffffffffffffffffffffffffff74873927;
/// The ownership handover slot of `newOwner` is given by:
/// ```
/// mstore(0x00, or(shl(96, user), _HANDOVER_SLOT_SEED))
/// let handoverSlot := keccak256(0x00, 0x20)
/// ```
/// It stores the expiry timestamp of the two-step ownership handover.
uint256 private constant _HANDOVER_SLOT_SEED = 0x389a75e1;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* INTERNAL FUNCTIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Override to return true to make `_initializeOwner` prevent double-initialization.
function _guardInitializeOwner() internal pure virtual returns (bool guard) {}
/// @dev Initializes the owner directly without authorization guard.
/// This function must be called upon initialization,
/// regardless of whether the contract is upgradeable or not.
/// This is to enable generalization to both regular and upgradeable contracts,
/// and to save gas in case the initial owner is not the caller.
/// For performance reasons, this function will not check if there
/// is an existing owner.
function _initializeOwner(address newOwner) internal virtual {
if (_guardInitializeOwner()) {
/// @solidity memory-safe-assembly
assembly {
let ownerSlot := _OWNER_SLOT
if sload(ownerSlot) {
mstore(0x00, 0x0dc149f0) // `AlreadyInitialized()`.
revert(0x1c, 0x04)
}
// Clean the upper 96 bits.
newOwner := shr(96, shl(96, newOwner))
// Store the new value.
sstore(ownerSlot, or(newOwner, shl(255, iszero(newOwner))))
// Emit the {OwnershipTransferred} event.
log3(0, 0, _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE, 0, newOwner)
}
} else {
/// @solidity memory-safe-assembly
assembly {
// Clean the upper 96 bits.
newOwner := shr(96, shl(96, newOwner))
// Store the new value.
sstore(_OWNER_SLOT, newOwner)
// Emit the {OwnershipTransferred} event.
log3(0, 0, _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE, 0, newOwner)
}
}
}
/// @dev Sets the owner directly without authorization guard.
function _setOwner(address newOwner) internal virtual {
if (_guardInitializeOwner()) {
/// @solidity memory-safe-assembly
assembly {
let ownerSlot := _OWNER_SLOT
// Clean the upper 96 bits.
newOwner := shr(96, shl(96, newOwner))
// Emit the {OwnershipTransferred} event.
log3(0, 0, _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE, sload(ownerSlot), newOwner)
// Store the new value.
sstore(ownerSlot, or(newOwner, shl(255, iszero(newOwner))))
}
} else {
/// @solidity memory-safe-assembly
assembly {
let ownerSlot := _OWNER_SLOT
// Clean the upper 96 bits.
newOwner := shr(96, shl(96, newOwner))
// Emit the {OwnershipTransferred} event.
log3(0, 0, _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE, sload(ownerSlot), newOwner)
// Store the new value.
sstore(ownerSlot, newOwner)
}
}
}
/// @dev Throws if the sender is not the owner.
function _checkOwner() internal view virtual {
/// @solidity memory-safe-assembly
assembly {
// If the caller is not the stored owner, revert.
if iszero(eq(caller(), sload(_OWNER_SLOT))) {
mstore(0x00, 0x82b42900) // `Unauthorized()`.
revert(0x1c, 0x04)
}
}
}
/// @dev Returns how long a two-step ownership handover is valid for in seconds.
/// Override to return a different value if needed.
/// Made internal to conserve bytecode. Wrap it in a public function if needed.
function _ownershipHandoverValidFor() internal view virtual returns (uint64) {
return 48 * 3600;
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* PUBLIC UPDATE FUNCTIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Allows the owner to transfer the ownership to `newOwner`.
function transferOwnership(address newOwner) public payable virtual onlyOwner {
/// @solidity memory-safe-assembly
assembly {
if iszero(shl(96, newOwner)) {
mstore(0x00, 0x7448fbae) // `NewOwnerIsZeroAddress()`.
revert(0x1c, 0x04)
}
}
_setOwner(newOwner);
}
/// @dev Allows the owner to renounce their ownership.
function renounceOwnership() public payable virtual onlyOwner {
_setOwner(address(0));
}
/// @dev Request a two-step ownership handover to the caller.
/// The request will automatically expire in 48 hours (172800 seconds) by default.
function requestOwnershipHandover() public payable virtual {
unchecked {
uint256 expires = block.timestamp + _ownershipHandoverValidFor();
/// @solidity memory-safe-assembly
assembly {
// Compute and set the handover slot to `expires`.
mstore(0x0c, _HANDOVER_SLOT_SEED)
mstore(0x00, caller())
sstore(keccak256(0x0c, 0x20), expires)
// Emit the {OwnershipHandoverRequested} event.
log2(0, 0, _OWNERSHIP_HANDOVER_REQUESTED_EVENT_SIGNATURE, caller())
}
}
}
/// @dev Cancels the two-step ownership handover to the caller, if any.
function cancelOwnershipHandover() public payable virtual {
/// @solidity memory-safe-assembly
assembly {
// Compute and set the handover slot to 0.
mstore(0x0c, _HANDOVER_SLOT_SEED)
mstore(0x00, caller())
sstore(keccak256(0x0c, 0x20), 0)
// Emit the {OwnershipHandoverCanceled} event.
log2(0, 0, _OWNERSHIP_HANDOVER_CANCELED_EVENT_SIGNATURE, caller())
}
}
/// @dev Allows the owner to complete the two-step ownership handover to `pendingOwner`.
/// Reverts if there is no existing ownership handover requested by `pendingOwner`.
function completeOwnershipHandover(address pendingOwner) public payable virtual onlyOwner {
/// @solidity memory-safe-assembly
assembly {
// Compute and set the handover slot to 0.
mstore(0x0c, _HANDOVER_SLOT_SEED)
mstore(0x00, pendingOwner)
let handoverSlot := keccak256(0x0c, 0x20)
// If the handover does not exist, or has expired.
if gt(timestamp(), sload(handoverSlot)) {
mstore(0x00, 0x6f5e8818) // `NoHandoverRequest()`.
revert(0x1c, 0x04)
}
// Set the handover slot to 0.
sstore(handoverSlot, 0)
}
_setOwner(pendingOwner);
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* PUBLIC READ FUNCTIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Returns the owner of the contract.
function owner() public view virtual returns (address result) {
/// @solidity memory-safe-assembly
assembly {
result := sload(_OWNER_SLOT)
}
}
/// @dev Returns the expiry timestamp for the two-step ownership handover to `pendingOwner`.
function ownershipHandoverExpiresAt(address pendingOwner)
public
view
virtual
returns (uint256 result)
{
/// @solidity memory-safe-assembly
assembly {
// Compute the handover slot.
mstore(0x0c, _HANDOVER_SLOT_SEED)
mstore(0x00, pendingOwner)
// Load the handover slot.
result := sload(keccak256(0x0c, 0x20))
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* MODIFIERS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Marks a function as only callable by the owner.
modifier onlyOwner() virtual {
_checkOwner();
_;
}
}
"
},
"solady/src/tokens/ERC721.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
/// @notice Simple ERC721 implementation with storage hitchhiking.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/tokens/ERC721.sol)
/// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC721.sol)
/// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/tree/master/contracts/token/ERC721/ERC721.sol)
///
/// @dev Note:
/// - The ERC721 standard allows for self-approvals.
/// For performance, this implementation WILL NOT revert for such actions.
/// Please add any checks with overrides if desired.
/// - For performance, methods are made payable where permitted by the ERC721 standard.
/// - The `safeTransfer` functions use the identity precompile (0x4)
/// to copy memory internally.
///
/// If you are overriding:
/// - NEVER violate the ERC721 invariant:
/// the balance of an owner MUST always be equal to their number of ownership slots.
/// The transfer functions do not have an underflow guard for user token balances.
/// - Make sure all variables written to storage are properly cleaned
/// (e.g. the bool value for `isApprovedForAll` MUST be either 1 or 0 under the hood).
/// - Check that the overridden function is actually used in the function you want to
/// change the behavior of. Much of the code has been manually inlined for performance.
abstract contract ERC721 {
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CONSTANTS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev An account can hold up to 4294967295 tokens.
uint256 internal constant _MAX_ACCOUNT_BALANCE = 0xffffffff;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CUSTOM ERRORS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Only the token owner or an approved account can manage the token.
error NotOwnerNorApproved();
/// @dev The token does not exist.
error TokenDoesNotExist();
/// @dev The token already exists.
error TokenAlreadyExists();
/// @dev Cannot query the balance for the zero address.
error BalanceQueryForZeroAddress();
/// @dev Cannot mint or transfer to the zero address.
error TransferToZeroAddress();
/// @dev The token must be owned by `from`.
error TransferFromIncorrectOwner();
/// @dev The recipient's balance has overflowed.
error AccountBalanceOverflow();
/// @dev Cannot safely transfer to a contract that does not implement
/// the ERC721Receiver interface.
error TransferToNonERC721ReceiverImplementer();
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* EVENTS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Emitted when token `id` is transferred from `from` to `to`.
event Transfer(address indexed from, address indexed to, uint256 indexed id);
/// @dev Emitted when `owner` enables `account` to manage the `id` token.
event Approval(address indexed owner, address indexed account, uint256 indexed id);
/// @dev Emitted when `owner` enables or disables `operator` to manage all of their tokens.
event ApprovalForAll(address indexed owner, address indexed operator, bool isApproved);
/// @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("ApprovalForAll(address,address,bool)"))`.
uint256 private constant _APPROVAL_FOR_ALL_EVENT_SIGNATURE =
0x17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c31;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* STORAGE */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev The ownership data slot of `id` is given by:
/// ```
/// mstore(0x00, id)
/// mstore(0x1c, _ERC721_MASTER_SLOT_SEED)
/// let ownershipSlot := add(id, add(id, keccak256(0x00, 0x20)))
/// ```
/// Bits Layout:
/// - [0..159] `addr`
/// - [160..255] `extraData`
///
/// The approved address slot is given by: `add(1, ownershipSlot)`.
///
/// See: https://notes.ethereum.org/%40vbuterin/verkle_tree_eip
///
/// The balance slot of `owner` is given by:
/// ```
/// mstore(0x1c, _ERC721_MASTER_SLOT_SEED)
/// mstore(0x00, owner)
/// let balanceSlot := keccak256(0x0c, 0x1c)
/// ```
/// Bits Layout:
/// - [0..31] `balance`
/// - [32..255] `aux`
///
/// The `operator` approval slot of `owner` is given by:
/// ```
/// mstore(0x1c, or(_ERC721_MASTER_SLOT_SEED, operator))
/// mstore(0x00, owner)
/// let operatorApprovalSlot := keccak256(0x0c, 0x30)
/// ```
uint256 private constant _ERC721_MASTER_SLOT_SEED = 0x7d8825530a5a2e7a << 192;
/// @dev Pre-shifted and pre-masked constant.
uint256 private constant _ERC721_MASTER_SLOT_SEED_MASKED = 0x0a5a2e7a00000000;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* ERC721 METADATA */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Returns the token collection name.
function name() public view virtual returns (string memory);
/// @dev Returns the token collection symbol.
function symbol() public view virtual returns (string memory);
/// @dev Returns the Uniform Resource Identifier (URI) for token `id`.
function tokenURI(uint256 id) public view virtual returns (string memory);
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* ERC721 */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Returns the owner of token `id`.
///
/// Requirements:
/// - Token `id` must exist.
function ownerOf(uint256 id) public view virtual returns (address result) {
result = _ownerOf(id);
/// @solidity memory-safe-assembly
assembly {
if iszero(result) {
mstore(0x00, 0xceea21b6) // `TokenDoesNotExist()`.
revert(0x1c, 0x04)
}
}
}
/// @dev Returns the number of tokens owned by `owner`.
///
/// Requirements:
/// - `owner` must not be the zero address.
function balanceOf(address owner) public view virtual returns (uint256 result) {
/// @solidity memory-safe-assembly
assembly {
// Revert if the `owner` is the zero address.
if iszero(owner) {
mstore(0x00, 0x8f4eb604) // `BalanceQueryForZeroAddress()`.
revert(0x1c, 0x04)
}
mstore(0x1c, _ERC721_MASTER_SLOT_SEED)
mstore(0x00, owner)
result := and(sload(keccak256(0x0c, 0x1c)), _MAX_ACCOUNT_BALANCE)
}
}
/// @dev Returns the account approved to manage token `id`.
///
/// Requirements:
/// - Token `id` must exist.
function getApproved(uint256 id) public view virtual returns (address result) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, id)
mstore(0x1c, _ERC721_MASTER_SLOT_SEED)
let ownershipSlot := add(id, add(id, keccak256(0x00, 0x20)))
if iszero(shl(96, sload(ownershipSlot))) {
mstore(0x00, 0xceea21b6) // `TokenDoesNotExist()`.
revert(0x1c, 0x04)
}
result := sload(add(1, ownershipSlot))
}
}
/// @dev Sets `account` as the approved account to manage token `id`.
///
/// Requirements:
/// - Token `id` must exist.
/// - The caller must be the owner of the token,
/// or an approved operator for the token owner.
///
/// Emits an {Approval} event.
function approve(address account, uint256 id) public payable virtual {
_approve(msg.sender, account, id);
}
/// @dev Returns whether `operator` is approved to manage the tokens of `owner`.
function isApprovedForAll(address owner, address operator)
public
view
virtual
returns (bool result)
{
/// @solidity memory-safe-assembly
assembly {
mstore(0x1c, operator)
mstore(0x08, _ERC721_MASTER_SLOT_SEED_MASKED)
mstore(0x00, owner)
result := sload(keccak256(0x0c, 0x30))
}
}
/// @dev Sets whether `operator` is approved to manage the tokens of the caller.
///
/// Emits an {ApprovalForAll} event.
function setApprovalForAll(address operator, bool isApproved) public virtual {
/// @solidity memory-safe-assembly
assembly {
// Convert to 0 or 1.
isApproved := iszero(iszero(isApproved))
// Update the `isApproved` for (`msg.sender`, `operator`).
mstore(0x1c, operator)
mstore(0x08, _ERC721_MASTER_SLOT_SEED_MASKED)
mstore(0x00, caller())
sstore(keccak256(0x0c, 0x30), isApproved)
// Emit the {ApprovalForAll} event.
mstore(0x00, isApproved)
// forgefmt: disable-next-item
log3(0x00, 0x20, _APPROVAL_FOR_ALL_EVENT_SIGNATURE, caller(), shr(96, shl(96, operator)))
}
}
/// @dev Transfers token `id` from `from` to `to`.
///
/// Requirements:
///
/// - Token `id` must exist.
/// - `from` must be the owner of the token.
/// - `to` cannot be the zero address.
/// - The caller must be the owner of the token, or be approved to manage the token.
///
/// Emits a {Transfer} event.
function transferFrom(address from, address to, uint256 id) public payable virtual {
_beforeTokenTransfer(from, to, id);
/// @solidity memory-safe-assembly
assembly {
// Clear the upper 96 bits.
let bitmaskAddress := shr(96, not(0))
from := and(bitmaskAddress, from)
to := and(bitmaskAddress, to)
// Load the ownership data.
mstore(0x00, id)
mstore(0x1c, or(_ERC721_MASTER_SLOT_SEED, caller()))
let ownershipSlot := add(id, add(id, keccak256(0x00, 0x20)))
let ownershipPacked := sload(ownershipSlot)
let owner := and(bitmaskAddress, ownershipPacked)
// Revert if the token does not exist, or if `from` is not the owner.
if iszero(mul(owner, eq(owner, from))) {
// `TokenDoesNotExist()`, `TransferFromIncorrectOwner()`.
mstore(shl(2, iszero(owner)), 0xceea21b6a1148100)
revert(0x1c, 0x04)
}
// Load, check, and update the token approval.
{
mstore(0x00, from)
let approvedAddress := sload(add(1, ownershipSlot))
// Revert if the caller is not the owner, nor approved.
if iszero(or(eq(caller(), from), eq(caller(), approvedAddress))) {
if iszero(sload(keccak256(0x0c, 0x30))) {
mstore(0x00, 0x4b6e7f18) // `NotOwnerNorApproved()`.
revert(0x1c, 0x04)
}
}
// Delete the approved address if any.
if approvedAddress { sstore(add(1, ownershipSlot), 0) }
}
// Update with the new owner.
sstore(ownershipSlot, xor(ownershipPacked, xor(from, to)))
// Decrement the balance of `from`.
{
let fromBalanceSlot := keccak256(0x0c, 0x1c)
sstore(fromBalanceSlot, sub(sload(fromBalanceSlot), 1))
}
// Increment the balance of `to`.
{
mstore(0x00, to)
let toBalanceSlot := keccak256(0x0c, 0x1c)
let toBalanceSlotPacked := add(sload(toBalanceSlot), 1)
// Revert if `to` is the zero address, or if the account balance overflows.
if iszero(mul(to, and(toBalanceSlotPacked, _MAX_ACCOUNT_BALANCE))) {
// `TransferToZeroAddress()`, `AccountBalanceOverflow()`.
mstore(shl(2, iszero(to)), 0xea553b3401336cea)
revert(0x1c, 0x04)
}
sstore(toBalanceSlot, toBalanceSlotPacked)
}
// Emit the {Transfer} event.
log4(codesize(), 0x00, _TRANSFER_EVENT_SIGNATURE, from, to, id)
}
_afterTokenTransfer(from, to, id);
}
/// @dev Equivalent to `safeTransferFrom(from, to, id, "")`.
function safeTransferFrom(address from, address to, uint256 id) public payable virtual {
transferFrom(from, to, id);
if (_hasCode(to)) _checkOnERC721Received(from, to, id, "");
}
/// @dev Transfers token `id` from `from` to `to`.
///
/// Requirements:
///
/// - Token `id` must exist.
/// - `from` must be the owner of the token.
/// - `to` cannot be the zero address.
/// - The caller must be the owner of the token, or be approved to manage the token.
/// - If `to` refers to a smart contract, it must implement
/// {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
///
/// Emits a {Transfer} event.
function safeTransferFrom(address from, address to, uint256 id, bytes calldata data)
public
payable
virtual
{
transferFrom(from, to, id);
if (_hasCode(to)) _checkOnERC721Received(from, to, id, data);
}
/// @dev Returns true if this contract implements the interface defined by `interfaceId`.
/// See: https://eips.ethereum.org/EIPS/eip-165
/// This function call must use less than 30000 gas.
function supportsInterface(bytes4 interfaceId) public view virtual returns (bool result) {
/// @solidity memory-safe-assembly
assembly {
let s := shr(224, interfaceId)
// ERC165: 0x01ffc9a7, ERC721: 0x80ac58cd, ERC721Metadata: 0x5b5e139f.
result := or(or(eq(s, 0x01ffc9a7), eq(s, 0x80ac58cd)), eq(s, 0x5b5e139f))
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* INTERNAL QUERY FUNCTIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Returns if token `id` exists.
function _exists(uint256 id) internal view virtual returns (bool result) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, id)
mstore(0x1c, _ERC721_MASTER_SLOT_SEED)
result := iszero(iszero(shl(96, sload(add(id, add(id, keccak256(0x00, 0x20)))))))
}
}
/// @dev Returns the owner of token `id`.
/// Returns the zero address instead of reverting if the token does not exist.
function _ownerOf(uint256 id) internal view virtual returns (address result) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, id)
mstore(0x1c, _ERC721_MASTER_SLOT_SEED)
result := shr(96, shl(96, sload(add(id, add(id, keccak256(0x00, 0x20))))))
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* INTERNAL DATA HITCHHIKING FUNCTIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
// For performance, no events are emitted for the hitchhiking setters.
// Please emit your own events if required.
/// @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 (uint224 result) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x1c, _ERC721_MASTER_SLOT_SEED)
mstore(0x00, owner)
result := shr(32, sload(keccak256(0x0c, 0x1c)))
}
}
/// @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, uint224 value) internal virtual {
/// @solidity memory-safe-assembly
assembly {
mstore(0x1c, _ERC721_MASTER_SLOT_SEED)
mstore(0x00, owner)
let balanceSlot := keccak256(0x0c, 0x1c)
let packed := sload(balanceSlot)
sstore(balanceSlot, xor(packed, shl(32, xor(value, shr(32, packed)))))
}
}
/// @dev Returns the extra data for token `id`.
/// Minting, transferring, burning a token will not change the extra data.
/// The extra data can be set on a non-existent token.
function _getExtraData(uint256 id) internal view virtual returns (uint96 result) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, id)
mstore(0x1c, _ERC721_MASTER_SLOT_SEED)
result := shr(160, sload(add(id, add(id, keccak256(0x00, 0x20)))))
}
}
/// @dev Sets the extra data for token `id` to `value`.
/// Minting, transferring, burning a token will not change the extra data.
/// The extra data can be set on a non-existent token.
function _setExtraData(uint256 id, uint96 value) internal virtual {
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, id)
mstore(0x1c, _ERC721_MASTER_SLOT_SEED)
let ownershipSlot := add(id, add(id, keccak256(0x00, 0x20)))
let packed := sload(ownershipSlot)
sstore(ownershipSlot, xor(packed, shl(160, xor(value, shr(160, packed)))))
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* INTERNAL MINT FUNCTIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Mints token `id` to `to`.
///
/// Requirements:
///
/// - Token `id` must not exist.
/// - `to` cannot be the zero address.
///
/// Emits a {Transfer} event.
function _mint(address to, uint256 id) internal virtual {
_beforeTokenTransfer(address(0), to, id);
/// @solidity memory-safe-assembly
assembly {
// Clear the upper 96 bits.
to := shr(96, shl(96, to))
// Load the ownership data.
mstore(0x00, id)
mstore(0x1c, _ERC721_MASTER_SLOT_SEED)
let ownershipSlot := add(id, add(id, keccak256(0x00, 0x20)))
let ownershipPacked := sload(ownershipSlot)
// Revert if the token already exists.
if shl(96, ownershipPacked) {
mstore(0x00, 0xc991cbb1) // `TokenAlreadyExists()`.
revert(0x1c, 0x04)
}
// Update with the owner.
sstore(ownershipSlot, or(ownershipPacked, to))
// Increment the balance of the owner.
{
mstore(0x00, to)
let balanceSlot := keccak256(0x0c, 0x1c)
let balanceSlotPacked := add(sload(balanceSlot), 1)
// Revert if `to` is the zero address, or if the account balance overflows.
if iszero(mul(to, and(balanceSlotPacked, _MAX_ACCOUNT_BALANCE))) {
// `TransferToZeroAddress()`, `AccountBalanceOverflow()`.
mstore(shl(2, iszero(to)), 0xea553b3401336cea)
revert(0x1c, 0x04)
}
sstore(balanceSlot, balanceSlotPacked)
}
// Emit the {Transfer} event.
log4(codesize(), 0x00, _TRANSFER_EVENT_SIGNATURE, 0, to, id)
}
_afterTokenTransfer(address(0), to, id);
}
/// @dev Mints token `id` to `to`, and updates the extra data for token `id` to `value`.
/// Does NOT check if token `id` already exists (assumes `id` is auto-incrementing).
///
/// Requirements:
///
/// - `to` cannot be the zero address.
///
/// Emits a {Transfer} event.
function _mintAndSetExtraDataUnchecked(address to, uint256 id, uint96 value) internal virtual {
_beforeTokenTransfer(address(0), to, id);
/// @solidity memory-safe-assembly
assembly {
// Clear the upper 96 bits.
to := shr(96, shl(96, to))
// Update with the owner and extra data.
mstore(0x00, id)
mstore(0x1c, _ERC721_MASTER_SLOT_SEED)
sstore(add(id, add(id, keccak256(0x00, 0x20))), or(shl(160, value), to))
// Increment the balance of the owner.
{
mstore(0x00, to)
let balanceSlot := keccak256(0x0c, 0x1c)
let balanceSlotPacked := add(sload(balanceSlot), 1)
// Revert if `to` is the zero address, or if the account balance overflows.
if iszero(mul(to, and(balanceSlotPacked, _MAX_ACCOUNT_BALANCE))) {
// `TransferToZeroAddress()`, `AccountBalanceOverflow()`.
mstore(shl(2, iszero(to)), 0xea553b3401336cea)
revert(0x1c, 0x04)
}
sstore(balanceSlot, balanceSlotPacked)
}
// Emit the {Transfer} event.
log4(codesize(), 0x00, _TRANSFER_EVENT_SIGNATURE, 0, to, id)
}
_afterTokenTransfer(address(0), to, id);
}
/// @dev Equivalent to `_safeMint(to, id, "")`.
function _safeMint(address to, uint256 id) internal virtual {
_safeMint(to, id, "");
}
/// @dev Mints token `id` to `to`.
///
/// Requirements:
///
/// - Token `id` must not exist.
/// - `to` cannot be the zero address.
/// - If `to` refers to a smart contract, it must implement
/// {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
///
/// Emits a {Transfer} event.
function _safeMint(address to, uint256 id, bytes memory data) internal virtual {
_mint(to, id);
if (_hasCode(to)) _checkOnERC721Received(address(0), to, id, data);
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* INTERNAL BURN FUNCTIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Equivalent to `_burn(address(0), id)`.
function _burn(uint256 id) internal virtual {
_burn(address(0), id);
}
/// @dev Destroys token `id`, using `by`.
///
/// Requirements:
///
/// - Token `id` must exist.
/// - If `by` is not the zero address,
/// it must be the owner of the token, or be approved to manage the token.
///
/// Emits a {Transfer} event.
function _burn(address by, uint256 id) internal virtual {
address owner = ownerOf(id);
_beforeTokenTransfer(owner, address(0), id);
/// @solidity memory-safe-assembly
assembly {
// Clear the upper 96 bits.
by := shr(96, shl(96, by))
// Load the ownership data.
mstore(0x00, id)
mstore(0x1c, or(_ERC721_MASTER_SLOT_SEED, by))
let ownershipSlot := add(id, add(id, keccak256(0x00, 0x20)))
let ownershipPacked := sload(ownershipSlot)
// Reload the owner in case it is changed in `_beforeTokenTransfer`.
owner := shr(96, shl(96, ownershipPacked))
// Revert if the token does not exist.
if iszero(owner) {
mstore(0x00, 0xceea21b6) // `TokenDoesNotExist()`.
revert(0x1c, 0x04)
}
// Load and check the token approval.
{
mstore(0x00, owner)
let approvedAddress := sload(add(1, ownershipSlot))
// If `by` is not the zero address, do the authorization check.
// Revert if the `by` is not the owner, nor approved.
if iszero(or(iszero(by), or(eq(by, owner), eq(by, approvedAddress)))) {
if iszero(sload(keccak256(0x0c, 0x30))) {
mstore(0x00, 0x4b6e7f18) // `NotOwnerNorApproved()`.
revert(0x1c, 0x04)
}
}
// Delete the approved address if any.
if approvedAddress { sstore(add(1, ownershipSlot), 0) }
}
// Clear the owner.
sstore(ownershipSlot, xor(ownershipPacked, owner))
// Decrement the balance of `owner`.
{
let balanceSlot := keccak256(0x0c, 0x1c)
sstore(balanceSlot, sub(sload(balanceSlot), 1))
}
// Emit the {Transfer} event.
log4(codesize(), 0x00, _TRANSFER_EVENT_SIGNATURE, owner, 0, id)
}
_afterTokenTransfer(owner, address(0), id);
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* INTERNAL APPROVAL FUNCTIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Returns whether `account` is the owner of token `id`, or is approved to manage it.
///
/// Requirements:
/// - Token `id` must exist.
function _isApprovedOrOwner(address account, uint256 id)
internal
view
virtual
returns (bool result)
{
/// @solidity memory-safe-assembly
assembly {
result := 1
// Clear the upper 96 bits.
account := shr(96, shl(96, account))
// Load the ownership data.
mstore(0x00, id)
mstore(0x1c, or(_ERC721_MASTER_SLOT_SEED, account))
let ownershipSlot := add(id, add(id, keccak256(0x00, 0x20)))
let owner := shr(96, shl(96, sload(ownershipSlot)))
// Revert if the token does not exist.
if iszero(owner) {
mstore(0x00, 0xceea21b6) // `TokenDoesNotExist()`.
revert(0x1c, 0x04)
}
// Check if `account` is the `owner`.
if iszero(eq(account, owner)) {
mstore(0x00, owner)
// Check if `account` is approved to manage the token.
if iszero(sload(keccak256(0x0c, 0x30))) {
result := eq(account, sload(add(1, ownershipSlot)))
}
}
}
}
/// @dev Returns the account approved to manage token `id`.
/// Returns the zero address instead of reverting if the token does not exist.
function _getApproved(uint256 id) internal view virtual returns (address result) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, id)
mstore(0x1c, _ERC721_MASTER_SLOT_SEED)
result := sload(add(1, add(id, add(id, keccak256(0x00, 0x20)))))
}
}
/// @dev Equivalent to `_approve(address(0), account, id)`.
function _approve(address account, uint256 id) internal virtual {
_approve(address(0), account, id);
}
/// @dev Sets `account` as the approved account to manage token `id`, using `by`.
///
/// Requirements:
/// - Token `id` must exist.
/// - If `by` is not the zero address, `by` must be the owner
/// or an approved operator for the token owner.
///
/// Emits a {Approval} event.
function _approve(address by, address account, uint256 id) internal virtual {
/// @solidity memory-safe-assembly
assembly {
// Clear the upper 96 bits.
let bitmaskAddress := shr(96, not(0))
account := and(bitmaskAddre
Submitted on: 2025-10-28 09:56:07
Comments
Log in to comment.
No comments yet.