Copy // 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)
}