diff --git a/account-manifest/.gitignore b/account-manifest/.gitignore
new file mode 100644
index 0000000000..bc76babded
--- /dev/null
+++ b/account-manifest/.gitignore
@@ -0,0 +1,7 @@
+# See http://help.github.com/ignore-files/ for more about ignoring files.
+
+# compiled output
+dist
+
+# dependencies
+node_modules
diff --git a/account-manifest/README.md b/account-manifest/README.md
new file mode 100644
index 0000000000..40ede56ea6
--- /dev/null
+++ b/account-manifest/README.md
@@ -0,0 +1,54 @@
+# React + TypeScript + Vite
+
+This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
+
+Currently, two official plugins are available:
+
+- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
+- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
+
+## Expanding the ESLint configuration
+
+If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules:
+
+```js
+export default tseslint.config({
+ extends: [
+ // Remove ...tseslint.configs.recommended and replace with this
+ ...tseslint.configs.recommendedTypeChecked,
+ // Alternatively, use this for stricter rules
+ ...tseslint.configs.strictTypeChecked,
+ // Optionally, add this for stylistic rules
+ ...tseslint.configs.stylisticTypeChecked,
+ ],
+ languageOptions: {
+ // other options...
+ parserOptions: {
+ project: ['./tsconfig.node.json', './tsconfig.app.json'],
+ tsconfigRootDir: import.meta.dirname,
+ },
+ },
+})
+```
+
+You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules:
+
+```js
+// eslint.config.js
+import reactX from 'eslint-plugin-react-x'
+import reactDom from 'eslint-plugin-react-dom'
+
+export default tseslint.config({
+ plugins: {
+ // Add the react-x and react-dom plugins
+ 'react-x': reactX,
+ 'react-dom': reactDom,
+ },
+ rules: {
+ // other rules...
+ // Enable its recommended typescript rules
+ ...reactX.configs['recommended-typescript'].rules,
+ ...reactDom.configs.recommended.rules,
+ },
+})
+```
diff --git a/account-manifest/eslint.config.js b/account-manifest/eslint.config.js
new file mode 100644
index 0000000000..092408a9f0
--- /dev/null
+++ b/account-manifest/eslint.config.js
@@ -0,0 +1,28 @@
+import js from '@eslint/js'
+import globals from 'globals'
+import reactHooks from 'eslint-plugin-react-hooks'
+import reactRefresh from 'eslint-plugin-react-refresh'
+import tseslint from 'typescript-eslint'
+
+export default tseslint.config(
+ { ignores: ['dist'] },
+ {
+ extends: [js.configs.recommended, ...tseslint.configs.recommended],
+ files: ['**/*.{ts,tsx}'],
+ languageOptions: {
+ ecmaVersion: 2020,
+ globals: globals.browser,
+ },
+ plugins: {
+ 'react-hooks': reactHooks,
+ 'react-refresh': reactRefresh,
+ },
+ rules: {
+ ...reactHooks.configs.recommended.rules,
+ 'react-refresh/only-export-components': [
+ 'warn',
+ { allowConstantExport: true },
+ ],
+ },
+ },
+)
diff --git a/account-manifest/index.html b/account-manifest/index.html
new file mode 100644
index 0000000000..778e917ed2
--- /dev/null
+++ b/account-manifest/index.html
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+ Account Manifest Generator
+
+
+
+
+
+
diff --git a/account-manifest/package.json b/account-manifest/package.json
new file mode 100644
index 0000000000..307d6338e8
--- /dev/null
+++ b/account-manifest/package.json
@@ -0,0 +1,35 @@
+{
+ "name": "account-manifest",
+ "private": true,
+ "version": "0.0.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "tsc -b && vite build",
+ "copy": "cp -R ./dist/* ../create-onchain/src/manifest",
+ "build:copy": "npm run build && npm run copy",
+ "lint": "eslint .",
+ "preview": "vite preview"
+ },
+ "dependencies": {
+ "@coinbase/onchainkit": "^0.37.5",
+ "react": "^19.0.0",
+ "react-dom": "^19.0.0",
+ "wagmi": "^2.14.12"
+ },
+ "devDependencies": {
+ "@eslint/js": "^9.21.0",
+ "@tailwindcss/vite": "^4.0.9",
+ "@types/react": "^19.0.10",
+ "@types/react-dom": "^19.0.4",
+ "@vitejs/plugin-react": "^4.3.4",
+ "eslint": "^9.21.0",
+ "eslint-plugin-react-hooks": "^5.1.0",
+ "eslint-plugin-react-refresh": "^0.4.19",
+ "globals": "^15.15.0",
+ "tailwindcss": "^4.0.9",
+ "typescript": "~5.7.2",
+ "typescript-eslint": "^8.24.1",
+ "vite": "^6.2.0"
+ }
+}
diff --git a/account-manifest/src/App.tsx b/account-manifest/src/App.tsx
new file mode 100644
index 0000000000..b4c5a30516
--- /dev/null
+++ b/account-manifest/src/App.tsx
@@ -0,0 +1,26 @@
+import { base } from 'wagmi/chains';
+import { OnchainKitProvider } from '@coinbase/onchainkit';
+import Page from './components/Page';
+
+function App() {
+ return (
+
+
+
+ )
+}
+
+export default App
diff --git a/account-manifest/src/components/Page.tsx b/account-manifest/src/components/Page.tsx
new file mode 100644
index 0000000000..d76a3a1d5c
--- /dev/null
+++ b/account-manifest/src/components/Page.tsx
@@ -0,0 +1,177 @@
+import { useEffect, useRef, useState } from 'react'
+import { useAccount } from 'wagmi';
+import {
+ ConnectWallet,
+ Wallet,
+ WalletDropdown,
+ WalletDropdownDisconnect,
+} from '@coinbase/onchainkit/wallet';
+import {
+ Address,
+ Avatar,
+ Name,
+ Identity,
+ EthBalance,
+} from '@coinbase/onchainkit/identity';
+import { Step } from './Step';
+import { useGetFid } from '../hooks/useGetFid';
+import { validateUrl } from '../utils';
+import { useSignManifest } from '../hooks/useSignManifest';
+
+function Page() {
+ const wsRef = useRef(null);
+ const [fid, setFid] = useState(null);
+ const [domain, setDomain] = useState('');
+ const [domainError, setDomainError] = useState(null);
+
+ const getFid = useGetFid();
+ const { address } = useAccount();
+ const { isPending, error, generateAccountAssociation } = useSignManifest({
+ domain,
+ fid,
+ address,
+ onSigned: (accountAssociation) => {
+ wsRef.current?.send(JSON.stringify(accountAssociation));
+ window.close();
+ }
+ });
+
+ useEffect(() => {
+ wsRef.current = new WebSocket('ws://localhost:3333');
+
+ return () => {
+ wsRef.current?.close();
+ }
+ }, []);
+
+ useEffect(() => {
+ if (address) {
+ getFid(address).then(setFid);
+ }
+ }, [address, getFid])
+
+ useEffect(() => {
+ // super hacky way to remove the sign up button and 'or continue' div from the wallet modal
+ // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: cause above
+ const observer = new MutationObserver((mutations) => {
+ for (const mutation of mutations) {
+ if (mutation.addedNodes.length) {
+ const modal = document.querySelector('[data-testid="ockModalOverlay"]');
+ if (modal) {
+ const signUpButton = modal.querySelector('div > div.flex.w-full.flex-col.gap-3 > button:first-of-type');
+ const orContinueDiv = modal.querySelector('div > div.flex.w-full.flex-col.gap-3 > div.relative');
+
+ if (signUpButton) {
+ signUpButton.style.display = 'none';
+ }
+ if (orContinueDiv) {
+ orContinueDiv.style.display = 'none';
+ }
+ }
+ }
+ };
+ });
+
+ observer.observe(document.body, {
+ childList: true,
+ subtree: true
+ });
+
+ return () => observer.disconnect();
+ }, []);
+
+ const handleValidateUrl = () => {
+ const isValid = validateUrl(domain);
+ if (!isValid) {
+ setDomainError('Invalid URL');
+ }
+ };
+
+ const handleDomainChange = (e: React.ChangeEvent) => {
+ setDomain(e.target.value);
+ setDomainError(null);
+ }
+
+ return (
+
+
+ Use coinbase smart wallet if you have a passkey farcaster account through TBA
+ Use MetaMask or Phantom to set up a wallet using your warpcast recovery key
+ >
+ }
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Enter the domain your app will be hosted on
+ This will be used to generate the account manifest and also added to your .env file as the `NEXT_PUBLIC_URL` variable
+ >
+ }
+ >
+
+
+ {domainError &&
{domainError}
}
+
+
+
+
+ This will generate the account manifest and sign it with your wallet
+ The account manifest will be saved to your .env file as `FARCASTER_HEADER`, `FARCASTER_PAYLOAD` and `FARCASTER_SIGNATURE` variables
+ >
+ }
+ >
+
+ {fid === 0
+ ?
There is no FID associated with this account, please connect with your TBA passkey account.
+ :
Your FID is {fid}
}
+
+
+ {error &&
{error.message.split('\n').map(line => {line}
)}
}
+
+
+
+ )
+}
+
+export default Page;
diff --git a/account-manifest/src/components/Step.tsx b/account-manifest/src/components/Step.tsx
new file mode 100644
index 0000000000..aa30825dc6
--- /dev/null
+++ b/account-manifest/src/components/Step.tsx
@@ -0,0 +1,20 @@
+type StepProps = {
+ disabled?: boolean;
+ label: string;
+ children: React.ReactNode;
+ description?: React.ReactNode;
+}
+
+export function Step({disabled, label, description, children}: StepProps) {
+ return (
+
+
+ {label}
+
+
+ {children}
+
+ {description &&
{description}
}
+
+ )
+}
\ No newline at end of file
diff --git a/account-manifest/src/hooks/useGetFid.ts b/account-manifest/src/hooks/useGetFid.ts
new file mode 100644
index 0000000000..6dc905d1b3
--- /dev/null
+++ b/account-manifest/src/hooks/useGetFid.ts
@@ -0,0 +1,36 @@
+import { http } from "viem";
+import { createPublicClient } from "viem";
+import type { Address } from "viem";
+import { optimism } from "viem/chains";
+
+// ID Registry Contract
+const ID_REGISTRY_ADDRESS = '0x00000000Fc6c5F01Fc30151999387Bb99A9f489b';
+const ID_REGISTRY_ABI = [
+ {
+ "inputs": [{"internalType": "address", "name": "owner", "type": "address"}],
+ "name": "idOf",
+ "outputs": [{"internalType": "uint256", "name": "fid", "type": "uint256"}],
+ "stateMutability": "view",
+ "type": "function"
+ }
+];
+
+export function useGetFid() {
+ return async function getFid(address: Address) {
+ const client = createPublicClient({
+ chain: optimism,
+ transport: http(),
+ });
+
+ // query the ID Registry contract for the fids custody address
+ const resolvedFid = await client.readContract({
+ address: ID_REGISTRY_ADDRESS,
+ abi: ID_REGISTRY_ABI,
+ functionName: 'idOf',
+ args: [address],
+ });
+
+ return Number(resolvedFid);
+ }
+}
+
diff --git a/account-manifest/src/hooks/useSignManifest.ts b/account-manifest/src/hooks/useSignManifest.ts
new file mode 100644
index 0000000000..0366800ebb
--- /dev/null
+++ b/account-manifest/src/hooks/useSignManifest.ts
@@ -0,0 +1,72 @@
+// hooks/useAccountManifest.ts
+import { useRef, useEffect } from 'react';
+import { useSignMessage } from 'wagmi';
+import { toBase64Url } from '../utils';
+
+export type AccountAssociation = {
+ header: string;
+ payload: string;
+ signature: string;
+ domain: string;
+}
+
+type SignManifestProps ={
+ domain: string;
+ fid: number | null;
+ address: string | undefined;
+ onSigned: (accountAssociation: AccountAssociation) => void;
+}
+
+export function useSignManifest({ domain, fid, address, onSigned }: SignManifestProps) {
+ const encodedHeader = useRef('');
+ const encodedPayload = useRef('');
+ const { data: signMessageData, isPending, error, signMessage } = useSignMessage();
+
+ useEffect(() => {
+ async function signManifest() {
+ if (signMessageData) {
+ const encodedSignature = toBase64Url(signMessageData);
+ const accountAssociation = {
+ header: encodedHeader.current,
+ payload: encodedPayload.current,
+ signature: encodedSignature,
+ domain: domain,
+ };
+
+ console.log('Account association generated:', accountAssociation);
+ onSigned(accountAssociation);
+ }
+ }
+
+ signManifest();
+ }, [signMessageData, domain, onSigned]);
+
+ const generateAccountAssociation = async () => {
+ if (!domain || !fid || !address) {
+ console.error('Domain, FID and wallet connection are required');
+ return;
+ }
+
+ const header = {
+ fid,
+ type: "custody",
+ key: address,
+ };
+
+ const payload = {
+ domain: domain.replace(/^(http|https):\/\//, ''),
+ };
+
+ encodedHeader.current = toBase64Url(JSON.stringify(header));
+ encodedPayload.current = toBase64Url(JSON.stringify(payload));
+ const messageToSign = `${encodedHeader.current}.${encodedPayload.current}`;
+
+ signMessage({message: messageToSign});
+ };
+
+ return {
+ isPending,
+ error,
+ generateAccountAssociation
+ };
+}
\ No newline at end of file
diff --git a/account-manifest/src/index.css b/account-manifest/src/index.css
new file mode 100644
index 0000000000..78db37f7cb
--- /dev/null
+++ b/account-manifest/src/index.css
@@ -0,0 +1,37 @@
+@import "tailwindcss";
+
+:root {
+ font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
+ line-height: 1.5;
+ font-weight: 400;
+
+ color-scheme: light dark;
+ color: rgba(255, 255, 255, 0.87);
+ background-color: #242424;
+
+ font-synthesis: none;
+ text-rendering: optimizeLegibility;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+}
+
+a {
+ font-weight: 500;
+ color: #646cff;
+ text-decoration: inherit;
+}
+
+a:hover {
+ color: #535bf2;
+}
+
+body {
+ margin: 0;
+ min-width: 320px;
+ min-height: 100vh;
+}
+
+header, main {
+ max-width: 600px;
+ margin: 0 auto;
+}
\ No newline at end of file
diff --git a/account-manifest/src/main.tsx b/account-manifest/src/main.tsx
new file mode 100644
index 0000000000..fde6998ed1
--- /dev/null
+++ b/account-manifest/src/main.tsx
@@ -0,0 +1,16 @@
+import { StrictMode } from 'react'
+import { createRoot } from 'react-dom/client'
+import './index.css'
+import '@coinbase/onchainkit/styles.css';
+import App from './App.tsx'
+
+const root = document.getElementById('root');
+if (!root) {
+ throw new Error('Root element not found');
+}
+
+createRoot(root).render(
+
+
+ ,
+)
diff --git a/account-manifest/src/utils.ts b/account-manifest/src/utils.ts
new file mode 100644
index 0000000000..5424a0897f
--- /dev/null
+++ b/account-manifest/src/utils.ts
@@ -0,0 +1,11 @@
+export const toBase64Url = (str: string) => {
+ return btoa(str)
+ .replace(/\+/g, '-')
+ .replace(/\//g, '_')
+ .replace(/=/g, '');
+};
+
+export const validateUrl = (domain: string) => {
+ const urlRegex = /^https?:\/\/([\da-z.-]+)\.([a-z.]{2,6})(\/[\w.-]*(?: [\w.-]*)*)*\/?$/
+ return domain.length === 0 || (domain.length > 0 && urlRegex.test(domain));
+};
\ No newline at end of file
diff --git a/account-manifest/src/vite-env.d.ts b/account-manifest/src/vite-env.d.ts
new file mode 100644
index 0000000000..11f02fe2a0
--- /dev/null
+++ b/account-manifest/src/vite-env.d.ts
@@ -0,0 +1 @@
+///
diff --git a/account-manifest/tailwind.config.js b/account-manifest/tailwind.config.js
new file mode 100644
index 0000000000..27a2e56a9a
--- /dev/null
+++ b/account-manifest/tailwind.config.js
@@ -0,0 +1,11 @@
+/** @type {import('tailwindcss').Config} */
+
+export default {
+ content: [
+ "./index.html",
+ "./src/**/*.{js,ts,jsx,tsx}",
+ ], theme: {
+ extend: {},
+ },
+ plugins: [],
+}
\ No newline at end of file
diff --git a/account-manifest/tsconfig.app.json b/account-manifest/tsconfig.app.json
new file mode 100644
index 0000000000..358ca9ba93
--- /dev/null
+++ b/account-manifest/tsconfig.app.json
@@ -0,0 +1,26 @@
+{
+ "compilerOptions": {
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
+ "target": "ES2020",
+ "useDefineForClassFields": true,
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
+ "module": "ESNext",
+ "skipLibCheck": true,
+
+ /* Bundler mode */
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+ "isolatedModules": true,
+ "moduleDetection": "force",
+ "noEmit": true,
+ "jsx": "react-jsx",
+
+ /* Linting */
+ "strict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "noFallthroughCasesInSwitch": true,
+ "noUncheckedSideEffectImports": true
+ },
+ "include": ["src"]
+}
diff --git a/account-manifest/tsconfig.json b/account-manifest/tsconfig.json
new file mode 100644
index 0000000000..1ffef600d9
--- /dev/null
+++ b/account-manifest/tsconfig.json
@@ -0,0 +1,7 @@
+{
+ "files": [],
+ "references": [
+ { "path": "./tsconfig.app.json" },
+ { "path": "./tsconfig.node.json" }
+ ]
+}
diff --git a/account-manifest/tsconfig.node.json b/account-manifest/tsconfig.node.json
new file mode 100644
index 0000000000..db0becc8b0
--- /dev/null
+++ b/account-manifest/tsconfig.node.json
@@ -0,0 +1,24 @@
+{
+ "compilerOptions": {
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
+ "target": "ES2022",
+ "lib": ["ES2023"],
+ "module": "ESNext",
+ "skipLibCheck": true,
+
+ /* Bundler mode */
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+ "isolatedModules": true,
+ "moduleDetection": "force",
+ "noEmit": true,
+
+ /* Linting */
+ "strict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "noFallthroughCasesInSwitch": true,
+ "noUncheckedSideEffectImports": true
+ },
+ "include": ["vite.config.ts"]
+}
diff --git a/account-manifest/vite.config.ts b/account-manifest/vite.config.ts
new file mode 100644
index 0000000000..0616e59599
--- /dev/null
+++ b/account-manifest/vite.config.ts
@@ -0,0 +1,7 @@
+import { defineConfig } from 'vite'
+import react from '@vitejs/plugin-react'
+import tailwindcss from '@tailwindcss/vite'
+
+export default defineConfig({
+ plugins: [react(), tailwindcss()],
+})
diff --git a/create-onchain/.gitignore b/create-onchain/.gitignore
new file mode 100644
index 0000000000..324d32c940
--- /dev/null
+++ b/create-onchain/.gitignore
@@ -0,0 +1,2 @@
+src/manifest/*
+!src/manifest/.gitkeep
\ No newline at end of file
diff --git a/create-onchain/package.json b/create-onchain/package.json
index 9933d9c9b9..8d844de3e3 100644
--- a/create-onchain/package.json
+++ b/create-onchain/package.json
@@ -4,7 +4,8 @@
"version": "0.0.16",
"license": "MIT",
"scripts": {
- "build": "bun run clean && bun run build:esm+types",
+ "build": "bun run clean && bun run build:esm+types && bun run build:manifest",
+ "build:manifest": "cd ../account-manifest && bun install && bun run build && cd ../create-onchain && cp -R ../account-manifest/dist/* ./src/manifest",
"build:esm+types": "tsc --project tsconfig.build.json --outDir ./dist/esm --declaration --declarationMap --declarationDir ./dist/types",
"check:types": "tsc --noEmit",
"clean": "rm -rf dist tsconfig.tsbuildinfo",
@@ -28,14 +29,17 @@
},
"dependencies": {
"cac": "^6.7.14",
- "cross-spawn": "^7.0.3",
+ "express": "^4.21.2",
+ "open": "^10.1.0",
"ora": "^8.1.0",
"picocolors": "^1.1.0",
- "prompts": "^2.4.2"
+ "prompts": "^2.4.2",
+ "ws": "^8.18.1"
},
"devDependencies": {
- "@types/cross-spawn": "^6.0.6",
+ "@types/express": "^5.0.0",
"@types/node": "^20.12.10",
- "@types/prompts": "^2.4.9"
+ "@types/prompts": "^2.4.9",
+ "@types/ws": "^8.5.14"
}
-}
\ No newline at end of file
+}
diff --git a/create-onchain/src/cli.ts b/create-onchain/src/cli.ts
index 1d723c704b..9e8c0274e1 100644
--- a/create-onchain/src/cli.ts
+++ b/create-onchain/src/cli.ts
@@ -11,7 +11,10 @@ import {
toValidPackageName,
optimizedCopy,
} from './utils.js';
-import { spawn } from 'child_process';
+import open from 'open';
+import express from 'express';
+import { createServer } from 'http';
+import { WebSocketServer, WebSocket } from 'ws';
const renameFiles: Record = {
_gitignore: '.gitignore',
@@ -41,6 +44,98 @@ async function copyDir(src: string, dest: string) {
}
}
+type WebpageData = { header: string, payload: string, signature: string, domain: string };
+
+async function getWebpageData(browser: 'safari' | 'google chrome' | 'none'): Promise {
+ const app = express();
+ const server = createServer(app);
+ const wss = new WebSocketServer({ server });
+
+ app.use(express.static(path.resolve(
+ fileURLToPath(import.meta.url),
+ '../../../src/manifest'
+ )));
+
+ return new Promise((resolve, reject) => {
+ wss.on('connection', (ws: WebSocket) => {
+ ws.on('message', (data: Buffer) => {
+ const parsedData = JSON.parse(data.toString());
+ server.close();
+ resolve(parsedData);
+ });
+ ws.on('close', () => {
+ server.close();
+ reject(new Error('WebSocket connection closed'));
+ });
+ });
+
+ server.listen(3333, () => {
+ open('http://localhost:3333', browser === 'none' ? undefined : {
+ app: {
+ name: browser
+ }
+ });
+ });
+ });
+}
+
+async function createMiniKitAccountAssociation(envPath?: string) {
+ if (!envPath) {
+ envPath = path.join(process.cwd(), '.env');
+ }
+
+ const existingEnv = await fs.promises.readFile(envPath, 'utf-8').catch(() => null);
+ if (!existingEnv) {
+ console.log(pc.red('\n* Failed to read .env file. Please ensure you are in your project directory.'));
+ return false;
+ }
+
+ let browserResult: prompts.Answers<'browser'>;
+ try {
+ browserResult = await prompts(
+ [
+ {
+ type: 'select',
+ name: 'browser',
+ message: pc.reset('If you want to sign your account manifest with your TBA account, please select the OS of your device:'),
+ choices: [
+ { title: 'iOS', value: 'safari' },
+ { title: 'Android', value: 'google chrome' },
+ { title: 'Not using a passkey account', value: 'none' },
+ ],
+ }
+ ],
+ {
+ onCancel: () => {
+ throw new Error('Browser selection cancelled.');
+ },
+ }
+ );
+ } catch (cancelled: any) {
+ console.log(pc.red(`\n${cancelled.message}`));
+ return false;
+ }
+
+ const { browser } = browserResult;
+ try {
+ const webpageData = await getWebpageData(browser);
+ const envContent = `FARCASTER_HEADER=${webpageData.header}\nFARCASTER_PAYLOAD=${webpageData.payload}\nFARCASTER_SIGNATURE=${webpageData.signature}\nNEXT_PUBLIC_URL=${webpageData.domain}`;
+ const updatedEnv = existingEnv
+ .split('\n')
+ .filter(line => !line.startsWith('FARCASTER_') && !line.startsWith('NEXT_PUBLIC_URL'))
+ .concat(envContent)
+ .join('\n');
+ await fs.promises.writeFile(envPath, updatedEnv);
+
+ console.log(pc.blue('\n* Account association generated successfully and added to your .env file!'));
+ } catch (error) {
+ console.log(pc.red('\n* Failed to generate account association. Please try again.'));
+ return false;
+ }
+
+ return true;
+}
+
async function createMiniKitTemplate() {
console.log(
`${pc.greenBright(`
@@ -153,10 +248,10 @@ REDIS_TOKEN=`
spinner.succeed();
- console.log(`\n${pc.magenta(`Created new MiniKit project in ${root}`)}`);
+ console.log(`\n${pc.magenta(`Created new MiniKit project in ${root}`)}\n`);
- console.log('\nWould you like to set up your Frames Account Association now?');
- console.log(pc.blue('* You can set this up later by running `npm run generate-account-association` or updating your `.env` file manually.\n'));
+ console.log(`\n${pc.reset('Do you want to set up your Frames Account Manifest now?')}`);
+ console.log(pc.blue('* You can run this later by running `npm create-onchain --generate` in your project directory.'));
let setUpFrameResult: prompts.Answers<'setUpFrame'>;
try {
@@ -165,49 +260,30 @@ REDIS_TOKEN=`
{
type: 'toggle',
name: 'setUpFrame',
- message: pc.reset('Set up Frame integration now?'),
+ message: pc.reset('Set up now?'),
initial: true,
active: 'yes',
inactive: 'no',
- }
+ },
],
{
onCancel: () => {
console.log('\nSetup frame cancelled.');
- process.exit(1);
+ return false;
},
}
);
} catch (cancelled: any) {
console.log(cancelled.message);
- process.exit(1);
+ return false;
}
const { setUpFrame } = setUpFrameResult;
if (setUpFrame) {
- const scriptPath = path.resolve(
- fileURLToPath(import.meta.url),
- '../../../templates/minikit/scripts/generateAccountAssociation.mjs'
- );
-
- // spawn the generate-account-association command
- const generateAccountAssociation = spawn('node', [scriptPath, root], {
- stdio: 'inherit',
- cwd: process.cwd(),
- shell: true
- });
-
- generateAccountAssociation.on('close', (code: number) => {
- if (code === 0) {
- logMiniKitSetupSummary(projectName, root, clientKey);
- } else {
- console.error('Failed to generate account association');
- logMiniKitSetupSummary(projectName, root, clientKey);
- }
- });
- } else {
- logMiniKitSetupSummary(projectName, root, clientKey);
+ await createMiniKitAccountAssociation(envPath);
}
+
+ logMiniKitSetupSummary(projectName, root, clientKey);
}
function logMiniKitSetupSummary(projectName: string, root: string, clientKey: string) {
@@ -392,9 +468,6 @@ async function createOnchainKitTemplate() {
async function init() {
const isHelp = process.argv.some(arg => ['--help', '-h'].includes(arg));
- const isVersion = process.argv.some(arg => ['--version', '-v'].includes(arg));
- const isMinikit = process.argv.some(arg => ['--mini', '-m'].includes(arg));
-
if (isHelp) {
console.log(
`${pc.greenBright(`
@@ -406,19 +479,32 @@ Creates an OnchainKit project based on nextJs.
Options:
--version, -v: Show version
--mini, -m: Create a MiniKit project
+--generate, -g: Generate your Frames account association
--help, -h: Show help
`)}`
);
process.exit(0);
}
+ const isVersion = process.argv.some(arg => ['--version', '-v'].includes(arg));
if (isVersion) {
- const packageJsonContent = fs.readFileSync('./package.json', 'utf8');
+ const pkgPath = path.resolve(
+ fileURLToPath(import.meta.url),
+ '../../../package.json'
+ );
+ const packageJsonContent = fs.readFileSync(pkgPath, 'utf8');
const packageJson = JSON.parse(packageJsonContent);
console.log(`${pc.greenBright(`v${packageJson.version}`)}`);
process.exit(0);
}
+ const isGenerate = process.argv.some(arg => ['--generate', '-g'].includes(arg));
+ if (isGenerate) {
+ await createMiniKitAccountAssociation();
+ process.exit(0);
+ }
+
+ const isMinikit = process.argv.some(arg => ['--mini', '-m'].includes(arg));
if (isMinikit) {
await createMiniKitTemplate();
} else {