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
Create a New Directory
mkdir solana-stake-project
cd solana-stake-project
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/
lib.rs
account_structs.rs
error.rs
events.rs
instructions.rs
state.rs
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)
}
}
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>,
}
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,
}
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,
}
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)
}
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
}
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.
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));
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
Compile the Solana Program
anchor build
Deploy the Solana Program
anchor deploy --provider.cluster devnet
Run the Solana Program
anchor run initialize --provider.cluster devnet
Output contains YOUR_SOLANA_DERIVED_STATE_ACCOUNT and YOUR_SOLANA_TRANSACTION_HASH.
Last updated