ICRC-1 Token Standard final draft

Hey Mario,

You are right. There is nothing else to do here. We are good since we already have namespacing due to the prefix icrc1_ for all functions. The only problem arises if there is a second incompatible interface that wants to use the same account labels of the kind (principal, subaccount) as ICRC-1. Then we need a way to distinguish if a given pair (principal, subaccount), when exchanged between users, is meant to be used for the ICRC-1 interface or for the other interface. The version doesn’t have to become an argument of the ICRC-1 functions.

It is really only a version byte in the serialization of (principal, subaccount) which is the account label that is being passed around between users. Btw, have we specified the serialization at all? It needs to be part of the standard. It can’t be left to the implementation.

It is common practice to have versions bytes in data structures and serializations. Bitcoin addresses have had version bytes for example (luckily).

1 Like

Btw, have we specified the serialization at all? It needs to be part of the standard. It can’t be left to the implementation.

I’m assuming the principal is standard Candid serialization, and subaccount is serialized just like with the ICP ledger canister.

1 Like

That can’t be. ICP ledger hashes principal and subaccount together to get to an account id. Here we have to keep them separate. So I’m guessing it might be something like principal:subaccount where subaccount is decimal maybe?

1 Like

Discount is an up to 32 byte array. Can’t that be a nat? Or a blob in base 64(or whatever account I’d is). Or do we Crc it like principal so it is harder to screw up? So a principal sub account ends up looking like principal-looking-thing.principal looking-thing. Is there a separator that double click doesn’t treat as a separator and thus a double click on mobile would select it all? I’m trying to optimize for usability here. Maybe —(double dash?)

nmkbs-aaaaa-aaaam-aadfa-cai—s6bzd-46mcd-mlbx5-cq2jv-m2mhx-nhj6y-erh6g-y73vq-fnfe6-zax3q-mqe

If we did this we could add an overloaded transfer function that backs out the sub account using the existing principal function(it is just a blob. We could call it the text-id-extension and it could be icrcx_transfer_by_id(to: Text,from:text;……) that just unwraps the 2nd principal of the to to the blob, confirms the first from part is the caller, and passes to a icrc1_transfer function handler. Would also be a good replacement for send_dfx

In my experience what double click selects highly depends on the environment, it is inconsistent between browsers, terminals and editors (even between different software in the same category).

If you want just one checksummed account identifier, encoded as one string, then we should have gone with the account ids of the ICP ledger, Hash(principal, subaccount). I thought the reason we have an explicit pair (principal, subaccount) was that it is self-explanatory to a user who already knows what a principal is. And also that a user can visually parse it and identify the receiving principal. Creating one long string will be confusing to users and require additional explanation. We should just represent it naturally as something like (nmkbs-aaaaa-aaaam-aadfa-cai,0) or nmkbs-aaaaa-aaaam-aadfa-cai:0 or nmkbs-aaaaa-aaaam-aadfa-cai.0. We could allow subaccount to be specified in decimal or hex where hex has the usual 0x prefix, for example (nmkbs-aaaaa-aaaam-aadfa-cai,0x00). We just need to define how they map to blobs.

Yes, unfortunately subaccount wouldn’t be checksummed. At least the funds would be recoverable is subaccount is accidentally wrong but principal is correct.

I kind of like this one the best. Great point about the sub accounts being recoverable.

I prefer {canisterId}/subaccount

1 Like

Slashes are not very url friendly. If we could keep it to something that won’t make a web dev’s life hell we should(I like slashes to, but try explaining how to enter in url encoded character your first customer and you will realize you’ve made a horrible mistake) :joy:

Here are some basics friendly url - What are the safe characters for making URLs? - Stack Overflow

2 Likes

We should also think about a prefix. We could make it a URI such as
icrc1:nmkbs-aaaaa-aaaam-aadfa-cai.0 or some other prefix instead of icrc1.

did:icp:nmkbs-aaaaa-aaaam-aadfa-cai.0 :stuck_out_tongue:

icpr should come first to the DID group :smiley:

Why ICRC-1 as opposed to DIP-20 or IC-20?

icrc1_transfer : (TransferArgs) -> (variant { Ok: nat; Err: TransferError; });

Type TransferError is from Ledger.did

public type TransferError = {
        #BadFee : { expected_fee : { e8s : Nat64; }; };
        #InsufficientFunds : { balance: { e8s : Nat64; }; };
        #TxTooOld : { allowed_window_nanos: Nat64 };
        #TxCreatedInFuture;
        #TxDuplicate : { duplicate_of: Nat64; }; // BlockIndex
    };

Is it misleading that the key representing the balance is named e8s in the tokens?

The Nat64 type will overflow if the total supply is large.

The type has already been changed to nat in current code.

As far as I understand the W3C standard, something that starts with did: is supposed to point to a document that describes a subject. That’s not the case here. The account label identifies the account directly, not indirectly through another document.

But something like hierarchical like ic:rc1:nmkbs-aaaaa-aaaam-aadfa-cai.0 would indeed be nice. Easier to remember because everything on the IC will start with ic:.

1 Like

Dear colleagues, what are your thoughts about adding a restricted transfer function where the token minter can define conditions for transfers?
This is an active discussion in the EIP forums as it would open many more real world DeFi use-cases and the Internet Computer could leapfrog here:

Use-Cases include transfer-level conditions such as lock-up periods, identities to be known or accepted by Internet Identity personhood check as well as token-level conditions i.e. the token contract enforces a maximum number of investors or a cap on the percentage held by any single investor.

function canTransfer(address _to, uint256 _value, bytes _data) external view returns (byte, bytes32);
function canTransferFrom(address _from, address _to, uint256 _value, bytes _data) external view returns (byte, bytes32);

And asked differently as I understand that the ICRC token standard could be extended: Is it possible to add such a function and still be compatible with the standard?

Not sure if there’s any active thread on ICRC1, so trying it here in this one.

I have always thought that the spec should specify more than just the interface. That it should also specify semantics of the API and aspects of behaviour.

So here is a question: If a client canister makes two calls to icrc1_transfer and icrc1_balance_of in that order, can the client canister assume to get the balance from after the transfer, not before the transfer? It is certainly true in a simple single-canister implementation of the ledger. But the spec doesn’t guarantee it I suppose. Could it be possible that in some implementation that ircrc1_balance_of returns directly whereas icrc1_transfer makes an inter-canister call (even if just for logging) before committing the balance change?

If the client canister waits for the icrc1_transfer call to come back with a successful response before making the icrc1_balance_of call, then the client canister can be sure that the balance is from after the transfer.

If the client canister calls icrc1_balance_of without waiting for the icrc1_transfer call to come back with a successful response, then there is no guarantee whether the balance response is from before or after the transfer.

Yes. That is why a client must wait for a transfer call to come back with a response before calling for the balance.

3 Likes