This release is a pre-release and may not be stable for production use.
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+.
Supported surface
| Capability | Supported |
|---|---|
| Public market data (spot config, trades, candles) | Yes |
| Order book snapshot + realtime | Yes |
| Market overview (list + subscribe) | Yes |
| Order book heatmap | Yes |
| API-key (Ed25519 signature) auth | Yes |
| Wallet / browser login | No |
| Session MFA enrollment and challenges | No |
| Profile (identity subscribe) | Yes |
| API keys (list/get/subscribe/generate_keypair) | Yes |
| Subaccounts (reads/subscribe) | Yes |
| Address book (reads/subscribe) | Yes |
| Policies (realtime subscribe) | Yes |
| Guard signer | Yes |
| Balances, holds, equity history | Yes |
| Orders (create, cancel, modify, batch, cancel-all) | Yes |
| User trades | Yes |
| Triggers | Yes |
| Internal transfers | Yes |
| Transfer history | Yes |
| Deposit addresses | Yes |
| Trading / funding withdraws | Yes |
| Zipper deposit-withdraw config | Yes |
| Chain analytics | Yes |
| Lifecycle flows | Yes |
| Polychart / layout / whiteboard | Yes |
| Realtime account and market streams | Yes |
| Reference catalogs + wait-for-ready | Yes |
| Qty / price decimal + scaled-int inputs | Yes |
| Social verification | Yes |
| Account resolve / lookup | No |
Rows marked No are intentional for API-key SDKs (use the TypeScript browser client for wallet login and session MFA).
Full cross-language comparison: SDK capability matrix.
Install
PyPI: https://pypi.org/project/polyester-sdk/
pip install polyester-sdk
Realtime (Centrifugo) and on-chain Funding helpers are included by default.
For development from a git checkout:
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]"
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.
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:
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:
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
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 or Decimal for human-facing qty / price inputs.
Do not pass floats. ticks on Price means Polyester protocol price units
(fixed 1e6), not market tick-size alignment (server validates tick size).
For bots (scaled integers)
Stay in integer space — no string round-trip:
from polyester import Price, Quantity
result = await client.orders.create(
symbol="BNB-USDT",
side="buy",
order_type="limit",
tif="gtc",
qty=Quantity.from_scaled(1_000_000, scale=8), # already wire units
price=Price.from_ticks(100_000_000), # 100.000000 at 1e6
post_only=True,
)
# Reads expose the same types: order.price.ticks, order.orig_qty.scaled
Compatible values from fills/books can be passed back into writes when the instrument/domain matches.
Your API key needs a policy that allows trading. Spot orders spend trading balance (see below).
Triggers
triggers.list(status=...) filters by lifecycle status. Valid values:
created, armed, running, completed, cancelled, failed, paused
Unknown values raise ValueError (they do not silently return an empty list).
Response status uses the same labels (British spelling cancelled).
orders.get(..., include_attached_risk=True) returns policy data on
order.attached_risk (take-profit / stop-loss / trailing-stop). Order also
exposes post_only.
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). Either encode calldata or submit a UserOp: pass an owner EOA private key toPolyesterSmartAccount(SDK derives the Polyester Safe — no UI-exported owner key). - Funding → external: on-chain
FundingAccount.withdrawToChain(samepolyester.chain). - Funding → another user's funding wallet: on-chain
FundingAccount.UAssetTransfervia wallet/smart-account signing in the Polyester app (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(...).
from polyester.chain import (
POLYESTER_TESTNET_ENVIRONMENT,
PolyesterSmartAccount,
encode_trading_gateway_deposit,
encode_funding_withdraw_to_chain,
encode_withdraw_destination,
quote_zipper_fee,
)
account = PolyesterSmartAccount(owner_private_key="0x…") # caller-supplied EOA
# Funding → Trading
deposit = encode_trading_gateway_deposit(
trading_gateway=POLYESTER_TESTNET_ENVIRONMENT.contracts.trading_gateway_address,
u_asset_id="0x…",
quantity_scaled=10**18, # 1 USDT at 18 decimals
)
account.send_calls([deposit])
# Funding → external (quote Zipper fee first)
fee = quote_zipper_fee(
chain_id=6, # BSC testnet Zipper id
z_token="0x…",
zipper_endpoint=POLYESTER_TESTNET_ENVIRONMENT.contracts.zipper_endpoint_address,
)
withdraw = encode_funding_withdraw_to_chain(
funding_account=POLYESTER_TESTNET_ENVIRONMENT.contracts.funding_account_address,
chain_id=6,
z_token="0x…",
withdraw_destination=encode_withdraw_destination(address="0x…", is_case_sensitive=False),
z_amount=5 * 10**18,
max_fee=fee.fee + fee.fee // 10,
)
account.send_calls([withdraw])
Whitelist toggles / destination entries / GuardRegistry signer setup are also encoded under
polyester.chain (encode_add_allowed_external_destinations, …).
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):
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.
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")
async with subscription:
async for trade in subscription:
print(trade.price.ticks if trade.price else None, trade.qty.scaled if trade.qty else None)
break
Merged market overview stream (snapshot + live updates):
sub = await client.market_overview.create_subscription()
async with sub:
async for markets in sub:
print(len(markets), "rows")
break
Realtime delivery contract
- Subscription queues are bounded. If the consumer falls behind, the SDK raises
PolyesterRealtimeOverflowErrorand faults the subscription — it does not silently drop updates. - Orderbook sequence gaps trigger a REST snapshot refresh. Use
on_sequence_gap/on_reconnect/on_snapshot_refreshonorderbook.create_subscription(...)for recovery observability. - Managed snapshot-then-stream subscriptions disable transport auto-reconnect so they can rebuild REST state between reconnect attempts.
Sync client
The sync Polyester client exposes the same service tree and constructor
parameters:
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. Private API-key policy snapshots use
client.policies.subscribe_api_policies_sync(...) (async:
await client.policies.subscribe_api_policies(...)).
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.
cp .env.example .env
# fill in POLYESTER_API_KEY_ID, POLYESTER_API_PRIVATE_KEY, POLYESTER_ACCOUNT_ID
pip install -e ".[dev]"
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.
CI requires every public Connect RPC in gen to be wrapped or listed in
sdk-coverage.toml. Contributors: python scripts/check_sdk_coverage.py.
CI refreshes sdk-capabilities.json and the README capability table on the
same branch when they drift (same-repo PRs / pushes to main).
Pre-release checklist (realtime changes):
cd polyester-sdk-python
python -m venv /tmp/polyester-pypi-test && source /tmp/polyester-pypi-test/bin/activate
pip install -e ".[dev]"
python -m pytest tests/unit -q
./scripts/smoke_realtime.sh
cd ../polyester-examples-python
pip install -e "../polyester-sdk-python"
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.
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.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file polyester_sdk-0.1.0a15.tar.gz.
File metadata
- Download URL: polyester_sdk-0.1.0a15.tar.gz
- Upload date:
- Size: 311.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/6.2.0 CPython/3.11.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6cee183bc53a4f875769e99abb767e05cac3a397743df75bb0a59b433fcd0eb6
|
|
| MD5 |
29c12267b0afe6ddf17c80dfe8466fbc
|
|
| BLAKE2b-256 |
d2f7930a48be8f76b1f75f5401397299ecae5f7589b646fb3378832eb61b51e6
|
File details
Details for the file polyester_sdk-0.1.0a15-py3-none-any.whl.
File metadata
- Download URL: polyester_sdk-0.1.0a15-py3-none-any.whl
- Upload date:
- Size: 419.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/6.2.0 CPython/3.11.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
90c622af9a40ee568cbba1d57112ba7b11e4104db30658b0ca65afb784c500b7
|
|
| MD5 |
9557ad2a050b7371a5eed2ad6b85a474
|
|
| BLAKE2b-256 |
a40651d44494b238e106d4d4524a7180b70b5c4ebde5b8648cdf01621e71fdb0
|