Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
ca74c69
Foundation for new AgentList section
janhqnguyen2020 Nov 28, 2025
f00a44e
Zustand attempt for new agent list integration and bet modal
janhqnguyen2020 Nov 28, 2025
08cc05e
Full Dashboard v2 + Backend API updates:
janhqnguyen2020 Dec 4, 2025
2e5ecf1
Backend cleanup:
janhqnguyen2020 Dec 4, 2025
6633f99
Merge frontend-expansion into feature/home-dashboard-v2
janhqnguyen2020 Dec 4, 2025
d9393f0
Ensure that agentlist reflects new data and filter
janhqnguyen2020 Dec 4, 2025
8a0f710
Majority of Web3 UI
janhqnguyen2020 Dec 5, 2025
4875c28
Updated home page to have web3 ui. Improved betting process
janhqnguyen2020 Dec 5, 2025
b0ace87
Admin frontend: initial UI, mock data, dashboards
janhqnguyen2020 Jan 13, 2026
dfab1dc
package.json merge conflicts fixed
kaylazhang07 Jan 17, 2026
ff9a7ad
Frontend update. Merging of front-end progress from Fall with new mas…
janhqnguyen2020 Jan 18, 2026
f07406f
Fixing bug issues. Mistake on Joseph, branch was made from neither ol…
janhqnguyen2020 Jan 18, 2026
658e6bb
Fixed Merge bugs. Lint issues should be mostly resolved
janhqnguyen2020 Jan 19, 2026
69d9c26
Fixed Bugs in landing page, and worked on consistency issue
janhqnguyen2020 Jan 19, 2026
1fc160c
Landing Page Integration with Fall
janhqnguyen2020 Jan 20, 2026
3c65021
Merge admin-frontend into frontend-expansion - adds admin dashboard
janhqnguyen2020 Jan 21, 2026
526fc69
landing page rework
janhqnguyen2020 Jan 21, 2026
c51d00f
WIP admin/landing
janhqnguyen2020 Jan 21, 2026
7d5dc9a
alteration to landing
janhqnguyen2020 Jan 22, 2026
7ab69fd
Landing Page revamped, home page and admin errors fixed
janhqnguyen2020 Jan 26, 2026
7d299cd
Bet interface was implemented. sign in feature for agnous
janhqnguyen2020 Jan 31, 2026
4bbb4d8
Integrate on-chain betting (backend + frontend wiring)
janhqnguyen2020 Feb 5, 2026
1dbdffd
Smart contract integration: on-chain tournament lifecycle + UI polish
janhqnguyen2020 Feb 7, 2026
d6d7c3e
Merge origin/main into smartContractFeature - resolve conflicts, fix …
janhqnguyen2020 Feb 10, 2026
576bb48
Test error fix with Name of testing data
janhqnguyen2020 Feb 10, 2026
e2d47b2
Fix test string, merge conflict issue
janhqnguyen2020 Feb 10, 2026
30bdced
Merge branch 'main' into smartContractFeature
janhqnguyen2020 Feb 10, 2026
728c018
Duplicate code identified
janhqnguyen2020 Feb 10, 2026
dfcfba8
added backend changes from main (sync db, seed data)
janhqnguyen2020 Feb 12, 2026
2afca17
Add columns for smart contract mapping
janhqnguyen2020 Feb 13, 2026
af4086c
Alterations to the bet interface
janhqnguyen2020 Feb 14, 2026
25a8f3b
Settle and update claims
janhqnguyen2020 Feb 14, 2026
9fd5d99
Fix decimal format issue
janhqnguyen2020 Feb 14, 2026
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 .github/workflows/backend-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ jobs:
- name: Test
env:
DATABASE_URL: postgresql+asyncpg://postgres:postgres@localhost:5432/test_db
PERPLEXITY_API_KEY: ${{ secrets.PERPLEXITY_API_KEY }}
run: pytest -v --cov=app --cov-report=xml

- name: Upload coverage
Expand Down
2 changes: 2 additions & 0 deletions backend/app/agents/scheduler.py
Original file line number Diff line number Diff line change
Expand Up @@ -658,6 +658,8 @@ async def _update_tournament_rankings_async() -> Dict[str, Any]:
}




# ============================================================================
# CRASH RECOVERY TASKS
# ============================================================================
Expand Down
16 changes: 8 additions & 8 deletions backend/app/api/deps.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,26 @@
SECRET_KEY = os.getenv("SECRET_KEY", "dev-secret-key")
ALGORITHM = "HS256"

def get_current_user(authorization: str = Header(...)) -> dict:
def get_current_user(authorization: str = Header(None)) -> dict:
"""Verify JWT token"""
if not authorization.startswith("Bearer "):
raise HTTPException(401, "Invalid authorization header")
if not authorization or not authorization.startswith("Bearer "):
raise HTTPException(401, "Missing or invalid authorization header")

token = authorization.split(" ")[1]

try:
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
user_address: str = payload.get("sub")
role: str = payload.get("role")

if user_address is None:
raise HTTPException(401, "Invalid token")

return {"address": user_address, "role": role}
except JWTError:
raise HTTPException(401, "Invalid token")

def require_admin(user: dict = Depends(get_current_user)) -> dict:
if user.get("role") != "admin":
raise HTTPException(403, "Admin access required")
return user
return user
7 changes: 3 additions & 4 deletions backend/app/api/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,9 @@
try:
from .routers import tournament, agent, agent_state, trade, bet, auth, market_data
except ImportError:
from routers import tournament, agent, agent_state, trade, bet, auth, market_data
from app.api.routers import tournament, agent, agent_state, trade, bet, auth, market_data
from os import getenv
import uvicorn
from contextlib import asynccontextmanager


APPLICATION_PORT = 8000
Expand All @@ -26,7 +25,7 @@
allow_headers=["*"], # Allow all headers
)

# Register routers
# Register Routers
app.include_router(tournament.router, prefix="/tournaments", tags=["Tournaments"])
app.include_router(agent.router, prefix="/agents", tags=["Agents"])
app.include_router(agent_state.router, prefix="/agent-states", tags=["Agent States"])
Expand All @@ -38,7 +37,7 @@

@app.get("/")
def root():
return {"message": "Agonus API running 🚀"}
return {"message": "Agonus API running"}


if __name__ == "__main__":
Expand Down
19 changes: 5 additions & 14 deletions backend/app/api/routers/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ async def get_agent_stats(agent_id: UUID, session: AsyncSession = Depends(get_db
if not agent:
raise HTTPException(status_code=404, detail="Agent Not Found")

# Return the stats JSON field
return agent.stats


Expand All @@ -47,14 +46,12 @@ async def create_agent(
admin: dict = Depends(require_admin),
):
"""POST route for creating a new agent (admin only)"""
# Create Agent model from schema, excluding None values to use model defaults
agent_dict = agent_data.model_dump(exclude_none=True)

# Ensure stats and memory have default values if not provided
if 'stats' not in agent_dict:
agent_dict['stats'] = {}
if 'memory' not in agent_dict:
agent_dict['memory'] = {}
if "stats" not in agent_dict:
agent_dict["stats"] = {}
if "memory" not in agent_dict:
agent_dict["memory"] = {}

agent = Agent(**agent_dict)

Expand All @@ -76,7 +73,6 @@ async def update_agent(
if not db_agent:
raise HTTPException(status_code=404, detail="Agent Not Found")

# Update only provided fields
update_data = agent_data.model_dump(exclude_unset=True)

for key, value in update_data.items():
Expand All @@ -101,7 +97,6 @@ async def update_agent_stats(
if not agent:
raise HTTPException(status_code=404, detail="Agent Not Found")

# Merge new stats with existing stats
agent.stats = {**agent.stats, **stats}

session.add(agent)
Expand All @@ -123,7 +118,6 @@ async def update_agent_memory(
if not agent:
raise HTTPException(status_code=404, detail="Agent Not Found")

# Merge new memory with existing memory
agent.memory = {**agent.memory, **memory}

session.add(agent)
Expand All @@ -139,15 +133,12 @@ async def delete_agent(
session: AsyncSession = Depends(get_db),
admin: dict = Depends(require_admin),
):
"""DELETE route for deleting an agent (admin only - use with caution)"""
"""DELETE route for deleting an agent (admin only)"""
agent = await session.get(Agent, agent_id)
if not agent:
raise HTTPException(status_code=404, detail="Agent Not Found")

# Note: This will fail if agent has related trades/bets due to foreign keys
# Consider soft delete instead (add 'active' boolean field)
await session.delete(agent)
await session.commit()

return {"message": f"Agent {agent_id} deleted successfully"}

47 changes: 33 additions & 14 deletions backend/app/api/routers/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,37 +11,56 @@
SECRET_KEY = os.getenv("SECRET_KEY", "dev-secret-key")
ALGORITHM = "HS256"

# Load admin addresses from env
raw_addresses = os.getenv("ADMIN_ADDRESSES", "")
ADMIN_ADDRESSES = set(addr.strip().lower() for addr in raw_addresses.split(",") if addr.strip())
ADMIN_ADDRESSES = {
addr.strip().lower() for addr in raw_addresses.split(",") if addr.strip()
}


class WalletSignIn(BaseModel):
address: str
signature: str

@router.post("/wallet") # Changed from /wallet-signin

@router.post("/wallet")
def wallet_auth(data: WalletSignIn) -> dict:
"""Sign in with wallet and return JWT"""

"""
Verify wallet signature and return JWT.
Used for login on the frontend.
"""

# Verify signature
message = "Sign in to Agonus"

try:
w3 = Web3()
encoded_message = encode_defunct(text=message)
recovered_address = w3.eth.account.recover_message(encoded_message, signature=data.signature)


recovered_address = w3.eth.account.recover_message(
encoded_message, signature=data.signature
)

if recovered_address.lower() != data.address.lower():
raise HTTPException(401, "Invalid signature")
raise HTTPException(status_code=401, detail="Invalid signature")

except Exception as e:
raise HTTPException(401, f"Signature verification failed: {str(e)}")
# Check role and issue JWT
raise HTTPException(status_code=401, detail=f"Signature verification failed: {str(e)}")

# Determine role
role = "admin" if data.address.lower() in ADMIN_ADDRESSES else "user"

print(f"[AUTH] Wallet: {data.address.lower()}, ADMIN_ADDRESSES: {ADMIN_ADDRESSES}, Role: {role}")

# Generate JWT
expire = datetime.utcnow() + timedelta(hours=24)
token = jwt.encode(
{"sub": data.address.lower(), "role": role, "exp": expire},
{
"sub": data.address.lower(),
"role": role,
"exp": expire,
},
SECRET_KEY,
algorithm=ALGORITHM
algorithm=ALGORITHM,
)

return {"access_token": token, "token_type": "bearer"}
36 changes: 21 additions & 15 deletions backend/app/api/routers/bet.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from fastapi import APIRouter, Depends, HTTPException
from datetime import timezone
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select
from uuid import UUID
Expand All @@ -20,10 +21,11 @@ async def list_bets(session: AsyncSession = Depends(get_db)):
return bets


# IMPORTANT: This route must come BEFORE /{bet_id} to avoid path conflicts
# IMPORTANT: This route must come BEFORE /{bet_id}
@router.get("/my-bets", response_model=list[BetResponse])
async def get_user_bets(
session: AsyncSession = Depends(get_db), user: dict = Depends(get_current_user)
session: AsyncSession = Depends(get_db),
user: dict = Depends(get_current_user)
):
"""GET route for current user's bets"""
statement = select(Bet).where(Bet.user_address == user["address"])
Expand All @@ -48,11 +50,14 @@ async def create_bet(
user: dict = Depends(get_current_user),
):
"""POST route for creating a new bet"""
# Override user_address with authenticated user's address
bet_dict = bet_data.model_dump()
bet_dict["user_address"] = user[
"address"
] # Ensure user can only bet for themselves
bet_dict = bet_data.model_dump(exclude_unset=True)
bet_dict["user_address"] = user["address"] # Force bet ownership

# Remove fields not in the database model
bet_dict.pop("tx_hash", None)
# Store naive UTC if DB column is TIMESTAMP WITHOUT TIME ZONE
if "placed_at" in bet_dict and bet_dict["placed_at"] is not None:
bet_dict["placed_at"] = bet_dict["placed_at"].replace(tzinfo=None)

bet = Bet(**bet_dict)

Expand All @@ -64,9 +69,11 @@ async def create_bet(

@router.patch("/{bet_id}/settle", response_model=BetResponse)
async def settle_bet(
bet_id: UUID, payout: float, session: AsyncSession = Depends(get_db)
bet_id: UUID,
payout: float,
session: AsyncSession = Depends(get_db),
):
"""PATCH route for settling a bet (admin/system use)"""
"""PATCH route for settling a bet"""
bet = await session.get(Bet, bet_id)
if not bet:
raise HTTPException(status_code=404, detail="Bet Not Found")
Expand All @@ -80,20 +87,20 @@ async def settle_bet(
session.add(bet)
await session.commit()
await session.refresh(bet)

return bet


@router.put("/{bet_id}", response_model=BetResponse)
async def update_bet(
bet_id: UUID, bet_data: BetUpdate, session: AsyncSession = Depends(get_db)
bet_id: UUID,
bet_data: BetUpdate,
session: AsyncSession = Depends(get_db),
):
"""PUT route for updating a bet (typically only for settling)"""
"""PUT route for updating a bet"""
db_bet = await session.get(Bet, bet_id)
if not db_bet:
raise HTTPException(status_code=404, detail="Bet Not Found")

# Update only provided fields
update_data = bet_data.model_dump(exclude_unset=True)

for key, value in update_data.items():
Expand All @@ -102,13 +109,12 @@ async def update_bet(
session.add(db_bet)
await session.commit()
await session.refresh(db_bet)

return db_bet


@router.delete("/{bet_id}")
async def delete_bet(bet_id: UUID, session: AsyncSession = Depends(get_db)):
"""DELETE route for deleting a bet (use with caution - breaks audit trail)"""
"""DELETE route for deleting a bet"""
bet = await session.get(Bet, bet_id)
if not bet:
raise HTTPException(status_code=404, detail="Bet Not Found")
Expand Down
7 changes: 2 additions & 5 deletions backend/app/api/routers/market_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
import requests
from typing import Optional

router = APIRouter()
# Add correct prefix + tag so API documentation is clean
router = APIRouter(prefix="/market-data", tags=["Market Data"])


@router.get("/prices")
Expand All @@ -15,13 +16,11 @@ async def get_market_prices(
):
"""GET route for live cryptocurrency prices from CoinGecko API"""
try:
# Clean up token list
token_list = [t.strip() for t in tokens.split(",") if t.strip()]

if not token_list:
raise HTTPException(status_code=400, detail="No tokens specified")

# Call CoinGecko API
url = "https://api.coingecko.com/api/v3/simple/price"
params = {
"ids": ",".join(token_list),
Expand All @@ -36,7 +35,6 @@ async def get_market_prices(

data = response.json()

# Transform response to be more frontend-friendly
result = {}
for token_id in token_list:
if token_id in data:
Expand Down Expand Up @@ -73,7 +71,6 @@ async def get_single_price(
):
"""GET route for a single token's current price"""
try:
# Call CoinGecko API
url = "https://api.coingecko.com/api/v3/simple/price"
params = {
"ids": token.lower(),
Expand Down
Loading
Loading