ICRC-1 Token Standard final draft

Hi everyone,

The final draft of the ICRC-1 Fungible Token Standard is available at dfinity/ICRC-1.

I’ve incorporated all the changes we discussed plus the missing transfer errors. Please have a look and feel free to give feedback here or on Discord.

I would like to share the next steps to make the standard official:

  1. Next Tuesday the working group will have the opportunity to vote on the standard.
  2. If the working group decides in favor of this standard, DFINITY will submit it as an NNS motion proposal, such that the community can decide whether or not they would like to make this a standard. DFINITY will voluntarily abstain from voting on the NNS proposal.
  3. If the NNS vote is successful then the standard can be considered finalized.

Looking forward to the next working group meeting.

Best,
Mario

21 Likes

Are Timo’s last minute suggestions of any value here?

1 Like

I know it’s a lot of work and there’s a lot of detail involved, but I wouldn’t feel comfortable voting yes on something as important as an “official” token standard until:

  • Flow diagrams for basic use cases with ICRC-1 are made available. For example, I saw an excellent diagram by @infu on Discord for a payment flow (attached below), but how about 3rd party transfers or atomic swaps?

  • A better explanation of fees. The github repo doesn’t specify how the fee is determined or who pays it.

  • A comparison of ICRC-1 with the current ICP ledger interface. Comparisons with DIP-20 and/or IS-20 would be even better.

  • Clear next steps on what will or will not be covered by future extensions. It’d be helpful to have a table with rows as “features” and columns as “extensions” and a checkmark for a cell if that feature will be enabled by that extension.

  • A clear statement on what this ICRC-1 standard will be used for moving forward.


This information would help voters make a better informed decision IMO. A lot of this stuff is buried in the forums or on Discord, but many of us don’t follow every thread (and can’t attend the working group meetings).

7 Likes

+1, I also liked what I read by @timo here. It seems quite general and powerful.

In support of this minimal standard which can be upgraded with extensions later. Right now it is not important for the ICRC-1 standard to be perfect, but for the ICRC-1 standard to be delivered so that the Foundation can deliver the SNS, BTC on-chain and remove uncertainty for canister developers.

2 Likes

They are but not for the base standard. We should consider them when we talk about the approve/transferFrom payment flow.

There are existing examples such as NNS, SNS and CMC that use payment flows with the standard. Adding diagrams would require a lot of work and time that I don’t have. Maybe that’s where the community can step in?

I’ve added to the README a sentence about who pays the fee. Note that no standard explains how the fee is determined because it depends on the implementation. There is no way for the standard to impose that.

All the candid files are available for you to make a comparison.

I’m not sure to understand. This is a standard that devs can use for the fungible tokens on the IC. Nothing less, nothing more. What else should be added? For comparison, this is the page of EIP-20.

That’s why the github page of ICRC-1 exists. It contains all the info needed. The forum and discord can be used to add more context.

I also think a better explanation of fees is needed. We are saying the implementation can decide, but in which way and how much freedom does the implementation have? For example, can the implementation:

  • charge a one-time fee when a subaccount is first used?
  • charge a recurring fee for any subaccount that has a non-zero balance?
  • charge a per-transfer fee in the token itself?
  • charge a per-transfer fee in cycles?

Does our interface allow or forbid any of the above?
Do we want to allow or forbid any of the above?

It seems like the fee argument in TransferArgs is specifically targeted at a per transfer fee in the token itself. It is not clear (to me) if the other ways to charge fees are possible with the interface we have.

1 Like

A couple of things that we should be aware of and should explicitly state in the description of the standard:

It is specifically designed for one-canister ledgers, i.e. not ledgers that are sharded over multiple canisters.

It does not guarantee the property that from a private key alone you can recover all your funds. If you used long subaccount ids and forgot them you can loose funds that way. You might be able to scan through all blocks and find all subaccount ids you ever used but blocks and their content are up to the implementation. The standard does not guarantee that that data is stored and accessible.

The implementation is free to decide when to apply the fee and how much it is. The idea is that the fee covers the cost of operations and the cost is based on the implementation.

The interface defines the fee in tokens of that ledger but the implementation can decide to charge the fee in e.g. cycles by just setting the fee to 0 tokens and require cycles attached to the call.

Generally speaking, an implementation of the interface is valid as long as it follows the description of the methods.

The ICP Ledger and the ICRC-1 Ledger from DFINITY are both multi-canister ledgers and both support the standard.

This is visible from the interface. There is no need to put redundant information.

Ok. I am struggling then a bit to understand to what extend it can be seen as a standard. Say for example a DEX implements something to handle ICRC-1 tokens. It expects to then be able to offer trading for all ICRC-1 tokens out there automatically. But that’s not the case if suddenly one of the tokens expects cycles attached to transfer calls. So either the usefulness of the standard is limited or we have to forbid that a token charges fees in cycles because the interface cannot express that fact.

More generally than this particular case, I am struggling with the argument that implementations can add arguments to calls and still satisfy the standard due to subtyping. While technically a correct argument, there may be issues coming up on a semantic level that break things for the user of the interface. Generally, I am afraid that for a standard to be standard we need to say many more words about semantics, properties and guarantees than we would like. More than just listing an interface.

As for the recurring account fees (maintenance fees for accounts with non-zero balance). That would mean if a principal runs out of funds then the account gets shut down or no new sub accounts can be opened. That in turn would mean there is a new TransferError type, one that is related to the receiver side, which occurs when the receiver can’t pay its fees. So I think we have to add something to the TransferErrors at least.

2 Likes

How do they shard? Over ranges of principals?

1 Like

Block ids. Queries return an index if it isn’t in the head canister.

1 Like

I think calling this a standard is a vestige of the eth ecosystem. We’ve designed an interface to enable interoperability. We don’t say anything about required functionality, and I don’t think we want to at this point to enable on y.

1 Like

Ok, got it. Sorry, my original statement wasn’t clear. What I meant to say was the standard isn’t designed for a multiple canister implementation that scales tps. Meaning an implementation where account balances are sharded over multiple canisters which can then be spread over multiple subnets. In your words, multiple “head canisters”.

2 Likes

It depends on the approach used to scale and it’s a big discussion. For instance, I think it should be possible for a gateway to implement the standard.

1 Like

But a gateway means it is still one gateway canister and then multiple ledger canisters behind it, or is it something else?

If it is one gateway and then multiple ledgers behind it then it scales arbitrarily in the number of open accounts that can be hosted but still has the tps limit of a single subnet, where the gateway is, maybe 400 tps or so of transfers initiated by external user via ingress messages to the gateway.

Scaling the tps would require that there’s one gateway on each subnet and all can be utilized by external users in parallel, so that 10 subnets would get ~4000 tps. That’s what we ultimately want to demonstrate tps scalability.

Yes, it’s a big discussion, and one for another thread. Here, I just wanted to make the point that unless we have written down all the details we have to assume that our standard may not support this and, moreover, if it does not and changes or extensions are required then at this point we do not know if breaking changes are required.

2 Likes

Here is a proposal for the next working group meeting which I think might lead to faster acceptance of ICRC-1.

ICRC-1 is supposed to be a base standard.
We acknowledge that it does not cover all future demands.
Through the concept of modular extensions we hope that an existing token can adapt to future demands, i.e. expand its interface by upgrading the ledger, without having to create a wrapped token.
We want to avoid having to create wrapped tokens at all costs, for good reason, because that would create fragmentation in tokens.

In short, I think extensions alone do not cut it.
The limitation of extensions is that they can only extend, they cannot remove and, most importantly, they cannot introduce anything that conflicts the already existing interface.

I want to ask the question how can a single ledger support two conflicting interfaces for the same token?
To make an extreme, hypothetical example, only for the purpose of demonstrating how far-reaching the concept of conflicting interfaces is:
can a ledger at the same time support an account based transaction model and a UTXO based transaction model?

The answer is quite simple.
Yes. All we have to do is version the interface.
Say version 1 is the proposed ICRC-1 interface, a set of functions and corresponding account labels.
Version 2 is some other set of functions which may or may not use the same kind of account labels (the pairs of principal and subaccount of version 1).
Versioning the interface actually means versioning the account labels.
Conflicting means that there is a function in the old interface that cannot be applied to an account of the new version.
When version 2 is introduced then a transfer function must come with it that allows to transfer from a version 1 account to a version 2 account, and vice versa if desired.

So what do we have to do at this point to prepare ICRC-1 for this? Not much. All we have to do is version the account labels. That’s all.
Instead of (principal, subaccount) we have (version, (principal, subaccount)) and the version is set to 1.

Note that when a new version N is created it must be a standard of course, i.e. all tokens that implement version N must implement the same interface including the same transfer functions to and from exisiting versions.

Again, to repeat the power of the concept.
With a new version number we are basically creating a new, non-overlapping realm for the token.
We are allowing everything that a wrapped token could do, but without wrapping and in the same already existing ledger canister.

Versioning the account labels is a very simple, natural change and a good practice.
It helps to make ICRC-1 future proof.
With it we have a good argument that any shortcoming of ICRC-1, known or unknown, can be overcome in the future.

3 Likes

Hey Timo,

thanks for sharing. This is interesting idea. It’s very flexible at the cost of introducing complexity. If you want to support any combination of APIs then you can do that.

Alternatively, you could create incompatible standards. They can still live in the same canister but they are not considered part of the same set of api (e.g. not part of icrc1_supported_standards). Namespacing takes care of name clashing.

The general rule could be that standards in icrc1_supported_standards are additive and compatible with ICRC-1 base standard while standards not in icrc1_supported_standards are incompatible with ICRC-1 and represent a separate set of APIs.

The role of the standard is to be as simple as possible. Let’s avoid versioning and complexity in general when possible.

The ICRC-1 Fungible Token Standard is ready to be voted in the current state.

1 Like