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