Custom Adaptors Creation

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

  • Position management

Required Components

1. State Accounts

First, implement the required state accounts:

#[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:

// 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:

#[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:

#[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:

#[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:

#[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:

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:

#[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

  1. Unit Tests

#[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
    }
}
  1. Integration Tests

#[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:

  1. Account Validation

  2. Position Safety

  3. State Management

Common Pitfalls

  1. Account Handling

    • Always validate account ownership

    • Check for account initialization

    • Verify account relationships

    • Handle remaining accounts properly

  2. Position Tracking

    • Account for protocol fees

    • Handle decimal precision

    • Track exchange rate changes

    • Update positions atomically

  3. Error Handling

    • Propagate protocol errors

    • Handle partial operations

    • Provide clear error messages

    • Maintain vault consistency

Best Practices

  1. Code Organization

    • Separate protocol logic

    • Use helper functions

    • Document complex calculations

    • Implement clean error handling

  2. Testing

    • Test edge cases

    • Verify calculations

    • Test error conditions

    • Use integration tests

  3. Documentation

    • Document all accounts

    • Explain calculations

    • Note assumptions

    • Provide examples

For additional support or questions, refer to the Voltr documentation or contact the development team.

Last updated