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

Refactor search to remove legacy web3js dependency #275

Merged
merged 2 commits into from
Jul 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions app/api/domain-info/[domain]/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
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"

type Params = {
params: {
domain: string
}
}

export type FetchedDomainInfo = Awaited<ReturnType<typeof getDomainInfo>>;

export async function GET(
_request: Request,
{ params: { domain } }: Params
) {
// Intentionally using legacy web3js for compatibility with bonfida library
// 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);

return NextResponse.json(domainInfo, {
headers: {
// 24 hours
"Cache-Control": "max-age=86400"
}
});
}
22 changes: 13 additions & 9 deletions app/components/SearchBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,16 @@
import { useCluster } from '@providers/cluster';
import { useTokenRegistry } from '@providers/token-registry';
import { TokenInfoMap } from '@solana/spl-token-registry';
import { Connection } from '@solana/web3.js';
import { Cluster } from '@utils/cluster';
import { getDomainInfo, hasDomainSyntax } from '@utils/name-service';
import { LOADER_IDS, LoaderName, PROGRAM_INFO_BY_ID, SPECIAL_IDS, SYSVAR_IDS } from '@utils/tx';
import bs58 from 'bs58';
import { useRouter, useSearchParams } from 'next/navigation';
import React, { useId } from 'react';
import { Search } from 'react-feather';
import Select, { ActionMeta, InputActionMeta, ValueType } from 'react-select';

import { FetchedDomainInfo } from '../api/domain-info/[domain]/route';
import { LOADER_IDS, LoaderName, PROGRAM_INFO_BY_ID, SPECIAL_IDS, SYSVAR_IDS } from '../utils/programs';

interface SearchOptions {
label: string;
options: {
Expand All @@ -22,6 +22,10 @@ interface SearchOptions {
}[];
}

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

export function SearchBar() {
const [search, setSearch] = React.useState('');
const searchRef = React.useRef('');
Expand All @@ -31,7 +35,7 @@ export function SearchBar() {
const selectRef = React.useRef<Select<any> | null>(null);
const router = useRouter();
const { tokenRegistry } = useTokenRegistry();
const { url, cluster, clusterInfo } = useCluster();
const { cluster, clusterInfo } = useCluster();
const searchParams = useSearchParams();
const onChange = ({ pathname }: ValueType<any, false>, meta: ActionMeta<any>) => {
if (meta.action === 'select-option') {
Expand All @@ -58,7 +62,7 @@ export function SearchBar() {
setSearchOptions(options);

// checking for non local search output
if (hasDomainSyntax(search)) {
if (hasDomainSyntax(search) && cluster === Cluster.MainnetBeta) {
// if search input is a potential domain we continue the loading state
domainSearch(options);
} else {
Expand All @@ -72,9 +76,8 @@ export function SearchBar() {
// 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);
const updatedOptions = await buildDomainOptions(search, options);
if (searchRef.current === searchTerm) {
setSearchOptions(updatedOptions);
// after attempting to fetch the domain name we can conclude the loading state
Expand Down Expand Up @@ -213,8 +216,9 @@ function buildTokenOptions(search: string, cluster: Cluster, tokenRegistry: Toke
}
}

async function buildDomainOptions(connection: Connection, search: string, options: SearchOptions[]) {
const domainInfo = await getDomainInfo(search, connection);
async function buildDomainOptions(search: string, options: SearchOptions[]) {
const domainInfoResponse = await fetch(`/api/domain-info/${search}`);
const domainInfo = await domainInfoResponse.json() as FetchedDomainInfo;
const updatedOptions: SearchOptions[] = [...options];
if (domainInfo && domainInfo.owner && domainInfo.address) {
updatedOptions.push({
Expand Down
4 changes: 3 additions & 1 deletion app/components/account/DomainsCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
import { Address } from '@components/common/Address';
import { ErrorCard } from '@components/common/ErrorCard';
import { LoadingCard } from '@components/common/LoadingCard';
import { DomainInfo, useUserDomains } from '@utils/name-service';
import { useUserDomains } from '@utils/name-service';
import React from 'react';

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

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

Expand Down
40 changes: 40 additions & 0 deletions app/utils/domain-info.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { getHashedName, getNameAccountKey, getNameOwner } from "@bonfida/spl-name-service";
import { Connection, PublicKey } from "@solana/web3.js";

// Address of the SOL TLD
export const SOL_TLD_AUTHORITY = new PublicKey('58PwtjSDuFHuUkYjH9BYnnQKHfwo9reZhC2zMJv9JPkx');

async function getDomainKey(name: string, nameClass?: PublicKey, nameParent?: PublicKey) {
const hashedDomainName = await getHashedName(name);
const nameKey = await getNameAccountKey(hashedDomainName, nameClass, nameParent);
return nameKey;
}

export interface DomainInfo {
name: string;
address: PublicKey;
}

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

// 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
? {
address: domainKey.toString(),
owner: registry.registry.owner.toString(),
}
: null;
} catch {
return null;
}
}
40 changes: 1 addition & 39 deletions app/utils/name-service.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,6 @@

import {
getFilteredProgramAccounts,
getHashedName,
getNameAccountKey,
getNameOwner,
NAME_PROGRAM_ID,
performReverseLookup,
} from '@bonfida/spl-name-service';
Expand All @@ -13,42 +10,7 @@ import { Connection, PublicKey } from '@solana/web3.js';
import { Cluster } from '@utils/cluster';
import { useEffect, useState } from 'react';

// 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
? {
address: domainKey.toString(),
owner: registry.registry.owner.toString(),
}
: null;
} catch {
return null;
}
}
import { DomainInfo, SOL_TLD_AUTHORITY } from './domain-info';

async function getUserDomainAddresses(connection: Connection, userAddress: string): Promise<PublicKey[]> {
const filters = [
Expand Down
Loading