Non-fungible tokens (NFTs) are all the rage these days, but their current manifestation (ERC721) is inefficient. It is not possible to move multiple tokens at the same time or package multiple tokens for e.g. deposit into a plasma child chain.
The following is a new design for an NFT token that allows aggregation of assets. The rest of the functionality can be inherited from ERC721.
contract NFT {
tokens mapping(address => mapping(bytes32 => uint));
transfer (bytes32 id, uint n, address to) {
assert(tokens[msg.sender][id] >= n);
tokens[msg.sender][id] -= n;
tokens[to][id] += n;
}
transferBundle(address to, bytes data) {
for (uint i = 0; i < data.length; i += 64) {
uint n = data.slice(i, i+32);
uint token = data.slice(i+32, i+64);
transfer(token, n, to);
}
}
}
Note that we are representing tokens in a different construction than ERC721. Each token has an id
, which is a hash of its metadata (this should be unique) and any user may own any number of each unique token.
Users can transfer one or more of a single token via transfer()
or move several tokens to a recipient via transferBundle()
This design enables us to package tokens reasonably efficiently and transfer them off chain to e.g. a plasma child chain.
Interestingly, these NFTs can also be represented as UTXOs:
{
id: <string> // keccak256(prevId, to, n, data)
to: <string> // new owner of this token
n: <int> // number of tokens in this UTXO
data: <string> // token metadata. This should be unique to the token (e.g. for a card in a trading card game)
}
The only difference between this and fungible token UTXOs is the presence of the data
field, which is simply hashed into the id
and never changes as tokens move through the system.
Such tokens can then be traded off-chain (via UTXO signing) and withdrawn later onto the root chain via a submission of the UTXO's provenance. (for example, if Bob got a card from Alice, he would need to submit her signed transaction in order to withdraw his new card).