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 +109 -0
- package/bin/cli.js +97 -0
- package/package.json +34 -0
- package/template/.env.example +10 -0
- package/template/README.md +121 -0
- package/template/app/globals.css +62 -0
- package/template/app/layout.tsx +25 -0
- package/template/app/page.tsx +95 -0
- package/template/app/providers.tsx +39 -0
- package/template/components/MemoDemo.tsx +130 -0
- package/template/components/SessionButton.tsx +74 -0
- package/template/components/ui/button.tsx +56 -0
- package/template/components/ui/card.tsx +79 -0
- package/template/components/ui/package-additions.json +6 -0
- package/template/hooks/useFogoSession.ts +23 -0
- package/template/lib/utils.ts +6 -0
- package/template/next.config.ts +15 -0
- package/template/package.json +39 -0
- package/template/postcss.config.mjs +6 -0
- package/template/tailwind.config.ts +79 -0
- package/template/tsconfig.json +26 -0
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,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,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,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
|
+
}
|