Completed: ICDevs.org - Bounty #26 - ICRC-1 Motoko - up to $10k

Oh @skilesare I wasnt assigned this bounty. Was just asking questions and learning.

3 Likes

Hey, @sardariuss I haven’t been keeping up with the developments of SNS but if it supports the icrc1 standard then you should be able to use this library. Maybe @skilesare can tell you for sure if it does. I’ve implemented all the functions for the icrc1 token standard but I would suggest waiting till the library has been reviewed by the IcDev org before using it in production.

Rosetta Integration

This is the function for the Rosetta API integration.

The GetTransactionsResponse type for this function has a field, archived_transactions that has a callback fn embedded in it. The problem with this is fns are considered shared types in motoko and shared functions are only allowed as public fields in actor classes for now so I keep getting errors when I implement a solution for this type.

I wrote this piece of code for the get_transactions fn but it returns an error:

Stderr:
/Users/dire.sol/Documents/dev/icp/icrc1/src/ICRC1/Canisters/Rosetta.mo:84.111-88.18: type error [M0077], a shared function is only allowed as a public field of an actor
  (This is a limitation of the current version.)

Is there any way to return a shared function from an actor without getting this error?

1 Like

Cc @claudio can you take a look?

1 Like

On mobile, but try to define function ‘callback’ as a public shared function at the same level as, and similarly to, for example, ‘get_transactions’ instead of locally within ‘get_transactions’.

You may want to protect this public function being called by any old caller by checking the caller id appropriately, if that makes sense here.

In Motoko programs targeting the IC, shared functions can only refer to public methods of actors, not locally declared or anonymous functions.

2 Likes

Thanks for your help @claudio!
I understand how to use shared functions better now.
I was able to assign the archive canister’s public get_transactions function to the callback field and return the response without any errors.

@skilesare The rosetta implementation is done and ready for a full review. Let me know if there is anything else that needs to be done.

4 Likes

Thanks to all involved in creating this. I’ll be using it soon and as a critical piece.

I have a basic question regarding ICRC and their interaction with canisters here, in case any of you know: How to get a canister's address to send ICRC tokens to, and generate further subaccounts(?) to do the same?

I’ll paste it here for ease:

I have a canister I need to send an ICRC token to. (The aim is for the canister to receive and hold an ICRC token balance).

How do I, as owner of the canister,

(a) get the canister’s default address to send ICRC tokens to, and

(b) generate additional addresses (not sure if they’re called subaccounts, whatever they are) controlled by the same canister to send ICRC tokens to?

Actually, does the ICRC-1 standard distinguish between such subaccounts (I don’t know if that’s the term), or does it only recognise one account per canister?

ICRC-1 uses the following type for accounts:

type Subaccount = blob;
type Account = record { owner : principal; subaccount : opt Subaccount; };

So the default accounts is just:

let canister account = {
   owner = Principal.fromActor(this);
   subaccount = null;
};

Additional accounts can be created by using the subaccount. You theoretically have infinity subaccount.

If you wanted to create an account that was specific for one of your users you would do:

let canister account = {
   owner = Principal.fromActor(this);
   subaccount = Principal.toBlob(msg.sender);  //maybe add some magic and hash for more privacy: see: https://github.com/ORIGYN-SA/origyn_nft/blob/99b9460eab0f841eb621deee7863d926d30421ae/src/origyn_nft_reference/utils.mo#L162
};
2 Likes

Thanks. Does this mean that if you want to send ICRC tokens to a canister’s subaccount, you have to separately specify both the principal and the subaccount?

I want to tell the user just: “transfer ICRC tokens to SOME_SINGLE_STRING because that’s the address of the account associated uniquely with you controlled by this canister.”

A bit like the deposit address centralized exchanges give you. It’s a single address, and if someone sends money to it, it is used to credit your account in the centralized exhange.

How do I get such SOME_SINGLE_STRING? Ideally I don’t want the user to deal with two distinct pieces. Just one place to send their tokens to.

type Account = record { owner : principal; subaccount : opt Subaccount; };

In other words, is it possible to represent the Account type above, in the case of a non-empty subaccount, as a single string address that I give the user for him to just send to that string? How do I do that?

The textual representation of accounts is part of the ICRC-1 spec.

Thank you. Does that mean that every developer who wants to display or read the textual representation of an account has to manually encode and decode the accounts with functions inside the dapp?

Ideally I’m looking for something along the lines of Account.toTextualRepresentation() that I can just use without implementing anything manually.

If there needs to be a dedicated implementation in every dapp, has anyone written the Motoko code to encode and decode them?

1 Like

Thanks. I think it clicked in my head now.

1 Like

Do you know why I’m getting

unbound variable this

in this context?:

type Account = {
    owner : Principal;
    subaccount : Subaccount;
  };

public shared (msg) func create_user() : async User {
    // Get caller principal
    let callerId = msg.caller;

    let repayment_account : Account = {
      owner = Principal.fromActor(this);
      subaccount = Principal.toBlob(msg.sender);
    };

That’s all inside my backend canister actor. It’s an actor class, not sure if that makes a difference.

When you declare your actor you can set this:

shared (deployer) actor class Nft_Canister(__initargs : Types.InitArgs) = this {

}

1 Like

Thank you. I only added = this to the actor class line, and I’m now getting back a valid Principal but not the right one (browser console):

1. owner: Principal

  1. _arr: Uint8Array(10) [0, 0, 0, 0, 0, 0, 0, 1, 1, 1, buffer: ArrayBuffer(10), byteLength: 10, byteOffset: 0, length: 10, Symbol(Symbol.toStringTag): 'Uint8Array']
  2. _isPrincipal: true

How do I get “this” to embody the backend canister’s Principal?

subaccount = Principal.toBlob(msg.sender); is returning as expected.

If it’s to do with this part, how do I integrate __initargs to my project?

False alarm, it seems.

Principal.toText(Principal.fromActor(this)); decodes to "rrkah-fqaaa-aaaaa-aaaaq-cai" , which is indeed my backend canister id.`

[0, 0, 0, 0, 0, 0, 0, 1, 1, 1] It’s probably the correct [Nat8] representation of the principal. It’s probably just the principal of the first canister deployed to the replica.

I’m using a canister’s principal, blob size 10, as owner, and the assertion is failing (line 22) because it requires a Principal of blob size 32.

Is this a bug? If not, what’s going on?

@roman-kashitsyn This looks like some legacy stuff from back when canisters couldn’t hold ICP.

Actually…now that I look at it, this may be an old implementation from back before the new textual encoding was being discussed.

I think the actual encoding is more like:

//note: does not consider crc check on the blob bytes
account_icrc1_text = Principal.toText(Principal.fromActor(this)) # ":" # Conversion.valueToText(#Bytes(#frozen(hash)));

Decoding should be pretty straight forward if you just split on “:” and use something like GitHub - aviate-labs/encoding.mo: Encoding Package for Motoko to decode the second part.

See ICRC-1 Account Human Readable Format - #42 by lastmjs
and Announcing "Token Standard" as topic of the first meeting of the Ledger & Tokenization Working Group - #53 by Maxfinity

We do really need to tie down the final format. I think we’re very close and if you go with principal:bytesinhexwithtruncated0s I think you’ll be ok. If it is intercanister you can ignore the CRC check anyway as no one will be copy/pasting it.

Do you mean the ICRC-1 standard is not finalised yet? The textual encoding at least?

I’ll tell you what I’m trying to do:

I need my backend canister to give each authenticated user a unique string for them to send ICRCs to, so that the user can do that from any wallet, and so that when ICRCs arrive there, the canister owns them and knows which user they belong to.

The approach I was taking is

let user_account : Account = {
      owner = Principal.fromActor(this);
      subaccount = ?Principal.toBlob(callerId);
    };

and then

let text_user_account = Account.toText(user_account);

That’s when I hit that line 22 error.

If I understand correctly I need a Motoko function to get that Principal.toBlob(callerId) into a subbacount form that is compatible with Account.toText() from ICRC-1/Account.mo at main · dfinity/ICRC-1 · GitHub

Though this doesn’t seem to include any referece to the subaccount. What is a textual representation for the case where the subaccount is present, and I want that subaccount to uniquely represent a user’s principal, so that the text end result is unique to the user.