nado-sdk 1.0.0

Official Rust SDK for the Nado Protocol API
Documentation
use alloy::rpc::types::TransactionReceipt;
use alloy_primitives::{Address, U256};
use async_trait::async_trait;
use ethers_core::types::Bytes as EthersBytes;
use eyre::{eyre, Result};

use crate::bindings::endpoint;
use crate::bindings::querier::Querier;
use crate::builders::execute::deposit_collateral::DepositCollateralParams;
use crate::builders::execute::slow_mode::SubmitSlowModeTxParams;
use crate::core::query::NadoQuery;
use crate::eip712_structs::{
    BurnNlp, Cancellation, CancellationProducts, DependencyUpdate, LinkSigner, LiquidateSubaccount,
    MintNlp, TransferQuote, WithdrawCollateral, WithdrawCollateralV2,
};
use crate::engine::{
    CancelOrdersResponse, Execute, ExecuteResponseData, PlaceOrder, PlaceOrderResponse,
};
use crate::provider::{
    signer_provider_from_signer_sync, wait_for_transaction_receipt, NadoProvider,
};
use crate::trigger;
use crate::utils::constants::ETHERS_DEFAULT_PENDING_TX_RETRIES;
use crate::utils::deposit::erc20_client;
use crate::utils::nonce::order_nonce;
use crate::utils::response::match_cancel_orders_response;

macro_rules! map_response_type {
    ($response_data:expr, $enum_variant:path => $output_type:ty) => {
        match $response_data {
            Some(data) => match data {
                $enum_variant(response) => Ok(Some(response as $output_type)),
                _ => Err(eyre!("Unexpected response type")),
            },
            None => Ok(None),
        }
    };
}

#[async_trait]
pub trait NadoExecute: NadoQuery {
    async fn execute(&self, execute: Execute) -> Result<Option<ExecuteResponseData>>;

    async fn execute_trigger(
        &self,
        execute: trigger::Execute,
    ) -> Result<Option<ExecuteResponseData>>;

    async fn submit_slow_mode_tx(
        &self,
        params: SubmitSlowModeTxParams,
    ) -> Result<Option<TransactionReceipt>>;

    async fn place_order(&self, place_order: PlaceOrder) -> Result<Option<PlaceOrderResponse>> {
        let execute = Execute::PlaceOrder(place_order);
        let execute_response_data = self.execute(execute).await?;
        map_response_type!(execute_response_data, ExecuteResponseData::PlaceOrder => PlaceOrderResponse)
    }

    async fn place_trigger_order(
        &self,
        place_trigger_order: trigger::PlaceTriggerOrder,
    ) -> Result<Option<PlaceOrderResponse>> {
        let execute = trigger::Execute::PlaceOrder(place_trigger_order);
        let execute_response_data = self.execute_trigger(execute).await?;
        map_response_type!(execute_response_data, ExecuteResponseData::PlaceOrder => PlaceOrderResponse)
    }

    async fn cancel_orders(&self, tx: Cancellation) -> Result<Option<CancelOrdersResponse>> {
        let signature = self.endpoint_signature(&tx)?;
        let execute = Execute::CancelOrders {
            tx,
            signature,
            required_unfilled_amount: None,
            id: None,
        };
        let execute_response_data = self.execute(execute).await?;
        match_cancel_orders_response(execute_response_data)
    }

    async fn cancel_trigger_orders(&self, tx: Cancellation) -> Result<()> {
        let signature: EthersBytes = self.endpoint_signature(&tx)?.into();
        let execute = trigger::Execute::CancelOrders { tx, signature };
        self.execute_trigger(execute).await?;
        Ok(())
    }

    async fn cancel_product_orders(
        &self,
        tx: CancellationProducts,
    ) -> Result<Option<CancelOrdersResponse>> {
        let signature = self.endpoint_signature(&tx)?;
        let digest = Some(self.signer().endpoint_digest(&tx)?);
        let execute = Execute::CancelProductOrders {
            tx,
            signature,
            digest,
            id: None,
        };
        let execute_response_data = self.execute(execute).await?;
        match_cancel_orders_response(execute_response_data)
    }

    async fn cancel_product_trigger_orders(&self, tx: CancellationProducts) -> Result<()> {
        let signature: EthersBytes = self.endpoint_signature(&tx)?.into();
        let execute = trigger::Execute::CancelProductOrders { tx, signature };
        self.execute_trigger(execute).await?;
        Ok(())
    }

    async fn update_dependency(
        &self,
        old_digest: [u8; 32],
        new_digest: [u8; 32],
    ) -> Result<Option<PlaceOrderResponse>> {
        let tx = DependencyUpdate {
            sender: self.subaccount()?,
            oldDigest: old_digest,
            newDigest: new_digest,
            nonce: order_nonce(None),
        };
        let signature: EthersBytes = self.endpoint_signature(&tx)?.into();
        let execute = trigger::Execute::UpdateDependency { tx, signature };
        let execute_response_data = self.execute_trigger(execute).await?;
        map_response_type!(execute_response_data, ExecuteResponseData::PlaceOrder => PlaceOrderResponse)
    }

    async fn cancel_and_place(
        &self,
        cancel_tx: Cancellation,
        place_order: PlaceOrder,
        place_requires_unfilled: bool,
    ) -> Result<Option<PlaceOrderResponse>> {
        let cancel_signature = self.endpoint_signature(&cancel_tx)?;
        let execute = Execute::CancelAndPlace {
            cancel_tx,
            cancel_signature,
            place_order,
            place_requires_unfilled: Some(place_requires_unfilled),
            required_unfilled_amount: None,
        };
        let execute_response_data = self.execute(execute).await?;
        map_response_type!(execute_response_data, ExecuteResponseData::PlaceOrder => PlaceOrderResponse)
    }

    async fn liquidate_subaccount(&self, tx: LiquidateSubaccount) -> Result<()> {
        let signature = self.endpoint_signature(&tx)?;
        let execute = Execute::LiquidateSubaccount { tx, signature };
        self.execute(execute).await?;
        Ok(())
    }

    async fn withdraw_collateral(
        &self,
        tx: WithdrawCollateral,
        spot_leverage: Option<bool>,
    ) -> Result<()> {
        let signature = self.endpoint_signature(&tx)?;
        let execute = Execute::WithdrawCollateral {
            tx,
            signature,
            spot_leverage,
            sequencer_risk_check: None,
        };
        self.execute(execute).await?;
        Ok(())
    }

    async fn withdraw_collateral_v2(
        &self,
        tx: WithdrawCollateralV2,
        spot_leverage: Option<bool>,
    ) -> Result<()> {
        let signature = self.endpoint_signature(&tx)?;
        let execute = Execute::WithdrawCollateralV2 {
            tx,
            signature,
            spot_leverage,
            sequencer_risk_check: None,
        };
        self.execute(execute).await?;
        Ok(())
    }

    async fn mint_nlp(&self, tx: MintNlp, spot_leverage: Option<bool>) -> Result<()> {
        let signature = self.endpoint_signature(&tx)?;
        let execute = Execute::MintNlp {
            tx,
            signature,
            spot_leverage,
        };
        self.execute(execute).await?;
        Ok(())
    }

    async fn burn_nlp(&self, tx: BurnNlp) -> Result<()> {
        let signature = self.endpoint_signature(&tx)?;
        let execute = Execute::BurnNlp { tx, signature };
        self.execute(execute).await?;
        Ok(())
    }

    async fn transfer_quote(&self, tx: TransferQuote) -> Result<()> {
        let signature = self.endpoint_signature(&tx)?;
        let execute = Execute::TransferQuote { tx, signature };
        self.execute(execute).await?;
        Ok(())
    }

    async fn link_signer(&self, tx: LinkSigner) -> Result<()> {
        let signature = self.endpoint_signature(&tx)?;
        let execute = Execute::LinkSigner { tx, signature };
        self.execute(execute).await?;
        Ok(())
    }

    async fn deposit_collateral(
        &self,
        deposit_collateral_params: DepositCollateralParams,
    ) -> Result<Option<TransactionReceipt>>;

    async fn approve_endpoint_allowance(
        &self,
        product_id: u32,
        amount: u128,
    ) -> Result<Option<TransactionReceipt>> {
        self.approve(self.endpoint_addr(), product_id, amount).await
    }

    #[deprecated(
        since = "0.2.2",
        note = "please use `approve_endpoint_allowance` or `approve` instead"
    )]
    async fn approve_allowance(
        &self,
        product_id: u32,
        amount: u128,
    ) -> Result<Option<TransactionReceipt>> {
        self.approve(self.endpoint_addr(), product_id, amount).await
    }

    async fn approve(
        &self,
        spender: Address,
        product_id: u32,
        amount: u128,
    ) -> Result<Option<TransactionReceipt>> {
        self.approve_with_gas_price(spender, product_id, amount, None)
            .await
    }

    async fn approve_with_gas_price(
        &self,
        spender: Address,
        product_id: u32,
        amount: u128,
        gas_price: Option<u128>,
    ) -> Result<Option<TransactionReceipt>> {
        let erc20_client = erc20_client(self, product_id).await?;
        let mut call = erc20_client.approve(spender, U256::from(amount));
        if let Some(price) = gas_price {
            call = call.gas_price(price);
        }
        let pending_tx = call.send().await?;
        let receipt = wait_for_transaction_receipt(
            erc20_client.provider(),
            *pending_tx.tx_hash(),
            ETHERS_DEFAULT_PENDING_TX_RETRIES,
        )
        .await?;
        println!("approved: {amount} of product id: {product_id}");
        Ok(receipt)
    }

    async fn get_token_allowance(&self, product_id: u32) -> Result<U256> {
        let erc20_client = erc20_client(self, product_id).await?;
        let owner = self.wallet()?.address();
        let allowance = erc20_client
            .allowance(owner, self.endpoint_addr())
            .call()
            .await?;
        Ok(allowance)
    }

    async fn get_token_balance(&self, product_id: u32) -> Result<U256> {
        let erc20_client = erc20_client(self, product_id).await?;
        let balance = erc20_client
            .balanceOf(self.wallet()?.address())
            .call()
            .await?;
        Ok(balance)
    }

    async fn get_token_decimals(&self, product_id: u32) -> Result<u8> {
        let erc20_client = erc20_client(self, product_id).await?;
        Ok(erc20_client.decimals().call().await?)
    }

    async fn mint_mock_erc20(
        &self,
        product_id: u32,
        amount: u128,
    ) -> Result<Option<TransactionReceipt>> {
        let erc20_client = erc20_client(self, product_id).await?;
        let pending_tx = erc20_client
            .mint(self.wallet()?.address(), U256::from(amount))
            .send()
            .await?;
        let receipt = wait_for_transaction_receipt(
            erc20_client.provider(),
            *pending_tx.tx_hash(),
            ETHERS_DEFAULT_PENDING_TX_RETRIES,
        )
        .await?;
        println!("minted: {amount} of product id: {product_id}");
        Ok(receipt)
    }

    fn endpoint(&self) -> Result<endpoint::EndpointInstance<NadoProvider>> {
        let provider = signer_provider_from_signer_sync(&self.node_url(), self.wallet()?.clone())?;
        Ok(endpoint::Endpoint::new(self.endpoint_addr(), provider))
    }

    fn querier(&self) -> Result<Querier::QuerierInstance<NadoProvider>> {
        let provider = signer_provider_from_signer_sync(&self.node_url(), self.wallet()?.clone())?;
        Ok(Querier::new(self.querier_addr(), provider))
    }
}