Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ANS domains integration. #236

Closed
wants to merge 15 commits into from
6 changes: 3 additions & 3 deletions app/api/domain-info/[domain]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Connection } from "@solana/web3.js"
import { NextResponse } from "next/server"

import { MAINNET_BETA_URL } from "@/app/utils/cluster"
import { getDomainInfo } from "@/app/utils/domain-info"
import { getANSDomainInfo,getDomainInfo } from "@/app/utils/domain-info"

type Params = {
params: {
Expand All @@ -20,12 +20,12 @@ export async function GET(
// This is an API route so won't affect client bundle
// We only fetch domains on mainnet
const connection = new Connection(MAINNET_BETA_URL);
const domainInfo = await getDomainInfo(domain, connection);
const domainInfo = await (domain.substring(domain.length - 4) === '.sol' ? getDomainInfo(domain, connection) : getANSDomainInfo(domain, connection));

return NextResponse.json(domainInfo, {
headers: {
// 24 hours
"Cache-Control": "max-age=86400"
"Cache-Control": "max-age=86400",
}
});
}
2 changes: 1 addition & 1 deletion app/components/SearchBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ interface SearchOptions {
}

const hasDomainSyntax = (value: string) => {
return value.length > 4 && value.substring(value.length - 4) === '.sol';
return value.length > 3 && value.split('.').length === 2;
};

export function SearchBar() {
Expand Down
22 changes: 18 additions & 4 deletions app/components/account/DomainsCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,33 @@ import React from 'react';

import { DomainInfo } from '@/app/utils/domain-info';

import { useUserANSDomains } from '../../utils/ans-domains';

export function DomainsCard({ address }: { address: string }) {
const [domains, domainsLoading] = useUserDomains(address);
const [domainsANS, domainsANSLoading] = useUserANSDomains(address);

if (domainsLoading && (!domains || domains.length === 0)) {
if (
(domainsLoading && (!domains || domains.length === 0)) ||
(domainsANSLoading && (!domainsANS || domainsANS.length === 0))
) {
return <LoadingCard message="Loading domains" />;
} else if (!domains) {
} else if (!domains || !domainsANS) {
return <ErrorCard text="Failed to fetch domains" />;
}

if (domains.length === 0) {
if (domains.length === 0 && domainsANS.length === 0) {
return <ErrorCard text="No domain name found" />;
}

let allDomains = domains;

if (domainsANS) {
allDomains = [...allDomains, ...domainsANS];
}

allDomains.sort((a, b) => a.name.localeCompare(b.name));

return (
<div className="card">
<div className="card-header align-items-center">
Expand All @@ -35,7 +49,7 @@ export function DomainsCard({ address }: { address: string }) {
</tr>
</thead>
<tbody className="list">
{domains.map(domain => (
{allDomains.map((domain) => (
<RenderDomainRow key={domain.address.toBase58()} domainInfo={domain} />
))}
</tbody>
Expand Down
76 changes: 76 additions & 0 deletions app/utils/ans-domains.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { NameRecordHeader,TldParser } from '@onsol/tldparser';
import { Connection } from '@solana/web3.js';
import pLimit from 'p-limit';
import { useEffect,useState } from 'react';

import { useCluster } from '../providers/cluster';
import { Cluster } from './cluster';
import { DomainInfo } from './domain-info';


export const useUserANSDomains = (userAddress: string): [DomainInfo[] | null, boolean] => {
const { url, cluster } = useCluster();
const [result, setResult] = useState<DomainInfo[] | null>(null);
const [loading, setLoading] = useState(false);

useEffect(() => {
const resolve = async () => {
// Allow only mainnet and custom
if (![Cluster.MainnetBeta, Cluster.Custom].includes(cluster)) return;
const connection = new Connection(url, 'confirmed');
try {
setLoading(true);

const parser = new TldParser(connection);
const allDomains = await parser.getAllUserDomains(userAddress);

if (!allDomains) {
return;
}
const userDomains: DomainInfo[] = [];
const limit = pLimit(5);
const promises = allDomains.map(address =>
limit(async () => {
const domainRecord = await NameRecordHeader.fromAccountAddress(connection, address);

// expired or not found
if (!domainRecord?.owner) return;

const domainParentNameAccount = await NameRecordHeader.fromAccountAddress(
connection,
domainRecord?.parentName
);

// not found
if (!domainParentNameAccount?.owner) return;

const tld = await parser.getTldFromParentAccount(domainRecord?.parentName);

const domain = await parser.reverseLookupNameAccount(
address,
domainParentNameAccount?.owner
);

// domain not found or might be a subdomain.
if (!domain) return;

userDomains.push({
address,
name: `${domain}${tld}`,
});
})
);

await Promise.all(promises);
setResult(userDomains);
} catch (err) {
console.log(`Error fetching user domains ${err}`);
} finally {
setLoading(false);
}
};
resolve();
}, [userAddress, url]); // eslint-disable-line react-hooks/exhaustive-deps

return [result, loading];
};
20 changes: 19 additions & 1 deletion app/utils/domain-info.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { getHashedName, getNameAccountKey, getNameOwner } from '@bonfida/spl-name-service';
import { getDomainKey as getANSDomainKey, getNameOwner as getANSNameOwner } from '@onsol/tldparser';
import { Connection, PublicKey } from '@solana/web3.js';

// Address of the SOL TLD
Expand All @@ -16,7 +17,7 @@ export interface DomainInfo {
}

export const hasDomainSyntax = (value: string) => {
return value.length > 4 && value.substring(value.length - 4) === '.sol';
return value.length > 3 && value.split('.').length === 2;
};

// returns non empty wallet string if a given .sol domain is owned by a wallet
Expand All @@ -38,3 +39,20 @@ export async function getDomainInfo(domain: string, connection: Connection) {
return null;
}
}

// returns owner address and name account address.
export async function getANSDomainInfo(domainTld: string, connection: Connection) {
const derivedDomainKey = await getANSDomainKey(domainTld.toLowerCase());
try {
// returns only non expired domains,
const owner = await getANSNameOwner(connection, derivedDomainKey.pubkey);
return owner
? {
address: derivedDomainKey.pubkey.toString(),
owner: owner.toString(),
}
: null;
} catch {
return null;
}
}
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"@solana/web3.js": "^1.66.0",
"@solflare-wallet/utl-sdk": "^1.4.0",
"@types/bn.js": "5.1.0",
"@onsol/tldparser": "^0.6.3",
"axios": "^0.27.2",
"bignumber.js": "^9.0.2",
"bn.js": "5.2.1",
Expand Down
Loading