Skip to content

Latest commit

 

History

History
321 lines (242 loc) · 8.52 KB

File metadata and controls

321 lines (242 loc) · 8.52 KB

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Project Overview

Privy Python SDK (v0.2.0) - An auto-generated OpenAPI client for the Privy API, a Web3 identity and wallet platform. The SDK is generated by Stainless with custom extensions in lib/ for enhanced cryptographic functionality.

Architecture

Layered Client Design

PrivyAPI / AsyncPrivyAPI (Client Layer)
  ↓
Resources: users, wallets, policies, transactions, fiat (Resource Layer)
  ↓
SyncAPIClient / AsyncAPIClient (HTTP Client Layer - httpx)
  ↓
Pydantic Models & Type System

Key Directories

  • lib/ - Custom extensions (preserved during code generation)

    • hpke.py - HPKE encryption/decryption (P-256 + ChaCha20-Poly1305)
    • authorization_signatures.py - ECDSA request signing
    • users.py - Extended Users resource with create_with_jwt_auth()
    • wallets.py - Extended Wallets resource with generate_user_signer()
    • http_client.py - Custom HTTP client configuration
  • resources/ - Auto-generated API resources

    • users.py - User CRUD operations
    • wallets/ - Wallet operations (nested: balance, transactions)
    • policies.py, key_quorums.py, transactions.py
    • fiat/ - Fiat onramp/offramp operations
  • types/ - Pydantic type definitions (~34 files)

    • Pattern: *_params.py for request parameters, *_response.py for responses
    • Heavy use of NotGiven for truly optional parameters
  • Core modules:

    • _client.py - Main PrivyAPI and AsyncPrivyAPI clients
    • _base_client.py - HTTP client abstraction with retry/streaming
    • _models.py - Pydantic BaseModel extensions
    • _response.py - Response parsing (APIResponse, AsyncAPIResponse)
    • _exceptions.py - Exception hierarchy (PrivyAPIError → APIStatusError → specific errors)

Development Patterns

Code Generation

This SDK is auto-generated from OpenAPI spec by Stainless.

IMPORTANT:

  • Core API resources regenerate on spec updates - do NOT manually modify files with "File generated from our OpenAPI spec by Stainless" header
  • Custom code belongs in lib/ directory (preserved during regeneration)
  • Extensions use inheritance pattern: class UsersResource(BaseUsersResource)

Dual Sync/Async Pattern

All resources have both synchronous and asynchronous variants:

# Sync
class UsersResource(SyncAPIResource):
    def create(...) -> User: ...

# Async
class AsyncUsersResource(AsyncAPIResource):
    async def create(...) -> User: ...

When adding custom methods, always implement both variants.

Response Wrappers

Resources support response wrapper patterns:

# Standard usage
user = client.users.get("user_id")

# Get full HTTP response
response = client.with_raw_response.users.get("user_id")
# response.http_response, response.parsed

# Stream large responses
stream = client.with_streaming_response.users.list()

Type Safety

  • Use NotGiven for truly optional parameters (vs None for nullable fields)
  • All request params use TypedDict definitions
  • Pydantic v1/v2 compatible via _compat.py
  • Always use types from typing_extensions (Literal, TypedDict, etc.)

Authentication

Client initialization:

from privy import PrivyAPI, AsyncPrivyAPI

# Production (default)
client = PrivyAPI(
    app_id="your_app_id",
    app_secret="your_app_secret"
)

# Staging
client = PrivyAPI(
    app_id="...",
    app_secret="...",
    base_url=PrivyAPI.ENVIRONMENTS["staging"]
)

# With authorization key (for wallet operations)
client = PrivyAPI(
    app_id="...",
    authorization_key="auth_key_from_jwt_flow"
)

Cryptography Implementation

HPKE (lib/hpke.py)

Hybrid Public Key Encryption using P-256 + ChaCha20-Poly1305:

from privy.lib.hpke import generate_keypair, seal, open

# Generate keypair
keypair = generate_keypair()
# Returns: {"public_key": "base64...", "private_key": "base64..."}

# Encrypt
encrypted = seal(public_key=recipient_public_key, plaintext=b"message")
# Returns: {"ciphertext": "base64...", "encapsulated_key": "base64..."}

# Decrypt
decrypted = open(
    private_key=private_key,
    encapsulated_key=enc_key,
    ciphertext=ciphertext
)
# Returns: {"message": "decrypted string"}

Key formats: All keys are base64-encoded DER format (uncompressed P-256 points)

ECDSA Signing (lib/authorization_signatures.py)

Request signing for API authentication:

from privy.lib.authorization_signatures import get_authorization_signature

signature = get_authorization_signature(
    signing_key="base64_encoded_key",
    request_method="POST",
    request_path="/v1/users",
    request_body={"key": "value"}
)

Uses ECDSA P-256 + SHA-256 with canonical JSON serialization.

Custom Extensions

users.create_with_jwt_auth()

Simplified wallet creation with JWT authentication:

# lib/users.py:22
response = client.users.create_with_jwt_auth(
    jwt_subject_id="user_jwt_subject",
    wallets=[{"chain_type": "ethereum"}],
    additional_linked_accounts=[{"type": "email", "address": "user@example.com"}]
)
# Returns: WalletCreateWalletsWithRecoveryResponse

Automatically:

  1. Sets up primary signer with JWT subject ID
  2. Creates recovery user with custom JWT account + additional accounts
  3. Creates specified wallet(s)

wallets.generate_user_signer()

All-in-one authentication with automatic key decryption:

# lib/wallets.py:33
response = client.wallets.generate_user_signer(user_jwt="jwt_token")

# Access decrypted authorization key
auth_key = response.decrypted_authorization_key
expires_at = response.expires_at
wallets = response.wallets

Automatically:

  1. Generates ephemeral keypair
  2. Authenticates with JWT
  3. Decrypts authorization key via HPKE
  4. Returns ready-to-use authorization key

Common Usage Patterns

Create User with Linked Accounts

# Basic user creation (generated resource)
user = client.users.create(
    linked_accounts=[
        {"type": "custom_auth", "custom_user_id": "user_123"}
    ]
)

# With JWT auth and wallet (custom extension)
response = client.users.create_with_jwt_auth(
    jwt_subject_id="jwt_subject_123",
    wallets=[{"chain_type": "ethereum"}]
)

Wallet Operations

# List user's wallets
wallets = client.wallets.list(user_id="did:privy:...")

# Get wallet balance
balance = client.wallets.balance.get(
    wallet_address="0x...",
    chain_type="ethereum"
)

# Create transaction (requires authorization key)
tx = client.wallets.transactions.create(
    wallet_id="wallet_id",
    chain_type="ethereum",
    transaction={...}
)

Error Handling

from privy import (
    AuthenticationError,
    NotFoundError,
    RateLimitError,
    APIStatusError
)

try:
    user = client.users.get("user_id")
except NotFoundError:
    # Handle 404
    pass
except AuthenticationError:
    # Handle 401
    pass
except RateLimitError:
    # Handle 429
    pass
except APIStatusError as e:
    # Generic HTTP error
    print(f"Status: {e.status_code}, Body: {e.response.text}")

Dependencies

Core

  • httpx (v0.23+) - Async HTTP client
  • pydantic (v1/v2) - Data validation and serialization
  • typing-extensions - Advanced type hints

Cryptography

  • pyhpke - HPKE implementation (P-256 + ChaCha20-Poly1305)
  • cryptography - ECDSA signing, key serialization

Utilities

  • anyio - Async compatibility
  • distro - OS detection

File References

When referencing code, use file:line format:

  • Main client: _client.py:1-100
  • Custom users extension: lib/users.py:22 (create_with_jwt_auth)
  • Custom wallets extension: lib/wallets.py:33 (generate_user_signer)
  • HPKE encryption: lib/hpke.py:1-124
  • Base HTTP client: _base_client.py:1-1500

Testing & Quality

No test configuration found in repository yet. When adding tests:

  • Use pytest (standard for Python API clients)
  • Test both sync and async variants
  • Mock httpx requests for unit tests
  • Integration tests should use staging environment
  • Test cryptography functions with known test vectors

Important Notes

  1. Never modify generated files - Only extend via lib/ inheritance
  2. Always provide sync + async variants for new methods
  3. Use NotGiven, not None for optional request parameters
  4. All keys are base64-encoded DER format (not PEM)
  5. HPKE uses P-256 curve (not secp256k1)
  6. Follow existing patterns in lib/ for custom extensions
  7. Pydantic v1/v2 compatible - use _compat.py shims
  8. No setup.py/pyproject.toml - configuration likely in parent directory or not committed yet