-
Notifications
You must be signed in to change notification settings - Fork 500
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* early wiring for basenames frame * renamed mint to inputSearchValue, improved logic * changed response object * improved flow for basenames frame * updated metadata for frame landing page * stronger type checking for isNameAvailable * removed unnecessary code * improved logic * created API endpoint to fetch registration price * deleted unused frame * refactored isNameAvailable * updated confirmation frame * prepped through confirmation * testing tx * testing in staging env * removed trailing slash from url * created dynamic frameImage * updates to frame response values * linter fixes * linter fixes * improved error handling * added formattedTargetName to state for confirmation frame * updated initialSearchValueFrame to take an optional error argument * linter fixes * updated return value to handle errors * improved error handling * added error handling to frameImage * added strict types for formatEthPrice * linter fix * add strict types * linter fix * minor type fixes * added strict types * removed console log * fixed type issues and improved state handling * added user input sanitization * updated domain for testing * tx test fixes * more debugging * debugging * decoded message state before parsing * logging message and message state * updated resolver and registrar conroller addresses * added name and address args to registration * debugging api encoding * added test address * added addressData to name registration * added nameData to registration * added tx success screen * linter fixes * linter fix * added public images * added dynamic images and image generators * deleted unused image generator * constant initial search frame * updated error handling * updated frameResponses with new images and CTAs * linter fix * updated domain handling for registration image * added error logging to capture message and messageState * debugging background image * restoring correct bg image for registration frame * debugging * allowed name to be string * fixed type issues * strictly typed response data * fixed typing issues * refactored tx frame logic * refactored to use base.id instead of 8453 * added type for initialFrame * improved error messaging * export type * updated txSuccessFrame * updated txSuccess logic * tx success button is now a link * images in public directory * reworked placeholder landing page * explicitly typed initialFrame * refactored to use viem instead of ethers * refactored to use viem * linter fixes * updated domain logic * updated to base instead of sepolia, removed comments * created normalizeName utility * implemented normalizeName, moved validation logic into try block * undoing changes to names landing page * undoing changes to names landing page * updated image name and import path * updated image import and implemented util * updated image import * updated image handling * moved images out of public * improvements from pairing session * modifying domain for tx testing * added enum type to raw error messages, fixed linter issues * error message for invalid underscores * updated neynar keys to env vars * separate functions for formatting wei and convering wei to eth * created file for shared constants * updated imports * updated imports * updated imports * added conditional error if no neynar key is detected * unified domain value across pages * updated imports * minor refactor * removed unused import * reduced abi to necessary method * better variable name
- Loading branch information
1 parent
e45d1e5
commit a172475
Showing
20 changed files
with
937 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
import type { Metadata } from 'next'; | ||
import Image from 'apps/web/node_modules/next/image'; | ||
import Link from 'apps/web/node_modules/next/link'; | ||
import initialFrameImage from 'apps/web/pages/api/basenames/frame/assets/initial-image.png'; | ||
import { initialFrame } from 'apps/web/pages/api/basenames/frame/frameResponses'; | ||
|
||
export const metadata: Metadata = { | ||
metadataBase: new URL('https://base.org'), | ||
title: `Basenames | Frame`, | ||
description: | ||
'Basenames are a core onchain building block that enables anyone to establish their identity on Base by registering human-readable names for their address(es). They are a fully onchain solution which leverages ENS infrastructure deployed on Base.', | ||
openGraph: { | ||
title: `Basenames | Frame`, | ||
url: `/frames/names`, | ||
images: [initialFrameImage.src], | ||
}, | ||
twitter: { | ||
site: '@base', | ||
card: 'summary_large_image', | ||
}, | ||
other: { | ||
...(initialFrame as Record<string, string>), | ||
}, | ||
}; | ||
|
||
export default async function NameFrame() { | ||
return ( | ||
<div className="mt-[-96px] flex w-full flex-col items-center bg-black pb-[96px]"> | ||
<div className="flex h-screen w-full max-w-[1440px] flex-col items-center justify-center gap-12 px-8 py-8 pt-28"> | ||
<div className="relative flex aspect-[993/516] h-auto w-full max-w-[1024px] flex-col items-center"> | ||
<Link href="/names"> | ||
<Image src={initialFrameImage.src} alt="Claim a basename today" fill /> | ||
</Link> | ||
</div> | ||
</div> | ||
</div> | ||
); | ||
} |
57 changes: 57 additions & 0 deletions
57
apps/web/pages/api/basenames/[name]/getBasenameRegistrationPrice.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
import { NextApiRequest, NextApiResponse } from 'apps/web/node_modules/next/dist/shared/lib/utils'; | ||
import { createPublicClient, http } from 'viem'; | ||
import { base } from 'viem/chains'; | ||
import { | ||
REGISTER_CONTRACT_ABI, | ||
REGISTER_CONTRACT_ADDRESSES, | ||
normalizeName, | ||
} from 'apps/web/src/utils/usernames'; | ||
import { weiToEth } from 'apps/web/src/utils/weiToEth'; | ||
import { formatWei } from 'apps/web/src/utils/formatWei'; | ||
|
||
export default async function handler(req: NextApiRequest, res: NextApiResponse) { | ||
const { name, years } = req.query; | ||
|
||
try { | ||
const registrationPrice = await getBasenameRegistrationPrice(String(name), Number(years)); | ||
if (!registrationPrice) { | ||
throw new Error('Could not get registration price.'); | ||
} | ||
|
||
const registrationPriceInWei = formatWei(registrationPrice).toString(); | ||
const registrationPriceInEth = weiToEth(registrationPrice).toString(); | ||
return res.status(200).json({ registrationPriceInWei, registrationPriceInEth }); | ||
} catch (error) { | ||
console.error('Could not get registration price: ', error); | ||
return res.status(500).json(error); | ||
} | ||
} | ||
|
||
async function getBasenameRegistrationPrice(name: string, years: number): Promise<bigint | null> { | ||
const client = createPublicClient({ | ||
chain: base, | ||
transport: http(), | ||
}); | ||
try { | ||
const normalizedName = normalizeName(name); | ||
if (!normalizedName) { | ||
throw new Error('Invalid ENS domain name'); | ||
} | ||
|
||
const price = await client.readContract({ | ||
address: REGISTER_CONTRACT_ADDRESSES[base.id], | ||
abi: REGISTER_CONTRACT_ABI, | ||
functionName: 'registerPrice', | ||
args: [normalizedName, secondsInYears(years)], | ||
}); | ||
return price; | ||
} catch (error) { | ||
console.error('Could not get claim price:', error); | ||
return null; | ||
} | ||
} | ||
|
||
function secondsInYears(years: number) { | ||
const secondsPerYear = 365.25 * 24 * 60 * 60; // .25 accounting for leap years | ||
return BigInt(Math.round(years * secondsPerYear)); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import { NextApiRequest, NextApiResponse } from 'apps/web/node_modules/next/dist/shared/lib/utils'; | ||
import { base } from 'viem/chains'; | ||
import { getBasenameAvailable } from 'apps/web/src/utils/usernames'; | ||
|
||
export type IsNameAvailableResponse = { | ||
nameIsAvailable: boolean; | ||
}; | ||
|
||
export default async function handler(req: NextApiRequest, res: NextApiResponse) { | ||
const { name } = req.query; | ||
try { | ||
const isNameAvailableResponse = await getBasenameAvailable(String(name), base); | ||
const responseData: IsNameAvailableResponse = { | ||
nameIsAvailable: isNameAvailableResponse, | ||
}; | ||
return res.status(200).json(responseData); | ||
} catch (error) { | ||
console.error('Could not read name availability:', error); | ||
return res.status(500).json({ error: 'Could not determine name availability' }); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
import { NextApiRequest, NextApiResponse } from 'next/dist/shared/lib/utils'; | ||
import { inputSearchValueFrame } from 'apps/web/pages/api/basenames/frame/frameResponses'; | ||
|
||
export default async function handler(req: NextApiRequest, res: NextApiResponse) { | ||
if (req.method !== 'POST') { | ||
return res.status(405).json({ error: `Search Screen — Method (${req.method}) Not Allowed` }); | ||
} | ||
|
||
try { | ||
return res.status(200).setHeader('Content-Type', 'text/html').send(inputSearchValueFrame); | ||
} catch (error) { | ||
console.error('Could not process request:', error); | ||
return res.status(500).json({ error: 'Internal Server Error' }); | ||
} | ||
} |
49 changes: 49 additions & 0 deletions
49
apps/web/pages/api/basenames/frame/02_validateSearchInputAndSetYears.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
import { NextApiRequest, NextApiResponse } from 'next/dist/shared/lib/utils'; | ||
import { FrameRequest } from '@coinbase/onchainkit/frame'; | ||
import { formatDefaultUsername, validateEnsDomainName } from 'apps/web/src/utils/usernames'; | ||
import type { IsNameAvailableResponse } from 'apps/web/pages/api/basenames/[name]/isNameAvailable'; | ||
import { | ||
retryInputSearchValueFrame, | ||
setYearsFrame, | ||
} from 'apps/web/pages/api/basenames/frame/frameResponses'; | ||
import { DOMAIN } from 'apps/web/pages/api/basenames/frame/constants'; | ||
|
||
export default async function handler(req: NextApiRequest, res: NextApiResponse) { | ||
if (req.method !== 'POST') { | ||
return res.status(405).json({ error: `Set Years Screen — Method (${req.method}) Not Allowed` }); | ||
} | ||
|
||
try { | ||
const body = req.body as FrameRequest; | ||
const { untrustedData } = body; | ||
const targetName: string = encodeURIComponent(untrustedData.inputText); | ||
|
||
const { valid, message } = validateEnsDomainName(targetName); | ||
if (!valid) { | ||
return res | ||
.status(200) | ||
.setHeader('Content-Type', 'text/html') | ||
.send(retryInputSearchValueFrame(message)); | ||
} | ||
|
||
const isNameAvailableResponse = await fetch( | ||
`${DOMAIN}/api/basenames/${targetName}/isNameAvailable`, | ||
); | ||
const isNameAvailableResponseData = await isNameAvailableResponse.json(); | ||
const { nameIsAvailable } = isNameAvailableResponseData as IsNameAvailableResponse; | ||
if (!nameIsAvailable) { | ||
return res | ||
.status(200) | ||
.setHeader('Content-Type', 'text/html') | ||
.send(retryInputSearchValueFrame('Name unavailable')); | ||
} | ||
|
||
const formattedTargetName = await formatDefaultUsername(targetName); | ||
return res | ||
.status(200) | ||
.setHeader('Content-Type', 'text/html') | ||
.send(setYearsFrame(targetName, formattedTargetName)); | ||
} catch (error) { | ||
return res.status(500).json({ error }); // TODO: figure out error state for the frame BAPP-452 | ||
} | ||
} |
64 changes: 64 additions & 0 deletions
64
apps/web/pages/api/basenames/frame/03_getPriceAndConfirm.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
import { NextApiRequest, NextApiResponse } from 'next/dist/shared/lib/utils'; | ||
import { FrameRequest } from '@coinbase/onchainkit/frame'; | ||
import { | ||
confirmationFrame, | ||
buttonIndexToYears, | ||
} from 'apps/web/pages/api/basenames/frame/frameResponses'; | ||
import { DOMAIN } from 'apps/web/pages/api/basenames/frame/constants'; | ||
|
||
type ButtonIndex = 1 | 2 | 3 | 4; | ||
const validButtonIndexes: readonly ButtonIndex[] = [1, 2, 3, 4] as const; | ||
|
||
type GetBasenameRegistrationPriceResponseType = { | ||
registrationPriceInWei: string; | ||
registrationPriceInEth: string; | ||
}; | ||
|
||
type ConfirmationFrameStateType = { | ||
targetName: string; | ||
formattedTargetName: string; | ||
}; | ||
|
||
export default async function handler(req: NextApiRequest, res: NextApiResponse) { | ||
if (req.method !== 'POST') { | ||
return res.status(405).json({ error: `Confirm Screen — Method (${req.method}) Not Allowed` }); | ||
} | ||
|
||
const body = req.body as FrameRequest; | ||
const { untrustedData } = body; | ||
const messageState = JSON.parse( | ||
decodeURIComponent(untrustedData.state), | ||
) as ConfirmationFrameStateType; | ||
const targetName = encodeURIComponent(messageState.targetName); | ||
const formattedTargetName = messageState.formattedTargetName; | ||
|
||
const buttonIndex = untrustedData.buttonIndex as ButtonIndex; | ||
if (!validButtonIndexes.includes(buttonIndex)) { | ||
return res.status(500).json({ error: 'Internal Server Error' }); | ||
} | ||
const targetYears = buttonIndexToYears[buttonIndex]; | ||
|
||
const getRegistrationPriceResponse = await fetch( | ||
`${DOMAIN}/api/basenames/${targetName}/getBasenameRegistrationPrice?years=${targetYears}`, | ||
); | ||
const getRegistrationPriceResponseData = await getRegistrationPriceResponse.json(); | ||
const { registrationPriceInWei, registrationPriceInEth } = | ||
getRegistrationPriceResponseData as GetBasenameRegistrationPriceResponseType; | ||
|
||
try { | ||
return res | ||
.status(200) | ||
.setHeader('Content-Type', 'text/html') | ||
.send( | ||
confirmationFrame( | ||
targetName, | ||
formattedTargetName, | ||
targetYears, | ||
registrationPriceInWei, | ||
registrationPriceInEth, | ||
), | ||
); | ||
} catch (error) { | ||
return res.status(500).json({ error: 'Internal Server Error' }); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
import { NextApiRequest, NextApiResponse } from 'next/dist/shared/lib/utils'; | ||
import { FrameRequest, getFrameMessage } from '@coinbase/onchainkit/frame'; | ||
import { txSuccessFrame } from 'apps/web/pages/api/basenames/frame/frameResponses'; | ||
import { NEYNAR_API_KEY } from 'apps/web/pages/api/basenames/frame/constants'; | ||
import type { TxFrameStateType } from 'apps/web/pages/api/basenames/frame/tx'; | ||
|
||
if (!NEYNAR_API_KEY) { | ||
throw new Error('missing NEYNAR_API_KEY'); | ||
} | ||
|
||
export default async function handler(req: NextApiRequest, res: NextApiResponse) { | ||
if (req.method !== 'POST') { | ||
return res.status(405).json({ error: `TxSuccess Screen — Method (${req.method}) Not Allowed` }); | ||
} | ||
|
||
const body = req.body as FrameRequest; | ||
let message; | ||
let isValid; | ||
let name; | ||
|
||
try { | ||
const result = await getFrameMessage(body, { | ||
neynarApiKey: NEYNAR_API_KEY, | ||
}); | ||
isValid = result.isValid; | ||
message = result.message; | ||
if (!isValid) { | ||
throw new Error('Message is not valid'); | ||
} | ||
if (!message) { | ||
throw new Error('No message received'); | ||
} | ||
|
||
const messageState = JSON.parse( | ||
decodeURIComponent(message.state?.serialized), | ||
) as TxFrameStateType; | ||
if (!messageState) { | ||
throw new Error('No message state received'); | ||
} | ||
name = messageState.targetName; | ||
return res.status(200).setHeader('Content-Type', 'text/html').send(txSuccessFrame(name)); | ||
} catch (e) { | ||
return res.status(500).json({ error: e }); | ||
} | ||
} |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Oops, something went wrong.