GitHub - DEFCONPRO/onchainkit: OnchainKit is a collection of tools to build world-class onchain apps with CSS, React, and typescript.

OnchainKit logo vibes

OnchainKit

OnchainKit is a collection of tools to build world-class onchain apps with CSS, React, and Typescript.

Version MIT License Downloads per month

Getting Started

Add OnchainKit to your project, install the required packages.

# Use Yarn
yarn add @coinbase/onchainkit viem@2.x react@18 react-dom@18

# Use NPM
npm install @coinbase/onchainkit viem@2.x react@18 react-dom@18

# Use PNPM
pnpm add @coinbase/onchainkit viem@2.x react@18 react-dom@18

OnchainKit is divided into various theme utilities and components that are available for your use:

Frame Kit 🖼️

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".

React component:

  • <FrameMetadata />: This component renders all the Frame metadata elements in one place.

Typescript utilities:

<FrameMetadata />

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.

export default function HomePage() {
  return (
    ...
    <FrameMetadata
      buttons={[
        {
          label: 'Tell me the story',
        },
        {
          action: 'link',
          label: 'Link to Google',
          target: 'https://www.google.com'
        },
        {
          action: 'post_redirect',
          label: 'Redirect to cute pictures',
        },
      ]}
      image={{
       src: 'https://zizzamia.xyz/park-3.png',
       aspectRatio: '1:1'
      }}
      input={{
        text: 'Tell me a boat story',
      }}
      postUrl="https://zizzamia.xyz/api/frame"
    />
    ...
  );
}

@Props

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<any>;
};

@Returns

<meta name="fc:frame" content="vNext" />
<meta name="fc:frame:button:1" content="Tell me the story" />
<meta name="fc:frame:button:2" content="Link to Google" />
<meta name="fc:frame:button:2:action" content="link" />
<meta name="fc:frame:button:2:target" content="https://www.google.com" />
<meta name="fc:frame:button:3" content="Redirect to cute pictures" />
<meta name="fc:frame:button:3:action" content="post_redirect" />
<meta name="fc:frame:image" content="https://zizzamia.xyz/park-3.png" />
<meta name="fc:frame:image:aspect_ratio" content="1:1" />
<meta name="fc:frame:input:text" content="Tell me a boat story" />
<meta name="fc:frame:post_url" content="https://zizzamia.xyz/api/frame" />

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.

// Step 1. import getFrameHtmlResponse from @coinbase/onchainkit
import { getFrameHtmlResponse } from '@coinbase/onchainkit';
import { NextRequest, NextResponse } from 'next/server';

async function getResponse(req: NextRequest): Promise<NextResponse> {
  // 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<Response> {
  return getResponse(req);
}

@Param

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

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.

// 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<NextResponse> {
  // 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<Response> {
  return getResponse(req);
}

@Param

// 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

type Promise<FrameValidationResponse>;

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.

// 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 <HomePage />;
}

@Param

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

type FrameMetadataResponse = Record<string, string>;

Identity Kit 👨‍🚀

Name

The Name component is used to display ENS names associated with Ethereum addresses. When an ENS name is not available, it defaults to showing a truncated version of the address.

import { Name } from '@coinbase/onchainkit';

<Name address="0x1234567890abcdef1234567890abcdef12345678" sliced={false} />;

@Props

type UseName = {
  // Ethereum address to be resolved from ENS.
  address: Address;
  // Optional CSS class for custom styling.
  className?: string;
  // Determines if the address should be sliced when no ENS name is available.
  sliced?: boolean;
  // Additional HTML attributes for the span element.
  props?: React.HTMLAttributes<HTMLSpanElement>;
};

Avatar

The Avatar component is used to display ENS avatar associated with Ethereum addresses. When an ENS avatar is not available, it defaults to blue color avatar.

import { Avatar } from '@coinbase/onchainkit';

<Avatar address="0x1234567890abcdef1234567890abcdef12345678" />;

@Props

type UseAvatar = {
  // Ethereum address to be resolved from ENS.
  address: Address;
  // Optional CSS class for custom styling.
  className?: string;
  // Additional HTML attributes for the span element.
  props?: React.HTMLAttributes<HTMLSpanElement>;
};

The Team and Our Community ☁️ 🌁 ☁️

OnchainKit is all about community; for any questions, feel free to:

  1. Reach out to the core maintainers on Twitter or Farcaster

License

This project is licensed under the MIT License - see the LICENSE.md file for details