Skip to content

Token Withdraw Timelock

Soroban Token Withdraw Timelock
#![no_std]
 
use soroban_sdk::{contract, contractimpl, contracttype, token, Address, Env, Vec, log};
 
#[derive(Clone)]
#[contracttype]
pub enum DataKey {
    Init,
    Balance,
}
 
#[derive(Clone)]
#[contracttype]
pub enum TimeBoundKind {
    Before,
    After,
}
 
#[derive(Clone)]
#[contracttype]
pub struct TimeBound {
    pub kind: TimeBoundKind,
    pub timestamp: u64,
}
 
#[derive(Clone)]
#[contracttype]
pub struct ClaimableBalance {
    pub token: Address,
    pub amount: i128,
    pub claimants: Vec<Address>,
    pub time_bound: TimeBound,
}
 
#[contract]
pub struct ClaimableBalanceContract;
 
// `timelock`: Check that provided timestamp is before or after
// the provided timestamp.
fn check_time_bound(env: &Env, time_bound: &TimeBound) -> bool {
    let ledger_timestamp = env.ledger().timestamp();
 
    match time_bound.kind {
        TimeBoundKind::Before => ledger_timestamp <= time_bound.timestamp,
        TimeBoundKind::After => ledger_timestamp >= time_bound.timestamp,
    }
}
 
#[contractimpl]
impl ClaimableBalanceContract {
    pub fn deposit(
        env: Env,
        from: Address,
        token: Address,
        amount: i128,
        claimants: Vec<Address>,
        time_bound: TimeBound,
    ) {
        /// @dev should remove logs before deploying smart contracts
        log!(&env, "There are {} number of claimants", claimants.len());
 
        if claimants.len() > 10 {
            panic!("Too many claimants");
        }
 
        if is_initialized(&env) {
            panic!("Contract has already been initialized");
        }
 
        // The 'from' address authorizes the deposit call
        from.require_auth();
 
        // Transfer to the specified contract address
        token::Client::new(&env, &token).transfer(&from, &env.current_contract_address(), &amount);
 
        // Storage transaction info for claimant
        env.storage().instance().set(
            &DataKey::Balance,
            &ClaimableBalance {
                token,
                amount,
                claimants,
                time_bound,
            }
        );
 
        // Mark contract as initialized to prevent double-usage.
        env.storage().instance().set(&DataKey::Balance, &());
    }
 
    pub fn claimant(env: Env, claimant: Address) {
        // Verify claimant identity for transaction authorization
        claimant.require_auth();
 
        // Retrieve claimable balance
        let claimable_balance: ClaimableBalance =
            env.storage().instance().get(&DataKey::Balance).unwrap();
 
        /// @dev should remove logs before deploying smart contracts
        log!(&env, "Claimable balance: {}", claimable_balance);
 
        if !check_time_bound(&env, &claimable_balance.time_bound) {
            panic!("Time requirement not fulfilled");
        }
 
        let claimants = &claimable_balance.claimants;
 
        if !claimants.contains(&claimant) {
            panic!("Claimant supplied not allowed to claim this balance");
        }
 
        // Transfer authorized sum to validated claimant
        token::Client::new(&env, &claimable_balance.token).transfer(
            &env.current_contract_address(),
            &claimant,
            &claimable_balance.amount,
        );
 
        // Remove claimed balance to prevent further claims
        env.storage().instance().remove(&DataKey::Balance);
    }
}
 
fn is_initialized(env: &Env) -> bool {
    env.storage().instance().has(&DataKey::Init)
}

Explanation

#![no_std] This attribute prevents linking to the standard library, making the code lighter and more efficient for Soroban contracts. It's big so we save on size.

use soroban_sdk::{contract, contractimpl, Env, log} Imports stuffs from the Soroban SDK. Env is basic Soroban type, we need it because we can't use the Rust standard library.

#[derive(Clone)] This allows copying the DataKey enum easily. #[contracttype] Exposes this enum to other contracts.

DataKey This enum defines storage keys for the contract (Init and Balance).

TimeBoundKind This enum represents the type of time restriction (Before or After).

TimeBound This struct stores the time restriction type and timestamp.

ClaimableBalance This struct holds information about the deposited funds, including token address, amount, list of claimants, and the time restriction.

check_time_bound This compares the current ledger timestamp with the provided time bound (before or after).

deposit This function in the contract allows users to deposit funds into a specified token contract for a set of claimants with a time restriction. It stores the deposit information and marks the contract as initialized to prevent double usage.

claimant This function allows authorized claimants to claim their share of the deposited funds after the specified time restriction has been met. It verifies the claimant's identity and transfers the claimable amount to them, removing the claimed balance from storage to prevent further claims.

is_initialized This helper function checks if the Init key exists in the contract storage, indicating if the contract has already been initialized.

Run in Playground

Loading playground...