Assigned: BNT-5 - ICRC-7 NFT Implementation (Rust or Motoko)

Hi there! First of all, thank you all for your effort! I’d like to know if there will be a reference implementation for Motoko as well? Or will I have to port the finished assignment by myself if I’d like to use the standard in Motoko?

1 Like

hello @benji
any update regarding the code review?
is there any further changes to be made?

Hi, would like to join this bounty.

Experience:

  • Currently utilizing IC DID for live NFT gated metaverse platform
  • Successfully completed Dev Grant milestones with frontend and backend app fully deployed to the IC

Timeline:

  1. Updating base canister with read methods outlined in spec - 2-3d
  2. Updating base canister with write methods outlined in spec - 2-3d
  3. Connecting calls to frontend interface - 2d
  4. Making sure implementation is conformant to spec and testing implementation 2-3d

Hi @Agent2222,

thanks a lot for your interest. However, the bounty is already assigned.

Have a look at Internet Computer Loading and Bounties - Internet Computer Developer Forum for more opportunities.

Ah, thanks. Still getting the hang of navigating the forums. :sweat_smile:

Hi @pramitgaha, I submitted my review to your github. Another colleague is currently on vacation but will review once he’s back.

1 Like

hello @benji , can you upgrade the candid version in ic-ledger-types package to 0.9.3?

ic_ledger_types - Rust is using candid = ^0.9 so it should already be possible to use it in a project using candid 0.9.3 . What error do you get? @pramitgaha

1 Like

ohh, I was importing the library by specifying the git path
icrc-ledger-types = { git ="https://github.com/dfinity/ic" }
In that git repo, candid’s version was ‘0.8.4’
I didn’t knew library was published!

hello @benji I can’t see structs like Account in the library!

I’m not sure what your question is, but Account is here Account in icrc_ledger_types::icrc1::account - Rust

1 Like

Hey all! It’s so incredible :boom:

By the way, what is the future fate of these standards? Will their quality be separately tested?

Besides, is it possible to create a separate library, for example, on Motoko, which will contain all the developed standards with the ability to import them and customize your canisters?

@benji , rewrote the work: icrc7
regarding clear of expired approvals: I didn’t do that. I saw in the revoke_approval method’s error there is one variant ApprovalExpired. To keep a track for that: I didn’t cleared the expired approvals.
Also regarding revoke_approval I’ve messaged you!
can you have a look?
thank you!

We intend to remove the ApprovalExpired error exactly for this reason.

1 Like

@benji @domwoe
what was the outcome of the today’s meeting?
any changes?

The docs listed here have some updates.

1 Like

:wave:

We are a group building code for the IC and supporting the integration of existing IC tech into enterprise workflows.

We have a base ICRC7 Motoko Class/Base Implementation that we’d like to submit for this bounty. We have a test suite, and it can be implemented by the following actor:

import Array "mo:base/Array";
import Vec "mo:vector";
import Principal "mo:base/Principal";
import Time "mo:base/Time";
import Nat "mo:base/Nat";
import D "mo:base/Debug";

import ICRC7 "../src";


import SB "mo:stablebuffer_1_2_0/StableBuffer";


shared(_init_msg) actor class Example(_args : {
  icrc7_args: ICRC7.InitArgs;
}) = this {

  type LogEntry =                         ICRC7.LogEntry;
  type Account =                          ICRC7.Account;
  type Environment =                      ICRC7.Environment;
  type Value =                            ICRC7.Value;
  type NFT =                              ICRC7.NFT;
  type NFTShared =                        ICRC7.NFTShared;
  type NFTMap =                           ICRC7.NFTMap;
  type OwnerOfResponse =                  ICRC7.OwnerOfResponse;
  type OwnerOfResponses =                 ICRC7.OwnerOfResponses;
  type ApprovalInfo =                     ICRC7.ApprovalInfo;
  type ApprovalResponse =                 ICRC7.ApprovalResponse;
  type ApprovalResponses =                ICRC7.ApprovalResponses;
  type ApprovalCollectionResponse =       ICRC7.ApprovalCollectionResponse;
  type TransferArgs =                     ICRC7.TransferArgs;
  type TransferResponse =                 ICRC7.TransferResponse;
  type TransferError =                    ICRC7.TransferArgs;
  type RevokeTokensArgs =                 ICRC7.RevokeTokensArgs;
  type RevokeTokensResponseItem =         ICRC7.RevokeTokensResponseItem;
  type RevokeCollectionArgs =             ICRC7.RevokeCollectionArgs;
  type RevokeCollectionResponseItem =     ICRC7.RevokeCollectionResponseItem;
  type TokenApproval =                    ICRC7.TokenApproval;
  type CollectionApproval =               ICRC7.CollectionApproval;

  stable var init_msg = _init_msg; //preserves original initialization;

  stable var icrc7_migration_state = ICRC7.init(ICRC7.initialState() , #v0_1_0(#id), switch(_args.icrc7_args){
    case(null){
      ?{
        symbol = ?"TST";
        name = ?"Test NFT";
        description = ?"A Test Collection";
        logo = ?"https://www.icpscan.co/img/motoko.jpeg";
        supply_cap = null;
        total_supply = 0;
        max_approvals_per_token_or_collection = ?10;
        max_query_batch_size = ?100;
        max_update_batch_size = ?100;
        default_take_value = ?1000;
        max_take_value = ?10000;
        max_revoke_approvals = ?100;
        max_memo_size = ?512;
        deployer = init_msg.caller;
      } : ICRC7.InitArgs;
    };
    case(?val) ?val;
    }, init_msg.caller);

  let #v0_1_0(#data(icrc7_state_current)) = icrc7_migration_state;

  private var _icrc7 : ?ICRC7.ICRC7 = null;

  private func get_icrc7_state() : ICRC7.CurrentState {
    return icrc7_state_current;
  };

  private func get_icrc7_environment() : ICRC7.Environment{
    {
      canister = get_canister;
      get_time = get_time;
      refresh_state = get_icrc7_state;
      log = ?addLog;
      ledger = ?{
        add_ledger_transaction = add_trx;
      };
    };
  };

  func icrc7() : ICRC7.ICRC7 {
    switch(_icrc7){
      case(null){
        let initclass : ICRC7.ICRC7 = ICRC7.ICRC7(?icrc7_migration_state, Principal.fromActor(this), get_icrc7_environment());
        _icrc7 := ?initclass;
        initclass;
      };
      case(?val) val;
    };
  };

  stable var log = SB.init<LogEntry>();

  private func addLog(entry : LogEntry){
    //note: Implement your own logging solution like canister geek. This will not scale without an implemented logging strategy
    SB.add(log, entry);
  };

  //we will use a stable log for this example, but encourage the use of ICRC3 in a full implementation.  see https://github.com/panindustrial/FullNFT.mo

  stable var trx_log = Vec.new<ICRC7.Value>();

  func add_trx(entry : Value) : Nat {
    Vec.add(trx_log, entry);
    return (Vec.size(trx_log) - 1);
  };

  private var canister_principal : ?Principal = null;

  private func get_canister() : Principal {
    switch (canister_principal) {
        case (null) {
            canister_principal := ?Principal.fromActor(this);
            Principal.fromActor(this);
        };
        case (?val) {
            val;
        };
    };
  };

  private func get_time() : Int{
      //note: you may want to implement a testing framework where you can set this time manually
      /* switch(state_current.testing.time_mode){
          case(#test){
              state_current.testing.test_time;
          };
          case(#standard){
               Time.now();
          };
      }; */
    Time.now();
  };



  public query func icrc_symbol() : async Text {

    return switch(icrc7().get_ledger_info().symbol){
      case(?val) val;
      case(null) "";
    };
  };

  public query func icrc_name() : async Text {
    return switch(icrc7().get_ledger_info().name){
      case(?val) val;
      case(null) "";
    };
  };

  public query func icrc7_description() : async ?Text {
    return icrc7().get_ledger_info().description;
  };

  public query func icrc7_logo() : async ?Text {
    return icrc7().get_ledger_info().logo;
  };

  public query func icrc7_max_memo_size() : async ?Nat {
    return icrc7().get_ledger_info().max_memo_size;
  };

  public query func icrc7_total_supply() : async Nat {
    return icrc7().get_ledger_info().total_supply;
  };

  public query func icrc7_supply_cap() : async ?Nat {
    return icrc7().get_ledger_info().supply_cap;
  };

  public query func icrc7_max_approvals_per_token_or_collection() : async ?Nat {
    return icrc7().get_ledger_info().max_approvals_per_token_or_collection;
  };

  public query func icrc7_max_query_batch_size() : async ?Nat {
    return icrc7().get_ledger_info().max_query_batch_size;
  };

  public query func icrc7_max_update_batch_size() : async ?Nat {
    return icrc7().get_ledger_info().max_update_batch_size;
  };

  public query func icrc7_default_take_value() : async ?Nat {
    return icrc7().get_ledger_info().default_take_value;
  };

  public query func icrc7_max_take_value() : async ?Nat {
    return icrc7().get_ledger_info().max_take_value;
  };

  public query func icrc7_max_revoke_approvals() : async ?Nat {
    return icrc7().get_ledger_info().max_revoke_approvals;
  };

  public query func icrc7_collection_metadata() : async {metadata: [(Text, Value)]} {

    let ledger_info = icrc7().get_ledger_info();
    let results = Vec.new<(Text, Value)>();

    switch(ledger_info.symbol){
      case(?val) Vec.add(results, ("icrc7:symbol", #Text(val)));
      case(null) {};
    };
    
    switch(ledger_info.name){
      case(?val) Vec.add(results, ("icrc7:name", #Text(val)));
      case(null) {};
    };

    switch(ledger_info.description){
      case(?val) Vec.add(results, ("icrc7:description", #Text(val)));
      case(null) {};
    };

    switch(ledger_info.logo){
      case(?val) Vec.add(results, ("icrc7:logo", #Text(val)));
      case(null) {};
    };

    Vec.add(results, ("icrc7:total_supply", #Nat(ledger_info.total_supply)));

    switch(ledger_info.supply_cap){
      case(?val) Vec.add(results, ("icrc7:supply_cap", #Nat(val)));
      case(null) {};
    };
 
    switch(ledger_info.max_approvals_per_token_or_collection){
      case(?val) Vec.add(results,("icrc7:max_approvals_per_token_or_collection", #Nat(val)));
      case(null) {};
    };

    switch(ledger_info.max_query_batch_size){
      case(?val) Vec.add(results,("icrc7:max_query_batch_size", #Nat(val)));
      case(null) {};
    };

    switch(ledger_info.max_update_batch_size){
      case(?val) Vec.add(results, ("icrc7:max_update_batch_size", #Nat(val)));
      case(null) {};
    };

    switch(ledger_info.default_take_value){
      case(?val) Vec.add(results,("icrc7:default_take_value", #Nat(val)));
      case(null) {};
    };

    switch(ledger_info.max_take_value){
      case(?val) Vec.add(results,("icrc7:max_take_value", #Nat(val)));
      case(null) {};
    };

    switch(ledger_info.max_revoke_approvals){
      case(?val) Vec.add(results, ("icrc7:max_revoke_approvals", #Nat(val)));
      case(null) {};
    };

    return {
      metadata = Vec.toArray(results);
    };
  };

  //todo: confirm the nullability of the NFT
  public query func icrc7_token_metadata(token_ids: [Nat]) : async [(Nat, ?NFTMap)]{
 
     switch(icrc7().get_token_infos_shared(token_ids)){
        case(#ok(val)) val;
        case(#err(err)) D.trap(err);
      };
  };

 

  public query func icrc7_owner_of(token_ids: [Nat]) : async OwnerOfResponses {
   
     switch( icrc7().get_token_owners(token_ids)){
        case(#ok(val)) val;
        case(#err(err)) D.trap(err);
      };
  };

  public query func icrc7_balance_of(account: Account) : async Nat {
    return icrc7().get_token_owners_tokens_count(account);
  };

  public query func icrc7_tokens(prev: ?Nat, take: ?Nat) : async [Nat] {
    return icrc7().get_tokens_paginated(prev, take);
  };

  public query func icrc7_tokens_of(account: Account, prev: ?Nat, take: ?Nat) : async [Nat] {
    return icrc7().get_tokens_of_paginated(account, prev, take);
  };

  public query func icrc7_is_approved(spender: Account, from_subaccount: ?Blob, token_id: Nat) : async Bool {
    return icrc7().is_approved(spender, from_subaccount, token_id);
  };

  public query func icrc7_get_approvals(token_ids: [Nat], prev: ?TokenApproval, take: ?Nat32) : async [TokenApproval] {
    
    switch(icrc7().get_token_approvals(token_ids, prev, take)){
        case(#ok(val)) val;
        case(#err(err)) D.trap(err);
      };
  };

  public query func icrc7_get_collection_approvals(owner : Account, prev: ?CollectionApproval, take: ?Nat32) : async [CollectionApproval] {
    
    switch(icrc7().get_collection_approvals(owner, prev, take)){
        case(#ok(val)) val;
        case(#err(err)) D.trap(err);
      };
  };

  public query func icrc7_supported_standards() : async [{ name : Text; url : Text }] {
    //todo: figure this out
    return [{name = "ICRC-7"; url = "https://github.com/dfinity/ICRC/ICRCs/ICRC-7"}];
  };


  //Update calls

  public shared(msg) func icrc7_approve(token_ids: [Nat], approval: ApprovalInfo) : async ApprovalResponses {

    switch(icrc7().approve_transfers(get_icrc7_environment(), msg.caller, token_ids, approval)){
        case(#ok(val)) val;
        case(#err(err)) D.trap(err);
      };
  };

  public shared(msg) func icrc7_approve_collection(approval: ApprovalInfo) : async ApprovalCollectionResponse {

      let result : ApprovalResponse = switch(icrc7().approve_collection(get_icrc7_environment(), msg.caller, approval)){
        case(#ok(val)) val;
        case(#err(err)) D.trap(err);
      };

      switch(result){
        case(#Err(val)){
          return (#Err(icrc7().TokenErrorToCollectionError(val)));
        };
        case(#Ok(val)){
          return #Ok(val);
        };
      };
  };

  public shared(msg) func icrc7_transfer(args: TransferArgs) : async [(Nat, TransferResponse)] {
      

      switch(icrc7().transfer(get_icrc7_environment(), msg.caller, args)){
        case(#ok(val)) val;
        case(#err(err)) D.trap(err);
      };
  };

  public shared(msg) func icrc7_revoke_token_approvals(args: RevokeTokensArgs) : async [RevokeTokensResponseItem] {
      switch(icrc7().revoke_token_approvals(get_icrc7_environment(), msg.caller, args)){
        case(#ok(val)) val;
        case(#err(err)) D.trap(err);
      };
  };

  public shared(msg) func icrc7_revoke_collection_approvals(args: RevokeCollectionArgs) : async [RevokeCollectionResponseItem] {
     
      switch(icrc7().revoke_collection_approvals(get_icrc7_environment(), msg.caller, args)){
        case(#ok(val)) val;
        case(#err(err)) D.trap(err);
      };
  };

};
2 Likes

Hello,
we would like to participate in this Bounty.

In Noku/Original we developed in June a Motoko version of the ICRC-7 standard.

You can see the code at our github page: noku-team/icrc7_motoko

We already opened a pull request to the awesome-motoko repository ( motoko-unofficial/awesome-motoko/pull/31 )

There are already a few collections that use this code, but the first one is Casino Lugano’s NFT Collection

5 Likes

Hi,
@ pramitgaha is your current implementation still WIP or it is production ready ? ( GitHub - rustacean1/icrc7: Icrc7 implementation in Rust )
If not finished, do you need help on it?
Gautier

Same question, does someone have an update on the state of this implementation?