Metadata-Version: 2.4
Name: polyester-sdk
Version: 0.1.0a3
Summary: Official Python SDK for Polyester APIs.
Project-URL: Homepage, https://github.com/Fabric-Labs/polyester-sdk-python
Project-URL: Documentation, https://polyester.ai/docs
Project-URL: Repository, https://github.com/Fabric-Labs/polyester-sdk-python
Project-URL: Issues, https://github.com/Fabric-Labs/polyester-sdk-python/issues
Author: Fabric Blockchain Labs Inc.
License: Proprietary
License-File: LICENSE
Keywords: connectrpc,polyester,protobuf,sdk,trading
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: Other/Proprietary License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Typing :: Typed
Requires-Python: >=3.11
Requires-Dist: base58>=2.1.1
Requires-Dist: connectrpc<0.11,>=0.9.0
Requires-Dist: cryptography>=42
Requires-Dist: httpx>=0.27
Requires-Dist: msgspec>=0.18
Requires-Dist: protobuf>=7.35.0
Provides-Extra: dev
Requires-Dist: build>=1.2; extra == 'dev'
Requires-Dist: mypy>=1.13; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
Requires-Dist: pytest-cov>=5; extra == 'dev'
Requires-Dist: pytest>=8; extra == 'dev'
Requires-Dist: python-dotenv>=1.0; extra == 'dev'
Requires-Dist: ruff>=0.8; extra == 'dev'
Requires-Dist: twine>=5; extra == 'dev'
Provides-Extra: realtime
Requires-Dist: websockets>=15; extra == 'realtime'
Description-Content-Type: text/markdown

# Polyester Python SDK

Official Python SDK for Polyester APIs, built for trading bots, backend jobs,
research notebooks, and automation.

**Status:** Alpha. Proprietary license (not open source). API-key only —
no browser login or JWT flows.

Requires **Python 3.11+**.

## Install

PyPI: https://pypi.org/project/polyester-sdk/

```bash
pip install "polyester-sdk[realtime]"
```

The `[realtime]` extra installs `websockets` for live market data and private
streams. Omit it if you only need REST/Connect RPC calls.

For development from a git checkout:

```bash
git clone https://github.com/Fabric-Labs/polyester-sdk-python.git
cd polyester-sdk-python
python3.11 -m venv .venv
source .venv/bin/activate
pip install -e ".[dev,realtime]"
```

## Quickstart

Create an API key in the Polyester app (**API** in the sidebar). Copy the key id
and private key when shown — the private key is only displayed once.

```python
import asyncio

from polyester import AsyncPolyester

async def main() -> None:
    async with AsyncPolyester(
        api_key_id="ak_...",           # from API key creation
        api_private_key="...",       # 64-char hex secret from API key creation
        default_account_id="...",    # Profile → Account ID (see below)
    ) as client:
        overview = await client.market_overview.list(limit=5)
        for market in overview.markets:
            print(market.symbol, market.last_price_ticks)

        open_orders = await client.orders.list_open()
        print(f"{len(open_orders.orders)} open orders")

asyncio.run(main())
```

## Credentials

| Value | Where to find it | Constructor parameter |
| --- | --- | --- |
| API key id | **API** → create or view key | `api_key_id` |
| API private key | Shown once when the key is created | `api_private_key` |
| Account ID | **Profile** → **Account ID** (e.g. `RLxqJGUDg92`) | `default_account_id` |

Pass all credentials as **constructor parameters**. The SDK does not read
environment variables unless you pass them in yourself (or use `from_env()` in
scripts — see below).

`api_private_key` accepts the 64-character hex Ed25519 secret from key creation,
or raw 32-byte key material.

`default_account_id` is the **Account ID** string from your Profile page. Use the
value exactly as shown in the app. Do not use an internal numeric id.

`default_account_id` is optional for public market-data calls. It is required for
account-scoped operations such as private realtime channels, bucket transfers, and
some ledger writes.

## Authentication patterns

**Recommended — explicit parameters:**

```python
from polyester import AsyncPolyester

client = AsyncPolyester(
    api_key_id="ak_...",
    api_private_key="...",
    default_account_id="RLxqJGUDg92",
)
```

**If your deployment stores secrets in environment variables**, read them in your
application and pass them to the constructor:

```python
import os

from polyester import AsyncPolyester

client = AsyncPolyester(
    api_key_id=os.environ["POLYESTER_API_KEY_ID"],
    api_private_key=os.environ["POLYESTER_API_PRIVATE_KEY"],
    default_account_id=os.environ["POLYESTER_ACCOUNT_ID"],
)
```

The plain `AsyncPolyester(...)` / `Polyester(...)` constructors never implicitly
read `os.environ`.

**Scripts and local tests only** — `AsyncPolyester.from_env()` and
`Polyester.from_env()` load `POLYESTER_API_KEY_ID`, `POLYESTER_API_PRIVATE_KEY`,
and `POLYESTER_ACCOUNT_ID` from the process environment. This is a convenience
helper, not the primary integration pattern.

## Create and cancel orders

```python
from polyester import AsyncPolyester

async with AsyncPolyester(
    api_key_id="ak_...",
    api_private_key="...",
    default_account_id="RLxqJGUDg92",
    default_sub_account_id="",  # main account; omit subaccount scoping
) as client:
    result = await client.orders.create(
        symbol="BNB-USDT",
        side="buy",
        order_type="limit",
        tif="gtc",
        qty="0.01",
        price="100",
        post_only=True,
        client_order_id="my-bot-001",
    )
    print(result.status, result.order_id)

    await client.orders.cancel(client_order_id="my-bot-001")
```

Use **decimal strings** for `qty` and `price`. Do not pass floats.

Your API key needs a policy that allows trading. Spot orders spend **trading**
balance (see below).

## Balances: funding vs trading

Ledger balances have separate **funding** and **trading** buckets per asset.

- Deposits land in **funding**.
- Spot orders spend **trading** balance.
- Move funds funding → trading in the Polyester UI (**Funding → Unified Trading**)
  or on-chain via the funding wallet.

SDK notes:

- **Funding → trading:** on-chain `TradingGateway.deposit` (not an API-key RPC).
- **Trading → funding:** `client.trading_withdraws.create_to_funding(...)` with a
  signed intent payload.
- **Trading → trading (another account):** `client.internal_transfers.create(...)`
  or `client.ledger_write.transfer_trading_to_trading(...)`.

Pass `default_account_id` (your Profile **Account ID**) on the client for bucket
transfers and other account-scoped ledger operations.

Format u128 wire amounts with the public helper (18-decimal scale):

```python
from polyester import format_ledger_u128

print(format_ledger_u128(balance.funding), format_ledger_u128(balance.trading))
```

## Public market data

Public endpoints do not require an API key. Authenticated endpoints use the
credentials above.

```python
candles = await client.market_data.get_candles(symbol="BTC-USDT", timeframe="1m", limit=50)
current = await client.market_data.get_current_candle(symbol="BTC-USDT", timeframe="1m")
trades = await client.market_data.get_trades(symbol="BTC-USDT", limit=20)

subscription = await client.market_data.subscribe_trades(symbol="BNB-USDT")
try:
    async for trade in subscription:
        print(trade.price_ticks, trade.qty_scaled)
        break
finally:
    await subscription.aclose()
```

Merged market overview stream (snapshot + live updates):

```python
sub = await client.market_overview.create_subscription()
async for markets in sub:
    print(len(markets), "rows")
    break
await sub.aclose()
```

## Sync client

The sync `Polyester` client exposes the same service tree and constructor
parameters:

```python
from polyester import Polyester

with Polyester(
    api_key_id="ak_...",
    api_private_key="...",
    default_account_id="RLxqJGUDg92",
) as client:
    balances = client.balances.list()
```

Realtime subscriptions are available via `subscribe_sync` helpers on the sync
client.

## Testing (contributors)

**CI (no network):** `python -m pytest tests/unit -q`

**Live devnet tests** use a local `.env` file in the test harness only. Fixtures
load values from env and pass them as explicit constructor parameters — the same
pattern application code should use.

```bash
cp .env.example .env
# fill in POLYESTER_API_KEY_ID, POLYESTER_API_PRIVATE_KEY, POLYESTER_ACCOUNT_ID

pip install -e ".[dev,realtime]"
python -m pytest tests/unit -q
./scripts/test_all.sh   # optional: unit + live tiers
./scripts/smoke_realtime.sh   # realtime unit + live heartbeat before release
```

Use `python -m pytest` (not bare `pytest`) so tests run in the same venv as `pip install`.

**Pre-release checklist (realtime changes):**

```bash
cd polyester-sdk-python
python -m venv /tmp/polyester-pypi-test && source /tmp/polyester-pypi-test/bin/activate
pip install -e ".[dev,realtime]"
python -m pytest tests/unit -q
./scripts/smoke_realtime.sh

cd ../polyester-examples-python
pip install -e "../polyester-sdk-python[realtime]"
python -m pytest -q
python3 examples/04_public_realtime_trades.py
python3 examples/05_public_orderbook_stream.py
```

Then bump the version, update `CHANGELOG.md`, build, and publish to PyPI. Install from the new wheel (not editable) and rerun `smoke_realtime.sh` once to confirm the published artifact.

## Changelog

See [CHANGELOG.md](CHANGELOG.md).

## Transport

Connect RPC over HTTP via generated clients in `src/polyester/gen/`. Wire format
defaults to **binary protobuf**; pass `wire_format="json"` for debugging.

Some RPCs may return HTTP 404 on devnet. The SDK raises `PolyesterRouteNotFoundError`
with a clearer message than `[unimplemented]: Not Found`.
