solana/explorer/src/utils/name-service.tsx

128 lines
3.2 KiB
TypeScript

import { PublicKey, Connection } from "@solana/web3.js";
import {
getFilteredProgramAccounts,
getHashedName,
getNameAccountKey,
getNameOwner,
NAME_PROGRAM_ID,
performReverseLookup,
} from "@bonfida/spl-name-service";
import { useState, useEffect } from "react";
import { Cluster, useCluster } from "providers/cluster";
// Address of the SOL TLD
const SOL_TLD_AUTHORITY = new PublicKey(
"58PwtjSDuFHuUkYjH9BYnnQKHfwo9reZhC2zMJv9JPkx"
);
export interface DomainInfo {
name: string;
address: PublicKey;
}
export const hasDomainSyntax = (value: string) => {
return value.length > 4 && value.substring(value.length - 4) === ".sol";
};
async function getDomainKey(
name: string,
nameClass?: PublicKey,
nameParent?: PublicKey
) {
const hashedDomainName = await getHashedName(name);
const nameKey = await getNameAccountKey(
hashedDomainName,
nameClass,
nameParent
);
return nameKey;
}
// returns non empty wallet string if a given .sol domain is owned by a wallet
export async function getDomainInfo(domain: string, connection: Connection) {
const domainKey = await getDomainKey(
domain.slice(0, -4), // remove .sol
undefined,
SOL_TLD_AUTHORITY
);
try {
const registry = await getNameOwner(connection, domainKey);
return registry && registry.registry.owner
? {
owner: registry.registry.owner.toString(),
address: domainKey.toString(),
}
: null;
} catch {
return null;
}
}
async function getUserDomainAddresses(
connection: Connection,
userAddress: PublicKey
): Promise<PublicKey[]> {
const filters = [
// parent
{
memcmp: {
offset: 0,
bytes: SOL_TLD_AUTHORITY.toBase58(),
},
},
// owner
{
memcmp: {
offset: 32,
bytes: userAddress.toBase58(),
},
},
];
const accounts = await getFilteredProgramAccounts(
connection,
NAME_PROGRAM_ID,
filters
);
return accounts.map((a) => a.publicKey);
}
export const useUserDomains = (
userAddress: PublicKey
): [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 userDomainAddresses = await getUserDomainAddresses(
connection,
userAddress
);
const userDomains = await Promise.all(
userDomainAddresses.map(async (address) => {
const domainName = await performReverseLookup(connection, address);
return {
name: `${domainName}.sol`,
address,
};
})
);
userDomains.sort((a, b) => a.name.localeCompare(b.name));
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];
};