sui_indexer_alt_jsonrpc/api/name_service/
response.rsuse anyhow::Context as _;
use diesel::{ExpressionMethods, QueryDsl};
use futures::future::OptionFuture;
use sui_indexer_alt_schema::schema::watermarks;
use sui_name_service::{Domain, NameRecord, NameServiceError};
use sui_types::{base_types::SuiAddress, dynamic_field::Field};
use tokio::join;
use crate::{
context::Context,
data::objects::load_live,
error::{invalid_params, InternalContext, RpcError},
};
use super::Error;
pub(super) async fn resolved_address(
ctx: &Context,
name: &str,
) -> Result<Option<SuiAddress>, RpcError<Error>> {
use Error as E;
let domain: Domain = name
.parse()
.map_err(|e| invalid_params(E::NameService(e)))?;
let config = &ctx.config().name_service;
let domain_record_id = config.record_field_id(&domain);
let parent_record_id = config.record_field_id(&domain.parent());
let domain_object = load_live(ctx, domain_record_id);
let parent_object: OptionFuture<_> = domain
.is_subdomain()
.then(|| load_live(ctx, parent_record_id))
.into();
let (timestamp_ms, domain_object, parent_object) =
join!(latest_timestamp_ms(ctx), domain_object, parent_object);
let timestamp_ms = timestamp_ms.context("Failed to fetch latest timestamp")?;
let Some(domain_object) = domain_object.context("Failed to fetch domain record")? else {
return Err(invalid_params(E::NotFound(domain.to_string())));
};
let domain_record =
NameRecord::try_from(domain_object).context("Failed to deserialize domain record")?;
if !domain_record.is_leaf_record() {
return if !domain_record.is_node_expired(timestamp_ms) {
Ok(domain_record.target_address)
} else {
return Err(invalid_params(E::NameService(
NameServiceError::NameExpired,
)));
};
}
let Some(parent_object) = parent_object
.transpose()
.context("Failed to fetch parent record")?
.flatten()
else {
return Err(invalid_params(E::NotFound(domain.parent().to_string())));
};
let parent_record =
NameRecord::try_from(parent_object).context("Failed to deserialize parent record")?;
if parent_record.is_valid_leaf_parent(&domain_record)
&& !parent_record.is_node_expired(timestamp_ms)
{
Ok(domain_record.target_address)
} else {
Err(invalid_params(E::NameService(
NameServiceError::NameExpired,
)))
}
}
pub(super) async fn resolved_name(
ctx: &Context,
address: SuiAddress,
) -> Result<Option<String>, RpcError<Error>> {
let config = &ctx.config().name_service;
let reverse_record_id = config.reverse_record_field_id(address.as_ref());
let Some(reverse_record_object) = load_live(ctx, reverse_record_id)
.await
.context("Failed to fetch reverse record")?
else {
return Ok(None);
};
let reverse_record: Field<SuiAddress, Domain> = bcs::from_bytes(
reverse_record_object
.data
.try_as_move()
.context("Reverse record not a Move object")?
.contents(),
)
.context("Failed to deserialize reverse record")?;
let domain = reverse_record.value.to_string();
match resolved_address(ctx, &domain).await {
Ok(Some(_)) => Ok(Some(domain)),
Ok(None) | Err(RpcError::InvalidParams(_)) => Ok(None),
Err(e) => Err(e).internal_context("Failed to resolve address"),
}
}
async fn latest_timestamp_ms(ctx: &Context) -> Result<u64, RpcError<Error>> {
use watermarks::dsl as w;
let mut conn = ctx
.pg_reader()
.connect()
.await
.context("Failed to connect to database")?;
let query = w::watermarks
.select(w::timestamp_ms_hi_inclusive)
.filter(w::pipeline.eq("obj_info"));
let timestamp_ms: i64 = conn
.first(query)
.await
.context("Failed to fetch latest timestamp")?;
Ok(timestamp_ms as u64)
}