diff --git a/README.md b/README.md index 5d996f3e56..d2b4688fab 100644 --- a/README.md +++ b/README.md @@ -54,11 +54,11 @@ For documentation and guides, visit [onchainkit.xyz](https://onchainkit.xyz/). - [Frame Kit](https://onchainkit.xyz/framekit/introduction) - Components: - - [``](https://onchainkit.xyz/framekit/introduction#framemetadata-) + - [``](https://onchainkit.xyz/framekit/frame-metadata) - Utilities: - - [`getFrameHtmlResponse`](https://onchainkit.xyz/framekit/introduction#getframehtmlresponseframemetadata) - - [`getFrameMessage`](https://onchainkit.xyz/framekit/introduction#getframemessageframerequest) - - [`getFrameMetadata`](https://onchainkit.xyz/framekit/introduction#getframemetadataframemetadata) + - [`getFrameHtmlResponse`](https://onchainkit.xyz/framekit/get-frame-html-response) + - [`getFrameMessage`](https://onchainkit.xyz/framekit/get-frame-message) + - [`getFrameMetadata`](https://onchainkit.xyz/framekit/get-frame-metadata) - [Identity Kit](https://onchainkit.xyz/identitykit/introduction) - Components: diff --git a/site/.yarn/install-state.gz b/site/.yarn/install-state.gz index 8e9efe360f..58566b7fa2 100644 Binary files a/site/.yarn/install-state.gz and b/site/.yarn/install-state.gz differ diff --git a/site/docs/pages/framekit/frame-metadata.mdx b/site/docs/pages/framekit/frame-metadata.mdx new file mode 100644 index 0000000000..7f8270451f --- /dev/null +++ b/site/docs/pages/framekit/frame-metadata.mdx @@ -0,0 +1,97 @@ +# `` + +This component is utilized for incorporating Frame metadata elements into the React page. + +Note: If you are using Next.js with App routing, it is recommended to use `getFrameMetadata` instead. + +## Usage + +```tsx +export default function HomePage() { + return ( + ... + + ... + ); +} +``` + +## Returns + +```html + + + + + + + + + + + +``` + +## Props + +```ts +type FrameButtonMetadata = + | { + action: 'link' | 'mint'; + label: string; + target: string; + } + | { + action?: 'post' | 'post_redirect'; + label: string; + }; + +type FrameImageMetadata = { + src: string; + aspectRatio?: '1.91:1' | '1:1'; +}; + +type FrameInputMetadata = { + text: string; +}; + +type FrameMetadataType = { + // A list of strings which are the label for the buttons in the frame (max 4 buttons). + buttons?: [FrameButtonMetadata, ...FrameButtonMetadata[]]; + // An image which must be smaller than 10MB and should have an aspect ratio of 1.91:1 or 1:1 + image: FrameImageMetadata; + // The text input to use for the Frame. + input?: FrameInputMetadata; + // A valid POST URL to send the Signature Packet to. + postUrl?: string; + // A period in seconds at which the app should expect the image to update. + refreshPeriod?: number; +}; + +type FrameMetadataReact = FrameMetadataType & { + wrapper?: React.ComponentType; +}; +``` diff --git a/site/docs/pages/framekit/get-frame-html-response.mdx b/site/docs/pages/framekit/get-frame-html-response.mdx new file mode 100644 index 0000000000..f9215c95a2 --- /dev/null +++ b/site/docs/pages/framekit/get-frame-html-response.mdx @@ -0,0 +1,78 @@ +# getFrameHtmlResponse + +When you need to send an HTML Frame Response, the `getFrameHtmlResponse` method is here to assist you. + +It generates a valid HTML string response with a frame and utilizes `FrameMetadata` types for page metadata. This eliminates the need to manually create server-side HTML strings. + +## Usage + +```ts +// Step 1. import getFrameHtmlResponse from @coinbase/onchainkit +import { getFrameHtmlResponse } from '@coinbase/onchainkit'; +import { NextRequest, NextResponse } from 'next/server'; + +async function getResponse(req: NextRequest): Promise { + // Step 2. Build your Frame logic + ... + + return new NextResponse( + // Step 3. Use getFrameHtmlResponse to create a Frame response + getFrameHtmlResponse({ + buttons: [ + { + label: `We love BOAT`, + }, + ], + image: 'https://build-onchain-apps.vercel.app/release/v-0-17.png', + postUrl: 'https://build-onchain-apps.vercel.app/api/frame', + }), + ); +} + +export async function POST(req: NextRequest): Promise { + return getResponse(req); +} +``` + +## Returns + +```ts +type FrameHTMLResponse = string; +``` + +## Parameters + +```ts +type FrameButtonMetadata = + | { + action: 'link' | 'mint'; + label: string; + target: string; + } + | { + action?: 'post' | 'post_redirect'; + label: string; + }; + +type FrameImageMetadata = { + src: string; + aspectRatio?: '1.91:1' | '1:1'; +}; + +type FrameInputMetadata = { + text: string; +}; + +type FrameMetadataType = { + // A list of strings which are the label for the buttons in the frame (max 4 buttons). + buttons?: [FrameButtonMetadata, ...FrameButtonMetadata[]]; + // An image which must be smaller than 10MB and should have an aspect ratio of 1.91:1 or 1:1 + image: FrameImageMetadata; + // The text input to use for the Frame. + input?: FrameInputMetadata; + // A valid POST URL to send the Signature Packet to. + postUrl?: string; + // A period in seconds at which the app should expect the image to update. + refreshPeriod?: number; +}; +``` diff --git a/site/docs/pages/framekit/get-frame-message.mdx b/site/docs/pages/framekit/get-frame-message.mdx new file mode 100644 index 0000000000..90a65c07a7 --- /dev/null +++ b/site/docs/pages/framekit/get-frame-message.mdx @@ -0,0 +1,90 @@ +# getFrameMessage + +When a user interacts with your Frame, you receive a JSON message called the "Frame Signature Packet". Decode and validate this message using the `getFrameMessage` function. + +You can also use `getFrameMessage` to access useful information such as: + +- button: number +- fid: number +- following: boolean +- liked: boolean +- recasted: boolean +- verified_accounts: string[] + +Note that if the `message` is not valid, it will be undefined. + +## Usage + +```ts +// Step 1. import getFrameMessage from @coinbase/onchainkit +import { FrameRequest, getFrameMessage } from '@coinbase/onchainkit'; +import { NextRequest, NextResponse } from 'next/server'; + +async function getResponse(req: NextRequest): Promise { + // Step 2. Read the body from the Next Request + const body: FrameRequest = await req.json(); + // Step 3. Validate the message + const { isValid, message } = await getFrameMessage(body , { + neynarApiKey: 'NEYNAR_ONCHAIN_KIT' + }); + + // Step 4. Determine the experience based on the validity of the message + if (isValid) { + // the message is valid + } else { + // sorry, the message is not valid and it will be undefined + } + + ... +} + +export async function POST(req: NextRequest): Promise { + return getResponse(req); +} +``` + +## Returns + +```ts +type Promise; + +type FrameValidationResponse = + | { isValid: true; message: FrameValidationData } + | { isValid: false; message: undefined }; + +interface FrameValidationData { + button: number; // Number of the button clicked + following: boolean; // Indicates if the viewer clicking the frame follows the cast author + input: string; // Text input from the viewer typing in the frame + interactor: { + fid: number; // Viewer Farcaster ID + custody_address: string; // Viewer custody address + verified_accounts: string[]; // Viewer account addresses + }; + liked: boolean; // Indicates if the viewer clicking the frame liked the cast + raw: NeynarFrameValidationInternalModel; + recasted: boolean; // Indicates if the viewer clicking the frame recasted the cast + valid: boolean; // Indicates if the frame is valid +} +``` + +## Parameters + +```ts +// The Frame Signature Packet body +type FrameMessage = { + body: FrameRequest; + messageOptions?: FrameMessageOptions; +}; + +type FrameMessageOptions = + | { + // The API key to use for validation. Default: NEYNAR_ONCHAIN_KIT + neynarApiKey?: string; + // Whether to cast the reaction context. Default: true + castReactionContext?: boolean; + // Whether to follow the context. Default: true + followContext?: boolean; + } + | undefined; +``` diff --git a/site/docs/pages/framekit/get-frame-metadata.mdx b/site/docs/pages/framekit/get-frame-metadata.mdx new file mode 100644 index 0000000000..1cace996c4 --- /dev/null +++ b/site/docs/pages/framekit/get-frame-metadata.mdx @@ -0,0 +1,78 @@ +# getFrameMetadata + +With Next.js App routing, use the `getFrameMetadata()` inside your `page.ts` to get the metadata need it for your Frame. + +## Usage + +```ts +// Step 1. import getFrameMetadata from @coinbase/onchainkit +import { getFrameMetadata } from '@coinbase/onchainkit'; +import type { Metadata } from 'next'; +import HomePage from './home'; + +// Step 2. Use getFrameMetadata to shape your Frame metadata +const frameMetadata = getFrameMetadata({ + buttons: [ + { + label: 'We love BOAT', + }, + ], + image: 'https://build-onchain-apps.vercel.app/release/v-0-17.png', + postUrl: 'https://build-onchain-apps.vercel.app/api/frame', +}); + +// Step 3. Add your metadata in the Next.js metadata utility +export const metadata: Metadata = { + manifest: '/manifest.json', + other: { + ...frameMetadata + }, +}; + +export default function Page() { + return ; +} +``` + +## Returns + +```ts +type FrameMetadataResponse = Record; +``` + +## Parameters + +```ts +type FrameButtonMetadata = + | { + action: 'link' | 'mint'; + label: string; + target: string; + } + | { + action?: 'post' | 'post_redirect'; + label: string; + }; + +type FrameImageMetadata = { + src: string; + aspectRatio?: '1.91:1' | '1:1'; +}; + +type FrameInputMetadata = { + text: string; +}; + +type FrameMetadataType = { + // A list of strings which are the label for the buttons in the frame (max 4 buttons). + buttons?: [FrameButtonMetadata, ...FrameButtonMetadata[]]; + // An image which must be smaller than 10MB and should have an aspect ratio of 1.91:1 + image: FrameImageMetadata; + // The text input to use for the Frame. + input?: FrameInputMetadata; + // A valid POST URL to send the Signature Packet to. + postUrl?: string; + // A period in seconds at which the app should expect the image to update. + refreshPeriod?: number; +}; +``` diff --git a/site/docs/pages/framekit/introduction.mdx b/site/docs/pages/framekit/introduction.mdx index 6370383d4a..34c6bfea85 100644 --- a/site/docs/pages/framekit/introduction.mdx +++ b/site/docs/pages/framekit/introduction.mdx @@ -2,361 +2,15 @@ A Frame transforms any cast into an interactive app. -Creating a frame is easy: select an image and add clickable buttons. When a button is clicked, you receive a callback and can send another image with more buttons. To learn more, check out "[Farcaster Frames Official Documentation](https://docs.farcaster.xyz/learn/what-is-farcaster/frames)". - -**React component**: - -- ``: This component renders all the Frame metadata elements in one place. - -**Typescript utilities**: - -- [`getFrameHtmlResponse()`](https://github.com/coinbase/onchainkit?tab=readme-ov-file#getframehtmlresponseframemetadata): Retrieves the **Frame HTML** for your HTTP responses. -- [`getFrameMessage()`](https://github.com/coinbase/onchainkit?tab=readme-ov-file#getframemessageframerequest): Retrieves a valid **Frame message** from the Frame Signature Packet. -- [`getFrameMetadata()`](https://github.com/coinbase/onchainkit?tab=readme-ov-file#getframeframemetadata): Retrieves valid **Frame metadata** for your initial HTML page with Next.js App Routing. - -
- -### `` - -This component is utilized for incorporating Frame metadata elements into the React page. - -Note: If you are using Next.js with App routing, it is recommended to use `getFrameMetadata` instead. - -```tsx -export default function HomePage() { - return ( - ... - - ... - ); -} -``` - -**@Props** - -```ts -type FrameButtonMetadata = - | { - action: 'link' | 'mint'; - label: string; - target: string; - } - | { - action?: 'post' | 'post_redirect'; - label: string; - }; - -type FrameImageMetadata = { - src: string; - aspectRatio?: '1.91:1' | '1:1'; -}; - -type FrameInputMetadata = { - text: string; -}; - -type FrameMetadataType = { - // A list of strings which are the label for the buttons in the frame (max 4 buttons). - buttons?: [FrameButtonMetadata, ...FrameButtonMetadata[]]; - // An image which must be smaller than 10MB and should have an aspect ratio of 1.91:1 or 1:1 - image: FrameImageMetadata; - // The text input to use for the Frame. - input?: FrameInputMetadata; - // A valid POST URL to send the Signature Packet to. - postUrl?: string; - // A period in seconds at which the app should expect the image to update. - refreshPeriod?: number; -}; - -type FrameMetadataReact = FrameMetadataType & { - wrapper?: React.ComponentType; -}; -``` - -**@Returns** - -```html - - - - - - - - - - - -``` - -
- -### getFrameHtmlResponse(frameMetadata) - -When you need to send an HTML Frame Response, the `getFrameHtmlResponse` method is here to assist you. - -It generates a valid HTML string response with a frame and utilizes `FrameMetadata` types for page metadata. This eliminates the need to manually create server-side HTML strings. - -```ts -// Step 1. import getFrameHtmlResponse from @coinbase/onchainkit -import { getFrameHtmlResponse } from '@coinbase/onchainkit'; -import { NextRequest, NextResponse } from 'next/server'; - -async function getResponse(req: NextRequest): Promise { - // Step 2. Build your Frame logic - ... - - return new NextResponse( - // Step 3. Use getFrameHtmlResponse to create a Frame response - getFrameHtmlResponse({ - buttons: [ - { - label: `We love BOAT`, - }, - ], - image: 'https://build-onchain-apps.vercel.app/release/v-0-17.png', - postUrl: 'https://build-onchain-apps.vercel.app/api/frame', - }), - ); -} - -export async function POST(req: NextRequest): Promise { - return getResponse(req); -} -``` - -**@Param** - -```ts -type FrameButtonMetadata = - | { - action: 'link' | 'mint'; - label: string; - target: string; - } - | { - action?: 'post' | 'post_redirect'; - label: string; - }; - -type FrameImageMetadata = { - src: string; - aspectRatio?: '1.91:1' | '1:1'; -}; - -type FrameInputMetadata = { - text: string; -}; - -type FrameMetadataType = { - // A list of strings which are the label for the buttons in the frame (max 4 buttons). - buttons?: [FrameButtonMetadata, ...FrameButtonMetadata[]]; - // An image which must be smaller than 10MB and should have an aspect ratio of 1.91:1 or 1:1 - image: FrameImageMetadata; - // The text input to use for the Frame. - input?: FrameInputMetadata; - // A valid POST URL to send the Signature Packet to. - postUrl?: string; - // A period in seconds at which the app should expect the image to update. - refreshPeriod?: number; -}; -``` - -**@Returns** - -```ts -type FrameHTMLResponse = string; -``` - -
- -### getFrameMessage(frameRequest) - -When a user interacts with your Frame, you receive a JSON message called the "Frame Signature Packet". Decode and validate this message using the `getFrameMessage` function. - -You can also use `getFrameMessage` to access useful information such as: - -- button: number -- fid: number -- following: boolean -- liked: boolean -- recasted: boolean -- verified_accounts: string[] - -Note that if the `message` is not valid, it will be undefined. - -```ts -// Step 1. import getFrameMessage from @coinbase/onchainkit -import { FrameRequest, getFrameMessage } from '@coinbase/onchainkit'; -import { NextRequest, NextResponse } from 'next/server'; - -async function getResponse(req: NextRequest): Promise { - // Step 2. Read the body from the Next Request - const body: FrameRequest = await req.json(); - // Step 3. Validate the message - const { isValid, message } = await getFrameMessage(body , { - neynarApiKey: 'NEYNAR_ONCHAIN_KIT' - }); - - // Step 4. Determine the experience based on the validity of the message - if (isValid) { - // the message is valid - } else { - // sorry, the message is not valid and it will be undefined - } - - ... -} - -export async function POST(req: NextRequest): Promise { - return getResponse(req); -} -``` - -**@Param** - -```ts -// The Frame Signature Packet body -type FrameMessage = { - body: FrameRequest; - messageOptions?: FrameMessageOptions; -}; - -type FrameMessageOptions = - | { - // The API key to use for validation. Default: NEYNAR_ONCHAIN_KIT - neynarApiKey?: string; - // Whether to cast the reaction context. Default: true - castReactionContext?: boolean; - // Whether to follow the context. Default: true - followContext?: boolean; - } - | undefined; -``` - -**@Returns** - -```ts -type Promise; - -type FrameValidationResponse = - | { isValid: true; message: FrameValidationData } - | { isValid: false; message: undefined }; - -interface FrameValidationData { - button: number; // Number of the button clicked - following: boolean; // Indicates if the viewer clicking the frame follows the cast author - input: string; // Text input from the viewer typing in the frame - interactor: { - fid: number; // Viewer Farcaster ID - custody_address: string; // Viewer custody address - verified_accounts: string[]; // Viewer account addresses - }; - liked: boolean; // Indicates if the viewer clicking the frame liked the cast - raw: NeynarFrameValidationInternalModel; - recasted: boolean; // Indicates if the viewer clicking the frame recasted the cast - valid: boolean; // Indicates if the frame is valid -} -``` - -
- -### getFrameMetadata(frameMetadata) - -With Next.js App routing, use the `getFrameMetadata()` inside your `page.ts` to get the metadata need it for your Frame. - -```ts -// Step 1. import getFrameMetadata from @coinbase/onchainkit -import { getFrameMetadata } from '@coinbase/onchainkit'; -import type { Metadata } from 'next'; -import HomePage from './home'; - -// Step 2. Use getFrameMetadata to shape your Frame metadata -const frameMetadata = getFrameMetadata({ - buttons: [ - { - label: 'We love BOAT', - }, - ], - image: 'https://build-onchain-apps.vercel.app/release/v-0-17.png', - postUrl: 'https://build-onchain-apps.vercel.app/api/frame', -}); - -// Step 3. Add your metadata in the Next.js metadata utility -export const metadata: Metadata = { - manifest: '/manifest.json', - other: { - ...frameMetadata - }, -}; - -export default function Page() { - return ; -} -``` - -**@Param** - -```ts -type FrameButtonMetadata = - | { - action: 'link' | 'mint'; - label: string; - target: string; - } - | { - action?: 'post' | 'post_redirect'; - label: string; - }; - -type FrameImageMetadata = { - src: string; - aspectRatio?: '1.91:1' | '1:1'; -}; - -type FrameInputMetadata = { - text: string; -}; - -type FrameMetadataType = { - // A list of strings which are the label for the buttons in the frame (max 4 buttons). - buttons?: [FrameButtonMetadata, ...FrameButtonMetadata[]]; - // An image which must be smaller than 10MB and should have an aspect ratio of 1.91:1 - image: FrameImageMetadata; - // The text input to use for the Frame. - input?: FrameInputMetadata; - // A valid POST URL to send the Signature Packet to. - postUrl?: string; - // A period in seconds at which the app should expect the image to update. - refreshPeriod?: number; -}; -``` - -**@Returns** - -```ts -type FrameMetadataResponse = Record; -``` +Creating a frame is easy: select an image and add clickable buttons. When a button is clicked, +you receive a callback and can send another image with more buttons. +To learn more, check out "[Farcaster Frames Official Documentation](https://docs.farcaster.xyz/learn/what-is-farcaster/frames)". + +To assist you in engaging with Frames, here is the Frame Kit which includes: + +- Components: + - [``](/framekit/frame-metadata) +- Utilities: + - [`getFrameHtmlResponse`](/framekit/get-frame-html-response) + - [`getFrameMessage`](/framekit/get-frame-message) + - [`getFrameMetadata`](/framekit/get-frame-metadata) diff --git a/site/docs/pages/framekit/types.mdx b/site/docs/pages/framekit/types.mdx new file mode 100644 index 0000000000..4de990f2b1 --- /dev/null +++ b/site/docs/pages/framekit/types.mdx @@ -0,0 +1 @@ +# Types [Glossary of Types in Frame Kit.] diff --git a/site/docs/pages/identitykit/types.mdx b/site/docs/pages/identitykit/types.mdx new file mode 100644 index 0000000000..2652ed5879 --- /dev/null +++ b/site/docs/pages/identitykit/types.mdx @@ -0,0 +1 @@ +# Types [Glossary of Types in Identity Kit.] diff --git a/site/sidebar.ts b/site/sidebar.ts index ea11b856cf..d61becdeb2 100644 --- a/site/sidebar.ts +++ b/site/sidebar.ts @@ -29,10 +29,48 @@ export const sidebar = [ }, { text: 'Frame Kit', - items: [{ text: 'Introduction', link: '/framekit/introduction' }], + items: [ + { text: 'Introduction', link: '/framekit/introduction' }, + { + text: 'Components', + items: [ + { + text: 'FrameMetadata', + link: '/framekit/frame-metadata', + }, + ], + }, + { + text: 'Utilities', + items: [ + { + text: 'getFrameHtmlResponse', + link: '/framekit/get-frame-html-response', + }, + { + text: 'getFrameMessage', + link: '/framekit/get-frame-message', + }, + { + text: 'getFrameMetadata', + link: '/framekit/get-frame-metadata', + }, + ], + }, + { + text: 'Types', + link: '/framekit/types', + }, + ], }, { text: 'Identity Kit', - items: [{ text: 'Introduction', link: '/identitykit/introduction' }], + items: [ + { text: 'Introduction', link: '/identitykit/introduction' }, + { + text: 'Types', + link: '/identitykit/types', + }, + ], }, ] as const satisfies Sidebar; diff --git a/src/core/getFrameMetadata.test.ts b/src/core/getFrameMetadata.test.ts index ffba48b76c..832dddadbe 100644 --- a/src/core/getFrameMetadata.test.ts +++ b/src/core/getFrameMetadata.test.ts @@ -166,4 +166,25 @@ describe('getFrameMetadata', () => { 'fc:frame:post_url': 'post_url', }); }); + + it('should render the target metadata for post and post_redirect when used', () => { + expect( + getFrameMetadata({ + buttons: [ + { label: 'button1', action: 'post', target: 'https://zizzamia.xyz/api/frame1' }, + { label: 'button2', action: 'post_redirect', target: 'https://zizzamia.xyz/api/frame2' }, + ], + image: 'image', + }), + ).toEqual({ + 'fc:frame': 'vNext', + 'fc:frame:button:1': 'button1', + 'fc:frame:button:1:action': 'post', + 'fc:frame:button:1:target': 'https://zizzamia.xyz/api/frame1', + 'fc:frame:button:2': 'button2', + 'fc:frame:button:2:action': 'post_redirect', + 'fc:frame:button:2:target': 'https://zizzamia.xyz/api/frame2', + 'fc:frame:image': 'image', + }); + }); });