A universal interface for CPI interactions, bounded by actions and routed client-side on Solana.
A Solana SDK that lets you build protocol-agnostic programs. Instead of hardcoding integrations for Kamino, Jupiter, Marginfi, etc., you write one instruction and let the client choose which protocol to route to.
// Your entire program
use beethoven::deposit;
#[program]
mod vault {
pub fn deposit_anywhere(ctx: Context<Deposit>) -> Result<()> {
deposit(&ctx.remaining_accounts, amount)?;
Ok(())
}
}Client picks the protocol:
// Kamino
tx.remainingAccounts([KAMINO_PROGRAM_ID, reserve, obligation, ...])
// Jupiter
tx.remainingAccounts([JUPITER_PROGRAM_ID, vault, userAccount, ...])
// Same instruction, different protocolBeethoven reduces integration overhead for both protocol teams and application developers. Protocols only implement the Beethoven traits once, and every downstream program can route to them without upgrades. Programs keep full control over which protocols are enabled through feature flags, making security reviews explicit and scoped.
Protocol detection: First account in the slice must be the target program ID.
pub fn try_from_deposit_context(accounts: &[AccountView])
-> Result<DepositContext, ProgramError>
{
let detector_account = accounts.first()?;
if pubkey_eq(detector_account.address(), &KAMINO_PROGRAM_ID) {
return Ok(DepositContext::Kamino(parse_kamino_accounts(accounts)?));
}
if pubkey_eq(detector_account.address(), &JUPITER_PROGRAM_ID) {
return Ok(DepositContext::Jupiter(parse_jupiter_accounts(accounts)?));
}
Err(ProgramError::InvalidAccountData)
}Type-safe contexts: Pattern match for custom validation before executing.
let ctx = try_from_deposit_context(accounts)?;
match &ctx {
DepositContext::Kamino(k) => {
require!(k.reserve.address() == approved_reserve);
}
DepositContext::Jupiter(j) => {
// different validation
}
}
DepositContext::deposit(&ctx, amount)?;Feature flags: Explicit security model.
Protocols are opt-in, not opt-out. When new protocols are added to Beethoven:
- Existing programs are unaffected
- You choose which protocols to trust
- Each integration is a conscious security decision
# You audit and explicitly enable each protocol
beethoven = { features = ["kamino", "jupiter"] } # Only these twoYour program will never route to protocols you haven't reviewed.
Three usage levels:
// 1. Convenience - auto-detect protocol and execute
// Use when: You don't need custom validation
beethoven::deposit(&accounts, amount)?;
// 2. Protocol-agnostic validation - auto-detect then validate
// Use when: You need to inspect/validate accounts before executing,
// but want to support multiple protocols
let ctx = try_from_deposit_context(&accounts)?;
match &ctx {
DepositContext::Kamino(k) => {
require!(k.reserve.address() == approved_reserve);
}
DepositContext::Jupiter(j) => {
require!(j.vault.address() == approved_vault);
}
}
DepositContext::deposit(&ctx, amount)?;
// 3. Protocol-specific - skip auto-detection
// Use when: You know exactly which protocol you're calling
let ctx = KaminoDepositAccounts::try_from(&accounts)?;
Kamino::deposit(&ctx, amount)?;All support PDA signing via deposit_signed(accounts, amount, &[signer_seeds]).
Add Beethoven to your program:
[dependencies]
beethoven = "0.1"Use the protocol-agnostic action helpers:
use beethoven::deposit;
pub fn deposit_anywhere(ctx: Context<Deposit>, amount: u64) -> Result<()> {
deposit(&ctx.remaining_accounts, amount)?;
Ok(())
}Enable only the protocols you have audited:
beethoven = { version = "0.1", features = ["kamino", "jupiter"] }make format
make clippy
make test
make test-upstreamTests require the Solana CLI and build the SBF program in program-test.
For protocol developers: Submit a PR to make your protocol available to all Beethoven users.
Once integrated, any program using Beethoven can immediately route to your protocol - no upgrades needed on their end. Users just enable your feature flag and pass your accounts.
For each action (deposit, withdraw, borrow, etc.), implement the corresponding trait:
// src/programs/your_protocol/mod.rs
pub const YOUR_PROTOCOL_PROGRAM_ID: [u8; 32] = [...];
pub struct YourProtocol;
pub struct YourProtocolDepositAccounts<'info> {
pub user: &'info AccountView,
pub vault: &'info AccountView,
// ... your protocol's required accounts
}
// Parse accounts from raw slice
impl<'info> TryFrom<&'info [AccountView]> for YourProtocolDepositAccounts<'info> {
type Error = ProgramError;
fn try_from(accounts: &'info [AccountView]) -> Result<Self, Self::Error> {
// Validate account count and parse
}
}
// Implement the action trait
impl<'info> Deposit<'info> for YourProtocol {
type Accounts = YourProtocolDepositAccounts<'info>;
fn deposit_signed(
ctx: &Self::Accounts,
amount: u64,
signer_seeds: &[Signer]
) -> ProgramResult {
// Build instruction + invoke_signed to your program
}
fn deposit(ctx: &Self::Accounts, amount: u64) -> ProgramResult {
Self::deposit_signed(ctx, amount, &[])
}
}Then add your protocol to the action's context enum:
// src/traits/deposit.rs
pub enum DepositContext<'info> {
#[cfg(feature = "kamino")]
Kamino(crate::programs::kamino::KaminoDepositAccounts<'info>),
#[cfg(feature = "jupiter")]
Jupiter(crate::programs::jupiter::JupiterEarnDepositAccounts<'info>),
#[cfg(feature = "your_protocol")]
YourProtocol(crate::programs::your_protocol::YourProtocolDepositAccounts<'info>),
}And add detection logic:
// In try_from_deposit_context()
#[cfg(feature = "your_protocol")]
if pubkey_eq(detector_account.address(), &crate::programs::your_protocol::YOUR_PROTOCOL_PROGRAM_ID) {
let ctx = crate::programs::your_protocol::YourProtocolDepositAccounts::try_from(accounts)?;
return Ok(DepositContext::YourProtocol(ctx));
}That's it. Submit the PR and programs can start routing to you.
For review expectations and checklists, see CONTRIBUTING.md.
deposit/deposit_signed- Kamino, Jupiter
More actions (withdraw, borrow, repay) coming when needed.
Uses pinocchio for zero-overhead abstractions. No anchor bloat.
We welcome protocol integrations, bug fixes, and improvements. Please read CONTRIBUTING.md for development setup, tests, and PR guidelines.
Beethoven - Client-side protocol routing for Solana programs.