New DeFi protocols launch on Solana every day—DEXs, lending markets, vaults, derivatives. Applications want to compose them, but integration work is the bottleneck: parsing accounts, encoding instruction data, writing CPI logic for each protocol. The problem isn’t the work, it’s the velocity: new protocols launch faster than teams can integrate them.
Beethoven inverts this. Instead of applications integrating protocols, protocols integrate themselves. You write one function, and every DeFi primitive on Solana composes through it. Applications can now do what they were designed for: building features, not writing integrations.
The trick is moving complexity to the client. Applications already serve everything through APIs anyway, so client-side complexity doesn’t cost anything—the API handles account ordering and instruction building. Push the hard parts there, keep the program simple.
Protocol teams can add themselves to every application without waiting for manual integration work.
The Integration Velocity Problem
Traditional DeFi composition works like this: for each protocol you want to support, you write protocol-specific integration code—parsing the accounts that protocol needs, encoding the instruction data in the format it expects, writing the CPI logic, testing it, and shipping it.
Want to aggregate DEX liquidity? Manifest needs 15 accounts in a specific order, Orca needs a different set, Raydium needs another. Want to compose lending protocols? Marginfi has its account structure, Solend has a different one. Each protocol has its own requirements, instruction formats, and quirks, all of which your application has to know.
This creates a bottleneck. New protocols launch constantly, each one requiring manual integration work, and even if you have engineers dedicated to this, you fall behind. The protocols users want access to aren’t available because the integration hasn’t happened yet.
Worse, this integration work becomes a competitive advantage. If you open source your protocol, you give away the integration work. So protocols stay closed, and the community can’t help. The only way to support more protocols is to hire more engineers to write more integrations.
This doesn’t scale with Solana’s velocity.
The Contrarian Bet: Client Complexity Doesn’t Matter
Here’s where most people get stuck: they think making the client more complex is bad, since building instructions with the right accounts for each protocol sounds hard. Why would you move that complexity to the client?
Because the client doesn’t cost anything.
On Solana, program complexity costs compute units. Every instruction you execute, every account you parse, every branch you take—all of it consumes CU. Hit the limit, and your transaction fails. This is why programs are optimized aggressively.
Client-side complexity has no CU cost. The client runs off-chain, so it can be as complex as needed without limits or constraints.
The key insight: DeFi applications already serve everything through APIs—Jupiter has one, lending protocols have them, every serious application does. Users don’t build transactions by hand; they call an API endpoint, and the API returns a transaction ready to sign.
The API already handles all the complexity of building instructions—knowing which accounts each protocol needs, how to encode the data, and how to construct the transaction correctly. That’s what APIs do.
So if the API is already handling client-side complexity, making the client side more complex costs nothing. The complexity is abstracted away behind an API endpoint. The user never sees it.
This is the contrarian bet: trade program simplicity for client complexity, knowing that the API layer makes client complexity disappear. Keep the program dead simple—one function for all DeFi primitives—and push everything else to the client, where it’s free.
How Beethoven Works
Beethoven defines action traits for DeFi primitives—Swap for trading on DEXs, Deposit for adding liquidity to vaults or lending protocols, Borrow for taking loans, Withdraw for exiting positions. Each trait defines a standard interface that protocols implement.
Here’s the Swap trait:
pub trait Swap<'info> { type Accounts; type Data; fn swap_signed( ctx: &Self::Accounts, in_amount: u64, minimum_out_amount: u64, data: &Self::Data, signer_seeds: &[Signer], ) -> ProgramResult; fn swap( ctx: &Self::Accounts, in_amount: u64, minimum_out_amount: u64, data: &Self::Data, ) -> ProgramResult; }
The trait is generic over accounts and data. Each protocol has different account requirements and different instruction data. The trait doesn’t prescribe what those are—it just defines the interface.
When you call beethoven::swap(), Beethoven looks at the first account in remaining_accounts—the program ID—which tells it which protocol to route to.
pub fn try_from_swap_context(accounts: &[AccountView]) -> Result<SwapContext, ProgramError> { let detector_account = accounts.first()?; if pubkey_eq(detector_account.address(), &MANIFEST_PROGRAM_ID) { return Ok(SwapContext::Manifest(parse_manifest_accounts(accounts)?)); } if pubkey_eq(detector_account.address(), &ORCA_PROGRAM_ID) { return Ok(SwapContext::Orca(parse_orca_accounts(accounts)?)); } Err(ProgramError::InvalidAccountData) }
Beethoven checks the program ID, parses the accounts for that protocol, and returns a context enum. The context contains the parsed, protocol-specific accounts ready for use.
Your program can then validate those accounts if needed:
let ctx = try_from_swap_context(accounts)?; match &ctx { SwapContext::Manifest(m) => { require!(m.market.address() == approved_market); } SwapContext::Orca(o) => { // different validation } } SwapContext::swap(&ctx, in_amount, min_out, &data)?;
Or skip validation and just execute:
beethoven::swap(&accounts, in_amount, min_out, &data)?;
One line. That’s the entire integration.
The Heavy Lifting (Protocol Side)
Protocol teams do the integration work. They implement the trait once, and everyone benefits.
Here’s what Manifest’s integration looks like:
pub struct ManifestSwapAccounts<'info> { pub manifest_program: &'info AccountView, pub payer: &'info AccountView, pub owner: &'info AccountView, pub market: &'info AccountView, pub system_program: &'info AccountView, pub trader_base: &'info AccountView, pub trader_quote: &'info AccountView, pub base_vault: &'info AccountView, pub quote_vault: &'info AccountView, pub token_program_base: &'info AccountView, pub base_mint: &'info AccountView, pub token_program_quote: &'info AccountView, pub quote_mint: &'info AccountView, pub global: &'info AccountView, pub global_vault: &'info AccountView, }
Fifteen accounts. Each one has to be parsed from the raw account slice and validated. Then the instruction data needs to be encoded correctly:
impl<'info> Swap<'info> for Manifest { type Accounts = ManifestSwapAccounts<'info>; type Data = ManifestSwapData; fn swap_signed( ctx: &Self::Accounts, in_amount: u64, minimum_out_amount: u64, data: &Self::Data, signer_seeds: &[Signer], ) -> ProgramResult { let accounts = [ InstructionAccount::writable_signer(ctx.payer.address()), InstructionAccount::readonly_signer(ctx.owner.address()), InstructionAccount::writable(ctx.market.address()), // ... 11 more accounts ]; let mut instruction_data = MaybeUninit::<[u8; 19]>::uninit(); unsafe { let ptr = instruction_data.as_mut_ptr() as *mut u8; core::ptr::write(ptr, SWAP_DISCRIMINATOR); core::ptr::copy_nonoverlapping(in_amount.to_le_bytes().as_ptr(), ptr.add(1), 8); core::ptr::copy_nonoverlapping(minimum_out_amount.to_le_bytes().as_ptr(), ptr.add(9), 8); core::ptr::write(ptr.add(17), data.is_base_in as u8); core::ptr::write(ptr.add(18), data.is_exact_in as u8); } let instruction = InstructionView { program_id: &MANIFEST_PROGRAM_ID, accounts: &accounts, data: unsafe { core::slice::from_raw_parts(instruction_data.as_ptr() as *const u8, 19) }, }; invoke_signed(&instruction, &account_infos, signer_seeds) } fn swap(ctx: &Self::Accounts, in_amount: u64, minimum_out_amount: u64, data: &Self::Data) -> ProgramResult { Self::swap_signed(ctx, in_amount, minimum_out_amount, data, &[]) } }
The implementation takes about 170 lines of careful code—raw pointer manipulation, manual memory management, protocol-specific instruction encoding—all the complexity that used to live in application code.
But the protocol team writes this once, and after that, every program using Beethoven can route to Manifest without program upgrades—users just enable the manifest feature flag in their Cargo.toml and pass the right accounts from the client side.
The Result (Application Side)
Before Beethoven, an application supporting 50 protocols looked like this:
pub fn swap_manifest(ctx: Context<SwapManifest>, amount: u64, min_out: u64) -> Result<()> { // 170 lines of Manifest-specific logic } pub fn deposit_kamino(ctx: Context<DepositKamino>, amount: u64) -> Result<()> { // 160 lines of Kamino-specific logic } pub fn borrow_marginfi(ctx: Context<BorrowMarginfi>, amount: u64) -> Result<()> { // 140 lines of Marginfi-specific logic } // × 50 protocols = 7,500+ lines of integration code
Every new protocol requires a new instruction, new account parsing, and new CPI logic, so the program grows with each integration. You can’t open source it because the integration work is your moat.
After Beethoven:
use beethoven::{swap, deposit, borrow}; pub fn compose_defi(ctx: Context<ComposeDeFi>, action: Action) -> Result<()> { match action { Action::Swap { in_amount, min_out, data } => { swap(&ctx.remaining_accounts, in_amount, min_out, &data)?; } Action::Deposit { amount, data } => { deposit(&ctx.remaining_accounts, amount, &data)?; } Action::Borrow { amount, data } => { borrow(&ctx.remaining_accounts, amount, &data)?; } } Ok(()) }
One function per DeFi primitive. Supporting 50 protocols or 500 protocols costs the same: zero additional code.
A new protocol launches? Protocol team submits a PR to Beethoven implementing the relevant trait. Once merged, you add their feature flag to your Cargo.toml:
beethoven = { version = "0.1", features = ["manifest", "orca", "raydium", "new-dex"] }
Rebuild, deploy, done. No new instructions, no new CPI logic, no integration work—the protocol did it for you.
You can open source your application. The integration work isn’t your moat anymore—your API, your composability logic, your user experience is. Community members can submit integrations themselves. Your application becomes infrastructure, not a collection of manual integrations.
Feature Flags: Explicit Security
Protocols are opt-in, not opt-out. When a new protocol is added to Beethoven, existing programs are unaffected. You control which protocols your program trusts through feature flags:
# You audit and explicitly enable each protocol beethoven = { features = ["manifest", "orca"] } # Only these two
Your program will never route to protocols you haven’t reviewed. Each integration becomes a conscious security decision. Security reviews are explicit and scoped: you audit Manifest’s integration once, enable the feature flag, and users can now route to Manifest through your program. Other protocols added to Beethoven later don’t affect you until you choose to enable them.
Composability as Infrastructure
Beethoven reframes composability as a public good. Instead of every application reimplementing the same integrations, protocols implement themselves once, applications become composable by default, and the integration layer becomes shared infrastructure.
This changes the economics. Protocol teams have an incentive to integrate with Beethoven because it gives them immediate access to every application using Beethoven. They don’t wait for aggregators to prioritize them or for applications to manually integrate them. They control their own composability.
Applications benefit because they can support hundreds of protocols without writing hundreds of integrations. They can open source their logic without giving away their competitive advantage. The community can contribute integrations.
The API layer makes this work by abstracting away client-side complexity. Users call an API endpoint, the API constructs the transaction with the right accounts for the right protocol, the program routes it correctly, and everything else is hidden.
This is how DeFi scales on Solana. Not by centralizing integration work in a few large teams, but by distributing it to the protocols themselves. Integration velocity stops being a bottleneck.
What This Means
Even Jupiter, the most well-resourced team on Solana, chose Beethoven. Not because they couldn’t write integrations—they’ve proven they can—but because composability at scale requires infrastructure, not integration work.
They saw the same problem: new protocols launching faster than they could integrate them, even with dedicated engineers. Client-side complexity wasn’t an issue since they already serve everything through APIs. Beethoven made the program side trivial.
This matters beyond aggregation. Any application that wants to compose DeFi primitives faces the same bottleneck: vaults that want to rebalance across lending protocols, portfolio managers that want to trade on multiple DEXs, structured products that need to compose derivatives. The integration velocity problem is universal.
Beethoven solves it by inverting the responsibility. Protocols integrate themselves once. Applications compose them with a single function call. The integration layer becomes shared infrastructure.
For protocol developers: integrate once, and every application using Beethoven can compose with you. Submit a PR implementing the trait, and you’re available to every Beethoven user immediately.
For application developers: build composable DeFi without integration work. Open source by default. New protocols are a feature flag away. Start with the Beethoven repository.
The integration velocity problem is solved. Now go build.