mirror of
https://github.com/neynarxyz/create-farcaster-mini-app.git
synced 2025-11-16 08:08:56 -05:00
use neynar notifs
This commit is contained in:
parent
710c8255bf
commit
c9deb0512c
@ -1,4 +1,4 @@
|
|||||||
# 🖼️ frames-v2-demo
|
# Frames v2 Quickstart by Neynar 🪐
|
||||||
|
|
||||||
A Farcaster Frames v2 quickstart npx script.
|
A Farcaster Frames v2 quickstart npx script.
|
||||||
|
|
||||||
@ -8,5 +8,5 @@ This is a [NextJS](https://nextjs.org/) + TypeScript + React app.
|
|||||||
|
|
||||||
To create a new frames project, run:
|
To create a new frames project, run:
|
||||||
```{bash}
|
```{bash}
|
||||||
npx frames-v2-demo
|
npx create-neynar-farcaster-frame
|
||||||
```
|
```
|
||||||
|
|||||||
59
bin/index.js
59
bin/index.js
@ -11,7 +11,7 @@ import { mnemonicToAccount } from 'viem/accounts';
|
|||||||
const __filename = fileURLToPath(import.meta.url);
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
const __dirname = dirname(__filename);
|
const __dirname = dirname(__filename);
|
||||||
|
|
||||||
const REPO_URL = 'https://github.com/lucas-neynar/frames-v2-quickstart.git';
|
const REPO_URL = 'https://github.com/neynarxyz/create-neynar-farcaster-frame.git';
|
||||||
const SCRIPT_VERSION = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'package.json'), 'utf8')).version;
|
const SCRIPT_VERSION = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'package.json'), 'utf8')).version;
|
||||||
|
|
||||||
function printWelcomeMessage() {
|
function printWelcomeMessage() {
|
||||||
@ -24,7 +24,7 @@ function printWelcomeMessage() {
|
|||||||
console.log(`
|
console.log(`
|
||||||
${purple}╔═══════════════════════════════════════════════════╗${reset}
|
${purple}╔═══════════════════════════════════════════════════╗${reset}
|
||||||
${purple}║ ║${reset}
|
${purple}║ ║${reset}
|
||||||
${purple}║${reset} ${bright}Welcome to Frames v2 Quickstart${reset} ${purple}║${reset}
|
${purple}║${reset} ${bright}Welcome to Frames v2 Quickstart by Neynar${reset} ${purple}║${reset}
|
||||||
${purple}║${reset} ${dim}The fastest way to build Farcaster Frames${reset} ${purple}║${reset}
|
${purple}║${reset} ${dim}The fastest way to build Farcaster Frames${reset} ${purple}║${reset}
|
||||||
${purple}║ ║${reset}
|
${purple}║ ║${reset}
|
||||||
${purple}╚═══════════════════════════════════════════════════╝${reset}
|
${purple}╚═══════════════════════════════════════════════════╝${reset}
|
||||||
@ -112,31 +112,48 @@ async function init() {
|
|||||||
name: 'iconImageUrl',
|
name: 'iconImageUrl',
|
||||||
message: 'Enter the URL for your app icon\n(optional -- leave blank to use the default public/icon.png image or replace public/icon.png with your own)\n\nExternal app icon URL:',
|
message: 'Enter the URL for your app icon\n(optional -- leave blank to use the default public/icon.png image or replace public/icon.png with your own)\n\nExternal app icon URL:',
|
||||||
default: null
|
default: null
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'confirm',
|
|
||||||
name: 'useNeynar',
|
|
||||||
message: 'Would you like to use Neynar in your frame?',
|
|
||||||
default: false
|
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// If using Neynar, ask for API key
|
// Handle Neynar API key
|
||||||
if (answers.useNeynar) {
|
const neynarFlow = await inquirer.prompt([
|
||||||
const neynarAnswers = await inquirer.prompt([
|
{
|
||||||
|
type: 'confirm',
|
||||||
|
name: 'useNeynar',
|
||||||
|
message: '🪐 Neynar is an API that makes it easy to build on Farcaster.\n\nBenefits of using Neynar in your frame:\n- Pre-configured webhook handling (no setup required)\n- Automatic frame analytics in your dev portal\n- Send manual notifications from dev.neynar.com\n- Built-in rate limiting and error handling\n\nWould you like to use Neynar in your frame?',
|
||||||
|
default: true
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (neynarFlow.useNeynar) {
|
||||||
|
const neynarKeyAnswer = await inquirer.prompt([
|
||||||
{
|
{
|
||||||
type: 'password',
|
type: 'password',
|
||||||
name: 'neynarApiKey',
|
name: 'neynarApiKey',
|
||||||
message: 'Enter your Neynar API key:',
|
message: 'Enter your Neynar API key (or press enter to skip):',
|
||||||
validate: (input) => {
|
default: null
|
||||||
if (input.trim() === '') {
|
|
||||||
return 'Neynar API key cannot be empty';
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
answers.neynarApiKey = neynarAnswers.neynarApiKey;
|
|
||||||
|
if (!neynarKeyAnswer.neynarApiKey) {
|
||||||
|
const useDemoKey = await inquirer.prompt([
|
||||||
|
{
|
||||||
|
type: 'confirm',
|
||||||
|
name: 'useDemo',
|
||||||
|
message: 'Would you like to try the demo Neynar API key?',
|
||||||
|
default: true
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
|
||||||
|
answers.useNeynar = useDemoKey.useDemo;
|
||||||
|
answers.neynarApiKey = useDemoKey.useDemo ? 'FARCASTER_V2_FRAMES_DEMO' : null;
|
||||||
|
} else {
|
||||||
|
answers.useNeynar = true;
|
||||||
|
answers.neynarApiKey = neynarKeyAnswer.neynarApiKey;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
answers.useNeynar = false;
|
||||||
|
answers.neynarApiKey = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ask for seed phrase last
|
// Ask for seed phrase last
|
||||||
@ -329,7 +346,7 @@ async function init() {
|
|||||||
// Update README
|
// Update README
|
||||||
console.log('\nUpdating README...');
|
console.log('\nUpdating README...');
|
||||||
const readmePath = path.join(projectPath, 'README.md');
|
const readmePath = path.join(projectPath, 'README.md');
|
||||||
const prependText = `<!-- generated by frames-v2-quickstart version ${SCRIPT_VERSION} -->\n\n`;
|
const prependText = `<!-- generated by create-neynar-farcaster-frame version ${SCRIPT_VERSION} -->\n\n`;
|
||||||
if (fs.existsSync(readmePath)) {
|
if (fs.existsSync(readmePath)) {
|
||||||
const originalReadmeContent = fs.readFileSync(readmePath, { encoding: 'utf8' });
|
const originalReadmeContent = fs.readFileSync(readmePath, { encoding: 'utf8' });
|
||||||
const updatedReadmeContent = prependText + originalReadmeContent;
|
const updatedReadmeContent = prependText + originalReadmeContent;
|
||||||
@ -353,7 +370,7 @@ async function init() {
|
|||||||
console.log('\nInitializing git repository...');
|
console.log('\nInitializing git repository...');
|
||||||
execSync('git init', { cwd: projectPath });
|
execSync('git init', { cwd: projectPath });
|
||||||
execSync('git add .', { cwd: projectPath });
|
execSync('git add .', { cwd: projectPath });
|
||||||
execSync('git commit -m "initial commit from frames-v2-quickstart"', { cwd: projectPath });
|
execSync('git commit -m "initial commit from create-neynar-farcaster-frame"', { cwd: projectPath });
|
||||||
|
|
||||||
// Calculate border length based on message length
|
// Calculate border length based on message length
|
||||||
const message = `✨🪐 Successfully created frame ${projectName} with git and dependencies installed! 🪐✨`;
|
const message = `✨🪐 Successfully created frame ${projectName} with git and dependencies installed! 🪐✨`;
|
||||||
|
|||||||
234
package-lock.json
generated
234
package-lock.json
generated
@ -1,25 +1,25 @@
|
|||||||
{
|
{
|
||||||
"name": "frames-v2-demo",
|
"name": "create-neynar-farcaster-frame",
|
||||||
"version": "0.1.3",
|
"version": "1.0.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "frames-v2-demo",
|
"name": "create-neynar-farcaster-frame",
|
||||||
"version": "0.1.3",
|
"version": "1.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"dotenv": "^16.4.7",
|
"dotenv": "^16.4.7",
|
||||||
"inquirer": "^12.4.3",
|
"inquirer": "^12.4.3",
|
||||||
"viem": "^2.23.6"
|
"viem": "^2.23.6"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"frames-v2-demo": "bin/index.js"
|
"create-neynar-farcaster-frame": "bin/index.js"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@adraffy/ens-normalize": {
|
"node_modules/@adraffy/ens-normalize": {
|
||||||
"version": "1.10.1",
|
"version": "1.11.0",
|
||||||
"resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.10.1.tgz",
|
"resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.11.0.tgz",
|
||||||
"integrity": "sha512-96Z2IP3mYmF1Xg2cDm8f1gWGf/HUVedQ3FMifV4kG/PQ4yEP51xDtRAEfhVNt5f/uzpNkZHwWQuUcu6D6K+Ekw==",
|
"integrity": "sha512-/3DDPKHqqIqxUULp8yP4zODUY1i+2xvVWsv8A79xGWdCAG+8sb0hRh0Rk2QyOJUnnbyPUAZYcpBuRe3nS2OIUg==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@inquirer/checkbox": {
|
"node_modules/@inquirer/checkbox": {
|
||||||
@ -389,17 +389,6 @@
|
|||||||
"url": "https://paulmillr.com/funding/"
|
"url": "https://paulmillr.com/funding/"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/node": {
|
|
||||||
"version": "20.17.7",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.7.tgz",
|
|
||||||
"integrity": "sha512-sZXXnpBFMKbao30dUAvzKbdwA2JM1fwUtVEq/kxKuPI5mMwZiRElCpTXb0Biq/LMEVpXDZL5G5V0RPnxKeyaYg==",
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
|
||||||
"undici-types": "~6.19.2"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/abitype": {
|
"node_modules/abitype": {
|
||||||
"version": "1.0.8",
|
"version": "1.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/abitype/-/abitype-1.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/abitype/-/abitype-1.0.8.tgz",
|
||||||
@ -436,18 +425,6 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/ansi-escapes/node_modules/type-fest": {
|
|
||||||
"version": "0.21.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz",
|
|
||||||
"integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==",
|
|
||||||
"license": "(MIT OR CC0-1.0)",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/ansi-regex": {
|
"node_modules/ansi-regex": {
|
||||||
"version": "5.0.1",
|
"version": "5.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
||||||
@ -472,21 +449,6 @@
|
|||||||
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/bufferutil": {
|
|
||||||
"version": "4.0.8",
|
|
||||||
"resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.8.tgz",
|
|
||||||
"integrity": "sha512-4T53u4PdgsXqKaIctwF8ifXlRTTmEPJ8iEPWFdGZvcf7sbwYo6FKFEX9eNNAnzFZ7EzJAQ3CJeOtCRA4rDp7Pw==",
|
|
||||||
"hasInstallScript": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
|
||||||
"node-gyp-build": "^4.3.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=6.14.2"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/chardet": {
|
"node_modules/chardet": {
|
||||||
"version": "0.7.0",
|
"version": "0.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz",
|
||||||
@ -532,6 +494,12 @@
|
|||||||
"url": "https://dotenvx.com"
|
"url": "https://dotenvx.com"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/emoji-regex": {
|
||||||
|
"version": "8.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
||||||
|
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/eventemitter3": {
|
"node_modules/eventemitter3": {
|
||||||
"version": "5.0.1",
|
"version": "5.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz",
|
||||||
@ -623,19 +591,6 @@
|
|||||||
"node": "^18.17.0 || >=20.5.0"
|
"node": "^18.17.0 || >=20.5.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/node-gyp-build": {
|
|
||||||
"version": "4.8.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz",
|
|
||||||
"integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==",
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"peer": true,
|
|
||||||
"bin": {
|
|
||||||
"node-gyp-build": "bin.js",
|
|
||||||
"node-gyp-build-optional": "optional.js",
|
|
||||||
"node-gyp-build-test": "build-test.js"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/os-tmpdir": {
|
"node_modules/os-tmpdir": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
|
||||||
@ -645,6 +600,35 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/ox": {
|
||||||
|
"version": "0.6.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/ox/-/ox-0.6.9.tgz",
|
||||||
|
"integrity": "sha512-wi5ShvzE4eOcTwQVsIPdFr+8ycyX+5le/96iAJutaZAvCes1J0+RvpEPg5QDPDiaR0XQQAvZVl7AwqQcINuUug==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/wevm"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@adraffy/ens-normalize": "^1.10.1",
|
||||||
|
"@noble/curves": "^1.6.0",
|
||||||
|
"@noble/hashes": "^1.5.0",
|
||||||
|
"@scure/bip32": "^1.5.0",
|
||||||
|
"@scure/bip39": "^1.4.0",
|
||||||
|
"abitype": "^1.0.6",
|
||||||
|
"eventemitter3": "5.0.1"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"typescript": ">=5.4.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"typescript": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/run-async": {
|
"node_modules/run-async": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/run-async/-/run-async-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/run-async/-/run-async-3.0.0.tgz",
|
||||||
@ -695,12 +679,6 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/string-width/node_modules/emoji-regex": {
|
|
||||||
"version": "8.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
|
||||||
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/strip-ansi": {
|
"node_modules/strip-ansi": {
|
||||||
"version": "6.0.1",
|
"version": "6.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
||||||
@ -726,53 +704,27 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/tslib": {
|
"node_modules/tslib": {
|
||||||
"version": "2.7.0",
|
"version": "2.8.1",
|
||||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||||
"integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==",
|
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||||
"license": "0BSD"
|
"license": "0BSD"
|
||||||
},
|
},
|
||||||
"node_modules/typescript": {
|
"node_modules/type-fest": {
|
||||||
"version": "5.7.2",
|
"version": "0.21.3",
|
||||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.2.tgz",
|
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz",
|
||||||
"integrity": "sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==",
|
"integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==",
|
||||||
"license": "Apache-2.0",
|
"license": "(MIT OR CC0-1.0)",
|
||||||
"optional": true,
|
|
||||||
"peer": true,
|
|
||||||
"bin": {
|
|
||||||
"tsc": "bin/tsc",
|
|
||||||
"tsserver": "bin/tsserver"
|
|
||||||
},
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=14.17"
|
"node": ">=10"
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/undici-types": {
|
|
||||||
"version": "6.19.8",
|
|
||||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
|
|
||||||
"integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==",
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"peer": true
|
|
||||||
},
|
|
||||||
"node_modules/utf-8-validate": {
|
|
||||||
"version": "5.0.10",
|
|
||||||
"resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.10.tgz",
|
|
||||||
"integrity": "sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==",
|
|
||||||
"hasInstallScript": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
|
||||||
"node-gyp-build": "^4.3.0"
|
|
||||||
},
|
},
|
||||||
"engines": {
|
"funding": {
|
||||||
"node": ">=6.14.2"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/viem": {
|
"node_modules/viem": {
|
||||||
"version": "2.23.6",
|
"version": "2.23.10",
|
||||||
"resolved": "https://registry.npmjs.org/viem/-/viem-2.23.6.tgz",
|
"resolved": "https://registry.npmjs.org/viem/-/viem-2.23.10.tgz",
|
||||||
"integrity": "sha512-+yUeK8rktbGFQaLIvY4Tki22HUjian9Z4eKGAUT72RF9bcfkYgK8CJZz9P83tgoeLpiTyX3xcBM4xJZrJyKmsA==",
|
"integrity": "sha512-va6Wde+v96PdfzdPEspCML1MjAqe+88O8BD+R9Kun/4s5KMUNcqfHbXdZP0ZZ2Zms80styvH2pDRAqCho6TqkA==",
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "github",
|
"type": "github",
|
||||||
@ -787,8 +739,8 @@
|
|||||||
"@scure/bip39": "1.5.4",
|
"@scure/bip39": "1.5.4",
|
||||||
"abitype": "1.0.8",
|
"abitype": "1.0.8",
|
||||||
"isows": "1.0.6",
|
"isows": "1.0.6",
|
||||||
"ox": "0.6.7",
|
"ox": "0.6.9",
|
||||||
"ws": "8.18.0"
|
"ws": "8.18.1"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"typescript": ">=5.0.4"
|
"typescript": ">=5.0.4"
|
||||||
@ -799,56 +751,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/viem/node_modules/ox": {
|
|
||||||
"version": "0.6.7",
|
|
||||||
"resolved": "https://registry.npmjs.org/ox/-/ox-0.6.7.tgz",
|
|
||||||
"integrity": "sha512-17Gk/eFsFRAZ80p5eKqv89a57uXjd3NgIf1CaXojATPBuujVc/fQSVhBeAU9JCRB+k7J50WQAyWTxK19T9GgbA==",
|
|
||||||
"funding": [
|
|
||||||
{
|
|
||||||
"type": "github",
|
|
||||||
"url": "https://github.com/sponsors/wevm"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"@adraffy/ens-normalize": "^1.10.1",
|
|
||||||
"@noble/curves": "^1.6.0",
|
|
||||||
"@noble/hashes": "^1.5.0",
|
|
||||||
"@scure/bip32": "^1.5.0",
|
|
||||||
"@scure/bip39": "^1.4.0",
|
|
||||||
"abitype": "^1.0.6",
|
|
||||||
"eventemitter3": "5.0.1"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"typescript": ">=5.4.0"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"typescript": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/viem/node_modules/ws": {
|
|
||||||
"version": "8.18.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz",
|
|
||||||
"integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==",
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10.0.0"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"bufferutil": "^4.0.1",
|
|
||||||
"utf-8-validate": ">=5.0.2"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"bufferutil": {
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"utf-8-validate": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/wrap-ansi": {
|
"node_modules/wrap-ansi": {
|
||||||
"version": "6.2.0",
|
"version": "6.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
|
||||||
@ -864,11 +766,10 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/ws": {
|
"node_modules/ws": {
|
||||||
"version": "8.17.1",
|
"version": "8.18.1",
|
||||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
|
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.1.tgz",
|
||||||
"integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
|
"integrity": "sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=10.0.0"
|
"node": ">=10.0.0"
|
||||||
},
|
},
|
||||||
@ -896,17 +797,6 @@
|
|||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"node_modules/zod": {
|
|
||||||
"version": "3.24.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/zod/-/zod-3.24.1.tgz",
|
|
||||||
"integrity": "sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==",
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"peer": true,
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/colinhacks"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "frames-v2-demo",
|
"name": "create-neynar-farcaster-frame",
|
||||||
"version": "0.2.0",
|
"version": "1.0.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"files": [
|
"files": [
|
||||||
"bin/index.js"
|
"bin/index.js"
|
||||||
@ -21,7 +21,7 @@
|
|||||||
"lint": "next lint"
|
"lint": "next lint"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"frames-v2-demo": "./bin/index.js"
|
"create-neynar-farcaster-frame": "./bin/index.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"dotenv": "^16.4.7",
|
"dotenv": "^16.4.7",
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import { NextRequest } from "next/server";
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { setUserNotificationDetails } from "~/lib/kv";
|
import { setUserNotificationDetails } from "~/lib/kv";
|
||||||
import { sendFrameNotification } from "~/lib/notifs";
|
import { sendFrameNotification } from "~/lib/notifs";
|
||||||
|
import { sendNeynarFrameNotification } from "~/lib/neynar";
|
||||||
|
|
||||||
const requestSchema = z.object({
|
const requestSchema = z.object({
|
||||||
fid: z.number(),
|
fid: z.number(),
|
||||||
@ -10,6 +11,10 @@ const requestSchema = z.object({
|
|||||||
});
|
});
|
||||||
|
|
||||||
export async function POST(request: NextRequest) {
|
export async function POST(request: NextRequest) {
|
||||||
|
// If Neynar is enabled, we don't need to store notification details
|
||||||
|
// as they will be managed by Neynar's system
|
||||||
|
const neynarEnabled = process.env.NEYNAR_API_KEY && process.env.NEYNAR_CLIENT_ID;
|
||||||
|
|
||||||
const requestJson = await request.json();
|
const requestJson = await request.json();
|
||||||
const requestBody = requestSchema.safeParse(requestJson);
|
const requestBody = requestSchema.safeParse(requestJson);
|
||||||
|
|
||||||
@ -20,13 +25,18 @@ export async function POST(request: NextRequest) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
await setUserNotificationDetails(
|
// Only store notification details if not using Neynar
|
||||||
requestBody.data.fid,
|
if (!neynarEnabled) {
|
||||||
requestBody.data.notificationDetails
|
await setUserNotificationDetails(
|
||||||
);
|
Number(requestBody.data.fid),
|
||||||
|
requestBody.data.notificationDetails
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const sendResult = await sendFrameNotification({
|
// Use appropriate notification function based on Neynar status
|
||||||
fid: requestBody.data.fid,
|
const sendNotification = neynarEnabled ? sendNeynarFrameNotification : sendFrameNotification;
|
||||||
|
const sendResult = await sendNotification({
|
||||||
|
fid: Number(requestBody.data.fid),
|
||||||
title: "Test notification",
|
title: "Test notification",
|
||||||
body: "Sent at " + new Date().toISOString(),
|
body: "Sent at " + new Date().toISOString(),
|
||||||
});
|
});
|
||||||
|
|||||||
@ -11,6 +11,13 @@ import {
|
|||||||
import { sendFrameNotification } from "~/lib/notifs";
|
import { sendFrameNotification } from "~/lib/notifs";
|
||||||
|
|
||||||
export async function POST(request: NextRequest) {
|
export async function POST(request: NextRequest) {
|
||||||
|
// If Neynar is enabled, we don't need to handle webhooks here
|
||||||
|
// as they will be handled by Neynar's webhook endpoint
|
||||||
|
const neynarEnabled = process.env.NEYNAR_API_KEY && process.env.NEYNAR_CLIENT_ID;
|
||||||
|
if (neynarEnabled) {
|
||||||
|
return Response.json({ success: true });
|
||||||
|
}
|
||||||
|
|
||||||
const requestJson = await request.json();
|
const requestJson = await request.json();
|
||||||
|
|
||||||
let data;
|
let data;
|
||||||
@ -45,6 +52,8 @@ export async function POST(request: NextRequest) {
|
|||||||
const fid = data.fid;
|
const fid = data.fid;
|
||||||
const event = data.event;
|
const event = data.event;
|
||||||
|
|
||||||
|
// Only handle notifications if Neynar is not enabled
|
||||||
|
// When Neynar is enabled, notifications are handled through their webhook
|
||||||
switch (event.event) {
|
switch (event.event) {
|
||||||
case "frame_added":
|
case "frame_added":
|
||||||
if (event.notificationDetails) {
|
if (event.notificationDetails) {
|
||||||
@ -57,12 +66,12 @@ export async function POST(request: NextRequest) {
|
|||||||
} else {
|
} else {
|
||||||
await deleteUserNotificationDetails(fid);
|
await deleteUserNotificationDetails(fid);
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "frame_removed":
|
case "frame_removed":
|
||||||
await deleteUserNotificationDetails(fid);
|
await deleteUserNotificationDetails(fid);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "notifications_enabled":
|
case "notifications_enabled":
|
||||||
await setUserNotificationDetails(fid, event.notificationDetails);
|
await setUserNotificationDetails(fid, event.notificationDetails);
|
||||||
await sendFrameNotification({
|
await sendFrameNotification({
|
||||||
@ -70,11 +79,10 @@ export async function POST(request: NextRequest) {
|
|||||||
title: "Ding ding ding",
|
title: "Ding ding ding",
|
||||||
body: "Notifications are now enabled",
|
body: "Notifications are now enabled",
|
||||||
});
|
});
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "notifications_disabled":
|
case "notifications_disabled":
|
||||||
await deleteUserNotificationDetails(fid);
|
await deleteUserNotificationDetails(fid);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -2,6 +2,9 @@ import { NeynarAPIClient } from '@neynar/nodejs-sdk';
|
|||||||
|
|
||||||
let neynarClient: NeynarAPIClient | null = null;
|
let neynarClient: NeynarAPIClient | null = null;
|
||||||
|
|
||||||
|
// Example usage:
|
||||||
|
// const client = getNeynarClient();
|
||||||
|
// const user = await client.lookupUserByFid(fid);
|
||||||
export function getNeynarClient() {
|
export function getNeynarClient() {
|
||||||
if (!neynarClient) {
|
if (!neynarClient) {
|
||||||
const apiKey = process.env.NEYNAR_API_KEY;
|
const apiKey = process.env.NEYNAR_API_KEY;
|
||||||
@ -13,6 +16,46 @@ export function getNeynarClient() {
|
|||||||
return neynarClient;
|
return neynarClient;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Example usage:
|
type SendFrameNotificationResult =
|
||||||
// const client = getNeynarClient();
|
| {
|
||||||
// const user = await client.lookupUserByFid(fid);
|
state: "error";
|
||||||
|
error: unknown;
|
||||||
|
}
|
||||||
|
| { state: "no_token" }
|
||||||
|
| { state: "rate_limit" }
|
||||||
|
| { state: "success" };
|
||||||
|
|
||||||
|
export async function sendNeynarFrameNotification({
|
||||||
|
fid,
|
||||||
|
title,
|
||||||
|
body,
|
||||||
|
}: {
|
||||||
|
fid: number;
|
||||||
|
title: string;
|
||||||
|
body: string;
|
||||||
|
}): Promise<SendFrameNotificationResult> {
|
||||||
|
try {
|
||||||
|
const client = getNeynarClient();
|
||||||
|
const targetFids = [fid];
|
||||||
|
const notification = {
|
||||||
|
title,
|
||||||
|
body,
|
||||||
|
target_url: process.env.NEXT_PUBLIC_URL,
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = await client.publishFrameNotifications({
|
||||||
|
targetFids,
|
||||||
|
notification
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
return { state: "success" };
|
||||||
|
} else if (result.status === 429) {
|
||||||
|
return { state: "rate_limit" };
|
||||||
|
} else {
|
||||||
|
return { state: "error", error: result.error || "Unknown error" };
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return { state: "error", error };
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -62,6 +62,13 @@ export async function generateFarcasterMetadata() {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Determine webhook URL based on whether Neynar is enabled
|
||||||
|
const neynarApiKey = process.env.NEYNAR_API_KEY;
|
||||||
|
const neynarClientId = process.env.NEYNAR_CLIENT_ID;
|
||||||
|
const webhookUrl = neynarApiKey && neynarClientId
|
||||||
|
? `https://api.neynar.com/f/app/${neynarClientId}/event`
|
||||||
|
: `${appUrl}/api/webhook`;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
accountAssociation,
|
accountAssociation,
|
||||||
frame: {
|
frame: {
|
||||||
@ -73,7 +80,7 @@ export async function generateFarcasterMetadata() {
|
|||||||
buttonTitle: process.env.NEXT_PUBLIC_FRAME_BUTTON_TEXT || "Launch Frame",
|
buttonTitle: process.env.NEXT_PUBLIC_FRAME_BUTTON_TEXT || "Launch Frame",
|
||||||
splashImageUrl: process.env.NEXT_PUBLIC_FRAME_SPLASH_IMAGE_URL || `${appUrl}/splash.png`,
|
splashImageUrl: process.env.NEXT_PUBLIC_FRAME_SPLASH_IMAGE_URL || `${appUrl}/splash.png`,
|
||||||
splashBackgroundColor: "#f7f7f7",
|
splashBackgroundColor: "#f7f7f7",
|
||||||
webhookUrl: `${appUrl}/api/webhook`,
|
webhookUrl,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user