look up domain owner on .sol search (explorer) (#24300)
* lookup domain owner on .sol search * add detected domain names to search options * lookup domain owner on .sol search * add detected domain names to search options * add loading state and only append domain search results if search state has not changed * rm url and rename fn * useRef to check if domain lookup is still valid
This commit is contained in:
parent
6e03e0e987
commit
5de8061bed
@ -13,14 +13,30 @@ import {
|
||||
import { Cluster, useCluster } from "providers/cluster";
|
||||
import { useTokenRegistry } from "providers/mints/token-registry";
|
||||
import { TokenInfoMap } from "@solana/spl-token-registry";
|
||||
import { Connection } from "@solana/web3.js";
|
||||
import { getDomainInfo, hasDomainSyntax } from "utils/name-service";
|
||||
|
||||
interface SearchOptions {
|
||||
label: string;
|
||||
options: {
|
||||
label: string;
|
||||
value: string[];
|
||||
pathname: string;
|
||||
}[];
|
||||
}
|
||||
|
||||
export function SearchBar() {
|
||||
const [search, setSearch] = React.useState("");
|
||||
const searchRef = React.useRef("");
|
||||
const [searchOptions, setSearchOptions] = React.useState<SearchOptions[]>([]);
|
||||
const [loadingSearch, setLoadingSearch] = React.useState<boolean>(false);
|
||||
const [loadingSearchMessage, setLoadingSearchMessage] =
|
||||
React.useState<string>("loading...");
|
||||
const selectRef = React.useRef<StateManager<any> | null>(null);
|
||||
const history = useHistory();
|
||||
const location = useLocation();
|
||||
const { tokenRegistry } = useTokenRegistry();
|
||||
const { cluster, clusterInfo } = useCluster();
|
||||
const { url, cluster, clusterInfo } = useCluster();
|
||||
|
||||
const onChange = (
|
||||
{ pathname }: ValueType<any, false>,
|
||||
@ -33,7 +49,54 @@ export function SearchBar() {
|
||||
};
|
||||
|
||||
const onInputChange = (value: string, { action }: InputActionMeta) => {
|
||||
if (action === "input-change") setSearch(value);
|
||||
if (action === "input-change") {
|
||||
setSearch(value);
|
||||
}
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
searchRef.current = search;
|
||||
setLoadingSearchMessage("Loading...");
|
||||
setLoadingSearch(true);
|
||||
|
||||
// builds and sets local search output
|
||||
const options = buildOptions(
|
||||
search,
|
||||
cluster,
|
||||
tokenRegistry,
|
||||
clusterInfo?.epochInfo.epoch
|
||||
);
|
||||
|
||||
setSearchOptions(options);
|
||||
|
||||
// checking for non local search output
|
||||
if (hasDomainSyntax(search)) {
|
||||
// if search input is a potential domain we continue the loading state
|
||||
domainSearch(options);
|
||||
} else {
|
||||
// if search input is not a potential domain we can conclude the search has finished
|
||||
setLoadingSearch(false);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [search]);
|
||||
|
||||
// appends domain lookup results to the local search state
|
||||
const domainSearch = async (options: SearchOptions[]) => {
|
||||
setLoadingSearchMessage("Looking up domain...");
|
||||
const connection = new Connection(url);
|
||||
const searchTerm = search;
|
||||
const updatedOptions = await buildDomainOptions(
|
||||
connection,
|
||||
search,
|
||||
options
|
||||
);
|
||||
if (searchRef.current === searchTerm) {
|
||||
setSearchOptions(updatedOptions);
|
||||
// after attempting to fetch the domain name we can conclude the loading state
|
||||
setLoadingSearch(false);
|
||||
setLoadingSearchMessage("Loading...");
|
||||
}
|
||||
};
|
||||
|
||||
const resetValue = "" as any;
|
||||
@ -44,13 +107,9 @@ export function SearchBar() {
|
||||
<Select
|
||||
autoFocus
|
||||
ref={(ref) => (selectRef.current = ref)}
|
||||
options={buildOptions(
|
||||
search,
|
||||
cluster,
|
||||
tokenRegistry,
|
||||
clusterInfo?.epochInfo.epoch
|
||||
)}
|
||||
options={searchOptions}
|
||||
noOptionsMessage={() => "No Results"}
|
||||
loadingMessage={() => loadingSearchMessage}
|
||||
placeholder="Search for blocks, accounts, transactions, programs, and tokens"
|
||||
value={resetValue}
|
||||
inputValue={search}
|
||||
@ -65,6 +124,7 @@ export function SearchBar() {
|
||||
onInputChange={onInputChange}
|
||||
components={{ DropdownIndicator }}
|
||||
classNamePrefix="search-bar"
|
||||
isLoading={loadingSearch}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -196,6 +256,39 @@ function buildTokenOptions(
|
||||
}
|
||||
}
|
||||
|
||||
async function buildDomainOptions(
|
||||
connection: Connection,
|
||||
search: string,
|
||||
options: SearchOptions[]
|
||||
) {
|
||||
const domainInfo = await getDomainInfo(search, connection);
|
||||
const updatedOptions: SearchOptions[] = [...options];
|
||||
if (domainInfo && domainInfo.owner && domainInfo.address) {
|
||||
updatedOptions.push({
|
||||
label: "Domain Owner",
|
||||
options: [
|
||||
{
|
||||
label: domainInfo.owner,
|
||||
value: [search],
|
||||
pathname: "/address/" + domainInfo.owner,
|
||||
},
|
||||
],
|
||||
});
|
||||
updatedOptions.push({
|
||||
label: "Name Service Account",
|
||||
options: [
|
||||
{
|
||||
label: search,
|
||||
value: [search],
|
||||
pathname: "/address/" + domainInfo.address,
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
return updatedOptions;
|
||||
}
|
||||
|
||||
// builds local search options
|
||||
function buildOptions(
|
||||
rawSearch: string,
|
||||
cluster: Cluster,
|
||||
@ -287,6 +380,7 @@ function buildOptions(
|
||||
});
|
||||
}
|
||||
} catch (err) {}
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,9 @@
|
||||
import { PublicKey, Connection } from "@solana/web3.js";
|
||||
import {
|
||||
getFilteredProgramAccounts,
|
||||
getHashedName,
|
||||
getNameAccountKey,
|
||||
getNameOwner,
|
||||
NAME_PROGRAM_ID,
|
||||
performReverseLookup,
|
||||
} from "@bonfida/spl-name-service";
|
||||
@ -11,10 +14,48 @@ import { Cluster, useCluster } from "providers/cluster";
|
||||
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,
|
||||
|
Loading…
x
Reference in New Issue
Block a user