Skip to content

Add SeaDrop adapters for existing ERC-721 contracts - #234

Open
ryanio wants to merge 6 commits into
mainfrom
mint-adapter
Open

ryanio wants to merge 6 commits into
mainfrom
mint-adapter

Conversation

@ryanio

@ryanio ryanio commented Nov 13, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

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.

🤖 Generated with Claude Code

ryanio and others added 2 commits November 13, 2026 09:46
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>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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.

5 open issues
What changed in this PR

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.
README.md Links to the existing-contract adapter guide.
docs/​ExistingContractAdapters.md Explains adapter requirements and operation.
docs/​BringYourOwnTokenContract.md Points already-deployed collections to adapters.

Review effort: Balanced

Comment thread docs/ExistingContractAdapters.md Resolved
Comment thread docs/ExistingContractAdapters.md Resolved
Comment thread test/foundry/ERC721SeaDropAdapter.t.sol Resolved
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>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Medium severity 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.

5 resolved since last review

Review effort: Balanced

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ryanio

ryanio commented Nov 13, 2026

Copy link
Copy Markdown
Collaborator Author

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.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Medium severity 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.

Review effort: Balanced

@ryanio

ryanio commented Nov 13, 2026

Copy link
Copy Markdown
Collaborator Author

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.

ryanio and others added 2 commits November 13, 2026 10:15
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
OpenSea runs drops with public and signed stages, so the docs drop the
token-gated guidance.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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.

1 open issue

Review effort: Balanced

Comment thread src/adapters/ERC721SeaDropAdapter.sol Resolved
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants