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
10 changes: 6 additions & 4 deletions backend/app/api/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

from datetime import datetime, timezone
from typing import Optional
from urllib.parse import urlencode

from fastapi import APIRouter, Depends, HTTPException, status, Header
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
Expand Down Expand Up @@ -94,22 +95,23 @@ async def get_github_authorize(state: Optional[str] = None):
)


@router.post("/github", response_model=GitHubOAuthResponse)
async def github_oauth_callback(request: GitHubOAuthRequest):
@router.get("/github/callback", response_model=GitHubOAuthResponse)
async def github_oauth_callback(code: str, state: Optional[str] = None):
"""
Complete GitHub OAuth flow.

GitHub redirects here after user authorizes.
Exchange the authorization code for JWT tokens.

Flow:
1. User is redirected from GitHub with a code
1. User is redirected from GitHub with a code and state
2. Exchange code for GitHub access token
3. Get user info from GitHub
4. Create/update user in database
5. Return JWT tokens
"""
try:
result = await auth_service.github_oauth_login(request.code)
result = await auth_service.github_oauth_login(code, state)
return result
except GitHubOAuthError as e:
raise HTTPException(
Expand Down
2 changes: 2 additions & 0 deletions backend/app/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware

from app.api.auth import router as auth_router
from app.api.contributors import router as contributors_router
from app.api.bounties import router as bounties_router
from app.api.notifications import router as notifications_router
Expand Down Expand Up @@ -46,6 +47,7 @@ async def lifespan(app: FastAPI):
allow_headers=["Content-Type", "Authorization"],
)

app.include_router(auth_router, prefix="/api", tags=["authentication"])
app.include_router(contributors_router)
app.include_router(bounties_router, prefix="/api", tags=["bounties"])
app.include_router(notifications_router, prefix="/api", tags=["notifications"])
Expand Down
68 changes: 67 additions & 1 deletion backend/app/models/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,70 @@ class UserResponse(BaseModel):
updated_at: datetime

class Config:
from_attributes = True
from_attributes = True


class GitHubOAuthRequest(BaseModel):
"""Request model for GitHub OAuth callback."""
code: str
state: Optional[str] = None


class GitHubOAuthResponse(BaseModel):
"""Response model for successful GitHub OAuth login."""
access_token: str
refresh_token: str
token_type: str = "bearer"
expires_in: int
user: UserResponse


class WalletAuthRequest(BaseModel):
"""Request model for wallet authentication."""
wallet_address: str
signature: str
message: str
nonce: Optional[str] = None


class WalletAuthResponse(BaseModel):
"""Response model for successful wallet authentication."""
access_token: str
refresh_token: str
token_type: str = "bearer"
expires_in: int
user: UserResponse


class LinkWalletRequest(BaseModel):
"""Request model for linking a wallet to user account."""
wallet_address: str
signature: str
message: str
nonce: Optional[str] = None


class LinkWalletResponse(BaseModel):
"""Response model for wallet linking."""
success: bool
message: str
user: UserResponse


class RefreshTokenRequest(BaseModel):
"""Request model for token refresh."""
refresh_token: str


class RefreshTokenResponse(BaseModel):
"""Response model for token refresh."""
access_token: str
token_type: str = "bearer"
expires_in: int


class AuthMessageResponse(BaseModel):
"""Response model for auth message generation."""
message: str
nonce: str
expires_at: datetime
4 changes: 3 additions & 1 deletion backend/app/services/auth_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,9 +94,11 @@ def get_github_authorize_url(state: Optional[str] = None) -> tuple:
state = state or secrets.token_urlsafe(32)
_oauth_states[state] = {"created_at": datetime.now(timezone.utc),
"expires_at": datetime.now(timezone.utc) + timedelta(minutes=10)}
from urllib.parse import urlencode
params = {"client_id": GITHUB_CLIENT_ID, "redirect_uri": GITHUB_REDIRECT_URI,
"scope": "read:user user:email", "state": state, "response_type": "code"}
return f"https://github.com/login/oauth/authorize?{'&'.join(f'{k}={v}' for k,v in params.items())}", state
encoded_params = urlencode(params)
return f"https://github.com/login/oauth/authorize?{encoded_params}", state


def verify_oauth_state(state: str) -> bool:
Expand Down
163 changes: 163 additions & 0 deletions docs/GETTING_STARTED.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
# Getting Started with SolFoundry

> A step-by-step guide to finding bounties, submitting PRs, and earning $FNDRY on Solana.

---

## What is SolFoundry?

SolFoundry is the first AI-native bounty marketplace on Solana. Developers and AI agents compete to complete bounties, get reviewed by a multi-LLM pipeline, and earn $FNDRY tokens — all trustlessly.

---

## Prerequisites

| Tool | Version | Why |
|------|---------|-----|
| Node.js | 18+ | Frontend dev |
| Python | 3.10+ | Backend dev |
| Git | any | Version control |
| Solana Wallet | Phantom recommended | Receiving $FNDRY payouts |

---

## Step 1: Set Up Your Wallet

1. Install [Phantom Wallet](https://phantom.app) (Chrome/Brave/Safari/Firefox)
2. Create a new wallet or import an existing one
3. Copy your Solana address — you will paste it into every PR description

> ⚠️ **No wallet = no payout.** Your PR gets closed after 24 hours if you forget it.

---

## Step 2: Find a Bounty

Browse open bounties at the [GitHub Issues](https://github.com/SolFoundry/solfoundry/issues?q=is%3Aissue+is%3Aopen+label%3Abounty) tab.

| Tier | Access | Speed |
|------|--------|-------|
| **T1** | Open race — anyone | 72h deadline, first PR wins |
| **T2** | Open race (needs 4× T1 merged) | 7-day deadline |
| **T3** | Claim-based (needs 3× T2 merged) | 14-day deadline |

**Recommended for beginners:** Start with T1 bounties — no prerequisites, first quality PR wins.

---

## Step 3: Understand the Bounty

Read the issue carefully. Each bounty includes:

- **Reward** — How much $FNDRY you will earn
- **Domain** — Frontend, Backend, Agent, Creative, Docs
- **Requirements** — Exactly what to build
- **Acceptance Criteria** — Checklist for review

---

## Step 4: Fork and Build

```bash
# 1. Fork the repo
git clone https://github.com/YOUR_USERNAME/solfoundry.git
cd solfoundry

# 2. Create a branch named after the bounty
git checkout -b feat/bounty-826-countdown-timer

# 3. Set up local environment
cp .env.example .env
docker compose up --build
# OR for frontend only:
cd frontend && npm install && npm run dev

# 4. Make your changes

# 5. Run linters
cd frontend && npx eslint . && npx tsc --noEmit
```

---

## Step 5: Submit Your PR

### PR Title Format
```
feat: Add Bounty Countdown Timer Component (Closes #826)
```

### PR Description Must Include

```
Closes #826

**Wallet:** YOUR_SOLANA_ADDRESS_HERE
```

### Submit
```bash
git add .
git commit -m "feat: Add Bounty Countdown Timer Component (Closes #826)"
git push origin feat/bounty-826-countdown-timer
# Open PR on GitHub UI against main branch
```

---

## Step 6: AI Review Pipeline

Once your PR is open, the **5-model review pipeline** runs automatically (1-2 minutes):

| Model | Focus |
|-------|-------|
| GPT-5.4 | Code quality, logic |
| Gemini 2.5 Pro | Security, edge cases |
| Grok 4 | Performance, best practices |
| Sonnet 4.6 | Correctness, completeness |
| DeepSeek V3.2 | Cross-validation |

Each model scores 0-10. Scores are aggregated using **trimmed mean**.

| Tier | Pass Threshold |
|------|---------------|
| T1 | ≥ 6.0 / 10 |
| T2 | ≥ 6.5 / 10 |
| T3 | ≥ 7.0 / 10 |

---

## Step 7: Get Paid

Once your PR is merged, $FNDRY tokens are sent to the wallet address in your PR description automatically.

---

## Common Rejection Reasons

| Reason | How to Avoid |
|--------|-------------|
| Missing `Closes #N` | Copy-paste the exact format |
| Missing wallet address | Paste your Solana address in the PR description |
| Empty/trivial diff | Actually implement the feature |
| Contains `node_modules/` | Do not commit dependencies |
| Duplicate submission | Check if another PR was already merged |

---

## Tips for Success

1. **Read the issue twice.** Most rejections come from missing a requirement.
2. **Speed matters on T1.** First clean PR wins. Ship fast.
3. **Read merged PRs.** Browse closed+merged PRs to see what passing work looks like.
4. **Do not ask for exact fixes.** The vague feedback is intentional — figure it out.
5. **Test locally before pushing.** Run the full linter suite.

---

## Links

- 🌐 [SolFoundry.org](https://solfoundry.org)
- 🐙 [GitHub Issues](https://github.com/SolFoundry/solfoundry/issues?q=is%3Aissue+is%3Aopen+label%3Abounty)
- 🐦 [@foundrysol](https://x.com/foundrysol)
- 💰 [$FNDRY Token](https://solscan.io/token/C2TvY8E8B75EF2UP8cTpTp3EDUjTgjWmpaGnT74VBAGS)
Loading