Python SDK

The Coalesce Finance Python SDK for building applications and scripts that interact with the permissioned lending protocol.

Installation

pip install coalescefi-sdk

Or with development dependencies:

pip install coalescefi-sdk[dev]

Requirements

  • Python 3.10+
  • solana-py >= 0.35.0
  • solders >= 0.21.0

Quick Start

from solana.rpc.async_api import AsyncClient
from solders.pubkey import Pubkey

from coalescefi_sdk import (
    configure_sdk,
    find_market_pda,
    find_lender_position_pda,
    fetch_market,
    create_deposit_instruction,
)

# Configure the SDK
configure_sdk(network="devnet")

# Or with explicit program ID
configure_sdk(program_id=Pubkey.from_string("CoaLfi..."))

# Derive PDAs
borrower = Pubkey.from_string("Borrower...")
market_pda, market_bump = find_market_pda(borrower, market_nonce=1)
lender_pda, lender_bump = find_lender_position_pda(market_pda, lender_pubkey)

# Fetch account data
async def main():
    connection = AsyncClient("https://api.devnet.solana.com")
    market = await fetch_market(connection, market_pda)
    if market:
        print(f"Total deposited: {market.total_deposited}")
        print(f"Interest rate: {market.annual_interest_bps} bps")

asyncio.run(main())

Configuration

Network-based Configuration

from coalescefi_sdk import configure_sdk, NetworkName

# Use predefined network
configure_sdk(network="mainnet")  # or "devnet", "localnet"

# With custom RPC
configure_sdk(
    network="mainnet",
    rpc_url="https://my-rpc.example.com"
)

Explicit Program ID

from solders.pubkey import Pubkey
from coalescefi_sdk import configure_sdk

configure_sdk(
    program_id=Pubkey.from_string("CoaLfi...")
)

Environment Variables

The SDK also reads from environment variables:

export COALESCEFI_PROGRAM_ID="CoaLfi..."
export COALESCEFI_NETWORK="devnet"

PDA Derivation

All PDA functions return a tuple of (Pubkey, bump):

from coalescefi_sdk import (
    find_protocol_config_pda,
    find_market_pda,
    find_market_authority_pda,
    find_vault_pda,
    find_lender_position_pda,
    find_borrower_whitelist_pda,
    find_blacklist_check_pda,
    derive_market_pdas,
)

# Protocol config (singleton)
config_pda, config_bump = find_protocol_config_pda()

# Market PDAs
market_pda, market_bump = find_market_pda(borrower, market_nonce=1)
authority_pda, authority_bump = find_market_authority_pda(market_pda)
vault_pda, vault_bump = find_vault_pda(market_pda)

# Lender position
lender_pda, lender_bump = find_lender_position_pda(market_pda, lender)

# Borrower whitelist
whitelist_pda, whitelist_bump = find_borrower_whitelist_pda(borrower)

# Derive all market-related PDAs at once
market_pdas = derive_market_pdas(borrower, market_nonce=1)
print(market_pdas.market)
print(market_pdas.market_authority)
print(market_pdas.vault)

Account Fetching

The SDK provides async fetchers with built-in retry logic:

from solana.rpc.async_api import AsyncClient
from coalescefi_sdk import (
    fetch_protocol_config,
    fetch_market,
    fetch_lender_position,
    fetch_borrower_whitelist,
    RetryConfig,
)

async def fetch_accounts():
    connection = AsyncClient("https://api.devnet.solana.com")

    # With default retry config (3 retries, exponential backoff)
    market = await fetch_market(connection, market_pda)

    # With custom retry config
    custom_retry = RetryConfig(
        max_retries=5,
        base_delay_ms=500,
        max_delay_ms=5000
    )
    config = await fetch_protocol_config(
        connection,
        config_pda,
        retry_config=custom_retry
    )

    # Returns None if account doesn't exist
    position = await fetch_lender_position(connection, lender_pda)
    if position is None:
        print("Position not found")

Account Decoding

For manual decoding of raw account data:

from coalescefi_sdk import (
    decode_protocol_config,
    decode_market,
    decode_lender_position,
    decode_borrower_whitelist,
    decode_account,  # Auto-detects type
)

# Decode specific account type
market = decode_market(account_data)
print(f"Borrower: {market.borrower}")
print(f"Annual interest: {market.annual_interest_bps} bps")
print(f"Maturity: {market.maturity_timestamp}")

# Auto-detect and decode
account = decode_account(raw_data)
if isinstance(account, Market):
    print("It's a market!")

Instruction Building

Build instructions for all protocol operations:

from coalescefi_sdk import (
    create_deposit_instruction,
    create_borrow_instruction,
    create_repay_instruction,
    create_withdraw_instruction,
)

# Deposit instruction
deposit_ix = create_deposit_instruction(
    accounts={
        "market": market_pda,
        "lender": lender_pubkey,
        "lender_token_account": lender_ata,
        "vault": vault_pda,
        "lender_position": lender_position_pda,
        "blacklist_check": blacklist_check_pda,
        "protocol_config": config_pda,
        "mint": usdc_mint,
        "token_program": TOKEN_PROGRAM_ID,
        "system_program": SYSTEM_PROGRAM_ID,
    },
    args={"amount": 1_000_000},  # 1 USDC (6 decimals)
)

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_re_settle_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

from coalescefi_sdk import (
    CoalescefiError,
    CoalescefiErrorCode,
    parse_coalescefi_error,
    is_user_recoverable_error,
    get_error_recovery_action,
)

try:
    # ... transaction execution
    pass
except Exception as e:
    error = parse_coalescefi_error(e)
    if error:
        print(f"Error code: {error.code.name}")
        print(f"Message: {error.message}")

        if is_user_recoverable_error(error.code):
            action = get_error_recovery_action(error.code)
            print(f"Recovery action: {action}")

Error Categories

from coalescefi_sdk import (
    get_error_category,
    get_error_severity,
    ErrorCategory,
    ErrorSeverity,
)

category = get_error_category(CoalescefiErrorCode.InsufficientBalance)
# ErrorCategory.Balance

severity = get_error_severity(CoalescefiErrorCode.Unauthorized)
# ErrorSeverity.Error

Idempotency Support

Prevent duplicate transactions with the idempotency manager:

from coalescefi_sdk import (
    IdempotencyManager,
    generate_idempotency_key,
)

manager = IdempotencyManager()

# Generate deterministic key for operation
key = generate_idempotency_key("deposit", {
    "market": str(market_pda),
    "amount": str(amount),
    "lender": str(lender),
})

# Execute with idempotency protection
async def safe_deposit():
    result = await manager.execute_once(
        connection,
        key,
        lambda: execute_deposit_transaction(),
    )
    return result

Type Annotations

The SDK is fully typed for IDE support:

from coalescefi_sdk import (
    ProtocolConfig,
    Market,
    LenderPosition,
    BorrowerWhitelist,
)

def process_market(market: Market) -> None:
    # Full type hints available
    borrower: Pubkey = market.borrower
    rate: int = market.annual_interest_bps
    maturity: int = market.maturity_timestamp

Constants

from coalescefi_sdk import (
    # Mathematical constants
    WAD,  # 10^18 - precision factor
    BPS,  # 10000 - basis points denominator
    SECONDS_PER_YEAR,

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

    # Account sizes
    PROTOCOL_CONFIG_SIZE,
    MARKET_SIZE,
    LENDER_POSITION_SIZE,
    BORROWER_WHITELIST_SIZE,

    # Seeds
    SEED_PROTOCOL_CONFIG,
    SEED_MARKET,
    SEED_VAULT,
)

Testing

import pytest
from coalescefi_sdk import configure_sdk, reset_sdk_config

@pytest.fixture(autouse=True)
def reset_config():
    """Reset SDK config between tests."""
    yield
    reset_sdk_config()

def test_pda_derivation():
    configure_sdk(network="localnet")
    pda, bump = find_market_pda(borrower, 1)
    assert bump > 0

Next Steps