Rust SDK

The Coalesce Finance Rust SDK for building on-chain programs that integrate with the permissioned lending protocol, or for off-chain Rust applications.

Installation

Add to your Cargo.toml:

[dependencies]
coalescefi-sdk = "0.1"

Feature Flags

# Default: std environment with all features
coalescefi-sdk = "0.1"

# No-std for on-chain programs (Solana BPF)
coalescefi-sdk = { version = "0.1", default-features = false }

Quick Start

use coalescefi_sdk::{
    constants::localnet_program_id,
    find_market_pda,
    find_lender_position_pda,
    accounts::decode_market,
};
use solana_program::pubkey::Pubkey;

// Derive PDAs
let program_id = localnet_program_id();
let borrower = Pubkey::new_unique();
let lender = Pubkey::new_unique();
let market_pda = find_market_pda(&borrower, 1, &program_id);
let lender_pda = find_lender_position_pda(&market_pda.address, &lender, &program_id);

// Parse account data (zero-copy)
let market = decode_market(&account_data)?;
println!("Total deposited: {}", market.total_deposited());
println!("Market bump: {}", market_pda.bump);
println!("Lender position PDA: {}", lender_pda.address);

PDA Derivation

All PDA functions return PdaResult { address, bump }:

use coalescefi_sdk::{
    constants::localnet_program_id,
    find_protocol_config_pda,
    find_market_pda,
    pdas::find_market_authority_pda,
    find_vault_pda,
    find_lender_position_pda,
    find_borrower_whitelist_pda,
    pdas::find_blacklist_check_pda,
};
use solana_program::pubkey::Pubkey;

let program_id = localnet_program_id();
let borrower = Pubkey::new_unique();
let lender = Pubkey::new_unique();
let blacklist_program = Pubkey::new_unique();

// Protocol config (singleton)
let config_pda = find_protocol_config_pda(&program_id);

// Market PDAs
let market_pda = find_market_pda(&borrower, 1, &program_id);
let authority_pda = find_market_authority_pda(&market_pda.address, &program_id);
let vault_pda = find_vault_pda(&market_pda.address, &program_id);

// Lender position
let lender_pda = find_lender_position_pda(&market_pda.address, &lender, &program_id);

// Borrower whitelist
let whitelist_pda = find_borrower_whitelist_pda(&borrower, &program_id);
let blacklist_check_pda = find_blacklist_check_pda(&lender, &blacklist_program);

Account Parsing

The SDK uses bytemuck for zero-copy account parsing:

use coalescefi_sdk::{accounts::decode_market, Market};
use solana_program::pubkey::Pubkey;

// Zero-copy parse (no allocation)
let market: &Market = decode_market(&account_data)?;

// Access fields directly
let borrower: Pubkey = market.borrower_pubkey();
let interest_rate: u16 = market.annual_interest_bps();
let maturity: i64 = market.maturity_timestamp();
let total_deposited: u64 = market.total_deposited();

Account Discriminators

Validate account types using discriminators:

use coalescefi_sdk::constants::{
    DISC_PROTOCOL_CONFIG,
    DISC_MARKET,
    DISC_LENDER_POSITION,
    DISC_BORROWER_WL,
};
use solana_program::program_error::ProgramError;

fn validate_market_account(data: &[u8]) -> Result<(), ProgramError> {
    if data.len() < 8 || &data[..8] != DISC_MARKET {
        return Err(ProgramError::InvalidAccountData);
    }
    Ok(())
}

Instruction Building

Build Instructions

use coalescefi_sdk::{
    constants::{localnet_program_id, spl_token_program_id, system_program_id},
    instructions::{create_deposit_instruction, DepositAccounts},
    types::DepositArgs,
};
use solana_program::pubkey::Pubkey;

let program_id = localnet_program_id();

let accounts = DepositAccounts {
    market: Pubkey::new_unique(),
    lender: Pubkey::new_unique(),
    lender_token_account: Pubkey::new_unique(),
    vault: Pubkey::new_unique(),
    lender_position: Pubkey::new_unique(),
    blacklist_check: Pubkey::new_unique(),
    protocol_config: Pubkey::new_unique(),
    mint: Pubkey::new_unique(),
    token_program: spl_token_program_id(),
    system_program: system_program_id(),
};

let args = DepositArgs {
    amount: 1_000_000, // 1 USDC
};

let ix = create_deposit_instruction(accounts, args, &program_id);

Available Instructions

FunctionDescription
create_initialize_protocol_instructionInitialize protocol config
create_set_fee_config_instructionUpdate fee settings
create_create_market_instructionCreate a new lending market
create_deposit_instructionDeposit tokens to a market
create_borrow_instructionBorrow from a market
create_repay_instructionRepay borrowed amount
create_repay_interest_instructionRepay accrued interest
create_withdraw_instructionWithdraw deposited tokens
create_collect_fees_instructionCollect protocol fees
create_resettle_instructionRe-settle after maturity
create_close_lender_position_instructionClose empty position
create_withdraw_excess_instructionWithdraw excess funds
create_set_borrower_whitelist_instructionManage whitelist
create_set_pause_instructionPause/unpause protocol
create_set_blacklist_mode_instructionConfigure blacklist
create_set_admin_instructionTransfer admin role
create_set_whitelist_manager_instructionSet whitelist manager

Error Handling

use coalescefi_sdk::errors::CoalescefiError;

match result {
    Err(CoalescefiError::InsufficientBalance) => {
        msg!("Not enough funds in account");
    }
    Err(CoalescefiError::Unauthorized) => {
        msg!("Signer not authorized");
    }
    Err(CoalescefiError::NotMatured) => {
        msg!("Market hasn't reached maturity");
    }
    Err(e) => {
        msg!("Other error: {:?}", e);
    }
    Ok(_) => {}
}

Error Codes

use coalescefi_sdk::errors::CoalescefiError;

// Initialization errors (0-4)
CoalescefiError::AlreadyInitialized   // 0
CoalescefiError::InvalidFeeRate       // 1
CoalescefiError::InvalidCapacity      // 2
CoalescefiError::InvalidMaturity      // 3
CoalescefiError::MarketAlreadyExists  // 4

// Authorization errors (5-9)
CoalescefiError::Unauthorized         // 5
CoalescefiError::NotWhitelisted       // 6
CoalescefiError::Blacklisted          // 7
CoalescefiError::ProtocolPaused       // 8
CoalescefiError::BorrowerHasActiveDebt // 9

// Account validation errors (10-16)
CoalescefiError::InvalidAddress       // 10
CoalescefiError::InvalidMint          // 11
CoalescefiError::InvalidVault         // 12
CoalescefiError::InvalidPDA           // 13
CoalescefiError::InvalidAccountOwner  // 14
CoalescefiError::InvalidTokenProgram  // 15
CoalescefiError::InvalidTokenAccountOwner // 16

// Input validation errors (17-20)
CoalescefiError::ZeroAmount           // 17
CoalescefiError::ZeroScaledAmount     // 18
CoalescefiError::InvalidScaleFactor   // 19
CoalescefiError::InvalidTimestamp     // 20

// Balance/capacity errors (21-27)
CoalescefiError::InsufficientBalance  // 21
CoalescefiError::InsufficientScaledBalance // 22
CoalescefiError::NoBalance            // 23
CoalescefiError::ZeroPayout           // 24
CoalescefiError::CapExceeded          // 25
CoalescefiError::BorrowAmountTooHigh  // 26
CoalescefiError::GlobalCapacityExceeded // 27

// Market state errors (28-35)
CoalescefiError::MarketMatured        // 28
CoalescefiError::NotMatured           // 29
CoalescefiError::NotSettled           // 30
CoalescefiError::SettlementNotImproved // 31
CoalescefiError::SettlementGracePeriod // 32
CoalescefiError::SettlementNotComplete // 33
CoalescefiError::PositionNotEmpty     // 34
CoalescefiError::RepaymentExceedsDebt // 35

// Fee/withdrawal errors (36-40)
CoalescefiError::NoFeesToCollect      // 36
CoalescefiError::FeeCollectionDuringDistress // 37
CoalescefiError::LendersPendingWithdrawals // 38
CoalescefiError::FeesNotCollected     // 39
CoalescefiError::NoExcessToWithdraw   // 40

// Operational errors (41-42)
CoalescefiError::MathOverflow         // 41
CoalescefiError::PayoutBelowMinimum   // 42

Constants

use coalescefi_sdk::constants::{
    // Mathematical constants
    WAD,              // 10^18
    BPS,              // 10000
    SECONDS_PER_YEAR, // 31_536_000

    // Protocol limits
    MAX_ANNUAL_INTEREST_BPS, // 10000 (100%)
    MAX_FEE_RATE_BPS,        // 10000 (100%)
    USDC_DECIMALS,           // 6

    // Account sizes
    PROTOCOL_CONFIG_SIZE,    // 194 bytes
    MARKET_SIZE,             // 250 bytes
    LENDER_POSITION_SIZE,    // 128 bytes
    BORROWER_WHITELIST_SIZE, // 96 bytes

    // Seeds
    SEED_PROTOCOL_CONFIG,
    SEED_MARKET,
    SEED_MARKET_AUTHORITY,
    SEED_VAULT,
    SEED_LENDER,
    SEED_BORROWER_WHITELIST,
    SEED_BLACKLIST,
};

No-Std Support

For on-chain programs, use the SDK without std:

[dependencies]
coalescefi-sdk = { version = "0.1", default-features = false }
#![no_std]

use coalescefi_sdk::find_market_pda;

// All core functionality works without std

For account layouts and byte offsets, see the architecture documentation.

Integration Example

Complete instruction-construction example:

use coalescefi_sdk::{
    constants::{localnet_program_id, spl_token_program_id, system_program_id},
    instructions::{create_deposit_instruction, DepositAccounts},
    types::DepositArgs,
};
use solana_program::pubkey::Pubkey;

let program_id = localnet_program_id();

let accounts = DepositAccounts {
    market: Pubkey::new_unique(),
    lender: Pubkey::new_unique(),
    lender_token_account: Pubkey::new_unique(),
    vault: Pubkey::new_unique(),
    lender_position: Pubkey::new_unique(),
    blacklist_check: Pubkey::new_unique(),
    protocol_config: Pubkey::new_unique(),
    mint: Pubkey::new_unique(),
    token_program: spl_token_program_id(),
    system_program: system_program_id(),
};

let ix = create_deposit_instruction(accounts, DepositArgs { amount: 1_000_000 }, &program_id);

Testing

#[cfg(test)]
mod tests {
    use super::*;
    use coalescefi_sdk::constants::*;

    #[test]
    fn test_pda_derivation() {
        let program_id = localnet_program_id();
        let borrower = Pubkey::new_unique();

        let market = find_market_pda(&borrower, 1, &program_id);

        assert!(market.bump > 0);
        assert_ne!(market.address, Pubkey::default());
    }

    #[test]
    fn test_account_size() {
        assert_eq!(std::mem::size_of::<Market>(), MARKET_SIZE);
    }
}

Next Steps