We have published a new version of the icrc-fungible mops package. This uses Pan Industrials icrc1-mo, icrc2-mo, icrc3-mo, and icrc4-mo packages to build out a basic fungible token in motoko that is easier to customize and add behavior to than the standard RUST based SNS ledger. See v0.0.6
v0.0.6
- implemented ICRC3 to include legacy get_transactions backfill
- added icrc-103 endpoint for retrieving allowances.
- added icrc-106 endpoint for retrieving index canister
- added sns token to match interface an intitialization as close to rust sns ledger as possible. Use this with Defi vectors test see GitHub - Neutrinomic/devefi_icrc_ledger
The big win here is we should be compatible with NTN’s DeFi Vector ecosystem.
Because the token has listeners and interceptors you can easily add features such as running code upon sending tokens to a specific address(ie mint an NFT upon payment). Add mint and burn functions that verify behavior elsewhere on the IC or on other chains through chain fusion.
For example, our lotto example adds a lotto like functionality where you can get a 50/50 chance at doubling your tokens by burning with just a few lines of code added to the boilerplate.
public shared(msg) func admin_init() : async () {
//can only be called once
if(_init == false){
//ensure metadata has been registered
let test1 = icrc1().metadata();
let test2 = icrc2().metadata();
let test3 = icrc3().stats();
//uncomment the following line to register the transfer_listener
icrc1().register_token_transferred_listener("lotto", transfer_listener);
//uncomment the following line to register the transfer_listener
//icrc2().register_token_approved_listener("my_namespace", approval_listener);
//uncomment the following line to register the transfer_listener
//icrc1().register_transfer_from_listener("my_namespace", transfer_from_listener);
};
_init := true;
};
/// Set up a vector to track the burns
let Vector = ICRC1.Vector;
stable let lotto_list : Vector.Vector<(ICRC1.Account, Nat)> = Vector.new<(ICRC1.Account, Nat)>();
var lotto_timmer : ?Nat = null;
/// Should be called whenever the is a transfer
private func transfer_listener<system>(trx : ICRC1.Transaction, trxid : Nat) : () {
// D.print("in transfer listener" # debug_show(trx));
switch(trx.burn){
case(?burn){
// D.print("have a burn" # debug_show(burn));
/// The lotto uses async calls to the random beacon, so we can't process them here.
/// We add it to a vector to be processed after 100 seconds.
Vector.add(lotto_list, (burn.from, burn.amount));
if(lotto_timmer == null){
lotto_timmer := ?Timer.setTimer<system>(#seconds(1), run_lotto);
};
};
case(_){};
};
};
/// runs the lotto process on a timer
private func run_lotto<system>() : async (){
//D.print("in lotto run");
let tempList = Vector.fromArray<(ICRC1.Account, Nat)>(Vector.toArray<(ICRC1.Account, Nat)>(lotto_list));
Vector.clear(lotto_list);
lotto_timmer := null;
var lottos_won : Int = 0;
var random = Random.Finite(await Random.blob());
for(thisItem in Vector.vals(tempList)){
let coin = switch(random.coin()){
case(null){
// D.print("coin was null. delaying timer");
//add it back to be processed next round
Vector.add(lotto_list, thisItem);
if(lotto_timmer == null){
lotto_timmer := ?Timer.setTimer<system>(#seconds(0), run_lotto);
};
};
case(?coin){
// D.print("coin was " # debug_show(coin));
if(coin){
let result = await* icrc1().mint_tokens(icrc1().minting_account().owner,{
to = thisItem.0;
amount = thisItem.1 * 2;
memo = ?Text.encodeUtf8("Lotto!");
created_at_time = ?(Nat64.fromNat(Int.abs(Time.now() + lottos_won)));
} );
// D.print("lotto awarded " # debug_show(result));
lottos_won += 1;
};
};
};
};
};
/// faucet
public shared ({ caller }) func mint(account : ICRC1.Account) : async ICRC1.TransferResult {
switch( await* icrc1().mint_tokens(icrc1().minting_account().owner, {
to = account;
amount = 100000000000;
memo = ?Text.encodeUtf8("Mint!");
created_at_time = null;
})){
case(#trappable(val)) val;
case(#awaited(val)) val;
case(#err(#trappable(err))) D.trap(err);
case(#err(#awaited(err))) D.trap(err);
};
};
If you are interested in implementing a fungible token, porting a token, upgrading a token, please reach out to us as we are open to integration projects and helping to expand the IC ecosystem.
If you need to upgrade an existing Pan Industrial deployment, please reach out. There are a few things you need to keep in mind:
- We convert the stable let icrc3_migration_state to a stable var icrc3_migration_state_new and reassign it for class plus.
- If you have archives you’ll want to upgrade them using the UpgradeHelper in the ICRC3 library, but you’ll probably want to make sure you are a controller of them first so you can stop and snapshot them.
- Snapshot your canister before you upgrade!