About a month ago we announced that OpenRelay was among the first relayers to support 0x v2’s launch on Kovan. Today we’re happy to announce that we’ve launched support for ERC721 tokens.
What is ERC721?
Since our inception, OpenRelay has supported ERC20 tokens. ERC20 tokens are “fungible,” meaning every unit of a given ERC20 token is the same as any other unit. If you have 1 unit of Embiggen, your 1 MBGN is indistinguishable from my 1 MBGN.
ERC20 tokens are “nonfungible,” meaning each unit of a given ERC721 token is unique. If you have a CryptoStrikers card and I have a CryptoStrikers card, the two cards are distinguishable from one another. Even if we have two cards representing the same player, each one has unique properties like a serial number, making a distinction between your card and my card.
Trading ERC721 Tokens on OpenRelays
As of this post, our API supports the submission and validation of orders to trade ERC721 tokens. You can do any of the following:
- Offer one ERC721 token for another ERC721 token (trading ERC721 tokens)
- Offer one ERC721 token for a certain quantity of a specific ERC20 token (selling ERC721 tokens)
- Offer a certain quantity of a specific ERC20 token in exchange for an ERC721 token (bidding on ERC721 tokens)
OpenRelay will accept the orders (assuming they are fillable) and enter them into the order book. It will prune the order book on an ongoing basis to make sure unfillable orders are removed promptly.
Technical Hurdles
So why didn’t we support ERC721 tokens a month ago when we added support for ERC20 tokens?
There were a few challenges. First, we had to be able to validate an order on ingest. This part was pretty straightforward, but it differs between ERC20 and ERC721 tokens because they have different methods for checking ownership and approvals. We did a small refactor to the fund checker to support multiple token types, and that was it.
Spend Monitoring
The larger challenge was ongoing pruning. Several different ERC721 tokens were implemented at different stages of the ERC721 standardization process, and one of the major differences between them was the signature of certain events, primarily the Transfer event. The final ERC721 spec gives Transfer events the following signature:
event Transfer(
address indexed _from,
address indexed _to,
uint256 indexed _tokenId
);
CryptoKitties was the first widely known token based on an early ERC721 draft, and it had the signature:
event Transfer(
address _from,
address _to,
uint256 _tokenId
);
Meanwhile other tokens developed later in the standardization process had the signature:
event Transfer(
address indexed _from,
address indexed _to,
uint256 _tokenId
);
Which is indistinguishable from the Transfer event in ERC20. To properly prune our order book we had to deal with each of these distinct event signatures to be able to handle ERC721 variants at each of these stages. Figuring out all of the corner cases took some time, and it’s plausible there are some variations out there we have yet to encounter.
Approvals
The other challenge of pruning the order book was handling approvals. For 0x to work, users approve the 0x exchange contract’s transfer proxy to transfer tokens on their behalf. With ERC20 this is very straightforward — users specify a quantity of the ERC20 token the transfer proxy is authorized to transfer, and that’s it. So with ERC20 tokens, we could monitor for approval events where the approved spender was the transfer proxy, and that was the only way (aside from spends, which are covered by the spend monitor) that the allowance could change.
ERC721 tokens are much more complicated. They have two separate concepts of approvals — Individual token approvals, and account wide operator approvals. That is, I could authorize the transfer proxy to move CryptoKitty #48637 from my account, or I could authorize it to transfer any of my CryptoKitties from my account (note that the transfer proxy is only going to transfer tokens if I sign a message telling it to do so).
These two types of approvals work through completely different mechanisms. You call different functions to set them. They emit different events when they are set. Individual tokens can only have one account authorized to transfer them, but you can approve as many account-wide operators as you like. Additionally, if you had the same account set as the account-wide operator and approved it for a specific token, you could revoke the account-wide operator permission without changing its permission to transfer a specific token.
This all gets a bit hairy when you’re trying to monitor for events to determine
whether or not a particular order is fillable. Instead of having to monitor for
Approval
events where the transfer proxy is the designated spender, we have
to monitor for all approval events, as any approval that designates a
different spender might implicitly be revoking the transfer proxy’s
authorization. But even then, an Approval
event designating a different
spender does not mean that the transfer proxy isn’t permitted to exchange
that token, as it might also have been designated as a global account
operator — but we can’t get that information from an Approval
event.
The account-wide ApprovalForAll
events are slightly easier to monitor for, in
that we only need to pay attention to ApprovalForAll
events where the
transfer proxy is the designated spender. Again though, if we get an
ApprovalForAll
event that revokes the transfer proxy’s account-wide approval,
it still might be authorized to transfer individual assets. The
ApprovalForAll
event doesn’t give us any information about what specific
tokens were impacted by that change, and unless the token implements the
ERC721Enumerable
extension we can’t even get that information from the
blockchain.
Ultimately, we decided to make this task easy for ourselves. If we see an event that would revoke either the account-wide revocation or an asset-specific revocation, we remove any related orders from our orderbook even if the transfer proxy had the other type of approval. This means that under certain circumstances we may end up pruning valid, fillable orders from our order book, but we believe these will be rare cases, and it drastically reduces the complexity of pruning our order book. In the event that we do improperly prune an order, it can simply be resubmitted through our API to be re-listed.
If we find that these cases are not as rare as we anticipate we may change our pruning process in the future to accommodate, but for now this seems like an acceptable trade-off.
Getting Started
Right now OpenRelay only supports trading ERC721 tokens on Kovan, and only through our API. We’re working on building out some UI widgets that will eventually support ERC721 trading, but for now trading ERC721 tokens on OpenRelay will require some technical know-how. If you want to get started, I’d recommend taking a look at the 0x Project’s v2 developer tools.
For the moment we’re still implementing an interim draft of the Standard Relayer API. We’re hoping to have full 0x v2 SRA HTTP compliance by the end of the week.