Sol.College

Full lesson

vaulx-protocol

Solana-native lending protocol for physical luxury collateral. Four Anchor programs · Next.js app · TS bridge · 88 Anchor tests · Colosseum Frontier 2026.

What it does

From the project README:

Asset-rich borrowers in emerging markets are trapped behind broken credit infrastructure: pawn monopolies value luxury watches as scrap metal, while institutional USDC liquidity sits on-chain unable to reach off-chain collateral. Vaulx is the rail between the two.
Original
Vaulxfi/vaulx-protocol
SuperStack fork
Blockchains/vaulx-protocol @ 48a68f9
Stars
0
Last push (original)
2026-05-26
Languages
TypeScript, PHP, Blade, Rust, PLpgSQL, CSS
Detected stack
AnchorNext.jsSolana Payweb3.js
Categories
DeFiNFTs & Digital Assets
Licence
MIT
Homepage
https://vaulx.fi

Architecture

444 source files. By extension: .ts ×231, .tsx ×185, .rs ×15, .js ×13.

Top-level directorySource files
apps354
scripts24
packages20
tests18
programs15
site13

On-chain programs: programs/auction, programs/loan, programs/trdc, programs/vault

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/vaulx-protocol/. 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.

Vaulxfi/vaulx-protocol · programs/trdc/src/lib.rs L445–468 @ 48a68f9 · MIT · original
        emit!(TrdcMinted {            trdc_state: ctx.accounts.trdc_state.key(),            loan_id: ctx.accounts.trdc_state.loan_id,            asset_id,            leaf_owner: ctx.accounts.borrower.key(),            tree: ctx.accounts.merkle_tree.key(),            num_minted,            ts: Clock::get()?.unix_timestamp,        });         Ok(())    }} #[derive(Accounts)]#[instruction(loan_id: Pubkey)]pub struct InitializeTrdcState<'info> {    #[account(        init,        payer = payer,        space = TRDCState::SIZE,        seeds = [TRDCState::SEED, loan_id.as_ref()],        bump,    )]
  1. Line 445 emit!(TrdcMinted {: Emits an Anchor event that clients can parse from the logs.
  2. Line 452 ts: Clock::get()?.unix_timestamp,: Reads on-chain time from the Clock sysvar.
  3. Line 463 init,: Creates and pays for a new account. Space and payer must be specified.
  4. Line 466 seeds = [TRDCState::SEED, loan_id.as_ref()],: Declares the PDA seeds. Anchor re-derives the address and rejects the account if it doesn't match.
  5. Line 467 bump,: Uses the bump seed, the extra byte that pushes the derived address off the ed25519 curve.
Vaulxfi/vaulx-protocol · programs/auction/src/lib.rs L109–132 @ 48a68f9 · MIT · original
    /// increment gate; subsequent bids must beat `high_bid + min_increment`.    pub fn place_bid(ctx: Context<PlaceBid>, amount: u64) -> Result<()> {        let now = Clock::get()?.unix_timestamp;        let a = &ctx.accounts.auction;        require!(a.status == AuctionStatus::Open, AuctionError::NotOpen);        require!(now < a.end_ts, AuctionError::AuctionEnded);        require!(amount >= a.reserve_price, AuctionError::BelowReserve);        if a.high_bid > 0 {            let min_acceptable = a                .high_bid                .checked_add(a.min_increment)                .ok_or(AuctionError::MathOverflow)?;            require!(amount >= min_acceptable, AuctionError::BidTooLow);        }         let previous_high_bid = a.high_bid;        let previous_high_bidder = a.high_bidder;         // 1) bidder -> escrow_ata        token::transfer(            CpiContext::new(                ctx.accounts.token_program.to_account_info(),                Transfer {                    from: ctx.accounts.bidder_ata.to_account_info(),
  1. Line 111 let now = Clock::get()?.unix_timestamp;: Reads on-chain time from the Clock sysvar.
  2. Line 113 require!(a.status == AuctionStatus::Open, AuctionError::NotOpen);: An explicit validation that aborts the instruction with an error.
  3. Line 119 .checked_add(a.min_increment): Uses checked arithmetic so overflow fails instead of wrapping.
  4. Line 128 token::transfer(: An SPL token transfer through the Token Program.
  5. Line 129 CpiContext::new(: Makes a cross-program invocation into another program.
Vaulxfi/vaulx-protocol · programs/vault/src/lib.rs L637–660 @ 48a68f9 · MIT · original
        seeds = [KycAttestation::SEED, owner.as_ref()],        bump = kyc_attestation.bump,        close = admin,    )]    pub kyc_attestation: Account<'info, KycAttestation>,    #[account(mut)]    pub admin: Signer<'info>,} #[derive(Accounts)]pub struct InitializeVault<'info> {    #[account(        init, payer = payer, space = Vault::SIZE,        seeds = [Vault::SEED, asset_mint.key().as_ref()], bump,    )]    pub vault: Account<'info, Vault>,    pub asset_mint: Account<'info, Mint>,    #[account(        init, payer = payer,        mint::decimals = 6, mint::authority = vault, mint::freeze_authority = vault,    )]    pub share_mint: Account<'info, Mint>,    #[account(mut)] pub payer: Signer<'info>,    pub token_program: Program<'info, Token>,
  1. Line 637 seeds = [KycAttestation::SEED, owner.as_ref()],: Declares the PDA seeds. Anchor re-derives the address and rejects the account if it doesn't match.
  2. Line 638 bump = kyc_attestation.bump,: Uses the bump seed, the extra byte that pushes the derived address off the ed25519 curve.
  3. Line 639 close = admin,: Closes the account at the end of the instruction and refunds its lamports.
  4. Line 643 pub admin: Signer<'info>,: Requires this account to have signed the transaction.
  5. Line 649 init, payer = payer, space = Vault::SIZE,: Creates and pays for a new account. Space and payer must be specified.
Vaulxfi/vaulx-protocol · programs/loan/src/lib.rs L73–96 @ 48a68f9 · MIT · original
        att.owner = owner;        att.attestor = ctx.accounts.admin.key();        att.attested_at = Clock::get()?.unix_timestamp;        att.jwt_hash = jwt_hash;        att.bump = ctx.bumps.kyc_attestation;        Ok(())    }     /// Admin-only: flip `loan_config.kyc_required` between true and false.    /// Gated on `signer == loan_config.admin`. Mirrors `vault::set_kyc_required`.    pub fn set_kyc_required(ctx: Context<SetKycRequired>, required: bool) -> Result<()> {        // V3 — defense-in-depth init-flag check.        require!(            ctx.accounts.loan_config.initialized,            LoanError::ConfigNotInitialized        );        require_keys_eq!(            ctx.accounts.admin.key(),            ctx.accounts.loan_config.admin,            LoanError::Unauthorized        );        ctx.accounts.loan_config.kyc_required = required;        emit!(KycRequiredChanged {            required,
  1. Line 75 att.attested_at = Clock::get()?.unix_timestamp;: Reads on-chain time from the Clock sysvar.
  2. Line 77 att.bump = ctx.bumps.kyc_attestation;: Uses the bump seed, the extra byte that pushes the derived address off the ed25519 curve.
  3. Line 85 require!(: An explicit validation that aborts the instruction with an error.
  4. Line 95 emit!(KycRequiredChanged {: Emits an Anchor event that clients can parse from the logs.

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
.npm run devturbo run dev
.npm run buildturbo run build
.npm run testturbo run test
.npm run lintturbo run lint
sitenpm run devvite
sitenpm run buildvite build
packages/termsnpm run testvitest run
packages/ccbnpm run testvitest run
apps/indexernpm run devtsx watch src/main.ts
apps/indexernpm run starttsx src/main.ts
apps/indexernpm run testvitest run
apps/bridgenpm run devtsx watch src/main.ts
apps/bridgenpm run starttsx src/main.ts
apps/bridgenpm run testvitest run
apps/webnpm run devnext dev
apps/webnpm run startnext start

What you could build from it

Used in lessons