tally_sdk/
transaction_builder.rs

1//! Transaction building utilities for Tally subscription flows
2
3use crate::{
4    ata::{get_associated_token_address_with_program, TokenProgram},
5    error::{Result, TallyError},
6    pda, program_id,
7    program_types::{
8        AdminWithdrawFeesArgs, CancelSubscriptionArgs, CreatePlanArgs, InitConfigArgs,
9        InitMerchantArgs, Merchant, Plan, StartSubscriptionArgs, UpdateConfigArgs, UpdatePlanArgs,
10        UpdatePlanTermsArgs,
11    },
12};
13use anchor_client::solana_sdk::instruction::{AccountMeta, Instruction};
14use anchor_lang::prelude::*;
15use anchor_lang::system_program;
16use spl_token::instruction::{approve_checked as approve_checked_token, revoke as revoke_token};
17use spl_token_2022::instruction::{
18    approve_checked as approve_checked_token2022, revoke as revoke_token2022,
19};
20
21/// Builder for start subscription transactions (approve → start flow)
22#[derive(Clone, Debug, Default)]
23pub struct StartSubscriptionBuilder {
24    plan: Option<Pubkey>,
25    subscriber: Option<Pubkey>,
26    payer: Option<Pubkey>,
27    allowance_periods: Option<u8>,
28    token_program: Option<TokenProgram>,
29    program_id: Option<Pubkey>,
30}
31
32/// Builder for cancel subscription transactions (revoke → cancel flow)
33#[derive(Clone, Debug, Default)]
34pub struct CancelSubscriptionBuilder {
35    plan: Option<Pubkey>,
36    subscriber: Option<Pubkey>,
37    payer: Option<Pubkey>,
38    token_program: Option<TokenProgram>,
39    program_id: Option<Pubkey>,
40}
41
42/// Builder for create merchant transactions
43#[derive(Clone, Debug, Default)]
44pub struct CreateMerchantBuilder {
45    authority: Option<Pubkey>,
46    payer: Option<Pubkey>,
47    usdc_mint: Option<Pubkey>,
48    treasury_ata: Option<Pubkey>,
49    platform_fee_bps: Option<u16>,
50    program_id: Option<Pubkey>,
51}
52
53/// Builder for create plan transactions
54#[derive(Clone, Debug, Default)]
55pub struct CreatePlanBuilder {
56    authority: Option<Pubkey>,
57    payer: Option<Pubkey>,
58    plan_args: Option<CreatePlanArgs>,
59    program_id: Option<Pubkey>,
60}
61
62/// Builder for update plan transactions
63#[derive(Clone, Debug, Default)]
64pub struct UpdatePlanBuilder {
65    authority: Option<Pubkey>,
66    payer: Option<Pubkey>,
67    plan_key: Option<Pubkey>,
68    update_args: Option<UpdatePlanArgs>,
69    program_id: Option<Pubkey>,
70}
71
72/// Builder for admin fee withdrawal transactions
73#[derive(Clone, Debug, Default)]
74pub struct AdminWithdrawFeesBuilder {
75    platform_authority: Option<Pubkey>,
76    platform_treasury_ata: Option<Pubkey>,
77    destination_ata: Option<Pubkey>,
78    usdc_mint: Option<Pubkey>,
79    amount: Option<u64>,
80    program_id: Option<Pubkey>,
81}
82
83/// Builder for initialize config transactions
84#[derive(Clone, Debug, Default)]
85pub struct InitConfigBuilder {
86    authority: Option<Pubkey>,
87    payer: Option<Pubkey>,
88    config_args: Option<InitConfigArgs>,
89    program_id: Option<Pubkey>,
90}
91
92/// Builder for renew subscription transactions
93#[derive(Clone, Debug, Default)]
94pub struct RenewSubscriptionBuilder {
95    plan: Option<Pubkey>,
96    subscriber: Option<Pubkey>,
97    keeper: Option<Pubkey>,
98    keeper_ata: Option<Pubkey>,
99    token_program: Option<TokenProgram>,
100    program_id: Option<Pubkey>,
101}
102
103/// Builder for close subscription transactions
104#[derive(Clone, Debug, Default)]
105pub struct CloseSubscriptionBuilder {
106    plan: Option<Pubkey>,
107    subscriber: Option<Pubkey>,
108    program_id: Option<Pubkey>,
109}
110
111/// Builder for transfer authority transactions
112#[derive(Clone, Debug, Default)]
113pub struct TransferAuthorityBuilder {
114    platform_authority: Option<Pubkey>,
115    new_authority: Option<Pubkey>,
116    program_id: Option<Pubkey>,
117}
118
119/// Builder for accept authority transactions
120#[derive(Clone, Debug, Default)]
121pub struct AcceptAuthorityBuilder {
122    new_authority: Option<Pubkey>,
123    program_id: Option<Pubkey>,
124}
125
126/// Builder for cancel authority transfer transactions
127#[derive(Clone, Debug, Default)]
128pub struct CancelAuthorityTransferBuilder {
129    platform_authority: Option<Pubkey>,
130    program_id: Option<Pubkey>,
131}
132
133/// Builder for pause program transactions
134#[derive(Clone, Debug, Default)]
135pub struct PauseBuilder {
136    platform_authority: Option<Pubkey>,
137    program_id: Option<Pubkey>,
138}
139
140/// Builder for unpause program transactions
141#[derive(Clone, Debug, Default)]
142pub struct UnpauseBuilder {
143    platform_authority: Option<Pubkey>,
144    program_id: Option<Pubkey>,
145}
146
147/// Builder for update config transactions
148#[derive(Clone, Debug, Default)]
149pub struct UpdateConfigBuilder {
150    platform_authority: Option<Pubkey>,
151    keeper_fee_bps: Option<u16>,
152    max_withdrawal_amount: Option<u64>,
153    max_grace_period_seconds: Option<u64>,
154    min_platform_fee_bps: Option<u16>,
155    max_platform_fee_bps: Option<u16>,
156    min_period_seconds: Option<u64>,
157    default_allowance_periods: Option<u8>,
158    program_id: Option<Pubkey>,
159}
160
161/// Builder for update merchant tier transactions
162#[derive(Clone, Debug, Default)]
163pub struct UpdateMerchantTierBuilder {
164    authority: Option<Pubkey>,
165    merchant: Option<Pubkey>,
166    new_tier: Option<u8>,
167    program_id: Option<Pubkey>,
168}
169
170/// Builder for update plan terms transactions
171#[derive(Clone, Debug, Default)]
172pub struct UpdatePlanTermsBuilder {
173    authority: Option<Pubkey>,
174    plan_key: Option<Pubkey>,
175    price_usdc: Option<u64>,
176    period_secs: Option<u64>,
177    grace_secs: Option<u64>,
178    name: Option<String>,
179    program_id: Option<Pubkey>,
180}
181
182impl StartSubscriptionBuilder {
183    /// Create a new start subscription builder
184    #[must_use]
185    pub fn new() -> Self {
186        Self::default()
187    }
188
189    /// Set the plan PDA
190    #[must_use]
191    pub const fn plan(mut self, plan: Pubkey) -> Self {
192        self.plan = Some(plan);
193        self
194    }
195
196    /// Set the subscriber pubkey
197    #[must_use]
198    pub const fn subscriber(mut self, subscriber: Pubkey) -> Self {
199        self.subscriber = Some(subscriber);
200        self
201    }
202
203    /// Set the transaction payer
204    #[must_use]
205    pub const fn payer(mut self, payer: Pubkey) -> Self {
206        self.payer = Some(payer);
207        self
208    }
209
210    /// Set the allowance periods multiplier (default 3)
211    #[must_use]
212    pub const fn allowance_periods(mut self, periods: u8) -> Self {
213        self.allowance_periods = Some(periods);
214        self
215    }
216
217    /// Set the token program to use
218    #[must_use]
219    pub const fn token_program(mut self, token_program: TokenProgram) -> Self {
220        self.token_program = Some(token_program);
221        self
222    }
223
224    /// Set the program ID to use
225    #[must_use]
226    pub const fn program_id(mut self, program_id: Pubkey) -> Self {
227        self.program_id = Some(program_id);
228        self
229    }
230
231    /// Build the transaction instructions
232    ///
233    /// # Arguments
234    /// * `merchant` - The merchant account data
235    /// * `plan_data` - The plan account data
236    /// * `platform_treasury_ata` - Platform treasury ATA address
237    ///
238    /// # Returns
239    /// * `Ok(Vec<Instruction>)` - The transaction instructions (`approve_checked` + `start_subscription`)
240    /// * `Err(TallyError)` - If building fails
241    pub fn build_instructions(
242        self,
243        merchant: &Merchant,
244        plan_data: &Plan,
245        platform_treasury_ata: &Pubkey,
246    ) -> Result<Vec<Instruction>> {
247        let plan = self.plan.ok_or("Plan not set")?;
248        let subscriber = self.subscriber.ok_or("Subscriber not set")?;
249        let _payer = self.payer.unwrap_or(subscriber);
250        let allowance_periods = self.allowance_periods.unwrap_or(3);
251        let token_program = self.token_program.unwrap_or(TokenProgram::Token);
252
253        let program_id = self.program_id.unwrap_or_else(program_id);
254
255        // Compute required PDAs
256        let config_pda = pda::config_address_with_program_id(&program_id);
257        let merchant_pda = pda::merchant_address_with_program_id(&merchant.authority, &program_id);
258        let subscription_pda =
259            pda::subscription_address_with_program_id(&plan, &subscriber, &program_id);
260        let delegate_pda = pda::delegate_address_with_program_id(&merchant_pda, &program_id);
261        let subscriber_ata = get_associated_token_address_with_program(
262            &subscriber,
263            &merchant.usdc_mint,
264            token_program,
265        )?;
266
267        // Calculate allowance amount based on plan price and periods
268        let allowance_amount = plan_data
269            .price_usdc
270            .checked_mul(u64::from(allowance_periods))
271            .ok_or_else(|| TallyError::Generic("Arithmetic overflow".to_string()))?;
272
273        // Create approve_checked instruction using the correct token program
274        let approve_ix = match token_program {
275            TokenProgram::Token => approve_checked_token(
276                &token_program.program_id(),
277                &subscriber_ata,
278                &merchant.usdc_mint,
279                &delegate_pda, // Program delegate PDA
280                &subscriber,   // Subscriber as owner
281                &[],           // No additional signers
282                allowance_amount,
283                6, // USDC decimals
284            )?,
285            TokenProgram::Token2022 => approve_checked_token2022(
286                &token_program.program_id(),
287                &subscriber_ata,
288                &merchant.usdc_mint,
289                &delegate_pda, // Program delegate PDA
290                &subscriber,   // Subscriber as owner
291                &[],           // No additional signers
292                allowance_amount,
293                6, // USDC decimals
294            )?,
295        };
296
297        // Create start_subscription instruction
298        let start_sub_accounts = vec![
299            AccountMeta::new_readonly(config_pda, false),   // config
300            AccountMeta::new(subscription_pda, false),      // subscription (PDA)
301            AccountMeta::new_readonly(plan, false),         // plan
302            AccountMeta::new_readonly(merchant_pda, false), // merchant
303            AccountMeta::new(subscriber, true),             // subscriber (signer)
304            AccountMeta::new(subscriber_ata, false),        // subscriber_usdc_ata
305            AccountMeta::new(merchant.treasury_ata, false), // merchant_treasury_ata
306            AccountMeta::new(*platform_treasury_ata, false), // platform_treasury_ata
307            AccountMeta::new_readonly(merchant.usdc_mint, false), // usdc_mint
308            AccountMeta::new_readonly(delegate_pda, false), // program_delegate
309            AccountMeta::new_readonly(token_program.program_id(), false), // token_program
310            AccountMeta::new_readonly(system_program::ID, false), // system_program
311        ];
312
313        let start_sub_args = StartSubscriptionArgs { allowance_periods };
314        let start_sub_data = {
315            let mut data = Vec::new();
316            // Instruction discriminator (computed from "start_subscription")
317            data.extend_from_slice(&[167, 59, 160, 222, 194, 175, 3, 13]);
318            borsh::to_writer(&mut data, &start_sub_args)
319                .map_err(|e| TallyError::Generic(format!("Failed to serialize args: {e}")))?;
320            data
321        };
322
323        let start_sub_ix = Instruction {
324            program_id,
325            accounts: start_sub_accounts,
326            data: start_sub_data,
327        };
328
329        Ok(vec![approve_ix, start_sub_ix])
330    }
331}
332
333impl CancelSubscriptionBuilder {
334    /// Create a new cancel subscription builder
335    #[must_use]
336    pub fn new() -> Self {
337        Self::default()
338    }
339
340    /// Set the plan PDA
341    #[must_use]
342    pub const fn plan(mut self, plan: Pubkey) -> Self {
343        self.plan = Some(plan);
344        self
345    }
346
347    /// Set the subscriber pubkey
348    #[must_use]
349    pub const fn subscriber(mut self, subscriber: Pubkey) -> Self {
350        self.subscriber = Some(subscriber);
351        self
352    }
353
354    /// Set the transaction payer
355    #[must_use]
356    pub const fn payer(mut self, payer: Pubkey) -> Self {
357        self.payer = Some(payer);
358        self
359    }
360
361    /// Set the token program to use
362    #[must_use]
363    pub const fn token_program(mut self, token_program: TokenProgram) -> Self {
364        self.token_program = Some(token_program);
365        self
366    }
367
368    /// Set the program ID to use
369    #[must_use]
370    pub const fn program_id(mut self, program_id: Pubkey) -> Self {
371        self.program_id = Some(program_id);
372        self
373    }
374
375    /// Build the transaction instructions
376    ///
377    /// # Arguments
378    /// * `merchant` - The merchant account data
379    ///
380    /// # Returns
381    /// * `Ok(Vec<Instruction>)` - The transaction instructions (revoke + `cancel_subscription`)
382    /// * `Err(TallyError)` - If building fails
383    pub fn build_instructions(self, merchant: &Merchant) -> Result<Vec<Instruction>> {
384        let plan = self.plan.ok_or("Plan not set")?;
385        let subscriber = self.subscriber.ok_or("Subscriber not set")?;
386        let _payer = self.payer.unwrap_or(subscriber);
387        let token_program = self.token_program.unwrap_or(TokenProgram::Token);
388
389        let program_id = self.program_id.unwrap_or_else(program_id);
390
391        // Compute required PDAs
392        let subscription_pda =
393            pda::subscription_address_with_program_id(&plan, &subscriber, &program_id);
394        let subscriber_ata = get_associated_token_address_with_program(
395            &subscriber,
396            &merchant.usdc_mint,
397            token_program,
398        )?;
399
400        // Create revoke instruction using the correct token program
401        let revoke_ix = match token_program {
402            TokenProgram::Token => revoke_token(
403                &token_program.program_id(),
404                &subscriber_ata,
405                &subscriber, // Subscriber as owner
406                &[],         // No additional signers
407            )?,
408            TokenProgram::Token2022 => revoke_token2022(
409                &token_program.program_id(),
410                &subscriber_ata,
411                &subscriber, // Subscriber as owner
412                &[],         // No additional signers
413            )?,
414        };
415
416        // Create cancel_subscription instruction
417        let merchant_pda = pda::merchant_address_with_program_id(&merchant.authority, &program_id);
418        let cancel_sub_accounts = vec![
419            AccountMeta::new(subscription_pda, false), // subscription (PDA)
420            AccountMeta::new_readonly(plan, false),    // plan
421            AccountMeta::new_readonly(merchant_pda, false), // merchant
422            AccountMeta::new_readonly(subscriber, true), // subscriber (signer)
423        ];
424
425        let cancel_sub_args = CancelSubscriptionArgs;
426        let cancel_sub_data = {
427            let mut data = Vec::new();
428            // Instruction discriminator (computed from "cancel_subscription")
429            data.extend_from_slice(&[60, 139, 189, 242, 191, 208, 143, 18]);
430            borsh::to_writer(&mut data, &cancel_sub_args)
431                .map_err(|e| TallyError::Generic(format!("Failed to serialize args: {e}")))?;
432            data
433        };
434
435        let cancel_sub_ix = Instruction {
436            program_id,
437            accounts: cancel_sub_accounts,
438            data: cancel_sub_data,
439        };
440
441        Ok(vec![revoke_ix, cancel_sub_ix])
442    }
443}
444
445impl CreateMerchantBuilder {
446    /// Create a new create merchant builder
447    #[must_use]
448    pub fn new() -> Self {
449        Self::default()
450    }
451
452    /// Set the merchant authority
453    #[must_use]
454    pub const fn authority(mut self, authority: Pubkey) -> Self {
455        self.authority = Some(authority);
456        self
457    }
458
459    /// Set the transaction payer
460    #[must_use]
461    pub const fn payer(mut self, payer: Pubkey) -> Self {
462        self.payer = Some(payer);
463        self
464    }
465
466    /// Set the USDC mint
467    #[must_use]
468    pub const fn usdc_mint(mut self, usdc_mint: Pubkey) -> Self {
469        self.usdc_mint = Some(usdc_mint);
470        self
471    }
472
473    /// Set the treasury ATA
474    #[must_use]
475    pub const fn treasury_ata(mut self, treasury_ata: Pubkey) -> Self {
476        self.treasury_ata = Some(treasury_ata);
477        self
478    }
479
480    /// Set the platform fee basis points
481    #[must_use]
482    pub const fn platform_fee_bps(mut self, fee_bps: u16) -> Self {
483        self.platform_fee_bps = Some(fee_bps);
484        self
485    }
486
487    /// Set the program ID to use
488    #[must_use]
489    pub const fn program_id(mut self, program_id: Pubkey) -> Self {
490        self.program_id = Some(program_id);
491        self
492    }
493
494    /// Build the transaction instruction
495    ///
496    /// # Returns
497    /// * `Ok(Instruction)` - The `init_merchant` instruction
498    /// * `Err(TallyError)` - If building fails
499    pub fn build_instruction(self) -> Result<Instruction> {
500        let authority = self.authority.ok_or("Authority not set")?;
501        let usdc_mint = self.usdc_mint.ok_or("USDC mint not set")?;
502        let treasury_ata = self.treasury_ata.ok_or("Treasury ATA not set")?;
503        let platform_fee_bps = self.platform_fee_bps.unwrap_or(0);
504
505        let program_id = self.program_id.unwrap_or_else(program_id);
506
507        // Compute required PDAs
508        let config_pda = pda::config_address_with_program_id(&program_id);
509        let merchant_pda = pda::merchant_address_with_program_id(&authority, &program_id);
510
511        let accounts = vec![
512            AccountMeta::new_readonly(config_pda, false),   // config
513            AccountMeta::new(merchant_pda, false),          // merchant (PDA)
514            AccountMeta::new(authority, true),              // authority (signer)
515            AccountMeta::new_readonly(usdc_mint, false),    // usdc_mint
516            AccountMeta::new_readonly(treasury_ata, false), // treasury_ata
517            AccountMeta::new_readonly(spl_token::id(), false), // token_program
518            AccountMeta::new_readonly(spl_associated_token_account::id(), false), // associated_token_program
519            AccountMeta::new_readonly(system_program::ID, false),                 // system_program
520        ];
521
522        let args = InitMerchantArgs {
523            usdc_mint,
524            treasury_ata,
525            platform_fee_bps,
526        };
527
528        let data = {
529            let mut data = Vec::new();
530            // Instruction discriminator (computed from "init_merchant")
531            data.extend_from_slice(&[209, 11, 214, 195, 222, 157, 124, 192]);
532            borsh::to_writer(&mut data, &args)
533                .map_err(|e| TallyError::Generic(format!("Failed to serialize args: {e}")))?;
534            data
535        };
536
537        Ok(Instruction {
538            program_id,
539            accounts,
540            data,
541        })
542    }
543}
544
545impl CreatePlanBuilder {
546    /// Create a new create plan builder
547    #[must_use]
548    pub fn new() -> Self {
549        Self::default()
550    }
551
552    /// Set the merchant authority
553    #[must_use]
554    pub const fn authority(mut self, authority: Pubkey) -> Self {
555        self.authority = Some(authority);
556        self
557    }
558
559    /// Set the transaction payer
560    #[must_use]
561    pub const fn payer(mut self, payer: Pubkey) -> Self {
562        self.payer = Some(payer);
563        self
564    }
565
566    /// Set the plan creation arguments
567    #[must_use]
568    pub fn plan_args(mut self, args: CreatePlanArgs) -> Self {
569        self.plan_args = Some(args);
570        self
571    }
572
573    /// Set the program ID to use
574    #[must_use]
575    pub const fn program_id(mut self, program_id: Pubkey) -> Self {
576        self.program_id = Some(program_id);
577        self
578    }
579
580    /// Build the transaction instruction
581    ///
582    /// # Returns
583    /// * `Ok(Instruction)` - The `create_plan` instruction
584    /// * `Err(TallyError)` - If building fails
585    pub fn build_instruction(self) -> Result<Instruction> {
586        let authority = self.authority.ok_or("Authority not set")?;
587        let _payer = self.payer.unwrap_or(authority);
588        let plan_args = self.plan_args.ok_or("Plan args not set")?;
589
590        let program_id = self.program_id.unwrap_or_else(program_id);
591
592        // Compute PDAs
593        let config_pda = pda::config_address_with_program_id(&program_id);
594        let merchant_pda = pda::merchant_address_with_program_id(&authority, &program_id);
595        let plan_pda =
596            pda::plan_address_with_program_id(&merchant_pda, &plan_args.plan_id_bytes, &program_id);
597
598        let accounts = vec![
599            AccountMeta::new_readonly(config_pda, false),   // config
600            AccountMeta::new(plan_pda, false),              // plan (PDA)
601            AccountMeta::new_readonly(merchant_pda, false), // merchant
602            AccountMeta::new(authority, true),              // authority (signer)
603            AccountMeta::new_readonly(system_program::ID, false), // system_program
604        ];
605
606        let data = {
607            let mut data = Vec::new();
608            // Instruction discriminator (computed from "create_plan")
609            data.extend_from_slice(&[77, 43, 141, 254, 212, 118, 41, 186]);
610            borsh::to_writer(&mut data, &plan_args)
611                .map_err(|e| TallyError::Generic(format!("Failed to serialize args: {e}")))?;
612            data
613        };
614
615        Ok(Instruction {
616            program_id,
617            accounts,
618            data,
619        })
620    }
621}
622
623impl UpdatePlanBuilder {
624    /// Create a new update plan builder
625    #[must_use]
626    pub fn new() -> Self {
627        Self::default()
628    }
629
630    /// Set the merchant authority
631    #[must_use]
632    pub const fn authority(mut self, authority: Pubkey) -> Self {
633        self.authority = Some(authority);
634        self
635    }
636
637    /// Set the transaction payer
638    #[must_use]
639    pub const fn payer(mut self, payer: Pubkey) -> Self {
640        self.payer = Some(payer);
641        self
642    }
643
644    /// Set the plan account key
645    #[must_use]
646    pub const fn plan_key(mut self, plan_key: Pubkey) -> Self {
647        self.plan_key = Some(plan_key);
648        self
649    }
650
651    /// Set the plan update arguments
652    #[must_use]
653    pub fn update_args(mut self, args: UpdatePlanArgs) -> Self {
654        self.update_args = Some(args);
655        self
656    }
657
658    /// Set the program ID to use
659    #[must_use]
660    pub const fn program_id(mut self, program_id: Pubkey) -> Self {
661        self.program_id = Some(program_id);
662        self
663    }
664
665    /// Build the transaction instruction
666    ///
667    /// # Arguments
668    /// * `merchant` - The merchant account data for validation
669    ///
670    /// # Returns
671    /// * `Ok(Instruction)` - The `update_plan` instruction
672    /// * `Err(TallyError)` - If building fails
673    pub fn build_instruction(self, merchant: &Merchant) -> Result<Instruction> {
674        let authority = self.authority.ok_or("Authority not set")?;
675        let plan_key = self.plan_key.ok_or("Plan key not set")?;
676        let update_args = self.update_args.ok_or("Update args not set")?;
677
678        // Validate that authority matches merchant authority
679        if authority != merchant.authority {
680            return Err(TallyError::Generic(
681                "Authority does not match merchant authority".to_string(),
682            ));
683        }
684
685        // Validate that at least one field is being updated
686        if !update_args.has_updates() {
687            return Err(TallyError::Generic(
688                "No updates specified in UpdatePlanArgs".to_string(),
689            ));
690        }
691
692        let program_id = self.program_id.unwrap_or_else(program_id);
693
694        // Compute required PDAs
695        let config_pda = pda::config_address_with_program_id(&program_id);
696        let merchant_pda = pda::merchant_address_with_program_id(&authority, &program_id);
697
698        let accounts = vec![
699            AccountMeta::new_readonly(config_pda, false),   // config
700            AccountMeta::new(plan_key, false),              // plan (PDA, mutable)
701            AccountMeta::new_readonly(merchant_pda, false), // merchant
702            AccountMeta::new(authority, true),              // authority (signer)
703        ];
704
705        let data = {
706            let mut data = Vec::new();
707            // Instruction discriminator (computed from "update_plan")
708            data.extend_from_slice(&[219, 200, 88, 176, 158, 63, 253, 127]);
709            borsh::to_writer(&mut data, &update_args)
710                .map_err(|e| TallyError::Generic(format!("Failed to serialize args: {e}")))?;
711            data
712        };
713
714        Ok(Instruction {
715            program_id,
716            accounts,
717            data,
718        })
719    }
720}
721
722impl AdminWithdrawFeesBuilder {
723    /// Create a new admin withdraw fees builder
724    #[must_use]
725    pub fn new() -> Self {
726        Self::default()
727    }
728
729    /// Set the platform authority
730    #[must_use]
731    pub const fn platform_authority(mut self, platform_authority: Pubkey) -> Self {
732        self.platform_authority = Some(platform_authority);
733        self
734    }
735
736    /// Set the platform treasury ATA (source of funds)
737    #[must_use]
738    pub const fn platform_treasury_ata(mut self, platform_treasury_ata: Pubkey) -> Self {
739        self.platform_treasury_ata = Some(platform_treasury_ata);
740        self
741    }
742
743    /// Set the destination ATA (where funds will be sent)
744    #[must_use]
745    pub const fn destination_ata(mut self, destination_ata: Pubkey) -> Self {
746        self.destination_ata = Some(destination_ata);
747        self
748    }
749
750    /// Set the USDC mint
751    #[must_use]
752    pub const fn usdc_mint(mut self, usdc_mint: Pubkey) -> Self {
753        self.usdc_mint = Some(usdc_mint);
754        self
755    }
756
757    /// Set the amount to withdraw
758    #[must_use]
759    pub const fn amount(mut self, amount: u64) -> Self {
760        self.amount = Some(amount);
761        self
762    }
763
764    /// Set the program ID to use
765    #[must_use]
766    pub const fn program_id(mut self, program_id: Pubkey) -> Self {
767        self.program_id = Some(program_id);
768        self
769    }
770
771    /// Build the transaction instruction
772    ///
773    /// # Returns
774    /// * `Ok(Instruction)` - The `admin_withdraw_fees` instruction
775    /// * `Err(TallyError)` - If building fails
776    pub fn build_instruction(self) -> Result<Instruction> {
777        let platform_authority = self
778            .platform_authority
779            .ok_or("Platform authority not set")?;
780        let platform_treasury_ata = self
781            .platform_treasury_ata
782            .ok_or("Platform treasury ATA not set")?;
783        let destination_ata = self.destination_ata.ok_or("Destination ATA not set")?;
784        let usdc_mint = self.usdc_mint.ok_or("USDC mint not set")?;
785        let amount = self.amount.ok_or("Amount not set")?;
786
787        let program_id = self.program_id.unwrap_or_else(program_id);
788
789        // Compute config PDA
790        let config_pda = pda::config_address_with_program_id(&program_id);
791
792        let accounts = vec![
793            AccountMeta::new_readonly(config_pda, false),   // config
794            AccountMeta::new(platform_authority, true),     // platform_authority (signer)
795            AccountMeta::new(platform_treasury_ata, false), // platform_treasury_ata (source, mutable)
796            AccountMeta::new(destination_ata, false), // platform_destination_ata (destination, mutable)
797            AccountMeta::new_readonly(usdc_mint, false), // usdc_mint (readonly)
798            AccountMeta::new_readonly(spl_token::id(), false), // token_program (readonly)
799        ];
800
801        let args = AdminWithdrawFeesArgs { amount };
802
803        let data = {
804            let mut data = Vec::new();
805            // Instruction discriminator (computed from "admin_withdraw_fees")
806            data.extend_from_slice(&[236, 186, 208, 151, 204, 142, 168, 30]);
807            borsh::to_writer(&mut data, &args)
808                .map_err(|e| TallyError::Generic(format!("Failed to serialize args: {e}")))?;
809            data
810        };
811
812        Ok(Instruction {
813            program_id,
814            accounts,
815            data,
816        })
817    }
818}
819
820impl InitConfigBuilder {
821    /// Create a new initialize config builder
822    #[must_use]
823    pub fn new() -> Self {
824        Self::default()
825    }
826
827    /// Set the authority (signer)
828    #[must_use]
829    pub const fn authority(mut self, authority: Pubkey) -> Self {
830        self.authority = Some(authority);
831        self
832    }
833
834    /// Set the transaction payer
835    #[must_use]
836    pub const fn payer(mut self, payer: Pubkey) -> Self {
837        self.payer = Some(payer);
838        self
839    }
840
841    /// Set the configuration arguments
842    #[must_use]
843    pub const fn config_args(mut self, args: InitConfigArgs) -> Self {
844        self.config_args = Some(args);
845        self
846    }
847
848    /// Set the program ID to use
849    #[must_use]
850    pub const fn program_id(mut self, program_id: Pubkey) -> Self {
851        self.program_id = Some(program_id);
852        self
853    }
854
855    /// Build the transaction instruction
856    ///
857    /// # Returns
858    /// * `Ok(Instruction)` - The `init_config` instruction
859    /// * `Err(TallyError)` - If building fails
860    pub fn build_instruction(self) -> Result<Instruction> {
861        let authority = self.authority.ok_or("Authority not set")?;
862        let config_args = self.config_args.ok_or("Config args not set")?;
863
864        let program_id = self.program_id.unwrap_or_else(program_id);
865
866        // Compute config PDA
867        let config_pda = pda::config_address_with_program_id(&program_id);
868
869        let accounts = vec![
870            AccountMeta::new(config_pda, false), // config (PDA)
871            AccountMeta::new(authority, true),   // authority (signer)
872            AccountMeta::new_readonly(system_program::ID, false), // system_program
873        ];
874
875        let data = {
876            let mut data = Vec::new();
877            // Instruction discriminator (computed from "init_config")
878            data.extend_from_slice(&[23, 235, 115, 232, 168, 96, 1, 231]);
879            borsh::to_writer(&mut data, &config_args)
880                .map_err(|e| TallyError::Generic(format!("Failed to serialize args: {e}")))?;
881            data
882        };
883
884        Ok(Instruction {
885            program_id,
886            accounts,
887            data,
888        })
889    }
890}
891
892impl RenewSubscriptionBuilder {
893    /// Create a new renew subscription builder
894    #[must_use]
895    pub fn new() -> Self {
896        Self::default()
897    }
898
899    /// Set the plan PDA
900    #[must_use]
901    pub const fn plan(mut self, plan: Pubkey) -> Self {
902        self.plan = Some(plan);
903        self
904    }
905
906    /// Set the subscriber pubkey
907    #[must_use]
908    pub const fn subscriber(mut self, subscriber: Pubkey) -> Self {
909        self.subscriber = Some(subscriber);
910        self
911    }
912
913    /// Set the keeper (transaction caller) who executes the renewal
914    #[must_use]
915    pub const fn keeper(mut self, keeper: Pubkey) -> Self {
916        self.keeper = Some(keeper);
917        self
918    }
919
920    /// Set the keeper's USDC ATA where keeper fee will be sent
921    #[must_use]
922    pub const fn keeper_ata(mut self, keeper_ata: Pubkey) -> Self {
923        self.keeper_ata = Some(keeper_ata);
924        self
925    }
926
927    /// Set the token program to use
928    #[must_use]
929    pub const fn token_program(mut self, token_program: TokenProgram) -> Self {
930        self.token_program = Some(token_program);
931        self
932    }
933
934    /// Set the program ID to use
935    #[must_use]
936    pub const fn program_id(mut self, program_id: Pubkey) -> Self {
937        self.program_id = Some(program_id);
938        self
939    }
940
941    /// Build the transaction instruction
942    ///
943    /// # Arguments
944    /// * `merchant` - The merchant account data
945    /// * `plan_data` - The plan account data
946    /// * `platform_treasury_ata` - Platform treasury ATA address
947    ///
948    /// # Returns
949    /// * `Ok(Instruction)` - The `renew_subscription` instruction
950    /// * `Err(TallyError)` - If building fails
951    pub fn build_instruction(
952        self,
953        merchant: &Merchant,
954        _plan_data: &Plan,
955        platform_treasury_ata: &Pubkey,
956    ) -> Result<Instruction> {
957        let plan = self.plan.ok_or("Plan not set")?;
958        let subscriber = self.subscriber.ok_or("Subscriber not set")?;
959        let keeper = self.keeper.ok_or("Keeper not set")?;
960        let keeper_ata = self.keeper_ata.ok_or("Keeper ATA not set")?;
961        let token_program = self.token_program.unwrap_or(TokenProgram::Token);
962
963        let program_id = self.program_id.unwrap_or_else(program_id);
964
965        // Compute required PDAs
966        let config_pda = pda::config_address_with_program_id(&program_id);
967        let merchant_pda = pda::merchant_address_with_program_id(&merchant.authority, &program_id);
968        let subscription_pda =
969            pda::subscription_address_with_program_id(&plan, &subscriber, &program_id);
970        let delegate_pda = pda::delegate_address_with_program_id(&merchant_pda, &program_id);
971        let subscriber_ata = get_associated_token_address_with_program(
972            &subscriber,
973            &merchant.usdc_mint,
974            token_program,
975        )?;
976
977        // Create renew_subscription instruction
978        let renew_sub_accounts = vec![
979            AccountMeta::new_readonly(config_pda, false),   // config
980            AccountMeta::new(subscription_pda, false),      // subscription (PDA, mutable)
981            AccountMeta::new_readonly(plan, false),         // plan
982            AccountMeta::new_readonly(merchant_pda, false), // merchant
983            AccountMeta::new(subscriber_ata, false),        // subscriber_usdc_ata (mutable)
984            AccountMeta::new(merchant.treasury_ata, false), // merchant_treasury_ata (mutable)
985            AccountMeta::new(*platform_treasury_ata, false), // platform_treasury_ata (mutable)
986            AccountMeta::new(keeper, true),                 // keeper (signer, mutable for fees)
987            AccountMeta::new(keeper_ata, false),            // keeper_usdc_ata (mutable)
988            AccountMeta::new_readonly(merchant.usdc_mint, false), // usdc_mint
989            AccountMeta::new_readonly(delegate_pda, false), // program_delegate
990            AccountMeta::new_readonly(token_program.program_id(), false), // token_program
991        ];
992
993        let renew_sub_args = crate::program_types::RenewSubscriptionArgs {};
994        let renew_sub_data = {
995            let mut data = Vec::new();
996            // Instruction discriminator (computed from "renew_subscription")
997            data.extend_from_slice(&[45, 75, 154, 194, 160, 10, 111, 183]);
998            borsh::to_writer(&mut data, &renew_sub_args)
999                .map_err(|e| TallyError::Generic(format!("Failed to serialize args: {e}")))?;
1000            data
1001        };
1002
1003        Ok(Instruction {
1004            program_id,
1005            accounts: renew_sub_accounts,
1006            data: renew_sub_data,
1007        })
1008    }
1009}
1010
1011impl CloseSubscriptionBuilder {
1012    /// Create a new close subscription builder
1013    #[must_use]
1014    pub fn new() -> Self {
1015        Self::default()
1016    }
1017
1018    /// Set the plan PDA
1019    #[must_use]
1020    pub const fn plan(mut self, plan: Pubkey) -> Self {
1021        self.plan = Some(plan);
1022        self
1023    }
1024
1025    /// Set the subscriber pubkey
1026    #[must_use]
1027    pub const fn subscriber(mut self, subscriber: Pubkey) -> Self {
1028        self.subscriber = Some(subscriber);
1029        self
1030    }
1031
1032    /// Set the program ID to use
1033    #[must_use]
1034    pub const fn program_id(mut self, program_id: Pubkey) -> Self {
1035        self.program_id = Some(program_id);
1036        self
1037    }
1038
1039    /// Build the transaction instruction
1040    ///
1041    /// # Returns
1042    /// * `Ok(Instruction)` - The `close_subscription` instruction
1043    /// * `Err(TallyError)` - If building fails
1044    pub fn build_instruction(self) -> Result<Instruction> {
1045        let plan = self.plan.ok_or("Plan not set")?;
1046        let subscriber = self.subscriber.ok_or("Subscriber not set")?;
1047
1048        let program_id = self.program_id.unwrap_or_else(program_id);
1049
1050        // Compute subscription PDA
1051        let subscription_pda =
1052            pda::subscription_address_with_program_id(&plan, &subscriber, &program_id);
1053
1054        // Create close_subscription instruction
1055        let close_sub_accounts = vec![
1056            AccountMeta::new(subscription_pda, false), // subscription (PDA, mutable, will be closed)
1057            AccountMeta::new(subscriber, true), // subscriber (signer, mutable, receives rent)
1058        ];
1059
1060        let close_sub_args = crate::program_types::CloseSubscriptionArgs {};
1061        let close_sub_data = {
1062            let mut data = Vec::new();
1063            // Instruction discriminator (computed from "close_subscription")
1064            data.extend_from_slice(&[33, 214, 169, 135, 35, 127, 78, 7]);
1065            borsh::to_writer(&mut data, &close_sub_args)
1066                .map_err(|e| TallyError::Generic(format!("Failed to serialize args: {e}")))?;
1067            data
1068        };
1069
1070        Ok(Instruction {
1071            program_id,
1072            accounts: close_sub_accounts,
1073            data: close_sub_data,
1074        })
1075    }
1076}
1077
1078impl TransferAuthorityBuilder {
1079    /// Create a new transfer authority builder
1080    #[must_use]
1081    pub fn new() -> Self {
1082        Self::default()
1083    }
1084
1085    /// Set the current platform authority (must be signer)
1086    #[must_use]
1087    pub const fn platform_authority(mut self, platform_authority: Pubkey) -> Self {
1088        self.platform_authority = Some(platform_authority);
1089        self
1090    }
1091
1092    /// Set the new authority to transfer to
1093    #[must_use]
1094    pub const fn new_authority(mut self, new_authority: Pubkey) -> Self {
1095        self.new_authority = Some(new_authority);
1096        self
1097    }
1098
1099    /// Set the program ID to use
1100    #[must_use]
1101    pub const fn program_id(mut self, program_id: Pubkey) -> Self {
1102        self.program_id = Some(program_id);
1103        self
1104    }
1105
1106    /// Build the transaction instruction
1107    ///
1108    /// # Returns
1109    /// * `Ok(Instruction)` - The `transfer_authority` instruction
1110    /// * `Err(TallyError)` - If building fails
1111    pub fn build_instruction(self) -> Result<Instruction> {
1112        let platform_authority = self
1113            .platform_authority
1114            .ok_or("Platform authority not set")?;
1115        let new_authority = self.new_authority.ok_or("New authority not set")?;
1116
1117        let program_id = self.program_id.unwrap_or_else(program_id);
1118
1119        // Compute config PDA
1120        let config_pda = pda::config_address_with_program_id(&program_id);
1121
1122        let accounts = vec![
1123            AccountMeta::new(config_pda, false), // config (PDA, mutable)
1124            AccountMeta::new_readonly(platform_authority, true), // platform_authority (signer)
1125        ];
1126
1127        let args = crate::program_types::TransferAuthorityArgs { new_authority };
1128
1129        let data = {
1130            let mut data = Vec::new();
1131            // Instruction discriminator (computed from "global:transfer_authority")
1132            data.extend_from_slice(&[48, 169, 76, 72, 229, 180, 55, 161]);
1133            borsh::to_writer(&mut data, &args)
1134                .map_err(|e| TallyError::Generic(format!("Failed to serialize args: {e}")))?;
1135            data
1136        };
1137
1138        Ok(Instruction {
1139            program_id,
1140            accounts,
1141            data,
1142        })
1143    }
1144}
1145
1146impl AcceptAuthorityBuilder {
1147    /// Create a new accept authority builder
1148    #[must_use]
1149    pub fn new() -> Self {
1150        Self::default()
1151    }
1152
1153    /// Set the new authority (must be signer and pending authority)
1154    #[must_use]
1155    pub const fn new_authority(mut self, new_authority: Pubkey) -> Self {
1156        self.new_authority = Some(new_authority);
1157        self
1158    }
1159
1160    /// Set the program ID to use
1161    #[must_use]
1162    pub const fn program_id(mut self, program_id: Pubkey) -> Self {
1163        self.program_id = Some(program_id);
1164        self
1165    }
1166
1167    /// Build the transaction instruction
1168    ///
1169    /// # Returns
1170    /// * `Ok(Instruction)` - The `accept_authority` instruction
1171    /// * `Err(TallyError)` - If building fails
1172    pub fn build_instruction(self) -> Result<Instruction> {
1173        let new_authority = self.new_authority.ok_or("New authority not set")?;
1174
1175        let program_id = self.program_id.unwrap_or_else(program_id);
1176
1177        // Compute config PDA
1178        let config_pda = pda::config_address_with_program_id(&program_id);
1179
1180        let accounts = vec![
1181            AccountMeta::new(config_pda, false), // config (PDA, mutable)
1182            AccountMeta::new_readonly(new_authority, true), // new_authority (signer)
1183        ];
1184
1185        let args = crate::program_types::AcceptAuthorityArgs::default();
1186
1187        let data = {
1188            let mut data = Vec::new();
1189            // Instruction discriminator (computed from "global:accept_authority")
1190            data.extend_from_slice(&[107, 86, 198, 91, 33, 12, 107, 160]);
1191            borsh::to_writer(&mut data, &args)
1192                .map_err(|e| TallyError::Generic(format!("Failed to serialize args: {e}")))?;
1193            data
1194        };
1195
1196        Ok(Instruction {
1197            program_id,
1198            accounts,
1199            data,
1200        })
1201    }
1202}
1203
1204impl CancelAuthorityTransferBuilder {
1205    /// Create a new cancel authority transfer builder
1206    #[must_use]
1207    pub fn new() -> Self {
1208        Self::default()
1209    }
1210
1211    /// Set the current platform authority (must be signer)
1212    #[must_use]
1213    pub const fn platform_authority(mut self, platform_authority: Pubkey) -> Self {
1214        self.platform_authority = Some(platform_authority);
1215        self
1216    }
1217
1218    /// Set the program ID to use
1219    #[must_use]
1220    pub const fn program_id(mut self, program_id: Pubkey) -> Self {
1221        self.program_id = Some(program_id);
1222        self
1223    }
1224
1225    /// Build the transaction instruction
1226    ///
1227    /// # Returns
1228    /// * `Ok(Instruction)` - The `cancel_authority_transfer` instruction
1229    /// * `Err(TallyError)` - If building fails
1230    pub fn build_instruction(self) -> Result<Instruction> {
1231        let platform_authority = self
1232            .platform_authority
1233            .ok_or("Platform authority not set")?;
1234
1235        let program_id = self.program_id.unwrap_or_else(program_id);
1236
1237        // Compute config PDA
1238        let config_pda = pda::config_address_with_program_id(&program_id);
1239
1240        let accounts = vec![
1241            AccountMeta::new(config_pda, false), // config (PDA, mutable)
1242            AccountMeta::new_readonly(platform_authority, true), // platform_authority (signer)
1243        ];
1244
1245        let args = crate::program_types::CancelAuthorityTransferArgs::default();
1246
1247        let data = {
1248            let mut data = Vec::new();
1249            // Instruction discriminator (computed from "global:cancel_authority_transfer")
1250            data.extend_from_slice(&[94, 131, 125, 184, 183, 24, 125, 229]);
1251            borsh::to_writer(&mut data, &args)
1252                .map_err(|e| TallyError::Generic(format!("Failed to serialize args: {e}")))?;
1253            data
1254        };
1255
1256        Ok(Instruction {
1257            program_id,
1258            accounts,
1259            data,
1260        })
1261    }
1262}
1263
1264impl PauseBuilder {
1265    /// Create a new pause builder
1266    #[must_use]
1267    pub fn new() -> Self {
1268        Self::default()
1269    }
1270
1271    /// Set the platform authority (must be signer)
1272    #[must_use]
1273    pub const fn platform_authority(mut self, platform_authority: Pubkey) -> Self {
1274        self.platform_authority = Some(platform_authority);
1275        self
1276    }
1277
1278    /// Set the program ID to use
1279    #[must_use]
1280    pub const fn program_id(mut self, program_id: Pubkey) -> Self {
1281        self.program_id = Some(program_id);
1282        self
1283    }
1284
1285    /// Build the transaction instruction
1286    ///
1287    /// # Returns
1288    /// * `Ok(Instruction)` - The `pause` instruction
1289    /// * `Err(TallyError)` - If building fails
1290    pub fn build_instruction(self) -> Result<Instruction> {
1291        let platform_authority = self
1292            .platform_authority
1293            .ok_or("Platform authority not set")?;
1294
1295        let program_id = self.program_id.unwrap_or_else(program_id);
1296
1297        // Compute config PDA
1298        let config_pda = pda::config_address_with_program_id(&program_id);
1299
1300        let accounts = vec![
1301            AccountMeta::new(config_pda, false), // config (PDA, mutable)
1302            AccountMeta::new_readonly(platform_authority, true), // platform_authority (signer)
1303        ];
1304
1305        let args = crate::program_types::PauseArgs {};
1306
1307        let data = {
1308            let mut data = Vec::new();
1309            // Instruction discriminator (computed from "global:pause")
1310            data.extend_from_slice(&[211, 22, 221, 251, 74, 121, 193, 47]);
1311            borsh::to_writer(&mut data, &args)
1312                .map_err(|e| TallyError::Generic(format!("Failed to serialize args: {e}")))?;
1313            data
1314        };
1315
1316        Ok(Instruction {
1317            program_id,
1318            accounts,
1319            data,
1320        })
1321    }
1322}
1323
1324impl UnpauseBuilder {
1325    /// Create a new unpause builder
1326    #[must_use]
1327    pub fn new() -> Self {
1328        Self::default()
1329    }
1330
1331    /// Set the platform authority (must be signer)
1332    #[must_use]
1333    pub const fn platform_authority(mut self, platform_authority: Pubkey) -> Self {
1334        self.platform_authority = Some(platform_authority);
1335        self
1336    }
1337
1338    /// Set the program ID to use
1339    #[must_use]
1340    pub const fn program_id(mut self, program_id: Pubkey) -> Self {
1341        self.program_id = Some(program_id);
1342        self
1343    }
1344
1345    /// Build the transaction instruction
1346    ///
1347    /// # Returns
1348    /// * `Ok(Instruction)` - The `unpause` instruction
1349    /// * `Err(TallyError)` - If building fails
1350    pub fn build_instruction(self) -> Result<Instruction> {
1351        let platform_authority = self
1352            .platform_authority
1353            .ok_or("Platform authority not set")?;
1354
1355        let program_id = self.program_id.unwrap_or_else(program_id);
1356
1357        // Compute config PDA
1358        let config_pda = pda::config_address_with_program_id(&program_id);
1359
1360        let accounts = vec![
1361            AccountMeta::new(config_pda, false), // config (PDA, mutable)
1362            AccountMeta::new_readonly(platform_authority, true), // platform_authority (signer)
1363        ];
1364
1365        let args = crate::program_types::UnpauseArgs {};
1366
1367        let data = {
1368            let mut data = Vec::new();
1369            // Instruction discriminator (computed from "global:unpause")
1370            data.extend_from_slice(&[169, 144, 4, 38, 10, 141, 188, 255]);
1371            borsh::to_writer(&mut data, &args)
1372                .map_err(|e| TallyError::Generic(format!("Failed to serialize args: {e}")))?;
1373            data
1374        };
1375
1376        Ok(Instruction {
1377            program_id,
1378            accounts,
1379            data,
1380        })
1381    }
1382}
1383
1384impl UpdateConfigBuilder {
1385    /// Create a new update config builder
1386    #[must_use]
1387    pub fn new() -> Self {
1388        Self::default()
1389    }
1390
1391    /// Set the platform authority (must be signer)
1392    #[must_use]
1393    pub const fn platform_authority(mut self, platform_authority: Pubkey) -> Self {
1394        self.platform_authority = Some(platform_authority);
1395        self
1396    }
1397
1398    /// Set the keeper fee in basis points (0-100)
1399    #[must_use]
1400    pub const fn keeper_fee_bps(mut self, keeper_fee_bps: u16) -> Self {
1401        self.keeper_fee_bps = Some(keeper_fee_bps);
1402        self
1403    }
1404
1405    /// Set the maximum withdrawal amount
1406    #[must_use]
1407    pub const fn max_withdrawal_amount(mut self, max_withdrawal_amount: u64) -> Self {
1408        self.max_withdrawal_amount = Some(max_withdrawal_amount);
1409        self
1410    }
1411
1412    /// Set the maximum grace period in seconds
1413    #[must_use]
1414    pub const fn max_grace_period_seconds(mut self, max_grace_period_seconds: u64) -> Self {
1415        self.max_grace_period_seconds = Some(max_grace_period_seconds);
1416        self
1417    }
1418
1419    /// Set the minimum platform fee in basis points
1420    #[must_use]
1421    pub const fn min_platform_fee_bps(mut self, min_platform_fee_bps: u16) -> Self {
1422        self.min_platform_fee_bps = Some(min_platform_fee_bps);
1423        self
1424    }
1425
1426    /// Set the maximum platform fee in basis points
1427    #[must_use]
1428    pub const fn max_platform_fee_bps(mut self, max_platform_fee_bps: u16) -> Self {
1429        self.max_platform_fee_bps = Some(max_platform_fee_bps);
1430        self
1431    }
1432
1433    /// Set the minimum period in seconds
1434    #[must_use]
1435    pub const fn min_period_seconds(mut self, min_period_seconds: u64) -> Self {
1436        self.min_period_seconds = Some(min_period_seconds);
1437        self
1438    }
1439
1440    /// Set the default allowance periods
1441    #[must_use]
1442    pub const fn default_allowance_periods(mut self, default_allowance_periods: u8) -> Self {
1443        self.default_allowance_periods = Some(default_allowance_periods);
1444        self
1445    }
1446
1447    /// Set the program ID to use
1448    #[must_use]
1449    pub const fn program_id(mut self, program_id: Pubkey) -> Self {
1450        self.program_id = Some(program_id);
1451        self
1452    }
1453
1454    /// Build the transaction instruction
1455    ///
1456    /// # Returns
1457    /// * `Ok(Instruction)` - The `update_config` instruction
1458    /// * `Err(TallyError)` - If building fails
1459    ///
1460    /// # Validation
1461    /// * Platform authority must be set
1462    /// * At least one field must be set for update
1463    /// * `keeper_fee_bps` <= 100 if provided
1464    /// * `min_platform_fee_bps` <= `max_platform_fee_bps` if both provided
1465    /// * All numeric values > 0 where required
1466    pub fn build_instruction(self) -> Result<Instruction> {
1467        let platform_authority = self
1468            .platform_authority
1469            .ok_or("Platform authority not set")?;
1470
1471        let program_id = self.program_id.unwrap_or_else(program_id);
1472
1473        // Validate at least one field is set for update
1474        let has_update = self.keeper_fee_bps.is_some()
1475            || self.max_withdrawal_amount.is_some()
1476            || self.max_grace_period_seconds.is_some()
1477            || self.min_platform_fee_bps.is_some()
1478            || self.max_platform_fee_bps.is_some()
1479            || self.min_period_seconds.is_some()
1480            || self.default_allowance_periods.is_some();
1481
1482        if !has_update {
1483            return Err("At least one configuration field must be set for update".into());
1484        }
1485
1486        // Validate keeper_fee_bps <= 100 if provided
1487        if let Some(keeper_fee) = self.keeper_fee_bps {
1488            if keeper_fee > 100 {
1489                return Err("Keeper fee must be <= 100 basis points (1%)".into());
1490            }
1491        }
1492
1493        // Validate min_platform_fee_bps <= max_platform_fee_bps if both provided
1494        if let Some(min_fee) = self.min_platform_fee_bps {
1495            if let Some(max_fee) = self.max_platform_fee_bps {
1496                if min_fee > max_fee {
1497                    return Err("Minimum platform fee must be <= maximum platform fee".into());
1498                }
1499            }
1500        }
1501
1502        // Validate numeric values > 0 where required
1503        if let Some(max_withdrawal) = self.max_withdrawal_amount {
1504            if max_withdrawal == 0 {
1505                return Err("Maximum withdrawal amount must be > 0".into());
1506            }
1507        }
1508
1509        if let Some(max_grace) = self.max_grace_period_seconds {
1510            if max_grace == 0 {
1511                return Err("Maximum grace period must be > 0".into());
1512            }
1513        }
1514
1515        if let Some(min_period) = self.min_period_seconds {
1516            if min_period == 0 {
1517                return Err("Minimum period must be > 0".into());
1518            }
1519        }
1520
1521        if let Some(allowance_periods) = self.default_allowance_periods {
1522            if allowance_periods == 0 {
1523                return Err("Default allowance periods must be > 0".into());
1524            }
1525        }
1526
1527        // Compute config PDA
1528        let config_pda = pda::config_address_with_program_id(&program_id);
1529
1530        let accounts = vec![
1531            AccountMeta::new(config_pda, false), // config (PDA, mutable)
1532            AccountMeta::new_readonly(platform_authority, true), // platform_authority (signer)
1533        ];
1534
1535        let args = UpdateConfigArgs {
1536            keeper_fee_bps: self.keeper_fee_bps,
1537            max_withdrawal_amount: self.max_withdrawal_amount,
1538            max_grace_period_seconds: self.max_grace_period_seconds,
1539            min_platform_fee_bps: self.min_platform_fee_bps,
1540            max_platform_fee_bps: self.max_platform_fee_bps,
1541            min_period_seconds: self.min_period_seconds,
1542            default_allowance_periods: self.default_allowance_periods,
1543        };
1544
1545        let data = {
1546            let mut data = Vec::new();
1547            // Instruction discriminator (computed from "global:update_config")
1548            data.extend_from_slice(&[29, 158, 252, 191, 10, 83, 219, 99]);
1549            borsh::to_writer(&mut data, &args)
1550                .map_err(|e| TallyError::Generic(format!("Failed to serialize args: {e}")))?;
1551            data
1552        };
1553
1554        Ok(Instruction {
1555            program_id,
1556            accounts,
1557            data,
1558        })
1559    }
1560}
1561
1562impl UpdateMerchantTierBuilder {
1563    /// Create a new update merchant tier builder
1564    #[must_use]
1565    pub fn new() -> Self {
1566        Self::default()
1567    }
1568
1569    /// Set the authority (must be either merchant authority OR platform authority)
1570    #[must_use]
1571    pub const fn authority(mut self, authority: Pubkey) -> Self {
1572        self.authority = Some(authority);
1573        self
1574    }
1575
1576    /// Set the merchant PDA to update
1577    #[must_use]
1578    pub const fn merchant(mut self, merchant: Pubkey) -> Self {
1579        self.merchant = Some(merchant);
1580        self
1581    }
1582
1583    /// Set the new tier (0=Free, 1=Pro, 2=Enterprise)
1584    #[must_use]
1585    pub const fn new_tier(mut self, new_tier: u8) -> Self {
1586        self.new_tier = Some(new_tier);
1587        self
1588    }
1589
1590    /// Set the program ID to use
1591    #[must_use]
1592    pub const fn program_id(mut self, program_id: Pubkey) -> Self {
1593        self.program_id = Some(program_id);
1594        self
1595    }
1596
1597    /// Build the transaction instruction
1598    ///
1599    /// # Returns
1600    /// * `Ok(Instruction)` - The `update_merchant_tier` instruction
1601    /// * `Err(TallyError)` - If building fails
1602    ///
1603    /// # Validation
1604    /// * Authority must be set (merchant or platform authority)
1605    /// * Merchant must be set
1606    /// * New tier must be set and valid (0-2)
1607    pub fn build_instruction(self) -> Result<Instruction> {
1608        let authority = self.authority.ok_or("Authority not set")?;
1609        let merchant = self.merchant.ok_or("Merchant not set")?;
1610        let new_tier = self.new_tier.ok_or("New tier not set")?;
1611
1612        // Validate tier value (0=Free, 1=Pro, 2=Enterprise)
1613        if new_tier > 2 {
1614            return Err("New tier must be 0 (Free), 1 (Pro), or 2 (Enterprise)".into());
1615        }
1616
1617        let program_id = self.program_id.unwrap_or_else(program_id);
1618
1619        // Compute config PDA
1620        let config_pda = pda::config_address_with_program_id(&program_id);
1621
1622        let accounts = vec![
1623            AccountMeta::new_readonly(config_pda, false), // config (PDA, readonly)
1624            AccountMeta::new(merchant, false),            // merchant (PDA, mutable)
1625            AccountMeta::new_readonly(authority, true),   // authority (signer)
1626        ];
1627
1628        let args = crate::program_types::UpdateMerchantTierArgs { new_tier };
1629
1630        let data = {
1631            let mut data = Vec::new();
1632            // Instruction discriminator (computed from "global:update_merchant_tier")
1633            data.extend_from_slice(&[24, 54, 190, 70, 221, 93, 3, 64]);
1634            borsh::to_writer(&mut data, &args)
1635                .map_err(|e| TallyError::Generic(format!("Failed to serialize args: {e}")))?;
1636            data
1637        };
1638
1639        Ok(Instruction {
1640            program_id,
1641            accounts,
1642            data,
1643        })
1644    }
1645}
1646
1647impl UpdatePlanTermsBuilder {
1648    /// Create a new update plan terms builder
1649    #[must_use]
1650    pub fn new() -> Self {
1651        Self::default()
1652    }
1653
1654    /// Set the authority (must be merchant authority)
1655    #[must_use]
1656    pub const fn authority(mut self, authority: Pubkey) -> Self {
1657        self.authority = Some(authority);
1658        self
1659    }
1660
1661    /// Set the plan PDA to update
1662    #[must_use]
1663    pub const fn plan_key(mut self, plan_key: Pubkey) -> Self {
1664        self.plan_key = Some(plan_key);
1665        self
1666    }
1667
1668    /// Set the plan price in USDC microlamports
1669    #[must_use]
1670    pub const fn price_usdc(mut self, price_usdc: u64) -> Self {
1671        self.price_usdc = Some(price_usdc);
1672        self
1673    }
1674
1675    /// Set the subscription period in seconds
1676    #[must_use]
1677    pub const fn period_secs(mut self, period_secs: u64) -> Self {
1678        self.period_secs = Some(period_secs);
1679        self
1680    }
1681
1682    /// Set the grace period in seconds
1683    #[must_use]
1684    pub const fn grace_secs(mut self, grace_secs: u64) -> Self {
1685        self.grace_secs = Some(grace_secs);
1686        self
1687    }
1688
1689    /// Set the plan name
1690    #[must_use]
1691    pub fn name(mut self, name: String) -> Self {
1692        self.name = Some(name);
1693        self
1694    }
1695
1696    /// Set the program ID to use
1697    #[must_use]
1698    pub const fn program_id(mut self, program_id: Pubkey) -> Self {
1699        self.program_id = Some(program_id);
1700        self
1701    }
1702
1703    /// Build the transaction instruction
1704    ///
1705    /// # Returns
1706    /// * `Ok(Instruction)` - The `update_plan_terms` instruction
1707    /// * `Err(TallyError)` - If building fails
1708    ///
1709    /// # Validation
1710    /// * Authority must be set
1711    /// * Plan key must be set
1712    /// * At least one field must be set for update
1713    /// * Price > 0 if provided
1714    /// * Price <= `MAX_PLAN_PRICE_USDC` (1,000,000 USDC) if provided
1715    /// * Period >= `config.min_period_seconds` if provided (validated on-chain)
1716    /// * Grace <= 30% of period if both provided
1717    /// * Grace <= `config.max_grace_period_seconds` if provided (validated on-chain)
1718    /// * Name not empty if provided
1719    ///
1720    /// # Note
1721    /// Some validations (`min_period_seconds`, `max_grace_period_seconds`) require config
1722    /// and are validated on-chain. This builder performs client-side validations where possible.
1723    pub fn build_instruction(self) -> Result<Instruction> {
1724        const MAX_PLAN_PRICE_USDC: u64 = 1_000_000_000_000; // 1 million USDC
1725
1726        let authority = self.authority.ok_or("Authority not set")?;
1727        let plan_key = self.plan_key.ok_or("Plan key not set")?;
1728        let program_id = self.program_id.unwrap_or_else(program_id);
1729
1730        // Validate at least one field is set for update
1731        let has_update = self.price_usdc.is_some()
1732            || self.period_secs.is_some()
1733            || self.grace_secs.is_some()
1734            || self.name.is_some();
1735
1736        if !has_update {
1737            return Err("At least one field must be set for update".into());
1738        }
1739
1740        // Validate price > 0 if provided
1741        if let Some(price) = self.price_usdc {
1742            if price == 0 {
1743                return Err("Price must be > 0".into());
1744            }
1745            if price > MAX_PLAN_PRICE_USDC {
1746                return Err("Price must be <= 1,000,000 USDC".into());
1747            }
1748        }
1749
1750        // Validate grace <= 30% of period if both provided
1751        if let (Some(grace), Some(period)) = (self.grace_secs, self.period_secs) {
1752            let max_grace = period
1753                .checked_mul(3)
1754                .and_then(|v| v.checked_div(10))
1755                .ok_or("Arithmetic overflow calculating max grace period")?;
1756
1757            if grace > max_grace {
1758                return Err("Grace period must be <= 30% of billing period".into());
1759            }
1760        }
1761
1762        // Validate name not empty if provided
1763        if let Some(ref name) = self.name {
1764            if name.is_empty() {
1765                return Err("Name must not be empty".into());
1766            }
1767            if name.len() > 32 {
1768                return Err("Name must be <= 32 bytes".into());
1769            }
1770        }
1771
1772        // Compute PDAs
1773        let config_pda = pda::config_address_with_program_id(&program_id);
1774        let merchant_pda = pda::merchant_address_with_program_id(&authority, &program_id);
1775
1776        let accounts = vec![
1777            AccountMeta::new_readonly(config_pda, false),  // config (PDA, readonly)
1778            AccountMeta::new(plan_key, false),             // plan (PDA, mutable)
1779            AccountMeta::new_readonly(merchant_pda, false), // merchant (PDA, readonly)
1780            AccountMeta::new_readonly(authority, true),    // authority (signer)
1781        ];
1782
1783        let args = UpdatePlanTermsArgs {
1784            price_usdc: self.price_usdc,
1785            period_secs: self.period_secs,
1786            grace_secs: self.grace_secs,
1787            name: self.name,
1788        };
1789
1790        let data = {
1791            let mut data = Vec::new();
1792            // Instruction discriminator (computed from "global:update_plan_terms")
1793            data.extend_from_slice(&[224, 68, 224, 41, 169, 52, 124, 221]);
1794            borsh::to_writer(&mut data, &args)
1795                .map_err(|e| TallyError::Generic(format!("Failed to serialize args: {e}")))?;
1796            data
1797        };
1798
1799        Ok(Instruction {
1800            program_id,
1801            accounts,
1802            data,
1803        })
1804    }
1805}
1806
1807// Convenience functions for common transaction building patterns
1808
1809/// Create a start subscription transaction builder
1810#[must_use]
1811pub fn start_subscription() -> StartSubscriptionBuilder {
1812    StartSubscriptionBuilder::new()
1813}
1814
1815/// Create a cancel subscription transaction builder
1816#[must_use]
1817pub fn cancel_subscription() -> CancelSubscriptionBuilder {
1818    CancelSubscriptionBuilder::new()
1819}
1820
1821/// Create a merchant initialization transaction builder
1822#[must_use]
1823pub fn create_merchant() -> CreateMerchantBuilder {
1824    CreateMerchantBuilder::new()
1825}
1826
1827/// Create a plan creation transaction builder
1828#[must_use]
1829pub fn create_plan() -> CreatePlanBuilder {
1830    CreatePlanBuilder::new()
1831}
1832
1833/// Create an admin withdraw fees transaction builder
1834#[must_use]
1835pub fn admin_withdraw_fees() -> AdminWithdrawFeesBuilder {
1836    AdminWithdrawFeesBuilder::new()
1837}
1838
1839/// Create a config initialization transaction builder
1840#[must_use]
1841pub fn init_config() -> InitConfigBuilder {
1842    InitConfigBuilder::new()
1843}
1844
1845/// Create a plan update transaction builder
1846#[must_use]
1847pub fn update_plan() -> UpdatePlanBuilder {
1848    UpdatePlanBuilder::new()
1849}
1850
1851/// Create a renew subscription transaction builder
1852#[must_use]
1853pub fn renew_subscription() -> RenewSubscriptionBuilder {
1854    RenewSubscriptionBuilder::new()
1855}
1856
1857/// Create a close subscription transaction builder
1858#[must_use]
1859pub fn close_subscription() -> CloseSubscriptionBuilder {
1860    CloseSubscriptionBuilder::new()
1861}
1862
1863/// Create a transfer authority transaction builder
1864#[must_use]
1865pub fn transfer_authority() -> TransferAuthorityBuilder {
1866    TransferAuthorityBuilder::new()
1867}
1868
1869/// Create an accept authority transaction builder
1870#[must_use]
1871pub fn accept_authority() -> AcceptAuthorityBuilder {
1872    AcceptAuthorityBuilder::new()
1873}
1874
1875/// Create a cancel authority transfer transaction builder
1876#[must_use]
1877pub fn cancel_authority_transfer() -> CancelAuthorityTransferBuilder {
1878    CancelAuthorityTransferBuilder::new()
1879}
1880
1881/// Create a pause program transaction builder
1882#[must_use]
1883pub fn pause() -> PauseBuilder {
1884    PauseBuilder::new()
1885}
1886
1887/// Create an unpause program transaction builder
1888#[must_use]
1889pub fn unpause() -> UnpauseBuilder {
1890    UnpauseBuilder::new()
1891}
1892
1893/// Create an update config transaction builder
1894#[must_use]
1895pub fn update_config() -> UpdateConfigBuilder {
1896    UpdateConfigBuilder::new()
1897}
1898
1899/// Create an update merchant tier transaction builder
1900#[must_use]
1901pub fn update_merchant_tier() -> UpdateMerchantTierBuilder {
1902    UpdateMerchantTierBuilder::new()
1903}
1904
1905/// Create an update plan terms transaction builder
1906#[must_use]
1907pub fn update_plan_terms() -> UpdatePlanTermsBuilder {
1908    UpdatePlanTermsBuilder::new()
1909}
1910
1911#[cfg(test)]
1912mod tests {
1913    use super::*;
1914    use anchor_client::solana_sdk::signature::{Keypair, Signer};
1915    use std::str::FromStr;
1916
1917    fn create_test_merchant() -> Merchant {
1918        Merchant {
1919            authority: Pubkey::from(Keypair::new().pubkey().to_bytes()),
1920            usdc_mint: Pubkey::from_str("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v").unwrap(),
1921            treasury_ata: Pubkey::from(Keypair::new().pubkey().to_bytes()),
1922            platform_fee_bps: 50,
1923            tier: 0, // Free tier
1924            bump: 255,
1925        }
1926    }
1927
1928    fn create_test_plan() -> Plan {
1929        let merchant = Pubkey::from(Keypair::new().pubkey().to_bytes());
1930        let mut plan_id = [0u8; 32];
1931        plan_id[..12].copy_from_slice(b"premium_plan");
1932        let mut name = [0u8; 32];
1933        name[..12].copy_from_slice(b"Premium Plan");
1934
1935        Plan {
1936            merchant,
1937            plan_id,
1938            price_usdc: 5_000_000,  // 5 USDC
1939            period_secs: 2_592_000, // 30 days
1940            grace_secs: 432_000,    // 5 days
1941            name,
1942            active: true,
1943        }
1944    }
1945
1946    #[test]
1947    fn test_start_subscription_builder() {
1948        let merchant = create_test_merchant();
1949        let plan_data = create_test_plan();
1950        let plan_key = Pubkey::from(Keypair::new().pubkey().to_bytes());
1951        let subscriber = Pubkey::from(Keypair::new().pubkey().to_bytes());
1952        let platform_treasury_ata = Pubkey::from(Keypair::new().pubkey().to_bytes());
1953
1954        let instructions = start_subscription()
1955            .plan(plan_key)
1956            .subscriber(subscriber)
1957            .allowance_periods(3) // 3x plan price
1958            .build_instructions(&merchant, &plan_data, &platform_treasury_ata)
1959            .unwrap();
1960
1961        assert_eq!(instructions.len(), 2);
1962
1963        // First instruction should be approve_checked
1964        assert_eq!(instructions[0].program_id, spl_token::id());
1965
1966        // Second instruction should be start_subscription
1967        let program_id = program_id();
1968        assert_eq!(instructions[1].program_id, program_id);
1969    }
1970
1971    #[test]
1972    fn test_cancel_subscription_builder() {
1973        let merchant = create_test_merchant();
1974        let plan_key = Pubkey::from(Keypair::new().pubkey().to_bytes());
1975        let subscriber = Pubkey::from(Keypair::new().pubkey().to_bytes());
1976
1977        let instructions = cancel_subscription()
1978            .plan(plan_key)
1979            .subscriber(subscriber)
1980            .build_instructions(&merchant)
1981            .unwrap();
1982
1983        assert_eq!(instructions.len(), 2);
1984
1985        // First instruction should be revoke
1986        assert_eq!(instructions[0].program_id, spl_token::id());
1987
1988        // Second instruction should be cancel_subscription
1989        let program_id = program_id();
1990        assert_eq!(instructions[1].program_id, program_id);
1991    }
1992
1993    #[test]
1994    fn test_create_merchant_builder() {
1995        let authority = Pubkey::from(Keypair::new().pubkey().to_bytes());
1996        let usdc_mint = Pubkey::from_str("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v").unwrap();
1997        let treasury_ata = Pubkey::from(Keypair::new().pubkey().to_bytes());
1998
1999        let instruction = create_merchant()
2000            .authority(authority)
2001            .usdc_mint(usdc_mint)
2002            .treasury_ata(treasury_ata)
2003            .platform_fee_bps(50)
2004            .build_instruction()
2005            .unwrap();
2006
2007        let program_id = program_id();
2008        assert_eq!(instruction.program_id, program_id);
2009        assert_eq!(instruction.accounts.len(), 8);
2010    }
2011
2012    #[test]
2013    fn test_create_plan_builder() {
2014        let authority = Pubkey::from(Keypair::new().pubkey().to_bytes());
2015        let plan_id_bytes = {
2016            let mut bytes = [0u8; 32];
2017            let id_bytes = b"premium";
2018            let len = id_bytes.len().min(32);
2019            bytes[..len].copy_from_slice(&id_bytes[..len]);
2020            bytes
2021        };
2022
2023        let plan_args = CreatePlanArgs {
2024            plan_id: "premium".to_string(),
2025            plan_id_bytes,
2026            price_usdc: 5_000_000,
2027            period_secs: 2_592_000,
2028            grace_secs: 432_000,
2029            name: "Premium Plan".to_string(),
2030        };
2031
2032        let instruction = create_plan()
2033            .authority(authority)
2034            .plan_args(plan_args)
2035            .build_instruction()
2036            .unwrap();
2037
2038        let program_id = program_id();
2039        assert_eq!(instruction.program_id, program_id);
2040        assert_eq!(instruction.accounts.len(), 5);
2041    }
2042
2043    #[test]
2044    fn test_builder_missing_required_fields() {
2045        let merchant = create_test_merchant();
2046        let plan_data = create_test_plan();
2047        let platform_treasury_ata = Pubkey::from(Keypair::new().pubkey().to_bytes());
2048
2049        // Test missing plan
2050        let result = start_subscription()
2051            .subscriber(Pubkey::from(Keypair::new().pubkey().to_bytes()))
2052            .allowance_periods(3)
2053            .build_instructions(&merchant, &plan_data, &platform_treasury_ata);
2054        assert!(result.is_err());
2055        assert!(result.unwrap_err().to_string().contains("Plan not set"));
2056
2057        // Test missing subscriber
2058        let result = start_subscription()
2059            .plan(Pubkey::from(Keypair::new().pubkey().to_bytes()))
2060            .allowance_periods(3)
2061            .build_instructions(&merchant, &plan_data, &platform_treasury_ata);
2062        assert!(result.is_err());
2063        assert!(result
2064            .unwrap_err()
2065            .to_string()
2066            .contains("Subscriber not set"));
2067    }
2068
2069    #[test]
2070    fn test_token_program_variants() {
2071        // Create separate merchants for different token programs to avoid compatibility issues
2072        let merchant_token = Merchant {
2073            authority: Pubkey::from(Keypair::new().pubkey().to_bytes()),
2074            usdc_mint: Pubkey::from(Keypair::new().pubkey().to_bytes()), // Use a test mint for classic token
2075            treasury_ata: Pubkey::from(Keypair::new().pubkey().to_bytes()),
2076            platform_fee_bps: 50,
2077            tier: 0, // Free tier
2078            bump: 255,
2079        };
2080
2081        let merchant_token2022 = Merchant {
2082            authority: Pubkey::from(Keypair::new().pubkey().to_bytes()),
2083            usdc_mint: Pubkey::from(Keypair::new().pubkey().to_bytes()), // Use a different test mint for Token-2022
2084            treasury_ata: Pubkey::from(Keypair::new().pubkey().to_bytes()),
2085            platform_fee_bps: 50,
2086            tier: 0, // Free tier
2087            bump: 255,
2088        };
2089
2090        let plan_data = create_test_plan();
2091        let plan_key = Pubkey::from(Keypair::new().pubkey().to_bytes());
2092        let subscriber = Pubkey::from(Keypair::new().pubkey().to_bytes());
2093        let platform_treasury_ata = Pubkey::from(Keypair::new().pubkey().to_bytes());
2094
2095        // Test with Token-2022
2096        let instructions_token2022 = start_subscription()
2097            .plan(plan_key)
2098            .subscriber(subscriber)
2099            .allowance_periods(3)
2100            .token_program(TokenProgram::Token2022)
2101            .build_instructions(&merchant_token2022, &plan_data, &platform_treasury_ata)
2102            .unwrap();
2103
2104        // Test with classic Token
2105        let instructions_token = start_subscription()
2106            .plan(plan_key)
2107            .subscriber(subscriber)
2108            .allowance_periods(3)
2109            .token_program(TokenProgram::Token)
2110            .build_instructions(&merchant_token, &plan_data, &platform_treasury_ata)
2111            .unwrap();
2112
2113        // Both should work but have different token program IDs
2114        assert_eq!(instructions_token2022[0].program_id, spl_token_2022::id());
2115        assert_eq!(instructions_token[0].program_id, spl_token::id());
2116    }
2117
2118    #[test]
2119    fn test_init_config_builder() {
2120        let authority = Pubkey::from(Keypair::new().pubkey().to_bytes());
2121        let config_args = InitConfigArgs {
2122            platform_authority: authority,
2123            max_platform_fee_bps: 1000,
2124            fee_basis_points_divisor: 10000,
2125            min_period_seconds: 86400,
2126            default_allowance_periods: 3,
2127        };
2128
2129        let instruction = init_config()
2130            .authority(authority)
2131            .config_args(config_args)
2132            .build_instruction()
2133            .unwrap();
2134
2135        let program_id = program_id();
2136        assert_eq!(instruction.program_id, program_id);
2137        assert_eq!(instruction.accounts.len(), 3);
2138
2139        // Verify account metas
2140        assert!(!instruction.accounts[0].is_signer); // config (PDA)
2141        assert!(instruction.accounts[0].is_writable); // config (PDA)
2142        assert!(instruction.accounts[1].is_signer); // authority
2143        assert!(instruction.accounts[1].is_writable); // authority
2144        assert!(!instruction.accounts[2].is_signer); // system_program
2145        assert!(!instruction.accounts[2].is_writable); // system_program
2146    }
2147
2148    #[test]
2149    fn test_init_config_missing_required_fields() {
2150        // Test missing authority
2151        let result = init_config()
2152            .config_args(InitConfigArgs {
2153                platform_authority: Pubkey::from(Keypair::new().pubkey().to_bytes()),
2154                max_platform_fee_bps: 1000,
2155                fee_basis_points_divisor: 10000,
2156                min_period_seconds: 86400,
2157                default_allowance_periods: 3,
2158            })
2159            .build_instruction();
2160        assert!(result.is_err());
2161        assert!(result
2162            .unwrap_err()
2163            .to_string()
2164            .contains("Authority not set"));
2165
2166        // Test missing config args
2167        let result = init_config()
2168            .authority(Pubkey::from(Keypair::new().pubkey().to_bytes()))
2169            .build_instruction();
2170        assert!(result.is_err());
2171        assert!(result
2172            .unwrap_err()
2173            .to_string()
2174            .contains("Config args not set"));
2175    }
2176
2177    #[test]
2178    fn test_update_plan_builder() {
2179        let merchant = create_test_merchant();
2180        let plan_key = Pubkey::from(Keypair::new().pubkey().to_bytes());
2181
2182        let update_args = UpdatePlanArgs::new()
2183            .with_name("Updated Plan".to_string())
2184            .with_active(false)
2185            .with_price_usdc(10_000_000); // 10 USDC
2186
2187        let instruction = update_plan()
2188            .authority(merchant.authority)
2189            .plan_key(plan_key)
2190            .update_args(update_args)
2191            .build_instruction(&merchant)
2192            .unwrap();
2193
2194        let program_id = program_id();
2195        assert_eq!(instruction.program_id, program_id);
2196        assert_eq!(instruction.accounts.len(), 4);
2197
2198        // Verify account structure
2199        assert!(!instruction.accounts[0].is_signer); // config (readonly)
2200        assert!(!instruction.accounts[0].is_writable); // config (readonly)
2201        assert!(!instruction.accounts[1].is_signer); // plan (mutable, but not signer)
2202        assert!(instruction.accounts[1].is_writable); // plan (mutable)
2203        assert!(!instruction.accounts[2].is_signer); // merchant (readonly)
2204        assert!(!instruction.accounts[2].is_writable); // merchant (readonly)
2205        assert!(instruction.accounts[3].is_signer); // authority (signer)
2206        assert!(instruction.accounts[3].is_writable); // authority (mutable for fees)
2207    }
2208
2209    #[test]
2210    fn test_update_plan_builder_missing_required_fields() {
2211        let merchant = create_test_merchant();
2212        let plan_key = Pubkey::from(Keypair::new().pubkey().to_bytes());
2213        let update_args = UpdatePlanArgs::new().with_name("Test".to_string());
2214
2215        // Test missing authority
2216        let result = update_plan()
2217            .plan_key(plan_key)
2218            .update_args(update_args.clone())
2219            .build_instruction(&merchant);
2220        assert!(result.is_err());
2221        assert!(result
2222            .unwrap_err()
2223            .to_string()
2224            .contains("Authority not set"));
2225
2226        // Test missing plan key
2227        let result = update_plan()
2228            .authority(merchant.authority)
2229            .update_args(update_args)
2230            .build_instruction(&merchant);
2231        assert!(result.is_err());
2232        assert!(result.unwrap_err().to_string().contains("Plan key not set"));
2233
2234        // Test missing update args
2235        let result = update_plan()
2236            .authority(merchant.authority)
2237            .plan_key(plan_key)
2238            .build_instruction(&merchant);
2239        assert!(result.is_err());
2240        assert!(result
2241            .unwrap_err()
2242            .to_string()
2243            .contains("Update args not set"));
2244    }
2245
2246    #[test]
2247    fn test_update_plan_builder_validation() {
2248        let merchant = create_test_merchant();
2249        let wrong_authority = Pubkey::from(Keypair::new().pubkey().to_bytes());
2250        let plan_key = Pubkey::from(Keypair::new().pubkey().to_bytes());
2251
2252        // Test wrong authority
2253        let update_args = UpdatePlanArgs::new().with_name("Test".to_string());
2254        let result = update_plan()
2255            .authority(wrong_authority)
2256            .plan_key(plan_key)
2257            .update_args(update_args)
2258            .build_instruction(&merchant);
2259        assert!(result.is_err());
2260        assert!(result
2261            .unwrap_err()
2262            .to_string()
2263            .contains("Authority does not match merchant authority"));
2264
2265        // Test empty update args
2266        let empty_args = UpdatePlanArgs::new();
2267        let result = update_plan()
2268            .authority(merchant.authority)
2269            .plan_key(plan_key)
2270            .update_args(empty_args)
2271            .build_instruction(&merchant);
2272        assert!(result.is_err());
2273        assert!(result
2274            .unwrap_err()
2275            .to_string()
2276            .contains("No updates specified"));
2277    }
2278
2279    #[test]
2280    fn test_update_plan_args_functionality() {
2281        // Test UpdatePlanArgs builder pattern
2282        let args = UpdatePlanArgs::new()
2283            .with_name("New Plan Name".to_string())
2284            .with_active(true)
2285            .with_price_usdc(5_000_000)
2286            .with_period_secs(2_592_000)
2287            .with_grace_secs(432_000);
2288
2289        assert!(args.has_updates());
2290        assert_eq!(args.name, Some("New Plan Name".to_string()));
2291        assert_eq!(args.active, Some(true));
2292        assert_eq!(args.price_usdc, Some(5_000_000));
2293        assert_eq!(args.period_secs, Some(2_592_000));
2294        assert_eq!(args.grace_secs, Some(432_000));
2295
2296        // Test name_bytes conversion
2297        let name_bytes = args.name_bytes().unwrap();
2298        let expected_name = "New Plan Name";
2299        assert_eq!(&name_bytes[..expected_name.len()], expected_name.as_bytes());
2300        // Check that the rest of the array is zero-padded
2301        for &byte in &name_bytes[expected_name.len()..] {
2302            assert_eq!(byte, 0);
2303        }
2304
2305        // Test empty args
2306        let empty_args = UpdatePlanArgs::new();
2307        assert!(!empty_args.has_updates());
2308        assert!(empty_args.name_bytes().is_none());
2309
2310        // Test default
2311        let default_args = UpdatePlanArgs::default();
2312        assert!(!default_args.has_updates());
2313    }
2314
2315    #[test]
2316    fn test_renew_subscription_builder() {
2317        let merchant = create_test_merchant();
2318        let plan_data = create_test_plan();
2319        let plan_key = Pubkey::from(Keypair::new().pubkey().to_bytes());
2320        let subscriber = Pubkey::from(Keypair::new().pubkey().to_bytes());
2321        let keeper = Pubkey::from(Keypair::new().pubkey().to_bytes());
2322        let keeper_ata = Pubkey::from(Keypair::new().pubkey().to_bytes());
2323        let platform_treasury_ata = Pubkey::from(Keypair::new().pubkey().to_bytes());
2324
2325        let instruction = renew_subscription()
2326            .plan(plan_key)
2327            .subscriber(subscriber)
2328            .keeper(keeper)
2329            .keeper_ata(keeper_ata)
2330            .build_instruction(&merchant, &plan_data, &platform_treasury_ata)
2331            .unwrap();
2332
2333        let program_id = program_id();
2334        assert_eq!(instruction.program_id, program_id);
2335        assert_eq!(instruction.accounts.len(), 12);
2336
2337        // Verify instruction discriminator matches program
2338        assert_eq!(
2339            &instruction.data[..8],
2340            &[45, 75, 154, 194, 160, 10, 111, 183]
2341        );
2342
2343        // Verify readonly accounts
2344        verify_renew_readonly_accounts(&instruction);
2345        // Verify mutable accounts
2346        verify_renew_mutable_accounts(&instruction);
2347        // Verify signer accounts
2348        verify_renew_signer_accounts(&instruction);
2349    }
2350
2351    fn verify_renew_readonly_accounts(instruction: &Instruction) {
2352        assert!(!instruction.accounts[0].is_writable); // config
2353        assert!(!instruction.accounts[2].is_writable); // plan
2354        assert!(!instruction.accounts[3].is_writable); // merchant
2355        assert!(!instruction.accounts[9].is_writable); // usdc_mint
2356        assert!(!instruction.accounts[10].is_writable); // program_delegate
2357        assert!(!instruction.accounts[11].is_writable); // token_program
2358    }
2359
2360    fn verify_renew_mutable_accounts(instruction: &Instruction) {
2361        assert!(instruction.accounts[1].is_writable); // subscription
2362        assert!(instruction.accounts[4].is_writable); // subscriber_usdc_ata
2363        assert!(instruction.accounts[5].is_writable); // merchant_treasury_ata
2364        assert!(instruction.accounts[6].is_writable); // platform_treasury_ata
2365        assert!(instruction.accounts[7].is_writable); // keeper
2366        assert!(instruction.accounts[8].is_writable); // keeper_usdc_ata
2367    }
2368
2369    fn verify_renew_signer_accounts(instruction: &Instruction) {
2370        assert!(!instruction.accounts[0].is_signer); // config
2371        assert!(!instruction.accounts[1].is_signer); // subscription
2372        assert!(!instruction.accounts[2].is_signer); // plan
2373        assert!(!instruction.accounts[3].is_signer); // merchant
2374        assert!(!instruction.accounts[4].is_signer); // subscriber_usdc_ata
2375        assert!(!instruction.accounts[5].is_signer); // merchant_treasury_ata
2376        assert!(!instruction.accounts[6].is_signer); // platform_treasury_ata
2377        assert!(instruction.accounts[7].is_signer); // keeper (only signer)
2378        assert!(!instruction.accounts[8].is_signer); // keeper_usdc_ata
2379        assert!(!instruction.accounts[9].is_signer); // usdc_mint
2380        assert!(!instruction.accounts[10].is_signer); // program_delegate
2381        assert!(!instruction.accounts[11].is_signer); // token_program
2382    }
2383
2384    #[test]
2385    fn test_renew_subscription_builder_missing_required_fields() {
2386        let merchant = create_test_merchant();
2387        let plan_data = create_test_plan();
2388        let platform_treasury_ata = Pubkey::from(Keypair::new().pubkey().to_bytes());
2389
2390        // Test missing plan
2391        let result = renew_subscription()
2392            .subscriber(Pubkey::from(Keypair::new().pubkey().to_bytes()))
2393            .keeper(Pubkey::from(Keypair::new().pubkey().to_bytes()))
2394            .keeper_ata(Pubkey::from(Keypair::new().pubkey().to_bytes()))
2395            .build_instruction(&merchant, &plan_data, &platform_treasury_ata);
2396        assert!(result.is_err());
2397        assert!(result.unwrap_err().to_string().contains("Plan not set"));
2398
2399        // Test missing subscriber
2400        let result = renew_subscription()
2401            .plan(Pubkey::from(Keypair::new().pubkey().to_bytes()))
2402            .keeper(Pubkey::from(Keypair::new().pubkey().to_bytes()))
2403            .keeper_ata(Pubkey::from(Keypair::new().pubkey().to_bytes()))
2404            .build_instruction(&merchant, &plan_data, &platform_treasury_ata);
2405        assert!(result.is_err());
2406        assert!(result
2407            .unwrap_err()
2408            .to_string()
2409            .contains("Subscriber not set"));
2410
2411        // Test missing keeper
2412        let result = renew_subscription()
2413            .plan(Pubkey::from(Keypair::new().pubkey().to_bytes()))
2414            .subscriber(Pubkey::from(Keypair::new().pubkey().to_bytes()))
2415            .keeper_ata(Pubkey::from(Keypair::new().pubkey().to_bytes()))
2416            .build_instruction(&merchant, &plan_data, &platform_treasury_ata);
2417        assert!(result.is_err());
2418        assert!(result.unwrap_err().to_string().contains("Keeper not set"));
2419
2420        // Test missing keeper_ata
2421        let result = renew_subscription()
2422            .plan(Pubkey::from(Keypair::new().pubkey().to_bytes()))
2423            .subscriber(Pubkey::from(Keypair::new().pubkey().to_bytes()))
2424            .keeper(Pubkey::from(Keypair::new().pubkey().to_bytes()))
2425            .build_instruction(&merchant, &plan_data, &platform_treasury_ata);
2426        assert!(result.is_err());
2427        assert!(result
2428            .unwrap_err()
2429            .to_string()
2430            .contains("Keeper ATA not set"));
2431    }
2432
2433    #[test]
2434    fn test_renew_subscription_token_program_variants() {
2435        // Create separate merchants for different token programs
2436        let merchant_token = Merchant {
2437            authority: Pubkey::from(Keypair::new().pubkey().to_bytes()),
2438            usdc_mint: Pubkey::from(Keypair::new().pubkey().to_bytes()),
2439            treasury_ata: Pubkey::from(Keypair::new().pubkey().to_bytes()),
2440            platform_fee_bps: 50,
2441            tier: 0,
2442            bump: 255,
2443        };
2444
2445        let merchant_token2022 = Merchant {
2446            authority: Pubkey::from(Keypair::new().pubkey().to_bytes()),
2447            usdc_mint: Pubkey::from(Keypair::new().pubkey().to_bytes()),
2448            treasury_ata: Pubkey::from(Keypair::new().pubkey().to_bytes()),
2449            platform_fee_bps: 50,
2450            tier: 0,
2451            bump: 255,
2452        };
2453
2454        let plan_data = create_test_plan();
2455        let plan_key = Pubkey::from(Keypair::new().pubkey().to_bytes());
2456        let subscriber = Pubkey::from(Keypair::new().pubkey().to_bytes());
2457        let keeper = Pubkey::from(Keypair::new().pubkey().to_bytes());
2458        let keeper_ata = Pubkey::from(Keypair::new().pubkey().to_bytes());
2459        let platform_treasury_ata = Pubkey::from(Keypair::new().pubkey().to_bytes());
2460
2461        // Test with Token-2022
2462        let instruction_token2022 = renew_subscription()
2463            .plan(plan_key)
2464            .subscriber(subscriber)
2465            .keeper(keeper)
2466            .keeper_ata(keeper_ata)
2467            .token_program(TokenProgram::Token2022)
2468            .build_instruction(&merchant_token2022, &plan_data, &platform_treasury_ata)
2469            .unwrap();
2470
2471        // Test with classic Token
2472        let instruction_token = renew_subscription()
2473            .plan(plan_key)
2474            .subscriber(subscriber)
2475            .keeper(keeper)
2476            .keeper_ata(keeper_ata)
2477            .token_program(TokenProgram::Token)
2478            .build_instruction(&merchant_token, &plan_data, &platform_treasury_ata)
2479            .unwrap();
2480
2481        // Both should work but have different token program IDs in the accounts
2482        assert_eq!(
2483            instruction_token2022.accounts[11].pubkey,
2484            spl_token_2022::id()
2485        );
2486        assert_eq!(instruction_token.accounts[11].pubkey, spl_token::id());
2487    }
2488
2489    #[test]
2490    fn test_close_subscription_builder() {
2491        let plan_key = Pubkey::from(Keypair::new().pubkey().to_bytes());
2492        let subscriber = Pubkey::from(Keypair::new().pubkey().to_bytes());
2493
2494        let instruction = close_subscription()
2495            .plan(plan_key)
2496            .subscriber(subscriber)
2497            .build_instruction()
2498            .unwrap();
2499
2500        let program_id = program_id();
2501        assert_eq!(instruction.program_id, program_id);
2502        assert_eq!(instruction.accounts.len(), 2);
2503
2504        // Verify instruction discriminator matches program
2505        assert_eq!(&instruction.data[..8], &[33, 214, 169, 135, 35, 127, 78, 7]);
2506
2507        // Verify account structure
2508        assert!(instruction.accounts[0].is_writable); // subscription (mutable)
2509        assert!(!instruction.accounts[0].is_signer); // subscription (not signer, it's a PDA)
2510        assert!(instruction.accounts[1].is_writable); // subscriber (mutable, receives rent)
2511        assert!(instruction.accounts[1].is_signer); // subscriber (signer)
2512    }
2513
2514    #[test]
2515    fn test_close_subscription_builder_missing_required_fields() {
2516        // Test missing plan
2517        let result = close_subscription()
2518            .subscriber(Pubkey::from(Keypair::new().pubkey().to_bytes()))
2519            .build_instruction();
2520        assert!(result.is_err());
2521        assert!(result.unwrap_err().to_string().contains("Plan not set"));
2522
2523        // Test missing subscriber
2524        let result = close_subscription()
2525            .plan(Pubkey::from(Keypair::new().pubkey().to_bytes()))
2526            .build_instruction();
2527        assert!(result.is_err());
2528        assert!(result
2529            .unwrap_err()
2530            .to_string()
2531            .contains("Subscriber not set"));
2532    }
2533
2534    #[test]
2535    fn test_close_subscription_builder_custom_program_id() {
2536        let plan_key = Pubkey::from(Keypair::new().pubkey().to_bytes());
2537        let subscriber = Pubkey::from(Keypair::new().pubkey().to_bytes());
2538        let custom_program_id = Pubkey::from(Keypair::new().pubkey().to_bytes());
2539
2540        let instruction = close_subscription()
2541            .plan(plan_key)
2542            .subscriber(subscriber)
2543            .program_id(custom_program_id)
2544            .build_instruction()
2545            .unwrap();
2546
2547        assert_eq!(instruction.program_id, custom_program_id);
2548    }
2549
2550    #[test]
2551    fn test_close_subscription_builder_pda_computation() {
2552        let plan_key = Pubkey::from(Keypair::new().pubkey().to_bytes());
2553        let subscriber = Pubkey::from(Keypair::new().pubkey().to_bytes());
2554
2555        let instruction = close_subscription()
2556            .plan(plan_key)
2557            .subscriber(subscriber)
2558            .build_instruction()
2559            .unwrap();
2560
2561        // Verify the computed subscription PDA is correct
2562        let program_id = program_id();
2563        let expected_subscription_pda =
2564            pda::subscription_address_with_program_id(&plan_key, &subscriber, &program_id);
2565
2566        assert_eq!(instruction.accounts[0].pubkey, expected_subscription_pda);
2567        assert_eq!(instruction.accounts[1].pubkey, subscriber);
2568    }
2569
2570    #[test]
2571    fn test_transfer_authority_builder() {
2572        let platform_authority = Pubkey::from(Keypair::new().pubkey().to_bytes());
2573        let new_authority = Pubkey::from(Keypair::new().pubkey().to_bytes());
2574
2575        let instruction = transfer_authority()
2576            .platform_authority(platform_authority)
2577            .new_authority(new_authority)
2578            .build_instruction()
2579            .unwrap();
2580
2581        let program_id = program_id();
2582        assert_eq!(instruction.program_id, program_id);
2583        assert_eq!(instruction.accounts.len(), 2);
2584
2585        // Verify instruction discriminator matches program
2586        assert_eq!(
2587            &instruction.data[..8],
2588            &[48, 169, 76, 72, 229, 180, 55, 161]
2589        );
2590
2591        // Verify account structure
2592        assert!(instruction.accounts[0].is_writable); // config (mutable)
2593        assert!(!instruction.accounts[0].is_signer); // config (not signer, it's a PDA)
2594        assert!(!instruction.accounts[1].is_writable); // platform_authority (readonly)
2595        assert!(instruction.accounts[1].is_signer); // platform_authority (signer)
2596
2597        // Verify account addresses
2598        assert_eq!(
2599            instruction.accounts[0].pubkey,
2600            pda::config_address_with_program_id(&program_id)
2601        );
2602        assert_eq!(instruction.accounts[1].pubkey, platform_authority);
2603    }
2604
2605    #[test]
2606    fn test_transfer_authority_builder_missing_required_fields() {
2607        // Test missing platform_authority
2608        let result = transfer_authority()
2609            .new_authority(Pubkey::from(Keypair::new().pubkey().to_bytes()))
2610            .build_instruction();
2611        assert!(result.is_err());
2612        assert!(result
2613            .unwrap_err()
2614            .to_string()
2615            .contains("Platform authority not set"));
2616
2617        // Test missing new_authority
2618        let result = transfer_authority()
2619            .platform_authority(Pubkey::from(Keypair::new().pubkey().to_bytes()))
2620            .build_instruction();
2621        assert!(result.is_err());
2622        assert!(result
2623            .unwrap_err()
2624            .to_string()
2625            .contains("New authority not set"));
2626    }
2627
2628    #[test]
2629    fn test_transfer_authority_builder_custom_program_id() {
2630        let platform_authority = Pubkey::from(Keypair::new().pubkey().to_bytes());
2631        let new_authority = Pubkey::from(Keypair::new().pubkey().to_bytes());
2632        let custom_program_id = Pubkey::from(Keypair::new().pubkey().to_bytes());
2633
2634        let instruction = transfer_authority()
2635            .platform_authority(platform_authority)
2636            .new_authority(new_authority)
2637            .program_id(custom_program_id)
2638            .build_instruction()
2639            .unwrap();
2640
2641        assert_eq!(instruction.program_id, custom_program_id);
2642    }
2643
2644    #[test]
2645    fn test_transfer_authority_builder_pda_computation() {
2646        let platform_authority = Pubkey::from(Keypair::new().pubkey().to_bytes());
2647        let new_authority = Pubkey::from(Keypair::new().pubkey().to_bytes());
2648
2649        let instruction = transfer_authority()
2650            .platform_authority(platform_authority)
2651            .new_authority(new_authority)
2652            .build_instruction()
2653            .unwrap();
2654
2655        // Verify the computed config PDA is correct
2656        let program_id = program_id();
2657        let expected_config_pda = pda::config_address_with_program_id(&program_id);
2658
2659        assert_eq!(instruction.accounts[0].pubkey, expected_config_pda);
2660        assert_eq!(instruction.accounts[1].pubkey, platform_authority);
2661    }
2662
2663    #[test]
2664    fn test_transfer_authority_args_serialization() {
2665        let new_authority = Pubkey::from(Keypair::new().pubkey().to_bytes());
2666
2667        // Test that args can be serialized and included in instruction data
2668        let instruction = transfer_authority()
2669            .platform_authority(Pubkey::from(Keypair::new().pubkey().to_bytes()))
2670            .new_authority(new_authority)
2671            .build_instruction()
2672            .unwrap();
2673
2674        // Verify the data contains the discriminator (8 bytes) followed by serialized args
2675        assert!(instruction.data.len() > 8);
2676
2677        // Verify we can deserialize the args from the instruction data
2678        let args_data = &instruction.data[8..];
2679        let deserialized_args =
2680            crate::program_types::TransferAuthorityArgs::try_from_slice(args_data).unwrap();
2681        assert_eq!(deserialized_args.new_authority, new_authority);
2682    }
2683
2684    #[test]
2685    fn test_transfer_authority_builder_clone_debug() {
2686        let builder = transfer_authority()
2687            .platform_authority(Pubkey::from(Keypair::new().pubkey().to_bytes()))
2688            .new_authority(Pubkey::from(Keypair::new().pubkey().to_bytes()));
2689
2690        // Test Clone trait
2691        let cloned_builder = builder.clone();
2692        assert_eq!(
2693            cloned_builder.platform_authority,
2694            builder.platform_authority
2695        );
2696        assert_eq!(cloned_builder.new_authority, builder.new_authority);
2697
2698        // Test Debug trait
2699        let debug_str = format!("{builder:?}");
2700        assert!(debug_str.contains("TransferAuthorityBuilder"));
2701    }
2702
2703    #[test]
2704    fn test_accept_authority_builder() {
2705        let new_authority = Pubkey::from(Keypair::new().pubkey().to_bytes());
2706
2707        let instruction = accept_authority()
2708            .new_authority(new_authority)
2709            .build_instruction()
2710            .unwrap();
2711
2712        let program_id = program_id();
2713        assert_eq!(instruction.program_id, program_id);
2714        assert_eq!(instruction.accounts.len(), 2);
2715
2716        // Verify instruction discriminator matches program
2717        assert_eq!(
2718            &instruction.data[..8],
2719            &[107, 86, 198, 91, 33, 12, 107, 160]
2720        );
2721
2722        // Verify account structure
2723        assert!(instruction.accounts[0].is_writable); // config (mutable)
2724        assert!(!instruction.accounts[0].is_signer); // config (not signer, it's a PDA)
2725        assert!(!instruction.accounts[1].is_writable); // new_authority (readonly)
2726        assert!(instruction.accounts[1].is_signer); // new_authority (signer)
2727
2728        // Verify account addresses
2729        assert_eq!(
2730            instruction.accounts[0].pubkey,
2731            pda::config_address_with_program_id(&program_id)
2732        );
2733        assert_eq!(instruction.accounts[1].pubkey, new_authority);
2734    }
2735
2736    #[test]
2737    fn test_accept_authority_builder_missing_required_fields() {
2738        // Test missing new_authority
2739        let result = accept_authority().build_instruction();
2740        assert!(result.is_err());
2741        assert!(result
2742            .unwrap_err()
2743            .to_string()
2744            .contains("New authority not set"));
2745    }
2746
2747    #[test]
2748    fn test_accept_authority_builder_custom_program_id() {
2749        let new_authority = Pubkey::from(Keypair::new().pubkey().to_bytes());
2750        let custom_program_id = Pubkey::from(Keypair::new().pubkey().to_bytes());
2751
2752        let instruction = accept_authority()
2753            .new_authority(new_authority)
2754            .program_id(custom_program_id)
2755            .build_instruction()
2756            .unwrap();
2757
2758        assert_eq!(instruction.program_id, custom_program_id);
2759    }
2760
2761    #[test]
2762    fn test_accept_authority_builder_pda_computation() {
2763        let new_authority = Pubkey::from(Keypair::new().pubkey().to_bytes());
2764
2765        let instruction = accept_authority()
2766            .new_authority(new_authority)
2767            .build_instruction()
2768            .unwrap();
2769
2770        // Verify the computed config PDA is correct
2771        let program_id = program_id();
2772        let expected_config_pda = pda::config_address_with_program_id(&program_id);
2773
2774        assert_eq!(instruction.accounts[0].pubkey, expected_config_pda);
2775        assert_eq!(instruction.accounts[1].pubkey, new_authority);
2776    }
2777
2778    #[test]
2779    fn test_accept_authority_args_serialization() {
2780        let new_authority = Pubkey::from(Keypair::new().pubkey().to_bytes());
2781
2782        // Test that args can be serialized and included in instruction data
2783        let instruction = accept_authority()
2784            .new_authority(new_authority)
2785            .build_instruction()
2786            .unwrap();
2787
2788        // Verify the data contains the discriminator (8 bytes) followed by serialized args
2789        // AcceptAuthorityArgs is empty, so data should be exactly 8 bytes (discriminator only)
2790        assert_eq!(instruction.data.len(), 8);
2791
2792        // Verify the discriminator matches
2793        assert_eq!(
2794            &instruction.data[..8],
2795            &[107, 86, 198, 91, 33, 12, 107, 160]
2796        );
2797    }
2798
2799    #[test]
2800    fn test_accept_authority_builder_clone_debug() {
2801        let builder =
2802            accept_authority().new_authority(Pubkey::from(Keypair::new().pubkey().to_bytes()));
2803
2804        // Test Clone trait
2805        let cloned_builder = builder.clone();
2806        assert_eq!(cloned_builder.new_authority, builder.new_authority);
2807
2808        // Test Debug trait
2809        let debug_str = format!("{builder:?}");
2810        assert!(debug_str.contains("AcceptAuthorityBuilder"));
2811    }
2812
2813    #[test]
2814    fn test_accept_authority_builder_default() {
2815        let builder = AcceptAuthorityBuilder::default();
2816        assert!(builder.new_authority.is_none());
2817        assert!(builder.program_id.is_none());
2818    }
2819
2820    #[test]
2821    fn test_accept_authority_convenience_function() {
2822        let new_authority = Pubkey::from(Keypair::new().pubkey().to_bytes());
2823
2824        // Test using convenience function
2825        let instruction = accept_authority()
2826            .new_authority(new_authority)
2827            .build_instruction()
2828            .unwrap();
2829
2830        // Verify it works the same as using the builder directly
2831        let direct_instruction = AcceptAuthorityBuilder::new()
2832            .new_authority(new_authority)
2833            .build_instruction()
2834            .unwrap();
2835
2836        assert_eq!(instruction.program_id, direct_instruction.program_id);
2837        assert_eq!(
2838            instruction.accounts.len(),
2839            direct_instruction.accounts.len()
2840        );
2841        assert_eq!(instruction.data, direct_instruction.data);
2842    }
2843
2844    #[test]
2845    fn test_cancel_authority_transfer_builder() {
2846        let platform_authority = Pubkey::from(Keypair::new().pubkey().to_bytes());
2847
2848        let instruction = cancel_authority_transfer()
2849            .platform_authority(platform_authority)
2850            .build_instruction()
2851            .unwrap();
2852
2853        let program_id = program_id();
2854        assert_eq!(instruction.program_id, program_id);
2855        assert_eq!(instruction.accounts.len(), 2);
2856
2857        // Verify instruction discriminator matches program
2858        assert_eq!(
2859            &instruction.data[..8],
2860            &[94, 131, 125, 184, 183, 24, 125, 229]
2861        );
2862
2863        // Verify account structure
2864        assert!(instruction.accounts[0].is_writable); // config (mutable)
2865        assert!(!instruction.accounts[0].is_signer); // config (not signer, it's a PDA)
2866        assert!(!instruction.accounts[1].is_writable); // platform_authority (readonly)
2867        assert!(instruction.accounts[1].is_signer); // platform_authority (signer)
2868
2869        // Verify account addresses
2870        assert_eq!(
2871            instruction.accounts[0].pubkey,
2872            pda::config_address_with_program_id(&program_id)
2873        );
2874        assert_eq!(instruction.accounts[1].pubkey, platform_authority);
2875    }
2876
2877    #[test]
2878    fn test_cancel_authority_transfer_builder_missing_required_fields() {
2879        // Test missing platform_authority
2880        let result = cancel_authority_transfer().build_instruction();
2881        assert!(result.is_err());
2882        assert!(result
2883            .unwrap_err()
2884            .to_string()
2885            .contains("Platform authority not set"));
2886    }
2887
2888    #[test]
2889    fn test_cancel_authority_transfer_builder_custom_program_id() {
2890        let platform_authority = Pubkey::from(Keypair::new().pubkey().to_bytes());
2891        let custom_program_id = Pubkey::from(Keypair::new().pubkey().to_bytes());
2892
2893        let instruction = cancel_authority_transfer()
2894            .platform_authority(platform_authority)
2895            .program_id(custom_program_id)
2896            .build_instruction()
2897            .unwrap();
2898
2899        assert_eq!(instruction.program_id, custom_program_id);
2900    }
2901
2902    #[test]
2903    fn test_cancel_authority_transfer_builder_pda_computation() {
2904        let platform_authority = Pubkey::from(Keypair::new().pubkey().to_bytes());
2905
2906        let instruction = cancel_authority_transfer()
2907            .platform_authority(platform_authority)
2908            .build_instruction()
2909            .unwrap();
2910
2911        // Verify the computed config PDA is correct
2912        let program_id = program_id();
2913        let expected_config_pda = pda::config_address_with_program_id(&program_id);
2914
2915        assert_eq!(instruction.accounts[0].pubkey, expected_config_pda);
2916        assert_eq!(instruction.accounts[1].pubkey, platform_authority);
2917    }
2918
2919    #[test]
2920    fn test_cancel_authority_transfer_args_serialization() {
2921        let platform_authority = Pubkey::from(Keypair::new().pubkey().to_bytes());
2922
2923        // Test that args can be serialized and included in instruction data
2924        let instruction = cancel_authority_transfer()
2925            .platform_authority(platform_authority)
2926            .build_instruction()
2927            .unwrap();
2928
2929        // Verify the data contains the discriminator (8 bytes) followed by serialized args
2930        // CancelAuthorityTransferArgs is empty, so data should be exactly 8 bytes (discriminator only)
2931        assert_eq!(instruction.data.len(), 8);
2932
2933        // Verify the discriminator matches
2934        assert_eq!(
2935            &instruction.data[..8],
2936            &[94, 131, 125, 184, 183, 24, 125, 229]
2937        );
2938    }
2939
2940    #[test]
2941    fn test_cancel_authority_transfer_builder_clone_debug() {
2942        let builder = cancel_authority_transfer()
2943            .platform_authority(Pubkey::from(Keypair::new().pubkey().to_bytes()));
2944
2945        // Test Clone trait
2946        let cloned_builder = builder.clone();
2947        assert_eq!(
2948            cloned_builder.platform_authority,
2949            builder.platform_authority
2950        );
2951
2952        // Test Debug trait
2953        let debug_str = format!("{builder:?}");
2954        assert!(debug_str.contains("CancelAuthorityTransferBuilder"));
2955    }
2956
2957    #[test]
2958    fn test_cancel_authority_transfer_builder_default() {
2959        let builder = CancelAuthorityTransferBuilder::default();
2960        assert!(builder.platform_authority.is_none());
2961        assert!(builder.program_id.is_none());
2962    }
2963
2964    #[test]
2965    fn test_cancel_authority_transfer_convenience_function() {
2966        let platform_authority = Pubkey::from(Keypair::new().pubkey().to_bytes());
2967
2968        // Test using convenience function
2969        let instruction = cancel_authority_transfer()
2970            .platform_authority(platform_authority)
2971            .build_instruction()
2972            .unwrap();
2973
2974        // Verify it works the same as using the builder directly
2975        let direct_instruction = CancelAuthorityTransferBuilder::new()
2976            .platform_authority(platform_authority)
2977            .build_instruction()
2978            .unwrap();
2979
2980        assert_eq!(instruction.program_id, direct_instruction.program_id);
2981        assert_eq!(
2982            instruction.accounts.len(),
2983            direct_instruction.accounts.len()
2984        );
2985        assert_eq!(instruction.data, direct_instruction.data);
2986    }
2987
2988    #[test]
2989    fn test_pause_builder() {
2990        let platform_authority = Pubkey::from(Keypair::new().pubkey().to_bytes());
2991
2992        let instruction = pause()
2993            .platform_authority(platform_authority)
2994            .build_instruction()
2995            .unwrap();
2996
2997        let program_id = program_id();
2998        assert_eq!(instruction.program_id, program_id);
2999        assert_eq!(instruction.accounts.len(), 2);
3000
3001        // Verify instruction discriminator matches program
3002        assert_eq!(
3003            &instruction.data[..8],
3004            &[211, 22, 221, 251, 74, 121, 193, 47]
3005        );
3006
3007        // Verify account structure
3008        assert!(instruction.accounts[0].is_writable); // config (mutable)
3009        assert!(!instruction.accounts[0].is_signer); // config (not signer, it's a PDA)
3010        assert!(!instruction.accounts[1].is_writable); // platform_authority (readonly)
3011        assert!(instruction.accounts[1].is_signer); // platform_authority (signer)
3012
3013        // Verify account addresses
3014        assert_eq!(
3015            instruction.accounts[0].pubkey,
3016            pda::config_address_with_program_id(&program_id)
3017        );
3018        assert_eq!(instruction.accounts[1].pubkey, platform_authority);
3019    }
3020
3021    #[test]
3022    fn test_pause_builder_missing_required_fields() {
3023        // Test missing platform_authority
3024        let result = pause().build_instruction();
3025        assert!(result.is_err());
3026        assert!(result
3027            .unwrap_err()
3028            .to_string()
3029            .contains("Platform authority not set"));
3030    }
3031
3032    #[test]
3033    fn test_pause_builder_custom_program_id() {
3034        let platform_authority = Pubkey::from(Keypair::new().pubkey().to_bytes());
3035        let custom_program_id = Pubkey::from(Keypair::new().pubkey().to_bytes());
3036
3037        let instruction = pause()
3038            .platform_authority(platform_authority)
3039            .program_id(custom_program_id)
3040            .build_instruction()
3041            .unwrap();
3042
3043        assert_eq!(instruction.program_id, custom_program_id);
3044    }
3045
3046    #[test]
3047    fn test_pause_builder_pda_computation() {
3048        let platform_authority = Pubkey::from(Keypair::new().pubkey().to_bytes());
3049
3050        let instruction = pause()
3051            .platform_authority(platform_authority)
3052            .build_instruction()
3053            .unwrap();
3054
3055        // Verify the computed config PDA is correct
3056        let program_id = program_id();
3057        let expected_config_pda = pda::config_address_with_program_id(&program_id);
3058
3059        assert_eq!(instruction.accounts[0].pubkey, expected_config_pda);
3060        assert_eq!(instruction.accounts[1].pubkey, platform_authority);
3061    }
3062
3063    #[test]
3064    fn test_pause_args_serialization() {
3065        let platform_authority = Pubkey::from(Keypair::new().pubkey().to_bytes());
3066
3067        // Test that args can be serialized and included in instruction data
3068        let instruction = pause()
3069            .platform_authority(platform_authority)
3070            .build_instruction()
3071            .unwrap();
3072
3073        // Verify the data contains the discriminator (8 bytes) followed by serialized args
3074        // PauseArgs is empty, so data should be exactly 8 bytes (discriminator only)
3075        assert_eq!(instruction.data.len(), 8);
3076
3077        // Verify the discriminator matches
3078        assert_eq!(
3079            &instruction.data[..8],
3080            &[211, 22, 221, 251, 74, 121, 193, 47]
3081        );
3082    }
3083
3084    #[test]
3085    fn test_pause_builder_clone_debug() {
3086        let builder = pause().platform_authority(Pubkey::from(Keypair::new().pubkey().to_bytes()));
3087
3088        // Test Clone trait
3089        let cloned_builder = builder.clone();
3090        assert_eq!(
3091            cloned_builder.platform_authority,
3092            builder.platform_authority
3093        );
3094
3095        // Test Debug trait
3096        let debug_str = format!("{builder:?}");
3097        assert!(debug_str.contains("PauseBuilder"));
3098    }
3099
3100    #[test]
3101    fn test_pause_builder_default() {
3102        let builder = PauseBuilder::default();
3103        assert!(builder.platform_authority.is_none());
3104        assert!(builder.program_id.is_none());
3105    }
3106
3107    #[test]
3108    fn test_pause_convenience_function() {
3109        let platform_authority = Pubkey::from(Keypair::new().pubkey().to_bytes());
3110
3111        // Test using convenience function
3112        let instruction = pause()
3113            .platform_authority(platform_authority)
3114            .build_instruction()
3115            .unwrap();
3116
3117        // Verify it works the same as using the builder directly
3118        let direct_instruction = PauseBuilder::new()
3119            .platform_authority(platform_authority)
3120            .build_instruction()
3121            .unwrap();
3122
3123        assert_eq!(instruction.program_id, direct_instruction.program_id);
3124        assert_eq!(
3125            instruction.accounts.len(),
3126            direct_instruction.accounts.len()
3127        );
3128        assert_eq!(instruction.data, direct_instruction.data);
3129    }
3130
3131    #[test]
3132    fn test_unpause_builder() {
3133        let platform_authority = Pubkey::from(Keypair::new().pubkey().to_bytes());
3134
3135        let instruction = unpause()
3136            .platform_authority(platform_authority)
3137            .build_instruction()
3138            .unwrap();
3139
3140        let program_id = program_id();
3141        assert_eq!(instruction.program_id, program_id);
3142        assert_eq!(instruction.accounts.len(), 2);
3143
3144        // Verify instruction discriminator matches program
3145        assert_eq!(
3146            &instruction.data[..8],
3147            &[169, 144, 4, 38, 10, 141, 188, 255]
3148        );
3149
3150        // Verify account structure
3151        assert!(instruction.accounts[0].is_writable); // config (mutable)
3152        assert!(!instruction.accounts[0].is_signer); // config (not signer, it's a PDA)
3153        assert!(!instruction.accounts[1].is_writable); // platform_authority (readonly)
3154        assert!(instruction.accounts[1].is_signer); // platform_authority (signer)
3155
3156        // Verify account addresses
3157        assert_eq!(
3158            instruction.accounts[0].pubkey,
3159            pda::config_address_with_program_id(&program_id)
3160        );
3161        assert_eq!(instruction.accounts[1].pubkey, platform_authority);
3162    }
3163
3164    #[test]
3165    fn test_unpause_builder_missing_required_fields() {
3166        // Test missing platform_authority
3167        let result = unpause().build_instruction();
3168        assert!(result.is_err());
3169        assert!(result
3170            .unwrap_err()
3171            .to_string()
3172            .contains("Platform authority not set"));
3173    }
3174
3175    #[test]
3176    fn test_unpause_builder_custom_program_id() {
3177        let platform_authority = Pubkey::from(Keypair::new().pubkey().to_bytes());
3178        let custom_program_id = Pubkey::from(Keypair::new().pubkey().to_bytes());
3179
3180        let instruction = unpause()
3181            .platform_authority(platform_authority)
3182            .program_id(custom_program_id)
3183            .build_instruction()
3184            .unwrap();
3185
3186        assert_eq!(instruction.program_id, custom_program_id);
3187    }
3188
3189    #[test]
3190    fn test_unpause_builder_pda_computation() {
3191        let platform_authority = Pubkey::from(Keypair::new().pubkey().to_bytes());
3192
3193        let instruction = unpause()
3194            .platform_authority(platform_authority)
3195            .build_instruction()
3196            .unwrap();
3197
3198        // Verify the computed config PDA is correct
3199        let program_id = program_id();
3200        let expected_config_pda = pda::config_address_with_program_id(&program_id);
3201
3202        assert_eq!(instruction.accounts[0].pubkey, expected_config_pda);
3203        assert_eq!(instruction.accounts[1].pubkey, platform_authority);
3204    }
3205
3206    #[test]
3207    fn test_unpause_args_serialization() {
3208        let platform_authority = Pubkey::from(Keypair::new().pubkey().to_bytes());
3209
3210        // Test that args can be serialized and included in instruction data
3211        let instruction = unpause()
3212            .platform_authority(platform_authority)
3213            .build_instruction()
3214            .unwrap();
3215
3216        // Verify the data contains the discriminator (8 bytes) followed by serialized args
3217        // UnpauseArgs is empty, so data should be exactly 8 bytes (discriminator only)
3218        assert_eq!(instruction.data.len(), 8);
3219
3220        // Verify the discriminator matches
3221        assert_eq!(
3222            &instruction.data[..8],
3223            &[169, 144, 4, 38, 10, 141, 188, 255]
3224        );
3225    }
3226
3227    #[test]
3228    fn test_unpause_builder_clone_debug() {
3229        let builder =
3230            unpause().platform_authority(Pubkey::from(Keypair::new().pubkey().to_bytes()));
3231
3232        // Test Clone trait
3233        let cloned_builder = builder.clone();
3234        assert_eq!(
3235            cloned_builder.platform_authority,
3236            builder.platform_authority
3237        );
3238
3239        // Test Debug trait
3240        let debug_str = format!("{builder:?}");
3241        assert!(debug_str.contains("UnpauseBuilder"));
3242    }
3243
3244    #[test]
3245    fn test_unpause_builder_default() {
3246        let builder = UnpauseBuilder::default();
3247        assert!(builder.platform_authority.is_none());
3248        assert!(builder.program_id.is_none());
3249    }
3250
3251    #[test]
3252    fn test_unpause_convenience_function() {
3253        let platform_authority = Pubkey::from(Keypair::new().pubkey().to_bytes());
3254
3255        // Test using convenience function
3256        let instruction = unpause()
3257            .platform_authority(platform_authority)
3258            .build_instruction()
3259            .unwrap();
3260
3261        // Verify it works the same as using the builder directly
3262        let direct_instruction = UnpauseBuilder::new()
3263            .platform_authority(platform_authority)
3264            .build_instruction()
3265            .unwrap();
3266
3267        assert_eq!(instruction.program_id, direct_instruction.program_id);
3268        assert_eq!(
3269            instruction.accounts.len(),
3270            direct_instruction.accounts.len()
3271        );
3272        assert_eq!(instruction.data, direct_instruction.data);
3273    }
3274
3275    // UpdateConfigBuilder tests
3276
3277    #[test]
3278    fn test_update_config_builder_basic() {
3279        let platform_authority = Pubkey::from(Keypair::new().pubkey().to_bytes());
3280
3281        let instruction = update_config()
3282            .platform_authority(platform_authority)
3283            .keeper_fee_bps(25)
3284            .build_instruction()
3285            .unwrap();
3286
3287        assert_eq!(instruction.program_id, program_id());
3288        assert_eq!(instruction.accounts.len(), 2);
3289
3290        // Verify config PDA is first account (mutable)
3291        let expected_config_pda = pda::config_address_with_program_id(&program_id());
3292        assert_eq!(instruction.accounts[0].pubkey, expected_config_pda);
3293        assert!(instruction.accounts[0].is_writable);
3294        assert!(!instruction.accounts[0].is_signer);
3295
3296        // Verify platform authority is second account (signer, read-only)
3297        assert_eq!(instruction.accounts[1].pubkey, platform_authority);
3298        assert!(!instruction.accounts[1].is_writable);
3299        assert!(instruction.accounts[1].is_signer);
3300    }
3301
3302    #[test]
3303    fn test_update_config_builder_all_fields() {
3304        let platform_authority = Pubkey::from(Keypair::new().pubkey().to_bytes());
3305
3306        let instruction = update_config()
3307            .platform_authority(platform_authority)
3308            .keeper_fee_bps(50)
3309            .max_withdrawal_amount(1_000_000_000)
3310            .max_grace_period_seconds(604_800)
3311            .min_platform_fee_bps(50)
3312            .max_platform_fee_bps(1000)
3313            .min_period_seconds(86_400)
3314            .default_allowance_periods(5)
3315            .build_instruction()
3316            .unwrap();
3317
3318        assert_eq!(instruction.program_id, program_id());
3319        assert_eq!(instruction.accounts.len(), 2);
3320    }
3321
3322    #[test]
3323    fn test_update_config_builder_missing_authority() {
3324        let result = update_config().keeper_fee_bps(25).build_instruction();
3325
3326        assert!(result.is_err());
3327        assert!(result
3328            .unwrap_err()
3329            .to_string()
3330            .contains("Platform authority not set"));
3331    }
3332
3333    #[test]
3334    fn test_update_config_builder_no_updates() {
3335        let platform_authority = Pubkey::from(Keypair::new().pubkey().to_bytes());
3336
3337        let result = update_config()
3338            .platform_authority(platform_authority)
3339            .build_instruction();
3340
3341        assert!(result.is_err());
3342        assert!(result
3343            .unwrap_err()
3344            .to_string()
3345            .contains("At least one configuration field must be set"));
3346    }
3347
3348    #[test]
3349    fn test_update_config_builder_keeper_fee_too_high() {
3350        let platform_authority = Pubkey::from(Keypair::new().pubkey().to_bytes());
3351
3352        let result = update_config()
3353            .platform_authority(platform_authority)
3354            .keeper_fee_bps(101)
3355            .build_instruction();
3356
3357        assert!(result.is_err());
3358        assert!(result
3359            .unwrap_err()
3360            .to_string()
3361            .contains("Keeper fee must be <= 100"));
3362    }
3363
3364    #[test]
3365    fn test_update_config_builder_min_fee_greater_than_max() {
3366        let platform_authority = Pubkey::from(Keypair::new().pubkey().to_bytes());
3367
3368        let result = update_config()
3369            .platform_authority(platform_authority)
3370            .min_platform_fee_bps(200)
3371            .max_platform_fee_bps(100)
3372            .build_instruction();
3373
3374        assert!(result.is_err());
3375        assert!(result
3376            .unwrap_err()
3377            .to_string()
3378            .contains("Minimum platform fee must be <= maximum platform fee"));
3379    }
3380
3381    #[test]
3382    fn test_update_config_builder_zero_max_withdrawal() {
3383        let platform_authority = Pubkey::from(Keypair::new().pubkey().to_bytes());
3384
3385        let result = update_config()
3386            .platform_authority(platform_authority)
3387            .max_withdrawal_amount(0)
3388            .build_instruction();
3389
3390        assert!(result.is_err());
3391        assert!(result
3392            .unwrap_err()
3393            .to_string()
3394            .contains("Maximum withdrawal amount must be > 0"));
3395    }
3396
3397    #[test]
3398    fn test_update_config_builder_zero_max_grace_period() {
3399        let platform_authority = Pubkey::from(Keypair::new().pubkey().to_bytes());
3400
3401        let result = update_config()
3402            .platform_authority(platform_authority)
3403            .max_grace_period_seconds(0)
3404            .build_instruction();
3405
3406        assert!(result.is_err());
3407        assert!(result
3408            .unwrap_err()
3409            .to_string()
3410            .contains("Maximum grace period must be > 0"));
3411    }
3412
3413    #[test]
3414    fn test_update_config_builder_zero_min_period() {
3415        let platform_authority = Pubkey::from(Keypair::new().pubkey().to_bytes());
3416
3417        let result = update_config()
3418            .platform_authority(platform_authority)
3419            .min_period_seconds(0)
3420            .build_instruction();
3421
3422        assert!(result.is_err());
3423        assert!(result
3424            .unwrap_err()
3425            .to_string()
3426            .contains("Minimum period must be > 0"));
3427    }
3428
3429    #[test]
3430    fn test_update_config_builder_zero_allowance_periods() {
3431        let platform_authority = Pubkey::from(Keypair::new().pubkey().to_bytes());
3432
3433        let result = update_config()
3434            .platform_authority(platform_authority)
3435            .default_allowance_periods(0)
3436            .build_instruction();
3437
3438        assert!(result.is_err());
3439        assert!(result
3440            .unwrap_err()
3441            .to_string()
3442            .contains("Default allowance periods must be > 0"));
3443    }
3444
3445    #[test]
3446    fn test_update_config_builder_custom_program_id() {
3447        let platform_authority = Pubkey::from(Keypair::new().pubkey().to_bytes());
3448        let custom_program_id = Pubkey::from(Keypair::new().pubkey().to_bytes());
3449
3450        let instruction = update_config()
3451            .platform_authority(platform_authority)
3452            .keeper_fee_bps(25)
3453            .program_id(custom_program_id)
3454            .build_instruction()
3455            .unwrap();
3456
3457        assert_eq!(instruction.program_id, custom_program_id);
3458    }
3459
3460    #[test]
3461    fn test_update_config_builder_pda_computation() {
3462        let platform_authority = Pubkey::from(Keypair::new().pubkey().to_bytes());
3463
3464        let instruction = update_config()
3465            .platform_authority(platform_authority)
3466            .keeper_fee_bps(25)
3467            .build_instruction()
3468            .unwrap();
3469
3470        // Verify the computed config PDA is correct
3471        let program_id = program_id();
3472        let expected_config_pda = pda::config_address_with_program_id(&program_id);
3473
3474        assert_eq!(instruction.accounts[0].pubkey, expected_config_pda);
3475        assert_eq!(instruction.accounts[1].pubkey, platform_authority);
3476    }
3477
3478    #[test]
3479    fn test_update_config_args_serialization() {
3480        let platform_authority = Pubkey::from(Keypair::new().pubkey().to_bytes());
3481
3482        // Test that args can be serialized and included in instruction data
3483        let instruction = update_config()
3484            .platform_authority(platform_authority)
3485            .keeper_fee_bps(25)
3486            .max_withdrawal_amount(1_000_000)
3487            .build_instruction()
3488            .unwrap();
3489
3490        // Verify the data contains the discriminator (8 bytes) followed by serialized args
3491        assert!(instruction.data.len() > 8);
3492
3493        // Verify the discriminator matches
3494        assert_eq!(
3495            &instruction.data[..8],
3496            &[29, 158, 252, 191, 10, 83, 219, 99]
3497        );
3498    }
3499
3500    #[test]
3501    fn test_update_config_builder_clone_debug() {
3502        let builder = update_config()
3503            .platform_authority(Pubkey::from(Keypair::new().pubkey().to_bytes()))
3504            .keeper_fee_bps(25);
3505
3506        // Test Clone trait
3507        let cloned_builder = builder.clone();
3508        assert_eq!(
3509            cloned_builder.platform_authority,
3510            builder.platform_authority
3511        );
3512        assert_eq!(cloned_builder.keeper_fee_bps, builder.keeper_fee_bps);
3513
3514        // Test Debug trait
3515        let debug_str = format!("{builder:?}");
3516        assert!(debug_str.contains("UpdateConfigBuilder"));
3517    }
3518
3519    #[test]
3520    fn test_update_config_builder_default() {
3521        let builder = UpdateConfigBuilder::default();
3522        assert!(builder.platform_authority.is_none());
3523        assert!(builder.keeper_fee_bps.is_none());
3524        assert!(builder.max_withdrawal_amount.is_none());
3525        assert!(builder.max_grace_period_seconds.is_none());
3526        assert!(builder.min_platform_fee_bps.is_none());
3527        assert!(builder.max_platform_fee_bps.is_none());
3528        assert!(builder.min_period_seconds.is_none());
3529        assert!(builder.default_allowance_periods.is_none());
3530        assert!(builder.program_id.is_none());
3531    }
3532
3533    #[test]
3534    fn test_update_config_convenience_function() {
3535        let platform_authority = Pubkey::from(Keypair::new().pubkey().to_bytes());
3536
3537        // Test using convenience function
3538        let instruction = update_config()
3539            .platform_authority(platform_authority)
3540            .keeper_fee_bps(25)
3541            .build_instruction()
3542            .unwrap();
3543
3544        // Verify it works the same as using the builder directly
3545        let direct_instruction = UpdateConfigBuilder::new()
3546            .platform_authority(platform_authority)
3547            .keeper_fee_bps(25)
3548            .build_instruction()
3549            .unwrap();
3550
3551        assert_eq!(instruction.program_id, direct_instruction.program_id);
3552        assert_eq!(
3553            instruction.accounts.len(),
3554            direct_instruction.accounts.len()
3555        );
3556        assert_eq!(instruction.data, direct_instruction.data);
3557    }
3558
3559    #[test]
3560    fn test_update_config_builder_partial_updates() {
3561        let platform_authority = Pubkey::from(Keypair::new().pubkey().to_bytes());
3562
3563        // Test updating only keeper fee
3564        let instruction1 = update_config()
3565            .platform_authority(platform_authority)
3566            .keeper_fee_bps(30)
3567            .build_instruction()
3568            .unwrap();
3569        assert_eq!(instruction1.accounts.len(), 2);
3570
3571        // Test updating only max withdrawal amount
3572        let instruction2 = update_config()
3573            .platform_authority(platform_authority)
3574            .max_withdrawal_amount(5_000_000)
3575            .build_instruction()
3576            .unwrap();
3577        assert_eq!(instruction2.accounts.len(), 2);
3578
3579        // Test updating only fee bounds
3580        let instruction3 = update_config()
3581            .platform_authority(platform_authority)
3582            .min_platform_fee_bps(100)
3583            .max_platform_fee_bps(500)
3584            .build_instruction()
3585            .unwrap();
3586        assert_eq!(instruction3.accounts.len(), 2);
3587    }
3588
3589    #[test]
3590    fn test_update_config_builder_edge_cases() {
3591        let platform_authority = Pubkey::from(Keypair::new().pubkey().to_bytes());
3592
3593        // Test max keeper fee (100 bps = 1%)
3594        let instruction1 = update_config()
3595            .platform_authority(platform_authority)
3596            .keeper_fee_bps(100)
3597            .build_instruction()
3598            .unwrap();
3599        assert_eq!(instruction1.accounts.len(), 2);
3600
3601        // Test min/max fee equal
3602        let instruction2 = update_config()
3603            .platform_authority(platform_authority)
3604            .min_platform_fee_bps(100)
3605            .max_platform_fee_bps(100)
3606            .build_instruction()
3607            .unwrap();
3608        assert_eq!(instruction2.accounts.len(), 2);
3609
3610        // Test minimum valid values
3611        let instruction3 = update_config()
3612            .platform_authority(platform_authority)
3613            .max_withdrawal_amount(1)
3614            .max_grace_period_seconds(1)
3615            .min_period_seconds(1)
3616            .default_allowance_periods(1)
3617            .build_instruction()
3618            .unwrap();
3619        assert_eq!(instruction3.accounts.len(), 2);
3620    }
3621
3622    #[test]
3623    fn test_update_merchant_tier_builder() {
3624        let authority = Pubkey::from(Keypair::new().pubkey().to_bytes());
3625        let merchant = Pubkey::from(Keypair::new().pubkey().to_bytes());
3626
3627        let instruction = update_merchant_tier()
3628            .authority(authority)
3629            .merchant(merchant)
3630            .new_tier(1) // Pro tier
3631            .build_instruction()
3632            .unwrap();
3633
3634        let program_id = program_id();
3635        assert_eq!(instruction.program_id, program_id);
3636        assert_eq!(instruction.accounts.len(), 3);
3637
3638        // Verify instruction discriminator matches program
3639        assert_eq!(
3640            &instruction.data[..8],
3641            &[24, 54, 190, 70, 221, 93, 3, 64]
3642        );
3643
3644        // Verify account structure
3645        assert!(!instruction.accounts[0].is_writable); // config (readonly)
3646        assert!(!instruction.accounts[0].is_signer); // config (PDA, not signer)
3647        assert!(instruction.accounts[1].is_writable); // merchant (mutable)
3648        assert!(!instruction.accounts[1].is_signer); // merchant (PDA, not signer)
3649        assert!(!instruction.accounts[2].is_writable); // authority (readonly)
3650        assert!(instruction.accounts[2].is_signer); // authority (signer)
3651
3652        // Verify account addresses
3653        assert_eq!(
3654            instruction.accounts[0].pubkey,
3655            pda::config_address_with_program_id(&program_id)
3656        );
3657        assert_eq!(instruction.accounts[1].pubkey, merchant);
3658        assert_eq!(instruction.accounts[2].pubkey, authority);
3659    }
3660
3661    #[test]
3662    fn test_update_merchant_tier_builder_all_tiers() {
3663        let authority = Pubkey::from(Keypair::new().pubkey().to_bytes());
3664        let merchant = Pubkey::from(Keypair::new().pubkey().to_bytes());
3665
3666        // Test Free tier (0)
3667        let instruction_free = update_merchant_tier()
3668            .authority(authority)
3669            .merchant(merchant)
3670            .new_tier(0)
3671            .build_instruction()
3672            .unwrap();
3673        assert_eq!(instruction_free.accounts.len(), 3);
3674
3675        // Test Pro tier (1)
3676        let instruction_pro = update_merchant_tier()
3677            .authority(authority)
3678            .merchant(merchant)
3679            .new_tier(1)
3680            .build_instruction()
3681            .unwrap();
3682        assert_eq!(instruction_pro.accounts.len(), 3);
3683
3684        // Test Enterprise tier (2)
3685        let instruction_enterprise = update_merchant_tier()
3686            .authority(authority)
3687            .merchant(merchant)
3688            .new_tier(2)
3689            .build_instruction()
3690            .unwrap();
3691        assert_eq!(instruction_enterprise.accounts.len(), 3);
3692    }
3693
3694    #[test]
3695    fn test_update_merchant_tier_builder_missing_required_fields() {
3696        let authority = Pubkey::from(Keypair::new().pubkey().to_bytes());
3697        let merchant = Pubkey::from(Keypair::new().pubkey().to_bytes());
3698
3699        // Test missing authority
3700        let result = update_merchant_tier()
3701            .merchant(merchant)
3702            .new_tier(1)
3703            .build_instruction();
3704        assert!(result.is_err());
3705        assert!(result
3706            .unwrap_err()
3707            .to_string()
3708            .contains("Authority not set"));
3709
3710        // Test missing merchant
3711        let result = update_merchant_tier()
3712            .authority(authority)
3713            .new_tier(1)
3714            .build_instruction();
3715        assert!(result.is_err());
3716        assert!(result
3717            .unwrap_err()
3718            .to_string()
3719            .contains("Merchant not set"));
3720
3721        // Test missing new_tier
3722        let result = update_merchant_tier()
3723            .authority(authority)
3724            .merchant(merchant)
3725            .build_instruction();
3726        assert!(result.is_err());
3727        assert!(result
3728            .unwrap_err()
3729            .to_string()
3730            .contains("New tier not set"));
3731    }
3732
3733    #[test]
3734    fn test_update_merchant_tier_builder_invalid_tier() {
3735        let authority = Pubkey::from(Keypair::new().pubkey().to_bytes());
3736        let merchant = Pubkey::from(Keypair::new().pubkey().to_bytes());
3737
3738        // Test tier > 2 (invalid)
3739        let result = update_merchant_tier()
3740            .authority(authority)
3741            .merchant(merchant)
3742            .new_tier(3)
3743            .build_instruction();
3744        assert!(result.is_err());
3745        assert!(result
3746            .unwrap_err()
3747            .to_string()
3748            .contains("New tier must be 0 (Free), 1 (Pro), or 2 (Enterprise)"));
3749
3750        // Test tier 255 (invalid)
3751        let result = update_merchant_tier()
3752            .authority(authority)
3753            .merchant(merchant)
3754            .new_tier(255)
3755            .build_instruction();
3756        assert!(result.is_err());
3757    }
3758
3759    #[test]
3760    fn test_update_merchant_tier_builder_custom_program_id() {
3761        let authority = Pubkey::from(Keypair::new().pubkey().to_bytes());
3762        let merchant = Pubkey::from(Keypair::new().pubkey().to_bytes());
3763        let custom_program_id = Pubkey::from(Keypair::new().pubkey().to_bytes());
3764
3765        let instruction = update_merchant_tier()
3766            .authority(authority)
3767            .merchant(merchant)
3768            .new_tier(1)
3769            .program_id(custom_program_id)
3770            .build_instruction()
3771            .unwrap();
3772
3773        assert_eq!(instruction.program_id, custom_program_id);
3774    }
3775
3776    #[test]
3777    fn test_update_merchant_tier_builder_pda_computation() {
3778        let authority = Pubkey::from(Keypair::new().pubkey().to_bytes());
3779        let merchant = Pubkey::from(Keypair::new().pubkey().to_bytes());
3780
3781        let instruction = update_merchant_tier()
3782            .authority(authority)
3783            .merchant(merchant)
3784            .new_tier(1)
3785            .build_instruction()
3786            .unwrap();
3787
3788        // Verify the computed config PDA is correct
3789        let program_id = program_id();
3790        let expected_config_pda = pda::config_address_with_program_id(&program_id);
3791
3792        assert_eq!(instruction.accounts[0].pubkey, expected_config_pda);
3793        assert_eq!(instruction.accounts[1].pubkey, merchant);
3794        assert_eq!(instruction.accounts[2].pubkey, authority);
3795    }
3796
3797    #[test]
3798    fn test_update_merchant_tier_args_serialization() {
3799        let authority = Pubkey::from(Keypair::new().pubkey().to_bytes());
3800        let merchant = Pubkey::from(Keypair::new().pubkey().to_bytes());
3801
3802        // Test that args can be serialized and included in instruction data
3803        let instruction = update_merchant_tier()
3804            .authority(authority)
3805            .merchant(merchant)
3806            .new_tier(1) // Pro tier
3807            .build_instruction()
3808            .unwrap();
3809
3810        // Verify the data contains the discriminator (8 bytes) followed by serialized args
3811        // UpdateMerchantTierArgs has 1 u8 field, so data should be 9 bytes
3812        assert_eq!(instruction.data.len(), 9);
3813
3814        // Verify the discriminator matches
3815        assert_eq!(
3816            &instruction.data[..8],
3817            &[24, 54, 190, 70, 221, 93, 3, 64]
3818        );
3819
3820        // Verify the tier value is serialized correctly
3821        assert_eq!(instruction.data[8], 1); // Pro tier
3822    }
3823
3824    #[test]
3825    fn test_update_merchant_tier_builder_clone_debug() {
3826        let builder = update_merchant_tier()
3827            .authority(Pubkey::from(Keypair::new().pubkey().to_bytes()))
3828            .merchant(Pubkey::from(Keypair::new().pubkey().to_bytes()))
3829            .new_tier(1);
3830
3831        // Test Clone trait
3832        let cloned_builder = builder.clone();
3833        assert_eq!(cloned_builder.authority, builder.authority);
3834        assert_eq!(cloned_builder.merchant, builder.merchant);
3835        assert_eq!(cloned_builder.new_tier, builder.new_tier);
3836
3837        // Test Debug trait
3838        let debug_str = format!("{builder:?}");
3839        assert!(debug_str.contains("UpdateMerchantTierBuilder"));
3840    }
3841
3842    #[test]
3843    fn test_update_merchant_tier_builder_default() {
3844        let builder = UpdateMerchantTierBuilder::default();
3845        assert!(builder.authority.is_none());
3846        assert!(builder.merchant.is_none());
3847        assert!(builder.new_tier.is_none());
3848        assert!(builder.program_id.is_none());
3849    }
3850
3851    #[test]
3852    fn test_update_merchant_tier_convenience_function() {
3853        let authority = Pubkey::from(Keypair::new().pubkey().to_bytes());
3854        let merchant = Pubkey::from(Keypair::new().pubkey().to_bytes());
3855
3856        // Test using convenience function
3857        let instruction = update_merchant_tier()
3858            .authority(authority)
3859            .merchant(merchant)
3860            .new_tier(1)
3861            .build_instruction()
3862            .unwrap();
3863
3864        // Verify it works the same as using the builder directly
3865        let direct_instruction = UpdateMerchantTierBuilder::new()
3866            .authority(authority)
3867            .merchant(merchant)
3868            .new_tier(1)
3869            .build_instruction()
3870            .unwrap();
3871
3872        assert_eq!(instruction.program_id, direct_instruction.program_id);
3873        assert_eq!(
3874            instruction.accounts.len(),
3875            direct_instruction.accounts.len()
3876        );
3877        assert_eq!(instruction.data, direct_instruction.data);
3878    }
3879
3880    #[test]
3881    fn test_update_plan_terms_builder_all_fields() {
3882        let authority = Pubkey::from(Keypair::new().pubkey().to_bytes());
3883        let plan_key = Pubkey::from(Keypair::new().pubkey().to_bytes());
3884
3885        let instruction = update_plan_terms()
3886            .authority(authority)
3887            .plan_key(plan_key)
3888            .price_usdc(10_000_000) // 10 USDC
3889            .period_secs(2_592_000) // 30 days
3890            .grace_secs(777_600)    // 9 days (< 30% of 30 days = 7.776 days)
3891            .name("Updated Plan".to_string())
3892            .build_instruction()
3893            .unwrap();
3894
3895        let program_id = program_id();
3896        assert_eq!(instruction.program_id, program_id);
3897        assert_eq!(instruction.accounts.len(), 4);
3898
3899        // Verify discriminator is correct for "global:update_plan_terms"
3900        assert_eq!(&instruction.data[..8], &[224, 68, 224, 41, 169, 52, 124, 221]);
3901    }
3902
3903    #[test]
3904    fn test_update_plan_terms_builder_single_field() {
3905        let authority = Pubkey::from(Keypair::new().pubkey().to_bytes());
3906        let plan_key = Pubkey::from(Keypair::new().pubkey().to_bytes());
3907
3908        // Test updating only price
3909        let instruction = update_plan_terms()
3910            .authority(authority)
3911            .plan_key(plan_key)
3912            .price_usdc(20_000_000)
3913            .build_instruction()
3914            .unwrap();
3915
3916        assert_eq!(instruction.program_id, program_id());
3917        assert_eq!(instruction.accounts.len(), 4);
3918
3919        // Test updating only period
3920        let instruction = update_plan_terms()
3921            .authority(authority)
3922            .plan_key(plan_key)
3923            .period_secs(604_800) // 7 days
3924            .build_instruction()
3925            .unwrap();
3926
3927        assert_eq!(instruction.program_id, program_id());
3928        assert_eq!(instruction.accounts.len(), 4);
3929
3930        // Test updating only grace
3931        let instruction = update_plan_terms()
3932            .authority(authority)
3933            .plan_key(plan_key)
3934            .grace_secs(86_400) // 1 day
3935            .build_instruction()
3936            .unwrap();
3937
3938        assert_eq!(instruction.program_id, program_id());
3939        assert_eq!(instruction.accounts.len(), 4);
3940
3941        // Test updating only name
3942        let instruction = update_plan_terms()
3943            .authority(authority)
3944            .plan_key(plan_key)
3945            .name("New Name".to_string())
3946            .build_instruction()
3947            .unwrap();
3948
3949        assert_eq!(instruction.program_id, program_id());
3950        assert_eq!(instruction.accounts.len(), 4);
3951    }
3952
3953    #[test]
3954    fn test_update_plan_terms_validation_no_fields() {
3955        let authority = Pubkey::from(Keypair::new().pubkey().to_bytes());
3956        let plan_key = Pubkey::from(Keypair::new().pubkey().to_bytes());
3957
3958        let result = update_plan_terms()
3959            .authority(authority)
3960            .plan_key(plan_key)
3961            .build_instruction();
3962
3963        assert!(result.is_err());
3964        assert!(result
3965            .unwrap_err()
3966            .to_string()
3967            .contains("At least one field must be set"));
3968    }
3969
3970    #[test]
3971    fn test_update_plan_terms_validation_missing_authority() {
3972        let plan_key = Pubkey::from(Keypair::new().pubkey().to_bytes());
3973
3974        let result = update_plan_terms()
3975            .plan_key(plan_key)
3976            .price_usdc(10_000_000)
3977            .build_instruction();
3978
3979        assert!(result.is_err());
3980        assert!(result.unwrap_err().to_string().contains("Authority not set"));
3981    }
3982
3983    #[test]
3984    fn test_update_plan_terms_validation_missing_plan() {
3985        let authority = Pubkey::from(Keypair::new().pubkey().to_bytes());
3986
3987        let result = update_plan_terms()
3988            .authority(authority)
3989            .price_usdc(10_000_000)
3990            .build_instruction();
3991
3992        assert!(result.is_err());
3993        assert!(result.unwrap_err().to_string().contains("Plan key not set"));
3994    }
3995
3996    #[test]
3997    fn test_update_plan_terms_validation_zero_price() {
3998        let authority = Pubkey::from(Keypair::new().pubkey().to_bytes());
3999        let plan_key = Pubkey::from(Keypair::new().pubkey().to_bytes());
4000
4001        let result = update_plan_terms()
4002            .authority(authority)
4003            .plan_key(plan_key)
4004            .price_usdc(0)
4005            .build_instruction();
4006
4007        assert!(result.is_err());
4008        assert!(result.unwrap_err().to_string().contains("Price must be > 0"));
4009    }
4010
4011    #[test]
4012    fn test_update_plan_terms_validation_max_price() {
4013        let authority = Pubkey::from(Keypair::new().pubkey().to_bytes());
4014        let plan_key = Pubkey::from(Keypair::new().pubkey().to_bytes());
4015
4016        let result = update_plan_terms()
4017            .authority(authority)
4018            .plan_key(plan_key)
4019            .price_usdc(1_000_000_000_001) // Just over 1 million USDC
4020            .build_instruction();
4021
4022        assert!(result.is_err());
4023        assert!(result
4024            .unwrap_err()
4025            .to_string()
4026            .contains("Price must be <= 1,000,000 USDC"));
4027    }
4028
4029    #[test]
4030    fn test_update_plan_terms_validation_grace_exceeds_30_percent() {
4031        let authority = Pubkey::from(Keypair::new().pubkey().to_bytes());
4032        let plan_key = Pubkey::from(Keypair::new().pubkey().to_bytes());
4033
4034        let result = update_plan_terms()
4035            .authority(authority)
4036            .plan_key(plan_key)
4037            .period_secs(2_592_000) // 30 days
4038            .grace_secs(800_000)    // > 30% of 30 days (should be <= 777,600)
4039            .build_instruction();
4040
4041        assert!(result.is_err());
4042        assert!(result
4043            .unwrap_err()
4044            .to_string()
4045            .contains("Grace period must be <= 30%"));
4046    }
4047
4048    #[test]
4049    fn test_update_plan_terms_validation_empty_name() {
4050        let authority = Pubkey::from(Keypair::new().pubkey().to_bytes());
4051        let plan_key = Pubkey::from(Keypair::new().pubkey().to_bytes());
4052
4053        let result = update_plan_terms()
4054            .authority(authority)
4055            .plan_key(plan_key)
4056            .name(String::new())
4057            .build_instruction();
4058
4059        assert!(result.is_err());
4060        assert!(result
4061            .unwrap_err()
4062            .to_string()
4063            .contains("Name must not be empty"));
4064    }
4065
4066    #[test]
4067    fn test_update_plan_terms_validation_name_too_long() {
4068        let authority = Pubkey::from(Keypair::new().pubkey().to_bytes());
4069        let plan_key = Pubkey::from(Keypair::new().pubkey().to_bytes());
4070
4071        let long_name = "a".repeat(33); // 33 bytes, > 32
4072
4073        let result = update_plan_terms()
4074            .authority(authority)
4075            .plan_key(plan_key)
4076            .name(long_name)
4077            .build_instruction();
4078
4079        assert!(result.is_err());
4080        assert!(result
4081            .unwrap_err()
4082            .to_string()
4083            .contains("Name must be <= 32 bytes"));
4084    }
4085
4086    #[test]
4087    fn test_update_plan_terms_convenience_function() {
4088        let authority = Pubkey::from(Keypair::new().pubkey().to_bytes());
4089        let plan_key = Pubkey::from(Keypair::new().pubkey().to_bytes());
4090
4091        let instruction = update_plan_terms()
4092            .authority(authority)
4093            .plan_key(plan_key)
4094            .price_usdc(15_000_000)
4095            .build_instruction()
4096            .unwrap();
4097
4098        // Verify it works the same as using the builder directly
4099        let direct_instruction = UpdatePlanTermsBuilder::new()
4100            .authority(authority)
4101            .plan_key(plan_key)
4102            .price_usdc(15_000_000)
4103            .build_instruction()
4104            .unwrap();
4105
4106        assert_eq!(instruction.program_id, direct_instruction.program_id);
4107        assert_eq!(
4108            instruction.accounts.len(),
4109            direct_instruction.accounts.len()
4110        );
4111        assert_eq!(instruction.data, direct_instruction.data);
4112    }
4113}