bolt-cw-sdk 1.0.0

SDK for the BOLT protocol, providing utilities for interacting with the BOLT contracts.
Documentation
use crate::cosmos_client::query_contract::QueryContractResponse;
use crate::router::client::RouterClient;
use crate::router::error::RouterError;
use cosmrs::Coin;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;

impl RouterClient {
    pub async fn quotes_for_user_all(
        &self,
        user: String,
        height: Option<u64>,
    ) -> Result<QueryContractResponse<HashMap<String, Vec<Coin>>>, RouterError> {
        let query = Query {
            quotes_for_user_all: QuotesForUserAllRequest { lp_address: user },
        };
        let response = self
            .query_contract::<_, QuotesForUserAllResponse>(
                self.router_contract_address.to_string(),
                &query,
                height,
            )
            .await?;

        let data = response
            .data
            .quotes
            .into_iter()
            .map(|(addr, coins)| {
                let cosmr_coins = coins
                    .iter()
                    .map(|c| Coin::new(c.amount.u128(), &c.denom))
                    .collect::<Result<Vec<_>, _>>();

                match cosmr_coins {
                    Ok(coins) => Ok((addr, coins)),
                    Err(err) => Err(err),
                }
            })
            .collect::<Result<HashMap<_, _>, _>>()
            .map_err(RouterError::EyreError)?;

        Ok(QueryContractResponse {
            data,
            height: response.height,
        })
    }
}

#[derive(Serialize)]
struct Query {
    pub quotes_for_user_all: QuotesForUserAllRequest,
}

#[derive(Serialize)]
pub struct QuotesForUserAllRequest {
    pub lp_address: String,
}

#[derive(Deserialize)]
pub struct QuotesForUserAllResponse {
    // mapping between market address and the amount of liquidity in it
    // NOTE: Deserialization fails when used for serialized `cosmwasm_std::Coin` to `cosmrs::Coin`.
    pub quotes: HashMap<String, Vec<cosmwasm_std::Coin>>,
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::market::client::MarketAdminClient;
    use crate::oracle::client::OracleAdminClient;
    use crate::router::client::RouterAdminClient;
    use crate::test_utils::helpers::{
        TEST_ASSET_ARCH_SYMBOL, TEST_ASSET_ETH_SYMBOL, TEST_ASSET_USDT_SYMBOL,
    };
    use crate::test_utils::test_scenario::TestScenario;
    use cosmrs::AccountId;
    use cosmwasm_std::{Decimal256, Uint128};
    use serial_test::serial;
    use std::ops::Add;
    use std::str::FromStr;
    use std::time::{Duration, SystemTime, UNIX_EPOCH};

    #[tokio::test]
    #[serial]
    async fn test_quotes_for_user_all() {
        let mut test_scenario = TestScenario::new_from_config("config.json".to_string()).await;

        // Initialize oracle contract
        let price_threshold_ratio = Decimal256::from_str("0.5").unwrap();
        let price_expire_millis = Some(1000);
        let oracle_contract_address = test_scenario
            .instantiate_oracle_contract(price_threshold_ratio, price_expire_millis)
            .await;

        // Initialize router contract
        let default_protocol_fee = Decimal256::from_str("0.1").unwrap();
        let default_lp_fee = Decimal256::from_str("0.1").unwrap();
        let router_contract_address = test_scenario
            .instantiate_router_contract(
                oracle_contract_address.clone(),
                oracle_contract_address.clone(),
                default_protocol_fee,
                default_lp_fee,
            )
            .await;

        // Set default assets and prices
        let oracle_client =
            OracleAdminClient::from_scenario(&test_scenario, &oracle_contract_address)
                .expect("Failed to create oracle admin client");
        let price_expiry_time = SystemTime::now().add(Duration::from_secs(7200)); // 2h from now
        let price_expiry_timestamp = price_expiry_time
            .duration_since(UNIX_EPOCH)
            .expect("Converting to timestamp failed");
        test_scenario.set_default_assets(&oracle_client).await;
        test_scenario
            .set_default_prices(&oracle_client, price_expiry_timestamp, "50000")
            .await;

        let router_client = RouterAdminClient::from_scenario(
            &test_scenario,
            router_contract_address.parse().unwrap(),
        )
        .expect("Failed to create router admin client");

        // Create market
        let market_address = test_scenario
            .create_market(
                &router_client,
                TEST_ASSET_USDT_SYMBOL,
                &[TEST_ASSET_ARCH_SYMBOL, TEST_ASSET_ETH_SYMBOL],
                Uint128::new(10),
            )
            .await;

        let market_client = MarketAdminClient::from_scenario(&test_scenario)
            .expect("Failed to create market client");

        // Fund market
        let admin_account = test_scenario.admin_account();
        test_scenario
            .deposit_base(
                &market_client,
                AccountId::from_str(market_address.as_str()).unwrap(),
                &admin_account,
                Coin::new(10000000, TEST_ASSET_USDT_SYMBOL).unwrap(),
            )
            .await;

        for (quote, amount) in [(TEST_ASSET_ARCH_SYMBOL, 100), (TEST_ASSET_ETH_SYMBOL, 90)] {
            test_scenario
                .swap_exact_in(
                    &router_client,
                    TEST_ASSET_USDT_SYMBOL,
                    None,
                    None,
                    amount,
                    quote.to_string(),
                )
                .await;
        }

        // Query quotes
        let quotes = router_client
            .public_router_client
            .quotes_for_user_all(test_scenario.admin_address, None)
            .await
            .expect("Failed to fetch quotes")
            .data;

        let assets = quotes
            .get(&market_address)
            .expect("Expected market address to be in response");

        assert_eq!(assets.len(), 2);
        let arch_amount = assets
            .iter()
            .find(|a| a.denom.as_ref() == TEST_ASSET_ARCH_SYMBOL)
            .map(|coin| coin.amount)
            .expect("Expected arch to be in response");
        assert_eq!(arch_amount, 90);

        let eth_amount = assets
            .iter()
            .find(|a| a.denom.as_ref() == TEST_ASSET_ETH_SYMBOL)
            .map(|coin| coin.amount)
            .expect("Expected eth to be in response");
        assert_eq!(eth_amount, 81);
    }
}