├── .gitignore ├── src ├── utils │ ├── mod.rs │ ├── config.rs │ ├── types.rs │ └── constants.rs ├── transactions │ ├── mod.rs │ ├── flashloan.rs │ ├── governance.rs │ ├── deepbook_admin.rs │ ├── balance_manager.rs │ └── deepbook.rs ├── lib.rs └── client.rs ├── Cargo.toml ├── examples ├── balance.rs ├── get_level2_range.rs ├── account_open_orders.rs └── account_order_map.rs ├── README.md ├── tests ├── deepbook_admin_tests.rs ├── balance_manager_tests.rs └── utils.rs └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | .idea 3 | -------------------------------------------------------------------------------- /src/utils/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod config; 2 | pub mod constants; 3 | pub mod types; 4 | -------------------------------------------------------------------------------- /src/transactions/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod balance_manager; 2 | pub mod deepbook; 3 | pub mod deepbook_admin; 4 | pub mod flashloan; 5 | pub mod governance; 6 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sui-deepbookv3" 3 | description = "Sui Deepbook V3" 4 | version = "0.1.0" 5 | authors = ["King "] 6 | license = "Apache-2.0" 7 | edition = "2021" 8 | readme = "README.md" 9 | keywords = ["sui", "deepbook"] 10 | categories = ["blockchain", "sui"] 11 | homepage = "https://github.com/lispking/sui-deepbookv3" 12 | repository = "https://github.com/lispking/sui-deepbookv3" 13 | 14 | [dependencies] 15 | sui-sdk = { git = "https://github.com/mystenlabs/sui", package = "sui-sdk"} 16 | tokio = { version = "1.2", features = ["full"] } 17 | anyhow = "1.0" 18 | lazy_static = "1.5.0" 19 | serde_json = "1.0.133" 20 | async-trait = "0.1.83" 21 | serde = "1.0.216" 22 | bcs = "0.1.6" 23 | 24 | [dev-dependencies] 25 | shared-crypto = { git = "https://github.com/mystenlabs/sui", package = "shared-crypto" } 26 | sui-keys = { git = "https://github.com/mystenlabs/sui", package = "sui-keys" } 27 | sui-config = { git = "https://github.com/mystenlabs/sui", package = "sui-config" } 28 | 29 | [[example]] 30 | name = "balance" 31 | path = "examples/balance.rs" 32 | 33 | [[example]] 34 | name = "account_order_map" 35 | path = "examples/account_order_map.rs" 36 | 37 | [[example]] 38 | name = "account_open_orders" 39 | path = "examples/account_open_orders.rs" 40 | 41 | [[example]] 42 | name = "get_level2_range" 43 | path = "examples/get_level2_range.rs" 44 | -------------------------------------------------------------------------------- /examples/balance.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use std::collections::HashMap; 3 | use sui_deepbookv3::utils::types::BalanceManager; 4 | use sui_sdk::types::base_types::SuiAddress; 5 | use sui_sdk::SuiClientBuilder; 6 | 7 | use sui_deepbookv3::client::DeepBookClient; 8 | use sui_deepbookv3::utils::config::Environment; 9 | 10 | #[tokio::main] 11 | async fn main() -> Result<()> { 12 | let env = Environment::Mainnet; 13 | let fullnode_url = "https://fullnode.mainnet.sui.io:443"; // Mainnet URL 14 | 15 | let mut balance_managers = HashMap::new(); 16 | balance_managers.insert( 17 | "MANAGER_1", 18 | BalanceManager { 19 | address: "0x344c2734b1d211bd15212bfb7847c66a3b18803f3f5ab00f5ff6f87b6fe6d27d" 20 | .to_string(), 21 | trade_cap: None, 22 | deposit_cap: None, 23 | withdraw_cap: None, 24 | }, 25 | ); 26 | 27 | let sui_client = SuiClientBuilder::default() 28 | .build(fullnode_url) 29 | .await 30 | .unwrap(); 31 | let db_client = DeepBookClient::new( 32 | sui_client, 33 | SuiAddress::random_for_testing_only(), 34 | env, 35 | Some(balance_managers), 36 | None, 37 | None, 38 | None, 39 | ); 40 | 41 | let assets = vec!["SUI", "USDC", "WUSDT", "WUSDC", "BETH", "DEEP"]; 42 | let manager = "MANAGER_1"; 43 | println!("Manager: {}", manager); 44 | 45 | for asset in assets { 46 | let balance = db_client.check_manager_balance(manager, asset).await?; 47 | println!("{:?}", balance); 48 | } 49 | 50 | Ok(()) 51 | } 52 | -------------------------------------------------------------------------------- /examples/get_level2_range.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use std::collections::HashMap; 3 | use sui_deepbookv3::client::DeepBookClient; 4 | use sui_deepbookv3::utils::config::Environment; 5 | use sui_deepbookv3::utils::types::BalanceManager; 6 | use sui_sdk::{types::base_types::SuiAddress, SuiClientBuilder}; 7 | 8 | #[tokio::main] 9 | async fn main() -> Result<()> { 10 | let env = Environment::Mainnet; 11 | let fullnode_url = "https://fullnode.mainnet.sui.io:443"; // Mainnet URL 12 | 13 | // Define balance managers 14 | let mut balance_managers = HashMap::new(); 15 | balance_managers.insert( 16 | "MANAGER_1", 17 | BalanceManager { 18 | address: "0x344c2734b1d211bd15212bfb7847c66a3b18803f3f5ab00f5ff6f87b6fe6d27d" 19 | .to_string(), 20 | trade_cap: None, 21 | deposit_cap: None, 22 | withdraw_cap: None, 23 | }, 24 | ); 25 | 26 | // Create SUI client 27 | let sui_client = SuiClientBuilder::default().build(fullnode_url).await?; 28 | 29 | // Create DeepBook client 30 | let db_client = DeepBookClient::new( 31 | sui_client, 32 | SuiAddress::random_for_testing_only(), 33 | env, 34 | Some(balance_managers), 35 | None, 36 | None, 37 | None, 38 | ); 39 | 40 | println!( 41 | "balance: {:?}", 42 | db_client.check_manager_balance("MANAGER_1", "SUI").await? 43 | ); 44 | println!( 45 | "level2: {:?}", 46 | db_client 47 | .get_level2_range("SUI_USDC", 0.1, 100.0, true) 48 | .await? 49 | ); 50 | 51 | Ok(()) 52 | } 53 | -------------------------------------------------------------------------------- /examples/account_open_orders.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use std::collections::HashMap; 3 | use sui_deepbookv3::client::DeepBookClient; 4 | use sui_deepbookv3::utils::config::Environment; 5 | use sui_deepbookv3::utils::types::BalanceManager; 6 | use sui_sdk::{types::base_types::SuiAddress, SuiClientBuilder}; 7 | 8 | #[tokio::main] 9 | async fn main() -> Result<()> { 10 | let env = Environment::Mainnet; 11 | let fullnode_url = "https://fullnode.mainnet.sui.io:443"; // Mainnet URL 12 | 13 | // Define balance managers 14 | let mut balance_managers = HashMap::new(); 15 | balance_managers.insert( 16 | "MANAGER_1", 17 | BalanceManager { 18 | address: "0x344c2734b1d211bd15212bfb7847c66a3b18803f3f5ab00f5ff6f87b6fe6d27d" 19 | .to_string(), 20 | trade_cap: None, 21 | deposit_cap: None, 22 | withdraw_cap: None, 23 | }, 24 | ); 25 | 26 | // Create SUI client 27 | let sui_client = SuiClientBuilder::default().build(fullnode_url).await?; 28 | 29 | // Create DeepBook client 30 | let db_client = DeepBookClient::new( 31 | sui_client, 32 | SuiAddress::random_for_testing_only(), 33 | env, 34 | Some(balance_managers), 35 | None, 36 | None, 37 | None, 38 | ); 39 | 40 | let manager = "MANAGER_1"; 41 | let pools = vec![ 42 | "SUI_USDC", 43 | "DEEP_SUI", 44 | "DEEP_USDC", 45 | "WUSDT_USDC", 46 | "WUSDC_USDC", 47 | "BETH_USDC", 48 | ]; 49 | 50 | println!("Manager: {}", manager); 51 | 52 | for pool in pools { 53 | println!("{}", pool); 54 | let open_orders = db_client.account_open_orders(pool, manager).await?; 55 | println!("{:?}", open_orders); 56 | } 57 | 58 | Ok(()) 59 | } 60 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Sui DeepBook v3 SDK 2 | 3 | A Rust SDK for interacting with DeepBook - a decentralized exchange (DEX) protocol on the Sui network. 4 | 5 | ## Features 6 | 7 | - Account management for orders and balances 8 | - DEX operations (place/cancel orders, check order status) 9 | - Liquidity pool interactions 10 | - Admin operations for DeepBook 11 | - Level2 orderbook data queries 12 | - Flash loan capabilities 13 | - Balance management utilities 14 | 15 | ## Installation 16 | 17 | Add this to your `Cargo.toml`: 18 | 19 | ```toml 20 | [dependencies] 21 | sui-deepbookv3 = { git = "https://github.com/hoh-zone/sui-deepbookv3" } 22 | ``` 23 | 24 | ## Quick Start 25 | 26 | Here are some example use cases: 27 | 28 | ### Check Account Open Orders 29 | ```rust 30 | use sui_deepbookv3::client::Client; 31 | 32 | #[tokio::main] 33 | async fn main() { 34 | let client = Client::new().await; 35 | let orders = client.get_account_open_orders().await; 36 | println!("Open orders: {:?}", orders); 37 | } 38 | ``` 39 | 40 | ### Query Level2 Order Book 41 | ```rust 42 | use sui_deepbookv3::client::Client; 43 | 44 | #[tokio::main] 45 | async fn main() { 46 | let client = Client::new().await; 47 | let level2_data = client.get_level2_book_status(0, 10).await; 48 | println!("Order book depth: {:?}", level2_data); 49 | } 50 | ``` 51 | 52 | ## Examples 53 | 54 | Check the `examples/` directory for more detailed usage: 55 | 56 | - `account_open_orders.rs` - Query account's open orders 57 | - `account_order_map.rs` - Map orders to account 58 | - `balance.rs` - Check account balances 59 | - `get_level2_range.rs` - Get order book depth 60 | 61 | ## Testing 62 | 63 | Run the test suite: 64 | 65 | ```bash 66 | cargo test 67 | ``` 68 | 69 | ## Contributing 70 | 71 | 1. Fork the repository 72 | 2. Create your feature branch (`git checkout -b feature/awesome-feature`) 73 | 3. Commit your changes (`git commit -am 'Add awesome feature'`) 74 | 4. Push to the branch (`git push origin feature/awesome-feature`) 75 | 5. Open a Pull Request 76 | 77 | ## License 78 | 79 | This project is licensed under the Apache License 2.0 - see the [LICENSE](LICENSE) file for details. 80 | 81 | -------------------------------------------------------------------------------- /tests/deepbook_admin_tests.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use sui_deepbookv3::{ 4 | transactions::deepbook_admin::DeepBookAdminContract, 5 | utils::{ 6 | config::{DeepBookConfig, Environment}, 7 | types::{Coin, Pool}, 8 | }, 9 | }; 10 | use sui_sdk::{ 11 | types::{ 12 | base_types::SuiAddress, programmable_transaction_builder::ProgrammableTransactionBuilder, 13 | }, 14 | SuiClientBuilder, 15 | }; 16 | use utils::dry_run_transaction; 17 | 18 | mod utils; 19 | 20 | #[tokio::test] 21 | async fn test_adjust_tick_size() { 22 | let sui_client = SuiClientBuilder::default().build_testnet().await.unwrap(); 23 | 24 | let config = deep_book_config(); 25 | 26 | let deepbook_admin = DeepBookAdminContract::new(sui_client.clone(), config); 27 | 28 | let mut ptb = ProgrammableTransactionBuilder::new(); 29 | 30 | let _ = deepbook_admin 31 | .adjust_tick_size(&mut ptb, "SUI_USDC", 100.0) 32 | .await; 33 | 34 | let result = dry_run_transaction(&sui_client, ptb).await.unwrap(); 35 | assert!(result.is_empty()); 36 | } 37 | 38 | #[tokio::test] 39 | async fn test_adjust_min_lot_size() { 40 | let sui_client = SuiClientBuilder::default().build_testnet().await.unwrap(); 41 | 42 | let config = deep_book_config(); 43 | 44 | let deepbook_admin = DeepBookAdminContract::new(sui_client.clone(), config); 45 | 46 | let mut ptb = ProgrammableTransactionBuilder::new(); 47 | 48 | let _ = deepbook_admin 49 | .adjust_min_lot_size(&mut ptb, "SUI_USDC", 100.0, 100.0) 50 | .await; 51 | 52 | let result = dry_run_transaction(&sui_client, ptb).await.unwrap(); 53 | assert!(result.is_empty()); 54 | } 55 | 56 | fn deep_book_config() -> DeepBookConfig { 57 | let mut wallet = utils::retrieve_wallet().unwrap(); 58 | let admin_cap = Some( 59 | "0x7731f9c105f3c2bde96f0eca645e718465394d609139342f3196383b823890a9".to_string(), 60 | ); 61 | 62 | let coins = HashMap::from([( 63 | "SUI", 64 | Coin { 65 | address: "0x2::sui::SUI".to_string(), 66 | type_name: "0x2::sui::SUI".to_string(), 67 | scalar: 1_000_000_000, 68 | }, 69 | )]); 70 | 71 | let pools = HashMap::from([( 72 | "SUI_USDC", 73 | Pool { 74 | address: "0x722c39b7b79831d534fbfa522e07101cb881f8807c28b9cf03a58b04c6c5ca9a" 75 | .to_string(), 76 | base_coin: "SUI".to_string(), 77 | quote_coin: "USDC".to_string(), 78 | }, 79 | )]); 80 | 81 | DeepBookConfig::new( 82 | Environment::Testnet, 83 | wallet.active_address().unwrap(), 84 | admin_cap, 85 | None, 86 | Some(coins), 87 | Some(pools), 88 | ) 89 | } -------------------------------------------------------------------------------- /examples/account_order_map.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use std::collections::HashMap; 3 | use sui_deepbookv3::utils::types::BalanceManager; 4 | use sui_sdk::types::base_types::SuiAddress; 5 | use sui_sdk::SuiClientBuilder; 6 | 7 | use sui_deepbookv3::client::DeepBookClient; 8 | use sui_deepbookv3::utils::config::Environment; 9 | 10 | #[tokio::main] 11 | async fn main() -> Result<()> { 12 | let env = Environment::Mainnet; 13 | let fullnode_url = "https://fullnode.mainnet.sui.io:443"; // Mainnet URL 14 | 15 | let mut balance_managers = HashMap::new(); 16 | balance_managers.insert( 17 | "MANAGER_1", 18 | BalanceManager { 19 | address: "0x344c2734b1d211bd15212bfb7847c66a3b18803f3f5ab00f5ff6f87b6fe6d27d" 20 | .to_string(), 21 | trade_cap: None, 22 | deposit_cap: None, 23 | withdraw_cap: None, 24 | }, 25 | ); 26 | 27 | let sui_client = SuiClientBuilder::default() 28 | .build(fullnode_url) 29 | .await 30 | .unwrap(); 31 | let db_client = DeepBookClient::new( 32 | sui_client, 33 | SuiAddress::random_for_testing_only(), 34 | env, 35 | Some(balance_managers), 36 | None, 37 | None, 38 | None, 39 | ); 40 | 41 | let pools = vec![ 42 | "SUI_USDC", 43 | "DEEP_SUI", 44 | "DEEP_USDC", 45 | "WUSDT_USDC", 46 | "WUSDC_USDC", 47 | "BETH_USDC", 48 | ]; 49 | let manager = "MANAGER_1"; 50 | println!("Manager: {}", manager); 51 | 52 | for pool in pools { 53 | let orders = db_client.account_open_orders(pool, manager).await?; 54 | let mut bid_orders: Vec<(f64, f64)> = Vec::new(); 55 | let mut ask_orders: Vec<(f64, f64)> = Vec::new(); 56 | 57 | for order_id in orders { 58 | if let Some(order) = db_client.get_order_normalized(pool, order_id).await? { 59 | let remaining_quantity = order.quantity.parse::().unwrap() 60 | - order.filled_quantity.parse::().unwrap(); 61 | let order_price = order.normalized_price.parse::().unwrap(); 62 | 63 | if order.is_bid { 64 | bid_orders.push((order_price, remaining_quantity)); 65 | } else { 66 | ask_orders.push((order_price, remaining_quantity)); 67 | } 68 | } 69 | } 70 | 71 | // Sort bids in descending order 72 | bid_orders.sort_by(|a, b| b.0.partial_cmp(&a.0).unwrap()); 73 | // Sort asks in ascending order 74 | ask_orders.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap()); 75 | 76 | println!("{} bid orders: {:?}", pool, bid_orders); 77 | println!("{} ask orders: {:?}", pool, ask_orders); 78 | } 79 | 80 | Ok(()) 81 | } 82 | -------------------------------------------------------------------------------- /tests/balance_manager_tests.rs: -------------------------------------------------------------------------------- 1 | use std::{collections::HashMap, str::FromStr}; 2 | 3 | use sui_deepbookv3::{ 4 | transactions::balance_manager::BalanceManagerContract, 5 | utils::{ 6 | config::{DeepBookConfig, Environment}, 7 | types::BalanceManager, 8 | }, 9 | }; 10 | use sui_sdk::{ 11 | types::{ 12 | base_types::SuiAddress, programmable_transaction_builder::ProgrammableTransactionBuilder, 13 | }, 14 | SuiClientBuilder, 15 | }; 16 | use utils::dry_run_transaction; 17 | 18 | mod utils; 19 | 20 | #[tokio::test] 21 | async fn test_create_and_share_balance_manager() { 22 | let sui_client = SuiClientBuilder::default().build_testnet().await.unwrap(); 23 | 24 | let config = deep_book_config(); 25 | println!("config: {:#?}", config); 26 | 27 | let balance_manager = BalanceManagerContract::new(sui_client.clone(), config); 28 | 29 | let mut ptb = ProgrammableTransactionBuilder::new(); 30 | 31 | let _ = balance_manager.create_and_share_balance_manager(&mut ptb); 32 | // execute_transaction(ptb).await; 33 | let result = dry_run_transaction(&sui_client, ptb).await.unwrap(); 34 | println!("result: {:#?}", result); 35 | } 36 | 37 | #[tokio::test] 38 | async fn test_balance_manager_owner() { 39 | let sui_client = SuiClientBuilder::default().build_testnet().await.unwrap(); 40 | 41 | let config = deep_book_config(); 42 | 43 | let balance_manager = BalanceManagerContract::new(sui_client.clone(), config); 44 | 45 | let mut ptb = ProgrammableTransactionBuilder::new(); 46 | 47 | let _ = balance_manager.owner(&mut ptb, "DEEP").await; 48 | 49 | let result = dry_run_transaction(&sui_client, ptb).await.unwrap(); 50 | let result = result.first().unwrap(); 51 | println!( 52 | "owner: {:#?}", 53 | bcs::from_bytes::(&result.0).unwrap() 54 | ); 55 | assert_eq!( 56 | bcs::from_bytes::(&result.0).unwrap(), 57 | SuiAddress::from_str("0x7731f9c105f3c2bde96f0eca645e718465394d609139342f3196383b823890a9") 58 | .unwrap() 59 | ); 60 | } 61 | 62 | #[tokio::test] 63 | async fn test_balance_manager_id() { 64 | let sui_client = SuiClientBuilder::default().build_testnet().await.unwrap(); 65 | 66 | let config = deep_book_config(); 67 | 68 | let balance_manager = BalanceManagerContract::new(sui_client.clone(), config); 69 | 70 | let mut ptb = ProgrammableTransactionBuilder::new(); 71 | 72 | let _ = balance_manager.id(&mut ptb, "DEEP").await; 73 | 74 | let result = dry_run_transaction(&sui_client, ptb).await.unwrap(); 75 | let result = result.first().unwrap(); 76 | println!( 77 | "id: {:#?}", 78 | bcs::from_bytes::(&result.0).unwrap() 79 | ); 80 | assert_eq!( 81 | bcs::from_bytes::(&result.0).unwrap(), 82 | SuiAddress::from_str("0x722c39b7b79831d534fbfa522e07101cb881f8807c28b9cf03a58b04c6c5ca9a") 83 | .unwrap() 84 | ); 85 | } 86 | 87 | fn deep_book_config() -> DeepBookConfig { 88 | let balance_managers = HashMap::from([( 89 | "DEEP", 90 | BalanceManager { 91 | address: "0x722c39b7b79831d534fbfa522e07101cb881f8807c28b9cf03a58b04c6c5ca9a" 92 | .to_string(), 93 | trade_cap: None, 94 | deposit_cap: None, 95 | withdraw_cap: None, 96 | }, 97 | )]); 98 | 99 | DeepBookConfig::new( 100 | Environment::Testnet, 101 | SuiAddress::random_for_testing_only(), 102 | None, 103 | Some(balance_managers), 104 | None, 105 | None, 106 | ) 107 | } 108 | -------------------------------------------------------------------------------- /src/utils/config.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use sui_sdk::types::base_types::SuiAddress; 3 | 4 | use crate::utils::constants::{ 5 | MAINNET_COINS, MAINNET_PACKAGE_IDS, MAINNET_POOLS, TESTNET_COINS, TESTNET_PACKAGE_IDS, 6 | TESTNET_POOLS, 7 | }; 8 | 9 | use super::types::{BalanceManager, Coin, DeepBookPackageIds, Pool}; 10 | 11 | // Constants 12 | pub const FLOAT_SCALAR: u64 = 1_000_000_000; 13 | pub const MAX_TIMESTAMP: u64 = 1_844_674_407_370_955_161; 14 | pub const GAS_BUDGET: u64 = 250_000_000; // 0.5 * 500000000 15 | pub const DEEP_SCALAR: u64 = 1_000_000; 16 | pub const POOL_CREATION_FEE: u64 = 500 * 1_000_000; // 500 DEEP 17 | 18 | // Type aliases 19 | pub type CoinMap = HashMap<&'static str, Coin>; 20 | pub type PoolMap = HashMap<&'static str, Pool>; 21 | pub type BalanceManagerMap = HashMap<&'static str, BalanceManager>; 22 | 23 | #[derive(Debug)] 24 | pub enum Environment { 25 | Mainnet, 26 | Testnet, 27 | } 28 | 29 | #[derive(Debug, Clone)] 30 | pub struct DeepBookConfig { 31 | coins: CoinMap, 32 | pools: PoolMap, 33 | balance_managers: BalanceManagerMap, 34 | address: SuiAddress, 35 | deepbook_package_id: String, 36 | registry_id: String, 37 | deep_treasury_id: String, 38 | admin_cap: Option, 39 | } 40 | 41 | impl DeepBookConfig { 42 | pub fn new( 43 | env: Environment, 44 | address: SuiAddress, 45 | admin_cap: Option, 46 | balance_managers: Option, 47 | coins: Option, 48 | pools: Option, 49 | ) -> Self { 50 | let package_ids: DeepBookPackageIds = match env { 51 | Environment::Mainnet => MAINNET_PACKAGE_IDS, 52 | Environment::Testnet => TESTNET_PACKAGE_IDS, 53 | }; 54 | 55 | Self { 56 | address, 57 | admin_cap, 58 | balance_managers: balance_managers.unwrap_or_default(), 59 | coins: coins.unwrap_or_else(|| match env { 60 | Environment::Mainnet => MAINNET_COINS.clone(), // Replace with mainnet coins 61 | Environment::Testnet => TESTNET_COINS.clone(), // Replace with testnet coins 62 | }), 63 | pools: pools.unwrap_or_else(|| match env { 64 | Environment::Mainnet => MAINNET_POOLS.clone(), // Replace with mainnet pools 65 | Environment::Testnet => TESTNET_POOLS.clone(), // Replace with testnet pools 66 | }), 67 | deepbook_package_id: package_ids.deepbook_package_id.to_string(), 68 | registry_id: package_ids.registry_id.to_string(), 69 | deep_treasury_id: package_ids.deep_treasury_id.to_string(), 70 | } 71 | } 72 | 73 | pub fn get_coin(&self, key: &str) -> anyhow::Result<&Coin> { 74 | self.coins 75 | .get(key) 76 | .ok_or(anyhow::anyhow!("Coin with key {} not found.", key)) 77 | } 78 | 79 | pub fn get_pool(&self, key: &str) -> anyhow::Result<&Pool> { 80 | self.pools 81 | .get(key) 82 | .ok_or(anyhow::anyhow!("Pool with key {} not found.", key)) 83 | } 84 | 85 | pub fn get_balance_manager(&self, manager_key: &str) -> anyhow::Result<&BalanceManager> { 86 | self.balance_managers 87 | .get(manager_key) 88 | .ok_or(anyhow::anyhow!( 89 | "Balance manager with key {} not found.", 90 | manager_key 91 | )) 92 | } 93 | 94 | pub fn address(&self) -> &SuiAddress { 95 | &self.address 96 | } 97 | 98 | pub fn deepbook_package_id(&self) -> &str { 99 | &self.deepbook_package_id 100 | } 101 | 102 | pub fn registry_id(&self) -> &str { 103 | &self.registry_id 104 | } 105 | 106 | pub fn deep_treasury_id(&self) -> &str { 107 | &self.deep_treasury_id 108 | } 109 | 110 | pub fn admin_cap(&self) -> Option { 111 | self.admin_cap.clone() 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/utils/types.rs: -------------------------------------------------------------------------------- 1 | use sui_sdk::types::base_types::{ObjectID, SuiAddress}; 2 | 3 | /// Represents a balance manager in the system 4 | #[derive(Debug, Clone)] 5 | pub struct BalanceManager { 6 | pub address: String, 7 | pub trade_cap: Option, 8 | pub deposit_cap: Option, 9 | pub withdraw_cap: Option, 10 | } 11 | 12 | /// Represents a coin in the system 13 | #[derive(Debug, Clone)] 14 | pub struct Coin { 15 | pub address: String, 16 | pub type_name: String, 17 | pub scalar: u64, 18 | } 19 | 20 | /// Represents a trading pool 21 | #[derive(Debug, Clone)] 22 | pub struct Pool { 23 | pub address: String, 24 | pub base_coin: String, 25 | pub quote_coin: String, 26 | } 27 | 28 | #[derive(Debug, Clone)] 29 | pub struct DeepBookPackageIds { 30 | pub deepbook_package_id: &'static str, 31 | pub registry_id: &'static str, 32 | pub deep_treasury_id: &'static str, 33 | } 34 | 35 | /// Trading order types 36 | #[derive(Debug, Clone, Copy, PartialEq)] 37 | pub enum OrderType { 38 | NoRestriction, 39 | ImmediateOrCancel, 40 | FillOrKill, 41 | PostOnly, 42 | } 43 | 44 | /// Self-matching options for orders 45 | #[derive(Debug, Clone, Copy, PartialEq)] 46 | pub enum SelfMatchingOptions { 47 | SelfMatchingAllowed, 48 | CancelTaker, 49 | CancelMaker, 50 | } 51 | 52 | /// Parameters for placing a limit order 53 | #[derive(Debug, Clone)] 54 | pub struct PlaceLimitOrderParams { 55 | pub pool_key: String, 56 | pub balance_manager_key: String, 57 | pub client_order_id: u64, 58 | pub price: f64, 59 | pub quantity: f64, 60 | pub is_bid: bool, 61 | pub expiration: Option, 62 | pub order_type: Option, 63 | pub self_matching_option: Option, 64 | pub pay_with_deep: Option, 65 | } 66 | 67 | /// Parameters for placing a market order 68 | #[derive(Debug, Clone)] 69 | pub struct PlaceMarketOrderParams { 70 | pub pool_key: String, 71 | pub balance_manager_key: String, 72 | pub client_order_id: u64, 73 | pub quantity: f64, 74 | pub is_bid: bool, 75 | pub self_matching_option: Option, 76 | pub pay_with_deep: Option, 77 | } 78 | 79 | /// Parameters for submitting a proposal 80 | #[derive(Debug, Clone)] 81 | pub struct ProposalParams { 82 | pub pool_key: String, 83 | pub balance_manager_key: String, 84 | pub taker_fee: f64, 85 | pub maker_fee: f64, 86 | pub stake_required: f64, 87 | } 88 | 89 | /// Parameters for swap operations 90 | #[derive(Debug, Clone)] 91 | pub struct SwapParams { 92 | pub sender: SuiAddress, 93 | pub pool_key: String, 94 | pub amount: f64, 95 | pub deep_amount: f64, 96 | pub min_out: f64, 97 | pub deep_coin: Option, 98 | pub base_coin: Option, 99 | pub quote_coin: Option, 100 | } 101 | 102 | /// Parameters for creating a pool admin 103 | #[derive(Debug, Clone)] 104 | pub struct CreatePoolAdminParams { 105 | pub base_coin_key: String, 106 | pub quote_coin_key: String, 107 | pub tick_size: f64, 108 | pub lot_size: f64, 109 | pub min_size: f64, 110 | pub whitelisted: bool, 111 | pub stable_pool: bool, 112 | pub deep_coin: Option, 113 | pub base_coin: Option, 114 | } 115 | 116 | /// Parameters for creating a permissionless pool 117 | #[derive(Debug, Clone)] 118 | pub struct CreatePermissionlessPoolParams { 119 | pub base_coin_key: String, 120 | pub quote_coin_key: String, 121 | pub tick_size: f64, 122 | pub lot_size: f64, 123 | pub min_size: f64, 124 | pub deep_coin: Option, 125 | } 126 | 127 | /// Configuration for the DeepBook system 128 | #[derive(Debug, Clone)] 129 | pub struct Config { 130 | pub deepbook_package_id: String, 131 | pub registry_id: String, 132 | pub deep_treasury_id: String, 133 | } 134 | 135 | /// Environment type for the system 136 | #[derive(Debug, Clone, Copy, PartialEq)] 137 | pub enum Environment { 138 | Mainnet, 139 | Testnet, 140 | } 141 | -------------------------------------------------------------------------------- /tests/utils.rs: -------------------------------------------------------------------------------- 1 | use shared_crypto::intent::Intent; 2 | use sui_config::{ 3 | sui_config_dir, Config, PersistedConfig, SUI_CLIENT_CONFIG, SUI_KEYSTORE_FILENAME, 4 | }; 5 | use sui_deepbookv3::DataReader; 6 | use sui_sdk::rpc_types::SuiTypeTag; 7 | use sui_keys::keystore::{AccountKeystore, FileBasedKeystore}; 8 | use sui_sdk::{ 9 | rpc_types::SuiTransactionBlockResponseOptions, 10 | sui_client_config::{SuiClientConfig, SuiEnv}, 11 | types::{ 12 | programmable_transaction_builder::ProgrammableTransactionBuilder, 13 | quorum_driver_types::ExecuteTransactionRequestType, 14 | transaction::{Transaction, TransactionData}, 15 | }, 16 | wallet_context::WalletContext, 17 | SuiClient, SuiClientBuilder, 18 | }; 19 | 20 | pub fn retrieve_wallet() -> anyhow::Result { 21 | let wallet_conf = sui_config_dir()?.join(SUI_CLIENT_CONFIG); 22 | let keystore_path = sui_config_dir()?.join(SUI_KEYSTORE_FILENAME); 23 | 24 | // check if a wallet exists and if not, create a wallet and a sui client config 25 | if !keystore_path.exists() { 26 | let keystore = FileBasedKeystore::new(&keystore_path)?; 27 | keystore.save()?; 28 | } 29 | 30 | if !wallet_conf.exists() { 31 | let keystore = FileBasedKeystore::new(&keystore_path)?; 32 | let mut client_config = SuiClientConfig::new(keystore.into()); 33 | 34 | client_config.add_env(SuiEnv::testnet()); 35 | client_config.add_env(SuiEnv::devnet()); 36 | client_config.add_env(SuiEnv::localnet()); 37 | 38 | if client_config.active_env.is_none() { 39 | client_config.active_env = client_config.envs.first().map(|env| env.alias.clone()); 40 | } 41 | 42 | client_config.save(&wallet_conf)?; 43 | println!("Client config file is stored in {:?}.", &wallet_conf); 44 | } 45 | 46 | let keystore = FileBasedKeystore::new(&keystore_path)?; 47 | let mut client_config: SuiClientConfig = PersistedConfig::read(&wallet_conf)?; 48 | 49 | let addresses = keystore.addresses(); 50 | let default_active_address = addresses.first().unwrap(); 51 | 52 | client_config.active_address = Some(default_active_address.clone()); 53 | client_config.save(&wallet_conf)?; 54 | 55 | let wallet = WalletContext::new(&wallet_conf, Some(std::time::Duration::from_secs(60)), None)?; 56 | 57 | Ok(wallet) 58 | } 59 | 60 | #[allow(dead_code)] 61 | pub async fn execute_transaction(ptb: ProgrammableTransactionBuilder) { 62 | let sui_client = SuiClientBuilder::default().build_testnet().await.unwrap(); 63 | println!("Sui testnet version: {}", sui_client.api_version()); 64 | 65 | let mut wallet = retrieve_wallet().unwrap(); 66 | let sender = wallet.active_address().unwrap(); 67 | println!("Sender: {}", sender); 68 | 69 | let coins = sui_client 70 | .coin_read_api() 71 | .get_coins(sender, None, None, None) 72 | .await 73 | .unwrap(); 74 | let coin = coins.data.into_iter().next().unwrap(); 75 | 76 | let gas_budget = 10_000_000; 77 | let gas_price = sui_client 78 | .read_api() 79 | .get_reference_gas_price() 80 | .await 81 | .unwrap(); 82 | 83 | let builder = ptb.finish(); 84 | println!("{:?}", builder); 85 | 86 | // create the transaction data that will be sent to the network 87 | let tx_data = TransactionData::new_programmable( 88 | sender, 89 | vec![coin.object_ref()], 90 | builder, 91 | gas_budget, 92 | gas_price, 93 | ); 94 | 95 | // 4) sign transaction 96 | let keystore = 97 | FileBasedKeystore::new(&sui_config_dir().unwrap().join(SUI_KEYSTORE_FILENAME)).unwrap(); 98 | let signature = keystore 99 | .sign_secure(&sender, &tx_data, Intent::sui_transaction()) 100 | .unwrap(); 101 | 102 | // 5) execute the transaction 103 | print!("Executing the transaction..."); 104 | let transaction_response = sui_client 105 | .quorum_driver_api() 106 | .execute_transaction_block( 107 | Transaction::from_data(tx_data, vec![signature]), 108 | SuiTransactionBlockResponseOptions::full_content(), 109 | Some(ExecuteTransactionRequestType::WaitForLocalExecution), 110 | ) 111 | .await 112 | .unwrap(); 113 | println!("{}", transaction_response); 114 | } 115 | 116 | pub async fn dry_run_transaction( 117 | sui_client: &SuiClient, 118 | ptb: ProgrammableTransactionBuilder, 119 | ) -> anyhow::Result, SuiTypeTag)>> { 120 | let mut wallet = retrieve_wallet().unwrap(); 121 | let sender = wallet.active_address().unwrap(); 122 | println!("Sender: {}", sender); 123 | 124 | sui_client.dev_inspect_transaction(sender, ptb).await 125 | } 126 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | use async_trait::async_trait; 2 | use sui_sdk::rpc_types::Coin; 3 | use sui_sdk::rpc_types::SuiObjectData; 4 | use sui_sdk::rpc_types::SuiObjectDataOptions; 5 | use sui_sdk::rpc_types::SuiTypeTag; 6 | use sui_sdk::types::base_types::SuiAddress; 7 | use sui_sdk::types::object::Owner; 8 | use sui_sdk::types::programmable_transaction_builder::ProgrammableTransactionBuilder; 9 | use sui_sdk::types::transaction::ObjectArg; 10 | use sui_sdk::types::transaction::TransactionKind; 11 | use sui_sdk::{types::base_types::ObjectID, SuiClient}; 12 | 13 | pub mod client; 14 | pub mod transactions; 15 | pub mod utils; 16 | 17 | #[async_trait] 18 | pub trait DataReader { 19 | async fn get_coin_object( 20 | &self, 21 | sender: SuiAddress, 22 | coin_type: String, 23 | amount: u64, 24 | ) -> anyhow::Result; 25 | async fn get_coin_objects( 26 | &self, 27 | sender: SuiAddress, 28 | coin_type: String, 29 | amount: u64, 30 | ) -> anyhow::Result>; 31 | async fn get_object(&self, object_id: ObjectID) -> anyhow::Result; 32 | async fn coin_object(&self, coin: Coin) -> anyhow::Result; 33 | async fn share_object(&self, object_id: ObjectID) -> anyhow::Result; 34 | async fn share_object_mutable(&self, object_id: ObjectID) -> anyhow::Result; 35 | async fn dev_inspect_transaction( 36 | &self, 37 | sender: SuiAddress, 38 | ptb: ProgrammableTransactionBuilder, 39 | ) -> anyhow::Result, SuiTypeTag)>>; 40 | } 41 | 42 | #[async_trait] 43 | impl DataReader for SuiClient { 44 | async fn get_coin_object( 45 | &self, 46 | sender: SuiAddress, 47 | coin_type: String, 48 | amount: u64, 49 | ) -> anyhow::Result { 50 | Ok(self 51 | .get_coin_objects(sender, coin_type, amount) 52 | .await? 53 | .first() 54 | .ok_or_else(|| anyhow::anyhow!("Failed to get base coin"))? 55 | .clone()) 56 | } 57 | 58 | async fn get_coin_objects( 59 | &self, 60 | sender: SuiAddress, 61 | coin_type: String, 62 | amount: u64, 63 | ) -> anyhow::Result> { 64 | Ok(self 65 | .coin_read_api() 66 | .select_coins(sender, Some(coin_type), amount as u128, vec![]) 67 | .await?) 68 | } 69 | 70 | async fn get_object(&self, object_id: ObjectID) -> anyhow::Result { 71 | self.read_api() 72 | .get_object_with_options(object_id, SuiObjectDataOptions::full_content()) 73 | .await? 74 | .data 75 | .ok_or(anyhow::anyhow!("Object {} not found", object_id)) 76 | } 77 | 78 | async fn coin_object(&self, coin: Coin) -> anyhow::Result { 79 | Ok(ObjectArg::ImmOrOwnedObject(( 80 | coin.coin_object_id, 81 | coin.version, 82 | coin.digest, 83 | ))) 84 | } 85 | 86 | async fn share_object(&self, object_id: ObjectID) -> anyhow::Result { 87 | let object = self.get_object(object_id).await?; 88 | if let Some(owner) = object.owner { 89 | if let Owner::Shared { 90 | initial_shared_version, 91 | } = owner 92 | { 93 | return Ok(ObjectArg::SharedObject { 94 | id: object_id, 95 | initial_shared_version, 96 | mutable: false, 97 | }); 98 | } 99 | } 100 | 101 | Err(anyhow::anyhow!( 102 | "Object {} is not shared or missing version information", 103 | object_id 104 | )) 105 | } 106 | 107 | async fn share_object_mutable(&self, object_id: ObjectID) -> anyhow::Result { 108 | let object = self.get_object(object_id).await?; 109 | if let Some(owner) = object.owner { 110 | if let Owner::Shared { 111 | initial_shared_version, 112 | } = owner 113 | { 114 | return Ok(ObjectArg::SharedObject { 115 | id: object_id, 116 | initial_shared_version, 117 | mutable: true, 118 | }); 119 | } 120 | } 121 | 122 | Err(anyhow::anyhow!( 123 | "Object {} is not shared or missing version information", 124 | object_id 125 | )) 126 | } 127 | 128 | async fn dev_inspect_transaction( 129 | &self, 130 | sender: SuiAddress, 131 | ptb: ProgrammableTransactionBuilder, 132 | ) -> anyhow::Result, SuiTypeTag)>> { 133 | let builder = ptb.finish(); 134 | let dry_run_response = self 135 | .read_api() 136 | .dev_inspect_transaction_block( 137 | sender, 138 | TransactionKind::ProgrammableTransaction(builder), 139 | None, 140 | None, 141 | None, 142 | ) 143 | .await?; 144 | Ok(dry_run_response 145 | .results 146 | .ok_or_else(|| anyhow::anyhow!("Failed to get results"))? 147 | .first() 148 | .ok_or_else(|| anyhow::anyhow!("Failed to get first result"))? 149 | .return_values 150 | .clone()) 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /src/transactions/flashloan.rs: -------------------------------------------------------------------------------- 1 | use std::str::FromStr; 2 | 3 | use crate::utils::config::DeepBookConfig; 4 | use sui_sdk::{ 5 | types::{ 6 | base_types::{ObjectID, ObjectRef}, 7 | programmable_transaction_builder::ProgrammableTransactionBuilder, 8 | transaction::{Argument, Command, ObjectArg}, 9 | Identifier, TypeTag, 10 | }, 11 | SuiClient, 12 | }; 13 | 14 | use crate::DataReader; 15 | 16 | /// FlashLoanContract struct for managing flash loans. 17 | pub struct FlashLoanContract { 18 | client: SuiClient, 19 | config: DeepBookConfig, 20 | } 21 | 22 | impl FlashLoanContract { 23 | /// Creates a new FlashLoanContract instance 24 | /// 25 | /// @param client - SuiClient instance 26 | /// @param config - Configuration object for DeepBook 27 | /// @param balance_manager_contract - BalanceManagerContract instance 28 | pub fn new(client: SuiClient, config: DeepBookConfig) -> Self { 29 | Self { client, config } 30 | } 31 | 32 | /// Borrow base asset from the pool 33 | /// 34 | /// @param ptb - ProgrammableTransactionBuilder instance 35 | /// @param pool_key - The key to identify the pool 36 | /// @param borrow_amount - The amount to borrow 37 | /// @returns A tuple containing the base coin result and flash loan object 38 | pub async fn borrow_base_asset<'a>( 39 | &self, 40 | ptb: &mut ProgrammableTransactionBuilder, 41 | pool_key: &str, 42 | borrow_amount: f64, 43 | ) -> anyhow::Result { 44 | let pool = self.config.get_pool(pool_key)?; 45 | let base_coin = self.config.get_coin(&pool.base_coin)?; 46 | let quote_coin = self.config.get_coin(&pool.quote_coin)?; 47 | let input_quantity = (borrow_amount * base_coin.scalar as f64).round() as u64; 48 | 49 | let pool_id = ObjectID::from_hex_literal(pool.address.as_str())?; 50 | 51 | let base_coin_tag = TypeTag::from_str(base_coin.type_name.as_str())?; 52 | let quote_coin_tag = TypeTag::from_str(quote_coin.type_name.as_str())?; 53 | 54 | let arguments = vec![ 55 | ptb.obj(self.client.share_object_mutable(pool_id).await?)?, 56 | ptb.pure(input_quantity)?, 57 | ]; 58 | 59 | Ok(ptb.programmable_move_call( 60 | ObjectID::from_hex_literal(self.config.deepbook_package_id())?, 61 | Identifier::new("pool")?, 62 | Identifier::new("borrow_flashloan_base")?, 63 | vec![base_coin_tag, quote_coin_tag], 64 | arguments, 65 | )) 66 | } 67 | 68 | /// Return base asset to the pool after a flash loan 69 | /// 70 | /// @param ptb - ProgrammableTransactionBuilder instance 71 | /// @param pool_key - The key to identify the pool 72 | /// @param borrow_amount - The amount of the base asset to return 73 | /// @param base_coin_input - Coin object representing the base asset to be returned 74 | /// @param flash_loan - FlashLoan object representing the loan to be settled 75 | pub async fn return_base_asset<'a>( 76 | &self, 77 | ptb: &mut ProgrammableTransactionBuilder, 78 | pool_key: &str, 79 | borrow_amount: f64, 80 | base_coin_input: Argument, 81 | flash_loan: Argument, 82 | ) -> anyhow::Result { 83 | let pool = self.config.get_pool(pool_key)?; 84 | let base_coin = self.config.get_coin(&pool.base_coin)?; 85 | let quote_coin = self.config.get_coin(&pool.quote_coin)?; 86 | let borrow_scalar = base_coin.scalar; 87 | 88 | let return_amount = ptb.pure((borrow_amount * borrow_scalar as f64).round() as u64)?; 89 | let base_coin_return = 90 | ptb.command(Command::SplitCoins(base_coin_input, vec![return_amount])); 91 | 92 | let pool_id = ObjectID::from_hex_literal(pool.address.as_str())?; 93 | let base_coin_tag = TypeTag::from_str(base_coin.type_name.as_str())?; 94 | let quote_coin_tag = TypeTag::from_str(quote_coin.type_name.as_str())?; 95 | 96 | let arguments = vec![ 97 | ptb.obj(self.client.share_object_mutable(pool_id).await?)?, 98 | base_coin_return, 99 | flash_loan, 100 | ]; 101 | 102 | ptb.programmable_move_call( 103 | ObjectID::from_hex_literal(self.config.deepbook_package_id())?, 104 | Identifier::new("pool")?, 105 | Identifier::new("return_flashloan_base")?, 106 | vec![base_coin_tag, quote_coin_tag], 107 | arguments, 108 | ); 109 | 110 | Ok(base_coin_input) 111 | } 112 | 113 | /// Borrow quote asset from the pool 114 | /// 115 | /// @param ptb - ProgrammableTransactionBuilder instance 116 | /// @param pool_key - The key to identify the pool 117 | /// @param borrow_amount - The amount to borrow 118 | /// @returns A tuple containing the quote coin result and flash loan object 119 | pub async fn borrow_quote_asset<'a>( 120 | &self, 121 | ptb: &mut ProgrammableTransactionBuilder, 122 | pool_key: &str, 123 | borrow_amount: f64, 124 | ) -> anyhow::Result { 125 | let pool = self.config.get_pool(pool_key)?; 126 | let base_coin = self.config.get_coin(&pool.base_coin)?; 127 | let quote_coin = self.config.get_coin(&pool.quote_coin)?; 128 | let input_quantity = (borrow_amount * quote_coin.scalar as f64).round() as u64; 129 | 130 | let pool_id = ObjectID::from_hex_literal(pool.address.as_str())?; 131 | let base_coin_tag = TypeTag::from_str(base_coin.type_name.as_str())?; 132 | let quote_coin_tag = TypeTag::from_str(quote_coin.type_name.as_str())?; 133 | 134 | let arguments = vec![ 135 | ptb.obj(self.client.share_object_mutable(pool_id).await?)?, 136 | ptb.pure(input_quantity)?, 137 | ]; 138 | 139 | Ok(ptb.programmable_move_call( 140 | ObjectID::from_hex_literal(self.config.deepbook_package_id())?, 141 | Identifier::new("pool")?, 142 | Identifier::new("borrow_flashloan_quote")?, 143 | vec![base_coin_tag, quote_coin_tag], 144 | arguments, 145 | )) 146 | } 147 | 148 | /// Return quote asset to the pool after a flash loan 149 | /// 150 | /// @param ptb - ProgrammableTransactionBuilder instance 151 | /// @param pool_key - The key to identify the pool 152 | /// @param borrow_amount - The amount of the quote asset to return 153 | /// @param quote_coin_input - Coin object representing the quote asset to be returned 154 | /// @param flash_loan - FlashLoan object representing the loan to be settled 155 | pub async fn return_quote_asset<'a>( 156 | &self, 157 | ptb: &mut ProgrammableTransactionBuilder, 158 | pool_key: &str, 159 | borrow_amount: f64, 160 | quote_coin_input: Argument, 161 | flash_loan: Argument, 162 | ) -> anyhow::Result { 163 | let pool = self.config.get_pool(pool_key)?; 164 | let base_coin = self.config.get_coin(&pool.base_coin)?; 165 | let quote_coin = self.config.get_coin(&pool.quote_coin)?; 166 | let borrow_scalar = quote_coin.scalar; 167 | 168 | let return_amount = ptb.pure((borrow_amount * borrow_scalar as f64).round() as u64)?; 169 | let quote_coin_return = 170 | ptb.command(Command::SplitCoins(quote_coin_input, vec![return_amount])); 171 | 172 | let pool_id = ObjectID::from_hex_literal(pool.address.as_str())?; 173 | let base_coin_tag = TypeTag::from_str(base_coin.type_name.as_str())?; 174 | let quote_coin_tag = TypeTag::from_str(quote_coin.type_name.as_str())?; 175 | 176 | let arguments = vec![ 177 | ptb.obj(self.client.share_object_mutable(pool_id).await?)?, 178 | quote_coin_return, 179 | flash_loan, 180 | ]; 181 | 182 | ptb.programmable_move_call( 183 | ObjectID::from_hex_literal(self.config.deepbook_package_id())?, 184 | Identifier::new("pool")?, 185 | Identifier::new("return_flashloan_quote")?, 186 | vec![base_coin_tag, quote_coin_tag], 187 | arguments, 188 | ); 189 | 190 | Ok(quote_coin_input) 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /src/transactions/governance.rs: -------------------------------------------------------------------------------- 1 | use std::str::FromStr; 2 | 3 | use sui_sdk::types::programmable_transaction_builder::ProgrammableTransactionBuilder; 4 | use sui_sdk::SuiClient; 5 | 6 | use crate::utils::config::{DeepBookConfig, DEEP_SCALAR, FLOAT_SCALAR}; 7 | use crate::utils::types::ProposalParams; 8 | 9 | use sui_sdk::types::base_types::ObjectID; 10 | use sui_sdk::types::{Identifier, TypeTag}; 11 | 12 | use super::balance_manager::BalanceManagerContract; 13 | use crate::DataReader; 14 | 15 | /// GovernanceContract struct for managing governance operations in DeepBook. 16 | pub struct GovernanceContract { 17 | client: SuiClient, 18 | config: DeepBookConfig, 19 | balance_manager_contract: BalanceManagerContract, 20 | } 21 | 22 | impl GovernanceContract { 23 | /// Creates a new GovernanceContract instance 24 | /// 25 | /// @param config - Configuration for GovernanceContract 26 | /// @param client - SuiClient instance 27 | /// @param balance_manager_contract - BalanceManagerContract instance 28 | pub fn new( 29 | client: SuiClient, 30 | config: DeepBookConfig, 31 | balance_manager_contract: BalanceManagerContract, 32 | ) -> Self { 33 | Self { 34 | client, 35 | config, 36 | balance_manager_contract, 37 | } 38 | } 39 | 40 | /// Stake a specified amount in the pool 41 | /// 42 | /// @param pool_key - The key to identify the pool 43 | /// @param balance_manager_key - The key to identify the BalanceManager 44 | /// @param stake_amount - The amount to stake 45 | pub async fn stake( 46 | &self, 47 | ptb: &mut ProgrammableTransactionBuilder, 48 | pool_key: &str, 49 | balance_manager_key: &str, 50 | stake_amount: f64, 51 | ) -> anyhow::Result<()> { 52 | let pool = self.config.get_pool(pool_key)?; 53 | let balance_manager = self.config.get_balance_manager(balance_manager_key)?; 54 | let trade_proof = self 55 | .balance_manager_contract 56 | .generate_proof(ptb, balance_manager_key) 57 | .await?; 58 | let base_coin = self.config.get_coin(&pool.base_coin)?; 59 | let quote_coin = self.config.get_coin(&pool.quote_coin)?; 60 | let stake_input = (stake_amount * DEEP_SCALAR as f64).round() as u64; 61 | 62 | let base_coin_tag = TypeTag::from_str(base_coin.type_name.as_str())?; 63 | let quote_coin_tag = TypeTag::from_str(quote_coin.type_name.as_str())?; 64 | 65 | let pool_id = ObjectID::from_hex_literal(pool.address.as_str())?; 66 | let balance_manager_id = ObjectID::from_hex_literal(balance_manager.address.as_str())?; 67 | let arguments = vec![ 68 | ptb.obj(self.client.share_object(pool_id).await?)?, 69 | ptb.obj(self.client.share_object(balance_manager_id).await?)?, 70 | trade_proof, 71 | ptb.pure(stake_input)?, 72 | ]; 73 | 74 | ptb.programmable_move_call( 75 | ObjectID::from_hex_literal(self.config.deepbook_package_id())?, 76 | Identifier::new("pool")?, 77 | Identifier::new("stake")?, 78 | vec![base_coin_tag, quote_coin_tag], 79 | arguments, 80 | ); 81 | Ok(()) 82 | } 83 | 84 | /// UnStake from the pool 85 | /// 86 | /// @param pool_key - The key to identify the pool 87 | /// @param balance_manager_key - The key to identify the BalanceManager 88 | pub async fn unstake( 89 | &self, 90 | ptb: &mut ProgrammableTransactionBuilder, 91 | pool_key: &str, 92 | balance_manager_key: &str, 93 | ) -> anyhow::Result<()> { 94 | let pool = self.config.get_pool(pool_key)?; 95 | let balance_manager = self.config.get_balance_manager(balance_manager_key)?; 96 | let base_coin = self.config.get_coin(&pool.base_coin)?; 97 | let quote_coin = self.config.get_coin(&pool.quote_coin)?; 98 | 99 | let base_coin_tag = TypeTag::from_str(base_coin.type_name.as_str())?; 100 | let quote_coin_tag = TypeTag::from_str(quote_coin.type_name.as_str())?; 101 | 102 | let trade_proof = self 103 | .balance_manager_contract 104 | .generate_proof(ptb, balance_manager_key) 105 | .await?; 106 | 107 | let pool_id = ObjectID::from_hex_literal(pool.address.as_str())?; 108 | let balance_manager_id = ObjectID::from_hex_literal(balance_manager.address.as_str())?; 109 | let arguments = vec![ 110 | ptb.obj(self.client.share_object(pool_id).await?)?, 111 | ptb.obj(self.client.share_object(balance_manager_id).await?)?, 112 | trade_proof, 113 | ]; 114 | 115 | ptb.programmable_move_call( 116 | ObjectID::from_hex_literal(self.config.deepbook_package_id())?, 117 | Identifier::new("pool")?, 118 | Identifier::new("unstake")?, 119 | vec![base_coin_tag, quote_coin_tag], 120 | arguments, 121 | ); 122 | Ok(()) 123 | } 124 | 125 | /// Submit a governance proposal 126 | /// 127 | /// @param params - Parameters for the proposal 128 | pub async fn submit_proposal( 129 | &self, 130 | ptb: &mut ProgrammableTransactionBuilder, 131 | params: ProposalParams, 132 | ) -> anyhow::Result<()> { 133 | let pool = self.config.get_pool(¶ms.pool_key)?; 134 | let balance_manager = self 135 | .config 136 | .get_balance_manager(¶ms.balance_manager_key)?; 137 | let base_coin = self.config.get_coin(&pool.base_coin)?; 138 | let quote_coin = self.config.get_coin(&pool.quote_coin)?; 139 | 140 | let taker_fee = (params.taker_fee * FLOAT_SCALAR as f64).round() as u64; 141 | let maker_fee = (params.maker_fee * FLOAT_SCALAR as f64).round() as u64; 142 | let stake_required = (params.stake_required * DEEP_SCALAR as f64).round() as u64; 143 | 144 | let pool_id = ObjectID::from_hex_literal(pool.address.as_str())?; 145 | let balance_manager_id = ObjectID::from_hex_literal(balance_manager.address.as_str())?; 146 | 147 | let base_coin_tag = TypeTag::from_str(base_coin.type_name.as_str())?; 148 | let quote_coin_tag = TypeTag::from_str(quote_coin.type_name.as_str())?; 149 | 150 | let trade_proof = self 151 | .balance_manager_contract 152 | .generate_proof(ptb, ¶ms.balance_manager_key) 153 | .await?; 154 | 155 | let arguments = vec![ 156 | ptb.obj(self.client.share_object(pool_id).await?)?, 157 | ptb.obj(self.client.share_object(balance_manager_id).await?)?, 158 | trade_proof, 159 | ptb.pure(taker_fee)?, 160 | ptb.pure(maker_fee)?, 161 | ptb.pure(stake_required)?, 162 | ]; 163 | 164 | ptb.programmable_move_call( 165 | ObjectID::from_hex_literal(self.config.deepbook_package_id())?, 166 | Identifier::new("pool")?, 167 | Identifier::new("submit_proposal")?, 168 | vec![base_coin_tag, quote_coin_tag], 169 | arguments, 170 | ); 171 | 172 | Ok(()) 173 | } 174 | 175 | /// Vote on a proposal 176 | /// 177 | /// @param pool_key - The key to identify the pool 178 | /// @param balance_manager_key - The key to identify the BalanceManager 179 | /// @param proposal_id - The ID of the proposal to vote on 180 | pub async fn vote( 181 | &self, 182 | ptb: &mut ProgrammableTransactionBuilder, 183 | pool_key: &str, 184 | balance_manager_key: &str, 185 | proposal_id: &str, 186 | ) -> anyhow::Result<()> { 187 | let pool = self.config.get_pool(pool_key)?; 188 | let balance_manager = self.config.get_balance_manager(balance_manager_key)?; 189 | let base_coin = self.config.get_coin(&pool.base_coin)?; 190 | let quote_coin = self.config.get_coin(&pool.quote_coin)?; 191 | 192 | let base_coin_tag = TypeTag::from_str(base_coin.type_name.as_str())?; 193 | let quote_coin_tag = TypeTag::from_str(quote_coin.type_name.as_str())?; 194 | 195 | let trade_proof = self 196 | .balance_manager_contract 197 | .generate_proof(ptb, balance_manager_key) 198 | .await?; 199 | 200 | let pool_id = ObjectID::from_hex_literal(pool.address.as_str())?; 201 | let balance_manager_id = ObjectID::from_hex_literal(balance_manager.address.as_str())?; 202 | let proposal_id = ObjectID::from_hex_literal(proposal_id)?; 203 | 204 | let arguments = vec![ 205 | ptb.obj(self.client.share_object(pool_id).await?)?, 206 | ptb.obj(self.client.share_object(balance_manager_id).await?)?, 207 | trade_proof, 208 | ptb.pure(proposal_id)?, 209 | ]; 210 | 211 | ptb.programmable_move_call( 212 | ObjectID::from_hex_literal(self.config.deepbook_package_id())?, 213 | Identifier::new("pool")?, 214 | Identifier::new("vote")?, 215 | vec![base_coin_tag, quote_coin_tag], 216 | arguments, 217 | ); 218 | Ok(()) 219 | } 220 | } 221 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /src/utils/constants.rs: -------------------------------------------------------------------------------- 1 | use super::types::{Coin, DeepBookPackageIds, Pool}; 2 | use lazy_static; 3 | use std::collections::HashMap; 4 | 5 | pub const TESTNET_PACKAGE_IDS: DeepBookPackageIds = DeepBookPackageIds { 6 | deepbook_package_id: "0xa3886aaa8aa831572dd39549242ca004a438c3a55967af9f0387ad2b01595068", 7 | registry_id: "0x7c256edbda983a2cd6f946655f4bf3f00a41043993781f8674a7046e8c0e11d1", 8 | deep_treasury_id: "0x69fffdae0075f8f71f4fa793549c11079266910e8905169845af1f5d00e09dcb", 9 | }; 10 | 11 | pub const MAINNET_PACKAGE_IDS: DeepBookPackageIds = DeepBookPackageIds { 12 | deepbook_package_id: "0xb29d83c26cdd2a64959263abbcfc4a6937f0c9fccaf98580ca56faded65be244", 13 | registry_id: "0xaf16199a2dff736e9f07a845f23c5da6df6f756eddb631aed9d24a93efc4549d", 14 | deep_treasury_id: "0x032abf8948dda67a271bcc18e776dbbcfb0d58c8d288a700ff0d5521e57a1ffe", 15 | }; 16 | 17 | lazy_static::lazy_static! { 18 | pub static ref TESTNET_COINS: HashMap<&'static str, Coin> = { 19 | let mut m = HashMap::new(); 20 | m.insert("DEEP", Coin { 21 | address: String::from("0x36dbef866a1d62bf7328989a10fb2f07d769f4ee587c0de4a0a256e57e0a58a8"), 22 | type_name: String::from("0x36dbef866a1d62bf7328989a10fb2f07d769f4ee587c0de4a0a256e57e0a58a8::deep::DEEP"), 23 | scalar: 1_000_000, 24 | }); 25 | m.insert("SUI", Coin { 26 | address: String::from("0x0000000000000000000000000000000000000000000000000000000000000002"), 27 | type_name: String::from("0x0000000000000000000000000000000000000000000000000000000000000002::sui::SUI"), 28 | scalar: 1_000_000_000, 29 | }); 30 | m.insert("DBUSDC", Coin { 31 | address: String::from("0xf7152c05930480cd740d7311b5b8b45c6f488e3a53a11c3f74a6fac36a52e0d7"), 32 | type_name: String::from("0xf7152c05930480cd740d7311b5b8b45c6f488e3a53a11c3f74a6fac36a52e0d7::DBUSDC::DBUSDC"), 33 | scalar: 1_000_000, 34 | }); 35 | m.insert("DBUSDT", Coin { 36 | address: String::from("0xf7152c05930480cd740d7311b5b8b45c6f488e3a53a11c3f74a6fac36a52e0d7"), 37 | type_name: String::from("0xf7152c05930480cd740d7311b5b8b45c6f488e3a53a11c3f74a6fac36a52e0d7::DBUSDT::DBUSDT"), 38 | scalar: 1_000_000, 39 | }); 40 | m 41 | }; 42 | 43 | pub static ref MAINNET_COINS: HashMap<&'static str, Coin> = { 44 | let mut m = HashMap::new(); 45 | m.insert("DEEP", Coin { 46 | address: String::from("0xdeeb7a4662eec9f2f3def03fb937a663dddaa2e215b8078a284d026b7946c270"), 47 | type_name: String::from("0xdeeb7a4662eec9f2f3def03fb937a663dddaa2e215b8078a284d026b7946c270::deep::DEEP"), 48 | scalar: 1_000_000, 49 | }); 50 | m.insert("SUI", Coin { 51 | address: String::from("0x0000000000000000000000000000000000000000000000000000000000000002"), 52 | type_name: String::from("0x0000000000000000000000000000000000000000000000000000000000000002::sui::SUI"), 53 | scalar: 1_000_000_000, 54 | }); 55 | m.insert("USDC", Coin { 56 | address: String::from("0xdba34672e30cb065b1f93e3ab55318768fd6fef66c15942c9f7cb846e2f900e7"), 57 | type_name: String::from("0xdba34672e30cb065b1f93e3ab55318768fd6fef66c15942c9f7cb846e2f900e7::usdc::USDC"), 58 | scalar: 1_000_000, 59 | }); 60 | m.insert("WUSDC", Coin { 61 | address: String::from("0x5d4b302506645c37ff133b98c4b50a5ae14841659738d6d733d59d0d217a93bf"), 62 | type_name: String::from("0x5d4b302506645c37ff133b98c4b50a5ae14841659738d6d733d59d0d217a93bf::coin::COIN"), 63 | scalar: 1_000_000, 64 | }); 65 | m.insert("WETH", Coin { 66 | address: String::from("0xaf8cd5edc19c4512f4259f0bee101a40d41ebed738ade5874359610ef8eeced5"), 67 | type_name: String::from("0xaf8cd5edc19c4512f4259f0bee101a40d41ebed738ade5874359610ef8eeced5::coin::COIN"), 68 | scalar: 100_000_000, 69 | }); 70 | m.insert("BETH", Coin { 71 | address: String::from("0xd0e89b2af5e4910726fbcd8b8dd37bb79b29e5f83f7491bca830e94f7f226d29"), 72 | type_name: String::from("0xd0e89b2af5e4910726fbcd8b8dd37bb79b29e5f83f7491bca830e94f7f226d29::eth::ETH"), 73 | scalar: 100_000_000, 74 | }); 75 | m.insert("WBTC", Coin { 76 | address: String::from("0x027792d9fed7f9844eb4839566001bb6f6cb4804f66aa2da6fe1ee242d896881"), 77 | type_name: String::from("0x027792d9fed7f9844eb4839566001bb6f6cb4804f66aa2da6fe1ee242d896881::coin::COIN"), 78 | scalar: 100_000_000, 79 | }); 80 | m.insert("WUSDT", Coin { 81 | address: String::from("0xc060006111016b8a020ad5b33834984a437aaa7d3c74c18e09a95d48aceab08c"), 82 | type_name: String::from("0xc060006111016b8a020ad5b33834984a437aaa7d3c74c18e09a95d48aceab08c::coin::COIN"), 83 | scalar: 1_000_000, 84 | }); 85 | m.insert("NS", Coin { 86 | address: String::from("0x5145494a5f5100e645e4b0aa950fa6b68f614e8c59e17bc5ded3495123a79178"), 87 | type_name: String::from("0x5145494a5f5100e645e4b0aa950fa6b68f614e8c59e17bc5ded3495123a79178::ns::NS"), 88 | scalar: 1_000_000, 89 | }); 90 | m.insert("TYPUS", Coin { 91 | address: String::from("0xf82dc05634970553615eef6112a1ac4fb7bf10272bf6cbe0f80ef44a6c489385"), 92 | type_name: String::from("0xf82dc05634970553615eef6112a1ac4fb7bf10272bf6cbe0f80ef44a6c489385::typus::TYPUS"), 93 | scalar: 1_000_000_000, 94 | }); 95 | m.insert("AUSD", Coin { 96 | address: String::from("0x2053d08c1e2bd02791056171aab0fd12bd7cd7efad2ab8f6b9c8902f14df2ff2"), 97 | type_name: String::from("0x2053d08c1e2bd02791056171aab0fd12bd7cd7efad2ab8f6b9c8902f14df2ff2::ausd::AUSD"), 98 | scalar: 1_000_000, 99 | }); 100 | m.insert("DRF", Coin { 101 | address: String::from("0x294de7579d55c110a00a7c4946e09a1b5cbeca2592fbb83fd7bfacba3cfeaf0e"), 102 | type_name: String::from("0x294de7579d55c110a00a7c4946e09a1b5cbeca2592fbb83fd7bfacba3cfeaf0e::drf::DRF"), 103 | scalar: 1_000_000, 104 | }); 105 | m.insert("SEND", Coin { 106 | address: String::from("0xb45fcfcc2cc07ce0702cc2d229621e046c906ef14d9b25e8e4d25f6e8763fef7"), 107 | type_name: String::from("0xb45fcfcc2cc07ce0702cc2d229621e046c906ef14d9b25e8e4d25f6e8763fef7::send::SEND"), 108 | scalar: 1_000_000, 109 | }); 110 | m.insert("WAL", Coin { 111 | address: String::from("0x356a26eb9e012a68958082340d4c4116e7f55615cf27affcff209cf0ae544f59"), 112 | type_name: String::from("0x356a26eb9e012a68958082340d4c4116e7f55615cf27affcff209cf0ae544f59::wal::WAL"), 113 | scalar: 1_000_000_000, 114 | }); 115 | m.insert("XBTC", Coin { 116 | address: String::from("0x876a4b7bce8aeaef60464c11f4026903e9afacab79b9b142686158aa86560b50"), 117 | type_name: String::from("0x876a4b7bce8aeaef60464c11f4026903e9afacab79b9b142686158aa86560b50::xbtc::XBTC"), 118 | scalar: 100_000_000, 119 | }); 120 | m.insert("IKA", Coin { 121 | address: String::from("0x7262fb2f7a3a14c888c438a3cd9b912469a58cf60f367352c46584262e8299aa"), 122 | type_name: String::from("0x7262fb2f7a3a14c888c438a3cd9b912469a58cf60f367352c46584262e8299aa::ika::IKA"), 123 | scalar: 1_000_000_000, 124 | }); 125 | // This coin is experimental 126 | m.insert("WGIGA", Coin { 127 | address: String::from("0xec32640add6d02a1d5f0425d72705eb76d9de7edfd4f34e0dba68e62ecceb05b"), 128 | type_name: String::from("0xec32640add6d02a1d5f0425d72705eb76d9de7edfd4f34e0dba68e62ecceb05b::coin::COIN"), 129 | scalar: 100_000, 130 | }); 131 | m 132 | }; 133 | 134 | pub static ref TESTNET_POOLS: HashMap<&'static str, Pool> = { 135 | let mut m = HashMap::new(); 136 | m.insert("DEEP_SUI", Pool { 137 | address: String::from("0x48c95963e9eac37a316b7ae04a0deb761bcdcc2b67912374d6036e7f0e9bae9f"), 138 | base_coin: String::from("DEEP"), 139 | quote_coin: String::from("SUI"), 140 | }); 141 | m.insert("SUI_DBUSDC", Pool { 142 | address: String::from("0x1c19362ca52b8ffd7a33cee805a67d40f31e6ba303753fd3a4cfdfacea7163a5"), 143 | base_coin: String::from("SUI"), 144 | quote_coin: String::from("DBUSDC"), 145 | }); 146 | m.insert("DEEP_DBUSDC", Pool { 147 | address: String::from("0xe86b991f8632217505fd859445f9803967ac84a9d4a1219065bf191fcb74b622"), 148 | base_coin: String::from("DEEP"), 149 | quote_coin: String::from("DBUSDC"), 150 | }); 151 | m.insert("DBUSDT_DBUSDC", Pool { 152 | address: String::from("0x83970bb02e3636efdff8c141ab06af5e3c9a22e2f74d7f02a9c3430d0d10c1ca"), 153 | base_coin: String::from("DBUSDT"), 154 | quote_coin: String::from("DBUSDC"), 155 | }); 156 | m.insert("WAL_DBUSDC", Pool { 157 | address: String::from("0xeb524b6aea0ec4b494878582e0b78924208339d360b62aec4a8ecd4031520dbb"), 158 | base_coin: String::from("WAL"), 159 | quote_coin: String::from("WAL"), 160 | }); 161 | m.insert("WAL_SUI", Pool { 162 | address: String::from("0x8c1c1b186c4fddab1ebd53e0895a36c1d1b3b9a77cd34e607bef49a38af0150a"), 163 | base_coin: String::from("WAL"), 164 | quote_coin: String::from("SUI"), 165 | }); 166 | m 167 | }; 168 | 169 | pub static ref MAINNET_POOLS: HashMap<&'static str, Pool> = { 170 | let mut m = HashMap::new(); 171 | m.insert("DEEP_SUI", Pool { 172 | address: String::from("0xb663828d6217467c8a1838a03793da896cbe745b150ebd57d82f814ca579fc22"), 173 | base_coin: String::from("DEEP"), 174 | quote_coin: String::from("SUI"), 175 | }); 176 | m.insert("SUI_USDC", Pool { 177 | address: String::from("0xe05dafb5133bcffb8d59f4e12465dc0e9faeaa05e3e342a08fe135800e3e4407"), 178 | base_coin: String::from("SUI"), 179 | quote_coin: String::from("USDC"), 180 | }); 181 | m.insert("DEEP_USDC", Pool { 182 | address: String::from("0xf948981b806057580f91622417534f491da5f61aeaf33d0ed8e69fd5691c95ce"), 183 | base_coin: String::from("DEEP"), 184 | quote_coin: String::from("USDC"), 185 | }); 186 | m.insert("WUSDT_USDC", Pool { 187 | address: String::from("0x4e2ca3988246e1d50b9bf209abb9c1cbfec65bd95afdacc620a36c67bdb8452f"), 188 | base_coin: String::from("WUSDT"), 189 | quote_coin: String::from("USDC"), 190 | }); 191 | m.insert("WUSDC_USDC", Pool { 192 | address: String::from("0xa0b9ebefb38c963fd115f52d71fa64501b79d1adcb5270563f92ce0442376545"), 193 | base_coin: String::from("WUSDC"), 194 | quote_coin: String::from("USDC"), 195 | }); 196 | m.insert("BETH_USDC", Pool { 197 | address: String::from("0x1109352b9112717bd2a7c3eb9a416fff1ba6951760f5bdd5424cf5e4e5b3e65c"), 198 | base_coin: String::from("BETH"), 199 | quote_coin: String::from("USDC"), 200 | }); 201 | m.insert("NS_USDC", Pool { 202 | address: String::from("0x0c0fdd4008740d81a8a7d4281322aee71a1b62c449eb5b142656753d89ebc060"), 203 | base_coin: String::from("NS"), 204 | quote_coin: String::from("USDC"), 205 | }); 206 | m.insert("NS_SUI", Pool { 207 | address: String::from("0x27c4fdb3b846aa3ae4a65ef5127a309aa3c1f466671471a806d8912a18b253e8"), 208 | base_coin: String::from("NS"), 209 | quote_coin: String::from("SUI"), 210 | }); 211 | m.insert("TYPUS_SUI", Pool { 212 | address: String::from("0xe8e56f377ab5a261449b92ac42c8ddaacd5671e9fec2179d7933dd1a91200eec"), 213 | base_coin: String::from("TYPUS"), 214 | quote_coin: String::from("SUI"), 215 | }); 216 | m.insert("SUI_AUSD", Pool { 217 | address: String::from("0x183df694ebc852a5f90a959f0f563b82ac9691e42357e9a9fe961d71a1b809c8"), 218 | base_coin: String::from("SUI"), 219 | quote_coin: String::from("AUSD"), 220 | }); 221 | m.insert("AUSD_USDC", Pool { 222 | address: String::from("0x5661fc7f88fbeb8cb881150a810758cf13700bb4e1f31274a244581b37c303c3"), 223 | base_coin: String::from("AUSD"), 224 | quote_coin: String::from("USDC"), 225 | }); 226 | m.insert("DRF_SUI", Pool { 227 | address: String::from("0x126865a0197d6ab44bfd15fd052da6db92fd2eb831ff9663451bbfa1219e2af2"), 228 | base_coin: String::from("DRF"), 229 | quote_coin: String::from("SUI"), 230 | }); 231 | m.insert("SEND_USDC", Pool { 232 | address: String::from("0x1fe7b99c28ded39774f37327b509d58e2be7fff94899c06d22b407496a6fa990"), 233 | base_coin: String::from("SEND"), 234 | quote_coin: String::from("USDC"), 235 | }); 236 | m.insert("WAL_USDC", Pool { 237 | address: String::from("0x56a1c985c1f1123181d6b881714793689321ba24301b3585eec427436eb1c76d"), 238 | base_coin: String::from("WAL"), 239 | quote_coin: String::from("USDC"), 240 | }); 241 | m.insert("WAL_SUI", Pool { 242 | address: String::from("0x81f5339934c83ea19dd6bcc75c52e83509629a5f71d3257428c2ce47cc94d08b"), 243 | base_coin: String::from("WAL"), 244 | quote_coin: String::from("SUI"), 245 | }); 246 | m.insert("XBTC_USDC", Pool { 247 | address: String::from("0x20b9a3ec7a02d4f344aa1ebc5774b7b0ccafa9a5d76230662fdc0300bb215307"), 248 | base_coin: String::from("XBTC"), 249 | quote_coin: String::from("USDC"), 250 | }); 251 | m.insert("IKA_USDC", Pool { 252 | address: String::from("0xfa732993af2b60d04d7049511f801e79426b2b6a5103e22769c0cead982b0f47"), 253 | base_coin: String::from("IKA"), 254 | quote_coin: String::from("USDC"), 255 | }); 256 | m 257 | }; 258 | } 259 | -------------------------------------------------------------------------------- /src/transactions/deepbook_admin.rs: -------------------------------------------------------------------------------- 1 | use std::str::FromStr; 2 | 3 | use anyhow::Result; 4 | use sui_sdk::{ 5 | types::{ 6 | base_types::{ObjectID, SuiAddress}, 7 | programmable_transaction_builder::ProgrammableTransactionBuilder, 8 | Identifier, TypeTag,SUI_CLOCK_OBJECT_ID 9 | }, 10 | SuiClient, 11 | }; 12 | 13 | use crate::utils::config::{DeepBookConfig, FLOAT_SCALAR}; 14 | 15 | use crate::DataReader; 16 | 17 | /// Parameters for creating a pool as admin 18 | pub struct CreatePoolAdminParams { 19 | pub base_coin_key: String, 20 | pub quote_coin_key: String, 21 | pub tick_size: f64, 22 | pub lot_size: f64, 23 | pub min_size: f64, 24 | pub whitelisted: bool, 25 | pub stable_pool: bool, 26 | } 27 | 28 | /// DeepBookAdminContract struct for managing admin actions 29 | pub struct DeepBookAdminContract { 30 | client: SuiClient, 31 | config: DeepBookConfig, 32 | } 33 | 34 | impl DeepBookAdminContract { 35 | /// Creates a new DeepBookAdminContract instance 36 | /// 37 | /// @param client - SuiClient instance 38 | /// @param config - Configuration for DeepBookAdminContract 39 | pub fn new(client: SuiClient, config: DeepBookConfig) -> Self { 40 | Self { client, config } 41 | } 42 | 43 | /// Gets the admin capability required for admin operations 44 | /// 45 | /// @returns The admin capability 46 | /// @throws Error if the admin capability is not set 47 | fn admin_cap(&self) -> anyhow::Result { 48 | self.config 49 | .admin_cap() 50 | .ok_or_else(|| anyhow::anyhow!("ADMIN_CAP environment variable not set")) 51 | } 52 | 53 | /// Create a new pool as admin 54 | /// 55 | /// @param ptb - ProgrammableTransactionBuilder instance 56 | /// @param params - Parameters for creating pool as admin 57 | pub async fn create_pool_admin( 58 | &self, 59 | ptb: &mut ProgrammableTransactionBuilder, 60 | params: CreatePoolAdminParams, 61 | ) -> Result<()> { 62 | let base_coin = self.config.get_coin(¶ms.base_coin_key)?; 63 | let quote_coin = self.config.get_coin(¶ms.quote_coin_key)?; 64 | 65 | let base_scalar = base_coin.scalar; 66 | let quote_scalar = quote_coin.scalar; 67 | 68 | let adjusted_tick_size = 69 | (params.tick_size * FLOAT_SCALAR as f64 * quote_scalar as f64) / base_scalar as f64; 70 | let adjusted_lot_size = params.lot_size * base_scalar as f64; 71 | let adjusted_min_size = params.min_size * base_scalar as f64; 72 | 73 | let base_coin_tag = TypeTag::from_str(&base_coin.type_name)?; 74 | let quote_coin_tag = TypeTag::from_str("e_coin.type_name)?; 75 | 76 | let registry_id = ObjectID::from_hex_literal(self.config.registry_id())?; 77 | let admin_cap = ObjectID::from_hex_literal(&self.admin_cap()?)?; 78 | let arguments = vec![ 79 | ptb.obj(self.client.share_object(registry_id).await?)?, 80 | ptb.pure(adjusted_tick_size as u64)?, 81 | ptb.pure(adjusted_lot_size as u64)?, 82 | ptb.pure(adjusted_min_size as u64)?, 83 | ptb.pure(params.whitelisted)?, 84 | ptb.pure(params.stable_pool)?, 85 | ptb.obj(self.client.share_object(admin_cap).await?)?, 86 | ]; 87 | 88 | ptb.programmable_move_call( 89 | ObjectID::from_hex_literal(self.config.deepbook_package_id())?, 90 | Identifier::new("pool")?, 91 | Identifier::new("create_pool_admin")?, 92 | vec![base_coin_tag, quote_coin_tag], 93 | arguments, 94 | ); 95 | 96 | Ok(()) 97 | } 98 | 99 | /// Unregister a pool as admin 100 | /// 101 | /// @param ptb - ProgrammableTransactionBuilder instance 102 | /// @param pool_key - The key of the pool to be unregistered by admin 103 | pub async fn unregister_pool_admin( 104 | &self, 105 | ptb: &mut ProgrammableTransactionBuilder, 106 | pool_key: &str, 107 | ) -> Result<()> { 108 | let pool = self.config.get_pool(pool_key)?; 109 | let base_coin = self.config.get_coin(&pool.base_coin)?; 110 | let quote_coin = self.config.get_coin(&pool.quote_coin)?; 111 | 112 | let base_coin_tag = TypeTag::from_str(&base_coin.type_name)?; 113 | let quote_coin_tag = TypeTag::from_str("e_coin.type_name)?; 114 | 115 | let pool_id = ObjectID::from_hex_literal(&pool.address)?; 116 | let registry_id = ObjectID::from_hex_literal(self.config.registry_id())?; 117 | let admin_cap = ObjectID::from_hex_literal(&self.admin_cap()?)?; 118 | 119 | let arguments = vec![ 120 | ptb.obj(self.client.share_object(pool_id).await?)?, 121 | ptb.obj(self.client.share_object(registry_id).await?)?, 122 | ptb.obj(self.client.share_object(admin_cap).await?)?, 123 | ]; 124 | 125 | ptb.programmable_move_call( 126 | ObjectID::from_hex_literal(self.config.deepbook_package_id())?, 127 | Identifier::new("pool")?, 128 | Identifier::new("unregister_pool_admin")?, 129 | vec![base_coin_tag, quote_coin_tag], 130 | arguments, 131 | ); 132 | 133 | Ok(()) 134 | } 135 | 136 | /// Update the allowed versions for a pool 137 | /// 138 | /// @param ptb - ProgrammableTransactionBuilder instance 139 | /// @param pool_key - The key of the pool to be updated 140 | pub async fn update_allowed_versions( 141 | &self, 142 | ptb: &mut ProgrammableTransactionBuilder, 143 | pool_key: &str, 144 | ) -> Result<()> { 145 | let pool = self.config.get_pool(pool_key)?; 146 | let base_coin = self.config.get_coin(&pool.base_coin)?; 147 | let quote_coin = self.config.get_coin(&pool.quote_coin)?; 148 | 149 | let base_coin_tag = TypeTag::from_str(&base_coin.type_name)?; 150 | let quote_coin_tag = TypeTag::from_str("e_coin.type_name)?; 151 | 152 | let pool_id = ObjectID::from_hex_literal(&pool.address)?; 153 | let registry_id = ObjectID::from_hex_literal(self.config.registry_id())?; 154 | let admin_cap = ObjectID::from_hex_literal(&self.admin_cap()?)?; 155 | 156 | let arguments = vec![ 157 | ptb.obj(self.client.share_object(pool_id).await?)?, 158 | ptb.obj(self.client.share_object(registry_id).await?)?, 159 | ptb.obj(self.client.share_object(admin_cap).await?)?, 160 | ]; 161 | 162 | ptb.programmable_move_call( 163 | ObjectID::from_hex_literal(self.config.deepbook_package_id())?, 164 | Identifier::new("pool")?, 165 | Identifier::new("update_allowed_versions")?, 166 | vec![base_coin_tag, quote_coin_tag], 167 | arguments, 168 | ); 169 | 170 | Ok(()) 171 | } 172 | 173 | /// Enable a specific version 174 | /// 175 | /// @param ptb - ProgrammableTransactionBuilder instance 176 | /// @param version - The version to be enabled 177 | pub async fn enable_version( 178 | &self, 179 | ptb: &mut ProgrammableTransactionBuilder, 180 | version: u64, 181 | ) -> Result<()> { 182 | let registry_id = ObjectID::from_hex_literal(self.config.registry_id())?; 183 | let admin_cap = ObjectID::from_hex_literal(&self.admin_cap()?)?; 184 | 185 | let arguments = vec![ 186 | ptb.obj(self.client.share_object(registry_id).await?)?, 187 | ptb.pure(version)?, 188 | ptb.obj(self.client.share_object(admin_cap).await?)?, 189 | ]; 190 | 191 | ptb.programmable_move_call( 192 | ObjectID::from_hex_literal(self.config.deepbook_package_id())?, 193 | Identifier::new("registry")?, 194 | Identifier::new("enable_version")?, 195 | vec![], 196 | arguments, 197 | ); 198 | 199 | Ok(()) 200 | } 201 | 202 | /// Disable a specific version 203 | /// 204 | /// @param ptb - ProgrammableTransactionBuilder instance 205 | /// @param version - The version to be disabled 206 | pub async fn disable_version( 207 | &self, 208 | ptb: &mut ProgrammableTransactionBuilder, 209 | version: u64, 210 | ) -> Result<()> { 211 | let registry_id = ObjectID::from_hex_literal(self.config.registry_id())?; 212 | let admin_cap = ObjectID::from_hex_literal(&self.admin_cap()?)?; 213 | 214 | let arguments = vec![ 215 | ptb.obj(self.client.share_object(registry_id).await?)?, 216 | ptb.pure(version)?, 217 | ptb.obj(self.client.share_object(admin_cap).await?)?, 218 | ]; 219 | 220 | ptb.programmable_move_call( 221 | ObjectID::from_hex_literal(self.config.deepbook_package_id())?, 222 | Identifier::new("registry")?, 223 | Identifier::new("disable_version")?, 224 | vec![], 225 | arguments, 226 | ); 227 | 228 | Ok(()) 229 | } 230 | 231 | /// Sets the treasury address where pool creation fees will be sent 232 | /// 233 | /// @param ptb - ProgrammableTransactionBuilder instance 234 | /// @param treasury_address - The treasury address 235 | pub async fn set_treasury_address( 236 | &self, 237 | ptb: &mut ProgrammableTransactionBuilder, 238 | treasury_address: &str, 239 | ) -> Result<()> { 240 | let registry_id = ObjectID::from_hex_literal(self.config.registry_id())?; 241 | let admin_cap = ObjectID::from_hex_literal(&self.admin_cap()?)?; 242 | 243 | let arguments = vec![ 244 | ptb.obj(self.client.share_object(registry_id).await?)?, 245 | ptb.pure(SuiAddress::from_str(treasury_address)?)?, 246 | ptb.obj(self.client.share_object(admin_cap).await?)?, 247 | ]; 248 | 249 | ptb.programmable_move_call( 250 | ObjectID::from_hex_literal(self.config.deepbook_package_id())?, 251 | Identifier::new("registry")?, 252 | Identifier::new("set_treasury_address")?, 253 | vec![], 254 | arguments, 255 | ); 256 | 257 | Ok(()) 258 | } 259 | 260 | /// Add a coin to whitelist of stable coins 261 | /// 262 | /// @param ptb - ProgrammableTransactionBuilder instance 263 | /// @param stable_coin_key - The name of the stable coin to be added 264 | pub async fn add_stable_coin( 265 | &self, 266 | ptb: &mut ProgrammableTransactionBuilder, 267 | stable_coin_key: &str, 268 | ) -> Result<()> { 269 | let stable_coin = self.config.get_coin(stable_coin_key)?; 270 | let stable_coin_tag = TypeTag::from_str(&stable_coin.type_name)?; 271 | 272 | let registry_id = ObjectID::from_hex_literal(self.config.registry_id())?; 273 | let admin_cap = ObjectID::from_hex_literal(&self.admin_cap()?)?; 274 | 275 | let arguments = vec![ 276 | ptb.obj(self.client.share_object(registry_id).await?)?, 277 | ptb.obj(self.client.share_object(admin_cap).await?)?, 278 | ]; 279 | 280 | ptb.programmable_move_call( 281 | ObjectID::from_hex_literal(self.config.deepbook_package_id())?, 282 | Identifier::new("registry")?, 283 | Identifier::new("add_stablecoin")?, 284 | vec![stable_coin_tag], 285 | arguments, 286 | ); 287 | 288 | Ok(()) 289 | } 290 | 291 | /// Remove a coin from whitelist of stable coins 292 | /// 293 | /// @param ptb - ProgrammableTransactionBuilder instance 294 | /// @param stable_coin_key - The name of the stable coin to be removed 295 | pub async fn remove_stable_coin( 296 | &self, 297 | ptb: &mut ProgrammableTransactionBuilder, 298 | stable_coin_key: &str, 299 | ) -> Result<()> { 300 | let stable_coin = self.config.get_coin(stable_coin_key)?; 301 | let stable_coin_tag = TypeTag::from_str(&stable_coin.type_name)?; 302 | 303 | let registry_id = ObjectID::from_hex_literal(self.config.registry_id())?; 304 | let admin_cap = ObjectID::from_hex_literal(&self.admin_cap()?)?; 305 | 306 | let arguments = vec![ 307 | ptb.obj(self.client.share_object(registry_id).await?)?, 308 | ptb.obj(self.client.share_object(admin_cap).await?)?, 309 | ]; 310 | 311 | ptb.programmable_move_call( 312 | ObjectID::from_hex_literal(self.config.deepbook_package_id())?, 313 | Identifier::new("registry")?, 314 | Identifier::new("remove_stablecoin")?, 315 | vec![stable_coin_tag], 316 | arguments, 317 | ); 318 | 319 | Ok(()) 320 | } 321 | 322 | /// Adjust the tick size of a pool 323 | /// 324 | /// @param ptb - ProgrammableTransactionBuilder instance 325 | /// @param pool_key - The key to identify the pool 326 | /// @param new_tick_size - The new tick size 327 | pub async fn adjust_tick_size( 328 | &self, 329 | ptb: &mut ProgrammableTransactionBuilder, 330 | pool_key: &str, 331 | new_tick_size: f64, 332 | ) -> Result<()> { 333 | let pool = self.config.get_pool(pool_key)?; 334 | let base_coin = self.config.get_coin(&pool.base_coin)?; 335 | let quote_coin = self.config.get_coin(&pool.quote_coin)?; 336 | 337 | let base_scalar = base_coin.scalar; 338 | let quote_scalar = quote_coin.scalar; 339 | 340 | let adjusted_tick_size = 341 | ((new_tick_size * FLOAT_SCALAR as f64 * quote_scalar as f64) / base_scalar as f64).round() as u64; 342 | 343 | let base_coin_tag = TypeTag::from_str(&base_coin.type_name)?; 344 | let quote_coin_tag = TypeTag::from_str("e_coin.type_name)?; 345 | 346 | let pool_id = ObjectID::from_hex_literal(&pool.address)?; 347 | let admin_cap = ObjectID::from_hex_literal(&self.admin_cap()?)?; 348 | 349 | let arguments = vec![ 350 | ptb.obj(self.client.share_object(pool_id).await?)?, 351 | ptb.pure(adjusted_tick_size)?, 352 | ptb.obj(self.client.share_object(admin_cap).await?)?, 353 | ptb.obj(self.client.share_object(SUI_CLOCK_OBJECT_ID).await?)?, 354 | ]; 355 | 356 | ptb.programmable_move_call( 357 | ObjectID::from_hex_literal(self.config.deepbook_package_id())?, 358 | Identifier::new("pool")?, 359 | Identifier::new("adjust_tick_size_admin")?, 360 | vec![base_coin_tag, quote_coin_tag], 361 | arguments, 362 | ); 363 | 364 | Ok(()) 365 | } 366 | 367 | /// Adjust the lot size and min size of a pool 368 | /// 369 | /// @param ptb - ProgrammableTransactionBuilder instance 370 | /// @param pool_key - The key to identify the pool 371 | /// @param new_lot_size - The new lot size 372 | /// @param new_min_size - The new min size 373 | pub async fn adjust_min_lot_size( 374 | &self, 375 | ptb: &mut ProgrammableTransactionBuilder, 376 | pool_key: &str, 377 | new_lot_size: f64, 378 | new_min_size: f64, 379 | ) -> Result<()> { 380 | let pool = self.config.get_pool(pool_key)?; 381 | let base_coin = self.config.get_coin(&pool.base_coin)?; 382 | let quote_coin = self.config.get_coin(&pool.quote_coin)?; 383 | 384 | let base_scalar = base_coin.scalar; 385 | 386 | let adjusted_lot_size = new_lot_size * base_scalar as f64; 387 | let adjusted_min_size = new_min_size * base_scalar as f64; 388 | 389 | let base_coin_tag = TypeTag::from_str(&base_coin.type_name)?; 390 | let quote_coin_tag = TypeTag::from_str("e_coin.type_name)?; 391 | 392 | let pool_id = ObjectID::from_hex_literal(&pool.address)?; 393 | let admin_cap = ObjectID::from_hex_literal(&self.admin_cap()?)?; 394 | 395 | let arguments = vec![ 396 | ptb.obj(self.client.share_object(pool_id).await?)?, 397 | ptb.pure(adjusted_lot_size as u64)?, 398 | ptb.pure(adjusted_min_size as u64)?, 399 | ptb.obj(self.client.share_object(admin_cap).await?)?, 400 | ptb.obj(self.client.share_object(SUI_CLOCK_OBJECT_ID).await?)?, 401 | ]; 402 | 403 | ptb.programmable_move_call( 404 | ObjectID::from_hex_literal(self.config.deepbook_package_id())?, 405 | Identifier::new("pool")?, 406 | Identifier::new("adjust_min_lot_size_admin")?, 407 | vec![base_coin_tag, quote_coin_tag], 408 | arguments, 409 | ); 410 | 411 | Ok(()) 412 | } 413 | } 414 | -------------------------------------------------------------------------------- /src/transactions/balance_manager.rs: -------------------------------------------------------------------------------- 1 | use std::str::FromStr; 2 | use sui_sdk::types::base_types::{ObjectID, SuiAddress}; 3 | use sui_sdk::types::programmable_transaction_builder::ProgrammableTransactionBuilder; 4 | use sui_sdk::types::transaction::Argument; 5 | use sui_sdk::types::{Identifier, TypeTag, SUI_FRAMEWORK_PACKAGE_ID}; 6 | use sui_sdk::SuiClient; 7 | 8 | use crate::utils::config::DeepBookConfig; 9 | 10 | use crate::DataReader; 11 | 12 | /// BalanceManagerContract struct for managing BalanceManager operations. 13 | #[derive(Clone)] 14 | pub struct BalanceManagerContract { 15 | client: SuiClient, 16 | config: DeepBookConfig, 17 | } 18 | 19 | impl BalanceManagerContract { 20 | /// Creates a new instance of BalanceManagerContract 21 | /// 22 | /// @param client - SuiClient instance 23 | /// @param config - Configuration object for DeepBook 24 | pub fn new(client: SuiClient, config: DeepBookConfig) -> Self { 25 | Self { client, config } 26 | } 27 | 28 | /// Create and share a new BalanceManager 29 | /// 30 | /// @param ptb - ProgrammableTransactionBuilder instance 31 | pub fn create_and_share_balance_manager( 32 | &self, 33 | ptb: &mut ProgrammableTransactionBuilder, 34 | ) -> anyhow::Result<()> { 35 | let package_id = ObjectID::from_hex_literal(self.config.deepbook_package_id())?; 36 | let manager = ptb.programmable_move_call( 37 | package_id, 38 | Identifier::new("balance_manager")?, 39 | Identifier::new("new")?, 40 | vec![], 41 | vec![], 42 | ); 43 | 44 | let manager_tag = TypeTag::from_str( 45 | format!( 46 | "{}::balance_manager::BalanceManager", 47 | self.config.deepbook_package_id() 48 | ) 49 | .as_str(), 50 | )?; 51 | 52 | ptb.programmable_move_call( 53 | SUI_FRAMEWORK_PACKAGE_ID, 54 | Identifier::new("transfer")?, 55 | Identifier::new("public_share_object")?, 56 | vec![manager_tag], 57 | vec![manager], 58 | ); 59 | Ok(()) 60 | } 61 | 62 | /// Create and share a new BalanceManager with a specified owner 63 | /// 64 | /// @param ptb - ProgrammableTransactionBuilder instance 65 | /// @param owner_address - The address of the owner 66 | pub fn create_and_share_balance_manager_with_owner( 67 | &self, 68 | ptb: &mut ProgrammableTransactionBuilder, 69 | owner_address: SuiAddress, 70 | ) -> anyhow::Result<()> { 71 | let package_id = ObjectID::from_hex_literal(self.config.deepbook_package_id())?; 72 | 73 | let arguments = vec![ptb.pure(owner_address)?]; 74 | 75 | let manager = ptb.programmable_move_call( 76 | package_id, 77 | Identifier::new("balance_manager")?, 78 | Identifier::new("new_with_custom_owner")?, 79 | vec![], 80 | arguments, 81 | ); 82 | 83 | let manager_tag = TypeTag::from_str( 84 | format!( 85 | "{}::balance_manager::BalanceManager", 86 | self.config.deepbook_package_id() 87 | ) 88 | .as_str(), 89 | )?; 90 | 91 | ptb.programmable_move_call( 92 | SUI_FRAMEWORK_PACKAGE_ID, 93 | Identifier::new("transfer")?, 94 | Identifier::new("public_share_object")?, 95 | vec![manager_tag], 96 | vec![manager], 97 | ); 98 | Ok(()) 99 | } 100 | 101 | /// Deposit funds into the BalanceManager 102 | /// 103 | /// @param ptb - ProgrammableTransactionBuilder instance 104 | /// @param manager_key - The key to identify the BalanceManager 105 | /// @param coin_key - The key to identify the coin 106 | /// @param amount_to_deposit - The amount to deposit 107 | pub async fn deposit_into_manager( 108 | &self, 109 | ptb: &mut ProgrammableTransactionBuilder, 110 | sender: SuiAddress, 111 | manager_key: &str, 112 | coin_key: &str, 113 | amount_to_deposit: f64, 114 | ) -> anyhow::Result<()> { 115 | let manager_address = self 116 | .config 117 | .get_balance_manager(manager_key)? 118 | .address 119 | .as_str(); 120 | let manager_id = ObjectID::from_hex_literal(manager_address)?; 121 | let package_id = ObjectID::from_hex_literal(self.config.deepbook_package_id())?; 122 | let coin = self.config.get_coin(coin_key)?.clone(); 123 | let deposit_input = (amount_to_deposit * coin.scalar as f64).round() as u64; 124 | let deposit_coin = self 125 | .client 126 | .get_coin_object(sender, coin.type_name.clone(), deposit_input) 127 | .await?; 128 | 129 | let arguments = vec![ 130 | ptb.obj(self.client.share_object_mutable(manager_id).await?)?, 131 | ptb.obj(self.client.coin_object(deposit_coin).await?)?, 132 | ]; 133 | 134 | ptb.programmable_move_call( 135 | package_id, 136 | Identifier::new("balance_manager")?, 137 | Identifier::new("deposit")?, 138 | vec![TypeTag::from_str(coin.type_name.as_str())?], 139 | arguments, 140 | ); 141 | 142 | Ok(()) 143 | } 144 | 145 | /// Withdraw funds from the BalanceManager 146 | /// 147 | /// @param ptb - ProgrammableTransactionBuilder instance 148 | /// @param manager_key - The key to identify the BalanceManager 149 | /// @param coin_key - The key to identify the coin 150 | /// @param amount_to_withdraw - The amount to withdraw 151 | /// @param recipient - The recipient of the funds 152 | pub async fn withdraw_from_manager( 153 | &self, 154 | ptb: &mut ProgrammableTransactionBuilder, 155 | manager_key: &str, 156 | coin_key: &str, 157 | amount_to_withdraw: f64, 158 | recipient: SuiAddress, 159 | ) -> anyhow::Result<()> { 160 | let manager_address = self 161 | .config 162 | .get_balance_manager(manager_key)? 163 | .address 164 | .as_str(); 165 | let manager_id = ObjectID::from_hex_literal(manager_address)?; 166 | let package_id = ObjectID::from_hex_literal(self.config.deepbook_package_id())?; 167 | let coin = self.config.get_coin(coin_key)?; 168 | let withdraw_input = (amount_to_withdraw * coin.scalar as f64).round() as u64; 169 | 170 | let arguments = vec![ 171 | ptb.obj(self.client.share_object_mutable(manager_id).await?)?, 172 | ptb.pure(withdraw_input)?, 173 | ]; 174 | let coin_object = ptb.programmable_move_call( 175 | package_id, 176 | Identifier::new("balance_manager")?, 177 | Identifier::new("withdraw")?, 178 | vec![TypeTag::from_str(coin.type_name.as_str())?], 179 | arguments, 180 | ); 181 | 182 | ptb.transfer_arg(recipient, coin_object); 183 | Ok(()) 184 | } 185 | 186 | /// Withdraw all funds from the BalanceManager 187 | /// 188 | /// @param ptb - ProgrammableTransactionBuilder instance 189 | /// @param manager_key - The key to identify the BalanceManager 190 | /// @param coin_key - The key to identify the coin 191 | /// @param recipient - The recipient of the funds 192 | pub async fn withdraw_all_from_manager( 193 | &self, 194 | ptb: &mut ProgrammableTransactionBuilder, 195 | manager_key: &str, 196 | coin_key: &str, 197 | recipient: SuiAddress, 198 | ) -> anyhow::Result<()> { 199 | let manager_address = self 200 | .config 201 | .get_balance_manager(manager_key)? 202 | .address 203 | .as_str(); 204 | let manager_id = ObjectID::from_hex_literal(manager_address)?; 205 | let package_id = ObjectID::from_hex_literal(self.config.deepbook_package_id())?; 206 | let coin = self.config.get_coin(coin_key)?; 207 | 208 | let arguments = vec![ptb.obj(self.client.share_object(manager_id).await?)?]; 209 | let withdrawal_coin = ptb.programmable_move_call( 210 | package_id, 211 | Identifier::new("balance_manager")?, 212 | Identifier::new("withdraw_all")?, 213 | vec![TypeTag::from_str(coin.type_name.as_str())?], 214 | arguments, 215 | ); 216 | 217 | ptb.transfer_arg(recipient, withdrawal_coin); 218 | Ok(()) 219 | } 220 | 221 | /// Check the balance of the BalanceManager 222 | /// 223 | /// @param ptb - ProgrammableTransactionBuilder instance 224 | /// @param manager_key - The key to identify the BalanceManager 225 | /// @param coin_key - The key to identify the coin 226 | pub async fn check_manager_balance( 227 | &self, 228 | ptb: &mut ProgrammableTransactionBuilder, 229 | manager_key: &str, 230 | coin_key: &str, 231 | ) -> anyhow::Result { 232 | let manager_address = self 233 | .config 234 | .get_balance_manager(manager_key)? 235 | .address 236 | .as_str(); 237 | let manager_id = ObjectID::from_hex_literal(manager_address)?; 238 | let package_id = ObjectID::from_hex_literal(self.config.deepbook_package_id())?; 239 | let coin = self.config.get_coin(coin_key)?; 240 | 241 | let arguments = vec![ptb.obj(self.client.share_object(manager_id).await?)?]; 242 | 243 | Ok(ptb.programmable_move_call( 244 | package_id, 245 | Identifier::new("balance_manager")?, 246 | Identifier::new("balance")?, 247 | vec![TypeTag::from_str(coin.type_name.as_str())?], 248 | arguments, 249 | )) 250 | } 251 | 252 | /// Generate a trade proof for the BalanceManager 253 | /// 254 | /// @param ptb - ProgrammableTransactionBuilder instance 255 | /// @param manager_key - The key to identify the BalanceManager 256 | pub async fn generate_proof( 257 | &self, 258 | ptb: &mut ProgrammableTransactionBuilder, 259 | manager_key: &str, 260 | ) -> anyhow::Result { 261 | let balance_manager = self.config.get_balance_manager(manager_key)?; 262 | let manager_address = balance_manager.address.as_str(); 263 | let trade_cap = balance_manager.trade_cap.clone(); 264 | let manager_id = ObjectID::from_hex_literal(manager_address)?; 265 | 266 | if let Some(trade_cap) = trade_cap { 267 | let trade_cap_id = ObjectID::from_hex_literal(trade_cap.as_str())?; 268 | Ok(self 269 | .generate_proof_as_trader(ptb, &manager_id, &trade_cap_id) 270 | .await?) 271 | } else { 272 | Ok(self.generate_proof_as_owner(ptb, &manager_id).await?) 273 | } 274 | } 275 | 276 | /// Generate a trade proof as the owner 277 | /// 278 | /// @param ptb - ProgrammableTransactionBuilder instance 279 | /// @param manager_id - The ID of the BalanceManager 280 | pub async fn generate_proof_as_owner( 281 | &self, 282 | ptb: &mut ProgrammableTransactionBuilder, 283 | manager_id: &ObjectID, 284 | ) -> anyhow::Result { 285 | let package_id = ObjectID::from_hex_literal(self.config.deepbook_package_id())?; 286 | let arguments = vec![ptb.obj(self.client.share_object_mutable(*manager_id).await?)?]; 287 | Ok(ptb.programmable_move_call( 288 | package_id, 289 | Identifier::new("balance_manager")?, 290 | Identifier::new("generate_proof_as_owner")?, 291 | vec![], 292 | arguments, 293 | )) 294 | } 295 | 296 | /// Generate a trade proof as a trader 297 | /// 298 | /// @param ptb - ProgrammableTransactionBuilder instance 299 | /// @param manager_id - The ID of the BalanceManager 300 | /// @param trade_cap_id - The ID of the TradeCap 301 | pub async fn generate_proof_as_trader( 302 | &self, 303 | ptb: &mut ProgrammableTransactionBuilder, 304 | manager_id: &ObjectID, 305 | trade_cap_id: &ObjectID, 306 | ) -> anyhow::Result { 307 | let package_id = ObjectID::from_hex_literal(self.config.deepbook_package_id())?; 308 | let arguments = vec![ 309 | ptb.obj(self.client.share_object_mutable(*manager_id).await?)?, 310 | ptb.obj(self.client.share_object(*trade_cap_id).await?)?, 311 | ]; 312 | Ok(ptb.programmable_move_call( 313 | package_id, 314 | Identifier::new("balance_manager")?, 315 | Identifier::new("generate_proof_as_trader")?, 316 | vec![], 317 | arguments, 318 | )) 319 | } 320 | 321 | /// Mint a TradeCap 322 | /// 323 | /// @param ptb - ProgrammableTransactionBuilder instance 324 | /// @param manager_key - The key to identify the BalanceManager 325 | pub async fn mint_trade_cap( 326 | &self, 327 | ptb: &mut ProgrammableTransactionBuilder, 328 | manager_key: &str, 329 | ) -> anyhow::Result<()> { 330 | let package_id = ObjectID::from_hex_literal(self.config.deepbook_package_id())?; 331 | let manager_address = self 332 | .config 333 | .get_balance_manager(manager_key)? 334 | .address 335 | .as_str(); 336 | let manager_id = ObjectID::from_hex_literal(manager_address)?; 337 | 338 | let arguments = vec![ptb.obj(self.client.share_object_mutable(manager_id).await?)?]; 339 | ptb.programmable_move_call( 340 | package_id, 341 | Identifier::new("balance_manager")?, 342 | Identifier::new("mint_trade_cap")?, 343 | vec![], 344 | arguments, 345 | ); 346 | Ok(()) 347 | } 348 | 349 | /// Mint a DepositCap 350 | /// 351 | /// @param ptb - ProgrammableTransactionBuilder instance 352 | /// @param manager_key - The key to identify the BalanceManager 353 | pub async fn mint_deposit_cap( 354 | &self, 355 | ptb: &mut ProgrammableTransactionBuilder, 356 | manager_key: &str, 357 | ) -> anyhow::Result<()> { 358 | let package_id = ObjectID::from_hex_literal(self.config.deepbook_package_id())?; 359 | let manager_address = self 360 | .config 361 | .get_balance_manager(manager_key)? 362 | .address 363 | .as_str(); 364 | let manager_id = ObjectID::from_hex_literal(manager_address)?; 365 | 366 | let arguments = vec![ptb.obj(self.client.share_object_mutable(manager_id).await?)?]; 367 | ptb.programmable_move_call( 368 | package_id, 369 | Identifier::new("balance_manager")?, 370 | Identifier::new("mint_deposit_cap")?, 371 | vec![], 372 | arguments, 373 | ); 374 | Ok(()) 375 | } 376 | 377 | /// Mint a WithdrawCap 378 | /// 379 | /// @param ptb - ProgrammableTransactionBuilder instance 380 | /// @param manager_key - The key to identify the BalanceManager 381 | pub async fn mint_withdraw_cap( 382 | &self, 383 | ptb: &mut ProgrammableTransactionBuilder, 384 | manager_key: &str, 385 | ) -> anyhow::Result<()> { 386 | let package_id = ObjectID::from_hex_literal(self.config.deepbook_package_id())?; 387 | let manager_address = self 388 | .config 389 | .get_balance_manager(manager_key)? 390 | .address 391 | .as_str(); 392 | let manager_id = ObjectID::from_hex_literal(manager_address)?; 393 | 394 | let arguments = vec![ptb.obj(self.client.share_object_mutable(manager_id).await?)?]; 395 | ptb.programmable_move_call( 396 | package_id, 397 | Identifier::new("balance_manager")?, 398 | Identifier::new("mint_withdraw_cap")?, 399 | vec![], 400 | arguments, 401 | ); 402 | Ok(()) 403 | } 404 | 405 | /// Deposit using DepositCap 406 | /// 407 | /// @param ptb - ProgrammableTransactionBuilder instance 408 | /// @param manager_key - The key to identify the BalanceManager 409 | /// @param coin_key - The key to identify the coin 410 | /// @param amount_to_deposit - The amount to deposit 411 | pub async fn deposit_with_cap( 412 | &self, 413 | ptb: &mut ProgrammableTransactionBuilder, 414 | sender: SuiAddress, 415 | manager_key: &str, 416 | coin_key: &str, 417 | amount_to_deposit: f64, 418 | ) -> anyhow::Result<()> { 419 | let balance_manager = self.config.get_balance_manager(manager_key)?; 420 | let manager_id = ObjectID::from_hex_literal(balance_manager.address.as_str())?; 421 | let deposit_cap_id = ObjectID::from_hex_literal( 422 | balance_manager 423 | .deposit_cap 424 | .clone() 425 | .ok_or_else(|| anyhow::anyhow!("DepositCap not set for {}", manager_key))? 426 | .as_str(), 427 | )?; 428 | let package_id = ObjectID::from_hex_literal(self.config.deepbook_package_id())?; 429 | let coin = self.config.get_coin(coin_key)?.clone(); 430 | let deposit_input = (amount_to_deposit * coin.scalar as f64).round() as u64; 431 | let deposit_coin = self 432 | .client 433 | .get_coin_object(sender, coin.type_name.clone(), deposit_input) 434 | .await?; 435 | 436 | let arguments = vec![ 437 | ptb.obj(self.client.share_object_mutable(manager_id).await?)?, 438 | ptb.obj(self.client.share_object(deposit_cap_id).await?)?, 439 | ptb.obj(self.client.coin_object(deposit_coin).await?)?, 440 | ]; 441 | 442 | ptb.programmable_move_call( 443 | package_id, 444 | Identifier::new("balance_manager")?, 445 | Identifier::new("deposit_with_cap")?, 446 | vec![TypeTag::from_str(coin.type_name.as_str())?], 447 | arguments, 448 | ); 449 | Ok(()) 450 | } 451 | 452 | /// Withdraw using WithdrawCap 453 | /// 454 | /// @param ptb - ProgrammableTransactionBuilder instance 455 | /// @param manager_key - The key to identify the BalanceManager 456 | /// @param coin_key - The key to identify the coin 457 | /// @param amount_to_withdraw - The amount to withdraw 458 | pub async fn withdraw_with_cap( 459 | &self, 460 | ptb: &mut ProgrammableTransactionBuilder, 461 | manager_key: &str, 462 | coin_key: &str, 463 | amount_to_withdraw: f64, 464 | recipient: SuiAddress, 465 | ) -> anyhow::Result<()> { 466 | let balance_manager = self.config.get_balance_manager(manager_key)?; 467 | let manager_id = ObjectID::from_hex_literal(balance_manager.address.as_str())?; 468 | let withdraw_cap_id = ObjectID::from_hex_literal( 469 | balance_manager 470 | .withdraw_cap 471 | .clone() 472 | .ok_or_else(|| anyhow::anyhow!("WithdrawCap not set for {}", manager_key))? 473 | .as_str(), 474 | )?; 475 | let package_id = ObjectID::from_hex_literal(self.config.deepbook_package_id())?; 476 | let coin = self.config.get_coin(coin_key)?; 477 | let withdraw_amount = (amount_to_withdraw * coin.scalar as f64).round() as u64; 478 | 479 | let arguments = vec![ 480 | ptb.obj(self.client.share_object_mutable(manager_id).await?)?, 481 | ptb.obj(self.client.share_object(withdraw_cap_id).await?)?, 482 | ptb.pure(withdraw_amount)?, 483 | ]; 484 | 485 | let coin_object = ptb.programmable_move_call( 486 | package_id, 487 | Identifier::new("balance_manager")?, 488 | Identifier::new("withdraw_with_cap")?, 489 | vec![TypeTag::from_str(coin.type_name.as_str())?], 490 | arguments, 491 | ); 492 | 493 | ptb.transfer_arg(recipient, coin_object); 494 | Ok(()) 495 | } 496 | 497 | /// Get the owner of the BalanceManager 498 | /// 499 | /// @param ptb - ProgrammableTransactionBuilder instance 500 | /// @param manager_key - The key to identify the BalanceManager 501 | pub async fn owner( 502 | &self, 503 | ptb: &mut ProgrammableTransactionBuilder, 504 | manager_key: &str, 505 | ) -> anyhow::Result { 506 | let package_id = ObjectID::from_hex_literal(self.config.deepbook_package_id())?; 507 | let manager_address = self 508 | .config 509 | .get_balance_manager(manager_key)? 510 | .address 511 | .as_str(); 512 | let manager_id = ObjectID::from_hex_literal(manager_address)?; 513 | let arguments = vec![ptb.obj(self.client.share_object(manager_id).await?)?]; 514 | Ok(ptb.programmable_move_call( 515 | package_id, 516 | Identifier::new("balance_manager")?, 517 | Identifier::new("owner")?, 518 | vec![], 519 | arguments, 520 | )) 521 | } 522 | 523 | /// Get the ID of the BalanceManager 524 | /// 525 | /// @param ptb - ProgrammableTransactionBuilder instance 526 | /// @param manager_key - The key to identify the BalanceManager 527 | pub async fn id( 528 | &self, 529 | ptb: &mut ProgrammableTransactionBuilder, 530 | manager_key: &str, 531 | ) -> anyhow::Result { 532 | let package_id = ObjectID::from_hex_literal(self.config.deepbook_package_id())?; 533 | let manager_address = self 534 | .config 535 | .get_balance_manager(manager_key)? 536 | .address 537 | .as_str(); 538 | let manager_id = ObjectID::from_hex_literal(manager_address)?; 539 | let arguments = vec![ptb.obj(self.client.share_object(manager_id).await?)?]; 540 | Ok(ptb.programmable_move_call( 541 | package_id, 542 | Identifier::new("balance_manager")?, 543 | Identifier::new("id")?, 544 | vec![], 545 | arguments, 546 | )) 547 | } 548 | } 549 | -------------------------------------------------------------------------------- /src/client.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use sui_sdk::types::base_types::SuiAddress; 3 | use sui_sdk::types::programmable_transaction_builder::ProgrammableTransactionBuilder; 4 | use sui_sdk::SuiClient; 5 | 6 | use crate::transactions::balance_manager::BalanceManagerContract; 7 | use crate::transactions::deepbook::DeepBookContract; 8 | use crate::transactions::deepbook_admin::DeepBookAdminContract; 9 | use crate::transactions::flashloan::FlashLoanContract; 10 | use crate::transactions::governance::GovernanceContract; 11 | use crate::utils::config::{ 12 | BalanceManagerMap, CoinMap, DeepBookConfig, Environment, PoolMap, DEEP_SCALAR, FLOAT_SCALAR, 13 | }; 14 | use crate::DataReader; 15 | 16 | #[derive(Debug, Serialize, Deserialize)] 17 | pub struct QuoteQuantityOut { 18 | pub base_quantity: f64, 19 | pub base_out: f64, 20 | pub quote_out: f64, 21 | pub deep_required: f64, 22 | } 23 | 24 | #[derive(Debug, Serialize, Deserialize)] 25 | pub struct QuantityOut { 26 | pub base_quantity: f64, 27 | pub quote_quantity: f64, 28 | pub base_out: f64, 29 | pub quote_out: f64, 30 | pub deep_required: f64, 31 | } 32 | 33 | #[derive(Debug, Serialize, Deserialize)] 34 | pub struct ID { 35 | pub bytes: SuiAddress, 36 | } 37 | 38 | #[derive(Debug, Serialize, Deserialize)] 39 | pub struct OrderDeepPrice { 40 | pub asset_is_base: bool, 41 | pub deep_per_asset: u64, 42 | } 43 | 44 | #[derive(Debug, Serialize, Deserialize)] 45 | pub struct Order { 46 | pub balance_manager_id: ID, 47 | pub order_id: u128, 48 | pub client_order_id: u64, 49 | pub quantity: u64, 50 | pub filled_quantity: u64, 51 | pub fee_is_deep: bool, 52 | pub order_deep_price: OrderDeepPrice, 53 | pub epoch: u64, 54 | pub status: u8, 55 | pub expire_timestamp: u64, 56 | } 57 | 58 | #[derive(Debug, Serialize, Deserialize)] 59 | pub struct NormalizedOrder { 60 | pub balance_manager_id: ID, 61 | pub order_id: u128, 62 | pub client_order_id: u64, 63 | pub quantity: String, 64 | pub filled_quantity: String, 65 | pub fee_is_deep: bool, 66 | pub order_deep_price: NormalizedOrderDeepPrice, 67 | pub epoch: u64, 68 | pub status: u8, 69 | pub expire_timestamp: u64, 70 | pub is_bid: bool, 71 | pub normalized_price: String, 72 | } 73 | 74 | #[derive(Debug, Serialize, Deserialize)] 75 | pub struct NormalizedOrderDeepPrice { 76 | pub asset_is_base: bool, 77 | pub deep_per_asset: String, 78 | } 79 | 80 | #[derive(Debug, Serialize, Deserialize)] 81 | pub struct Level2Range { 82 | pub prices: Vec, 83 | pub quantities: Vec, 84 | } 85 | 86 | #[derive(Debug, Serialize, Deserialize)] 87 | pub struct Level2TicksFromMid { 88 | pub bid_prices: Vec, 89 | pub bid_quantities: Vec, 90 | pub ask_prices: Vec, 91 | pub ask_quantities: Vec, 92 | } 93 | 94 | #[derive(Debug, Serialize, Deserialize)] 95 | pub struct VaultBalances { 96 | pub base: f64, 97 | pub quote: f64, 98 | pub deep: f64, 99 | } 100 | 101 | #[derive(Debug, Serialize, Deserialize)] 102 | pub struct PoolTradeParams { 103 | pub taker_fee: f64, 104 | pub maker_fee: f64, 105 | pub stake_required: f64, 106 | } 107 | 108 | #[derive(Debug, Serialize, Deserialize)] 109 | pub struct PoolBookParams { 110 | pub tick_size: f64, 111 | pub lot_size: f64, 112 | pub min_size: f64, 113 | } 114 | 115 | #[derive(Debug, Serialize, Deserialize)] 116 | pub struct Balances { 117 | pub base: f64, 118 | pub quote: f64, 119 | pub deep: f64, 120 | } 121 | 122 | #[derive(Deserialize)] 123 | struct RawOrderDeepPrice { 124 | asset_is_base: bool, 125 | deep_per_asset: u64, 126 | } 127 | 128 | #[derive(Debug, Serialize, Deserialize)] 129 | pub struct Account { 130 | pub epoch: u64, 131 | pub open_orders: Vec, 132 | pub taker_volume: f64, 133 | pub maker_volume: f64, 134 | pub active_stake: f64, 135 | pub inactive_stake: f64, 136 | pub created_proposal: bool, 137 | pub voted_proposal: Option, 138 | pub unclaimed_rebates: Balances, 139 | pub settled_balances: Balances, 140 | pub owed_balances: Balances, 141 | } 142 | 143 | #[derive(Debug, Serialize, Deserialize)] 144 | pub struct PoolDeepPrice { 145 | pub asset_is_base: bool, 146 | pub deep_per_base: Option, 147 | pub deep_per_quote: Option, 148 | } 149 | 150 | /// DeepBookClient struct for managing DeepBook operations. 151 | pub struct DeepBookClient { 152 | client: SuiClient, 153 | config: DeepBookConfig, 154 | address: SuiAddress, 155 | pub balance_manager: BalanceManagerContract, 156 | pub deep_book: DeepBookContract, 157 | pub deep_book_admin: DeepBookAdminContract, 158 | pub flash_loans: FlashLoanContract, 159 | pub governance: GovernanceContract, 160 | } 161 | 162 | impl DeepBookClient { 163 | /// Creates a new DeepBookClient instance 164 | /// 165 | /// @param client - The SuiClient instance 166 | /// @param address - The address of the DeepBook contract 167 | /// @param env - The environment of the DeepBook contract 168 | /// @param balance_managers - The balance managers associated with the DeepBook contract 169 | /// @param coins - The coins associated with the DeepBook contract 170 | /// @param pools - The pools associated with the DeepBook contract 171 | /// @param admin_cap - The admin cap associated with the DeepBook contract 172 | pub fn new( 173 | client: SuiClient, 174 | address: SuiAddress, 175 | env: Environment, 176 | balance_managers: Option, 177 | coins: Option, 178 | pools: Option, 179 | admin_cap: Option, 180 | ) -> Self { 181 | let config = DeepBookConfig::new(env, address, admin_cap, balance_managers, coins, pools); 182 | let balance_manager = BalanceManagerContract::new(client.clone(), config.clone()); 183 | Self { 184 | client: client.clone(), 185 | address, 186 | config: config.clone(), 187 | balance_manager: balance_manager.clone(), 188 | deep_book: DeepBookContract::new( 189 | client.clone(), 190 | config.clone(), 191 | balance_manager.clone(), 192 | ), 193 | deep_book_admin: DeepBookAdminContract::new(client.clone(), config.clone()), 194 | flash_loans: FlashLoanContract::new(client.clone(), config.clone()), 195 | governance: GovernanceContract::new( 196 | client.clone(), 197 | config.clone(), 198 | balance_manager.clone(), 199 | ), 200 | } 201 | } 202 | 203 | /// Check the balance of a balance manager for a specific coin 204 | /// 205 | /// @param manager_key - The key of the balance manager 206 | /// @param coin_key - The key of the coin 207 | pub async fn check_manager_balance( 208 | &self, 209 | manager_key: &str, 210 | coin_key: &str, 211 | ) -> anyhow::Result<(String, f64)> { 212 | let mut ptb = ProgrammableTransactionBuilder::new(); 213 | let coin = self.config.get_coin(coin_key)?; 214 | 215 | self.balance_manager 216 | .check_manager_balance(&mut ptb, manager_key, coin_key) 217 | .await?; 218 | match self.client.dev_inspect_transaction(self.address, ptb).await { 219 | Ok(res) => { 220 | let res = res 221 | .first() 222 | .ok_or_else(|| anyhow::anyhow!("Failed to get first result"))?; 223 | let balance = bcs::from_bytes::(&res.0)?; 224 | let adjusted_balance = balance as f64 / coin.scalar as f64; 225 | 226 | Ok(( 227 | coin.type_name.clone(), 228 | (adjusted_balance * 1e9).round() / 1e9, 229 | )) 230 | } 231 | Err(e) => Err(e), 232 | } 233 | } 234 | 235 | /// Check if a pool is whitelisted 236 | /// 237 | /// @param pool_key - The key of the pool 238 | pub async fn whitelisted(&self, pool_key: &str) -> anyhow::Result { 239 | let mut ptb = ProgrammableTransactionBuilder::new(); 240 | self.deep_book.whitelisted(&mut ptb, pool_key).await?; 241 | 242 | match self.client.dev_inspect_transaction(self.address, ptb).await { 243 | Ok(res) => { 244 | let res = res 245 | .first() 246 | .ok_or_else(|| anyhow::anyhow!("Failed to get first result"))?; 247 | let whitelisted = bcs::from_bytes::(&res.0)?; 248 | Ok(whitelisted) 249 | } 250 | Err(e) => Err(e), 251 | } 252 | } 253 | 254 | /// Get the quote quantity out for a given base quantity 255 | /// 256 | /// @param pool_key - The key of the pool 257 | /// @param base_quantity - The base quantity to convert 258 | pub async fn get_quote_quantity_out( 259 | &self, 260 | pool_key: &str, 261 | base_quantity: f64, 262 | ) -> anyhow::Result { 263 | let mut ptb = ProgrammableTransactionBuilder::new(); 264 | self.deep_book 265 | .get_quote_quantity_out(&mut ptb, pool_key, base_quantity) 266 | .await?; 267 | 268 | self.get_quote_quantity_out_inner(ptb, pool_key, base_quantity) 269 | .await 270 | } 271 | 272 | /// Get the base quantity out for a given quote quantity 273 | /// 274 | /// @param pool_key - The key of the pool 275 | /// @param quote_quantity - The quote quantity to convert 276 | pub async fn get_base_quantity_out( 277 | &self, 278 | pool_key: &str, 279 | quote_quantity: f64, 280 | ) -> anyhow::Result { 281 | let mut ptb = ProgrammableTransactionBuilder::new(); 282 | self.deep_book 283 | .get_base_quantity_out(&mut ptb, pool_key, quote_quantity) 284 | .await?; 285 | self.get_quote_quantity_out_inner(ptb, pool_key, quote_quantity) 286 | .await 287 | } 288 | 289 | /// Get the output quantities for given base and quote quantities. Only one quantity can be non-zero 290 | /// 291 | /// @param pool_key - The key of the pool 292 | /// @param base_quantity - Base quantity to convert 293 | /// @param quote_quantity - Quote quantity to convert 294 | pub async fn get_quantity_out( 295 | &self, 296 | pool_key: &str, 297 | base_quantity: f64, 298 | quote_quantity: f64, 299 | ) -> anyhow::Result { 300 | let mut ptb = ProgrammableTransactionBuilder::new(); 301 | self.deep_book 302 | .get_quantity_out(&mut ptb, pool_key, base_quantity, quote_quantity) 303 | .await?; 304 | let result = self 305 | .get_quote_quantity_out_inner(ptb, pool_key, quote_quantity) 306 | .await?; 307 | Ok(QuantityOut { 308 | base_quantity, 309 | quote_quantity, 310 | base_out: result.base_out, 311 | quote_out: result.quote_out, 312 | deep_required: result.deep_required, 313 | }) 314 | } 315 | 316 | /// Get open orders for a balance manager in a pool 317 | /// 318 | /// @param pool_key - The key of the pool 319 | /// @param manager_key - The key of the balance manager 320 | pub async fn account_open_orders( 321 | &self, 322 | pool_key: &str, 323 | manager_key: &str, 324 | ) -> anyhow::Result> { 325 | let mut ptb = ProgrammableTransactionBuilder::new(); 326 | self.deep_book 327 | .account_open_orders(&mut ptb, pool_key, manager_key) 328 | .await?; 329 | 330 | match self.client.dev_inspect_transaction(self.address, ptb).await { 331 | Ok(res) => { 332 | let res = res 333 | .first() 334 | .ok_or_else(|| anyhow::anyhow!("Failed to get first result"))?; 335 | let order_ids = bcs::from_bytes::>(&res.0)?; 336 | Ok(order_ids) 337 | } 338 | Err(e) => Err(e), 339 | } 340 | } 341 | 342 | /// Get the order information for a specific order in a pool 343 | /// 344 | /// @param pool_key - The key of the pool 345 | /// @param order_id - The order ID 346 | pub async fn get_order(&self, pool_key: &str, order_id: u128) -> anyhow::Result> { 347 | let mut ptb = ProgrammableTransactionBuilder::new(); 348 | self.deep_book 349 | .get_order(&mut ptb, pool_key, order_id) 350 | .await?; 351 | 352 | match self.client.dev_inspect_transaction(self.address, ptb).await { 353 | Ok(res) => { 354 | let res = res 355 | .first() 356 | .ok_or_else(|| anyhow::anyhow!("Failed to get first result"))?; 357 | let order = bcs::from_bytes::(&res.0)?; 358 | Ok(Some(order)) 359 | } 360 | Err(e) => Err(e), 361 | } 362 | } 363 | 364 | /// Get the order information for a specific order in a pool, with normalized price 365 | /// 366 | /// @param pool_key - The key of the pool 367 | /// @param order_id - The order ID 368 | pub async fn get_order_normalized( 369 | &self, 370 | pool_key: &str, 371 | order_id: u128, 372 | ) -> anyhow::Result> { 373 | let order = match self.get_order(pool_key, order_id).await? { 374 | Some(order) => order, 375 | None => return Ok(None), 376 | }; 377 | 378 | let pool = self.config.get_pool(pool_key)?; 379 | let base_coin = self.config.get_coin(&pool.base_coin)?; 380 | let quote_coin = self.config.get_coin(&pool.quote_coin)?; 381 | 382 | let (is_bid, raw_price, _order_id) = self.decode_order_id(order.order_id)?; 383 | let normalized_price = (raw_price as f64 * base_coin.scalar as f64) 384 | / (quote_coin.scalar as f64 * FLOAT_SCALAR as f64); 385 | 386 | Ok(Some(NormalizedOrder { 387 | balance_manager_id: order.balance_manager_id, 388 | order_id: order.order_id, 389 | client_order_id: order.client_order_id, 390 | quantity: format!("{:.9}", order.quantity as f64 / base_coin.scalar as f64), 391 | filled_quantity: format!( 392 | "{:.9}", 393 | order.filled_quantity as f64 / base_coin.scalar as f64 394 | ), 395 | fee_is_deep: order.fee_is_deep, 396 | order_deep_price: NormalizedOrderDeepPrice { 397 | asset_is_base: order.order_deep_price.asset_is_base, 398 | deep_per_asset: format!( 399 | "{:.9}", 400 | order.order_deep_price.deep_per_asset as f64 / DEEP_SCALAR as f64 401 | ), 402 | }, 403 | epoch: order.epoch, 404 | status: order.status, 405 | expire_timestamp: order.expire_timestamp, 406 | is_bid, 407 | normalized_price: format!("{:.9}", normalized_price), 408 | })) 409 | } 410 | 411 | /// Get multiple orders from a pool 412 | /// 413 | /// @param pool_key - The key of the pool 414 | /// @param order_ids - List of order IDs to retrieve 415 | pub async fn get_orders( 416 | &self, 417 | pool_key: &str, 418 | order_ids: Vec, 419 | ) -> anyhow::Result>> { 420 | let mut ptb = ProgrammableTransactionBuilder::new(); 421 | self.deep_book 422 | .get_orders(&mut ptb, pool_key, order_ids) 423 | .await?; 424 | 425 | match self.client.dev_inspect_transaction(self.address, ptb).await { 426 | Ok(res) => { 427 | let res = res 428 | .first() 429 | .ok_or_else(|| anyhow::anyhow!("Failed to get first result"))?; 430 | let orders = bcs::from_bytes::>(&res.0)?; 431 | Ok(Some(orders)) 432 | } 433 | Err(e) => Err(e), 434 | } 435 | } 436 | 437 | /// Get level 2 order book specifying range of price 438 | /// 439 | /// @param pool_key - Key of the pool 440 | /// @param price_low - Lower bound of the price range 441 | /// @param price_high - Upper bound of the price range 442 | /// @param is_bid - Whether to get bid or ask orders 443 | pub async fn get_level2_range( 444 | &self, 445 | pool_key: &str, 446 | price_low: f64, 447 | price_high: f64, 448 | is_bid: bool, 449 | ) -> anyhow::Result { 450 | let mut ptb = ProgrammableTransactionBuilder::new(); 451 | let pool = self.config.get_pool(pool_key)?; 452 | let base_coin = self.config.get_coin(&pool.base_coin)?; 453 | let quote_coin = self.config.get_coin(&pool.quote_coin)?; 454 | 455 | self.deep_book 456 | .get_level2_range(&mut ptb, pool_key, price_low, price_high, is_bid) 457 | .await?; 458 | 459 | match self.client.dev_inspect_transaction(self.address, ptb).await { 460 | Ok(mut res) => { 461 | let prices = res 462 | .pop() 463 | .ok_or_else(|| anyhow::anyhow!("Failed to get first result"))?; 464 | let prices = bcs::from_bytes::>(&prices.0)?; 465 | 466 | let quantities = res 467 | .pop() 468 | .ok_or_else(|| anyhow::anyhow!("Failed to get first result"))?; 469 | let quantities = bcs::from_bytes::>(&quantities.0)?; 470 | 471 | Ok(Level2Range { 472 | prices: prices 473 | .into_iter() 474 | .map(|price| { 475 | ((price as f64 * base_coin.scalar as f64) 476 | / (FLOAT_SCALAR as f64 * quote_coin.scalar as f64) 477 | * 1e9) 478 | .round() 479 | / 1e9 480 | }) 481 | .collect(), 482 | quantities: quantities 483 | .into_iter() 484 | .map(|qty| ((qty as f64 / base_coin.scalar as f64) * 1e9).round() / 1e9) 485 | .collect(), 486 | }) 487 | } 488 | Err(e) => Err(e), 489 | } 490 | } 491 | 492 | /// Get level 2 order book ticks from mid-price for a pool 493 | /// 494 | /// @param pool_key - Key of the pool 495 | /// @param ticks - Number of ticks from mid-price 496 | pub async fn get_level2_ticks_from_mid( 497 | &self, 498 | pool_key: &str, 499 | ticks: u64, 500 | ) -> anyhow::Result { 501 | let mut ptb = ProgrammableTransactionBuilder::new(); 502 | let pool = self.config.get_pool(pool_key)?; 503 | let base_coin = self.config.get_coin(&pool.base_coin)?; 504 | let quote_coin = self.config.get_coin(&pool.quote_coin)?; 505 | 506 | self.deep_book 507 | .get_level2_ticks_from_mid(&mut ptb, pool_key, ticks) 508 | .await?; 509 | 510 | match self.client.dev_inspect_transaction(self.address, ptb).await { 511 | Ok(mut res) => { 512 | let bid_prices = res 513 | .pop() 514 | .ok_or_else(|| anyhow::anyhow!("Failed to get first result"))?; 515 | let bid_prices = bcs::from_bytes::>(&bid_prices.0)?; 516 | 517 | let bid_quantities = res 518 | .pop() 519 | .ok_or_else(|| anyhow::anyhow!("Failed to get first result"))?; 520 | let bid_quantities = bcs::from_bytes::>(&bid_quantities.0)?; 521 | 522 | let ask_prices = res 523 | .pop() 524 | .ok_or_else(|| anyhow::anyhow!("Failed to get first result"))?; 525 | let ask_prices = bcs::from_bytes::>(&ask_prices.0)?; 526 | 527 | let ask_quantities = res 528 | .pop() 529 | .ok_or_else(|| anyhow::anyhow!("Failed to get first result"))?; 530 | let ask_quantities = bcs::from_bytes::>(&ask_quantities.0)?; 531 | 532 | Ok(Level2TicksFromMid { 533 | bid_prices: bid_prices 534 | .into_iter() 535 | .map(|price| { 536 | ((price as f64 * base_coin.scalar as f64) 537 | / (FLOAT_SCALAR as f64 * quote_coin.scalar as f64) 538 | * 1e9) 539 | .round() 540 | / 1e9 541 | }) 542 | .collect(), 543 | bid_quantities: bid_quantities 544 | .into_iter() 545 | .map(|qty| ((qty as f64 / base_coin.scalar as f64) * 1e9).round() / 1e9) 546 | .collect(), 547 | ask_prices: ask_prices 548 | .into_iter() 549 | .map(|price| { 550 | ((price as f64 * base_coin.scalar as f64) 551 | / (FLOAT_SCALAR as f64 * quote_coin.scalar as f64) 552 | * 1e9) 553 | .round() 554 | / 1e9 555 | }) 556 | .collect(), 557 | ask_quantities: ask_quantities 558 | .into_iter() 559 | .map(|qty| ((qty as f64 / base_coin.scalar as f64) * 1e9).round() / 1e9) 560 | .collect(), 561 | }) 562 | } 563 | Err(e) => Err(e), 564 | } 565 | } 566 | 567 | /// Get the vault balances for a pool 568 | /// 569 | /// @param pool_key - Key of the pool 570 | pub async fn vault_balances(&self, pool_key: &str) -> anyhow::Result { 571 | let mut ptb = ProgrammableTransactionBuilder::new(); 572 | let pool = self.config.get_pool(pool_key)?; 573 | let base_scalar = self.config.get_coin(&pool.base_coin)?.scalar; 574 | let quote_scalar = self.config.get_coin(&pool.quote_coin)?.scalar; 575 | 576 | self.deep_book.vault_balances(&mut ptb, pool_key).await?; 577 | 578 | match self.client.dev_inspect_transaction(self.address, ptb).await { 579 | Ok(mut res) => { 580 | let base_in_vault = res 581 | .pop() 582 | .ok_or_else(|| anyhow::anyhow!("Failed to get first result"))?; 583 | let base_in_vault = bcs::from_bytes::(&base_in_vault.0)?; 584 | 585 | let quote_in_vault = res 586 | .pop() 587 | .ok_or_else(|| anyhow::anyhow!("Failed to get first result"))?; 588 | let quote_in_vault = bcs::from_bytes::("e_in_vault.0)?; 589 | 590 | let deep_in_vault = res 591 | .pop() 592 | .ok_or_else(|| anyhow::anyhow!("Failed to get first result"))?; 593 | let deep_in_vault = bcs::from_bytes::(&deep_in_vault.0)?; 594 | 595 | Ok(VaultBalances { 596 | base: ((base_in_vault as f64 / base_scalar as f64) * 1e9).round() / 1e9, 597 | quote: ((quote_in_vault as f64 / quote_scalar as f64) * 1e9).round() / 1e9, 598 | deep: ((deep_in_vault as f64 / DEEP_SCALAR as f64) * 1e9).round() / 1e9, 599 | }) 600 | } 601 | Err(e) => Err(e), 602 | } 603 | } 604 | 605 | /// Get the pool ID by asset types 606 | /// 607 | /// @param base_type - Type of the base asset 608 | /// @param quote_type - Type of the quote asset 609 | pub async fn get_pool_id_by_assets( 610 | &self, 611 | base_type: &str, 612 | quote_type: &str, 613 | ) -> anyhow::Result { 614 | let mut ptb = ProgrammableTransactionBuilder::new(); 615 | self.deep_book 616 | .get_pool_id_by_assets(&mut ptb, base_type, quote_type) 617 | .await?; 618 | 619 | match self.client.dev_inspect_transaction(self.address, ptb).await { 620 | Ok(res) => { 621 | let res = res 622 | .first() 623 | .ok_or_else(|| anyhow::anyhow!("Failed to get first result"))?; 624 | let pool_id = bcs::from_bytes::(&res.0)?; 625 | Ok(pool_id.to_string()) 626 | } 627 | Err(e) => Err(e), 628 | } 629 | } 630 | 631 | /// Get the mid price for a pool 632 | /// 633 | /// @param pool_key - Key of the pool 634 | pub async fn mid_price(&self, pool_key: &str) -> anyhow::Result { 635 | let mut ptb = ProgrammableTransactionBuilder::new(); 636 | let pool = self.config.get_pool(pool_key)?; 637 | let base_coin = self.config.get_coin(&pool.base_coin)?; 638 | let quote_coin = self.config.get_coin(&pool.quote_coin)?; 639 | 640 | self.deep_book.mid_price(&mut ptb, pool_key).await?; 641 | 642 | match self.client.dev_inspect_transaction(self.address, ptb).await { 643 | Ok(res) => { 644 | let res = res 645 | .first() 646 | .ok_or_else(|| anyhow::anyhow!("Failed to get first result"))?; 647 | 648 | let mid_price = bcs::from_bytes::(&res.0)?; 649 | let adjusted_mid_price = (mid_price as f64 * base_coin.scalar as f64) 650 | / (quote_coin.scalar as f64 * FLOAT_SCALAR as f64); 651 | 652 | Ok((adjusted_mid_price * 1e9).round() / 1e9) 653 | } 654 | Err(e) => Err(e), 655 | } 656 | } 657 | 658 | /// Get the trade parameters for a given pool 659 | /// 660 | /// @param pool_key - Key of the pool 661 | pub async fn pool_trade_params(&self, pool_key: &str) -> anyhow::Result { 662 | let mut ptb = ProgrammableTransactionBuilder::new(); 663 | self.deep_book.pool_trade_params(&mut ptb, pool_key).await?; 664 | 665 | match self.client.dev_inspect_transaction(self.address, ptb).await { 666 | Ok(mut res) => { 667 | let taker_fee = res 668 | .pop() 669 | .ok_or_else(|| anyhow::anyhow!("Failed to get first result"))?; 670 | let taker_fee = bcs::from_bytes::(&taker_fee.0)?; 671 | 672 | let maker_fee = res 673 | .pop() 674 | .ok_or_else(|| anyhow::anyhow!("Failed to get first result"))?; 675 | let maker_fee = bcs::from_bytes::(&maker_fee.0)?; 676 | 677 | let stake_required = res 678 | .pop() 679 | .ok_or_else(|| anyhow::anyhow!("Failed to get first result"))?; 680 | let stake_required = bcs::from_bytes::(&stake_required.0)?; 681 | 682 | Ok(PoolTradeParams { 683 | taker_fee: (taker_fee as f64 / FLOAT_SCALAR as f64 * 1e9).round() / 1e9, 684 | maker_fee: (maker_fee as f64 / FLOAT_SCALAR as f64 * 1e9).round() / 1e9, 685 | stake_required: (stake_required as f64 / DEEP_SCALAR as f64 * 1e9).round() 686 | / 1e9, 687 | }) 688 | } 689 | Err(e) => Err(e), 690 | } 691 | } 692 | 693 | /// Get the trade parameters for a given pool, including tick size, lot size, and min size 694 | /// 695 | /// @param pool_key - Key of the pool 696 | pub async fn pool_book_params(&self, pool_key: &str) -> anyhow::Result { 697 | let mut ptb = ProgrammableTransactionBuilder::new(); 698 | let pool = self.config.get_pool(pool_key)?; 699 | let base_scalar = self.config.get_coin(&pool.base_coin)?.scalar; 700 | let quote_scalar = self.config.get_coin(&pool.quote_coin)?.scalar; 701 | 702 | self.deep_book.pool_book_params(&mut ptb, pool_key).await?; 703 | 704 | match self.client.dev_inspect_transaction(self.address, ptb).await { 705 | Ok(mut res) => { 706 | let tick_size = res 707 | .pop() 708 | .ok_or_else(|| anyhow::anyhow!("Failed to get first result"))?; 709 | let tick_size = bcs::from_bytes::(&tick_size.0)?; 710 | 711 | let lot_size = res 712 | .pop() 713 | .ok_or_else(|| anyhow::anyhow!("Failed to get first result"))?; 714 | let lot_size = bcs::from_bytes::(&lot_size.0)?; 715 | 716 | let min_size = res 717 | .pop() 718 | .ok_or_else(|| anyhow::anyhow!("Failed to get first result"))?; 719 | let min_size = bcs::from_bytes::(&min_size.0)?; 720 | 721 | Ok(PoolBookParams { 722 | tick_size: (tick_size as f64 * base_scalar as f64) 723 | / (quote_scalar as f64 * FLOAT_SCALAR as f64), 724 | lot_size: lot_size as f64 / base_scalar as f64, 725 | min_size: min_size as f64 / base_scalar as f64, 726 | }) 727 | } 728 | Err(e) => Err(e), 729 | } 730 | } 731 | 732 | /// Get the account information for a given pool and balance manager 733 | /// 734 | /// @param pool_key - Key of the pool 735 | /// @param manager_key - The key of the BalanceManager 736 | pub async fn account(&self, pool_key: &str, manager_key: &str) -> anyhow::Result { 737 | let mut ptb = ProgrammableTransactionBuilder::new(); 738 | let pool = self.config.get_pool(pool_key)?; 739 | let base_scalar = self.config.get_coin(&pool.base_coin)?.scalar; 740 | let quote_scalar = self.config.get_coin(&pool.quote_coin)?.scalar; 741 | 742 | self.deep_book 743 | .account(&mut ptb, pool_key, manager_key) 744 | .await?; 745 | 746 | match self.client.dev_inspect_transaction(self.address, ptb).await { 747 | Ok(res) => { 748 | let res = res 749 | .first() 750 | .ok_or_else(|| anyhow::anyhow!("Failed to get first result"))?; 751 | let raw_account = bcs::from_bytes::(&res.0)?; 752 | Ok(Account { 753 | epoch: raw_account.epoch, 754 | open_orders: raw_account.open_orders, 755 | taker_volume: raw_account.taker_volume as f64 / base_scalar as f64, 756 | maker_volume: raw_account.maker_volume as f64 / base_scalar as f64, 757 | active_stake: raw_account.active_stake as f64 / DEEP_SCALAR as f64, 758 | inactive_stake: raw_account.inactive_stake as f64 / DEEP_SCALAR as f64, 759 | created_proposal: raw_account.created_proposal, 760 | voted_proposal: raw_account.voted_proposal, 761 | unclaimed_rebates: Balances { 762 | base: raw_account.unclaimed_rebates.base as f64 / base_scalar as f64, 763 | quote: raw_account.unclaimed_rebates.quote as f64 / quote_scalar as f64, 764 | deep: raw_account.unclaimed_rebates.deep as f64 / DEEP_SCALAR as f64, 765 | }, 766 | settled_balances: Balances { 767 | base: raw_account.settled_balances.base as f64 / base_scalar as f64, 768 | quote: raw_account.settled_balances.quote as f64 / quote_scalar as f64, 769 | deep: raw_account.settled_balances.deep as f64 / DEEP_SCALAR as f64, 770 | }, 771 | owed_balances: Balances { 772 | base: raw_account.owed_balances.base as f64 / base_scalar as f64, 773 | quote: raw_account.owed_balances.quote as f64 / quote_scalar as f64, 774 | deep: raw_account.owed_balances.deep as f64 / DEEP_SCALAR as f64, 775 | }, 776 | }) 777 | } 778 | Err(e) => Err(e), 779 | } 780 | } 781 | 782 | /// Get the locked balances for a pool and balance manager 783 | /// 784 | /// @param pool_key - Key of the pool 785 | /// @param balance_manager_key - The key of the BalanceManager 786 | pub async fn locked_balance( 787 | &self, 788 | pool_key: &str, 789 | balance_manager_key: &str, 790 | ) -> anyhow::Result { 791 | let mut ptb = ProgrammableTransactionBuilder::new(); 792 | let pool = self.config.get_pool(pool_key)?; 793 | let base_scalar = self.config.get_coin(&pool.base_coin)?.scalar; 794 | let quote_scalar = self.config.get_coin(&pool.quote_coin)?.scalar; 795 | 796 | self.deep_book 797 | .locked_balance(&mut ptb, pool_key, balance_manager_key) 798 | .await?; 799 | 800 | match self.client.dev_inspect_transaction(self.address, ptb).await { 801 | Ok(mut res) => { 802 | let base_locked = res 803 | .pop() 804 | .ok_or_else(|| anyhow::anyhow!("Failed to get first result"))?; 805 | let base_locked = bcs::from_bytes::(&base_locked.0)?; 806 | 807 | let quote_locked = res 808 | .pop() 809 | .ok_or_else(|| anyhow::anyhow!("Failed to get first result"))?; 810 | let quote_locked = bcs::from_bytes::("e_locked.0)?; 811 | 812 | let deep_locked = res 813 | .pop() 814 | .ok_or_else(|| anyhow::anyhow!("Failed to get first result"))?; 815 | let deep_locked = bcs::from_bytes::(&deep_locked.0)?; 816 | 817 | Ok(Balances { 818 | base: ((base_locked as f64 / base_scalar as f64) * 1e9).round() / 1e9, 819 | quote: ((quote_locked as f64 / quote_scalar as f64) * 1e9).round() / 1e9, 820 | deep: ((deep_locked as f64 / DEEP_SCALAR as f64) * 1e9).round() / 1e9, 821 | }) 822 | } 823 | Err(e) => Err(e), 824 | } 825 | } 826 | 827 | /// Get the DEEP price conversion for a pool 828 | /// 829 | /// @param pool_key - Key of the pool 830 | pub async fn get_pool_deep_price(&self, pool_key: &str) -> anyhow::Result { 831 | let mut ptb = ProgrammableTransactionBuilder::new(); 832 | let pool = self.config.get_pool(pool_key)?; 833 | let base_coin = self.config.get_coin(&pool.base_coin)?; 834 | let quote_coin = self.config.get_coin(&pool.quote_coin)?; 835 | let deep_coin = self.config.get_coin("DEEP")?; 836 | 837 | self.deep_book 838 | .get_pool_deep_price(&mut ptb, pool_key) 839 | .await?; 840 | 841 | match self.client.dev_inspect_transaction(self.address, ptb).await { 842 | Ok(res) => { 843 | let res = res 844 | .first() 845 | .ok_or_else(|| anyhow::anyhow!("Failed to get first result"))?; 846 | let pool_deep_price = bcs::from_bytes::(&res.0)?; 847 | let deep_per_asset = 848 | (pool_deep_price.deep_per_asset as f64 / FLOAT_SCALAR as f64) * 1e9; 849 | 850 | Ok(PoolDeepPrice { 851 | asset_is_base: pool_deep_price.asset_is_base, 852 | deep_per_base: if pool_deep_price.asset_is_base { 853 | Some( 854 | (deep_per_asset * base_coin.scalar as f64 / deep_coin.scalar as f64) 855 | .round() 856 | / 1e9, 857 | ) 858 | } else { 859 | None 860 | }, 861 | deep_per_quote: if !pool_deep_price.asset_is_base { 862 | Some( 863 | (deep_per_asset * quote_coin.scalar as f64 / deep_coin.scalar as f64) 864 | .round() 865 | / 1e9, 866 | ) 867 | } else { 868 | None 869 | }, 870 | }) 871 | } 872 | Err(e) => Err(e), 873 | } 874 | } 875 | 876 | async fn get_quote_quantity_out_inner( 877 | &self, 878 | ptb: ProgrammableTransactionBuilder, 879 | pool_key: &str, 880 | base_quantity: f64, 881 | ) -> anyhow::Result { 882 | let pool = self.config.get_pool(pool_key)?; 883 | let base_scalar = self.config.get_coin(&pool.base_coin)?.scalar; 884 | let quote_scalar = self.config.get_coin(&pool.quote_coin)?.scalar; 885 | 886 | match self.client.dev_inspect_transaction(self.address, ptb).await { 887 | Ok(mut res) => { 888 | let base_out = res 889 | .pop() 890 | .ok_or_else(|| anyhow::anyhow!("Failed to get last result"))?; 891 | let base_out = bcs::from_bytes::(&base_out.0)?; 892 | 893 | let quote_out = res 894 | .pop() 895 | .ok_or_else(|| anyhow::anyhow!("Failed to get last result"))?; 896 | let quote_out = bcs::from_bytes::("e_out.0)?; 897 | 898 | let deep_required = res 899 | .pop() 900 | .ok_or_else(|| anyhow::anyhow!("Failed to get last result"))?; 901 | let deep_required = bcs::from_bytes::(&deep_required.0)?; 902 | 903 | Ok(QuoteQuantityOut { 904 | base_quantity, 905 | base_out: (base_out as f64 / base_scalar as f64 * 1e9).round() / 1e9, 906 | quote_out: (quote_out as f64 / quote_scalar as f64 * 1e9).round() / 1e9, 907 | deep_required: (deep_required as f64 / DEEP_SCALAR as f64 * 1e9).round() / 1e9, 908 | }) 909 | } 910 | Err(e) => Err(e), 911 | } 912 | } 913 | 914 | /// Decode the order ID to get bid/ask status, price, and order ID 915 | /// 916 | /// @param encoded_order_id - Encoded order ID 917 | pub fn decode_order_id(&self, encoded_order_id: u128) -> anyhow::Result<(bool, u64, u64)> { 918 | let is_bid = (encoded_order_id >> 127) == 0; 919 | let price = ((encoded_order_id >> 64) & ((1 << 63) - 1)) as u64; 920 | let order_id = (encoded_order_id & ((1 << 64) - 1)) as u64; 921 | 922 | Ok((is_bid, price, order_id)) 923 | } 924 | } 925 | -------------------------------------------------------------------------------- /src/transactions/deepbook.rs: -------------------------------------------------------------------------------- 1 | use std::str::FromStr; 2 | use sui_sdk::{ 3 | rpc_types::Coin, 4 | types::{ 5 | base_types::{ObjectID, SuiAddress}, 6 | programmable_transaction_builder::ProgrammableTransactionBuilder, 7 | transaction::Argument, 8 | Identifier, TypeTag, SUI_CLOCK_OBJECT_ID, 9 | }, 10 | SuiClient, 11 | }; 12 | 13 | use crate::utils::{ 14 | config::{DeepBookConfig, DEEP_SCALAR, FLOAT_SCALAR, MAX_TIMESTAMP}, 15 | types::{ 16 | OrderType, PlaceLimitOrderParams, PlaceMarketOrderParams, SelfMatchingOptions, SwapParams, 17 | }, 18 | }; 19 | 20 | use super::balance_manager::BalanceManagerContract; 21 | 22 | use crate::DataReader; 23 | 24 | /// DeepBookContract struct for managing DeepBook operations 25 | pub struct DeepBookContract { 26 | client: SuiClient, 27 | config: DeepBookConfig, 28 | balance_manager_contract: BalanceManagerContract, 29 | } 30 | 31 | impl DeepBookContract { 32 | /// Creates a new DeepBookContract instance 33 | /// 34 | /// @param client - The SuiClient instance 35 | /// @param config - The DeepBookConfig instance 36 | /// @param balance_manager_contract - The BalanceManagerContract instance 37 | pub fn new( 38 | client: SuiClient, 39 | config: DeepBookConfig, 40 | balance_manager_contract: BalanceManagerContract, 41 | ) -> Self { 42 | Self { 43 | client, 44 | config, 45 | balance_manager_contract, 46 | } 47 | } 48 | 49 | /// Place a limit order 50 | /// 51 | /// @param ptb - ProgrammableTransactionBuilder instance 52 | /// @param params - The PlaceLimitOrderParams instance 53 | /// @returns The place limit order call 54 | pub async fn place_limit_order( 55 | &self, 56 | ptb: &mut ProgrammableTransactionBuilder, 57 | params: PlaceLimitOrderParams, 58 | ) -> anyhow::Result { 59 | let pool = self.config.get_pool(¶ms.pool_key)?; 60 | let balance_manager = self 61 | .config 62 | .get_balance_manager(¶ms.balance_manager_key)?; 63 | let base_coin = self.config.get_coin(&pool.base_coin)?; 64 | let quote_coin = self.config.get_coin(&pool.quote_coin)?; 65 | 66 | let input_price = ((params.price * FLOAT_SCALAR as f64 * quote_coin.scalar as f64) 67 | / base_coin.scalar as f64) 68 | .round() as u64; 69 | let input_quantity = (params.quantity * base_coin.scalar as f64).round() as u64; 70 | 71 | let trade_proof = self 72 | .balance_manager_contract 73 | .generate_proof(ptb, ¶ms.balance_manager_key) 74 | .await?; 75 | 76 | let pool_id = ObjectID::from_hex_literal(&pool.address)?; 77 | let balance_manager_id = ObjectID::from_hex_literal(&balance_manager.address)?; 78 | let expiration = params.expiration.unwrap_or(MAX_TIMESTAMP); 79 | let order_type = params.order_type.unwrap_or(OrderType::NoRestriction); 80 | let self_matching_option = params 81 | .self_matching_option 82 | .unwrap_or(SelfMatchingOptions::SelfMatchingAllowed); 83 | let pay_with_deep = params.pay_with_deep.unwrap_or(true); 84 | 85 | let base_coin_tag = TypeTag::from_str(&base_coin.type_name)?; 86 | let quote_coin_tag = TypeTag::from_str("e_coin.type_name)?; 87 | 88 | let arguments = vec![ 89 | ptb.obj(self.client.share_object_mutable(pool_id).await?)?, 90 | ptb.obj(self.client.share_object_mutable(balance_manager_id).await?)?, 91 | trade_proof, 92 | ptb.pure(params.client_order_id)?, 93 | ptb.pure(order_type as u8)?, 94 | ptb.pure(self_matching_option as u8)?, 95 | ptb.pure(input_price)?, 96 | ptb.pure(input_quantity)?, 97 | ptb.pure(params.is_bid)?, 98 | ptb.pure(pay_with_deep)?, 99 | ptb.pure(expiration)?, 100 | ptb.obj(self.client.share_object(SUI_CLOCK_OBJECT_ID).await?)?, 101 | ]; 102 | 103 | Ok(ptb.programmable_move_call( 104 | ObjectID::from_hex_literal(self.config.deepbook_package_id())?, 105 | Identifier::new("pool")?, 106 | Identifier::new("place_limit_order")?, 107 | vec![base_coin_tag, quote_coin_tag], 108 | arguments, 109 | )) 110 | } 111 | 112 | /// Place a market order 113 | /// 114 | /// @param ptb - ProgrammableTransactionBuilder instance 115 | /// @param params - The PlaceMarketOrderParams instance 116 | /// @returns The place market order call 117 | pub async fn place_market_order( 118 | &self, 119 | ptb: &mut ProgrammableTransactionBuilder, 120 | params: PlaceMarketOrderParams, 121 | ) -> anyhow::Result { 122 | let pool = self.config.get_pool(¶ms.pool_key)?; 123 | let balance_manager = self 124 | .config 125 | .get_balance_manager(¶ms.balance_manager_key)?; 126 | let base_coin = self.config.get_coin(&pool.base_coin)?; 127 | let quote_coin = self.config.get_coin(&pool.quote_coin)?; 128 | 129 | let input_quantity = (params.quantity * base_coin.scalar as f64).round() as u64; 130 | let trade_proof = self 131 | .balance_manager_contract 132 | .generate_proof(ptb, ¶ms.balance_manager_key) 133 | .await?; 134 | 135 | let pool_id = ObjectID::from_hex_literal(&pool.address)?; 136 | let balance_manager_id = ObjectID::from_hex_literal(&balance_manager.address)?; 137 | let self_matching_option = params 138 | .self_matching_option 139 | .unwrap_or(SelfMatchingOptions::SelfMatchingAllowed); 140 | let pay_with_deep = params.pay_with_deep.unwrap_or(true); 141 | 142 | let base_coin_tag = TypeTag::from_str(&base_coin.type_name)?; 143 | let quote_coin_tag = TypeTag::from_str("e_coin.type_name)?; 144 | 145 | let arguments = vec![ 146 | ptb.obj(self.client.share_object_mutable(pool_id).await?)?, 147 | ptb.obj(self.client.share_object_mutable(balance_manager_id).await?)?, 148 | trade_proof, 149 | ptb.pure(params.client_order_id)?, 150 | ptb.pure(self_matching_option as u8)?, 151 | ptb.pure(input_quantity)?, 152 | ptb.pure(params.is_bid)?, 153 | ptb.pure(pay_with_deep)?, 154 | ptb.obj(self.client.share_object(SUI_CLOCK_OBJECT_ID).await?)?, 155 | ]; 156 | 157 | Ok(ptb.programmable_move_call( 158 | ObjectID::from_hex_literal(self.config.deepbook_package_id())?, 159 | Identifier::new("pool")?, 160 | Identifier::new("place_market_order")?, 161 | vec![base_coin_tag, quote_coin_tag], 162 | arguments, 163 | )) 164 | } 165 | 166 | /// Modify an existing order 167 | /// 168 | /// @param ptb - ProgrammableTransactionBuilder instance 169 | /// @param pool_key - The key to identify the pool 170 | /// @param balance_manager_key - The key to identify the BalanceManager 171 | /// @param order_id - The ID of the order to modify 172 | /// @param new_quantity - The new quantity to set for the order 173 | /// @returns The modify order call 174 | pub async fn modify_order( 175 | &self, 176 | ptb: &mut ProgrammableTransactionBuilder, 177 | pool_key: &str, 178 | balance_manager_key: &str, 179 | order_id: u128, 180 | new_quantity: f64, 181 | ) -> anyhow::Result { 182 | let pool = self.config.get_pool(pool_key)?; 183 | let balance_manager = self.config.get_balance_manager(balance_manager_key)?; 184 | let base_coin = self.config.get_coin(&pool.base_coin)?; 185 | let quote_coin = self.config.get_coin(&pool.quote_coin)?; 186 | 187 | let input_quantity = (new_quantity * base_coin.scalar as f64).round() as u64; 188 | let trade_proof = self 189 | .balance_manager_contract 190 | .generate_proof(ptb, balance_manager_key) 191 | .await?; 192 | 193 | let pool_id = ObjectID::from_hex_literal(&pool.address)?; 194 | let balance_manager_id = ObjectID::from_hex_literal(&balance_manager.address)?; 195 | 196 | let base_coin_tag = TypeTag::from_str(&base_coin.type_name)?; 197 | let quote_coin_tag = TypeTag::from_str("e_coin.type_name)?; 198 | 199 | let arguments = vec![ 200 | ptb.obj(self.client.share_object_mutable(pool_id).await?)?, 201 | ptb.obj(self.client.share_object_mutable(balance_manager_id).await?)?, 202 | trade_proof, 203 | ptb.pure(order_id)?, 204 | ptb.pure(input_quantity)?, 205 | ptb.obj(self.client.share_object(SUI_CLOCK_OBJECT_ID).await?)?, 206 | ]; 207 | 208 | Ok(ptb.programmable_move_call( 209 | ObjectID::from_hex_literal(self.config.deepbook_package_id())?, 210 | Identifier::new("pool")?, 211 | Identifier::new("modify_order")?, 212 | vec![base_coin_tag, quote_coin_tag], 213 | arguments, 214 | )) 215 | } 216 | 217 | /// Cancel an existing order 218 | /// 219 | /// @param ptb - ProgrammableTransactionBuilder instance 220 | /// @param pool_key - The key to identify the pool 221 | /// @param balance_manager_key - The key to identify the BalanceManager 222 | /// @param order_id - The ID of the order to cancel 223 | /// @returns The cancel order call 224 | pub async fn cancel_order( 225 | &self, 226 | ptb: &mut ProgrammableTransactionBuilder, 227 | pool_key: &str, 228 | balance_manager_key: &str, 229 | order_id: u128, 230 | ) -> anyhow::Result { 231 | let pool = self.config.get_pool(pool_key)?; 232 | let balance_manager = self.config.get_balance_manager(balance_manager_key)?; 233 | let base_coin = self.config.get_coin(&pool.base_coin)?; 234 | let quote_coin = self.config.get_coin(&pool.quote_coin)?; 235 | 236 | let trade_proof = self 237 | .balance_manager_contract 238 | .generate_proof(ptb, balance_manager_key) 239 | .await?; 240 | 241 | let pool_id = ObjectID::from_hex_literal(&pool.address)?; 242 | let balance_manager_id = ObjectID::from_hex_literal(&balance_manager.address)?; 243 | 244 | let base_coin_tag = TypeTag::from_str(&base_coin.type_name)?; 245 | let quote_coin_tag = TypeTag::from_str("e_coin.type_name)?; 246 | 247 | let arguments = vec![ 248 | ptb.obj(self.client.share_object_mutable(pool_id).await?)?, 249 | ptb.obj(self.client.share_object_mutable(balance_manager_id).await?)?, 250 | trade_proof, 251 | ptb.pure(order_id)?, 252 | ptb.obj(self.client.share_object(SUI_CLOCK_OBJECT_ID).await?)?, 253 | ]; 254 | 255 | Ok(ptb.programmable_move_call( 256 | ObjectID::from_hex_literal(self.config.deepbook_package_id())?, 257 | Identifier::new("pool")?, 258 | Identifier::new("cancel_order")?, 259 | vec![base_coin_tag, quote_coin_tag], 260 | arguments, 261 | )) 262 | } 263 | 264 | /// Cancel all open orders for a balance manager 265 | /// 266 | /// @param ptb - ProgrammableTransactionBuilder instance 267 | /// @param pool_key - The key to identify the pool 268 | /// @param balance_manager_key - The key to identify the BalanceManager 269 | /// @returns The cancel all orders call 270 | pub async fn cancel_all_orders( 271 | &self, 272 | ptb: &mut ProgrammableTransactionBuilder, 273 | pool_key: &str, 274 | balance_manager_key: &str, 275 | ) -> anyhow::Result { 276 | let pool = self.config.get_pool(pool_key)?; 277 | let balance_manager = self.config.get_balance_manager(balance_manager_key)?; 278 | let base_coin = self.config.get_coin(&pool.base_coin)?; 279 | let quote_coin = self.config.get_coin(&pool.quote_coin)?; 280 | 281 | let trade_proof = self 282 | .balance_manager_contract 283 | .generate_proof(ptb, balance_manager_key) 284 | .await?; 285 | 286 | let pool_id = ObjectID::from_hex_literal(&pool.address)?; 287 | let balance_manager_id = ObjectID::from_hex_literal(&balance_manager.address)?; 288 | 289 | let base_coin_tag = TypeTag::from_str(&base_coin.type_name)?; 290 | let quote_coin_tag = TypeTag::from_str("e_coin.type_name)?; 291 | 292 | let arguments = vec![ 293 | ptb.obj(self.client.share_object_mutable(pool_id).await?)?, 294 | ptb.obj(self.client.share_object_mutable(balance_manager_id).await?)?, 295 | trade_proof, 296 | ptb.obj(self.client.share_object(SUI_CLOCK_OBJECT_ID).await?)?, 297 | ]; 298 | 299 | Ok(ptb.programmable_move_call( 300 | ObjectID::from_hex_literal(self.config.deepbook_package_id())?, 301 | Identifier::new("pool")?, 302 | Identifier::new("cancel_all_orders")?, 303 | vec![base_coin_tag, quote_coin_tag], 304 | arguments, 305 | )) 306 | } 307 | 308 | /// Withdraw settled amounts for a balance manager 309 | /// 310 | /// @param ptb - ProgrammableTransactionBuilder instance 311 | /// @param pool_key - The key to identify the pool 312 | /// @param balance_manager_key - The key to identify the BalanceManager 313 | /// @returns The withdraw settled amounts call 314 | pub async fn withdraw_settled_amounts( 315 | &self, 316 | ptb: &mut ProgrammableTransactionBuilder, 317 | pool_key: &str, 318 | balance_manager_key: &str, 319 | ) -> anyhow::Result { 320 | let pool = self.config.get_pool(pool_key)?; 321 | let balance_manager = self.config.get_balance_manager(balance_manager_key)?; 322 | let base_coin = self.config.get_coin(&pool.base_coin)?; 323 | let quote_coin = self.config.get_coin(&pool.quote_coin)?; 324 | 325 | let trade_proof = self 326 | .balance_manager_contract 327 | .generate_proof(ptb, balance_manager_key) 328 | .await?; 329 | 330 | let pool_id = ObjectID::from_hex_literal(&pool.address)?; 331 | let balance_manager_id = ObjectID::from_hex_literal(&balance_manager.address)?; 332 | 333 | let base_coin_tag = TypeTag::from_str(&base_coin.type_name)?; 334 | let quote_coin_tag = TypeTag::from_str("e_coin.type_name)?; 335 | 336 | let arguments = vec![ 337 | ptb.obj(self.client.share_object(pool_id).await?)?, 338 | ptb.obj(self.client.share_object(balance_manager_id).await?)?, 339 | trade_proof, 340 | ]; 341 | 342 | Ok(ptb.programmable_move_call( 343 | ObjectID::from_hex_literal(self.config.deepbook_package_id())?, 344 | Identifier::new("pool")?, 345 | Identifier::new("withdraw_settled_amounts")?, 346 | vec![base_coin_tag, quote_coin_tag], 347 | arguments, 348 | )) 349 | } 350 | 351 | /// Add a deep price point for a target pool using a reference pool 352 | /// 353 | /// @param ptb - ProgrammableTransactionBuilder instance 354 | /// @param target_pool_key - The key to identify the target pool 355 | /// @param reference_pool_key - The key to identify the reference pool 356 | /// @returns The add deep price point call 357 | pub async fn add_deep_price_point( 358 | &self, 359 | ptb: &mut ProgrammableTransactionBuilder, 360 | target_pool_key: &str, 361 | reference_pool_key: &str, 362 | ) -> anyhow::Result { 363 | let target_pool = self.config.get_pool(target_pool_key)?; 364 | let reference_pool = self.config.get_pool(reference_pool_key)?; 365 | 366 | let target_base_coin = self.config.get_coin(&target_pool.base_coin)?; 367 | let target_quote_coin = self.config.get_coin(&target_pool.quote_coin)?; 368 | let reference_base_coin = self.config.get_coin(&reference_pool.base_coin)?; 369 | let reference_quote_coin = self.config.get_coin(&reference_pool.quote_coin)?; 370 | 371 | let target_pool_id = ObjectID::from_hex_literal(&target_pool.address)?; 372 | let reference_pool_id = ObjectID::from_hex_literal(&reference_pool.address)?; 373 | 374 | let arguments = vec![ 375 | ptb.obj(self.client.share_object(target_pool_id).await?)?, 376 | ptb.obj(self.client.share_object(reference_pool_id).await?)?, 377 | ptb.obj(self.client.share_object(SUI_CLOCK_OBJECT_ID).await?)?, 378 | ]; 379 | 380 | Ok(ptb.programmable_move_call( 381 | ObjectID::from_hex_literal(self.config.deepbook_package_id())?, 382 | Identifier::new("pool")?, 383 | Identifier::new("add_deep_price_point")?, 384 | vec![ 385 | TypeTag::from_str(&target_base_coin.type_name)?, 386 | TypeTag::from_str(&target_quote_coin.type_name)?, 387 | TypeTag::from_str(&reference_base_coin.type_name)?, 388 | TypeTag::from_str(&reference_quote_coin.type_name)?, 389 | ], 390 | arguments, 391 | )) 392 | } 393 | 394 | /// Claim rebates for a balance manager 395 | /// 396 | /// @param ptb - ProgrammableTransactionBuilder instance 397 | /// @param pool_key - The key to identify the pool 398 | /// @param balance_manager_key - The key to identify the BalanceManager 399 | /// @returns The claim rebates call 400 | pub async fn claim_rebates( 401 | &self, 402 | ptb: &mut ProgrammableTransactionBuilder, 403 | pool_key: &str, 404 | balance_manager_key: &str, 405 | ) -> anyhow::Result { 406 | let pool = self.config.get_pool(pool_key)?; 407 | let balance_manager = self.config.get_balance_manager(balance_manager_key)?; 408 | let base_coin = self.config.get_coin(&pool.base_coin)?; 409 | let quote_coin = self.config.get_coin(&pool.quote_coin)?; 410 | 411 | let trade_proof = self 412 | .balance_manager_contract 413 | .generate_proof(ptb, balance_manager_key) 414 | .await?; 415 | 416 | let pool_id = ObjectID::from_hex_literal(&pool.address)?; 417 | let balance_manager_id = ObjectID::from_hex_literal(&balance_manager.address)?; 418 | 419 | let base_coin_tag = TypeTag::from_str(&base_coin.type_name)?; 420 | let quote_coin_tag = TypeTag::from_str("e_coin.type_name)?; 421 | 422 | let arguments = vec![ 423 | ptb.obj(self.client.share_object(pool_id).await?)?, 424 | ptb.obj(self.client.share_object(balance_manager_id).await?)?, 425 | trade_proof, 426 | ]; 427 | 428 | Ok(ptb.programmable_move_call( 429 | ObjectID::from_hex_literal(self.config.deepbook_package_id())?, 430 | Identifier::new("pool")?, 431 | Identifier::new("claim_rebates")?, 432 | vec![base_coin_tag, quote_coin_tag], 433 | arguments, 434 | )) 435 | } 436 | 437 | /// Gets an order 438 | /// 439 | /// @param ptb - ProgrammableTransactionBuilder instance 440 | /// @param pool_key - The key to identify the pool 441 | /// @param order_id - The ID of the order to get 442 | /// @returns The order 443 | pub async fn get_order( 444 | &self, 445 | ptb: &mut ProgrammableTransactionBuilder, 446 | pool_key: &str, 447 | order_id: u128, 448 | ) -> anyhow::Result { 449 | let pool = self.config.get_pool(pool_key)?; 450 | let base_coin = self.config.get_coin(&pool.base_coin)?; 451 | let quote_coin = self.config.get_coin(&pool.quote_coin)?; 452 | 453 | let pool_id = ObjectID::from_hex_literal(&pool.address)?; 454 | 455 | let base_coin_tag = TypeTag::from_str(&base_coin.type_name)?; 456 | let quote_coin_tag = TypeTag::from_str("e_coin.type_name)?; 457 | 458 | let arguments = vec![ 459 | ptb.obj(self.client.share_object(pool_id).await?)?, 460 | ptb.pure(order_id)?, 461 | ]; 462 | 463 | Ok(ptb.programmable_move_call( 464 | ObjectID::from_hex_literal(self.config.deepbook_package_id())?, 465 | Identifier::new("pool")?, 466 | Identifier::new("get_order")?, 467 | vec![base_coin_tag, quote_coin_tag], 468 | arguments, 469 | )) 470 | } 471 | 472 | /// Gets multiple orders from a specified pool 473 | /// 474 | /// @param ptb - ProgrammableTransactionBuilder instance 475 | /// @param pool_key - The key to identify the pool 476 | /// @param order_ids - Array of order IDs to retrieve 477 | /// @returns The orders 478 | pub async fn get_orders( 479 | &self, 480 | ptb: &mut ProgrammableTransactionBuilder, 481 | pool_key: &str, 482 | order_ids: Vec, 483 | ) -> anyhow::Result { 484 | let pool = self.config.get_pool(pool_key)?; 485 | let base_coin = self.config.get_coin(&pool.base_coin)?; 486 | let quote_coin = self.config.get_coin(&pool.quote_coin)?; 487 | 488 | let pool_id = ObjectID::from_hex_literal(&pool.address)?; 489 | let order_ids: Vec = order_ids 490 | .iter() 491 | .map(|id| ObjectID::from_hex_literal(id)) 492 | .collect::, _>>()?; 493 | 494 | let base_coin_tag = TypeTag::from_str(&base_coin.type_name)?; 495 | let quote_coin_tag = TypeTag::from_str("e_coin.type_name)?; 496 | 497 | let arguments = vec![ 498 | ptb.obj(self.client.share_object(pool_id).await?)?, 499 | ptb.pure(order_ids)?, 500 | ]; 501 | 502 | Ok(ptb.programmable_move_call( 503 | ObjectID::from_hex_literal(self.config.deepbook_package_id())?, 504 | Identifier::new("pool")?, 505 | Identifier::new("get_orders")?, 506 | vec![base_coin_tag, quote_coin_tag], 507 | arguments, 508 | )) 509 | } 510 | 511 | /// Burns DEEP tokens from the pool 512 | /// 513 | /// @param ptb - ProgrammableTransactionBuilder instance 514 | /// @param pool_key - The key to identify the pool 515 | /// @returns The burn deep call 516 | pub async fn burn_deep( 517 | &self, 518 | ptb: &mut ProgrammableTransactionBuilder, 519 | pool_key: &str, 520 | ) -> anyhow::Result { 521 | let pool = self.config.get_pool(pool_key)?; 522 | let base_coin = self.config.get_coin(&pool.base_coin)?; 523 | let quote_coin = self.config.get_coin(&pool.quote_coin)?; 524 | 525 | let pool_id = ObjectID::from_hex_literal(&pool.address)?; 526 | let treasury_id = ObjectID::from_hex_literal(self.config.deep_treasury_id())?; 527 | 528 | let base_coin_tag = TypeTag::from_str(&base_coin.type_name)?; 529 | let quote_coin_tag = TypeTag::from_str("e_coin.type_name)?; 530 | 531 | let arguments = vec![ 532 | ptb.obj(self.client.share_object(pool_id).await?)?, 533 | ptb.obj(self.client.share_object(treasury_id).await?)?, 534 | ]; 535 | 536 | Ok(ptb.programmable_move_call( 537 | ObjectID::from_hex_literal(self.config.deepbook_package_id())?, 538 | Identifier::new("pool")?, 539 | Identifier::new("burn_deep")?, 540 | vec![base_coin_tag, quote_coin_tag], 541 | arguments, 542 | )) 543 | } 544 | 545 | /// Gets the mid price for a pool 546 | /// 547 | /// @param ptb - ProgrammableTransactionBuilder instance 548 | /// @param pool_key - The key to identify the pool 549 | /// @returns The mid price 550 | pub async fn mid_price( 551 | &self, 552 | ptb: &mut ProgrammableTransactionBuilder, 553 | pool_key: &str, 554 | ) -> anyhow::Result { 555 | let pool = self.config.get_pool(pool_key)?; 556 | let base_coin = self.config.get_coin(&pool.base_coin)?; 557 | let quote_coin = self.config.get_coin(&pool.quote_coin)?; 558 | 559 | let pool_id = ObjectID::from_hex_literal(&pool.address)?; 560 | 561 | let base_coin_tag = TypeTag::from_str(&base_coin.type_name)?; 562 | let quote_coin_tag = TypeTag::from_str("e_coin.type_name)?; 563 | 564 | let arguments = vec![ 565 | ptb.obj(self.client.share_object(pool_id).await?)?, 566 | ptb.obj(self.client.share_object(SUI_CLOCK_OBJECT_ID).await?)?, 567 | ]; 568 | 569 | Ok(ptb.programmable_move_call( 570 | ObjectID::from_hex_literal(self.config.deepbook_package_id())?, 571 | Identifier::new("pool")?, 572 | Identifier::new("mid_price")?, 573 | vec![base_coin_tag, quote_coin_tag], 574 | arguments, 575 | )) 576 | } 577 | 578 | /// Checks if a pool is whitelisted 579 | /// 580 | /// @param ptb - ProgrammableTransactionBuilder instance 581 | /// @param pool_key - The key to identify the pool 582 | /// @returns The whitelisted status 583 | pub async fn whitelisted( 584 | &self, 585 | ptb: &mut ProgrammableTransactionBuilder, 586 | pool_key: &str, 587 | ) -> anyhow::Result { 588 | let pool = self.config.get_pool(pool_key)?; 589 | let base_coin = self.config.get_coin(&pool.base_coin)?; 590 | let quote_coin = self.config.get_coin(&pool.quote_coin)?; 591 | 592 | let pool_id = ObjectID::from_hex_literal(&pool.address)?; 593 | 594 | let base_coin_tag = TypeTag::from_str(&base_coin.type_name)?; 595 | let quote_coin_tag = TypeTag::from_str("e_coin.type_name)?; 596 | 597 | let arguments = vec![ptb.obj(self.client.share_object(pool_id).await?)?]; 598 | 599 | Ok(ptb.programmable_move_call( 600 | ObjectID::from_hex_literal(self.config.deepbook_package_id())?, 601 | Identifier::new("pool")?, 602 | Identifier::new("whitelisted")?, 603 | vec![base_coin_tag, quote_coin_tag], 604 | arguments, 605 | )) 606 | } 607 | 608 | /// Gets the quote quantity out for a given base quantity in 609 | /// 610 | /// @param ptb - ProgrammableTransactionBuilder instance 611 | /// @param pool_key - The key to identify the pool 612 | /// @param base_quantity - Base quantity to convert 613 | /// @returns The quote quantity out 614 | pub async fn get_quote_quantity_out( 615 | &self, 616 | ptb: &mut ProgrammableTransactionBuilder, 617 | pool_key: &str, 618 | base_quantity: f64, 619 | ) -> anyhow::Result { 620 | let pool = self.config.get_pool(pool_key)?; 621 | let base_coin = self.config.get_coin(&pool.base_coin)?; 622 | let quote_coin = self.config.get_coin(&pool.quote_coin)?; 623 | 624 | let pool_id = ObjectID::from_hex_literal(&pool.address)?; 625 | let input_quantity = (base_quantity * base_coin.scalar as f64).round() as u64; 626 | 627 | let base_coin_tag = TypeTag::from_str(&base_coin.type_name)?; 628 | let quote_coin_tag = TypeTag::from_str("e_coin.type_name)?; 629 | 630 | let arguments = vec![ 631 | ptb.obj(self.client.share_object(pool_id).await?)?, 632 | ptb.pure(input_quantity)?, 633 | ptb.obj(self.client.share_object(SUI_CLOCK_OBJECT_ID).await?)?, 634 | ]; 635 | 636 | Ok(ptb.programmable_move_call( 637 | ObjectID::from_hex_literal(self.config.deepbook_package_id())?, 638 | Identifier::new("pool")?, 639 | Identifier::new("get_quote_quantity_out")?, 640 | vec![base_coin_tag, quote_coin_tag], 641 | arguments, 642 | )) 643 | } 644 | 645 | /// Gets the base quantity out for a given quote quantity in 646 | /// 647 | /// @param ptb - ProgrammableTransactionBuilder instance 648 | /// @param pool_key - The key to identify the pool 649 | /// @param quote_quantity - Quote quantity to convert 650 | /// @returns The base quantity out 651 | pub async fn get_base_quantity_out( 652 | &self, 653 | ptb: &mut ProgrammableTransactionBuilder, 654 | pool_key: &str, 655 | quote_quantity: f64, 656 | ) -> anyhow::Result { 657 | let pool = self.config.get_pool(pool_key)?; 658 | let base_coin = self.config.get_coin(&pool.base_coin)?; 659 | let quote_coin = self.config.get_coin(&pool.quote_coin)?; 660 | 661 | let pool_id = ObjectID::from_hex_literal(&pool.address)?; 662 | let input_quantity = (quote_quantity * quote_coin.scalar as f64).round() as u64; 663 | 664 | let base_coin_tag = TypeTag::from_str(&base_coin.type_name)?; 665 | let quote_coin_tag = TypeTag::from_str("e_coin.type_name)?; 666 | 667 | let arguments = vec![ 668 | ptb.obj(self.client.share_object(pool_id).await?)?, 669 | ptb.pure(input_quantity)?, 670 | ptb.obj(self.client.share_object(SUI_CLOCK_OBJECT_ID).await?)?, 671 | ]; 672 | 673 | Ok(ptb.programmable_move_call( 674 | ObjectID::from_hex_literal(self.config.deepbook_package_id())?, 675 | Identifier::new("pool")?, 676 | Identifier::new("get_base_quantity_out")?, 677 | vec![base_coin_tag, quote_coin_tag], 678 | arguments, 679 | )) 680 | } 681 | 682 | /// Gets the quantity out for a given base or quote quantity 683 | /// 684 | /// @param ptb - ProgrammableTransactionBuilder instance 685 | /// @param pool_key - The key to identify the pool 686 | /// @param base_quantity - Base quantity to convert 687 | /// @param quote_quantity - Quote quantity to convert 688 | /// @returns The quantity out 689 | pub async fn get_quantity_out( 690 | &self, 691 | ptb: &mut ProgrammableTransactionBuilder, 692 | pool_key: &str, 693 | base_quantity: f64, 694 | quote_quantity: f64, 695 | ) -> anyhow::Result { 696 | let pool = self.config.get_pool(pool_key)?; 697 | let base_coin = self.config.get_coin(&pool.base_coin)?; 698 | let quote_coin = self.config.get_coin(&pool.quote_coin)?; 699 | 700 | let pool_id = ObjectID::from_hex_literal(&pool.address)?; 701 | let base_input = (base_quantity * base_coin.scalar as f64).round() as u64; 702 | let quote_input = (quote_quantity * quote_coin.scalar as f64).round() as u64; 703 | 704 | let base_coin_tag = TypeTag::from_str(&base_coin.type_name)?; 705 | let quote_coin_tag = TypeTag::from_str("e_coin.type_name)?; 706 | 707 | let arguments = vec![ 708 | ptb.obj(self.client.share_object(pool_id).await?)?, 709 | ptb.pure(base_input)?, 710 | ptb.pure(quote_input)?, 711 | ptb.obj(self.client.share_object(SUI_CLOCK_OBJECT_ID).await?)?, 712 | ]; 713 | 714 | Ok(ptb.programmable_move_call( 715 | ObjectID::from_hex_literal(self.config.deepbook_package_id())?, 716 | Identifier::new("pool")?, 717 | Identifier::new("get_quantity_out")?, 718 | vec![base_coin_tag, quote_coin_tag], 719 | arguments, 720 | )) 721 | } 722 | 723 | /// Gets open orders for a balance manager in a pool 724 | /// 725 | /// @param ptb - ProgrammableTransactionBuilder instance 726 | /// @param pool_key - The key to identify the pool 727 | /// @param manager_key - Key of the balance manager 728 | /// @returns The open orders 729 | pub async fn account_open_orders( 730 | &self, 731 | ptb: &mut ProgrammableTransactionBuilder, 732 | pool_key: &str, 733 | manager_key: &str, 734 | ) -> anyhow::Result { 735 | let pool = self.config.get_pool(pool_key)?; 736 | let manager = self.config.get_balance_manager(manager_key)?; 737 | let base_coin = self.config.get_coin(&pool.base_coin)?; 738 | let quote_coin = self.config.get_coin(&pool.quote_coin)?; 739 | 740 | let pool_id = ObjectID::from_hex_literal(&pool.address)?; 741 | let manager_id = ObjectID::from_hex_literal(&manager.address)?; 742 | 743 | let base_coin_tag = TypeTag::from_str(&base_coin.type_name)?; 744 | let quote_coin_tag = TypeTag::from_str("e_coin.type_name)?; 745 | 746 | let arguments = vec![ 747 | ptb.obj(self.client.share_object(pool_id).await?)?, 748 | ptb.obj(self.client.share_object(manager_id).await?)?, 749 | ]; 750 | 751 | Ok(ptb.programmable_move_call( 752 | ObjectID::from_hex_literal(self.config.deepbook_package_id())?, 753 | Identifier::new("pool")?, 754 | Identifier::new("account_open_orders")?, 755 | vec![base_coin_tag, quote_coin_tag], 756 | arguments, 757 | )) 758 | } 759 | 760 | /// Gets level 2 order book specifying range of price 761 | /// 762 | /// @param ptb - ProgrammableTransactionBuilder instance 763 | /// @param pool_key - The key to identify the pool 764 | /// @param price_low - Lower bound of the price range 765 | /// @param price_high - Upper bound of the price range 766 | /// @param is_bid - Whether to get bid or ask orders 767 | /// @returns The level 2 order book ticks from mid-price 768 | pub async fn get_level2_range( 769 | &self, 770 | ptb: &mut ProgrammableTransactionBuilder, 771 | pool_key: &str, 772 | price_low: f64, 773 | price_high: f64, 774 | is_bid: bool, 775 | ) -> anyhow::Result { 776 | let pool = self.config.get_pool(pool_key)?; 777 | let base_coin = self.config.get_coin(&pool.base_coin)?; 778 | let quote_coin = self.config.get_coin(&pool.quote_coin)?; 779 | 780 | let pool_id = ObjectID::from_hex_literal(&pool.address)?; 781 | let low_price = ((price_low * FLOAT_SCALAR as f64 * quote_coin.scalar as f64) 782 | / base_coin.scalar as f64) 783 | .round() as u64; 784 | let high_price = ((price_high * FLOAT_SCALAR as f64 * quote_coin.scalar as f64) 785 | / base_coin.scalar as f64) 786 | .round() as u64; 787 | 788 | let base_coin_tag = TypeTag::from_str(&base_coin.type_name)?; 789 | let quote_coin_tag = TypeTag::from_str("e_coin.type_name)?; 790 | 791 | let arguments = vec![ 792 | ptb.obj(self.client.share_object(pool_id).await?)?, 793 | ptb.pure(low_price)?, 794 | ptb.pure(high_price)?, 795 | ptb.pure(is_bid)?, 796 | ptb.obj(self.client.share_object(SUI_CLOCK_OBJECT_ID).await?)?, 797 | ]; 798 | 799 | Ok(ptb.programmable_move_call( 800 | ObjectID::from_hex_literal(self.config.deepbook_package_id())?, 801 | Identifier::new("pool")?, 802 | Identifier::new("get_level2_range")?, 803 | vec![base_coin_tag, quote_coin_tag], 804 | arguments, 805 | )) 806 | } 807 | 808 | /// Gets level 2 order book ticks from mid-price for a pool 809 | /// 810 | /// @param ptb - ProgrammableTransactionBuilder instance 811 | /// @param pool_key - The key to identify the pool 812 | /// @param tick_from_mid - Number of ticks from mid-price 813 | /// @returns The level 2 order book ticks from mid-price 814 | pub async fn get_level2_ticks_from_mid( 815 | &self, 816 | ptb: &mut ProgrammableTransactionBuilder, 817 | pool_key: &str, 818 | tick_from_mid: u64, 819 | ) -> anyhow::Result { 820 | let pool = self.config.get_pool(pool_key)?; 821 | let base_coin = self.config.get_coin(&pool.base_coin)?; 822 | let quote_coin = self.config.get_coin(&pool.quote_coin)?; 823 | 824 | let pool_id = ObjectID::from_hex_literal(&pool.address)?; 825 | 826 | let base_coin_tag = TypeTag::from_str(&base_coin.type_name)?; 827 | let quote_coin_tag = TypeTag::from_str("e_coin.type_name)?; 828 | 829 | let arguments = vec![ 830 | ptb.obj(self.client.share_object(pool_id).await?)?, 831 | ptb.pure(tick_from_mid)?, 832 | ptb.obj(self.client.share_object(SUI_CLOCK_OBJECT_ID).await?)?, 833 | ]; 834 | 835 | Ok(ptb.programmable_move_call( 836 | ObjectID::from_hex_literal(self.config.deepbook_package_id())?, 837 | Identifier::new("pool")?, 838 | Identifier::new("get_level2_ticks_from_mid")?, 839 | vec![base_coin_tag, quote_coin_tag], 840 | arguments, 841 | )) 842 | } 843 | 844 | /// Gets the vault balances for a pool 845 | /// 846 | /// @param ptb - ProgrammableTransactionBuilder instance 847 | /// @param pool_key - The key to identify the pool 848 | /// @returns The vault balances 849 | pub async fn vault_balances( 850 | &self, 851 | ptb: &mut ProgrammableTransactionBuilder, 852 | pool_key: &str, 853 | ) -> anyhow::Result { 854 | let pool = self.config.get_pool(pool_key)?; 855 | let base_coin = self.config.get_coin(&pool.base_coin)?; 856 | let quote_coin = self.config.get_coin(&pool.quote_coin)?; 857 | 858 | let pool_id = ObjectID::from_hex_literal(&pool.address)?; 859 | 860 | let base_coin_tag = TypeTag::from_str(&base_coin.type_name)?; 861 | let quote_coin_tag = TypeTag::from_str("e_coin.type_name)?; 862 | 863 | let arguments = vec![ptb.obj(self.client.share_object(pool_id).await?)?]; 864 | 865 | Ok(ptb.programmable_move_call( 866 | ObjectID::from_hex_literal(self.config.deepbook_package_id())?, 867 | Identifier::new("pool")?, 868 | Identifier::new("vault_balances")?, 869 | vec![base_coin_tag, quote_coin_tag], 870 | arguments, 871 | )) 872 | } 873 | 874 | /// Gets the pool ID by asset types 875 | /// 876 | /// @param ptb - ProgrammableTransactionBuilder instance 877 | /// @param base_type - Type of the base asset 878 | /// @param quote_type - Type of the quote asset 879 | /// @returns The pool ID 880 | pub async fn get_pool_id_by_assets( 881 | &self, 882 | ptb: &mut ProgrammableTransactionBuilder, 883 | base_type: &str, 884 | quote_type: &str, 885 | ) -> anyhow::Result { 886 | let registry_id = ObjectID::from_hex_literal(self.config.registry_id())?; 887 | 888 | let base_coin_tag = TypeTag::from_str(base_type)?; 889 | let quote_coin_tag = TypeTag::from_str(quote_type)?; 890 | 891 | let arguments = vec![ptb.obj(self.client.share_object(registry_id).await?)?]; 892 | 893 | Ok(ptb.programmable_move_call( 894 | ObjectID::from_hex_literal(self.config.deepbook_package_id())?, 895 | Identifier::new("pool")?, 896 | Identifier::new("get_pool_id_by_asset")?, 897 | vec![base_coin_tag, quote_coin_tag], 898 | arguments, 899 | )) 900 | } 901 | 902 | /// Swap exact base amount for quote amount 903 | /// 904 | /// @param ptb - ProgrammableTransactionBuilder instance 905 | /// @param params - Parameters for the swap 906 | pub async fn swap_exact_base_for_quote( 907 | &self, 908 | ptb: &mut ProgrammableTransactionBuilder, 909 | params: SwapParams, 910 | ) -> anyhow::Result<()> { 911 | if params.quote_coin.is_some() { 912 | return Err(anyhow::anyhow!( 913 | "quote_coin is not accepted for swapping base asset" 914 | )); 915 | } 916 | 917 | let pool = self.config.get_pool(¶ms.pool_key)?; 918 | let base_coin = self.config.get_coin(&pool.base_coin)?; 919 | let quote_coin = self.config.get_coin(&pool.quote_coin)?; 920 | let deep_coin = self.config.get_coin("DEEP")?; 921 | 922 | let base_amount = (params.amount * base_coin.scalar as f64).round() as u64; 923 | let deep_amount = (params.deep_amount * DEEP_SCALAR as f64).round() as u64; 924 | let min_quote = (params.min_out * quote_coin.scalar as f64).round() as u64; 925 | 926 | let pool_id = ObjectID::from_hex_literal(&pool.address)?; 927 | 928 | let base_coin_tag = TypeTag::from_str(&base_coin.type_name)?; 929 | let quote_coin_tag = TypeTag::from_str("e_coin.type_name)?; 930 | 931 | let base_coin = match params.base_coin { 932 | Some(coin) => coin, 933 | None => { 934 | self.client 935 | .get_coin_object(params.sender, base_coin.type_name.clone(), base_amount) 936 | .await? 937 | } 938 | }; 939 | 940 | let deep_coin = match params.deep_coin { 941 | Some(coin) => coin, 942 | None => { 943 | self.client 944 | .get_coin_object(params.sender, deep_coin.type_name.clone(), deep_amount) 945 | .await? 946 | } 947 | }; 948 | 949 | let arguments = vec![ 950 | ptb.obj(self.client.share_object_mutable(pool_id).await?)?, 951 | ptb.obj(self.client.coin_object(base_coin).await?)?, 952 | ptb.obj(self.client.coin_object(deep_coin).await?)?, 953 | ptb.pure(min_quote)?, 954 | ptb.obj(self.client.share_object(SUI_CLOCK_OBJECT_ID).await?)?, 955 | ]; 956 | 957 | ptb.programmable_move_call( 958 | ObjectID::from_hex_literal(self.config.deepbook_package_id())?, 959 | Identifier::new("pool")?, 960 | Identifier::new("swap_exact_base_for_quote")?, 961 | vec![base_coin_tag, quote_coin_tag], 962 | arguments, 963 | ); 964 | 965 | Ok(()) 966 | } 967 | 968 | /// Swap exact quote amount for base amount 969 | /// 970 | /// @param ptb - ProgrammableTransactionBuilder instance 971 | /// @param params - Parameters for the swap 972 | pub async fn swap_exact_quote_for_base( 973 | &self, 974 | ptb: &mut ProgrammableTransactionBuilder, 975 | params: SwapParams, 976 | ) -> anyhow::Result<()> { 977 | if params.base_coin.is_some() { 978 | return Err(anyhow::anyhow!( 979 | "base_coin is not accepted for swapping quote asset" 980 | )); 981 | } 982 | 983 | let pool = self.config.get_pool(¶ms.pool_key)?; 984 | let base_coin = self.config.get_coin(&pool.base_coin)?; 985 | let quote_coin = self.config.get_coin(&pool.quote_coin)?; 986 | let deep_coin = self.config.get_coin("DEEP")?; 987 | 988 | let quote_amount = (params.amount * quote_coin.scalar as f64).round() as u64; 989 | let deep_amount = (params.deep_amount * DEEP_SCALAR as f64).round() as u64; 990 | let min_base = (params.min_out * base_coin.scalar as f64).round() as u64; 991 | 992 | let pool_id = ObjectID::from_hex_literal(&pool.address)?; 993 | 994 | let base_coin_tag = TypeTag::from_str(&base_coin.type_name)?; 995 | let quote_coin_tag = TypeTag::from_str("e_coin.type_name)?; 996 | 997 | let quote_coin = match params.base_coin { 998 | Some(coin) => coin, 999 | None => { 1000 | self.client 1001 | .get_coin_object(params.sender, quote_coin.type_name.clone(), quote_amount) 1002 | .await? 1003 | } 1004 | }; 1005 | 1006 | let deep_coin = match params.deep_coin { 1007 | Some(coin) => coin, 1008 | None => { 1009 | self.client 1010 | .get_coin_object(params.sender, deep_coin.type_name.clone(), deep_amount) 1011 | .await? 1012 | } 1013 | }; 1014 | 1015 | let arguments = vec![ 1016 | ptb.obj(self.client.share_object_mutable(pool_id).await?)?, 1017 | ptb.obj(self.client.coin_object(quote_coin).await?)?, 1018 | ptb.obj(self.client.coin_object(deep_coin).await?)?, 1019 | ptb.pure(min_base)?, 1020 | ptb.obj(self.client.share_object(SUI_CLOCK_OBJECT_ID).await?)?, 1021 | ]; 1022 | 1023 | ptb.programmable_move_call( 1024 | ObjectID::from_hex_literal(self.config.deepbook_package_id())?, 1025 | Identifier::new("pool")?, 1026 | Identifier::new("swap_exact_quote_for_base")?, 1027 | vec![base_coin_tag, quote_coin_tag], 1028 | arguments, 1029 | ); 1030 | 1031 | Ok(()) 1032 | } 1033 | 1034 | /// Get the trade parameters for a given pool 1035 | /// 1036 | /// @param ptb - ProgrammableTransactionBuilder instance 1037 | /// @param pool_key - Key of the pool 1038 | /// @returns The trade parameters 1039 | pub async fn pool_trade_params( 1040 | &self, 1041 | ptb: &mut ProgrammableTransactionBuilder, 1042 | pool_key: &str, 1043 | ) -> anyhow::Result { 1044 | let pool = self.config.get_pool(pool_key)?; 1045 | let base_coin = self.config.get_coin(&pool.base_coin)?; 1046 | let quote_coin = self.config.get_coin(&pool.quote_coin)?; 1047 | 1048 | let pool_id = ObjectID::from_hex_literal(&pool.address)?; 1049 | 1050 | let base_coin_tag = TypeTag::from_str(&base_coin.type_name)?; 1051 | let quote_coin_tag = TypeTag::from_str("e_coin.type_name)?; 1052 | 1053 | let arguments = vec![ptb.obj(self.client.share_object(pool_id).await?)?]; 1054 | 1055 | Ok(ptb.programmable_move_call( 1056 | ObjectID::from_hex_literal(self.config.deepbook_package_id())?, 1057 | Identifier::new("pool")?, 1058 | Identifier::new("pool_trade_params")?, 1059 | vec![base_coin_tag, quote_coin_tag], 1060 | arguments, 1061 | )) 1062 | } 1063 | 1064 | /// Get the book parameters for a given pool 1065 | /// 1066 | /// @param ptb - ProgrammableTransactionBuilder instance 1067 | /// @param pool_key - Key of the pool 1068 | /// @returns The book parameters 1069 | pub async fn pool_book_params( 1070 | &self, 1071 | ptb: &mut ProgrammableTransactionBuilder, 1072 | pool_key: &str, 1073 | ) -> anyhow::Result { 1074 | let pool = self.config.get_pool(pool_key)?; 1075 | let base_coin = self.config.get_coin(&pool.base_coin)?; 1076 | let quote_coin = self.config.get_coin(&pool.quote_coin)?; 1077 | 1078 | let pool_id = ObjectID::from_hex_literal(&pool.address)?; 1079 | 1080 | let base_coin_tag = TypeTag::from_str(&base_coin.type_name)?; 1081 | let quote_coin_tag = TypeTag::from_str("e_coin.type_name)?; 1082 | 1083 | let arguments = vec![ptb.obj(self.client.share_object(pool_id).await?)?]; 1084 | 1085 | Ok(ptb.programmable_move_call( 1086 | ObjectID::from_hex_literal(self.config.deepbook_package_id())?, 1087 | Identifier::new("pool")?, 1088 | Identifier::new("pool_book_params")?, 1089 | vec![base_coin_tag, quote_coin_tag], 1090 | arguments, 1091 | )) 1092 | } 1093 | 1094 | /// Get the account information for a given pool and balance manager 1095 | /// 1096 | /// @param ptb - ProgrammableTransactionBuilder instance 1097 | /// @param pool_key - Key of the pool 1098 | /// @param manager_key - The key of the BalanceManager 1099 | /// @returns The account information 1100 | pub async fn account( 1101 | &self, 1102 | ptb: &mut ProgrammableTransactionBuilder, 1103 | pool_key: &str, 1104 | manager_key: &str, 1105 | ) -> anyhow::Result { 1106 | let pool = self.config.get_pool(pool_key)?; 1107 | let manager = self.config.get_balance_manager(manager_key)?; 1108 | let base_coin = self.config.get_coin(&pool.base_coin)?; 1109 | let quote_coin = self.config.get_coin(&pool.quote_coin)?; 1110 | 1111 | let pool_id = ObjectID::from_hex_literal(&pool.address)?; 1112 | let manager_id = ObjectID::from_hex_literal(&manager.address)?; 1113 | 1114 | let base_coin_tag = TypeTag::from_str(&base_coin.type_name)?; 1115 | let quote_coin_tag = TypeTag::from_str("e_coin.type_name)?; 1116 | 1117 | let arguments = vec![ 1118 | ptb.obj(self.client.share_object(pool_id).await?)?, 1119 | ptb.obj(self.client.share_object(manager_id).await?)?, 1120 | ]; 1121 | 1122 | Ok(ptb.programmable_move_call( 1123 | ObjectID::from_hex_literal(self.config.deepbook_package_id())?, 1124 | Identifier::new("pool")?, 1125 | Identifier::new("account")?, 1126 | vec![base_coin_tag, quote_coin_tag], 1127 | arguments, 1128 | )) 1129 | } 1130 | 1131 | /// Get the locked balance for a given pool and balance manager 1132 | /// 1133 | /// @param ptb - ProgrammableTransactionBuilder instance 1134 | /// @param pool_key - Key of the pool 1135 | /// @param manager_key - The key of the BalanceManager 1136 | /// @returns The locked balance 1137 | pub async fn locked_balance( 1138 | &self, 1139 | ptb: &mut ProgrammableTransactionBuilder, 1140 | pool_key: &str, 1141 | manager_key: &str, 1142 | ) -> anyhow::Result { 1143 | let pool = self.config.get_pool(pool_key)?; 1144 | let manager = self.config.get_balance_manager(manager_key)?; 1145 | let base_coin = self.config.get_coin(&pool.base_coin)?; 1146 | let quote_coin = self.config.get_coin(&pool.quote_coin)?; 1147 | 1148 | let pool_id = ObjectID::from_hex_literal(&pool.address)?; 1149 | let manager_id = ObjectID::from_hex_literal(&manager.address)?; 1150 | 1151 | let base_coin_tag = TypeTag::from_str(&base_coin.type_name)?; 1152 | let quote_coin_tag = TypeTag::from_str("e_coin.type_name)?; 1153 | 1154 | let arguments = vec![ 1155 | ptb.obj(self.client.share_object(pool_id).await?)?, 1156 | ptb.obj(self.client.share_object(manager_id).await?)?, 1157 | ]; 1158 | 1159 | Ok(ptb.programmable_move_call( 1160 | ObjectID::from_hex_literal(self.config.deepbook_package_id())?, 1161 | Identifier::new("pool")?, 1162 | Identifier::new("locked_balance")?, 1163 | vec![base_coin_tag, quote_coin_tag], 1164 | arguments, 1165 | )) 1166 | } 1167 | 1168 | /// Get the DEEP price conversion for a pool 1169 | /// 1170 | /// @param ptb - ProgrammableTransactionBuilder instance 1171 | /// @param pool_key - The key to identify the pool 1172 | /// @returns The DEEP price conversion 1173 | pub async fn get_pool_deep_price( 1174 | &self, 1175 | ptb: &mut ProgrammableTransactionBuilder, 1176 | pool_key: &str, 1177 | ) -> anyhow::Result { 1178 | let pool = self.config.get_pool(pool_key)?; 1179 | let base_coin = self.config.get_coin(&pool.base_coin)?; 1180 | let quote_coin = self.config.get_coin(&pool.quote_coin)?; 1181 | 1182 | let pool_id = ObjectID::from_hex_literal(&pool.address)?; 1183 | 1184 | let base_coin_tag = TypeTag::from_str(&base_coin.type_name)?; 1185 | let quote_coin_tag = TypeTag::from_str("e_coin.type_name)?; 1186 | 1187 | let arguments = vec![ptb.obj(self.client.share_object(pool_id).await?)?]; 1188 | 1189 | Ok(ptb.programmable_move_call( 1190 | ObjectID::from_hex_literal(self.config.deepbook_package_id())?, 1191 | Identifier::new("pool")?, 1192 | Identifier::new("get_order_deep_price")?, 1193 | vec![base_coin_tag, quote_coin_tag], 1194 | arguments, 1195 | )) 1196 | } 1197 | 1198 | /// Create a new pool permissionlessly 1199 | /// 1200 | /// @param ptb - ProgrammableTransactionBuilder instance 1201 | /// @param base_coin_key - Key of the base coin 1202 | /// @param quote_coin_key - Key of the quote coin 1203 | /// @param tick_size - Tick size for the pool 1204 | /// @param lot_size - Lot size for the pool 1205 | /// @param min_size - Minimum size for orders 1206 | /// @param deep_coin - Optional DEEP coin object 1207 | /// @returns The create permissionless pool call 1208 | pub async fn create_permissionless_pool( 1209 | &self, 1210 | ptb: &mut ProgrammableTransactionBuilder, 1211 | sender: SuiAddress, 1212 | base_coin_key: &str, 1213 | quote_coin_key: &str, 1214 | tick_size: f64, 1215 | lot_size: f64, 1216 | min_size: f64, 1217 | deep_coin: Option, 1218 | ) -> anyhow::Result { 1219 | let base_coin = self.config.get_coin(base_coin_key)?; 1220 | let quote_coin = self.config.get_coin(quote_coin_key)?; 1221 | let deep_coin_type = self.config.get_coin("DEEP")?; 1222 | 1223 | let base_scalar = base_coin.scalar; 1224 | let quote_scalar = quote_coin.scalar; 1225 | 1226 | let adjusted_tick_size = ((tick_size * FLOAT_SCALAR as f64 * quote_scalar as f64) 1227 | / base_scalar as f64) 1228 | .round() as u64; 1229 | let adjusted_lot_size = (lot_size * base_scalar as f64).round() as u64; 1230 | let adjusted_min_size = (min_size * base_scalar as f64).round() as u64; 1231 | 1232 | let registry_id = ObjectID::from_hex_literal(self.config.registry_id())?; 1233 | 1234 | // Handle DEEP coin 1235 | let deep_coin_obj = match deep_coin { 1236 | Some(coin_id) => coin_id, 1237 | None => { 1238 | self.client 1239 | .get_coin_object( 1240 | sender, 1241 | deep_coin_type.type_name.clone(), 1242 | deep_coin_type.scalar, 1243 | ) 1244 | .await? 1245 | } 1246 | }; 1247 | 1248 | let base_coin_tag = TypeTag::from_str(&base_coin.type_name)?; 1249 | let quote_coin_tag = TypeTag::from_str("e_coin.type_name)?; 1250 | 1251 | let arguments = vec![ 1252 | ptb.obj(self.client.share_object(registry_id).await?)?, 1253 | ptb.pure(adjusted_tick_size)?, 1254 | ptb.pure(adjusted_lot_size)?, 1255 | ptb.pure(adjusted_min_size)?, 1256 | ptb.obj(self.client.coin_object(deep_coin_obj).await?)?, 1257 | ]; 1258 | 1259 | Ok(ptb.programmable_move_call( 1260 | ObjectID::from_hex_literal(self.config.deepbook_package_id())?, 1261 | Identifier::new("pool")?, 1262 | Identifier::new("create_permissionless_pool")?, 1263 | vec![base_coin_tag, quote_coin_tag], 1264 | arguments, 1265 | )) 1266 | } 1267 | } 1268 | --------------------------------------------------------------------------------