Deploy Liquidity Provision Contract that integrates with Solana Swap

Liquidity Provision Contract integrates with X-Talk Swap Contract to provide liquidity for Solana swaps. Liquidity provision contracts are one of the examples of the use case of Solana-EVM Swaps.

Step 1: Pre-Requisites

Ensure Pre-Requisites for Solana and L1X are installed.

Step 2: Set up Solana Project

Open a new terminal to create Solana project

Step 1: Initialize a New Project

  1. Create a New Directory

mkdir solana-stake-project
cd solana-stake-project
  1. Initialize a New Anchor Project

anchor init stake

Step 2: Write your Solana smart contract

In this, below listed smart contracts are to be created at /programs/stake/src/

  1. lib.rs

  2. account_structs.rs

  3. error.rs

  4. events.rs

  5. instructions.rs

  6. state.rs

  7. utils.rs

Paste your contract code at /programs/stake/src/lib.rs

Update declare_id! with YOUR_SOLANA_PROGRAM_ID

// lib.rs

use anchor_lang::prelude::*;

pub mod account_structs;
pub mod error;
pub mod events;
pub mod instructions;
pub mod state;
pub mod utils;

use crate::account_structs::*;
use crate::state::*;

// Update declare_id! with YOUR_SOLANA_PROGRAM_ID
declare_id!("9dC1GNxYTMNjh1ftfjCifk8oUntLFJvtz6yzSpXHQg17");

#[program]
pub mod stake {
    use super::*;

    pub fn initialize(
        ctx: Context<Initialize>,
        native_symbol: String,
        native_asset_symbol: String,
        native_asset_decimal: u8,
        treasury_contract: Pubkey,
        treasury_distribution_percent: u16,
        deposit_fee_percent: u8,
        withdrawal_penalty_in_days: u64,
        withdrawal_penalty_in_percentage: u16,
    ) -> Result<()> {
        instructions::initialize(
            ctx,
            native_symbol,
            native_asset_symbol,
            native_asset_decimal,
            treasury_contract,
            treasury_distribution_percent,
            deposit_fee_percent,
            withdrawal_penalty_in_days,
            withdrawal_penalty_in_percentage,
        )
    }

    pub fn withdraw_stake_by_id_for_erc20(
        ctx: Context<WithdrawStakeByIdForErc20>,
        internal_id: String,
    ) -> Result<()> {
        instructions::withdraw_stake_by_id_for_erc20(ctx, internal_id)
    }

    pub fn withdraw_stake_by_id_for_native(
        ctx: Context<WithdrawStakeByIdForNative>,
        internal_id: String,
    ) -> Result<()> {
        instructions::withdraw_stake_by_id_for_native(ctx, internal_id)
    }

    pub fn deposit_for_native(
        ctx: Context<DepositForNative>,
        internal_id: String,
        duration_in_days: u64,
        reward_address: String,
        amount: u64,
    ) -> Result<()> {
        instructions::deposit_for_native(ctx, internal_id, duration_in_days, reward_address, amount)
    }

    pub fn deposit_for_erc20(
        ctx: Context<DepositForERC20>,
        internal_id: String,
        duration_in_days: u64,
        reward_address: String,
        asset_symbol: String,
        asset_address: Pubkey,
        amount: u64,
        decimals: u8,
    ) -> Result<()> {
        instructions::deposit_for_erc20(
            ctx,
            internal_id,
            duration_in_days,
            reward_address,
            asset_symbol,
            asset_address,
            amount,
            decimals,
        )
    }

    pub fn transfer_to_treasury_native(
        ctx: Context<TransferToTreasuryNative>,
        amount: u64,
    ) -> Result<()> {
        instructions::transfer_to_treasury_native(ctx, amount)
    }

    pub fn transfer_to_treasury_erc20(
        ctx: Context<TransferToTreasuryErc20>,
        amount: u64,
    ) -> Result<()> {
        instructions::transfer_to_treasury_erc20(ctx, amount)
    }

    pub fn check_withdraw_stake_by_id(ctx: Context<CheckWithdrawStakeById>) -> Result<bool> {
        instructions::check_withdraw_stake_by_id(ctx)
    }

    pub fn set_deposit_fee_percent(
        ctx: Context<SetDepositFeePercent>,
        deposit_fee_percent: u8,
    ) -> Result<()> {
        instructions::set_deposit_fee_percent(ctx, deposit_fee_percent)
    }

    pub fn set_treasury_address(
        ctx: Context<SetTreasuryAddress>,
        treasury_contract_address: Pubkey,
    ) -> Result<()> {
        instructions::set_treasury_address(ctx, treasury_contract_address)
    }

    pub fn set_treasury_percent(ctx: Context<SetTreasuryPercent>, percent: u16) -> Result<()> {
        instructions::set_treasury_percent(ctx, percent)
    }

    pub fn set_withdrawal_penalty_percent(
        ctx: Context<SetWithdrawalPenaltyPercent>,
        penalty_in_percentage: u16,
    ) -> Result<()> {
        instructions::set_withdrawal_penalty_percent(ctx, penalty_in_percentage)
    }

    pub fn set_minimum_withdrawal_days(
        ctx: Context<SetMinimumWithdrawalDays>,
        penalty_in_days: u64,
    ) -> Result<()> {
        instructions::set_minimum_withdrawal_days(ctx, penalty_in_days)
    }

    pub fn set_deposit_status(ctx: Context<SetDepositStatus>, status: bool) -> Result<()> {
        instructions::set_deposit_status(ctx, status)
    }

    pub fn set_withdraw_status(ctx: Context<SetWithdrawStatus>, status: bool) -> Result<()> {
        instructions::set_withdraw_status(ctx, status)
    }

    pub fn set_locking_period_apr(
        ctx: Context<SetLockingPeriodApr>,
        duration_in_days: u64,
        apr: u64,
        asset_address: Pubkey
    ) -> Result<()> {
        instructions::set_locking_period_apr(ctx, duration_in_days, apr, asset_address)
    }

    pub fn add_authorized_caller(
        ctx: Context<UpdateAuthorizedCaller>,
        user: Pubkey,
    ) -> Result<()> {
        instructions::add_authorized_caller(ctx, user)
    }

    pub fn remove_authorized_caller(
        ctx: Context<UpdateAuthorizedCaller>,
        user: Pubkey,
    ) -> Result<()> {
        instructions::remove_authorized_caller(ctx, user)
    }

    pub fn get_stake_details(ctx: Context<StakeByTxIdAccount>) -> Result<Stake> {
        instructions::get_stake_details(&ctx.accounts.user_stake_by_tx_id_account)
    }

    pub fn check_withdraw_eligibility(ctx: Context<CheckWithdrawEligibility>) -> Result<bool> {
        instructions::check_withdraw_eligibility(&ctx.accounts.user_stake_by_tx_id_account)
    }

    pub fn is_stake_unlocked(ctx: Context<StakeByTxIdAccount>) -> Result<bool> {
        instructions::is_stake_unlocked(&ctx.accounts.user_stake_by_tx_id_account)
    }

    pub fn calculate_amount_left_after_penalty(
        ctx: Context<CalculateAmountLeftAfterPenalty>,
    ) -> Result<AmountLeftAfterPenalty> {
        instructions::calculate_amount_left_after_penalty(
            &ctx.accounts.state_account,
            &ctx.accounts.user_stakes_by_tx_id_account,
        )
    }

    pub fn get_stake_withdraw_flagged_details(
        ctx: Context<GetStakeWithdrawFlaggedDetails>,
    ) -> Result<StakeWithdrawFlagDetails> {
        instructions::get_stake_withdraw_flagged_details(
            &ctx.accounts.user_stakes_by_tx_id_account
        )
    }

    pub fn set_stake_withdraw_flagged_details(
        ctx: Context<SetStakeWithdrawFlaggedDetails>,
        hold_withdrawal: bool,
        message: String
    ) -> Result<()> {
        instructions::set_stake_withdraw_flagged_details(
            ctx,
            hold_withdrawal, 
            message
        )
    }
    pub fn get_applicable_apr(
        ctx: Context<GetApplicableApr>,
        duration_in_days: u64,
        asset_address: Pubkey,
    ) -> Result<u64> {
        instructions::get_applicable_apr(ctx,duration_in_days,asset_address)
    }
    
}

  1. Paste your contract code at /programs/stake/src/account_structs.rs

// account_structs.rs

use crate::state::StateAccount;
use anchor_lang::prelude::*;
use anchor_spl::token::{Mint, Token, TokenAccount};

#[derive(Accounts)]
pub struct Initialize<'info> {
    #[account(mut, signer)]
    pub payer: Signer<'info>,
    #[account(
        init,
        seeds = [b"state_account"],
        bump,
        payer = payer,
        space = 5025 // Adjusted space for the account
    )]
    pub state_account: Box<Account<'info, StateAccount>>,
    pub token_program: Program<'info, Token>,
    pub system_program: Program<'info, System>,
    pub rent: Sysvar<'info, Rent>,
}

#[derive(Accounts)]
pub struct SetLockingPeriodApr<'info> {
    #[account(mut)]
    pub state_account: Account<'info, StateAccount>,
    #[account(
        mut,
        signer,
        constraint = state_account.authorized_callers.contains(&signer.key()),
    )]
    pub signer: Signer<'info>,
}

#[derive(Accounts)]
pub struct UpdateAuthorizedCaller<'info> {
    #[account(mut)]
    pub state_account: Account<'info, StateAccount>,
    #[account(
        mut,
        signer,
        constraint = state_account.authorized_callers.contains(&signer.key()),
    )]
    pub signer: Signer<'info>,
}

#[derive(Accounts)]
#[instruction(internal_id: String)]
pub struct DepositForNative<'info> {
    #[account(mut)]
    pub state_account: Account<'info, StateAccount>,
    #[account(mut, signer)]
    pub user: SystemAccount<'info>,
    #[account(
        mut,
        constraint = treasury_account.key() == state_account.treasury_contract
    )]
    pub treasury_account: SystemAccount<'info>,
    #[account(
        mut,
        seeds = [b"program_token_account"],
        bump,
    )]
    pub program_token_account: SystemAccount<'info>,
    pub system_program: Program<'info, System>,
    #[account(
        init,
        payer = user,
        space = 8 + std::mem::size_of::<Stake>(), // Adjust space as needed
        seeds = [internal_id.as_bytes()],
        bump,
    )]
    pub user_stakes_by_tx_id_account: Box<Account<'info, Stake>>,
}

#[derive(Accounts)]
#[instruction(internal_id: String)]
pub struct DepositForERC20<'info> {
    #[account(
        mut, 
        seeds = [b"state_account"],
        bump,
    )]
    pub state_account: Box<Account<'info, StateAccount>>,
    #[account(mut)]
    pub user: SystemAccount<'info>,
    pub token_mint: Account<'info, Mint>,
    #[account(
        mut,
        associated_token::mint = token_mint,
        associated_token::authority = user
    )]
    pub signer_token_account: Account<'info, TokenAccount>,
    #[account(
        mut,
        associated_token::mint = token_mint,
        associated_token::authority = state_account.treasury_contract,
    )]
    pub treasury_account: Account<'info, TokenAccount>,
    #[account(
        mut,
        associated_token::mint = token_mint,
        associated_token::authority = state_account
    )]
    pub program_token_account: Box<Account<'info, TokenAccount>>,
    pub system_program: Program<'info, System>,
    pub token_program: Program<'info, Token>,
    #[account(
        init,
        payer = user,
        space = 8 + std::mem::size_of::<Stake>(), // Adjust space as needed
        seeds = [internal_id.as_bytes()],
        bump,
    )]
    pub user_stakes_by_tx_id_account: Box<Account<'info, Stake>>,
}

#[derive(Accounts)]
#[instruction(internal_id: String)]
pub struct WithdrawStakeByIdForErc20<'info> {
    #[account(
        mut, 
        seeds = [b"state_account"],
        bump,
    )]
    pub state_account: Account<'info, StateAccount>,
    #[account(mut, signer)]
    pub user: Signer<'info>,
    #[account(
        mut,
        associated_token::mint = token_mint,
        associated_token::authority = state_account
    )]
    pub program_token_account: Account<'info, TokenAccount>,
    #[account(
        mut,
        associated_token::mint = token_mint,
        associated_token::authority = user
    )]
    pub user_token_account: Account<'info, TokenAccount>,
    pub token_program: Program<'info, Token>,
    pub system_program: Program<'info, System>, // Ensure this field is present
    #[account(mut)]
    pub user_stakes_by_tx_id_account: Box<Account<'info, Stake>>,
    pub token_mint: Account<'info, Mint>,
}

#[derive(Accounts)]
#[instruction(internal_id: String)]
pub struct WithdrawStakeByIdForNative<'info> {
    #[account(mut)]
    pub state_account: Account<'info, StateAccount>,
    #[account(mut, signer)]
    pub user: Signer<'info>,
    #[account(
        mut,
        seeds = [b"program_token_account"],
        bump,
    )]
    pub program_token_account: SystemAccount<'info>,
    pub system_program: Program<'info, System>, // Ensure this field is present
    #[account(mut)]
    pub user_stakes_by_tx_id_account: Box<Account<'info, Stake>>,
}

#[derive(Accounts)]
pub struct GetStakeDetails<'info> {
    pub state_account: Account<'info, StateAccount>,
}

#[derive(Accounts)]
pub struct IsStakeUnlocked<'info> {
    pub state_account: Account<'info, StateAccount>,
}

#[derive(Accounts)]
pub struct CalculateAmountLeftAfterPenalty<'info> {
    pub state_account: Account<'info, StateAccount>,
    pub user_stakes_by_tx_id_account: Account<'info, Stake>,
}

#[derive(Accounts)]
pub struct GetStakeWithdrawFlaggedDetails<'info> {
    pub user_stakes_by_tx_id_account: Account<'info, Stake>,
}

#[derive(Accounts)]
pub struct GetApplicableApr<'info> {
    pub state_account: Account<'info, StateAccount>,
}

#[derive(Accounts)]
pub struct SetStakeWithdrawFlaggedDetails<'info> {
    #[account(mut)]
    pub state_account: Account<'info, StateAccount>,
    #[account(
        mut,
        signer,
        constraint = state_account.authorized_callers.contains(&signer.key()),
    )]
    pub signer: Signer<'info>,
    #[account(mut)]
    pub user_stakes_by_tx_id_account: Box<Account<'info, Stake>>,
}

#[derive(Accounts)]
pub struct TransferToTreasuryNative<'info> {
    #[account(mut)]
    pub state_account: Account<'info, StateAccount>,
    #[account(
        mut,
        signer,
        constraint = state_account.authorized_callers.contains(&signer.key()),
    )]
    pub signer: Signer<'info>,
    #[account(mut)]
    pub treasury_account: SystemAccount<'info>,
    pub system_program: Program<'info, System>,

    #[account(
        mut,
        seeds = [b"program_token_account"],
        bump,
    )]
    pub program_token_account: SystemAccount<'info>
}

#[derive(Accounts)]
pub struct TransferToTreasuryErc20<'info> {
    #[account(mut)]
    pub state_account: Account<'info, StateAccount>,
    #[account(
        mut,
        signer,
        constraint = state_account.authorized_callers.contains(&signer.key()),
    )]
    pub signer: Signer<'info>,
    #[account(
        mut,
        associated_token::mint = token_mint,
        associated_token::authority = signer
    )]
    pub program_token_account: Account<'info, TokenAccount>,
    #[account(mut)]
    pub treasury_account: Account<'info, TokenAccount>,
    pub token_program: Program<'info, Token>,
    pub system_program: Program<'info, System>,
    pub token_mint: Account<'info, Mint>,
}

#[derive(Accounts)]
pub struct SetDepositStatus<'info> {
    #[account(mut)]
    pub state_account: Account<'info, StateAccount>,
    #[account(
        mut,
        signer,
        constraint = state_account.authorized_callers.contains(&signer.key()),
    )]
    pub signer: Signer<'info>,
}

#[derive(Accounts)]
pub struct SetWithdrawStatus<'info> {
    #[account(mut)]
    pub state_account: Account<'info, StateAccount>,
    #[account(
        mut,
        signer,
        constraint = state_account.authorized_callers.contains(&signer.key()),
    )]
    pub signer: Signer<'info>,
}

#[derive(Accounts)]
pub struct SetMinimumWithdrawalDays<'info> {
    #[account(mut)]
    pub state_account: Account<'info, StateAccount>,
    #[account(
        mut,
        signer,
        constraint = state_account.authorized_callers.contains(&signer.key()),
    )]
    pub signer: Signer<'info>,
}

#[derive(Accounts)]
pub struct SetWithdrawalPenaltyPercent<'info> {
    #[account(mut)]
    pub state_account: Account<'info, StateAccount>,
    #[account(
        mut,
        signer,
        constraint = state_account.authorized_callers.contains(&signer.key()),
    )]
    pub signer: Signer<'info>,
}

#[derive(Accounts)]
pub struct SetTreasuryAddress<'info> {
    #[account(mut)]
    pub state_account: Account<'info, StateAccount>,
    #[account(
        mut,
        signer,
        constraint = state_account.authorized_callers.contains(&signer.key()),
    )]
    pub signer: Signer<'info>,
}

#[derive(Accounts)]
pub struct SetTreasuryPercent<'info> {
    #[account(mut)]
    pub state_account: Account<'info, StateAccount>,
    #[account(
        mut,
        signer,
        constraint = state_account.authorized_callers.contains(&signer.key()),
    )]
    pub signer: Signer<'info>,
}

#[derive(Accounts)]
pub struct SetDepositFeePercent<'info> {
    #[account(mut)]
    pub state_account: Account<'info, StateAccount>,
    #[account(
        mut,
        signer,
        constraint = state_account.authorized_callers.contains(&signer.key()),
    )]
    pub signer: Signer<'info>,
}

#[derive(Accounts)]
pub struct CheckWithdrawStakeById<'info> {
    pub user_stakes_by_tx_id_account: Account<'info, Stake>,
}

#[account]
pub struct Stake {
    pub owner: Pubkey,
    pub start_date: u64,
    pub duration_in_days: u64,
    pub amount: u64,
    pub asset_address: Pubkey,
    pub reward_address: String,
    pub asset_symbol: String,
    pub asset_decimal: u64,
    pub apr: u64,
    pub is_erc20: bool,
    pub is_redeemed: bool,
    pub redeemed_timestamp: u64,
    pub penalty_amount: u64,
    pub penalty_percentage: u64,
    pub eligible_to_withdraw: bool,
    pub withdraw_message: String,
    pub caller_of_flag_withdraw: Pubkey
}

#[derive(Accounts)]
pub struct StakeByTxIdAccount<'info> {
    pub user_stake_by_tx_id_account: Account<'info, Stake>,
}

#[derive(Accounts)]
pub struct CheckWithdrawEligibility<'info> {
    pub user_stake_by_tx_id_account: Account<'info, Stake>,
}

  1. Paste your contract code at /programs/stake/src/error.rs

// error.rs

use anchor_lang::prelude::*;

#[error_code]
pub enum ErrorCode {
    #[msg("The provided key was not found in the state account.")]
    KeyNotFound,
    #[msg("Unauthorized access.")]
    Unauthorized,
    #[msg("Already Authorized.")]
    AlreadyAuthorized,
    #[msg("This user is not even authorized.")]
    NotEvenAuthorized,
    #[msg("Percent should be between 0 and 100.")]
    InvalidPercentage,
    #[msg("Invalid percent value.")]
    InvalidPercent,
    #[msg("The user was not found in the state account.")]
    UserNotFound,
    #[msg("The asset was not found for the user in the state account.")]
    AssetNotFound,
    #[msg("The transaction ID was not found for the user in the state account.")]
    TransactionIdNotFound,
    #[msg("Invalid start date")]
    InvalidStartDate,
    #[msg("Already unstaked the amount")]
    AlreadyUnstaked,
    #[msg("The stake is not available for withdrawal yet.")]
    StakeNotAvailableForWithdrawal,
    #[msg("Treasury address is not set.")]
    TreasuryNotSet,
    #[msg("Invalid treasury address.")]
    InvalidTreasuryAddress,
    #[msg("The stake with the given internal ID already exists.")]
    StakeAlreadyExists,
    #[msg("The deposit is paused.")]
    DepositPaused,
    #[msg("Invalid amount.")]
    InvalidAmount,
    #[msg("Invalid decimals.")]
    InvalidDecimals,
    #[msg("Invalid reward address.")]
    InvalidRewardAddress,
    #[msg("Invalid asset address.")]
    InvalidAssetAddress,
    #[msg("Invalid asset symbol.")]
    InvalidAssetSymbol,
    #[msg("Cannot transfer to self.")]
    TransferToSelf,
    #[msg("Insufficient native balance.")]
    InsufficientNativeBalance,
    #[msg("Insufficient ERC20 Balance.")]
    InsufficientERC20Balance,
    #[msg("Flagged stake.")]
    FlaggedStake,
    #[msg("Withdrawal paused.")]
    WithdrawalPaused,
}

  1. Paste your contract code at /programs/stake/src/events.rs

//events.rs

use anchor_lang::prelude::*;

#[event]
pub struct StakeDeposited {
    pub internal_id: String,
    pub wallet_address: Pubkey,
    pub asset_address: Pubkey,
    pub reward_address: Pubkey,
    pub network: String,
    pub asset_symbol: String,
    pub asset_decimal: u64,
    pub apr: u64,
    pub amount: u64,
    pub duration_in_days: u64,
    pub deposit_fee_percentage: u8,
    pub stake_type: u8,
    pub timestamp: u64,
}

#[event]
pub struct StakeWithdraw {
    pub internal_id: String,
    pub wallet_address: Pubkey,
    pub asset_address: Pubkey,
    pub reward_address: Pubkey,
    pub network: String,
    pub asset_symbol: String,
    pub asset_decimal: u64,
    pub apr: u64,
    pub amount: u64,
    pub amount_transferred: u64,
    pub penalty_amount: u64,
    pub duration_in_days: u64,
    pub withdrawal_penalty_in_percentage: u16,
    pub stake_type: u8,
    pub timestamp: u64,
}

#[event]
pub struct LockInUpdated {
    pub duration_in_days: u64,
    pub apr: u64,
}

#[event]
pub struct AuthorizationUpdated {
    pub sender: Pubkey,
    pub status: bool,
}

#[event]
pub struct XTalkMessageBroadcasted {
    pub message: Vec<u8>,
    pub destination_network: String,
    pub destination_smart_contract_address: String,
}

  1. Paste your contract code at /programs/stake/src/instructions.rs

// instructions.rs

use crate::account_structs::*;
use crate::error::ErrorCode;
use crate::events::*;
use crate::state::*;
use crate::utils::*;
use anchor_lang::prelude::*;
use anchor_lang::system_program::{transfer, Transfer as NativeTransfer};
use anchor_spl::token;
use anchor_spl::token::transfer_checked;
use anchor_spl::token::Transfer;
use anchor_spl::token::TransferChecked;
use solana_program::program::invoke_signed;
use solana_program::system_instruction;

pub fn initialize(
    ctx: Context<Initialize>,
    native_symbol: String,
    native_asset_symbol: String,
    native_asset_decimal: u8,
    treasury_contract: Pubkey,
    treasury_distribution_percent: u16,
    deposit_fee_percent: u8,
    withdrawal_penalty_in_days: u64,
    withdrawal_penalty_in_percentage: u16,
) -> Result<()> {
    let state_account = &mut ctx.accounts.state_account;
    state_account.locking_period_apr = Vec::new();
    state_account.authorized_callers = vec![ctx.accounts.payer.key()];
    state_account.native_symbol = native_symbol;
    state_account.native_asset_symbol = native_asset_symbol;
    state_account.native_asset_decimal = native_asset_decimal;
    state_account.treasury_contract = treasury_contract;
    state_account.treasury_distribution_percent = treasury_distribution_percent;
    state_account.deposit_fee_percent = deposit_fee_percent;
    state_account.withdrawal_penalty_in_days = withdrawal_penalty_in_days;
    state_account.withdrawal_penalty_in_percentage = withdrawal_penalty_in_percentage;
    state_account.owner = ctx.accounts.payer.key();
    state_account.deposit_status = true;
    state_account.withdraw_status = true;
    Ok(())
}

pub fn withdraw_stake_by_id_for_erc20(
    ctx: Context<WithdrawStakeByIdForErc20>,
    internal_id: String,
) -> Result<()> {
    let state_account = &mut ctx.accounts.state_account;
    let user_stakes_by_tx_id_account = &mut ctx.accounts.user_stakes_by_tx_id_account;
    let wallet_address = ctx.accounts.user.key();

    require!(state_account.withdraw_status, ErrorCode::WithdrawalPaused);

    require!(
        user_stakes_by_tx_id_account.eligible_to_withdraw,
        ErrorCode::FlaggedStake
    );

    require!(
        user_stakes_by_tx_id_account.is_erc20,
        ErrorCode::KeyNotFound
    );

    // Immutable borrow for checks
    let stake_unlocked = { is_stake_unlocked(user_stakes_by_tx_id_account)? };
    require!(stake_unlocked, ErrorCode::StakeNotAvailableForWithdrawal);

    // Retrieve Stake
    let stake = { get_stake_details(user_stakes_by_tx_id_account)? };

    // Check if already redeemed
    require!(!stake.is_redeemed, ErrorCode::AlreadyUnstaked);

    let amount_left_after_penalty = {
        let state_account_ref = &*state_account;
        calculate_amount_left_after_penalty(state_account_ref, user_stakes_by_tx_id_account)?
    };

    let withdrawal_penalty_in_percentage = state_account.withdrawal_penalty_in_percentage;

    user_stakes_by_tx_id_account.is_redeemed = true;
    user_stakes_by_tx_id_account.penalty_amount = amount_left_after_penalty.penalty_amount;
    user_stakes_by_tx_id_account.redeemed_timestamp = get_current_timestamp()?;

    if amount_left_after_penalty.penalty_amount > 0 {
        user_stakes_by_tx_id_account.penalty_percentage = withdrawal_penalty_in_percentage as u64;
    }

    let seeds = &["state_account".as_bytes(), &[ctx.bumps.state_account]];
    let seeds_account = [&seeds[..]];

    let cpi_context = CpiContext::new_with_signer(
        ctx.accounts.token_program.to_account_info(),
        TransferChecked {
            from: ctx.accounts.program_token_account.to_account_info(),
            to: ctx.accounts.user_token_account.to_account_info(),
            authority: state_account.to_account_info(),
            mint: ctx.accounts.token_mint.to_account_info(),
        },
        &seeds_account,
    );
    transfer_checked(
        cpi_context,
        amount_left_after_penalty.amount_left
            - calculate_deposit_fee(state_account, amount_left_after_penalty.amount_left),
        ctx.accounts.token_mint.decimals,
    )?;

    emit!(StakeWithdraw {
        internal_id: internal_id.clone(),
        wallet_address,
        asset_address: stake.asset_address,
        reward_address: stake.reward_address.clone(),
        network: "Solana".to_string(),
        asset_symbol: stake.asset_symbol.clone(),
        asset_decimal: stake.asset_decimal,
        apr: stake.apr,
        amount: stake.amount,
        amount_transferred: amount_left_after_penalty.amount_left,
        penalty_amount: amount_left_after_penalty.penalty_amount,
        duration_in_days: stake.duration_in_days,
        withdrawal_penalty_in_percentage: state_account.withdrawal_penalty_in_percentage,
        stake_type: 1,
        timestamp: get_current_timestamp()?,
    });

    let message_bytes = bincode::serialize(&(
        internal_id,
        wallet_address.to_string(),
        stake.asset_address.to_string(),
        stake.reward_address.to_string(),
        state_account.native_symbol.clone(),
        stake.asset_symbol,
        stake.asset_decimal,
        stake.apr,
        stake.amount,
        amount_left_after_penalty.amount_left,
        amount_left_after_penalty.penalty_amount,
        stake.duration_in_days,
        state_account.withdrawal_penalty_in_percentage,
        1,
        get_current_timestamp()?,
    ))
    .unwrap();
    emit!(XTalkMessageBroadcasted {
        message: message_bytes.to_vec(),
        destination_network: "L1X".to_string(),
        destination_smart_contract_address: Pubkey::default().to_string(),
    });

    Ok(())
}

pub fn withdraw_stake_by_id_for_native(
    ctx: Context<WithdrawStakeByIdForNative>,
    internal_id: String,
) -> Result<()> {
    let state_account = &mut ctx.accounts.state_account;
    let user_stakes_by_tx_id_account = &mut ctx.accounts.user_stakes_by_tx_id_account;
    let wallet_address = ctx.accounts.user.key();

    require!(state_account.withdraw_status, ErrorCode::WithdrawalPaused);

    require!(
        user_stakes_by_tx_id_account.eligible_to_withdraw,
        ErrorCode::FlaggedStake
    );

    require!(
        !user_stakes_by_tx_id_account.is_erc20,
        ErrorCode::KeyNotFound
    );

    // Immutable borrow for checks
    let stake_unlocked = { is_stake_unlocked(user_stakes_by_tx_id_account)? };
    require!(stake_unlocked, ErrorCode::StakeNotAvailableForWithdrawal);

    // Retrieve Stake
    let stake = { get_stake_details(user_stakes_by_tx_id_account)? };

    // Check if already redeemed
    require!(!stake.is_redeemed, ErrorCode::AlreadyUnstaked);

    let amount_left_after_penalty = {
        let state_account_ref = &*state_account;
        calculate_amount_left_after_penalty(state_account_ref, user_stakes_by_tx_id_account)?
    };

    let withdrawal_penalty_in_percentage = state_account.withdrawal_penalty_in_percentage;

    user_stakes_by_tx_id_account.is_redeemed = true;
    user_stakes_by_tx_id_account.penalty_amount = amount_left_after_penalty.penalty_amount;
    user_stakes_by_tx_id_account.redeemed_timestamp = get_current_timestamp()?;

    if amount_left_after_penalty.penalty_amount > 0 {
        user_stakes_by_tx_id_account.penalty_percentage = withdrawal_penalty_in_percentage as u64;
    }

    let seeds = &[
        "program_token_account".as_bytes(),
        &[ctx.bumps.program_token_account],
    ];
    let seeds_account = [&seeds[..]];
    let cpi_context = CpiContext::new_with_signer(
        ctx.accounts.system_program.to_account_info(),
        NativeTransfer {
            from: ctx.accounts.program_token_account.to_account_info(),
            to: ctx.accounts.user.to_account_info(),
        },
        &seeds_account,
    );
    transfer(
        cpi_context,
        amount_left_after_penalty.amount_left
            - calculate_deposit_fee(&state_account, amount_left_after_penalty.amount_left),
    )?;

    emit!(StakeWithdraw {
        internal_id: internal_id.clone(),
        wallet_address,
        asset_address: stake.asset_address,
        reward_address: stake.reward_address.clone(),
        network: "Solana".to_string(),
        asset_symbol: stake.asset_symbol.clone(),
        asset_decimal: stake.asset_decimal,
        apr: stake.apr,
        amount: stake.amount,
        amount_transferred: amount_left_after_penalty.amount_left,
        penalty_amount: amount_left_after_penalty.penalty_amount,
        duration_in_days: stake.duration_in_days,
        withdrawal_penalty_in_percentage: state_account.withdrawal_penalty_in_percentage,
        stake_type: 0,
        timestamp: get_current_timestamp()?,
    });

    let message_bytes = bincode::serialize(&(
        internal_id,
        wallet_address.to_string(),
        stake.asset_address.to_string(),
        stake.reward_address.to_string(),
        state_account.native_symbol.clone(),
        stake.asset_symbol,
        stake.asset_decimal,
        stake.apr,
        stake.amount,
        amount_left_after_penalty.amount_left,
        amount_left_after_penalty.penalty_amount,
        stake.duration_in_days,
        state_account.withdrawal_penalty_in_percentage,
        0,
        get_current_timestamp()?,
    ))
    .unwrap();
    emit!(XTalkMessageBroadcasted {
        message: message_bytes.to_vec(),
        destination_network: "L1X".to_string(),
        destination_smart_contract_address: Pubkey::default().to_string(),
    });

    Ok(())
}

pub fn deposit_for_native(
    ctx: Context<DepositForNative>,
    internal_id: String,
    duration_in_days: u64,
    reward_address: String,
    amount: u64,
) -> Result<()> {
    let state_account = &mut ctx.accounts.state_account;
    let user_stakes_by_tx_id_account = &mut ctx.accounts.user_stakes_by_tx_id_account;

    let user = ctx.accounts.user.key();
    require!(state_account.deposit_status, ErrorCode::DepositPaused);
    require!(amount > 0, ErrorCode::InvalidAmount);

    // Handle deposit fee
    let deposit_fee = calculate_deposit_fee(state_account, amount);
    if deposit_fee > 0 {
        transfer_sol(
            &ctx.accounts.user.to_account_info(),
            &ctx.accounts.treasury_account.to_account_info(),
            deposit_fee,
            &ctx.accounts.system_program.to_account_info(),
        )?;
    }

    // Handle treasury share
    let treasury_share = calculate_treasury_share(state_account, amount);
    if treasury_share > 0 {
        transfer_sol(
            &ctx.accounts.user.to_account_info(),
            &ctx.accounts.treasury_account.to_account_info(),
            treasury_share,
            &ctx.accounts.system_program.to_account_info(),
        )?;
    }

    //transfer the remaining to program
    transfer_sol(
        &ctx.accounts.user.to_account_info(),
        &ctx.accounts.program_token_account.to_account_info(),
        amount - deposit_fee - treasury_share,
        &ctx.accounts.system_program.to_account_info(),
    )?;

    let apr = state_account
        .locking_period_apr
        .iter()
        .find(|apr| apr.key == duration_in_days && apr.asset_address == Pubkey::default())
        .map_or(0, |apr| apr.value);

    user_stakes_by_tx_id_account.owner = ctx.accounts.user.key();
    user_stakes_by_tx_id_account.start_date = get_current_timestamp()?;
    user_stakes_by_tx_id_account.duration_in_days = duration_in_days;
    user_stakes_by_tx_id_account.amount = amount;
    user_stakes_by_tx_id_account.asset_address = Pubkey::default();
    user_stakes_by_tx_id_account.reward_address = reward_address.clone();
    user_stakes_by_tx_id_account.asset_symbol = state_account.native_symbol.clone();
    user_stakes_by_tx_id_account.asset_decimal = state_account.native_asset_decimal as u64;
    user_stakes_by_tx_id_account.apr = apr;
    user_stakes_by_tx_id_account.is_erc20 = false;
    user_stakes_by_tx_id_account.is_redeemed = false;
    user_stakes_by_tx_id_account.redeemed_timestamp = 0;
    user_stakes_by_tx_id_account.penalty_amount = 0;
    user_stakes_by_tx_id_account.penalty_percentage = 0;
    user_stakes_by_tx_id_account.eligible_to_withdraw = true;

    emit!(StakeDeposited {
        internal_id: internal_id.clone(),
        wallet_address: user,
        asset_address: Pubkey::default(),
        reward_address: reward_address.clone(),
        network: "Solana".to_string(),
        asset_symbol: state_account.native_symbol.clone(),
        asset_decimal: state_account.native_asset_decimal as u64,
        apr,
        amount,
        duration_in_days,
        deposit_fee_percentage: state_account.deposit_fee_percent,
        stake_type: 0,
        timestamp: get_current_timestamp()?,
    });

    let message_bytes = bincode::serialize(&(
        internal_id,
        user.to_string(),
        Pubkey::default().to_string(),
        reward_address.clone(),
        state_account.native_symbol.clone(),
        state_account.native_symbol.clone(),
        state_account.native_asset_decimal,
        state_account
            .locking_period_apr
            .iter()
            .find(|apr| apr.key == duration_in_days && apr.asset_address == Pubkey::default())
            .map_or(0, |apr| apr.value),
        amount,
        duration_in_days,
        state_account.deposit_fee_percent,
        0,
        get_current_timestamp()?,
    ))
    .unwrap();
    emit!(XTalkMessageBroadcasted {
        message: message_bytes.to_vec(),
        destination_network: "L1X".to_string(),
        destination_smart_contract_address: Pubkey::default().to_string(),
    });

    Ok(())
}

pub fn deposit_for_erc20(
    ctx: Context<DepositForERC20>,
    internal_id: String,
    duration_in_days: u64,
    reward_address: String,
    asset_symbol: String,
    asset_address: Pubkey,
    amount: u64,
    decimals: u8,
) -> Result<()> {
    let state_account = &mut ctx.accounts.state_account;
    let user_stakes_by_tx_id_account = &mut ctx.accounts.user_stakes_by_tx_id_account;

    let user = ctx.accounts.user.key();

    // Ensure the deposit is not paused and amount is valid
    require!(state_account.deposit_status, ErrorCode::DepositPaused);
    require!(amount > 0, ErrorCode::InvalidAmount);
    require!(decimals > 0, ErrorCode::InvalidDecimals);
    require!(
        reward_address != "0x0000000000000000000000000000000000000000",
        ErrorCode::InvalidRewardAddress
    );
    require!(
        asset_address != Pubkey::default(),
        ErrorCode::InvalidAssetAddress
    );
    require!(
        !is_empty_string(asset_symbol.clone()),
        ErrorCode::InvalidAssetSymbol
    );

    let deposit_fee = calculate_deposit_fee(state_account, amount);
    if deposit_fee > 0 {
        transfer_token(
            &ctx.accounts.signer_token_account.to_account_info(),
            &ctx.accounts.treasury_account.to_account_info(),
            &&ctx.accounts.user.to_account_info(),
            deposit_fee,
            &ctx.accounts.token_program.to_account_info(),
        )?;
    }

    // Handle Treasury Share
    let treasury_share = calculate_treasury_share(state_account, amount);
    if treasury_share > 0 {
        transfer_token(
            &ctx.accounts.signer_token_account.to_account_info(),
            &ctx.accounts.treasury_account.to_account_info(),
            &&ctx.accounts.user.to_account_info(),
            treasury_share,
            &ctx.accounts.token_program.to_account_info(),
        )?;
    }

    // Transfer remaining funds to contract
    transfer_token(
        &ctx.accounts.signer_token_account.to_account_info(),
        &ctx.accounts.program_token_account.to_account_info(),
        &&ctx.accounts.user.to_account_info(),
        amount - treasury_share - deposit_fee,
        &ctx.accounts.token_program.to_account_info(),
    )?;

    let apr = state_account
        .locking_period_apr
        .iter()
        .find(|apr| apr.key == duration_in_days && apr.asset_address == asset_address)
        .map_or(0, |apr| apr.value);

    user_stakes_by_tx_id_account.owner = ctx.accounts.user.key();
    user_stakes_by_tx_id_account.start_date = get_current_timestamp()?;
    user_stakes_by_tx_id_account.duration_in_days = duration_in_days;
    user_stakes_by_tx_id_account.amount = amount;
    user_stakes_by_tx_id_account.asset_address = asset_address;
    user_stakes_by_tx_id_account.reward_address = reward_address.clone();
    user_stakes_by_tx_id_account.asset_symbol = asset_symbol.clone();
    user_stakes_by_tx_id_account.asset_decimal = decimals as u64;
    user_stakes_by_tx_id_account.apr = apr;
    user_stakes_by_tx_id_account.is_erc20 = true;
    user_stakes_by_tx_id_account.is_redeemed = false;
    user_stakes_by_tx_id_account.redeemed_timestamp = 0;
    user_stakes_by_tx_id_account.penalty_amount = 0;
    user_stakes_by_tx_id_account.penalty_percentage = 0;
    user_stakes_by_tx_id_account.eligible_to_withdraw = true;

    emit!(StakeDeposited {
        internal_id: internal_id.clone(),
        wallet_address: user,
        asset_address,
        reward_address: reward_address.clone(),
        network: "Solana".to_string(),
        asset_symbol: asset_symbol.clone(),
        asset_decimal: decimals as u64,
        apr,
        amount,
        duration_in_days,
        deposit_fee_percentage: state_account.deposit_fee_percent,
        stake_type: 1,
        timestamp: get_current_timestamp()?,
    });

    let message_bytes = bincode::serialize(&(
        internal_id,
        user.to_string(),
        asset_address.to_string(),
        reward_address.to_string(),
        state_account.native_symbol.clone(),
        asset_symbol,
        decimals,
        state_account
            .locking_period_apr
            .iter()
            .find(|apr| apr.key == duration_in_days && apr.asset_address == asset_address)
            .map_or(0, |apr| apr.value),
        amount,
        duration_in_days,
        state_account.deposit_fee_percent,
        1, // stake_type
        get_current_timestamp()?,
    ))
    .unwrap();
    emit!(XTalkMessageBroadcasted {
        message: message_bytes.to_vec(),
        destination_network: "L1X".to_string(),
        destination_smart_contract_address: Pubkey::default().to_string(),
    });

    Ok(())
}

pub fn transfer_to_treasury_native(
    ctx: Context<TransferToTreasuryNative>,
    amount: u64,
) -> Result<()> {
    let state_account = &ctx.accounts.state_account;
    require!(
        state_account.treasury_contract != Pubkey::default(),
        ErrorCode::TreasuryNotSet
    );

    require!(
        state_account.treasury_contract == ctx.accounts.treasury_account.key(),
        ErrorCode::InvalidTreasuryAddress
    );

    // transfer_sol(
    //     &ctx.accounts.signer.to_account_info(),
    //     &ctx.accounts.treasury_account.to_account_info(),
    //     amount,
    //     &ctx.accounts.system_program.to_account_info(),
    // )?;

    let pda = ctx.accounts.program_token_account.to_account_info();
    let recipient = ctx.accounts.treasury_account.to_account_info();
    let system_program = ctx.accounts.system_program.to_account_info(); 
    
    let bump = ctx.bumps.program_token_account;
    let seeds = &[b"program_token_account".as_ref(), &[bump]];

    let transfer_instruction = system_instruction::transfer(
        &pda.key,
        &recipient.key,
        amount,
    );

    invoke_signed(
        &transfer_instruction,
        &[pda, recipient, system_program],
        &[&seeds[..]],
    )?;

    Ok(())
}

pub fn transfer_to_treasury_erc20(
    ctx: Context<TransferToTreasuryErc20>,
    amount: u64,
) -> Result<()> {
    let state_account = &ctx.accounts.state_account;
    require!(
        state_account.treasury_contract != Pubkey::default(),
        ErrorCode::TreasuryNotSet
    );

    require!(
        state_account.treasury_contract != ctx.accounts.treasury_account.key(),
        ErrorCode::InvalidTreasuryAddress
    );

    let cpi_accounts = Transfer {
        from: ctx.accounts.program_token_account.to_account_info(),
        to: ctx.accounts.treasury_account.to_account_info(),
        authority: ctx.accounts.signer.to_account_info(),
    };
    let cpi_program = ctx.accounts.token_program.to_account_info();
    let cpi_ctx = CpiContext::new(cpi_program, cpi_accounts);
    token::transfer(cpi_ctx, amount)?;

    Ok(())
}

pub fn check_withdraw_stake_by_id(ctx: Context<CheckWithdrawStakeById>) -> Result<bool> {
    let user_stakes_by_tx_id_account = &ctx.accounts.user_stakes_by_tx_id_account;

    let stake_unlocked = is_stake_unlocked(user_stakes_by_tx_id_account)?;
    require!(stake_unlocked, ErrorCode::StakeNotAvailableForWithdrawal);
    let stake = get_stake_details(user_stakes_by_tx_id_account)?;
    Ok(stake.is_redeemed)
}

pub fn get_applicable_apr(
    ctx: Context<GetApplicableApr>, 
    duration_in_days: u64,
    asset_address: Pubkey) -> Result<u64> {
    let apr_applicable =  ctx.accounts.state_account
    .locking_period_apr
    .iter()
    .find(|&apr| apr.key == duration_in_days && apr.asset_address == asset_address)
    .map_or(0, |apr| apr.value);

    Ok(apr_applicable)
}

pub fn set_deposit_fee_percent(
    ctx: Context<SetDepositFeePercent>,
    deposit_fee_percent: u8,
) -> Result<()> {
    require!(deposit_fee_percent <= 100, ErrorCode::InvalidPercent);
    let state_account = &mut ctx.accounts.state_account;
    state_account.deposit_fee_percent = deposit_fee_percent;
    Ok(())
}

pub fn set_treasury_address(
    ctx: Context<SetTreasuryAddress>,
    treasury_contract_address: Pubkey,
) -> Result<()> {
    let state_account = &mut ctx.accounts.state_account;
    state_account.treasury_contract = treasury_contract_address;
    Ok(())
}

pub fn set_treasury_percent(ctx: Context<SetTreasuryPercent>, percent: u16) -> Result<()> {
    require!(percent <= 100, ErrorCode::InvalidPercent);
    let state_account = &mut ctx.accounts.state_account;
    state_account.treasury_distribution_percent = percent;
    Ok(())
}

pub fn set_withdrawal_penalty_percent(
    ctx: Context<SetWithdrawalPenaltyPercent>,
    penalty_in_percentage: u16,
) -> Result<()> {
    require!(penalty_in_percentage <= 100, ErrorCode::InvalidPercentage);
    ctx.accounts.state_account.withdrawal_penalty_in_percentage = penalty_in_percentage;
    Ok(())
}

pub fn set_minimum_withdrawal_days(
    ctx: Context<SetMinimumWithdrawalDays>,
    penalty_in_days: u64,
) -> Result<()> {
    let state_account = &mut ctx.accounts.state_account;
    state_account.withdrawal_penalty_in_days = penalty_in_days;
    Ok(())
}

pub fn set_deposit_status(ctx: Context<SetDepositStatus>, status: bool) -> Result<()> {
    let state_account = &mut ctx.accounts.state_account;
    state_account.deposit_status = status;
    Ok(())
}

pub fn set_withdraw_status(ctx: Context<SetWithdrawStatus>, status: bool) -> Result<()> {
    let state_account = &mut ctx.accounts.state_account;
    state_account.withdraw_status = status;
    Ok(())
}

pub fn set_locking_period_apr(
    ctx: Context<SetLockingPeriodApr>,
    duration_in_days: u64,
    apr: u64,
    asset_address: Pubkey
) -> Result<()> {
    let state_account = &mut ctx.accounts.state_account;
    if let Some(locking_period) = state_account
        .locking_period_apr
        .iter_mut()
        .find(|lp| lp.key == duration_in_days && asset_address == lp.asset_address)
    {
        locking_period.value = apr;
    } else {
        state_account.locking_period_apr.push(LockingPeriodApr {
            key: duration_in_days,
            value: apr,
            asset_address: asset_address
        });
    }

    emit!(LockInUpdated {
        duration_in_days,
        apr,
        asset_address
    });

    Ok(())
}

pub fn add_authorized_caller(
    ctx: Context<UpdateAuthorizedCaller>,
    user: Pubkey,
) -> Result<()> {
    let state_account = &mut ctx.accounts.state_account;
    if !state_account.authorized_callers.contains(&user) {
        state_account.authorized_callers.push(user);
    } else {
        return Err(ErrorCode::AlreadyAuthorized.into());
    }

    // Emit event
    emit!(AuthorizationUpdated {
        sender: user,
        status: true,
    });

    Ok(())
}

pub fn remove_authorized_caller(
    ctx: Context<UpdateAuthorizedCaller>,
    user: Pubkey,
) -> Result<()> {
    let state_account = &mut ctx.accounts.state_account;
    if state_account.authorized_callers.contains(&user) {
        state_account.authorized_callers.retain(|&x| x == user);
    } else {
        return Err(ErrorCode::NotEvenAuthorized.into());
    }

    // Emit event
    emit!(AuthorizationUpdated {
        sender: user,
        status: false,
    });

    Ok(())
}

pub fn get_stake_details(user_stakes_by_tx_id_account: &Stake) -> Result<Stake> {
    Ok(user_stakes_by_tx_id_account.clone())
}

pub fn is_stake_unlocked(user_stakes_by_tx_id_account: &Stake) -> Result<bool> {
    if user_stakes_by_tx_id_account.start_date == 0 {
        return Ok(false);
    }
    let elapsed_time = get_current_timestamp()? - user_stakes_by_tx_id_account.start_date;
    if elapsed_time >= user_stakes_by_tx_id_account.duration_in_days * 86400 {
        return Ok(true);
    }
    return Ok(false);
}

pub fn calculate_amount_left_after_penalty(
    state_account: &StateAccount,
    user_stakes_by_tx_id_account: &Stake,
) -> Result<AmountLeftAfterPenalty> {
    let stake = get_stake_details(user_stakes_by_tx_id_account)?;
    let mut penalty_amount: u64 = 0;
    require!(stake.start_date > 0, ErrorCode::InvalidStartDate);
    require!(!stake.is_redeemed, ErrorCode::AlreadyUnstaked);
    let elapsed_time = get_current_timestamp()? - stake.start_date;
    if stake.duration_in_days == 0
        && elapsed_time < state_account.withdrawal_penalty_in_days * (1 * 86400)
    {
        penalty_amount = calculate_percent_amount(
            state_account.withdrawal_penalty_in_percentage as u64,
            stake.amount,
        );
    }
    let amount_left = stake.amount - penalty_amount;
    Ok(AmountLeftAfterPenalty {
        amount_left,
        penalty_amount,
    })
}

pub fn set_stake_withdraw_flagged_details(
    ctx: Context<SetStakeWithdrawFlaggedDetails>,
    hold_withdrawal: bool,
    message: String
) -> Result<()> {
    let user_stakes_by_tx_id_account = &mut ctx.accounts.user_stakes_by_tx_id_account;
    user_stakes_by_tx_id_account.eligible_to_withdraw = hold_withdrawal;
    user_stakes_by_tx_id_account.withdraw_message= message;
    user_stakes_by_tx_id_account.caller_of_flag_withdraw = ctx.accounts.signer.key();
    Ok(())
}

pub fn get_stake_withdraw_flagged_details(
    user_stakes_by_tx_id_account: &Stake,
) -> Result<StakeWithdrawFlagDetails> {
    
    Ok(StakeWithdrawFlagDetails {
        hold_withdrawal: user_stakes_by_tx_id_account.eligible_to_withdraw,
        message: user_stakes_by_tx_id_account.withdraw_message.clone(),
        caller: user_stakes_by_tx_id_account.caller_of_flag_withdraw,
    })
}

pub fn check_withdraw_eligibility(user_stakes_by_tx_id_account: &Stake) -> Result<bool> {
    Ok(user_stakes_by_tx_id_account.eligible_to_withdraw)
}

  1. Paste your contract code at /programs/stake/src/state.rs

// state.rs

use anchor_lang::prelude::*;

#[account]
pub struct StateAccount {
    pub native_symbol: String,
    pub native_asset_symbol: String,
    pub native_asset_decimal: u8,
    pub treasury_contract: Pubkey,
    pub treasury_distribution_percent: u16,
    pub deposit_fee_percent: u8,
    pub withdrawal_penalty_in_days: u64,
    pub withdrawal_penalty_in_percentage: u16,
    pub owner: Pubkey,
    pub deposit_status: bool,
    pub withdraw_status: bool,
    pub locking_period_apr: Vec<LockingPeriodApr>,
    pub authorized_callers: Vec<Pubkey>,
}

#[derive(AnchorSerialize, AnchorDeserialize, Clone)]
pub struct LockingPeriodApr {
    pub key: u64,
    pub value: u64,
}

#[derive(AnchorSerialize, AnchorDeserialize, Clone)]
pub struct AmountLeftAfterPenalty {
    pub amount_left: u64,
    pub penalty_amount: u64,
}


#[derive(AnchorSerialize, AnchorDeserialize, Clone)]
pub struct StakeWithdrawFlagDetails {
    pub hold_withdrawal: bool,
    pub message: String,
    pub caller: Pubkey
}

  1. Paste your contract code at /programs/stake/src/utils.rs

// utils.rs

use crate::state::*;
use anchor_lang::prelude::*;
use anchor_spl::token::Transfer;

pub fn calculate_percent_amount(percent: u64, amount: u64) -> u64 {
    return (amount * percent) / 100;
}

pub fn is_empty_string(ref_string: String) -> bool {
    ref_string.is_empty()
}

pub fn calculate_treasury_share(state_account: &StateAccount, amount: u64) -> u64 {
    calculate_percent_amount(state_account.treasury_distribution_percent as u64, amount)
}

pub fn calculate_deposit_fee(state_account: &StateAccount, amount: u64) -> u64 {
    calculate_percent_amount(state_account.deposit_fee_percent as u64, amount)
}

pub fn transfer_token<'info>(
    from: &AccountInfo<'info>,
    to: &AccountInfo<'info>,
    user: &AccountInfo<'info>,
    amount: u64,
    token_program: &AccountInfo<'info>,
) -> Result<()> {
    let transfer_instruction = Transfer {
        from: from.to_account_info(),
        to: to.to_account_info(),
        authority: user.to_account_info(),
    };
    anchor_spl::token::transfer(
        CpiContext::new(token_program.to_account_info(), transfer_instruction),
        amount,
    )?;

    Ok(())
}

pub fn transfer_sol<'info>(
    from: &AccountInfo<'info>,
    to: &AccountInfo<'info>,
    amount: u64,
    system_program: &AccountInfo<'info>,
) -> Result<()> {
    let ix =
        anchor_lang::solana_program::system_instruction::transfer(&from.key(), &to.key(), amount);
    anchor_lang::solana_program::program::invoke(
        &ix,
        &[from.clone(), to.clone(), system_program.clone()],
    )?;

    Ok(())
}

pub fn get_current_timestamp() -> Result<u64> {
    let clock = Clock::get()?;
    Ok(clock.unix_timestamp as u64)
}

pub fn calculate_treasury_share_internal(state_account: &StateAccount, amount: u64) -> u64 {
    calculate_percent_amount(state_account.treasury_distribution_percent as u64, amount)
}

Step 3: Update Cargo.toml

Ensure that you update at programs/stake/Cargo.toml

// Cargo.toml

[package]
name = "stake"
version = "0.1.0"
description = "Created with Anchor"
edition = "2021"

[lib]
crate-type = ["cdylib", "lib"]
name = "stake"

[features]
no-entrypoint = []
no-idl = []
no-log-ix-name = []
cpi = ["no-entrypoint"]
default = []

[dependencies]
anchor-lang = "0.30.0"
anchor-spl = "0.30.0"
bincode = "1.3.3"

Step 4: Install Dependencies

npm install

Step 5: Compile the Smart Contract

anchor build

Step 6: Display the list of Key Pairs

anchor keys list

Output is YOUR_SOLANA_PROGRAM_ID. Save it as it is used later at various instances.

stake: 8LHf4FmTPrXkPg9Jtgoexj64GTE8SbaSXNJuNeQBDSUG

Step 7: Declare PROGRAM_ID

Goto program/stake/src/lib.rs and update declare_id! with YOUR_SOLANA_PROGRAM_ID.

Below is the snapshot for your reference

declare_id!("YOUR_SOLANA_PROGRAM_ID");

Step 8: Configure Scripts

Create scripts folder and add below scripts to it.

  1. Script to Initiate Contract

  • Ensure that the connection is set to devnet

  • Add YOUR_SOLANA_PROGRAM_ID as programId

  • Add YOUR_TREASURY_CONTRACT_ADDRESS

  • Load the wallet keypair path (for the file id.json) as YOUR_KEYPAIR_PATH

  • Load idlString with the relative path to stake.json as ./target/idl/stake.json as YOUR_IDL_PATH

// initialize.ts

import * as anchor from "@coral-xyz/anchor";
import { Program, AnchorProvider, Wallet, web3, Idl, BN } from "@coral-xyz/anchor";
import { Connection, PublicKey, Keypair, SystemProgram, clusterApiUrl } from "@solana/web3.js";
import { createMint, getOrCreateAssociatedTokenAccount, mintTo } from "@solana/spl-token";
import fs from "fs";

// Set the connection to the Solana network
const connection = new Connection(clusterApiUrl('devnet'), "confirmed");

// Load the wallet keypair from the file
const walletKeypair = Keypair.fromSecretKey(
  new Uint8Array(JSON.parse(fs.readFileSync(process.env.DEPLOYER_KEY_PATH, "utf8")))
);
const wallet = new Wallet(walletKeypair);
const provider = new AnchorProvider(connection, wallet, AnchorProvider.defaultOptions());
anchor.setProvider(provider);

// Ensure the correct program ID and IDL are used
const programId = new PublicKey(process.env.STAKE_PROGRAM_ID);
const idlString = fs.readFileSync("./target/idl/stake.json", "utf8");
const idl = JSON.parse(idlString) as Idl;
const program = new Program(idl, programId, provider);

async function deriveStateAccountPDA(): Promise<PublicKey> {
  const [stateAccount, stateBump] = await PublicKey.findProgramAddress(
    [Buffer.from("state_account")],
    program.programId
  );
  console.log("Derived State Account:", stateAccount.toBase58());
  return stateAccount;
}

async function callInitialize() {
  const stateAccount = await deriveStateAccountPDA();

  return
  try {
    const txHash = await program.methods.initialize(
      "SOL", // Native symbol
      "Solana", // Native asset symbol
      9, // Native asset decimal
      new PublicKey('71qVXwNrvouQDXYB85oUFQJBqtyk2ZpWXmzqBUxE1fNs'), // Treasury contract
      10, // Treasury distribution percent
      5, // Deposit fee percent
      new BN(30), // Withdrawal penalty in days
      10 // Withdrawal penalty in percentage
    ).accounts({
      stateAccount: stateAccount,
      payer: provider.wallet.publicKey,
      systemProgram: web3.SystemProgram.programId,
      rent: web3.SYSVAR_RENT_PUBKEY,
    }).signers([walletKeypair])
      .rpc();

    console.log("Transaction hash:", txHash);

    // Optional: Wait for confirmation
    await connection.confirmTransaction(txHash);
  } catch (err) {
    console.error("Error calling initialize function:", err);

    if (err.logs) {
      err.logs.forEach(log => console.error(log));
    }
  }
}

// Example usage:
callInitialize().catch(err => console.error("Error calling initialize function:", err));
  1. Configure Anchor.toml

  • Update stake with YOUR_SOLANA_PROGRAM_ID

  • Update wallet with YOUR_KEYPAIR_PATH

  • Set path for initialize.ts script

// Anchor.toml

[toolchain]

[features]
seeds = false
skip-lint = false

[programs.devnet]
stake = "YOUR_SOLANA_PROGRAM_ID"

[registry]
url = "https://api.apr.dev"

[provider]
cluster = "devnet"
wallet = "YOUR_KEYPAIR_PATH"

[scripts]
test = "yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts"

//Set scripts path
initialize = "npx ts-node ./scripts/initialize.ts"

Step 9: Compile, Deploy and Run Solana Program

  1. Compile the Solana Program

anchor build
  1. Deploy the Solana Program

anchor deploy --provider.cluster devnet
  1. Run the Solana Program

anchor run initialize --provider.cluster devnet

Output contains YOUR_SOLANA_DERIVED_STATE_ACCOUNT and YOUR_SOLANA_TRANSACTION_HASH.

Last updated