Sol.College

Full lesson

solana-contract

What it does

From the project README:

PersonalVault is a decentralized personal asset management contract built on Solana, enabling users to securely store, manage, and trade multiple token assets. Developed using the Anchor framework, the contract provides comprehensive asset management and DEX integration capabilities.
Original
TradingFlow-OpenSource/solana-contract
SuperStack fork
Blockchains/solana-contract @ 03d31f3
Stars
0
Last push (original)
2025-10-27
Languages
TypeScript, Rust, Shell
Detected stack
Anchorweb3.js
Categories
Other
Licence
MIT

Architecture

12 source files. By extension: .rs ×8, .ts ×4.

Top-level directorySource files
PersonalVault12

Dapp: devnet deploy pending

⏳ Devnet deploy pending — goes live when the devnet wallet is topped up. This fork's own dapp is not deployed yet, so there is no program address or transaction to show. It is queued in dapp-queue.txt; once devnet SOL is available it is deployed, exercised with real devnet transactions, and this section switches to the live dapp at /dapps/solana-contract/. Until then, the lesson and step-by-step guide below are complete, and you can run it locally with the commands below.

Key code, explained

Excerpts chosen automatically where the code uses the most Solana concepts, pinned to the fork's commit.

TradingFlow-OpenSource/solana-contract · PersonalVault/src/instructions.rs L229–252 @ 03d31f3 · MIT · original
        let cpi_ctx = CpiContext::new(            ctx.accounts.token_program.to_account_info(),            transfer_instruction,        );         token::transfer(cpi_ctx, amount)?;         msg!("代币转移成功,更新内部余额记录...");         // 更新内部余额记录        let current_balance = get_token_balance(vault, ctx.accounts.mint.key());        msg!("当前余额记录: {}", current_balance);         let new_balance = current_balance.checked_add(amount)            .ok_or(ErrorCode::MathOverflow)?;        set_token_balance(vault, ctx.accounts.mint.key(), new_balance);        msg!("更新后余额记录: {}", new_balance);         emit!(UserDepositEvent {            user: ctx.accounts.user.key(),            token: ctx.accounts.mint.key(),            asset_metadata: ctx.accounts.mint.key(), // 对应 Aptos 的 Object<Metadata            amount,            timestamp: Clock::get()?.unix_timestamp,
  1. Line 229 let cpi_ctx = CpiContext::new(: Makes a cross-program invocation into another program.
  2. Line 234 token::transfer(cpi_ctx, amount)?;: An SPL token transfer through the Token Program.
  3. Line 236 msg!("代币转移成功,更新内部余额记录...");: Logs to the program log, which is visible in explorers and the test output.
  4. Line 242 let new_balance = current_balance.checked_add(amount): Uses checked arithmetic so overflow fails instead of wrapping.
  5. Line 247 emit!(UserDepositEvent {: Emits an Anchor event that clients can parse from the logs.
  6. Line 252 timestamp: Clock::get()?.unix_timestamp,: Reads on-chain time from the Clock sysvar.
TradingFlow-OpenSource/solana-contract · PersonalVault/src/structs.rs L37–60 @ 03d31f3 · MIT · original
    /// PDA bump seed    pub bump: u8,} /// 初始化全局配置上下文#[derive(Accounts)]pub struct InitializeGlobalConfig<'info> {    #[account(        init,        payer = admin,        space = 8 + 32 + 32 + 32 + 32 + 1,        seeds = [b"global_config"],        bump    )]    pub global_config: Account<'info, GlobalConfig>,     #[account(mut)]    pub admin: Signer<'info>,     pub system_program: Program<'info, System>,} /// 创建余额管理器上下文#[derive(Accounts)]
  1. Line 37 /// PDA bump seed: Uses the bump seed, the extra byte that pushes the derived address off the ed25519 curve.
  2. Line 45 init,: Creates and pays for a new account. Space and payer must be specified.
  3. Line 48 seeds = [b"global_config"],: Declares the PDA seeds. Anchor re-derives the address and rejects the account if it doesn't match.
  4. Line 54 pub admin: Signer<'info>,: Requires this account to have signed the transaction.
TradingFlow-OpenSource/solana-contract · PersonalVault/src/dex/raydium_clmm.rs L85–108 @ 03d31f3 · MIT · original
                msg!("✅ 确认为临时 WSOL 账户");        msg!("  Owner: {}", token_acc.owner);        msg!("  Mint: {}", token_acc.mint);        msg!("  Amount: {}", token_acc.amount);                Ok(true)    }     /// 关闭临时 WSOL 账户    ///     /// 使用 CPI 调用 Token Program 的 CloseAccount 指令    fn close_temp_wsol_account<'info>(        temp_account: &AccountInfo<'info>,        destination: &AccountInfo<'info>,        authority: &AccountInfo<'info>,        signer_seeds: &[&[&[u8]]],    ) -> Result<()> {        msg!("🔧 关闭临时 WSOL 账户...");        msg!("  临时账户: {}", temp_account.key());        msg!("  租金接收者: {}", destination.key());        msg!("  权限账户: {}", authority.key());                use anchor_lang::solana_program::program::invoke_signed;
  1. Line 86 msg!("✅ 确认为临时 WSOL 账户");: Logs to the program log, which is visible in explorers and the test output.
  2. Line 108 use anchor_lang::solana_program::program::invoke_signed;: Makes a CPI signed by a PDA. The signer seeds prove the program controls that address.
TradingFlow-OpenSource/solana-contract · PersonalVault/src/dex/raydium_amm.rs L145–168 @ 03d31f3 · MIT · original
        msg!("  程序 ID: {}", instruction.program_id);        msg!("  账户数量: {}", instruction.accounts.len());        msg!("  数据大小: {} 字节", instruction.data.len());         // 🔧 验证 AccountInfo 数组        msg!("🔧 验证 CPI 调用所需的 AccountInfo 数组...");                if account_infos.len() < instruction.accounts.len() {            msg!("❌ AccountInfo 数组长度不足");            msg!("  需要: {} 个账户", instruction.accounts.len());            msg!("  提供: {} 个账户", account_infos.len());            return Err(error!(                crate::constants::ErrorCode::InvalidAccountAddressFormat            ));        }         msg!("✅ AccountInfo 数组验证通过");        msg!("🚀 开始执行 Raydium AMM V4 交换(使用 PDA 签名)...");         // 🎯 执行真正的 CPI 调用!        // 🔐 使用 invoke_signed 来让 vault PDA 签名        match anchor_lang::solana_program::program::invoke_signed(&instruction, account_infos, signer_seeds) {            Ok(()) => {                msg!("✅ Raydium AMM V4 交换执行成功!");
  1. Line 145 msg!(" 程序 ID: {}", instruction.program_id);: Logs to the program log, which is visible in explorers and the test output.
  2. Line 165 // 🔐 使用 invoke_signed 来让 vault PDA 签名: Makes a CPI signed by a PDA. The signer seeds prove the program controls that address.

How to run, build and test

These commands come from the repo's own manifests (package.json scripts, Anchor.toml, Cargo, go.mod, Makefile). Check the README for prerequisites and environment variables.

DirectoryCommandRuns
PersonalVaultnpm run buildtsc
PersonalVaultnpm run testts-node test/test.ts
PersonalVault/testnpm run devts-node --watch test.ts
PersonalVault/testnpm run buildtsc
PersonalVault/testnpm run testts-node test.ts
PersonalVaultanchor buildcompile programs and generate IDLs
PersonalVaultanchor testlocal validator + test suite
PersonalVaultanchor run testcd test && yarn run ts-mocha -p tsconfig.json -t 1000000 test.ts

What you could build from it