Custom Adaptor Implementation Guide
This guide walks through the process of creating a custom adaptor for integrating your DeFi protocol with Voltr vaults.
Overview
A Voltr adaptor serves as a bridge between vaults and your protocol, handling:
Protocol-specific account management
Asset conversions and calculations
State tracking and updates
Required Components
1. State Accounts
First, implement the required state accounts:
Copy #[account]
pub struct Strategy {
pub counterparty_asset_ta : Pubkey , // Your protocol's token account
pub protocol_program : Pubkey , // Your protocol's program ID
pub strategy_type : StrategyType , // Your strategy enum variant
}
#[account]
pub struct VaultStrategy {
pub vault_asset_idle_auth : Pubkey ,
pub strategy : Pubkey ,
pub current_amount : u64 ,
}
2. Core Instructions
Implement these four mandatory instructions:
Copy // 1. Create Strategy
pub fn create_strategy (ctx : Context < CreateStrategy >, strategy_type : StrategyType ) -> Result <()>;
// 2. Initialize
pub fn initialize (ctx : Context < Initialize >) -> Result <()>;
// 3. Deposit
pub fn deposit (ctx : Context < Deposit >, amount : u64 ) -> Result <()>;
// 4. Withdraw
pub fn withdraw (ctx : Context < Withdraw >, amount : u64 ) -> Result <()>;
Implementation Guide
1. Strategy Setup
Define your strategy context and handler:
Copy #[derive( Accounts )]
pub struct CreateStrategy <' info > {
#[account( mut )]
pub payer : Signer <' info >,
#[account()]
pub admin : Signer <' info >,
#[account( mut )]
pub counterparty_asset_ta : Box < InterfaceAccount <' info , TokenAccount >>,
#[account(
init,
seeds = [ STRATEGY_SEED , counterparty_asset_ta . key() . as_ref()],
bump,
payer = payer,
space = Strategy :: LEN + 8
)]
pub strategy : Account <' info , Strategy >,
pub protocol_program : AccountInfo <' info >,
pub system_program : Program <' info , System >,
}
pub fn handle_create_strategy (
ctx : Context < CreateStrategy >,
strategy_type : StrategyType
) -> Result <()> {
let strategy = &mut ctx . accounts . strategy;
strategy . counterparty_asset_ta = ctx . accounts . counterparty_asset_ta . key ();
strategy . protocol_program = ctx . accounts . protocol_program . key ();
strategy . strategy_type = strategy_type;
Ok (())
}
2. Initialization
Implement protocol-specific initialization:
Copy #[derive( Accounts )]
pub struct Initialize <' info > {
#[account( mut )]
pub payer : Signer <' info >,
#[account( mut )]
pub vault_asset_idle_auth : Signer <' info >,
#[account()]
pub strategy : Account <' info , Strategy >,
#[account(
init,
seeds = [ VAULT_STRATEGY_SEED , vault_asset_idle_auth . key() . as_ref(), strategy . key() . as_ref()],
bump,
payer = payer,
space = VaultStrategy :: LEN + 8
)]
pub vault_strategy : Account <' info , VaultStrategy >,
// Add your protocol-specific accounts here
pub protocol_program : AccountInfo <' info >,
pub system_program : Program <' info , System >,
}
pub fn handle_initialize (ctx : Context < Initialize >) -> Result <()> {
// 1. Initialize vault strategy
let vault_strategy = &mut ctx . accounts . vault_strategy;
vault_strategy . vault_asset_idle_auth = ctx . accounts . vault_asset_idle_auth . key ();
vault_strategy . strategy = ctx . accounts . strategy . key ();
vault_strategy . current_amount = 0 ;
// 2. Initialize protocol state
your_protocol :: cpi :: initialize (
CpiContext :: new (
ctx . accounts . protocol_program . to_account_info (),
your_protocol :: cpi :: accounts :: Initialize {
// Your protocol's initialization accounts
}
)
) ? ;
Ok (())
}
3. Deposit Implementation
Handle deposits to your protocol:
Copy #[derive( Accounts )]
pub struct Deposit <' info > {
#[account( mut )]
pub vault_asset_idle_auth : Signer <' info >,
#[account(constraint = strategy . counterparty_asset_ta . key() == counterparty_asset_ta . key())]
pub strategy : Box < Account <' info , Strategy >>,
#[account( mut )]
pub vault_strategy : Box < Account <' info , VaultStrategy >>,
#[account( mut )]
pub vault_asset_mint : Box < InterfaceAccount <' info , Mint >>,
pub vault_asset_idle_ata : Box < InterfaceAccount <' info , TokenAccount >>,
pub counterparty_asset_ta : Box < InterfaceAccount <' info , TokenAccount >>,
pub protocol_program : AccountInfo <' info >,
pub asset_token_program : Interface <' info , TokenInterface >,
}
pub fn handle_deposit (ctx : Context < Deposit >, amount : u64 ) -> Result <()> {
// 1. Execute protocol deposit
your_protocol :: cpi :: deposit (
CpiContext :: new (
ctx . accounts . protocol_program . to_account_info (),
your_protocol :: cpi :: accounts :: Deposit {
// Your protocol's deposit accounts
}
),
amount
) ? ;
// 2. Calculate current amount
let current_amount = calculate_protocol_position (
ctx . accounts . your_position_account,
ctx . accounts . your_market_account
) ? ;
// 3. Update strategy state
ctx . accounts . vault_strategy . current_amount = current_amount;
Ok (())
}
4. Withdraw Implementation
Handle withdrawals from your protocol:
Copy #[derive( Accounts )]
pub struct Withdraw <' info > {
#[account( mut )]
pub vault_asset_idle_auth : Signer <' info >,
#[account(constraint = strategy . counterparty_asset_ta . key() == counterparty_asset_ta . key())]
pub strategy : Box < Account <' info , Strategy >>,
#[account( mut )]
pub vault_strategy : Account <' info , VaultStrategy >,
#[account( mut )]
pub vault_asset_mint : Box < InterfaceAccount <' info , Mint >>,
pub vault_asset_idle_ata : Box < InterfaceAccount <' info , TokenAccount >>,
pub counterparty_asset_ta : Box < InterfaceAccount <' info , TokenAccount >>,
pub counterparty_asset_ta_auth : AccountInfo <' info >,
pub protocol_program : AccountInfo <' info >,
pub asset_token_program : Interface <' info , TokenInterface >,
}
pub fn handle_withdraw (ctx : Context < Withdraw >, amount : u64 ) -> Result <()> {
// 1. Execute protocol withdrawal
your_protocol :: cpi :: withdraw (
CpiContext :: new (
ctx . accounts . protocol_program . to_account_info (),
your_protocol :: cpi :: accounts :: Withdraw {
// Your protocol's withdrawal accounts
}
),
amount
) ? ;
// 2. Calculate remaining amount
let remaining_amount = calculate_protocol_position (
ctx . accounts . your_position_account,
ctx . accounts . your_market_account
) ? ;
// 3. Update strategy state
ctx . accounts . vault_strategy . current_amount = remaining_amount;
Ok (())
}
Position Tracking
Implement accurate position tracking:
Copy pub fn calculate_protocol_position < T : ProtocolPosition >(
position : & T ,
market : & ProtocolMarket
) -> Result < u64 > {
// 1. Get base values
let raw_amount = position . get_raw_amount () ? ;
let exchange_rate = market . get_current_rate () ? ;
// 2. Apply protocol-specific calculations
let adjusted_amount = apply_protocol_adjustments (
raw_amount,
exchange_rate,
market . get_parameters () ?
) ? ;
// 3. Handle decimals
let normalized_amount = normalize_amount (
adjusted_amount,
market . decimals
) ? ;
Ok (normalized_amount)
}
Error Handling
Define protocol-specific errors:
Copy #[error_code]
pub enum AdaptorError {
#[msg( "Invalid amount provided" )]
InvalidAmount ,
#[msg( "Invalid account owner" )]
InvalidAccountOwner ,
#[msg( "Protocol initialization failed" )]
ProtocolInitializeFailed ,
#[msg( "Protocol deposit failed" )]
ProtocolDepositFailed ,
#[msg( "Protocol withdrawal failed" )]
ProtocolWithdrawFailed ,
#[msg( "Math overflow" )]
MathOverflow ,
// Add your protocol-specific errors
}
Testing Requirements
Copy #[cfg(test)]
mod tests {
#[test]
fn test_create_strategy () {
// Test strategy creation
}
#[test]
fn test_initialize () {
// Test initialization
}
#[test]
fn test_deposit () {
// Test deposits
}
#[test]
fn test_withdraw () {
// Test withdrawals
}
#[test]
fn test_position_calculations () {
// Test position tracking
}
}
Copy #[cfg(test)]
mod integration_tests {
#[test]
async fn test_full_workflow () {
// 1. Create strategy
let strategy = create_test_strategy () .await? ;
// 2. Initialize
initialize_strategy ( & strategy) .await? ;
// 3. Deposit
let deposit_amount = 1000000 ;
deposit_to_strategy ( & strategy, deposit_amount) .await? ;
// 4. Verify position
let position = get_strategy_position ( & strategy) .await? ;
assert_eq! (position, deposit_amount);
// 5. Withdraw
withdraw_from_strategy ( & strategy, deposit_amount) .await? ;
// 6. Verify zero balance
let final_position = get_strategy_position ( & strategy) .await? ;
assert_eq! (final_position, 0 );
}
}
Security Checklist
Before deployment, ensure:
Common Pitfalls
Account Handling
Always validate account ownership
Check for account initialization
Verify account relationships
Handle remaining accounts properly
Position Tracking
Account for protocol fees
Track exchange rate changes
Update positions atomically
Error Handling
Propagate protocol errors
Handle partial operations
Provide clear error messages
Maintain vault consistency
Best Practices
Code Organization
Document complex calculations
Implement clean error handling
For additional support or questions, refer to the Voltr documentation or contact the development team.