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

docs: polish README for getFrameMessage() #38

Merged
merged 2 commits into from
Jan 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
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
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
Loading