Skip to main content

lightcone_sdk/program/
instructions.rs

1//! Instruction builders for all 14 Lightcone Pinocchio instructions.
2//!
3//! This module provides functions to build transaction instructions for interacting
4//! with the Lightcone Pinocchio program.
5
6use solana_sdk::{
7    instruction::{AccountMeta, Instruction},
8    pubkey::Pubkey,
9};
10
11// System program ID
12fn system_program_id() -> Pubkey {
13    solana_system_interface::program::ID
14}
15
16use crate::program::constants::{
17    instruction, ASSOCIATED_TOKEN_PROGRAM_ID, INSTRUCTIONS_SYSVAR_ID, MAX_MAKERS,
18    TOKEN_2022_PROGRAM_ID, TOKEN_PROGRAM_ID,
19};
20use crate::program::error::{SdkError, SdkResult};
21use crate::program::orders::FullOrder;
22use crate::program::pda::{
23    get_conditional_mint_pda, get_exchange_pda, get_market_pda, get_mint_authority_pda,
24    get_order_status_pda, get_position_pda, get_user_nonce_pda, get_vault_pda,
25};
26use crate::program::types::{
27    ActivateMarketParams, AddDepositMintParams, CreateMarketParams, MatchOrdersMultiParams,
28    MergeCompleteSetParams, MintCompleteSetParams, RedeemWinningsParams,
29    SettleMarketParams, WithdrawFromPositionParams,
30};
31use crate::program::utils::{
32    get_conditional_token_ata, get_deposit_token_ata, serialize_outcome_metadata,
33    validate_outcome_count, OutcomeMetadataInput,
34};
35
36// ============================================================================
37// Helper Functions
38// ============================================================================
39
40/// Create an account meta for a signer+writable account.
41fn signer_mut(pubkey: Pubkey) -> AccountMeta {
42    AccountMeta::new(pubkey, true)
43}
44
45/// Create an account meta for a writable account.
46fn writable(pubkey: Pubkey) -> AccountMeta {
47    AccountMeta::new(pubkey, false)
48}
49
50/// Create an account meta for a read-only account.
51fn readonly(pubkey: Pubkey) -> AccountMeta {
52    AccountMeta::new_readonly(pubkey, false)
53}
54
55// ============================================================================
56// Instruction Builders
57// ============================================================================
58
59/// Build Initialize instruction.
60///
61/// Creates the exchange account (singleton).
62///
63/// Accounts:
64/// 0. authority (signer, mut) - Initial admin
65/// 1. exchange (mut) - Exchange PDA
66/// 2. system_program (readonly)
67pub fn build_initialize_ix(authority: &Pubkey, program_id: &Pubkey) -> Instruction {
68    let (exchange, _) = get_exchange_pda(program_id);
69
70    let keys = vec![
71        signer_mut(*authority),
72        writable(exchange),
73        readonly(system_program_id()),
74    ];
75
76    let data = vec![instruction::INITIALIZE];
77
78    Instruction {
79        program_id: *program_id,
80        accounts: keys,
81        data,
82    }
83}
84
85/// Build CreateMarket instruction.
86///
87/// Creates a new market in Pending status.
88///
89/// Accounts:
90/// 0. authority (signer, mut) - Must be exchange authority
91/// 1. exchange (mut) - Exchange PDA
92/// 2. market (mut) - Market PDA
93/// 3. system_program (readonly)
94pub fn build_create_market_ix(
95    params: &CreateMarketParams,
96    market_id: u64,
97    program_id: &Pubkey,
98) -> SdkResult<Instruction> {
99    validate_outcome_count(params.num_outcomes)?;
100
101    let (exchange, _) = get_exchange_pda(program_id);
102    let (market, _) = get_market_pda(market_id, program_id);
103
104    let keys = vec![
105        signer_mut(params.authority),
106        writable(exchange),
107        writable(market),
108        readonly(system_program_id()),
109    ];
110
111    // Data: [discriminator, num_outcomes (u8), oracle (32), question_id (32)]
112    let mut data = Vec::with_capacity(66);
113    data.push(instruction::CREATE_MARKET);
114    data.push(params.num_outcomes);
115    data.extend_from_slice(params.oracle.as_ref());
116    data.extend_from_slice(&params.question_id);
117
118    Ok(Instruction {
119        program_id: *program_id,
120        accounts: keys,
121        data,
122    })
123}
124
125/// Build AddDepositMint instruction.
126///
127/// Sets up vault and conditional mints for a deposit token.
128///
129/// Accounts:
130/// 0. payer (signer)
131/// 1. market
132/// 2. deposit_mint
133/// 3. vault
134/// 4. mint_authority
135/// 5. token_program (SPL Token)
136/// 6. token_2022_program
137/// 7. system_program
138/// 8. conditional_mints\[0..num_outcomes\]
139pub fn build_add_deposit_mint_ix(
140    params: &AddDepositMintParams,
141    market: &Pubkey,
142    num_outcomes: u8,
143    program_id: &Pubkey,
144) -> SdkResult<Instruction> {
145    if params.outcome_metadata.len() != num_outcomes as usize {
146        return Err(SdkError::InvalidOutcomeCount { count: params.outcome_metadata.len() as u8 });
147    }
148
149    let (vault, _) = get_vault_pda(&params.deposit_mint, market, program_id);
150    let (mint_authority, _) = get_mint_authority_pda(market, program_id);
151
152    let mut keys = vec![
153        signer_mut(params.payer),
154        writable(*market),
155        readonly(params.deposit_mint),
156        writable(vault),
157        readonly(mint_authority),
158        readonly(TOKEN_PROGRAM_ID),
159        readonly(TOKEN_2022_PROGRAM_ID),
160        readonly(system_program_id()),
161    ];
162
163    // Add conditional mints
164    for i in 0..num_outcomes {
165        let (mint, _) = get_conditional_mint_pda(market, &params.deposit_mint, i, program_id);
166        keys.push(writable(mint));
167    }
168
169    // Convert OutcomeMetadata to OutcomeMetadataInput
170    let metadata_input: Vec<OutcomeMetadataInput> = params
171        .outcome_metadata
172        .iter()
173        .map(|m| OutcomeMetadataInput {
174            name: m.name.clone(),
175            symbol: m.symbol.clone(),
176            uri: m.uri.clone(),
177        })
178        .collect();
179
180    let mut data = vec![instruction::ADD_DEPOSIT_MINT];
181    data.extend(serialize_outcome_metadata(&metadata_input));
182
183    Ok(Instruction {
184        program_id: *program_id,
185        accounts: keys,
186        data,
187    })
188}
189
190/// Build MintCompleteSet instruction.
191///
192/// Deposits collateral and mints all outcome tokens into Position PDA.
193///
194/// Accounts:
195/// 0. user (signer)
196/// 1. exchange
197/// 2. market
198/// 3. deposit_mint
199/// 4. vault
200/// 5. user_deposit_ata
201/// 6. position
202/// 7. position_collateral_ata
203/// 8. mint_authority
204/// 9. token_program
205/// 10. token_2022_program
206/// 11. associated_token_program
207/// 12. system_program
208/// + remaining accounts (conditional_mint, position_conditional_ata) pairs
209pub fn build_mint_complete_set_ix(
210    params: &MintCompleteSetParams,
211    num_outcomes: u8,
212    program_id: &Pubkey,
213) -> Instruction {
214    let (exchange, _) = get_exchange_pda(program_id);
215    let (vault, _) = get_vault_pda(&params.deposit_mint, &params.market, program_id);
216    let (mint_authority, _) = get_mint_authority_pda(&params.market, program_id);
217    let (position, _) = get_position_pda(&params.user, &params.market, program_id);
218    let user_deposit_ata = get_deposit_token_ata(&params.user, &params.deposit_mint);
219    let position_collateral_ata = get_deposit_token_ata(&position, &params.deposit_mint);
220
221    let mut keys = vec![
222        signer_mut(params.user),
223        readonly(exchange),
224        readonly(params.market),
225        readonly(params.deposit_mint),
226        writable(vault),
227        writable(user_deposit_ata),
228        writable(position),
229        writable(position_collateral_ata),
230        readonly(mint_authority),
231        readonly(TOKEN_PROGRAM_ID),
232        readonly(TOKEN_2022_PROGRAM_ID),
233        readonly(ASSOCIATED_TOKEN_PROGRAM_ID),
234        readonly(system_program_id()),
235    ];
236
237    // Add conditional mint and position ATA pairs
238    for i in 0..num_outcomes {
239        let (mint, _) =
240            get_conditional_mint_pda(&params.market, &params.deposit_mint, i, program_id);
241        keys.push(writable(mint));
242        let position_ata = get_conditional_token_ata(&position, &mint);
243        keys.push(writable(position_ata));
244    }
245
246    // Data: [discriminator, amount (u64)]
247    let mut data = Vec::with_capacity(9);
248    data.push(instruction::MINT_COMPLETE_SET);
249    data.extend_from_slice(&params.amount.to_le_bytes());
250
251    Instruction {
252        program_id: *program_id,
253        accounts: keys,
254        data,
255    }
256}
257
258/// Build MergeCompleteSet instruction.
259///
260/// Burns all outcome tokens from Position and releases collateral.
261pub fn build_merge_complete_set_ix(
262    params: &MergeCompleteSetParams,
263    num_outcomes: u8,
264    program_id: &Pubkey,
265) -> Instruction {
266    let (exchange, _) = get_exchange_pda(program_id);
267    let (vault, _) = get_vault_pda(&params.deposit_mint, &params.market, program_id);
268    let (mint_authority, _) = get_mint_authority_pda(&params.market, program_id);
269    let (position, _) = get_position_pda(&params.user, &params.market, program_id);
270    let user_deposit_ata = get_deposit_token_ata(&params.user, &params.deposit_mint);
271
272    let mut keys = vec![
273        signer_mut(params.user),
274        readonly(exchange),
275        readonly(params.market),
276        readonly(params.deposit_mint),
277        writable(vault),
278        writable(position),
279        writable(user_deposit_ata),
280        readonly(mint_authority),
281        readonly(TOKEN_PROGRAM_ID),
282        readonly(TOKEN_2022_PROGRAM_ID),
283    ];
284
285    // Add conditional mint and position ATA pairs
286    for i in 0..num_outcomes {
287        let (mint, _) =
288            get_conditional_mint_pda(&params.market, &params.deposit_mint, i, program_id);
289        keys.push(writable(mint));
290        let position_ata = get_conditional_token_ata(&position, &mint);
291        keys.push(writable(position_ata));
292    }
293
294    let mut data = Vec::with_capacity(9);
295    data.push(instruction::MERGE_COMPLETE_SET);
296    data.extend_from_slice(&params.amount.to_le_bytes());
297
298    Instruction {
299        program_id: *program_id,
300        accounts: keys,
301        data,
302    }
303}
304
305/// Build CancelOrder instruction.
306///
307/// Marks an order as cancelled.
308pub fn build_cancel_order_ix(
309    maker: &Pubkey,
310    order: &FullOrder,
311    program_id: &Pubkey,
312) -> Instruction {
313    let order_hash = order.hash();
314    let (order_status, _) = get_order_status_pda(&order_hash, program_id);
315
316    let keys = vec![
317        signer_mut(*maker),
318        writable(order_status),
319        readonly(system_program_id()),
320    ];
321
322    // Data: [discriminator, order_hash (32), full_order (225)]
323    let mut data = Vec::with_capacity(258);
324    data.push(instruction::CANCEL_ORDER);
325    data.extend_from_slice(&order_hash);
326    data.extend_from_slice(&order.serialize());
327
328    Instruction {
329        program_id: *program_id,
330        accounts: keys,
331        data,
332    }
333}
334
335/// Build IncrementNonce instruction.
336///
337/// Increments user's nonce for replay protection / mass cancellation.
338pub fn build_increment_nonce_ix(user: &Pubkey, program_id: &Pubkey) -> Instruction {
339    let (user_nonce, _) = get_user_nonce_pda(user, program_id);
340
341    let keys = vec![
342        signer_mut(*user),
343        writable(user_nonce),
344        readonly(system_program_id()),
345    ];
346
347    let data = vec![instruction::INCREMENT_NONCE];
348
349    Instruction {
350        program_id: *program_id,
351        accounts: keys,
352        data,
353    }
354}
355
356/// Build SettleMarket instruction.
357///
358/// Oracle resolves the market with winning outcome.
359pub fn build_settle_market_ix(params: &SettleMarketParams, program_id: &Pubkey) -> Instruction {
360    let (exchange, _) = get_exchange_pda(program_id);
361    let (market, _) = get_market_pda(params.market_id, program_id);
362
363    let keys = vec![
364        signer_mut(params.oracle),
365        readonly(exchange),
366        writable(market),
367    ];
368
369    let data = vec![instruction::SETTLE_MARKET, params.winning_outcome];
370
371    Instruction {
372        program_id: *program_id,
373        accounts: keys,
374        data,
375    }
376}
377
378/// Build RedeemWinnings instruction.
379///
380/// Redeem winning outcome tokens from Position for collateral.
381pub fn build_redeem_winnings_ix(
382    params: &RedeemWinningsParams,
383    winning_outcome: u8,
384    program_id: &Pubkey,
385) -> Instruction {
386    let (vault, _) = get_vault_pda(&params.deposit_mint, &params.market, program_id);
387    let (mint_authority, _) = get_mint_authority_pda(&params.market, program_id);
388    let (position, _) = get_position_pda(&params.user, &params.market, program_id);
389    let (winning_mint, _) = get_conditional_mint_pda(
390        &params.market,
391        &params.deposit_mint,
392        winning_outcome,
393        program_id,
394    );
395    let position_winning_ata = get_conditional_token_ata(&position, &winning_mint);
396    let user_deposit_ata = get_deposit_token_ata(&params.user, &params.deposit_mint);
397
398    let keys = vec![
399        signer_mut(params.user),
400        readonly(params.market),
401        readonly(params.deposit_mint),
402        writable(vault),
403        writable(winning_mint),
404        writable(position),
405        writable(position_winning_ata),
406        writable(user_deposit_ata),
407        readonly(mint_authority),
408        readonly(TOKEN_PROGRAM_ID),
409        readonly(TOKEN_2022_PROGRAM_ID),
410    ];
411
412    let mut data = Vec::with_capacity(9);
413    data.push(instruction::REDEEM_WINNINGS);
414    data.extend_from_slice(&params.amount.to_le_bytes());
415
416    Instruction {
417        program_id: *program_id,
418        accounts: keys,
419        data,
420    }
421}
422
423/// Build SetPaused instruction.
424///
425/// Admin: pause/unpause exchange.
426pub fn build_set_paused_ix(
427    authority: &Pubkey,
428    paused: bool,
429    program_id: &Pubkey,
430) -> Instruction {
431    let (exchange, _) = get_exchange_pda(program_id);
432
433    let keys = vec![signer_mut(*authority), writable(exchange)];
434
435    let data = vec![instruction::SET_PAUSED, if paused { 1 } else { 0 }];
436
437    Instruction {
438        program_id: *program_id,
439        accounts: keys,
440        data,
441    }
442}
443
444/// Build SetOperator instruction.
445///
446/// Admin: change operator pubkey.
447pub fn build_set_operator_ix(
448    authority: &Pubkey,
449    new_operator: &Pubkey,
450    program_id: &Pubkey,
451) -> Instruction {
452    let (exchange, _) = get_exchange_pda(program_id);
453
454    let keys = vec![signer_mut(*authority), writable(exchange)];
455
456    let mut data = Vec::with_capacity(33);
457    data.push(instruction::SET_OPERATOR);
458    data.extend_from_slice(new_operator.as_ref());
459
460    Instruction {
461        program_id: *program_id,
462        accounts: keys,
463        data,
464    }
465}
466
467/// Build WithdrawFromPosition instruction.
468///
469/// Withdraw tokens from Position ATA to user's ATA.
470pub fn build_withdraw_from_position_ix(
471    params: &WithdrawFromPositionParams,
472    is_token_2022: bool,
473    program_id: &Pubkey,
474) -> Instruction {
475    let (position, _) = get_position_pda(&params.user, &params.market, program_id);
476    let position_ata = if is_token_2022 {
477        get_conditional_token_ata(&position, &params.mint)
478    } else {
479        get_deposit_token_ata(&position, &params.mint)
480    };
481    let user_ata = if is_token_2022 {
482        get_conditional_token_ata(&params.user, &params.mint)
483    } else {
484        get_deposit_token_ata(&params.user, &params.mint)
485    };
486    let token_program = if is_token_2022 {
487        TOKEN_2022_PROGRAM_ID
488    } else {
489        TOKEN_PROGRAM_ID
490    };
491
492    let keys = vec![
493        signer_mut(params.user),
494        writable(position),
495        readonly(params.mint),
496        writable(position_ata),
497        writable(user_ata),
498        readonly(token_program),
499    ];
500
501    let mut data = Vec::with_capacity(9);
502    data.push(instruction::WITHDRAW_FROM_POSITION);
503    data.extend_from_slice(&params.amount.to_le_bytes());
504
505    Instruction {
506        program_id: *program_id,
507        accounts: keys,
508        data,
509    }
510}
511
512/// Build ActivateMarket instruction.
513///
514/// Authority: Pending → Active.
515pub fn build_activate_market_ix(
516    params: &ActivateMarketParams,
517    program_id: &Pubkey,
518) -> Instruction {
519    let (exchange, _) = get_exchange_pda(program_id);
520    let (market, _) = get_market_pda(params.market_id, program_id);
521
522    let keys = vec![
523        signer_mut(params.authority),
524        readonly(exchange),
525        writable(market),
526    ];
527
528    let data = vec![instruction::ACTIVATE_MARKET];
529
530    Instruction {
531        program_id: *program_id,
532        accounts: keys,
533        data,
534    }
535}
536
537/// Build MatchOrdersMulti instruction.
538///
539/// Match taker against up to 5 makers.
540///
541/// Accounts (13 fixed + 5 per maker):
542/// 0. operator (signer)
543/// 1. exchange (readonly)
544/// 2. market (readonly)
545/// 3. taker_order_status (mut)
546/// 4. taker_nonce (readonly)
547/// 5. taker_position (mut)
548/// 6. base_mint (readonly)
549/// 7. quote_mint (readonly)
550/// 8. taker_position_base_ata (mut)
551/// 9. taker_position_quote_ata (mut)
552/// 10. token_2022_program (readonly)
553/// 11. system_program (readonly)
554/// 12. instructions_sysvar (readonly)
555///
556/// Per maker: order_status, nonce, position, base_ata, quote_ata
557pub fn build_match_orders_multi_ix(
558    params: &MatchOrdersMultiParams,
559    program_id: &Pubkey,
560) -> SdkResult<Instruction> {
561    if params.maker_orders.is_empty() {
562        return Err(SdkError::MissingField("maker_orders".to_string()));
563    }
564    if params.maker_orders.len() > MAX_MAKERS {
565        return Err(SdkError::TooManyMakers { count: params.maker_orders.len() });
566    }
567    if params.maker_orders.len() != params.fill_amounts.len() {
568        return Err(SdkError::MissingField("fill_amounts".to_string()));
569    }
570
571    let (exchange, _) = get_exchange_pda(program_id);
572    let taker_order_hash = params.taker_order.hash();
573    let (taker_order_status, _) = get_order_status_pda(&taker_order_hash, program_id);
574    let (taker_nonce, _) = get_user_nonce_pda(&params.taker_order.maker, program_id);
575    let (taker_position, _) =
576        get_position_pda(&params.taker_order.maker, &params.market, program_id);
577    let taker_base_ata = get_conditional_token_ata(&taker_position, &params.base_mint);
578    let taker_quote_ata = get_conditional_token_ata(&taker_position, &params.quote_mint);
579
580    let mut keys = vec![
581        signer_mut(params.operator),
582        readonly(exchange),
583        readonly(params.market),
584        writable(taker_order_status),
585        readonly(taker_nonce),
586        writable(taker_position),
587        readonly(params.base_mint),
588        readonly(params.quote_mint),
589        writable(taker_base_ata),
590        writable(taker_quote_ata),
591        readonly(TOKEN_2022_PROGRAM_ID),
592        readonly(system_program_id()),
593        readonly(INSTRUCTIONS_SYSVAR_ID),
594    ];
595
596    // Add maker accounts
597    for maker_order in &params.maker_orders {
598        let maker_order_hash = maker_order.hash();
599        let (maker_order_status, _) = get_order_status_pda(&maker_order_hash, program_id);
600        let (maker_nonce, _) = get_user_nonce_pda(&maker_order.maker, program_id);
601        let (maker_position, _) = get_position_pda(&maker_order.maker, &params.market, program_id);
602        let maker_base_ata = get_conditional_token_ata(&maker_position, &params.base_mint);
603        let maker_quote_ata = get_conditional_token_ata(&maker_position, &params.quote_mint);
604
605        keys.push(writable(maker_order_status));
606        keys.push(readonly(maker_nonce));
607        keys.push(writable(maker_position));
608        keys.push(writable(maker_base_ata));
609        keys.push(writable(maker_quote_ata));
610    }
611
612    // Build data
613    // [discriminator, taker_hash(32), taker_compact(65), taker_sig(64), num_makers(1)]
614    // Per maker: [hash(32), compact(65), sig(64), fill(8)]
615    let taker_compact = params.taker_order.to_compact();
616    let num_makers = params.maker_orders.len() as u8;
617
618    let data_size = 1 + 32 + 65 + 64 + 1 + (params.maker_orders.len() * (32 + 65 + 64 + 8));
619    let mut data = Vec::with_capacity(data_size);
620
621    data.push(instruction::MATCH_ORDERS_MULTI);
622    data.extend_from_slice(&taker_order_hash);
623    data.extend_from_slice(&taker_compact.serialize());
624    data.extend_from_slice(&params.taker_order.signature);
625    data.push(num_makers);
626
627    for (i, maker_order) in params.maker_orders.iter().enumerate() {
628        let maker_hash = maker_order.hash();
629        let maker_compact = maker_order.to_compact();
630
631        data.extend_from_slice(&maker_hash);
632        data.extend_from_slice(&maker_compact.serialize());
633        data.extend_from_slice(&maker_order.signature);
634        data.extend_from_slice(&params.fill_amounts[i].to_le_bytes());
635    }
636
637    Ok(Instruction {
638        program_id: *program_id,
639        accounts: keys,
640        data,
641    })
642}
643
644#[cfg(test)]
645mod tests {
646    use super::*;
647    use crate::program::constants::PROGRAM_ID;
648
649    fn test_program_id() -> Pubkey {
650        *PROGRAM_ID
651    }
652
653    #[test]
654    fn test_build_initialize_ix() {
655        let authority = Pubkey::new_unique();
656        let program_id = test_program_id();
657
658        let ix = build_initialize_ix(&authority, &program_id);
659
660        assert_eq!(ix.program_id, program_id);
661        assert_eq!(ix.accounts.len(), 3);
662        assert_eq!(ix.data, vec![instruction::INITIALIZE]);
663    }
664
665    #[test]
666    fn test_build_increment_nonce_ix() {
667        let user = Pubkey::new_unique();
668        let program_id = test_program_id();
669
670        let ix = build_increment_nonce_ix(&user, &program_id);
671
672        assert_eq!(ix.program_id, program_id);
673        assert_eq!(ix.accounts.len(), 3);
674        assert_eq!(ix.data, vec![instruction::INCREMENT_NONCE]);
675    }
676
677    #[test]
678    fn test_build_set_paused_ix() {
679        let authority = Pubkey::new_unique();
680        let program_id = test_program_id();
681
682        let ix_pause = build_set_paused_ix(&authority, true, &program_id);
683        assert_eq!(ix_pause.data, vec![instruction::SET_PAUSED, 1]);
684
685        let ix_unpause = build_set_paused_ix(&authority, false, &program_id);
686        assert_eq!(ix_unpause.data, vec![instruction::SET_PAUSED, 0]);
687    }
688
689    #[test]
690    fn test_build_set_operator_ix() {
691        let authority = Pubkey::new_unique();
692        let new_operator = Pubkey::new_unique();
693        let program_id = test_program_id();
694
695        let ix = build_set_operator_ix(&authority, &new_operator, &program_id);
696
697        assert_eq!(ix.data.len(), 33);
698        assert_eq!(ix.data[0], instruction::SET_OPERATOR);
699        assert_eq!(&ix.data[1..33], new_operator.as_ref());
700    }
701
702    #[test]
703    fn test_build_create_market_ix() {
704        let params = CreateMarketParams {
705            authority: Pubkey::new_unique(),
706            num_outcomes: 3,
707            oracle: Pubkey::new_unique(),
708            question_id: [42u8; 32],
709        };
710        let program_id = test_program_id();
711
712        let ix = build_create_market_ix(&params, 0, &program_id).unwrap();
713
714        assert_eq!(ix.accounts.len(), 4);
715        assert_eq!(ix.data.len(), 66); // 1 + 1 + 32 + 32
716        assert_eq!(ix.data[0], instruction::CREATE_MARKET);
717        assert_eq!(ix.data[1], 3);
718    }
719
720    #[test]
721    fn test_build_create_market_invalid_outcomes() {
722        let params = CreateMarketParams {
723            authority: Pubkey::new_unique(),
724            num_outcomes: 7, // Invalid - max is 6
725            oracle: Pubkey::new_unique(),
726            question_id: [0u8; 32],
727        };
728        let program_id = test_program_id();
729
730        let result = build_create_market_ix(&params, 0, &program_id);
731        assert!(result.is_err());
732    }
733
734    #[test]
735    fn test_build_activate_market_ix() {
736        let params = ActivateMarketParams {
737            authority: Pubkey::new_unique(),
738            market_id: 5,
739        };
740        let program_id = test_program_id();
741
742        let ix = build_activate_market_ix(&params, &program_id);
743
744        assert_eq!(ix.accounts.len(), 3);
745        assert_eq!(ix.data, vec![instruction::ACTIVATE_MARKET]);
746    }
747
748    #[test]
749    fn test_build_settle_market_ix() {
750        let params = SettleMarketParams {
751            oracle: Pubkey::new_unique(),
752            market_id: 1,
753            winning_outcome: 2,
754        };
755        let program_id = test_program_id();
756
757        let ix = build_settle_market_ix(&params, &program_id);
758
759        assert_eq!(ix.accounts.len(), 3);
760        assert_eq!(ix.data.len(), 2);
761        assert_eq!(ix.data[0], instruction::SETTLE_MARKET);
762        assert_eq!(ix.data[1], 2);
763    }
764}