You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Add SeaDrop adapters for existing ERC-721 contracts - #234
SeaDrop mints by calling mintSeaDrop on the token contract, so an ERC-721 deployed without SeaDrop support can't run an OpenSea drop today. This adds adapters that SeaDrop treats as the drop's token contract. Each adapter delivers the mint on the existing contract (the target).
ERC721SeaDropAdapter (abstract) holds the SeaDrop surface: the allowed SeaDrop list, owner-only drop config forwarded to SeaDrop, a max supply for this drop, and getMintStats counted over this drop only. Subclasses implement _deliver.
ERC721SeaDropMintAdapter calls a target mint function shaped (address to, uint256 quantity), chosen by selector, after the target's owner grants the adapter the right to call it. It checks that the minter's balance rose by exactly quantity. A selector whose second argument is a token ID therefore reverts with UnexpectedMintCount and never mints the wrong count.
ERC721SeaDropTransferAdapter is for targets where only the owner can mint. The owner mints tokens to the adapter, or sends them with safeTransferFrom. The adapter queues only tokens that come from the owner and hands them out in the order they arrived. withdraw returns unsold tokens, and rescue returns tokens that were never queued.
Metadata and royalties stay on the target. setBaseURI and setRoyaltyInfo revert, and the royalty getters read the target's EIP-2981 values.
The constructor emits SeaDropTokenDeployed and SeaDropShimDeployed(address indexed tokenContract). OpenSea uses the second event to show the drop on the target's collection.
docs/ExistingContractAdapters.md explains how to pick an adapter, lists the deploy and configure steps, and says what stays on the target.
Testing
test/foundry/ERC721SeaDropAdapter.t.sol has 19 tests. They run real SeaDrop mints against a stand-in for an existing contract and cover:
public mints and their payouts
the drop's supply cap and the allowed SeaDrop check
a revert passed up from the target, the wrong-mint-shape revert, and mint counting by supply or by balance (including a recipient that forwards tokens)
a signed (allowlist) mint through the adapter, signed the way OpenSea's signer signs
owner-only config, metadata behavior (including a target with a fallback), interfaces and the shim event
the transfer adapter's queue order, supply cap, deposit rules, withdraw and rescue
forge test passes all 318 tests, and scripts/lint.sh check passes with Foundry 1.9.0. .gas-snapshot gains entries for the adapter tests, and no existing entry changed. The adapters have not been audited.
Notes
Anyone can deploy an adapter that names any target. Before OpenSea shows a drop on an existing collection, it has to confirm that the collection's owner set up the adapter. Until then, the docs tell creators to contact OpenSea for that link.
ERC721SeaDropAdapter lets a contract deployed without SeaDrop support run a
drop: SeaDrop treats the adapter as the token contract and the adapter
delivers each mint on the target. ERC721SeaDropMintAdapter calls a granted
(address,uint256) mint function and checks the minted count;
ERC721SeaDropTransferAdapter sells tokens the owner deposited, in order.
The adapter emits SeaDropShimDeployed(target) so OpenSea can show the drop
on the existing collection.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The reason will be displayed to describe this comment to others. Learn more.
🟡 Changes recommended
The mint adapter can revert valid purchases when a receiver forwards tokens during its callback because it checks the recipient’s retained balance rather than the quantity minted.
Adds adapters that let existing ERC-721 collections run SeaDrop drops without changing their deployed token contracts. SeaDrop handles drop configuration and payments through the adapter, while tokens remain on the original collection.
Changes:
Adds shared drop configuration and accounting, with mint-call and queued-transfer adapters.
Adds a test target and 16 tests covering minting, payouts, permissions, metadata, and inventory handling.
Documents adapter selection, deployment, configuration, and OpenSea linking.
File
Description
test/foundry/ERC721SeaDropAdapter.t.sol
Adds adapter integration tests using SeaDrop.
src/test/TestExistingERC721.sol
Provides an existing-token test fixture with configurable mint permissions.
src/adapters/ERC721SeaDropTransferAdapter.sol
Delivers queued inventory and supports withdrawals and recovery.
src/adapters/ERC721SeaDropMintAdapter.sol
Calls a target mint selector and checks recipient balance changes.
src/adapters/ERC721SeaDropAdapter.sol
Provides shared configuration, supply accounting, metadata behavior, and deployment events.
Count a mint by the target's totalSupply() when it has one, so a recipient
that forwards tokens in onERC721Received no longer fails the check. Note
that single-token purchases can't catch a token-ID mint function, that the
transfer adapter queues only tokens that arrive through the receiver
callback, and that ending one stage leaves the others open. Add gas
snapshots for the adapter tests.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The reason will be displayed to describe this comment to others. Learn more.
🔵 Needs a closer look
A successful fallback with empty returndata triggers an uncaught decoding revert, causing all three royalty getters to revert rather than return zero for targets without EIP-2981 support.
No open issues 🎉
1 previously missed issue
In code that hasn't changed since last review
Royalty getters revert on successful calls with empty returndata
src/adapters/ERC721SeaDropAdapter.sol:408
For a target without EIP-2981 but with a fallback that succeeds without returning data, this call succeeds and Solidity then reverts while decoding the empty returndata. That caller-side decoding error is not handled by catch, so all three royalty getters revert instead of returning zero. Use a low-level staticcall and validate the returndata before decoding; add a test with a non-reverting fallback.
Copilot's second review found that royaltyInfo reverted for a target whose fallback succeeds with empty returndata, because try/catch doesn't catch a decoding failure. That's fixed in ad2228b: the adapter now reads royalties with a raw staticcall and decodes only when the call returns at least 64 bytes. testRoyaltyGettersWithFallbackTarget covers it, and the gas snapshot has been refreshed.
The reason will be displayed to describe this comment to others. Learn more.
🔵 Needs a closer look
The adapter incorrectly treats totalSupply() as a monotonic mint counter, causing valid mints to revert when a receiver burns an existing token during its callback.
No open issues 🎉
1 previously missed issue
In code that hasn't changed since last review
Use a monotonic mint counter for burnable targets
src/adapters/ERC721SeaDropMintAdapter.sol:75
For burnable targets, totalSupply() measures circulating tokens, not the number minted. A receiver can burn a previously held target token during onERC721Received; minting two fresh tokens then increases supply by only one, so _deliver reverts a correctly shaped mint with UnexpectedMintCount. Please support a target-specific monotonic mint counter rather than automatically treating every available totalSupply() as one, and cover a receiver that burns an existing token during its callback.
Re the third Copilot review (a receiver that burns a token inside onERC721Received makes the totalSupply() delta come up short): I'm keeping the exact check on purpose. The only contract that can burn during that callback is the recipient's own, so the only purchase that reverts is the one that recipient made, and the revert refunds it. Nobody else's mint or funds are affected. Counting by balance wouldn't help, since a burn lowers the balance as well. Loosening the check to >= or "either counter matches" would weaken the check that catches token-ID mint functions, which is the reason the count exists.
The reason will be displayed to describe this comment to others. Learn more.
🟡 Changes recommended
The adapter suite lacks integration coverage for the existing-holder gated mint flow, leaving delivery, adapter-local mint accounting and limits, and repeat-redemption rejection unverified.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
SeaDrop mints by calling
mintSeaDropon the token contract, so an ERC-721 deployed without SeaDrop support can't run an OpenSea drop today. This adds adapters that SeaDrop treats as the drop's token contract. Each adapter delivers the mint on the existing contract (the target).ERC721SeaDropAdapter(abstract) holds the SeaDrop surface: the allowed SeaDrop list, owner-only drop config forwarded to SeaDrop, a max supply for this drop, andgetMintStatscounted over this drop only. Subclasses implement_deliver.ERC721SeaDropMintAdaptercalls a target mint function shaped(address to, uint256 quantity), chosen by selector, after the target's owner grants the adapter the right to call it. It checks that the minter's balance rose by exactlyquantity. A selector whose second argument is a token ID therefore reverts withUnexpectedMintCountand never mints the wrong count.ERC721SeaDropTransferAdapteris for targets where only the owner can mint. The owner mints tokens to the adapter, or sends them withsafeTransferFrom. The adapter queues only tokens that come from the owner and hands them out in the order they arrived.withdrawreturns unsold tokens, andrescuereturns tokens that were never queued.Metadata and royalties stay on the target.
setBaseURIandsetRoyaltyInforevert, and the royalty getters read the target's EIP-2981 values.The constructor emits
SeaDropTokenDeployedandSeaDropShimDeployed(address indexed tokenContract). OpenSea uses the second event to show the drop on the target's collection.docs/ExistingContractAdapters.md explains how to pick an adapter, lists the deploy and configure steps, and says what stays on the target.
Testing
test/foundry/ERC721SeaDropAdapter.t.solhas 19 tests. They run real SeaDrop mints against a stand-in for an existing contract and cover:withdrawandrescueforge testpasses all 318 tests, andscripts/lint.sh checkpasses with Foundry 1.9.0..gas-snapshotgains entries for the adapter tests, and no existing entry changed. The adapters have not been audited.Notes
Anyone can deploy an adapter that names any target. Before OpenSea shows a drop on an existing collection, it has to confirm that the collection's owner set up the adapter. Until then, the docs tell creators to contact OpenSea for that link.
🤖 Generated with Claude Code