1use std::collections::{BTreeMap, HashMap, HashSet};
2use std::marker::PhantomData;
3use std::sync::OnceLock;
4
5use futures::future::join_all;
6
7use serde::{Deserialize, Serialize};
8use serde_json::Value;
9
10pub struct ResolveContext<'a> {
12 #[allow(dead_code)]
13 pub(crate) state_id: u32,
14 pub(crate) slot: u64,
15 pub(crate) signature: String,
16 pub(crate) reverse_lookups:
17 &'a mut std::collections::HashMap<String, crate::vm::PdaReverseLookup>,
18}
19
20impl<'a> ResolveContext<'a> {
21 pub fn new(
23 state_id: u32,
24 slot: u64,
25 signature: String,
26 reverse_lookups: &'a mut std::collections::HashMap<String, crate::vm::PdaReverseLookup>,
27 ) -> Self {
28 Self {
29 state_id,
30 slot,
31 signature,
32 reverse_lookups,
33 }
34 }
35
36 pub fn pda_reverse_lookup(&mut self, pda_address: &str) -> Option<String> {
39 let lookup_name = "default_pda_lookup";
40 self.reverse_lookups
41 .get_mut(lookup_name)
42 .and_then(|t| t.lookup(pda_address))
43 }
44
45 pub fn slot(&self) -> u64 {
46 self.slot
47 }
48
49 pub fn signature(&self) -> &str {
50 &self.signature
51 }
52}
53
54pub enum KeyResolution {
56 Found(String),
58
59 QueueUntil(&'static [u8]),
62
63 Skip,
65}
66
67pub struct InstructionContext<'a> {
69 pub(crate) accounts: HashMap<String, String>,
70 #[allow(dead_code)]
71 pub(crate) state_id: u32,
72 pub(crate) reverse_lookup_tx: *mut (dyn ReverseLookupUpdater + 'a),
73 pub(crate) pending_updates: Vec<crate::vm::PendingAccountUpdate>,
74 pub(crate) registers: Option<*mut Vec<crate::vm::RegisterValue>>,
75 pub(crate) state_reg: Option<crate::vm::Register>,
76 #[allow(dead_code)]
77 pub(crate) compiled_paths: Option<*const HashMap<String, crate::metrics_context::CompiledPath>>,
78 pub(crate) instruction_data: Option<&'a serde_json::Value>,
79 pub(crate) slot: Option<u64>,
80 pub(crate) signature: Option<String>,
81 pub(crate) timestamp: Option<i64>,
82 pub(crate) dirty_tracker: crate::vm::DirtyTracker,
83 _borrow: PhantomData<&'a mut ()>,
84}
85
86pub trait ReverseLookupUpdater {
87 fn update(
88 &mut self,
89 pda_address: String,
90 seed_value: String,
91 ) -> Vec<crate::vm::PendingAccountUpdate>;
92 fn flush_pending(&mut self, pda_address: &str) -> Vec<crate::vm::PendingAccountUpdate>;
93}
94
95#[derive(Debug, Clone, Serialize, Deserialize)]
96pub struct TokenMetadata {
97 pub mint: String,
98 pub name: Option<String>,
99 pub symbol: Option<String>,
100 pub decimals: Option<u8>,
101 pub logo_uri: Option<String>,
102}
103
104#[derive(Debug, Clone, Copy)]
105pub struct ResolverTypeScriptSchema {
106 pub name: &'static str,
107 pub definition: &'static str,
108}
109
110#[derive(Debug, Clone, Copy)]
111pub struct ResolverComputedMethod {
112 pub name: &'static str,
113 pub arg_count: usize,
114}
115
116pub trait ResolverDefinition: Send + Sync {
117 fn name(&self) -> &'static str;
118 fn output_type(&self) -> &'static str;
119 fn computed_methods(&self) -> &'static [ResolverComputedMethod];
120 fn evaluate_computed(
121 &self,
122 method: &str,
123 args: &[Value],
124 ) -> std::result::Result<Value, Box<dyn std::error::Error>>;
125 fn typescript_interface(&self) -> Option<&'static str> {
126 None
127 }
128 fn typescript_schema(&self) -> Option<ResolverTypeScriptSchema> {
129 None
130 }
131 fn extra_output_types(&self) -> &'static [&'static str] {
132 &[]
133 }
134}
135
136pub struct ResolverRegistry {
137 resolvers: BTreeMap<String, Box<dyn ResolverDefinition>>,
138}
139
140impl Default for ResolverRegistry {
141 fn default() -> Self {
142 Self::new()
143 }
144}
145
146impl ResolverRegistry {
147 pub fn new() -> Self {
148 Self {
149 resolvers: BTreeMap::new(),
150 }
151 }
152
153 pub fn register(&mut self, resolver: Box<dyn ResolverDefinition>) {
154 self.resolvers.insert(resolver.name().to_string(), resolver);
155 }
156
157 pub fn resolver(&self, name: &str) -> Option<&dyn ResolverDefinition> {
158 self.resolvers.get(name).map(|resolver| resolver.as_ref())
159 }
160
161 pub fn definitions(&self) -> impl Iterator<Item = &dyn ResolverDefinition> {
162 self.resolvers.values().map(|resolver| resolver.as_ref())
163 }
164
165 pub fn is_output_type(&self, type_name: &str) -> bool {
166 self.resolvers.values().any(|resolver| {
167 resolver.output_type() == type_name
168 || resolver.extra_output_types().contains(&type_name)
169 })
170 }
171
172 pub fn evaluate_computed(
173 &self,
174 resolver: &str,
175 method: &str,
176 args: &[Value],
177 ) -> std::result::Result<Value, Box<dyn std::error::Error>> {
178 let resolver_impl = self
179 .resolver(resolver)
180 .ok_or_else(|| format!("Unknown resolver '{}'", resolver))?;
181
182 let method_spec = resolver_impl
183 .computed_methods()
184 .iter()
185 .find(|spec| spec.name == method)
186 .ok_or_else(|| {
187 format!(
188 "Resolver '{}' does not provide method '{}'",
189 resolver, method
190 )
191 })?;
192
193 if method_spec.arg_count != args.len() {
194 return Err(format!(
195 "Resolver '{}' method '{}' expects {} args, got {}",
196 resolver,
197 method,
198 method_spec.arg_count,
199 args.len()
200 )
201 .into());
202 }
203
204 resolver_impl.evaluate_computed(method, args)
205 }
206
207 pub fn validate_computed_expr(
208 &self,
209 expr: &crate::ast::ComputedExpr,
210 errors: &mut Vec<String>,
211 ) {
212 match expr {
213 crate::ast::ComputedExpr::ResolverComputed {
214 resolver,
215 method,
216 args,
217 } => {
218 let resolver_impl = self.resolver(resolver);
219 if resolver_impl.is_none() {
220 errors.push(format!("Unknown resolver '{}'", resolver));
221 } else if let Some(resolver_impl) = resolver_impl {
222 let method_spec = resolver_impl
223 .computed_methods()
224 .iter()
225 .find(|spec| spec.name == method);
226 if let Some(method_spec) = method_spec {
227 if method_spec.arg_count != args.len() {
228 errors.push(format!(
229 "Resolver '{}' method '{}' expects {} args, got {}",
230 resolver,
231 method,
232 method_spec.arg_count,
233 args.len()
234 ));
235 }
236 } else {
237 errors.push(format!(
238 "Resolver '{}' does not provide method '{}'",
239 resolver, method
240 ));
241 }
242 }
243
244 for arg in args {
245 self.validate_computed_expr(arg, errors);
246 }
247 }
248 crate::ast::ComputedExpr::FieldRef { .. }
249 | crate::ast::ComputedExpr::Literal { .. }
250 | crate::ast::ComputedExpr::None
251 | crate::ast::ComputedExpr::Var { .. }
252 | crate::ast::ComputedExpr::ByteArray { .. }
253 | crate::ast::ComputedExpr::ContextSlot
254 | crate::ast::ComputedExpr::ContextTimestamp => {}
255 crate::ast::ComputedExpr::UnwrapOr { expr, .. }
256 | crate::ast::ComputedExpr::Cast { expr, .. }
257 | crate::ast::ComputedExpr::Paren { expr }
258 | crate::ast::ComputedExpr::Some { value: expr }
259 | crate::ast::ComputedExpr::Slice { expr, .. }
260 | crate::ast::ComputedExpr::Index { expr, .. }
261 | crate::ast::ComputedExpr::U64FromLeBytes { bytes: expr }
262 | crate::ast::ComputedExpr::U64FromBeBytes { bytes: expr }
263 | crate::ast::ComputedExpr::JsonToBytes { expr }
264 | crate::ast::ComputedExpr::Keccak256 { expr }
265 | crate::ast::ComputedExpr::Unary { expr, .. } => {
266 self.validate_computed_expr(expr, errors);
267 }
268 crate::ast::ComputedExpr::Binary { left, right, .. } => {
269 self.validate_computed_expr(left, errors);
270 self.validate_computed_expr(right, errors);
271 }
272 crate::ast::ComputedExpr::MethodCall { expr, args, .. } => {
273 self.validate_computed_expr(expr, errors);
274 for arg in args {
275 self.validate_computed_expr(arg, errors);
276 }
277 }
278 crate::ast::ComputedExpr::Let { value, body, .. } => {
279 self.validate_computed_expr(value, errors);
280 self.validate_computed_expr(body, errors);
281 }
282 crate::ast::ComputedExpr::If {
283 condition,
284 then_branch,
285 else_branch,
286 } => {
287 self.validate_computed_expr(condition, errors);
288 self.validate_computed_expr(then_branch, errors);
289 self.validate_computed_expr(else_branch, errors);
290 }
291 crate::ast::ComputedExpr::Closure { body, .. } => {
292 self.validate_computed_expr(body, errors);
293 }
294 }
295 }
296}
297
298static BUILTIN_RESOLVER_REGISTRY: OnceLock<ResolverRegistry> = OnceLock::new();
299
300pub fn register_builtin_resolvers(registry: &mut ResolverRegistry) {
301 registry.register(Box::new(SlotHashResolver));
302 registry.register(Box::new(TokenMetadataResolver));
303}
304
305pub fn builtin_resolver_registry() -> &'static ResolverRegistry {
306 BUILTIN_RESOLVER_REGISTRY.get_or_init(|| {
307 let mut registry = ResolverRegistry::new();
308 register_builtin_resolvers(&mut registry);
309 registry
310 })
311}
312
313pub fn evaluate_resolver_computed(
314 resolver: &str,
315 method: &str,
316 args: &[Value],
317) -> std::result::Result<Value, Box<dyn std::error::Error>> {
318 builtin_resolver_registry().evaluate_computed(resolver, method, args)
319}
320
321pub fn validate_resolver_computed_specs(
322 specs: &[crate::ast::ComputedFieldSpec],
323) -> std::result::Result<(), Box<dyn std::error::Error>> {
324 let registry = builtin_resolver_registry();
325 let mut errors = Vec::new();
326
327 for spec in specs {
328 registry.validate_computed_expr(&spec.expression, &mut errors);
329 }
330
331 if errors.is_empty() {
332 Ok(())
333 } else {
334 Err(errors.join("\n").into())
335 }
336}
337
338pub fn is_resolver_output_type(type_name: &str) -> bool {
339 builtin_resolver_registry().is_output_type(type_name)
340}
341
342const DEFAULT_DAS_BATCH_SIZE: usize = 100;
343const DEFAULT_DAS_TIMEOUT_SECS: u64 = 10;
344const DAS_API_ENDPOINT_ENV: &str = "DAS_API_ENDPOINT";
345const DAS_API_BATCH_ENV: &str = "DAS_API_BATCH_SIZE";
346
347pub struct TokenMetadataResolverClient {
348 endpoint: String,
349 client: reqwest::Client,
350 batch_size: usize,
351}
352
353impl TokenMetadataResolverClient {
354 pub fn new(
355 endpoint: String,
356 batch_size: usize,
357 ) -> Result<Self, Box<dyn std::error::Error + Send + Sync>> {
358 let client = reqwest::Client::builder()
359 .timeout(std::time::Duration::from_secs(DEFAULT_DAS_TIMEOUT_SECS))
360 .build()?;
361
362 Ok(Self {
363 endpoint,
364 client,
365 batch_size: batch_size.max(1),
366 })
367 }
368
369 pub fn from_env() -> Result<Option<Self>, Box<dyn std::error::Error + Send + Sync>> {
370 let Some(endpoint) = std::env::var(DAS_API_ENDPOINT_ENV).ok() else {
371 return Ok(None);
372 };
373
374 let batch_size = std::env::var(DAS_API_BATCH_ENV)
375 .ok()
376 .and_then(|value| value.parse::<usize>().ok())
377 .unwrap_or(DEFAULT_DAS_BATCH_SIZE);
378
379 Ok(Some(Self::new(endpoint, batch_size)?))
380 }
381
382 pub async fn resolve_token_metadata(
383 &self,
384 mints: &[String],
385 ) -> Result<HashMap<String, Value>, Box<dyn std::error::Error + Send + Sync>> {
386 let mut unique = HashSet::new();
387 let mut deduped = Vec::new();
388
389 for mint in mints {
390 if mint.is_empty() {
391 continue;
392 }
393 if unique.insert(mint.clone()) {
394 deduped.push(mint.clone());
395 }
396 }
397
398 let mut results = HashMap::new();
399 if deduped.is_empty() {
400 return Ok(results);
401 }
402
403 for chunk in deduped.chunks(self.batch_size) {
404 let assets = self.fetch_assets(chunk).await?;
405 for asset in assets {
406 if let Some((mint, value)) = Self::build_token_metadata(&asset) {
407 results.insert(mint, value);
408 }
409 }
410 }
411
412 Ok(results)
413 }
414
415 async fn fetch_assets(
416 &self,
417 ids: &[String],
418 ) -> Result<Vec<Value>, Box<dyn std::error::Error + Send + Sync>> {
419 let payload = serde_json::json!({
420 "jsonrpc": "2.0",
421 "id": "1",
422 "method": "getAssetBatch",
423 "params": {
424 "ids": ids,
425 "options": {
426 "showFungible": true,
427 },
428 },
429 });
430
431 let response = self
432 .client
433 .post(&self.endpoint)
434 .json(&payload)
435 .send()
436 .await?;
437 let response = response.error_for_status()?;
438 let value = response.json::<Value>().await?;
439
440 if let Some(error) = value.get("error") {
441 return Err(format!("Resolver response error: {}", error).into());
442 }
443
444 let assets = value
445 .get("result")
446 .and_then(|result| match result {
447 Value::Array(items) => Some(items.clone()),
448 Value::Object(obj) => obj.get("items").and_then(|items| items.as_array()).cloned(),
449 _ => None,
450 })
451 .ok_or_else(|| "Resolver response missing result".to_string())?;
452
453 let assets = assets.into_iter().filter(|a| !a.is_null()).collect();
454 Ok(assets)
455 }
456
457 fn build_token_metadata(asset: &Value) -> Option<(String, Value)> {
458 let mint = asset
459 .get("id")
460 .and_then(|value| value.as_str())?
461 .to_string();
462
463 let name = asset
464 .pointer("/content/metadata/name")
465 .and_then(|value| value.as_str());
466
467 let symbol = asset
468 .pointer("/content/metadata/symbol")
469 .and_then(|value| value.as_str());
470
471 let token_info = asset
472 .get("token_info")
473 .or_else(|| asset.pointer("/content/token_info"));
474
475 let decimals = token_info
476 .and_then(|info| info.get("decimals"))
477 .and_then(|value| value.as_u64());
478
479 let logo_uri = asset
480 .pointer("/content/links/image")
481 .and_then(|value| value.as_str())
482 .or_else(|| {
483 asset
484 .pointer("/content/links/image_uri")
485 .and_then(|value| value.as_str())
486 });
487
488 let mut obj = serde_json::Map::new();
489 obj.insert("mint".to_string(), serde_json::json!(mint));
490 obj.insert(
491 "name".to_string(),
492 name.map(|value| serde_json::json!(value))
493 .unwrap_or(Value::Null),
494 );
495 obj.insert(
496 "symbol".to_string(),
497 symbol
498 .map(|value| serde_json::json!(value))
499 .unwrap_or(Value::Null),
500 );
501 obj.insert(
502 "decimals".to_string(),
503 decimals
504 .map(|value| serde_json::json!(value))
505 .unwrap_or(Value::Null),
506 );
507 obj.insert(
508 "logo_uri".to_string(),
509 logo_uri
510 .map(|value| serde_json::json!(value))
511 .unwrap_or(Value::Null),
512 );
513
514 Some((mint, Value::Object(obj)))
515 }
516}
517
518const DEFAULT_URL_TIMEOUT_SECS: u64 = 30;
523
524pub struct UrlResolverClient {
525 client: reqwest::Client,
526}
527
528impl Default for UrlResolverClient {
529 fn default() -> Self {
530 Self::new()
531 }
532}
533
534impl UrlResolverClient {
535 pub fn new() -> Self {
536 let client = reqwest::Client::builder()
537 .timeout(std::time::Duration::from_secs(DEFAULT_URL_TIMEOUT_SECS))
538 .build()
539 .expect("Failed to create HTTP client for URL resolver");
540
541 Self { client }
542 }
543
544 pub fn with_timeout(timeout_secs: u64) -> Self {
545 let client = reqwest::Client::builder()
546 .timeout(std::time::Duration::from_secs(timeout_secs))
547 .build()
548 .expect("Failed to create HTTP client for URL resolver");
549
550 Self { client }
551 }
552
553 pub async fn resolve(
555 &self,
556 url: &str,
557 method: &crate::ast::HttpMethod,
558 ) -> Result<Value, Box<dyn std::error::Error + Send + Sync>> {
559 if url.is_empty() {
560 return Err("URL is empty".into());
561 }
562
563 let response = match method {
564 crate::ast::HttpMethod::Get => self.client.get(url).send().await?,
565 crate::ast::HttpMethod::Post => self.client.post(url).send().await?,
566 };
567
568 let response = response.error_for_status()?;
569 let value = response.json::<Value>().await?;
570
571 Ok(value)
572 }
573
574 pub async fn resolve_with_extract(
576 &self,
577 url: &str,
578 method: &crate::ast::HttpMethod,
579 extract_path: Option<&str>,
580 ) -> Result<Value, Box<dyn std::error::Error + Send + Sync>> {
581 let response = self.resolve(url, method).await?;
582
583 if let Some(path) = extract_path {
584 Self::extract_json_path(&response, path)
585 } else {
586 Ok(response)
587 }
588 }
589
590 pub fn extract_json_path(
593 value: &Value,
594 path: &str,
595 ) -> Result<Value, Box<dyn std::error::Error + Send + Sync>> {
596 if path.is_empty() {
597 return Ok(value.clone());
598 }
599
600 let mut current = value;
601 for segment in path.split('.') {
602 if let Some(next) = current.get(segment) {
604 current = next;
605 } else if let Ok(index) = segment.parse::<usize>() {
606 if let Some(next) = current.get(index) {
608 current = next;
609 } else {
610 return Err(
611 format!("Index '{}' out of bounds in path '{}'", index, path).into(),
612 );
613 }
614 } else {
615 return Err(format!("Key '{}' not found in path '{}'", segment, path).into());
616 }
617 }
618
619 Ok(current.clone())
620 }
621
622 pub async fn resolve_batch(
625 &self,
626 urls: &[(String, crate::ast::HttpMethod)],
627 ) -> HashMap<(String, crate::ast::HttpMethod), Value> {
628 let mut unique: HashMap<(String, crate::ast::HttpMethod), ()> = HashMap::new();
629 for (url, method) in urls {
630 if !url.is_empty() {
631 unique.entry((url.clone(), method.clone())).or_insert(());
632 }
633 }
634
635 let futures = unique.into_keys().map(|(url, method)| async move {
636 let result = self.resolve(&url, &method).await;
637 ((url, method), result)
638 });
639
640 join_all(futures)
641 .await
642 .into_iter()
643 .filter_map(|((url, method), result)| match result {
644 Ok(value) => Some(((url, method), value)),
645 Err(e) => {
646 tracing::warn!(url = %url, error = %e, "Failed to resolve URL");
647 None
648 }
649 })
650 .collect()
651 }
652}
653
654struct SlotHashResolver;
657
658const SLOT_HASH_METHODS: &[ResolverComputedMethod] = &[
659 ResolverComputedMethod {
660 name: "slot_hash",
661 arg_count: 1,
662 },
663 ResolverComputedMethod {
664 name: "keccak_rng",
665 arg_count: 3,
666 },
667];
668
669impl SlotHashResolver {
670 fn evaluate_keccak_rng(args: &[Value]) -> Result<Value, Box<dyn std::error::Error>> {
675 if args.len() != 3 {
676 return Ok(Value::Null);
677 }
678
679 let slot_hash_bytes = match &args[0] {
681 Value::Object(obj) => obj.get("bytes").cloned().unwrap_or(Value::Null),
682 _ => args[0].clone(),
683 };
684 let slot_hash = Self::json_array_to_bytes(&slot_hash_bytes, 32);
685 let seed = Self::json_array_to_bytes(&args[1], 32);
686 let samples = match &args[2] {
687 Value::Number(n) => n.as_u64(),
688 _ => None,
689 };
690
691 let (slot_hash, seed, samples) = match (slot_hash, seed, samples) {
692 (Some(s), Some(sd), Some(sm)) => (s, sd, sm),
693 _ => return Ok(Value::Null),
694 };
695
696 let mut input = Vec::with_capacity(72);
698 input.extend_from_slice(&slot_hash);
699 input.extend_from_slice(&seed);
700 input.extend_from_slice(&samples.to_le_bytes());
701
702 use sha3::{Digest, Keccak256};
704 let hash = Keccak256::digest(&input);
705
706 let r1 = u64::from_le_bytes(hash[0..8].try_into()?);
708 let r2 = u64::from_le_bytes(hash[8..16].try_into()?);
709 let r3 = u64::from_le_bytes(hash[16..24].try_into()?);
710 let r4 = u64::from_le_bytes(hash[24..32].try_into()?);
711 let rng = r1 ^ r2 ^ r3 ^ r4;
712
713 Ok(Value::Number(serde_json::Number::from(rng)))
714 }
715
716 fn json_array_to_bytes(value: &Value, expected_len: usize) -> Option<Vec<u8>> {
718 let arr = value.as_array()?;
719 let bytes: Vec<u8> = arr
720 .iter()
721 .filter_map(|v| v.as_u64().and_then(|n| u8::try_from(n).ok()))
722 .collect();
723 if bytes.len() == expected_len {
724 Some(bytes)
725 } else {
726 tracing::debug!(
727 got = bytes.len(),
728 expected = expected_len,
729 "json_array_to_bytes: length mismatch or out-of-range element"
730 );
731 None
732 }
733 }
734
735 fn evaluate_slot_hash(args: &[Value]) -> Result<Value, Box<dyn std::error::Error>> {
736 if args.len() != 1 {
737 return Ok(Value::Null);
738 }
739
740 let slot = match &args[0] {
741 Value::Number(n) => n.as_u64().unwrap_or(0),
742 _ => return Ok(Value::Null),
743 };
744
745 if slot == 0 {
746 return Ok(Value::Null);
747 }
748
749 let slot_hash = crate::slot_hash_cache::get_slot_hash(slot);
751
752 match slot_hash {
753 Some(hash) => {
754 match bs58::decode(&hash).into_vec() {
757 Ok(bytes) if bytes.len() == 32 => {
758 let json_bytes: Vec<Value> =
760 bytes.into_iter().map(|b| Value::Number(b.into())).collect();
761 let mut obj = serde_json::Map::new();
762 obj.insert("bytes".to_string(), Value::Array(json_bytes));
763 Ok(Value::Object(obj))
764 }
765 _ => {
766 tracing::warn!(slot = slot, hash = hash, "Failed to decode slot hash");
767 Ok(Value::Null)
768 }
769 }
770 }
771 None => {
772 tracing::debug!(slot = slot, "Slot hash not found in cache");
773 Ok(Value::Null)
774 }
775 }
776 }
777}
778
779impl ResolverDefinition for SlotHashResolver {
780 fn name(&self) -> &'static str {
781 "SlotHash"
782 }
783
784 fn output_type(&self) -> &'static str {
785 "SlotHash"
786 }
787
788 fn computed_methods(&self) -> &'static [ResolverComputedMethod] {
789 SLOT_HASH_METHODS
790 }
791
792 fn evaluate_computed(
793 &self,
794 method: &str,
795 args: &[Value],
796 ) -> std::result::Result<Value, Box<dyn std::error::Error>> {
797 match method {
798 "slot_hash" => Self::evaluate_slot_hash(args),
799 "keccak_rng" => Self::evaluate_keccak_rng(args),
800 _ => Err(format!("Unknown SlotHash method '{}'", method).into()),
801 }
802 }
803
804 fn typescript_interface(&self) -> Option<&'static str> {
805 Some(
806 r#"export interface SlotHashBytes {
807 /** 32-byte slot hash as array of numbers (0-255) */
808 bytes: number[];
809}
810
811export type KeccakRngValue = string;"#,
812 )
813 }
814
815 fn extra_output_types(&self) -> &'static [&'static str] {
816 &["SlotHashBytes", "KeccakRngValue"]
817 }
818
819 fn typescript_schema(&self) -> Option<ResolverTypeScriptSchema> {
820 Some(ResolverTypeScriptSchema {
821 name: "SlotHashTypes",
822 definition: r#"export const SlotHashBytesSchema = z.object({
823 bytes: z.array(z.number().int().min(0).max(255)).length(32),
824});
825
826export const KeccakRngValueSchema = z.string();"#,
827 })
828 }
829}
830
831struct TokenMetadataResolver;
832
833const TOKEN_METADATA_METHODS: &[ResolverComputedMethod] = &[
834 ResolverComputedMethod {
835 name: "ui_amount",
836 arg_count: 2,
837 },
838 ResolverComputedMethod {
839 name: "raw_amount",
840 arg_count: 2,
841 },
842];
843
844impl TokenMetadataResolver {
845 fn optional_f64(value: &Value) -> Option<f64> {
846 if value.is_null() {
847 return None;
848 }
849 match value {
850 Value::Number(number) => number.as_f64(),
851 Value::String(text) => text.parse::<f64>().ok(),
852 _ => None,
853 }
854 }
855
856 fn optional_u8(value: &Value) -> Option<u8> {
857 if value.is_null() {
858 return None;
859 }
860 match value {
861 Value::Number(number) => number
862 .as_u64()
863 .or_else(|| {
864 number
865 .as_i64()
866 .and_then(|v| if v >= 0 { Some(v as u64) } else { None })
867 })
868 .and_then(|v| u8::try_from(v).ok()),
869 Value::String(text) => text.parse::<u8>().ok(),
870 _ => None,
871 }
872 }
873
874 fn evaluate_ui_amount(
875 args: &[Value],
876 ) -> std::result::Result<Value, Box<dyn std::error::Error>> {
877 let raw_value = Self::optional_f64(&args[0]);
878 let decimals = Self::optional_u8(&args[1]);
879
880 match (raw_value, decimals) {
881 (Some(value), Some(decimals)) => {
882 let factor = 10_f64.powi(decimals as i32);
883 let result = value / factor;
884 if result.is_finite() {
885 serde_json::Number::from_f64(result)
886 .map(Value::Number)
887 .ok_or_else(|| "Failed to serialize ui_amount".into())
888 } else {
889 Err("ui_amount result is not finite".into())
890 }
891 }
892 _ => Ok(Value::Null),
893 }
894 }
895
896 fn evaluate_raw_amount(
897 args: &[Value],
898 ) -> std::result::Result<Value, Box<dyn std::error::Error>> {
899 let ui_value = Self::optional_f64(&args[0]);
900 let decimals = Self::optional_u8(&args[1]);
901
902 match (ui_value, decimals) {
903 (Some(value), Some(decimals)) => {
904 let factor = 10_f64.powi(decimals as i32);
905 let result = value * factor;
906 if !result.is_finite() || result < 0.0 {
907 return Err("raw_amount result is not finite".into());
908 }
909 let rounded = result.round();
910 if rounded > u64::MAX as f64 {
911 return Err("raw_amount result exceeds u64".into());
912 }
913 Ok(Value::Number(serde_json::Number::from(rounded as u64)))
914 }
915 _ => Ok(Value::Null),
916 }
917 }
918}
919
920impl ResolverDefinition for TokenMetadataResolver {
921 fn name(&self) -> &'static str {
922 "TokenMetadata"
923 }
924
925 fn output_type(&self) -> &'static str {
926 "TokenMetadata"
927 }
928
929 fn computed_methods(&self) -> &'static [ResolverComputedMethod] {
930 TOKEN_METADATA_METHODS
931 }
932
933 fn evaluate_computed(
934 &self,
935 method: &str,
936 args: &[Value],
937 ) -> std::result::Result<Value, Box<dyn std::error::Error>> {
938 match method {
939 "ui_amount" => Self::evaluate_ui_amount(args),
940 "raw_amount" => Self::evaluate_raw_amount(args),
941 _ => Err(format!("Unknown TokenMetadata method '{}'", method).into()),
942 }
943 }
944
945 fn typescript_interface(&self) -> Option<&'static str> {
946 Some(
947 r#"export interface TokenMetadata {
948 mint: string;
949 name?: string | null;
950 symbol?: string | null;
951 decimals?: number | null;
952 logoUri?: string | null;
953}"#,
954 )
955 }
956
957 fn typescript_schema(&self) -> Option<ResolverTypeScriptSchema> {
958 Some(ResolverTypeScriptSchema {
959 name: "TokenMetadataSchema",
960 definition: r#"export const TokenMetadataSchema = z.object({
961 mint: z.string(),
962 name: z.string().nullable().optional(),
963 symbol: z.string().nullable().optional(),
964 decimals: z.number().nullable().optional(),
965 logo_uri: z.string().nullable().optional(),
966}).transform((value) => ({
967 mint: value.mint,
968 ...(value.name !== undefined ? { name: value.name } : {}),
969 ...(value.symbol !== undefined ? { symbol: value.symbol } : {}),
970 ...(value.decimals !== undefined ? { decimals: value.decimals } : {}),
971 ...(value.logo_uri !== undefined ? { logoUri: value.logo_uri } : {}),
972}));
973
974export const TokenMetadataPatchSchema = z.object({
975 mint: z.string().optional(),
976 name: z.string().nullable().optional(),
977 symbol: z.string().nullable().optional(),
978 decimals: z.number().nullable().optional(),
979 logo_uri: z.string().nullable().optional(),
980}).transform((value) => ({
981 ...(value.mint !== undefined ? { mint: value.mint } : {}),
982 ...(value.name !== undefined ? { name: value.name } : {}),
983 ...(value.symbol !== undefined ? { symbol: value.symbol } : {}),
984 ...(value.decimals !== undefined ? { decimals: value.decimals } : {}),
985 ...(value.logo_uri !== undefined ? { logoUri: value.logo_uri } : {}),
986}));"#,
987 })
988 }
989}
990
991impl<'a> InstructionContext<'a> {
992 pub fn new(
993 accounts: HashMap<String, String>,
994 state_id: u32,
995 reverse_lookup_tx: &'a mut dyn ReverseLookupUpdater,
996 ) -> Self {
997 Self {
998 accounts,
999 state_id,
1000 reverse_lookup_tx: reverse_lookup_tx as *mut (dyn ReverseLookupUpdater + 'a),
1001 pending_updates: Vec::new(),
1002 registers: None,
1003 state_reg: None,
1004 compiled_paths: None,
1005 instruction_data: None,
1006 slot: None,
1007 signature: None,
1008 timestamp: None,
1009 dirty_tracker: crate::vm::DirtyTracker::new(),
1010 _borrow: PhantomData,
1011 }
1012 }
1013
1014 #[allow(clippy::too_many_arguments)]
1015 pub fn with_metrics(
1016 accounts: HashMap<String, String>,
1017 state_id: u32,
1018 vm: &'a mut crate::vm::VmContext,
1019 state_reg: crate::vm::Register,
1020 instruction_data: &'a serde_json::Value,
1021 slot: Option<u64>,
1022 signature: Option<String>,
1023 timestamp: i64,
1024 ) -> Self {
1025 let reverse_lookup_tx =
1028 vm as *mut crate::vm::VmContext as *mut (dyn ReverseLookupUpdater + 'a);
1029 let registers = vm.registers_mut() as *mut Vec<crate::vm::RegisterValue>;
1030 let compiled_paths =
1031 vm.path_cache() as *const HashMap<String, crate::metrics_context::CompiledPath>;
1032
1033 Self {
1034 accounts,
1035 state_id,
1036 reverse_lookup_tx,
1037 pending_updates: Vec::new(),
1038 registers: Some(registers),
1039 state_reg: Some(state_reg),
1040 compiled_paths: Some(compiled_paths),
1041 instruction_data: Some(instruction_data),
1042 slot,
1043 signature,
1044 timestamp: Some(timestamp),
1045 dirty_tracker: crate::vm::DirtyTracker::new(),
1046 _borrow: PhantomData,
1047 }
1048 }
1049
1050 fn registers(&self) -> Option<&Vec<crate::vm::RegisterValue>> {
1051 self.registers.map(|registers| unsafe { &*registers })
1052 }
1053
1054 fn registers_mut(&mut self) -> Option<&mut Vec<crate::vm::RegisterValue>> {
1055 self.registers.map(|registers| unsafe { &mut *registers })
1056 }
1057
1058 pub fn account(&self, name: &str) -> Option<String> {
1060 self.accounts.get(name).cloned()
1061 }
1062
1063 pub fn register_pda_reverse_lookup(&mut self, pda_address: &str, seed_value: &str) {
1069 let pending = unsafe {
1070 (&mut *self.reverse_lookup_tx).update(pda_address.to_string(), seed_value.to_string())
1071 };
1072 self.pending_updates.extend(pending);
1073 }
1074
1075 pub fn take_pending_updates(&mut self) -> Vec<crate::vm::PendingAccountUpdate> {
1080 std::mem::take(&mut self.pending_updates)
1081 }
1082
1083 pub fn dirty_tracker(&self) -> &crate::vm::DirtyTracker {
1084 &self.dirty_tracker
1085 }
1086
1087 pub fn dirty_tracker_mut(&mut self) -> &mut crate::vm::DirtyTracker {
1088 &mut self.dirty_tracker
1089 }
1090
1091 pub fn state_value(&self) -> Option<&serde_json::Value> {
1093 let state_reg = self.state_reg?;
1094 let registers = self.registers()?;
1095 Some(®isters[state_reg])
1096 }
1097
1098 pub fn get<T: serde::de::DeserializeOwned>(&self, field_path: &str) -> Option<T> {
1101 let state_reg = self.state_reg?;
1102 let registers = self.registers()?;
1103 let state = ®isters[state_reg];
1104 self.get_nested_value(state, field_path)
1105 .and_then(|v| serde_json::from_value(v.clone()).ok())
1106 }
1107
1108 pub fn set<T: serde::Serialize>(&mut self, field_path: &str, value: T) {
1109 let Some(state_reg) = self.state_reg else {
1110 println!(" ⚠️ Cannot set field '{}': metrics not configured (registers={}, state_reg={:?})",
1111 field_path, self.registers.is_some(), self.state_reg);
1112 return;
1113 };
1114
1115 if let Some(registers) = self.registers_mut() {
1116 let serialized = serde_json::to_value(value).ok();
1117 if let Some(val) = serialized {
1118 Self::set_nested_value_static(&mut registers[state_reg], field_path, val);
1119 self.dirty_tracker.mark_replaced(field_path);
1120 println!(" ✓ Set field '{}' and marked as dirty", field_path);
1121 }
1122 } else {
1123 println!(" ⚠️ Cannot set field '{}': metrics not configured (registers={}, state_reg={:?})",
1124 field_path, self.registers.is_some(), self.state_reg);
1125 }
1126 }
1127
1128 pub fn increment(&mut self, field_path: &str, amount: i64) {
1129 let current = self.get::<i64>(field_path).unwrap_or(0);
1130 self.set(field_path, current + amount);
1131 }
1132
1133 pub fn append<T: serde::Serialize>(&mut self, field_path: &str, value: T) {
1134 let Some(state_reg) = self.state_reg else {
1135 println!(
1136 " ⚠️ Cannot append to '{}': metrics not configured",
1137 field_path
1138 );
1139 return;
1140 };
1141
1142 if let Some(registers) = self.registers_mut() {
1143 let serialized = serde_json::to_value(&value).ok();
1144 if let Some(val) = serialized {
1145 Self::append_to_array_static(&mut registers[state_reg], field_path, val.clone());
1146 self.dirty_tracker.mark_appended(field_path, val);
1147 println!(
1148 " ✓ Appended to '{}' and marked as appended",
1149 field_path
1150 );
1151 }
1152 } else {
1153 println!(
1154 " ⚠️ Cannot append to '{}': metrics not configured",
1155 field_path
1156 );
1157 }
1158 }
1159
1160 fn append_to_array_static(
1161 value: &mut serde_json::Value,
1162 path: &str,
1163 new_value: serde_json::Value,
1164 ) {
1165 let segments: Vec<&str> = path.split('.').collect();
1166 if segments.is_empty() {
1167 return;
1168 }
1169
1170 let mut current = value;
1171 for segment in &segments[..segments.len() - 1] {
1172 if !current.is_object() {
1173 *current = serde_json::json!({});
1174 }
1175 let obj = current.as_object_mut().unwrap();
1176 current = obj
1177 .entry(segment.to_string())
1178 .or_insert(serde_json::json!({}));
1179 }
1180
1181 let last_segment = segments[segments.len() - 1];
1182 if !current.is_object() {
1183 *current = serde_json::json!({});
1184 }
1185 let obj = current.as_object_mut().unwrap();
1186 let arr = obj
1187 .entry(last_segment.to_string())
1188 .or_insert_with(|| serde_json::json!([]));
1189 if let Some(arr) = arr.as_array_mut() {
1190 arr.push(new_value);
1191 }
1192 }
1193
1194 fn get_nested_value<'b>(
1195 &self,
1196 value: &'b serde_json::Value,
1197 path: &str,
1198 ) -> Option<&'b serde_json::Value> {
1199 let mut current = value;
1200 for segment in path.split('.') {
1201 current = current.get(segment)?;
1202 }
1203 Some(current)
1204 }
1205
1206 fn set_nested_value_static(
1207 value: &mut serde_json::Value,
1208 path: &str,
1209 new_value: serde_json::Value,
1210 ) {
1211 let segments: Vec<&str> = path.split('.').collect();
1212 if segments.is_empty() {
1213 return;
1214 }
1215
1216 let mut current = value;
1217 for segment in &segments[..segments.len() - 1] {
1218 if !current.is_object() {
1219 *current = serde_json::json!({});
1220 }
1221 let obj = current.as_object_mut().unwrap();
1222 current = obj
1223 .entry(segment.to_string())
1224 .or_insert(serde_json::json!({}));
1225 }
1226
1227 if !current.is_object() {
1228 *current = serde_json::json!({});
1229 }
1230 if let Some(obj) = current.as_object_mut() {
1231 obj.insert(segments[segments.len() - 1].to_string(), new_value);
1232 }
1233 }
1234
1235 pub fn data<T: serde::de::DeserializeOwned>(&self, field: &str) -> Option<T> {
1237 self.instruction_data
1238 .and_then(|data| data.get(field))
1239 .and_then(|v| serde_json::from_value(v.clone()).ok())
1240 }
1241
1242 pub fn timestamp(&self) -> i64 {
1244 self.timestamp.unwrap_or(0)
1245 }
1246
1247 pub fn slot(&self) -> Option<u64> {
1249 self.slot
1250 }
1251
1252 pub fn signature(&self) -> Option<&str> {
1254 self.signature.as_deref()
1255 }
1256}