1use anchor_lang::prelude::*;
37use anchor_spl::token::{self, Token, Transfer};
38
39pub mod events;
41pub mod services;
42
43use events::{PoolInitialized, PoolUpdated, RewardsClaimed, Staked, Unstaked};
45
46declare_id!("Go2BZRhNLoaVni3QunrKPAXYdHtwZtTXuVspxpdAeDS8");
47
48#[program]
56pub mod tribewarez_staking {
57 use super::*;
58
59 pub fn initialize_pool(
61 ctx: Context<InitializePool>,
62 reward_rate: u64, lock_duration: i64, ) -> Result<()> {
65 let pool = &mut ctx.accounts.staking_pool;
66
67 pool.authority = ctx.accounts.authority.key();
68 pool.token_mint = ctx.accounts.token_mint.key();
69 pool.reward_mint = ctx.accounts.reward_mint.key();
70 pool.pool_token_account = ctx.accounts.pool_token_account.key();
71 pool.reward_token_account = ctx.accounts.reward_token_account.key();
72 pool.reward_rate = reward_rate;
73 pool.lock_duration = lock_duration;
74 pool.total_staked = 0;
75 pool.total_rewards_distributed = 0;
76 pool.bump = ctx.bumps.staking_pool;
77 pool.is_active = true;
78 pool.created_at = Clock::get()?.unix_timestamp;
79
80 emit!(PoolInitialized {
81 pool: pool.key(),
82 authority: pool.authority,
83 token_mint: pool.token_mint,
84 reward_rate,
85 lock_duration,
86 });
87
88 Ok(())
89 }
90
91 pub fn stake(ctx: Context<Stake>, amount: u64) -> Result<()> {
93 require!(amount > 0, StakingError::InvalidAmount);
94 require!(
95 ctx.accounts.staking_pool.is_active,
96 StakingError::PoolInactive
97 );
98
99 let clock = Clock::get()?;
100 let stake_account = &mut ctx.accounts.stake_account;
101 let pool = &mut ctx.accounts.staking_pool;
102
103 if stake_account.amount > 0 {
105 let pending = calculate_rewards(
106 stake_account.amount,
107 stake_account.last_reward_time,
108 clock.unix_timestamp,
109 pool.reward_rate,
110 )?;
111 stake_account.pending_rewards = stake_account
112 .pending_rewards
113 .checked_add(pending)
114 .ok_or(StakingError::MathOverflow)?;
115 }
116
117 let cpi_accounts = Transfer {
119 from: ctx.accounts.user_token_account.to_account_info(),
120 to: ctx.accounts.pool_token_account.to_account_info(),
121 authority: ctx.accounts.user.to_account_info(),
122 };
123 token::transfer(
124 CpiContext::new(ctx.accounts.token_program.to_account_info(), cpi_accounts),
125 amount,
126 )?;
127
128 stake_account.owner = ctx.accounts.user.key();
130 stake_account.pool = pool.key();
131 stake_account.amount = stake_account
132 .amount
133 .checked_add(amount)
134 .ok_or(StakingError::MathOverflow)?;
135 stake_account.stake_time = clock.unix_timestamp;
136 stake_account.last_reward_time = clock.unix_timestamp;
137 stake_account.unlock_time = clock.unix_timestamp + pool.lock_duration;
138
139 pool.total_staked = pool
141 .total_staked
142 .checked_add(amount)
143 .ok_or(StakingError::MathOverflow)?;
144
145 emit!(Staked {
146 user: ctx.accounts.user.key(),
147 pool: pool.key(),
148 amount,
149 total_staked: stake_account.amount,
150 unlock_time: stake_account.unlock_time,
151 });
152
153 Ok(())
154 }
155
156 pub fn unstake(ctx: Context<Unstake>, amount: u64) -> Result<()> {
158 require!(amount > 0, StakingError::InvalidAmount);
159
160 let clock = Clock::get()?;
161 let pool_account_info = ctx.accounts.staking_pool.to_account_info();
164 let stake_account = &mut ctx.accounts.stake_account;
165 let pool = &mut ctx.accounts.staking_pool;
166
167 require!(
168 stake_account.amount >= amount,
169 StakingError::InsufficientStake
170 );
171 require!(
172 clock.unix_timestamp >= stake_account.unlock_time,
173 StakingError::StillLocked
174 );
175
176 let pending = calculate_rewards(
178 stake_account.amount,
179 stake_account.last_reward_time,
180 clock.unix_timestamp,
181 pool.reward_rate,
182 )?;
183 stake_account.pending_rewards = stake_account
184 .pending_rewards
185 .checked_add(pending)
186 .ok_or(StakingError::MathOverflow)?;
187 stake_account.last_reward_time = clock.unix_timestamp;
188
189 let token_mint = pool.token_mint;
191 let seeds = &[b"staking_pool", token_mint.as_ref(), &[pool.bump]];
192 let signer = &[&seeds[..]];
193
194 let cpi_accounts = Transfer {
195 from: ctx.accounts.pool_token_account.to_account_info(),
196 to: ctx.accounts.user_token_account.to_account_info(),
197 authority: pool_account_info,
198 };
199 token::transfer(
200 CpiContext::new_with_signer(
201 ctx.accounts.token_program.to_account_info(),
202 cpi_accounts,
203 signer,
204 ),
205 amount,
206 )?;
207
208 stake_account.amount = stake_account
210 .amount
211 .checked_sub(amount)
212 .ok_or(StakingError::MathOverflow)?;
213
214 pool.total_staked = pool
216 .total_staked
217 .checked_sub(amount)
218 .ok_or(StakingError::MathOverflow)?;
219
220 emit!(Unstaked {
221 user: ctx.accounts.user.key(),
222 pool: pool.key(),
223 amount,
224 remaining_stake: stake_account.amount,
225 });
226
227 Ok(())
228 }
229
230 pub fn claim_rewards(ctx: Context<ClaimRewards>) -> Result<()> {
232 let clock = Clock::get()?;
233 let pool_account_info = ctx.accounts.staking_pool.to_account_info();
236 let stake_account = &mut ctx.accounts.stake_account;
237 let pool = &mut ctx.accounts.staking_pool;
238
239 let pending = calculate_rewards(
241 stake_account.amount,
242 stake_account.last_reward_time,
243 clock.unix_timestamp,
244 pool.reward_rate,
245 )?;
246
247 let total_rewards = stake_account
248 .pending_rewards
249 .checked_add(pending)
250 .ok_or(StakingError::MathOverflow)?;
251
252 require!(total_rewards > 0, StakingError::NoRewardsToClaim);
253
254 let token_mint = pool.token_mint;
256 let seeds = &[b"staking_pool", token_mint.as_ref(), &[pool.bump]];
257 let signer = &[&seeds[..]];
258
259 let cpi_accounts = Transfer {
260 from: ctx.accounts.reward_token_account.to_account_info(),
261 to: ctx.accounts.user_reward_account.to_account_info(),
262 authority: pool_account_info,
263 };
264 token::transfer(
265 CpiContext::new_with_signer(
266 ctx.accounts.token_program.to_account_info(),
267 cpi_accounts,
268 signer,
269 ),
270 total_rewards,
271 )?;
272
273 stake_account.pending_rewards = 0;
275 stake_account.last_reward_time = clock.unix_timestamp;
276 stake_account.total_rewards_claimed = stake_account
277 .total_rewards_claimed
278 .checked_add(total_rewards)
279 .ok_or(StakingError::MathOverflow)?;
280
281 pool.total_rewards_distributed = pool
282 .total_rewards_distributed
283 .checked_add(total_rewards)
284 .ok_or(StakingError::MathOverflow)?;
285
286 emit!(RewardsClaimed {
287 user: ctx.accounts.user.key(),
288 pool: pool.key(),
289 amount: total_rewards,
290 total_claimed: stake_account.total_rewards_claimed,
291 });
292
293 Ok(())
294 }
295
296 pub fn update_pool(
298 ctx: Context<UpdatePool>,
299 new_reward_rate: Option<u64>,
300 new_lock_duration: Option<i64>,
301 is_active: Option<bool>,
302 ) -> Result<()> {
303 let pool = &mut ctx.accounts.staking_pool;
304
305 if let Some(rate) = new_reward_rate {
306 pool.reward_rate = rate;
307 }
308 if let Some(duration) = new_lock_duration {
309 pool.lock_duration = duration;
310 }
311 if let Some(active) = is_active {
312 pool.is_active = active;
313 }
314
315 emit!(PoolUpdated {
316 pool: pool.key(),
317 reward_rate: pool.reward_rate,
318 lock_duration: pool.lock_duration,
319 is_active: pool.is_active,
320 });
321
322 Ok(())
323 }
324}
325
326fn calculate_rewards(
329 staked_amount: u64,
330 last_reward_time: i64,
331 current_time: i64,
332 reward_rate: u64,
333) -> Result<u64> {
334 let time_elapsed = (current_time - last_reward_time) as u64;
335
336 let rewards = (staked_amount as u128)
338 .checked_mul(time_elapsed as u128)
339 .ok_or(StakingError::MathOverflow)?
340 .checked_mul(reward_rate as u128)
341 .ok_or(StakingError::MathOverflow)?
342 .checked_div(10000 * 86400)
343 .ok_or(StakingError::MathOverflow)?;
344
345 Ok(rewards as u64)
346}
347
348#[derive(Accounts)]
351pub struct InitializePool<'info> {
352 #[account(mut)]
353 pub authority: Signer<'info>,
354
355 #[account(
356 init,
357 payer = authority,
358 space = 8 + StakingPool::INIT_SPACE,
359 seeds = [b"staking_pool", token_mint.key().as_ref()],
360 bump,
361 )]
362 pub staking_pool: Account<'info, StakingPool>,
363
364 pub token_mint: AccountInfo<'info>,
366 pub reward_mint: AccountInfo<'info>,
368
369 #[account(mut)]
371 pub pool_token_account: AccountInfo<'info>,
372
373 #[account(mut)]
375 pub reward_token_account: AccountInfo<'info>,
376
377 pub token_program: Program<'info, Token>,
379 pub system_program: Program<'info, System>,
380}
381
382#[derive(Accounts)]
383pub struct Stake<'info> {
384 #[account(mut)]
385 pub user: Signer<'info>,
386
387 #[account(
388 mut,
389 seeds = [b"staking_pool", staking_pool.token_mint.as_ref()],
390 bump = staking_pool.bump,
391 )]
392 pub staking_pool: Account<'info, StakingPool>,
393
394 #[account(
395 init_if_needed,
396 payer = user,
397 space = 8 + StakeAccount::INIT_SPACE,
398 seeds = [b"stake", staking_pool.key().as_ref(), user.key().as_ref()],
399 bump,
400 )]
401 pub stake_account: Account<'info, StakeAccount>,
402
403 #[account(mut)]
405 pub user_token_account: AccountInfo<'info>,
406
407 #[account(mut)]
409 pub pool_token_account: AccountInfo<'info>,
410
411 pub token_program: Program<'info, Token>,
412 pub system_program: Program<'info, System>,
413}
414
415#[derive(Accounts)]
416pub struct Unstake<'info> {
417 #[account(mut)]
418 pub user: Signer<'info>,
419
420 #[account(
421 mut,
422 seeds = [b"staking_pool", staking_pool.token_mint.as_ref()],
423 bump = staking_pool.bump,
424 )]
425 pub staking_pool: Account<'info, StakingPool>,
426
427 #[account(
428 mut,
429 seeds = [b"stake", staking_pool.key().as_ref(), user.key().as_ref()],
430 bump,
431 constraint = stake_account.owner == user.key(),
432 )]
433 pub stake_account: Account<'info, StakeAccount>,
434
435 #[account(mut)]
437 pub user_token_account: AccountInfo<'info>,
438
439 #[account(mut)]
441 pub pool_token_account: AccountInfo<'info>,
442
443 pub token_program: Program<'info, Token>,
444}
445
446#[derive(Accounts)]
447pub struct ClaimRewards<'info> {
448 #[account(mut)]
449 pub user: Signer<'info>,
450
451 #[account(
452 mut,
453 seeds = [b"staking_pool", staking_pool.token_mint.as_ref()],
454 bump = staking_pool.bump,
455 )]
456 pub staking_pool: Account<'info, StakingPool>,
457
458 #[account(
459 mut,
460 seeds = [b"stake", staking_pool.key().as_ref(), user.key().as_ref()],
461 bump,
462 constraint = stake_account.owner == user.key(),
463 )]
464 pub stake_account: Account<'info, StakeAccount>,
465
466 #[account(mut)]
468 pub user_reward_account: AccountInfo<'info>,
469
470 #[account(mut)]
472 pub reward_token_account: AccountInfo<'info>,
473
474 pub token_program: Program<'info, Token>,
475}
476
477#[derive(Accounts)]
478pub struct UpdatePool<'info> {
479 #[account(
480 constraint = authority.key() == staking_pool.authority,
481 )]
482 pub authority: Signer<'info>,
483
484 #[account(
485 mut,
486 seeds = [b"staking_pool", staking_pool.token_mint.as_ref()],
487 bump = staking_pool.bump,
488 )]
489 pub staking_pool: Account<'info, StakingPool>,
490}
491
492#[account]
497#[derive(InitSpace)]
498pub struct StakingPool {
499 pub authority: Pubkey,
501 pub token_mint: Pubkey,
503 pub reward_mint: Pubkey,
505 pub pool_token_account: Pubkey,
507 pub reward_token_account: Pubkey,
509 pub reward_rate: u64,
511 pub lock_duration: i64,
513 pub total_staked: u64,
515 pub total_rewards_distributed: u64,
517 pub bump: u8,
519 pub is_active: bool,
521 pub created_at: i64,
523
524 pub tensor_enabled: u8, pub s_max: u64, pub entropy_weight: u64, pub total_entangled_stakes: u32, pub total_pool_entropy: u64, pub average_coherence: u64, }
538
539#[account]
542#[derive(InitSpace)]
543pub struct StakeAccount {
544 pub owner: Pubkey,
546 pub pool: Pubkey,
548 pub amount: u64,
550 pub stake_time: i64,
552 pub unlock_time: i64,
554 pub last_reward_time: i64,
556 pub pending_rewards: u64,
558 pub total_rewards_claimed: u64,
560
561 pub entropy_score: u64, pub coherence: u64, pub pool_id: u32, pub last_entropy_update: i64, pub unlock_probability: u64, pub coherence_bonus: u64, }
575
576#[error_code]
579pub enum StakingError {
580 #[msg("Invalid amount")]
581 InvalidAmount,
582 #[msg("Pool is not active")]
583 PoolInactive,
584 #[msg("Insufficient stake balance")]
585 InsufficientStake,
586 #[msg("Tokens are still locked")]
587 StillLocked,
588 #[msg("No rewards to claim")]
589 NoRewardsToClaim,
590 #[msg("No stake to withdraw")]
591 NoStake,
592 #[msg("Math overflow")]
593 MathOverflow,
594}