SeaDrop
0x00005ea00ac477b1030ce78506496e8c2de24bf5
Verification
Verified
v0.8.17+commit.8df45f5f
Type
Contract
21,081 bytes
ABI entries
68
12 read · 12 write
License
none
Contract information
- Address
- 0x00005ea00ac477b1030ce78506496e8c2de24bf5
- Chain
- Robinhood Chain (4663)
- Compiler
- v0.8.17+commit.8df45f5f
- Optimization
- Enabled
- Creator
- 0x0000000000…2212439497
- Creation tx
- 0xefa2c72e73…895e46ba41
Token
Not a token
This contract does not expose ERC-20 metadata.
Read contract (12)
getAllowListMerkleRoot(address) → bytes32
getAllowedFeeRecipients(address) → address[]
getAllowedNftTokenIdIsRedeemed(address, address, uint256) → bool
getCreatorPayoutAddress(address) → address
getFeeRecipientIsAllowed(address, address) → bool
getPayerIsAllowed(address, address) → bool
getPayers(address) → address[]
getPublicDrop(address) → tuple
getSignedMintValidationParams(address, address) → tuple
getSigners(address) → address[]
getTokenGatedAllowedTokens(address) → address[]
getTokenGatedDrop(address, address) → tuple
Events (9)
AllowListUpdatedAllowedFeeRecipientUpdatedCreatorPayoutAddressUpdatedDropURIUpdatedPayerUpdatedPublicDropUpdatedSeaDropMintSignedMintValidationParamsUpdatedTokenGatedDropStageUpdated
ABI
[
{
"inputs": [],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"inputs": [],
"name": "CreatorPayoutAddressCannotBeZeroAddress",
"type": "error"
},
{
"inputs": [],
"name": "DuplicateFeeRecipient",
"type": "error"
},
{
"inputs": [],
"name": "DuplicatePayer",
"type": "error"
},
{
"inputs": [],
"name": "FeeRecipientCannotBeZeroAddress",
"type": "error"
},
{
"inputs": [],
"name": "FeeRecipientNotAllowed",
"type": "error"
},
{
"inputs": [],
"name": "FeeRecipientNotPresent",
"type": "error"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "got",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "want",
"type": "uint256"
}
],
"name": "IncorrectPayment",
"type": "error"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "feeBps",
"type": "uint256"
}
],
"name": "InvalidFeeBps",
"type": "error"
},
{
"inputs": [],
"name": "InvalidProof",
"type": "error"
},
{
"inputs": [
{
"internalType": "address",
"name": "recoveredSigner",
"type": "address"
}
],
"name": "InvalidSignature",
"type": "error"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "got",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "maximum",
"type": "uint256"
}
],
"name": "InvalidSignedEndTime",
"type": "error"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "got",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "minimumOrMaximum",
"type": "uint256"
}
],
"name": "InvalidSignedFeeBps",
"type": "error"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "got",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "maximum",
"type": "uint256"
}
],
"name": "InvalidSignedMaxTokenSupplyForStage",
"type": "error"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "got",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "maximum",
"type": "uint256"
}
],
"name": "InvalidSignedMaxTotalMintableByWallet",
"type": "error"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "got",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "minimum",
"type": "uint256"
}
],
"name": "InvalidSignedMintPrice",
"type": "error"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "got",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "minimum",
"type": "uint256"
}
],
"name": "InvalidSignedStartTime",
"type": "error"
},
{
"inputs": [],
"name": "MintQuantityCannotBeZero",
"type": "error"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "total",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "allowed",
"type": "uint256"
}
],
"name": "MintQuantityExceedsMaxMintedPerWallet",
"type": "error"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "total",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "maxSupply",
"type": "uint256"
}
],
"name": "MintQuantityExceedsMaxSupply",
"type": "error"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "total",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "maxTokenSupplyForStage",
"type": "uint256"
}
],
"name": "MintQuantityExceedsMaxTokenSupplyForStage",
"type": "error"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "currentTimestamp",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "startTimestamp",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "endTimestamp",
"type": "uint256"
}
],
"name": "NotActive",
"type": "error"
},
{
"inputs": [
{
"internalType": "address",
"name": "sender",
"type": "address"
}
],
"name": "OnlyINonFungibleSeaDropToken",
"type": "error"
},
{
"inputs": [],
"name": "PayerCannotBeZeroAddress",
"type": "error"
},
{
"inputs": [],
"name": "PayerNotAllowed",
"type": "error"
},
{
"inputs": [],
"name": "PayerNotPresent",
"type": "error"
},
{
"inputs": [],
"name": "SignatureAlreadyUsed",
"type": "error"
},
{
"inputs": [],
"name": "SignedMintsMustRestrictFeeRecipients",
"type": "error"
},
{
"inputs": [],
"name": "SignerCannotBeZeroAddress",
"type": "error"
},
{
"inputs": [],
"name": "SignerNotPresent",
"type": "error"
},
{
"inputs": [],
"name": "TokenGatedDropAllowedNftTokenCannotBeDropToken",
"type": "error"
},
{
"inputs": [],
"name": "TokenGatedDropAllowedNftTokenCannotBeZeroAddress",
"type": "error"
},
{
"inputs": [],
"name": "TokenGatedDropStageNotPresent",
"type": "error"
},
{
"inputs": [
{
"internalType": "address",
"name": "nftContract",
"type": "address"
},
{
"internalType": "address",
"name": "allowedNftToken",
"type": "address"
},
{
"internalType": "uint256",
"name": "allowedNftTokenId",
"type": "uint256"
}
],
"name": "TokenGatedNotTokenOwner",
"type": "error"
},
{
"inputs": [
{
"internalType": "address",
"name": "nftContract",
"type": "address"
},
{
"internalType": "address",
"name": "allowedNftToken",
"type": "address"
},
{
"internalType": "uint256",
"name": "allowedNftTokenId",
"type": "uint256"
}
],
"name": "TokenGatedTokenIdAlreadyRedeemed",
"type": "error"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "nftContract",
"type": "address"
},
{
"indexed": true,
"internalType": "bytes32",
"name": "previousMerkleRoot",
"type": "bytes32"
},
{
"indexed": true,
"internalType": "bytes32",
"name": "newMerkleRoot",
"type": "bytes32"
},
{
"indexed": false,
"internalType": "string[]",
"name": "publicKeyURI",
"type": "string[]"
},
{
"indexed": false,
"internalType": "string",
"name": "allowListURI",
"type": "string"
}
],
"name": "AllowListUpdated",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "nftContract",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "feeRecipient",
"type": "address"
},
{
"indexed": true,
"internalType": "bool",
"name": "allowed",
"type": "bool"
}
],
"name": "AllowedFeeRecipientUpdated",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "nftContract",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "newPayoutAddress",
"type": "address"
}
],
"name": "CreatorPayoutAddressUpdated",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "nftContract",
"type": "address"
},
{
"indexed": false,
"internalType": "string",
"name": "newDropURI",
"type": "string"
}
],
"name": "DropURIUpdated",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "nftContract",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "payer",
"type": "address"
},
{
"indexed": true,
"internalType": "bool",
"name": "allowed",
"type": "bool"
}
],
"name": "PayerUpdated",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "nftContract",
"type": "address"
},
{
"components": [
{
"internalType": "uint80",
"name": "mintPrice",
"type": "uint80"
},
{
"internalType": "uint48",
"name": "startTime",
"type": "uint48"
},
{
"internalType": "uint48",
"name": "endTime",
"type": "uint48"
},
{
"internalType": "uint16",
"name": "maxTotalMintableByWallet",
"type": "uint16"
},
{
"internalType": "uint16",
"name": "feeBps",
"type": "uint16"
},
{
"internalType": "bool",
"name": "restrictFeeRecipients",
"type": "bool"
}
],
"indexed": false,
"internalType": "struct PublicDrop",
"name": "publicDrop",
"type": "tuple"
}
],
"name": "PublicDropUpdated",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "nftContract",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "minter",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "feeRecipient",
"type": "address"
},
{
"indexed": false,
"internalType": "address",
"name": "payer",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "quantityMinted",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "unitMintPrice",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "feeBps",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "dropStageIndex",
"type": "uint256"
}
],
"name": "SeaDropMint",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "nftContract",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "signer",
"type": "address"
},
{
"components": [
{
"internalType": "uint80",
"name": "minMintPrice",
"type": "uint80"
},
{
"internalType": "uint24",
"name": "maxMaxTotalMintableByWallet",
"type": "uint24"
},
{
"internalType": "uint40",
"name": "minStartTime",
"type": "uint40"
},
{
"internalType": "uint40",
"name": "maxEndTime",
"type": "uint40"
},
{
"internalType": "uint40",
"name": "maxMaxTokenSupplyForStage",
"type": "uint40"
},
{
"internalType": "uint16",
"name": "minFeeBps",
"type": "uint16"
},
{
"internalType": "uint16",
"name": "maxFeeBps",
"type": "uint16"
}
],
"indexed": false,
"internalType": "struct SignedMintValidationParams",
"name": "signedMintValidationParams",
"type": "tuple"
}
],
"name": "SignedMintValidationParamsUpdated",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "nftContract",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "allowedNftToken",
"type": "address"
},
{
"components": [
{
"internalType": "uint80",
"name": "mintPrice",
"type": "uint80"
},
{
"internalType": "uint16",
"name": "maxTotalMintableByWallet",
"type": "uint16"
},
{
"internalType": "uint48",
"name": "startTime",
"type": "uint48"
},
{
"internalType": "uint48",
"name": "endTime",
"type": "uint48"
},
{
"internalType": "uint8",
"name": "dropStageIndex",
"type": "uint8"
},
{
"internalType": "uint32",
"name": "maxTokenSupplyForStage",
"type": "uint32"
},
{
"internalType": "uint16",
"name": "feeBps",
"type": "uint16"
},
{
"internalType": "bool",
"name": "restrictFeeRecipients",
"type": "bool"
}
],
"indexed": false,
"internalType": "struct TokenGatedDropStage",
"name": "dropStage",
"type": "tuple"
}
],
"name": "TokenGatedDropStageUpdated",
"type": "event"
},
{
"inputs": [
{
"internalType": "address",
"name": "nftContract",
"type": "address"
}
],
"name": "getAllowListMerkleRoot",
"outputs": [
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "nftContract",
"type": "address"
}
],
"name": "getAllowedFeeRecipients",
"outputs": [
{
"internalType": "address[]",
"name": "",
"type": "address[]"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "nftContract",
"type": "address"
},
{
"internalType": "address",
"name": "allowedNftToken",
"type": "address"
},
{
"internalType": "uint256",
"name": "allowedNftTokenId",
"type": "uint256"
}
],
"name": "getAllowedNftTokenIdIsRedeemed",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "nftContract",
"type": "address"
}
],
"name": "getCreatorPayoutAddress",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "nftContract",
"type": "address"
},
{
"internalType": "address",
"name": "feeRecipient",
"type": "address"
}
],
"name": "getFeeRecipientIsAllowed",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "nftContract",
"type": "address"
},
{
"internalType": "address",
"name": "payer",
"type": "address"
}
],
"name": "getPayerIsAllowed",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "nftContract",
"type": "address"
}
],
"name": "getPayers",
"outputs": [
{
"internalType": "address[]",
"name": "",
"type": "address[]"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "nftContract",
"type": "address"
}
],
"name": "getPublicDrop",
"outputs": [
{
"components": [
{
"internalType": "uint80",
"name": "mintPrice",
"type": "uint80"
},
{
"internalType": "uint48",
"name": "startTime",
"type": "uint48"
},
{
"internalType": "uint48",
"name": "endTime",
"type": "uint48"
},
{
"internalType": "uint16",
"name": "maxTotalMintableByWallet",
"type": "uint16"
},
{
"internalType": "uint16",
"name": "feeBps",
"type": "uint16"
},
{
"internalType": "bool",
"name": "restrictFeeRecipients",
"type": "bool"
}
],
"internalType": "struct PublicDrop",
"name": "",
"type": "tuple"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "nftContract",
"type": "address"
},
{
"internalType": "address",
"name": "signer",
"type": "address"
}
],
"name": "getSignedMintValidationParams",
"outputs": [
{
"components": [
{
"internalType": "uint80",
"name": "minMintPrice",
"type": "uint80"
},
{
"internalType": "uint24",
"name": "maxMaxTotalMintableByWallet",
"type": "uint24"
},
{
"internalType": "uint40",
"name": "minStartTime",
"type": "uint40"
},
{
"internalType": "uint40",
"name": "maxEndTime",
"type": "uint40"
},
{
"internalType": "uint40",
"name": "maxMaxTokenSupplyForStage",
"type": "uint40"
},
{
"internalType": "uint16",
"name": "minFeeBps",
"type": "uint16"
},
{
"internalType": "uint16",
"name": "maxFeeBps",
"type": "uint16"
}
],
"internalType": "struct SignedMintValidationParams",
"name": "",
"type": "tuple"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "nftContract",
"type": "address"
}
],
"name": "getSigners",
"outputs": [
{
"internalType": "address[]",
"name": "",
"type": "address[]"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "nftContract",
"type": "address"
}
],
"name": "getTokenGatedAllowedTokens",
"outputs": [
{
"internalType": "address[]",
"name": "",
"type": "address[]"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "nftContract",
"type": "address"
},
{
"internalType": "address",
"name": "allowedNftToken",
"type": "address"
}
],
"name": "getTokenGatedDrop",
"outputs": [
{
"components": [
{
"internalType": "uint80",
"name": "mintPrice",
"type": "uint80"
},
{
"internalType": "uint16",
"name": "maxTotalMintableByWallet",
"type": "uint16"
},
{
"internalType": "uint48",
"name": "startTime",
"type": "uint48"
},
{
"internalType": "uint48",
"name": "endTime",
"type": "uint48"
},
{
"internalType": "uint8",
"name": "dropStageIndex",
"type": "uint8"
},
{
"internalType": "uint32",
"name": "maxTokenSupplyForStage",
"type": "uint32"
},
{
"internalType": "uint16",
"name": "feeBps",
"type": "uint16"
},
{
"internalType": "bool",
"name": "restrictFeeRecipients",
"type": "bool"
}
],
"internalType": "struct TokenGatedDropStage",
"name": "",
"type": "tuple"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "nftContract",
"type": "address"
},
{
"internalType": "address",
"name": "feeRecipient",
"type": "address"
},
{
"internalType": "address",
"name": "minterIfNotPayer",
"type": "address"
},
{
"internalType": "uint256",
"name": "quantity",
"type": "uint256"
},
{
"components": [
{
"internalType": "uint256",
"name": "mintPrice",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "maxTotalMintableByWallet",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "startTime",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "endTime",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "dropStageIndex",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "maxTokenSupplyForStage",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "feeBps",
"type": "uint256"
},
{
"internalType": "bool",
"name": "restrictFeeRecipients",
"type": "bool"
}
],
"internalType": "struct MintParams",
"name": "mintParams",
"type": "tuple"
},
{
"internalType": "bytes32[]",
"name": "proof",
"type": "bytes32[]"
}
],
"name": "mintAllowList",
"outputs": [],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "nftContract",
"type": "address"
},
{
"internalType": "address",
"name": "feeRecipient",
"type": "address"
},
{
"internalType": "address",
"name": "minterIfNotPayer",
"type": "address"
},
{
"components": [
{
"internalType": "address",
"name": "allowedNftToken",
"type": "address"
},
{
"internalType": "uint256[]",
"name": "allowedNftTokenIds",
"type": "uint256[]"
}
],
"internalType": "struct TokenGatedMintParams",
"name": "mintParams",
"type": "tuple"
}
],
"name": "mintAllowedTokenHolder",
"outputs": [],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "nftContract",
"type": "address"
},
{
"internalType": "address",
"name": "feeRecipient",
"type": "address"
},
{
"internalType": "address",
"name": "minterIfNotPayer",
"type": "address"
},
{
"internalType": "uint256",
"name": "quantity",
"type": "uint256"
}
],
"name": "mintPublic",
"outputs": [],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "nftContract",
"type": "address"
},
{
"internalType": "address",
"name": "feeRecipient",
"type": "address"
},
{
"internalType": "address",
"name": "minterIfNotPayer",
"type": "address"
},
{
"internalType": "uint256",
"name": "quantity",
"type": "uint256"
},
{
"components": [
{
"internalType": "uint256",
"name": "mintPrice",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "maxTotalMintableByWallet",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "startTime",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "endTime",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "dropStageIndex",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "maxTokenSupplyForStage",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "feeBps",
"type": "uint256"
},
{
"internalType": "bool",
"name": "restrictFeeRecipients",
"type": "bool"
}
],
"internalType": "struct MintParams",
"name": "mintParams",
"type": "tuple"
},
{
"internalType": "uint256",
"name": "salt",
"type": "uint256"
},
{
"internalType": "bytes",
"name": "signature",
"type": "bytes"
}
],
"name": "mintSigned",
"outputs": [],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [
{
"components": [
{
"internalType": "bytes32",
"name": "merkleRoot",
"type": "bytes32"
},
{
"internalType": "string[]",
"name": "publicKeyURIs",
"type": "string[]"
},
{
"internalType": "string",
"name": "allowListURI",
"type": "string"
}
],
"internalType": "struct AllowListData",
"name": "allowListData",
"type": "tuple"
}
],
"name": "updateAllowList",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "feeRecipient",
"type": "address"
},
{
"internalType": "bool",
"name": "allowed",
"type": "bool"
}
],
"name": "updateAllowedFeeRecipient",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "_payoutAddress",
"type": "address"
}
],
"name": "updateCreatorPayoutAddress",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "string",
"name": "dropURI",
"type": "string"
}
],
"name": "updateDropURI",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "payer",
"type": "address"
},
{
"internalType": "bool",
"name": "allowed",
"type": "bool"
}
],
"name": "updatePayer",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"components": [
{
"internalType": "uint80",
"name": "mintPrice",
"type": "uint80"
},
{
"internalType": "uint48",
"name": "startTime",
"type": "uint48"
},
{
"internalType": "uint48",
"name": "endTime",
"type": "uint48"
},
{
"internalType": "uint16",
"name": "maxTotalMintableByWallet",
"type": "uint16"
},
{
"internalType": "uint16",
"name": "feeBps",
"type": "uint16"
},
{
"internalType": "bool",
"name": "restrictFeeRecipients",
"type": "bool"
}
],
"internalType": "struct PublicDrop",
"name": "publicDrop",
"type": "tuple"
}
],
"name": "updatePublicDrop",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "signer",
"type": "address"
},
{
"components": [
{
"internalType": "uint80",
"name": "minMintPrice",
"type": "uint80"
},
{
"internalType": "uint24",
"name": "maxMaxTotalMintableByWallet",
"type": "uint24"
},
{
"internalType": "uint40",
"name": "minStartTime",
"type": "uint40"
},
{
"internalType": "uint40",
"name": "maxEndTime",
"type": "uint40"
},
{
"internalType": "uint40",
"name": "maxMaxTokenSupplyForStage",
"type": "uint40"
},
{
"internalType": "uint16",
"name": "minFeeBps",
"type": "uint16"
},
{
"internalType": "uint16",
"name": "maxFeeBps",
"type": "uint16"
}
],
"internalType": "struct SignedMintValidationParams",
"name": "signedMintValidationParams",
"type": "tuple"
}
],
"name": "updateSignedMintValidationParams",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "allowedNftToken",
"type": "address"
},
{
"components": [
{
"internalType": "uint80",
"name": "mintPrice",
"type": "uint80"
},
{
"internalType": "uint16",
"name": "maxTotalMintableByWallet",
"type": "uint16"
},
{
"internalType": "uint48",
"name": "startTime",
"type": "uint48"
},
{
"internalType": "uint48",
"name": "endTime",
"type": "uint48"
},
{
"internalType": "uint8",
"name": "dropStageIndex",
"type": "uint8"
},
{
"internalType": "uint32",
"name": "maxTokenSupplyForStage",
"type": "uint32"
},
{
"internalType": "uint16",
"name": "feeBps",
"type": "uint16"
},
{
"internalType": "bool",
"name": "restrictFeeRecipients",
"type": "bool"
}
],
"internalType": "struct TokenGatedDropStage",
"name": "dropStage",
"type": "tuple"
}
],
"name": "updateTokenGatedDrop",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
}
]Source code
// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;
import { ISeaDrop } from "./interfaces/ISeaDrop.sol";
import {
INonFungibleSeaDropToken
} from "./interfaces/INonFungibleSeaDropToken.sol";
import {
AllowListData,
MintParams,
PublicDrop,
TokenGatedDropStage,
TokenGatedMintParams,
SignedMintValidationParams
} from "./lib/SeaDropStructs.sol";
import { SafeTransferLib } from "solmate/utils/SafeTransferLib.sol";
import { ReentrancyGuard } from "solmate/utils/ReentrancyGuard.sol";
import {
IERC721
} from "openzeppelin-contracts/contracts/token/ERC721/IERC721.sol";
import {
IERC165
} from "openzeppelin-contracts/contracts/utils/introspection/IERC165.sol";
import {
ECDSA
} from "openzeppelin-contracts/contracts/utils/cryptography/ECDSA.sol";
import {
MerkleProof
} from "openzeppelin-contracts/contracts/utils/cryptography/MerkleProof.sol";
/**
* @title SeaDrop
* @author James Wenzel (emo.eth)
* @author Ryan Ghods (ralxz.eth)
* @author Stephan Min (stephanm.eth)
* @notice SeaDrop is a contract to help facilitate ERC721 token drops
* with functionality for public, allow list, server-side signed,
* and token-gated drops.
*/
contract SeaDrop is ISeaDrop, ReentrancyGuard {
using ECDSA for bytes32;
/// @notice Track the public drops.
mapping(address => PublicDrop) private _publicDrops;
/// @notice Track the creator payout addresses.
mapping(address => address) private _creatorPayoutAddresses;
/// @notice Track the allow list merkle roots.
mapping(address => bytes32) private _allowListMerkleRoots;
/// @notice Track the allowed fee recipients.
mapping(address => mapping(address => bool)) private _allowedFeeRecipients;
/// @notice Track the enumerated allowed fee recipients.
mapping(address => address[]) private _enumeratedFeeRecipients;
/// @notice Track the parameters for allowed signers for server-side drops.
mapping(address => mapping(address => SignedMintValidationParams))
private _signedMintValidationParams;
/// @notice Track the signers for each server-side drop.
mapping(address => address[]) private _enumeratedSigners;
/// @notice Track the used signature digests.
mapping(bytes32 => bool) private _usedDigests;
/// @notice Track the allowed payers.
mapping(address => mapping(address => bool)) private _allowedPayers;
/// @notice Track the enumerated allowed payers.
mapping(address => address[]) private _enumeratedPayers;
/// @notice Track the token gated drop stages.
mapping(address => mapping(address => TokenGatedDropStage))
private _tokenGatedDrops;
/// @notice Track the tokens for token gated drops.
mapping(address => address[]) private _enumeratedTokenGatedTokens;
/// @notice Track the redeemed token IDs for token gated drop stages.
mapping(address => mapping(address => mapping(uint256 => bool)))
private _tokenGatedRedeemed;
/// @notice Internal constants for EIP-712: Typed structured
/// data hashing and signing
bytes32 internal constant _SIGNED_MINT_TYPEHASH =
// prettier-ignore
keccak256(
"SignedMint("
"address nftContract,"
"address minter,"
"address feeRecipient,"
"MintParams mintParams,"
"uint256 salt"
")"
"MintParams("
"uint256 mintPrice,"
"uint256 maxTotalMintableByWallet,"
"uint256 startTime,"
"uint256 endTime,"
"uint256 dropStageIndex,"
"uint256 maxTokenSupplyForStage,"
"uint256 feeBps,"
"bool restrictFeeRecipients"
")"
);
bytes32 internal constant _MINT_PARAMS_TYPEHASH =
// prettier-ignore
keccak256(
"MintParams("
"uint256 mintPrice,"
"uint256 maxTotalMintableByWallet,"
"uint256 startTime,"
"uint256 endTime,"
"uint256 dropStageIndex,"
"uint256 maxTokenSupplyForStage,"
"uint256 feeBps,"
"bool restrictFeeRecipients"
")"
);
bytes32 internal constant _EIP_712_DOMAIN_TYPEHASH =
// prettier-ignore
keccak256(
"EIP712Domain("
"string name,"
"string version,"
"uint256 chainId,"
"address verifyingContract"
")"
);
bytes32 internal constant _NAME_HASH = keccak256("SeaDrop");
bytes32 internal constant _VERSION_HASH = keccak256("1.0");
uint256 internal immutable _CHAIN_ID = block.chainid;
bytes32 internal immutable _DOMAIN_SEPARATOR;
/// @notice Constant for an unlimited `maxTokenSupplyForStage`.
/// Used in `mintPublic` where no `maxTokenSupplyForStage`
/// is stored in the `PublicDrop` struct.
uint256 internal constant _UNLIMITED_MAX_TOKEN_SUPPLY_FOR_STAGE =
type(uint256).max;
/// @notice Constant for a public mint's `dropStageIndex`.
/// Used in `mintPublic` where no `dropStageIndex`
/// is stored in the `PublicDrop` struct.
uint256 internal constant _PUBLIC_DROP_STAGE_INDEX = 0;
/**
* @notice Ensure only tokens implementing INonFungibleSeaDropToken can
* call the update methods.
*/
modifier onlyINonFungibleSeaDropToken() virtual {
if (
!IERC165(msg.sender).supportsInterface(
type(INonFungibleSeaDropToken).interfaceId
)
) {
revert OnlyINonFungibleSeaDropToken(msg.sender);
}
_;
}
/**
* @notice Constructor for the contract deployment.
*/
constructor() {
// Derive the domain separator.
_DOMAIN_SEPARATOR = _deriveDomainSeparator();
}
/**
* @notice Mint a public drop.
*
* @param nftContract The nft contract to mint.
* @param feeRecipient The fee recipient.
* @param minterIfNotPayer The mint recipient if different than the payer.
* @param quantity The number of tokens to mint.
*/
function mintPublic(
address nftContract,
address feeRecipient,
address minterIfNotPayer,
uint256 quantity
) external payable override {
// Get the public drop data.
PublicDrop memory publicDrop = _publicDrops[nftContract];
// Ensure that the drop has started.
_checkActive(publicDrop.startTime, publicDrop.endTime);
// Put the mint price on the stack.
uint256 mintPrice = publicDrop.mintPrice;
// Validate payment is correct for number minted.
_checkCorrectPayment(quantity, mintPrice);
// Get the minter address.
address minter = minterIfNotPayer != address(0)
? minterIfNotPayer
: msg.sender;
// Ensure the payer is allowed if not the minter.
if (minter != msg.sender) {
if (!_allowedPayers[nftContract][msg.sender]) {
revert PayerNotAllowed();
}
}
// Check that the minter is allowed to mint the desired quantity.
_checkMintQuantity(
nftContract,
minter,
quantity,
publicDrop.maxTotalMintableByWallet,
_UNLIMITED_MAX_TOKEN_SUPPLY_FOR_STAGE
);
// Check that the fee recipient is allowed if restricted.
_checkFeeRecipientIsAllowed(
nftContract,
feeRecipient,
publicDrop.restrictFeeRecipients
);
// Mint the token(s), split the payout, emit an event.
_mintAndPay(
nftContract,
minter,
quantity,
mintPrice,
_PUBLIC_DROP_STAGE_INDEX,
publicDrop.feeBps,
feeRecipient
);
}
/**
* @notice Mint from an allow list.
*
* @param nftContract The nft contract to mint.
* @param feeRecipient The fee recipient.
* @param minterIfNotPayer The mint recipient if different than the payer.
* @param quantity The number of tokens to mint.
* @param mintParams The mint parameters.
* @param proof The proof for the leaf of the allow list.
*/
function mintAllowList(
address nftContract,
address feeRecipient,
address minterIfNotPayer,
uint256 quantity,
MintParams calldata mintParams,
bytes32[] calldata proof
) external payable override {
// Check that the drop stage is active.
_checkActive(mintParams.startTime, mintParams.endTime);
// Put the mint price on the stack.
uint256 mintPrice = mintParams.mintPrice;
// Validate payment is correct for number minted.
_checkCorrectPayment(quantity, mintPrice);
// Get the minter address.
address minter = minterIfNotPayer != address(0)
? minterIfNotPayer
: msg.sender;
// Ensure the payer is allowed if not the minter.
if (minter != msg.sender) {
if (!_allowedPayers[nftContract][msg.sender]) {
revert PayerNotAllowed();
}
}
// Check that the minter is allowed to mint the desired quantity.
_checkMintQuantity(
nftContract,
minter,
quantity,
mintParams.maxTotalMintableByWallet,
mintParams.maxTokenSupplyForStage
);
// Check that the fee recipient is allowed if restricted.
_checkFeeRecipientIsAllowed(
nftContract,
feeRecipient,
mintParams.restrictFeeRecipients
);
// Verify the proof.
if (
!MerkleProof.verify(
proof,
_allowListMerkleRoots[nftContract],
keccak256(abi.encode(minter, mintParams))
)
) {
revert InvalidProof();
}
// Mint the token(s), split the payout, emit an event.
_mintAndPay(
nftContract,
minter,
quantity,
mintPrice,
mintParams.dropStageIndex,
mintParams.feeBps,
feeRecipient
);
}
/**
* @notice Mint with a server-side signature.
* Note that a signature can only be used once.
*
* @param nftContract The nft contract to mint.
* @param feeRecipient The fee recipient.
* @param minterIfNotPayer The mint recipient if different than the payer.
* @param quantity The number of tokens to mint.
* @param mintParams The mint parameters.
* @param salt The salt for the signed mint.
* @param signature The server-side signature, must be an allowed
* signer.
*/
function mintSigned(
address nftContract,
address feeRecipient,
address minterIfNotPayer,
uint256 quantity,
MintParams calldata mintParams,
uint256 salt,
bytes calldata signature
) external payable override {
// Check that the drop stage is active.
_checkActive(mintParams.startTime, mintParams.endTime);
// Validate payment is correct for number minted.
_checkCorrectPayment(quantity, mintParams.mintPrice);
// Get the minter address.
address minter = minterIfNotPayer != address(0)
? minterIfNotPayer
: msg.sender;
// Ensure the payer is allowed if not the minter.
if (minter != msg.sender) {
if (!_allowedPayers[nftContract][msg.sender]) {
revert PayerNotAllowed();
}
}
// Check that the minter is allowed to mint the desired quantity.
_checkMintQuantity(
nftContract,
minter,
quantity,
mintParams.maxTotalMintableByWallet,
mintParams.maxTokenSupplyForStage
);
// Check that the fee recipient is allowed if restricted.
_checkFeeRecipientIsAllowed(
nftContract,
feeRecipient,
mintParams.restrictFeeRecipients
);
// Validate the signature in a block scope to avoid "stack too deep".
{
// Get the digest to verify the EIP-712 signature.
bytes32 digest = _getDigest(
nftContract,
minter,
feeRecipient,
mintParams,
salt
);
// Ensure the digest has not already been used.
if (_usedDigests[digest]) {
revert SignatureAlreadyUsed();
}
// Mark the digest as used.
_usedDigests[digest] = true;
// Use the recover method to see what address was used to create
// the signature on this data.
// Note that if the digest doesn't exactly match what was signed we'll
// get a random recovered address.
address recoveredAddress = digest.recover(signature);
_validateSignerAndParams(nftContract, mintParams, recoveredAddress);
}
// Mint the token(s), split the payout, emit an event.
_mintAndPay(
nftContract,
minter,
quantity,
mintParams.mintPrice,
mintParams.dropStageIndex,
mintParams.feeBps,
feeRecipient
);
}
/**
* @notice Enforce stored parameters for signed mints to mitigate
* the effects of a malicious signer.
*/
function _validateSignerAndParams(
address nftContract,
MintParams memory mintParams,
address signer
) internal view {
SignedMintValidationParams
memory signedMintValidationParams = _signedMintValidationParams[
nftContract
][signer];
// Check that SignedMintValidationParams have been initialized; if not,
// this is an invalid signer.
if (signedMintValidationParams.maxMaxTotalMintableByWallet == 0) {
revert InvalidSignature(signer);
}
// Validate individual params.
if (mintParams.mintPrice < signedMintValidationParams.minMintPrice) {
revert InvalidSignedMintPrice(
mintParams.mintPrice,
signedMintValidationParams.minMintPrice
);
}
if (
mintParams.maxTotalMintableByWallet >
signedMintValidationParams.maxMaxTotalMintableByWallet
) {
revert InvalidSignedMaxTotalMintableByWallet(
mintParams.maxTotalMintableByWallet,
signedMintValidationParams.maxMaxTotalMintableByWallet
);
}
if (mintParams.startTime < signedMintValidationParams.minStartTime) {
revert InvalidSignedStartTime(
mintParams.startTime,
signedMintValidationParams.minStartTime
);
}
if (mintParams.endTime > signedMintValidationParams.maxEndTime) {
revert InvalidSignedEndTime(
mintParams.endTime,
signedMintValidationParams.maxEndTime
);
}
if (
mintParams.maxTokenSupplyForStage >
signedMintValidationParams.maxMaxTokenSupplyForStage
) {
revert InvalidSignedMaxTokenSupplyForStage(
mintParams.maxTokenSupplyForStage,
signedMintValidationParams.maxMaxTokenSupplyForStage
);
}
if (mintParams.feeBps > signedMintValidationParams.maxFeeBps) {
revert InvalidSignedFeeBps(
mintParams.feeBps,
signedMintValidationParams.maxFeeBps
);
}
if (mintParams.feeBps < signedMintValidationParams.minFeeBps) {
revert InvalidSignedFeeBps(
mintParams.feeBps,
signedMintValidationParams.minFeeBps
);
}
if (!mintParams.restrictFeeRecipients) {
revert SignedMintsMustRestrictFeeRecipients();
}
}
/**
* @notice Mint as an allowed token holder.
* This will mark the token ids as redeemed and will revert if the
* same token id is attempted to be redeemed twice.
*
* @param nftContract The nft contract to mint.
* @param feeRecipient The fee recipient.
* @param minterIfNotPayer The mint recipient if different than the payer.
* @param mintParams The token gated mint params.
*/
function mintAllowedTokenHolder(
address nftContract,
address feeRecipient,
address minterIfNotPayer,
TokenGatedMintParams calldata mintParams
) external payable override {
// Get the minter address.
address minter = minterIfNotPayer != address(0)
? minterIfNotPayer
: msg.sender;
// Ensure the payer is allowed if not the minter.
if (minter != msg.sender) {
if (!_allowedPayers[nftContract][msg.sender]) {
revert PayerNotAllowed();
}
}
// Put the allowedNftToken on the stack for more efficient access.
address allowedNftToken = mintParams.allowedNftToken;
// Set the dropStage to a variable.
TokenGatedDropStage memory dropStage = _tokenGatedDrops[nftContract][
allowedNftToken
];
// Validate that the dropStage is active.
_checkActive(dropStage.startTime, dropStage.endTime);
// Check that the fee recipient is allowed if restricted.
_checkFeeRecipientIsAllowed(
nftContract,
feeRecipient,
dropStage.restrictFeeRecipients
);
// Put the mint quantity on the stack for more efficient access.
uint256 mintQuantity = mintParams.allowedNftTokenIds.length;
// Validate payment is correct for number minted.
_checkCorrectPayment(mintQuantity, dropStage.mintPrice);
// Check that the minter is allowed to mint the desired quantity.
_checkMintQuantity(
nftContract,
minter,
mintQuantity,
dropStage.maxTotalMintableByWallet,
dropStage.maxTokenSupplyForStage
);
// Iterate through each allowedNftTokenId
// to ensure it is not already redeemed.
for (uint256 i = 0; i < mintQuantity; ) {
// Put the tokenId on the stack.
uint256 tokenId = mintParams.allowedNftTokenIds[i];
// Check that the minter is the owner of the allowedNftTokenId.
if (IERC721(allowedNftToken).ownerOf(tokenId) != minter) {
revert TokenGatedNotTokenOwner(
nftContract,
allowedNftToken,
tokenId
);
}
// Cache the storage pointer for cheaper access.
mapping(uint256 => bool)
storage redeemedTokenIds = _tokenGatedRedeemed[nftContract][
allowedNftToken
];
// Check that the token id has not already been redeemed.
if (redeemedTokenIds[tokenId]) {
revert TokenGatedTokenIdAlreadyRedeemed(
nftContract,
allowedNftToken,
tokenId
);
}
// Mark the token id as redeemed.
redeemedTokenIds[tokenId] = true;
unchecked {
++i;
}
}
// Mint the token(s), split the payout, emit an event.
_mintAndPay(
nftContract,
minter,
mintQuantity,
dropStage.mintPrice,
dropStage.dropStageIndex,
dropStage.feeBps,
feeRecipient
);
}
/**
* @notice Check that the drop stage is active.
*
* @param startTime The drop stage start time.
* @param endTime The drop stage end time.
*/
function _checkActive(uint256 startTime, uint256 endTime) internal view {
if (block.timestamp < startTime || block.timestamp > endTime) {
// Revert if the drop stage is not active.
revert NotActive(block.timestamp, startTime, endTime);
}
}
/**
* @notice Check that the fee recipient is allowed.
*
* @param nftContract The nft contract.
* @param feeRecipient The fee recipient.
* @param restrictFeeRecipients If the fee recipients are restricted.
*/
function _checkFeeRecipientIsAllowed(
address nftContract,
address feeRecipient,
bool restrictFeeRecipients
) internal view {
// Ensure the fee recipient is not the zero address.
if (feeRecipient == address(0)) {
revert FeeRecipientCannotBeZeroAddress();
}
// Revert if the fee recipient is restricted and not allowed.
if (restrictFeeRecipients)
if (!_allowedFeeRecipients[nftContract][feeRecipient]) {
revert FeeRecipientNotAllowed();
}
}
/**
* @notice Check that the wallet is allowed to mint the desired quantity.
*
* @param nftContract The nft contract.
* @param minter The mint recipient.
* @param quantity The number of tokens to mint.
* @param maxTotalMintableByWallet The max allowed mints per wallet.
* @param maxTokenSupplyForStage The max token supply for the drop stage.
*/
function _checkMintQuantity(
address nftContract,
address minter,
uint256 quantity,
uint256 maxTotalMintableByWallet,
uint256 maxTokenSupplyForStage
) internal view {
// Mint quantity of zero is not valid.
if (quantity == 0) {
revert MintQuantityCannotBeZero();
}
// Get the mint stats.
(
uint256 minterNumMinted,
uint256 currentTotalSupply,
uint256 maxSupply
) = INonFungibleSeaDropToken(nftContract).getMintStats(minter);
// Ensure mint quantity doesn't exceed maxTotalMintableByWallet.
if (quantity + minterNumMinted > maxTotalMintableByWallet) {
revert MintQuantityExceedsMaxMintedPerWallet(
quantity + minterNumMinted,
maxTotalMintableByWallet
);
}
// Ensure mint quantity doesn't exceed maxSupply.
if (quantity + currentTotalSupply > maxSupply) {
revert MintQuantityExceedsMaxSupply(
quantity + currentTotalSupply,
maxSupply
);
}
// Ensure mint quantity doesn't exceed maxTokenSupplyForStage.
if (quantity + currentTotalSupply > maxTokenSupplyForStage) {
revert MintQuantityExceedsMaxTokenSupplyForStage(
quantity + currentTotalSupply,
maxTokenSupplyForStage
);
}
}
/**
* @notice Revert if the payment is not the quantity times the mint price.
*
* @param quantity The number of tokens to mint.
* @param mintPrice The mint price per token.
*/
function _checkCorrectPayment(uint256 quantity, uint256 mintPrice)
internal
view
{
// Revert if the tx's value doesn't match the total cost.
if (msg.value != quantity * mintPrice) {
revert IncorrectPayment(msg.value, quantity * mintPrice);
}
}
/**
* @notice Split the payment payout for the creator and fee recipient.
*
* @param nftContract The nft contract.
* @param feeRecipient The fee recipient.
* @param feeBps The fee basis points.
*/
function _splitPayout(
address nftContract,
address feeRecipient,
uint256 feeBps
) internal {
// Revert if the fee basis points is greater than 10_000.
if (feeBps > 10_000) {
revert InvalidFeeBps(feeBps);
}
// Get the creator payout address.
address creatorPayoutAddress = _creatorPayoutAddresses[nftContract];
// Ensure the creator payout address is not the zero address.
if (creatorPayoutAddress == address(0)) {
revert CreatorPayoutAddressCannotBeZeroAddress();
}
// msg.value has already been validated by this point, so can use it directly.
// If the fee is zero, just transfer to the creator and return.
if (feeBps == 0) {
SafeTransferLib.safeTransferETH(creatorPayoutAddress, msg.value);
return;
}
// Get the fee amount.
// Note that the fee amount is rounded down in favor of the creator.
uint256 feeAmount = (msg.value * feeBps) / 10_000;
// Get the creator payout amount. Fee amount is <= msg.value per above.
uint256 payoutAmount;
unchecked {
payoutAmount = msg.value - feeAmount;
}
// Transfer the fee amount to the fee recipient.
if (feeAmount > 0) {
SafeTransferLib.safeTransferETH(feeRecipient, feeAmount);
}
// Transfer the creator payout amount to the creator.
SafeTransferLib.safeTransferETH(creatorPayoutAddress, payoutAmount);
}
/**
* @notice Mints a number of tokens, splits the payment,
* and emits an event.
*
* @param nftContract The nft contract.
* @param minter The mint recipient.
* @param quantity The number of tokens to mint.
* @param mintPrice The mint price per token.
* @param dropStageIndex The drop stage index.
* @param feeBps The fee basis points.
* @param feeRecipient The fee recipient.
*/
function _mintAndPay(
address nftContract,
address minter,
uint256 quantity,
uint256 mintPrice,
uint256 dropStageIndex,
uint256 feeBps,
address feeRecipient
) internal nonReentrant {
// Mint the token(s).
INonFungibleSeaDropToken(nftContract).mintSeaDrop(minter, quantity);
if (mintPrice != 0) {
// Split the payment between the creator and fee recipient.
_splitPayout(nftContract, feeRecipient, feeBps);
}
// Emit an event for the mint.
emit SeaDropMint(
nftContract,
minter,
feeRecipient,
msg.sender,
quantity,
mintPrice,
feeBps,
dropStageIndex
);
}
/**
* @dev Internal view function to get the EIP-712 domain separator. If the
* chainId matches the chainId set on deployment, the cached domain
* separator will be returned; otherwise, it will be derived from
* scratch.
*
* @return The domain separator.
*/
function _domainSeparator() internal view returns (bytes32) {
// prettier-ignore
return block.chainid == _CHAIN_ID
? _DOMAIN_SEPARATOR
: _deriveDomainSeparator();
}
/**
* @dev Internal view function to derive the EIP-712 domain separator.
*
* @return The derived domain separator.
*/
function _deriveDomainSeparator() internal view returns (bytes32) {
// prettier-ignore
return keccak256(
abi.encode(
_EIP_712_DOMAIN_TYPEHASH,
_NAME_HASH,
_VERSION_HASH,
block.chainid,
address(this)
)
);
}
/**
* @notice Returns the public drop data for the nft contract.
*
* @param nftContract The nft contract.
*/
function getPublicDrop(address nftContract)
external
view
returns (PublicDrop memory)
{
return _publicDrops[nftContract];
}
/**
* @notice Returns the creator payout address for the nft contract.
*
* @param nftContract The nft contract.
*/
function getCreatorPayoutAddress(address nftContract)
external
view
returns (address)
{
return _creatorPayoutAddresses[nftContract];
}
/**
* @notice Returns the allow list merkle root for the nft contract.
*
* @param nftContract The nft contract.
*/
function getAllowListMerkleRoot(address nftContract)
external
view
returns (bytes32)
{
return _allowListMerkleRoots[nftContract];
}
/**
* @notice Returns if the specified fee recipient is allowed
* for the nft contract.
*
* @param nftContract The nft contract.
*/
function getFeeRecipientIsAllowed(address nftContract, address feeRecipient)
external
view
returns (bool)
{
return _allowedFeeRecipients[nftContract][feeRecipient];
}
/**
* @notice Returns an enumeration of allowed fee recipients for an
* nft contract when fee recipients are enforced.
*
* @param nftContract The nft contract.
*/
function getAllowedFeeRecipients(address nftContract)
external
view
returns (address[] memory)
{
return _enumeratedFeeRecipients[nftContract];
}
/**
* @notice Returns the server-side signers for the nft contract.
*
* @param nftContract The nft contract.
*/
function getSigners(address nftContract)
external
view
returns (address[] memory)
{
return _enumeratedSigners[nftContract];
}
/**
* @notice Returns the struct of SignedMintValidationParams for a signer.
*
* @param nftContract The nft contract.
* @param signer The signer.
*/
function getSignedMintValidationParams(address nftContract, address signer)
external
view
returns (SignedMintValidationParams memory)
{
return _signedMintValidationParams[nftContract][signer];
}
/**
* @notice Returns the payers for the nft contract.
*
* @param nftContract The nft contract.
*/
function getPayers(address nftContract)
external
view
returns (address[] memory)
{
return _enumeratedPayers[nftContract];
}
/**
* @notice Returns if the specified payer is allowed
* for the nft contract.
*
* @param nftContract The nft contract.
* @param payer The payer.
*/
function getPayerIsAllowed(address nftContract, address payer)
external
view
returns (bool)
{
return _allowedPayers[nftContract][payer];
}
/**
* @notice Returns the allowed token gated drop tokens for the nft contract.
*
* @param nftContract The nft contract.
*/
function getTokenGatedAllowedTokens(address nftContract)
external
view
returns (address[] memory)
{
return _enumeratedTokenGatedTokens[nftContract];
}
/**
* @notice Returns the token gated drop data for the nft contract
* and token gated nft.
*
* @param nftContract The nft contract.
* @param allowedNftToken The token gated nft token.
*/
function getTokenGatedDrop(address nftContract, address allowedNftToken)
external
view
returns (TokenGatedDropStage memory)
{
return _tokenGatedDrops[nftContract][allowedNftToken];
}
/**
* @notice Returns whether the token id for a token gated drop has been
* redeemed.
*
* @param nftContract The nft contract.
* @param allowedNftToken The token gated nft token.
* @param allowedNftTokenId The token gated nft token id to check.
*/
function getAllowedNftTokenIdIsRedeemed(
address nftContract,
address allowedNftToken,
uint256 allowedNftTokenId
) external view returns (bool) {
return
_tokenGatedRedeemed[nftContract][allowedNftToken][
allowedNftTokenId
];
}
/**
* @notice Emits an event to notify update of the drop URI.
*
* @param dropURI The new drop URI.
*/
function updateDropURI(string calldata dropURI)
external
onlyINonFungibleSeaDropToken
{
// Emit an event with the update.
emit DropURIUpdated(msg.sender, dropURI);
}
/**
* @notice Updates the public drop for the nft contract and emits an event.
*
* @param publicDrop The public drop data.
*/
function updatePublicDrop(PublicDrop calldata publicDrop)
external
override
onlyINonFungibleSeaDropToken
{
// Revert if the fee basis points is greater than 10_000.
if (publicDrop.feeBps > 10_000) {
revert InvalidFeeBps(publicDrop.feeBps);
}
// Set the public drop data.
_publicDrops[msg.sender] = publicDrop;
// Emit an event with the update.
emit PublicDropUpdated(msg.sender, publicDrop);
}
/**
* @notice Updates the allow list merkle root for the nft contract
* and emits an event.
*
* Note: Be sure only authorized users can call this from
* token contracts that implement INonFungibleSeaDropToken.
*
* @param allowListData The allow list data.
*/
function updateAllowList(AllowListData calldata allowListData)
external
override
onlyINonFungibleSeaDropToken
{
// Track the previous root.
bytes32 prevRoot = _allowListMerkleRoots[msg.sender];
// Update the merkle root.
_allowListMerkleRoots[msg.sender] = allowListData.merkleRoot;
// Emit an event with the update.
emit AllowListUpdated(
msg.sender,
prevRoot,
allowListData.merkleRoot,
allowListData.publicKeyURIs,
allowListData.allowListURI
);
}
/**
* @notice Updates the token gated drop stage for the nft contract
* and emits an event.
*
* 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 allowedNftToken The token gated nft token.
* @param dropStage The token gated drop stage data.
*/
function updateTokenGatedDrop(
address allowedNftToken,
TokenGatedDropStage calldata dropStage
) external override onlyINonFungibleSeaDropToken {
// Ensure the allowedNftToken is not the zero address.
if (allowedNftToken == address(0)) {
revert TokenGatedDropAllowedNftTokenCannotBeZeroAddress();
}
// Ensure the allowedNftToken cannot be the drop token itself.
if (allowedNftToken == msg.sender) {
revert TokenGatedDropAllowedNftTokenCannotBeDropToken();
}
// Revert if the fee basis points is greater than 10_000.
if (dropStage.feeBps > 10_000) {
revert InvalidFeeBps(dropStage.feeBps);
}
// Use maxTotalMintableByWallet != 0 as a signal that this update should
// add or update the drop stage, otherwise we will be removing.
bool addOrUpdateDropStage = dropStage.maxTotalMintableByWallet != 0;
// Get pointers to the token gated drop data and enumerated addresses.
TokenGatedDropStage storage existingDropStageData = _tokenGatedDrops[
msg.sender
][allowedNftToken];
address[] storage enumeratedTokens = _enumeratedTokenGatedTokens[
msg.sender
];
// Stage struct packs to a single slot, so load it
// as a uint256; if it is 0, it is empty.
bool dropStageDoesNotExist;
assembly {
dropStageDoesNotExist := iszero(sload(existingDropStageData.slot))
}
if (addOrUpdateDropStage) {
_tokenGatedDrops[msg.sender][allowedNftToken] = dropStage;
// Add to enumeration if it does not exist already.
if (dropStageDoesNotExist) {
enumeratedTokens.push(allowedNftToken);
}
} else {
// Check we are not deleting a drop stage that does not exist.
if (dropStageDoesNotExist) {
revert TokenGatedDropStageNotPresent();
}
// Clear storage slot and remove from enumeration.
delete _tokenGatedDrops[msg.sender][allowedNftToken];
_removeFromEnumeration(allowedNftToken, enumeratedTokens);
}
// Emit an event with the update.
emit TokenGatedDropStageUpdated(msg.sender, allowedNftToken, dropStage);
}
/**
* @notice Updates the creator payout address and emits an event.
*
* @param _payoutAddress The creator payout address.
*/
function updateCreatorPayoutAddress(address _payoutAddress)
external
onlyINonFungibleSeaDropToken
{
if (_payoutAddress == address(0)) {
revert CreatorPayoutAddressCannotBeZeroAddress();
}
// Set the creator payout address.
_creatorPayoutAddresses[msg.sender] = _payoutAddress;
// Emit an event with the update.
emit CreatorPayoutAddressUpdated(msg.sender, _payoutAddress);
}
/**
* @notice Updates the allowed fee recipient and emits an event.
*
* @param feeRecipient The fee recipient.
* @param allowed If the fee recipient is allowed.
*/
function updateAllowedFeeRecipient(address feeRecipient, bool allowed)
external
onlyINonFungibleSeaDropToken
{
if (feeRecipient == address(0)) {
revert FeeRecipientCannotBeZeroAddress();
}
// Track the enumerated storage.
address[] storage enumeratedStorage = _enumeratedFeeRecipients[
msg.sender
];
mapping(address => bool)
storage feeRecipientsMap = _allowedFeeRecipients[msg.sender];
if (allowed) {
if (feeRecipientsMap[feeRecipient]) {
revert DuplicateFeeRecipient();
}
feeRecipientsMap[feeRecipient] = true;
enumeratedStorage.push(feeRecipient);
} else {
if (!feeRecipientsMap[feeRecipient]) {
revert FeeRecipientNotPresent();
}
delete _allowedFeeRecipients[msg.sender][feeRecipient];
_removeFromEnumeration(feeRecipient, enumeratedStorage);
}
// Emit an event with the update.
emit AllowedFeeRecipientUpdated(msg.sender, feeRecipient, allowed);
}
/**
* @notice Updates the allowed server-side signers and emits an event.
*
* @param signer The signer to update.
* @param signedMintValidationParams Minimum and maximum parameters
* to enforce for signed mints.
*/
function updateSignedMintValidationParams(
address signer,
SignedMintValidationParams calldata signedMintValidationParams
) external onlyINonFungibleSeaDropToken {
if (signer == address(0)) {
revert SignerCannotBeZeroAddress();
}
if (signedMintValidationParams.minFeeBps > 10_000) {
revert InvalidFeeBps(signedMintValidationParams.minFeeBps);
}
if (signedMintValidationParams.maxFeeBps > 10_000) {
revert InvalidFeeBps(signedMintValidationParams.maxFeeBps);
}
// Track the enumerated storage.
address[] storage enumeratedStorage = _enumeratedSigners[msg.sender];
mapping(address => SignedMintValidationParams)
storage signedMintValidationParamsMap = _signedMintValidationParams[
msg.sender
];
SignedMintValidationParams
storage existingSignedMintValidationParams = signedMintValidationParamsMap[
signer
];
bool signedMintValidationParamsDoNotExist;
assembly {
signedMintValidationParamsDoNotExist := iszero(
sload(existingSignedMintValidationParams.slot)
)
}
// Use maxMaxTotalMintableByWallet as sentry for add/update or delete.
bool addOrUpdate = signedMintValidationParams
.maxMaxTotalMintableByWallet > 0;
if (addOrUpdate) {
signedMintValidationParamsMap[signer] = signedMintValidationParams;
if (signedMintValidationParamsDoNotExist) {
enumeratedStorage.push(signer);
}
} else {
if (
existingSignedMintValidationParams
.maxMaxTotalMintableByWallet == 0
) {
revert SignerNotPresent();
}
delete _signedMintValidationParams[msg.sender][signer];
_removeFromEnumeration(signer, enumeratedStorage);
}
// Emit an event with the update.
emit SignedMintValidationParamsUpdated(
msg.sender,
signer,
signedMintValidationParams
);
}
/**
* @notice Updates the allowed payer and emits an event.
*
* @param payer The payer to add or remove.
* @param allowed Whether to add or remove the payer.
*/
function updatePayer(address payer, bool allowed)
external
onlyINonFungibleSeaDropToken
{
if (payer == address(0)) {
revert PayerCannotBeZeroAddress();
}
// Track the enumerated storage.
address[] storage enumeratedStorage = _enumeratedPayers[msg.sender];
mapping(address => bool) storage payersMap = _allowedPayers[msg.sender];
if (allowed) {
if (payersMap[payer]) {
revert DuplicatePayer();
}
payersMap[payer] = true;
enumeratedStorage.push(payer);
} else {
if (!payersMap[payer]) {
revert PayerNotPresent();
}
delete _allowedPayers[msg.sender][payer];
_removeFromEnumeration(payer, enumeratedStorage);
}
// Emit an event with the update.
emit PayerUpdated(msg.sender, payer, allowed);
}
/**
* @notice Remove an address from a supplied enumeration.
*
* @param toRemove The address to remove.
* @param enumeration The enumerated addresses to parse.
*/
function _removeFromEnumeration(
address toRemove,
address[] storage enumeration
) internal {
// Cache the length.
uint256 enumerationLength = enumeration.length;
for (uint256 i = 0; i < enumerationLength; ) {
// Check if the enumerated element is the one we are deleting.
if (enumeration[i] == toRemove) {
// Swap with the last element.
enumeration[i] = enumeration[enumerationLength - 1];
// Delete the (now duplicated) last element.
enumeration.pop();
// Exit the loop.
break;
}
unchecked {
++i;
}
}
}
/**
* @notice Verify an EIP-712 signature by recreating the data structure
* that we signed on the client side, and then using that to recover
* the address that signed the signature for this data.
*
* @param nftContract The nft contract.
* @param minter The mint recipient.
* @param feeRecipient The fee recipient.
* @param mintParams The mint params.
* @param salt The salt for the signed mint.
*/
function _getDigest(
address nftContract,
address minter,
address feeRecipient,
MintParams memory mintParams,
uint256 salt
) internal view returns (bytes32 digest) {
bytes32 mintParamsHashStruct = keccak256(
abi.encode(
_MINT_PARAMS_TYPEHASH,
mintParams.mintPrice,
mintParams.maxTotalMintableByWallet,
mintParams.startTime,
mintParams.endTime,
mintParams.dropStageIndex,
mintParams.maxTokenSupplyForStage,
mintParams.feeBps,
mintParams.restrictFeeRecipients
)
);
digest = keccak256(
bytes.concat(
bytes2(0x1901),
_domainSeparator(),
keccak256(
abi.encode(
_SIGNED_MINT_TYPEHASH,
nftContract,
minter,
feeRecipient,
mintParamsHashStruct,
salt
)
)
)
);
}
}
Chain explorer8368msChain node79ms