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