Crypto Composables ERC-998 Update #2
WOW! I’m in NYC! Consensus2018 is going on and I’m not there 😂.
Now let’s talk about something more practical… Building a standard interface for Composable Non-Fungible Tokens (CNFTs). Thanks Richard Burton for hosting us at Balance Studios to actually build things. #BUIDLWEEK
Since I posted, “Introducing Crypto Composable” and “Crypto Composables — Building Blocks and Applications”, I’ve been inundated with a number of projects in the NFT space, interest from the Ethereum community and overall support 🤗. Reaching out to the Ethereum community and this support will make this a standard interface and extension to ERC-721, allowing CNFTs to own both NFTs (ERC-721) and FTs (ERC-20). A working draft implementation is here:
This update covers a refactoring of ERC-998 into 2 opt-in interfaces. The bulk of code for owning FTs was literally written on the plane to NYC Sunday night. This code is already loosely tested in the working repo above. While there is still a lot of work to be done before this becomes a standard, having a draft implementation will allow other developers and myself to spin up some dapps. Once on testnet, these dapps will demonstrate the power of an open and inter-operable composable token standard. Hint: pay attention in the next weeks 😂.
Refactoring the ERC-998 Interface
Recently I completed the first, and imho hardest part of ERC-998: having a CNFT own other CNFTs and standard NFTs. This is due in large part to NFTs being more complex than FTs. With NFTs you have to keep track of the unique tokenId
for each token, whereas with FTs you don’t care because well… they are not unique. Any fungible token is identical to any other fungible token, and balances are represented as a single integer balance. So how about this refactoring?
I chose to refactor the ERC-998 interface into 2 pieces, following in the footsteps of several OpenZeppelin standard implementations, keeping the code modular. In the following code and discussion, a Non-Fungible Token Possession (NFTP) and Fungible Token Possession (FTP) stand for the NFTs and FTs that are owned by a composable. Here’s what the refactor looks like:
With this new architecture, it will be possible to create Composables that own and administer only NFTs (721) or only FTs (20s), or both. For example: a Composable contract that represents an index fund would have a unique NFT for each fund, but would only need to inherit from the ERC998PossessERC20.sol
contract. This would also reduce the size of the contracts being deployed to the blockchain. The recent refactoring supports this logic, while still leaving the door open for CNFTs to own both NFTs and FTs.
CNFTs Owning FTs
I knew this would be easy after the difficulty solving management of the CNFT owning NFT case, i.e. a unique token ID that owns arrays of other unique token IDs. While CNFTs owning NFTs is still a draft and requires some additional functionality and robustness, I wanted to explore the concept of CNFTs owning FTs. This was made a lot easier since most of the code from the “possess NFT” case could be used here and streamlined to represent balances instead of arrays of unique token IDs. Here’s a glance at the public interface:
// mapping from nft to all ftp contracts
mapping(uint256 => address[]) ftpContracts;// mapping for the ftp contract index
mapping(uint256 => mapping(address => uint256)) ftpContractIndex;// mapping from contract pseudo-address owner ftp to the tokenIds
mapping(address => uint256) ftpBalances;/**************************************
* Public View Functions (wallet integration)
**************************************/// returns the ftp contracts owned by a composable
function ftpContractsOwnedBy(uint256 _tokenId) public view returns(address[]) {
return ftpContracts[_tokenId];
}// returns the ftps owned by the composable for a specific ftp contract
function ftpBalanceOf(uint256 _tokenId, address _ftpContract) public view returns(uint256) {
return ftpBalances[_ftpAddress(_tokenId, _ftpContract)];
}/**************************************
* Public Transfer and Receive Methods
**************************************/function safeTransferFTP(
address _to, uint256 _tokenId, address _ftpContract, uint256 _value, bytes _data
) public {
transferFTP(_to, _tokenId, _ftpContract, _value);
ftpReceived(_ftpContract, _value, _data);
}function onERC20Received(address _from, uint256 _value, bytes _data) public returns(bytes4) {
ftpReceived(msg.sender, _value, _data);
return ERC20_RECEIVED;
}
If you don’t code, don’t worry. Here comes the Layman’s explanation.
Bookkeeping
Start with the mappings up top. You need to keep track of and be able to add and remove ERC-20 contract addresses. This helps wallets, exchanges and applications enumerate all contracts and balances for a specific CNFT that may contain several fungible tokens.
So why is the ftpBalances
mapping only 1 level deep (i.e. flat)? That’s because I’m using a trick explained in my previous code update that I call “pseudo addresses”. Basically, for each CNFT tokenId
that has a balance at an ERC-20 contract address
, we’re going to create a new hash of those items and truncate / cast that to the address type in Solidity. This allows us to avoid deep mappings and wasted storage at the cost of modest additional computation.
Enumeration
Wallets, exchanges and applications are all going to need visibility into what token contracts and balances each NFT possesses. This is tackled by the public view functions that return both a list of all Fungible Token Possession (FTP) contracts and if provided with the owning tokenId
and the FTP contract address
one can find the balance.
Transferring and Receiving
This was big. Currently, transferring ERC-20s in and out of a Composable is modeled after the ERC-721 standard where there is an ERC20Receiver.sol
much like in ERC721Receiver.sol
that is inherited from and the ERC-20 must fire a callback. In order for this to work, older ERC-20 contracts would need to be upgraded. As this is either impossible or undesirable, there is still the option to approve and then use the transferFrom
function of the ERC-20 from within the ERC-998. As Mikhail Larionov pointed out on github, there are attempts to make the callback behavior standard with ERC-20s. I hope they do… *cough* 😉
Transferring simply checks that the ftpBalances
at pseudo address (tokenId
x ftp address
) has enough of a token balance and then calls the transfer function of that ftp token. Then we need to remove the ftp contract from the array of contracts and tidy up ftpBalances
.
Time to Standardize!
Amazingly, I have yet to run into any negative response from the community. I typically get one of two responses from people. Sunday night’s Curation Markets meetup in NYC, hosted by FOAM was no different:
- I don’t get it. Oh really? That’s so cool! I supposed you could do X Y Z with that?
- I thought of this months ago, I wrote some code that does this, I want to use this!
Great! Either way. Let’s work as a community to BUILD THIS TOGETHER!
As I explained in, “Blockchain Standards are Eating the World”, if we continue to build out protocols in silos we run the risk of decentralized, peer to peer value creation and exchange being co-opted by incumbents. This cannot happen! We must work, “Towards a Hierarchy of Token Building Blocks”, or the Ethereum community and other chains will lose their relevance if not adopted by the masses. Here’s why standards matter in a nutshell:
I send you a CNFT built by “non-standard” protocol X, you open it up in Wallet Y that only implements the CNFT interface Z… Wallet Y cannot enumerate and thus you cannot see any of the possessions of the Composable I sent you. This is a terrible user experience. This will also bring adoption of dapps and cryptoassets to a grinding halt. Especially non-fungible tokens that have so much promise to form the building blocks of more complex assets and second layers layers. See userfeeds.io and cryptogoods.net.
Wrapping Up
A fully functional draft implementation of ERC-998 is loaded up and the train is leaving the station. Now is the time to get involved! I am 100% reachable, open and transparent. I understand everyone has projects to work on, but standards matter and will affect your project in the long run. I’ve applied for an Ethereum Foundation Grant to place bounties with my buds Kevin Owocki and Mark Beylin on their networks to solve the following issues:
- Agree on the interface
- Minimize gas costs
- Harden implementation security
- Callbacks for extensibility
- and more…
Please let me know if you’re interested in helping out!
medium.com/@mattdlockyer
twitter.com/mattdlockyer
linkedin.com/in/mattlockyer