Proposal for a standard token interface

With the Sodium launch happening soon, I thought it’d be a good time to start thinking about token standards.

Here’s my proposal for a unified canister interface, with support for single/multiple tokens per canister, fungible and non-fungible tokens. Feedback wanted!

We should probably come up with a process for proposals (tentatively calling them ICIPs (Internet Computer Improvement Proposals). Perhaps a public github repo under dfinity?


Hi @wang

In terms of operator permissions I was thinking they should be more granular?

From the top of my head even though an operator has access to just one token type maybe the principal should have the option to set a hard expense limit ? like 10% (or a fixed nuber) of the total bucket?

Eg: I want my canister to be free to a certain point (trial period).

1 Like

Agreed, more granular permissions are needed.

A simple way to do this is to set token permissions for each operator:

type TokenAllowance = {
  tokenId: TokenId;
  allowance: ?Nat; // if null, unlimited allowance for this TokenId

type TokenPermission = {
  #Some: [TokenAllowance];

type OperatorAction = {
  #SetPermissions: TokenPermission;

type OperatorRequest = {
  owner: User;
  operators: [(User, OperatorAction)]

Another approach is to handle permissions in a separate canister (see EIP-1761 scoped approvals). This TokenPermissions canister could dynamically update allowances, eg. 10% of total $ value per day. Operators would then call TokenPermissions.transfer(), and if approved, the transfer would be proxied to the token canister. This approach gives users a central place to manage permissions, but requires operators to know about this second canister and also adds another async call.

1 Like

I really like the idea of having some spec like this BEFORE we’re actually flood with different tokens for all our projects.

Good job!

Thanks for the creation of this. Are you thinking of a token canister of being static after being set up? By that i mean that the interface can’t change? If not it might be useful to have some space for upgrades / new implementations.

I think the core functions (eg. getBalance, transfer) should never change. Any additional functions that devs want to add are non-standard, and should be documented properly.

There is a potential issue of adding non-standard functionality to the transfer function; some rebase tokens update their supply on every transfer call, which could lead to weird behavior. Consumers need to be aware of this when integrating tokens, but I don’t have a good solution to this…

1 Like

Also, we have experimental token code in motoko base. Looks like Nat is used for balances: if 1e12 cycle = 1 CHF, then the smallest unit of 1 cycle = 1e-12 CHF

@crusso Has the team thought about a unified API for system funds and user tokens? On Ethereum, system ETH is handled completely different from userspace erc20 tokens, creating the need for a wrapped ETH. It would be ideal if we didn’t need this :slight_smile:

1 Like

And what about a running canister that already has thousands of users. Imagine I want to add additional functionality to it. I know that there’s a Motoko keyword to ensure either stability or flexibility, although that is only for variables. From the talks I imagined it so far that you can create a token canister following your proposal, having the same interface and implementing the proposed functionality in a way that satisfies the constraints.
As a user of that canister I wouldn’t want anyone to have access to the canister in a way that changes it’s functionality or state. I want to see the token canister deployed, be able to review it’s code and be sure that it will stay like this forever.
So if there has to be some new functionality added to the token canister, would I always need to go through the NNS to do so? What if the token canister hasn’t got enough users and that way my proposal to change won’t even be voted on because not enough people are interested in it. What can I as a owner/maintainer do to change it? I could if course create new one with the needed functionality and transfer the state to it. But this new one needs to be again accepted by the users.

So my point was if it would be possible to add some default extensibility – like extra bits in protocol headers – to not have to go through all that. And I would be happy to hear your thoughts on how the above mentionend process would look like, maybe I’m completely off track.

Right, how can we ensure that a token canister doesn’t upgrade maliciously? We don’t have a keyword to force canisters to not be upgradeable (at least not yet). It seems like there’s two options for token issuers:

  1. No upgrade - Set the owner of your canister to a known unused address (eg. 0x0000…), allowing your users to verify that no upgrade is possible.
  2. Upgrade - Retain the ability to upgrade at any time, but you must convince your users that you won’t steal their funds, and be transparent about all upgrades. If you’re introducing breaking changes, it’s up to you to notify all consumers. If your token is a key dependency of many other projects, using a formal governance process is probably a good idea.
1 Like