/* eslint-disable @typescript-eslint/naming-convention */ import { gql } from 'graphql-request' import type { ClientWithPns } from '../../contracts/consts.js' import { InvalidOrderByError } from '../../errors/subgraph.js' import { EMPTY_ADDRESS } from '../../utils/consts.js' import { namehash } from '../../utils/normalise.js' import { createSubgraphClient } from './client.js' import { getExpiryDateOrderFilter, type DomainFilter, getCreatedAtOrderFilter, } from './filters.js' import { domainDetailsWithoutParentFragment, registrationDetailsFragment, wrappedDomainDetailsFragment, type SubgraphDomain, } from './fragments.js' import { makeNameObject, type Name } from './utils.js' type GetSubnamesOrderBy = 'expiryDate' | 'name' | 'labelName' | 'createdAt' export type GetSubnamesParameters = { /** Name to get subnames for */ name: string /** Search string filter for subname label */ searchString?: string /** Allows expired names to be included (default: false) */ allowExpired?: boolean /** Allows deleted names to be included (default: false) */ allowDeleted?: boolean /** Parameter to order names by (default: name) */ orderBy?: GetSubnamesOrderBy /** Direction to order names in (default: asc) */ orderDirection?: 'asc' | 'desc' /** Previous page of subnames, used for pagination */ previousPage?: Name[] /** Page size (default: 100) */ pageSize?: number } export type GetSubnamesReturnType = Name[] type SubgraphResult = { domain?: { subdomains: SubgraphDomain[] } } const getOrderByFilter = ({ orderBy, orderDirection, previousPage, }: Required< Pick >): DomainFilter => { const lastDomain = previousPage[previousPage.length - 1] const operator = orderDirection === 'asc' ? 'gt' : 'lt' switch (orderBy) { case 'expiryDate': { return getExpiryDateOrderFilter({ lastDomain, orderDirection, }) } case 'name': { return { [`name_${operator}`]: lastDomain.name ?? '', } } case 'labelName': { return { [`labelName_${operator}`]: lastDomain.labelName ?? '', } } case 'createdAt': { return getCreatedAtOrderFilter({ lastDomain, orderDirection }) } default: throw new InvalidOrderByError({ orderBy: orderBy || '', supportedOrderBys: ['expiryDate', 'name', 'labelName', 'createdAt'], }) } } /** * Gets the subnames for a name from the subgraph. * @param client - {@link ClientWithPns} * @param parameters - {@link GetSubnamesParameters} * @returns Subname array. {@link GetSubnamesReturnType} * * @example * import { createPublicClient, http } from 'viem' * import { mainnet } from 'viem/chains' * import { addPnsContracts } from '@pnsdomains/pnsjs' * import { getSubnames } from '@pnsdomains/pnsjs/subgraph' * * const client = createPublicClient({ * chain: addPnsContracts(mainnet), * transport: http(), * }) * const result = await getSubnames(client, { name: 'pns.pls' }) */ const getSubnames = async ( client: ClientWithPns, { name, searchString, allowExpired = false, allowDeleted = false, orderBy = 'name', orderDirection = 'asc', pageSize = 100, previousPage, }: GetSubnamesParameters, ): Promise => { const subgraphClient = createSubgraphClient({ client }) const whereFilters: DomainFilter[] = [] if (previousPage?.length) { whereFilters.push( getOrderByFilter({ orderBy, orderDirection, previousPage, }), ) } if (!allowExpired) { // Exclude domains that are expired // if expiryDate is null, there is no expiry on the domain (registration or wrapped) whereFilters.push({ or: [ { expiryDate_gt: `${Math.floor(Date.now() / 1000)}` }, { expiryDate: null }, ], }) } if (!allowDeleted) { // exclude "deleted" domains // when owner/resolver/registrant = null whereFilters.push({ or: [ { owner_not: EMPTY_ADDRESS, }, { resolver_not: null, }, ...(name.toLowerCase() === 'pls' ? [ { registrant_not: EMPTY_ADDRESS, }, ] : []), ], }) } if (searchString) { // using labelName_contains instead of name_contains because name_contains // includes the parent name whereFilters.push({ labelName_contains: searchString, }) } let whereFilter: DomainFilter = {} if (whereFilters.length > 1) { whereFilter = { and: whereFilters, } } else if (whereFilters.length === 1) { ;[whereFilter] = whereFilters } const query = gql` query getSubnames( $id: String! $orderBy: Domain_orderBy $orderDirection: OrderDirection $whereFilter: Domain_filter $first: Int ) { domain(id: $id) { subdomains( orderBy: $orderBy orderDirection: $orderDirection first: $first where: $whereFilter ) { ...DomainDetailsWithoutParent registration { ...RegistrationDetails } wrappedDomain { ...WrappedDomainDetails } } } } ${domainDetailsWithoutParentFragment} ${registrationDetailsFragment} ${wrappedDomainDetailsFragment} ` const queryVars = { id: namehash(name), orderBy, orderDirection, first: pageSize, whereFilter, } const result = await subgraphClient.request( query, queryVars, ) if (!result.domain) return [] const names = result.domain.subdomains.map((domain) => makeNameObject({ ...domain, parent: { name } }), ) return names } export default getSubnames