Tutorial: How To Build A Token With Recurrent Payments On The Internet Computer Using ic-cron Library
Tags: internetcomputer, dfinity, smartcontracts, token
Published: 2022-03-01 19:19:16

---
title: "Tutorial: How To Build A Token With Recurrent Payments On The Internet Computer Using ic-cron Library"
published: true
description: "In this tutorial, I’ll show you how to extend your token in order to add recurrent payment functionality to it using ic-cron library."
tags: internetcomputer, dfinity, smartcontracts, token
//cover_image: https://direct_url_to_image.jpg
---

This tutorial is dedicated to canister development on the [Internet Computer (Dfinity)](https://dfinity.org/) platform. Completing it you:

1. Would know some of advanced canister (smart-contract) development techniques on the Internet Computer using Rust programming language. 
2. Would build your own token canister.
3. Would use [ic-cron](https://github.com/seniorjoinu/ic-cron) library in order to add recurrent payment mechanics to that token canister.

> This tutorial is intended for advanced developers who already understand the basics of developing Rust canisters on the IC. 

> Before digging into it, it is recommended to recover the basics once again. Some good starting points are: [the official website](https://smartcontracts.org/), dedicated to canister development on the IC; [IC developer forum](https://forum.dfinity.org/), where you can find an answer to almost any technical question.

## Motivation

There are a lot of token standards out there. One of the most popular token standard on Ethereum is the famous [ERC20](https://ethereum.org/en/developers/docs/standards/tokens/erc-20/). It was so successful, that many other people base their token standards on top of ERC20 (including other networks, like [DIP20](https://github.com/Psychedelic/DIP20)). It would be fair to say, that ERC20-like tokens are the most common in the wild on any network.

Despite this standard being so good and easy-to-use, it is pretty trivial in terms of functionality. Tokens are supposed to replace money, but there are some things you could do with money, but can’t do with tokens. For example, using web2 banking (classic money) you can easily “subscribe” to some service, making automatic periodical payments to it. This pattern is called “recurrent payments” and there is no such thing in web3. Yet.

In this tutorial, I’ll show you how to extend your token in order to add recurrent payment functionality to it using ic-cron library.

The complete source code for this tutorial can be found here:

[https://github.com/seniorjoinu/ic-cron-recurrent-payments-example](https://github.com/seniorjoinu/ic-cron-recurrent-payments-example)

Let’s go.

## Project initialization

In order to proceed, make sure you have these tools installed on your machine:

- [dfx 0.9.0](https://smartcontracts.org/docs/developers-guide/install-upgrade-remove.html)
- [rust 1.54+](https://www.rust-lang.org/tools/install) and `wasm32-unknown-unknown` toolchain
- [ic-cdk-optimizer](https://smartcontracts.org/docs/rust-guide/rust-optimize.html)

You can use any file system layout for this project, but I will use this one:

```rust
- src
  - actor.rs              // canister API description
  - common                
     - mod.rs
		 - currency_token.rs  // token internals
     - guards.rs          // guard canister functions
     - types.rs           // related types
- cargo.toml
- dfx.json
- build.sh         // canister buildscript
- can.did          // canister candid interface
```

First of all, we have to set up dependencies for the project, using the `cargo.toml` file:

```rust
// cargo.toml

[package]
name = "ic-cron-recurrent-payments-example"
version = "0.1.0"
edition = "2018"

[lib]
crate-type = ["cdylib"]
path = "src/actor.rs"

[dependencies]
ic-cdk = "0.3.3"
ic-cdk-macros = "0.3.3"
serde = "1.0"
ic-cron = "0.5.1"
```

Then we need to put this script inside the `build.sh` file that will build and optimize the wasm-module for us:

```bash
# build.sh

#!/usr/bin/env bash

cargo build --target wasm32-unknown-unknown --release --package ic-cron-recurrent-payments-example && \
 ic-cdk-optimizer ./target/wasm32-unknown-unknown/release/ic_cron_recurrent_payments_example.wasm -o ./target/wasm32-unknown-unknown/release/ic-cron-recurrent-payments-example-opt.wasm
```

And after that, we would fill the `dfx.json` file in order to describe the canister we are going to build:

```json
// dfx.json

{
  "canisters": {
    "ic-cron-recurrent-payments-example": {
      "build": "./build.sh",
      "candid": "./can.did",
      "wasm": "./target/wasm32-unknown-unknown/release/ic-cron-recurrent-payments-example-opt.wasm",
      "type": "custom"
    }
  },
  "defaults": {
    "build": {
      "packtool": ""
    }
  },
  "dfx": "0.9.0",
  "networks": {
    "local": {
      "bind": "127.0.0.1:8000",
      "type": "ephemeral"
    }
  },
  "version": 1
}
```

## Token internals

To better understand recurrent payments logic let’s make other parts of the token as simple as possible. Using that token canister users should be able to: 

- mint new tokens;
- transfer tokens to a different account;
- burn tokens;
- check the balance of any account;
- check the total supply of tokens;
- check the token’s info (name, ticker/symbol, decimals).

Also we would add these new functionalities: 

- recurrent token minting;
- recurrent token transferring;
- an ability to check current user’s recurrent tasks and to cancel them.

### Basic token functions

> It is important to say, that we’re going to define these basic functions (token state and some functions to modify this state) without using any of Internet Computer APIs. This would allow us to test this code using only the Rust’s default testing framework. 

> We will add Internet Computer API calls (e.g. `caller()` function) later in the file named `actor.rs`.

#### Token state

First of all, let’s describe a data type for all the data that would be stored in the canister:

```rust
// src/common/currency_token.rs

pub struct CurrencyToken {
    pub balances: HashMap<Principal, u64>,
    pub total_supply: u64,
    pub info: TokenInfo,
    pub controllers: Controllers,
    pub recurrent_mint_tasks: HashSet<TaskId>,
    pub recurrent_transfer_tasks: HashMap<Principal, HashSet<TaskId>>,
}

// src/common/types.rs

#[derive(Clone, CandidType, Deserialize)]
pub struct TokenInfo {
    pub name: String,
    pub symbol: String,
    pub decimals: u8,
}

pub type Controllers = Vec<Principal>;
pub type TaskId = u64;
```

For token balances storage for each account we will use the `balances` property. Notice that it is a hashmap, the key of which is a `Principal` of the user.

Inside the `total_supply` property we’ll store total amount of tokens in circulation (total minted tokens minus total burned tokens).

Inside the `info` property we will store some basic information about our token - `TokenInfo`, which is a name, a ticker/symbol and an amount of decimals.

Inside the `controllers` property we’ll store a list of `Principal`s of token administrators, which are users who can mint new tokens.

Also, in order to index ic-cron’s background tasks for efficient access, we would need a couple of additional properties: `recurrent_mint_tasks` and `recurrent_transfer_tasks`. We need these new indexes because ic-cron’s task scheduler stores all of its background tasks in a single collection that is optimized for quick scheduling. There are no other indexes there, so we would need to add these new ones.

#### Token minting

Let’s define a method to mint new tokens:

```rust
// src/common/currency_token.rs

impl CurrencyToken {

    ...

    pub fn mint(&mut self, to: Principal, qty: u64) -> Result<(), Error> {
        if qty == 0 {
            return Err(Error::ZeroQuantity);
        }

        let prev_balance = self.balance_of(&to);
        let new_balance = prev_balance + qty;

        self.total_supply += qty;
        self.balances.insert(to, new_balance);

        Ok(())
    }

    ...

}
```

This method takes as arguments an account identifier to mint tokens to and an amount of tokens that should be minted. Inside this method we just increment users current balance by the given amount and then update the total supply value.

The method returns `()`, if the operation was successful, or returns `Error`, if the value of `qty` argument equals to `0`. `Error` type is defined the following way:

```rust
// src/common/types.rs

#[derive(Debug)]
pub enum Error {
    InsufficientBalance,
    ZeroQuantity,
    AccessDenied,
    ForbiddenOperation,
} 
```

#### Token burning

The method to burn tokens works in exactly the opposite way, decrementing an amount of tokens from the account’s balance and updating the total supply counter:

```rust
// src/common/currency_token.rs

impl CurrencyToken {

    ...

    pub fn burn(&mut self, from: Principal, qty: u64) -> Result<(), Error> {
        if qty == 0 {
            return Err(Error::ZeroQuantity);
        }

        let prev_balance = self.balance_of(&from);

        if prev_balance < qty {
            return Err(Error::InsufficientBalance);
        }

        let new_balance = prev_balance - qty;

        if new_balance == 0 {
            self.balances.remove(&from);
        } else {
            self.balances.insert(from, new_balance);
        }

        self.total_supply -= qty;

        Ok(())
    }

    ...

}
```

This method takes an account identifier and an amount of tokens to burn as arguments. It returns `()`, if the operation went good, or an `Error`, if the value of `qty` argument is `0` or bigger than the account balance.

> Notice that we’re using a specific pattern to write code: 1) check for arguments validity; 2) return any possible error; 3) change the state of the token.

> It is always useful to follow this pattern in order to prevent future errors, including bugs which introduce a [re-entrancy attack vulnerability](https://solidity-by-example.org/hacks/re-entrancy/).

#### Token transferring

Now let’s define a function to transfer tokens between accounts. This function will reduce an amount of tokens from one account and add them to another:

```rust
// src/common/currency_token.rs

impl CurrencyToken {

    ...

    pub fn transfer(&mut self, from: Principal, to: Principal, qty: u64) -> Result<(), Error> {
        if qty == 0 {
            return Err(Error::ZeroQuantity);
        }

        let prev_from_balance = self.balance_of(&from);
        let prev_to_balance = self.balance_of(&to);

        if prev_from_balance < qty {
            return Err(Error::InsufficientBalance);
        }

        let new_from_balance = prev_from_balance - qty;
        let new_to_balance = prev_to_balance + qty;

        if new_from_balance == 0 {
            self.balances.remove(&from);
        } else {
            self.balances.insert(from, new_from_balance);
        }

        self.balances.insert(to, new_to_balance);

        Ok(())
    }

    ...

}
```

This method takes account identifiers and an amount of tokens to transfer as arguments. In case of success it returns `()`, in case of the amount of tokens to transfer is `0` or exceeds current sender’s balance, this method returns an `Error`.

#### Getting balance

The balance getting function is pretty simple so we won’t discuss it in details: 

```rust
// src/common/currency_token.rs

impl CurrencyToken {

    ...

    pub fn balance_of(&self, account_owner: &Principal) -> u64 {
        match self.balances.get(account_owner) {
            None => 0,
            Some(b) => *b,
        }
    }

    ...

} 
```

#### Recurrent tasks management

As was previously mentioned, we have to maintain our own background task indexes in order to access them efficiently. Later you’ll see why, but now it is okay to have troubles with understanding of this move. It would be easier to start with recurrent mint tasks first:

```rust
// src/common/currency_token.rs

impl CurrencyToken {

    ...	

    pub fn register_recurrent_mint_task(&mut self, task_id: TaskId) {
        self.recurrent_mint_tasks.insert(task_id);
    }

    pub fn unregister_recurrent_mint_task(&mut self, task_id: TaskId) -> bool {
        self.recurrent_mint_tasks.remove(&task_id)
    }

    pub fn get_recurrent_mint_tasks(&self) -> Vec<TaskId> {
        self.recurrent_mint_tasks.iter().cloned().collect()
    }

    ...

}
```

So, all we want is to maintain a list of all recurrent minting tasks by their ids and to be able to return that list any time we need it.

It is a little bit harder with recurrent transfer tasks, because we need to maintain such a list of tasks for each account separately. To achieve that, we’ll use a mapping where to each account we map its own list of recurrent transfer tasks:

```rust
// src/common/currency_token.rs

impl CurrencyToken {

    ...

    pub fn register_recurrent_transfer_task(&mut self, from: Principal, task_id: TaskId) {
        match self.recurrent_transfer_tasks.entry(from) {
            Entry::Occupied(mut entry) => {
                entry.get_mut().insert(task_id);
            }
            Entry::Vacant(entry) => {
                let mut s = HashSet::new();
                s.insert(task_id);

                entry.insert(s);
            }
        };
    }

    pub fn unregister_recurrent_transfer_task(&mut self, from: Principal, task_id: TaskId) -> bool {
        match self.recurrent_transfer_tasks.get_mut(&from) {
            Some(tasks) => tasks.remove(&task_id),
            None => false,
        }
    }

    pub fn get_recurrent_transfer_tasks(&self, from: Principal) -> Vec<TaskId> {
        self.recurrent_transfer_tasks
            .get(&from)
            .map(|t| t.iter().cloned().collect::<Vec<_>>())
            .unwrap_or_default()
    }

    ...

}
```

### Basic functionality testing

This is all we had to do to implement internal token functions. Let’s now use Rust’s standard testing framework to check if we did everything right.

First of all, for easy testing we would need a couple of utility functions: 

```rust
// src/common/currency_token.rs

#[cfg(test)]
mod tests {
 
    ...

    pub fn random_principal_test() -> Principal {
        Principal::from_slice(
            &SystemTime::now()
                .duration_since(UNIX_EPOCH)
                .unwrap()
                .as_nanos()
                .to_be_bytes(),
        )
    }

    fn create_currency_token() -> (CurrencyToken, Principal) {
        let controller = random_principal_test();
        let token = CurrencyToken {
            balances: HashMap::new(),
            total_supply: 0,
            info: TokenInfo {
                name: String::from("test"),
                symbol: String::from("TST"),
                decimals: 8,
            },
            controllers: vec![controller],
            recurrent_mint_tasks: HashSet::new(),
            recurrent_transfer_tasks: HashMap::new(),
        };

        (token, controller)
    }

    ...

}
```

`random_principal_test()` function creates a unique account `Principal` identifier using current system time as a seed. `create_currency_token()` function creates a default token object filled with test data.

```rust
// src/common/currency_token.rs

#[cfg(test)]
mod tests {
 
    ...
		
    #[test]
    fn minting_works_right() {
        let (mut token, controller) = create_currency_token();
        let user_1 = random_principal_test();

        token.mint(user_1, 100).ok().unwrap();

        assert_eq!(token.total_supply, 100);
        assert_eq!(token.balances.len(), 1);
        assert_eq!(token.balances.get(&user_1).unwrap().clone(), 100);

        token.mint(controller, 200).ok().unwrap();

        assert_eq!(token.total_supply, 300);
        assert_eq!(token.balances.len(), 2);
        assert_eq!(token.balances.get(&user_1).unwrap().clone(), 100);
        assert_eq!(token.balances.get(&controller).unwrap().clone(), 200);
    }

    #[test]
    fn burning_works_fine() {
        let (mut token, _) = create_currency_token();
        let user_1 = random_principal_test();

        token.mint(user_1, 100).ok().unwrap();

        token.burn(user_1, 90).ok().unwrap();

        assert_eq!(token.balances.len(), 1);
        assert_eq!(token.balances.get(&user_1).unwrap().clone(), 10);
        assert_eq!(token.total_supply, 10);

        token.burn(user_1, 20).err().unwrap();

        token.burn(user_1, 10).ok().unwrap();

        assert!(token.balances.is_empty());
        assert!(token.balances.get(&user_1).is_none());
        assert_eq!(token.total_supply, 0);

        token.burn(user_1, 20).err().unwrap();
    }

    #[test]
    fn transfer_works_fine() {
        let (mut token, controller) = create_currency_token();
        let user_1 = random_principal_test();
        let user_2 = random_principal_test();

        token.mint(user_1, 1000).ok().unwrap();

        token.transfer(user_1, user_2, 100).ok().unwrap();

        assert_eq!(token.balances.len(), 2);
        assert_eq!(token.balances.get(&user_1).unwrap().clone(), 900);
        assert_eq!(token.balances.get(&user_2).unwrap().clone(), 100);
        assert_eq!(token.total_supply, 1000);

        token.transfer(user_1, user_2, 1000).err().unwrap();

        token.transfer(controller, user_2, 100).err().unwrap();

        token.transfer(user_2, user_1, 100).ok().unwrap();

        assert_eq!(token.balances.len(), 1);
        assert_eq!(token.balances.get(&user_1).unwrap().clone(), 1000);
        assert!(token.balances.get(&user_2).is_none());
        assert_eq!(token.total_supply, 1000);

        token.transfer(user_2, user_1, 1).err().unwrap();

        token.transfer(user_2, user_1, 0).err().unwrap();
    } 

    ...

}
```

We won’t stop on these test cases for too long. In each of them there is a series of checks which make sure that the state stays in a way it should stay no matter what function do we call. But tests are very important, because they give us confidence in our code. Never skip tests.

## Token canister API

We’re almost at the finish line. All that’s left to do is: 

- to add a canister state initializing function;
- to add token management functions using internal functions we wrote earlier;
- to add recurrent mechanics into these token management functions.

#### State initialization

So, we need a function that will fill the canister state with some default values at the moment of canister creation:

```rust
// src/actor.rs

static mut STATE: Option<CurrencyToken> = None;

pub fn get_token() -> &'static mut CurrencyToken {
    unsafe { STATE.as_mut().unwrap() }
}

#[init]
fn init(controller: Principal, info: TokenInfo) {
    let token = CurrencyToken {
        balances: HashMap::new(),
        total_supply: 0,
        info,
        controllers: vec![controller],
        recurrent_mint_tasks: HashSet::new(),
        recurrent_transfer_tasks: HashMap::new(),
    };

    unsafe {
        STATE = Some(token);
    }
}
```

`init()` function takes as an argument a `Principal` of the controller - admin user that will be able to mint new tokens. And it takes as an argument an information about the token `TokenInfo`.  Also here we have an utility function named `get_token()`, that returns a safe reference to the token’s state.

### Token management

Let’s start with token minting:

```rust
// src/actor.rs

#[update(guard = "controller_guard")]
fn mint(to: Principal, qty: u64, scheduling_interval: Option<SchedulingInterval>) {
    match scheduling_interval {
        Some(interval) => {
            let task_id = cron_enqueue(
                CronTaskKind::RecurrentMint(RecurrentMintTask { to, qty }),
                interval,
            )
            .expect("Mint scheduling failed");

            get_token().register_recurrent_mint_task(task_id);
        }
        None => {
            get_token().mint(to, qty).expect("Minting failed");
        }
    }
}
```

> Notice, that the `update` macro, annotating the function, also contains a `guard` function named `controller_guard()`, that will automatically check if the caller is a controller of the token, and will proceed to `mint()` function only if it’s so. Otherwise the user will be responded with an error.

> This `guard` function is pretty simple and you can check it out in the Github repo in the `src/common/guards.rs` file, if you want to.

This function is able to perform recurrent mints as well as common ones. To make this happen we need to add an additional argument to this function to let a user pass some background task parameters to the function - `scheduling_interval`. `SchedulingInterval` type is a part of the ic-cron library and is defined the following way:

```rust
#[derive(Clone, Copy, CandidType, Deserialize)]
pub struct SchedulingInterval {
    pub delay_nano: u64,
    pub interval_nano: u64,
    pub iterations: Iterations,
}

#[derive(Clone, Copy, CandidType, Deserialize)]
pub enum Iterations {
    Infinite,
    Exact(u64),
}
```

This argument lets a user to define recurrent minting time settings and repetitions count.

If the value of the `scheduling_interval` argument is `None`, then the function will perform the common minting procedure calling `CurrencyToken::mint()` method of the state, otherwise it will schedule a new background task for a task scheduler using `cron_enqueue()` function, after which it will register this new background task in our recurrent minting tasks index.

> `cron_enqueue()` function, as well as any other functionality from the ic-cron library, is only available after you use the `implement_cron!()` macros. So make sure you put it somewhere in your `actor.rs` file.

Recurrent minting tasks which we pass into the `cron_enqueue()` function, are defined the following way:

```rust
// src/common/types.rs

#[derive(CandidType, Deserialize, Debug)]
pub struct RecurrentMintTask {
    pub to: Principal,
    pub qty: u64,
}
```

Token transfer function is defined in a similar fashion:

```rust
// src/actor.rs

#[update]
fn transfer(to: Principal, qty: u64, scheduling_interval: Option<SchedulingInterval>) {
    let from = caller();

    match scheduling_interval {
        Some(interval) => {
            let task_id = cron_enqueue(
                CronTaskKind::RecurrentTransfer(RecurrentTransferTask { from, to, qty }),
                interval,
            )
            .expect("Transfer scheduling failed");

            get_token().register_recurrent_transfer_task(from, task_id);
        }
        None => {
            get_token()
                .transfer(from, to, qty)
                .expect("Transfer failed");
        }
    }
}
```

If the `scheduling_interval` argument is `None`, then this function performs a common token transfer calling `CurrencyToken::transfer()` method of the state. But if this argument has any payload inside, than the function schedules a recurrent transfer task to the task scheduler and saves this task into the index.

Recurrent transfer tasks are defined this way: 

```rust
#[derive(CandidType, Deserialize, Debug)]
pub struct RecurrentTransferTask {
    pub from: Principal,
    pub to: Principal,
    pub qty: u64,
}
```

This data is everything our canister needs in order to call `CurrencyToken::transfer()` method later.

In order to differentiate between background task types we’ll use this `enum`:

```rust
#[derive(CandidType, Deserialize, Debug)]
pub enum CronTaskKind {
    RecurrentTransfer(RecurrentTransferTask),
    RecurrentMint(RecurrentMintTask),
}
```

Don’t worry if you still feel confused by now - we’re almost at the point where it all will make sense.

Token burning function is much more simpler, since there is no recurrent tasks in there:

```rust
// src/actor.rs

#[update]
fn burn(qty: u64) {
    get_token().burn(caller(), qty).expect("Burning failed");
}
```

> Here and in the previous function we pass the `caller()` as `from` argument, because these actions can only be performed by the account owner.

> Data getters are also pretty simple: 

```rust
// src/actor.rs

#[query]
fn get_balance_of(account_owner: Principal) -> u64 {
    get_token().balance_of(&account_owner)
}

#[query]
fn get_total_supply() -> u64 {
    get_token().total_supply
}

#[query]
fn get_info() -> TokenInfo {
    get_token().info.clone()
}
```

#### Recurrent tasks

Now to the most interesting part of this tutorial. Inside `mint()` and `transfer()` functions we scheduled some tasks for the task scheduler. We want users of the token to be able to list their active tasks and to be able to cancel them.

Recurrent mint tasks getter and cancel functions are defined this way:

```rust
// src/actor.rs

#[update]
pub fn cancel_recurrent_mint_task(task_id: TaskId) -> bool {
    cron_dequeue(task_id).expect("Task id not found");
    get_token().unregister_recurrent_mint_task(task_id)
}

#[query(guard = "controller_guard")]
pub fn get_recurrent_mint_tasks() -> Vec<RecurrentMintTaskExt> {
    get_token()
        .get_recurrent_mint_tasks()
        .into_iter()
        .map(|task_id| {
            let task = get_cron_state().get_task_by_id(&task_id).unwrap();
            let kind: CronTaskKind = task
                .get_payload()
                .expect("Unable to decode a recurrent mint task");

            match kind {
                CronTaskKind::RecurrentTransfer(_) => trap("Invalid task kind"),
                CronTaskKind::RecurrentMint(mint_task) => RecurrentMintTaskExt {
                    task_id: task.id,
                    to: mint_task.to,
                    qty: mint_task.qty,
                    scheduled_at: task.scheduled_at,
                    rescheduled_at: task.rescheduled_at,
                    scheduling_interval: task.scheduling_interval,
                },
            }
        })
        .collect()
}
```

Task canceling is simple: we just remove the task from the task scheduler using `cron_dequeue()` function and then remove this same task from the index. If everything went good the caller will see a `true` response.

It is a little bit harder with recurrent tasks listing though. The `CurrencyToken::get_recurrent_mint_tasks()` function only returns task identifiers instead of task data (which is much more useful for the end user). To change that we need to define a new task type that will contain all the task data we want to show to the user:

```rust
// src/common/types.rs

#[derive(CandidType, Deserialize)]
pub struct RecurrentMintTaskExt {
    pub task_id: TaskId,
    pub to: Principal,
    pub qty: u64,
    pub scheduled_at: u64,
    pub rescheduled_at: Option<u64>,
    pub scheduling_interval: SchedulingInterval,
} 
```

Besides a task identifier this type also contains this information:

- `to` and `qty` - target account and an amount of tokens to mint;
- `scheduled_at` - the task’s creation timestamp;
- `rescheduled_at` - last minting timestamp;
- `scheduling_interval` - background task time parameters provided by the user at the moment of the task creation.

So, we just map the list of `TaskId` into this new type using `Iterator::map()`.

The same exact things we need to do in order to make recurrent transfers to work:

```rust
// src/actor.rs

#[update]
pub fn cancel_my_recurrent_transfer_task(task_id: TaskId) -> bool {
    cron_dequeue(task_id).expect("Task id not found");
    get_token().unregister_recurrent_transfer_task(caller(), task_id)
}

#[query]
pub fn get_my_recurrent_transfer_tasks() -> Vec<RecurrentTransferTaskExt> {
    get_token()
        .get_recurrent_transfer_tasks(caller())
        .into_iter()
        .map(|task_id| {
            let task = get_cron_state().get_task_by_id(&task_id).unwrap();
            let kind: CronTaskKind = task
                .get_payload()
                .expect("Unable to decode a recurrent transfer task");

            match kind {
                CronTaskKind::RecurrentMint(_) => trap("Invalid task kind"),
                CronTaskKind::RecurrentTransfer(transfer_task) => RecurrentTransferTaskExt {
                    task_id: task.id,
                    from: transfer_task.from,
                    to: transfer_task.to,
                    qty: transfer_task.qty,
                    scheduled_at: task.scheduled_at,
                    rescheduled_at: task.rescheduled_at,
                    scheduling_interval: task.scheduling_interval,
                },
            }
        })
        .collect()
} 
```

For these background tasks we use the following type:

```rust
// src/common/types.rs

#[derive(CandidType, Deserialize)]
pub struct RecurrentTransferTaskExt {
    pub task_id: TaskId,
    pub from: Principal,
    pub to: Principal,
    pub qty: u64,
    pub scheduled_at: u64,
    pub rescheduled_at: Option<u64>,
    pub scheduling_interval: SchedulingInterval,
}
```

It differs from the `RecurrentMintTaskExt` only with one extra field - `from`, that defines the sender’s account.

Finally. This is the moment where everything should come together and start to make sense. 

Now we need to describe how exactly we’ll execute these recurrent tasks. This should be done inside the special system function annotated with the `heartbeat` function:

```rust
// src/actor.rs

#[heartbeat]
pub fn tick() {
    let token = get_token();

    for task in cron_ready_tasks() {
        let kind: CronTaskKind = task.get_payload().expect("Unable to decode task payload");

        match kind {
            CronTaskKind::RecurrentMint(mint_task) => {
                token
                    .mint(mint_task.to, mint_task.qty)
                    .expect("Unable to perform scheduled mint");

                if let Iterations::Exact(n) = task.scheduling_interval.iterations {
                    if n == 1 {
                        token.unregister_recurrent_mint_task(task.id);
                    }
                };
            }
            CronTaskKind::RecurrentTransfer(transfer_task) => {
                token
                    .transfer(transfer_task.from, transfer_task.to, transfer_task.qty)
                    .expect("Unable to perform scheduled transfer");

                if let Iterations::Exact(n) = task.scheduling_interval.iterations {
                    if n == 1 {
                        token.unregister_recurrent_transfer_task(transfer_task.from, task.id);
                    }
                };
            }
        }
    }
}
```

In this function we iterate through the list of all tasks ready to be executed right now (supplied by the `cron_ready_tasks()` function). For each of these tasks we figure out its kind denoted by `CronTaskKind` enum, and then depending on that task kind we execute one of two following methods: `CurrencyToken::mint()` or `CurrencyToken::transfer()`.

> Notice, that inside this function we also need to check how many executions is left for the task. If there is only one execution left, we have to manually remove it from our recurrent task indexes.

> Unfortunately, ic-cron library doesn’t support callback execution on certain events like the end of a task execution. This is why we have to manually watch for this event to happen and react to it in an imperative manner.

## Candid interface description

This is it! We finished the coding part. All we left to do is to describe the interface of our canister inside the `.did` file, in order to be able to communicate with it from the console:

```rust
// can.did

type TaskId = nat64;

type Iterations = variant {
    Infinite;
    Exact : nat64;
};

type SchedulingInterval = record {
    delay_nano : nat64;
    interval_nano : nat64;
    iterations : Iterations;
};

type RecurrentTransferTaskExt = record {
    task_id : TaskId;
    from : principal;
    to : principal;
    qty : nat64;
    scheduled_at : nat64;
    rescheduled_at : opt nat64;
    scheduling_interval : SchedulingInterval;
};

type RecurrentMintTaskExt = record {
    task_id : TaskId;
    to : principal;
    qty : nat64;
    scheduled_at : nat64;
    rescheduled_at : opt nat64;
    scheduling_interval : SchedulingInterval;
};

type TokenInfo = record {
    name : text;
    symbol : text;
    decimals : nat8;
};

service : (principal, TokenInfo) -> {
    "mint" : (principal, nat64, opt SchedulingInterval) -> ();
    "transfer" : (principal, nat64, opt SchedulingInterval) -> ();
    "burn" : (nat64) -> ();
    "get_balance_of" : (principal) -> (nat64) query;
    "get_total_supply" : () -> (nat64) query;
    "get_info" : () -> (TokenInfo) query;

    "cancel_recurrent_mint_task" : (TaskId) -> (bool);
    "get_recurrent_mint_tasks" : () -> (vec RecurrentMintTaskExt) query;

    "cancel_my_recurrent_transfer_task" : (TaskId) -> (bool);
    "get_my_recurrent_transfer_tasks" : () -> (vec RecurrentTransferTaskExt) query;
}
```

## Interacting with the token

Now let’s finally use the console to check whether it works or not. Let’s start a development environment in order to deploy our canister to it:

```rust
$ dfx start --clean
```

Then in a new console window:

```rust
$ dfx deploy --argument '(principal "<your-principal>", record { name = "Test token"; symbol = "TST"; decimals = 2 : nat8 })'
...
Deployed canisters
```

> Instead of `<your-principal>` one needs to paste their own `Principal` which can be obtained with `dfx identity get-principal` command.

Okay, let’s mint us some tokens:

```rust
$ dfx canister call ic-cron-recurrent-payments-example mint '(principal "<your-principal>", 100000 : nat64, null )'
()
```

Checking the balance:

```rust
$ dfx canister call ic-cron-recurrent-payments-example get_balance_of '(principal "<your-principal>")'
(100_000 : nat64)
```

Nice, common token minting procedure works as expected. Let’s try the recurrent one then:

```rust
$ dfx canister call ic-cron-recurrent-payments-example mint '(principal "<your-principal>", 10_00 : nat64, opt record { delay_nano = 0 : nat64; interval_nano = 10_000_000_000 : nat64; iterations = variant { Exact = 5 : nat64 } } )'
()
```

Balance check:

```rust
$ dfx canister call ic-cron-recurrent-payments-example get_balance_of '(principal "<your-principal>")'
(102_000 : nat64)
```

Another balance check after a minute or so:

```rust
$ dfx canister call ic-cron-recurrent-payments-example get_balance_of '(principal "<your-principal>")'
(105_000 : nat64)
```

The balance won’t grow anymore, because we set the iterations count to `5`, which means that we received `5000` tokens total in 5 portions of 1000 tokens each in intervals of ~10 seconds. It took us only ~40 seconds, because the first portion was received immediately after we created the task (`delay_nano` was `0`).

So, it looks like recurrent token minting works fine. Let’s check recurrent token transferring now:

```rust
$ dfx canister call ic-cron-recurrent-payments-example transfer '(principal "aaaaa-aa", 1_00 : nat64, opt record { delay_nano = 0 : nat64; interval_nano = 10_000_000_000 : nat64; iterations = vari
ant { Infinite } } )'
()
```

> Warning! Notice that we transfer tokens to the `aaaaa-aa` account. This is the `Principal` of so called management canister. Don’t ever transfer real money to that account - you’ll never get them back.
> 

Let’s check our balance:

```rust
$ dfx canister call ic-cron-recurrent-payments-example get_balance_of '(principal "<your-principal>")'
(104_900 : nat64)
```

Checking again after some time:

```rust
$ dfx canister call ic-cron-recurrent-payments-example get_balance_of '(principal "<your-principal>")'
(104_600 : nat64)
```

Looks like it worked. We’re sure being charged by `100` tokens each 10 seconds, as we wanted. Let’s see our recurrent transfer tasks list:

```rust
$ dfx canister call ic-cron-recurrent-payments-example get_my_recurrent_transfer_tasks '()'
(
  vec {
    record {
      to = principal "aaaaa-aa";
      qty = 100 : nat64;
      task_id = 1 : nat64;
      from = principal "<your-principal>";
      scheduled_at = 1_645_660_528_445_056_278 : nat64;
      rescheduled_at = opt (1_645_660_528_445_056_278 : nat64);
      scheduling_interval = record {
        interval_nano = 10_000_000_000 : nat64;
        iterations = variant { Infinite };
        delay_nano = 0 : nat64;
      };
    };
  },
)
```

As we can see from the response currently we only have a single recurrent transfer task doing its job. Let’s cancel it: 

```rust
$ dfx canister call ic-cron-recurrent-payments-example cancel_my_recurrent_transfer_task '(1 : nat64)'
(true)
```

Checking recurrent transfer tasks list again:

```rust
$ dfx canister call ic-cron-recurrent-payments-example get_my_recurrent_transfer_tasks '()'
(vec {})
```

Good, the task was removed from the list. Let’s check we’re no longer charged: 

```rust
$ dfx canister call ic-cron-recurrent-payments-example get_balance_of '(principal "<your-principal>")'
(104_100 : nat64)
```

And after some time it stills the same:

```rust
$ dfx canister call ic-cron-recurrent-payments-example get_balance_of '(principal "<your-principal>")'
(104_100 : nat64)
```

Woohoo! It works!

## Afterword

As you could see, it is possible to create some really cool things with the Internet Computer. Some things that don’t exist anywhere else yet, like we’re some kind of artists or so. Recurrent payments were not possible before the IC and ic-cron. There is no other web3 platform with such a feature so easy to use and to build on top of. 

The Internet Computer is the new Internet. And this tutorial is just another proof of that statement.  

My other tutorials on ic-cron:

- [https://hackernoon.com/how-to-execute-background-tasks-on-particular-weekdays-with-ic-cron-and-chrono](https://hackernoon.com/how-to-execute-background-tasks-on-particular-weekdays-with-ic-cron-and-chrono)
- [https://hackernoon.com/tutorial-extending-sonic-with-limit-orders-using-ic-cron-library](https://hackernoon.com/tutorial-extending-sonic-with-limit-orders-using-ic-cron-library)

Complete source code of this tutorial is here:

[https://github.com/seniorjoinu/ic-cron-recurrent-payments-example](https://github.com/seniorjoinu/ic-cron-recurrent-payments-example)

**Thanks for reading!**