TPlus Python Client Utilities
Python clients for interacting with tplus.
Install
To install, use either pip or uv pip:
uv pip install -e .
Onboarding with Codex CLI
This repo includes a Codex-discoverable onboarding skill at
.agents/skills/onboard for connecting an EVM wallet key to T+, checking whether
it already controls a T+ account, and guiding account creation when needed.
From the repo root:
codex
/skills
Choose onboard, or invoke it directly with:
$onboard onboard me to T+
The flow reads TPLUS_PRIVATE_KEY and TPLUS_API_BASE_URL from .env. Do not
paste private keys into chat.
CLI
tpluspy installs a tplus command for interacting with T+ services from the
shell — managing local accounts, placing orders, deposits/withdrawals, and
on-chain registry/vault operations. Run tplus --help for the full command
tree.
For a comprehensive guide covering every subcommand, global options, and the local dev bootstrap flow, see docs/userguides/cli.md.
Usage Example
REST and WebSocket Client (tplus.client)
The tpluspy library also provides an asynchronous client (OrderBookClient) for interacting with the tplus-core REST API and WebSocket streams.
Initialization
To use the client, first initialize it with a User object (for signing requests) and the base URL of your tplus-core instance:
import asyncio
from tplus.client import OrderBookClient
from tplus.utils.user import User
API_BASE_URL = "http://127.0.0.1:8000" # Replace with your API URL
user = User()
async def run_client():
# Use async context manager for automatic cleanup
async with OrderBookClient(API_BASE_URL, default_user=user) as client:
print("Client initialized.")
# ... use client methods ...
asyncio.run(run_client())
Use EVM accounts as T+ accounts
An EVM account can back a T+ account: it signs one fixed message and the T+ identity is derived from that signature, so the same wallet always maps to the same T+ account (the one the T+ frontend derives on wallet login).
Pass the account itself anywhere a User is accepted, whether that is default_user=, a per-call user=, or a signer=:
from ape import accounts
account = accounts.load("me")
async with OrderBookClient(API_BASE_URL, default_user=account) as client:
await client.get_user_inventory() # signs as the T+ user behind the account
await client.create_limit_order(..., user=account) # per-call override
An eth_account signer works the same way, with no [evm] extra:
from eth_account import Account
client = OrderBookClient(API_BASE_URL, default_user=Account.from_key("0x..."))
Use load_user_from_ape_account to hold the User itself, for example to read its public key or to pick a sub-account. load_user loads stored keyfiles by name, so Ape aliases and keyfile names occupy separate namespaces and cannot collide:
from tplus.utils.user import load_user_from_ape_account
user = load_user_from_ape_account("me") # by Ape alias (needs the `evm` extra)
user = load_user_from_ape_account(account) # an already-loaded Ape account
The result is a normal T+ User and signs every request the same way.
REST API Usage
The client offers async methods for common REST endpoints:
Fetching Data:
# Get Order Book Snapshot for asset index 200
from tplus.model.asset_identifier import AssetIdentifier
example_asset = AssetIdentifier(Index=200)
orderbook = await client.get_orderbook_snapshot(example_asset)
print(f"Snapshot Sequence: {orderbook.sequence_number}")
# Get Klines for an asset
klines = await client.get_klines(example_asset)
print(f"Klines: {klines}")
# Get Market Details for an asset
market_details = await client.get_market(example_asset)
print(
f"Market Details: Price Decimals={market_details.book_price_decimals}, Quantity Decimals={market_details.book_quantity_decimals}"
)
# Get orders for the user
user_orders, _ = await client.get_user_orders()
print(f"User Orders: {user_orders}")
# Get trades for the user and asset (per-user trades live on the market-data-service)
from tplus.client import MarketDataClient
md_client = MarketDataClient("http://localhost:8011", default_user=user)
user_asset_trades = await md_client.get_user_trades_for_asset(example_asset, user=user)
print(f"User Asset Trades: {user_asset_trades}")
# Get user inventory
inventory = await client.get_user_inventory()
print(f"Inventory: {inventory}")
Creating Orders:
# Ensure example_asset is defined (e.g., from "Fetching Data" section)
from tplus.model.asset_identifier import AssetIdentifier
example_asset = AssetIdentifier(200)
# Create a Market for an asset (idempotent)
market_creation_response = await client.create_market(asset_id=example_asset)
print(f"Market Creation Response: {market_creation_response}")
# Create a Market Order for a specific asset
market_response = await client.create_market_order(
asset_id=example_asset,
quantity=10, # integer quantity
side="Buy",
fill_or_kill=False,
)
print(f"Market Order Response: {market_response}")
# Create a Limit Order for a specific asset
# Good-Till-Cancelled limit order
from tplus.model.limit_order import GTC
limit_response = await client.create_limit_order(
asset_id=example_asset,
quantity=5,
price=1_000,
side="Sell",
time_in_force=GTC(),
)
print(f"Limit Order Response: {limit_response}")
# Cancel an Order
# Order ID should be obtained from an order creation response.
order_id_to_cancel = "actual-order-id-from-api" # Replace with a real order ID
cancel_response = await client.cancel_order(order_id=order_id_to_cancel, asset_id=example_asset)
print(f"Cancel Order Response: {cancel_response}")
# Replace an Order
# Original Order ID should be from an existing, open order.
original_order_id_to_replace = "actual-original-order-id" # Replace with a real order ID
replace_response, revision = await client.replace_order(
original_order_id=original_order_id_to_replace,
asset_id=example_asset,
new_quantity=6, # Effective lifetime-total quantity
new_price=1050, # Effective limit price
)
print(f"Replace Order Response: {replace_response}")
# `revision` is the replacement's timestamp_ns; a later amend_order must quote it as
# expected_authorization_revision.
# Replace several open orders in one request (up to 50). Each item is signed on its own;
# the OMS sends one message per market and returns one status per item, in order.
batch_replace_response = await client.replace_multiple_orders(
[
await client.prepare_replace_order_request(
original_order_id=oid, asset_id=example_asset, new_quantity=6, new_price=price
)
for oid, price in [("order-a", 1050), ("order-b", 1040)]
]
)
for status in batch_replace_response.batch_order_status:
print(f"{status.order_id}: {status.status} {status.reason or ''}")
See examples/rest_usage.py for a runnable demonstration.
WebSocket Streaming
The client provides async iterators to stream real-time data:
from tplus.model.asset_identifier import AssetIdentifier
from tplus.model.orderbook import OrderBookDiff
from tplus.model.trades import Trade
example_asset = AssetIdentifier(200)
# Stream Order Book Diffs
async for diff_update in client.stream_depth(example_asset):
if isinstance(diff_update, OrderBookDiff):
print(
f"[Depth] Seq={diff_update.sequence_number}, Asks={len(diff_update.asks)}, Bids={len(diff_update.bids)}"
)
# Add logic to handle the update, e.g., update a local order book
# Stream Finalized Trades
async for trade in client.stream_finalized_trades():
if isinstance(trade, Trade):
print(f"[Trade] ID: {trade.trade_id}, Price: {trade.price}, Qty: {trade.quantity}")
# Add logic to handle the trade
# Other available streams:
# client.stream_orders() -> OrderEvent
# client.stream_all_trades() -> TradeEvent
# client.stream_klines(asset_id) -> Timebar
See examples/websocket_usage.py for a runnable demonstration using asyncio.gather to run multiple streams concurrently.
Token Recovery
When a bearer token stops working before it actually expires (e.g. from a service restart), both transports recover on their own. A REST response or WebSocket handshake rejected with 401 or 403 re-authenticates and retries once; a second rejection is raised to the caller. Requests rejected concurrently share a single re-authentication.
Contracts
To interact with the on-chain T+ contracts (or sign T+ settlement messages), install one of the EVM extras:
pip install "tpluspy[evm]" # web3.py backend (no Ape)
pip install "tpluspy[evm-ape]" # adds the Ape backend on top of [evm]
The Python API is identical either way. With the web3.py backend, point it
at a node via WEB3_PROVIDER_URI (read by web3.auto) — or call
tplus.evm.use_web3(rpc_url=...):
export WEB3_PROVIDER_URI="https://arb1.arbitrum.io/rpc"
from tplus.evm.contracts import registry, vault
registry.admin()
vault.getApprovedSettlers()
With the Ape backend, just use ape console / ape run as usual; it's
auto-detected whenever Ape is connected to a network:
ape console --network ethereum:sepolia:alchemy
In [1]: from tplus.evm.contracts import registry
In [2]: registry.getAssets()
Out[2]: [
getAssets_return(
assetAddress=HexBytes("0x000000000000000000000000f08a50178dfcde18524640ea6618a1f965821715"),
chainId=11155111,
maxDeposits=100,
)
]
In [3]: registry.admin()
Out[3]: '0x467a95fC5359edE5d5dDc4f10A1F4B680694858E'
See docs/userguides/contracts.md for the full
guide, including accounts, settlements, and the higher-level managers.
Metadata
Release files for tpluspy 0.5.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| tpluspy-0.5.0.tar.gz | 275.3 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| tpluspy-0.5.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 597.2 kB
Release files / tpluspy-0.5.0.tar.gz
| Download URL | tpluspy-0.5.0.tar.gz |
|---|---|
| Size | 275.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
8d9b77484fe3606dcfc72a88f0d48c2dbaa1c8ace9e1ebfb7487f3050e70b8a5
|
|
BLAKE2b-256 checksum How to use checksums |
c7acd3aed8d8d687aaa29c8afef1dac9126442373439508eb27a413a2309bd60
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 2, 2026.
Transparency logRelease files / tpluspy-0.5.0-py3-none-any.whl
| Download URL | tpluspy-0.5.0-py3-none-any.whl |
|---|---|
| Size | 321.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
ef492398e5e627344f32ced892740c968288a0af6904f53c65816e480e06b1c2
|
|
BLAKE2b-256 checksum How to use checksums |
0763429f664c1fad7d87b6c8784b19c8349e41edb9a286d3724ef2e6d091bc69
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 2, 2026.
Transparency log