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

Feat/basenames frame #862

Merged
merged 117 commits into from
Aug 22, 2024
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
117 commits
Select commit Hold shift + click to select a range
ea4b305
early wiring for basenames frame
brendan-defi Aug 16, 2024
a6b7d22
Merge branch 'master' into feat/basenames-frame
brendan-defi Aug 16, 2024
4822965
renamed mint to inputSearchValue, improved logic
brendan-defi Aug 16, 2024
4252af5
changed response object
brendan-defi Aug 16, 2024
05dafa2
improved flow for basenames frame
brendan-defi Aug 16, 2024
cdf360f
updated metadata for frame landing page
brendan-defi Aug 16, 2024
d343704
stronger type checking for isNameAvailable
brendan-defi Aug 16, 2024
620d5fa
removed unnecessary code
brendan-defi Aug 16, 2024
1bbbad4
improved logic
brendan-defi Aug 16, 2024
2b7950d
created API endpoint to fetch registration price
brendan-defi Aug 16, 2024
8603a69
deleted unused frame
brendan-defi Aug 16, 2024
5b08c7d
refactored isNameAvailable
brendan-defi Aug 16, 2024
960f207
updated confirmation frame
brendan-defi Aug 16, 2024
e9395b2
prepped through confirmation
brendan-defi Aug 16, 2024
46849fd
testing tx
brendan-defi Aug 16, 2024
d2c83be
testing in staging env
brendan-defi Aug 18, 2024
0abedea
removed trailing slash from url
brendan-defi Aug 18, 2024
6795235
created dynamic frameImage
brendan-defi Aug 19, 2024
fb60a69
updates to frame response values
brendan-defi Aug 19, 2024
cdd65cf
linter fixes
brendan-defi Aug 19, 2024
96b598e
linter fixes
brendan-defi Aug 19, 2024
69b4b86
improved error handling
brendan-defi Aug 19, 2024
e918029
added formattedTargetName to state for confirmation frame
brendan-defi Aug 19, 2024
d553228
updated initialSearchValueFrame to take an optional error argument
brendan-defi Aug 19, 2024
cf79697
linter fixes
brendan-defi Aug 19, 2024
08b8da4
updated return value to handle errors
brendan-defi Aug 19, 2024
b074988
improved error handling
brendan-defi Aug 19, 2024
0343b25
added error handling to frameImage
brendan-defi Aug 19, 2024
21a904d
added strict types for formatEthPrice
brendan-defi Aug 19, 2024
a161cdc
linter fix
brendan-defi Aug 19, 2024
245a2c3
add strict types
brendan-defi Aug 19, 2024
6c16a8b
linter fix
brendan-defi Aug 19, 2024
f90052a
minor type fixes
brendan-defi Aug 19, 2024
322c803
added strict types
brendan-defi Aug 19, 2024
ffefd7c
removed console log
brendan-defi Aug 19, 2024
b58088a
fixed type issues and improved state handling
brendan-defi Aug 19, 2024
564f395
added user input sanitization
brendan-defi Aug 19, 2024
4244a7c
updated domain for testing
brendan-defi Aug 19, 2024
58f89c1
tx test fixes
brendan-defi Aug 19, 2024
75bc3ee
more debugging
brendan-defi Aug 19, 2024
1f0ee81
debugging
brendan-defi Aug 19, 2024
c3dd7e1
decoded message state before parsing
brendan-defi Aug 19, 2024
d604b5c
logging message and message state
brendan-defi Aug 19, 2024
1677a1c
Merge branch 'master' into feat/basenames-frame
brendan-defi Aug 19, 2024
41f307a
updated resolver and registrar conroller addresses
brendan-defi Aug 19, 2024
35bc4f0
added name and address args to registration
brendan-defi Aug 19, 2024
ad1fce4
debugging api encoding
brendan-defi Aug 19, 2024
c351da3
added test address
brendan-defi Aug 19, 2024
74fc0eb
added addressData to name registration
brendan-defi Aug 19, 2024
ece2d58
added nameData to registration
brendan-defi Aug 19, 2024
42ec6ce
added tx success screen
brendan-defi Aug 19, 2024
da9491d
linter fixes
brendan-defi Aug 19, 2024
e8eae4d
linter fix
brendan-defi Aug 19, 2024
dd2843c
added public images
brendan-defi Aug 20, 2024
5fbb89d
added dynamic images and image generators
brendan-defi Aug 20, 2024
4fb37bf
deleted unused image generator
brendan-defi Aug 20, 2024
4a31480
constant initial search frame
brendan-defi Aug 20, 2024
f6597f7
updated error handling
brendan-defi Aug 20, 2024
c0d6851
updated frameResponses with new images and CTAs
brendan-defi Aug 20, 2024
7d96115
linter fix
brendan-defi Aug 20, 2024
ad351d6
Merge branch 'master' into feat/basenames-frame
brendan-defi Aug 20, 2024
dc45e75
updated domain handling for registration image
brendan-defi Aug 20, 2024
9032661
added error logging to capture message and messageState
brendan-defi Aug 20, 2024
fd0b9fe
debugging background image
brendan-defi Aug 20, 2024
5d59c78
restoring correct bg image for registration frame
brendan-defi Aug 20, 2024
eb117c1
Merge branch 'master' into feat/basenames-frame
brendan-defi Aug 21, 2024
dcc30bd
debugging
brendan-defi Aug 21, 2024
5a3deff
allowed name to be string
brendan-defi Aug 21, 2024
6fa11a3
fixed type issues
brendan-defi Aug 21, 2024
e76e685
strictly typed response data
brendan-defi Aug 21, 2024
1ede3ab
fixed typing issues
brendan-defi Aug 21, 2024
761125e
refactored tx frame logic
brendan-defi Aug 21, 2024
03e01c8
refactored to use base.id instead of 8453
brendan-defi Aug 21, 2024
341c443
added type for initialFrame
brendan-defi Aug 21, 2024
8d27627
improved error messaging
brendan-defi Aug 21, 2024
53d0a71
export type
brendan-defi Aug 21, 2024
fa340fb
updated txSuccessFrame
brendan-defi Aug 21, 2024
fd89a5b
updated txSuccess logic
brendan-defi Aug 21, 2024
17f0f6e
tx success button is now a link
brendan-defi Aug 22, 2024
71ef280
images in public directory
brendan-defi Aug 22, 2024
1b8a2c5
reworked placeholder landing page
brendan-defi Aug 22, 2024
eba240d
Merge branch 'master' into feat/basenames-frame
brendan-defi Aug 22, 2024
560d781
explicitly typed initialFrame
brendan-defi Aug 22, 2024
c1ef779
refactored to use viem instead of ethers
brendan-defi Aug 22, 2024
86b4f4e
refactored to use viem
brendan-defi Aug 22, 2024
5d0239a
linter fixes
brendan-defi Aug 22, 2024
97418ce
updated domain logic
brendan-defi Aug 22, 2024
3b63d2c
updated to base instead of sepolia, removed comments
brendan-defi Aug 22, 2024
ee922de
created normalizeName utility
brendan-defi Aug 22, 2024
c904fae
implemented normalizeName, moved validation logic into try block
brendan-defi Aug 22, 2024
cc7eee7
undoing changes to names landing page
brendan-defi Aug 22, 2024
4d99359
undoing changes to names landing page
brendan-defi Aug 22, 2024
fc66b62
Merge branch 'master' into feat/basenames-frame
brendan-defi Aug 22, 2024
77915fa
updated image name and import path
brendan-defi Aug 22, 2024
18cc0b4
updated image import and implemented util
brendan-defi Aug 22, 2024
730cfed
updated image import
brendan-defi Aug 22, 2024
ea66bdd
updated image handling
brendan-defi Aug 22, 2024
452b40d
moved images out of public
brendan-defi Aug 22, 2024
6ed88e2
improvements from pairing session
brendan-defi Aug 22, 2024
760d29a
modifying domain for tx testing
brendan-defi Aug 22, 2024
f4d8691
added enum type to raw error messages, fixed linter issues
brendan-defi Aug 22, 2024
2b8546a
error message for invalid underscores
brendan-defi Aug 22, 2024
535390d
Merge branch 'master' into feat/basenames-frame
brendan-defi Aug 22, 2024
c5990d3
Merge branch 'master' into feat/basenames-frame
brendan-defi Aug 22, 2024
a5e38f0
updated neynar keys to env vars
brendan-defi Aug 22, 2024
ff73d33
separate functions for formatting wei and convering wei to eth
brendan-defi Aug 22, 2024
404d83f
created file for shared constants
brendan-defi Aug 22, 2024
2de7f5e
updated imports
brendan-defi Aug 22, 2024
0c10f22
updated imports
brendan-defi Aug 22, 2024
3cd8f24
updated imports
brendan-defi Aug 22, 2024
4d193e3
added conditional error if no neynar key is detected
brendan-defi Aug 22, 2024
69607fb
unified domain value across pages
brendan-defi Aug 22, 2024
c1cafb7
updated imports
brendan-defi Aug 22, 2024
615bc71
minor refactor
brendan-defi Aug 22, 2024
a05e621
removed unused import
brendan-defi Aug 22, 2024
b81cdc2
reduced abi to necessary method
brendan-defi Aug 22, 2024
10baaa9
better variable name
brendan-defi Aug 22, 2024
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
26 changes: 26 additions & 0 deletions apps/web/app/(base-org)/frames/names/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import type { Metadata } from 'next';
import basenameCover from 'apps/web/app/(basenames)/names/basename_cover.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: [basenameCover.src],
},
twitter: {
site: '@base',
card: 'summary_large_image',
},
other: {
...initialFrame,
},
};

export default async function NameFrame() {
return <div>Hello Neo</div>;
}
4 changes: 2 additions & 2 deletions apps/web/app/(basenames)/names/page.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { Suspense } from 'react';
import type { Metadata } from 'next';
import RegistrationProviders from 'apps/web/app/(basenames)/names/RegistrationProviders';
import ErrorsProvider from 'apps/web/contexts/Errors';
import RegistrationFlow from 'apps/web/src/components/Basenames/RegistrationFlow';
import type { Metadata } from 'next';
import { Suspense } from 'react';
import basenameCover from './basename_cover.png';

export const metadata: Metadata = {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { NextApiRequest, NextApiResponse } from 'apps/web/node_modules/next/dist/shared/lib/utils';
import { ethers } from 'ethers';
import {
normalizeEnsDomainName,
REGISTER_CONTRACT_ABI,
REGISTER_CONTRACT_ADDRESSES,
validateEnsDomainName,
} from 'apps/web/src/utils/usernames';
import { formatEthPrice, formatWeiPrice } from 'apps/web/src/utils/formatEthPrice';

const url = 'https://mainnet.base.org';
const provider = new ethers.providers.JsonRpcProvider(url);
const baseMainnetChainId = 8453;
const contractAddress = REGISTER_CONTRACT_ADDRESSES[baseMainnetChainId];
const contract = new ethers.Contract(contractAddress, REGISTER_CONTRACT_ABI, provider);

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const { name, years } = req.query;

try {
const registrationPrice = await getBasenameRegistrationPrice(String(name), Number(years));
const registrationPriceInWei = formatWeiPrice(registrationPrice).toString();
const registrationPriceInEth = formatEthPrice(registrationPrice);
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) {
const normalizedName = normalizeName(name);
if (!normalizedName) {
throw new Error('Invalid ENS domain name');
}

try {
const claimPrice = await contract.registerPrice(normalizedName, secondsInYears(years));
return claimPrice;
} catch (error) {
console.error('Could not get claim price:', error);
return null;
}
}

// async function getDiscountedBasenameClaimPrice(
// name: string,
// years: number,
// discountKey: `0x${string}` | undefined,
// ) {
// const normalizedName = normalizeName(name);
// if (!normalizedName) {
// throw new Error('Invalid ENS domain name');
// }

// try {
// const discountedClaimPrice = await contract.discountedRegisterPrice(
// normalizedName,
// secondsInYears(years),
// discountKey ?? '0x',
// );
// return discountedClaimPrice;
// } catch (error) {
// console.error('Could not get discounted claim price:', error);
// }
// }

function normalizeName(name: string) {
const normalizedName: string = normalizeEnsDomainName(name);
const { valid } = validateEnsDomainName(name);

if (!valid) {
return null;
}
return normalizedName;
}

function secondsInYears(years: number) {
const secondsPerYear = 365.25 * 24 * 60 * 60; // .25 accounting for leap years
return BigInt(Math.round(years * secondsPerYear));
}
45 changes: 45 additions & 0 deletions apps/web/pages/api/basenames/[name]/isNameAvailable.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { NextApiRequest, NextApiResponse } from 'apps/web/node_modules/next/dist/shared/lib/utils';
import {
normalizeEnsDomainName,
REGISTER_CONTRACT_ABI,
REGISTER_CONTRACT_ADDRESSES,
validateEnsDomainName,
} from 'apps/web/src/utils/usernames';
import { ethers } from 'ethers';

const url = 'https://mainnet.base.org';
const provider = new ethers.providers.JsonRpcProvider(url);
const baseMainnetChainId = 8453;
const contractAddress = REGISTER_CONTRACT_ADDRESSES[baseMainnetChainId];
const contract = new ethers.Contract(contractAddress, REGISTER_CONTRACT_ABI, provider);

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const { name } = req.query;
try {
const nameIsAvailable = await isNameAvailable(String(name));
return res.status(200).json({ nameIsAvailable });
} catch (error) {
console.error('Could not read name availability:', error);
return res.status(500).json({ error: 'Could not determine name availability' });
}
}

async function isNameAvailable(name: string): Promise<boolean> {
const normalizedName: string = normalizeEnsDomainName(name);
const { valid } = validateEnsDomainName(name);

if (!valid) {
throw new Error('Invalid ENS domain name');
}

try {
const available = await contract.available(normalizedName);
if (typeof available !== 'boolean') {
throw new Error('Invalid return type, expected boolean');
}
return available;
} catch (error) {
console.error('Error checking name availability:', error);
throw error;
}
}
35 changes: 35 additions & 0 deletions apps/web/pages/api/basenames/frame/confirmation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { NextApiRequest, NextApiResponse } from 'next/dist/shared/lib/utils';
import { FrameRequest, getFrameMessage, getFrameHtmlResponse } from '@coinbase/onchainkit/frame';
import {
confirmationFrame,
buttonIndexToYears,
} 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: 'Method Not Allowed' });
}

const body: FrameRequest = req.body;
const { untrustedData } = body;
console.log('confirmation.....', { untrustedData });

const messageState = JSON.parse(untrustedData.state);
const targetName = messageState.targetName;
const targetYears = buttonIndexToYears[untrustedData.buttonIndex];

const response = await fetch(
`http://localhost:3000/api/basenames/${targetName}/getBasenameRegistrationPrice?years=${targetYears}`,
);
Fixed Show fixed Hide fixed
const { registrationPriceInWei, registrationPriceInEth} = await response.json();

try {
return res
.status(200)
.setHeader('Content-Type', 'text/html')
.send(confirmationFrame(targetName, targetYears, registrationPriceInWei, registrationPriceInEth));
} catch (error) {
console.error('Failed to fetch questions:', error);
return res.status(500).json({ error: 'Internal Server Error' });
}
}
97 changes: 97 additions & 0 deletions apps/web/pages/api/basenames/frame/frameResponses.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import { getFrameMetadata, getFrameHtmlResponse } from '@coinbase/onchainkit/frame';
brendan-defi marked this conversation as resolved.
Show resolved Hide resolved

export const initialFrame = getFrameMetadata({
buttons: [
{
label: 'Claim a basename today!',
},
],
image: {
src: `http://localhost:3000/images/basenames/contract-uri/feature-image.png`,
},
postUrl: `http://localhost:3000/api/basenames/frame/inputSearchValue`,
});

export const inputSearchValueFrame = getFrameHtmlResponse({
buttons: [
{
action: 'post',
label: 'Claim name',
target: `http://localhost:3000/api/basenames/frame/validateSearchInputAndSetYears`,
},
],
image: {
src: `http://localhost:3000/images/basenames/contract-uri/cover-image.png`,
},
input: {
text: 'Search for a name',
},
postUrl: `http://localhost:3000/api/basenames/frame/validateSearchInputAndSetYears`,
});

export const buttonIndexToYears = {
1: 1,
2: 5,
3: 10,
4: 100,
};

export const setYearsFrame = (targetName: string) =>
getFrameHtmlResponse({
buttons: [
{
action: 'post',
label: '1 year',
target: `http://localhost:3000/api/basenames/frame/confirmation`,
},
{
action: 'post',
label: '5 years',
target: `http://localhost:3000/api/basenames/frame/confirmation`,
},
{
action: 'post',
label: '10 years',
target: `http://localhost:3000/api/basenames/frame/confirmation`,
},
{
action: 'post',
label: '100 years',
target: `http://localhost:3000/api/basenames/frame/confirmation`,
},
],
image: {
src: `http://localhost:3000/api/basenames/${targetName}/assets/coverImage.png`,
},
postUrl: `http://localhost:3000/api/basenames/frame/confirmation`,
state: {
targetName,
},
});

export const confirmationFrame = (
targetName: string,
targetYears: number,
registrationPriceInWei: string,
registrationPriceInEth: number,
) =>
getFrameHtmlResponse({
buttons: [
{
action: 'tx',
label: `Confirm: Register ${targetName} for ${targetYears} years for ${registrationPriceInEth} ETH`,
target: `http://localhost:3000/api/basenames/frame/tx`,
},
],
image: {
src: `http://localhost:3000/api/basenames/${targetName}/assets/coverImage.png`,
},
target: `http://localhost:3000/api/basenames/frame/tx`,
postUrl: `http://localhost:3000/api/basenames/frame/tx`,
state: {
name: targetName,
years: targetYears,
priceInWei: registrationPriceInWei,
priceInEth: registrationPriceInEth,
},
});
15 changes: 15 additions & 0 deletions apps/web/pages/api/basenames/frame/inputSearchValue.ts
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: 'Method Not Allowed' });
}

try {
return res.status(200).setHeader('Content-Type', 'text/html').send(inputSearchValueFrame);
} catch (error) {
console.error('Failed to fetch questions:', error);
return res.status(500).json({ error: 'Internal Server Error' });
}
}
Loading
Loading