Skip to content

Fastapi Contract Functionality #25

@SumanthPal

Description

@SumanthPal

Ok good job ygs with the database setup! Now we gotta add backend contract interaction so the FastAPI server can directly interact with the smart contract for settlement and admin operations.

What we need to build:

New Service Layer (backend/app/services/contract_service.py):

Create a service that wraps web3 interactions with the AgonusBetting contract:

Core functions:

  1. create_tournament_on_contract(agent_count: int)

    • Calls contract's createTournament(agentCount)
    • Returns contract tournament ID
    • Uses admin wallet
  2. close_betting(contract_tournament_id: int)

    • Calls contract's closeBetting(tournamentId)
    • Prevents new bets
    • Uses admin wallet
  3. settle_tournament(contract_tournament_id: int, winning_agent_id: int)

    • Calls contract's settleTournament(tournamentId, winningAgentId)
    • Declares winner, distributes funds
    • Uses admin wallet
  4. cancel_tournament(contract_tournament_id: int)

    • Calls contract's cancelTournament(tournamentId)
    • Enables refunds
    • Uses admin wallet

Read-only functions:
5. get_tournament_state(contract_tournament_id: int)

  • Reads tournament struct from contract
  • Returns: isActive, isSettled, totalPool, winningAgentId, agentCount
  1. get_agent_pool(contract_tournament_id: int, agent_id: int)

    • Reads agentPools[tournamentId][agentId]
    • Returns total bet on that agent
  2. get_agent_odds(contract_tournament_id: int, agent_id: int)

    • Calls contract's getAgentOdds(tournamentId, agentId)
    • Returns odds in basis points

Configuration (backend/app/core/config.py):

Add contract settings: (ASK ME FOR THESE)

CONTRACT_ADDRESS: str = "0x..."  # Base Sepolia address
ADMIN_PRIVATE_KEY: str = "..."  # Admin wallet key (from env)
RPC_URL: str = "https://sepolia.base.org"  # Base Sepolia RPC
CONTRACT_ABI: str = "..."  # Load from file or embed

Security: Store private key in environment variables, NEVER commit it.

Web3 Setup:

Use web3.py for Python contract interactions:

from web3 import Web3
from eth_account import Account

# Initialize
w3 = Web3(Web3.HTTPProvider(settings.RPC_URL))
contract = w3.eth.contract(
    address=settings.CONTRACT_ADDRESS,
    abi=settings.CONTRACT_ABI
)
admin_account = Account.from_key(settings.ADMIN_PRIVATE_KEY)

Backend Routes (backend/app/api/routes/tournaments.py):

Modified tournament creation flow:

@router.post("/", response_model=TournamentResponse)
async def create_tournament(
    tournament_data: TournamentCreate,
    session: AsyncSession = Depends(get_db),
    admin: dict = Depends(require_admin),
):
    """Create tournament in DB AND on contract"""
    
    # 1. Validate agents exist
    agents = await validate_agents(session, tournament_data.agent_ids)
    
    # 2. Create tournament in DB
    tournament = Tournament(...)
    tournament.agent_contract_mapping = {
        str(agent.id): idx + 1 
        for idx, agent in enumerate(agents)
    }
    session.add(tournament)
    await session.commit()
    
    # 3. Create on contract
    try:
        contract_id = await contract_service.create_tournament_on_contract(
            agent_count=len(agents)
        )
        tournament.contract_tournament_id = contract_id
        tournament.status = "ACTIVE"
        await session.commit()
    except Exception as e:
        # Rollback if contract fails
        tournament.status = "FAILED"
        await session.commit()
        raise HTTPException(500, f"Contract creation failed: {e}")
    
    return tournament

New settlement endpoint:

@router.post("/{tournament_id}/settle")
async def settle_tournament(
    tournament_id: UUID,
    session: AsyncSession = Depends(get_db),
    admin: dict = Depends(require_admin),
):
    """Close betting, settle on contract, sync to DB"""
    
    tournament = await session.get(Tournament, tournament_id)
    if not tournament or not tournament.contract_tournament_id:
        raise HTTPException(404, "Tournament not found")
    
    if tournament.status != "ACTIVE":
        raise HTTPException(400, "Tournament not active")
    
    # 1. Close betting on contract
    await contract_service.close_betting(tournament.contract_tournament_id)
    
    # 2. Determine winner (from your game logic/AI battles)
    winner_uuid = await determine_winner(tournament_id)  # Your logic
    
    # 3. Get winner's contract ID from mapping
    winner_contract_id = tournament.agent_contract_mapping[str(winner_uuid)]
    
    # 4. Settle on contract
    try:
        await contract_service.settle_tournament(
            tournament.contract_tournament_id,
            winner_contract_id
        )
        
        # 5. Update DB
        tournament.winner_agent_id = winner_uuid
        tournament.status = "COMPLETED"
        await session.commit()
        
        return tournament
    except Exception as e:
        raise HTTPException(500, f"Settlement failed: {e}")

Cancel endpoint:

@router.post("/{tournament_id}/cancel")
async def cancel_tournament(
    tournament_id: UUID,
    session: AsyncSession = Depends(get_db),
    admin: dict = Depends(require_admin),
):
    """Cancel tournament on contract (enables refunds)"""
    
    tournament = await session.get(Tournament, tournament_id)
    
    await contract_service.cancel_tournament(tournament.contract_tournament_id)
    
    tournament.status = "CANCELLED"
    await session.commit()
    
    return tournament

Transaction Handling:

Important: Web3 transactions need proper handling:

  • Gas estimation
  • Nonce management
  • Transaction confirmation waiting
  • Error handling for reverts
  • Retry logic for network issues

Dependencies to install:

pip install web3 eth-account

Error Handling:

Wrap contract calls in try/except to catch:

  • Gas estimation failures
  • Transaction reverts
  • Network timeouts
  • Invalid parameters

Log all transactions with:

  • Transaction hash
  • Block number
  • Gas used
  • Timestamp

Testing:

Use Base Sepolia testnet first:

  • Get testnet ETH from faucet
  • Test full flow: create → bet → settle
  • Verify events are emitted
  • Check DB sync is correct

Security Considerations:

  • Admin private key must be in .env, never committed
  • Use read-only RPC for view functions (no key needed)
  • Rate limit admin endpoints
  • Add transaction monitoring/alerts
  • Consider using a hardware wallet or signing service for production

This gives your backend full control over the contract lifecycle while keeping DB as the source of truth for display data. The backend becomes the bridge between contract (money) and database (metadata).

Metadata

Metadata

Labels

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions