ERC-6066: Signature Validation for NFTs
ERC-6066 extends ERC-1271 (smart-contract signatures) to NFTs: instead of asking "is this signature valid for this contract address?", you ask "is this signature valid for this specific tokenId held by this contract?". The motivation: NFT marketplaces, attestation systems, and gated communities want to validate signatures from the holder of a specific NFT, not just from the NFT contract as a whole.
OpenSea-like permissions ("only token #42 can sign this off-chain listing"), DAO membership-by-NFT, ERC-6551 token-bound account signatures all benefit from per-NFT signature validation.
Required Interface
interface IERC6066 {
/// Returns IERC6066.isValidSignature.selector (0x12edb34f) when the
/// signature is valid for the tokenId; bytes4(0) otherwise.
function isValidSignature(
uint256 tokenId,
bytes32 hash,
bytes calldata signature
) external view returns (bytes4 magicValue);
}A wallet, marketplace, or scoped-access contract calls isValidSignature(tokenId, hash, sig) and trusts the result iff the return value is 0x12edb34f. The NFT contract's implementation typically delegates to whoever currently holds tokenId — a simple recipe is "return valid iff signer == ownerOf(tokenId)".
Neo Equivalent: NEP-11 + NEP-30 Verify, Per-tokenId
Neo's native NEP-30Verify method is the contract-side signature validator — it returns true if the contract considers the supplied witness valid. The same shape extends to NFTs by adding a tokenId parameter and resolving the witness check against the holder of that token.
public static bool VerifyForToken(ByteString tokenId, ByteString hash, ByteString signature)
{
var holder = OwnerOf(tokenId);
if (holder is null) return false;
// The NFT holder's signature is valid for this token.
var holderPubKey = ResolvePubKeyForAccount(holder);
return (bool)CryptoLib.VerifyWithECDsa(hash, holderPubKey, signature, NamedCurveHash.secp256r1SHA256);
}| ERC-6066 (Ethereum) | Neo Equivalent | Notes |
|---|---|---|
IERC6066.isValidSignature(tokenId, hash, sig) returns 0x12edb34f if valid | VerifyForToken(tokenId, hash, sig) returns true if valid | Same shape; magic-selector handshake → boolean |
signer == ownerOf(tokenId) is the canonical recipe | holder = OwnerOf(tokenId); CheckWitness(holder) is the canonical recipe | Same |
| Composable with ERC-6551 (TBA holds the keys) | Composable with ERC-6551 mirror — TBA's Verify() returns true iff NFT holder signed | Direct port |
| Used by OpenSea, Reservoir, etc. for off-chain order signing | Same use case; Neo NFT marketplaces would call this for off-chain orders | Adoption is downstream-tooling-driven |
Composition With ERC-6551 (TBA)
The most powerful use of ERC-6066 is paired with token-bound accounts:
- The NFT's TBA (see ERC-6551) implements
Verify(NEP-30) such that signing on behalf of the TBA delegates to the NFT holder. - ERC-6066 /
VerifyForTokenon the NFT contract lets external callers validate "did the holder of token #N sign X?" without going through the TBA's address.
Together these two cover the full spectrum:
- TBA-as-signer (you want the TBA to sign on behalf of the NFT) → ERC-1271 + ERC-6551.
- NFT-as-signer (you want a per-tokenId predicate) → ERC-6066.
Migration Notes
For NFT marketplaces being ported from Ethereum to Neo:
- The off-chain signed-order shape stays the same (canonical hash over listing fields) — only the curve changes (secp256r1 instead of secp256k1).
- The on-chain validation hook
IERC6066.isValidSignature(tokenId, hash, sig)becomesVerifyForToken(tokenId, hash, sig)on the NEP-11 contract. - For order-cancellation flows, store consumed-nonce per
(tokenId, nonce)in the NEP-11's storage (same pattern as the ERC-3009 mirror).
This pattern is currently informal on Neo — no NEP-X formalises per-tokenId signature validation. The mirror page proposes the shape and recommends adopting it for any NFT contract that wants OpenSea-style off-chain order support.
