1use solana_client::nonblocking::rpc_client::RpcClient;
7use solana_commitment_config::CommitmentConfig;
8use solana_sdk::{
9 hash::Hash,
10 pubkey::Pubkey,
11 signature::Keypair,
12 transaction::Transaction,
13};
14
15use crate::program::accounts::{Exchange, Market, OrderStatus, Position, UserNonce};
16use crate::program::constants::PROGRAM_ID;
17use crate::program::ed25519::{create_cross_ref_ed25519_instructions, create_order_verify_instruction};
18use crate::program::error::{SdkError, SdkResult};
19use crate::program::instructions::*;
20use crate::program::orders::{derive_condition_id, FullOrder};
21use crate::program::pda::{
22 get_all_conditional_mint_pdas, get_exchange_pda, get_market_pda, get_order_status_pda,
23 get_position_pda, get_user_nonce_pda, Pda,
24};
25use crate::program::types::*;
26
27pub struct LightconePinocchioClient {
29 pub rpc_client: RpcClient,
31 pub program_id: Pubkey,
33}
34
35impl LightconePinocchioClient {
36 pub fn new(rpc_url: &str) -> Self {
38 Self {
39 rpc_client: RpcClient::new_with_commitment(
40 rpc_url.to_string(),
41 CommitmentConfig::confirmed(),
42 ),
43 program_id: *PROGRAM_ID,
44 }
45 }
46
47 pub fn with_program_id(rpc_url: &str, program_id: Pubkey) -> Self {
49 Self {
50 rpc_client: RpcClient::new_with_commitment(
51 rpc_url.to_string(),
52 CommitmentConfig::confirmed(),
53 ),
54 program_id,
55 }
56 }
57
58 pub fn from_rpc_client(rpc_client: RpcClient) -> Self {
60 Self {
61 rpc_client,
62 program_id: *PROGRAM_ID,
63 }
64 }
65
66 pub fn pda(&self) -> &Pda {
68 &Pda
69 }
70
71 pub async fn get_exchange(&self) -> SdkResult<Exchange> {
77 let (pda, _) = get_exchange_pda(&self.program_id);
78 let account = self
79 .rpc_client
80 .get_account(&pda)
81 .await
82 .map_err(|e| SdkError::AccountNotFound(format!("Exchange: {}", e)))?;
83 Exchange::deserialize(&account.data)
84 }
85
86 pub async fn get_market(&self, market_id: u64) -> SdkResult<Market> {
88 let (pda, _) = get_market_pda(market_id, &self.program_id);
89 self.get_market_by_pubkey(&pda).await
90 }
91
92 pub async fn get_market_by_pubkey(&self, market: &Pubkey) -> SdkResult<Market> {
94 let account = self
95 .rpc_client
96 .get_account(market)
97 .await
98 .map_err(|e| SdkError::AccountNotFound(format!("Market: {}", e)))?;
99 Market::deserialize(&account.data)
100 }
101
102 pub async fn get_position(
104 &self,
105 owner: &Pubkey,
106 market: &Pubkey,
107 ) -> SdkResult<Option<Position>> {
108 let (pda, _) = get_position_pda(owner, market, &self.program_id);
109 match self.rpc_client.get_account(&pda).await {
110 Ok(account) => Ok(Some(Position::deserialize(&account.data)?)),
111 Err(_) => Ok(None),
112 }
113 }
114
115 pub async fn get_order_status(&self, order_hash: &[u8; 32]) -> SdkResult<Option<OrderStatus>> {
117 let (pda, _) = get_order_status_pda(order_hash, &self.program_id);
118 match self.rpc_client.get_account(&pda).await {
119 Ok(account) => Ok(Some(OrderStatus::deserialize(&account.data)?)),
120 Err(_) => Ok(None),
121 }
122 }
123
124 pub async fn get_user_nonce(&self, user: &Pubkey) -> SdkResult<u64> {
126 let (pda, _) = get_user_nonce_pda(user, &self.program_id);
127 match self.rpc_client.get_account(&pda).await {
128 Ok(account) => {
129 let user_nonce = UserNonce::deserialize(&account.data)?;
130 Ok(user_nonce.nonce)
131 }
132 Err(_) => Ok(0),
133 }
134 }
135
136 pub async fn get_next_nonce(&self, user: &Pubkey) -> SdkResult<u64> {
141 self.get_user_nonce(user).await
142 }
143
144 pub async fn get_next_market_id(&self) -> SdkResult<u64> {
146 let exchange = self.get_exchange().await?;
147 Ok(exchange.market_count)
148 }
149
150 pub async fn get_latest_blockhash(&self) -> SdkResult<Hash> {
156 self.rpc_client
157 .get_latest_blockhash()
158 .await
159 .map_err(SdkError::Rpc)
160 }
161
162 pub async fn initialize(&self, authority: &Pubkey) -> SdkResult<Transaction> {
164 let ix = build_initialize_ix(authority, &self.program_id);
165 Ok(Transaction::new_with_payer(&[ix], Some(authority)))
166 }
167
168 pub async fn create_market(&self, params: CreateMarketParams) -> SdkResult<Transaction> {
170 let market_id = self.get_next_market_id().await?;
171 let ix = build_create_market_ix(¶ms, market_id, &self.program_id)?;
172 Ok(Transaction::new_with_payer(&[ix], Some(¶ms.authority)))
173 }
174
175 pub async fn add_deposit_mint(
177 &self,
178 params: AddDepositMintParams,
179 market: &Pubkey,
180 num_outcomes: u8,
181 ) -> SdkResult<Transaction> {
182 let ix = build_add_deposit_mint_ix(¶ms, market, num_outcomes, &self.program_id)?;
183 Ok(Transaction::new_with_payer(&[ix], Some(¶ms.payer)))
184 }
185
186 pub async fn mint_complete_set(
188 &self,
189 params: MintCompleteSetParams,
190 num_outcomes: u8,
191 ) -> SdkResult<Transaction> {
192 let ix = build_mint_complete_set_ix(¶ms, num_outcomes, &self.program_id);
193 Ok(Transaction::new_with_payer(&[ix], Some(¶ms.user)))
194 }
195
196 pub async fn merge_complete_set(
198 &self,
199 params: MergeCompleteSetParams,
200 num_outcomes: u8,
201 ) -> SdkResult<Transaction> {
202 let ix = build_merge_complete_set_ix(¶ms, num_outcomes, &self.program_id);
203 Ok(Transaction::new_with_payer(&[ix], Some(¶ms.user)))
204 }
205
206 pub async fn cancel_order(
208 &self,
209 maker: &Pubkey,
210 order: &FullOrder,
211 ) -> SdkResult<Transaction> {
212 let ix = build_cancel_order_ix(maker, order, &self.program_id);
213 Ok(Transaction::new_with_payer(&[ix], Some(maker)))
214 }
215
216 pub async fn increment_nonce(&self, user: &Pubkey) -> SdkResult<Transaction> {
218 let ix = build_increment_nonce_ix(user, &self.program_id);
219 Ok(Transaction::new_with_payer(&[ix], Some(user)))
220 }
221
222 pub async fn settle_market(&self, params: SettleMarketParams) -> SdkResult<Transaction> {
224 let ix = build_settle_market_ix(¶ms, &self.program_id);
225 Ok(Transaction::new_with_payer(&[ix], Some(¶ms.oracle)))
226 }
227
228 pub async fn redeem_winnings(
230 &self,
231 params: RedeemWinningsParams,
232 winning_outcome: u8,
233 ) -> SdkResult<Transaction> {
234 let ix = build_redeem_winnings_ix(¶ms, winning_outcome, &self.program_id);
235 Ok(Transaction::new_with_payer(&[ix], Some(¶ms.user)))
236 }
237
238 pub async fn set_paused(
240 &self,
241 authority: &Pubkey,
242 paused: bool,
243 ) -> SdkResult<Transaction> {
244 let ix = build_set_paused_ix(authority, paused, &self.program_id);
245 Ok(Transaction::new_with_payer(&[ix], Some(authority)))
246 }
247
248 pub async fn set_operator(
250 &self,
251 authority: &Pubkey,
252 new_operator: &Pubkey,
253 ) -> SdkResult<Transaction> {
254 let ix = build_set_operator_ix(authority, new_operator, &self.program_id);
255 Ok(Transaction::new_with_payer(&[ix], Some(authority)))
256 }
257
258 pub async fn withdraw_from_position(
260 &self,
261 params: WithdrawFromPositionParams,
262 is_token_2022: bool,
263 ) -> SdkResult<Transaction> {
264 let ix = build_withdraw_from_position_ix(¶ms, is_token_2022, &self.program_id);
265 Ok(Transaction::new_with_payer(&[ix], Some(¶ms.user)))
266 }
267
268 pub async fn activate_market(&self, params: ActivateMarketParams) -> SdkResult<Transaction> {
270 let ix = build_activate_market_ix(¶ms, &self.program_id);
271 Ok(Transaction::new_with_payer(&[ix], Some(¶ms.authority)))
272 }
273
274 pub async fn match_orders_multi(
279 &self,
280 params: MatchOrdersMultiParams,
281 ) -> SdkResult<Transaction> {
282 let ix = build_match_orders_multi_ix(¶ms, &self.program_id)?;
283 Ok(Transaction::new_with_payer(&[ix], Some(¶ms.operator)))
284 }
285
286 pub async fn match_orders_multi_with_verify(
290 &self,
291 params: MatchOrdersMultiParams,
292 ) -> SdkResult<Transaction> {
293 let mut instructions = Vec::new();
294
295 instructions.push(create_order_verify_instruction(¶ms.taker_order));
297
298 for maker_order in ¶ms.maker_orders {
300 instructions.push(create_order_verify_instruction(maker_order));
301 }
302
303 let match_ix = build_match_orders_multi_ix(¶ms, &self.program_id)?;
305 instructions.push(match_ix);
306
307 Ok(Transaction::new_with_payer(
308 &instructions,
309 Some(¶ms.operator),
310 ))
311 }
312
313 pub async fn match_orders_multi_cross_ref(
318 &self,
319 params: MatchOrdersMultiParams,
320 ) -> SdkResult<Transaction> {
321 let num_makers = params.maker_orders.len();
322
323 let match_ix_index = (1 + num_makers) as u16;
325
326 let mut instructions = create_cross_ref_ed25519_instructions(num_makers, match_ix_index);
328
329 let match_ix = build_match_orders_multi_ix(¶ms, &self.program_id)?;
331 instructions.push(match_ix);
332
333 Ok(Transaction::new_with_payer(
334 &instructions,
335 Some(¶ms.operator),
336 ))
337 }
338
339 pub fn create_bid_order(&self, params: BidOrderParams) -> FullOrder {
345 FullOrder::new_bid(params)
346 }
347
348 pub fn create_ask_order(&self, params: AskOrderParams) -> FullOrder {
350 FullOrder::new_ask(params)
351 }
352
353 pub fn create_signed_bid_order(&self, params: BidOrderParams, keypair: &Keypair) -> FullOrder {
355 FullOrder::new_bid_signed(params, keypair)
356 }
357
358 pub fn create_signed_ask_order(&self, params: AskOrderParams, keypair: &Keypair) -> FullOrder {
360 FullOrder::new_ask_signed(params, keypair)
361 }
362
363 pub fn hash_order(&self, order: &FullOrder) -> [u8; 32] {
365 order.hash()
366 }
367
368 pub fn sign_order(&self, order: &mut FullOrder, keypair: &Keypair) {
370 order.sign(keypair);
371 }
372
373 pub fn derive_condition_id(
379 &self,
380 oracle: &Pubkey,
381 question_id: &[u8; 32],
382 num_outcomes: u8,
383 ) -> [u8; 32] {
384 derive_condition_id(oracle, question_id, num_outcomes)
385 }
386
387 pub fn get_conditional_mints(
389 &self,
390 market: &Pubkey,
391 deposit_mint: &Pubkey,
392 num_outcomes: u8,
393 ) -> Vec<Pubkey> {
394 get_all_conditional_mint_pdas(market, deposit_mint, num_outcomes, &self.program_id)
395 .into_iter()
396 .map(|(pubkey, _)| pubkey)
397 .collect()
398 }
399
400 pub fn get_exchange_pda(&self) -> Pubkey {
402 get_exchange_pda(&self.program_id).0
403 }
404
405 pub fn get_market_pda(&self, market_id: u64) -> Pubkey {
407 get_market_pda(market_id, &self.program_id).0
408 }
409
410 pub fn get_position_pda(&self, owner: &Pubkey, market: &Pubkey) -> Pubkey {
412 get_position_pda(owner, market, &self.program_id).0
413 }
414
415 pub fn get_order_status_pda(&self, order_hash: &[u8; 32]) -> Pubkey {
417 get_order_status_pda(order_hash, &self.program_id).0
418 }
419
420 pub fn get_user_nonce_pda(&self, user: &Pubkey) -> Pubkey {
422 get_user_nonce_pda(user, &self.program_id).0
423 }
424}
425
426#[cfg(test)]
427mod tests {
428 use super::*;
429
430 #[test]
431 fn test_client_creation() {
432 let client = LightconePinocchioClient::new("https://api.devnet.solana.com");
433 assert_eq!(client.program_id, *PROGRAM_ID);
434 }
435
436 #[test]
437 fn test_client_with_custom_program_id() {
438 let custom_id = Pubkey::new_unique();
439 let client =
440 LightconePinocchioClient::with_program_id("https://api.devnet.solana.com", custom_id);
441 assert_eq!(client.program_id, custom_id);
442 }
443
444 #[test]
445 fn test_pda_helpers() {
446 let client = LightconePinocchioClient::new("https://api.devnet.solana.com");
447
448 let exchange_pda = client.get_exchange_pda();
449 assert_ne!(exchange_pda, Pubkey::default());
450
451 let market_pda = client.get_market_pda(0);
452 assert_ne!(market_pda, Pubkey::default());
453
454 let owner = Pubkey::new_unique();
455 let market = Pubkey::new_unique();
456 let position_pda = client.get_position_pda(&owner, &market);
457 assert_ne!(position_pda, Pubkey::default());
458 }
459
460 #[test]
461 fn test_create_bid_order() {
462 let client = LightconePinocchioClient::new("https://api.devnet.solana.com");
463
464 let params = BidOrderParams {
465 nonce: 1,
466 maker: Pubkey::new_unique(),
467 market: Pubkey::new_unique(),
468 base_mint: Pubkey::new_unique(),
469 quote_mint: Pubkey::new_unique(),
470 maker_amount: 1000,
471 taker_amount: 500,
472 expiration: 0,
473 };
474
475 let order = client.create_bid_order(params.clone());
476 assert_eq!(order.nonce, params.nonce);
477 assert_eq!(order.maker, params.maker);
478 assert_eq!(order.maker_amount, params.maker_amount);
479 }
480
481 #[test]
482 fn test_condition_id_derivation() {
483 let client = LightconePinocchioClient::new("https://api.devnet.solana.com");
484
485 let oracle = Pubkey::new_unique();
486 let question_id = [1u8; 32];
487 let num_outcomes = 3;
488
489 let condition_id1 = client.derive_condition_id(&oracle, &question_id, num_outcomes);
490 let condition_id2 = client.derive_condition_id(&oracle, &question_id, num_outcomes);
491
492 assert_eq!(condition_id1, condition_id2);
493 }
494
495 #[test]
496 fn test_get_conditional_mints() {
497 let client = LightconePinocchioClient::new("https://api.devnet.solana.com");
498
499 let market = Pubkey::new_unique();
500 let deposit_mint = Pubkey::new_unique();
501
502 let mints = client.get_conditional_mints(&market, &deposit_mint, 3);
503 assert_eq!(mints.len(), 3);
504
505 assert_ne!(mints[0], mints[1]);
507 assert_ne!(mints[1], mints[2]);
508 assert_ne!(mints[0], mints[2]);
509 }
510}