This release is a pre-release and may not be stable for production use.
tria-trade (Python)
Python port of @tria-sdk/api-trading — programmatic access to Hyperliquid
and Decibel perpetuals through Tria, with builder-fee attribution. Fully
async, mirrors the TypeScript SDK's surface 1:1.
Install
pip install tria-trade
Requires Python 3.11+.
Quickstart
import asyncio
from tria_trade import (
TriaClient,
TriaClientConfig,
HlConfig,
DecibelConfig,
generate_client_order_id,
)
async def main():
client = TriaClient(
TriaClientConfig(
hl=HlConfig(agent_private_key="0x…", account_address="0x…"),
decibel=DecibelConfig(
delegate_private_key="0x…", # omit for a read-only Decibel client
aptos_owner_address="0x…", # SDK derives the primary subaccount
node_api_key="<geomi-node-key>", # required (Decibel rejects anonymous reads)
gas_station="<geomi-gs-key>", # optional — sponsored (no-APT) writes
),
)
)
# Market data
print(await client.ticker(venue="hl", market="BTC"))
print(await client.markets(venue="decibel"))
# Trade
status = await client.place_order(
{
"venue": "hl",
"market": "BTC",
"side": "buy",
"size": "0.001",
"price": "50000",
"tif": "gtc",
"clientOrderId": generate_client_order_id(),
}
)
await client.cancel_order(venue="hl", order_id=status["orderId"])
asyncio.run(main())
Build the config from a generated .env with os.environ (see examples/), or
generate one with the provisioning CLI below.
Surface
Every method is async and routed through non-blocking telemetry. venue is
"hl" or "decibel".
- Orders —
place_order,place_batch,modify_order,cancel_order,cancel_all_orders,order_status,open_orders,attach_tp_sl - Positions / account —
positions,balances,margin_state,account,close_position - Market data —
markets,ticker,orderbook,candles,recent_trades,fills - Risk —
scheduled_cancel(HL native; Decibel raises) - Streams —
subscribe_fills,subscribe_positions,subscribe_orders,subscribe_account, andon_connectionfor lifecycle events - Lifecycle —
await client.close()releases the venue WebSocket(s), REST/HTTP clients, and the telemetry sink so the event loop can shut down. Idempotent and a no-op for REST-only callers.async with TriaClient(...) as client:calls it for you.
All amounts are decimal strings (full precision; never float). Errors raise
TriaError with a .code of VENUE_REJECTED / VENUE_RATE_LIMIT /
VENUE_UNREACHABLE / INVALID_ARGUMENT / MISSING_BUILDER / CONFIG.
clientOrderId is optional — omit it and the SDK mints one; provide your own to
use it as a dedup key (re-using a live one is rejected). On HL, price is rounded
to the venue rule (≤5 sig figs and ≤ 6 - szDecimals decimals) before signing, a
fill populates OrderStatus["avgFillPrice"], and account() reads Unified Account
collateral from spotClearinghouseState (account()["mode"] is "unified" /
"legacy_cross").
Tiers
development=False (default) uses Tria's production builder; development=True
uses the staging builder. Both trade on real mainnet venues — only the
builder/treasury address differs.
Decibel: acknowledged at submit
place_order on Decibel returns state="submitted" as soon as Aptos accepts the
transaction — it does not wait for the commit, and makes no indexer reads unless
you supplied your own clientOrderId (one read, to reject a cloid that is already
live). orderId is "" and filled / remaining are placeholders in that
state; look the order up later with
order_status(venue="decibel", client_order_id=...).
The commit is verified in the background. If it aborts, the SDK logs a warning on
the tria_trade.decibel logger and order_status reports order_not_found.
Pass "resolve": "confirmed" to await the commit (an abort then raises) and read
the resolved state from the indexer. A reader failure after a successful submit
(e.g. a rate limit) returns the submitted status rather than an error — the
order is live either way. modify_order and close_position take the same
resolve= keyword (a Decibel modify_order is one atomic update and always
waits for its commit; "confirmed" also reads the replacement's state). A
Decibel order modified this way gets a new orderId and no client order id:
track and cancel it by the returned orderId, because a cancel by the old
client_order_id returns not_found while the modified order stays open. Indexer reads count against a per-organization rate limit
shared with other Tria traffic, so prefer the default unless you need the outcome
inline.
Cancel with market (cancel_order(venue="decibel", client_order_id=..., market="BTC/USD")) whenever you know it: the cancel then goes straight to the
chain, usually with no open-orders lookup against the venue's rate limit (one
still happens if a gas station refuses the direct cancel, or for a client order id
with capitals). Without it the SDK looks the order up (at most two reads). Pass
the order's OWN market: an unknown market raises INVALID_ARGUMENT, and a
different valid market returns not_found while the order stays open.
Your fee is your Tria fee
Every order carries Tria's builder address and fee in the venue payload (HL: the
action's builder; Decibel: builder_addr + fee on place_order). The standard
builder fee is 5 bps on both venues, and each order is charged the same fee
you pay in the Tria app: your VIP trading-badge discount, your staking-badge
discount and any fee offer on your account apply exactly as in the app, with the
same math.
How the SDK knows your fee, without any extra credential:
- It signs a short request with the trading key it already has (HL agent / Decibel delegate) and asks Tria which discounts apply to that account. The request carries your account address, a timestamp and that signature — never a private key.
- Tria verifies the signature, checks with the venue that the key trades for the account, and answers with your discounts.
- The answer is cached for the whole process (shared by every client you create) and refreshed in the background.
It never gets in the way of an order:
- If the fee can't be determined (backend unreachable, key not yet recognised, account not linked to Tria), the order goes out at the standard fee — never higher.
- The first order in a process waits at most ~1 second for the answer; every later order uses the cached one.
- An offer stops applying at its end date even between refreshes.
The fee is paid by the venue out of the fill, not added on top.
Telemetry
The SDK emits anonymous usage telemetry (trade actions, latencies, error categories) to a Tria-owned proxy so the team can monitor SDK health on the same "Trade Health" dashboard the apps feed. It is non-blocking and fire-and-forget — a hung or unreachable endpoint is indistinguishable from telemetry-off and never delays or fails a trade.
- What's collected: trade action, venue, outcome, latency, error category/
severity, size bucket, a truncated wallet (
first6…last4), an optionalclient_id. Free-text is scrubbed of hex/base58/email runs. - What's never collected: private keys, full addresses, or order-level PII — the redaction pass makes leaking signing material structurally impossible.
- Always on: telemetry is part of the SDK's operating contract and has no
disable switch.
TRIA_TRADE_TELEMETRY_QUIET=1silences the one-time first-run notice (telemetry itself stays on).
Provisioning CLI
tria-trade provision sets up an operator's HL + Decibel accounts (mint the HL
agent, register the Decibel delegate, approve builders, fund the venues) and
writes a ready-to-use .env. All actions are signed on-chain with the operator's
own master key — the wizard never calls a Tria backend.
export HL_MASTER_PRIVATE_KEY=0x… # or TRIA_MNEMONIC="word1 word2 …"
export APTOS_MASTER_PRIVATE_KEY=0x… # for Decibel (or derived from TRIA_MNEMONIC)
export DECIBEL_NODE_API_KEY=<geomi-key>
tria-trade provision --dry-run # print the plan, no chain writes
tria-trade provision --hl-amount 50 --decibel-amount 50 --output .env
Flags: --hl-amount / --decibel-amount (target USDC), --output, --dry-run,
--skip-hl / --skip-decibel, --development, --gas-station-key,
--force-new-agent / --force-new-decibel-delegate.
Examples
See examples/: place_order.py, cancel_order.py,
subscribe_fills.py, multi_venue.py. Each reads credentials from the
environment — source a generated .env first.
Development
cd packages/trade-sdk-py
python -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"
pytest # unit tests (telemetry forced off)
ruff check src tests && ruff format --check src tests
mypy
Live e2e (tests/test_live_e2e.py) is skipped unless funded credentials are
present at ~/.config/tria/sdk-test.env. It runs reads + far-below-market
place/cancel round-trips on both venues (real signed mainnet txns; no fills; it
cancels only the orders it placed). Run with
TRIA_RUN_LIVE_E2E=1 pytest tests/test_live_e2e.py.
Reference
The TypeScript SDK (@tria-sdk/api-trading at packages/trade-sdk/) is the
canonical implementation; this port mirrors it 1:1. The OpenAPI schema at
packages/trade-sdk/openapi.yaml is the cross-language source of truth for shapes.
Metadata
Release files for tria-trade 8.6.2b0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| tria_trade-8.6.2b0.tar.gz | 152.2 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| tria_trade-8.6.2b0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 279.4 kB
Release files / tria_trade-8.6.2b0.tar.gz
| Download URL | tria_trade-8.6.2b0.tar.gz |
|---|---|
| Size | 152.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
a05f7c6f655d27a31fd20e1b5d49cbbc22694fda49e0d58a7aab0f98a3542289
|
|
BLAKE2b-256 checksum How to use checksums |
59bf5b32c8c4bafb8090ef8277f6d9054d2292279214c1930068675f87de2a4c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.15
|
Release files / tria_trade-8.6.2b0-py3-none-any.whl
| Download URL | tria_trade-8.6.2b0-py3-none-any.whl |
|---|---|
| Size | 127.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
89c87176e441ca0782f50fb9e1e869b4a43e54e673520e791db8fc62dc3eaf2a
|
|
BLAKE2b-256 checksum How to use checksums |
c10267ddfc7e4fb4c0dde2ba52ce59c1d780b9f11a0f25307d8588c79c35a17a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.15
|