EIP-721 has two functions for transfering ownership. The simple one is transferFrom
:
/// @notice Transfer ownership of an NFT -- THE CALLER IS RESPONSIBLE
/// TO CONFIRM THAT `_to` IS CAPABLE OF RECEIVING NFTS OR ELSE
/// THEY MAY BE PERMANENTLY LOST
/// @dev Throws unless `msg.sender` is the current owner, an authorized
/// operator, or the approved address for this NFT. Throws if `_from` is
/// not the current owner. Throws if `_to` is the zero address. Throws if
/// `_tokenId` is not a valid NFT.
/// @param _from The current owner of the NFT
/// @param _to The new owner
/// @param _tokenId The NFT to transfer
function transferFrom(address _from, address _to, uint256 _tokenId) external payable;
As expected, this just changes the associated owner.
However safeTransferFrom
is a bit more tricky:
/// @notice Transfers the ownership of an NFT from one address to another address
/// @dev Throws unless `msg.sender` is the current owner, an authorized
/// operator, or the approved address for this NFT. Throws if `_from` is
/// not the current owner. Throws if `_to` is the zero address. Throws if
/// `_tokenId` is not a valid NFT. When transfer is complete, this function
/// checks if `_to` is a smart contract (code size > 0). If so, it calls
/// `onERC721Received` on `_to` and throws if the return value is not
/// `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`.
/// @param _from The current owner of the NFT
/// @param _to The new owner
/// @param _tokenId The NFT to transfer
/// @param data Additional data with no specified format, sent in call to `_to`
function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes data) external payable;
The important part here is:
When transfer is complete, this function checks if
_to
is a smart contract (code size > 0). If so, it callsonERC721Received
on_to
and throws if the return value is notbytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))
.
This means a contract must opt-in, as opposed to opt-out, into receiving EIP-721 compatible tokens.
Once an EOA authorizes a contract with EIP-3074, there are two ways such an address can interact with others:
- As an EOA
- Through a contract wallet
In both cases msg.sender.code.length == 0
, but in the second case the safe transfer should check for onERC721Received
. Happy side effect: EIP-3074 compatible contract wallets do not need to implement it.
Account Abstraction and Smart Contract Wallet solutions however are a bit more drastic, as some versions of them propose a default bytecode for EOAs. These default bytecodes are rather basic and do not deal with application layer standards. It would be weird just specifically supporting onERC721Received
in them. This means they won’t be able to receive NFTs via “safe transfers”, and if every EOA has such an assumed code, then none of them can interact that way.
It is important to note that there is the “unsafe” transferFrom
function for unchecked transfers, but various NFTs interpret the standard differently, and tooling / apps may have additional restrictions (such as only supporting “safe transfers”).
I do not have any proposal(s), only a discussion starter