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 (0.1.0a50). 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/local keypair generation) | Yes |
| Subaccounts (list/get/members/invites/activity/subscribe) | Yes |
| Address book (list/view/create/update/subscribe) | Yes |
| Policies (realtime subscribe) | Yes |
| Guard signer | Yes |
| VIP tiers + status | Yes |
| Spot fee rates | Yes |
| Trading rate limits | 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.
Rows marked Yes mean that an SDK wrapper exists; deployment authorization still applies. In particular, whiteboard/social-verification and some layout/polychart routes may require a JWT session or may not be mounted. Private streams require an Account ID and the corresponding API-key permission. A successful subscribe call means the token exchange and realtime handshake completed. Treat a structured permission denial as non-transient and update the API-key policy before retrying.
Install
PyPI: https://pypi.org/project/polyester-sdk/
pip install "polyester-sdk==0.1.0a50"
Realtime (Centrifugo) and on-chain Funding helpers are included by default.
The PyPI sdist/wheel do not include tests/. Full pytest (unit, hardening,
live) requires a git checkout of this repository.
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]"
Examples
Runnable cookbook examples live in the sibling repo
polyester-examples-python
(REST market data, realtime streams, decimal + scaled-int order paths, and bots).
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. Open the key's Permissions, enable Spot trading, select the markets it may trade, and set a maximum order size appropriate for the strategy. For a subaccount-scoped key, attach an API-key policy that grants ledger reads for balances and private balance streams, plus Spot trading for order mutations. This is separate from the subaccount policy: authorization is the intersection of both policies, so configuring only the subaccount policy is insufficient.
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:
last = market.last_price.ticks if market.last_price is not None else None
index = market.index_price.ticks if market.index_price is not None else None
print(market.symbol, last, index)
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 | 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.
An account username is optional; Account-ID-based authentication and private
channel scoping are valid without one.
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.
Automatic request signing gives concurrent identical calls distinct authentication tuples.
Timestamps can lead the local clock by at most five seconds. The async client applies bounded,
cooperative backpressure without blocking the event loop. The low-level synchronous
polyester.auth.sign_request helper instead raises PolyesterRateLimitError immediately when
capacity is exhausted; honor retry_after. Neither path reuses a signature or drifts outside the
API's 10-second freshness window.
Connect resource_exhausted and HTTP 429 responses raise PolyesterRateLimitError. When the
server attaches polyester.ratelimit.v1.RateLimitDetail (top-level Connect detail or nested under
orders.v1.ErrorDetail.rate_limit), inspect error.detail for policy_class, scope,
operation_id, and presence-aware quota fields. retry_after prefers detail.retry_after_ms,
then Retry-After / Retry-After-Ms / Grpc-Retry-Pushback-Ms headers on HTTP paths. Preview
and batch rejections expose the same payload on OrderErrorDetail.rate_limit / batch item
rate_limit. That error payload is distinct from client.rate_limits
(ratelimit.v1.RateLimitService), which returns the public trading quota catalog and
authenticated account / API-key limits.
Authentication patterns
Recommended: explicit parameters
from polyester import AsyncPolyester
client = AsyncPolyester(
api_key_id="ak_...",
api_private_key="...",
default_account_id="YOUR_ACCOUNT_ID",
)
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,
POLYESTER_ACCOUNT_ID, and optional POLYESTER_API_URL / POLYESTER_WS_URL
from the process environment. This is a convenience helper, not the primary
integration pattern.
Create and cancel orders
from polyester import AsyncPolyester
from polyester.models import ClientOrderId
async with AsyncPolyester(
api_key_id="ak_...",
api_private_key="...",
default_account_id="YOUR_ACCOUNT_ID",
default_sub_account_id="", # main account; omit subaccount scoping
) as client:
result = await client.orders.create(
symbol="BTC-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(key=ClientOrderId("my-bot-001"))
client_order_id is optional (matches the API). Omit it for one-shot
creates. Set a stable non-empty value when you may retry after an ambiguous
transport/server failure, and reuse that same id on retry / reconciliation -
without it you cannot safely tell whether the first attempt admitted the order.
Client order ids accept 1 to 36 ASCII letters, digits, ., _, :, /, and
-. Batch create, cancel, and replace accept at most 20 items. batch_create
always sends request_id (caller value or a generated batch-create-* key);
that batch-level id is the idempotency boundary, so per-item client_order_id
may be omitted. Treat a cancel
response as an admission acknowledgement and reconcile with list_open before
releasing local state.
Attached take-profit / stop-loss use the same friendly keys as the read
model (trigger_price, order_type, limit_price). Child execution is
market (default) or limit, not limit_ioc / limit_fok, and not a
nested child.execution wrapper.
result = await client.orders.create(
symbol="BTC-USDT",
side="buy",
order_type="limit",
tif="gtc",
qty="0.01",
price="100",
post_only=True,
attached_risk={
"take_profit": {"trigger_price": "140", "order_type": "market"},
"stop_loss": {"trigger_price": "80", "order_type": "market"},
"oco": True,
},
)
detail = await client.orders.get(
key=ClientOrderId(result.client_order_id),
include_attached_risk=True,
)
Create sizing is explicit: set exactly one of base qty or max_quote_debit
(a hard all-in quote-debit budget). Decimal/str quote budgets use the pair's
catalog quote_quantity_scale. Typed budgets must use
QuantityDomain.ORDER_QUOTE and embed that scale; construct with
Quantity.from_quote_decimal_str / from_quote_decimal / from_quote_scaled
and validate against client.catalogs.quote_quantity_scale_for_symbol.
quote_scale = client.catalogs.quote_quantity_scale_for_symbol("BTC-USDT")
assert quote_scale is not None # await client.wait_for_catalogs() first
result = await client.orders.create(
symbol="BTC-USDT",
side="buy",
order_type="market",
max_quote_debit=Quantity.from_quote_decimal_str(
"25.00", quote_scale, symbol="BTC-USDT"
),
)
Use orders.preview_order(...) to check current order admissibility and, when
available, resolved base quantity plus protected price bound before submitting.
Preview accepts the same public create kwargs and encodes them as an
OrderIntent (same wire contract as create). It does not return fee or
quote-debit estimates. protected_price_bound is a protective execution
boundary, not an expected fill price. Rejections carry stable labels such as
BAD_QTY plus field violations; evaluated_at_ms records when admission was
evaluated. Preview is not deployed on every API
host, so handle an unimplemented/not-found response and do not make Preview a
prerequisite for order submission.
Market orders are IOC and enforce a slippage-derived execution boundary. See Market Order Price Protection before overriding market slippage.
Scaled transfer/withdraw AssetAmount inputs must carry their source scale.
AssetAmount.from_scaled(..., scale=None) is accepted for composition only and
fails closed on encoding unless the request's amount_scale /
quantity_scale is explicit.
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).
Catalog hydration resolves symbol → symbol_id and quantity scales for
encoding; pair admission (tick/step/min qty/notional) stays on the API.
orders.preview_order(...) remains the authority for balances, reservations,
liquidity, and other stateful rules.
For bots (scaled integers)
Stay in integer space; no string round-trip:
from polyester import Price, Quantity
result = await client.orders.create(
symbol="BTC-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
# orig_qty is the current accepted total and changes after a successful modify.
Compatible values from fills/books can be passed back into writes when the instrument/domain matches.
orders.batch_replace(...) returns an admission receipt, not final execution.
The predecessor ID can be stale after admission; reconcile the successor IDs
and per-item phases by polling get_batch_replace_status. Use
is_batch_replace_settled(status) only to determine that all items have moved
to working, rejected, or terminal, not as a finality signal. Retry an
ambiguous batch request with the same request_id.
Your API key needs a policy that allows trading. Spot orders spend trading balance (see below).
User trade fees
UserTrade fee fields are fixed 18-decimal magnitudes of fee_asset
(fee_amount_e18, referral_share_amount_e18), not catalog asset-scaled
integers. Convert e18 → the fee asset's catalog scale before subtracting from a
BUY fill's base quantity.
Magnitudes are unsigned. Treat fee_amount_e18 as a debit unless
fee_is_rebate is true (then it is a credit). Proto3 omits false, so the
rebate flag is sparse on the wire.
client.fees.get_spot_fee_rates() returns the account target's current effective maker/taker
percents per listed symbol (optional symbol_id filter). That is the live effective-rate
surface, not a guaranteed quote of every future fill. The completed user-trade record still
owns the exact charge.
trades.list(after_match_id=...) is a durable fill-replay cursor and requires
symbol or symbol_id.
VIP, spot fees, and trading rate limits
Public catalog reads need no credentials. Authenticated VIP status, effective spot fees, and
account trading limits require an API key. GetVIPStatus has no subaccount selector: JWT and
API-key callers receive the owning root group only. USD amounts and fee percents are decimal
strings. Optional qualification metrics, timestamps, and next-tier thresholds stay omitted
when unset. policy_class uses full protobuf enum names
(TRADING_RATE_LIMIT_CLASS_PLACE / _CANCEL). Rate-limit rules expose
vip_tier (not tier).
tiers = await client.vip.list_vip_tiers()
status = await client.vip.get_vip_status()
fees = await client.fees.get_spot_fee_rates()
catalog = await client.rate_limits.get_rate_limit_config()
limits = await client.rate_limits.get_trading_rate_limits()
Address book and social handles
Address-book reads (list_books, list_entries, get_view) and writes
(create_entry, update_entry, delete_entry, tag CRUD) are wrapped.
get_view(minimum_view_revision=...) asks the server for a newer snapshot;
the view and invalidation events expose view_revision.
Create and update accept new_tags so a tag can be created and attached in
the same request. Update is a durable PATCH: pass expected_revision from a
prior read and only the fields you set are selected. When tag_ids is also
set, the resulting set is those ids plus new_tags; otherwise new_tags are
appended to the current tags.
entry = await client.address_book.create_entry(
label="ops",
address="0x…",
polychain_chain_id=1,
new_tags=[{"name": "hot", "color": "#f00"}],
)
entry = await client.address_book.update_entry(
address_book_entry_id=entry.address_book_entry_id,
expected_revision=entry.revision,
new_tags=[{"name": "vip"}],
)
social_verification.start(..., handle="@alice") accepts an optional leading
@ on Twitter handles. The SDK forwards the handle as supplied.
Triggers
triggers.list(status=...) filters by lifecycle status. Valid values:
created, armed, running, completed, cancelled, failed, paused
Invalid status and event-type labels raise PolyesterValidationError.
Connect requests and responses use numeric symbol_id. Public methods still
accept display symbol strings and resolve them through the hydrated catalog
after wait_for_catalogs(). Unknown symbols fail closed. Only
get_spot_config returns both symbol and symbol_id; other Connect
payloads are ID-only. Display symbol on models such as market overview,
fees, triggers, and policy rules is filled from the catalog when available.
Unknown values raise PolyesterValidationError (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). Independently,
include_attached_risk_state=True can return state-only leg wrappers when
policy inclusion is false. Each returned RiskLeg / TrailingStop exposes
that runtime AttachedRiskLegState at .state with status, nanosecond
armed/terminal timestamps, trigger_id, and child_order_id; absent policy
fields remain unset. Order also exposes post_only. Create/modify accept the
same policy shape as a dict or AttachedRisk.
When modifying a trigger, omit activation_price / max_slippage_ticks /
max_slippage_bps to preserve the current values. Send an explicit zero
(Price.from_ticks(0) or 0) to clear an existing activation price or
maximum-slippage cap. Create/modify max_slippage_bps must be 1–10000;
modify still accepts 0 to clear. Trailing distance in bps uses the same
1–10000 range; trigger modify accepts 0 to clear.
orders.list_open(trigger_id=...) / orders.list_history(trigger_id=...)
return only child orders created by that trigger (TWAP/ladder slices).
Trigger-event fire_price is None for time-scheduled TWAP slice fires
(fire_price_ticks is optional on the wire). Canceled and failed triggers
expose typed cancel_reason / failure_reason labels (empty when unset).
Balances: funding vs trading
Ledger balances have separate funding and trading buckets per asset.
- An external deposit can stop in funding or continue to trading, depending on its configured route.
- 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: prepare and persist an exact API-key signature with
client.trading_withdraws.prepare_api_key_to_funding(...), then callsubmit_prepared(...). One-callcreate_api_key_to_funding(...)is also available. - Trading → external destination check:
await client.trading_withdraws.validate_destination(destination_chain_id=..., destination_address=...)returns user-safevalid/code/message/canonical_destination_addresswithout creating a withdraw (create RPCs remain authoritative). - 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")
if current is not None:
print(current.close, current.is_closed)
trades = await client.market_data.get_trades(symbol="BTC-USDT", limit=20)
subscription = await client.market_data.subscribe_trades(symbol="BTC-USDT")
subscription.set_on_error(lambda error: print(f"realtime interruption: {error}"))
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
List and heatmap limit arguments must be integers (booleans and other types
are rejected for wire safety). Optional limits omit when unset so the server
default applies; allowed ranges are enforced by each API, not a global SDK cap.
Response fields named ts_ns are treated as epoch nanoseconds per the API
contract.
get_candles() returns row-oriented candles newest-first (an incomplete open
candle, when requested, is prepended). get_candles_columns() normalizes its
column response oldest-first. Sort explicitly by ts_sec before feeding a
chronological indicator.
Merged market overview stream (snapshot + live updates). The create call waits for the WebSocket handshake and initial snapshot:
sub = await client.market_overview.create_subscription(
on_error=lambda error: print(f"managed overview failed: {error}")
)
async with sub:
async for markets in sub:
print(len(markets), "rows")
break
Realtime delivery contract
- Realtime is binary-only. The client negotiates the
centrifuge-protobufWebSocket subprotocol and consumes protobuf publications from:protochannels. ConnectRPC's optional JSON wire mode does not apply to realtime. Inbound WebSocket messages are capped at 4 MiB. - Subscription queues are bounded. If the consumer falls behind, the SDK raises
PolyesterRealtimeOverflowErrorand faults the subscription; it does not silently drop updates. - Reconnects use capped exponential backoff with per-subscription jitter.
set_on_error(...)observes background feed interruptions; async iteration still raises terminal failures. - 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. Buffered publications survive a failed snapshot retry and are merged exactly once after recovery; cancellation closes the replacement socket. Managed create methods await the handshake and initial snapshot before returning.
- Private order stream quantities are raw scaled values. Their
Quantity.scalemay beNone; resolve the hydrated catalog scale withclient.catalogs.base_quantity_scale_for_symbol_id(order.symbol_id)(or by symbol), then callquantity.format(scale). Never guess or inject scale 8.
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="YOUR_ACCOUNT_ID",
) 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 tests/hardening -q
Live selection (A7): public smoke vs credentialed suites are selectable via markers.
# Public smoke (read-only smoke; no mutation/funded)
python -m pytest -m "public_smoke" -q
# Credentialed live integration (requires API-key env)
python -m pytest -m "credentialed and not mutation and not funded" -q
# Local L2 hardening only (no network)
python -m pytest tests/hardening -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 tests/hardening -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.
Set POLYESTER_TEST_MUTATION=1 for state-changing tests. Funded mutations
require both POLYESTER_TEST_MUTATION=1 and POLYESTER_TEST_FUNDED=1. For a
release-certification run, set POLYESTER_TEST_STRICT_LIVE=1; any skipped test
then fails instead of making an incomplete live run appear green. Missing or
malformed credentials must fail under strict live (not soft-skip as green). The
session printer scopes executed/skipped/failed counts to @pytest.mark.integration
tests and enforces POLYESTER_TEST_MIN_EXECUTED (default 5) when credentials are
present.
Legacy stress tests that use non-dry-run cancel_all require
POLYESTER_TEST_ACCOUNT_WIDE_CLEANUP=1. Set it only for a dedicated test
account; those tests may cancel every open order in their selected symbol.
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.
Lifecycle transaction lookups
One chain transaction can reference multiple lifecycle flows when operations are bundled. Transaction lookups therefore return a page, not one flow:
page = await client.lifecycle.list_flows_by_tx(tx_hash=tx_hash, limit=50)
flow_ids = [flow.intent_id for flow in page.flows]
while page.next_page_token:
page = await client.lifecycle.list_flows_by_tx(
tx_hash=tx_hash,
limit=50,
page_token=page.next_page_token,
)
flow_ids.extend(flow.intent_id for flow in page.flows)
get_flow_by_tx(...) also returns the complete first page. Use
list_flows_by_tx(...) when following pagination.
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.0a50.tar.gz.
File metadata
- Download URL: polyester_sdk-0.1.0a50.tar.gz
- Upload date:
- Size: 402.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.12.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e32087d41c8a12651b606069499360b2dd62bfb9d6e254d0b18a52575735f657
|
|
| MD5 |
2b9f351dcea069e690472c5473cebacd
|
|
| BLAKE2b-256 |
a6267441052f3a03eda3436fb74ea93cbfefe5a2cb9bd4a89687c9d770f1359b
|
File details
Details for the file polyester_sdk-0.1.0a50-py3-none-any.whl.
File metadata
- Download URL: polyester_sdk-0.1.0a50-py3-none-any.whl
- Upload date:
- Size: 506.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.12.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6c0ef02156f9d0682f073f108dc7c0bb87bbdbb65795880e62558c81516c7e58
|
|
| MD5 |
486973d7ca59e619ad4214f3b566c208
|
|
| BLAKE2b-256 |
6cb94273f60ff1a9a5540302415da1474cf1e256cadaf5bed45ea11c83e5b31e
|