From ddec3dadcc260e0d4d0ed2b43c502e0915a14203 Mon Sep 17 00:00:00 2001 From: jnsdls Date: Mon, 28 Oct 2024 23:24:52 +0000 Subject: [PATCH] Refactor Farcaster frame validation (#5211) --- apps/dashboard/src/classes/CoinbaseKit.ts | 12 ------------ apps/dashboard/src/lib/farcaster-frames.ts | 18 ++++++++++++++++++ .../src/pages/api/frame/base/get-tx-frame.ts | 15 ++++++--------- apps/dashboard/src/pages/api/frame/connect.ts | 12 ++++++------ .../src/pages/api/frame/degen/mint.ts | 12 +++++++----- apps/dashboard/src/pages/api/frame/engine.ts | 11 ++++++----- .../src/pages/api/frame/superchain.ts | 7 +++---- .../src/pages/api/superchain/frame.ts | 7 +++---- apps/dashboard/src/utils/farcaster.ts | 8 -------- apps/dashboard/src/utils/tx-frame.ts | 8 -------- 10 files changed, 49 insertions(+), 61 deletions(-) delete mode 100644 apps/dashboard/src/classes/CoinbaseKit.ts create mode 100644 apps/dashboard/src/lib/farcaster-frames.ts delete mode 100644 apps/dashboard/src/utils/farcaster.ts diff --git a/apps/dashboard/src/classes/CoinbaseKit.ts b/apps/dashboard/src/classes/CoinbaseKit.ts deleted file mode 100644 index c658840a7fb..00000000000 --- a/apps/dashboard/src/classes/CoinbaseKit.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { type FrameRequest, getFrameMessage } from "@coinbase/onchainkit"; - -// biome-ignore lint/complexity/noStaticOnlyClass: FIXME: refactor to standalone functions -export class CoinbaseKit { - public static validateMessage = async (body: FrameRequest) => { - const { isValid, message } = await getFrameMessage(body, { - neynarApiKey: process.env.NEYNAR_API_KEY, - }); - - return { isValid, message }; - }; -} diff --git a/apps/dashboard/src/lib/farcaster-frames.ts b/apps/dashboard/src/lib/farcaster-frames.ts new file mode 100644 index 00000000000..d075b7a4782 --- /dev/null +++ b/apps/dashboard/src/lib/farcaster-frames.ts @@ -0,0 +1,18 @@ +import { + type FrameRequest, + type FrameValidationData, + getFrameMessage, +} from "@coinbase/onchainkit"; + +export function validateFrameMessage(body: FrameRequest) { + return getFrameMessage(body, { + neynarApiKey: process.env.NEYNAR_API_KEY, + }); +} + +export function getFarcasterAccountAddress( + interactor: FrameValidationData["interactor"], +) { + // Get the first verified account or custody account if first verified account doesn't exist + return interactor.verified_accounts[0] ?? interactor.custody_address; +} diff --git a/apps/dashboard/src/pages/api/frame/base/get-tx-frame.ts b/apps/dashboard/src/pages/api/frame/base/get-tx-frame.ts index e75b586a572..057fc3621c3 100644 --- a/apps/dashboard/src/pages/api/frame/base/get-tx-frame.ts +++ b/apps/dashboard/src/pages/api/frame/base/get-tx-frame.ts @@ -1,14 +1,13 @@ import { getThirdwebClient } from "@/constants/thirdweb.server"; -import type { FrameRequest } from "@coinbase/onchainkit"; -import { CoinbaseKit } from "classes/CoinbaseKit"; +import { + getFarcasterAccountAddress, + validateFrameMessage, +} from "lib/farcaster-frames"; import type { NextApiRequest, NextApiResponse } from "next"; import { getContract } from "thirdweb"; import { base } from "thirdweb/chains"; import { errorResponse } from "utils/api"; -import { - getErc721PreparedEncodedData, - getFarcasterAccountAddress, -} from "utils/tx-frame"; +import { getErc721PreparedEncodedData } from "utils/tx-frame"; import { abi } from "./abi"; // https://thirdweb.com/base/0x352810fF1c51a42B568662D46570A30B590a715a @@ -24,9 +23,7 @@ export default async function handler( } // Validate message with @coinbase/onchainkit - const { isValid, message } = await CoinbaseKit.validateMessage( - req.body as FrameRequest, - ); + const { isValid, message } = await validateFrameMessage(req.body); // Validate if message is valid if (!isValid || !message) { diff --git a/apps/dashboard/src/pages/api/frame/connect.ts b/apps/dashboard/src/pages/api/frame/connect.ts index 15680860abc..2ecfb3ce0ab 100644 --- a/apps/dashboard/src/pages/api/frame/connect.ts +++ b/apps/dashboard/src/pages/api/frame/connect.ts @@ -1,13 +1,14 @@ -import type { FrameRequest } from "@coinbase/onchainkit"; -import { CoinbaseKit } from "classes/CoinbaseKit"; import { ConnectFrame } from "classes/ConnectFrame"; +import { + getFarcasterAccountAddress, + validateFrameMessage, +} from "lib/farcaster-frames"; import type { NextRequest } from "next/server"; import { errorResponse, redirectResponse, successHtmlResponse, } from "utils/api"; -import { getFarcasterAccountAddress } from "utils/farcaster"; export const config = { runtime: "edge", @@ -18,9 +19,8 @@ export default async function handler(req: NextRequest) { return errorResponse("Invalid method", 400); } - const body = (await req.json()) as FrameRequest; - - const { isValid, message } = await CoinbaseKit.validateMessage(body); + const body = await req.json(); + const { isValid, message } = await validateFrameMessage(body); if (!isValid || !message) { return errorResponse("Invalid message", 400); diff --git a/apps/dashboard/src/pages/api/frame/degen/mint.ts b/apps/dashboard/src/pages/api/frame/degen/mint.ts index f555ad37969..a8b3d834943 100644 --- a/apps/dashboard/src/pages/api/frame/degen/mint.ts +++ b/apps/dashboard/src/pages/api/frame/degen/mint.ts @@ -1,6 +1,9 @@ -import { type FrameRequest, getFrameHtmlResponse } from "@coinbase/onchainkit"; -import { CoinbaseKit } from "classes/CoinbaseKit"; +import { getFrameHtmlResponse } from "@coinbase/onchainkit"; import { ThirdwebDegenEngine } from "classes/ThirdwebDegenEngine"; +import { + getFarcasterAccountAddress, + validateFrameMessage, +} from "lib/farcaster-frames"; import { getAbsoluteUrl } from "lib/vercel-utils"; import type { NextRequest } from "next/server"; import { @@ -8,7 +11,6 @@ import { redirectResponse, successHtmlResponse, } from "utils/api"; -import { getFarcasterAccountAddress } from "utils/farcaster"; import { shortenAddress } from "utils/string"; const postUrl = `${getAbsoluteUrl()}/api/frame/degen/mint`; @@ -26,9 +28,9 @@ export default async function handler(req: NextRequest) { return errorResponse("Invalid method", 400); } - const body = (await req.json()) as FrameRequest; + const body = await req.json(); - const { isValid, message } = await CoinbaseKit.validateMessage(body); + const { isValid, message } = await validateFrameMessage(body); if (!isValid || !message) { return errorResponse("Invalid message", 400); diff --git a/apps/dashboard/src/pages/api/frame/engine.ts b/apps/dashboard/src/pages/api/frame/engine.ts index 15680860abc..f502485caf9 100644 --- a/apps/dashboard/src/pages/api/frame/engine.ts +++ b/apps/dashboard/src/pages/api/frame/engine.ts @@ -1,13 +1,14 @@ -import type { FrameRequest } from "@coinbase/onchainkit"; -import { CoinbaseKit } from "classes/CoinbaseKit"; import { ConnectFrame } from "classes/ConnectFrame"; +import { + getFarcasterAccountAddress, + validateFrameMessage, +} from "lib/farcaster-frames"; import type { NextRequest } from "next/server"; import { errorResponse, redirectResponse, successHtmlResponse, } from "utils/api"; -import { getFarcasterAccountAddress } from "utils/farcaster"; export const config = { runtime: "edge", @@ -18,9 +19,9 @@ export default async function handler(req: NextRequest) { return errorResponse("Invalid method", 400); } - const body = (await req.json()) as FrameRequest; + const body = await req.json(); - const { isValid, message } = await CoinbaseKit.validateMessage(body); + const { isValid, message } = await validateFrameMessage(body); if (!isValid || !message) { return errorResponse("Invalid message", 400); diff --git a/apps/dashboard/src/pages/api/frame/superchain.ts b/apps/dashboard/src/pages/api/frame/superchain.ts index a7a32b4259b..174b2aa624a 100644 --- a/apps/dashboard/src/pages/api/frame/superchain.ts +++ b/apps/dashboard/src/pages/api/frame/superchain.ts @@ -1,6 +1,5 @@ -import type { FrameRequest } from "@coinbase/onchainkit"; -import { CoinbaseKit } from "classes/CoinbaseKit"; import { SuperChainFrame } from "classes/SuperChainFrame"; +import { validateFrameMessage } from "lib/farcaster-frames"; import { finalGrowthPlanFrameMetaData, growthPlanFrameMetaData, @@ -23,9 +22,9 @@ export default async function handler(req: NextRequest) { return errorResponse("Invalid method", 400); } - const body = (await req.json()) as FrameRequest; + const body = await req.json(); - const { isValid, message } = await CoinbaseKit.validateMessage(body); + const { isValid, message } = await validateFrameMessage(body); if (!isValid || !message) { return errorResponse("Invalid message", 400); diff --git a/apps/dashboard/src/pages/api/superchain/frame.ts b/apps/dashboard/src/pages/api/superchain/frame.ts index af23fff6e84..9ae86eed0eb 100644 --- a/apps/dashboard/src/pages/api/superchain/frame.ts +++ b/apps/dashboard/src/pages/api/superchain/frame.ts @@ -1,7 +1,6 @@ -import type { FrameRequest } from "@coinbase/onchainkit"; import * as Sentry from "@sentry/nextjs"; -import { CoinbaseKit } from "classes/CoinbaseKit"; import { SuperChainFormFrame } from "classes/SuperchainFormFrame"; +import { validateFrameMessage } from "lib/farcaster-frames"; import type { NextRequest } from "next/server"; import { errorResponse, @@ -27,9 +26,9 @@ export default async function handler(req: NextRequest) { } try { - const body = (await req.json()) as FrameRequest; + const body = await req.json(); - const { isValid, message } = await CoinbaseKit.validateMessage(body); + const { isValid, message } = await validateFrameMessage(body); if (!isValid || !message) { return errorResponse("Invalid message", 400); diff --git a/apps/dashboard/src/utils/farcaster.ts b/apps/dashboard/src/utils/farcaster.ts deleted file mode 100644 index d2f279e8dc7..00000000000 --- a/apps/dashboard/src/utils/farcaster.ts +++ /dev/null @@ -1,8 +0,0 @@ -import type { FrameValidationData } from "@coinbase/onchainkit"; - -export const getFarcasterAccountAddress = ( - interactor: FrameValidationData["interactor"], -) => { - // Get the first verified account or custody account if first verified account doesn't exist - return interactor.verified_accounts[0] ?? interactor.custody_address; -}; diff --git a/apps/dashboard/src/utils/tx-frame.ts b/apps/dashboard/src/utils/tx-frame.ts index 27d8e26b76f..bb8b2072d07 100644 --- a/apps/dashboard/src/utils/tx-frame.ts +++ b/apps/dashboard/src/utils/tx-frame.ts @@ -1,4 +1,3 @@ -import type { FrameValidationData } from "@coinbase/onchainkit"; import { type ThirdwebContract, encode } from "thirdweb"; import { claimTo } from "thirdweb/extensions/erc721"; @@ -15,10 +14,3 @@ export async function getErc721PreparedEncodedData( // Return encoded transaction data return encodedTransactionData; } - -export const getFarcasterAccountAddress = ( - interactor: FrameValidationData["interactor"], -) => { - // Get the first verified account or custody account if first verified account doesn't exist - return interactor.verified_accounts[0] ?? interactor.custody_address; -};