diff --git a/README.md b/README.md index c985f0d..d484b98 100644 --- a/README.md +++ b/README.md @@ -1 +1,66 @@ -# proxy-server +# ENS API + +## Running Locally + +``` +npm install +npm i -g vercel +vercel dev +``` + +## Example Requests + +### Get Ens Name + +``` +GET http://localhost:3000/api/name?accountId=0x903322c7e45a60d7c8c3ea236c5bea9af86310c7 +``` + +Example response: + +``` +{ + "name": "winstonwolfe.eth" +} +``` + +### Get Ens Address + +``` +GET http://localhost:3000/api/address?name=winstonwolfe.eth +``` + +Example response: + +``` +0x903322C7E45A60d7c8C3EA236c5beA9Af86310c7 +``` + +### Get Ens Text Records + +``` +GET http://localhost:3000/api/text?name=jellymcjellyfish.eth +``` + +Example response: + +``` +[ + { + "key": "url", + "value": "https://oceanprotocol.com" + }, + { + "key": "avatar", + "value": "https://raw.githubusercontent.com/oceanprotocol/art/main/logo/favicon-white.png" + }, + { + "key": "com.github", + "value": "oceanprotocol" + }, + { + "key": "com.twitter", + "value": "oceanprotocol" + } +] +``` diff --git a/pages/api/_types.ts b/pages/api/_types.ts deleted file mode 100644 index e69de29..0000000 diff --git a/pages/api/address.ts b/pages/api/address.ts new file mode 100644 index 0000000..da77270 --- /dev/null +++ b/pages/api/address.ts @@ -0,0 +1,20 @@ +import { NextApiRequest, NextApiResponse } from 'next' +import { getEns } from './_utils' + +export default async function getEnsAddress( + request: NextApiRequest, + response: NextApiResponse +) { + try { + const ensName = request.query.name + console.log('ensName', ensName) + const ens = await getEns() + const address = await ens.name(ensName).getAddress() + console.log('address', address) + + response.setHeader('Cache-Control', 's-maxage=86400') + response.status(200).send(address) + } catch (error) { + response.status(500).send(`${error}`) + } +} diff --git a/pages/api/name.ts b/pages/api/name.ts index c35adf7..89bec39 100644 --- a/pages/api/name.ts +++ b/pages/api/name.ts @@ -1,20 +1,23 @@ import { NextApiRequest, NextApiResponse } from 'next' import { getEns } from './_utils' -export default async function getEnsName( +export async function getEnsName(accountId: string) { + const ens = await getEns() + let name = await ens.getName(accountId) + + // Check to be sure the reverse record is correct. + const reverseAccountId = await ens.name(name.name).getAddress() + if (accountId.toLowerCase() !== reverseAccountId.toLowerCase()) name = null + return name +} + +export default async function nameApi( request: NextApiRequest, response: NextApiResponse ) { try { const accountId = String(request.query.accountId) - const ens = await getEns() - let name = await ens.getName(accountId) - - // Check to be sure the reverse record is correct. - const reverseAccountId = await ens.name(name.name).getAddress() - console.log('reverseAccountId', reverseAccountId) - if (accountId.toLowerCase() !== reverseAccountId.toLowerCase()) name = null - + const name = await getEnsName(accountId) response.setHeader('Cache-Control', 's-maxage=86400') response.status(200).send(name) } catch (error) { diff --git a/pages/api/profile.ts b/pages/api/profile.ts new file mode 100644 index 0000000..46728e8 --- /dev/null +++ b/pages/api/profile.ts @@ -0,0 +1,57 @@ +import { getEnsName } from './name' +import { getEnsTextRecords } from './text' + +interface ProfileLink { + key: string + value: string +} + +interface Profile { + name: string + url?: string + avatar?: string + description?: string + links?: ProfileLink[] +} + +function getEnsAvatar(ensName: string): string { + return ensName + ? `https://metadata.ens.domains/mainnet/avatar/${ensName}` + : null +} + +export async function getEnsProfile(accountId: string): Promise { + const name = await getEnsName(accountId) + if (!name) return { name: null } + + const records = await getEnsTextRecords(name) + if (!records) return { name } + + const avatar = records.filter((record) => record.key === 'avatar')[0]?.value + const description = records.filter( + (record) => record.key === 'description' + )[0]?.value + + // filter out what we need from the fetched text records + const linkKeys = [ + 'url', + 'com.twitter', + 'com.github', + 'org.telegram', + 'com.discord', + 'com.reddit' + ] + + const links: ProfileLink[] = records.filter((record) => + linkKeys.includes(record.key) + ) + + const profile: Profile = { + name, + ...(avatar && { avatar: getEnsAvatar(name) }), + ...(description && { description }), + ...(links.length > 0 && { links }) + } + + return profile +} diff --git a/pages/api/text.ts b/pages/api/text.ts index c5bde71..3f19560 100644 --- a/pages/api/text.ts +++ b/pages/api/text.ts @@ -1,6 +1,6 @@ import { NextApiRequest, NextApiResponse } from 'next' import { gql, OperationResult } from 'urql' -import { fetchData } from './_utils' +import { fetchData, getEns } from './_utils' const ProfileTextRecordsQuery = gql<{ domains: [{ resolver: { texts: string[] } }] @@ -14,36 +14,46 @@ const ProfileTextRecordsQuery = gql<{ } ` -export default async function getEnsTextRecords( +export async function getEnsTextRecords( + ensName: string +): Promise<{ key: string; value: string }[]> { + // 1. Check which text records are set for the domain with ENS subgraph, + // to prevent unnecessary contract calls. + const result: OperationResult<{ + domains: [{ resolver: { texts: string[] } }] + }> = await fetchData( + ProfileTextRecordsQuery, + { name: ensName }, + { + url: `https://api.thegraph.com/subgraphs/name/ensdomains/ens`, + requestPolicy: 'cache-and-network' + } + ) + if (!result?.data?.domains[0]?.resolver) return + + // 2. Retrieve the text records. + const { texts } = result.data.domains[0].resolver + + const records = [] + const ens = await getEns() + + for (let index = 0; index < texts?.length; index++) { + const key = texts[index] + const value = await ens.name(ensName).getText(key) + console.log(value) + records.push({ key, value }) + } + + return records +} + +export default async function ensTextApi( request: NextApiRequest, response: NextApiResponse -): Promise<{ key: string; value: string }[]> { +) { try { const ensName = String(request.query.name) - // 1. Check which text records are set for the domain with ENS subgraph, - // to prevent unnecessary contract calls. - const result: OperationResult<{ - domains: [{ resolver: { texts: string[] } }] - }> = await fetchData( - ProfileTextRecordsQuery, - { name: ensName }, - { - url: `https://api.thegraph.com/subgraphs/name/ensdomains/ens`, - requestPolicy: 'cache-and-network' - } - ) - if (!result?.data?.domains[0]?.resolver) return - - // 2. Retrieve the text records. - const { texts } = result.data.domains[0].resolver - const records = [] - let ens: any - - for (let index = 0; index < texts?.length; index++) { - const key = texts[index] - const value = await ens.name(ensName).getText(key) - records.push({ key, value }) - } + const records = await getEnsTextRecords(ensName) response.setHeader('Cache-Control', 's-maxage=86400') response.status(200).send(records)