Tutorial PydanticAI March 4, 2026 · 8 min read

Building Type-Safe Crypto Agents with PydanticAI

PydanticAI, created by Samuel Colvin (author of Pydantic), brings the same rigorous type safety that Python developers love to the world of AI agents. When you combine PydanticAI's strict schema validation with Purple Flea's financial APIs, you get something powerful: crypto agents that are not just functional, but provably correct at the type level.

This guide walks through building a complete crypto agent with PydanticAI — one that can create wallets, check balances, open trading positions, and play casino games, all with full Pydantic schema validation on every input and output.

Why Type Safety Matters for Financial Agents

Financial operations are uniquely intolerant of errors. Sending 0.1 when you meant 0.01, or passing a Solana address to an Ethereum function, can result in real money loss. Type safety catches these bugs at development time, not at runtime when funds are already moving.

PydanticAI enforces types at the boundary between the LLM and your code. When GPT-4o or Claude calls a tool, PydanticAI validates that the arguments match the expected schema before your code even sees them. This eliminates an entire class of bugs that are common in loosely-typed agent frameworks.

"Type safety in financial agents isn't pedantry — it's the difference between a working treasury and a catastrophic loss."

Installation

# Install PydanticAI and the Purple Flea SDK
pip install pydantic-ai purpleflea-pydanticai

# Set your API key
export PURPLE_FLEA_API_KEY="pf_..."

Defining Typed Tool Schemas

Before building the agent, let's define our Pydantic models for tool inputs and outputs. These schemas are shared between the tool definitions and your business logic, ensuring consistency end-to-end.

from pydantic import BaseModel, Field
from typing import Literal

# Wallet models
class WalletResult(BaseModel):
    address: str = Field(description="The wallet's public address")
    chain: str = Field(description="The blockchain this address is for")
    balance_usd: float = Field(default=0.0)

class BalanceRequest(BaseModel):
    address: str
    chain: Literal["eth", "btc", "sol", "bnb", "matic", "trx"] = "eth"

# Trading models
class TradeRequest(BaseModel):
    market: str = Field(description="e.g. 'ETH-PERP', 'BTC-PERP'")
    size_usd: float = Field(gt=0, le=100_000)
    side: Literal["long", "short"]
    leverage: float = Field(default=1.0, ge=1.0, le=50.0)

class TradeResult(BaseModel):
    position_id: str
    market: str
    entry_price: float
    size_usd: float
    side: str
    leverage: float

# Casino models
class CoinFlipRequest(BaseModel):
    amount_usd: float = Field(gt=0, le=1000)
    choice: Literal["heads", "tails"]

class CoinFlipResult(BaseModel):
    outcome: Literal["heads", "tails"]
    won: bool
    payout_usd: float
    proof_hash: str  # Cryptographic proof of fairness

Building the Agent

Now we wire everything together with PydanticAI's Agent class and Tool wrapper. Notice how tool functions are typed with the Pydantic models we defined — PydanticAI automatically validates LLM outputs before they reach our functions.

import asyncio
import os
from pydantic_ai import Agent
from pydantic_ai.tools import Tool
from purpleflea import PurpleFleas

flea = PurpleFleas(api_key=os.environ["PURPLE_FLEA_API_KEY"])

# Define tools as typed async functions
async def create_wallet() -> WalletResult:
    """Create a new non-custodial BIP-39 crypto wallet."""
    raw = await flea.wallet.create()
    return WalletResult(**raw)

async def get_balance(request: BalanceRequest) -> WalletResult:
    """Get the balance of a crypto wallet on a specific chain."""
    raw = await flea.wallet.balance(request.address, request.chain)
    return WalletResult(**raw)

async def open_trade(request: TradeRequest) -> TradeResult:
    """Open a perpetual futures position on Hyperliquid."""
    raw = await flea.trading.open_position(
        market=request.market,
        size_usd=request.size_usd,
        side=request.side,
        leverage=request.leverage,
    )
    return TradeResult(**raw)

async def coin_flip(request: CoinFlipRequest) -> CoinFlipResult:
    """Flip a provably fair coin for a USDC wager."""
    raw = await flea.casino.coin_flip(
        amount=request.amount_usd,
        choice=request.choice,
    )
    return CoinFlipResult(**raw)

# Create the agent with typed tools
agent = Agent(
    model='openai:gpt-4o',
    tools=[
        Tool(create_wallet, takes_ctx=False),
        Tool(get_balance, takes_ctx=False),
        Tool(open_trade, takes_ctx=False),
        Tool(coin_flip, takes_ctx=False),
    ],
    system_prompt="""You are a crypto agent with financial capabilities.
    Always confirm with the user before opening trades or placing bets.
    Provide clear summaries of any financial operations you perform."""
)

Running the Agent

async def main():
    result = await agent.run(
        "Create a new wallet, then flip a coin for $1 USDC on heads. Show me the proof hash."
    )
    print(result.data)

asyncio.run(main())

The agent will call create_wallet(), receive a WalletResult, then call coin_flip(CoinFlipRequest(amount_usd=1.0, choice='heads')). PydanticAI validates both the LLM's function call arguments AND your return values.

Type-Safe Error Handling

PydanticAI's validation happens before your business logic runs. If the LLM hallucinates an invalid leverage value (like 200.0 when max is 50.0), Pydantic catches it at the boundary and returns a structured error to the model for self-correction — no money moves.

from pydantic import ValidationError

# This will fail validation before reaching your code
# leverage=200 violates le=50.0 constraint
try:
    bad_trade = TradeRequest(
        market="BTC-PERP",
        size_usd=100,
        side="long",
        leverage=200.0  # INVALID: max is 50
    )
except ValidationError as e:
    print(e.errors())
    # [{'type': 'less_than_equal', 'loc': ('leverage',), 'msg': 'Input should be <= 50'}]

Testing Your Agent

Because all inputs and outputs are typed Pydantic models, testing is straightforward. You can mock the Purple Flea client and verify that your agent handles edge cases correctly — without making real API calls or spending real money.

from unittest.mock import AsyncMock, patch

async def test_coin_flip_win():
    mock_result = CoinFlipResult(
        outcome="heads",
        won=True,
        payout_usd=2.0,
        proof_hash="0xabc123..."
    )
    with patch.object(flea.casino, 'coin_flip', AsyncMock(return_value=mock_result.dict())):
        result = await agent.run("Flip a coin for $1 on heads")
        assert "won" in result.data.lower()

Referral Commissions

Every Purple Flea API key carries an embedded referral code. When you build PydanticAI agents that use Purple Flea and share them with others, you earn:

Trading: 20% of fees · Casino: 10% of wagers · Wallets: 10% of swap fees · Domains: 15% of registration fees

Commissions accrue automatically and are withdrawable at any time via the Purple Flea dashboard.

Next Steps

You've built a type-safe crypto agent with PydanticAI and Purple Flea. The Pydantic schemas protect you from LLM hallucination bugs, and the full suite of financial tools means your agents can do real on-chain work.

Explore more: Purple Flea for PydanticAI · LangChain integration · Trading API reference · Wallet API reference