├── readme.md ├── package.json ├── tsconfig.json ├── src └── index.ts └── .gitignore /readme.md: -------------------------------------------------------------------------------- 1 | # Farcaster/Warpcast Frames with Express 2 | 3 | Simple express app to post frames. 4 | 5 | Test your frames with the [warpcast embed page](https://warpcast.com/~/developers/embeds) 6 | - You cannot test the redirect (loading a new frame via button click) in the embed, you have to tweet 7 | 8 | ## Frames Quirks 9 | - Frames don't like to render if the route is "/", so target e.g. "/frame" 10 | - Frames require `````` 11 | - Frames don't like to render at port 3000? 12 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frames-express", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "build": "tsc -p .", 9 | "dev": "nodemon --watch 'src/**/*.ts' --exec 'ts-node' src/index.ts", 10 | "start": "node dist/index.js" 11 | }, 12 | "dependencies": { 13 | "cors": "^2.8.5", 14 | "express": "^4.17.1" 15 | }, 16 | "devDependencies": { 17 | "@types/cors": "^2.8.17", 18 | "@types/express": "^4.17.9", 19 | "@types/node": "^14.14.10", 20 | "nodemon": "^2.0.6", 21 | "ts-node": "^10.9.1", 22 | "typescript": "^5.3.3" 23 | }, 24 | "keywords": [], 25 | "author": "", 26 | "license": "ISC", 27 | "engines": { 28 | "node": "16.x" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", // Compile to ES6 4 | "module": "commonjs", // Use commonjs module system 5 | "lib": ["es6"], // Include the ES6 library 6 | "outDir": "./dist", // Output to 'dist' folder 7 | "rootDir": "./src", // Root directory is 'src' 8 | "strict": true, // Enable all strict type-checking options 9 | "moduleResolution": "node", // Module resolution strategy 10 | "esModuleInterop": true, // Enables __importDefault helper 11 | "skipLibCheck": true, // Skip type checking of all declaration files (*.d.ts) 12 | "forceConsistentCasingInFileNames": true // Disallow inconsistently-cased references to the same file 13 | }, 14 | "include": [ 15 | "src/**/*.ts" // Include all TypeScript files in src 16 | ], 17 | "exclude": [ 18 | "node_modules", // Exclude the node_modules directory 19 | "**/*.spec.ts" // Exclude test files 20 | ] 21 | } -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import cors from 'cors'; 3 | 4 | const app = express(); 5 | app.use(express.json()); 6 | app.use(cors({ 7 | origin: '*' 8 | })); 9 | 10 | const port = 8080; 11 | 12 | interface IFrameProps { 13 | frame?: string; 14 | imageUrl: string; 15 | buttons?: string[]; 16 | postUrl?: string; 17 | } 18 | 19 | function generateFarcasterFrameMetaTag({ frame, imageUrl, postUrl, buttons }: IFrameProps): string { 20 | // Default to vNext 21 | if (!frame) { 22 | frame = "vNext" 23 | } 24 | // Ensure there are at most four buttons 25 | if (buttons && buttons.length > 4) { 26 | throw new Error("Maximum of four buttons are allowed per frame."); 27 | } 28 | 29 | // Generate Open Graph tags for image, redirect, and buttons 30 | let metaTag = `\n`; 31 | metaTag += `\n`; 32 | 33 | if (buttons) { 34 | buttons.forEach((button, index) => { 35 | metaTag += `\n`; 36 | }); 37 | } 38 | 39 | // post URL if exists 40 | if (postUrl) { 41 | metaTag += ` \n` 42 | } 43 | 44 | return metaTag; 45 | } 46 | 47 | function frameGenerator(frameProps: IFrameProps): string { 48 | 49 | const metaTag = generateFarcasterFrameMetaTag(frameProps); 50 | 51 | const html = ` 52 | 53 | 54 | 55 | Farcaster x Express Frame template 56 | 57 | 58 | ${metaTag} 59 | 60 | 61 | `; 62 | return html; 63 | } 64 | 65 | app.get('/frame', (req, res) => { 66 | 67 | const frameProps: IFrameProps = { 68 | imageUrl: 'https://i.imgur.com/osEKmhB.png', 69 | buttons: ['get', 'button2'], 70 | }; 71 | 72 | res.status(200).send(frameGenerator(frameProps)); 73 | }); 74 | 75 | app.post('/frame', (req, res) => { 76 | 77 | console.log(req.body) 78 | 79 | const frameProps: IFrameProps = { 80 | imageUrl: 'https://i.imgur.com/osEKmhB.png', 81 | buttons: ['post', 'button2'], 82 | 83 | }; 84 | 85 | res.status(200).send(frameGenerator(frameProps)); 86 | }); 87 | 88 | app.listen(port, () => { 89 | console.log(`Server running on port ${port}`); 90 | }); 91 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | .pnpm-debug.log* 9 | 10 | # Diagnostic reports (https://nodejs.org/api/report.html) 11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | *.pid.lock 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | *.lcov 25 | 26 | # nyc test coverage 27 | .nyc_output 28 | 29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 30 | .grunt 31 | 32 | # Bower dependency directory (https://bower.io/) 33 | bower_components 34 | 35 | # node-waf configuration 36 | .lock-wscript 37 | 38 | # Compiled binary addons (https://nodejs.org/api/addons.html) 39 | build/Release 40 | 41 | # Dependency directories 42 | node_modules/ 43 | jspm_packages/ 44 | 45 | # Snowpack dependency directory (https://snowpack.dev/) 46 | web_modules/ 47 | 48 | # TypeScript cache 49 | *.tsbuildinfo 50 | 51 | # Optional npm cache directory 52 | .npm 53 | 54 | # Optional eslint cache 55 | .eslintcache 56 | 57 | # Optional stylelint cache 58 | .stylelintcache 59 | 60 | # Microbundle cache 61 | .rpt2_cache/ 62 | .rts2_cache_cjs/ 63 | .rts2_cache_es/ 64 | .rts2_cache_umd/ 65 | 66 | # Optional REPL history 67 | .node_repl_history 68 | 69 | # Output of 'npm pack' 70 | *.tgz 71 | 72 | # Yarn Integrity file 73 | .yarn-integrity 74 | 75 | # dotenv environment variable files 76 | .env 77 | .env.development.local 78 | .env.test.local 79 | .env.production.local 80 | .env.local 81 | 82 | # parcel-bundler cache (https://parceljs.org/) 83 | .cache 84 | .parcel-cache 85 | 86 | # Next.js build output 87 | .next 88 | out 89 | 90 | # Nuxt.js build / generate output 91 | .nuxt 92 | dist 93 | 94 | # Gatsby files 95 | .cache/ 96 | # Comment in the public line in if your project uses Gatsby and not Next.js 97 | # https://nextjs.org/blog/next-9-1#public-directory-support 98 | # public 99 | 100 | # vuepress build output 101 | .vuepress/dist 102 | 103 | # vuepress v2.x temp and cache directory 104 | .temp 105 | .cache 106 | 107 | # Docusaurus cache and generated files 108 | .docusaurus 109 | 110 | # Serverless directories 111 | .serverless/ 112 | 113 | # FuseBox cache 114 | .fusebox/ 115 | 116 | # DynamoDB Local files 117 | .dynamodb/ 118 | 119 | # TernJS port file 120 | .tern-port 121 | 122 | # Stores VSCode versions used for testing VSCode extensions 123 | .vscode-test 124 | 125 | # yarn v2 126 | .yarn/cache 127 | .yarn/unplugged 128 | .yarn/build-state.yml 129 | .yarn/install-state.gz 130 | .pnp.* 131 | --------------------------------------------------------------------------------