Non Fungible Token (NFT) Standard [Community Consideration]

I always have a liability concern about canister as smart contract engine. Maybe I am wrong. as my understand canister’s controller can always make change of actor and canister need cycles to keep it live.
On the contrary, smart contract on Ethereum can not be changed and offline ever. If it’s true, maybe consider some technical methods to avoid it as NFT standard.

1 Like

Yeah, but you only need it for interfaces that others are supposed to match. I’d use a prefix convention that is simple enough:

ext__transfer    departure__transfer

Theoretically, yes, but would writing a::f in actual code be any less ugly than a_f? And it’d be introducing quite a bit of extra machinery for it for all parties involved (Candid, Motoko, all other language bindings).

I wouldn’t necessarily assume that. More importantly, we shouldn’t be satisfied with a solution that would be limited to the specific needs of tokens.

I have seen horribly overengineered solutions to the namespacing problem. Ultimately, they just push the need for agreeing on a good naming convention elsewhere.

4 Likes

NFT ledger canister must then be deployed on the System Subnet ( like ICP ledger canister on system subnet)

some transaction fees should go to the ledger to keep the canister live

I like this line of thought.

There’s an alternative to this approach to solving compressibility, determinism, and uniqueness, which comes from embracing the idea that all tokens are non-fungible.

I have perhaps a very long post to write about semi-fungibility, but it’s summary is, “most tokens are neither fungible nor non-fungbile, they instead occupy a point on the spectrum of fungibility.”

For example, USD is perfectly fungible until you need to deposit more than 10k into a bank at one time. At that point its effective fungibility is limited because the U.S. government wants to prevent money laundering and terrorist financing.

On the other hand, even perfectly distinguishable items can have fungible properties. E.g. the famous rai stones on the island of Yap. These stones were so large they couldn’t be physically transferred to one another. Ownership was instead tracked through community consensus.

In other words, I think the emphasis on fungibility is overstated. Instead, what’s interesting is the degree of fungibility and the price inference mechanisms to make fungibility possible.

My claim is that everything becomes perfectly fungible, including NFTs, as long as you have a sufficiently subscribed price inference mechanism. I think this is non-controversial, but it doesn’t seem like what it offers has been metabolized.

To give a quick example: let’s say there’s an NFT generating canister that outputs an image with 4 pixels that are each 1 of 4 colors. The probability of a red pixel is 4/10, of a blue is 3/10, of a yellow is 2/10, and a green is 1/10. An NFT image with all green pixels would have low, 0.01%, probability of occurring, it’s the least probable image. Let’s say an all green NFT is generated. Will it be highly desired or not?

Right now, the default price inference mechanism that we reach for is scarcity. The rarer the item, the more highly we value it. But what if instead the market had a strange preference for the color blue (as it should, blue is better. HODL BLEU becomes the mantra.) A completely blue image wouldn’t be all that uncommon, still quite rare, but not as rare as the all green. However, the preference for blue would still drive up the overall price. Maybe this is what Vitalik means about memes: if each person believes that every other person values blue above all else, then blue would have more value. The value is driven (in part) by the expected fungibility of the asset once you own it. Fungibility is about subscribing to a price inference mechanism.

So, memes are a mechanism to drive prices. As people subscribe to memes, the assets that represent those memes can take on value. I know this is basic, but it seems like lots of people prefer to think about NFTs and fungible tokens as if they’re different things. In reality, fungible tokens are just a class of nfts, backed by some set of memes that make price inference easy.

E.g. a government that says your dollar can be exchanged for a new one, no matter how tattered your dollar may be, has introduced a mechanism that makes price inference easy.

Notice that one way to make an NFT more accepted as a currency is to make more of them. You can either collateralize an asset with tokens, or have many similar assets, each with one token. The reason a singular and unique nft performs worse than Bored Apes is because Bored Apes is actually a fungible token. You know that others know how to value it. This is also why Bored Apes would be less successful if it were perfectly random, perfect randomness defies parsimonious valuation.

My bet is that if more people bought this idea we’d have tokens that do more interesting things. In the next post, this claim will also allows us to unify nfts and fts into a single implementation.

See this post for another example of a non-fungible, yet still “fungible” token.

7 Likes

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

I know these have lots of grammatical and formatting errors. I tried to go back and edit but I keep getting 403 errors. Perhaps this is because the posts are so long. You’ll have to put up with the mistakes until I can figure out the issue :confused:

Figured out the issue, I was using uBlock Origin and that seemed to be triggering some security stuff on dinity.org’s side. Nope, that wasn’t it. You now just get to read this in four posts instead of 2. They’re worth it though, I promise :wink:

1 Like

[Note: This was originally included in my first post, but had to be moved so I could edit the original without a 403 error]

Let me give one more example so that you can get the flavor of how nfts can become fungible with the right price inference mechanism. Let’s say there’s a wallet + nft scheme such that when the nft is transferred it checks to see what its “special value” is. If the special value is prime, the nft thereafter can’t be transferred to another account (it becomes worth 0). To find that special value we first number each nft by its order of creation:
0
1
2
etc.

Then, each transaction is numbered. The special value is transaction number + creation number. If it’s prime you can’t transfer it. The prime tokens die.

In this market, if I sent you token #3, and my payment to you was the 1018th transaction in the network’s life then, bummer, your newly received token would be worth nothing (because 1018 + 3 = 1021 which is prime)!

In principle, this system is still “fungible” because you can (at a certain computational expense) estimate the likelihood that each token’s special value will be prime. E.g. larger numbered tokens will have higher probability of being safe, since prime density is approximately n/log(n). Similarly, tokens will have higher value if they are transacted later in the network’s life (i.e. they become more fungible as the transaction count increases, for the same n/log(n) reason). Classes of tokens will change in value depending on the timing. E.g. if someone held tokens 10 000, 10 001 and 10 002, then as the market approached 21 398 transactions the value of their tokens would go up in value and stay high until the market got closer to 21 466 transactions. This is because tokens 10 000 through 10 002 have no special number primes for the transactions from 21 389 through 21 466.

The fungibility of these tokens would be a function of the price inference mechanism you put in place. If evaluated manually by each human for each transaction, you can bet that it would be considered highly non-fungible. But, you could create a second layer to this network that computationally priced each token, rolling up all the uncertainties of the value of a wallet into a single “fungible” number that told you how much you’d have to transfer for the other person to get the value they care about.

This is a proof, of sorts, that fungibility is created by the price inference mechanism.

2 Likes

[Note: This was originally included in my second post, but had to be moved so I could edit the original without a 403 error]

In Summary

  1. Fungibility is defined by your price inference mechanism, not by your token standard.
  2. All “fungible” tokens can be implemented as types of non-fungible tokens, where the data portion of the non-fungible token tracks the current “balance” and follows certain ownership and burn rules.
  3. Treating fungible tokens as a type of non-fungible token gives us advantages over classic fungible tokens. For example, it makes it possible to implement a decentralized exchange without ever needing rollback.

Finally, and perhaps most importantly, this approach can likely dramatically simplify the token standard that each wallet needs to support. The nft itself will carry the logic and permissions, and wallets can call simple handles like share, transfer, and balance.

I’d like to at some point explore implementing this standard but I’m still learning rust. If anyone else finds anything interesting about the idea of unifying nfts and fts into a single standard feel free to steal, riff, rip, and repurpose.

An open question is how much additional cost overhead in terms of storage and compute this approach would incur.

Also, please let me know the ways in which I’m wrong.

This is actually really deep. I’m still trying to fully understand it:

  1. DEX now shares ownership of Token B_1 with Token A, and shares Token A_1 with Token B.

I thought in order to share a NFT, the caller has to specify an amount to “lock up” and split the NFT? Does the DEX canister do that here?

  1. DEX still co-owns both nfts right now, so they’re still worthless. DEX now has to revoke ownership.

Like the above, doesn’t the DEX canister have to burn tokens to revoke ownership? Or maybe NFTs only get split or burned when the caller of share and revoke is the first owner, but not for subsequent owners?

2 Likes

Hi @jzxchiang, I was hoping you would see this. Excited to hear your thoughts :slight_smile:

share() could infer from its arguments whether it’s a split share, or just a co-own share. If share(addr) it’s clearly just trying to co-own with addr.

Alternatively, share(0, addr) could be taken to mean that you’re sharing ownership without duplicating any tokens.

Less elegantly, but equivalently and perhaps more pragmatically, you could have two functions: splitShare(quantity, address) and share(address).

Yeah I think you have to require the original owner to burn to revoke. That’s the only thing that makes sense if you want to preserve fungibility.

2 Likes

This is a total distraction, but I played with the idea of allowing anyone to revoke for free as long as there still remain 2 other owners.

tl;dr. It doesn't work for fungible tokens.

It produces two problems:

  1. In the DEX example, Token A could revoke right after sharing. That would force Token B or DEX to pay the burn bill. Maybe you can get around that by making burn a separate function that DEX tells Token A to call. Hard for me to understand the consequences of that.

  2. Allowing anyone to pay to revoke ownership would create weird floating token pairs. They have no real value, but can still be shared, perhaps making for weird economics wherein people end up valuing the co-ownership of the token. They feel like Cooper Pairs of the token world because local rules give rise to weird collective “virtual” behavior.

Given that 2 remaining owners is an arbitrary threshold, you could play with implementing nfts that pay burn fees if n or fewer owners remain. For this, the more general approach would probably be to charge burn due / n - 1 to burn.

For example, given an nft with 100 tokens that has been shared, we can vary the n that defines the revoke threshold. This would change the burn fee:
n | burn fee
2 | 100
3 | 50
4 | 33.33…
5 | 25
etc.

If someone pays the burn fee, the remaining “burn due” is decreased by that amount. E.g. with an n of 5, when I pay my 25 tokens to revoke ownership now the remaining group needs to pay off 75 tokens. there’s 4 of them so with that formula burn due / n - 1 that would be 75 / 3 or still 25 to revoke. However, if they could find another address to share it with it would go down to 18.75.

What’s wild about this is it creates an economics of negative pressure: either you have to find a new co-owner to take your spot, or you have to pay to leave the organization. In that world, people would want tools to prevent themselves from receiving tokens, and would have no qualms about sharing | “spending” them. In fact, in that world I would be willing to pay other people to accept ownership.

There’s no reason money has to work as a thing we want. It could also be a thing we flee from. I can see how the behavioral side probably works out better if you’re trying to accrue things rather than avoid accruing them, but it’s still interesting to think about.

This might be an interesting economic model to explore for abundance pricing. What if currency was something you desperately wanted to give away? You go to a restaurant, but instead of them accepting your money, they give this negative pressure token to you. You accept participation in the token in exchange for eating the food. Now, you go to work and they pay you by accepting your negative tokens, etc. It’s a bit like debt, but you’d own it as a group.

Alternatively, maybe you could use it for externality pricing. If I’m an oil company I can accept ownership of a negative pressure token that allows me to drill in a certain area. Years later there’s an oil spill and an oil spill-tracking dao causes the weekly fees associated with carrying the token go up and it becomes expensive for me. I’ll pay an oil cleanup company if they’ll take the token off my hands. The cleanup company fixes the mess and the weekly fees go down, the cleanup company pockets the difference between the payment they received for accepting it and the cost of cleanup. Of course if the cleanup is more expensive than they bargained, they’re stuck paying the weekly fees.

One weird thing about it would be that given that the price to call your personal revoke is burn due / n - 1 but the cost of collective action is burn due / n, this could create an incentive for people to pay for collective projects since burn due / n - 1 > burn due / n. It’s like taxation with compensation, you pay toward the community effort and you actually earn money (or really, lose loss).

Maybe this could be some sort of alternative to quadratic voting for addressing problems of collective action.


To me this behavior is really interesting, but probably not what you mean by fungible token. File it under the category of, “interesting behavior you could implement in your fungible nfts by playing with the rules.”

There’s lots of that sort of thing, by the way.

Treating fungible tokens as just a type of this special nft makes operations more atomic, and I believe it would be simpler to implement (no more rollbacks). But the real benefit is the way in which it increases the space of functionality we can implement.

Two functions and one rule are needed to implement fungible tokens. But what would you get if you had different internal rules? What if you could share but a shared nft could still be spent by all owners? What if you needed a minimum agreement threshold for spending upon share? What about a minimum agreement threshold for sharing with a new address? You can see how this approach to tokens creates an intermediate space between daos and tokens. Not quite an org, but much more than a token.

2 Likes

Really interesting stuff, thanks for sharing.

A couple of dumb questions to make sure I understand the mechanics:

  • Where are the tokens burned from when revoke is called? For example, in step 5 Alice burns 200 tokens to revoke ownership of Token A_1. But aren’t those 200 tokens supposed to go to Bob (or whoever the owner of token B is)?
  • When you say “That would force Token B or DEX to pay the burn bill”, are you referring to the cycle cost of calling the DEX and token canisters?

The share/revoke mechanism kinda reminds me of a counting semaphore in programming, but instead of N threads being able to access a shared resource in parallel, we have N owners who cannot access a shared token until the other owners revoke access (kind of the opposite in some sense). :thinking:

2 Likes

It’s probably not a dumb question but rather a dumb explanation.

By the time revoke is called, Bob already owns the nft with 200 tokens in it. The tokens were minted there when Alice first called share. Alice’s Token A still has 500 tokens. Because Token A_1 has 2 owners, it’s currently valueless.

When Alice wants the transaction to be complete, she can do so by revokeing her ownership. In order to revoke (and thus complete the transaction) we require her to burn 200 tokens in Token A, this way the total number of tokens in circulation remains constant.

Ooh, that is a fascinating analogy. It makes sense it would show up here, IC is async. I never really understood semaphores to be honest. I think the name always made me feel like they’re so sophisticated and I felt intimidated.

1 Like

Going to update the prior post to try to make that clearer.

Hmm interesting, so when Alice first called share to “split” her NFT of 500 tokens, she never actually split the 500 into a 300 and a 200. It’s not until she calls revoke later on that the real token split happens. Does that sound right?

2 Likes

Yep that’s exactly right!

This way, if the DEX transaction were to fail (let’s say Bob never sends her his Token B_1s because he ran out of cycles) Alice still has all of her tokens that she started with. No rollback required

1 Like

In fact, I suppose send could just be implemented as a share and then a revoke.

I am fairly new to The Internet Computer but it seems to me that it may be worth supporting multiple standards and also looking at things as well as ETH style standards.

Over on WAX (an eosio chain with a focus on NFTs) there is an interesting dominant standard called Atomic Assets. It organises metadata neatly and has a features like author royalties and notifications built in.

This effectively runs at contract level which I am assuming is similar to cannister level.

I’d be interested in community members thoughts on this.

2 Likes

Pretty interesting, seems no one has taken you up on the opportunity to explore this more.

Would you be able to share more about how Atomic Assets is implemented?