Non Fungible Token (NFT) Standard [Community Consideration]

4. Currency token

I want to start from myself and present you a token, I’ve been working on for some time now, that (at least in
my opinion) should represent a currency really well.

https://github.com/seniorjoinu/tokens/tree/master/currency-token

Take a look at it and let me know what you think! I see this repository as a library of different tokens which you
could clone, modify and use in your projects.

Here I’m going to tease some of its core features and new concepts.

Overview

service : (InitRequest) -> {
    "mint" : (TransferRequest) -> ();
    "transfer" : (TransferRequest) -> ();
    "burn" : (BurnRequest) -> ();
    "get_balance_of" : (GetBalanceOfRequest) -> (GetBalanceOfResponse) query;
    "get_total_supply" : () -> (GetTotalSupplyResponse) query;
    "get_info" : () -> (GetInfoResponse) query;
    "update_info" : (UpdateInfoRequest) -> (UpdateInfoResponse);

    "get_controllers" : () -> (GetControllersResponse) query;
    "update_info_controller" : (UpdateControllersRequest) -> (UpdateControllersResponse);
    "update_mint_controller" : (UpdateControllersRequest) -> (UpdateControllersResponse);

    "dequeue_recurrent_transfer_tasks" : (DequeueRecurrentTaskRequest) -> (DequeueRecurrentTaskResponse);
    "get_recurrent_transfer_tasks" : (GetRecurrentTransferTasksRequest) -> (GetRecurrentTransferTasksResponse) query;
    "dequeue_recurrent_mint_tasks" : (DequeueRecurrentTaskRequest) -> (DequeueRecurrentTaskResponse);
    "get_recurrent_mint_tasks" : () -> (GetRecurrentMintTasksResponse) query;
}

The main signature of this actor is based on what we’ve seen in almost any fungible token before. We have same mint,
transfer, burn, balance_of and total_supply functions and their core idea stayed the same - these are a basic
functions which any developer should find familiar themselves with.

The only difference here is that instead of separate name, symbol and decimals fields, we have a single field
info that incorporates all this data inside it. This is done for encapsulation purposes. Fewer details, more could fit
inside.

5 Likes

Batches

As you might notice, all basic functions now have a single argument instead of their traditional signature. The one reason for that is the same old encapsulation, and the other is that each update method now supports batch requests. This means that if you need to perform multiple transfers, you could to them all at once, during a single function invocation. The same goes for mints. This would help with cost efficiency of this token.

query methods do not support batches right now, but I believe they should, since one day queries will also charge cycles, so it is reasonable to optimize them as well.

Granular control

Other thing you might see in the candid above is several “controller”-related methods. Yes, a canister has a controller, but what are these methods for?

A token itself has several permissions. A token could be minted - this is a permission not everyone should have. So, there is a list of principals which should. In the candid this list is referred as mint_controllers. Only principals from this list are able to mint this token. If the list is empty - no one could mint it anymore.

Current controllers are able to modify their controllers list. So, if you’re a mint_controller of this token, you can also add your friend to it, so you both could mint tokens for everybody else. This is done via update_mint_controller function. Or you could pass an empty vec to this method, letting go the control over minting the token.

The same goes to token info - there is an info_controllers field also. Yes, name, symbol and decimals can also be changed at runtime, if there is a need for such a thing.

Why do we need such a thing? As the title says, it lets you (and potentially, your token holders) to control your token with better granularity. For example, the info could be controlled by some trusted person, while minting could be performed via third party canister.

Fun fact: being in the mint_controllers list you could say “I have a mint controller token of this token”

3 Likes

Recurrent payments with ic-cron

Yes, honest authorized on-chain recurrent payments. You can set a recurrent transfer task, that would transfer funds
on your behalf, for example, each week. Just add a special argument to your transfer call, and you’re good to go.
Tasks could be listed and unscheduled using functions at the end of mentioned candid interface.

Recurrent mints are also supported.

Integration through events with ic-event-hub

Each time a transfer, a mint or a burn occurs, the following event is emitted to every event listener:

#[derive(Event, CandidType, Deserialize)]
pub struct TokenMoveEvent {
    #[topic]
    pub from: Option<Principal>,
    #[topic]
    pub to: Option<Principal>,
    pub qty: u64,
    pub event_payload: Payload,
}

which is enough to integrate this token with almost any other canister.

For example, let’s imagine you’re building a transaction ledger for one of these tokens. All your ledger need to do
is to listen for all of these events and to simply log them formatting the data the way you need it.

Moreover, one could implement a ledger canister, supporting Rosetta API and ic-event-hub. Such a ledger will automatically
work with every token, which can emit such events, automatically making them exchange-ready.

Another example: you’re implementing a DEX and you want to integrate such tokens, so your users could exchange them.
All you need to do is to listen for events which have your DEX’s principal in their to field. That way the DEX canister
can be sure, that someone sent some tokens to it, and then use event_payload to determine what exactly did sender wanted
to do (sell or buy).

Events are a very convenient way to integrate canisters. They let your canister tell it’s going through something to
“the world”, not knowing who will receive this data. Moreover, once the heartbeat mechanism is ready to use, ic-event-hub
will be updated to use ic-cron (so events could be sent in batches). This will optimize “hot” integrations (with lots
of events flying each block), making this kind of integrations not only more convenient, but also more cost-effective.

3 Likes

Rust client and membership token

There is also a rust canister client - a struct that wraps ic_cdk::call invocations into type-safe operations for ease of use from another canisters.

In a sibling directory you’ll find another token implementation - a much more permissioned membership-token. Which could be used by organizations to, for example, reflect their employees in form of token.

Final overview

With all these features, I think it is fair to claim, that this currency token is the most advanced one presented to this day. And I believe we could make it much better together.

Key takeaways

  • we don’t need token standards, we need better tokens, which solve their tasks
  • we don’t need to generalize tokens, we need to generalize integration
8 Likes

While currency token is a fungible one, ideas from these posts fit both fungible and non-fungible tokens. Moreover, I don’t actually see any significant difference (not anymore) between these two categories, so I will just let it lay here.

6 Likes

Hey folks,

I wanted to let folks know (specially @senior.joinu who definitely put time and effort) that Igor Lilic (@ililic ) will be the person who manages this thread from the Foundation side. This project is clearly very early compared to other much more baked ones (see Increased Canister Storage), but I wanted to let people that I am working to get every thread spinning, engaged, and with the same clarity as the more baked ones.

In the meantime… please feel free to discuss, tell the community what you agree/disagree on from prioritization to implementaton.

5 Likes

Thanks, @senior.joinu , pretty in depth about tokens. Here’s my understanding of your explanation in a couple of paragraphs and then rephrasing of your key question. Please let me know what you think.

A token is described through a set of common behaviors and characteristics for a collection of things; for it to be suitable to be owned and used; according to the it’s perceived value in time and space.

The richness of different tokens comes from the variety of behaviors and characteristics that makes each type of token unique.

why ethereum wants token standards (rephrased your original question):

In certain instances of token types, it is useful to proveably “fix” certain behavior so that it can be never changed( “thou shalt not mint more than 21M tokens of this type; the last one of which will come out in 2140”). This implies that there is some expectation, at the time of token acquisition, of some stability over what does it mean to be that token.

1 Like

I just read a work of art.

Thank you for this story. The point about canister upgradability is salient and compelling.

4 Likes

My thought : 1. The ETH ecosystem has been nurtured by the availability of real-world asset based tokens such as USDT and USDC. At IC, we can have a base stable currency (already being implemented by some as wrapped cycles) that can be based on the XDR. The advantage here is that the price discovery for this is already being done by a powerful DAO - IC! 2. Another key operator in the ETH ecosystem is the Oracle, key player being Chainlink. Should the powerful DAO IC play the Oracle role for a few key real-world stables, and not just for the XDR/cycles computation? - I think this is a question worth debating over. 3. Another question which I think is worth debating over is whether there should be a registry maintained by IC itself, providing the ability to independently verify current supply (and max supply) and other primary info of a token (akin to Etherscan).

1 Like

I’m currently drinking from a crypto firehouse (or maybe a blockchain dump truck) and trying to absorb as much as I can. I entered crypto with a casual investment interest. Since discovering IC, I’m now engaged with a developer interest. After listening to Dominic’s interview recently on the Epicenter poscast I knew this was something different (good listen…their was contention/curiosity w/ the hosts trying to imply as-is blockchain technology to IC).

I’m trying to interpret as much as I can from this thread, and the token standard thread, and ending up with some doubt regarding the current handful of NFTs on IC.

Consumers of these NFTs are assuming the (blockchain) implementation is comparable to Ethereum (others). Though given the below references, it seems we’re a dfx command (malicious update, stop, delete) away (via canister owner) from validating the authenticity of what’s currently in circulation.

I’m wondering if anyone else shares these thoughts, or maybe I haven’t fully appreciated the conversations/develops docs yet.

https://forum.dfinity.org/t/how-does-the-storage-mechanism-in-dfinity-works/2733/6

Immutability (Read-Only Code)

https://sdk.dfinity.org/docs/developers-guide/working-with-canisters.html

1 Like

You can remove all the controllers from a canister, so the API can no longer be changed. Soon we will have a setup so you can have a dao-like group serve as a controller, to allow for decentalized approval of updates.

9 Likes

These are actually great points.

I wish more details were made available about the IC price oracle.

1 Like

In order to protect artists from copyright theft and people minting works they don’t own, is there a way to either guarantee correct copyright within an NFT token on IC? Or can we have something like the tick on twitter, when we know that an NFT is defiantly from a certified artist?

A certified NFT could potentially be more valuable than a non-certified

That’s great to know, and possibly an alternative to the Black Hole previously mentioned. Will stay tuned for the DAO’ish functionality.

I think the main thought I would like to continue, is the community could benefit from having an approach for retrofitting current/new NFT implementations as standards are adopted.

Immutability (Read-Only Code)

On a related note (and more valuable to discuss outside of this thread), I have an unwarranted sense of urgency to know if existing NFT implementations are immutable, recording transactions, and making those transactions (ICP & token exchanges) available through some easily accessible medium other than traversing ic.rocks.

It seems to be a hot topic on the socials, and it would be unfortunate for people to blindly buy into a speculative trend and have an unintentional (canister) update wipe out existing data, leaving people with the inability to prove their stake of ownership in this slice of internet culture…let alone recoup any ICP.

2 Likes

I think nft on dfinity is different from Ethereum, nft on dfnity can be an independent canister that has own specific combination of functions,

it’s the first draft NFT standards written by our team, and we will launch an NFT on dfinity according to this standard soon,
welcome comment and submit PRs

3 Likes

I think the approve function must allow the user to specify a memo (which could be a Nat64) rationale behind that is with Internet Identity, the Principal that owns an NFT in Platform A, will have a different Principal in Platform B; So the Platform B will need to creates an unique UserId which would serve as Memo;

As the owner Principal of NFT id X, I approve Platform B Canister to make any transfer on my behalf with a reference to the account UserId.

The NNS can always change a canister though, correct?

Can an NFT be modified by an action in such a way that the NFT gets rewritten?
For example if owning a particular NFT gave you access to a reward, you’d only want that reward to be claimed once. A new owner of the NFT would need to know the reward is already claimed.

I’ll preface this with stating that I’m still learning about NFTs, but willing to share some thoughts. I’m on the fence on some of them though.

This is a great question, and has led to some great internal deliberation, summarized by this fictitious conversation:

A: On this reward NFT we’re working on, can the reward (w/ a unique ID) represent the same metadata while still being non-fungible?

B : Yes, because each reward has a unique id.

A: But what if the reward’s metadata represent the same number of games points? For example, reward 1 and reward 2 could both be for a reward of 25 points.

B: I see your point. So does that mean they’re fungible, since we could swap them with each other, and still receive the same number of game points?

A: Hmm…

B: Hmm…

A: How about we separate concerns, and have two NFTs; one representing access to a reward and one representing the reward. The 1st one will contain an ID for a reward NFT (and will be immutable) and the 2nd will contain the number of free game points & claim status. That way, if we swap our access NFTs, they’re still unique because they’re pointing to different reward NFT IDs.

B: Sure, I guess, but I think you’re moving the problem further downstream. The NFTs we swapped are unique/immutable, but the point values could still be the same in the reward NFTs!

A: You’re right! Maybe we don’t get caught up in trying to call them NFTs and just make sure we’re fully transparent to our users how the reward process works.

B: Good plan! I’ll make sure our source code is visible and that all transactions are captured/visible and the necessary APIs are available to see who currently owns each reward (and claim status). I know that the IC blockchain is ensuring the calls to our canisters are fully legit, but it’s up to us to make sure we’re fully transparent about what our dapp does. Just because we’re hosting a dapp on IC that allows people to swap unique things, doesn’t automatically make it an NFT.

A: Next time, let’s make the game rewards unique, that way there’s no question about their non-fungibility.

I think it all depends on the expectations of the consumers of your dapp/(N)FT. The expectations can be established by somehow proving that your code (1) will do what it says it will do, (2) permanently captures all events (as a result of function calls), and (3) has a way for anyone to view those events.

  1. This can be accomplished by sharing the source code for your NFT and have some (yet to be developed) verification process to ensure its credible.
  2. The IC blockchain provides the means to do so (as long as the container hosting the NFT’s code is somehow made immutable). However, as stated in other posts, IC does not inherently have a mechanism where all transactions are automatically captured in a way for public consumption.
  3. You would need to develop this functionality or rely on an (yet to be developed) app that can interrogate your NFT for the necessary data.

I’ve seen services on other blockchains permit (only) the URI to change after the token is minted. The URI could point to a JSON file containing metadata on a centralized server. This could lead to the same situation though. Two JSON objects could potentially contain the same data over time.

From my current understanding of NFTs, irregardless of blockchain, is that the consumer (at a minimum) expects the NFT to be unique. Someone a bit more in tune with the technology, will want reassurance the data cannot be funged with after the fact and that there’s a full transaction history from genesis until now.

In your situation, if every reward is unique, then I would say that it is okay for the claim status to change. I would want to be reassured though, that after the reward is claimed, it cannot be claimed again. If the rewards could represent that same value (ex: 10 game points), then I feel they fall more into the category of fungible tokens.

An alternative approach, is to avoid putting a token label on it, and just call it a reward.