Transfers, EventsFungible Token Contract
4/11 tutorials
36%

Implementing Token Transfers and Events

Let's extend the fungible token contract by adding the ability to transfer tokens between users and by introducing events to notify off-chain subscribers about changes in the application state.

Code explanation

The Token contract has been extended to include a transfer method for sending tokens from one user to another. It now integrates events to notify off-chain subscribers about these transfers.

The Events enum defines the events that the Token service can emit. Each variant represents a specific event and its associated data. In this example, there are two events: Transferred and Minted. They have fields for the sender, receiver, and the token amount transferred or minted, respectively.

The #[service(events = Events)] attribute configures the Token service to emit events of type Events. This autogenerates a notify_on method, which emits events from within the service methods.

The transfer method handles the token transfer between users. It performs a balance check to ensure the sender has enough funds for the transfer. If the sender has a low balance, it triggers an error message showing "insufficient balance”. If the sender has enough balance, the method deducts the specified amount from the sender's balance, adds it to the recipient's balance, and emits a Transferred event using the notify_on method.

The mint method adds tokens to a user's balance and emits a Minted event.

#![no_std]
use sails_rs::{collections::HashMap, prelude::*};

pub struct State {
    name: String,
    balances: HashMap<ActorId, U256>,
}

static mut STATE: Option<State> = None;

impl State {
    pub fn get() -> &'static Self {
        unsafe { STATE.as_ref().expect("State is not initialized") }
    }

    pub fn get_mut() -> &'static mut Self {
        unsafe { STATE.as_mut().expect("State is not initialized") }
    }
}

#[derive(Default)]
pub struct Token;

#[derive(Encode, TypeInfo)]
enum Events {
    Transferred {
        from: ActorId,
        to: ActorId,
        value: U256,
    },
    Minted { to: ActorId, value: U256 },
}

#[service(events = Events)]
impl Token {
    pub fn init(name: String) {
        unsafe {
            STATE = Some(State {
                name,
                balances: HashMap::new(),
            });
        }
    }

    pub fn mint(&mut self, to: ActorId, value: U256) {
        let state = State::get_mut();
        let balance = state.balances.entry(to).or_insert(U256::zero());
        *balance += value;
        let _ = self.notify_on(Events::Minted { to, value });
    }

    pub fn transfer(&mut self, from: ActorId, to: ActorId, value: U256) {
        let state = State::get_mut();
        let from_balance = state.balances.entry(from).or_insert(U256::zero());

        if *from_balance < value {
            panic!("Insufficient balance");
        }

        *from_balance -= value;
        let to_balance = state.balances.entry(to).or_insert(U256::zero());
        *to_balance += value;

        let _ = self.notify_on(Events::Transferred { from, to, value });
    }

    pub fn name(&self) -> &'static str {
        let state = State::get();
        &state.name
    }

    pub fn balance_of(&self, account: ActorId) -> U256 {
        let state = State::get();
        *state.balances.get(&account).unwrap_or(&U256::zero())
    }
}

pub struct MyProgram;

#[program]
impl MyProgram {
    pub fn new(name: String) -> Self {
        Token::init(name);
        Self
    }

    pub fn token(&self) -> Token {
        Token::default()
    }
}