Contract detail
ERC721SeaDrop
ERC721SeaDrop is a verified Robinhood Chain smart contract at 0x897d9706…8e6f2e40 with 768 indexed transactions.
- Address type
- Smart contract
- Transactions
- 768
- First indexed block
- #28321474
- Latest indexed block
- #61100626
- Native balance
- 0 ETH
- Balance observed at
- Block #32244583
- Emitted events
- 5401
- Token tracker
- Robin Hoppers NFT (BHOP)
- Verification
- Verified
- Compiler
- v0.8.17+commit.8df45f5f
- Verification match
- partial
- Creation transaction
- 0x2ff407d7…b4d25890
- Created at block
- #28288022
- Language
- solidity
- EVM version
- london
- License
- none
- Compiler optimization
- Enabled · 1000000 runs
Related activity
Transactions
Verified contract
Contract source code
// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;
import {
ERC721ContractMetadata,
ISeaDropTokenContractMetadata
} from "./ERC721ContractMetadata.sol";
import {
INonFungibleSeaDropToken
} from "./interfaces/INonFungibleSeaDropToken.sol";
import { ISeaDrop } from "./interfaces/ISeaDrop.sol";
import {
AllowListData,
PublicDrop,
TokenGatedDropStage,
SignedMintValidationParams
} from "./lib/SeaDropStructs.sol";
import {
ERC721SeaDropStructsErrorsAndEvents
} from "./lib/ERC721SeaDropStructsErrorsAndEvents.sol";
import { ERC721A } from "ERC721A/ERC721A.sol";
import { ReentrancyGuard } from "solmate/utils/ReentrancyGuard.sol";
import {
IERC165
} from "openzeppelin-contracts/utils/introspection/IERC165.sol";
/**
* @title ERC721SeaDrop
* @author James Wenzel (emo.eth)
* @author Ryan Ghods (ralxz.eth)
* @author Stephan Min (stephanm.eth)
* @author Michael Cohen (notmichael.eth)
* @custom:contributor Limit Break (@limitbreak)
* @notice ERC721SeaDrop is a token contract that contains methods
* to properly interact with SeaDrop.
* Implements Limit Break's Creator Token Standards transfer
* validation for royalty enforcement.
*/
contract ERC721SeaDrop is
ERC721ContractMetadata,
INonFungibleSeaDropToken,
ERC721SeaDropStructsErrorsAndEvents,
ReentrancyGuard
{
/// @notice Track the allowed SeaDrop addresses.
mapping(address => bool) internal _allowedSeaDrop;
/// @notice Track the enumerated allowed SeaDrop addresses.
address[] internal _enumeratedAllowedSeaDrop;
/**
* @dev Reverts if not an allowed SeaDrop contract.
* This function is inlined instead of being a modifier
* to save contract space from being inlined N times.
*
* @param seaDrop The SeaDrop address to check if allowed.
*/
function _onlyAllowedSeaDrop(address seaDrop) internal view {
if (_allowedSeaDrop[seaDrop] != true) {
revert OnlyAllowedSeaDrop();
}
}
/**
* @notice Deploy the token contract with its name, symbol,
* and allowed SeaDrop addresses.
*/
constructor(
string memory name,
string memory symbol,
address[] memory allowedSeaDrop
) ERC721ContractMetadata(name, symbol) {
// Put the length on the stack for more efficient access.
uint256 allowedSeaDropLength = allowedSeaDrop.length;
// Set the mapping for allowed SeaDrop contracts.
for (uint256 i = 0; i < allowedSeaDropLength; ) {
_allowedSeaDrop[allowedSeaDrop[i]] = true;
unchecked {
++i;
}
}
// Set the enumeration.
_enumeratedAllowedSeaDrop = allowedSeaDrop;
// Emit an event noting the contract deployment.
emit SeaDropTokenDeployed();
}
/**
* @notice Update the allowed SeaDrop contracts.
* Only the owner can use this function.
*
* @param allowedSeaDrop The allowed SeaDrop addresses.
*/
function updateAllowedSeaDrop(address[] calldata allowedSeaDrop)
external
virtual
override
onlyOwner
{
_updateAllowedSeaDrop(allowedSeaDrop);
}
/**
* @notice Internal function to update the allowed SeaDrop contracts.
*
* @param allowedSeaDrop The allowed SeaDrop addresses.
*/
function _updateAllowedSeaDrop(address[] calldata allowedSeaDrop) internal {
// Put the length on the stack for more efficient access.
uint256 enumeratedAllowedSeaDropLength = _enumeratedAllowedSeaDrop
.length;
uint256 allowedSeaDropLength = allowedSeaDrop.length;
// Reset the old mapping.
for (uint256 i = 0; i < enumeratedAllowedSeaDropLength; ) {
_allowedSeaDrop[_enumeratedAllowedSeaDrop[i]] = false;
unchecked {
++i;
}
}
// Set the new mapping for allowed SeaDrop contracts.
for (uint256 i = 0; i < allowedSeaDropLength; ) {
_allowedSeaDrop[allowedSeaDrop[i]] = true;
unchecked {
++i;
}
}
// Set the enumeration.
_enumeratedAllowedSeaDrop = allowedSeaDrop;
// Emit an event for the update.
emit AllowedSeaDropUpdated(allowedSeaDrop);
}
/**
* @notice Burns `tokenId`. The caller must own `tokenId` or be an
* approved operator.
*
* @param tokenId The token id to burn.
*/
// solhint-disable-next-line comprehensive-interface
function burn(uint256 tokenId) external {
_burn(tokenId, true);
}
/**
* @dev Overrides the `_startTokenId` function from ERC721A
* to start at token id `1`.
*
* This is to avoid future possible problems since `0` is usually
* used to signal values that have not been set or have been removed.
*/
function _startTokenId() internal view virtual override returns (uint256) {
return 1;
}
/**
* @dev Overrides the `tokenURI()` function from ERC721A
* to return just the base URI if it is implied to not be a directory.
*
* This is to help with ERC721 contracts in which the same token URI
* is desired for each token, such as when the tokenURI is 'unrevealed'.
*/
function tokenURI(uint256 tokenId)
public
view
virtual
override
returns (string memory)
{
if (!_exists(tokenId)) revert URIQueryForNonexistentToken();
string memory baseURI = _baseURI();
// Exit early if the baseURI is empty.
if (bytes(baseURI).length == 0) {
return "";
}
// Check if the last character in baseURI is a slash.
if (bytes(baseURI)[bytes(baseURI).length - 1] != bytes("/")[0]) {
return baseURI;
}
return string(abi.encodePacked(baseURI, _toString(tokenId)));
}
/**
* @notice Mint tokens, restricted to the SeaDrop contract.
*
* @dev NOTE: If a token registers itself with multiple SeaDrop
* contracts, the implementation of this function should guard
* against reentrancy. If the implementing token uses
* _safeMint(), or a feeRecipient with a malicious receive() hook
* is specified, the token or fee recipients may be able to execute
* another mint in the same transaction via a separate SeaDrop
* contract.
* This is dangerous if an implementing token does not correctly
* update the minterNumMinted and currentTotalSupply values before
* transferring minted tokens, as SeaDrop references these values
* to enforce token limits on a per-wallet and per-stage basis.
*
* ERC721A tracks these values automatically, but this note and
* nonReentrant modifier are left here to encourage best-practices
* when referencing this contract.
*
* @param minter The address to mint to.
* @param quantity The number of tokens to mint.
*/
function mintSeaDrop(address minter, uint256 quantity)
external
virtual
override
nonReentrant
{
// Ensure the SeaDrop is allowed.
_onlyAllowedSeaDrop(msg.sender);
// Extra safety check to ensure the max supply is not exceeded.
if (_totalMinted() + quantity > maxSupply()) {
revert MintQuantityExceedsMaxSupply(
_totalMinted() + quantity,
maxSupply()
);
}
// Mint the quantity of tokens to the minter.
_safeMint(minter, quantity);
}
/**
* @notice Update the public drop data for this nft contract on SeaDrop.
* Only the owner can use this function.
*
* @param seaDropImpl The allowed SeaDrop contract.
* @param publicDrop The public drop data.
*/
function updatePublicDrop(
address seaDropImpl,
PublicDrop calldata publicDrop
) external virtual override {
// Ensure the sender is only the owner or contract itself.
_onlyOwnerOrSelf();
// Ensure the SeaDrop is allowed.
_onlyAllowedSeaDrop(seaDropImpl);
// Update the public drop data on SeaDrop.
ISeaDrop(seaDropImpl).updatePublicDrop(publicDrop);
}
/**
* @notice Update the allow list data for this nft contract on SeaDrop.
* Only the owner can use this function.
*
* @param seaDropImpl The allowed SeaDrop contract.
* @param allowListData The allow list data.
*/
function updateAllowList(
address seaDropImpl,
AllowListData calldata allowListData
) external virtual override {
// Ensure the sender is only the owner or contract itself.
_onlyOwnerOrSelf();
// Ensure the SeaDrop is allowed.
_onlyAllowedSeaDrop(seaDropImpl);
// Update the allow list on SeaDrop.
ISeaDrop(seaDropImpl).updateAllowList(allowListData);
}
/**
* @notice Update the token gated drop stage data for this nft contract
* on SeaDrop.
* Only the owner can use this function.
*
* Note: If two INonFungibleSeaDropToken tokens are doing
* simultaneous token gated drop promotions for each other,
* they can be minted by the same actor until
* `maxTokenSupplyForStage` is reached. Please ensure the
* `allowedNftToken` is not running an active drop during the
* `dropStage` time period.
*
* @param seaDropImpl The allowed SeaDrop contract.
* @param allowedNftToken The allowed nft token.
* @param dropStage The token gated drop stage data.
*/
function updateTokenGatedDrop(
address seaDropImpl,
address allowedNftToken,
TokenGatedDropStage calldata dropStage
) external virtual override {
// Ensure the sender is only the owner or contract itself.
_onlyOwnerOrSelf();
// Ensure the SeaDrop is allowed.
_onlyAllowedSeaDrop(seaDropImpl);
// Update the token gated drop stage.
ISeaDrop(seaDropImpl).updateTokenGatedDrop(allowedNftToken, dropStage);
}
/**
* @notice Update the drop URI for this nft contract on SeaDrop.
* Only the owner can use this function.
*
* @param seaDropImpl The allowed SeaDrop contract.
* @param dropURI The new drop URI.
*/
function updateDropURI(address seaDropImpl, string calldata dropURI)
external
virtual
override
{
// Ensure the sender is only the owner or contract itself.
_onlyOwnerOrSelf();
// Ensure the SeaDrop is allowed.
_onlyAllowedSeaDrop(seaDropImpl);
// Update the drop URI.
ISeaDrop(seaDropImpl).updateDropURI(dropURI);
}
/**
* @notice Update the creator payout address for this nft contract on
* SeaDrop.
* Only the owner can set the creator payout address.
*
* @param seaDropImpl The allowed SeaDrop contract.
* @param payoutAddress The new payout address.
*/
function updateCreatorPayoutAddress(
address seaDropImpl,
address payoutAddress
) external {
// Ensure the sender is only the owner or contract itself.
_onlyOwnerOrSelf();
// Ensure the SeaDrop is allowed.
_onlyAllowedSeaDrop(seaDropImpl);
// Update the creator payout address.
ISeaDrop(seaDropImpl).updateCreatorPayoutAddress(payoutAddress);
}
/**
* @notice Update the allowed fee recipient for this nft contract
* on SeaDrop.
* Only the owner can set the allowed fee recipient.
*
* @param seaDropImpl The allowed SeaDrop contract.
* @param feeRecipient The new fee recipient.
* @param allowed If the fee recipient is allowed.
*/
function updateAllowedFeeRecipient(
address seaDropImpl,
address feeRecipient,
bool allowed
) external virtual {
// Ensure the sender is only the owner or contract itself.
_onlyOwnerOrSelf();
// Ensure the SeaDrop is allowed.
_onlyAllowedSeaDrop(seaDropImpl);
// Update the allowed fee recipient.
ISeaDrop(seaDropImpl).updateAllowedFeeRecipient(feeRecipient, allowed);
}
/**
* @notice Update the server-side signers for this nft contract
* on SeaDrop.
* Only the owner can use this function.
*
* @param seaDropImpl The allowed SeaDrop contract.
* @param signer The signer to update.
* @param signedMintValidationParams Minimum and maximum parameters to
* enforce for signed mints.
*/
function updateSignedMintValidationParams(
address seaDropImpl,
address signer,
SignedMintValidationParams memory signedMintValidationParams
) external virtual override {
// Ensure the sender is only the owner or contract itself.
_onlyOwnerOrSelf();
// Ensure the SeaDrop is allowed.
_onlyAllowedSeaDrop(seaDropImpl);
// Update the signer.
ISeaDrop(seaDropImpl).updateSignedMintValidationParams(
signer,
signedMintValidationParams
);
}
/**
* @notice Update the allowed payers for this nft contract on SeaDrop.
* Only the owner can use this function.
*
* @param seaDropImpl The allowed SeaDrop contract.
* @param payer The payer to update.
* @param allowed Whether the payer is allowed.
*/
function updatePayer(
address seaDropImpl,
address payer,
bool allowed
) external virtual override {
// Ensure the sender is only the owner or contract itself.
_onlyOwnerOrSelf();
// Ensure the SeaDrop is allowed.
_onlyAllowedSeaDrop(seaDropImpl);
// Update the payer.
ISeaDrop(seaDropImpl).updatePayer(payer, allowed);
}
/**
* @notice Returns a set of mint stats for the address.
* This assists SeaDrop in enforcing maxSupply,
* maxTotalMintableByWallet, and maxTokenSupplyForStage checks.
*
* @dev NOTE: Implementing contracts should always update these numbers
* before transferring any tokens with _safeMint() to mitigate
* consequences of malicious onERC721Received() hooks.
*
* @param minter The minter address.
*/
function getMintStats(address minter)
external
view
override
returns (
uint256 minterNumMinted,
uint256 currentTotalSupply,
uint256 maxSupply
)
{
minterNumMinted = _numberMinted(minter);
currentTotalSupply = _totalMinted();
maxSupply = _maxSupply;
}
/**
* @notice Returns whether the interface is supported.
*
* @param interfaceId The interface id to check against.
*/
function supportsInterface(bytes4 interfaceId)
public
view
virtual
override(IERC165, ERC721ContractMetadata)
returns (bool)
{
return
interfaceId == type(INonFungibleSeaDropToken).interfaceId ||
interfaceId == type(ISeaDropTokenContractMetadata).interfaceId ||
// ERC721ContractMetadata returns supportsInterface true for
// EIP-2981
// ERC721A returns supportsInterface true for
// ERC165, ERC721, ERC721Metadata
super.supportsInterface(interfaceId);
}
/**
* @notice Configure multiple properties at a time.
*
* Note: The individual configure methods should be used
* to unset or reset any properties to zero, as this method
* will ignore zero-value properties in the config struct.
*
* @param config The configuration struct.
*/
function multiConfigure(MultiConfigureStruct calldata config)
external
onlyOwner
{
if (config.maxSupply > 0) {
this.setMaxSupply(config.maxSupply);
}
if (bytes(config.baseURI).length != 0) {
this.setBaseURI(config.baseURI);
}
if (bytes(config.contractURI).length != 0) {
this.setContractURI(config.contractURI);
}
if (
_cast(config.publicDrop.startTime != 0) |
_cast(config.publicDrop.endTime != 0) ==
1
) {
this.updatePublicDrop(config.seaDropImpl, config.publicDrop);
}
if (bytes(config.dropURI).length != 0) {
this.updateDropURI(config.seaDropImpl, config.dropURI);
}
if (config.allowListData.merkleRoot != bytes32(0)) {
this.updateAllowList(config.seaDropImpl, config.allowListData);
}
if (config.creatorPayoutAddress != address(0)) {
this.updateCreatorPayoutAddress(
config.seaDropImpl,
config.creatorPayoutAddress
);
}
if (config.provenanceHash != bytes32(0)) {
this.setProvenanceHash(config.provenanceHash);
}
if (config.allowedFeeRecipients.length > 0) {
for (uint256 i = 0; i < config.allowedFeeRecipients.length; ) {
this.updateAllowedFeeRecipient(
config.seaDropImpl,
config.allowedFeeRecipients[i],
true
);
unchecked {
++i;
}
}
}
if (config.disallowedFeeRecipients.length > 0) {
for (uint256 i = 0; i < config.disallowedFeeRecipients.length; ) {
this.updateAllowedFeeRecipient(
config.seaDropImpl,
config.disallowedFeeRecipients[i],
false
);
unchecked {
++i;
}
}
}
if (config.allowedPayers.length > 0) {
for (uint256 i = 0; i < config.allowedPayers.length; ) {
this.updatePayer(
config.seaDropImpl,
config.allowedPayers[i],
true
);
unchecked {
++i;
}
}
}
if (config.disallowedPayers.length > 0) {
for (uint256 i = 0; i < config.disallowedPayers.length; ) {
this.updatePayer(
config.seaDropImpl,
config.disallowedPayers[i],
false
);
unchecked {
++i;
}
}
}
if (config.tokenGatedDropStages.length > 0) {
if (
config.tokenGatedDropStages.length !=
config.tokenGatedAllowedNftTokens.length
) {
revert TokenGatedMismatch();
}
for (uint256 i = 0; i < config.tokenGatedDropStages.length; ) {
this.updateTokenGatedDrop(
config.seaDropImpl,
config.tokenGatedAllowedNftTokens[i],
config.tokenGatedDropStages[i]
);
unchecked {
++i;
}
}
}
if (config.disallowedTokenGatedAllowedNftTokens.length > 0) {
for (
uint256 i = 0;
i < config.disallowedTokenGatedAllowedNftTokens.length;
) {
TokenGatedDropStage memory emptyStage;
this.updateTokenGatedDrop(
config.seaDropImpl,
config.disallowedTokenGatedAllowedNftTokens[i],
emptyStage
);
unchecked {
++i;
}
}
}
if (config.signedMintValidationParams.length > 0) {
if (
config.signedMintValidationParams.length !=
config.signers.length
) {
revert SignersMismatch();
}
for (
uint256 i = 0;
i < config.signedMintValidationParams.length;
) {
this.updateSignedMintValidationParams(
config.seaDropImpl,
config.signers[i],
config.signedMintValidationParams[i]
);
unchecked {
++i;
}
}
}
if (config.disallowedSigners.length > 0) {
for (uint256 i = 0; i < config.disallowedSigners.length; ) {
SignedMintValidationParams memory emptyParams;
this.updateSignedMintValidationParams(
config.seaDropImpl,
config.disallowedSigners[i],
emptyParams
);
unchecked {
++i;
}
}
}
}
}