diff --git a/.bumpversion.toml b/.bumpversion.toml index a8f8100..ef3abf7 100644 --- a/.bumpversion.toml +++ b/.bumpversion.toml @@ -1,5 +1,5 @@ [bumpversion] -current_version = 0.0.10 +current_version = 0.0.11 [bumpversion:file:Cargo.toml] search = 'version = "{current_version}"' diff --git a/Cargo.lock b/Cargo.lock index d8120f6..223bb11 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2461,6 +2461,16 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + [[package]] name = "num" version = "0.2.1" @@ -2687,7 +2697,7 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] name = "openbook" -version = "0.0.10" +version = "0.0.11" dependencies = [ "anchor-spl", "anyhow", @@ -2696,7 +2706,6 @@ dependencies = [ "bump2version", "clap 4.5.1", "crossterm", - "log", "memoffset 0.9.0", "openbook_dex", "pyth-sdk-solana", @@ -2716,6 +2725,8 @@ dependencies = [ "spl-associated-token-account", "strum", "tokio", + "tracing", + "tracing-subscriber", "tui-input", "unicode-width", ] @@ -2756,6 +2767,12 @@ version = "6.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2355d85b9a3786f481747ced0e0ff2ba35213a1f9bd406ed906554d7af805a1" +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + [[package]] name = "parking_lot" version = "0.12.1" @@ -3742,6 +3759,15 @@ dependencies = [ "keccak", ] +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + [[package]] name = "shell-words" version = "1.1.0" @@ -5086,6 +5112,16 @@ dependencies = [ "syn 2.0.52", ] +[[package]] +name = "thread_local" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +dependencies = [ + "cfg-if", + "once_cell", +] + [[package]] name = "time" version = "0.3.34" @@ -5304,6 +5340,32 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" dependencies = [ "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +dependencies = [ + "nu-ansi-term", + "sharded-slab", + "smallvec", + "thread_local", + "tracing-core", + "tracing-log", ] [[package]] @@ -5467,6 +5529,12 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + [[package]] name = "vec_map" version = "0.8.2" diff --git a/Cargo.toml b/Cargo.toml index 15eaa54..bb0e242 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "openbook" -version = "0.0.10" +version = "0.0.11" edition = "2021" description = "📖 A CLI, TUI and SDK to interact with the OpenBook market on the Solana blockchain (WIP)." license = "MIT" @@ -31,7 +31,6 @@ pyth-sdk-solana = "0.10.0" memoffset = "0.9.0" borsh = "1.3.1" serde_json = "1.0.114" -log = "0.4.21" tokio = { version = "1.36.0", features = ["full"] } spl-associated-token-account = "=2.3.0" solana-account-decoder = "=1.18.6" @@ -45,9 +44,11 @@ serde = { version = "1.0.200", features = ["derive"], optional = true } crossterm = { version = "0.27.0", optional = true } unicode-width = { version = "0.1.12", optional = true } strum = { version = "0.26.2", optional = true } +tracing = "0.1.40" +tracing-subscriber = { version = "0.3.18", features = ["std"], optional = true } [features] -cli = ["clap", "solana-cli-output", "ratatui", "tui-input", "serde", "crossterm", "unicode-width", "strum"] +cli = ["clap", "solana-cli-output", "ratatui", "tui-input", "serde", "crossterm", "unicode-width", "strum", "tracing-subscriber"] [dev-dependencies] bump2version = "0.1.3" diff --git a/README.md b/README.md index 70b0f40..d5a0538 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,13 @@ # 📖 OpenBook -[![CircleCI](https://dl.circleci.com/status-badge/img/gh/gigadao/openbook/tree/master.svg?style=svg)](https://dl.circleci.com/status-badge/redirect/gh/gigadao/openbook/tree/master) +[![CircleCI](https://dl.circleci.com/status-badge/img/gh/GigaDAO/openbook/tree/master.svg?style=svg)](https://dl.circleci.com/status-badge/redirect/gh/GigaDAO/openbook/tree/master) [![Crates.io](https://img.shields.io/crates/v/openbook.svg)](https://crates.io/crates/openbook) +[![Crates.io Downloads](https://img.shields.io/crates/d/openbook)](https://crates.io/crates/openbook) [![docs](https://docs.rs/openbook/badge.svg)](https://docs.rs/openbook/) [![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) +![Banner](https://github.com/GigaDAO/openbook/assets/62179149/ed83a9a8-4b8d-421d-be31-8eea73529444) + 📖 A CLI, TUI and SDK to interact with the OpenBook market on the Solana blockchain. > [!WARNING] @@ -51,9 +54,6 @@ export RPC_URL=https://api.mainnet-beta.solana.com export KEY_PATH= ``` -> [!NOTE] -> Certain RPC methods, like `getProgramAccounts`, are no longer available on `api.mainnet-beta.solana.com`. We recommend utilizing [helius.dev](https://www.helius.dev) as an alternative. - ## ⌨ Usage as TUI https://github.com/GigaDAO/openbook/assets/62179149/23b411ac-243c-4f89-b8a2-fcc021eb9fdd @@ -63,7 +63,7 @@ openbook tui ``` > [!NOTE] -> To interact with the openbook market by placing bids or asking, you'll need to set up an open order account for your wallet. In future releases, this crate will automatically fetch your associated open order account. However, for now, if you already have one, you'll need to set up this environment variable before launching the tui: +> To trade on the openbook market, you need an open order account. This current release of this crate generates one for your wallet, but it's empty. You'll have to add funds to it. In the future, you can add funds through the TUI. But, if you already have funds in your open order account, set up this variable before starting the TUI: ```sh export OOS_KEY= @@ -147,16 +147,17 @@ openbook consume-permissioned --limit 2 ```toml [dependencies] -openbook = "0.0.10" +openbook = "0.0.11" ``` ```rust -use openbook::{pubkey::Pubkey, signature::Keypair, rpc_client::RpcClient}; +use openbook::{pubkey::Pubkey, rpc_client::RpcClient}; use openbook::market::Market; use openbook::utils::read_keypair; use openbook::matching::Side; use openbook::commitment_config::CommitmentConfig; use openbook::market::OrderReturnType; +use openbook::tokens_and_markets::{Token, DexVersion}; #[tokio::main] async fn main() -> Result<(), Box> { @@ -168,18 +169,18 @@ async fn main() -> Result<(), Box> { let keypair = read_keypair(&key_path); - let mut market = Market::new(rpc_client, 3, "jlp", "usdc", keypair, true).await; + let mut market = Market::new(rpc_client, DexVersion::default(), Token::JLP, Token::USDC, keypair, true).await?; println!("Initialized Market: {:?}", market); println!("[*] Place Limit Order"); if let Some(ord_ret_type) = market .place_limit_order( - 10.0, + 0.1, Side::Bid, // or Side::Ask - 0.5, + 0.1, true, - 15.0, + 2.0, ) .await? { diff --git a/src/fees.rs b/src/fees.rs index 7ca694c..0d5d3d1 100644 --- a/src/fees.rs +++ b/src/fees.rs @@ -25,7 +25,7 @@ use solana_sdk::pubkey::Pubkey; /// assert_eq!(is_supported, true); /// ``` pub fn supports_srm_fee_discounts(program_id: &Pubkey) -> bool { - get_layout_version(program_id) > 1 + get_layout_version(program_id) as u64 > 0 } /// Gets the fee rates for a given fee tier. diff --git a/src/main.rs b/src/main.rs index fc6973a..2b6381d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,5 @@ +use anyhow::Result; + /// The entry point for the OpenBook CLI application. /// /// # Returns @@ -15,14 +17,29 @@ async fn main() -> Result<(), Box> { use openbook::matching::Side; use openbook::rpc_client::RpcClient; use openbook::signature::Signer; + use openbook::tokens_and_markets::{DexVersion, Token}; use openbook::tui::run_tui; use openbook::utils::read_keypair; use solana_cli_output::display::println_transaction; + use tracing_subscriber::{filter, fmt}; + + // Start configuring a `fmt` subscriber + let filter = filter::LevelFilter::INFO; + let subscriber = fmt() + .compact() + .with_max_level(filter) + .with_file(false) + .with_line_number(false) + .with_thread_ids(false) + .with_target(false) + .finish(); + tracing::subscriber::set_global_default(subscriber)?; let args = Cli::parse(); let mut market; - let rpc_url = std::env::var("RPC_URL").unwrap_or("".to_string()); + let rpc_url = + std::env::var("RPC_URL").unwrap_or("https://api.mainnet-beta.solana.com".to_string()); let key_path = std::env::var("KEY_PATH").unwrap_or("".to_string()); let commitment_config = CommitmentConfig::confirmed(); @@ -32,9 +49,25 @@ async fn main() -> Result<(), Box> { assert_eq!(rpc_client.commitment(), CommitmentConfig::confirmed()); if args.command == Some(Commands::Tui) { - market = Market::new(rpc_client, 3, "jlp", "usdc", owner, false).await; + market = Market::new( + rpc_client, + DexVersion::default(), + Token::JLP, + Token::USDC, + owner, + false, + ) + .await?; } else { - market = Market::new(rpc_client, 3, "jlp", "usdc", owner, true).await; + market = Market::new( + rpc_client, + DexVersion::default(), + Token::JLP, + Token::USDC, + owner, + true, + ) + .await?; } match args.command { diff --git a/src/market.rs b/src/market.rs index 5716df2..e21be55 100644 --- a/src/market.rs +++ b/src/market.rs @@ -3,11 +3,11 @@ use crate::{ orders::{MarketInfo, OpenOrders, OpenOrdersCacheEntry}, rpc::Rpc, rpc_client::RpcClient, - tokens_and_markets::{get_market_name, get_program_id}, + tokens_and_markets::{get_market_name, get_program_id, DexVersion, Token}, utils::{get_unix_secs, u64_slice_to_bytes}, }; use anchor_spl::token::spl_token; -use log::{debug, error}; +use anyhow::{Error, Result}; use openbook_dex::{ critbit::Slab, instruction::SelfTradeBehavior, @@ -15,29 +15,28 @@ use openbook_dex::{ state::{gen_vault_signer_key, MarketState}, }; use rand::random; -use solana_client::{client_error::ClientError, rpc_request::TokenAccountsFilter}; +use solana_client::client_error::ClientError; use solana_program::{account_info::AccountInfo, instruction::Instruction, pubkey::Pubkey}; use solana_rpc_client_api::config::RpcSendTransactionConfig; use solana_sdk::sysvar::slot_history::ProgramError; use solana_sdk::{ account::Account, compute_budget::ComputeBudgetInstruction, - message::Message, signature::Signature, signature::{Keypair, Signer}, transaction::Transaction, }; use spl_associated_token_account::{ - get_associated_token_address, instruction::create_associated_token_account, + get_associated_token_address, }; use std::{ + str::FromStr, cell::RefMut, collections::HashMap, - error::Error, num::NonZeroU64, time::{SystemTime, UNIX_EPOCH}, }; -use std::str::FromStr; +use tracing::{debug, error}; #[derive(Debug)] pub enum OrderReturnType { @@ -60,9 +59,6 @@ pub struct Market { /// The keypair of the owner used for signing transactions related to the market. pub owner: Keypair, - /// The associated token account. - pub ata_address: Pubkey, - /// The number of decimal places for the base currency (coin) in the market. pub coin_decimals: u8, @@ -78,12 +74,18 @@ pub struct Market { /// The lot size for the quote currency (pc) in the market. pub pc_lot_size: u64, - /// The public key of the account holding USDC tokens. + /// The public key of the associated account holding the quote quote tokens. pub quote_ata: Pubkey, - /// The public key of the base market token. + /// The public key of the associated account holding the base quote tokens. pub base_ata: Pubkey, + /// The public key of the market quote mint. + pub quote_mint: Pubkey, + + /// The public key of the market base mint. + pub base_mint: Pubkey, + /// The public key of the vault holding base currency (coin) tokens. pub coin_vault: Pubkey, @@ -105,24 +107,26 @@ pub struct Market { /// A HashMap containing open orders cache entries associated with their public keys. pub open_orders_accounts_cache: HashMap, - /// Information about the market. + /// Account info of the wallet on the market (e.g. open orders). pub market_info: MarketInfo, } impl Market { - /// This function is responsible for creating a new instance of the `Market` struct, representing an OpenBook market - /// on the Solana blockchain. It requires essential parameters such as the RPC client, program version, market name, - /// and a keypair for transaction signing. The example demonstrates how to set up the necessary environment variables, - /// initialize the RPC client, and read the keypair from a file path. After initializing the market, information about - /// the newly created instance is printed for verification and further usage. Ensure that the required environment variables, - /// such as `RPC_URL`, `KEY_PATH` are appropriately configured before executing this method. + /// Initializes a new instance of the `Market` struct, representing an OpenBook market on the Solana blockchain. + /// + /// This method initializes the `Market` struct, containing information about the requested market, + /// having the base and quote mints. It fetches and stores all data about this OpenBook market. + /// Additionally, it includes information about the account associated with the wallet on the OpenBook market + /// (e.g., open orders, bids, asks, etc.). /// /// # Arguments /// - /// * `rpc_client` - The RPC client for interacting with the Solana blockchain. - /// * `program_version` - The program dex version representing the market. - /// * `market_name` - The market name. - /// * `owner` - The keypair of the owner used for signing transactions. + /// * `rpc_client` - RPC client for interacting with Solana blockchain. + /// * `program_version` - Program dex version representing the market. + /// * `base_mint` - Base mint symbol. + /// * `quote_mint` - Quote mint symbol. + /// * `owner` - Keypair of the owner used for signing transactions. + /// * `load` - Boolean indicating whether to load market data immediately. /// /// # Returns /// @@ -134,6 +138,7 @@ impl Market { /// use openbook::{pubkey::Pubkey, signature::Keypair, rpc_client::RpcClient}; /// use openbook::market::Market; /// use openbook::utils::read_keypair; + /// use openbook::tokens_and_markets::{DexVersion, Token}; /// /// #[tokio::main] /// async fn main() -> Result<(), Box> { @@ -144,7 +149,7 @@ impl Market { /// /// let owner = read_keypair(&key_path); /// - /// let market = Market::new(rpc_client, 3, "jlp", "usdc", owner, true).await; + /// let market = Market::new(rpc_client, DexVersion::default(), Token::JLP, Token::USDC, owner, true).await?; /// /// println!("Initialized Market: {:?}", market); /// @@ -153,47 +158,54 @@ impl Market { /// ``` pub async fn new( rpc_client: RpcClient, - program_version: u8, - base_mint: &'static str, - quote_mint: &'static str, + program_version: DexVersion, + base_mint: Token, + quote_mint: Token, owner: Keypair, load: bool, - ) -> Self { - let quote_ata = get_market_name(quote_mint).1.parse().unwrap(); - let base_ata = get_market_name(base_mint).1.parse().unwrap(); - let ata_address = get_market_name(base_mint).1.parse().unwrap(); + ) -> Result { + let market_address = get_market_name(base_mint).0.parse()?; + let quote_mint = get_market_name(quote_mint).1.parse()?; + let base_mint = get_market_name(base_mint).1.parse()?; let orders_key = Default::default(); let coin_vault = Default::default(); + let quote_ata = Default::default(); + let base_ata = Default::default(); let pc_vault = Default::default(); let vault_signer_key = Default::default(); let event_queue = Default::default(); let request_queue = Default::default(); let market_info = Default::default(); - let decoded = Default::default(); - let program_id = get_program_id(program_version).parse().unwrap(); - let market_address = get_market_name(base_mint).0.parse().unwrap(); - let open_orders = OpenOrders::new(market_address, decoded, owner.pubkey()); + let program_id = get_program_id(program_version).parse()?; + let rpc_client = Rpc::new(rpc_client); + let pub_owner_key = owner.pubkey().clone(); + let owner_bytes = owner.to_bytes(); + let cloned_owner = Keypair::from_bytes(&owner_bytes)?; + let open_orders = + OpenOrders::new(rpc_client.clone(), program_id, cloned_owner, market_address).await?; let mut open_orders_accounts_cache = HashMap::new(); let open_orders_cache_entry = OpenOrdersCacheEntry { accounts: vec![open_orders], ts: 123456789, }; - open_orders_accounts_cache.insert(owner.pubkey(), open_orders_cache_entry); + + open_orders_accounts_cache.insert(pub_owner_key, open_orders_cache_entry.clone()); let mut market = Self { - rpc_client: Rpc::new(rpc_client), + rpc_client, program_id, market_address, owner, - ata_address, coin_decimals: 9, pc_decimals: 6, coin_lot_size: 1_000_000, pc_lot_size: 1, quote_ata, base_ata, + quote_mint, + base_mint, coin_vault, pc_vault, vault_signer_key, @@ -205,42 +217,10 @@ impl Market { market_info, }; - - let oos_key_str = std::env::var("OOS_KEY").unwrap_or("J393rZhx4VcGaRA48N21T1EZmCJuCkxfzUo8mQWoY7LS".to_string()); - - let orders_key = Pubkey::from_str(oos_key_str.as_str()); - - // TODO: use this method to get oos account - // let oo_accounts = market - // .rpc_client - // .inner() - // .get_token_accounts_by_owner( - // &market.owner.pubkey(), - // TokenAccountsFilter::ProgramId(anchor_spl::token::ID), - // ) - // .await.unwrap(); - - // if oo_accounts.is_empty() { - // println!("[*] Orders Key not found, creating Orders Key..."); - - // let key = OpenOrders::make_create_account_transaction( - // &market.rpc_client, - // program_id, - // &market.owner, - // market_address, - // ) - // .await - // .unwrap(); - // println!("[*] Orders Key created successfully!"); - // market.orders_key = key; - // } else { - // market.orders_key = oo_accounts[0].pubkey.parse().unwrap(); - // } - - market.orders_key = orders_key.unwrap(); + market.orders_key = open_orders_cache_entry.accounts[0].address; if load { - market.load().await.unwrap(); + market.load().await?; } let (_, vault_signer_key) = { @@ -255,77 +235,18 @@ impl Market { }; market.vault_signer_key = vault_signer_key; - market.base_ata = - get_associated_token_address(&market.owner.pubkey().clone(), &market.base_ata); - market.quote_ata = - get_associated_token_address(&market.owner.pubkey().clone(), &market.quote_ata); - - market - } + let oos_key_str = std::env::var("OOS_KEY").unwrap_or("".to_string()); - /// Finds or creates an associated token account for a given wallet and mint. - /// - /// # Arguments - /// - /// * `&self` - A reference to the `Market` struct. - /// * `wallet` - A reference to the wallet's `Keypair`. - /// * `mint` - A reference to the mint's `Pubkey`. - /// - /// # Returns - /// - /// A `Result` containing the `Pubkey` of the associated token account or a boxed `Error` if an error occurs. - pub async fn find_or_create_associated_token_account( - &self, - wallet: &Keypair, - mint: &Pubkey, - ) -> Result> { - let ata_address = get_associated_token_address(&wallet.pubkey(), mint); - - let tokens = self - .rpc_client - .inner() - .get_token_accounts_by_owner( - &wallet.pubkey(), - TokenAccountsFilter::ProgramId(anchor_spl::token::ID), - ) - .await?; + let orders_key = Pubkey::from_str(oos_key_str.as_str()); - if !tokens.is_empty() { - debug!("[*] Found ATA: {:?}", tokens[0].pubkey); - return Ok(tokens[0].pubkey.parse().unwrap()); + if !orders_key.is_err() { + market.orders_key = orders_key.unwrap(); } - debug!("[*] ATA not found, creating ATA"); + market.base_ata = get_associated_token_address(&pub_owner_key.clone(), &market.base_mint); + market.quote_ata = get_associated_token_address(&pub_owner_key.clone(), &market.quote_mint); - let create_ata_ix = create_associated_token_account( - &wallet.pubkey(), - &ata_address, - mint, - &anchor_spl::token::ID, - ); - let message = Message::new(&[create_ata_ix], Some(&wallet.pubkey())); - let mut transaction = Transaction::new_unsigned(message); - let recent_blockhash = self - .rpc_client - .inner() - .get_latest_blockhash_with_commitment(self.rpc_client.inner().commitment()) - .await? - .0; - - transaction.sign(&[wallet], recent_blockhash); - - let result = self - .rpc_client - .inner() - .send_and_confirm_transaction(&transaction) - .await; - - match result { - Ok(sig) => debug!("[*] Transaction successful, signature: {:?}", sig), - Err(err) => error!("[*] Transaction failed: {:?}", err), - }; - - Ok(ata_address) + Ok(market) } /// Loads market information, including account details and state, using the provided RPC client. @@ -346,7 +267,7 @@ impl Market { /// /// This function may return an error if there is an issue with fetching accounts /// or processing the market information. - pub async fn load(&mut self) -> anyhow::Result { + pub async fn load(&mut self) -> Result { let mut account = self .rpc_client .inner() @@ -392,7 +313,7 @@ impl Market { pub async fn load_market_state_bids_info<'a>( &'a mut self, account_info: &'a AccountInfo<'_>, - ) -> anyhow::Result> { + ) -> Result> { let mut market_state = MarketState::load(account_info, &self.program_id, false)?; { @@ -443,7 +364,7 @@ impl Market { pub async fn load_bids_asks_info( &mut self, market_state: &RefMut<'_, MarketState>, - ) -> anyhow::Result<(Pubkey, Pubkey, MarketInfo)> { + ) -> Result<(Pubkey, Pubkey, MarketInfo)> { let (bids_address, asks_address) = self.get_bids_asks_addresses(market_state); let mut bids_account = self.rpc_client.inner().get_account(&bids_address).await?; @@ -501,10 +422,7 @@ impl Market { /// # Errors /// /// This function may return an error if there is an issue with processing the bids information. - pub fn process_bids( - &self, - bids: &mut RefMut, - ) -> anyhow::Result<(Vec, Vec, u64)> { + pub fn process_bids(&self, bids: &mut RefMut) -> Result<(Vec, Vec, u64)> { let mut max_bid = 0; let mut open_bids = Vec::new(); let mut open_bids_prices = Vec::new(); @@ -530,8 +448,7 @@ impl Market { open_bids_prices.push(ui_price); } } - None => { - } + None => {} } Ok((open_bids, open_bids_prices, max_bid)) } @@ -549,10 +466,7 @@ impl Market { /// # Returns /// /// A `Result` containing the maximum bid price if successful, or an error if processing asks fails. - pub fn process_asks( - &self, - asks: &mut RefMut, - ) -> anyhow::Result<(Vec, Vec, u64)> { + pub fn process_asks(&self, asks: &mut RefMut) -> Result<(Vec, Vec, u64)> { let mut min_ask = 0; let mut open_asks = Vec::new(); let mut open_asks_prices = Vec::new(); @@ -606,21 +520,22 @@ impl Market { /// use openbook::utils::read_keypair; /// use openbook::state::MarketState; /// use openbook::tokens_and_markets::{get_market_name, get_program_id}; + /// use openbook::tokens_and_markets::{DexVersion, Token}; /// /// #[tokio::main] /// async fn main() -> Result<(), Box> { /// let rpc_url = std::env::var("RPC_URL").expect("RPC_URL is not set in .env file"); /// let key_path = std::env::var("KEY_PATH").expect("KEY_PATH is not set in .env file"); /// - /// let program_id = get_program_id(3).parse().unwrap(); - /// let market_address = get_market_name("usdc").0.parse().unwrap(); + /// let program_id = get_program_id(DexVersion::default()).parse()?; + /// let market_address = get_market_name(Token::USDC).0.parse()?; /// /// let rpc_client1 = RpcClient::new(rpc_url.clone()); /// let rpc_client2 = RpcClient::new(rpc_url.clone()); /// /// let keypair = read_keypair(&key_path); /// - /// let market = Market::new(rpc_client1, 3, "jlp", "usdc", keypair, true).await; + /// let market = Market::new(rpc_client1, DexVersion::default(), Token::JLP, Token::USDC, keypair, true).await?; /// /// let mut account = rpc_client2.get_account(&market_address).await?; /// @@ -676,21 +591,22 @@ impl Market { /// use openbook::market::Market; /// use openbook::utils::read_keypair; /// use openbook::state::MarketState; + /// use openbook::tokens_and_markets::{DexVersion, Token}; /// /// #[tokio::main] /// async fn main() -> Result<(), Box> { /// let rpc_url = std::env::var("RPC_URL").expect("RPC_URL is not set in .env file"); /// let key_path = std::env::var("KEY_PATH").expect("KEY_PATH is not set in .env file"); /// - /// let program_id = get_program_id(3).parse().unwrap(); - /// let market_address = get_market_name("usdc").0.parse().unwrap(); + /// let program_id = get_program_id(DexVersion::default()).parse()?; + /// let market_address = get_market_name(Token::USDC).0.parse()?; /// /// let rpc_client1 = RpcClient::new(rpc_url.clone()); /// let rpc_client2 = RpcClient::new(rpc_url.clone()); /// /// let keypair = read_keypair(&key_path); /// - /// let market = Market::new(rpc_client1, 3, "jlp", "usdc", keypair, true).await; + /// let market = Market::new(rpc_client1, DexVersion::default(), Token::JLP, Token::USDC, keypair, true).await?; /// /// let mut account = rpc_client2.get_account(&market_address).await?; /// @@ -755,6 +671,7 @@ impl Market { /// use openbook::utils::read_keypair; /// use openbook::matching::Side; /// use openbook::market::OrderReturnType; + /// use openbook::tokens_and_markets::{DexVersion, Token}; /// /// #[tokio::main] /// async fn main() -> Result<(), Box> { @@ -765,13 +682,13 @@ impl Market { /// /// let keypair = read_keypair(&key_path); /// - /// let mut market = Market::new(rpc_client, 3, "jlp", "usdc", keypair, true).await; + /// let mut market = Market::new(rpc_client, DexVersion::default(), Token::JLP, Token::USDC, keypair, true).await?; /// - /// let target_amount_quote = 10.0; + /// let target_amount_quote = 5.0; /// let side = Side::Bid; // or Side::Ask - /// let best_offset_usdc = 0.5; + /// let best_offset_usdc = 5.0; /// let execute = true; - /// let target_price = 15.0; + /// let target_price = 2.1; /// /// if let Some(ord_ret_type) = market.place_limit_order( /// target_amount_quote, @@ -801,7 +718,7 @@ impl Market { best_offset_usdc: f64, execute: bool, target_price: f64, - ) -> anyhow::Result, ClientError> { + ) -> Result, Error> { // coin: base // pc: quote let base_d_factor = 10u32.pow(self.coin_decimals as u32) as f64; @@ -876,8 +793,7 @@ impl Market { u16::MAX, max_native_pc_qty_including_fees, (get_unix_secs() + 30) as i64, - ) - .unwrap(); + )?; let instructions = vec![place_order_ix]; @@ -934,6 +850,7 @@ impl Market { /// use openbook::market::OrderReturnType; /// use openbook::market::Market; /// use openbook::utils::read_keypair; + /// use openbook::tokens_and_markets::{DexVersion, Token}; /// /// #[tokio::main] /// async fn main() -> Result<(), Box> { @@ -944,7 +861,7 @@ impl Market { /// /// let keypair = read_keypair(&key_path); /// - /// let market = Market::new(rpc_client, 3, "jlp", "usdc", keypair, true).await; + /// let market = Market::new(rpc_client, DexVersion::default(), Token::JLP, Token::USDC, keypair, true).await?; /// /// if let Some(ord_ret_type) = market /// .cancel_orders(true) @@ -963,10 +880,7 @@ impl Market { /// Ok(()) /// } /// ``` - pub async fn cancel_orders( - &self, - execute: bool, - ) -> anyhow::Result, ClientError> { + pub async fn cancel_orders(&self, execute: bool) -> Result, Error> { let mut ixs = Vec::new(); for oid in &self.market_info.open_bids { @@ -980,8 +894,7 @@ impl Market { &self.event_queue, Side::Bid, *oid, - ) - .unwrap(); + )?; ixs.push(ix); } @@ -996,8 +909,7 @@ impl Market { &self.event_queue, Side::Ask, *oid, - ) - .unwrap(); + )?; ixs.push(ix); } @@ -1057,6 +969,7 @@ impl Market { /// use openbook::market::Market; /// use openbook::utils::read_keypair; /// use openbook::market::OrderReturnType; + /// use openbook::tokens_and_markets::{DexVersion, Token}; /// /// #[tokio::main] /// async fn main() -> Result<(), Box> { @@ -1067,7 +980,7 @@ impl Market { /// /// let keypair = read_keypair(&key_path); /// - /// let market = Market::new(rpc_client, 3, "jlp", "usdc", keypair, true).await; + /// let market = Market::new(rpc_client, DexVersion::default(), Token::JLP, Token::USDC, keypair, true).await?; /// /// if let Some(ord_ret_type) = market /// .settle_balance(true) @@ -1086,10 +999,7 @@ impl Market { /// Ok(()) /// } /// ``` - pub async fn settle_balance( - &self, - execute: bool, - ) -> anyhow::Result, ClientError> { + pub async fn settle_balance(&self, execute: bool) -> Result, Error> { let ix = openbook_dex::instruction::settle_funds( &self.program_id, &self.market_address, @@ -1102,8 +1012,7 @@ impl Market { &self.quote_ata, None, &self.vault_signer_key, - ) - .unwrap(); + )?; let instructions = vec![ix]; @@ -1155,6 +1064,7 @@ impl Market { /// use openbook::{pubkey::Pubkey, signature::Keypair, rpc_client::RpcClient}; /// use openbook::market::Market; /// use openbook::utils::read_keypair; + /// use openbook::tokens_and_markets::{DexVersion, Token}; /// /// #[tokio::main] /// async fn main() -> Result<(), Box> { @@ -1165,7 +1075,7 @@ impl Market { /// /// let keypair = read_keypair(&key_path); /// - /// let market = Market::new(rpc_client, 3, "jlp", "usdc", keypair, true).await; + /// let market = Market::new(rpc_client, DexVersion::default(), Token::JLP, Token::USDC, keypair, true).await?; /// /// let result = market.make_match_orders_transaction(100).await?; /// @@ -1177,7 +1087,7 @@ impl Market { pub async fn make_match_orders_transaction( &self, limit: u16, - ) -> anyhow::Result { + ) -> Result { let ix = openbook_dex::instruction::match_orders( &self.program_id, &self.market_address, @@ -1235,6 +1145,7 @@ impl Market { /// use openbook::{pubkey::Pubkey, signature::Keypair, rpc_client::RpcClient}; /// use openbook::market::Market; /// use openbook::utils::read_keypair; + /// use openbook::tokens_and_markets::{DexVersion, Token}; /// /// #[tokio::main] /// async fn main() -> Result<(), Box> { @@ -1245,7 +1156,7 @@ impl Market { /// /// let keypair = read_keypair(&key_path); /// - /// let mut market = Market::new(rpc_client, 3, "jlp", "usdc", keypair, true).await; + /// let mut market = Market::new(rpc_client, DexVersion::default(), Token::JLP, Token::USDC, keypair, true).await?; /// /// let target_size_usdc_ask = 0.5; /// let target_size_usdc_bid = 1.0; @@ -1265,7 +1176,7 @@ impl Market { target_size_usdc_bid: f64, bid_price_jlp_usdc: f64, ask_price_jlp_usdc: f64, - ) -> anyhow::Result> { + ) -> Result> { let mut instructions = Vec::new(); // Fetch recent prioritization fees @@ -1371,7 +1282,7 @@ impl Market { { Ok(sign) => Ok(Some(sign)), Err(err) => { - error!("err combo'ing: {err}, {}", kp_str); + error!("[*] err combo'ing: {err}, {}", kp_str); Ok(None) } } @@ -1394,6 +1305,7 @@ impl Market { /// use openbook::{pubkey::Pubkey, signature::Keypair, rpc_client::RpcClient}; /// use openbook::market::Market; /// use openbook::utils::read_keypair; + /// use openbook::tokens_and_markets::{DexVersion, Token}; /// /// #[tokio::main] /// async fn main() -> Result<(), Box> { @@ -1404,7 +1316,7 @@ impl Market { /// /// let keypair = read_keypair(&key_path); /// - /// let mut market = Market::new(rpc_client, 3, "jlp", "usdc", keypair, true).await; + /// let mut market = Market::new(rpc_client, DexVersion::default(), Token::JLP, Token::USDC, keypair, true).await?; /// /// let result = market.cancel_settle_place_bid(1.5, 1.0).await?; /// @@ -1417,7 +1329,7 @@ impl Market { &mut self, target_size_usdc_bid: f64, bid_price_jlp_usdc: f64, - ) -> anyhow::Result> { + ) -> Result> { let mut instructions = Vec::new(); // Fetch recent prioritization fees @@ -1504,10 +1416,10 @@ impl Market { { Ok(sign) => Ok(Some(sign)), Err(err) => { - error!("err bidding: {err}, {}", kp_str); + error!("[*] err bidding: {err}, {}", kp_str); let e = err.get_transaction_error(); if let Some(e) = e { - error!("got tx err: {e}"); + error!("[*] got tx err: {e}"); } Ok(None) } @@ -1531,6 +1443,7 @@ impl Market { /// use openbook::{pubkey::Pubkey, signature::Keypair, rpc_client::RpcClient}; /// use openbook::market::Market; /// use openbook::utils::read_keypair; + /// use openbook::tokens_and_markets::{DexVersion, Token}; /// /// #[tokio::main] /// async fn main() -> Result<(), Box> { @@ -1541,7 +1454,7 @@ impl Market { /// /// let keypair = read_keypair(&key_path); /// - /// let mut market = Market::new(rpc_client, 3, "jlp", "usdc", keypair, true).await; + /// let mut market = Market::new(rpc_client, DexVersion::default(), Token::JLP, Token::USDC, keypair, true).await?; /// /// let result = market.cancel_settle_place_ask(1.5, 1.0).await?; /// @@ -1554,7 +1467,7 @@ impl Market { &mut self, target_size_usdc_ask: f64, ask_price_jlp_usdc: f64, - ) -> anyhow::Result> { + ) -> Result> { let mut instructions = Vec::new(); // Fetch recent prioritization fees @@ -1642,7 +1555,7 @@ impl Market { { Ok(sign) => Ok(Some(sign)), Err(err) => { - error!("err asking: {err}, {}", kp_str); + error!("[*] err asking: {err}, {}", kp_str); Ok(None) } } @@ -1660,6 +1573,7 @@ impl Market { /// use openbook::{pubkey::Pubkey, signature::Keypair, rpc_client::RpcClient}; /// use openbook::market::Market; /// use openbook::utils::read_keypair; + /// use openbook::tokens_and_markets::{DexVersion, Token}; /// /// #[tokio::main] /// async fn main() -> Result<(), Box> { @@ -1670,7 +1584,7 @@ impl Market { /// /// let keypair = read_keypair(&key_path); /// - /// let mut market = Market::new(rpc_client, 3, "jlp", "usdc", keypair, true).await; + /// let mut market = Market::new(rpc_client, DexVersion::default(), Token::JLP, Token::USDC, keypair, true).await?; /// /// let result = market.cancel_settle().await?; /// @@ -1679,7 +1593,7 @@ impl Market { /// Ok(()) /// } /// ``` - pub async fn cancel_settle(&mut self) -> anyhow::Result> { + pub async fn cancel_settle(&mut self) -> Result> { let mut instructions = Vec::new(); // Fetch recent prioritization fees @@ -1748,7 +1662,7 @@ impl Market { { Ok(sign) => Ok(Some(sign)), Err(err) => { - error!("err canceling: {err}\npk: {}", kp_str); + error!("[*] err canceling: {err}\npk: {}", kp_str); Ok(None) } } @@ -1770,6 +1684,7 @@ impl Market { /// use openbook::{pubkey::Pubkey, signature::Keypair, rpc_client::RpcClient}; /// use openbook::market::Market; /// use openbook::utils::read_keypair; + /// use openbook::tokens_and_markets::{DexVersion, Token}; /// /// #[tokio::main] /// async fn main() -> Result<(), Box> { @@ -1780,7 +1695,7 @@ impl Market { /// /// let keypair = read_keypair(&key_path); /// - /// let mut market = Market::new(rpc_client, 3, "jlp", "usdc", keypair, true).await; + /// let mut market = Market::new(rpc_client, DexVersion::default(), Token::JLP, Token::USDC, keypair, true).await?; /// /// let result = market.load_bids()?; /// @@ -1809,6 +1724,7 @@ impl Market { /// use openbook::{pubkey::Pubkey, signature::Keypair, rpc_client::RpcClient}; /// use openbook::market::Market; /// use openbook::utils::read_keypair; + /// use openbook::tokens_and_markets::{DexVersion, Token}; /// /// #[tokio::main] /// async fn main() -> Result<(), Box> { @@ -1819,7 +1735,7 @@ impl Market { /// /// let keypair = read_keypair(&key_path); /// - /// let mut market = Market::new(rpc_client, 3, "jlp", "usdc", keypair, true).await; + /// let mut market = Market::new(rpc_client, DexVersion::default(), Token::JLP, Token::USDC, keypair, true).await?; /// /// let result = market.load_asks()?; /// @@ -1842,7 +1758,7 @@ impl Market { /// /// # Returns /// - /// A `Result` containing the `Signature` of the transaction or a `ClientError` if an error occurs. + /// A `Result` containing the `Signature` of the transaction or a `Error` if an error occurs. /// /// # Examples /// @@ -1850,6 +1766,7 @@ impl Market { /// use openbook::{pubkey::Pubkey, signature::Keypair, rpc_client::RpcClient}; /// use openbook::market::Market; /// use openbook::utils::read_keypair; + /// use openbook::tokens_and_markets::{DexVersion, Token}; /// /// #[tokio::main] /// async fn main() -> Result<(), Box> { @@ -1860,7 +1777,7 @@ impl Market { /// /// let keypair = read_keypair(&key_path); /// - /// let market = Market::new(rpc_client, 3, "jlp", "usdc", keypair, true).await; + /// let market = Market::new(rpc_client, DexVersion::default(), Token::JLP, Token::USDC, keypair, true).await?; /// /// let open_orders_accounts = vec![Pubkey::new_from_array([0; 32])]; /// let limit = 10; @@ -1909,7 +1826,7 @@ impl Market { /// /// # Returns /// - /// A `Result` containing the `Signature` of the transaction or a `ClientError` if an error occurs. + /// A `Result` containing the `Signature` of the transaction or a `Error` if an error occurs. /// /// # Examples /// @@ -1917,6 +1834,7 @@ impl Market { /// use openbook::{pubkey::Pubkey, signature::Keypair, rpc_client::RpcClient}; /// use openbook::market::Market; /// use openbook::utils::read_keypair; + /// use openbook::tokens_and_markets::{DexVersion, Token}; /// /// #[tokio::main] /// async fn main() -> Result<(), Box> { @@ -1927,7 +1845,7 @@ impl Market { /// /// let keypair = read_keypair(&key_path); /// - /// let market = Market::new(rpc_client, 3, "jlp", "usdc", keypair, true).await; + /// let market = Market::new(rpc_client, DexVersion::default(), Token::JLP, Token::USDC, keypair, true).await?; /// /// let open_orders_accounts = vec![Pubkey::new_from_array([0; 32])]; /// let limit = 10; @@ -1984,6 +1902,7 @@ impl Market { /// use openbook::{pubkey::Pubkey, signature::Keypair, rpc_client::RpcClient}; /// use openbook::market::Market; /// use openbook::utils::read_keypair; + /// use openbook::tokens_and_markets::{DexVersion, Token}; /// /// #[tokio::main] /// async fn main() -> Result<(), Box> { @@ -1994,7 +1913,7 @@ impl Market { /// /// let keypair = read_keypair(&key_path); /// - /// let mut market = Market::new(rpc_client, 3, "jlp", "usdc", keypair, true).await; + /// let mut market = Market::new(rpc_client, DexVersion::default(), Token::JLP, Token::USDC, keypair, true).await?; /// /// let result = market.load_orders_for_owner().await?; /// @@ -2036,6 +1955,7 @@ impl Market { /// use openbook::{pubkey::Pubkey, signature::Keypair, rpc_client::RpcClient}; /// use openbook::market::Market; /// use openbook::utils::read_keypair; + /// use openbook::tokens_and_markets::{DexVersion, Token}; /// /// #[tokio::main] /// async fn main() -> Result<(), Box> { @@ -2046,7 +1966,7 @@ impl Market { /// /// let keypair = read_keypair(&key_path); /// - /// let market = Market::new(rpc_client, 3, "jlp", "usdc", keypair, true).await; + /// let market = Market::new(rpc_client, DexVersion::default(), Token::JLP, Token::USDC, keypair, true).await?; /// /// let bids = market.market_info.clone(); /// let asks = market.market_info.clone(); @@ -2094,6 +2014,7 @@ impl Market { /// use openbook::{pubkey::Pubkey, signature::Keypair, rpc_client::RpcClient}; /// use openbook::market::Market; /// use openbook::utils::read_keypair; + /// use openbook::tokens_and_markets::{DexVersion, Token}; /// /// #[tokio::main] /// async fn main() -> Result<(), Box> { @@ -2104,7 +2025,7 @@ impl Market { /// /// let keypair = read_keypair(&key_path); /// - /// let mut market = Market::new(rpc_client, 3, "jlp", "usdc", keypair, true).await; + /// let mut market = Market::new(rpc_client, DexVersion::default(), Token::JLP, Token::USDC, keypair, true).await?; /// let owner_address = &Pubkey::new_from_array([0; 32]); /// /// let result = market.find_open_orders_accounts_for_owner(&owner_address, 5000).await?; diff --git a/src/orders.rs b/src/orders.rs index 4cc531d..475cde2 100644 --- a/src/orders.rs +++ b/src/orders.rs @@ -1,7 +1,11 @@ //! This module contains structs and functions related to open orders on the Solana blockchain. #![allow(dead_code, deprecated)] -use crate::{rpc::Rpc, tokens_and_markets::get_layout_version}; +use crate::{ + rpc::Rpc, + tokens_and_markets::{get_layout_version, DexVersion}, +}; +use anyhow::{anyhow, Error, Result}; use borsh::{BorshDeserialize, BorshSerialize}; use memoffset::offset_of; use solana_client::{ @@ -14,7 +18,8 @@ use solana_sdk::{ nonce::state::Data as NonceData, pubkey::Pubkey, signature::Signer, signer::keypair::Keypair, transaction::Transaction, }; -use std::{borrow::Borrow, convert::TryInto, error::Error}; +use std::{borrow::Borrow, convert::TryInto}; +use tracing::{debug, error}; /// Struct representing an open orders account. #[derive(Debug, BorshDeserialize, BorshSerialize, Clone)] @@ -58,17 +63,23 @@ impl OpenOrders { /// /// # Arguments /// - /// * `address` - The public key of the open orders account. - /// * `decoded` - The decoded layout data of the open orders account. - /// * `_program_id` - The program ID associated with the open orders. + /// * `rpc_client` - RPC client for interacting with Solana blockchain. + /// * `program_id` - Program dex pub key representing the market. + /// * `keypair` - Keypair of the owner used for signing transactions. + /// * `market_address` - Market address pub key. /// /// # Returns /// /// An instance of `OpenOrders`. - pub fn new(address: Pubkey, decoded: OpenOrdersLayoutV1, _program_id: Pubkey) -> Self { + pub async fn new( + rpc_client: Rpc, + program_id: Pubkey, + keypair: Keypair, + market_address: Pubkey, + ) -> Result { let OpenOrdersLayoutV1 { market, - owner, + owner: _, base_token_free, base_token_total, quote_token_free, @@ -79,12 +90,12 @@ impl OpenOrders { client_ids, account_flags: _, padding: _, - } = decoded; + } = Default::default(); - Self { - address, + let mut oo_account = Self { + address: Default::default(), market: market.into(), - owner: owner.into(), + owner: keypair.pubkey(), base_token_free, base_token_total, quote_token_free, @@ -93,13 +104,19 @@ impl OpenOrders { is_bid_bits: is_bid_bits.try_into().unwrap(), orders: orders.into(), client_ids: client_ids.into(), - } + }; + + let _ = oo_account + .make_create_account_transaction(&rpc_client, program_id, &keypair, market_address) + .await?; + + Ok(oo_account) } /// Returns the layout size of the `OpenOrders` struct based on the program ID. pub fn get_layout(program_id: Pubkey) -> usize { match get_layout_version(&program_id) { - 1 => std::mem::size_of::(), + DexVersion::DepDexV1 => std::mem::size_of::(), _ => std::mem::size_of::(), } } @@ -123,7 +140,7 @@ impl OpenOrders { owner_address: Pubkey, market_address: Pubkey, program_id: Pubkey, - ) -> Result> { + ) -> Result { let seed = market_address .to_string() .chars() @@ -152,7 +169,7 @@ impl OpenOrders { connection: &Rpc, owner_address: Pubkey, program_id: Pubkey, - ) -> Result, Box> { + ) -> Result, Error> { let offset = offset_of!(OpenOrdersLayoutV1, owner); let filters = vec![ RpcFilterType::Memcmp(Memcmp { @@ -202,7 +219,7 @@ impl OpenOrders { market_address: Pubkey, owner_address: Pubkey, force_seed_account: bool, - ) -> Result, Box> { + ) -> Result, Error> { let _account_info = connection.inner().get_account(&owner_address).await?; if force_seed_account { @@ -257,7 +274,7 @@ impl OpenOrders { connection: &Rpc, address: Pubkey, program_id: Pubkey, - ) -> Result> { + ) -> Result { let account = connection.inner().get_account(&address).await?; OpenOrders::from_account_info(account, program_id) } @@ -276,17 +293,14 @@ impl OpenOrders { /// # Errors /// /// Returns a `Box` if there is an error during deserialization or ownership check. - pub fn from_account_info( - mut account_info: Account, - program_id: Pubkey, - ) -> Result> { + pub fn from_account_info(mut account_info: Account, program_id: Pubkey) -> Result { // Fix: Not all bytes read let data_size: usize = 165; account_info.data.resize(data_size, 0); let decoded = OpenOrdersLayoutV1::try_from_slice(account_info.data.borrow())?; let _account_flags = decoded.account_flags; if !account_info.owner.eq(&program_id) { - return Err("Address not owned by program".into()); + return Err(anyhow!("Address not owned by program")); } // if !account_flags.initialized || !account_flags.open_orders { @@ -341,11 +355,12 @@ impl OpenOrders { /// /// Returns a `Box` if there is an error during the RPC call or transaction creation. pub async fn make_create_account_transaction( + &mut self, connection: &Rpc, program_id: Pubkey, keypair: &Keypair, market_account: Pubkey, - ) -> Result> { + ) -> Result { let new_account_address = Keypair::new(); let space = 0; let minimum_balance = connection @@ -383,8 +398,8 @@ impl OpenOrders { &market_account, None, )?; - println!( - "Got New Account Address: {:?}", + debug!( + "[*] Got New Account Address: {:?}", new_account_address.pubkey() ); @@ -409,7 +424,7 @@ impl OpenOrders { instructions.push(instruction); instructions.push(init_ix); - println!("Using Pubkey: {}", &keypair.pubkey().to_string()); + debug!("[*] Using Pubkey: {}", &keypair.pubkey().to_string()); let recent_hash = connection.inner().get_latest_blockhash().await?; let txn = Transaction::new_signed_with_payer( @@ -428,10 +443,12 @@ impl OpenOrders { .await; match result { - Ok(sig) => println!("Transaction successful, signature: {:?}", sig), - Err(err) => println!("Transaction failed: {:?}", err), + Ok(sig) => debug!("[*] Transaction successful, signature: {:?}", sig), + Err(err) => error!("[*] Transaction failed: {:?}", err), }; + self.address = new_account_address.pubkey(); + Ok(new_account_address.pubkey()) } @@ -571,13 +588,13 @@ impl Default for OpenOrdersLayoutV2 { } } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct OpenOrdersCacheEntry { pub accounts: Vec, pub ts: u128, } -#[derive(Debug, Clone, Default)] +#[derive(Debug, Clone, Default, BorshDeserialize, BorshSerialize)] pub struct MarketInfo { pub min_ask: u64, pub max_bid: u64, diff --git a/src/rpc.rs b/src/rpc.rs index 5eae4e6..5dde93f 100644 --- a/src/rpc.rs +++ b/src/rpc.rs @@ -21,6 +21,7 @@ use solana_transaction_status::{EncodedConfirmedTransactionWithStatusMeta, UiTra /// Wrapper type for RpcClient providing additional functionality and enabling Debug trait implementation. /// /// This struct holds an `Arc` of `RpcClient` to ensure thread safety and efficient resource sharing. +#[derive(Clone)] pub struct Rpc(Arc); impl Rpc { diff --git a/src/tokens_and_markets.rs b/src/tokens_and_markets.rs index 7a97e19..d77429c 100644 --- a/src/tokens_and_markets.rs +++ b/src/tokens_and_markets.rs @@ -2,57 +2,112 @@ use solana_sdk::pubkey::Pubkey; +/// DEX versions, dep means deprecated +#[derive(Debug, PartialEq, PartialOrd, Default, Clone, Copy)] +pub enum DexVersion { + /// DEX Version 1 + DepDexV0, + /// DEX Version 1 + DepDexV1, + /// DEX Version 2 + DexV2, + /// DEX Version 3 + #[default] + DexV3, +} + +/// Represents tokens associated with markets on the Solana blockchain. +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum Token { + SOL, + USDC, + SLND, + RAY, + ETH, + MNDE, + JLP, + // TODO: Add more tokens +} + +impl Token { + /// Parse a string into a Token enum value, ignoring case. + pub fn from_str(s: &str) -> Option { + match s.to_lowercase().as_str() { + "sol" => Some(Token::SOL), + "usdc" => Some(Token::USDC), + "slnd" => Some(Token::SLND), + "ray" => Some(Token::RAY), + "eth" => Some(Token::ETH), + "mnde" => Some(Token::MNDE), + "jlp" => Some(Token::JLP), + _ => None, + } + } +} + /// Represents the layout versions associated with Solana programs. /// /// This static array contains tuples where the first element represents the program ID in string format, /// and the second element represents the associated layout version. -pub static PROGRAM_LAYOUT_VERSIONS: [(&str, u8); 4] = [ - ("4ckmDgGdxQoPDLUkDT3vHgSAkzA3QRdNq5ywwY4sUSJn", 1), // DEX Version 1 - ("BJ3jrUzddfuSrZHXSCxMUUQsjKEyLmuuyZebkcaFp2fg", 1), // DEX Version 1 - ("EUqojwWA2rd19FZrzeBncJsm38Jm1hEhE3zsmX3bRc2o", 2), // DEX Version 2 - ("srmqPvymJeFKQ4zGQed1GFppgkRHL9kaELCbyksJtPX", 3), // DEX Version 3 +pub static PROGRAM_LAYOUT_VERSIONS: [(&str, DexVersion); 4] = [ + ( + "4ckmDgGdxQoPDLUkDT3vHgSAkzA3QRdNq5ywwY4sUSJn", + DexVersion::DepDexV0, + ), + ( + "BJ3jrUzddfuSrZHXSCxMUUQsjKEyLmuuyZebkcaFp2fg", + DexVersion::DepDexV1, + ), + ( + "EUqojwWA2rd19FZrzeBncJsm38Jm1hEhE3zsmX3bRc2o", + DexVersion::DexV2, + ), + ( + "srmqPvymJeFKQ4zGQed1GFppgkRHL9kaELCbyksJtPX", + DexVersion::DexV3, + ), ]; -/// Represents openbook market ids, and base mints associated with the tokens. +/// Represents openbook market ids, base mints, and associated tokens. /// /// This static array contains tuples where the first element represents the market ID, /// the second element represents the associated base mint, and the third element represents -/// the associated token. -pub static MARKET_IDS_TO_NAMES: [(&str, &str, &str); 7] = [ +/// the associated token, expressed as an enum. +pub static MARKET_IDS_TO_NAMES: [(&'static str, &'static str, Token); 7] = [ ( "8BnEgHoWFysVcuFFX7QztDmzuH8r5ZFvyP3sYwn1XTh6", "So11111111111111111111111111111111111111112", - "sol", + Token::SOL, ), ( "8BnEgHoWFysVcuFFX7QztDmzuH8r5ZFvyP3sYwn1XTh6", "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", - "usdc", + Token::USDC, ), ( "HTHMfoxePjcXFhrV74pfCUNoWGe374ecFwiDjPGTkzHr", "SLNDpmoWTVADgEdndyvWzroNL7zSi1dF9PC3xHGtPwp", - "slnd", + Token::SLND, ), ( "DZjbn4XC8qoHKikZqzmhemykVzmossoayV9ffbsUqxVj", "4k3Dyjzvzp8eMZWUXbBCjEvwSkkk59S5iCNLY3QrkX6R", - "ray", + Token::RAY, ), ( "BbJgE7HZMaDp5NTYvRh5jZSkQPVDTU8ubPFtpogUkEj4", "7vfCXTUXx5WJV5JADk17DUJ4ksgau7utNKj4b963voxs", - "eth", + Token::ETH, ), ( "CC9VYJprbxacpiS94tPJ1GyBhfvrLQbUiUSVMWvFohNW", "MNDEFzGvMt87ueuHvVU9VcTqsAP5b3fTGPsHuuPA5ey", - "mnde", + Token::MNDE, ), ( "ASUyMMNBpFzpW3zDSPYdDVggKajq1DMKFFPK1JS9hoSR", "27G8MtK7VtTcCHkpASjSDdkWWYfoqT6ggEuKidVJidD4", - "jlp", + Token::JLP, ), // TODO: add all markets ]; @@ -72,18 +127,19 @@ pub static MARKET_IDS_TO_NAMES: [(&str, &str, &str); 7] = [ /// ```rust /// use openbook::pubkey::Pubkey; /// use openbook::tokens_and_markets::get_layout_version; +/// use openbook::tokens_and_markets::DexVersion; /// /// let program_id = Pubkey::new_unique(); /// let version = get_layout_version(&program_id); /// -/// assert_eq!(version, 3); +/// assert_eq!(version, DexVersion::DexV3); /// ``` -pub fn get_layout_version(program_id: &Pubkey) -> u8 { +pub fn get_layout_version(program_id: &Pubkey) -> DexVersion { PROGRAM_LAYOUT_VERSIONS .iter() .find(|(id, _)| *id == program_id.to_string()) .map(|(_, version)| *version) - .unwrap_or(3) + .unwrap_or(DexVersion::DexV3) } /// Gets the program ID for the given layout version. @@ -102,12 +158,13 @@ pub fn get_layout_version(program_id: &Pubkey) -> u8 { /// ```rust /// use openbook::pubkey::Pubkey; /// use openbook::tokens_and_markets::get_program_id; +/// use openbook::tokens_and_markets::DexVersion; /// -/// let program_id = get_program_id(3); +/// let program_id = get_program_id(DexVersion::default()); /// /// assert_eq!(&program_id, "srmqPvymJeFKQ4zGQed1GFppgkRHL9kaELCbyksJtPX"); /// ``` -pub fn get_program_id(version: u8) -> String { +pub fn get_program_id(version: DexVersion) -> String { PROGRAM_LAYOUT_VERSIONS .iter() .find(|(_, v)| *v == version) @@ -131,12 +188,13 @@ pub fn get_program_id(version: u8) -> String { /// ```rust /// use openbook::pubkey::Pubkey; /// use openbook::tokens_and_markets::get_market_name; +/// use openbook::tokens_and_markets::Token; /// -/// let market_id = get_market_name("usdc").0; +/// let market_id = get_market_name(Token::USDC).0; /// /// assert_eq!(&market_id, "8BnEgHoWFysVcuFFX7QztDmzuH8r5ZFvyP3sYwn1XTh6"); /// ``` -pub fn get_market_name(market_name: &str) -> (String, String) { +pub fn get_market_name(market_name: Token) -> (String, String) { MARKET_IDS_TO_NAMES .iter() .find(|(_, _, val)| *val == market_name) diff --git a/src/tui.rs b/src/tui.rs index 1f78092..37ae08f 100644 --- a/src/tui.rs +++ b/src/tui.rs @@ -19,8 +19,8 @@ use ratatui::{ use strum::{Display, EnumIter, FromRepr, IntoEnumIterator}; +use solana_sdk::signature::Signer; use std::collections::HashMap; -use std::{error::Error, io}; use tui_input::backend::crossterm::EventHandler; use tui_input::Input; @@ -31,6 +31,9 @@ use crate::matching::Side; use crate::rpc_client::RpcClient; use crate::utils::read_keypair; +use crate::tokens_and_markets::{DexVersion, Token}; +use anyhow::{Error, Result}; + enum InputMode { Normal, Editing, @@ -102,7 +105,8 @@ struct App { input_mode: InputMode, transaction_status: String, current_input: Option, - messages: HashMap, + market_info: HashMap, + wallet_info: HashMap, market: Option, selected_tab: SelectedTab, } @@ -119,17 +123,18 @@ impl Default for App { transaction_status: Default::default(), input_mode: InputMode::Normal, current_input: Some(CurrentInput::RpcUrl), - messages: HashMap::new(), + market_info: HashMap::new(), + wallet_info: HashMap::new(), market: None, selected_tab: Default::default(), } } } -pub async fn run_tui() -> Result<(), Box> { +pub async fn run_tui() -> Result<(), Error> { // setup terminal enable_raw_mode()?; - let mut stdout = io::stdout(); + let mut stdout = std::io::stdout(); execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?; let backend = CrosstermBackend::new(stdout); let mut terminal = Terminal::new(backend)?; @@ -154,7 +159,7 @@ pub async fn run_tui() -> Result<(), Box> { Ok(()) } -async fn run_app(terminal: &mut Terminal, mut app: App) -> io::Result<()> { +async fn run_app(terminal: &mut Terminal, mut app: App) -> Result<(), Error> { loop { terminal.draw(|f| ui(f, &mut app))?; @@ -188,56 +193,55 @@ async fn run_app(terminal: &mut Terminal, mut app: App) -> io::Re assert_eq!(rpc_client.commitment(), CommitmentConfig::confirmed()); let base_mint = - Box::leak(app.base_mint_input.value().to_string().into_boxed_str()); + Token::from_str(app.base_mint_input.value()).unwrap_or(Token::JLP); let quote_mint = - Box::leak(app.quote_mint_input.value().to_string().into_boxed_str()); + Token::from_str(app.quote_mint_input.value()).unwrap_or(Token::USDC); if app.market.is_none() { app.market = Some( - Market::new(rpc_client, 3, base_mint, quote_mint, owner, true) - .await, + Market::new( + rpc_client, + DexVersion::default(), + base_mint, + quote_mint, + owner, + true, + ) + .await?, ); } match app.selected_tab { SelectedTab::Tab1 => { - app.messages.insert( + app.market_info.insert( "Market Address".to_string(), app.market.as_ref().unwrap().market_address.to_string(), ); - app.messages.insert( - "Base ATA".to_string(), - app.market.as_ref().unwrap().base_ata.to_string(), - ); - app.messages.insert( - "Quote ATA".to_string(), - app.market.as_ref().unwrap().quote_ata.to_string(), + app.market_info.insert( + "Base Mint".to_string(), + app.market.as_ref().unwrap().base_mint.to_string(), ); - app.messages.insert( - "Mint ATA".to_string(), - app.market.as_ref().unwrap().ata_address.to_string(), + app.market_info.insert( + "Quote Mint".to_string(), + app.market.as_ref().unwrap().quote_mint.to_string(), ); - app.messages.insert( + app.market_info.insert( "Coin Vault".to_string(), app.market.as_ref().unwrap().coin_vault.to_string(), ); - app.messages.insert( + app.market_info.insert( "PC Vault".to_string(), app.market.as_ref().unwrap().pc_vault.to_string(), ); - app.messages.insert( - "Vault Signer Key".to_string(), - app.market.as_ref().unwrap().vault_signer_key.to_string(), - ); - app.messages.insert( + app.market_info.insert( "Event Queue".to_string(), app.market.as_ref().unwrap().event_queue.to_string(), ); - app.messages.insert( + app.market_info.insert( "Request Queue".to_string(), app.market.as_ref().unwrap().request_queue.to_string(), ); - app.messages.insert( + app.market_info.insert( "Asks Address".to_string(), app.market .as_ref() @@ -246,7 +250,7 @@ async fn run_app(terminal: &mut Terminal, mut app: App) -> io::Re .asks_address .to_string(), ); - app.messages.insert( + app.market_info.insert( "Bids Address".to_string(), app.market .as_ref() @@ -255,22 +259,75 @@ async fn run_app(terminal: &mut Terminal, mut app: App) -> io::Re .bids_address .to_string(), ); - app.messages.insert( + app.market_info.insert( "Coin Decimals".to_string(), app.market.as_ref().unwrap().coin_decimals.to_string(), ); - app.messages.insert( + app.market_info.insert( "PC Decimals".to_string(), app.market.as_ref().unwrap().pc_decimals.to_string(), ); - app.messages.insert( + app.market_info.insert( "Coin Lot Size".to_string(), app.market.as_ref().unwrap().coin_lot_size.to_string(), ); - app.messages.insert( + app.market_info.insert( "PC Lot Size".to_string(), app.market.as_ref().unwrap().pc_lot_size.to_string(), ); + + app.wallet_info.insert( + "Wallet Public Key".to_string(), + app.market.as_ref().unwrap().owner.pubkey().to_string(), + ); + app.wallet_info.insert( + "Base ATA".to_string(), + app.market.as_ref().unwrap().base_ata.to_string(), + ); + app.wallet_info.insert( + "Quote ATA".to_string(), + app.market.as_ref().unwrap().quote_ata.to_string(), + ); + app.wallet_info.insert( + "Vault Signer Key".to_string(), + app.market.as_ref().unwrap().vault_signer_key.to_string(), + ); + app.wallet_info.insert( + "Open Order Account".to_string(), + app.market.as_ref().unwrap().orders_key.to_string(), + ); + app.wallet_info.insert( + "Min Ask".to_string(), + app.market.as_ref().unwrap().market_info.min_ask.to_string(), + ); + app.wallet_info.insert( + "Max Bid".to_string(), + app.market.as_ref().unwrap().market_info.max_bid.to_string(), + ); + + let open_asks: String = app + .market + .as_ref() + .unwrap() + .market_info + .open_asks + .iter() + .map(|&x| x.to_string()) + .collect::>() + .join(", "); + app.wallet_info.insert("Open Asks".to_string(), open_asks); + + let open_bids: String = app + .market + .as_ref() + .unwrap() + .market_info + .open_bids + .iter() + .map(|&x| x.to_string()) + .collect::>() + .join(", "); + app.wallet_info.insert("Open Bids".to_string(), open_bids); } SelectedTab::Tab2 => { let side = match app.side_input.value() { @@ -285,8 +342,7 @@ async fn run_app(terminal: &mut Terminal, mut app: App) -> io::Re .as_ref() .unwrap() .place_limit_order(5.0, side, 5.0, true, price) - .await - .unwrap(); + .await?; match result { Some(OrderReturnType::Signature(signature)) => { app.transaction_status = format!( @@ -630,16 +686,28 @@ fn ui(frame: &mut Frame, app: &mut App) { } } - let messages: Vec = app - .messages + let market_info: Vec = app + .market_info .iter() .map(|(key, val)| { let content = vec![Line::from(Span::raw(format!("{}: {}", key, val)))]; ListItem::new(content) }) .collect(); - let messages = - List::new(messages).block(Block::default().borders(Borders::ALL).title("Market Info")); + + let wallet_info: Vec = app + .wallet_info + .iter() + .map(|(key, val)| { + let content = vec![Line::from(Span::raw(format!("{}: {}", key, val)))]; + ListItem::new(content) + }) + .collect(); + let market_info = + List::new(market_info).block(Block::default().borders(Borders::ALL).title("Market Info")); + + let wallet_info = + List::new(wallet_info).block(Block::default().borders(Borders::ALL).title("Wallet Info")); let transaction_status = Paragraph::new(app.transaction_status.clone()) .block(Block::default().borders(Borders::ALL).title("Response")); @@ -649,7 +717,11 @@ fn ui(frame: &mut Frame, app: &mut App) { match app.selected_tab { SelectedTab::Tab1 => { - frame.render_widget(messages, third_row_layout[0]); + let third_row_info_layout = + Layout::new(Direction::Horizontal, [Percentage(50), Percentage(50)]) + .split(third_row_layout[0]); + frame.render_widget(market_info, third_row_info_layout[0]); + frame.render_widget(wallet_info, third_row_info_layout[1]); } SelectedTab::Tab2 => { frame.render_widget(side_input, order_row_layout[0]); diff --git a/tests/market.rs b/tests/market.rs index 79f19e6..29700d5 100644 --- a/tests/market.rs +++ b/tests/market.rs @@ -1,9 +1,10 @@ use openbook::market::Market; use openbook::rpc_client::RpcClient; +use openbook::tokens_and_markets::{DexVersion, Token}; use openbook::utils::read_keypair; #[tokio::test] -async fn test_market_state_info() { +async fn test_market_state_info() -> anyhow::Result<(), anyhow::Error> { let rpc_url = std::env::var("RPC_URL").expect("RPC_URL is not set in .env file"); let key_path = std::env::var("KEY_PATH").expect("KEY_PATH is not set in .env file"); @@ -11,7 +12,15 @@ async fn test_market_state_info() { let keypair = read_keypair(&key_path); - let market = Market::new(rpc_client, 3, "jlp", "usdc", keypair, true).await; + let market = Market::new( + rpc_client, + DexVersion::default(), + Token::JLP, + Token::USDC, + keypair, + true, + ) + .await?; assert_eq!( &market.program_id.to_string(), @@ -53,4 +62,6 @@ async fn test_market_state_info() { assert_eq!(market.coin_lot_size, 100000); assert_eq!(market.pc_lot_size, 10); + + Ok(()) } diff --git a/tests/tokens_and_markets.rs b/tests/tokens_and_markets.rs index 0136c2f..4fcf9d2 100644 --- a/tests/tokens_and_markets.rs +++ b/tests/tokens_and_markets.rs @@ -1,3 +1,4 @@ +use openbook::tokens_and_markets::DexVersion; use openbook::{ pubkey::Pubkey, tokens_and_markets::{get_layout_version, get_program_id}, @@ -8,35 +9,30 @@ use std::str::FromStr; fn test_get_layout_version_known_id() { let known_program_id = Pubkey::from_str("4ckmDgGdxQoPDLUkDT3vHgSAkzA3QRdNq5ywwY4sUSJn").unwrap(); - assert_eq!(get_layout_version(&known_program_id), 1); + assert_eq!( + get_layout_version(&known_program_id), + DexVersion::DepDexV0 + ); } #[test] fn test_get_layout_version_non_existing_id() { let program_id = Pubkey::new_unique(); - assert_eq!(get_layout_version(&program_id), 3); + assert_eq!(get_layout_version(&program_id), DexVersion::DexV3); } #[test] -fn test_get_program_id_existing_version() { +fn test_get_program_id_version() { assert_eq!( - &get_program_id(1), + &get_program_id(DexVersion::DepDexV0), "4ckmDgGdxQoPDLUkDT3vHgSAkzA3QRdNq5ywwY4sUSJn" ); assert_eq!( - &get_program_id(2), + &get_program_id(DexVersion::DexV2), "EUqojwWA2rd19FZrzeBncJsm38Jm1hEhE3zsmX3bRc2o" ); assert_eq!( - &get_program_id(3), - "srmqPvymJeFKQ4zGQed1GFppgkRHL9kaELCbyksJtPX" - ); -} - -#[test] -fn test_get_program_id_non_existing_version() { - assert_eq!( - &get_program_id(4), + &get_program_id(DexVersion::DexV3), "srmqPvymJeFKQ4zGQed1GFppgkRHL9kaELCbyksJtPX" ); }