solify_client/
lib.rs

1use anyhow::{Context, Result};
2use solana_client::rpc_client::RpcClient;
3use solana_commitment_config::CommitmentConfig;
4use solana_sdk::instruction::Instruction as SolanaInstruction;
5use solana_sdk::{
6    pubkey::Pubkey,
7    signature::{Signature, Signer},
8    transaction::Transaction,
9};
10
11use solify_common::types::{IdlData as CommonIdlData, TestMetadata as CommonTestMetadata};
12use solify_common::ArgumentType as C;
13use types::ArgumentType as T;
14use std::str::FromStr;
15
16#[path = "clients/rust/src/generated/mod.rs"]
17pub mod generated;
18
19pub use generated::programs::SOLIFY_ID;
20pub use generated::{accounts, errors, instructions, types};
21
22pub struct SolifyClient {
23    rpc: RpcClient,
24    commitment: CommitmentConfig,
25}
26
27impl SolifyClient {
28    pub fn new(rpc_url: impl AsRef<str>) -> Result<Self> {
29        Self::new_with_commitment(rpc_url, CommitmentConfig::confirmed())
30    }
31
32    pub fn new_with_commitment(
33        rpc_url: impl AsRef<str>,
34        commitment: CommitmentConfig,
35    ) -> Result<Self> {
36        let rpc = RpcClient::new_with_commitment(rpc_url.as_ref().to_string(), commitment);
37        Ok(Self { rpc, commitment })
38    }
39
40    pub fn from_rpc_client(rpc: RpcClient, commitment: CommitmentConfig) -> Self {
41        Self { rpc, commitment }
42    }
43
44    pub fn rpc(&self) -> &RpcClient {
45        &self.rpc
46    }
47
48    pub fn commitment(&self) -> CommitmentConfig {
49        self.commitment
50    }
51
52    pub fn store_idl_data<S: Signer>(
53        &self,
54        authority: &S,
55        program_id: Pubkey,
56        idl_data: &CommonIdlData,
57    ) -> Result<Signature> {
58        let generated_idl = convert_idl_data(idl_data)?;
59        let (idl_storage, _) = derive_idl_storage_address(&program_id, &authority.pubkey());
60
61        let accounts = instructions::StoreIdlData {
62            idl_storage,
63            authority: authority.pubkey(),
64            system_program: system_program_id(),
65        };
66        let args = instructions::StoreIdlDataInstructionArgs {
67            idl_data: generated_idl,
68            program_id,
69        };
70        let instruction = accounts.instruction(args);
71
72        self.send_instruction(authority, &[instruction])
73    }
74
75    pub fn update_idl_data<S: Signer>(
76        &self,
77        authority: &S,
78        program_id: Pubkey,
79        idl_data: &CommonIdlData,
80    ) -> Result<Signature> {
81        let generated_idl = convert_idl_data(idl_data)?;
82        let (idl_storage, _) = derive_idl_storage_address(&program_id, &authority.pubkey());
83
84        let accounts = instructions::UpdateIdlData {
85            idl_storage,
86            authority: authority.pubkey(),
87            system_program: system_program_id(),
88        };
89        let args = instructions::UpdateIdlDataInstructionArgs {
90            idl_data: generated_idl,
91            program_id,
92        };
93        let instruction = accounts.instruction(args);
94
95        self.send_instruction(authority, &[instruction])
96    }
97
98    pub fn generate_metadata<S: Signer>(
99        &self,
100        authority: &S,
101        program_id: Pubkey,
102        execution_order: Vec<String>,
103        paraphrase: &str,
104        program_name: impl Into<String>,
105    ) -> Result<Signature> {
106        let (idl_storage, _) = derive_idl_storage_address(&program_id, &authority.pubkey());
107        let (test_metadata_config, _) =
108            derive_test_metadata_config_address(&program_id, &authority.pubkey(), paraphrase);
109
110        let accounts = instructions::GenerateMetadata {
111            test_metadata_config,
112            idl_storage,
113            authority: authority.pubkey(),
114            system_program: system_program_id(),
115        };
116        let args = instructions::GenerateMetadataInstructionArgs {
117            execution_order,
118            program_id,
119            program_name: program_name.into(),
120            paraphrase: paraphrase.to_string(),
121        };
122        let instruction = accounts.instruction(args);
123
124        self.send_instruction(authority, &[instruction])
125    }
126
127
128
129    pub fn fetch_idl_storage(
130        &self,
131        authority: Pubkey,
132        program_id: Pubkey,
133    ) -> Result<Option<IdlStorageAccount>> {
134        let (address, _) = derive_idl_storage_address(&program_id, &authority);
135        let response = self
136            .rpc
137            .get_account_with_commitment(&address, self.commitment)
138            .context("Failed to fetch IDL storage account")?;
139
140        if let Some(account) = response.value {
141            let decoded = accounts::idl_storage::IdlStorage::from_bytes(&account.data)
142                .context("Failed to decode IDL storage account data")?;
143            let idl_data = convert_idl_data_back(&decoded.idl_data);
144
145            Ok(Some(IdlStorageAccount {
146                address,
147                authority: decoded.authority,
148                program_id: decoded.program_id,
149                idl_data,
150                timestamp: decoded.timestamp,
151            }))
152        } else {
153            Ok(None)
154        }
155    }
156
157    pub fn fetch_test_metadata(
158        &self,
159        authority: Pubkey,
160        program_id: Pubkey,
161        paraphrase: &str,
162    ) -> Result<Option<TestMetadataAccount>> {
163        let (address, _) = derive_test_metadata_config_address(&program_id, &authority, &paraphrase);
164        let response = self
165            .rpc
166            .get_account_with_commitment(&address, self.commitment)
167            .context("Failed to fetch test metadata account")?;
168
169        if let Some(account) = response.value {
170            
171            let decoded = accounts::test_metadata_config::TestMetadataConfig::from_bytes(&account.data)
172            .with_context(|| {
173                format!(
174                    "Failed to decode TestMetadataConfig due to serialization format mismatch. \
175                    On-chain uses Anchor's fixed-size strings (max_len), client uses Borsh variable-size. \
176                    This happens because the generated client code doesn't respect Anchor's max_len format. \
177                    Account data length: {} bytes. \
178                    SOLUTION: Regenerate the client code with proper Anchor support, or remove max_len from on-chain types.",
179                    account.data.len()
180                )
181            })?;
182            let test_metadata = convert_test_metadata_back(&decoded.test_metadata)?;
183
184            Ok(Some(TestMetadataAccount {
185                address,
186                authority: decoded.authority,
187                program_id: decoded.program_id,
188                program_name: decoded.program_name,
189                test_metadata,
190                timestamp: decoded.timestamp,
191            }))
192        } else {
193            Ok(None)
194        }
195    }
196
197    fn send_instruction<S: Signer>(
198        &self,
199        authority: &S,
200        instructions: &[SolanaInstruction],
201    ) -> Result<Signature> {
202        let recent_blockhash = self
203            .rpc
204            .get_latest_blockhash()
205            .context("Failed to fetch latest blockhash")?;
206
207        let transaction = Transaction::new_signed_with_payer(
208            instructions,
209            Some(&authority.pubkey()),
210            &[authority],
211            recent_blockhash,
212        );
213
214        // Simulate the transaction first to catch errors early
215        let simulation_result = self.rpc.simulate_transaction(&transaction);
216        if let Ok(simulation) = simulation_result {
217            if let Some(err) = simulation.value.err {
218                return Err(anyhow::anyhow!(
219                    "Transaction simulation failed: {:?}. Logs: {:?}",
220                    err,
221                    simulation.value.logs
222                ));
223            }
224        }
225
226        // Send and confirm the transaction
227        self.rpc
228            .send_and_confirm_transaction_with_spinner_and_commitment(
229                &transaction,
230                self.commitment,
231            )
232            .map_err(|e| {
233                anyhow::anyhow!(
234                    "Failed to send Solify transaction: {}. \
235                    This could be due to: insufficient funds, network issues, \
236                    or program execution errors. Check your wallet balance and RPC connection.",
237                    e
238                )
239            })
240    }
241}
242
243pub fn derive_idl_storage_address(program_id: &Pubkey, authority: &Pubkey) -> (Pubkey, u8) {
244    Pubkey::find_program_address(
245        &[b"idl_storage", program_id.as_ref(), authority.as_ref()],
246        &generated::SOLIFY_ID,
247    )
248}
249
250pub fn derive_test_metadata_config_address(
251    program_id: &Pubkey,
252    authority: &Pubkey,
253    paraphrase: &str,
254) -> (Pubkey, u8) {
255    Pubkey::find_program_address(
256        &[b"tests_metadata", program_id.as_ref(), authority.as_ref(), paraphrase.as_bytes()],
257        &generated::SOLIFY_ID,
258    )
259}
260
261#[derive(Debug, Clone)]
262pub struct IdlStorageAccount {
263    pub address: Pubkey,
264    pub authority: Pubkey,
265    pub program_id: Pubkey,
266    pub idl_data: CommonIdlData,
267    pub timestamp: i64,
268}
269
270
271#[derive(Debug, Clone)]
272pub struct TestMetadataAccount {
273    pub address: Pubkey,
274    pub authority: Pubkey,
275    pub program_id: Pubkey,
276    pub program_name: String,
277    pub test_metadata: CommonTestMetadata,
278    pub timestamp: i64,
279}
280
281
282pub fn convert_idl_data(common: &solify_common::IdlData) -> Result<types::IdlData> {
283    Ok(types::IdlData {
284        name: common.name.clone(),
285        version: common.version.clone(),
286        instructions: common
287            .instructions
288            .iter()
289            .map(convert_idl_instruction)
290            .collect::<Result<Vec<_>>>()?,
291        accounts: common
292            .accounts
293            .iter()
294            .map(convert_idl_account)
295            .collect::<Result<Vec<_>>>()?,
296        types: common
297            .types
298            .iter()
299            .map(convert_idl_typedef)
300            .collect::<Result<Vec<_>>>()?,
301        errors: common
302            .errors
303            .iter()
304            .map(convert_idl_error)
305            .collect::<Result<Vec<_>>>()?,
306        constants: common
307            .constants
308            .iter()
309            .map(convert_idl_constant)
310            .collect::<Result<Vec<_>>>()?,
311        events: common
312            .events
313            .iter()
314            .map(convert_idl_event)
315            .collect::<Result<Vec<_>>>()?,
316    })
317}
318
319fn convert_idl_instruction(src: &solify_common::IdlInstruction) -> Result<types::IdlInstruction> {
320    Ok(types::IdlInstruction {
321        name: src.name.clone(),
322        accounts: src
323            .accounts
324            .iter()
325            .map(convert_idl_account_item)
326            .collect::<Result<Vec<_>>>()?,
327        args: src
328            .args
329            .iter()
330            .map(convert_idl_field)
331            .collect::<Result<Vec<_>>>()?,
332        docs: src.docs.clone(),
333    })
334}
335
336fn convert_idl_account_item(src: &solify_common::IdlAccountItem) -> Result<types::IdlAccountItem> {
337    Ok(types::IdlAccountItem {
338        name: src.name.clone(),
339        is_mut: src.is_mut,
340        is_signer: src.is_signer,
341        is_optional: src.is_optional,
342        docs: src.docs.clone(),
343        pda: match &src.pda {
344            Some(p) => Some(convert_idl_pda(p)?),
345            None => None,
346        },
347    })
348}
349
350fn convert_idl_pda(src: &solify_common::IdlPda) -> Result<types::IdlPda> {
351    Ok(types::IdlPda {
352        seeds: src
353            .seeds
354            .iter()
355            .map(convert_idl_seed)
356            .collect::<Result<Vec<_>>>()?,
357        // on-chain uses Option<String> for program in your anchor defs
358        program: if src.program.is_empty() {
359            None
360        } else {
361            Some(src.program.clone())
362        },
363    })
364}
365
366fn convert_idl_seed(src: &solify_common::IdlSeed) -> Result<types::IdlSeed> {
367    Ok(types::IdlSeed {
368        kind: src.kind.clone(),
369        path: src.path.clone(),
370        value: src.value.clone(),
371    })
372}
373
374fn convert_idl_account(src: &solify_common::IdlAccount) -> Result<types::IdlAccount> {
375    Ok(types::IdlAccount {
376        name: src.name.clone(),
377        fields: src
378            .fields
379            .iter()
380            .map(convert_idl_field)
381            .collect::<Result<Vec<_>>>()?,
382    })
383}
384
385fn convert_idl_field(src: &solify_common::IdlField) -> Result<types::IdlField> {
386    Ok(types::IdlField {
387        name: src.name.clone(),
388        field_type: src.field_type.clone(),
389    })
390}
391
392fn convert_idl_typedef(src: &solify_common::IdlTypeDef) -> Result<types::IdlTypeDef> {
393    Ok(types::IdlTypeDef {
394        name: src.name.clone(),
395        kind: src.kind.clone(),
396        fields: src.fields.clone(),
397    })
398}
399
400fn convert_idl_error(src: &solify_common::IdlError) -> Result<types::IdlError> {
401    Ok(types::IdlError {
402        code: src.code,
403        name: src.name.clone(),
404        msg: src.msg.clone(),
405    })
406}
407
408fn convert_idl_constant(src: &solify_common::IdlConstant) -> Result<types::IdlConstant> {
409    Ok(types::IdlConstant {
410        name: src.name.clone(),
411        constant_type: src.constant_type.clone(),
412        value: src.value.clone(),
413    })
414}
415
416fn convert_idl_event(src: &solify_common::IdlEvent) -> Result<types::IdlEvent> {
417    Ok(types::IdlEvent {
418        name: src.name.clone(),
419        discriminator: src.discriminator.clone(),
420        fields: src
421            .fields
422            .iter()
423            .map(convert_idl_field)
424            .collect::<Result<Vec<_>>>()?,
425    })
426}
427
428// Convert from generated types back to common types for IdlData
429fn convert_idl_data_back(generated: &types::IdlData) -> CommonIdlData {
430    CommonIdlData {
431        name: generated.name.clone(),
432        version: generated.version.clone(),
433        instructions: generated.instructions.iter().map(convert_idl_instruction_back).collect(),
434        accounts: generated.accounts.iter().map(convert_idl_account_back).collect(),
435        types: generated.types.iter().map(convert_idl_type_def_back).collect(),
436        errors: generated.errors.iter().map(convert_idl_error_back).collect(),
437        constants: generated.constants.iter().map(convert_idl_constant_back).collect(),
438        events: generated.events.iter().map(convert_idl_event_back).collect(),
439    }
440}
441
442fn convert_idl_instruction_back(generated: &types::IdlInstruction) -> solify_common::IdlInstruction {
443    solify_common::IdlInstruction {
444        name: generated.name.clone(),
445        accounts: generated.accounts.iter().map(convert_idl_account_item_back).collect(),
446        args: generated.args.iter().map(convert_idl_field_back).collect(),
447        docs: generated.docs.clone(),
448    }
449}
450
451fn convert_idl_account_item_back(generated: &types::IdlAccountItem) -> solify_common::IdlAccountItem {
452    solify_common::IdlAccountItem {
453        name: generated.name.clone(),
454        is_mut: generated.is_mut,
455        is_signer: generated.is_signer,
456        is_optional: generated.is_optional,
457        docs: generated.docs.clone(),
458        pda: generated.pda.as_ref().map(convert_idl_pda_back),
459    }
460}
461
462fn convert_idl_pda_back(generated: &types::IdlPda) -> solify_common::IdlPda {
463    solify_common::IdlPda {
464        seeds: generated.seeds.iter().map(convert_idl_seed_back).collect(),
465        program: generated.program.clone().unwrap_or_default(),
466    }
467}
468
469fn convert_idl_seed_back(generated: &types::IdlSeed) -> solify_common::IdlSeed {
470    solify_common::IdlSeed {
471        kind: generated.kind.clone(),
472        path: generated.path.clone(),
473        value: generated.value.clone(),
474    }
475}
476
477fn convert_idl_field_back(generated: &types::IdlField) -> solify_common::IdlField {
478    solify_common::IdlField {
479        name: generated.name.clone(),
480        field_type: generated.field_type.clone(),
481    }
482}
483
484fn convert_idl_account_back(generated: &types::IdlAccount) -> solify_common::IdlAccount {
485    solify_common::IdlAccount {
486        name: generated.name.clone(),
487        fields: generated.fields.iter().map(convert_idl_field_back).collect(),
488    }
489}
490
491fn convert_idl_type_def_back(generated: &types::IdlTypeDef) -> solify_common::IdlTypeDef {
492    solify_common::IdlTypeDef {
493        name: generated.name.clone(),
494        kind: generated.kind.clone(),
495        fields: generated.fields.clone(),
496    }
497}
498
499fn convert_idl_error_back(generated: &types::IdlError) -> solify_common::IdlError {
500    solify_common::IdlError {
501        code: generated.code,
502        name: generated.name.clone(),
503        msg: generated.msg.clone(),
504    }
505}
506
507fn convert_idl_constant_back(generated: &types::IdlConstant) -> solify_common::IdlConstant {
508    solify_common::IdlConstant {
509        name: generated.name.clone(),
510        constant_type: generated.constant_type.clone(),
511        value: generated.value.clone(),
512    }
513}
514
515fn convert_idl_event_back(generated: &types::IdlEvent) -> solify_common::IdlEvent {
516    solify_common::IdlEvent {
517        name: generated.name.clone(),
518        discriminator: generated.discriminator.clone(),
519        fields: generated.fields.iter().map(convert_idl_field_back).collect(),
520    }
521}
522
523// ---------- TestMetadata conversion ----------
524
525pub fn convert_test_metadata(src: &CommonTestMetadata) -> Result<types::TestMetadata> {
526    Ok(types::TestMetadata {
527        instruction_order: src.instruction_order.clone(),
528        account_dependencies: src
529            .account_dependencies
530            .iter()
531            .map(convert_account_dependency)
532            .collect::<Result<Vec<_>>>()?,
533        pda_init_sequence: src
534            .pda_init_sequence
535            .iter()
536            .map(convert_pda_init)
537            .collect::<Result<Vec<_>>>()?,
538        setup_requirements: src
539            .setup_requirements
540            .iter()
541            .map(convert_setup_requirement)
542            .collect::<Result<Vec<_>>>()?,
543        test_cases: src
544            .test_cases
545            .iter()
546            .map(convert_instruction_test_cases)
547            .collect::<Result<Vec<_>>>()?,
548    })
549}
550
551fn convert_account_dependency(src: &solify_common::AccountDependency) -> Result<types::AccountDependency> {
552    Ok(types::AccountDependency {
553        account_name: src.account_name.clone(),
554        depends_on: src.depends_on.clone(),
555        is_pda: src.is_pda,
556        is_signer: src.is_signer,
557        is_mut: src.is_mut,
558        must_be_initialized: src.must_be_initialized,
559        initialization_order: src.initialization_order,
560    })
561}
562
563fn convert_pda_init(src: &solify_common::PdaInit) -> Result<types::PdaInit> {
564    // convert program_id string -> Pubkey
565    let program_id = Pubkey::from_str(&src.program_id)
566        .with_context(|| format!("Failed to parse program id '{}'", src.program_id))?;
567
568    Ok(types::PdaInit {
569        account_name: src.account_name.clone(),
570        seeds: src
571            .seeds
572            .iter()
573            .map(convert_seed_component)
574            .collect::<Result<Vec<_>>>()?,
575        program_id,
576        space: src.space,
577    })
578}
579
580fn convert_seed_component(src: &solify_common::SeedComponent) -> Result<types::SeedComponent> {
581    Ok(types::SeedComponent {
582        seed_type: match src.seed_type {
583            solify_common::SeedType::Static => types::SeedType::Static,
584            solify_common::SeedType::AccountKey => types::SeedType::AccountKey,
585            solify_common::SeedType::Argument => types::SeedType::Argument,
586        },
587        value: src.value.clone(),
588    })
589}
590
591fn convert_setup_requirement(src: &solify_common::SetupRequirement) -> Result<types::SetupRequirement> {
592    Ok(types::SetupRequirement {
593        requirement_type: match src.requirement_type {
594            solify_common::SetupType::CreateKeypair => types::SetupType::CreateKeypair,
595            solify_common::SetupType::FundAccount => types::SetupType::FundAccount,
596            solify_common::SetupType::InitializePda => types::SetupType::InitializePda,
597            solify_common::SetupType::MintTokens => types::SetupType::MintTokens,
598            solify_common::SetupType::CreateAta => types::SetupType::CreateAta,
599        },
600        description: src.description.clone(),
601        dependencies: src.dependencies.clone(),
602    })
603}
604
605fn convert_instruction_test_cases(src: &solify_common::InstructionTestCases) -> Result<types::InstructionTestCases> {
606    Ok(types::InstructionTestCases {
607        instruction_name: src.instruction_name.clone(),
608        arguments: src
609            .arguments
610            .iter()
611            .map(convert_argument_info)
612            .collect::<Result<Vec<_>>>()?,
613        positive_cases: src
614            .positive_cases
615            .iter()
616            .map(convert_test_case)
617            .collect::<Result<Vec<_>>>()?,
618        negative_cases: src
619            .negative_cases
620            .iter()
621            .map(convert_test_case)
622            .collect::<Result<Vec<_>>>()?,
623    })
624}
625
626fn convert_argument_info(src: &solify_common::ArgumentInfo) -> Result<types::ArgumentInfo> {
627    Ok(types::ArgumentInfo {
628        name: src.name.clone(),
629        arg_type: convert_argument_type(&src.arg_type)?,
630        constraints: src.constraints.clone().into_iter().map(convert_constraint).collect::<Result<Vec<_>>>()?,
631        is_optional: src.is_optional,
632    })
633}
634
635fn convert_argument_type(src: &solify_common::ArgumentType) -> Result<types::ArgumentType> {
636    // helper: produce a concise name string for an argument type (used for VecType/OptionType)
637    fn arg_type_name(t: &C) -> Result<String> {
638        match t {
639            C::U8 => Ok("u8".to_string()),
640            C::U16 => Ok("u16".to_string()),
641            C::U32 => Ok("u32".to_string()),
642            C::U64 => Ok("u64".to_string()),
643            C::U128 => Ok("u128".to_string()),
644            C::I8 => Ok("i8".to_string()),
645            C::I16 => Ok("i16".to_string()),
646            C::I32 => Ok("i32".to_string()),
647            C::I64 => Ok("i64".to_string()),
648            C::I128 => Ok("i128".to_string()),
649            C::Bool => Ok("bool".to_string()),
650            C::String { .. } => Ok("String".to_string()),
651            C::Pubkey => Ok("Pubkey".to_string()),
652            C::Vec { inner_type, .. } => {
653                // recursive: produce inner name and wrap in Vec<...>
654                let inner = arg_type_name(inner_type)?;
655                Ok(format!("Vec<{}>", inner))
656            }
657            C::Option { inner_type } => {
658                let inner = arg_type_name(inner_type)?;
659                Ok(format!("Option<{}>", inner))
660            }
661            C::Struct { name } => Ok(name.clone()),
662            C::Enum { name, .. } => Ok(name.clone()),
663        }
664    }
665
666    let out = match src {
667        C::U8 => T::U8,
668        C::U16 => T::U16,
669        C::U32 => T::U32,
670        C::U64 => T::U64,
671        C::U128 => T::U128,
672        C::I8 => T::I8,
673        C::I16 => T::I16,
674        C::I32 => T::I32,
675        C::I64 => T::I64,
676        C::I128 => T::I128,
677        C::Bool => T::Bool,
678        C::String { max_length } => T::String { max_length: *max_length },
679        C::Pubkey => T::Pubkey,
680        C::Vec { inner_type, max_length } => {
681            // Generated enum uses VecType { inner_type_name: String, max_length: Option<u32> }
682            let inner_name = arg_type_name(inner_type)?;
683            T::VecType {
684                inner_type_name: inner_name,
685                max_length: *max_length,
686            }
687        }
688        C::Option { inner_type } => {
689            let inner_name = arg_type_name(inner_type)?;
690            T::OptionType {
691                inner_type_name: inner_name,
692            }
693        }
694        C::Struct { name } => {
695            // Generated type doesn't have Struct variant, return error
696            anyhow::bail!("Struct types are not supported in generated ArgumentType: {}", name);
697        }
698        C::Enum { name, .. } => {
699            // Generated type doesn't have Enum variant, return error
700            anyhow::bail!("Enum types are not supported in generated ArgumentType: {}", name);
701        }
702    };
703
704    Ok(out)
705}
706
707fn convert_constraint(src: solify_common::ArgumentConstraint) -> Result<types::ArgumentConstraint> {
708    use solify_common::ArgumentConstraint as C;
709    use types::ArgumentConstraint as T;
710
711    let out = match src {
712        C::Min { value } => T::Min { value },
713        C::Max { value } => T::Max { value },
714        C::Range { min, max } => T::Range { min, max },
715        C::NonZero => T::NonZero,
716        C::MaxLength { value } => T::MaxLength { value },
717        C::MinLength { value } => T::MinLength { value },
718        C::Custom { .. } => {
719            // If your generated type has Custom variant with description, adapt accordingly.
720            // Here we fallback to MaxLength 0 to avoid mismatch — better to extend generated types.
721            return Err(anyhow::anyhow!("Custom constraint mapping not implemented"))
722        }
723    };
724
725    Ok(out)
726}
727
728fn convert_test_case(src: &solify_common::TestCase) -> Result<types::TestCase> {
729    Ok(types::TestCase {
730        test_type: match src.test_type {
731            solify_common::TestCaseType::Positive => types::TestCaseType::Positive,
732            solify_common::TestCaseType::NegativeBoundary => types::TestCaseType::NegativeBoundary,
733            solify_common::TestCaseType::NegativeType => types::TestCaseType::NegativeType,
734            solify_common::TestCaseType::NegativeConstraint => types::TestCaseType::NegativeConstraint,
735            solify_common::TestCaseType::NegativeNull => types::TestCaseType::NegativeNull,
736            solify_common::TestCaseType::NegativeOverflow => types::TestCaseType::NegativeOverflow,
737        },
738        description: src.description.clone(),
739        argument_values: src
740            .argument_values
741            .iter()
742            .map(convert_test_argument_value)
743            .collect::<Result<Vec<_>>>()?,
744        expected_outcome: convert_expected_outcome(&src.expected_outcome)?,
745    })
746}
747
748fn convert_test_argument_value(src: &solify_common::TestArgumentValue) -> Result<types::TestArgumentValue> {
749    Ok(types::TestArgumentValue {
750        argument_name: src.argument_name.clone(),
751        value_type: match &src.value_type {
752            solify_common::TestValueType::Valid { description } => types::TestValueType::Valid { description: description.clone() },
753            solify_common::TestValueType::Invalid { description, reason } => types::TestValueType::Invalid { description: description.clone(), reason: reason.clone() },
754        },
755    })
756}
757
758fn convert_expected_outcome(src: &solify_common::ExpectedOutcome) -> Result<types::ExpectedOutcome> {
759    Ok(match src {
760        solify_common::ExpectedOutcome::Success { state_changes } => types::ExpectedOutcome::Success { state_changes: state_changes.clone() },
761        solify_common::ExpectedOutcome::Failure { error_code, error_message } => types::ExpectedOutcome::Failure { error_code: error_code.clone(), error_message: error_message.clone() },
762    })
763}
764
765// Convert from generated types back to common types for TestMetadata
766fn convert_test_metadata_back(src: &types::TestMetadata) -> Result<CommonTestMetadata> {
767    Ok(CommonTestMetadata {
768        instruction_order: src.instruction_order.clone(),
769        account_dependencies: src
770            .account_dependencies
771            .iter()
772            .map(convert_account_dependency_back)
773            .collect(),
774        pda_init_sequence: src
775            .pda_init_sequence
776            .iter()
777            .map(convert_pda_init_back)
778            .collect::<Result<Vec<_>>>()?,
779        setup_requirements: src
780            .setup_requirements
781            .iter()
782            .map(convert_setup_requirement_back)
783            .collect(),
784        test_cases: src
785            .test_cases
786            .iter()
787            .map(convert_instruction_test_cases_back)
788            .collect::<Result<Vec<_>>>()?,
789    })
790}
791
792fn convert_account_dependency_back(src: &types::AccountDependency) -> solify_common::AccountDependency {
793    solify_common::AccountDependency {
794        account_name: src.account_name.clone(),
795        depends_on: src.depends_on.clone(),
796        is_pda: src.is_pda,
797        is_signer: src.is_signer,
798        is_mut: src.is_mut,
799        must_be_initialized: src.must_be_initialized,
800        initialization_order: src.initialization_order,
801    }
802}
803
804fn convert_pda_init_back(src: &types::PdaInit) -> Result<solify_common::PdaInit> {
805    Ok(solify_common::PdaInit {
806        account_name: src.account_name.clone(),
807        seeds: src
808            .seeds
809            .iter()
810            .map(convert_seed_component_back)
811            .collect(),
812        program_id: src.program_id.to_string(),
813        space: src.space,
814    })
815}
816
817fn convert_seed_component_back(src: &types::SeedComponent) -> solify_common::SeedComponent {
818    solify_common::SeedComponent {
819        seed_type: match src.seed_type {
820            types::SeedType::Static => solify_common::SeedType::Static,
821            types::SeedType::AccountKey => solify_common::SeedType::AccountKey,
822            types::SeedType::Argument => solify_common::SeedType::Argument,
823        },
824        value: src.value.clone(),
825    }
826}
827
828fn convert_setup_requirement_back(src: &types::SetupRequirement) -> solify_common::SetupRequirement {
829    solify_common::SetupRequirement {
830        requirement_type: match src.requirement_type {
831            types::SetupType::CreateKeypair => solify_common::SetupType::CreateKeypair,
832            types::SetupType::FundAccount => solify_common::SetupType::FundAccount,
833            types::SetupType::InitializePda => solify_common::SetupType::InitializePda,
834            types::SetupType::MintTokens => solify_common::SetupType::MintTokens,
835            types::SetupType::CreateAta => solify_common::SetupType::CreateAta,
836        },
837        description: src.description.clone(),
838        dependencies: src.dependencies.clone(),
839    }
840}
841
842fn convert_instruction_test_cases_back(src: &types::InstructionTestCases) -> Result<solify_common::InstructionTestCases> {
843    Ok(solify_common::InstructionTestCases {
844        instruction_name: src.instruction_name.clone(),
845        arguments: src
846            .arguments
847            .iter()
848            .map(convert_argument_info_back)
849            .collect::<Result<Vec<_>>>()?,
850        positive_cases: src
851            .positive_cases
852            .iter()
853            .map(convert_test_case_back)
854            .collect::<Result<Vec<_>>>()?,
855        negative_cases: src
856            .negative_cases
857            .iter()
858            .map(convert_test_case_back)
859            .collect::<Result<Vec<_>>>()?,
860    })
861}
862
863fn convert_argument_info_back(src: &types::ArgumentInfo) -> Result<solify_common::ArgumentInfo> {
864    Ok(solify_common::ArgumentInfo {
865        name: src.name.clone(),
866        arg_type: convert_argument_type_back(&src.arg_type)?,
867        constraints: src.constraints.iter().map(convert_constraint_back).collect(),
868        is_optional: src.is_optional,
869    })
870}
871
872fn convert_argument_type_back(src: &types::ArgumentType) -> Result<solify_common::ArgumentType> {
873    use types::ArgumentType as T;
874    use solify_common::ArgumentType as C;
875
876    let out = match src {
877        T::U8 => C::U8,
878        T::U16 => C::U16,
879        T::U32 => C::U32,
880        T::U64 => C::U64,
881        T::U128 => C::U128,
882        T::I8 => C::I8,
883        T::I16 => C::I16,
884        T::I32 => C::I32,
885        T::I64 => C::I64,
886        T::I128 => C::I128,
887        T::Bool => C::Bool,
888        T::String { max_length } => C::String { max_length: *max_length },
889        T::Pubkey => C::Pubkey,
890        T::VecType { inner_type_name, max_length } => {
891            // Parse the inner type name back to ArgumentType
892            let inner_type = parse_argument_type_from_name(inner_type_name)?;
893            C::Vec {
894                inner_type: Box::new(inner_type),
895                max_length: *max_length,
896            }
897        }
898        T::OptionType { inner_type_name } => {
899            let inner_type = parse_argument_type_from_name(inner_type_name)?;
900            C::Option {
901                inner_type: Box::new(inner_type),
902            }
903        }
904    };
905    Ok(out)
906}
907
908fn parse_argument_type_from_name(name: &str) -> Result<solify_common::ArgumentType> {
909    // Simple parser for basic types - this is a simplified version
910    match name {
911        "u8" => Ok(solify_common::ArgumentType::U8),
912        "u16" => Ok(solify_common::ArgumentType::U16),
913        "u32" => Ok(solify_common::ArgumentType::U32),
914        "u64" => Ok(solify_common::ArgumentType::U64),
915        "u128" => Ok(solify_common::ArgumentType::U128),
916        "i8" => Ok(solify_common::ArgumentType::I8),
917        "i16" => Ok(solify_common::ArgumentType::I16),
918        "i32" => Ok(solify_common::ArgumentType::I32),
919        "i64" => Ok(solify_common::ArgumentType::I64),
920        "i128" => Ok(solify_common::ArgumentType::I128),
921        "bool" => Ok(solify_common::ArgumentType::Bool),
922        "String" => Ok(solify_common::ArgumentType::String { max_length: None }),
923        "Pubkey" => Ok(solify_common::ArgumentType::Pubkey),
924        _ => {
925            // Try to parse Vec<...> or Option<...>
926            if let Some(inner) = name.strip_prefix("Vec<").and_then(|s| s.strip_suffix('>')) {
927                let inner_type = parse_argument_type_from_name(inner)?;
928                Ok(solify_common::ArgumentType::Vec {
929                    inner_type: Box::new(inner_type),
930                    max_length: None,
931                })
932            } else if let Some(inner) = name.strip_prefix("Option<").and_then(|s| s.strip_suffix('>')) {
933                let inner_type = parse_argument_type_from_name(inner)?;
934                Ok(solify_common::ArgumentType::Option {
935                    inner_type: Box::new(inner_type),
936                })
937            } else {
938                // For unknown types, treat as Struct
939                Ok(solify_common::ArgumentType::Struct { name: name.to_string() })
940            }
941        }
942    }
943}
944
945fn convert_constraint_back(src: &types::ArgumentConstraint) -> solify_common::ArgumentConstraint {
946    use types::ArgumentConstraint as T;
947    use solify_common::ArgumentConstraint as C;
948
949    match src {
950        T::Min { value } => C::Min { value: *value },
951        T::Max { value } => C::Max { value: *value },
952        T::Range { min, max } => C::Range { min: *min, max: *max },
953        T::NonZero => C::NonZero,
954        T::MaxLength { value } => C::MaxLength { value: *value },
955        T::MinLength { value } => C::MinLength { value: *value }
956    }
957}
958
959fn convert_test_case_back(src: &types::TestCase) -> Result<solify_common::TestCase> {
960    Ok(solify_common::TestCase {
961        test_type: match src.test_type {
962            types::TestCaseType::Positive => solify_common::TestCaseType::Positive,
963            types::TestCaseType::NegativeBoundary => solify_common::TestCaseType::NegativeBoundary,
964            types::TestCaseType::NegativeType => solify_common::TestCaseType::NegativeType,
965            types::TestCaseType::NegativeConstraint => solify_common::TestCaseType::NegativeConstraint,
966            types::TestCaseType::NegativeNull => solify_common::TestCaseType::NegativeNull,
967            types::TestCaseType::NegativeOverflow => solify_common::TestCaseType::NegativeOverflow,
968        },
969        description: src.description.clone(),
970        argument_values: src.argument_values.iter().map(convert_test_argument_value_back).collect(),
971        expected_outcome: convert_expected_outcome_back(&src.expected_outcome),
972    })
973}
974
975fn convert_test_argument_value_back(src: &types::TestArgumentValue) -> solify_common::TestArgumentValue {
976    solify_common::TestArgumentValue {
977        argument_name: src.argument_name.clone(),
978        value_type: match &src.value_type {
979            types::TestValueType::Valid { description } => {
980                solify_common::TestValueType::Valid { description: description.clone() }
981            }
982            types::TestValueType::Invalid { description, reason } => {
983                solify_common::TestValueType::Invalid {
984                    description: description.clone(),
985                    reason: reason.clone(),
986                }
987            }
988        },
989    }
990}
991
992fn convert_expected_outcome_back(src: &types::ExpectedOutcome) -> solify_common::ExpectedOutcome {
993    match src {
994        types::ExpectedOutcome::Success { state_changes } => {
995            solify_common::ExpectedOutcome::Success {
996                state_changes: state_changes.clone(),
997            }
998        }
999        types::ExpectedOutcome::Failure { error_code, error_message } => {
1000            solify_common::ExpectedOutcome::Failure {
1001                error_code: error_code.clone(),
1002                error_message: error_message.clone(),
1003            }
1004        }
1005    }
1006}
1007
1008#[inline]
1009fn system_program_id() -> Pubkey {
1010    Pubkey::from_str("11111111111111111111111111111111").unwrap()
1011}