Skip to content

Commit

Permalink
docs: polish README for getFrameMessage() (#38)
Browse files Browse the repository at this point in the history
  • Loading branch information
Zizzamia authored Jan 30, 2024
1 parent c0e35a0 commit 218b65e
Show file tree
Hide file tree
Showing 8 changed files with 160 additions and 53 deletions.
61 changes: 61 additions & 0 deletions .changeset/good-cougars-boil.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
---
'@coinbase/onchainkit': minor
---

- **docs**: Polished README for `getFrameMessage()`. By @zizzamia #38
- **fix**: Refactor Farcaster typing to be explicit, and added a Farcaster message verification integration test. By @robpolak @cnasc @zizzamia #37
- **feat**: Added a concept of integration tests where we can assert the actual values coming back from `neynar`. We decoupled these from unit tests as we should not commingle. By @robpolak #35
- **feat**: Refactored `neynar` client out of the `./src/core` code-path, for better composability and testability. By @robpolak #35

BREAKING CHANGES

We made the `getFrameValidatedMessage` method more type-safe and renamed it to `getFrameMessage`.

Before

```ts
import { getFrameValidatedMessage } from '@coinbase/onchainkit';

...

const validatedMessage = await getFrameValidatedMessage(body);
```

**@Returns**

```ts
type Promise<Message | undefined>
```

After

```ts
import { getFrameMessage } from '@coinbase/onchainkit';
...
const { isValid, message } = await getFrameMessage(body);
```

**@Returns**

```ts
type Promise<FrameValidationResponse>;
type FrameValidationResponse =
| { isValid: true; message: FrameData }
| { isValid: false; message: undefined };
interface FrameData {
fid: number;
url: string;
messageHash: string;
timestamp: number;
network: number;
buttonIndex: number;
castId: {
fid: number;
hash: string;
};
}
```
120 changes: 83 additions & 37 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
<img src='./docs/logo-v-0-1.png' width='800' alt='OnchainKit'>
<img src='./docs/logo-v-0-2.png' width='800' alt='OnchainKit'>

# [OnchainKit](https://github.com/coinbase/onchainkit/)

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

Add OnchainKit to your project, install the required packages.

<br />

```bash
# Use Yarn
yarn add @coinbase/onchainkit
Expand All @@ -31,10 +33,12 @@ Creating a frame is easy: select an image and add clickable buttons. When a butt
Utilities:

- [getFrameAccountAddress()](https://github.com/coinbase/onchainkit?tab=readme-ov-file#getframeaccountaddress)
- [getFrameMessage()](https://github.com/coinbase/onchainkit?tab=readme-ov-file#getFrameMessage)
- [getFrameMetadata()](https://github.com/coinbase/onchainkit?tab=readme-ov-file#getFrameMetadata)
- [getFrameValidatedMessage()](https://github.com/coinbase/onchainkit?tab=readme-ov-file#getFrameValidatedMessage)

### getFrameAccountAddress()
<br />

### getFrameAccountAddress(body, options)

When a user interacts with your Frame, you will receive a JSON message called `Frame Signature Packet`. From this message, you can extract the Account Address using the `getFrameAccountAddress()` function.

Expand Down Expand Up @@ -83,12 +87,79 @@ export async function POST(req: NextRequest): Promise<Response> {
export const dynamic = 'force-dynamic';
```

`getFrameAccountAddress` params
**@Param**

- `body`: The Frame Signature Packet body
- `options`:
- `NEYNAR_API_KEY`: The NEYNAR_API_KEY used to access [Neynar Farcaster Indexer](https://docs.neynar.com/reference/user-bulk)

**@Returns**

```ts
type AccountAddressResponse = Promise<string | undefined>;
```

<br />

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

It returns undefined if the message is not valid.

```ts
// Steps 1. import getFrameMessage from @coinbase/onchainkit
import { 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 = await req.json();
// Step 3. Validate the message
const { isValid, message } = await getFrameMessage(body);

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

export const dynamic = 'force-dynamic';
```

**@Param**

- `body`: The Frame Signature Packet body

**@Returns**

```ts
type FrameValidationResponse =
| { isValid: true; message: FrameData }
| { isValid: false; message: undefined };

interface FrameData {
fid: number;
url: string;
messageHash: string;
timestamp: number;
network: number;
buttonIndex: number;
castId: {
fid: number;
hash: string;
};
}
```

<br />

### getFrameMetadata()
Expand Down Expand Up @@ -121,45 +192,20 @@ export default function Page() {
}
```

`getFrameMetadata` params
**@Param**

- `buttons`: A list of strings which are the label for the buttons in the frame (max 4 buttons).
- `image`: An image which must be smaller than 10MB and should have an aspect ratio of 1.91:1
- `post_url`: A valid POST URL to send the Signature Packet to.

<br />

### getFrameValidatedMessage()

When a user interacts with your Frame, you receive a JSON message called the "Frame Signature Packet". Decode and validate this message using the `getFrameValidatedMessage` function. It returns undefined if the message is not valid.
**@Returns**

```ts
// Steps 1. import getFrameValidatedMessage from @coinbase/onchainkit
import { getFrameValidatedMessage } from '@coinbase/onchainkit';
import { NextRequest, NextResponse } from 'next/server';

async function getResponse(req: NextRequest): Promise<NextResponse> {
try {
// Step 2. Read the body from the Next Request
const body = await req.json();
// Step 3. Validate the message
validatedMessage = await getFrameValidatedMessage(body);

// Step 4. Determine the Frame experience based on the validity of the message
if (validatedMessage) {
// the message is valid
} else {
// sorry, the message is not valid
}

...
}

export async function POST(req: NextRequest): Promise<Response> {
return getResponse(req);
}

export const dynamic = 'force-dynamic';
type FrameMetadataResponse = {
buttons: string[];
image: string;
post_url: string;
};
```

<br />
Expand Down
Binary file added docs/logo-v-0-2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 7 additions & 5 deletions src/core/getFrameAccountAddress.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,29 +6,31 @@ type FidResponse = {
verifications: string[];
};

type AccountAddressResponse = Promise<string | undefined>;

/**
* Get the Account Address from the Farcaster ID using the Frame.
* This uses a Neynar api to get verified addresses belonging
* to the user wht that FID.
*
* This is using a demo api key so please register
* on through https://neynar.com/.
* @param body
* @param param1
* @returns
* @param body The JSON received by server on frame callback
* @param NEYNAR_API_KEY The api key for the Neynar API
* @returns The account address or undefined
*/
async function getFrameAccountAddress(
body: FrameRequest,
{ NEYNAR_API_KEY = 'NEYNAR_API_DOCS' },
): Promise<string | undefined> {
): AccountAddressResponse {
const validatedMessage = await getFrameMessage(body);
if (!validatedMessage?.isValid) {
return;
}
// Get the Farcaster ID from the message
const farcasterID = validatedMessage?.message?.fid ?? 0;
// Get the user verifications from the Farcaster Indexer
const bulkUserLookupResponse = await neynarBulkUserLookup([farcasterID]);
const bulkUserLookupResponse = await neynarBulkUserLookup([farcasterID], NEYNAR_API_KEY);
if (bulkUserLookupResponse?.users) {
const userVerifications = bulkUserLookupResponse?.users[0] as FidResponse;
if (userVerifications.verifications) {
Expand Down
File renamed without changes.
2 changes: 1 addition & 1 deletion src/core/getFrameMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export function getHubClient(): HubRpcClient {
*
* @param body The JSON received by server on frame callback
*/
async function getFrameMessage(body: FrameRequest): Promise<FrameValidationResponse | undefined> {
async function getFrameMessage(body: FrameRequest): Promise<FrameValidationResponse> {
// Get the message from the request body
const frameMessage: Message = Message.decode(
Buffer.from(body?.trustedData?.messageBytes ?? '', 'hex'),
Expand Down
16 changes: 7 additions & 9 deletions src/core/getFrameMetadata.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,17 @@
type FrameMetadataResponse = {
buttons: string[];
image: string;
post_url: string;
};

/**
* This function generates the metadata for a Farcaster Frame.
* @param buttons: An array of button names.
* @param image: The image to use for the frame.
* @param post_url: The URL to post the frame to.
* @returns The metadata for the frame.
*/
export const getFrameMetadata = function ({
buttons,
image,
post_url,
}: {
buttons: string[];
image: string;
post_url: string;
}) {
export const getFrameMetadata = function ({ buttons, image, post_url }: FrameMetadataResponse) {
const metadata: Record<string, string> = {
'fc:frame': 'vNext',
};
Expand Down
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// 🌲
const version = '0.1.6';
const version = '0.2.0';

export { version };
export { getFrameAccountAddress } from './core/getFrameAccountAddress';
Expand Down

0 comments on commit 218b65e

Please sign in to comment.