tpluspy 0.2.3__tar.gz → 0.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {tpluspy-0.2.3 → tpluspy-0.3.0}/PKG-INFO +54 -18
- {tpluspy-0.2.3 → tpluspy-0.3.0}/README.md +52 -16
- {tpluspy-0.2.3 → tpluspy-0.3.0}/setup.py +2 -2
- {tpluspy-0.2.3 → tpluspy-0.3.0}/tplus/__init__.py +4 -0
- {tpluspy-0.2.3 → tpluspy-0.3.0}/tplus/_cli/_context.py +71 -21
- {tpluspy-0.2.3 → tpluspy-0.3.0}/tplus/_cli/env.py +11 -1
- {tpluspy-0.2.3 → tpluspy-0.3.0}/tplus/_cli/markets.py +5 -0
- {tpluspy-0.2.3 → tpluspy-0.3.0}/tplus/_cli/orders.py +13 -7
- {tpluspy-0.2.3 → tpluspy-0.3.0}/tplus/_cli/trades.py +6 -3
- {tpluspy-0.2.3 → tpluspy-0.3.0}/tplus/_cli/vaults.py +1 -4
- tpluspy-0.3.0/tplus/asset_metadata.py +116 -0
- {tpluspy-0.2.3 → tpluspy-0.3.0}/tplus/client/__init__.py +2 -0
- tpluspy-0.3.0/tplus/client/api.py +50 -0
- {tpluspy-0.2.3 → tpluspy-0.3.0}/tplus/client/auth.py +9 -4
- {tpluspy-0.2.3 → tpluspy-0.3.0}/tplus/client/base.py +37 -32
- {tpluspy-0.2.3 → tpluspy-0.3.0}/tplus/client/clearingengine/admin.py +9 -1
- {tpluspy-0.2.3 → tpluspy-0.3.0}/tplus/client/clearingengine/cross_venue.py +5 -3
- tpluspy-0.3.0/tplus/client/market_data.py +336 -0
- {tpluspy-0.2.3 → tpluspy-0.3.0}/tplus/client/oms/assetregistry.py +2 -4
- tpluspy-0.3.0/tplus/client/oms/oms_admin.py +82 -0
- {tpluspy-0.2.3 → tpluspy-0.3.0}/tplus/client/orderbook.py +123 -99
- {tpluspy-0.2.3 → tpluspy-0.3.0}/tplus/evm/contracts.py +16 -0
- {tpluspy-0.2.3 → tpluspy-0.3.0}/tplus/evm/dev/env.py +1 -1
- {tpluspy-0.2.3 → tpluspy-0.3.0}/tplus/evm/managers/settle.py +1 -0
- {tpluspy-0.2.3 → tpluspy-0.3.0}/tplus/evm/managers/withdraw.py +106 -5
- {tpluspy-0.2.3 → tpluspy-0.3.0}/tplus/exceptions.py +17 -0
- {tpluspy-0.2.3 → tpluspy-0.3.0}/tplus/model/cancel_order.py +2 -1
- {tpluspy-0.2.3 → tpluspy-0.3.0}/tplus/model/chain_address.py +10 -0
- {tpluspy-0.2.3 → tpluspy-0.3.0}/tplus/model/multisig.py +3 -3
- {tpluspy-0.2.3 → tpluspy-0.3.0}/tplus/model/order.py +14 -34
- tpluspy-0.3.0/tplus/model/order_id.py +17 -0
- {tpluspy-0.2.3 → tpluspy-0.3.0}/tplus/model/order_trigger.py +3 -1
- tpluspy-0.3.0/tplus/model/pagination.py +24 -0
- {tpluspy-0.2.3 → tpluspy-0.3.0}/tplus/model/position.py +3 -12
- {tpluspy-0.2.3 → tpluspy-0.3.0}/tplus/model/replace_order.py +20 -12
- {tpluspy-0.2.3 → tpluspy-0.3.0}/tplus/model/risk_parameters.py +2 -0
- {tpluspy-0.2.3 → tpluspy-0.3.0}/tplus/model/settlement.py +12 -0
- {tpluspy-0.2.3 → tpluspy-0.3.0}/tplus/model/signed_message.py +2 -1
- {tpluspy-0.2.3 → tpluspy-0.3.0}/tplus/model/trades.py +3 -12
- {tpluspy-0.2.3 → tpluspy-0.3.0}/tplus/model/types.py +3 -1
- {tpluspy-0.2.3 → tpluspy-0.3.0}/tplus/utils/limit_order.py +5 -2
- {tpluspy-0.2.3 → tpluspy-0.3.0}/tplus/utils/market_order.py +5 -2
- {tpluspy-0.2.3 → tpluspy-0.3.0}/tplus/utils/replace_order.py +21 -9
- {tpluspy-0.2.3 → tpluspy-0.3.0}/tplus/utils/signing.py +8 -2
- {tpluspy-0.2.3 → tpluspy-0.3.0}/tplus/utils/user/__init__.py +2 -2
- {tpluspy-0.2.3 → tpluspy-0.3.0}/tplus/utils/user/model.py +74 -0
- {tpluspy-0.2.3 → tpluspy-0.3.0}/tpluspy.egg-info/PKG-INFO +54 -18
- {tpluspy-0.2.3 → tpluspy-0.3.0}/tpluspy.egg-info/SOURCES.txt +4 -0
- {tpluspy-0.2.3 → tpluspy-0.3.0}/tpluspy.egg-info/requires.txt +1 -1
- tpluspy-0.2.3/tplus/client/market_data.py +0 -159
- tpluspy-0.2.3/tplus/client/oms/oms_admin.py +0 -41
- {tpluspy-0.2.3 → tpluspy-0.3.0}/pyproject.toml +0 -0
- {tpluspy-0.2.3 → tpluspy-0.3.0}/setup.cfg +0 -0
- {tpluspy-0.2.3 → tpluspy-0.3.0}/tplus/_cli/__init__.py +0 -0
- {tpluspy-0.2.3 → tpluspy-0.3.0}/tplus/_cli/__main__.py +0 -0
- {tpluspy-0.2.3 → tpluspy-0.3.0}/tplus/_cli/accounts.py +0 -0
- {tpluspy-0.2.3 → tpluspy-0.3.0}/tplus/_cli/assets.py +0 -0
- {tpluspy-0.2.3 → tpluspy-0.3.0}/tplus/_cli/balance.py +0 -0
- {tpluspy-0.2.3 → tpluspy-0.3.0}/tplus/_cli/debug.py +0 -0
- {tpluspy-0.2.3 → tpluspy-0.3.0}/tplus/_cli/decimals.py +0 -0
- {tpluspy-0.2.3 → tpluspy-0.3.0}/tplus/_cli/deposit.py +0 -0
- {tpluspy-0.2.3 → tpluspy-0.3.0}/tplus/_cli/params.py +0 -0
- {tpluspy-0.2.3 → tpluspy-0.3.0}/tplus/_cli/settle.py +0 -0
- {tpluspy-0.2.3 → tpluspy-0.3.0}/tplus/_cli/sign.py +0 -0
- {tpluspy-0.2.3 → tpluspy-0.3.0}/tplus/_cli/stream.py +0 -0
- {tpluspy-0.2.3 → tpluspy-0.3.0}/tplus/_cli/withdraw.py +0 -0
- {tpluspy-0.2.3 → tpluspy-0.3.0}/tplus/_cli/withdrawal.py +0 -0
- {tpluspy-0.2.3 → tpluspy-0.3.0}/tplus/cli_tools/__init__.py +0 -0
- {tpluspy-0.2.3 → tpluspy-0.3.0}/tplus/cli_tools/options.py +0 -0
- {tpluspy-0.2.3 → tpluspy-0.3.0}/tplus/cli_tools/output.py +0 -0
- {tpluspy-0.2.3 → tpluspy-0.3.0}/tplus/client/blockchain.py +0 -0
- {tpluspy-0.2.3 → tpluspy-0.3.0}/tplus/client/clearingengine/__init__.py +0 -0
- {tpluspy-0.2.3 → tpluspy-0.3.0}/tplus/client/clearingengine/admin_settlement.py +0 -0
- {tpluspy-0.2.3 → tpluspy-0.3.0}/tplus/client/clearingengine/assetregistry.py +0 -0
- {tpluspy-0.2.3 → tpluspy-0.3.0}/tplus/client/clearingengine/base.py +0 -0
- {tpluspy-0.2.3 → tpluspy-0.3.0}/tplus/client/clearingengine/decimal.py +0 -0
- {tpluspy-0.2.3 → tpluspy-0.3.0}/tplus/client/clearingengine/vault.py +0 -0
- {tpluspy-0.2.3 → tpluspy-0.3.0}/tplus/client/oms/__init__.py +0 -0
- {tpluspy-0.2.3 → tpluspy-0.3.0}/tplus/client/withdrawal.py +0 -0
- {tpluspy-0.2.3 → tpluspy-0.3.0}/tplus/constants.py +0 -0
- {tpluspy-0.2.3 → tpluspy-0.3.0}/tplus/evm/__init__.py +0 -0
- {tpluspy-0.2.3 → tpluspy-0.3.0}/tplus/evm/abi.py +0 -0
- {tpluspy-0.2.3 → tpluspy-0.3.0}/tplus/evm/address.py +0 -0
- {tpluspy-0.2.3 → tpluspy-0.3.0}/tplus/evm/constants.py +0 -0
- {tpluspy-0.2.3 → tpluspy-0.3.0}/tplus/evm/dev/__init__.py +0 -0
- {tpluspy-0.2.3 → tpluspy-0.3.0}/tplus/evm/dev/contracts.py +0 -0
- {tpluspy-0.2.3 → tpluspy-0.3.0}/tplus/evm/dev/token.py +0 -0
- {tpluspy-0.2.3 → tpluspy-0.3.0}/tplus/evm/exceptions.py +0 -0
- {tpluspy-0.2.3 → tpluspy-0.3.0}/tplus/evm/managers/__init__.py +0 -0
- {tpluspy-0.2.3 → tpluspy-0.3.0}/tplus/evm/managers/chaindata.py +0 -0
- {tpluspy-0.2.3 → tpluspy-0.3.0}/tplus/evm/managers/credential_manager.py +0 -0
- {tpluspy-0.2.3 → tpluspy-0.3.0}/tplus/evm/managers/deposit.py +0 -0
- {tpluspy-0.2.3 → tpluspy-0.3.0}/tplus/evm/managers/evm.py +0 -0
- {tpluspy-0.2.3 → tpluspy-0.3.0}/tplus/evm/managers/registry.py +0 -0
- {tpluspy-0.2.3 → tpluspy-0.3.0}/tplus/evm/managers/vault.py +0 -0
- {tpluspy-0.2.3 → tpluspy-0.3.0}/tplus/evm/utils.py +0 -0
- {tpluspy-0.2.3 → tpluspy-0.3.0}/tplus/logger.py +0 -0
- {tpluspy-0.2.3 → tpluspy-0.3.0}/tplus/managers/__init__.py +0 -0
- {tpluspy-0.2.3 → tpluspy-0.3.0}/tplus/managers/base.py +0 -0
- {tpluspy-0.2.3 → tpluspy-0.3.0}/tplus/model/__init__.py +0 -0
- {tpluspy-0.2.3 → tpluspy-0.3.0}/tplus/model/approval.py +0 -0
- {tpluspy-0.2.3 → tpluspy-0.3.0}/tplus/model/asset_identifier.py +0 -0
- {tpluspy-0.2.3 → tpluspy-0.3.0}/tplus/model/batch_order.py +0 -0
- {tpluspy-0.2.3 → tpluspy-0.3.0}/tplus/model/close_all_positions_preview.py +0 -0
- {tpluspy-0.2.3 → tpluspy-0.3.0}/tplus/model/config.py +0 -0
- {tpluspy-0.2.3 → tpluspy-0.3.0}/tplus/model/interest_rates.py +0 -0
- {tpluspy-0.2.3 → tpluspy-0.3.0}/tplus/model/klines.py +0 -0
- {tpluspy-0.2.3 → tpluspy-0.3.0}/tplus/model/limit_order.py +0 -0
- {tpluspy-0.2.3 → tpluspy-0.3.0}/tplus/model/market.py +0 -0
- {tpluspy-0.2.3 → tpluspy-0.3.0}/tplus/model/market_order.py +0 -0
- {tpluspy-0.2.3 → tpluspy-0.3.0}/tplus/model/orderbook.py +0 -0
- {tpluspy-0.2.3 → tpluspy-0.3.0}/tplus/model/user_event.py +0 -0
- {tpluspy-0.2.3 → tpluspy-0.3.0}/tplus/model/user_inventory.py +0 -0
- {tpluspy-0.2.3 → tpluspy-0.3.0}/tplus/model/user_margin.py +0 -0
- {tpluspy-0.2.3 → tpluspy-0.3.0}/tplus/model/user_simulated_margin.py +0 -0
- {tpluspy-0.2.3 → tpluspy-0.3.0}/tplus/model/user_solvency.py +0 -0
- {tpluspy-0.2.3 → tpluspy-0.3.0}/tplus/model/withdrawal.py +0 -0
- {tpluspy-0.2.3 → tpluspy-0.3.0}/tplus/py.typed +0 -0
- {tpluspy-0.2.3 → tpluspy-0.3.0}/tplus/types.py +0 -0
- {tpluspy-0.2.3 → tpluspy-0.3.0}/tplus/utils/__init__.py +0 -0
- {tpluspy-0.2.3 → tpluspy-0.3.0}/tplus/utils/address.py +0 -0
- {tpluspy-0.2.3 → tpluspy-0.3.0}/tplus/utils/amount.py +0 -0
- {tpluspy-0.2.3 → tpluspy-0.3.0}/tplus/utils/bytes32.py +0 -0
- {tpluspy-0.2.3 → tpluspy-0.3.0}/tplus/utils/decimals.py +0 -0
- {tpluspy-0.2.3 → tpluspy-0.3.0}/tplus/utils/domain.py +0 -0
- {tpluspy-0.2.3 → tpluspy-0.3.0}/tplus/utils/hex.py +0 -0
- {tpluspy-0.2.3 → tpluspy-0.3.0}/tplus/utils/operator.py +0 -0
- {tpluspy-0.2.3 → tpluspy-0.3.0}/tplus/utils/serializers.py +0 -0
- {tpluspy-0.2.3 → tpluspy-0.3.0}/tplus/utils/timeout.py +0 -0
- {tpluspy-0.2.3 → tpluspy-0.3.0}/tplus/utils/user/decrypt.py +0 -0
- {tpluspy-0.2.3 → tpluspy-0.3.0}/tplus/utils/user/ed_keyfile.py +0 -0
- {tpluspy-0.2.3 → tpluspy-0.3.0}/tplus/utils/user/manager.py +0 -0
- {tpluspy-0.2.3 → tpluspy-0.3.0}/tplus/utils/user/token_cache.py +0 -0
- {tpluspy-0.2.3 → tpluspy-0.3.0}/tplus/utils/user/validate.py +0 -0
- {tpluspy-0.2.3 → tpluspy-0.3.0}/tpluspy.egg-info/dependency_links.txt +0 -0
- {tpluspy-0.2.3 → tpluspy-0.3.0}/tpluspy.egg-info/entry_points.txt +0 -0
- {tpluspy-0.2.3 → tpluspy-0.3.0}/tpluspy.egg-info/not-zip-safe +0 -0
- {tpluspy-0.2.3 → tpluspy-0.3.0}/tpluspy.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tpluspy
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: tpluspy: Client utilities for interacting with tplus
|
|
5
5
|
Home-page: https://github.com/tpluslabs/tpluspy
|
|
6
6
|
Author: TPlus Labs
|
|
@@ -37,7 +37,7 @@ Requires-Dist: anyio>=4; extra == "test"
|
|
|
37
37
|
Requires-Dist: pytest-anyio>=0.0.0; extra == "test"
|
|
38
38
|
Requires-Dist: trio>=0.24; extra == "test"
|
|
39
39
|
Provides-Extra: lint
|
|
40
|
-
Requires-Dist: ruff
|
|
40
|
+
Requires-Dist: ruff<0.16,>=0.11.7; extra == "lint"
|
|
41
41
|
Requires-Dist: mypy<2,>=1.18.2; extra == "lint"
|
|
42
42
|
Provides-Extra: release
|
|
43
43
|
Requires-Dist: setuptools>=75.6.0; extra == "release"
|
|
@@ -81,6 +81,28 @@ To install, use either `pip` or `uv pip`:
|
|
|
81
81
|
uv pip install -e .
|
|
82
82
|
```
|
|
83
83
|
|
|
84
|
+
## Onboarding with Codex CLI
|
|
85
|
+
|
|
86
|
+
This repo includes a Codex-discoverable onboarding skill at
|
|
87
|
+
`.agents/skills/onboard` for connecting an EVM wallet key to T+, checking whether
|
|
88
|
+
it already controls a T+ account, and guiding account creation when needed.
|
|
89
|
+
|
|
90
|
+
From the repo root:
|
|
91
|
+
|
|
92
|
+
```text
|
|
93
|
+
codex
|
|
94
|
+
/skills
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Choose `onboard`, or invoke it directly with:
|
|
98
|
+
|
|
99
|
+
```text
|
|
100
|
+
$onboard onboard me to T+
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
The flow reads `TPLUS_PRIVATE_KEY` and `TPLUS_API_BASE_URL` from `.env`. Do not
|
|
104
|
+
paste private keys into chat.
|
|
105
|
+
|
|
84
106
|
## CLI
|
|
85
107
|
|
|
86
108
|
`tpluspy` installs a `tplus` command for interacting with T+ services from the
|
|
@@ -109,12 +131,14 @@ from tplus.utils.user import User
|
|
|
109
131
|
API_BASE_URL = "http://127.0.0.1:8000" # Replace with your API URL
|
|
110
132
|
user = User()
|
|
111
133
|
|
|
134
|
+
|
|
112
135
|
async def run_client():
|
|
113
136
|
# Use async context manager for automatic cleanup
|
|
114
137
|
async with OrderBookClient(API_BASE_URL, default_user=user) as client:
|
|
115
138
|
print("Client initialized.")
|
|
116
139
|
# ... use client methods ...
|
|
117
140
|
|
|
141
|
+
|
|
118
142
|
asyncio.run(run_client())
|
|
119
143
|
```
|
|
120
144
|
|
|
@@ -138,14 +162,19 @@ print(f"Klines: {klines}")
|
|
|
138
162
|
|
|
139
163
|
# Get Market Details for an asset
|
|
140
164
|
market_details = await client.get_market(example_asset)
|
|
141
|
-
print(
|
|
165
|
+
print(
|
|
166
|
+
f"Market Details: Price Decimals={market_details.book_price_decimals}, Quantity Decimals={market_details.book_quantity_decimals}"
|
|
167
|
+
)
|
|
142
168
|
|
|
143
169
|
# Get orders for the user
|
|
144
170
|
user_orders, _ = await client.get_user_orders()
|
|
145
171
|
print(f"User Orders: {user_orders}")
|
|
146
172
|
|
|
147
|
-
# Get trades for the user and asset
|
|
148
|
-
|
|
173
|
+
# Get trades for the user and asset (per-user trades live on the market-data-service)
|
|
174
|
+
from tplus.client import MarketDataClient
|
|
175
|
+
|
|
176
|
+
md_client = MarketDataClient("http://localhost:8011", default_user=user)
|
|
177
|
+
user_asset_trades = await md_client.get_user_trades_for_asset(example_asset, user=user)
|
|
149
178
|
print(f"User Asset Trades: {user_asset_trades}")
|
|
150
179
|
|
|
151
180
|
# Get user inventory
|
|
@@ -158,6 +187,7 @@ print(f"Inventory: {inventory}")
|
|
|
158
187
|
```python
|
|
159
188
|
# Ensure example_asset is defined (e.g., from "Fetching Data" section)
|
|
160
189
|
from tplus.model.asset_identifier import AssetIdentifier
|
|
190
|
+
|
|
161
191
|
example_asset = AssetIdentifier(200)
|
|
162
192
|
|
|
163
193
|
# Create a Market for an asset (idempotent)
|
|
@@ -176,6 +206,7 @@ print(f"Market Order Response: {market_response}")
|
|
|
176
206
|
# Create a Limit Order for a specific asset
|
|
177
207
|
# Good-Till-Cancelled limit order
|
|
178
208
|
from tplus.model.limit_order import GTC
|
|
209
|
+
|
|
179
210
|
limit_response = await client.create_limit_order(
|
|
180
211
|
asset_id=example_asset,
|
|
181
212
|
quantity=5,
|
|
@@ -188,10 +219,7 @@ print(f"Limit Order Response: {limit_response}")
|
|
|
188
219
|
# Cancel an Order
|
|
189
220
|
# Order ID should be obtained from an order creation response.
|
|
190
221
|
order_id_to_cancel = "actual-order-id-from-api" # Replace with a real order ID
|
|
191
|
-
cancel_response = await client.cancel_order(
|
|
192
|
-
order_id=order_id_to_cancel,
|
|
193
|
-
asset_id=example_asset
|
|
194
|
-
)
|
|
222
|
+
cancel_response = await client.cancel_order(order_id=order_id_to_cancel, asset_id=example_asset)
|
|
195
223
|
print(f"Cancel Order Response: {cancel_response}")
|
|
196
224
|
|
|
197
225
|
# Replace an Order
|
|
@@ -200,8 +228,8 @@ original_order_id_to_replace = "actual-original-order-id" # Replace with a real
|
|
|
200
228
|
replace_response = await client.replace_order(
|
|
201
229
|
original_order_id=original_order_id_to_replace,
|
|
202
230
|
asset_id=example_asset,
|
|
203
|
-
new_quantity=6,
|
|
204
|
-
new_price=1050
|
|
231
|
+
new_quantity=6, # Optional: New integer quantity
|
|
232
|
+
new_price=1050, # Optional: New integer price
|
|
205
233
|
)
|
|
206
234
|
print(f"Replace Order Response: {replace_response}")
|
|
207
235
|
```
|
|
@@ -222,14 +250,16 @@ example_asset = AssetIdentifier(200)
|
|
|
222
250
|
# Stream Order Book Diffs
|
|
223
251
|
async for diff_update in client.stream_depth(example_asset):
|
|
224
252
|
if isinstance(diff_update, OrderBookDiff):
|
|
225
|
-
print(
|
|
253
|
+
print(
|
|
254
|
+
f"[Depth] Seq={diff_update.sequence_number}, Asks={len(diff_update.asks)}, Bids={len(diff_update.bids)}"
|
|
255
|
+
)
|
|
226
256
|
# Add logic to handle the update, e.g., update a local order book
|
|
227
257
|
|
|
228
258
|
# Stream Finalized Trades
|
|
229
259
|
async for trade in client.stream_finalized_trades():
|
|
230
|
-
|
|
260
|
+
if isinstance(trade, Trade):
|
|
231
261
|
print(f"[Trade] ID: {trade.trade_id}, Price: {trade.price}, Qty: {trade.quantity}")
|
|
232
|
-
|
|
262
|
+
# Add logic to handle the trade
|
|
233
263
|
|
|
234
264
|
# Other available streams:
|
|
235
265
|
# client.stream_orders() -> OrderEvent
|
|
@@ -259,10 +289,16 @@ ape console --network ethereum:sepolia:alchemy
|
|
|
259
289
|
Then, once in the console, you will already have access to contracts that you can call methods on:
|
|
260
290
|
|
|
261
291
|
```python
|
|
262
|
-
In
|
|
263
|
-
Out[1]: [
|
|
264
|
-
|
|
265
|
-
|
|
292
|
+
In[1]: registry.getAssets()
|
|
293
|
+
Out[1]: [
|
|
294
|
+
getAssets_return(
|
|
295
|
+
assetAddress=HexBytes("0x000000000000000000000000f08a50178dfcde18524640ea6618a1f965821715"),
|
|
296
|
+
chainId=11155111,
|
|
297
|
+
maxDeposits=100,
|
|
298
|
+
)
|
|
299
|
+
]
|
|
300
|
+
In[2]: registry.admin()
|
|
301
|
+
Out[2]: "0x467a95fC5359edE5d5dDc4f10A1F4B680694858E"
|
|
266
302
|
```
|
|
267
303
|
|
|
268
304
|
#### Settlement signatures
|
|
@@ -10,6 +10,28 @@ To install, use either `pip` or `uv pip`:
|
|
|
10
10
|
uv pip install -e .
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
+
## Onboarding with Codex CLI
|
|
14
|
+
|
|
15
|
+
This repo includes a Codex-discoverable onboarding skill at
|
|
16
|
+
`.agents/skills/onboard` for connecting an EVM wallet key to T+, checking whether
|
|
17
|
+
it already controls a T+ account, and guiding account creation when needed.
|
|
18
|
+
|
|
19
|
+
From the repo root:
|
|
20
|
+
|
|
21
|
+
```text
|
|
22
|
+
codex
|
|
23
|
+
/skills
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Choose `onboard`, or invoke it directly with:
|
|
27
|
+
|
|
28
|
+
```text
|
|
29
|
+
$onboard onboard me to T+
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
The flow reads `TPLUS_PRIVATE_KEY` and `TPLUS_API_BASE_URL` from `.env`. Do not
|
|
33
|
+
paste private keys into chat.
|
|
34
|
+
|
|
13
35
|
## CLI
|
|
14
36
|
|
|
15
37
|
`tpluspy` installs a `tplus` command for interacting with T+ services from the
|
|
@@ -38,12 +60,14 @@ from tplus.utils.user import User
|
|
|
38
60
|
API_BASE_URL = "http://127.0.0.1:8000" # Replace with your API URL
|
|
39
61
|
user = User()
|
|
40
62
|
|
|
63
|
+
|
|
41
64
|
async def run_client():
|
|
42
65
|
# Use async context manager for automatic cleanup
|
|
43
66
|
async with OrderBookClient(API_BASE_URL, default_user=user) as client:
|
|
44
67
|
print("Client initialized.")
|
|
45
68
|
# ... use client methods ...
|
|
46
69
|
|
|
70
|
+
|
|
47
71
|
asyncio.run(run_client())
|
|
48
72
|
```
|
|
49
73
|
|
|
@@ -67,14 +91,19 @@ print(f"Klines: {klines}")
|
|
|
67
91
|
|
|
68
92
|
# Get Market Details for an asset
|
|
69
93
|
market_details = await client.get_market(example_asset)
|
|
70
|
-
print(
|
|
94
|
+
print(
|
|
95
|
+
f"Market Details: Price Decimals={market_details.book_price_decimals}, Quantity Decimals={market_details.book_quantity_decimals}"
|
|
96
|
+
)
|
|
71
97
|
|
|
72
98
|
# Get orders for the user
|
|
73
99
|
user_orders, _ = await client.get_user_orders()
|
|
74
100
|
print(f"User Orders: {user_orders}")
|
|
75
101
|
|
|
76
|
-
# Get trades for the user and asset
|
|
77
|
-
|
|
102
|
+
# Get trades for the user and asset (per-user trades live on the market-data-service)
|
|
103
|
+
from tplus.client import MarketDataClient
|
|
104
|
+
|
|
105
|
+
md_client = MarketDataClient("http://localhost:8011", default_user=user)
|
|
106
|
+
user_asset_trades = await md_client.get_user_trades_for_asset(example_asset, user=user)
|
|
78
107
|
print(f"User Asset Trades: {user_asset_trades}")
|
|
79
108
|
|
|
80
109
|
# Get user inventory
|
|
@@ -87,6 +116,7 @@ print(f"Inventory: {inventory}")
|
|
|
87
116
|
```python
|
|
88
117
|
# Ensure example_asset is defined (e.g., from "Fetching Data" section)
|
|
89
118
|
from tplus.model.asset_identifier import AssetIdentifier
|
|
119
|
+
|
|
90
120
|
example_asset = AssetIdentifier(200)
|
|
91
121
|
|
|
92
122
|
# Create a Market for an asset (idempotent)
|
|
@@ -105,6 +135,7 @@ print(f"Market Order Response: {market_response}")
|
|
|
105
135
|
# Create a Limit Order for a specific asset
|
|
106
136
|
# Good-Till-Cancelled limit order
|
|
107
137
|
from tplus.model.limit_order import GTC
|
|
138
|
+
|
|
108
139
|
limit_response = await client.create_limit_order(
|
|
109
140
|
asset_id=example_asset,
|
|
110
141
|
quantity=5,
|
|
@@ -117,10 +148,7 @@ print(f"Limit Order Response: {limit_response}")
|
|
|
117
148
|
# Cancel an Order
|
|
118
149
|
# Order ID should be obtained from an order creation response.
|
|
119
150
|
order_id_to_cancel = "actual-order-id-from-api" # Replace with a real order ID
|
|
120
|
-
cancel_response = await client.cancel_order(
|
|
121
|
-
order_id=order_id_to_cancel,
|
|
122
|
-
asset_id=example_asset
|
|
123
|
-
)
|
|
151
|
+
cancel_response = await client.cancel_order(order_id=order_id_to_cancel, asset_id=example_asset)
|
|
124
152
|
print(f"Cancel Order Response: {cancel_response}")
|
|
125
153
|
|
|
126
154
|
# Replace an Order
|
|
@@ -129,8 +157,8 @@ original_order_id_to_replace = "actual-original-order-id" # Replace with a real
|
|
|
129
157
|
replace_response = await client.replace_order(
|
|
130
158
|
original_order_id=original_order_id_to_replace,
|
|
131
159
|
asset_id=example_asset,
|
|
132
|
-
new_quantity=6,
|
|
133
|
-
new_price=1050
|
|
160
|
+
new_quantity=6, # Optional: New integer quantity
|
|
161
|
+
new_price=1050, # Optional: New integer price
|
|
134
162
|
)
|
|
135
163
|
print(f"Replace Order Response: {replace_response}")
|
|
136
164
|
```
|
|
@@ -151,14 +179,16 @@ example_asset = AssetIdentifier(200)
|
|
|
151
179
|
# Stream Order Book Diffs
|
|
152
180
|
async for diff_update in client.stream_depth(example_asset):
|
|
153
181
|
if isinstance(diff_update, OrderBookDiff):
|
|
154
|
-
print(
|
|
182
|
+
print(
|
|
183
|
+
f"[Depth] Seq={diff_update.sequence_number}, Asks={len(diff_update.asks)}, Bids={len(diff_update.bids)}"
|
|
184
|
+
)
|
|
155
185
|
# Add logic to handle the update, e.g., update a local order book
|
|
156
186
|
|
|
157
187
|
# Stream Finalized Trades
|
|
158
188
|
async for trade in client.stream_finalized_trades():
|
|
159
|
-
|
|
189
|
+
if isinstance(trade, Trade):
|
|
160
190
|
print(f"[Trade] ID: {trade.trade_id}, Price: {trade.price}, Qty: {trade.quantity}")
|
|
161
|
-
|
|
191
|
+
# Add logic to handle the trade
|
|
162
192
|
|
|
163
193
|
# Other available streams:
|
|
164
194
|
# client.stream_orders() -> OrderEvent
|
|
@@ -188,10 +218,16 @@ ape console --network ethereum:sepolia:alchemy
|
|
|
188
218
|
Then, once in the console, you will already have access to contracts that you can call methods on:
|
|
189
219
|
|
|
190
220
|
```python
|
|
191
|
-
In
|
|
192
|
-
Out[1]: [
|
|
193
|
-
|
|
194
|
-
|
|
221
|
+
In[1]: registry.getAssets()
|
|
222
|
+
Out[1]: [
|
|
223
|
+
getAssets_return(
|
|
224
|
+
assetAddress=HexBytes("0x000000000000000000000000f08a50178dfcde18524640ea6618a1f965821715"),
|
|
225
|
+
chainId=11155111,
|
|
226
|
+
maxDeposits=100,
|
|
227
|
+
)
|
|
228
|
+
]
|
|
229
|
+
In[2]: registry.admin()
|
|
230
|
+
Out[2]: "0x467a95fC5359edE5d5dDc4f10A1F4B680694858E"
|
|
195
231
|
```
|
|
196
232
|
|
|
197
233
|
#### Settlement signatures
|
|
@@ -8,7 +8,7 @@ long_description = (here / "README.md").read_text(encoding="utf-8")
|
|
|
8
8
|
|
|
9
9
|
setup(
|
|
10
10
|
name="tpluspy",
|
|
11
|
-
version="0.
|
|
11
|
+
version="0.3.0",
|
|
12
12
|
description="""tpluspy: Client utilities for interacting with tplus""",
|
|
13
13
|
long_description=long_description,
|
|
14
14
|
long_description_content_type="text/markdown",
|
|
@@ -43,7 +43,7 @@ setup(
|
|
|
43
43
|
"trio>=0.24",
|
|
44
44
|
],
|
|
45
45
|
"lint": [
|
|
46
|
-
"ruff>=0.11.7",
|
|
46
|
+
"ruff>=0.11.7,<0.16",
|
|
47
47
|
"mypy>=1.18.2,<2",
|
|
48
48
|
],
|
|
49
49
|
"release": [
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
from tplus.client import TplusApiClient
|
|
1
2
|
from tplus.exceptions import (
|
|
2
3
|
AuthError,
|
|
3
4
|
NotFoundError,
|
|
@@ -5,6 +6,7 @@ from tplus.exceptions import (
|
|
|
5
6
|
OrderRejected,
|
|
6
7
|
RateLimitError,
|
|
7
8
|
ServerError,
|
|
9
|
+
SignerRegistryUnavailable,
|
|
8
10
|
)
|
|
9
11
|
|
|
10
12
|
__all__ = [
|
|
@@ -14,4 +16,6 @@ __all__ = [
|
|
|
14
16
|
"OrderRejected",
|
|
15
17
|
"RateLimitError",
|
|
16
18
|
"ServerError",
|
|
19
|
+
"SignerRegistryUnavailable",
|
|
20
|
+
"TplusApiClient",
|
|
17
21
|
]
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import os
|
|
1
2
|
from collections.abc import Callable
|
|
2
3
|
from functools import cached_property
|
|
3
4
|
from pathlib import Path
|
|
4
5
|
from typing import TYPE_CHECKING
|
|
6
|
+
from urllib.parse import urlsplit, urlunsplit
|
|
5
7
|
|
|
6
8
|
import click
|
|
7
9
|
|
|
@@ -18,12 +20,38 @@ if TYPE_CHECKING:
|
|
|
18
20
|
_AUTH_CACHE_DIR = Path.home() / ".tplus" / "auth"
|
|
19
21
|
|
|
20
22
|
|
|
23
|
+
def _derive_market_data_base_url(oms_base_url: str) -> str:
|
|
24
|
+
"""Derive the market-data-service URL from the OMS URL (swap the host label
|
|
25
|
+
``oms``->``mds``). Returns the input unchanged for non-oms hosts (e.g. localhost)."""
|
|
26
|
+
parsed = urlsplit(oms_base_url)
|
|
27
|
+
host = parsed.hostname
|
|
28
|
+
if host is None:
|
|
29
|
+
return oms_base_url
|
|
30
|
+
if host.startswith("oms."):
|
|
31
|
+
mds_host = f"mds.{host.removeprefix('oms.')}"
|
|
32
|
+
elif host.startswith("oms-"):
|
|
33
|
+
mds_host = f"mds-{host.removeprefix('oms-')}"
|
|
34
|
+
else:
|
|
35
|
+
return oms_base_url
|
|
36
|
+
userinfo = ""
|
|
37
|
+
if parsed.username is not None:
|
|
38
|
+
userinfo = parsed.username
|
|
39
|
+
if parsed.password is not None:
|
|
40
|
+
userinfo = f"{userinfo}:{parsed.password}"
|
|
41
|
+
userinfo = f"{userinfo}@"
|
|
42
|
+
port = f":{parsed.port}" if parsed.port is not None else ""
|
|
43
|
+
return urlunsplit(
|
|
44
|
+
(parsed.scheme, f"{userinfo}{mds_host}{port}", parsed.path, parsed.query, parsed.fragment)
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
|
|
21
48
|
# ``dict`` base mirrors ``ape.cli.options.ApeCliContextObject`` so ape's
|
|
22
49
|
# ``network_option`` callback can do ``ctx.obj["network"] = value`` without
|
|
23
50
|
# importing ape here (which would slow down ``tplus --help``).
|
|
24
51
|
class CLIContext(dict):
|
|
25
52
|
def __init__(self):
|
|
26
53
|
super().__init__()
|
|
54
|
+
self.api_base_url: str | None = None
|
|
27
55
|
self.orderbook_base_url: str | None = None
|
|
28
56
|
self.clearing_base_url: str | None = None
|
|
29
57
|
self.market_data_base_url: str | None = None
|
|
@@ -51,22 +79,44 @@ class CLIContext(dict):
|
|
|
51
79
|
manager = self.user_manager
|
|
52
80
|
return manager._default_user or next(iter(manager.usernames), None)
|
|
53
81
|
|
|
54
|
-
def
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
82
|
+
def _api_base_url(self) -> str | None:
|
|
83
|
+
"""The single T+ base URL (the OMS). Per-service URLs derive from it; an
|
|
84
|
+
explicit per-service flag / env var overrides it."""
|
|
85
|
+
return self.api_base_url or os.environ.get("TPLUS_API_BASE_URL")
|
|
86
|
+
|
|
87
|
+
def _resolved_orderbook_url(self) -> str:
|
|
88
|
+
# The OMS host *is* the orderbook service, so TPLUS_API_BASE_URL is used directly.
|
|
89
|
+
url = self.orderbook_base_url or self._api_base_url()
|
|
90
|
+
if not url:
|
|
58
91
|
raise click.UsageError(
|
|
59
|
-
"No
|
|
60
|
-
"
|
|
92
|
+
"No T+ base URL. Set TPLUS_API_BASE_URL (the OMS URL — the single T+ URL "
|
|
93
|
+
"everything derives from), or pass --orderbook-base-url / set "
|
|
94
|
+
"TPLUS_ORDERBOOK_BASE_URL to override."
|
|
61
95
|
)
|
|
96
|
+
return url
|
|
62
97
|
|
|
98
|
+
def _resolved_market_data_url(self) -> str:
|
|
99
|
+
if self.market_data_base_url:
|
|
100
|
+
return self.market_data_base_url
|
|
101
|
+
api = self._api_base_url()
|
|
102
|
+
if not api:
|
|
103
|
+
raise click.UsageError(
|
|
104
|
+
"No T+ base URL. Set TPLUS_API_BASE_URL (the market-data URL is derived "
|
|
105
|
+
"by swapping the host label oms->mds), or pass --market-data-base-url / "
|
|
106
|
+
"set TPLUS_MARKET_DATA_BASE_URL to override."
|
|
107
|
+
)
|
|
108
|
+
return _derive_market_data_base_url(api)
|
|
109
|
+
|
|
110
|
+
def orderbook_client(
|
|
111
|
+
self, alias: str | None = None, *, anonymous: bool = False
|
|
112
|
+
) -> "OrderBookClient":
|
|
63
113
|
from tplus.client.auth import Auth
|
|
64
114
|
from tplus.client.orderbook import OrderBookClient
|
|
65
115
|
from tplus.utils.user.model import User
|
|
66
116
|
|
|
67
117
|
user = User() if anonymous else self.load_user(alias)
|
|
68
118
|
return OrderBookClient(
|
|
69
|
-
base_url=self.
|
|
119
|
+
base_url=self._resolved_orderbook_url(),
|
|
70
120
|
default_user=user,
|
|
71
121
|
auth=Auth(cache_dir=_AUTH_CACHE_DIR),
|
|
72
122
|
insecure_ssl=self.ignore_ssl,
|
|
@@ -92,12 +142,7 @@ class CLIContext(dict):
|
|
|
92
142
|
)
|
|
93
143
|
|
|
94
144
|
def _oms_base_url(self) -> str:
|
|
95
|
-
|
|
96
|
-
raise click.UsageError(
|
|
97
|
-
"No orderbook base URL specified. "
|
|
98
|
-
"Pass --orderbook-base-url or set TPLUS_ORDERBOOK_BASE_URL."
|
|
99
|
-
)
|
|
100
|
-
return self.orderbook_base_url
|
|
145
|
+
return self._resolved_orderbook_url()
|
|
101
146
|
|
|
102
147
|
def withdrawal_client(self, alias: str | None = None) -> "WithdrawalClient":
|
|
103
148
|
from tplus.client.withdrawal import WithdrawalClient
|
|
@@ -119,17 +164,22 @@ class CLIContext(dict):
|
|
|
119
164
|
|
|
120
165
|
return BlockchainClient(base_url=self.blockchain_base_url, insecure_ssl=self.ignore_ssl)
|
|
121
166
|
|
|
122
|
-
def market_data_client(
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
"Pass --market-data-base-url or set TPLUS_MARKET_DATA_BASE_URL."
|
|
127
|
-
)
|
|
128
|
-
|
|
167
|
+
def market_data_client(
|
|
168
|
+
self, alias: str | None = None, *, authed: bool = False
|
|
169
|
+
) -> "MarketDataClient":
|
|
170
|
+
from tplus.client.auth import Auth
|
|
129
171
|
from tplus.client.market_data import MarketDataClient
|
|
130
172
|
|
|
173
|
+
if authed:
|
|
174
|
+
return MarketDataClient(
|
|
175
|
+
base_url=self._resolved_market_data_url(),
|
|
176
|
+
default_user=self.load_user(alias),
|
|
177
|
+
auth=Auth(cache_dir=_AUTH_CACHE_DIR),
|
|
178
|
+
insecure_ssl=self.ignore_ssl,
|
|
179
|
+
)
|
|
180
|
+
|
|
131
181
|
return MarketDataClient(
|
|
132
|
-
base_url=self.
|
|
182
|
+
base_url=self._resolved_market_data_url(),
|
|
133
183
|
insecure_ssl=self.ignore_ssl,
|
|
134
184
|
)
|
|
135
185
|
|
|
@@ -20,7 +20,17 @@ def env(output_format: str):
|
|
|
20
20
|
"""Show CLI-relevant environment variables and their current values."""
|
|
21
21
|
entries: list[tuple[str, str, str]] = [
|
|
22
22
|
("TPLUS_ACCOUNT", _default_account(), "Default local account alias."),
|
|
23
|
-
(
|
|
23
|
+
(
|
|
24
|
+
"TPLUS_API_BASE_URL",
|
|
25
|
+
"",
|
|
26
|
+
"Single T+ base URL (OMS); orderbook + market-data derive from it.",
|
|
27
|
+
),
|
|
28
|
+
("TPLUS_ORDERBOOK_BASE_URL", "", "Override: orderbook service base URL."),
|
|
29
|
+
(
|
|
30
|
+
"TPLUS_MARKET_DATA_BASE_URL",
|
|
31
|
+
"",
|
|
32
|
+
"Override: market-data URL (else derived from API URL, oms->mds).",
|
|
33
|
+
),
|
|
24
34
|
("TPLUS_CLEARING_BASE_URL", "", "Clearing engine base URL."),
|
|
25
35
|
("TPLUS_IGNORE_SSL", "false", "Skip TLS certificate verification."),
|
|
26
36
|
("TPLUS_OUTPUT_FORMAT", "table", "Default output format (table | json)."),
|
|
@@ -11,6 +11,7 @@ from tplus._cli._context import (
|
|
|
11
11
|
pass_cli_context,
|
|
12
12
|
tplus_account_option,
|
|
13
13
|
)
|
|
14
|
+
from tplus.asset_metadata import asset_metadata_dict
|
|
14
15
|
from tplus.cli_tools import (
|
|
15
16
|
echo_with_pager,
|
|
16
17
|
ignore_ssl_option,
|
|
@@ -71,9 +72,13 @@ def _list(cli_ctx: CLIContext, output_format: str, no_pager: bool):
|
|
|
71
72
|
records = []
|
|
72
73
|
for market in response or []:
|
|
73
74
|
fee_schedule = market.get("fee_schedule") or {}
|
|
75
|
+
asset_metadata = asset_metadata_dict(market.get("asset_id")) or {}
|
|
74
76
|
records.append(
|
|
75
77
|
{
|
|
76
78
|
"asset_id": market.get("asset_id"),
|
|
79
|
+
"symbol": asset_metadata.get("symbol"),
|
|
80
|
+
"asset_class": asset_metadata.get("asset_class"),
|
|
81
|
+
"representations": asset_metadata.get("representations"),
|
|
77
82
|
"price_decimals": market.get("book_price_decimals"),
|
|
78
83
|
"quantity_decimals": market.get("book_quantity_decimals"),
|
|
79
84
|
"max_leverage": market.get("max_leverage"),
|
|
@@ -94,20 +94,26 @@ def _cancel(cli_ctx: CLIContext, order_id: str, asset_id: str):
|
|
|
94
94
|
@tplus_account_option()
|
|
95
95
|
@click.argument("order_id")
|
|
96
96
|
@click.option("--asset", "asset_id", required=True, help="Asset identifier.")
|
|
97
|
-
@click.option(
|
|
98
|
-
|
|
97
|
+
@click.option(
|
|
98
|
+
"--quantity",
|
|
99
|
+
type=int,
|
|
100
|
+
required=True,
|
|
101
|
+
help="Effective lifetime-total base quantity after the replace.",
|
|
102
|
+
)
|
|
103
|
+
@click.option("--price", type=int, required=True, help="Effective price after the replace.")
|
|
99
104
|
@pass_cli_context
|
|
100
105
|
def _replace(
|
|
101
106
|
cli_ctx: CLIContext,
|
|
102
107
|
order_id: str,
|
|
103
108
|
asset_id: str,
|
|
104
|
-
quantity: int
|
|
105
|
-
price: int
|
|
109
|
+
quantity: int,
|
|
110
|
+
price: int,
|
|
106
111
|
):
|
|
107
|
-
"""Replace ORDER_ID with new parameters.
|
|
108
|
-
if quantity is None and price is None:
|
|
109
|
-
raise click.UsageError("Pass --quantity and/or --price.")
|
|
112
|
+
"""Replace ORDER_ID with new parameters.
|
|
110
113
|
|
|
114
|
+
A replacement is signed over its complete effective terms, so both --quantity and
|
|
115
|
+
--price are required even when only one of them changes.
|
|
116
|
+
"""
|
|
111
117
|
from tplus.model.asset_identifier import AssetIdentifier
|
|
112
118
|
|
|
113
119
|
client = cli_ctx.orderbook_client()
|
|
@@ -4,6 +4,7 @@ import click
|
|
|
4
4
|
|
|
5
5
|
from tplus._cli._context import (
|
|
6
6
|
CLIContext,
|
|
7
|
+
market_data_url_option,
|
|
7
8
|
orderbook_url_option,
|
|
8
9
|
pass_cli_context,
|
|
9
10
|
tplus_account_option,
|
|
@@ -23,6 +24,7 @@ def trades():
|
|
|
23
24
|
|
|
24
25
|
|
|
25
26
|
@trades.command("list")
|
|
27
|
+
@market_data_url_option()
|
|
26
28
|
@orderbook_url_option()
|
|
27
29
|
@ignore_ssl_option()
|
|
28
30
|
@tplus_account_option()
|
|
@@ -34,11 +36,12 @@ def _list(cli_ctx: CLIContext, output_format: str, no_pager: bool, asset_id: str
|
|
|
34
36
|
"""List user trades."""
|
|
35
37
|
from tplus.model.asset_identifier import AssetIdentifier
|
|
36
38
|
|
|
37
|
-
client = cli_ctx.
|
|
39
|
+
client = cli_ctx.market_data_client(authed=True)
|
|
40
|
+
user = cli_ctx.load_user()
|
|
38
41
|
if asset_id:
|
|
39
|
-
result = asyncio.run(client.get_user_trades_for_asset(AssetIdentifier(asset_id)))
|
|
42
|
+
result = asyncio.run(client.get_user_trades_for_asset(AssetIdentifier(asset_id), user=user))
|
|
40
43
|
else:
|
|
41
|
-
result = asyncio.run(client.get_user_trades())
|
|
44
|
+
result = asyncio.run(client.get_user_trades(user=user))
|
|
42
45
|
|
|
43
46
|
if not result:
|
|
44
47
|
click.echo("No trades.")
|
|
@@ -36,11 +36,8 @@ def vaults():
|
|
|
36
36
|
|
|
37
37
|
|
|
38
38
|
def _list_via_oms(cli_ctx: CLIContext, output_format: str, no_pager: bool):
|
|
39
|
-
from tplus.model.chain_address import ChainAddress
|
|
40
|
-
|
|
41
39
|
client = cli_ctx.orderbook_client(anonymous=True)
|
|
42
|
-
|
|
43
|
-
addresses = [ChainAddress.model_validate(a) for a in raw]
|
|
40
|
+
addresses = asyncio.run(client.assets.get_vaults())
|
|
44
41
|
|
|
45
42
|
if output_format == "raw":
|
|
46
43
|
echo_with_pager([str(a) for a in addresses], no_pager=no_pager)
|