Skip to content

Repository files navigation

Baskt Perpetual Trading SC

Prerequirements

Need to build up anchor development environment

anchor version 0.30.1
solana version 1.18.18
node version 23.3.0

Key Components

1. Data Structures (Structs)

1.1 Asset Struct

#[account]
pub struct Asset {
    pub asset_id: Pubkey, // Unique ID of the asset
    pub oracle: Pubkey, // Oracle providing asset price
    pub open_interest_long: u64, // Total open interest for long positions
    pub open_interest_short: u64, // Total open interest for short positions
    pub disabled: u8, // 1 - if asset is removed
}

asset_id: Unique identifier for each asset.
open_interest_long/open_interest_short: Tracks total exposure for positions. \

1.2 Position Struct

#[account]
pub struct Position {
    pub seed: u64, // identifier of position for a baskt

    pub owner: Pubkey, // Trader's address
    pub baskt_id: Pubkey, // Baskt ID
    pub asset_id: Pubkey, // Asset being traded

    pub size: u64, // Position size in USDC
    pub collateral: u64, // USDC collateral
    pub entry_price: u64, // Entry price
    pub close_price: Option<u64>, // Closing price
    pub funding_accumulated: i64, // Funding accured

    pub is_long: bool, // True = Long, False = Short
    pub status: PositionStatus, // OPEN, CLOSED, LIQUIDATED
}
#[account]
pub struct UserBasktState {
    pub owner: Pubkey, // Trader's address
    pub baskt_id: Pubkey, // Baskt ID
    pub user_pos_count: u8, // User opened position count for a baskt
}
#[derive(AnchorSerialize, AnchorDeserialize, Clone, PartialEq)]
pub enum PositionStatus {
    Open,
    Closed,
    Liquidated,
}

1.2 Baskt Struct

#[account]
pub struct Baskt {
    pub seed: u64, // identifier of baskt
    pub creator: Pubkey, // Baskt creator's address
    pub name: [u8; 64], // String of baskt name
    pub predefined: u8, // 1 - if baskt created by community

    pub token_count: u8, // Asset configs count
    pub configs: [TokenConfig; MAX_TOKENS_IN_BASKT], // Asset configs include weights

    pub total_pos_size: u64, // Total opened positions size within Baskt
    pub total_pos_count: u64, // Total opened positions count within Baskt
    pub baskt_pos_seq: u64, // Sequental number for position seed within Baskt

    pub created_at: i64 // Baskt created timestamp
}

assets: Asset IDs and whether they're long or short (tracked dynamically). NAV calculated from each assets oracle feed according to the weights.

1.3 Liquidity Pool Struct

#[account]
pub struct LiquidityPool {
    pub total_usdc: u64, // Total USDC liquidity
    pub open_interest: u64, // Total open interest across all positions
    pub lp_token_supply: u64, // Total issued LP tokens
    // More variables to keep track of the fees earned total 
    // 1. Liquidations
    // 2. Funding rate 
    // 3. Opening and Closing Fee
}

LPs deposit USDC and receive SPL tokens representing their share. Fees earned are added back into the liquidity pool.

1.4 LP Deposit Account Struct

#[account]
pub struct LPDepositAccount {
    pub owner: Pubkey, // LP's address
    pub deposits: Vec<DepositEntry>, // User deposit history
}
#[derive(AnchorSerialize, AnchorDeserialize, Clone)]
pub struct DepositEntry {
    pub deposited_usdc: u64, // Amount deposited
    pub lp_tokens: u64, // LP tokens minted
    pub total_usdc_in_pool: u64, // Total USDC at deposit time
    pub lp_token_supply: u64, // Total LP tokens at deposit
    pub timestamp: i64, // Deposit timestamp
}

Tracks LP deposits, pool size, and LP token supply at deposit time for accurate PnL calculation.

2. Instructions (Core Functions)

2.1 Create New Baskt

#[derive(Accounts)]
pub struct CreateBaskt<'info> {
    #[account(mut)]
    pub creator: Signer<'info>,
    #[account(init, payer = creator, space = 8 + 128)]
    pub baskt: Account<'info, Baskt>,
    pub system_program: Program<'info, System>,
}

User initializes a new Baskt. Assets must be predefined and compatible.

2.2 Add New Asset

#[derive(Accounts)]
pub struct AddAsset<'info> {
    #[account(mut)]
    pub admin: Signer<'info>,
    #[account(init, payer = admin, space = 8 + 64)]
    pub asset: Account<'info, Asset>,
    pub oracle: AccountInfo<'info>, // Oracle providing price feed
    pub system_program: Program<'info, System>,
}

Admin adds new assets that have valid oracle feeds.

2.3 Open Position

#[derive(Accounts)]
pub struct OpenPosition<'info> {
    #[account(mut)]
    pub trader: Signer<'info>,
    #[account(mut)]
    pub position: Account<'info, Position>,
    #[account(mut)]
    pub baskt: Account<'info, Baskt>,
    #[account(mut)]
    pub liquidity_pool: Account<'info, LiquidityPool>,
}

Creates a long/short position inside a Baskt. Funding rate updates dynamically upon opening/closing.

2.4 Close Position

#[derive(Accounts)]
pub struct ClosePosition<'info> {
    #[account(mut)]
    pub trader: Signer<'info>,
    #[account(mut, close = trader)]
    pub position: Account<'info, Position>,
    #[account(mut)]
    pub baskt: Account<'info, Baskt>,
    #[account(mut)]
    pub liquidity_pool: Account<'info, LiquidityPool>,
}

Finalizes PnL and updates NAV. Adjusts open interest and funding rates dynamically.

2.5 LP Withdrawal and Deposit

#[derive(Accounts)]
pub struct DepositLiquidity<'info> {
    #[account(mut)]
    pub lp: Signer<'info>,
    #[account(mut)]
    pub liquidity_pool: Account<'info, LiquidityPool>,
    #[account(mut)]
    pub lp_deposit: Account<'info, LPDeposit>,
}
#[derive(Accounts)]
pub struct WithdrawLiquidity<'info> {
    #[account(mut)]
    pub lp: Signer<'info>,
    #[account(mut)]
    pub liquidity_pool: Account<'info, LiquidityPool>,
    #[account(mut, close = lp)]
    pub lp_deposit: Account<'info, LPDeposit>,
}

LPs deposit USDC and receive LP tokens. Upon withdrawal, LP tokens are burned, and fees apply.

2.6 Liquidate Position

#[derive(Accounts)]
pub struct LiquidatePosition<'info> {
    #[account(mut)]
    pub liquidator: Signer<'info>,
    #[account(mut, close = liquidator)]
    pub position: Account<'info, Position>,
    #[account(mut)]
    pub baskt: Account<'info, Baskt>,
    #[account(mut)]
    pub liquidity_pool: Account<'info, LiquidityPool>,
}
fn check_liquidation(position: &Position) -> bool {
    let margin_ratio = position.collateral * 100 / position.size;
    margin_ratio < LIQUIDATION_THRESHOLD
}

If a position’s margin falls below the threshold, it is liquidated. Liquidation fees are sent back to the liquidity pool.

3. Funding Rate Calculation

fn calculate_funding_rate(asset: &Asset) -> i64 {
    let imbalance = asset.open_interest_long as i64 - asset.open_interest_short as i64;
    (imbalance * FUNDING_MULTIPLIER) / (asset.open_interest_long + asset.open_interest_short).max(1)
}

Not sure if funding rate should be calculated dynamically in smart contract. Don't need this if it's enough to calculate in web3 side.

4. Oracle Integration

Oracle prices are not stored directly in each Asset struct. Whenever need each feed prices will be fetched via external oracle calls (Chainlink/Pyth).

5. Fee Tracking

Fees earned from funding payments, opening/closing positions, and liquidations are aggregated in the Liquidity Pool struct (total_fees_earned).

6. LP Profit & Loss (PnL) Calculation

LP profitability is calculated by comparing their entry LP token price and the current LP token price. Upon withdrawal, LPs redeem LP tokens proportionally from the pool minus fees.

About

Solana Smart Contract for Baskt Perp Trading

Topics

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages