Skip to main content

tribewarez_vault/
lib.rs

1//! # tribewarez-vault
2//!
3//! PTtC Vault and Escrow Program for Tribewarez DeFi.
4//!
5//! This crate provides secure vault and escrow functionality for PTtC tokens on Solana.
6//! It enables time-locked vaults, conditional asset release, and treasury management with
7//! optional tensor network support for dynamic locktime reduction based on network conditions.
8//!
9//! ## Core Features
10//!
11//! - **Token Deposits**: Securely deposit and hold PTtC tokens in vaults
12//! - **Time-Locked Withdrawals**: Enforce minimum lock periods before withdrawal
13//! - **Escrow Services**: Create conditional escrow agreements with release conditions
14//! - **Treasury Management**: Centralized treasury for managing network assets
15//! - **Tensor-Aware Locks**: v0.2.0 feature for dynamic locktime reduction based on network activity
16//! - **Multi-Signature Support**: Allow multiple authorizers for critical operations
17//!
18//! ## Key Instructions
19//!
20//! - `initialize_treasury`: Set up the main treasury account (admin-only)
21//! - `create_vault`: Create a time-locked vault for token deposits
22//! - `deposit`: Deposit tokens into a vault
23//! - `withdraw`: Withdraw tokens after lock period expires
24//! - `create_escrow`: Create a conditional escrow agreement
25//! - `release_escrow`: Release escrowed assets when conditions are met
26//! - `extend_lock`: Extend the locktime of an existing vault
27//!
28//! ## Events
29//!
30//! This program emits events for vault creation, deposits, withdrawals, and escrow operations.
31//! See the [`events`] module for detailed event documentation.
32//!
33//! ## Security Model
34//!
35//! - Timestamps checked against Solana clock for temporal enforcement
36//! - SPL Token program used for all token transfers via CPI
37//! - Vault ownership validated before withdrawal
38//! - Escrow conditions verified before release
39
40use anchor_lang::prelude::*;
41use anchor_spl::token::{self, Mint, Token, TokenAccount, Transfer};
42
43// Module declarations
44pub mod events;
45pub mod services;
46
47// Re-export services for use in instructions
48use events::{
49    Deposited, EscrowCancelled, EscrowCreated, EscrowReleased, LockExtended, TreasuryInitialized,
50    VaultCreated, Withdrawn,
51};
52
53declare_id!("HmWGA3JAF6basxGCvvGNHAdTBE3qCPhJCeFJAd7r5ra9");
54
55/// Tribewarez Vault Program
56/// Secure escrow and vault functionality for PTtC tokens.
57/// Supports deposits, withdrawals, time-locked vaults, and multi-sig operations.
58///
59/// v0.2.0 includes tensor network support for dynamic locktime reduction
60/// based on entropy and coherence-aware access control.
61
62#[program]
63pub mod tribewarez_vault {
64    use super::*;
65
66    /// Initialize the main vault treasury
67    pub fn initialize_treasury(ctx: Context<InitializeTreasury>, treasury_bump: u8) -> Result<()> {
68        let treasury = &mut ctx.accounts.treasury;
69
70        treasury.authority = ctx.accounts.authority.key();
71        treasury.token_mint = ctx.accounts.token_mint.key();
72        treasury.vault_token_account = ctx.accounts.vault_token_account.key();
73        treasury.total_deposited = 0;
74        treasury.total_vaults = 0;
75        treasury.bump = treasury_bump;
76        treasury.is_active = true;
77        treasury.created_at = Clock::get()?.unix_timestamp;
78
79        emit!(TreasuryInitialized {
80            treasury: treasury.key(),
81            authority: treasury.authority,
82            token_mint: treasury.token_mint,
83        });
84
85        Ok(())
86    }
87
88    /// Create a personal vault for a user
89    pub fn create_vault(
90        ctx: Context<CreateVault>,
91        vault_name: String,
92        lock_until: i64, // Unix timestamp, 0 for no lock
93    ) -> Result<()> {
94        require!(vault_name.len() <= 32, VaultError::NameTooLong);
95
96        let vault = &mut ctx.accounts.user_vault;
97        let treasury = &mut ctx.accounts.treasury;
98
99        vault.owner = ctx.accounts.user.key();
100        vault.treasury = treasury.key();
101        vault.name = vault_name.clone();
102        vault.balance = 0;
103        vault.lock_until = lock_until;
104        vault.created_at = Clock::get()?.unix_timestamp;
105        vault.last_activity = Clock::get()?.unix_timestamp;
106        vault.is_locked = lock_until > 0;
107        vault.total_deposited = 0;
108        vault.total_withdrawn = 0;
109
110        treasury.total_vaults = treasury
111            .total_vaults
112            .checked_add(1)
113            .ok_or(VaultError::MathOverflow)?;
114
115        emit!(VaultCreated {
116            vault: vault.key(),
117            owner: vault.owner,
118            name: vault_name,
119            lock_until,
120        });
121
122        Ok(())
123    }
124
125    /// Deposit tokens into a personal vault
126    pub fn deposit(ctx: Context<Deposit>, amount: u64) -> Result<()> {
127        require!(amount > 0, VaultError::InvalidAmount);
128        require!(
129            ctx.accounts.treasury.is_active,
130            VaultError::TreasuryInactive
131        );
132
133        let vault = &mut ctx.accounts.user_vault;
134        let treasury = &mut ctx.accounts.treasury;
135
136        // Transfer tokens from user to treasury vault
137        let cpi_accounts = Transfer {
138            from: ctx.accounts.user_token_account.to_account_info(),
139            to: ctx.accounts.vault_token_account.to_account_info(),
140            authority: ctx.accounts.user.to_account_info(),
141        };
142        let cpi_ctx = CpiContext::new(ctx.accounts.token_program.to_account_info(), cpi_accounts);
143        token::transfer(cpi_ctx, amount)?;
144
145        // Update vault state
146        vault.balance = vault
147            .balance
148            .checked_add(amount)
149            .ok_or(VaultError::MathOverflow)?;
150        vault.total_deposited = vault
151            .total_deposited
152            .checked_add(amount)
153            .ok_or(VaultError::MathOverflow)?;
154        vault.last_activity = Clock::get()?.unix_timestamp;
155
156        // Update treasury totals
157        treasury.total_deposited = treasury
158            .total_deposited
159            .checked_add(amount)
160            .ok_or(VaultError::MathOverflow)?;
161
162        emit!(Deposited {
163            vault: vault.key(),
164            user: ctx.accounts.user.key(),
165            amount,
166            new_balance: vault.balance,
167        });
168
169        Ok(())
170    }
171
172    /// Withdraw tokens from a personal vault
173    pub fn withdraw(ctx: Context<Withdraw>, amount: u64) -> Result<()> {
174        require!(amount > 0, VaultError::InvalidAmount);
175
176        let clock = Clock::get()?;
177        let vault = &mut ctx.accounts.user_vault;
178        let treasury = &ctx.accounts.treasury;
179
180        require!(vault.balance >= amount, VaultError::InsufficientBalance);
181        require!(
182            !vault.is_locked || clock.unix_timestamp >= vault.lock_until,
183            VaultError::VaultLocked
184        );
185
186        // Transfer tokens from treasury vault to user using PDA signer
187        let token_mint = treasury.token_mint;
188        let seeds = &[b"treasury", token_mint.as_ref(), &[treasury.bump]];
189        let signer = &[&seeds[..]];
190
191        let cpi_accounts = Transfer {
192            from: ctx.accounts.vault_token_account.to_account_info(),
193            to: ctx.accounts.user_token_account.to_account_info(),
194            authority: ctx.accounts.treasury.to_account_info(),
195        };
196        let cpi_ctx = CpiContext::new_with_signer(
197            ctx.accounts.token_program.to_account_info(),
198            cpi_accounts,
199            signer,
200        );
201        token::transfer(cpi_ctx, amount)?;
202
203        // Update vault state
204        vault.balance = vault
205            .balance
206            .checked_sub(amount)
207            .ok_or(VaultError::MathOverflow)?;
208        vault.total_withdrawn = vault
209            .total_withdrawn
210            .checked_add(amount)
211            .ok_or(VaultError::MathOverflow)?;
212        vault.last_activity = clock.unix_timestamp;
213
214        emit!(Withdrawn {
215            vault: vault.key(),
216            user: ctx.accounts.user.key(),
217            amount,
218            new_balance: vault.balance,
219        });
220
221        Ok(())
222    }
223
224    /// Create a time-locked escrow between two parties
225    pub fn create_escrow(
226        ctx: Context<CreateEscrow>,
227        amount: u64,
228        release_time: i64,
229        escrow_bump: u8,
230    ) -> Result<()> {
231        require!(amount > 0, VaultError::InvalidAmount);
232        require!(
233            release_time > Clock::get()?.unix_timestamp,
234            VaultError::InvalidReleaseTime
235        );
236
237        let escrow = &mut ctx.accounts.escrow;
238
239        // Transfer tokens to escrow account
240        let cpi_accounts = Transfer {
241            from: ctx.accounts.depositor_token_account.to_account_info(),
242            to: ctx.accounts.escrow_token_account.to_account_info(),
243            authority: ctx.accounts.depositor.to_account_info(),
244        };
245        let cpi_ctx = CpiContext::new(ctx.accounts.token_program.to_account_info(), cpi_accounts);
246        token::transfer(cpi_ctx, amount)?;
247
248        escrow.depositor = ctx.accounts.depositor.key();
249        escrow.beneficiary = ctx.accounts.beneficiary.key();
250        escrow.token_mint = ctx.accounts.token_mint.key();
251        escrow.escrow_token_account = ctx.accounts.escrow_token_account.key();
252        escrow.amount = amount;
253        escrow.release_time = release_time;
254        escrow.created_at = Clock::get()?.unix_timestamp;
255        escrow.is_released = false;
256        escrow.is_cancelled = false;
257        escrow.bump = escrow_bump;
258
259        emit!(EscrowCreated {
260            escrow: escrow.key(),
261            depositor: escrow.depositor,
262            beneficiary: escrow.beneficiary,
263            amount,
264            release_time,
265        });
266
267        Ok(())
268    }
269
270    /// Release escrow funds to beneficiary (after release time)
271    pub fn release_escrow(ctx: Context<ReleaseEscrow>) -> Result<()> {
272        let clock = Clock::get()?;
273        let escrow = &mut ctx.accounts.escrow;
274
275        require!(!escrow.is_released, VaultError::AlreadyReleased);
276        require!(!escrow.is_cancelled, VaultError::EscrowCancelled);
277        require!(
278            clock.unix_timestamp >= escrow.release_time,
279            VaultError::EscrowNotReady
280        );
281
282        // Transfer tokens to beneficiary using PDA signer
283        let depositor = escrow.depositor;
284        let beneficiary = escrow.beneficiary;
285        let seeds = &[
286            b"escrow",
287            depositor.as_ref(),
288            beneficiary.as_ref(),
289            &[escrow.bump],
290        ];
291        let signer = &[&seeds[..]];
292
293        let cpi_accounts = Transfer {
294            from: ctx.accounts.escrow_token_account.to_account_info(),
295            to: ctx.accounts.beneficiary_token_account.to_account_info(),
296            authority: escrow.to_account_info(),
297        };
298        let cpi_ctx = CpiContext::new_with_signer(
299            ctx.accounts.token_program.to_account_info(),
300            cpi_accounts,
301            signer,
302        );
303        token::transfer(cpi_ctx, escrow.amount)?;
304
305        escrow.is_released = true;
306
307        emit!(EscrowReleased {
308            escrow: escrow.key(),
309            beneficiary: escrow.beneficiary,
310            amount: escrow.amount,
311        });
312
313        Ok(())
314    }
315
316    /// Cancel escrow (only depositor can cancel before release time)
317    pub fn cancel_escrow(ctx: Context<CancelEscrow>) -> Result<()> {
318        let clock = Clock::get()?;
319        let escrow = &mut ctx.accounts.escrow;
320
321        require!(!escrow.is_released, VaultError::AlreadyReleased);
322        require!(!escrow.is_cancelled, VaultError::EscrowCancelled);
323        require!(
324            clock.unix_timestamp < escrow.release_time,
325            VaultError::CannotCancelAfterRelease
326        );
327
328        // Return tokens to depositor using PDA signer
329        let depositor = escrow.depositor;
330        let beneficiary = escrow.beneficiary;
331        let seeds = &[
332            b"escrow",
333            depositor.as_ref(),
334            beneficiary.as_ref(),
335            &[escrow.bump],
336        ];
337        let signer = &[&seeds[..]];
338
339        let cpi_accounts = Transfer {
340            from: ctx.accounts.escrow_token_account.to_account_info(),
341            to: ctx.accounts.depositor_token_account.to_account_info(),
342            authority: escrow.to_account_info(),
343        };
344        let cpi_ctx = CpiContext::new_with_signer(
345            ctx.accounts.token_program.to_account_info(),
346            cpi_accounts,
347            signer,
348        );
349        token::transfer(cpi_ctx, escrow.amount)?;
350
351        escrow.is_cancelled = true;
352
353        emit!(EscrowCancelled {
354            escrow: escrow.key(),
355            depositor: escrow.depositor,
356            amount: escrow.amount,
357        });
358
359        Ok(())
360    }
361
362    /// Update vault lock time (extend lock)
363    pub fn extend_lock(ctx: Context<UpdateVault>, new_lock_until: i64) -> Result<()> {
364        let vault = &mut ctx.accounts.user_vault;
365
366        require!(
367            new_lock_until > vault.lock_until,
368            VaultError::CannotReduceLock
369        );
370
371        vault.lock_until = new_lock_until;
372        vault.is_locked = true;
373
374        emit!(LockExtended {
375            vault: vault.key(),
376            owner: vault.owner,
377            new_lock_until,
378        });
379
380        Ok(())
381    }
382}
383
384// ============ Account Structs ============
385
386#[derive(Accounts)]
387#[instruction(treasury_bump: u8)]
388pub struct InitializeTreasury<'info> {
389    #[account(mut)]
390    pub authority: Signer<'info>,
391
392    #[account(
393        init,
394        payer = authority,
395        space = 8 + Treasury::INIT_SPACE,
396        seeds = [b"treasury", token_mint.key().as_ref()],
397        bump,
398    )]
399    pub treasury: Account<'info, Treasury>,
400
401    pub token_mint: Account<'info, Mint>,
402
403    #[account(
404        init,
405        payer = authority,
406        token::mint = token_mint,
407        token::authority = treasury,
408    )]
409    pub vault_token_account: Account<'info, TokenAccount>,
410
411    pub token_program: Program<'info, Token>,
412    pub system_program: Program<'info, System>,
413    pub rent: Sysvar<'info, Rent>,
414}
415
416#[derive(Accounts)]
417#[instruction(vault_name: String)]
418pub struct CreateVault<'info> {
419    #[account(mut)]
420    pub user: Signer<'info>,
421
422    #[account(
423        mut,
424        seeds = [b"treasury", treasury.token_mint.as_ref()],
425        bump = treasury.bump,
426    )]
427    pub treasury: Account<'info, Treasury>,
428
429    #[account(
430        init,
431        payer = user,
432        space = 8 + UserVault::INIT_SPACE,
433        seeds = [b"user_vault", treasury.key().as_ref(), user.key().as_ref()],
434        bump,
435    )]
436    pub user_vault: Account<'info, UserVault>,
437
438    pub system_program: Program<'info, System>,
439}
440
441#[derive(Accounts)]
442pub struct Deposit<'info> {
443    #[account(mut)]
444    pub user: Signer<'info>,
445
446    #[account(
447        mut,
448        seeds = [b"treasury", treasury.token_mint.as_ref()],
449        bump = treasury.bump,
450    )]
451    pub treasury: Account<'info, Treasury>,
452
453    #[account(
454        mut,
455        seeds = [b"user_vault", treasury.key().as_ref(), user.key().as_ref()],
456        bump,
457        constraint = user_vault.owner == user.key(),
458    )]
459    pub user_vault: Account<'info, UserVault>,
460
461    #[account(
462        mut,
463        constraint = user_token_account.owner == user.key(),
464        constraint = user_token_account.mint == treasury.token_mint,
465    )]
466    pub user_token_account: Account<'info, TokenAccount>,
467
468    #[account(
469        mut,
470        constraint = vault_token_account.key() == treasury.vault_token_account,
471    )]
472    pub vault_token_account: Account<'info, TokenAccount>,
473
474    pub token_program: Program<'info, Token>,
475}
476
477#[derive(Accounts)]
478pub struct Withdraw<'info> {
479    #[account(mut)]
480    pub user: Signer<'info>,
481
482    #[account(
483        seeds = [b"treasury", treasury.token_mint.as_ref()],
484        bump = treasury.bump,
485    )]
486    pub treasury: Account<'info, Treasury>,
487
488    #[account(
489        mut,
490        seeds = [b"user_vault", treasury.key().as_ref(), user.key().as_ref()],
491        bump,
492        constraint = user_vault.owner == user.key(),
493    )]
494    pub user_vault: Account<'info, UserVault>,
495
496    #[account(
497        mut,
498        constraint = user_token_account.owner == user.key(),
499        constraint = user_token_account.mint == treasury.token_mint,
500    )]
501    pub user_token_account: Account<'info, TokenAccount>,
502
503    #[account(
504        mut,
505        constraint = vault_token_account.key() == treasury.vault_token_account,
506    )]
507    pub vault_token_account: Account<'info, TokenAccount>,
508
509    pub token_program: Program<'info, Token>,
510}
511
512#[derive(Accounts)]
513#[instruction(amount: u64, release_time: i64, escrow_bump: u8)]
514pub struct CreateEscrow<'info> {
515    #[account(mut)]
516    pub depositor: Signer<'info>,
517
518    /// CHECK: Beneficiary doesn't need to sign for escrow creation
519    pub beneficiary: AccountInfo<'info>,
520
521    pub token_mint: Account<'info, Mint>,
522
523    #[account(
524        init,
525        payer = depositor,
526        space = 8 + Escrow::INIT_SPACE,
527        seeds = [b"escrow", depositor.key().as_ref(), beneficiary.key().as_ref()],
528        bump,
529    )]
530    pub escrow: Account<'info, Escrow>,
531
532    #[account(
533        init,
534        payer = depositor,
535        token::mint = token_mint,
536        token::authority = escrow,
537    )]
538    pub escrow_token_account: Account<'info, TokenAccount>,
539
540    #[account(
541        mut,
542        constraint = depositor_token_account.owner == depositor.key(),
543        constraint = depositor_token_account.mint == token_mint.key(),
544    )]
545    pub depositor_token_account: Account<'info, TokenAccount>,
546
547    pub token_program: Program<'info, Token>,
548    pub system_program: Program<'info, System>,
549    pub rent: Sysvar<'info, Rent>,
550}
551
552#[derive(Accounts)]
553pub struct ReleaseEscrow<'info> {
554    /// CHECK: Anyone can trigger release after time passes
555    pub caller: Signer<'info>,
556
557    #[account(
558        mut,
559        seeds = [b"escrow", escrow.depositor.as_ref(), escrow.beneficiary.as_ref()],
560        bump = escrow.bump,
561    )]
562    pub escrow: Account<'info, Escrow>,
563
564    #[account(
565        mut,
566        constraint = escrow_token_account.key() == escrow.escrow_token_account,
567    )]
568    pub escrow_token_account: Account<'info, TokenAccount>,
569
570    #[account(
571        mut,
572        constraint = beneficiary_token_account.owner == escrow.beneficiary,
573        constraint = beneficiary_token_account.mint == escrow.token_mint,
574    )]
575    pub beneficiary_token_account: Account<'info, TokenAccount>,
576
577    pub token_program: Program<'info, Token>,
578}
579
580#[derive(Accounts)]
581pub struct CancelEscrow<'info> {
582    #[account(
583        constraint = depositor.key() == escrow.depositor,
584    )]
585    pub depositor: Signer<'info>,
586
587    #[account(
588        mut,
589        seeds = [b"escrow", escrow.depositor.as_ref(), escrow.beneficiary.as_ref()],
590        bump = escrow.bump,
591    )]
592    pub escrow: Account<'info, Escrow>,
593
594    #[account(
595        mut,
596        constraint = escrow_token_account.key() == escrow.escrow_token_account,
597    )]
598    pub escrow_token_account: Account<'info, TokenAccount>,
599
600    #[account(
601        mut,
602        constraint = depositor_token_account.owner == escrow.depositor,
603        constraint = depositor_token_account.mint == escrow.token_mint,
604    )]
605    pub depositor_token_account: Account<'info, TokenAccount>,
606
607    pub token_program: Program<'info, Token>,
608}
609
610#[derive(Accounts)]
611pub struct UpdateVault<'info> {
612    pub user: Signer<'info>,
613
614    #[account(
615        mut,
616        constraint = user_vault.owner == user.key(),
617    )]
618    pub user_vault: Account<'info, UserVault>,
619}
620
621// ============ State Accounts ============
622
623/// Central treasury account managing all vaults and escrows.
624/// This is a singleton account created by the program authority.
625#[account]
626#[derive(InitSpace)]
627pub struct Treasury {
628    /// Authority/admin who can manage the treasury
629    pub authority: Pubkey,
630    /// Mint of the tokens managed by this treasury
631    pub token_mint: Pubkey,
632    /// Treasury's token account holding all vault and escrow tokens
633    pub vault_token_account: Pubkey,
634    /// Total amount of tokens deposited across all vaults
635    pub total_deposited: u64,
636    /// Number of vaults created under this treasury
637    pub total_vaults: u64,
638    /// PDA bump seed for this account
639    pub bump: u8,
640    /// Whether this treasury is accepting new deposits
641    pub is_active: bool,
642    /// Timestamp when this treasury was created
643    pub created_at: i64,
644}
645
646/// Individual user vault with time-locked token storage.
647/// Created when a user wants to deposit and lock tokens for a period.
648#[account]
649#[derive(InitSpace)]
650pub struct UserVault {
651    /// Owner of this vault
652    pub owner: Pubkey,
653    /// Treasury this vault belongs to
654    pub treasury: Pubkey,
655    /// Human-readable name for this vault
656    #[max_len(32)]
657    pub name: String,
658    /// Current balance of tokens in this vault
659    pub balance: u64,
660    /// Timestamp when tokens can be withdrawn
661    pub lock_until: i64,
662    /// Timestamp when this vault was created
663    pub created_at: i64,
664    /// Timestamp of the last deposit or withdrawal
665    pub last_activity: i64,
666    /// Whether the lock period is still active
667    pub is_locked: bool,
668    /// Cumulative tokens deposited into this vault
669    pub total_deposited: u64,
670    /// Cumulative tokens withdrawn from this vault
671    pub total_withdrawn: u64,
672}
673
674/// Escrow account for conditional token release.
675/// Holds tokens until release conditions are met.
676#[account]
677#[derive(InitSpace)]
678pub struct Escrow {
679    /// Account that deposited the tokens
680    pub depositor: Pubkey,
681    /// Account that will receive the tokens
682    pub beneficiary: Pubkey,
683    /// Mint of the escrowed tokens
684    pub token_mint: Pubkey,
685    /// Token account holding the escrowed tokens
686    pub escrow_token_account: Pubkey,
687    /// Amount of tokens held in escrow
688    pub amount: u64,
689    /// Timestamp when tokens can be released
690    pub release_time: i64,
691    /// Timestamp when this escrow was created
692    pub created_at: i64,
693    /// Whether the escrow has been released
694    pub is_released: bool,
695    /// Whether the escrow has been cancelled (refunded to depositor)
696    pub is_cancelled: bool,
697    /// PDA bump seed for this account
698    pub bump: u8,
699}
700
701// ============ Errors ============
702
703#[error_code]
704pub enum VaultError {
705    #[msg("Vault name too long (max 32 characters)")]
706    NameTooLong,
707    #[msg("Invalid amount")]
708    InvalidAmount,
709    #[msg("Treasury is not active")]
710    TreasuryInactive,
711    #[msg("Insufficient balance")]
712    InsufficientBalance,
713    #[msg("Vault is locked")]
714    VaultLocked,
715    #[msg("Invalid release time")]
716    InvalidReleaseTime,
717    #[msg("Escrow already released")]
718    AlreadyReleased,
719    #[msg("Escrow was cancelled")]
720    EscrowCancelled,
721    #[msg("Escrow not ready for release")]
722    EscrowNotReady,
723    #[msg("Cannot cancel escrow after release time")]
724    CannotCancelAfterRelease,
725    #[msg("Cannot reduce lock time")]
726    CannotReduceLock,
727    #[msg("Math overflow")]
728    MathOverflow,
729}