24 days ago, I proposed ERC-998 Composable Non-Fungible Token Standard for Ethereum both in Github and this post. The motivation behind this standard is to create a common interface for standardized tokens on the Ethereum blockchain to be composed into sets and hierarchies. A building block for composable assets. For example: a set of ERC-20 tokens representing an index fund; a virtual backpack of ERC-721 non-fungible tokens (NFTs) and ERC-20 fungible tokens (FTs); complete tokenized accountability of home, appliances, garden and trees, all nested inside a single land title token. This was an opportunity to discover the most efficient way to create this building block, sourcing ideas from a massive and active Ethereum developer community. 🤗

This post will get a little code heavy in parts. I also provide the Layman’s version, so I encourage you to soldier on.

EDCON After Party Banner — PUMP IT!

I’ll also provide an update from #EDCON in Toronto at the end. Who I spoke with from the community, what’s going on with NFTs, and how I plan to drum up support for this standard.

What’s Happening Now? 🤨

So it begins

I’m documenting this experience so that other Ethereum developers may see what it’s like to propose a standard. My first pass had running code that protected the ownership of the child assets (which I called them at the time). Admittedly, this code had many faults. I worked as a blockchain consultant for 10 months and my Solidity skills were a bit rusty 😣. Also, I needed to brush up on the standards a bit more. Before diving into a comparison of the code I’ll break down the scope of the standard.

A secure way for Composable NFTs (ERC-998)to own other Composable NFTs (ERC-998), NFTs (ERC-721) or FTs (ERC-20)

Open for extension, closed for modification

Minimal gas costs

Standard interface — token and protocol interoperability

Minimal integration overhead — dapps, wallets and exchanges

Approaching this from my background of OOP and functional programming, I saw this as a problem of maintaining parent child relationships. So I figured the best way to get started was to model that. I will show the first attempt at adding an ERC-721 to a composable ERC-998, then I will explain the issues and show the current implementation. The nomenclature I am currently using is “possession” not “child” to represent the NFT owned by the composable.

Adding a Possession — First Pass 😕

/// tokenId of composable, mapped to child contract address

/// child contract address mapped to child tokenId or amount

mapping(uint256 => mapping(address => uint256)) children; /// add ERC-721 children by tokenId

/// @requires owner to approve transfer from this contract

/// call _childContract.approve(this, _childTokenId)

/// where this is the address of the parent token contract

addChild(

uint256 _tokenId,

address _childContract,

uint256 _childTokenId

) {

// call the transfer function of the child contract

// if approve was called with the address of this contract

// the ownership of the child token(s) will be transferred to this contract

require(

_childContract.call(

bytes4(sha3("transferFrom(address,address,uint256)")),

msg.sender, this, _childTokenId

)

);

// if successful, add children to the mapping

// generate a 'pseudo address' for the specific child tokenId

// address construction is analogous to 'contract address + nonce'

// use 0 == no child token, and 1 == child token exists

address childToken = address(

keccak256(_childContract, _childTokenId)

);

children[_tokenId][childToken] = 1;

}

🤔 Yikes! What’s especially ugly is the requirement to call “approve” first on the ERC-721 you want to transfer to this composable. Meaning the user would have to make 2 function calls to compose. Additionally, I was planning to use a single mapping to represent both the balance of an ERC-20 possession and the ownership of an ERC-721 using the integer 1 for owned and 0 for not owned. Nasty! Lastly, there is not additional bookkeeping to track the contracts of NFTs owned by the composable or the token IDs that are owned at those contracts. Some significant work needed to be done!

Adding a Possession — Second Pass 😁

🚨 WARNING MASSIVE CODE BLOCK 🚨

Scroll down further for a nice explanation of what’s happening!

/**************************************

* ERC-998 Begin Composable

**************************************/ // mapping from nft to all ftp and nftp contracts

mapping(uint256 => address[]) nftpContracts; // mapping for the nftp contract index

mapping(uint256 => mapping(address => uint256)) nftpContractIndex; // mapping from contract pseudo-address owner nftp to the tokenIds

mapping(address => uint256[]) nftpTokens; // mapping from pseudo owner address to nftpTokenId to array index

mapping(address => mapping(uint256 => uint256)) nftpTokenIndex; // mapping NFTP pseudo-address to bool

mapping(address => bool) nftpOwned; /**************************************

* Public View Methods (wallet integration)

**************************************/ // returns the nftp contracts owned by a composable

function nftpContractsOwnedBy(uint256 _tokenId) public view returns (address[]) {

return nftpContracts[_tokenId];

} // returns the nftps owned by the composable for a specific nftp contract

function nftpsOwnedBy(uint256 _tokenId, address _nftpContract) public view returns (uint256[]) {

return nftpTokens[_nftpOwner(_tokenId, _nftpContract)];

}



// check if nftp is owned by this composable

function nftpIsOwned(uint256 _tokenId, address _nftpContract, uint256 _nftpTokenId) public view returns (bool) {

return nftpOwned[_nftpAddress(_tokenId, _nftpContract, _nftpTokenId)];

} /**************************************

* Composition of ERC-721/998 NFTs

**************************************/ // adding nonfungible possessions

// receives _data which determines which NFT composable of this contract the possession will belong to

function onERC721Received(address _from, uint256 _nftpTokenId, bytes _data) public returns(bytes4) {

handleReceived(msg.sender, _nftpTokenId, _data);

return ERC721_RECEIVED;

} // internal call from composable safeTransferNFTP

function fromComposable(address _from, uint256 _nftpTokenId, bytes _data) internal {

handleReceived(_from, _nftpTokenId, _data);

} function handleReceived(address _from, uint256 _nftpTokenId, bytes _data) internal {

// convert _data bytes to uint256, owner nft tokenId passed as string in bytes

// bytesToUint(_data)

// i.e. tokenId = 5 would be "5" coming from web3 or another contract

uint256 _tokenId = bytesToUint(_data);

// log the nftp contract and index

nftpContractIndex[_tokenId][_from] = nftpContracts[_tokenId].length;

nftpContracts[_tokenId].push(_from);

// log the tokenId and index

address nftpOwner = _nftpOwner(_tokenId, _from);

nftpTokenIndex[nftpOwner][_nftpTokenId] = nftpTokens[nftpOwner].length;

nftpTokens[nftpOwner].push(_nftpTokenId);

// set bool of owned to true

nftpOwned[_nftpAddress(_tokenId, _from, _nftpTokenId)] = true;

// emit event

emit Added(_tokenId, _from, _nftpTokenId);

// return safely from callback of nft

}

A lot has changed. Starting from the top, there are a number of additional mappings. This is in order to keep track of the contracts and token IDs of possessions this composable owns. There is still a simple boolean mapping in order to return true when provided with the composable token ID, the NFT possession (NFTP) contract address and the NFTP token ID. This makes checking ownership of NFTPs straightforward and simple.

What Tokens Does My Token Own? 🤨

The other mappings are to ensure that dapps, wallets and decentralized exchanges have a way to enumerate all the NFTPs of a composable NFT. This comment was made by Maciej Górski 🤩 in addition to some other incredibly useful comments. By providing view functions for the contracts of NFTPs and the ability to return an array of the token IDs from each contract, dapps can easily query the NFTPs of a composable.

Maximizing Efficiency 😍

A final, but large issue was removing the requirement of calling “approve” first on the NFT and then adding that NFT to the composable. This would have resulted in two steps for composing tokens, and that’s no good! Again, Mikhail Larionov 🤩 from the community provided this comment that set me on my way to implementing the onERC721Received function you see above. Basically, every “well implemented” ERC-721 has a function called safeTransferFrom that can trigger a callback of another contract.

The function caller can provide an address of a smart contract that should implement the ERC721Receiver interface and callback function. Think of the contract address as a pointer to where a known callback function in another contract is. This function will be called once the ERC-721 has completed it’s transfer. I really wanted to make this standard backwards compatible with any ERC-721, so they don’t have to implement the ERC721Receiver interface, only the composable does.

Where Does My Token Go? 🤪

Now comes the big question… If I send my NFT to the Composable smart contract address, which Composable NFT does my transferred NFT belong to? This is where the _data argument comes into play. Using an overloaded version of the ERC-721 function:

safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes _data)

We can pass some arbitrary bit of data that will go all the way through to the onERC721Received function of our composable!

In the handleReceived function of a composable, we take the _data argument, which must be of the type bytes and convert it to uint256 Here is the code that does that, just for reference (sadly I lost the link to this #latenightcoding):

function bytesToUint(bytes b) internal pure returns (uint256 result) {

result = 0;

for (uint256 i = 0; i < b.length; i++) {

uint256 c = uint256(b[i]);

if (c >= 48 && c <= 57) {

result = result * 10 + (c - 48);

}

}

}

Converting this bytes data to an unsigned integer allows us to transfer NFTs to our composable contract while providing the ID for the composable NFT that will own the recently transferred NFTP, Non-Fungible Token Possession.

Quick Recap

An NFT is sent to the composable smart contract

The composable now owns the NFT as a NFTP

The onERC721Received function is called in the composable contract

function is called in the composable contract Turning the bytes argument into an unsigned integer, we know which composable should “posses” the NFTP

Funky Addresses 😯

You might have noticed some calls out to _nftpOwner and _nftpAddress. These are internal functions that generate unique addresses to reduce the number of nested mappings needed.

// generates a pseudo-address from the nft that owns, nftp contract function _nftpOwner(uint256 _tokenId, address _nftpContract) internal pure returns (address) {

return address(keccak256(_tokenId, _nftpContract));

} // generates a pseudo-address for the nftp from the nft that owns, nftp contract, nftp tokenId function _nftpAddress(uint256 _tokenId, address _nftpContract, uint256 _nftpTokenId) internal pure returns (address) {

return address(keccak256(_tokenId, _nftpContract, _nftpTokenId));

}

These functions hash the inputs and return an address type. I could have left the hash as bytes32, but since we are representing what is essentially a pseudo-address, an address mapping felt more appropriate.

“Nobody Said It Was Easy”

Enough Code — When EIP? 🤣

A number of exciting things are happening for ERC-998. While I was at #EDCON in Toronto, I spoke to some of the Ethereum community I know including: Joseph Lubin, Vitalik Buterin, Hudson Jameson, David Knott, and jon choi. This provided me with the necessary feedback, contacts and leads to get the ball rolling on standardizing ERC-998, while also enlisting some top code talent to take a look at the progress I’ve made so far. Encouraged!

Now comes the cool part. Did you know you can get a small grant from the Ethereum Foundation to work on things like standards? Neither did I until jon choi suggested it! And thanks to my friend Kevin Owocki and GitCoin I will have an excellent bounty system for developers to help me work on this standard and also benefit from the generosity of the Ethereum Foundation. Full disclosure: I have not started this process, but I plan to… *cough* 😂

The Takeaway 💖

When thinking about Joe Lubin’s talk at EDCON you imagine a world where the word “work” doesn’t have that stereotypical feeling anymore. Where people gather around the ideas and values they are passionate about. Fluid organization replacing once rigid structures and hierarchies. It may be Cryptokitties today and self organizing local infrastructure projects tomorrow. One thing is certain. We only accomplish this future by building it together.

The Future of NTFs 😎

Also while at #EDCON, I received a lot of interest from the wider Ethereum community. There are a ton of cool people doing amazing things with NFTs including the first ever Non-Fungible Token Conference in HK! Follow Jehan Chu for more details on that! There is also a crypto game studio wrapping up funding at the moment based out of NYC by Jiashan Wu! Awesome stuff.

In addition to these great upcoming projects, Brian Flynn started the Not So Fungible Weekly Newsletter. If you’re interested in NFTs and upcoming collectibles, games and composable, do subscribe!

Wrap Up

A working repository of the code for composables here:

Don’t forget to npm i and truffle test as a starting point.

Finally, the latest comments on ERC-998 (please make some of your own) are here:

You can find me here:

medium.com/@mattdlockyer

twitter.com/mattdlockyer

linkedin.com/in/mattlockyer

Thanks for making it this far!