Thoughts on the token standard

These are all great thoughts. I’ll add a few to the mix:

Re: Subscribe - Please don’t call it subscribe! Use notify. We will soon want to support subscriptions(the ability to take a certain amount of tokens every (x) time period and it will get really confusing. see Subscription Services on the Blockchain: ERC-948 | ConsenSys

Re: Notify(instead of subscribe) - This is a great solution in as much as a canister can notify another canister with some metadata, it may be nice to be able to return something more than a true/false.

Re: OpenCan - I’ve seen @dostro pushing us over there a good bit. Perhaps if you gave some instructions, best practices, rollout plan, etc. Right now it just looks like a github site that you translate into a static site and push to the IC? Are we supposed to just use the github issue features? Who controls the github site? How does publishing work? This forum gets a good bit of attention and a lot of people are directed here. It has some great community and notification features. I’m all for moving onto an IC based system or documenting our findings on OpenCan, but until it has the features that we have here it may not be time yet.

Finally, I’ll propose that we don’t really need giant standards. Little pieces of candid interface seem to work well. We could add a meta layer on top as well.

Given the following type:

//this is the DIPs type file
module {
    type Meta = actor{
       dip_supports: query (dip_feature: Text) -> async Bool;
    }
    type MetaData = [byte];
    type Payment_Notifiable = actor{
       notify_of_payment: (amount: Nat, metadata: ?MetaData) -> async ?Payment_Notifiable_Response;
    }

    type Payment_Notifiable_Response = ?MetaData
}

Then you can do something like the following

public shared(msg) func transfer(recipient : Principal, amount : Nat) : async Bool {
  //handle transaction stuff
  //....

  let metaActor : DIP.Meta = actor.fromPrincipal(recipient);

  //figure out if the principal is a subscriber
  let bWantsNotification : Bool = wantsNotification(principal);
    
  if(bWantsNotification and metaActor.dip_supports('DIP_Payment_Notifiable') == true){
    let notifiable : DIP.Payment_Notifiable = actor.fromPrincipal(recipient);
    let response: DIP.Payment_Notifiable_Response = notifiable.notify_of_payment(amount, metadata);
    //do something with the response
  };
}

The canister requesting notification will need a “dip_supports” function that returns true for the string “DIP_Payment_Notifiable”. I think you’d generally want to say that this should not be a dynamic set of supports, but hardcoded into your canister.

1 Like

Yup, this is just a super short term restriction, we’re just rolling everything out in steps hence the restricted and unrestricted subnets.

If you want to start developing, I’d advise just deploying a new ledger canister to a subnet and disabling the whitelist. The ledger doesn’t do anything NNS specific so it runs fine anywhere.

You can fill this DIP module with other ‘standards’ that the community comes up with:

I don’t think that Motoko supports multiple inheritance right now, but it would be nice if it did so that we could do the following and get compile-time checks:

 //this is the DIPs type file
    module {
        type Meta = actor{
           supports: query (dip_feature: Text) -> async Bool;
        }
        type MetaData = [byte];
        type Payment_Notifiable = actor{
           notify_of_payment: (amount: Nat, metadata: ?MetaData) -> async ?Payment_Notifiable_Response;
        }

        type Payment_Notifiable_Response = ?MetaData

        type Token_Metadata = actor {
          name: query ()  -> async Text;
          symbol: query () -> async Text;
          decimals: query () -> async Nat;
          totalSupply: query () -> async Nat;
          balanceOf: (address: Principal) -> async ?Nat;
        }

        type Token_Transferable = actor {
          transfer: (recipient: Principal, amount: nat) -> async Bool
        }
type Token_Allowances = actor {
      transferFrom: (sender: Principal, recipient: Principal, amount: nat) -> async Bool;
      approve: (spender : Principal, amount : Nat) : async Bool);
      allowance: (owner : Principal, spender : Principal) : async ?Nat;
    }

    type Token_Mint_And_Burnable = actor {
      mint: (to: Principal, amount: Nat) -> async Bool;
      burn: (amount : Nat) : async Bool);
      canMint: query (controller: Principal) -> async Bool;
      canBurn: query (controller: Principal) -> async Bool;
    }
       updateSubscriptionAddress: (
        subscriptionId: Hash,
        payeeAddress: Principal
      ) -> Bool
      //etc see erc948
    }

I don’t think that Motoko supports multiple inheritance right now, but it would be nice if it did so that we could do the following and get compile-time checks:

shared(msg) actor class CoolCoin(_name : Text, _symbol : Text, _decimals : Nat): async DIP.Token_Transferable, DIP.Token_Allowances, DIP.Token_Metadata {}

What is a short term restriction? That canisters can’t send ICP? This will go away eventually?

Yup and any tokens they’ve recieved will become accessible to the canister.

2 Likes

Another idea I’ve had, but not explored too much yet is that what if the token canister didn’t actually hold any balances and was just a router to wallet actors that hold the balances? This way the token canister needs far fewer cycles and people can be in charge of funding their own infrastructure?

1 Like

If you make a canister that does delegated identities you can do more than that, you can pick what user or users holds a certain account.
It’s the same feature that the Identity Provider uses to share identities on stuff like the NNS UI.

1 Like

The compacting GC is about to get merged to master as a command line option to the compiler. I don’t think it solves the issue that dmd rightly points out, but will give access to more heap.

Doing less aggressive GC should be possible but we haven’t investigated it yet. That isn’t a fundamental problem with Motoko the language, just our current GC implementation.

Motoko will, I think, be open source in a matter of days or small number of weeks. The main issues are degree of history preservation, sorting our CI dependencies so they are publically available and, ideally, ensuring the same experience for Dfinity and external contributors.

10 Likes

I’m not sure I understand why we can’t implement this interface in Motoko.

As this example illustrates, we can share functions among actors to allow dynamic calling.

This sounds amazing and I don’t know that we have a real example of what is happening under the hood with this. Any good examples that would help us understand this paradigm better?

Yes please do this. I would love to know exactly how icp is broken down and how I can set up my own sun governance system
On my own project

2 Likes

Thanks for the enlightenment!

PrincipalId vs AccountIdentifier

Using AccountIdentifiers could run into composability issues. My token in subaccount 0 doesn’t know about tokens in subaccount 1. Slightly better privacy, but at the cost of convenience. I’m used to the Ethereum ecosystem where one account = one account, representing all my assets and transaction history, which can be plugged into any application.

In general, having both PrincipalId and AccountIdentifier increases friction for users and devs - why do I have two IDs? Why can’t I just use one? Why does this one have dashes but this one looks like an eth address? and so on…

I believe we should use PrincipalId as the one and only identifier. Would it be possible to upgrade the ICP ledger to support this? The existing entries will have to be migrated and subaccount data could be lost, but I think the tradeoffs are worth it.

Alternatively, if SubAccounts are here to stay, then we’ll want a registry to lookup PrincipalId from AccountIdentifier. Or, maybe we can change the implementation of AccountIdentifier to remove hashing.

8 Likes

This is a hurdle for users, I thought the same. I’ll second that.

1 Like

It was explained by dmd earlier:

“The account identifier vs principal ID argument is a good one, we did it mainly to keep the cost of storage down, most of our transaction fee is going to go towards the costs of long term storage of transactions and storing Hash(Principal ID, Subaccount) vs (Principal ID, Subaccount) knocked about 25% off the transaction size.”

Yeah my preference is Principal - it’s a lot easier to work with. Upgrading ledger icp to work with principal would be a good idea.

FYI, the datastruct used in ledger canister.


7 Likes