create-fogo-app 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,109 @@
1
+ # create-fogo-app
2
+
3
+ Scaffold a production-ready Fogo dApp with Sessions SDK in seconds.
4
+
5
+ ## 🚀 Quick Start
6
+
7
+ ```bash
8
+ npx create-fogo-app my-fogo-app
9
+ cd my-fogo-app
10
+ npm install
11
+ npm run dev
12
+ ```
13
+
14
+ ## 📦 What's Included
15
+
16
+ - **Next.js 14** with App Router and TypeScript
17
+ - **Fogo Sessions SDK** pre-configured for gasless transactions
18
+ - **Solana Wallet Adapters** (Phantom, Solflare, etc.)
19
+ - **TailwindCSS** + **shadcn/ui** for beautiful UI
20
+ - **SPL Memo Demo** to showcase session key signing
21
+
22
+ ## 🎯 Features
23
+
24
+ - ✅ Zero-friction setup - just run and start building
25
+ - ✅ Gasless transactions via Fogo Sessions
26
+ - ✅ Session key management out of the box
27
+ - ✅ Modern, responsive UI components
28
+ - ✅ TypeScript for type safety
29
+ - ✅ Production-ready configuration
30
+
31
+ ## 📖 Usage
32
+
33
+ ### Create a New Project
34
+
35
+ ```bash
36
+ # Interactive mode
37
+ npx create-fogo-app
38
+
39
+ # With project name
40
+ npx create-fogo-app my-awesome-dapp
41
+ ```
42
+
43
+ ### Configure Your App
44
+
45
+ After creating your project:
46
+
47
+ 1. Update `.env` with your paymaster details:
48
+
49
+ ```env
50
+ NEXT_PUBLIC_PAYMASTER_URL=your-paymaster-url
51
+ NEXT_PUBLIC_SPONSOR_PUBLIC_KEY=your-sponsor-key
52
+ ```
53
+
54
+ 2. Install dependencies:
55
+
56
+ ```bash
57
+ npm install
58
+ ```
59
+
60
+ 3. Start developing:
61
+ ```bash
62
+ npm run dev
63
+ ```
64
+
65
+ ## 🏗️ What Gets Scaffolded
66
+
67
+ ```
68
+ my-fogo-app/
69
+ ├── app/
70
+ │ ├── layout.tsx # Root layout with Fogo providers
71
+ │ ├── page.tsx # Home page with demo
72
+ │ ├── providers.tsx # Fogo Sessions + Wallet setup
73
+ │ └── globals.css # Global styles
74
+ ├── components/
75
+ │ ├── ui/ # shadcn/ui components
76
+ │ ├── SessionButton.tsx # Session management
77
+ │ └── MemoDemo.tsx # SPL Memo demo
78
+ ├── hooks/
79
+ │ └── useFogoSession.ts # Custom session hook
80
+ ├── lib/
81
+ │ └── utils.ts # Utilities
82
+ ├── .env.example # Environment template
83
+ ├── next.config.ts # Next.js config
84
+ ├── tailwind.config.ts # Tailwind config
85
+ └── package.json
86
+ ```
87
+
88
+ ## 🔧 Requirements
89
+
90
+ - Node.js 18 or higher
91
+ - npm, yarn, or pnpm
92
+
93
+ ## 📚 Learn More
94
+
95
+ - [Fogo Documentation](https://fogo.io/docs)
96
+ - [Fogo Sessions SDK](https://github.com/fogo-foundation/sessions-sdk)
97
+ - [Example Apps](https://github.com/fogo-foundation/sessions-example-vite)
98
+
99
+ ## 🤝 Contributing
100
+
101
+ Contributions are welcome! Please open an issue or submit a PR.
102
+
103
+ ## 📄 License
104
+
105
+ MIT
106
+
107
+ ---
108
+
109
+ Built with 🔥 by the Fogo community
package/bin/cli.js ADDED
@@ -0,0 +1,97 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { Command } from "commander";
4
+ import prompts from "prompts";
5
+ import fs from "fs-extra";
6
+ import path from "path";
7
+ import { fileURLToPath } from "url";
8
+ import chalk from "chalk";
9
+
10
+ const __filename = fileURLToPath(import.meta.url);
11
+ const __dirname = path.dirname(__filename);
12
+
13
+ const program = new Command();
14
+
15
+ program
16
+ .name("create-fogo-app")
17
+ .description("Scaffold a production-ready Fogo dApp with Sessions SDK")
18
+ .version("1.0.0")
19
+ .argument("[project-name]", "Name of your project")
20
+ .action(async (projectName) => {
21
+ console.log(chalk.bold.cyan("\n🔥 Welcome to create-fogo-app!\n"));
22
+
23
+ // Prompt for project name if not provided
24
+ if (!projectName) {
25
+ const response = await prompts({
26
+ type: "text",
27
+ name: "projectName",
28
+ message: "What is your project named?",
29
+ initial: "my-fogo-app",
30
+ validate: (value) => {
31
+ if (!value) return "Project name is required";
32
+ if (!/^[a-z0-9-_]+$/.test(value)) {
33
+ return "Project name can only contain lowercase letters, numbers, hyphens, and underscores";
34
+ }
35
+ return true;
36
+ },
37
+ });
38
+
39
+ if (!response.projectName) {
40
+ console.log(chalk.red("\n❌ Project creation cancelled\n"));
41
+ process.exit(1);
42
+ }
43
+
44
+ projectName = response.projectName;
45
+ }
46
+
47
+ const targetDir = path.join(process.cwd(), projectName);
48
+
49
+ // Check if directory already exists
50
+ if (fs.existsSync(targetDir)) {
51
+ console.log(
52
+ chalk.red(`\n❌ Directory "${projectName}" already exists!\n`)
53
+ );
54
+ process.exit(1);
55
+ }
56
+
57
+ console.log(
58
+ chalk.blue(`\n📦 Creating project in ${chalk.bold(targetDir)}...\n`)
59
+ );
60
+
61
+ try {
62
+ // Copy template directory
63
+ const templateDir = path.join(__dirname, "..", "template");
64
+ await fs.copy(templateDir, targetDir);
65
+
66
+ // Update package.json with project name
67
+ const packageJsonPath = path.join(targetDir, "package.json");
68
+ const packageJson = await fs.readJson(packageJsonPath);
69
+ packageJson.name = projectName;
70
+ await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
71
+
72
+ // Copy .env.example to .env
73
+ const envExamplePath = path.join(targetDir, ".env.example");
74
+ const envPath = path.join(targetDir, ".env");
75
+ if (fs.existsSync(envExamplePath)) {
76
+ await fs.copy(envExamplePath, envPath);
77
+ }
78
+
79
+ console.log(chalk.green("✅ Project created successfully!\n"));
80
+ console.log(chalk.bold("Next steps:\n"));
81
+ console.log(chalk.cyan(` cd ${projectName}`));
82
+ console.log(chalk.cyan(" npm install"));
83
+ console.log(chalk.cyan(" npm run dev\n"));
84
+ console.log(chalk.yellow("⚠️ Important:"));
85
+ console.log(
86
+ chalk.yellow(
87
+ " Update .env with your Paymaster URL and Sponsor Public Key\n"
88
+ )
89
+ );
90
+ console.log(chalk.magenta("📚 Learn more: https://fogo.io/docs\n"));
91
+ } catch (error) {
92
+ console.log(chalk.red(`\n❌ Error creating project: ${error.message}\n`));
93
+ process.exit(1);
94
+ }
95
+ });
96
+
97
+ program.parse();
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "create-fogo-app",
3
+ "version": "1.0.0",
4
+ "description": "CLI to scaffold a Fogo dApp with Sessions SDK pre-configured",
5
+ "type": "module",
6
+ "bin": {
7
+ "create-fogo-app": "bin/cli.js"
8
+ },
9
+ "files": [
10
+ "bin",
11
+ "template"
12
+ ],
13
+ "keywords": [
14
+ "fogo",
15
+ "solana",
16
+ "web3",
17
+ "blockchain",
18
+ "sessions",
19
+ "dapp",
20
+ "cli",
21
+ "scaffold"
22
+ ],
23
+ "author": "",
24
+ "license": "MIT",
25
+ "dependencies": {
26
+ "chalk": "^5.3.0",
27
+ "commander": "^12.0.0",
28
+ "fs-extra": "^11.2.0",
29
+ "prompts": "^2.4.2"
30
+ },
31
+ "engines": {
32
+ "node": ">=18.0.0"
33
+ }
34
+ }
@@ -0,0 +1,10 @@
1
+ # Fogo RPC Endpoint (Testnet - zero friction for development)
2
+ NEXT_PUBLIC_FOGO_RPC_URL=https://testnet.fogo.io
3
+
4
+ # Uncomment for Mainnet (requires FluxRPC or custom endpoint)
5
+ # NEXT_PUBLIC_FOGO_RPC_URL=https://your-mainnet-rpc-url.com
6
+
7
+ # Paymaster Configuration
8
+ # Update these values with your actual paymaster details
9
+ NEXT_PUBLIC_PAYMASTER_URL=https://paymaster.testnet.fogo.io
10
+ NEXT_PUBLIC_SPONSOR_PUBLIC_KEY=YourSponsorPublicKeyHere
@@ -0,0 +1,121 @@
1
+ # Fogo Sessions dApp
2
+
3
+ A production-ready Next.js application with Fogo Sessions SDK pre-configured for gasless transactions.
4
+
5
+ ## 🚀 Features
6
+
7
+ - **Gasless Transactions**: Users interact without paying gas fees
8
+ - **Session Keys**: One-time approval for seamless UX
9
+ - **SPL Memo Demo**: "Say GM" button demonstrates session key signing
10
+ - **Wallet Integration**: Supports Phantom, Solflare, and all Solana wallets
11
+ - **Modern Stack**: Next.js 14, TypeScript, TailwindCSS, shadcn/ui
12
+
13
+ ## 📋 Prerequisites
14
+
15
+ - Node.js 18+ installed
16
+ - A Solana wallet (Phantom, Solflare, etc.)
17
+ - Fogo testnet tokens (for testing)
18
+
19
+ ## 🛠️ Getting Started
20
+
21
+ ### 1. Install Dependencies
22
+
23
+ ```bash
24
+ npm install
25
+ ```
26
+
27
+ ### 2. Configure Environment
28
+
29
+ Copy `.env.example` to `.env` and update the values:
30
+
31
+ ```env
32
+ NEXT_PUBLIC_FOGO_RPC_URL=https://testnet.fogo.io
33
+ NEXT_PUBLIC_PAYMASTER_URL=https://paymaster.testnet.fogo.io
34
+ NEXT_PUBLIC_SPONSOR_PUBLIC_KEY=YourSponsorPublicKeyHere
35
+ ```
36
+
37
+ > **Important**: You need to set up a paymaster sponsor account. Visit [Fogo Documentation](https://fogo.io/docs) for instructions.
38
+
39
+ ### 3. Run Development Server
40
+
41
+ ```bash
42
+ npm run dev
43
+ ```
44
+
45
+ Open [http://localhost:3000](http://localhost:3000) in your browser.
46
+
47
+ ## 🎮 How to Use
48
+
49
+ 1. **Connect Wallet**: Click the wallet button to connect your Solana wallet
50
+ 2. **Establish Session**: Click "Establish Session" to create a session key (one-time approval)
51
+ 3. **Say GM**: Click "Say GM 👋" to send a memo transaction without a wallet popup!
52
+
53
+ The transaction is signed by your session key in the background and sent via the paymaster.
54
+
55
+ ## 📁 Project Structure
56
+
57
+ ```
58
+ ├── app/
59
+ │ ├── layout.tsx # Root layout with providers
60
+ │ ├── page.tsx # Home page
61
+ │ ├── providers.tsx # Fogo Sessions & Wallet providers
62
+ │ └── globals.css # Global styles
63
+ ├── components/
64
+ │ ├── ui/ # shadcn/ui components
65
+ │ ├── SessionButton.tsx # Session management UI
66
+ │ └── MemoDemo.tsx # SPL Memo demo
67
+ ├── hooks/
68
+ │ └── useFogoSession.ts # Custom session hook
69
+ └── lib/
70
+ └── utils.ts # Utility functions
71
+ ```
72
+
73
+ ## 🔑 Key Concepts
74
+
75
+ ### Fogo Sessions
76
+
77
+ Fogo Sessions enable a Web2-like UX in Web3 by:
78
+
79
+ - Creating temporary session keys with scoped permissions
80
+ - Allowing transactions without repeated wallet approvals
81
+ - Enabling gasless transactions via paymasters
82
+
83
+ ### Session Flow
84
+
85
+ 1. User connects main wallet
86
+ 2. App requests session with specific limits
87
+ 3. User approves once
88
+ 4. Session key signs transactions in background
89
+ 5. Paymaster covers gas fees
90
+
91
+ ## 🚢 Deployment
92
+
93
+ ### Build for Production
94
+
95
+ ```bash
96
+ npm run build
97
+ npm start
98
+ ```
99
+
100
+ ### Deploy to Vercel
101
+
102
+ ```bash
103
+ npx vercel
104
+ ```
105
+
106
+ Make sure to add environment variables in your Vercel project settings.
107
+
108
+ ## 📚 Learn More
109
+
110
+ - [Fogo Documentation](https://fogo.io/docs)
111
+ - [Fogo Sessions SDK](https://github.com/fogo-foundation/sessions-sdk)
112
+ - [Next.js Documentation](https://nextjs.org/docs)
113
+ - [Solana Web3.js](https://solana-labs.github.io/solana-web3.js/)
114
+
115
+ ## 🤝 Contributing
116
+
117
+ Contributions are welcome! Please feel free to submit a Pull Request.
118
+
119
+ ## 📄 License
120
+
121
+ MIT
@@ -0,0 +1,62 @@
1
+ @tailwind base;
2
+ @tailwind components;
3
+ @tailwind utilities;
4
+
5
+ @layer base {
6
+ :root {
7
+ --background: 0 0% 100%;
8
+ --foreground: 222.2 84% 4.9%;
9
+ --card: 0 0% 100%;
10
+ --card-foreground: 222.2 84% 4.9%;
11
+ --popover: 0 0% 100%;
12
+ --popover-foreground: 222.2 84% 4.9%;
13
+ --primary: 222.2 47.4% 11.2%;
14
+ --primary-foreground: 210 40% 98%;
15
+ --secondary: 210 40% 96.1%;
16
+ --secondary-foreground: 222.2 47.4% 11.2%;
17
+ --muted: 210 40% 96.1%;
18
+ --muted-foreground: 215.4 16.3% 46.9%;
19
+ --accent: 210 40% 96.1%;
20
+ --accent-foreground: 222.2 47.4% 11.2%;
21
+ --destructive: 0 84.2% 60.2%;
22
+ --destructive-foreground: 210 40% 98%;
23
+ --border: 214.3 31.8% 91.4%;
24
+ --input: 214.3 31.8% 91.4%;
25
+ --ring: 222.2 84% 4.9%;
26
+ --radius: 0.5rem;
27
+ }
28
+
29
+ .dark {
30
+ --background: 222.2 84% 4.9%;
31
+ --foreground: 210 40% 98%;
32
+ --card: 222.2 84% 4.9%;
33
+ --card-foreground: 210 40% 98%;
34
+ --popover: 222.2 84% 4.9%;
35
+ --popover-foreground: 210 40% 98%;
36
+ --primary: 210 40% 98%;
37
+ --primary-foreground: 222.2 47.4% 11.2%;
38
+ --secondary: 217.2 32.6% 17.5%;
39
+ --secondary-foreground: 210 40% 98%;
40
+ --muted: 217.2 32.6% 17.5%;
41
+ --muted-foreground: 215 20.2% 65.1%;
42
+ --accent: 217.2 32.6% 17.5%;
43
+ --accent-foreground: 210 40% 98%;
44
+ --destructive: 0 62.8% 30.6%;
45
+ --destructive-foreground: 210 40% 98%;
46
+ --border: 217.2 32.6% 17.5%;
47
+ --input: 217.2 32.6% 17.5%;
48
+ --ring: 212.7 26.8% 83.9%;
49
+ }
50
+ }
51
+
52
+ @layer base {
53
+ * {
54
+ @apply border-border;
55
+ }
56
+ body {
57
+ @apply bg-background text-foreground;
58
+ }
59
+ }
60
+
61
+ /* Solana Wallet Adapter Styles */
62
+ @import "@solana/wallet-adapter-react-ui/styles.css";
@@ -0,0 +1,25 @@
1
+ import type { Metadata } from "next";
2
+ import { Inter } from "next/font/google";
3
+ import "./globals.css";
4
+ import { Providers } from "./providers";
5
+
6
+ const inter = Inter({ subsets: ["latin"] });
7
+
8
+ export const metadata: Metadata = {
9
+ title: "Fogo Sessions dApp",
10
+ description: "Experience gasless transactions with Fogo Sessions",
11
+ };
12
+
13
+ export default function RootLayout({
14
+ children,
15
+ }: Readonly<{
16
+ children: React.ReactNode;
17
+ }>) {
18
+ return (
19
+ <html lang="en">
20
+ <body className={inter.className}>
21
+ <Providers>{children}</Providers>
22
+ </body>
23
+ </html>
24
+ );
25
+ }
@@ -0,0 +1,95 @@
1
+ import { SessionButton } from '@/components/SessionButton';
2
+ import { MemoDemo } from '@/components/MemoDemo';
3
+
4
+ export default function Home() {
5
+ return (
6
+ <main className="min-h-screen bg-gradient-to-b from-background to-muted/20">
7
+ <div className="container mx-auto px-4 py-16">
8
+ <div className="max-w-4xl mx-auto space-y-12">
9
+ {/* Hero Section */}
10
+ <div className="text-center space-y-4">
11
+ <h1 className="text-5xl font-bold bg-gradient-to-r from-orange-500 to-red-500 bg-clip-text text-transparent">
12
+ Welcome to Fogo Sessions
13
+ </h1>
14
+ <p className="text-xl text-muted-foreground max-w-2xl mx-auto">
15
+ Experience the future of Web3 UX with gasless transactions powered by session keys
16
+ </p>
17
+ </div>
18
+
19
+ {/* Demo Cards */}
20
+ <div className="grid md:grid-cols-2 gap-6">
21
+ <SessionButton />
22
+ <MemoDemo />
23
+ </div>
24
+
25
+ {/* How It Works */}
26
+ <div className="bg-card border rounded-lg p-8 space-y-6">
27
+ <h2 className="text-2xl font-semibold">How It Works</h2>
28
+
29
+ <div className="grid gap-4">
30
+ <div className="flex gap-4">
31
+ <div className="flex-shrink-0 w-8 h-8 rounded-full bg-primary text-primary-foreground flex items-center justify-center font-bold">
32
+ 1
33
+ </div>
34
+ <div>
35
+ <h3 className="font-semibold mb-1">Connect Your Wallet</h3>
36
+ <p className="text-sm text-muted-foreground">
37
+ Use any Solana-compatible wallet (Phantom, Solflare, etc.)
38
+ </p>
39
+ </div>
40
+ </div>
41
+
42
+ <div className="flex gap-4">
43
+ <div className="flex-shrink-0 w-8 h-8 rounded-full bg-primary text-primary-foreground flex items-center justify-center font-bold">
44
+ 2
45
+ </div>
46
+ <div>
47
+ <h3 className="font-semibold mb-1">Establish a Session</h3>
48
+ <p className="text-sm text-muted-foreground">
49
+ Approve once to create a temporary session key with scoped permissions
50
+ </p>
51
+ </div>
52
+ </div>
53
+
54
+ <div className="flex gap-4">
55
+ <div className="flex-shrink-0 w-8 h-8 rounded-full bg-primary text-primary-foreground flex items-center justify-center font-bold">
56
+ 3
57
+ </div>
58
+ <div>
59
+ <h3 className="font-semibold mb-1">Interact Seamlessly</h3>
60
+ <p className="text-sm text-muted-foreground">
61
+ Click "Say GM" and watch the transaction complete without any wallet popup!
62
+ </p>
63
+ </div>
64
+ </div>
65
+
66
+ <div className="flex gap-4">
67
+ <div className="flex-shrink-0 w-8 h-8 rounded-full bg-primary text-primary-foreground flex items-center justify-center font-bold">
68
+ 4
69
+ </div>
70
+ <div>
71
+ <h3 className="font-semibold mb-1">Gasless Transactions</h3>
72
+ <p className="text-sm text-muted-foreground">
73
+ The paymaster covers gas fees, and your session key signs in the background
74
+ </p>
75
+ </div>
76
+ </div>
77
+ </div>
78
+ </div>
79
+
80
+ {/* Tech Stack */}
81
+ <div className="text-center space-y-4">
82
+ <h3 className="text-lg font-semibold text-muted-foreground">Built With</h3>
83
+ <div className="flex flex-wrap justify-center gap-4 text-sm">
84
+ <span className="px-4 py-2 bg-muted rounded-full">Next.js 14</span>
85
+ <span className="px-4 py-2 bg-muted rounded-full">Fogo Sessions SDK</span>
86
+ <span className="px-4 py-2 bg-muted rounded-full">TypeScript</span>
87
+ <span className="px-4 py-2 bg-muted rounded-full">TailwindCSS</span>
88
+ <span className="px-4 py-2 bg-muted rounded-full">Solana Web3.js</span>
89
+ </div>
90
+ </div>
91
+ </div>
92
+ </div>
93
+ </main>
94
+ );
95
+ }
@@ -0,0 +1,39 @@
1
+ 'use client';
2
+
3
+ import { FogoSessionProvider } from '@fogo/sessions-sdk-react';
4
+ import { WalletAdapterNetwork } from '@solana/wallet-adapter-base';
5
+ import { ConnectionProvider, WalletProvider } from '@solana/wallet-adapter-react';
6
+ import { WalletModalProvider } from '@solana/wallet-adapter-react-ui';
7
+ import { PhantomWalletAdapter, SolflareWalletAdapter } from '@solana/wallet-adapter-wallets';
8
+ import { useMemo } from 'react';
9
+
10
+ export function Providers({ children }: { children: React.ReactNode }) {
11
+ const endpoint = process.env.NEXT_PUBLIC_FOGO_RPC_URL || 'https://testnet.fogo.io';
12
+
13
+ const wallets = useMemo(
14
+ () => [
15
+ new PhantomWalletAdapter(),
16
+ new SolflareWalletAdapter(),
17
+ ],
18
+ []
19
+ );
20
+
21
+ return (
22
+ <ConnectionProvider endpoint={endpoint}>
23
+ <WalletProvider wallets={wallets} autoConnect>
24
+ <WalletModalProvider>
25
+ <FogoSessionProvider
26
+ endpoint={endpoint}
27
+ paymasterUrl={process.env.NEXT_PUBLIC_PAYMASTER_URL || ''}
28
+ sponsor={process.env.NEXT_PUBLIC_SPONSOR_PUBLIC_KEY || ''}
29
+ domain={typeof window !== 'undefined' ? window.location.origin : ''}
30
+ tokens={[]}
31
+ defaultRequestedLimits={{}}
32
+ >
33
+ {children}
34
+ </FogoSessionProvider>
35
+ </WalletModalProvider>
36
+ </WalletProvider>
37
+ </ConnectionProvider>
38
+ );
39
+ }
@@ -0,0 +1,130 @@
1
+ 'use client';
2
+
3
+ import { useFogoSession } from '@/hooks/useFogoSession';
4
+ import { Button } from '@/components/ui/button';
5
+ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
6
+ import { useConnection } from '@solana/wallet-adapter-react';
7
+ import { TransactionMessage, VersionedTransaction, PublicKey } from '@solana/web3.js';
8
+ import { useState } from 'react';
9
+ import { MessageSquare, Loader2, CheckCircle2, XCircle } from 'lucide-react';
10
+
11
+ export function MemoDemo() {
12
+ const { sessionPublicKey, sendTransaction, payer } = useFogoSession();
13
+ const { connection } = useConnection();
14
+ const [status, setStatus] = useState<'idle' | 'loading' | 'success' | 'error'>('idle');
15
+ const [message, setMessage] = useState('');
16
+ const [signature, setSignature] = useState('');
17
+
18
+ const handleSayGM = async () => {
19
+ if (!sessionPublicKey || !payer) {
20
+ setStatus('error');
21
+ setMessage('Please establish a session first');
22
+ return;
23
+ }
24
+
25
+ try {
26
+ setStatus('loading');
27
+ setMessage('Sending memo...');
28
+
29
+ // SPL Memo Program ID
30
+ const MEMO_PROGRAM_ID = new PublicKey('MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr');
31
+
32
+ // Create memo instruction
33
+ const memoInstruction = {
34
+ programId: MEMO_PROGRAM_ID,
35
+ keys: [],
36
+ data: Buffer.from('Hello Fogo! 🔥', 'utf-8'),
37
+ };
38
+
39
+ // Get recent blockhash
40
+ const { blockhash } = await connection.getLatestBlockhash();
41
+
42
+ // Build transaction with session key as signer
43
+ const messageV0 = new TransactionMessage({
44
+ payerKey: payer,
45
+ recentBlockhash: blockhash,
46
+ instructions: [memoInstruction],
47
+ }).compileToV0Message();
48
+
49
+ const transaction = new VersionedTransaction(messageV0);
50
+
51
+ // Send via paymaster (no wallet popup!)
52
+ const sig = await sendTransaction(transaction);
53
+
54
+ setStatus('success');
55
+ setMessage('Memo sent successfully! No wallet popup needed 🎉');
56
+ setSignature(sig);
57
+ } catch (error: any) {
58
+ setStatus('error');
59
+ setMessage(`Error: ${error.message || 'Failed to send memo'}`);
60
+ console.error('Memo transaction error:', error);
61
+ }
62
+ };
63
+
64
+ return (
65
+ <Card className="w-full max-w-md">
66
+ <CardHeader>
67
+ <CardTitle className="flex items-center gap-2">
68
+ <MessageSquare className="h-5 w-5" />
69
+ Say GM Demo
70
+ </CardTitle>
71
+ <CardDescription>
72
+ Send a memo transaction using your session key (gasless!)
73
+ </CardDescription>
74
+ </CardHeader>
75
+ <CardContent className="space-y-4">
76
+ <Button
77
+ onClick={handleSayGM}
78
+ disabled={!sessionPublicKey || status === 'loading'}
79
+ className="w-full"
80
+ size="lg"
81
+ >
82
+ {status === 'loading' ? (
83
+ <>
84
+ <Loader2 className="h-4 w-4 mr-2 animate-spin" />
85
+ Sending...
86
+ </>
87
+ ) : (
88
+ <>
89
+ Say GM 👋
90
+ </>
91
+ )}
92
+ </Button>
93
+
94
+ {status !== 'idle' && (
95
+ <div className={`p-4 rounded-md ${
96
+ status === 'success' ? 'bg-green-50 dark:bg-green-950 border border-green-200 dark:border-green-800' :
97
+ status === 'error' ? 'bg-red-50 dark:bg-red-950 border border-red-200 dark:border-red-800' :
98
+ 'bg-blue-50 dark:bg-blue-950 border border-blue-200 dark:border-blue-800'
99
+ }`}>
100
+ <div className="flex items-start gap-2">
101
+ {status === 'success' && <CheckCircle2 className="h-5 w-5 text-green-600 dark:text-green-400 mt-0.5" />}
102
+ {status === 'error' && <XCircle className="h-5 w-5 text-red-600 dark:text-red-400 mt-0.5" />}
103
+ {status === 'loading' && <Loader2 className="h-5 w-5 text-blue-600 dark:text-blue-400 mt-0.5 animate-spin" />}
104
+ <div className="flex-1">
105
+ <p className={`text-sm font-medium ${
106
+ status === 'success' ? 'text-green-900 dark:text-green-100' :
107
+ status === 'error' ? 'text-red-900 dark:text-red-100' :
108
+ 'text-blue-900 dark:text-blue-100'
109
+ }`}>
110
+ {message}
111
+ </p>
112
+ {signature && (
113
+ <p className="text-xs mt-2 font-mono break-all text-green-700 dark:text-green-300">
114
+ Signature: {signature.slice(0, 16)}...{signature.slice(-16)}
115
+ </p>
116
+ )}
117
+ </div>
118
+ </div>
119
+ </div>
120
+ )}
121
+
122
+ {!sessionPublicKey && (
123
+ <p className="text-sm text-muted-foreground text-center">
124
+ Connect your wallet and establish a session to try this demo
125
+ </p>
126
+ )}
127
+ </CardContent>
128
+ </Card>
129
+ );
130
+ }
@@ -0,0 +1,74 @@
1
+ 'use client';
2
+
3
+ import { useWallet } from '@solana/wallet-adapter-react';
4
+ import { WalletMultiButton } from '@solana/wallet-adapter-react-ui';
5
+ import { useFogoSession } from '@/hooks/useFogoSession';
6
+ import { Button } from '@/components/ui/button';
7
+ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
8
+ import { Wallet, Key, CheckCircle2 } from 'lucide-react';
9
+
10
+ export function SessionButton() {
11
+ const { connected } = useWallet();
12
+ const { sessionPublicKey, establishSession, endSession } = useFogoSession();
13
+
14
+ const handleEstablishSession = async () => {
15
+ try {
16
+ await establishSession();
17
+ } catch (error) {
18
+ console.error('Failed to establish session:', error);
19
+ }
20
+ };
21
+
22
+ const handleEndSession = async () => {
23
+ try {
24
+ await endSession();
25
+ } catch (error) {
26
+ console.error('Failed to end session:', error);
27
+ }
28
+ };
29
+
30
+ return (
31
+ <Card className="w-full max-w-md">
32
+ <CardHeader>
33
+ <CardTitle className="flex items-center gap-2">
34
+ <Wallet className="h-5 w-5" />
35
+ Session Management
36
+ </CardTitle>
37
+ <CardDescription>
38
+ Connect your wallet and establish a session for gasless transactions
39
+ </CardDescription>
40
+ </CardHeader>
41
+ <CardContent className="space-y-4">
42
+ <div className="flex flex-col gap-2">
43
+ <WalletMultiButton className="!bg-primary !rounded-md" />
44
+
45
+ {connected && !sessionPublicKey && (
46
+ <Button onClick={handleEstablishSession} className="w-full">
47
+ <Key className="h-4 w-4 mr-2" />
48
+ Establish Session
49
+ </Button>
50
+ )}
51
+
52
+ {sessionPublicKey && (
53
+ <div className="space-y-2">
54
+ <div className="flex items-center gap-2 text-sm text-green-600 dark:text-green-400">
55
+ <CheckCircle2 className="h-4 w-4" />
56
+ <span>Session Active</span>
57
+ </div>
58
+ <p className="text-xs text-muted-foreground font-mono break-all">
59
+ {sessionPublicKey.toBase58()}
60
+ </p>
61
+ <Button
62
+ onClick={handleEndSession}
63
+ variant="outline"
64
+ className="w-full"
65
+ >
66
+ End Session
67
+ </Button>
68
+ </div>
69
+ )}
70
+ </div>
71
+ </CardContent>
72
+ </Card>
73
+ );
74
+ }
@@ -0,0 +1,56 @@
1
+ import * as React from "react";
2
+ import { Slot } from "@radix-ui/react-slot";
3
+ import { cva, type VariantProps } from "class-variance-authority";
4
+
5
+ import { cn } from "@/lib/utils";
6
+
7
+ const buttonVariants = cva(
8
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
9
+ {
10
+ variants: {
11
+ variant: {
12
+ default: "bg-primary text-primary-foreground hover:bg-primary/90",
13
+ destructive:
14
+ "bg-destructive text-destructive-foreground hover:bg-destructive/90",
15
+ outline:
16
+ "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
17
+ secondary:
18
+ "bg-secondary text-secondary-foreground hover:bg-secondary/80",
19
+ ghost: "hover:bg-accent hover:text-accent-foreground",
20
+ link: "text-primary underline-offset-4 hover:underline",
21
+ },
22
+ size: {
23
+ default: "h-10 px-4 py-2",
24
+ sm: "h-9 rounded-md px-3",
25
+ lg: "h-11 rounded-md px-8",
26
+ icon: "h-10 w-10",
27
+ },
28
+ },
29
+ defaultVariants: {
30
+ variant: "default",
31
+ size: "default",
32
+ },
33
+ }
34
+ );
35
+
36
+ export interface ButtonProps
37
+ extends React.ButtonHTMLAttributes<HTMLButtonElement>,
38
+ VariantProps<typeof buttonVariants> {
39
+ asChild?: boolean;
40
+ }
41
+
42
+ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
43
+ ({ className, variant, size, asChild = false, ...props }, ref) => {
44
+ const Comp = asChild ? Slot : "button";
45
+ return (
46
+ <Comp
47
+ className={cn(buttonVariants({ variant, size, className }))}
48
+ ref={ref}
49
+ {...props}
50
+ />
51
+ );
52
+ }
53
+ );
54
+ Button.displayName = "Button";
55
+
56
+ export { Button, buttonVariants };
@@ -0,0 +1,79 @@
1
+ import * as React from "react";
2
+
3
+ import { cn } from "@/lib/utils";
4
+
5
+ const Card = React.forwardRef<
6
+ HTMLDivElement,
7
+ React.HTMLAttributes<HTMLDivElement>
8
+ >(({ className, ...props }, ref) => (
9
+ <div
10
+ ref={ref}
11
+ className={cn(
12
+ "rounded-lg border bg-card text-card-foreground shadow-sm",
13
+ className
14
+ )}
15
+ {...props}
16
+ />
17
+ ));
18
+ Card.displayName = "Card";
19
+
20
+ const CardHeader = React.forwardRef<
21
+ HTMLDivElement,
22
+ React.HTMLAttributes<HTMLDivElement>
23
+ >(({ className, ...props }, ref) => (
24
+ <div
25
+ ref={ref}
26
+ className={cn("flex flex-col space-y-1.5 p-6", className)}
27
+ {...props}
28
+ />
29
+ ));
30
+ CardHeader.displayName = "CardHeader";
31
+
32
+ const CardTitle = React.forwardRef<
33
+ HTMLParagraphElement,
34
+ React.HTMLAttributes<HTMLHeadingElement>
35
+ >(({ className, ...props }, ref) => (
36
+ <h3
37
+ ref={ref}
38
+ className={cn(
39
+ "text-2xl font-semibold leading-none tracking-tight",
40
+ className
41
+ )}
42
+ {...props}
43
+ />
44
+ ));
45
+ CardTitle.displayName = "CardTitle";
46
+
47
+ const CardDescription = React.forwardRef<
48
+ HTMLParagraphElement,
49
+ React.HTMLAttributes<HTMLParagraphElement>
50
+ >(({ className, ...props }, ref) => (
51
+ <p
52
+ ref={ref}
53
+ className={cn("text-sm text-muted-foreground", className)}
54
+ {...props}
55
+ />
56
+ ));
57
+ CardDescription.displayName = "CardDescription";
58
+
59
+ const CardContent = React.forwardRef<
60
+ HTMLDivElement,
61
+ React.HTMLAttributes<HTMLDivElement>
62
+ >(({ className, ...props }, ref) => (
63
+ <div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
64
+ ));
65
+ CardContent.displayName = "CardContent";
66
+
67
+ const CardFooter = React.forwardRef<
68
+ HTMLDivElement,
69
+ React.HTMLAttributes<HTMLDivElement>
70
+ >(({ className, ...props }, ref) => (
71
+ <div
72
+ ref={ref}
73
+ className={cn("flex items-center p-6 pt-0", className)}
74
+ {...props}
75
+ />
76
+ ));
77
+ CardFooter.displayName = "CardFooter";
78
+
79
+ export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent };
@@ -0,0 +1,6 @@
1
+ {
2
+ "dependencies": {
3
+ "tailwindcss-animate": "^1.0.7",
4
+ "@radix-ui/react-slot": "^1.0.2"
5
+ }
6
+ }
@@ -0,0 +1,23 @@
1
+ 'use client';
2
+
3
+ import { useSession } from '@fogo/sessions-sdk-react';
4
+ import { useWallet } from '@solana/wallet-adapter-react';
5
+
6
+ export function useFogoSession() {
7
+ const { connected, publicKey } = useWallet();
8
+ const session = useSession();
9
+
10
+ const establishSession = async (requestedLimits = {}) => {
11
+ if (!connected) {
12
+ throw new Error('Please connect your wallet first');
13
+ }
14
+ await session.establish(requestedLimits);
15
+ };
16
+
17
+ return {
18
+ ...session,
19
+ establishSession,
20
+ isWalletConnected: connected,
21
+ walletAddress: publicKey?.toBase58(),
22
+ };
23
+ }
@@ -0,0 +1,6 @@
1
+ import { clsx, type ClassValue } from "clsx";
2
+ import { twMerge } from "tailwind-merge";
3
+
4
+ export function cn(...inputs: ClassValue[]) {
5
+ return twMerge(clsx(inputs));
6
+ }
@@ -0,0 +1,15 @@
1
+ /** @type {import('next').NextConfig} */
2
+ const nextConfig = {
3
+ reactStrictMode: true,
4
+ webpack: (config) => {
5
+ config.resolve.fallback = {
6
+ ...config.resolve.fallback,
7
+ fs: false,
8
+ net: false,
9
+ tls: false,
10
+ };
11
+ return config;
12
+ },
13
+ };
14
+
15
+ export default nextConfig;
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "{{PROJECT_NAME}}",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "scripts": {
6
+ "dev": "next dev",
7
+ "build": "next build",
8
+ "start": "next start",
9
+ "lint": "next lint"
10
+ },
11
+ "dependencies": {
12
+ "@fogo/sessions-sdk-react": "latest",
13
+ "@solana/wallet-adapter-base": "^0.9.23",
14
+ "@solana/wallet-adapter-react": "^0.15.35",
15
+ "@solana/wallet-adapter-react-ui": "^0.9.35",
16
+ "@solana/wallet-adapter-wallets": "^0.19.32",
17
+ "@solana/web3.js": "^1.95.0",
18
+ "next": "14.2.0",
19
+ "react": "^18.3.0",
20
+ "react-dom": "^18.3.0",
21
+ "class-variance-authority": "^0.7.0",
22
+ "clsx": "^2.1.0",
23
+ "tailwind-merge": "^2.2.0",
24
+ "lucide-react": "^0.344.0",
25
+ "tailwindcss-animate": "^1.0.7",
26
+ "@radix-ui/react-slot": "^1.0.2"
27
+ },
28
+ "devDependencies": {
29
+ "@types/node": "^20",
30
+ "@types/react": "^18",
31
+ "@types/react-dom": "^18",
32
+ "typescript": "^5",
33
+ "tailwindcss": "^3.4.0",
34
+ "postcss": "^8",
35
+ "autoprefixer": "^10.0.1",
36
+ "eslint": "^8",
37
+ "eslint-config-next": "14.2.0"
38
+ }
39
+ }
@@ -0,0 +1,6 @@
1
+ export default {
2
+ plugins: {
3
+ tailwindcss: {},
4
+ autoprefixer: {},
5
+ },
6
+ };
@@ -0,0 +1,79 @@
1
+ import type { Config } from "tailwindcss";
2
+
3
+ const config: Config = {
4
+ darkMode: ["class"],
5
+ content: [
6
+ "./pages/**/*.{ts,tsx}",
7
+ "./components/**/*.{ts,tsx}",
8
+ "./app/**/*.{ts,tsx}",
9
+ "./src/**/*.{ts,tsx}",
10
+ ],
11
+ theme: {
12
+ container: {
13
+ center: true,
14
+ padding: "2rem",
15
+ screens: {
16
+ "2xl": "1400px",
17
+ },
18
+ },
19
+ extend: {
20
+ colors: {
21
+ border: "hsl(var(--border))",
22
+ input: "hsl(var(--input))",
23
+ ring: "hsl(var(--ring))",
24
+ background: "hsl(var(--background))",
25
+ foreground: "hsl(var(--foreground))",
26
+ primary: {
27
+ DEFAULT: "hsl(var(--primary))",
28
+ foreground: "hsl(var(--primary-foreground))",
29
+ },
30
+ secondary: {
31
+ DEFAULT: "hsl(var(--secondary))",
32
+ foreground: "hsl(var(--secondary-foreground))",
33
+ },
34
+ destructive: {
35
+ DEFAULT: "hsl(var(--destructive))",
36
+ foreground: "hsl(var(--destructive-foreground))",
37
+ },
38
+ muted: {
39
+ DEFAULT: "hsl(var(--muted))",
40
+ foreground: "hsl(var(--muted-foreground))",
41
+ },
42
+ accent: {
43
+ DEFAULT: "hsl(var(--accent))",
44
+ foreground: "hsl(var(--accent-foreground))",
45
+ },
46
+ popover: {
47
+ DEFAULT: "hsl(var(--popover))",
48
+ foreground: "hsl(var(--popover-foreground))",
49
+ },
50
+ card: {
51
+ DEFAULT: "hsl(var(--card))",
52
+ foreground: "hsl(var(--card-foreground))",
53
+ },
54
+ },
55
+ borderRadius: {
56
+ lg: "var(--radius)",
57
+ md: "calc(var(--radius) - 2px)",
58
+ sm: "calc(var(--radius) - 4px)",
59
+ },
60
+ keyframes: {
61
+ "accordion-down": {
62
+ from: { height: "0" },
63
+ to: { height: "var(--radix-accordion-content-height)" },
64
+ },
65
+ "accordion-up": {
66
+ from: { height: "var(--radix-accordion-content-height)" },
67
+ to: { height: "0" },
68
+ },
69
+ },
70
+ animation: {
71
+ "accordion-down": "accordion-down 0.2s ease-out",
72
+ "accordion-up": "accordion-up 0.2s ease-out",
73
+ },
74
+ },
75
+ },
76
+ plugins: [require("tailwindcss-animate")],
77
+ };
78
+
79
+ export default config;
@@ -0,0 +1,26 @@
1
+ {
2
+ "compilerOptions": {
3
+ "lib": ["dom", "dom.iterable", "esnext"],
4
+ "allowJs": true,
5
+ "skipLibCheck": true,
6
+ "strict": true,
7
+ "noEmit": true,
8
+ "esModuleInterop": true,
9
+ "module": "esnext",
10
+ "moduleResolution": "bundler",
11
+ "resolveJsonModule": true,
12
+ "isolatedModules": true,
13
+ "jsx": "preserve",
14
+ "incremental": true,
15
+ "plugins": [
16
+ {
17
+ "name": "next"
18
+ }
19
+ ],
20
+ "paths": {
21
+ "@/*": ["./*"]
22
+ }
23
+ },
24
+ "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
25
+ "exclude": ["node_modules"]
26
+ }