use std::collections::BTreeMap;
use serde_json::Value;
use sha2::{Digest, Sha256};
use thiserror::Error;
use super::schema::{
EnumVariant, FieldType, InstructionSchema, NamedAccount, NamedField, ProgramSchema,
};
#[derive(Debug, Error)]
pub enum AnchorParseError {
#[error("invalid JSON: {0}")]
InvalidJson(#[from] serde_json::Error),
#[error("expected JSON object at IDL root")]
NotAnObject,
#[error("instruction discriminator must be 8 u8s, got {0:?}")]
BadDiscriminator(Vec<i64>),
#[error("missing field '{0}'")]
MissingField(&'static str),
#[error("unsupported type shape at {path}: {value}")]
UnsupportedType { path: String, value: String },
#[error("type '{name}' missing 'kind' (expected 'struct', 'enum', or 'type')")]
UnsupportedTypeKind { name: String },
}
pub fn schema_from_anchor_idl_json(json: &str) -> Result<ProgramSchema, AnchorParseError> {
let root: Value = serde_json::from_str(json)?;
let obj = root.as_object().ok_or(AnchorParseError::NotAnObject)?;
let program_id = obj
.get("address")
.and_then(Value::as_str)
.or_else(|| {
obj.get("metadata")
.and_then(|m| m.get("address"))
.and_then(Value::as_str)
})
.unwrap_or("")
.to_string();
let mut defined_types = BTreeMap::new();
if let Some(arr) = obj.get("types").and_then(Value::as_array) {
for t in arr {
let name = t
.get("name")
.and_then(Value::as_str)
.ok_or(AnchorParseError::MissingField("types[].name"))?
.to_string();
let ty_node = t
.get("type")
.ok_or(AnchorParseError::MissingField("types[].type"))?;
let ty = parse_type_def(&name, ty_node)?;
defined_types.insert(name, ty);
}
}
let mut instructions: BTreeMap<Vec<u8>, InstructionSchema> = BTreeMap::new();
let arr = obj
.get("instructions")
.and_then(Value::as_array)
.ok_or(AnchorParseError::MissingField("instructions"))?;
for ix in arr {
let name = ix
.get("name")
.and_then(Value::as_str)
.ok_or(AnchorParseError::MissingField("instructions[].name"))?
.to_string();
let discriminator = match ix.get("discriminator") {
Some(Value::Array(arr)) => {
let bytes: Vec<i64> = arr.iter().filter_map(Value::as_i64).collect();
if bytes.len() != arr.len() || bytes.iter().any(|b| !(0..=255).contains(b)) {
return Err(AnchorParseError::BadDiscriminator(bytes));
}
bytes.into_iter().map(|b| b as u8).collect::<Vec<u8>>()
}
_ => legacy_discriminator(&name),
};
let accounts = parse_accounts(ix.get("accounts"))?;
let args = parse_args(ix.get("args"))?;
instructions.insert(
discriminator.clone(),
InstructionSchema {
name,
discriminator,
accounts,
args,
},
);
}
Ok(ProgramSchema::build(
program_id,
instructions,
defined_types,
))
}
pub fn legacy_discriminator(name: &str) -> Vec<u8> {
let snake = to_snake_case(name);
let mut h = Sha256::new();
h.update(b"global:");
h.update(snake.as_bytes());
h.finalize()[..8].to_vec()
}
fn to_snake_case(s: &str) -> String {
let mut out = String::with_capacity(s.len() + 4);
let mut prev_lower = false;
for c in s.chars() {
if c.is_ascii_uppercase() {
if prev_lower {
out.push('_');
}
for lc in c.to_lowercase() {
out.push(lc);
}
prev_lower = false;
} else {
out.push(c);
prev_lower = c.is_ascii_lowercase() || c.is_ascii_digit();
}
}
out
}
fn parse_accounts(node: Option<&Value>) -> Result<Vec<NamedAccount>, AnchorParseError> {
let Some(arr) = node.and_then(Value::as_array) else {
return Ok(Vec::new());
};
let mut out = Vec::with_capacity(arr.len());
for a in arr {
if let Some(nested) = a.get("accounts").and_then(Value::as_array) {
let _ = nested;
let nested_parsed = parse_accounts(a.get("accounts"))?;
out.extend(nested_parsed);
continue;
}
let name = a
.get("name")
.and_then(Value::as_str)
.ok_or(AnchorParseError::MissingField("accounts[].name"))?
.to_string();
let writable = a
.get("writable")
.or_else(|| a.get("isMut"))
.and_then(Value::as_bool)
.unwrap_or(false);
let signer = a
.get("signer")
.or_else(|| a.get("isSigner"))
.and_then(Value::as_bool)
.unwrap_or(false);
let optional = a
.get("optional")
.or_else(|| a.get("isOptional"))
.and_then(Value::as_bool)
.unwrap_or(false);
out.push(NamedAccount {
name,
writable,
signer,
optional,
});
}
Ok(out)
}
fn parse_args(node: Option<&Value>) -> Result<Vec<NamedField>, AnchorParseError> {
let Some(arr) = node.and_then(Value::as_array) else {
return Ok(Vec::new());
};
let mut out = Vec::with_capacity(arr.len());
for f in arr {
out.push(parse_named_field(f, "args[]")?);
}
Ok(out)
}
fn parse_named_field(f: &Value, path: &str) -> Result<NamedField, AnchorParseError> {
let name = f
.get("name")
.and_then(Value::as_str)
.ok_or(AnchorParseError::MissingField("name"))?
.to_string();
let ty_node = f
.get("type")
.ok_or(AnchorParseError::MissingField("type"))?;
let ty = parse_type(ty_node, path)?;
Ok(NamedField { name, ty })
}
fn parse_type(node: &Value, path: &str) -> Result<FieldType, AnchorParseError> {
if let Some(s) = node.as_str() {
return Ok(parse_primitive(s));
}
if let Some(obj) = node.as_object() {
if let Some(inner) = obj.get("option") {
return Ok(FieldType::Option(Box::new(parse_type(
inner,
&format!("{path}.option"),
)?)));
}
if let Some(inner) = obj.get("vec") {
return Ok(FieldType::Vec(Box::new(parse_type(
inner,
&format!("{path}.vec"),
)?)));
}
if let Some(arr) = obj.get("array").and_then(Value::as_array) {
if arr.len() != 2 {
return Err(AnchorParseError::UnsupportedType {
path: path.to_string(),
value: node.to_string(),
});
}
let inner = parse_type(&arr[0], &format!("{path}.array[0]"))?;
let len = arr[1].as_u64().ok_or(AnchorParseError::UnsupportedType {
path: format!("{path}.array[1]"),
value: arr[1].to_string(),
})? as usize;
return Ok(FieldType::Array {
ty: Box::new(inner),
len,
});
}
if let Some(d) = obj.get("defined") {
let name = if let Some(s) = d.as_str() {
s.to_string()
} else if let Some(name) = d.get("name").and_then(Value::as_str) {
name.to_string()
} else {
return Err(AnchorParseError::UnsupportedType {
path: format!("{path}.defined"),
value: d.to_string(),
});
};
return Ok(FieldType::Defined(name));
}
}
Err(AnchorParseError::UnsupportedType {
path: path.to_string(),
value: node.to_string(),
})
}
fn parse_primitive(s: &str) -> FieldType {
match s {
"bool" => FieldType::Bool,
"u8" => FieldType::U8,
"u16" => FieldType::U16,
"u32" => FieldType::U32,
"u64" => FieldType::U64,
"u128" => FieldType::U128,
"i8" => FieldType::I8,
"i16" => FieldType::I16,
"i32" => FieldType::I32,
"i64" => FieldType::I64,
"i128" => FieldType::I128,
"f32" => FieldType::F32,
"f64" => FieldType::F64,
"string" => FieldType::String,
"bytes" => FieldType::Bytes,
"pubkey" | "publicKey" => FieldType::Pubkey,
other => FieldType::Defined(other.to_string()),
}
}
fn parse_type_def(name: &str, node: &Value) -> Result<FieldType, AnchorParseError> {
let kind = node.get("kind").and_then(Value::as_str).ok_or_else(|| {
AnchorParseError::UnsupportedTypeKind {
name: name.to_string(),
}
})?;
match kind {
"struct" => {
let fields = node
.get("fields")
.and_then(Value::as_array)
.map(|arr| {
arr.iter()
.map(|f| parse_named_field(f, &format!("types.{name}.fields")))
.collect::<Result<Vec<_>, _>>()
})
.transpose()?
.unwrap_or_default();
Ok(FieldType::Struct(fields))
}
"enum" => {
let variants = node
.get("variants")
.and_then(Value::as_array)
.map(|arr| {
arr.iter()
.map(|v| parse_enum_variant(v, name))
.collect::<Result<Vec<_>, _>>()
})
.transpose()?
.unwrap_or_default();
Ok(FieldType::Enum(variants))
}
"type" => {
let alias = node
.get("alias")
.or_else(|| node.get("type"))
.ok_or_else(|| AnchorParseError::UnsupportedType {
path: format!("types.{name}.alias"),
value: node.to_string(),
})?;
parse_type(alias, &format!("types.{name}.alias"))
}
_ => Err(AnchorParseError::UnsupportedTypeKind {
name: name.to_string(),
}),
}
}
fn parse_enum_variant(v: &Value, enum_name: &str) -> Result<EnumVariant, AnchorParseError> {
let name = v
.get("name")
.and_then(Value::as_str)
.ok_or(AnchorParseError::MissingField("variants[].name"))?
.to_string();
let fields = match v.get("fields") {
None | Some(Value::Null) => None,
Some(Value::Array(arr)) => {
let mut out = Vec::with_capacity(arr.len());
for (i, f) in arr.iter().enumerate() {
if f.is_object() && f.get("name").and_then(Value::as_str).is_some() {
out.push(parse_named_field(
f,
&format!("types.{enum_name}.{name}.fields[{i}]"),
)?);
} else {
out.push(NamedField {
name: format!("_{i}"),
ty: parse_type(f, &format!("types.{enum_name}.{name}[{i}]"))?,
});
}
}
Some(out)
}
Some(other) => {
return Err(AnchorParseError::UnsupportedType {
path: format!("types.{enum_name}.{name}.fields"),
value: other.to_string(),
})
}
};
Ok(EnumVariant { name, fields })
}