⏲️Vesting

In this page, we will go over, explain and review the INK! smart contract that associated with the vesting program and all of its functions.

The only users who are eligible to view the vesting section are seed event participants only, they will be able to claim X amount of PANX each day (24 hours).

Github repository: https://github.com/RottenKiwi/Panorama-Swap-INK-SC/tree/main/vesting_contract

Example of how seed participants would see the vesting section:

Vesting contract source code:

imports:

We are importing openbrush::{contracts::{traits::psp22::PSP22Ref,} in order to make cross contract calls to PSP22 object, we use this import to reference PSP22 token and last we import ink::storage::Mapping; in order to initialize the contract's hashmaps.

use openbrush::{
        contracts::{

            traits::psp22::PSP22Ref,
        },
    };
use ink::storage::Mapping; 

Contract's struct:

"manager": we are declaring AccoutId object (wallet address) named "manager". We will initialize "manager" with the deployer (the account that deployes the smart contract to the network) and only the "manager" can add accounts into the vesting program.

"panx_psp22": the smart contract address of PANX PSP22 token.

"started_date_in_timestamp": the date of the vesting contract issuance as timestamp (seconds).

"panx_reserve": the amount of PANX that the vesting contract is holding in order to distribute to the participants.

"balances": hash-map used to store the total vesting amount of each participant.

"collected_tge": he status of tge (token generation event) claim of the caller. If the caller status is 0 it means he didnt collect the one time 10% claim. Status 1 means the caller already collected.

"panx_to_give_in_a_day": the amount of PANX to give to caller each 24 hour.

"last_redeemed": the last date that the caller claimed the daily PANX vesting rewards in timestamp (seconds)

pub struct VestingContract {
        
        //Deployer address 
        manager: AccountId,
        //PANX psp22 contract address
        panx_psp22: AccountId,
        //Vesting contract deploy date in tsp 
        started_date_in_timestamp:Balance,
        //Locked PANX amount of users
        balances: Mapping<AccountId, Balance>,
        // 0 didnt collect, 1 did collect.
        collected_tge: Mapping<AccountId, Balance>,
        //Panx reward for a day, for each account
        panx_to_give_in_a_day:Mapping<AccountId, Balance>,
        //Last date of caller redeem
        last_redeemed:Mapping<AccountId, Balance>,


}

Functions:

new (constructor)

Constructor function, used to initialize vesting smart contract. We pass "panx_contract" token address to the constructor function, which initializing contract.panx_psp22, the PANX token address that we pass is used in "PSP22Ref::transfer" function, the contract.started_date_in_timestamp (current date in timestamp[sec]) is used to store the smart contract deployment date and contract.manager is used to store the deployer address.

#[ink(constructor)]
pub fn new(panx_contract:AccountId) -> Self {
            

                let panx_psp22 = panx_contract;  
                let started_date_in_timestamp:Balance = Self::env().block_timestamp();
                let manager = Self::env().caller();
                let balances = Mapping::default();
                let collected_tge = Mapping::default();
                let panx_to_give_in_a_day = Mapping::default();
                let last_redeemed =  Mapping::default();

            
            

            Self{

                manager,
                panx_psp22,
                started_date_in_timestamp,
                balances,
                collected_tge,
                panx_to_give_in_a_day,
                last_redeemed

            }
            
}

add_to_vesting

Function to add accounts to the vesting program. Only seed event participants will be able to enjoy the vesting program and the manager address is the only address that can add account to the vesting program.

We pass "account", (accountId object, wallet address), to "add_to_vesting" in order to add the given "account" to the vesting program and we pass "panx_to_give_overall", (Balance object, u128), in order to set the total vesting amount of the given "account".

Only the manager can add account to the vesting program, in order to enforce this rule, we added a validation:

if self.env().caller() != self.manager {
           panic!(
                "The caller is not the manager, cannot add account to vesting program."
           )}

to check if the caller address is the same as the manager address, if FALSE, the function will exit and return, if TRUE, we can continue.

In order to determine the amount of PANX to give to account daily (24 hours) we:

we store the result in "panx_to_give_in_a_day" hash-map as value with the "account", as the key.

#[ink(message)]
pub fn add_to_vesting(&mut self,account:AccountId,panx_to_give_overall:Balance)  {

           //Making sure caller is the manager (Only manager can add to the vesting program)
           if self.env().caller() != self.manager {
           panic!(
                "The caller is not the manager, cannot add account to vesting program."
           )
           }

           let account_balance = self.balances.get(&account).unwrap_or(0);

           let new_vesting_panx_amount:Balance;

           //calculating the new vesting amount of the caller.
           match account_balance.checked_add(panx_to_give_overall) {
            Some(result) => {
                new_vesting_panx_amount = result;
            }
            None => {
                panic!("overflow!");
            }
            };


           self.balances.insert(account, &(new_vesting_panx_amount));

           let panx_amount_to_give_each_day:Balance;

           //calculating how much PANX tokens the caller needs to get each day.
           match new_vesting_panx_amount.checked_div(365) {
            Some(result) => {
                panx_amount_to_give_each_day = result;
            }
            None => {
                panic!("overflow!");
            }
            };

           self.panx_to_give_in_a_day.insert(account,&panx_amount_to_give_each_day);
           //Allow account to collect TGE
           self.collected_tge.insert(account,&0);
           //Insert the current date as last redeem date for account.
           self.last_redeemed.insert(account, &self.get_current_timestamp());
              
}

collect_tge_tokens

Function which lets the caller to claim 10% of his total vesting amount (one time collection).

Seed event participants are automatically participating in the vesting program with their total PANX bought at the seed event.

We are first making sure that "account" (caller account address) didnt collect 10% of his tokens before (0 = didnt collect yet, 1 = did collect) and his overall vesting balance is more then 0 PANX.

if self.collected_tge.get(&account).unwrap_or(0) != 0 {
            panic!(
                 "The caller is not manager, cannot add to account to vesting program."
            )}
if account_balance <= 0 {
            panic!(
                 "Caller has balance of 0 locked tokens."
            )}

To calculate how much PANX the user need to receive we do (10% from total vesting amount):

​After we transfer to the user his 10%, we deduct the total vesting amount of the user with the "amount_to_give" in order to keep the total vesting amount updated.

Lastly we change the user's "collect_tge" status to 1 (collected).

#[ink(message)]
pub fn collect_tge_tokens(&mut self)  {

           let caller = self.env().caller();

           let caller_current_balance:Balance = self.balances.get(&caller).unwrap_or(0);

           //making sure caller didnt redeem tge yet
           if self.collected_tge.get(&caller).unwrap_or(0) != 0 {
            panic!(
                 "The caller already redeemed his TGE alloction, cannot redeem again."
            )
            }
           //making sure caller has more then 0 tokens
           if caller_current_balance <= 0 {
            panic!(
                 "Caller has balance of 0 locked tokens."
            )
            }

           let caller_locked_panx_after_tge:Balance;

           //calculating how callers balance after reducing tge amount (10%)
           match (caller_current_balance * 900).checked_div(1000) {
            Some(result) => {
                caller_locked_panx_after_tge = result;
            }
            None => {
                panic!("overflow!");
            }
            };

           let amount_of_panx_to_give:Balance;

           //calculating the amount of PANX to give to the caller
           match caller_current_balance.checked_sub(caller_locked_panx_after_tge) {
            Some(result) => {
                amount_of_panx_to_give = result;
            }
            None => {
                panic!("overflow!");
            }
            };

           //transfers the TGE tokens to caller
           PSP22Ref::transfer(&self.panx_psp22, self.env().caller(), amount_of_panx_to_give, ink::prelude::vec![]).unwrap_or_else(|error| {
            panic!(
                "Failed to transfer PSP22 tokens to caller : {:?}",
                error
            )
            });
           //deducts from overall vesting amount to give
           self.balances.insert(caller, &(caller_current_balance - amount_of_panx_to_give));

           //make sure to change his collected tge status to 1 to prevent the user to call it again
           self.collected_tge.insert(caller,&1);



}

get_redeemable_amount

Function which returns the redeemable amount of PANX tokens the caller can claim.

In order to determine how many days (24 hours) passed since the last claim date, we take the the current time as a timestamp "current_tsp" and we divide it by the last date of claim "last_redeemed" (also timestamp).

​We are making sure that more than 1 day passed since the last claim date and we are validating if the user has enough total vesting amount.

#[ink(message)]
pub fn get_redeemable_amount(&mut self) -> Balance {

            
            let caller = self.env().caller();

            let current_date_in_tsp = self.get_current_timestamp();

            let caller_total_vesting_amount:Balance = self.get_account_total_vesting_amount(caller);

            let date_of_last_redeem_in_tsp:Balance = self.last_redeemed.get(caller).unwrap_or(0);

            let panx_to_give_each_day:Balance = self.panx_to_give_in_a_day.get(caller).unwrap_or(0);

            let days_difference:Balance;

            //calculating the days difference between the last redeem date and current date
            match (current_date_in_tsp - date_of_last_redeem_in_tsp).checked_div(86400) {
                Some(result) => {
                    days_difference = result;
                }
                None => {
                    panic!("overflow!");
                }
            };

            //making sure that 24 hours has passed since last redeem
            if days_difference <= 0 {
                panic!(
                     "0 Days passed since the last redeem, kindly wait 24 hours after redeem."
                )
                }
            //making sure that caller has more then 0 PANX to redeem
            if caller_total_vesting_amount <= 0 {
                panic!(
                     "Caller has balance of 0 locked tokens. "
                )
                }


            let mut redeemable_amount:Balance;

            //calculating the amount of PANX the caller needs to get.
            match panx_to_give_each_day.checked_mul(days_difference) {
                Some(result) => {
                    redeemable_amount = result;
                }
                None => {
                    panic!("overflow!");
                }
            };


            //if caller has less tokens from the daily amount, give him the rest of tokens
            if redeemable_amount > caller_total_vesting_amount{

                redeemable_amount = caller_total_vesting_amount
            }

            redeemable_amount

            

}

redeem_redeemable_amount

Function to claim daily PANX rewards from vesting program.

"self.get_redeemable_amount" function returns us the amount of redeemable PANX tokens the caller can withdraw and claim. This function also validates if the user has enough total vesting amount in order to give him tokens and making sure the more than 24 hours has passed since the last claim.

After the user claim his PANX, we updated the user "last_reedem" date and his overall vesting amount (deduct the current claim)

#[ink(message)]
pub fn redeem_redeemable_amount(&mut self) {

            
            let caller = self.env().caller();

            let current_date_in_tsp = self.get_current_timestamp();

            let caller_total_vesting_amount = self.get_account_total_vesting_amount(caller);

            let mut redeemable_amount = self.get_redeemable_amount();

            //make sure to set new date of reedem for the caller.
            self.last_redeemed.insert(caller,&current_date_in_tsp);

            let caller_new_vesting_amount:Balance;

            //calculating the callers new total vesting amount
            match caller_total_vesting_amount.checked_sub(redeemable_amount) {
                Some(result) => {
                    caller_new_vesting_amount = result;
                }
                None => {
                    panic!("overflow!");
                }
            };

            //make sure to deducte from overall amount
            self.balances.insert(caller, &(caller_new_vesting_amount));

            //cross contract call to PANX contract to transfer PANX to caller
            PSP22Ref::transfer(&self.panx_psp22, caller, redeemable_amount, ink::prelude::vec![]).unwrap_or_else(|error| {
                panic!(
                    "Failed to transfer PSP22 tokens to caller : {:?}",
                    error
                )
            });

}

get_account_total_vesting_amount

Function to get account's total remaining vesting amount.

#[ink(message)]
pub fn get_account_total_vesting_amount(&mut self,account:AccountId)-> Balance  {
        
           let account_balance:Balance = self.balances.get(&account).unwrap_or(0);
           account_balance

 }

get_account_last_redeem

Function to get account's last claim date in timestamp (seconds).

#[ink(message)]
pub fn get_account_last_redeem(&mut self,account:AccountId)->Balance  {
        
           let time_stamp = self.last_redeemed.get(&account).unwrap_or(0);
           time_stamp

}

get_amount_to_give_each_day_to_account

Function to get account's daily PANX reward amount.

#[ink(message)]
pub fn get_amount_to_give_each_day_to_account(&mut self,account:AccountId)->Balance  {
        
           let account_balance = self.panx_to_give_in_a_day.get(&account).unwrap_or(0);
           account_balance

}

get_vesting_contract_panx_reserve

Function that returns the amount of PANX that the vesting contract is holding.

#[ink(message)]
pub fn get_vesting_contract_panx_reserve(&self)-> Balance  {
        
            let vesting_panx_reserve = PSP22Ref::balance_of(&self.panx_psp22, Self::env().account_id());
            vesting_panx_reserve


}

user_tge_collection_status

Function to get account's TGE status (if the account used the single time 10% claim)

#[ink(message)]
pub fn user_tge_collection_status(&mut self,account:AccountId)->Balance  {

            let tge_status = self.collected_tge.get(account).unwrap_or(0);
            tge_status


}

get_started_date

Function to get the vesting contract deployment date in timestamp (seconds)

#[ink(message)]
pub fn get_started_date(&self) -> u64 {
            let timestamp = self.started_date_in_timestamp;
            timestamp
        }

get_current_timestamp

Function to get current date in timestamp (seconds).

#[ink(message)]
pub fn get_current_timestamp(&self) -> u64 {
            let bts = self.env().block_timestamp() / 1000;
            bts
        }

get_days_passed_since_issue

Function to get total days since vesting contract deployment.

#[ink(message)]
pub fn get_days_passed_since_issue(&self) -> Balance {
            let current_tsp = self.env().block_timestamp() / 1000;

            let days_diff :Balance;

            match  (current_tsp - self.started_date_in_timestamp).checked_div(86400) {
                Some(result) => {
                    days_diff = result;
                }
                None => {
                    panic!("overflow!");
                }
            };

            days_diff
}

Last updated