v0.1.0 · Bags Hackathon Edition

ImpactAI Documentation

Everything you need to understand, run, extend, and deploy ImpactAI — the platform that turns any community cause into a Solana token in 60 seconds.

Overview

ImpactAI is a Next.js 14 web application that lets anyone launch a Solana token for a real-world community cause — clean energy, clean water, education, healthcare — with no code, no bank account, and no smart contract knowledge.

The platform is built on three foundations:

🤖
Gemini AI
Generates token name, ticker, description and viral hook from a one-sentence cause description.
Bags SDK
Deploys the token on Solana and configures automatic fee splits on every trade.
🔐
NextAuth + Wallets
Authenticates users via Phantom, Solflare, Backpack, Google, or GitHub.

Quick start

Clone the repo and install dependencies:

bash
git clone https://github.com/your-username/impactai.git
cd impactai
npm install

Copy the example environment file:

bash
cp .env.local.example .env.local

Add your Gemini API key (minimum required to run locally):

bash
# .env.local
GEMINI_API_KEY=your-gemini-key
NEXTAUTH_SECRET=any-random-string-for-local-dev
NEXTAUTH_URL=http://localhost:3000

Start the development server:

bash
npm run dev
# Open http://localhost:3000
Tip
You can use the app without Google/GitHub credentials — wallet connect works immediately. Social login requires OAuth credentials (see OAuth setup).

Environment variables

All variables live in .env.local which is never committed to Git.

VariableRequiredDescription
GEMINI_API_KEY✅ YesGemini API key — get at aistudio.google.com/app/apikey
NEXTAUTH_SECRET✅ YesRandom secret for signing JWT sessions
NEXTAUTH_URL✅ YesFull URL of your app (http://localhost:3000 locally)
KV_REST_API_URL✅ YesUpstash Redis REST URL — stores launched tokens & rate limits
KV_REST_API_TOKEN✅ YesUpstash Redis REST token (auth for the above)
GOOGLE_CLIENT_IDOptionalGoogle OAuth app client ID
GOOGLE_CLIENT_SECRETOptionalGoogle OAuth app client secret
GITHUB_CLIENT_IDOptionalGitHub OAuth app client ID
GITHUB_CLIENT_SECRETOptionalGitHub OAuth app client secret
BAGS_API_KEYOptionalBags SDK key for production token launch
Tip
The Vercel Upstash/KV integration provisions KV_REST_API_URL / KV_REST_API_TOKEN automatically. The app also accepts STORAGE_URL / STORAGE_TOKEN or UPSTASH_REDIS_REST_URL / UPSTASH_REDIS_REST_TOKEN — whichever is present. Without storage configured, the app still runs: token persistence and rate limiting simply no-op (fail open).

Project structure

text
impactai/
├── src/
│   ├── app/
│   │   ├── api/
│   │   │   ├── auth/[...nextauth]/route.ts   # NextAuth handler
│   │   │   ├── generate/route.ts             # Gemini AI generation (rate-limited)
│   │   │   └── tokens/
│   │   │       ├── route.ts                  # GET list / POST save tokens
│   │   │       ├── [id]/route.ts             # GET / PATCH a single token
│   │   │       └── stats/route.ts            # Aggregate platform stats
│   │   ├── docs/page.tsx                     # This page
│   │   ├── launch/page.tsx                   # Token creation app
│   │   ├── portfolio/page.tsx               # Creator's launched tokens
│   │   ├── layout.tsx                        # Root layout + providers
│   │   ├── page.tsx                          # Landing page
│   │   └── globals.css
│   ├── components/
│   │   ├── AuthModal.tsx                     # Wallet + OAuth modal
│   │   ├── AuthSessionProvider.tsx           # NextAuth SessionProvider
│   │   ├── FeeSplitVisual.tsx                # Interactive fee bars
│   │   ├── GeneratingScreen.tsx              # AI loading animation
│   │   ├── HomeScreen.tsx                    # Cause input screen
│   │   ├── LaunchedScreen.tsx                # Post-launch dashboard (saves token)
│   │   ├── LiveLaunchesList.tsx              # Live launches list
│   │   ├── Navbar.tsx                        # Header with auth state
│   │   ├── PreviewScreen.tsx                 # Token review screen
│   │   ├── ThemeProvider.tsx                 # next-themes wrapper
│   │   ├── ThemeToggle.tsx                   # Light/dark/system toggle
│   │   └── TickerBar.tsx                     # Live launches ticker
│   ├── lib/
│   │   ├── constants.ts                      # Static data & AI prompt
│   │   ├── tokens.ts                         # Redis token store + stats
│   │   ├── ratelimit.ts                      # Upstash rate limiter (fail-open)
│   │   ├── useConnectedUser.ts               # Unified wallet + OAuth identity
│   │   ├── useAnimatedCounter.ts             # Animated number hook
│   │   └── utils.ts                          # cn(), formatCurrency(), etc.
│   └── types/
│       ├── index.ts                          # App + UI types
│       └── token.ts                          # LiveToken & PlatformStats
├── .env.local.example
├── next.config.js
├── tailwind.config.ts
└── tsconfig.json

How it works

The full user journey from cause description to live token takes four steps:

01

User describes their cause

A single text input on the launch page. The user types one sentence describing the community problem they want to solve — e.g. "Solar panels for 200 homes in rural Ethiopia". Example prompts are provided to lower the barrier.

02

AI generates the token identity

The cause text is sent to the Gemini API via a Next.js route. Gemini returns a JSON object containing a token name, ticker (4–6 capital letters), description, relevant emoji, cause wallet label, and a pre-written viral share hook.

03

User reviews and launches

The generated token is shown in a preview screen. The user can regenerate, edit their cause, or click Launch. In production this calls bagsSDK.launchToken() which deploys the token on Solana and registers the fee split configuration.

04

Fees flow to the cause

Every on-chain trade of the token triggers the fee split: 40% goes to a designated cause wallet, 30% is redistributed to all token holders as cashback, 20% goes to the creator, and 10% to the platform. No manual distribution needed.

Fee splits

The fee split is set once at token launch via the Bags SDK and applies automatically to every trade thereafter. The four recipients are:

40%
Cause wallet
On-chain wallet designated to fund the community project.
30%
Holders
Distributed proportionally to all current token holders.
20%
Creator
Sent to the wallet that launched the token.
10%
Platform
ImpactAI platform wallet for ongoing development.
Note
Fee splits are enforced by the Bags protocol on-chain. Once set at launch, they cannot be changed without redeploying the token.

Token launch flow

In production, replace the mock setScreen("launched") call in src/app/launch/page.tsx with the real Bags SDK call:

bash
npm install @bagsfm/bags-sdk @solana/web3.js @solana/wallet-adapter-react
typescript
import { BagsSDK } from "@bagsfm/bags-sdk";

const sdk = new BagsSDK({ apiKey: process.env.BAGS_API_KEY });

const result = await sdk.launchToken({
  name: token.name,
  ticker: token.ticker,
  description: token.description,
  feeShares: {
    cause:    "0.40",  // 40% → community wallet
    holders:  "0.30",  // 30% → token holders
    creator:  "0.20",  // 20% → you
    platform: "0.10",  // 10% → ImpactAI
  },
  causeWallet: "SOLANA_WALLET_ADDRESS_FOR_CAUSE",
  metadata: {
    image: token.emoji,
    tags:  token.tags,
  },
});

console.log("Token live:", result.mintAddress);

Token storage

Launched tokens are persisted to Upstash Redis via src/lib/tokens.ts. Each token is stored as a JSON value keyed by id, plus a sorted set (scored by creation time) so lists come back newest-first.

  • LaunchedScreen saves the token once on mount via POST /api/tokens, then polls GET /api/tokens/[id] every 20s for live stats.
  • TickerBar and LiveLaunchesList read GET /api/tokens to render the home page launches.
  • Stats start at zero — a mock launch has no on-chain volume until real Bags trades exist.
Note
If Redis isn't configured, saves fail gracefully and the live lists simply stay empty — the rest of the app keeps working.

Portfolio

/portfolio shows the tokens launched by the connected user, with aggregate summary cards (tokens, total raised, supporters, on-chain txns) and a card per token. It polls GET /api/tokens every 15s and filters client-side by creator identity.

Attribution uses a stable creatorId derived by creatorIdOf() in src/lib/useConnectedUser.ts: wallet users key off their address (wallet:<addr>), social users off their display name (user:<name>). The same id is written on the token at launch and used to filter the portfolio.

A Portfolio link appears in the navbar and the profile dropdown once a user is connected, plus a View my tokens button on the launched screen.

Wallet connection

The AuthModal component handles three Solana wallets by calling their browser extension APIs directly — no adapter library required:

WalletDetectionConnect call
Phantomwindow.solana.isPhantomwindow.solana.connect()
Solflarewindow.solflare.isSolflarewindow.solflare.connect()
Backpackwindow.xnft.solanawindow.xnft.solana.connect()

When a wallet is detected, the modal shows a green Detected badge. If not installed, it shows an install link to the wallet's website. After connecting, the public key is shortened and shown in the navbar.

OAuth — Google & GitHub

Social login uses NextAuth.js v4 with JWT sessions (no database required).

Setting up Google OAuth

  1. Go to console.cloud.google.com and create a new project.
  2. Navigate to APIs & Services → Credentials → Create Credentials → OAuth 2.0 Client ID.
  3. Set Application type to Web application.
  4. Add http://localhost:3000/api/auth/callback/google as an authorised redirect URI.
  5. Copy the Client ID and Client Secret into .env.local.

Setting up GitHub OAuth

  1. Go to github.com/settings/developers.
  2. Click OAuth Apps → New OAuth App.
  3. Set the Authorization callback URL to http://localhost:3000/api/auth/callback/github.
  4. Click Register application, then generate a client secret.
  5. Copy both values into .env.local.
Warning
For production deployments, update the callback URLs in both Google and GitHub consoles to use your live domain, e.g. https://impactai.vercel.app/api/auth/callback/google, and update NEXTAUTH_URL in your Vercel environment variables.

Session management

Identity is unified through the useConnectedUser() hook (src/lib/useConnectedUser.ts), which merges two sources and survives navigation and reloads:

  • NextAuth session — stored in a signed JWT cookie, persists across page loads. Read via useSession().
  • Wallet state — persisted to localStorage (key impactai:wallet-user) so a connected wallet is still recognized on /portfolio and after a refresh.

Wallet identity takes priority; the hook returns a single user plus walletUser, setWalletUser, and disconnect:

typescript
const { user, walletUser, setWalletUser, disconnect } = useConnectedUser();

// user = persisted wallet user, else the NextAuth session user
// creatorIdOf(user) → stable id used to attribute & filter launched tokens

POST /api/generate

Route that calls the Gemini API and returns a structured token object. The cause text is sanitized (control characters stripped, whitespace collapsed) and length-validated before it reaches the model, and the endpoint is rate-limited per client IP (10 requests / minute) to protect against abuse and runaway cost.

Request

json
POST /api/generate
Content-Type: application/json

{
  "cause": "Solar panels for 200 homes in rural Ethiopia"
}

Response

json
{
  "token": {
    "name": "EthioSolar",
    "ticker": "ESOL",
    "description": "EthioSolar is lighting up 200 homes...",
    "emoji": "☀️",
    "causeWallet": "Solar panel installation for 200 rural homes",
    "viralHook": "I just launched a solar token — trade it and power Ethiopia",
    "tags": ["energy", "africa", "solar"]
  }
}

Errors

StatusReason
400Cause text missing or under 5 characters
400Cause text over 300 characters
429Rate limit exceeded — too many requests from this IP
500GEMINI_API_KEY not set in environment
502Gemini API returned a non-200 response
502AI response empty or could not be parsed as valid JSON

Tokens API

CRUD-ish endpoints over the Redis token store. Backed by src/lib/tokens.ts.

EndpointDescription
GET /api/tokensList all launched tokens, newest first
POST /api/tokensSave a launched token (sets creatorId, stats start at 0)
GET /api/tokens/[id]Fetch a single token by id (used for live-stat polling)
PATCH /api/tokens/[id]Update a token's stats / proof photos
GET /api/tokens/statsAggregate platform stats (totals + countries)

POST body & saved shape

json
POST /api/tokens
{
  "name": "EthioSolar",
  "ticker": "ESOL",
  "description": "...",
  "emoji": "☀️",
  "causeWallet": "...",
  "viralHook": "...",
  "tags": ["energy", "solar"],
  "creatorId": "wallet:7xk...",   // stable creator identity
  "creatorWallet": "7xk...",
  "creatorDisplay": "7xk…9fA"
}

// → 201 { token: LiveToken }  (raised/supporters/volume24h/change24h/txCount = 0)

NextAuth routes

The catch-all handler at src/app/api/auth/[...nextauth]/route.ts exposes these endpoints automatically:

text
GET  /api/auth/signin              # Sign-in page redirect
GET  /api/auth/signout             # Signs the user out
GET  /api/auth/session             # Returns current session JSON
GET  /api/auth/callback/google     # Google OAuth callback
GET  /api/auth/callback/github     # GitHub OAuth callback
GET  /api/auth/csrf                # CSRF token

Deploy to Vercel

bash
# Install Vercel CLI
npm i -g vercel

# Deploy (first time sets up the project)
vercel

# Set environment variables
vercel env add GEMINI_API_KEY
vercel env add NEXTAUTH_SECRET
vercel env add NEXTAUTH_URL          # https://your-app.vercel.app
# Storage — or add a Vercel KV / Upstash store, which injects these for you
vercel env add KV_REST_API_URL
vercel env add KV_REST_API_TOKEN
vercel env add GOOGLE_CLIENT_ID
vercel env add GOOGLE_CLIENT_SECRET
vercel env add GITHUB_CLIENT_ID
vercel env add GITHUB_CLIENT_SECRET

# Deploy to production
vercel --prod
Warning
Changing environment variables in Vercel does not redeploy automatically. After adding or editing vars (e.g. connecting storage), trigger a fresh deployment so the new values take effect.
Warning
Remember to update your OAuth redirect URIs in the Google Cloud Console and GitHub developer settings to match your Vercel production URL before going live.

Production checklist

Set NEXTAUTH_URL to your live domaincritical
Generate a strong NEXTAUTH_SECRET with openssl rand -base64 32critical
Update Google OAuth redirect URI to production URLcritical
Update GitHub OAuth callback URL to production URLcritical
Connect Upstash/Vercel KV storage (KV_REST_API_URL / KV_REST_API_TOKEN)critical
Add BAGS_API_KEY from dev.bags.fmrequired
Replace mock launch with real bagsSDK.launchToken() callrequired
Add a real cause wallet Solana address for fee routingrequired
Rate limiting on /api/generate✓ done
Set up IPFS or similar for proof-of-impact photo uploadsoptional
Wire up live on-chain stats polling for launched tokensoptional

Wiring up Bags SDK

The current codebase has a placeholder launch. Here is how to make it real:

1. Install the SDK:

bash
npm install @bagsfm/bags-sdk @solana/web3.js

2. In src/app/launch/page.tsx, find the handleLaunch function and replace it:

typescript
const handleLaunch = useCallback(async () => {
  if (!user && !walletUser) { setAuthOpen(true); return; }

  const { BagsSDK } = await import("@bagsfm/bags-sdk");
  const sdk = new BagsSDK({ apiKey: process.env.NEXT_PUBLIC_BAGS_API_KEY });

  try {
    const result = await sdk.launchToken({
      name:        token!.name,
      ticker:      token!.ticker,
      description: token!.description,
      feeShares: {
        cause:    "0.40",
        holders:  "0.30",
        creator:  "0.20",
        platform: "0.10",
      },
      causeWallet: "YOUR_CAUSE_SOLANA_WALLET_ADDRESS",
    });
    console.log("Mint address:", result.mintAddress);
    setScreen("launched");
  } catch (err) {
    setError("Launch failed. Check your Bags API key.");
  }
}, [token, user, walletUser]);
ImpactAI

Community tokens. Real change. Built for #BagsHackathon.