diff --git a/mops.toml b/mops.toml index 693499c..ca37006 100644 --- a/mops.toml +++ b/mops.toml @@ -1,10 +1,10 @@ [dependencies] -base = "https://github.com/dfinity/motoko-base#moc-0.10.1@80ededb39954739b18b88fc0e5561264003cf9ab" +base = "0.11.0" cert = "https://github.com/nomeata/ic-certification#v0.1.3@c403ffec0a60f658a1009976c785aa567ff0da77" -icrc1-mo = "0.0.10" -icrc2-mo = "0.0.8" -icrc3-mo = "0.2.3" -icrc4-mo = "0.0.1" +icrc1-mo = "0.0.14" +icrc2-mo = "0.0.12" +icrc3-mo = "0.2.4" +icrc4-mo = "0.0.4" [dev-dependencies] test = "1.2.0" @@ -13,7 +13,7 @@ matchers = "https://github.com/kritzcreek/motoko-matchers#v1.3.0@3dac8a071b69e4e [package] name = "icrc-fungible" -version = "0.0.2" -description = "ICRC1, ICRC2, and ICRC3 fungible token for motoko" +version = "0.0.3" +description = "ICRC1, ICRC2, ICRC3, ICRC4, and ICRC61 fungible token for motoko" repository = "hhttps://github.com/PanIndustrial-Org/ICRC_fungible" -keywords = [ "icrc2", "icrc1", "icrc3", "fungible", "token", "standard" ] +keywords = [ "icrc2", "icrc1", "icrc3", "icrc4", "icrc61", "fungible", "token", "standard" ] diff --git a/runners/prod_deploy.sh b/runners/prod_deploy.sh index 9be5c29..9d7b0c0 100644 --- a/runners/prod_deploy.sh +++ b/runners/prod_deploy.sh @@ -74,7 +74,13 @@ icrc3 = opt record { archiveIndexType = variant {Stable = null}; maxRecordsToArchive = 8000; archiveCycles = 20_000_000_000_000; + supportedBlocks = vec {}; archiveControllers = null; +}; +icrc4 = opt record { + max_balances = opt 200; + max_transfers = opt 200; + fee = opt variant { ICRC1 = null}; };})" --mode reinstall # Fetch the canister ID after deployment diff --git a/runners/test_deploy.sh b/runners/test_deploy.sh index 9dbf954..2c85f7e 100644 --- a/runners/test_deploy.sh +++ b/runners/test_deploy.sh @@ -64,7 +64,13 @@ icrc3 = opt record { archiveIndexType = variant {Stable = null}; maxRecordsToArchive = 8000; archiveCycles = 20_000_000_000_000; + supportedBlocks = vec {}; archiveControllers = null; +}; +icrc4 = opt record { + max_balances = opt 200; + max_transfers = opt 200; + fee = opt variant { ICRC1 = null}; };})" --mode reinstall ICRC_CANISTER=$(dfx canister id token) diff --git a/src/Token.mo b/src/Token.mo index da7411a..be3c803 100644 --- a/src/Token.mo +++ b/src/Token.mo @@ -1,8 +1,7 @@ -import Array "mo:base/Array"; +import Buffer "mo:base/Buffer"; import D "mo:base/Debug"; import ExperimentalCycles "mo:base/ExperimentalCycles"; -import Iter "mo:base/Iter"; -import Option "mo:base/Option"; + import Principal "mo:base/Principal"; import Time "mo:base/Time"; @@ -17,7 +16,7 @@ import ICRC4 "mo:icrc4-mo/ICRC4"; shared ({ caller = _owner }) actor class Token (args: ?{ icrc1 : ?ICRC1.InitArgs; icrc2 : ?ICRC2.InitArgs; - icrc3 : ?ICRC3.InitArgs; + icrc3 : ICRC3.InitArgs; //already typed nullable icrc4 : ?ICRC4.InitArgs; } ) = this{ @@ -62,11 +61,29 @@ shared ({ caller = _owner }) actor class Token (args: ?{ maxRecordsToArchive = 8000; archiveCycles = 20_000_000_000_000; archiveControllers = null; //??[put cycle ops prinicpal here]; + supportedBlocks = [ + { + block_type = "1xfer"; + url="https://github.com/dfinity/ICRC-1/tree/main/standards/ICRC-3"; + }, + { + block_type = "1approve"; + url="https://github.com/dfinity/ICRC-1/tree/main/standards/ICRC-3"; + }, + { + block_type = "1mint"; + url="https://github.com/dfinity/ICRC-1/tree/main/standards/ICRC-3"; + }, + { + block_type = "1burn"; + url="https://github.com/dfinity/ICRC-1/tree/main/standards/ICRC-3"; + } + ]; }; let default_icrc4_args : ICRC4.InitArgs = { - max_balances = ?3000; - max_transfers = ?3000; + max_balances = ?200; + max_transfers = ?200; fee = ?#ICRC1; }; @@ -107,7 +124,7 @@ shared ({ caller = _owner }) actor class Token (args: ?{ case(?args){ switch(args.icrc3){ case(null) default_icrc3_args; - case(?val) val; + case(?val) ?val; }; }; }; @@ -155,7 +172,11 @@ shared ({ caller = _owner }) actor class Token (args: ?{ let initclass : ICRC1.ICRC1 = ICRC1.ICRC1(?icrc1_migration_state, Principal.fromActor(this), get_icrc1_environment()); ignore initclass.register_supported_standards({ name = "ICRC-3"; - url = "https://github.com/dfinity/ICRC-1/tree/icrc-3/standards/ICRC-3" + url = "https://github.com/dfinity/ICRC/ICRCs/icrc-3/" + }); + ignore initclass.register_supported_standards({ + name = "ICRC-61"; + url = "https://github.com/dfinity/ICRC/ICRCs/icrc-61/" }); _icrc1 := ?initclass; initclass; @@ -239,11 +260,51 @@ shared ({ caller = _owner }) actor class Token (args: ?{ }; }; + func ensure_block_types(icrc3Class: ICRC3.ICRC3) : () { + let supportedBlocks = Buffer.fromIter(icrc3Class.supported_block_types().vals()); + + let blockequal = func(a : {block_type: Text}, b : {block_type: Text}) : Bool { + a.block_type == b.block_type; + }; + + if(Buffer.indexOf({block_type = "1xfer"; url="";}, supportedBlocks, blockequal) == null){ + supportedBlocks.add({ + block_type = "1xfer"; + url="https://github.com/dfinity/ICRC-1/tree/main/standards/ICRC-3"; + }); + }; + + if(Buffer.indexOf({block_type = "1approve";url="";}, supportedBlocks, blockequal) == null){ + supportedBlocks.add({ + block_type = "1approve"; + url="https://github.com/dfinity/ICRC-1/tree/main/standards/ICRC-3"; + }); + }; + + if(Buffer.indexOf({block_type = "1mint";url="";}, supportedBlocks, blockequal) == null){ + supportedBlocks.add({ + block_type = "1mint"; + url="https://github.com/dfinity/ICRC-1/tree/main/standards/ICRC-3"; + }); + }; + + if(Buffer.indexOf({block_type = "1burn";url="";}, supportedBlocks, blockequal) == null){ + supportedBlocks.add({ + block_type = "1burn"; + url="https://github.com/dfinity/ICRC-1/tree/main/standards/ICRC-3"; + }); + }; + + icrc3Class.update_supported_blocks(Buffer.toArray(supportedBlocks)); + }; + func icrc3() : ICRC3.ICRC3 { switch(_icrc3){ case(null){ let initclass : ICRC3.ICRC3 = ICRC3.ICRC3(?icrc3_migration_state, Principal.fromActor(this), get_icrc3_environment()); _icrc3 := ?initclass; + ensure_block_types(initclass); + initclass; }; case(?val) val; @@ -300,6 +361,10 @@ shared ({ caller = _owner }) actor class Token (args: ?{ icrc1().supported_standards(); }; + public shared query func icrc61_supported_standards() : async [ICRC1.SupportedStandard] { + icrc1().supported_standards(); + }; + public shared ({ caller }) func icrc1_transfer(args : ICRC1.TransferArgs) : async ICRC1.TransferResult { switch(await* icrc1().transfer_tokens(caller, args, false, null)){ case(#trappable(val)) val; @@ -363,16 +428,20 @@ shared ({ caller = _owner }) actor class Token (args: ?{ return icrc3().get_tip_certificate(); }; + public query func icrc3_supported_block_types() : async [ICRC3.BlockType] { + return icrc3().supported_block_types(); + }; + public query func get_tip() : async ICRC3.Tip { return icrc3().get_tip(); }; - public shared ({ caller }) func icrc4_transfer_batch(args: ICRC4.TransferBatchArgs) : async ICRC4.TransferBatchResult { + public shared ({ caller }) func icrc4_transfer_batch(args: ICRC4.TransferBatchArgs) : async ICRC4.TransferBatchResults { switch(await* icrc4().transfer_batch_tokens(caller, args, null, null)){ case(#trappable(val)) val; case(#awaited(val)) val; - case(#err(#trappable(err))) D.trap(err); - case(#err(#awaited(err))) D.trap(err); + case(#err(#trappable(err))) err; + case(#err(#awaited(err))) err; }; }; @@ -380,6 +449,14 @@ shared ({ caller = _owner }) actor class Token (args: ?{ icrc4().balance_of_batch(request); }; + public shared query func icrc4_maximum_update_batch_size() : async ?Nat { + ?icrc4().get_state().ledger_info.max_transfers; + }; + + public shared query func icrc4_maximum_query_batch_size() : async ?Nat { + ?icrc4().get_state().ledger_info.max_balances; + }; + public shared ({ caller }) func admin_update_owner(new_owner : Principal) : async Bool { if(caller != owner){ D.trap("Unauthorized")}; owner := new_owner; @@ -444,7 +521,7 @@ shared ({ caller = _owner }) actor class Token (args: ?{ // Deposit cycles into this canister. public shared func deposit_cycles() : async () { let amount = ExperimentalCycles.available(); - let accepted = ExperimentalCycles.accept(amount); + let accepted = ExperimentalCycles.accept(amount); assert (accepted == amount); }; diff --git a/src/examples/Allowlist.mo b/src/examples/Allowlist.mo index 62d212f..8800ddc 100644 --- a/src/examples/Allowlist.mo +++ b/src/examples/Allowlist.mo @@ -19,16 +19,13 @@ /// ///////////////////// -import Array "mo:base/Array"; +import Buffer "mo:base/Buffer"; import D "mo:base/Debug"; import ExperimentalCycles "mo:base/ExperimentalCycles"; -import Iter "mo:base/Iter"; -import Option "mo:base/Option"; + import Principal "mo:base/Principal"; import Result "mo:base/Result"; -import Time "mo:base/Time"; -import CertifiedData "mo:base/CertifiedData"; import CertTree "mo:cert/CertTree"; import ICRC1 "mo:icrc1-mo/ICRC1"; @@ -39,7 +36,7 @@ import ICRC4 "mo:icrc4-mo/ICRC4"; shared ({ caller = _owner }) actor class Token (args: ?{ icrc1 : ?ICRC1.InitArgs; icrc2 : ?ICRC2.InitArgs; - icrc3 : ?ICRC3.InitArgs; + icrc3 : ICRC3.InitArgs;//already typed nullable icrc4 : ?ICRC4.InitArgs; } @@ -87,6 +84,24 @@ shared ({ caller = _owner }) actor class Token (args: ?{ maxRecordsToArchive = 8000; archiveCycles = 20_000_000_000_000; archiveControllers = null; //??[put cycle ops prinicpal here]; + supportedBlocks = [ + { + block_type = "1xfer"; + url="https://github.com/dfinity/ICRC-1/tree/main/standards/ICRC-3"; + }, + { + block_type = "1approve"; + url="https://github.com/dfinity/ICRC-1/tree/main/standards/ICRC-3"; + }, + { + block_type = "1mint"; + url="https://github.com/dfinity/ICRC-1/tree/main/standards/ICRC-3"; + }, + { + block_type = "1burn"; + url="https://github.com/dfinity/ICRC-1/tree/main/standards/ICRC-3"; + } + ]; }; let default_icrc4_args : ICRC4.InitArgs = { @@ -132,7 +147,7 @@ shared ({ caller = _owner }) actor class Token (args: ?{ case(?args){ switch(args.icrc3){ case(null) default_icrc3_args; - case(?val) val; + case(?val) ?val; }; }; }; @@ -258,11 +273,50 @@ shared ({ caller = _owner }) actor class Token (args: ?{ }; }; + func ensure_block_types(icrc3Class: ICRC3.ICRC3) : () { + let supportedBlocks = Buffer.fromIter(icrc3Class.supported_block_types().vals()); + + let blockequal = func(a : {block_type: Text}, b : {block_type: Text}) : Bool { + a.block_type == b.block_type; + }; + + if(Buffer.indexOf({block_type = "1xfer"; url="";}, supportedBlocks, blockequal) == null){ + supportedBlocks.add({ + block_type = "1xfer"; + url="https://github.com/dfinity/ICRC-1/tree/main/standards/ICRC-3"; + }); + }; + + if(Buffer.indexOf({block_type = "1approve";url="";}, supportedBlocks, blockequal) == null){ + supportedBlocks.add({ + block_type = "1approve"; + url="https://github.com/dfinity/ICRC-1/tree/main/standards/ICRC-3"; + }); + }; + + if(Buffer.indexOf({block_type = "1mint";url="";}, supportedBlocks, blockequal) == null){ + supportedBlocks.add({ + block_type = "1mint"; + url="https://github.com/dfinity/ICRC-1/tree/main/standards/ICRC-3"; + }); + }; + + if(Buffer.indexOf({block_type = "1burn";url="";}, supportedBlocks, blockequal) == null){ + supportedBlocks.add({ + block_type = "1burn"; + url="https://github.com/dfinity/ICRC-1/tree/main/standards/ICRC-3"; + }); + }; + + icrc3Class.update_supported_blocks(Buffer.toArray(supportedBlocks)); + }; + func icrc3() : ICRC3.ICRC3 { switch(_icrc3){ case(null){ let initclass : ICRC3.ICRC3 = ICRC3.ICRC3(?icrc3_migration_state, Principal.fromActor(this), get_icrc3_environment()); _icrc3 := ?initclass; + ensure_block_types(initclass); initclass; }; case(?val) val; @@ -369,12 +423,12 @@ shared ({ caller = _owner }) actor class Token (args: ?{ }; }; - public shared ({ caller }) func icrc4_transfer_batch(args: ICRC4.TransferBatchArgs) : async ICRC4.TransferBatchResult { + public shared ({ caller }) func icrc4_transfer_batch(args: ICRC4.TransferBatchArgs) : async ICRC4.TransferBatchResults { switch(await* icrc4().transfer_batch_tokens(caller, args, ?#Sync(can_transfer), ?#Sync(can_transfer_batch))){ case(#trappable(val)) val; case(#awaited(val)) val; - case(#err(#trappable(err))) D.trap(err); - case(#err(#awaited(err))) D.trap(err); + case(#err(#trappable(err))) err; + case(#err(#awaited(err))) err; }; }; @@ -469,7 +523,7 @@ shared ({ caller = _owner }) actor class Token (args: ?{ }; - private func can_transfer(trx: ICRC1.Value, trxtop: ?ICRC1.Value, notification: ICRC1.TransactionRequestNotification) : Result.Result<(trx: ICRC1.Value, trxtop: ?ICRC1.Value, notification: ICRC1.TransactionRequestNotification), Text>{ + private func can_transfer(trx: ICRC1.Value, trxtop: ?ICRC1.Value, notification: ICRC1.TransactionRequestNotification) : Result.Result<(trx: ICRC1.Value, trxtop: ?ICRC1.Value, notification: ICRC1.TransactionRequestNotification), Text>{ if(Set.has(allowlist, Set.phash, notification.from.owner)){ return #ok(trx, update_fee(trxtop,0), {notification with calculated_fee = 0;}); @@ -477,7 +531,7 @@ shared ({ caller = _owner }) actor class Token (args: ?{ return #err("Not allowed"); }; - private func can_approve(trx: ICRC2.Value, trxtop: ?ICRC2.Value, notification: ICRC2.TokenApprovalNotification) : Result.Result<(trx: ICRC2.Value, trxtop: ?ICRC2.Value, notification: ICRC2.TokenApprovalNotification), Text>{ + private func can_approve(trx: ICRC2.Value, trxtop: ?ICRC2.Value, notification: ICRC2.TokenApprovalNotification) : Result.Result<(trx: ICRC2.Value, trxtop: ?ICRC2.Value, notification: ICRC2.TokenApprovalNotification), Text>{ if(Set.has(allowlist, Set.phash, notification.from.owner)){ return #ok(trx,update_fee(trxtop,0),{notification with calculated_fee = 0;}); @@ -485,7 +539,7 @@ shared ({ caller = _owner }) actor class Token (args: ?{ return #err("Not allowed"); }; - private func can_transfer_from(trx: ICRC2.Value, trxtop: ?ICRC2.Value, notification: ICRC2.TransferFromNotification) : Result.Result<(trx: ICRC2.Value, trxtop: ?ICRC2.Value, notification: ICRC2.TransferFromNotification), Text>{ + private func can_transfer_from(trx: ICRC2.Value, trxtop: ?ICRC2.Value, notification: ICRC2.TransferFromNotification) : Result.Result<(trx: ICRC2.Value, trxtop: ?ICRC2.Value, notification: ICRC2.TransferFromNotification), Text>{ if(Set.has(allowlist, Set.phash, notification.from.owner)){ return #ok(trx,update_fee(trxtop,0),{notification with calculated_fee = 0;}); @@ -493,11 +547,11 @@ shared ({ caller = _owner }) actor class Token (args: ?{ return #err("Not allowed"); }; - private func can_transfer_batch(notification: ICRC4.TransferBatchNotification) : Result.Result<(notification: ICRC4.TransferBatchNotification), Text>{ + private func can_transfer_batch(notification: ICRC4.TransferBatchNotification) : Result.Result<(notification: ICRC4.TransferBatchNotification), ICRC4.TransferBatchResults>{ if(Set.has(allowlist, Set.phash, notification.from)){ return #ok(notification); }; - return #err("Not allowed"); + return #err([?#Err(#GenericBatchError({message = "Not allowed"; error_code = 1}))]); }; public shared ({ caller }) func admin_update_allowlist(request : [{principal: Principal; allow: Bool}]) : async () { diff --git a/src/examples/Lotto.mo b/src/examples/Lotto.mo index f1f0470..49ffaa1 100644 --- a/src/examples/Lotto.mo +++ b/src/examples/Lotto.mo @@ -9,21 +9,17 @@ /// ///////////////////// -import Array "mo:base/Array"; +import Buffer "mo:base/Buffer"; import D "mo:base/Debug"; import ExperimentalCycles "mo:base/ExperimentalCycles"; import Int "mo:base/Int"; -import Iter "mo:base/Iter"; -import Option "mo:base/Option"; import Principal "mo:base/Principal"; -import Result "mo:base/Result"; import Nat64 "mo:base/Nat64"; import Text "mo:base/Text"; import Time "mo:base/Time"; import Timer "mo:base/Timer"; import Random "mo:base/Random"; -import CertifiedData "mo:base/CertifiedData"; import CertTree "mo:cert/CertTree"; import ICRC1 "mo:icrc1-mo/ICRC1"; @@ -34,7 +30,7 @@ import ICRC4 "mo:icrc4-mo/ICRC4"; shared ({ caller = _owner }) actor class Token (args: ?{ icrc1 : ?ICRC1.InitArgs; icrc2 : ?ICRC2.InitArgs; - icrc3 : ?ICRC3.InitArgs; + icrc3 : ICRC3.InitArgs; //already typed nullable icrc4 : ?ICRC4.InitArgs; } @@ -80,6 +76,24 @@ shared ({ caller = _owner }) actor class Token (args: ?{ maxRecordsToArchive = 8000; archiveCycles = 20_000_000_000_000; archiveControllers = null; //??[put cycle ops prinicpal here]; + supportedBlocks = [ + { + block_type = "1xfer"; + url="https://github.com/dfinity/ICRC-1/tree/main/standards/ICRC-3"; + }, + { + block_type = "1approve"; + url="https://github.com/dfinity/ICRC-1/tree/main/standards/ICRC-3"; + }, + { + block_type = "1mint"; + url="https://github.com/dfinity/ICRC-1/tree/main/standards/ICRC-3"; + }, + { + block_type = "1burn"; + url="https://github.com/dfinity/ICRC-1/tree/main/standards/ICRC-3"; + } + ]; }; let default_icrc4_args : ICRC4.InitArgs = { @@ -125,7 +139,7 @@ shared ({ caller = _owner }) actor class Token (args: ?{ case(?args){ switch(args.icrc3){ case(null) default_icrc3_args; - case(?val) val; + case(?val) ?val; }; }; }; @@ -253,11 +267,50 @@ shared ({ caller = _owner }) actor class Token (args: ?{ }; }; + func ensure_block_types(icrc3Class: ICRC3.ICRC3) : () { + let supportedBlocks = Buffer.fromIter(icrc3Class.supported_block_types().vals()); + + let blockequal = func(a : {block_type: Text}, b : {block_type: Text}) : Bool { + a.block_type == b.block_type; + }; + + if(Buffer.indexOf({block_type = "1xfer"; url="";}, supportedBlocks, blockequal) == null){ + supportedBlocks.add({ + block_type = "1xfer"; + url="https://github.com/dfinity/ICRC-1/tree/main/standards/ICRC-3"; + }); + }; + + if(Buffer.indexOf({block_type = "1approve";url="";}, supportedBlocks, blockequal) == null){ + supportedBlocks.add({ + block_type = "1approve"; + url="https://github.com/dfinity/ICRC-1/tree/main/standards/ICRC-3"; + }); + }; + + if(Buffer.indexOf({block_type = "1mint";url="";}, supportedBlocks, blockequal) == null){ + supportedBlocks.add({ + block_type = "1mint"; + url="https://github.com/dfinity/ICRC-1/tree/main/standards/ICRC-3"; + }); + }; + + if(Buffer.indexOf({block_type = "1burn";url="";}, supportedBlocks, blockequal) == null){ + supportedBlocks.add({ + block_type = "1burn"; + url="https://github.com/dfinity/ICRC-1/tree/main/standards/ICRC-3"; + }); + }; + + icrc3Class.update_supported_blocks(Buffer.toArray(supportedBlocks)); + }; + func icrc3() : ICRC3.ICRC3 { switch(_icrc3){ case(null){ let initclass : ICRC3.ICRC3 = ICRC3.ICRC3(?icrc3_migration_state, Principal.fromActor(this), get_icrc3_environment()); _icrc3 := ?initclass; + ensure_block_types(initclass); initclass; }; case(?val) val; @@ -368,12 +421,12 @@ shared ({ caller = _owner }) actor class Token (args: ?{ return icrc3().get_tip(); }; - public shared ({ caller }) func icrc4_transfer_batch(args: ICRC4.TransferBatchArgs) : async ICRC4.TransferBatchResult { + public shared ({ caller }) func icrc4_transfer_batch(args: ICRC4.TransferBatchArgs) : async ICRC4.TransferBatchResults { switch(await* icrc4().transfer_batch_tokens(caller, args, null, null)){ case(#trappable(val)) val; case(#awaited(val)) val; - case(#err(#trappable(err))) D.trap(err); - case(#err(#awaited(err))) D.trap(err); + case(#err(#trappable(err))) err; + case(#err(#awaited(err))) err; }; }; @@ -442,7 +495,7 @@ shared ({ caller = _owner }) actor class Token (args: ?{ var lotto_timmer : ?Nat = null; /// Should be called whenever the is a transfer - private func transfer_listener(trx : ICRC1.Transaction, trxid : Nat) : () { + private func transfer_listener(trx : ICRC1.Transaction, trxid : Nat) : () { // D.print("in transfer listener" # debug_show(trx)); switch(trx.burn){ case(?burn){