1use std::str::FromStr;
6
7use async_graphql::{connection::Connection, *};
8use fastcrypto::encoding::{Base64, Encoding};
9use iota_json_rpc_types::DevInspectArgs;
10use iota_sdk::IotaClient;
11use iota_types::{
12 TypeTag,
13 gas_coin::GAS,
14 transaction::{TransactionData, TransactionDataAPI, TransactionKind},
15};
16use move_core_types::account_address::AccountAddress;
17use serde::de::DeserializeOwned;
18
19use crate::{
20 config::ServiceConfig,
21 connection::ScanConnection,
22 error::Error,
23 mutation::Mutation,
24 server::watermark_task::Watermark,
25 types::{
26 address::Address,
27 available_range::AvailableRange,
28 base64::Base64 as GraphQLBase64,
29 chain_identifier::ChainIdentifier,
30 checkpoint::{self, Checkpoint, CheckpointId},
31 coin::Coin,
32 coin_metadata::CoinMetadata,
33 cursor::Page,
34 digest::Digest,
35 dry_run_result::DryRunResult,
36 epoch::Epoch,
37 event::{self, Event, EventFilter},
38 iota_address::IotaAddress,
39 iota_names_registration::{Domain, IotaNames},
40 move_package::{self, MovePackage, MovePackageCheckpointFilter, MovePackageVersionFilter},
41 move_type::MoveType,
42 object::{self, Object, ObjectFilter},
43 owner::Owner,
44 protocol_config::ProtocolConfigs,
45 transaction_block::{self, TransactionBlock, TransactionBlockFilter},
46 transaction_metadata::TransactionMetadata,
47 type_filter::ExactTypeFilter,
48 uint53::UInt53,
49 zklogin_verify_signature::{
50 ZkLoginIntentScope, ZkLoginVerifyResult, verify_zklogin_signature,
51 },
52 },
53};
54
55pub(crate) struct Query;
56pub(crate) type IotaGraphQLSchema = async_graphql::Schema<Query, Mutation, EmptySubscription>;
57
58#[Object]
59impl Query {
60 async fn chain_identifier(&self, ctx: &Context<'_>) -> Result<String> {
63 Ok(ChainIdentifier::query(ctx.data_unchecked())
64 .await
65 .extend()?
66 .to_string())
67 }
68
69 async fn available_range(&self, ctx: &Context<'_>) -> Result<AvailableRange> {
72 let Watermark { checkpoint, .. } = *ctx.data()?;
73 AvailableRange::query(ctx.data_unchecked(), checkpoint)
74 .await
75 .extend()
76 }
77
78 async fn service_config(&self, ctx: &Context<'_>) -> Result<ServiceConfig> {
80 ctx.data()
81 .map_err(|_| Error::Internal("Unable to fetch service configuration.".to_string()))
82 .cloned()
83 .extend()
84 }
85
86 async fn dry_run_transaction_block(
104 &self,
105 ctx: &Context<'_>,
106 tx_bytes: String,
107 tx_meta: Option<TransactionMetadata>,
108 skip_checks: Option<bool>,
109 ) -> Result<DryRunResult> {
110 let skip_checks = skip_checks.unwrap_or(false);
111
112 let iota_sdk_client: &Option<IotaClient> = ctx
113 .data()
114 .map_err(|_| Error::Internal("Unable to fetch IOTA SDK client".to_string()))
115 .extend()?;
116 let iota_sdk_client = iota_sdk_client
117 .as_ref()
118 .ok_or_else(|| Error::Internal("IOTA SDK client not initialized".to_string()))
119 .extend()?;
120
121 let (sender_address, tx_kind, gas_price, gas_sponsor, gas_budget, gas_objects) =
122 if let Some(TransactionMetadata {
123 sender,
124 gas_price,
125 gas_objects,
126 gas_budget,
127 gas_sponsor,
128 }) = tx_meta
129 {
130 let tx_kind = deserialize_tx_data::<TransactionKind>(&tx_bytes)?;
132
133 let sender_address = sender.unwrap_or_else(|| AccountAddress::ZERO.into()).into();
135
136 let gas_sponsor = gas_sponsor.map(|addr| addr.into());
137
138 let gas_objects = gas_objects.map(|objs| {
139 objs.into_iter()
140 .map(|obj| (obj.address.into(), obj.version.into(), obj.digest.into()))
141 .collect()
142 });
143
144 (
145 sender_address,
146 tx_kind,
147 gas_price.map(|p| p.into()),
148 gas_sponsor,
149 gas_budget.map(|b| b.into()),
150 gas_objects,
151 )
152 } else {
153 let tx_data = deserialize_tx_data::<TransactionData>(&tx_bytes)?;
155
156 (
157 tx_data.sender(),
158 tx_data.clone().into_kind(),
159 Some(tx_data.gas_price().into()),
160 Some(tx_data.gas_owner()),
161 Some(tx_data.gas_budget().into()),
162 Some(tx_data.gas().to_vec()),
163 )
164 };
165
166 let dev_inspect_args = DevInspectArgs {
167 gas_sponsor,
168 gas_budget,
169 gas_objects,
170 show_raw_txn_data_and_effects: Some(true),
171 skip_checks: Some(skip_checks),
172 };
173
174 let res = iota_sdk_client
175 .read_api()
176 .dev_inspect_transaction_block(
177 sender_address,
178 tx_kind,
179 gas_price,
180 None,
181 Some(dev_inspect_args),
182 )
183 .await?;
184
185 DryRunResult::try_from(res).extend()
186 }
187
188 async fn owner(
208 &self,
209 ctx: &Context<'_>,
210 address: IotaAddress,
211 root_version: Option<UInt53>,
212 ) -> Result<Option<Owner>> {
213 let Watermark { checkpoint, .. } = *ctx.data()?;
214 Ok(Some(Owner {
215 address,
216 checkpoint_viewed_at: checkpoint,
217 root_version: root_version.map(|v| v.into()),
218 }))
219 }
220
221 async fn object(
224 &self,
225 ctx: &Context<'_>,
226 address: IotaAddress,
227 version: Option<UInt53>,
228 ) -> Result<Option<Object>> {
229 let Watermark { checkpoint, .. } = *ctx.data()?;
230 let key = match version {
231 Some(version) => Object::at_version(version.into(), checkpoint),
232 None => Object::latest_at(checkpoint),
233 };
234
235 Object::query(ctx, address, key).await.extend()
236 }
237
238 async fn package(
253 &self,
254 ctx: &Context<'_>,
255 address: IotaAddress,
256 version: Option<UInt53>,
257 ) -> Result<Option<MovePackage>> {
258 let Watermark { checkpoint, .. } = *ctx.data()?;
259 let key = match version {
260 Some(version) => MovePackage::by_version(version.into(), checkpoint),
261 None => MovePackage::by_id_at(checkpoint),
262 };
263
264 MovePackage::query(ctx, address, key).await.extend()
265 }
266
267 async fn latest_package(
272 &self,
273 ctx: &Context<'_>,
274 address: IotaAddress,
275 ) -> Result<Option<MovePackage>> {
276 let Watermark { checkpoint, .. } = *ctx.data()?;
277 MovePackage::query(ctx, address, MovePackage::latest_at(checkpoint))
278 .await
279 .extend()
280 }
281
282 async fn address(&self, ctx: &Context<'_>, address: IotaAddress) -> Result<Option<Address>> {
284 let Watermark { checkpoint, .. } = *ctx.data()?;
285
286 Ok(Some(Address {
287 address,
288 checkpoint_viewed_at: checkpoint,
289 }))
290 }
291
292 async fn type_(&self, type_: String) -> Result<MoveType> {
295 Ok(MoveType::new(
296 TypeTag::from_str(&type_)
297 .map_err(|e| Error::Client(format!("Bad type: {e}")))
298 .extend()?,
299 ))
300 }
301
302 async fn epoch(&self, ctx: &Context<'_>, id: Option<UInt53>) -> Result<Option<Epoch>> {
304 let Watermark { checkpoint, .. } = *ctx.data()?;
305 Epoch::query(ctx, id.map(|id| id.into()), checkpoint)
306 .await
307 .extend()
308 }
309
310 async fn checkpoint(
313 &self,
314 ctx: &Context<'_>,
315 id: Option<CheckpointId>,
316 ) -> Result<Option<Checkpoint>> {
317 let Watermark { checkpoint, .. } = *ctx.data()?;
318 Checkpoint::query(ctx, id.unwrap_or_default(), checkpoint)
319 .await
320 .extend()
321 }
322
323 async fn transaction_block(
325 &self,
326 ctx: &Context<'_>,
327 digest: Digest,
328 ) -> Result<Option<TransactionBlock>> {
329 let Watermark { checkpoint, .. } = *ctx.data()?;
330 TransactionBlock::query(ctx, digest, checkpoint)
331 .await
332 .extend()
333 }
334
335 async fn coins(
341 &self,
342 ctx: &Context<'_>,
343 first: Option<u64>,
344 after: Option<object::Cursor>,
345 last: Option<u64>,
346 before: Option<object::Cursor>,
347 type_: Option<ExactTypeFilter>,
348 ) -> Result<Connection<String, Coin>> {
349 let Watermark { checkpoint, .. } = *ctx.data()?;
350
351 let page = Page::from_params(ctx.data_unchecked(), first, after, last, before)?;
352 let coin = type_.map_or_else(GAS::type_tag, |t| t.0);
353 Coin::paginate(
354 ctx.data_unchecked(),
355 page,
356 coin,
357 None,
359 checkpoint,
360 )
361 .await
362 .extend()
363 }
364
365 async fn checkpoints(
367 &self,
368 ctx: &Context<'_>,
369 first: Option<u64>,
370 after: Option<checkpoint::Cursor>,
371 last: Option<u64>,
372 before: Option<checkpoint::Cursor>,
373 ) -> Result<Connection<String, Checkpoint>> {
374 let Watermark { checkpoint, .. } = *ctx.data()?;
375
376 let page = Page::from_params(ctx.data_unchecked(), first, after, last, before)?;
377 Checkpoint::paginate(
378 ctx.data_unchecked(),
379 page,
380 None,
382 checkpoint,
383 )
384 .await
385 .extend()
386 }
387
388 async fn transaction_blocks(
414 &self,
415 ctx: &Context<'_>,
416 first: Option<u64>,
417 after: Option<transaction_block::Cursor>,
418 last: Option<u64>,
419 before: Option<transaction_block::Cursor>,
420 filter: Option<TransactionBlockFilter>,
421 scan_limit: Option<u64>,
422 ) -> Result<ScanConnection<String, TransactionBlock>> {
423 let Watermark { checkpoint, .. } = *ctx.data()?;
424
425 let page = Page::from_params(ctx.data_unchecked(), first, after, last, before)?;
426
427 TransactionBlock::paginate(
428 ctx,
429 page,
430 filter.unwrap_or_default(),
431 checkpoint,
432 scan_limit,
433 )
434 .await
435 .extend()
436 }
437
438 async fn events(
443 &self,
444 ctx: &Context<'_>,
445 first: Option<u64>,
446 after: Option<event::Cursor>,
447 last: Option<u64>,
448 before: Option<event::Cursor>,
449 filter: Option<EventFilter>,
450 ) -> Result<Connection<String, Event>> {
451 let Watermark { checkpoint, .. } = *ctx.data()?;
452
453 let page = Page::from_params(ctx.data_unchecked(), first, after, last, before)?;
454 Event::paginate(
455 ctx.data_unchecked(),
456 page,
457 filter.unwrap_or_default(),
458 checkpoint,
459 )
460 .await
461 .extend()
462 }
463
464 async fn objects(
466 &self,
467 ctx: &Context<'_>,
468 first: Option<u64>,
469 after: Option<object::Cursor>,
470 last: Option<u64>,
471 before: Option<object::Cursor>,
472 filter: Option<ObjectFilter>,
473 ) -> Result<Connection<String, Object>> {
474 let Watermark { checkpoint, .. } = *ctx.data()?;
475
476 let page = Page::from_params(ctx.data_unchecked(), first, after, last, before)?;
477 Object::paginate(
478 ctx.data_unchecked(),
479 page,
480 filter.unwrap_or_default(),
481 checkpoint,
482 )
483 .await
484 .extend()
485 }
486
487 async fn packages(
495 &self,
496 ctx: &Context<'_>,
497 first: Option<u64>,
498 after: Option<move_package::Cursor>,
499 last: Option<u64>,
500 before: Option<move_package::Cursor>,
501 filter: Option<MovePackageCheckpointFilter>,
502 ) -> Result<Connection<String, MovePackage>> {
503 let Watermark { checkpoint, .. } = *ctx.data()?;
504
505 let page = Page::from_params(ctx.data_unchecked(), first, after, last, before)?;
506 MovePackage::paginate_by_checkpoint(ctx.data_unchecked(), page, filter, checkpoint)
507 .await
508 .extend()
509 }
510
511 async fn package_versions(
515 &self,
516 ctx: &Context<'_>,
517 first: Option<u64>,
518 after: Option<move_package::Cursor>,
519 last: Option<u64>,
520 before: Option<move_package::Cursor>,
521 address: IotaAddress,
522 filter: Option<MovePackageVersionFilter>,
523 ) -> Result<Connection<String, MovePackage>> {
524 let Watermark { checkpoint, .. } = *ctx.data()?;
525
526 let page = Page::from_params(ctx.data_unchecked(), first, after, last, before)?;
527 MovePackage::paginate_by_version(ctx.data_unchecked(), page, address, filter, checkpoint)
528 .await
529 .extend()
530 }
531
532 async fn protocol_config(
535 &self,
536 ctx: &Context<'_>,
537 protocol_version: Option<UInt53>,
538 ) -> Result<ProtocolConfigs> {
539 ProtocolConfigs::query(ctx.data_unchecked(), protocol_version.map(|v| v.into()))
540 .await
541 .extend()
542 }
543
544 async fn resolve_iota_names_address(
547 &self,
548 ctx: &Context<'_>,
549 domain: Domain,
550 ) -> Result<Option<Address>> {
551 let Watermark { checkpoint, .. } = *ctx.data()?;
552
553 Ok(IotaNames::resolve_to_record(ctx, &domain, checkpoint)
554 .await
555 .extend()?
556 .and_then(|r| r.target_address)
557 .map(|a| Address {
558 address: a.into(),
559 checkpoint_viewed_at: checkpoint,
560 }))
561 }
562
563 async fn coin_metadata(
565 &self,
566 ctx: &Context<'_>,
567 coin_type: ExactTypeFilter,
568 ) -> Result<Option<CoinMetadata>> {
569 let Watermark { checkpoint, .. } = *ctx.data()?;
570 CoinMetadata::query(ctx.data_unchecked(), coin_type.0, checkpoint)
571 .await
572 .extend()
573 }
574
575 async fn verify_zklogin_signature(
590 &self,
591 ctx: &Context<'_>,
592 bytes: GraphQLBase64,
593 signature: GraphQLBase64,
594 intent_scope: ZkLoginIntentScope,
595 author: IotaAddress,
596 ) -> Result<ZkLoginVerifyResult> {
597 verify_zklogin_signature(ctx, bytes, signature, intent_scope, author)
598 .await
599 .extend()
600 }
601}
602
603fn deserialize_tx_data<T>(tx_bytes: &str) -> Result<T>
604where
605 T: DeserializeOwned,
606{
607 bcs::from_bytes(
608 &Base64::decode(tx_bytes)
609 .map_err(|e| {
610 Error::Client(format!(
611 "Unable to deserialize transaction bytes from Base64: {e}"
612 ))
613 })
614 .extend()?,
615 )
616 .map_err(|e| {
617 Error::Client(format!(
618 "Unable to deserialize transaction bytes as BCS: {e}"
619 ))
620 })
621 .extend()
622}