Skip to content

Commit 4c57385

Browse files
committed
added manifest
1 parent 5af0c30 commit 4c57385

32 files changed

Lines changed: 14334 additions & 0 deletions

minikit/mini-neynar/.eslintrc.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"extends": ["next/core-web-vitals", "next/typescript"]
3+
}

minikit/mini-neynar/.gitignore

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2+
3+
# dependencies
4+
/node_modules
5+
/.pnp
6+
.pnp.js
7+
.yarn/install-state.gz
8+
9+
# testing
10+
/coverage
11+
12+
# next.js
13+
/.next/
14+
/out/
15+
16+
# production
17+
/build
18+
19+
# misc
20+
.DS_Store
21+
*.pem
22+
23+
# debug
24+
npm-debug.log*
25+
yarn-debug.log*
26+
yarn-error.log*
27+
28+
# local env files
29+
.env*
30+
31+
# vercel
32+
.vercel
33+
34+
# typescript
35+
*.tsbuildinfo
36+
next-env.d.ts

minikit/mini-neynar/.prettierrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{}

minikit/mini-neynar/.yarnrc.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# You can remove this file if you don't want to use Yarn package manager.
2+
nodeLinker: node-modules

minikit/mini-neynar/README.md

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
# MiniKit Template
2+
3+
This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-onchain --mini`](), configured with:
4+
5+
- [MiniKit](https://docs.base.org/builderkits/minikit/overview)
6+
- [OnchainKit](https://www.base.org/builders/onchainkit)
7+
- [Tailwind CSS](https://tailwindcss.com)
8+
- [Next.js](https://nextjs.org/docs)
9+
10+
## Getting Started
11+
12+
1. Install dependencies:
13+
```bash
14+
npm install
15+
# or
16+
yarn install
17+
# or
18+
pnpm install
19+
# or
20+
bun install
21+
```
22+
23+
2. Verify environment variables, these will be set up by the `npx create-onchain --mini` command:
24+
25+
You can regenerate the FARCASTER Account Association environment variables by running `npx create-onchain --manifest` in your project directory.
26+
27+
The environment variables enable the following features:
28+
29+
- Frame metadata - Sets up the Frame Embed that will be shown when you cast your frame
30+
- Account association - Allows users to add your frame to their account, enables notifications
31+
- Redis API keys - Enable Webhooks and background notifications for your application by storing users notification details
32+
33+
```bash
34+
# Required for Frame metadata
35+
NEXT_PUBLIC_URL=
36+
NEXT_PUBLIC_VERSION=
37+
NEXT_PUBLIC_ONCHAINKIT_PROJECT_NAME=
38+
NEXT_PUBLIC_ICON_URL=
39+
NEXT_PUBLIC_IMAGE_URL=
40+
NEXT_PUBLIC_SPLASH_IMAGE_URL=
41+
NEXT_PUBLIC_SPLASH_BACKGROUND_COLOR=
42+
43+
# Required to allow users to add your frame
44+
FARCASTER_HEADER=
45+
FARCASTER_PAYLOAD=
46+
FARCASTER_SIGNATURE=
47+
48+
# Required for webhooks and background notifications
49+
REDIS_URL=
50+
REDIS_TOKEN=
51+
```
52+
53+
3. Start the development server:
54+
```bash
55+
npm run dev
56+
```
57+
58+
## Template Features
59+
60+
### Frame Configuration
61+
- `.well-known/farcaster.json` endpoint configured for Frame metadata and account association
62+
- Frame metadata automatically added to page headers in `layout.tsx`
63+
64+
### Background Notifications
65+
- Redis-backed notification system using Upstash
66+
- Ready-to-use notification endpoints in `api/notify` and `api/webhook`
67+
- Notification client utilities in `lib/notification-client.ts`
68+
69+
### Theming
70+
- Custom theme defined in `theme.css` with OnchainKit variables
71+
- Pixel font integration with Pixelify Sans
72+
- Dark/light mode support through OnchainKit
73+
74+
### MiniKit Provider
75+
The app is wrapped with `MiniKitProvider` in `providers.tsx`, configured with:
76+
- OnchainKit integration
77+
- Access to Frames context
78+
- Sets up Wagmi Connectors
79+
- Sets up Frame SDK listeners
80+
- Applies Safe Area Insets
81+
82+
## Customization
83+
84+
To get started building your own frame, follow these steps:
85+
86+
1. Remove the DemoComponents:
87+
- Delete `components/DemoComponents.tsx`
88+
- Remove demo-related imports from `page.tsx`
89+
90+
2. Start building your Frame:
91+
- Modify `page.tsx` to create your Frame UI
92+
- Update theme variables in `theme.css`
93+
- Adjust MiniKit configuration in `providers.tsx`
94+
95+
3. Add your frame to your account:
96+
- Cast your frame to see it in action
97+
- Share your frame with others to start building your community
98+
99+
## Learn More
100+
101+
- [MiniKit Documentation](https://docs.base.org/builderkits/minikit/overview)
102+
- [OnchainKit Documentation](https://docs.base.org/builderkits/onchainkit/getting-started)
103+
- [Next.js Documentation](https://nextjs.org/docs)
104+
- [Tailwind CSS Documentation](https://tailwindcss.com/docs)
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
export async function GET() {
2+
const URL = process.env.NEXT_PUBLIC_URL;
3+
4+
return Response.json({
5+
accountAssociation: {
6+
header: process.env.FARCASTER_HEADER,
7+
payload: process.env.FARCASTER_PAYLOAD,
8+
signature: process.env.FARCASTER_SIGNATURE,
9+
},
10+
frame: {
11+
version: process.env.NEXT_PUBLIC_VERSION,
12+
name: process.env.NEXT_PUBLIC_ONCHAINKIT_PROJECT_NAME,
13+
homeUrl: URL,
14+
iconUrl: process.env.NEXT_PUBLIC_ICON_URL,
15+
imageUrl: process.env.NEXT_PUBLIC_IMAGE_URL,
16+
buttonTitle: `Launch ${process.env.NEXT_PUBLIC_ONCHAINKIT_PROJECT_NAME}`,
17+
splashImageUrl: process.env.NEXT_PUBLIC_SPLASH_IMAGE_URL,
18+
splashBackgroundColor: `#${process.env.NEXT_PUBLIC_SPLASH_BACKGROUND_COLOR}`,
19+
webhookUrl: `${URL}/api/webhook`,
20+
},
21+
});
22+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { getFollows } from '@/lib/neynar';
2+
import { NextRequest, NextResponse } from 'next/server';
3+
4+
export const dynamic = 'force-dynamic';
5+
6+
export async function GET(request: NextRequest) {
7+
try {
8+
const { searchParams } = new URL(request.url);
9+
const fid = searchParams.get('fid');
10+
const limit = searchParams.get('limit') || '10';
11+
12+
if (!fid) {
13+
return NextResponse.json(
14+
{ error: 'Missing required parameter: fid' },
15+
{ status: 400 }
16+
);
17+
}
18+
19+
const follows = await getFollows(Number(fid), Number(limit));
20+
21+
return NextResponse.json(follows);
22+
} catch (error) {
23+
// Handle any errors
24+
console.error('Error in GET route:', error);
25+
return NextResponse.json(
26+
{
27+
success: false,
28+
error: 'Internal server error'
29+
},
30+
{ status: 500 }
31+
);
32+
}
33+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { sendFrameNotification } from "@/lib/notification-client";
2+
import { NextResponse } from "next/server";
3+
4+
export async function POST(request: Request) {
5+
try {
6+
const body = await request.json();
7+
const { fid, notification } = body;
8+
9+
const result = await sendFrameNotification({
10+
fid,
11+
title: notification.title,
12+
body: notification.body,
13+
notificationDetails: notification.notificationDetails,
14+
});
15+
16+
if (result.state === "error") {
17+
return NextResponse.json(
18+
{ error: result.error },
19+
{ status: 500 },
20+
);
21+
}
22+
23+
return NextResponse.json({ success: true }, { status: 200 });
24+
} catch (error) {
25+
return NextResponse.json(
26+
{
27+
error: error instanceof Error ? error.message : "Unknown error",
28+
},
29+
{ status: 400 },
30+
);
31+
}
32+
}
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
import {
2+
setUserNotificationDetails,
3+
deleteUserNotificationDetails,
4+
} from "@/lib/notification";
5+
import { sendFrameNotification } from "@/lib/notification-client";
6+
import { http } from "viem";
7+
import { createPublicClient } from "viem";
8+
import { optimism } from "viem/chains";
9+
10+
const appName = process.env.NEXT_PUBLIC_ONCHAINKIT_PROJECT_NAME;
11+
12+
const KEY_REGISTRY_ADDRESS = "0x00000000Fc1237824fb747aBDE0FF18990E59b7e";
13+
14+
const KEY_REGISTRY_ABI = [
15+
{
16+
inputs: [
17+
{ name: "fid", type: "uint256" },
18+
{ name: "key", type: "bytes" },
19+
],
20+
name: "keyDataOf",
21+
outputs: [
22+
{
23+
components: [
24+
{ name: "state", type: "uint8" },
25+
{ name: "keyType", type: "uint32" },
26+
],
27+
name: "",
28+
type: "tuple",
29+
},
30+
],
31+
stateMutability: "view",
32+
type: "function",
33+
},
34+
] as const;
35+
36+
async function verifyFidOwnership(fid: number, appKey: `0x${string}`) {
37+
const client = createPublicClient({
38+
chain: optimism,
39+
transport: http(),
40+
});
41+
42+
try {
43+
const result = await client.readContract({
44+
address: KEY_REGISTRY_ADDRESS,
45+
abi: KEY_REGISTRY_ABI,
46+
functionName: "keyDataOf",
47+
args: [BigInt(fid), appKey],
48+
});
49+
50+
return result.state === 1 && result.keyType === 1;
51+
} catch (error) {
52+
console.error("Key Registry verification failed:", error);
53+
return false;
54+
}
55+
}
56+
57+
function decode(encoded: string) {
58+
return JSON.parse(Buffer.from(encoded, "base64url").toString("utf-8"));
59+
}
60+
61+
export async function POST(request: Request) {
62+
const requestJson = await request.json();
63+
64+
const { header: encodedHeader, payload: encodedPayload } = requestJson;
65+
66+
const headerData = decode(encodedHeader);
67+
const event = decode(encodedPayload);
68+
69+
const { fid, key } = headerData;
70+
71+
const valid = await verifyFidOwnership(fid, key);
72+
73+
if (!valid) {
74+
return Response.json(
75+
{ success: false, error: "Invalid FID ownership" },
76+
{ status: 401 },
77+
);
78+
}
79+
80+
switch (event.event) {
81+
case "frame_added":
82+
console.log(
83+
"frame_added",
84+
"event.notificationDetails",
85+
event.notificationDetails,
86+
);
87+
if (event.notificationDetails) {
88+
await setUserNotificationDetails(fid, event.notificationDetails);
89+
await sendFrameNotification({
90+
fid,
91+
title: `Welcome to ${appName}`,
92+
body: `Thank you for adding ${appName}`,
93+
});
94+
} else {
95+
await deleteUserNotificationDetails(fid);
96+
}
97+
98+
break;
99+
case "frame_removed": {
100+
console.log("frame_removed");
101+
await deleteUserNotificationDetails(fid);
102+
break;
103+
}
104+
case "notifications_enabled": {
105+
console.log("notifications_enabled", event.notificationDetails);
106+
await setUserNotificationDetails(fid, event.notificationDetails);
107+
await sendFrameNotification({
108+
fid,
109+
title: `Welcome to ${appName}`,
110+
body: `Thank you for enabling notifications for ${appName}`,
111+
});
112+
113+
break;
114+
}
115+
case "notifications_disabled": {
116+
console.log("notifications_disabled");
117+
await deleteUserNotificationDetails(fid);
118+
119+
break;
120+
}
121+
}
122+
123+
return Response.json({ success: true });
124+
}

0 commit comments

Comments
 (0)