1use async_graphql::{connection::Connection, *};
6use iota_names::config::IotaNamesConfig;
7use iota_sdk_types::{StructTag, TypeTag};
8use iota_types::dynamic_field::DynamicFieldType;
9
10use crate::{
11 data::Db,
12 types::{
13 address::Address,
14 balance::{self, Balance},
15 coin::Coin,
16 coin_metadata::CoinMetadata,
17 cursor::Page,
18 dynamic_field::{DynamicField, DynamicFieldName},
19 iota_address::IotaAddress,
20 iota_names_registration::{IotaNames, NameFormat, NameRegistration},
21 move_object::MoveObject,
22 move_package::MovePackage,
23 object::{self, Object, ObjectFilter},
24 stake::StakedIota,
25 type_filter::ExactTypeFilter,
26 },
27};
28
29#[derive(Clone, Debug)]
30pub(crate) struct Owner {
31 pub address: IotaAddress,
32 pub checkpoint_viewed_at: u64,
34 pub root_version: Option<u64>,
51}
52
53pub(crate) struct OwnerImpl {
55 pub address: IotaAddress,
56 pub checkpoint_viewed_at: u64,
58}
59
60#[expect(clippy::duplicated_attributes)]
66#[derive(Interface)]
67#[graphql(
68 name = "IOwner",
69 field(name = "address", ty = "IotaAddress"),
70 field(
71 name = "objects",
72 arg(name = "first", ty = "Option<u64>"),
73 arg(name = "after", ty = "Option<object::Cursor>"),
74 arg(name = "last", ty = "Option<u64>"),
75 arg(name = "before", ty = "Option<object::Cursor>"),
76 arg(name = "filter", ty = "Option<ObjectFilter>"),
77 ty = "Connection<String, MoveObject>",
78 desc = "Objects owned by this object or address, optionally `filter`-ed."
79 ),
80 field(
81 name = "balance",
82 arg(name = "type", ty = "Option<ExactTypeFilter>"),
83 ty = "Option<Balance>",
84 desc = "Total balance of all coins with marker type owned by this object or address. If \
85 type is not supplied, it defaults to `0x2::iota::IOTA`."
86 ),
87 field(
88 name = "balances",
89 arg(name = "first", ty = "Option<u64>"),
90 arg(name = "after", ty = "Option<balance::Cursor>"),
91 arg(name = "last", ty = "Option<u64>"),
92 arg(name = "before", ty = "Option<balance::Cursor>"),
93 ty = "Connection<String, Balance>",
94 desc = "The balances of all coin types owned by this object or address."
95 ),
96 field(
97 name = "coins",
98 arg(name = "first", ty = "Option<u64>"),
99 arg(name = "after", ty = "Option<object::Cursor>"),
100 arg(name = "last", ty = "Option<u64>"),
101 arg(name = "before", ty = "Option<object::Cursor>"),
102 arg(name = "type", ty = "Option<ExactTypeFilter>"),
103 ty = "Connection<String, Coin>",
104 desc = "The coin objects for this object or address.\n\n\
105 `type` is a filter on the coin's type parameter, defaulting to `0x2::iota::IOTA`."
106 ),
107 field(
108 name = "staked_iotas",
109 arg(name = "first", ty = "Option<u64>"),
110 arg(name = "after", ty = "Option<object::Cursor>"),
111 arg(name = "last", ty = "Option<u64>"),
112 arg(name = "before", ty = "Option<object::Cursor>"),
113 ty = "Connection<String, StakedIota>",
114 desc = "The `0x3::staking_pool::StakedIota` objects owned by this object or address."
115 ),
116 field(
117 name = "iota_names_default_name",
118 arg(name = "format", ty = "Option<NameFormat>"),
119 ty = "Option<String>",
120 desc = "The name explicitly configured as the default name pointing to this object or \
121 address."
122 ),
123 field(
124 name = "iota_names_registrations",
125 arg(name = "first", ty = "Option<u64>"),
126 arg(name = "after", ty = "Option<object::Cursor>"),
127 arg(name = "last", ty = "Option<u64>"),
128 arg(name = "before", ty = "Option<object::Cursor>"),
129 ty = "Connection<String, NameRegistration>",
130 desc = "The NameRegistration NFTs owned by this object or address. These grant the owner \
131 the capability to manage the associated name."
132 )
133)]
134pub(crate) enum IOwner {
135 Owner(Owner),
136 Address(Address),
137 Object(Object),
138 MovePackage(MovePackage),
139 MoveObject(MoveObject),
140 Coin(Coin),
141 CoinMetadata(CoinMetadata),
142 StakedIota(StakedIota),
143 NameRegistration(NameRegistration),
144}
145
146#[Object]
151impl Owner {
152 pub(crate) async fn address(&self) -> IotaAddress {
153 OwnerImpl::from(self).address().await
154 }
155
156 pub(crate) async fn objects(
158 &self,
159 ctx: &Context<'_>,
160 first: Option<u64>,
161 after: Option<object::Cursor>,
162 last: Option<u64>,
163 before: Option<object::Cursor>,
164 filter: Option<ObjectFilter>,
165 ) -> Result<Connection<String, MoveObject>> {
166 OwnerImpl::from(self)
167 .objects(ctx, first, after, last, before, filter)
168 .await
169 }
170
171 pub(crate) async fn balance(
174 &self,
175 ctx: &Context<'_>,
176 type_: Option<ExactTypeFilter>,
177 ) -> Result<Option<Balance>> {
178 OwnerImpl::from(self).balance(ctx, type_).await
179 }
180
181 pub(crate) async fn balances(
183 &self,
184 ctx: &Context<'_>,
185 first: Option<u64>,
186 after: Option<balance::Cursor>,
187 last: Option<u64>,
188 before: Option<balance::Cursor>,
189 ) -> Result<Connection<String, Balance>> {
190 OwnerImpl::from(self)
191 .balances(ctx, first, after, last, before)
192 .await
193 }
194
195 pub(crate) async fn coins(
200 &self,
201 ctx: &Context<'_>,
202 first: Option<u64>,
203 after: Option<object::Cursor>,
204 last: Option<u64>,
205 before: Option<object::Cursor>,
206 type_: Option<ExactTypeFilter>,
207 ) -> Result<Connection<String, Coin>> {
208 OwnerImpl::from(self)
209 .coins(ctx, first, after, last, before, type_)
210 .await
211 }
212
213 pub(crate) async fn staked_iotas(
216 &self,
217 ctx: &Context<'_>,
218 first: Option<u64>,
219 after: Option<object::Cursor>,
220 last: Option<u64>,
221 before: Option<object::Cursor>,
222 ) -> Result<Connection<String, StakedIota>> {
223 OwnerImpl::from(self)
224 .staked_iotas(ctx, first, after, last, before)
225 .await
226 }
227
228 pub(crate) async fn iota_names_default_name(
231 &self,
232 ctx: &Context<'_>,
233 format: Option<NameFormat>,
234 ) -> Result<Option<String>> {
235 OwnerImpl::from(self)
236 .iota_names_default_name(ctx, format)
237 .await
238 }
239
240 pub(crate) async fn iota_names_registrations(
243 &self,
244 ctx: &Context<'_>,
245 first: Option<u64>,
246 after: Option<object::Cursor>,
247 last: Option<u64>,
248 before: Option<object::Cursor>,
249 ) -> Result<Connection<String, NameRegistration>> {
250 OwnerImpl::from(self)
251 .iota_names_registrations(ctx, first, after, last, before)
252 .await
253 }
254
255 async fn as_address(&self) -> Option<Address> {
256 Some(Address {
258 address: self.address,
259 checkpoint_viewed_at: self.checkpoint_viewed_at,
260 })
261 }
262
263 async fn as_object(&self, ctx: &Context<'_>) -> Result<Option<Object>> {
264 Object::query(
265 ctx,
266 self.address,
267 if let Some(parent_version) = self.root_version {
268 Object::under_parent(parent_version, self.checkpoint_viewed_at)
269 } else {
270 Object::latest_at(self.checkpoint_viewed_at)
271 },
272 )
273 .await
274 .extend()
275 }
276
277 async fn dynamic_field(
284 &self,
285 ctx: &Context<'_>,
286 name: DynamicFieldName,
287 ) -> Result<Option<DynamicField>> {
288 OwnerImpl::from(self)
289 .dynamic_field(ctx, name, self.root_version)
290 .await
291 }
292
293 async fn dynamic_object_field(
302 &self,
303 ctx: &Context<'_>,
304 name: DynamicFieldName,
305 ) -> Result<Option<DynamicField>> {
306 OwnerImpl::from(self)
307 .dynamic_object_field(ctx, name, self.root_version)
308 .await
309 }
310
311 async fn dynamic_fields(
316 &self,
317 ctx: &Context<'_>,
318 first: Option<u64>,
319 after: Option<object::Cursor>,
320 last: Option<u64>,
321 before: Option<object::Cursor>,
322 ) -> Result<Connection<String, DynamicField>> {
323 OwnerImpl::from(self)
324 .dynamic_fields(ctx, first, after, last, before, self.root_version)
325 .await
326 }
327}
328
329impl OwnerImpl {
330 pub(crate) async fn address(&self) -> IotaAddress {
331 self.address
332 }
333
334 pub(crate) async fn objects(
335 &self,
336 ctx: &Context<'_>,
337 first: Option<u64>,
338 after: Option<object::Cursor>,
339 last: Option<u64>,
340 before: Option<object::Cursor>,
341 filter: Option<ObjectFilter>,
342 ) -> Result<Connection<String, MoveObject>> {
343 let page = Page::from_params(ctx.data_unchecked(), first, after, last, before)?;
344
345 let Some(filter) = filter.unwrap_or_default().intersect(ObjectFilter {
346 owner: Some(self.address),
347 ..Default::default()
348 }) else {
349 return Ok(Connection::new(false, false));
350 };
351
352 MoveObject::paginate(
353 ctx.data_unchecked(),
354 page,
355 filter,
356 self.checkpoint_viewed_at,
357 )
358 .await
359 .extend()
360 }
361
362 pub(crate) async fn balance(
363 &self,
364 ctx: &Context<'_>,
365 type_: Option<ExactTypeFilter>,
366 ) -> Result<Option<Balance>> {
367 let coin = type_.map_or_else(|| TypeTag::from(StructTag::new_gas()), |t| t.0);
368 Balance::query(
369 ctx.data_unchecked(),
370 self.address,
371 coin,
372 self.checkpoint_viewed_at,
373 )
374 .await
375 .extend()
376 }
377
378 pub(crate) async fn balances(
379 &self,
380 ctx: &Context<'_>,
381 first: Option<u64>,
382 after: Option<balance::Cursor>,
383 last: Option<u64>,
384 before: Option<balance::Cursor>,
385 ) -> Result<Connection<String, Balance>> {
386 let page = Page::from_params(ctx.data_unchecked(), first, after, last, before)?;
387 Balance::paginate(
388 ctx.data_unchecked(),
389 page,
390 self.address,
391 self.checkpoint_viewed_at,
392 )
393 .await
394 .extend()
395 }
396
397 pub(crate) async fn coins(
398 &self,
399 ctx: &Context<'_>,
400 first: Option<u64>,
401 after: Option<object::Cursor>,
402 last: Option<u64>,
403 before: Option<object::Cursor>,
404 type_: Option<ExactTypeFilter>,
405 ) -> Result<Connection<String, Coin>> {
406 let page = Page::from_params(ctx.data_unchecked(), first, after, last, before)?;
407 let coin = type_.map_or_else(|| TypeTag::from(StructTag::new_gas()), |t| t.0);
408 Coin::paginate(
409 ctx.data_unchecked(),
410 page,
411 coin,
412 Some(self.address),
413 self.checkpoint_viewed_at,
414 )
415 .await
416 .extend()
417 }
418
419 pub(crate) async fn staked_iotas(
420 &self,
421 ctx: &Context<'_>,
422 first: Option<u64>,
423 after: Option<object::Cursor>,
424 last: Option<u64>,
425 before: Option<object::Cursor>,
426 ) -> Result<Connection<String, StakedIota>> {
427 let page = Page::from_params(ctx.data_unchecked(), first, after, last, before)?;
428 StakedIota::paginate(
429 ctx.data_unchecked(),
430 page,
431 self.address,
432 self.checkpoint_viewed_at,
433 )
434 .await
435 .extend()
436 }
437
438 pub(crate) async fn iota_names_default_name(
439 &self,
440 ctx: &Context<'_>,
441 format: Option<NameFormat>,
442 ) -> Result<Option<String>> {
443 Ok(
444 IotaNames::reverse_resolve_to_name(ctx, self.address, self.checkpoint_viewed_at)
445 .await
446 .extend()?
447 .map(|d| d.format(format.unwrap_or(NameFormat::Dot).into())),
448 )
449 }
450
451 pub(crate) async fn iota_names_registrations(
452 &self,
453 ctx: &Context<'_>,
454 first: Option<u64>,
455 after: Option<object::Cursor>,
456 last: Option<u64>,
457 before: Option<object::Cursor>,
458 ) -> Result<Connection<String, NameRegistration>> {
459 let page = Page::from_params(ctx.data_unchecked(), first, after, last, before)?;
460 NameRegistration::paginate(
461 ctx.data_unchecked::<Db>(),
462 ctx.data_unchecked::<IotaNamesConfig>(),
463 page,
464 self.address,
465 self.checkpoint_viewed_at,
466 )
467 .await
468 .extend()
469 }
470
471 pub(crate) async fn dynamic_field(
476 &self,
477 ctx: &Context<'_>,
478 name: DynamicFieldName,
479 parent_version: Option<u64>,
480 ) -> Result<Option<DynamicField>> {
481 use DynamicFieldType as T;
482 DynamicField::query(
483 ctx,
484 self.address,
485 parent_version,
486 name,
487 T::DynamicField,
488 self.checkpoint_viewed_at,
489 )
490 .await
491 .extend()
492 }
493
494 pub(crate) async fn dynamic_object_field(
495 &self,
496 ctx: &Context<'_>,
497 name: DynamicFieldName,
498 parent_version: Option<u64>,
499 ) -> Result<Option<DynamicField>> {
500 use DynamicFieldType as T;
501 DynamicField::query(
502 ctx,
503 self.address,
504 parent_version,
505 name,
506 T::DynamicObject,
507 self.checkpoint_viewed_at,
508 )
509 .await
510 .extend()
511 }
512
513 pub(crate) async fn dynamic_fields(
514 &self,
515 ctx: &Context<'_>,
516 first: Option<u64>,
517 after: Option<object::Cursor>,
518 last: Option<u64>,
519 before: Option<object::Cursor>,
520 parent_version: Option<u64>,
521 ) -> Result<Connection<String, DynamicField>> {
522 let page = Page::from_params(ctx.data_unchecked(), first, after, last, before)?;
523 DynamicField::paginate(
524 ctx.data_unchecked(),
525 page,
526 self.address,
527 parent_version,
528 self.checkpoint_viewed_at,
529 )
530 .await
531 .extend()
532 }
533}
534
535impl From<&Owner> for OwnerImpl {
536 fn from(owner: &Owner) -> Self {
537 OwnerImpl {
538 address: owner.address,
539 checkpoint_viewed_at: owner.checkpoint_viewed_at,
540 }
541 }
542}