Skip to main content

hyperstack_idl/
types.rs

1//! Core type definitions for IDL
2
3use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, Deserialize, Serialize)]
6pub struct IdlSpec {
7    #[serde(default)]
8    pub version: Option<String>,
9    #[serde(default)]
10    pub name: Option<String>,
11    #[serde(default)]
12    pub address: Option<String>,
13    pub instructions: Vec<IdlInstruction>,
14    pub accounts: Vec<IdlAccount>,
15    #[serde(default)]
16    pub types: Vec<IdlTypeDef>,
17    #[serde(default)]
18    pub events: Vec<IdlEvent>,
19    #[serde(default)]
20    pub errors: Vec<IdlError>,
21    #[serde(default)]
22    pub constants: Vec<IdlConstant>,
23    pub metadata: Option<IdlMetadata>,
24}
25
26#[derive(Debug, Clone, Deserialize, Serialize)]
27pub struct IdlConstant {
28    pub name: String,
29    #[serde(rename = "type")]
30    pub type_: IdlType,
31    pub value: String,
32}
33
34#[derive(Debug, Clone, Deserialize, Serialize)]
35pub struct IdlMetadata {
36    #[serde(default)]
37    pub name: Option<String>,
38    #[serde(default)]
39    pub version: Option<String>,
40    #[serde(default)]
41    pub address: Option<String>,
42    #[serde(default)]
43    pub spec: Option<String>,
44    #[serde(default)]
45    pub description: Option<String>,
46    #[serde(default)]
47    pub origin: Option<String>,
48}
49
50/// Steel-style discriminant format: {"type": "u8", "value": N}
51#[derive(Debug, Clone, Deserialize, Serialize)]
52pub struct SteelDiscriminant {
53    #[serde(rename = "type")]
54    pub type_: String,
55    pub value: u64,
56}
57
58#[derive(Debug, Clone, Deserialize, Serialize)]
59pub struct IdlInstruction {
60    pub name: String,
61    /// Anchor-style discriminator: 8-byte array
62    #[serde(default)]
63    pub discriminator: Vec<u8>,
64    /// Steel-style discriminant: {"type": "u8", "value": N}
65    #[serde(default)]
66    pub discriminant: Option<SteelDiscriminant>,
67    #[serde(default)]
68    pub docs: Vec<String>,
69    pub accounts: Vec<IdlAccountArg>,
70    pub args: Vec<IdlField>,
71}
72
73impl IdlInstruction {
74    pub fn get_discriminator(&self) -> Vec<u8> {
75        if !self.discriminator.is_empty() {
76            return self.discriminator.clone();
77        }
78
79        if let Some(disc) = &self.discriminant {
80            let value = disc.value as u8;
81            return vec![value, 0, 0, 0, 0, 0, 0, 0];
82        }
83
84        crate::discriminator::anchor_discriminator(&format!("global:{}", self.name))
85    }
86}
87
88/// PDA definition in Anchor IDL format
89#[derive(Debug, Clone, Deserialize, Serialize)]
90pub struct IdlPda {
91    pub seeds: Vec<IdlPdaSeed>,
92    #[serde(default)]
93    pub program: Option<IdlPdaProgram>,
94}
95
96/// PDA seed in Anchor IDL format
97#[derive(Debug, Clone, Deserialize, Serialize)]
98#[serde(tag = "kind", rename_all = "lowercase")]
99pub enum IdlPdaSeed {
100    /// Constant byte array seed
101    Const { value: Vec<u8> },
102    /// Reference to another account in the instruction
103    Account {
104        path: String,
105        #[serde(default)]
106        account: Option<String>,
107    },
108    /// Reference to an instruction argument
109    Arg {
110        path: String,
111        #[serde(rename = "type", default)]
112        arg_type: Option<String>,
113    },
114}
115
116/// Program reference for cross-program PDAs
117#[derive(Debug, Clone, Deserialize, Serialize)]
118#[serde(untagged)]
119pub enum IdlPdaProgram {
120    /// Reference to another account that holds the program ID
121    Account { kind: String, path: String },
122    /// Literal program ID
123    Literal { kind: String, value: String },
124    /// Constant program ID as bytes
125    Const { kind: String, value: Vec<u8> },
126}
127
128#[derive(Debug, Clone, Deserialize, Serialize)]
129pub struct IdlAccountArg {
130    pub name: String,
131    #[serde(rename = "isMut", alias = "writable", default)]
132    pub is_mut: bool,
133    #[serde(rename = "isSigner", alias = "signer", default)]
134    pub is_signer: bool,
135    #[serde(default)]
136    pub address: Option<String>,
137    #[serde(default)]
138    pub optional: bool,
139    #[serde(default)]
140    pub docs: Vec<String>,
141    #[serde(default)]
142    pub pda: Option<IdlPda>,
143}
144
145#[derive(Debug, Clone, Deserialize, Serialize)]
146pub struct IdlAccount {
147    pub name: String,
148    #[serde(default)]
149    pub discriminator: Vec<u8>,
150    #[serde(default)]
151    pub docs: Vec<String>,
152    /// Steel format embedded type definition
153    #[serde(rename = "type", default)]
154    pub type_def: Option<IdlTypeDefKind>,
155}
156
157impl IdlAccount {
158    pub fn get_discriminator(&self) -> Vec<u8> {
159        if !self.discriminator.is_empty() {
160            return self.discriminator.clone();
161        }
162
163        crate::discriminator::anchor_discriminator(&format!("account:{}", self.name))
164    }
165}
166
167#[derive(Debug, Clone, Deserialize, Serialize)]
168pub struct IdlTypeDefStruct {
169    pub kind: String,
170    pub fields: Vec<IdlField>,
171}
172
173#[derive(Debug, Clone, Deserialize, Serialize)]
174pub struct IdlField {
175    pub name: String,
176    #[serde(rename = "type")]
177    pub type_: IdlType,
178}
179
180#[derive(Debug, Clone, Deserialize, Serialize)]
181#[serde(untagged)]
182pub enum IdlType {
183    Simple(String),
184    Array(IdlTypeArray),
185    Option(IdlTypeOption),
186    Vec(IdlTypeVec),
187    HashMap(IdlTypeHashMap),
188    Defined(IdlTypeDefined),
189}
190
191#[derive(Debug, Clone, Deserialize, Serialize)]
192pub struct IdlTypeOption {
193    pub option: Box<IdlType>,
194}
195
196#[derive(Debug, Clone, Deserialize, Serialize)]
197pub struct IdlTypeVec {
198    pub vec: Box<IdlType>,
199}
200
201#[derive(Debug, Clone, Deserialize, Serialize)]
202pub struct IdlTypeHashMap {
203    #[serde(alias = "bTreeMap")]
204    #[serde(rename = "hashMap")]
205    pub hash_map: (Box<IdlType>, Box<IdlType>),
206}
207
208#[derive(Debug, Clone, Deserialize, Serialize)]
209pub struct IdlTypeArray {
210    pub array: Vec<IdlTypeArrayElement>,
211}
212
213#[derive(Debug, Clone, Deserialize, Serialize)]
214#[serde(untagged)]
215pub enum IdlTypeArrayElement {
216    Nested(IdlType),
217    Type(String),
218    Size(u32),
219}
220
221#[derive(Debug, Clone, Deserialize, Serialize)]
222pub struct IdlTypeDefined {
223    pub defined: IdlTypeDefinedInner,
224}
225
226#[derive(Debug, Clone, Deserialize, Serialize)]
227#[serde(untagged)]
228pub enum IdlTypeDefinedInner {
229    Named { name: String },
230    Simple(String),
231}
232
233#[derive(Debug, Clone, Deserialize, Serialize)]
234pub struct IdlRepr {
235    pub kind: String,
236    #[serde(default)]
237    pub packed: Option<bool>,
238}
239
240/// Account serialization format as specified in the IDL.
241/// Defaults to Borsh when not specified.
242#[derive(Debug, Clone, Default, PartialEq, Eq, Deserialize, Serialize)]
243#[serde(rename_all = "lowercase")]
244pub enum IdlSerialization {
245    #[default]
246    Borsh,
247    Bytemuck,
248    #[serde(alias = "bytemuckunsafe")]
249    BytemuckUnsafe,
250}
251
252#[derive(Debug, Clone, Deserialize, Serialize)]
253pub struct IdlTypeDef {
254    pub name: String,
255    #[serde(default)]
256    pub docs: Vec<String>,
257    /// Serialization format: "borsh" (default), "bytemuck", or "bytemuckunsafe"
258    #[serde(default)]
259    pub serialization: Option<IdlSerialization>,
260    /// Repr annotation for zero-copy types (e.g., {"kind": "c"})
261    #[serde(default)]
262    pub repr: Option<IdlRepr>,
263    #[serde(rename = "type")]
264    pub type_def: IdlTypeDefKind,
265}
266
267#[derive(Debug, Clone, Deserialize, Serialize)]
268#[serde(untagged)]
269pub enum IdlTypeDefKind {
270    Struct {
271        kind: String,
272        fields: Vec<IdlField>,
273    },
274    TupleStruct {
275        kind: String,
276        fields: Vec<IdlType>,
277    },
278    Enum {
279        kind: String,
280        variants: Vec<IdlEnumVariant>,
281    },
282}
283
284#[derive(Debug, Clone, Deserialize, Serialize)]
285pub struct IdlEnumVariant {
286    pub name: String,
287}
288
289#[derive(Debug, Clone, Deserialize, Serialize)]
290pub struct IdlEvent {
291    pub name: String,
292    #[serde(default)]
293    pub discriminator: Vec<u8>,
294    #[serde(default)]
295    pub docs: Vec<String>,
296}
297
298impl IdlEvent {
299    pub fn get_discriminator(&self) -> Vec<u8> {
300        if !self.discriminator.is_empty() {
301            return self.discriminator.clone();
302        }
303        crate::discriminator::anchor_discriminator(&format!("event:{}", self.name))
304    }
305}
306
307#[derive(Debug, Clone, Deserialize, Serialize)]
308pub struct IdlError {
309    pub code: u32,
310    pub name: String,
311    #[serde(default)]
312    pub msg: Option<String>,
313}
314
315pub type IdlInstructionAccount = IdlAccountArg;
316pub type IdlInstructionArg = IdlField;
317pub type IdlTypeDefTy = IdlTypeDefKind;
318
319impl IdlSpec {
320    pub fn get_name(&self) -> &str {
321        self.name
322            .as_deref()
323            .or_else(|| self.metadata.as_ref().and_then(|m| m.name.as_deref()))
324            .unwrap_or("unknown")
325    }
326
327    pub fn get_version(&self) -> &str {
328        self.version
329            .as_deref()
330            .or_else(|| self.metadata.as_ref().and_then(|m| m.version.as_deref()))
331            .unwrap_or("0.1.0")
332    }
333
334    /// Check if a field is an account (vs an arg/data field) for a given instruction
335    /// Returns Some("accounts") if it's an account, Some("data") if it's an arg, None if not found
336    pub fn get_instruction_field_prefix(
337        &self,
338        instruction_name: &str,
339        field_name: &str,
340    ) -> Option<&'static str> {
341        let normalized_name = to_snake_case(instruction_name);
342
343        for instruction in &self.instructions {
344            if instruction.name == normalized_name
345                || instruction.name.eq_ignore_ascii_case(instruction_name)
346            {
347                for account in &instruction.accounts {
348                    if account.name == field_name {
349                        return Some("accounts");
350                    }
351                }
352                for arg in &instruction.args {
353                    if arg.name == field_name {
354                        return Some("data");
355                    }
356                }
357                return None;
358            }
359        }
360        None
361    }
362
363    /// Get the discriminator bytes for an instruction by name
364    pub fn get_instruction_discriminator(&self, instruction_name: &str) -> Option<Vec<u8>> {
365        let normalized_name = to_snake_case(instruction_name);
366        for instruction in &self.instructions {
367            if instruction.name == normalized_name {
368                let disc = instruction.get_discriminator();
369                if !disc.is_empty() {
370                    return Some(disc);
371                }
372            }
373        }
374        None
375    }
376}
377
378pub fn to_snake_case(s: &str) -> String {
379    let mut result = String::new();
380
381    for c in s.chars() {
382        if c.is_uppercase() {
383            if !result.is_empty() {
384                result.push('_');
385            }
386            result.push(c.to_lowercase().next().unwrap());
387        } else {
388            result.push(c);
389        }
390    }
391
392    result
393}
394
395pub fn to_pascal_case(s: &str) -> String {
396    s.split('_')
397        .map(|word| {
398            let mut chars = word.chars();
399            match chars.next() {
400                None => String::new(),
401                Some(f) => f.to_uppercase().collect::<String>() + chars.as_str(),
402            }
403        })
404        .collect()
405}