1use 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#[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 #[serde(default)]
63 pub discriminator: Vec<u8>,
64 #[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#[derive(Debug, Clone, Deserialize, Serialize)]
90pub struct IdlPda {
91 pub seeds: Vec<IdlPdaSeed>,
92 #[serde(default)]
93 pub program: Option<IdlPdaProgram>,
94}
95
96#[derive(Debug, Clone, Deserialize, Serialize)]
98#[serde(tag = "kind", rename_all = "lowercase")]
99pub enum IdlPdaSeed {
100 Const { value: Vec<u8> },
102 Account {
104 path: String,
105 #[serde(default)]
106 account: Option<String>,
107 },
108 Arg {
110 path: String,
111 #[serde(rename = "type", default)]
112 arg_type: Option<String>,
113 },
114}
115
116#[derive(Debug, Clone, Deserialize, Serialize)]
118#[serde(untagged)]
119pub enum IdlPdaProgram {
120 Account { kind: String, path: String },
122 Literal { kind: String, value: String },
124 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 #[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#[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 #[serde(default)]
259 pub serialization: Option<IdlSerialization>,
260 #[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 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 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}