Need to build up anchor development environment
anchor version 0.30.1
solana version 1.18.18
node version 23.3.0
#[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. \
#[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,
}
#[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.
#[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.
#[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.
#[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.
#[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.
#[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.
#[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.
#[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.
#[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.
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.
Oracle prices are not stored directly in each Asset struct. Whenever need each feed prices will be fetched via external oracle calls (Chainlink/Pyth).
Fees earned from funding payments, opening/closing positions, and liquidations are aggregated in the Liquidity Pool struct (total_fees_earned).
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.