# SDK-003 — `HyperscaledClient` Core Class

**Status:** Complete
**Date:** 2026-03-05
**Branch:** `sdk-003/hyperscaled-client` (off `main`)
**Location:** `hyperscaled-sdk/`

---

## Summary

The main entry point for programmatic SDK use. Loads config from `~/.hyperscaled/config.toml`, manages a shared `httpx.AsyncClient` session with base URL, default headers, and timeout, and lazy-loads sub-clients on first access. Supports both async context manager and synchronous usage. Constructor overrides take precedence over config file, which takes precedence over environment variables.

---

## What Was Built

### Modified Files

| File | Change |
|------|--------|
| `hyperscaled/sdk/client.py` | Full `HyperscaledClient` implementation (replaced 26-line stub) |

### New Files

```
hyperscaled-sdk/
└── tests/
    └── test_client.py          # 28 tests
```

### SDK Interface

```python
from hyperscaled import HyperscaledClient

# Loads config from ~/.hyperscaled/config.toml, overridable via constructor
client = HyperscaledClient(
    hl_wallet="0x...",            # Optional — overrides config
    payout_wallet="0x...",        # Optional — overrides config
    base_url="https://...",       # Optional — overrides config
)

# Async context manager
async with HyperscaledClient() as client:
    miners = await client.miners.list_all()  # once SDK-005 is wired

# Sync usage (outside an event loop)
client = HyperscaledClient()
client.open_sync()
# ...
client.close_sync()

# Config and HTTP session are accessible
client.config            # → resolved Config instance
client.http              # → shared httpx.AsyncClient (lazy-created)
```

### Sub-Clients (Lazy-Loaded)

All 10 sub-clients use a descriptor pattern. Accessing an unimplemented sub-client raises `NotImplementedError` with the target sprint. Once a sub-client class is wired (e.g., by SDK-005), assigning it caches it on the instance.

| Property | Target | Status |
|----------|--------|--------|
| `client.miners` | SDK-005 | Stub — `NotImplementedError` |
| `client.register` | Sprint 05 | Stub — `NotImplementedError` |
| `client.trade` | Sprint 06 | Stub — `NotImplementedError` |
| `client.portfolio` | Sprint 06 | Stub — `NotImplementedError` |
| `client.account` | Sprint 06 | Stub — `NotImplementedError` |
| `client.payouts` | Sprint 06 | Stub — `NotImplementedError` |
| `client.kyc` | Sprint 06 | Stub — `NotImplementedError` |
| `client.rules` | Sprint 06 | Stub — `NotImplementedError` |
| `client.data` | Phase 2 | Stub — `NotImplementedError` |
| `client.backtest` | Phase 2 | Stub — `NotImplementedError` |

### Config Precedence (highest → lowest)

1. Constructor arguments (`hl_wallet`, `payout_wallet`, `base_url`)
2. Config file (`~/.hyperscaled/config.toml`)
3. Environment variables (`HYPERSCALED_HL_ADDRESS`, etc.)
4. Defaults

### HTTP Session

- Shared `httpx.AsyncClient` created lazily on first `.http` access
- Configured with `base_url` from config, `User-Agent: hyperscaled-sdk` header, 30s timeout
- Reused across all sub-client access
- Automatically recreated if accessed after close

### Sync/Async Support

- **Async:** `async with HyperscaledClient() as client:` — opens session on enter, closes on exit
- **Sync:** `open_sync()` / `close_sync()` — wraps async methods via `asyncio.run()`
- Clear `RuntimeError` raised if sync helpers are called from inside a running event loop (e.g., Jupyter) — callers should `await` directly instead

---

## Acceptance Criteria — All Met

| Criterion | Status |
|-----------|--------|
| `HyperscaledClient()` loads config and creates a usable instance | Pass |
| Accessing an unimplemented sub-client raises `NotImplementedError` with the target sprint | Pass |
| `async with` and sync usage both work | Pass |
| Shared httpx session is reused across sub-clients | Pass |
| Constructor overrides take precedence over config file, which takes precedence over env vars | Pass |

### Verification Results

```
ruff check .          → All checks passed!
mypy hyperscaled      → Success: no issues found in 26 source files
pytest -v             → 55 passed in 0.38s
```

### Test Coverage

| Test Group | Count | What's Covered |
|------------|-------|----------------|
| Construction & config precedence | 6 | Default construction, constructor overrides config, constructor overrides env, config file overrides env, payout wallet override, base URL override |
| HTTP session | 4 | Lazy creation, base URL wiring, default headers, reuse across accesses |
| Async context manager | 2 | Opens and closes session, returns client |
| Sync helpers | 3 | `open_sync`, `close_sync`, error on running event loop |
| Lazy sub-clients | 12 | All 10 stubs raise `NotImplementedError` with target, settable, cached after set |
| Session recreation | 1 | HTTP client recreated after close |

---

## What's Next

This unblocks **SDK-005** (entity miner discovery), which depends on both SDK-003 and SDK-004. SDK-004 (Pydantic models & exception hierarchy) can proceed immediately — it depends only on SDK-001.

```
SDK-001 ──┬── SDK-002 ✓ ── SDK-003 ✓ ──┐
          │                             ├── SDK-005
          └── SDK-004 ──────────────────┘
```
