Skip to content

Latest commit

 

History

History

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 

README.md

PVQ Extension Swap

A specialized extension for the PVQ (PolkaVM Query) system that enables guest programs to interact with decentralized exchange (DEX) and liquidity pool functionality. This extension provides comprehensive access to swap information, price quotes, and liquidity data.

Overview

The Swap Extension integrates with DEX pallets and liquidity protocols to provide PVQ guest programs with read-only access to trading and liquidity information. It supports various AMM (Automated Market Maker) models and liquidity pool configurations.

Features

  • 🏊 Pool Discovery: List and query available liquidity pools
  • 💱 Price Quotes: Get accurate swap price estimates
  • 📊 Liquidity Information: Query pool reserves and liquidity depth
  • 📈 Trading Analytics: Calculate slippage, fees, and trading metrics
  • 🔍 Route Finding: Discover optimal trading paths
  • Multi-DEX Support: Support for various DEX implementations

Architecture

┌─────────────────────────────────────────────────────────────┐
│                  ExtensionSwap                              │
├─────────────────────────────────────────────────────────────┤
│  ┌─────────────────┐  ┌─────────────────┐  ┌──────────────┐ │
│  │     Pools       │  │     Pricing     │  │   Analytics  │ │
│  │                 │  │                 │  │              │ │
│  │ ├─ Pool List    │  │ ├─ Quote        │  │ ├─ Volume    │ │
│  │ ├─ Reserves     │  │ │  Calculation  │  │ ├─ Slippage  │ │
│  │ ├─ Pool Info    │  │ ├─ Price Impact │  │ ├─ Fees      │ │
│  │ ├─ Liquidity    │  │ ├─ Route        │  │ ├─ APR/APY   │ │
│  │ │  Depth        │  │ │  Discovery    │  │ └─ Historical│ │
│  │ └─ Pool Status  │  │ └─ Multi-hop    │  │    Data      │ │
│  │                 │  │    Routing      │  │              │ │
│  └─────────────────┘  └─────────────────┘  └──────────────┘ │
└─────────────────────────────────────────────────────────────┘

API Reference

Pool Discovery

list_pools() -> Vec<PoolId>

Get a list of all available liquidity pools.

use pvq_extension_swap::ExtensionSwap;

#[pvq_program::program]
fn available_pools() -> Vec<PoolId> {
    ExtensionSwap::list_pools()
}

pool_exists(pool_id: PoolId) -> bool

Check if a specific pool exists.

use pvq_extension_swap::{ExtensionSwap, PoolId};

let pool_id = PoolId::new(1, 2); // Token pair (1, 2)
if ExtensionSwap::pool_exists(pool_id) {
    // Pool exists
}

pools_for_asset(asset_id: u32) -> Vec<PoolId>

Find all pools containing a specific asset.

use pvq_extension_swap::ExtensionSwap;

let pools = ExtensionSwap::pools_for_asset(asset_id);
println!("Found {} pools with asset {}", pools.len(), asset_id);

Pool Information

pool_info(pool_id: PoolId) -> Option<PoolInfo>

Get comprehensive information about a pool.

use pvq_extension_swap::{ExtensionSwap, PoolId};

if let Some(info) = ExtensionSwap::pool_info(pool_id) {
    println!("Pool reserves: {} / {}", info.reserve_0, info.reserve_1);
    println!("Total liquidity: {}", info.total_liquidity);
}

pool_reserves(pool_id: PoolId) -> Option<(u128, u128)>

Get current pool reserves for both assets.

use pvq_extension_swap::{ExtensionSwap, PoolId};

if let Some((reserve_a, reserve_b)) = ExtensionSwap::pool_reserves(pool_id) {
    println!("Reserve A: {}, Reserve B: {}", reserve_a, reserve_b);
}

pool_total_liquidity(pool_id: PoolId) -> Option<u128>

Get the total liquidity in a pool.

use pvq_extension_swap::{ExtensionSwap, PoolId};

if let Some(liquidity) = ExtensionSwap::pool_total_liquidity(pool_id) {
    println!("Total liquidity: {}", liquidity);
}

Price Quotation

quote_exact_in(asset_in: u32, asset_out: u32, amount_in: u128) -> Option<SwapQuote>

Get a quote for swapping an exact input amount.

use pvq_extension_swap::ExtensionSwap;

#[pvq_program::program]
fn get_swap_quote(asset_in: u32, asset_out: u32, amount_in: u128) -> String {
    match ExtensionSwap::quote_exact_in(asset_in, asset_out, amount_in) {
        Some(quote) => {
            format!(
                "Output: {}, Price Impact: {:.2}%, Fee: {}",
                quote.amount_out, quote.price_impact, quote.fee
            )
        }
        None => "No route available".to_string(),
    }
}

quote_exact_out(asset_in: u32, asset_out: u32, amount_out: u128) -> Option<SwapQuote>

Get a quote for receiving an exact output amount.

use pvq_extension_swap::ExtensionSwap;

if let Some(quote) = ExtensionSwap::quote_exact_out(asset_in, asset_out, amount_out) {
    println!("Required input: {}", quote.amount_in);
}

get_price(asset_in: u32, asset_out: u32) -> Option<f64>

Get the current price ratio between two assets.

use pvq_extension_swap::ExtensionSwap;

if let Some(price) = ExtensionSwap::get_price(asset_in, asset_out) {
    println!("Price: 1 token {} = {} token {}", asset_in, price, asset_out);
}

Trading Analytics

calculate_slippage(pool_id: PoolId, amount_in: u128) -> Option<f64>

Calculate expected slippage for a trade.

use pvq_extension_swap::{ExtensionSwap, PoolId};

if let Some(slippage) = ExtensionSwap::calculate_slippage(pool_id, amount_in) {
    println!("Expected slippage: {:.2}%", slippage);
}

pool_fee_rate(pool_id: PoolId) -> Option<u32>

Get the fee rate for a pool (in basis points).

use pvq_extension_swap::{ExtensionSwap, PoolId};

if let Some(fee) = ExtensionSwap::pool_fee_rate(pool_id) {
    println!("Pool fee: {:.2}%", fee as f64 / 100.0);
}

pool_volume_24h(pool_id: PoolId) -> Option<u128>

Get 24-hour trading volume for a pool.

use pvq_extension_swap::{ExtensionSwap, PoolId};

if let Some(volume) = ExtensionSwap::pool_volume_24h(pool_id) {
    println!("24h volume: {}", volume);
}

Route Discovery

find_route(asset_in: u32, asset_out: u32) -> Option<Vec<PoolId>>

Find the optimal trading route between two assets.

use pvq_extension_swap::ExtensionSwap;

if let Some(route) = ExtensionSwap::find_route(asset_in, asset_out) {
    println!("Route found with {} hops", route.len());
} else {
    println!("No route available");
}

multi_hop_quote(route: Vec<PoolId>, amount_in: u128) -> Option<SwapQuote>

Get a quote for a multi-hop swap route.

use pvq_extension_swap::ExtensionSwap;

let route = ExtensionSwap::find_route(asset_in, asset_out)?;
if let Some(quote) = ExtensionSwap::multi_hop_quote(route, amount_in) {
    println!("Multi-hop output: {}", quote.amount_out);
}

Data Types

PoolId

Unique identifier for a liquidity pool:

pub struct PoolId {
    pub asset_0: u32,
    pub asset_1: u32,
}

impl PoolId {
    pub fn new(asset_0: u32, asset_1: u32) -> Self {
        // Ensures asset_0 < asset_1 for consistency
        if asset_0 < asset_1 {
            Self { asset_0, asset_1 }
        } else {
            Self { asset_0: asset_1, asset_1: asset_0 }
        }
    }
}

PoolInfo

Comprehensive pool information:

pub struct PoolInfo {
    pub pool_id: PoolId,
    pub reserve_0: u128,
    pub reserve_1: u128,
    pub total_liquidity: u128,
    pub fee_rate: u32, // In basis points
    pub is_active: bool,
}

SwapQuote

Detailed swap quotation:

pub struct SwapQuote {
    pub amount_in: u128,
    pub amount_out: u128,
    pub price_impact: f64,   // Percentage
    pub fee: u128,
    pub route: Vec<PoolId>,
    pub slippage_tolerance: f64,
}

Usage Examples

Basic Pool Information

use pvq_extension_swap::{ExtensionSwap, PoolId};

#[pvq_program::program]
fn pool_summary(asset_a: u32, asset_b: u32) -> String {
    let pool_id = PoolId::new(asset_a, asset_b);
    
    match ExtensionSwap::pool_info(pool_id) {
        Some(info) => {
            format!(
                "Pool {}-{}: Reserves: {} / {}, Liquidity: {}, Fee: {:.2}%",
                asset_a, asset_b,
                info.reserve_0, info.reserve_1,
                info.total_liquidity,
                info.fee_rate as f64 / 100.0
            )
        }
        None => "Pool not found".to_string(),
    }
}

Price Comparison Across Pools

use pvq_extension_swap::ExtensionSwap;

#[pvq_program::program]
fn compare_prices(asset_in: u32, asset_out: u32) -> Vec<(u32, f64)> {
    let pools = ExtensionSwap::pools_for_asset(asset_in);
    let mut prices = Vec::new();
    
    for pool in pools {
        if pool.asset_0 == asset_out || pool.asset_1 == asset_out {
            if let Some(price) = ExtensionSwap::get_price(asset_in, asset_out) {
                prices.push((pool.asset_0.max(pool.asset_1), price));
            }
        }
    }
    
    prices
}

Arbitrage Opportunity Scanner

use pvq_extension_swap::{ExtensionSwap, PoolId};

#[pvq_program::program]
fn find_arbitrage(asset_a: u32, asset_b: u32, amount: u128) -> f64 {
    let pools = ExtensionSwap::list_pools();
    let mut best_profit = 0.0;
    
    for pool in pools {
        if (pool.asset_0 == asset_a && pool.asset_1 == asset_b) ||
           (pool.asset_0 == asset_b && pool.asset_1 == asset_a) {
            
            // Check price in this pool vs others
            if let Some(quote) = ExtensionSwap::quote_exact_in(asset_a, asset_b, amount) {
                let return_quote = ExtensionSwap::quote_exact_in(asset_b, asset_a, quote.amount_out);
                
                if let Some(return_quote) = return_quote {
                    let profit = return_quote.amount_out as f64 - amount as f64;
                    let profit_pct = (profit / amount as f64) * 100.0;
                    
                    if profit_pct > best_profit {
                        best_profit = profit_pct;
                    }
                }
            }
        }
    }
    
    best_profit
}

Liquidity Analysis

use pvq_extension_swap::ExtensionSwap;

#[pvq_program::program]
fn liquidity_analysis(asset_id: u32) -> String {
    let pools = ExtensionSwap::pools_for_asset(asset_id);
    let mut total_liquidity = 0u128;
    let mut active_pools = 0u32;
    
    for pool_id in pools {
        if let Some(info) = ExtensionSwap::pool_info(pool_id) {
            if info.is_active {
                total_liquidity += info.total_liquidity;
                active_pools += 1;
            }
        }
    }
    
    format!(
        "Asset {}: {} active pools, Total liquidity: {}",
        asset_id, active_pools, total_liquidity
    )
}

Best Route Finder

use pvq_extension_swap::ExtensionSwap;

#[pvq_program::program]
fn best_swap_route(asset_in: u32, asset_out: u32, amount: u128) -> String {
    match ExtensionSwap::find_route(asset_in, asset_out) {
        Some(route) => {
            match ExtensionSwap::multi_hop_quote(route.clone(), amount) {
                Some(quote) => {
                    format!(
                        "Best route: {} hops, Output: {}, Price Impact: {:.2}%",
                        route.len(), quote.amount_out, quote.price_impact
                    )
                }
                None => "Route found but quote failed".to_string(),
            }
        }
        None => "No route available".to_string(),
    }
}

Integration

With Runtime

Configure the swap extension in your runtime:

// In runtime configuration  
impl pvq_extension_swap::Config for Runtime {
    type AssetId = u32;
    type Balance = u128;
    type DexPallet = Dex;
    type PoolId = PoolIdOf<Self>;
}

With Extension Executor

Register the swap extension:

use pvq_extension::ExtensionsExecutor;
use pvq_extension_swap::ExtensionSwap;

let mut executor = ExtensionsExecutor::new();
executor.register::<ExtensionSwap>();

Configuration

DEX Integration

Configure supported DEX types:

impl pvq_extension_swap::Config for Runtime {
    type SupportedDexes = (
        UniswapV2Pallet,
        CurveStablePallet,
        BalancerPallet,
    );
}

Fee Configuration

Set fee calculation parameters:

impl pvq_extension_swap::Config for Runtime {
    type DefaultFeeRate = ConstU32<30>; // 0.30%
    type MaxSlippageTolerance = ConstU32<500>; // 5.00%
}

Development

Building

# Build swap extension
cargo build -p pvq-extension-swap

# Run tests
cargo test -p pvq-extension-swap

# Generate documentation
cargo doc -p pvq-extension-swap --open

Testing

# Unit tests
cargo test -p pvq-extension-swap

# Integration tests with DEX pallets
cargo test -p pvq-extension-swap --test dex_integration

# Test with guest programs
cargo run -p pvq-test-runner -- --program test-swap

Benchmarking

# Run performance benchmarks
cargo bench -p pvq-extension-swap

# Profile quote calculations
cargo run --example profile_quotes --release

Performance Considerations

Optimization Tips

  1. Cache Pool Data: Pool information changes infrequently
  2. Batch Route Discovery: Find multiple routes in single queries
  3. Limit Route Depth: Set maximum hops for complex routes
  4. Filter Inactive Pools: Only query active pools

Query Patterns

// Efficient: Batch multiple quotes
#[pvq_program::program]
fn batch_quotes(pairs: Vec<(u32, u32)>, amount: u128) -> Vec<Option<u128>> {
    pairs.iter()
        .map(|(asset_in, asset_out)| {
            ExtensionSwap::quote_exact_in(*asset_in, *asset_out, amount)
                .map(|quote| quote.amount_out)
        })
        .collect()
}

Security Considerations

  • All functions are read-only and cannot execute trades
  • Price quotes are estimates and may differ from actual execution
  • Pool data reflects current state, which can change rapidly
  • Route calculations consider current liquidity conditions

Common Use Cases

DEX Aggregation

// Find best price across multiple DEXes
#[pvq_program::program]
fn best_price_aggregation(asset_in: u32, asset_out: u32, amount: u128) -> SwapQuote {
    let all_routes = ExtensionSwap::find_all_routes(asset_in, asset_out);
    
    all_routes.into_iter()
        .filter_map(|route| ExtensionSwap::multi_hop_quote(route, amount))
        .max_by_key(|quote| quote.amount_out)
        .unwrap_or_default()
}

Yield Farming Analysis

// Calculate potential LP returns
#[pvq_program::program]
fn lp_yield_analysis(pool_id: PoolId) -> f64 {
    let volume = ExtensionSwap::pool_volume_24h(pool_id).unwrap_or(0);
    let liquidity = ExtensionSwap::pool_total_liquidity(pool_id).unwrap_or(1);
    let fee_rate = ExtensionSwap::pool_fee_rate(pool_id).unwrap_or(0);
    
    // Simple APR calculation based on fees
    let daily_fees = (volume * fee_rate as u128) / 10000;
    let apr = (daily_fees as f64 / liquidity as f64) * 365.0 * 100.0;
    
    apr
}

Related Components


The Swap Extension provides comprehensive DEX and liquidity pool access for PVQ programs.