Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ This repository contains example applications demonstrating various [Base] and [
| **Mini Zora** | MiniKit | `minikit/my-mini-zora/` | MiniKit template integrated with Zora protocol for NFT interactions |
| **Simple Mini App** | MiniKit | `minikit/my-simple-mini-app/` | Basic MiniKit template with essential features and notifications |
| **Three Card Monte** | MiniKit | `minikit/three-card-monte/` | Interactive card game mini app with onchain rewards and leaderboard |
| **Onchain Voting Demo** | MiniKit | `mini-apps/onchain-voting-demo/` | Governance voting system with proposal creation, voting, and result tracking |

## Getting Started

Expand Down
1 change: 1 addition & 0 deletions mini-apps/onchain-voting-demo/.example.env
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
NEXT_PUBLIC_URL=
41 changes: 41 additions & 0 deletions mini-apps/onchain-voting-demo/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.*
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/versions

# testing
/coverage

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*

# env files (can opt-in for committing if needed)
.env*

# vercel
.vercel

# typescript
*.tsbuildinfo
next-env.d.ts
1 change: 1 addition & 0 deletions mini-apps/onchain-voting-demo/.prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
176 changes: 176 additions & 0 deletions mini-apps/onchain-voting-demo/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
# Onchain Voting Demo

A Base Mini App demonstrating onchain governance and voting functionality. Users can participate in decentralized decision-making with proposals, voting, and result tracking.

## Features

- **πŸ“Š Proposal Creation**: Submit governance proposals with titles and descriptions
- **πŸ—³οΈ Democratic Voting**: Cast votes on active proposals (Yes/No/Abstain)
- **πŸ“ˆ Real-time Results**: Live vote counting and result visualization
- **⏰ Proposal Lifecycle**: Active β†’ Voting Period β†’ Executed/Failed states
- **πŸ” Wallet Integration**: Secure voting using connected wallets
- **πŸ“± Mobile-First**: Optimized for Base Mini App experience

## Voting Mechanism

### Proposal States
- **Active**: Proposal submitted, open for voting
- **Passed**: Majority voted yes, ready for execution
- **Failed**: Majority voted no or voting period expired
- **Executed**: Proposal successfully implemented

### Voting Rules
- One vote per wallet address
- Vote changes allowed during voting period
- Quorum requirement: 3 minimum votes
- Simple majority wins (50% + 1)

## Technology Stack

- **Framework**: Next.js 15 with App Router
- **Blockchain**: Base (Ethereum L2)
- **Wallet**: MiniKit SDK integration
- **Styling**: Tailwind CSS
- **State Management**: React hooks + local storage
- **TypeScript**: Full type safety

## Getting Started

### Prerequisites
- Node.js 18+
- npm or yarn
- Base-compatible wallet

### Installation

```bash
# Clone the repository
git clone https://github.com/base/demos.git
cd demos/mini-apps/onchain-voting-demo

# Install dependencies
npm install

# Copy environment file
cp .example.env .env.local

# Add your MiniKit app ID
echo "NEXT_PUBLIC_MINIKIT_APP_ID=your_app_id" >> .env.local
```

### Development

```bash
# Start development server
npm run dev

# Open http://localhost:3000
```

### Building

```bash
# Build for production
npm run build

# Start production server
npm start
```

## Project Structure

```
onchain-voting-demo/
β”œβ”€β”€ app/
β”‚ β”œβ”€β”€ api/proposals/ # API routes for proposals
β”‚ β”œβ”€β”€ components/ # React components
β”‚ β”‚ β”œβ”€β”€ ProposalCard.tsx
β”‚ β”‚ β”œβ”€β”€ VotingInterface.tsx
β”‚ β”‚ └── ResultsChart.tsx
β”‚ β”œβ”€β”€ lib/
β”‚ β”‚ β”œβ”€β”€ proposals.ts # Proposal management
β”‚ β”‚ └── voting.ts # Voting logic
β”‚ └── page.tsx # Main voting interface
β”œβ”€β”€ minikit.config.ts # MiniKit configuration
└── package.json
```

## Usage

### Creating Proposals
1. Connect wallet using MiniKit
2. Click "Create Proposal"
3. Enter title and description
4. Submit to blockchain

### Voting on Proposals
1. Browse active proposals
2. Click "Vote" on desired proposal
3. Select Yes/No/Abstain
4. Confirm transaction

### Viewing Results
- Real-time vote counts
- Proposal status updates
- Historical voting data

## API Endpoints

- `GET /api/proposals` - Fetch all proposals
- `POST /api/proposals` - Create new proposal
- `GET /api/proposals/[id]` - Get specific proposal
- `POST /api/proposals/[id]/vote` - Cast vote on proposal

## Smart Contract Integration

While this demo uses local state for simplicity, it demonstrates the patterns for integrating with governance contracts:

```typescript
// Example contract interaction
const vote = async (proposalId: string, option: VoteOption) => {
const hash = await walletClient.writeContract({
address: GOVERNANCE_CONTRACT,
abi: GOVERNANCE_ABI,
functionName: 'castVote',
args: [proposalId, option]
});
return hash;
};
```

## Contributing

1. Fork the repository
2. Create a feature branch
3. Make changes
4. Test thoroughly
5. Submit a pull request

## Demo Flow

1. **Landing**: Welcome screen with voting overview
2. **Proposal List**: Browse active proposals with status indicators
3. **Create Proposal**: Form to submit new governance proposals
4. **Voting Interface**: Interactive voting with confirmation
5. **Results Dashboard**: Real-time results and analytics

## Security Considerations

- Input validation for all user inputs
- Rate limiting for proposal creation
- Wallet signature verification
- Timestamp-based voting periods
- Duplicate vote prevention

## Future Enhancements

- [ ] Integration with actual governance contracts
- [ ] Quadratic voting mechanisms
- [ ] Proposal categories and tagging
- [ ] Delegate voting system
- [ ] Proposal execution automation
- [ ] Multi-chain voting support

---

Built for the Base ecosystem β€’ Powered by MiniKit
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { withValidManifest } from "@coinbase/onchainkit/minikit";
import { minikitConfig } from "../../../minikit.config";

export async function GET() {
return Response.json(withValidManifest(minikitConfig));
}
82 changes: 82 additions & 0 deletions mini-apps/onchain-voting-demo/app/api/auth/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { Errors, createClient } from "@farcaster/quick-auth";
import { NextRequest, NextResponse } from "next/server";

const client = createClient();

// Helper function to determine the correct domain for JWT verification
function getUrlHost(request: NextRequest): string {
// First try to get the origin from the Origin header (most reliable for CORS requests)
const origin = request.headers.get("origin");
if (origin) {
try {
const url = new URL(origin);
return url.host;
} catch (error) {
console.warn("Invalid origin header:", origin, error);
}
}

// Fallback to Host header
const host = request.headers.get("host");
if (host) {
return host;
}

// Final fallback to environment variables (your original logic)
let urlValue: string;
if (process.env.VERCEL_ENV === "production") {
urlValue = process.env.NEXT_PUBLIC_URL!;
} else if (process.env.VERCEL_URL) {
urlValue = `https://${process.env.VERCEL_URL}`;
} else {
urlValue = "http://localhost:3000";
}

const url = new URL(urlValue);
return url.host;
}

export async function GET(request: NextRequest) {
// Because we're fetching this endpoint via `sdk.quickAuth.fetch`,
// if we're in a mini app, the request will include the necessary `Authorization` header.
const authorization = request.headers.get("Authorization");

// Here we ensure that we have a valid token.
if (!authorization || !authorization.startsWith("Bearer ")) {
return NextResponse.json({ message: "Missing token" }, { status: 401 });
}

try {
// Now we verify the token. `domain` must match the domain of the request.
// In our case, we're using the `getUrlHost` function to get the domain of the request
// based on the Vercel environment. This will vary depending on your hosting provider.
const payload = await client.verifyJwt({
token: authorization.split(" ")[1] as string,
domain: getUrlHost(request),
});

console.log("payload", payload);

// If the token was valid, `payload.sub` will be the user's Farcaster ID.
const userFid = payload.sub;

// Return user information for your waitlist application
return NextResponse.json({
success: true,
user: {
fid: userFid,
issuedAt: payload.iat,
expiresAt: payload.exp,
},
});

} catch (e) {
if (e instanceof Errors.InvalidTokenError) {
return NextResponse.json({ message: "Invalid token" }, { status: 401 });
}
if (e instanceof Error) {
return NextResponse.json({ message: e.message }, { status: 500 });
}
throw e;
}
}
Loading