Crypto Composables ERC-998 Update šŸ˜Š

Matt Lockyer
Coinmonks
Published in
9 min readMay 8, 2018

--

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
  • 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 contractfunction _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 tokenIdfunction _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!

--

--

Matt Lockyer
Coinmonks

Building Blockchain Solutions to Real World Problems - "The revolution will not be centralized."