Skip to content
Closed
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
58 changes: 56 additions & 2 deletions backend/app/api/contributors.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

from app.auth import get_current_user_id
from app.constants import INTERNAL_SYSTEM_USER_ID
from app.exceptions import ContributorNotFoundError, TierNotUnlockedError

Check failure on line 14 in backend/app/api/contributors.py

View workflow job for this annotation

GitHub Actions / Backend Lint (Ruff)

ruff (F401)

app/api/contributors.py:14:54: F401 `app.exceptions.TierNotUnlockedError` imported but unused help: Remove unused import: `app.exceptions.TierNotUnlockedError`

Check failure on line 14 in backend/app/api/contributors.py

View workflow job for this annotation

GitHub Actions / Backend Lint (Ruff)

ruff (F401)

app/api/contributors.py:14:54: F401 `app.exceptions.TierNotUnlockedError` imported but unused help: Remove unused import: `app.exceptions.TierNotUnlockedError`

Check failure on line 14 in backend/app/api/contributors.py

View workflow job for this annotation

GitHub Actions / Backend Lint (Ruff)

ruff (F401)

app/api/contributors.py:14:54: F401 `app.exceptions.TierNotUnlockedError` imported but unused help: Remove unused import: `app.exceptions.TierNotUnlockedError`
from app.models.contributor import (
ContributorCreate,
ContributorListResponse,
Expand Down Expand Up @@ -254,5 +254,59 @@
return await reputation_service.record_reputation(data)
except ContributorNotFoundError as error:
raise HTTPException(status_code=404, detail=str(error))
except TierNotUnlockedError as error:
raise HTTPException(status_code=400, detail=str(error))
class DashboardData(BaseModel):

Check failure on line 257 in backend/app/api/contributors.py

View workflow job for this annotation

GitHub Actions / Backend Lint (Ruff)

ruff (F821)

app/api/contributors.py:257:21: F821 Undefined name `BaseModel`

Check failure on line 257 in backend/app/api/contributors.py

View workflow job for this annotation

GitHub Actions / Backend Lint (Ruff)

ruff (F821)

app/api/contributors.py:257:21: F821 Undefined name `BaseModel`

Check failure on line 257 in backend/app/api/contributors.py

View workflow job for this annotation

GitHub Actions / Backend Lint (Ruff)

ruff (F821)

app/api/contributors.py:257:21: F821 Undefined name `BaseModel`
"""Payload for the contributor dashboard."""
stats: dict
bounties: list
activities: list
notifications: list = []
earnings: list = []
linkedAccounts: list = []
Comment on lines +257 to +264
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Backend DashboardData schema mismatches frontend DashboardData interface.

The backend returns stats, bounties, activities, notifications, earnings, linkedAccounts, but the frontend interface in frontend/src/types/dashboard.ts expects summary, activeBounties, earnings (as EarningsSummary), recentActivity, notifications, and settings (containing linkedAccounts). This contract mismatch will cause runtime errors or incorrect UI rendering when the frontend consumes this endpoint.

πŸ€– Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/app/api/contributors.py` around lines 257 - 264, The DashboardData
Pydantic model currently uses fields stats, bounties, activities, notifications,
earnings, linkedAccounts which do not match the frontend DashboardData
interface; update the DashboardData model (class DashboardData) to expose the
frontend-expected keys: summary, activeBounties, earnings (use the
EarningsSummary shape), recentActivity, notifications, and settings (with
linkedAccounts inside settings), or create a response transformation function
that maps existing internal fields to those exact names and types before
returning to the API client; ensure field names and nested shapes
(EarningsSummary, settings.linkedAccounts) match the frontend types so the JSON
contract is identical.

⚠️ Potential issue | πŸ”΄ Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify BaseModel is not imported in contributors.py
rg -n "from pydantic import" backend/app/api/contributors.py

Repository: SolFoundry/solfoundry

Length of output: 47


🏁 Script executed:

head -25 backend/app/api/contributors.py

Repository: SolFoundry/solfoundry

Length of output: 809


🏁 Script executed:

rg -n "from app.models" backend/app/api/contributors.py

Repository: SolFoundry/solfoundry

Length of output: 143


🏁 Script executed:

cat -n backend/app/models/contributor.py | head -30

Repository: SolFoundry/solfoundry

Length of output: 1023


🏁 Script executed:

sed -n '250,270p' backend/app/api/contributors.py

Repository: SolFoundry/solfoundry

Length of output: 714


🏁 Script executed:

rg -n "class DashboardData" backend/app/api/contributors.py -A 10

Repository: SolFoundry/solfoundry

Length of output: 389


🏁 Script executed:

cat backend/app/models/contributor.py | grep -E "^__all__|class (ContributorCreate|ContributorListResponse|ContributorResponse|ContributorUpdate|BaseModel)"

Repository: SolFoundry/solfoundry

Length of output: 228


Missing BaseModel import will cause runtime NameError.

The DashboardData class at line 257 inherits from BaseModel, but pydantic.BaseModel is never imported in backend/app/api/contributors.py. The file imports specific classes from app.models.contributor (lines 15-20) but not BaseModel. This will cause a NameError when the module loads.

Add to the import section: from pydantic import BaseModel

πŸ€– Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/app/api/contributors.py` around lines 257 - 264, The DashboardData
Pydantic model references BaseModel but BaseModel is not imported; add the
missing import "from pydantic import BaseModel" to the top import block so the
class DashboardData can inherit from BaseModel without raising a NameError.



@router.get("/me/dashboard", response_model=DashboardData)
async def get_my_dashboard(
user_id: str = Depends(get_current_user_id)
) -> DashboardData:
Comment on lines +267 to +270
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | πŸ”΄ Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify route ordering in contributors.py - /me/dashboard should come before /{contributor_id}
rg -n "@router\.(get|post|patch|delete)" backend/app/api/contributors.py | head -20

Repository: SolFoundry/solfoundry

Length of output: 663


🏁 Script executed:

# Check the context around the /{contributor_id} route and /me/dashboard route
sed -n '105,115p' backend/app/api/contributors.py
echo "---"
sed -n '263,275p' backend/app/api/contributors.py

Repository: SolFoundry/solfoundry

Length of output: 712


🏁 Script executed:

# Check if there are any tests for the /me/dashboard endpoint
find . -type f -name "*.py" | xargs grep -l "me/dashboard\|/me.*dashboard" | head -10

Repository: SolFoundry/solfoundry

Length of output: 98


🏁 Script executed:

# Check the router setup and any middleware
head -50 backend/app/api/contributors.py

Repository: SolFoundry/solfoundry

Length of output: 1656


🏁 Script executed:

# Look for any tests that actually test the /me/dashboard endpoint
find . -type f -name "test_*.py" -o -name "*_test.py" | xargs grep -l "dashboard\|me.*dashboard" 2>/dev/null

Repository: SolFoundry/solfoundry

Length of output: 143


🏁 Script executed:

# Check the complete route definitions to understand path matching context
sed -n '100,280p' backend/app/api/contributors.py | cat -n

Repository: SolFoundry/solfoundry

Length of output: 6989


🏁 Script executed:

# Verify how the router is initialized and if there are any special configs
grep -n "router.*=" backend/app/api/contributors.py | head -5

Repository: SolFoundry/solfoundry

Length of output: 420


🏁 Script executed:

# Check if there are any tests specifically for the /me/dashboard route
grep -r "me/dashboard" backend/tests/ --include="*.py"

Repository: SolFoundry/solfoundry

Length of output: 47


🏁 Script executed:

# Verify the exact line numbers for the routes in question
sed -n '105,115p' backend/app/api/contributors.py | cat -n
echo "---"
sed -n '165,175p' backend/app/api/contributors.py | cat -n

Repository: SolFoundry/solfoundry

Length of output: 798


🏁 Script executed:

# Check if this endpoint is actually being tested anywhere
grep -r "get_my_dashboard" backend/tests/ --include="*.py"

Repository: SolFoundry/solfoundry

Length of output: 47


🏁 Script executed:

# Check if there's a test file for the contributors API
find backend/tests -name "*contributor*" -type f

Repository: SolFoundry/solfoundry

Length of output: 99


🏁 Script executed:

# Look for any test that makes a request to /{contributor_id}
grep -r "/{contributor_id}" backend/ --include="*.py" -B 2 -A 2

Repository: SolFoundry/solfoundry

Length of output: 1904


🏁 Script executed:

# Search for actual integration tests or any tests that would exercise these routes
find backend/tests -name "test_*.py" | head -5 | xargs cat | grep -A 5 "contributors"

Repository: SolFoundry/solfoundry

Length of output: 47


🏁 Script executed:

# Check if DashboardData model is properly imported where the route is defined
sed -n '1,30p' backend/app/api/contributors.py

Repository: SolFoundry/solfoundry

Length of output: 943


🏁 Script executed:

# Check the test file to see if /me/dashboard is being tested
cat backend/tests/test_contributors.py

Repository: SolFoundry/solfoundry

Length of output: 11770


🏁 Script executed:

# Verify the exact content around the DashboardData and /me/dashboard route
sed -n '155,180p' backend/app/api/contributors.py

Repository: SolFoundry/solfoundry

Length of output: 803


🏁 Script executed:

# Check if DashboardData is properly imported/defined in the file
grep -n "class DashboardData\|from.*DashboardData\|import.*DashboardData" backend/app/api/contributors.py

Repository: SolFoundry/solfoundry

Length of output: 100


🏁 Script executed:

# Look for any base model import
grep -n "BaseModel\|from pydantic" backend/app/api/contributors.py | head -5

Repository: SolFoundry/solfoundry

Length of output: 100


🏁 Script executed:

# Check if there are any router configuration or middleware that might affect route matching
grep -rn "router.*=" backend/app/ --include="*.py" | grep -i contrib

Repository: SolFoundry/solfoundry

Length of output: 1064


🏁 Script executed:

# Verify the actual line numbers more carefully
nl -ba backend/app/api/contributors.py | sed -n '105,115p'
echo "---SEPARATOR---"
nl -ba backend/app/api/contributors.py | sed -n '263,273p'

Repository: SolFoundry/solfoundry

Length of output: 179


🏁 Script executed:

# Check if there are any integration tests or acceptance tests that would test the full path
find backend/tests -name "*.py" -type f -exec grep -l "me/dashboard\|/api/contributors/me" {} \;

Repository: SolFoundry/solfoundry

Length of output: 47


🏁 Script executed:

# Look for any documentation or API spec that documents this endpoint
find . -name "*.md" -o -name "*.openapi*" -o -name "*.swagger*" | xargs grep -l "me/dashboard" 2>/dev/null | head -5

Repository: SolFoundry/solfoundry

Length of output: 47


🏁 Script executed:

# Get the complete file and check the exact context around both routes
sed -n '100,120p' backend/app/api/contributors.py
echo "---DIVIDER---"
sed -n '250,275p' backend/app/api/contributors.py

Repository: SolFoundry/solfoundry

Length of output: 1438


🏁 Script executed:

# Look for how the main app is configured and if there's anything special about routing
sed -n '1,30p' backend/app/main.py

Repository: SolFoundry/solfoundry

Length of output: 1461


🏁 Script executed:

# Search for any existing route tests that would test the /me pattern
grep -n "test.*me\|/me" backend/tests/test_contributors.py

Repository: SolFoundry/solfoundry

Length of output: 286


🏁 Script executed:

# Verify the actual behavior by checking if there's any documentation or comments about route matching
grep -B 5 -A 5 "/me/dashboard" backend/app/api/contributors.py

Repository: SolFoundry/solfoundry

Length of output: 381


🏁 Script executed:

# Check if this route is actually working or if there are any known issues
grep -r "dashboard" backend/ --include="*.py" | grep -i "test\|issue\|fixme\|todo" | head -10

Repository: SolFoundry/solfoundry

Length of output: 256


🏁 Script executed:

# Verify by creating a minimal test to check path matching behavior
python3 << 'EOF'
# Simulate FastAPI route matching behavior
paths_to_test = [
    "/contributors/me",
    "/contributors/me/dashboard",
    "/contributors/alice",
    "/contributors/some-uuid-123",
]

# Pattern: /{contributor_id} matches single segment only
single_segment_pattern = r"^/contributors/([^/]+)$"

# Pattern: /me/dashboard matches two segments
multi_segment_pattern = r"^/contributors/me/dashboard$"

import re

print("Single segment pattern /{contributor_id}:")
for path in paths_to_test:
    match = re.match(single_segment_pattern, path)
    print(f"  {path}: {'MATCH' if match else 'NO MATCH'}")

print("\nMulti segment pattern /me/dashboard:")
for path in paths_to_test:
    match = re.match(multi_segment_pattern, path)
    print(f"  {path}: {'MATCH' if match else 'NO MATCH'}")
EOF

Repository: SolFoundry/solfoundry

Length of output: 412


🏁 Script executed:

# Check if BaseModel is imported at the top of the file
head -30 backend/app/api/contributors.py | grep -i "pydantic\|basemodel"

Repository: SolFoundry/solfoundry

Length of output: 47


🏁 Script executed:

# Get the complete imports section
sed -n '1,50p' backend/app/api/contributors.py

Repository: SolFoundry/solfoundry

Length of output: 1656


🏁 Script executed:

# Verify if the endpoint is incomplete or has syntax errors
sed -n '257,290p' backend/app/api/contributors.py

Repository: SolFoundry/solfoundry

Length of output: 1131


🏁 Script executed:

# Check if BaseModel is imported anywhere in the file (maybe as part of a wildcard import)
grep -n "from pydantic\|import.*BaseModel\|from.*models" backend/app/api/contributors.py

Repository: SolFoundry/solfoundry

Length of output: 143


🏁 Script executed:

# Check the models.contributor module to see if BaseModel is re-exported
grep -n "BaseModel" backend/app/models/contributor.py | head -5

Repository: SolFoundry/solfoundry

Length of output: 263


🏁 Script executed:

# Check what's actually imported from app.models.contributor
sed -n '15,23p' backend/app/api/contributors.py

Repository: SolFoundry/solfoundry

Length of output: 295


🏁 Script executed:

# Verify if BaseModel is missing from the imports in contributors.py
rg "^from pydantic import|^import.*BaseModel" backend/app/api/contributors.py

Repository: SolFoundry/solfoundry

Length of output: 47


🏁 Script executed:

# Look at the complete DashboardData definition and the imports just before it
sed -n '245,280p' backend/app/api/contributors.py

Repository: SolFoundry/solfoundry

Length of output: 1164


🏁 Script executed:

# Final verification: Check if there's a __init__.py or any wildcard imports that could export BaseModel
grep -r "from pydantic import.*\*\|__all__.*BaseModel" backend/app/ --include="*.py" | head -5

Repository: SolFoundry/solfoundry

Length of output: 47


🏁 Script executed:

# Check if the file can even be imported (syntax check)
python3 -c "import sys; sys.path.insert(0, 'backend'); from app.api.contributors import router; print('Import successful')" 2>&1 | head -20

Repository: SolFoundry/solfoundry

Length of output: 332


🏁 Script executed:

# Check the exact imports more carefully and see if BaseModel might be available indirectly
sed -n '1,30p' backend/app/api/contributors.py | cat -n

Repository: SolFoundry/solfoundry

Length of output: 1153


🏁 Script executed:

# Verify the complete import section including any missed lines
sed -n '1,60p' backend/app/api/contributors.py | grep -E "^from|^import"

Repository: SolFoundry/solfoundry

Length of output: 456


Missing BaseModel import causes runtime error.

The DashboardData class at line 257 uses BaseModel which is not imported. Add from pydantic import BaseModel to the imports at the top of the file.

Note: The route ordering concern is unfounded. FastAPI's path matching correctly distinguishes between /{contributor_id} (single-segment route) and /me/dashboard (two-segment literal route), so these patterns do not conflict regardless of definition order.

πŸ€– Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/app/api/contributors.py` around lines 267 - 270, The file is missing
the BaseModel import used by the DashboardData pydantic model; add the import
"from pydantic import BaseModel" to the module imports so DashboardData (and any
other pydantic models) can inherit BaseModel without causing a runtime NameError
β€” locate the DashboardData class and the get_my_dashboard route to confirm the
import is available at the top of the file.

"""Get metrics and state for the authenticated contributor's dashboard.

Args:
user_id: Authenticated user (contributor) ID.

Returns:
Structured dashboard data with stats, bounties, and activity history.

Raises:
HTTPException 404: Contributor profile not found.
"""
contributor = await contributor_service.get_contributor(user_id)
if not contributor:
raise HTTPException(status_code=404, detail="Contributor profile not found")

# In a real app, query database/indexers for these lists
stats = {
"totalEarned": contributor.total_earnings,
"activeBounties": 2, # Mocked count
"pendingPayouts": 0,
"reputationRank": 15,
"totalContributors": 120,
}

bounties = [
{"id": "b1", "title": "Implement API Rate Limiting", "reward": 500, "deadline": "2026-03-30", "status": "in_progress", "progress": 75},
{"id": "b2", "title": "Refactor Frontend Hooks", "reward": 300, "deadline": "2026-03-29", "status": "claimed", "progress": 10},
]

activities = [
{"id": "a1", "type": "payout", "title": "Payout Received", "description": "Earned 500 $FNDRY for rate limiter", "timestamp": "2026-03-21T10:00:00Z", "amount": 500},
{"id": "a2", "type": "pr_submitted", "title": "PR Submitted", "description": "Submitted PR for security middleware", "timestamp": "2026-03-21T09:00:00Z"},
]
Comment on lines +286 to +303
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Dashboard endpoint returns hardcoded mock data.

The bounties and activities fields return hardcoded static data rather than querying the database. This contradicts the PR objective of replacing mock data with real API integration. The comment on line 286 acknowledges this ("In a real app, query database/indexers"), but for a production-ready integration, these should fetch actual contributor-specific data.

Do you want me to help design the database queries to fetch real bounty and activity data for the authenticated contributor?

πŸ€– Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/app/api/contributors.py` around lines 286 - 303, The dashboard
currently returns hardcoded bounties/activities (symbols: bounties, activities,
stats, contributor.total_earnings) β€” replace these mocks with real DB/indexer
queries: call a repository or ORM methods (e.g.,
get_bounties_for_contributor(contributor.id) and
get_activities_for_contributor(contributor.id, limit, cursor)) to fetch
contributor-specific records, map DB fields into the existing response shape
(id, title, reward, deadline, status, progress for bounties; id, type, title,
description, timestamp, amount for activities), and compute derived stats
(activeBounties, pendingPayouts, totalContributors) from query results or count
queries instead of hardcoded numbers; ensure you handle pagination, errors, and
an empty-result case before returning the assembled stats, bounties, and
activities.


return DashboardData(
stats=stats,
bounties=bounties,
activities=activities,
notifications=[],
earnings=[],
linkedAccounts=[{"type": "github", "username": contributor.username, "connected": True}],
)
47 changes: 45 additions & 2 deletions backend/app/api/stats.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import logging
import time
from datetime import datetime, timezone
from typing import Dict, List, Optional

Check failure on line 10 in backend/app/api/stats.py

View workflow job for this annotation

GitHub Actions / Backend Lint (Ruff)

ruff (F401)

app/api/stats.py:10:26: F401 `typing.List` imported but unused help: Remove unused import: `typing.List`

Check failure on line 10 in backend/app/api/stats.py

View workflow job for this annotation

GitHub Actions / Backend Lint (Ruff)

ruff (F401)

app/api/stats.py:10:26: F401 `typing.List` imported but unused help: Remove unused import: `typing.List`

Check failure on line 10 in backend/app/api/stats.py

View workflow job for this annotation

GitHub Actions / Backend Lint (Ruff)

ruff (F401)

app/api/stats.py:10:26: F401 `typing.List` imported but unused help: Remove unused import: `typing.List`

from fastapi import APIRouter
from pydantic import BaseModel
Expand Down Expand Up @@ -135,7 +135,36 @@
return data


@router.get("/api/stats", response_model=StatsResponse)
class TokenomicsData(BaseModel):
"""Tokenomics data response."""
tokenName: str = "$FNDRY"
tokenCA: str = "Fndry...1H7"
totalSupply: int = 1_000_000_000
circulatingSupply: int = 420_000_000
treasuryHoldings: int = 250_000_000
totalDistributed: int = 150_000_000
totalBuybacks: int = 50_000_000
totalBurned: int = 30_000_000
feeRevenueSol: float = 1245.50
distributionBreakdown: Dict[str, int] = {
"Circulating": 420_000_000,
"Treasury": 250_000_000,
"Staking": 200_000_000,
"Team_Vested": 100_000_000,
"Burned": 30_000_000,
}
lastUpdated: str = datetime.now(timezone.utc).isoformat()
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟑 Minor

lastUpdated is evaluated at module load time, not request time.

datetime.now(timezone.utc).isoformat() as a default value is evaluated once when the module loads, not when each request is made. All responses will show the same stale timestamp until the server restarts.

Proposed fix
 class TokenomicsData(BaseModel):
     """Tokenomics data response."""
     tokenName: str = "$FNDRY"
     # ... other fields ...
-    lastUpdated: str = datetime.now(timezone.utc).isoformat()
+    lastUpdated: Optional[str] = None

 `@router.get`("/stats/tokenomics", response_model=TokenomicsData)
 async def get_tokenomics() -> TokenomicsData:
     """Get tokenomics statistics for $FNDRY."""
-    return TokenomicsData()
+    return TokenomicsData(lastUpdated=datetime.now(timezone.utc).isoformat())
πŸ€– Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/app/api/stats.py` at line 156, The module-level assignment
lastUpdated = datetime.now(timezone.utc).isoformat() is evaluated at import time
so the timestamp is stale; instead compute the timestamp at request/response
time. Remove the module-level default and either (A) set lastUpdated using
datetime.now(timezone.utc).isoformat() right before building the response inside
the stats endpoint function (e.g., in the handler that returns the stats
dict/response), or (B) if lastUpdated is a model field (dataclass/Pydantic), use
a default_factory/Field(default_factory=...) to call
datetime.now(timezone.utc).isoformat() per instance; update references to
lastUpdated accordingly (look for the symbol lastUpdated and the stats response
builder in backend/app/api/stats.py).



class TreasuryStats(BaseModel):
"""Treasury stats response."""
solBalance: float = 1245.50
fndryBalance: int = 250_000_000
totalPayouts: int = 1240
treasuryWallet: str = "6v...m7p"
Comment on lines +138 to +164
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | πŸ”΅ Trivial

Tokenomics and treasury endpoints return hardcoded placeholder data.

Similar to the dashboard endpoint, these endpoints return static mock data rather than querying real blockchain/indexer sources. The inline comments acknowledge this limitation. For a complete API integration, consider implementing actual data fetching or clearly documenting these as placeholder endpoints that require future implementation.

πŸ€– Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/app/api/stats.py` around lines 138 - 164, The TokenomicsData and
TreasuryStats Pydantic models (TokenomicsData, TreasuryStats) currently hold
hardcoded mock values; replace these placeholders by wiring the tokenomics and
treasury endpoints to real data sources (e.g., call the indexer/blockchain
client used elsewhere in the service or implement helper functions like
fetch_tokenomics(), fetch_treasury_stats()) and populate the models from those
calls, or if immediate integration isn't possible, add clear
documentation/comments and an explicit β€œplaceholder” flag/property on the
response models so callers know the values are mocked; update any endpoint
handlers that return TokenomicsData/TreasuryStats to use the new fetchers or to
set the placeholder indicator.



@router.get("/stats", response_model=StatsResponse)
async def get_stats() -> StatsResponse:
"""Get bounty program statistics.

Expand All @@ -151,4 +180,18 @@
Cached for 5 minutes.
"""
data = _get_cached_stats()
return StatsResponse(**data)
return StatsResponse(**data)


@router.get("/stats/tokenomics", response_model=TokenomicsData)
async def get_tokenomics() -> TokenomicsData:
"""Get tokenomics statistics for $FNDRY."""
# In a real app, this would query the blockchain/indexers
return TokenomicsData()


@router.get("/stats/treasury", response_model=TreasuryStats)
async def get_treasury() -> TreasuryStats:
"""Get treasury wallet statistics."""
# In a real app, this would query the treasury wallet address
return TreasuryStats()
6 changes: 5 additions & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,11 @@
"@solana/wallet-adapter-wallets": "^0.19.32",
"@solana/web3.js": "^1.95.0",
"@tailwindcss/postcss": "^4.2.2",
"@tanstack/react-query": "^5.94.5",
"@tanstack/react-query-devtools": "^5.94.5",
"autoprefixer": "^10.4.27",
"axios": "^1.13.6",
"lucide-react": "^0.577.0",
"postcss": "^8.5.8",
"react": "^18.3.1",
"react-dom": "^18.3.1",
Expand All @@ -32,4 +36,4 @@
"vite": "^6.0.0",
"vitest": "^3.0.0"
}
}
}
35 changes: 25 additions & 10 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,18 @@ import { SiteLayout } from './components/layout/SiteLayout';
import { ThemeProvider } from './contexts/ThemeContext';
import { ToastProvider } from './contexts/ToastContext';
import { ToastContainer } from './components/common/ToastContainer';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';

const queryClient = new QueryClient({
defaultOptions: {
queries: {
retry: 1,
refetchOnWindowFocus: false,
staleTime: 1000 * 60 * 5, // 5 minutes
},
},
});

// ── Lazy-loaded page components ──────────────────────────────────────────────
const BountiesPage = lazy(() => import('./pages/BountiesPage'));
Expand Down Expand Up @@ -87,15 +99,18 @@ function AppLayout() {
// ── Root App ─────────────────────────────────────────────────────────────────
export default function App() {
return (
<BrowserRouter>
<ThemeProvider defaultTheme="dark">
<ToastProvider>
<WalletProvider defaultNetwork="mainnet-beta">
<AppLayout />
</WalletProvider>
<ToastContainer />
</ToastProvider>
</ThemeProvider>
</BrowserRouter>
<QueryClientProvider client={queryClient}>
<BrowserRouter>
<ThemeProvider defaultTheme="dark">
<ToastProvider>
<WalletProvider defaultNetwork="mainnet-beta">
<AppLayout />
</WalletProvider>
<ToastContainer />
</ToastProvider>
</ThemeProvider>
</BrowserRouter>
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
);
}
Loading
Loading