Non Fungible Token (NFT) Standard [Community Consideration]

On the internet computer, given that storage is quite cheap, we can have many effectively-fungible, still technically non-fungible tokens. These kinds of token will each have different data associated with them (non-fungible), but they’ll still be easily compared (fungible).

Our question is: is it possible to make a fungible token standard that inherits from the non-fungible one?

A ‘fungible’ NFT (which I’ll hereon just call “nft”) would basically work like this:

  1. On mint, Alice receives an nft whose data is just a number “balance” that represents the starting tokens in circulation
  2. On send, Alice intends to send x tokens to Bob. Alice’s nft burns x tokens in order to create a new nft with a starting balance of x, then Alice’s nft transfers ownership of the new nft to Bob.

What’s cool about this approach is that these are atomic operations. We can use that attribute to solve the DEX problems that the token standard conversation is talking about.

To do so, introduce 2 new functions and a new rule:

New “share” function: To share an nft let’s create a function share(quantity, target). When called it creates a new nft that mints the desired quantity of tokens, and shares ownership of this new nft with the target address (after share, the token is co-owned by both the original nft owner and the target address). Unlike send it doesn’t burn tokens before minting, you’ll see why next.

New transaction rule that the nft respects: If the nft has more than one owner (it is shared) it cannot call the send method. It can only share and revoke ownership. This makes it so that nfts with multiple owners are valueless, at least until revoke is called.

New “revoke” function: To revoke ownership of an nft (which you’ll want to do so that the address you shared it with can spend it), let’s create the function revoke(). In order to revoke ownership, the calling nft must burn the amount of tokens specified as the starting quantity when share(quantity, target) was called.

How about an example?

Let’s say Alice has an nft called Token A with a starting balance of 500, she wants to use a decentralized exchange (DEX) to swap 200 Token As for Token B. Here’s what happens:

  1. Alice calls the method share(200, DEX) on the Token A nft that she holds. This prompts Token A to create a new Token A nft that mints 200 new tokens, let’s call it Token A_1. Alice’s Token A still has 500 tokens, and Token A_1 has 200. However, Token A_1 is now co-owned with the DEX canister and therefore valueless because it can’t be spent, so the total quantity of tokens in circulation is equivalent to before calling share.
  2. The DEX canister calculates the number of tokens to request of Token B, let’s call this value q where q = exchange rate * 200
  3. The DEX canister now requests that Token B also shares its tokens with DEX. Token B calls share(q, DEX), following the same process as Token A: split the nft token, share ownership of the newly created nft which we’ll call Token B_1.
  4. DEX now shares ownership of Token B_1 with Token A, and shares Token A_1 with Token B. The swap has happened, but at this point, Tokens A_1 and B_1 are still valueless since they have more than one owner. (in fact, they each have 3 owners: DEX, Token A, and Token B)
  5. Upon receipt of Token B_1, Alice’s Token A revokes ownership of Token A_1, burning 200 tokens to do so.
  6. Upon receipt of Token A_1, Token B revokes ownership of Token B_1, burning q tokens to do so.
  7. DEX still co-owns both nfts right now, so they’re still worthless. DEX now has to revoke ownership. DEX first ensures that Token A and B have already revoked ownership of A_1 and B_1 respectively. Upon confirmation, DEX revokes its ownership of Token B_1 and Token A_1 finally making them usable. The swap is complete.

It’s elegant because the process can fail at any point as if nothing happened. No need to roll back any transfers. You can run out of cycles at any time or be as malicious as you want, you’ll only complete the exchange if it went right.

4 Likes