tpluspy 0.2.3__tar.gz → 0.2.4__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.
Files changed (133) hide show
  1. {tpluspy-0.2.3 → tpluspy-0.2.4}/PKG-INFO +23 -1
  2. {tpluspy-0.2.3 → tpluspy-0.2.4}/README.md +22 -0
  3. {tpluspy-0.2.3 → tpluspy-0.2.4}/setup.py +1 -1
  4. {tpluspy-0.2.3 → tpluspy-0.2.4}/tplus/_cli/_context.py +59 -20
  5. {tpluspy-0.2.3 → tpluspy-0.2.4}/tplus/_cli/env.py +11 -1
  6. {tpluspy-0.2.3 → tpluspy-0.2.4}/tplus/_cli/markets.py +5 -0
  7. tpluspy-0.2.4/tplus/asset_metadata.py +116 -0
  8. {tpluspy-0.2.3 → tpluspy-0.2.4}/tpluspy.egg-info/PKG-INFO +23 -1
  9. {tpluspy-0.2.3 → tpluspy-0.2.4}/tpluspy.egg-info/SOURCES.txt +1 -0
  10. {tpluspy-0.2.3 → tpluspy-0.2.4}/pyproject.toml +0 -0
  11. {tpluspy-0.2.3 → tpluspy-0.2.4}/setup.cfg +0 -0
  12. {tpluspy-0.2.3 → tpluspy-0.2.4}/tplus/__init__.py +0 -0
  13. {tpluspy-0.2.3 → tpluspy-0.2.4}/tplus/_cli/__init__.py +0 -0
  14. {tpluspy-0.2.3 → tpluspy-0.2.4}/tplus/_cli/__main__.py +0 -0
  15. {tpluspy-0.2.3 → tpluspy-0.2.4}/tplus/_cli/accounts.py +0 -0
  16. {tpluspy-0.2.3 → tpluspy-0.2.4}/tplus/_cli/assets.py +0 -0
  17. {tpluspy-0.2.3 → tpluspy-0.2.4}/tplus/_cli/balance.py +0 -0
  18. {tpluspy-0.2.3 → tpluspy-0.2.4}/tplus/_cli/debug.py +0 -0
  19. {tpluspy-0.2.3 → tpluspy-0.2.4}/tplus/_cli/decimals.py +0 -0
  20. {tpluspy-0.2.3 → tpluspy-0.2.4}/tplus/_cli/deposit.py +0 -0
  21. {tpluspy-0.2.3 → tpluspy-0.2.4}/tplus/_cli/orders.py +0 -0
  22. {tpluspy-0.2.3 → tpluspy-0.2.4}/tplus/_cli/params.py +0 -0
  23. {tpluspy-0.2.3 → tpluspy-0.2.4}/tplus/_cli/settle.py +0 -0
  24. {tpluspy-0.2.3 → tpluspy-0.2.4}/tplus/_cli/sign.py +0 -0
  25. {tpluspy-0.2.3 → tpluspy-0.2.4}/tplus/_cli/stream.py +0 -0
  26. {tpluspy-0.2.3 → tpluspy-0.2.4}/tplus/_cli/trades.py +0 -0
  27. {tpluspy-0.2.3 → tpluspy-0.2.4}/tplus/_cli/vaults.py +0 -0
  28. {tpluspy-0.2.3 → tpluspy-0.2.4}/tplus/_cli/withdraw.py +0 -0
  29. {tpluspy-0.2.3 → tpluspy-0.2.4}/tplus/_cli/withdrawal.py +0 -0
  30. {tpluspy-0.2.3 → tpluspy-0.2.4}/tplus/cli_tools/__init__.py +0 -0
  31. {tpluspy-0.2.3 → tpluspy-0.2.4}/tplus/cli_tools/options.py +0 -0
  32. {tpluspy-0.2.3 → tpluspy-0.2.4}/tplus/cli_tools/output.py +0 -0
  33. {tpluspy-0.2.3 → tpluspy-0.2.4}/tplus/client/__init__.py +0 -0
  34. {tpluspy-0.2.3 → tpluspy-0.2.4}/tplus/client/auth.py +0 -0
  35. {tpluspy-0.2.3 → tpluspy-0.2.4}/tplus/client/base.py +0 -0
  36. {tpluspy-0.2.3 → tpluspy-0.2.4}/tplus/client/blockchain.py +0 -0
  37. {tpluspy-0.2.3 → tpluspy-0.2.4}/tplus/client/clearingengine/__init__.py +0 -0
  38. {tpluspy-0.2.3 → tpluspy-0.2.4}/tplus/client/clearingengine/admin.py +0 -0
  39. {tpluspy-0.2.3 → tpluspy-0.2.4}/tplus/client/clearingengine/admin_settlement.py +0 -0
  40. {tpluspy-0.2.3 → tpluspy-0.2.4}/tplus/client/clearingengine/assetregistry.py +0 -0
  41. {tpluspy-0.2.3 → tpluspy-0.2.4}/tplus/client/clearingengine/base.py +0 -0
  42. {tpluspy-0.2.3 → tpluspy-0.2.4}/tplus/client/clearingengine/cross_venue.py +0 -0
  43. {tpluspy-0.2.3 → tpluspy-0.2.4}/tplus/client/clearingengine/decimal.py +0 -0
  44. {tpluspy-0.2.3 → tpluspy-0.2.4}/tplus/client/clearingengine/vault.py +0 -0
  45. {tpluspy-0.2.3 → tpluspy-0.2.4}/tplus/client/market_data.py +0 -0
  46. {tpluspy-0.2.3 → tpluspy-0.2.4}/tplus/client/oms/__init__.py +0 -0
  47. {tpluspy-0.2.3 → tpluspy-0.2.4}/tplus/client/oms/assetregistry.py +0 -0
  48. {tpluspy-0.2.3 → tpluspy-0.2.4}/tplus/client/oms/oms_admin.py +0 -0
  49. {tpluspy-0.2.3 → tpluspy-0.2.4}/tplus/client/orderbook.py +0 -0
  50. {tpluspy-0.2.3 → tpluspy-0.2.4}/tplus/client/withdrawal.py +0 -0
  51. {tpluspy-0.2.3 → tpluspy-0.2.4}/tplus/constants.py +0 -0
  52. {tpluspy-0.2.3 → tpluspy-0.2.4}/tplus/evm/__init__.py +0 -0
  53. {tpluspy-0.2.3 → tpluspy-0.2.4}/tplus/evm/abi.py +0 -0
  54. {tpluspy-0.2.3 → tpluspy-0.2.4}/tplus/evm/address.py +0 -0
  55. {tpluspy-0.2.3 → tpluspy-0.2.4}/tplus/evm/constants.py +0 -0
  56. {tpluspy-0.2.3 → tpluspy-0.2.4}/tplus/evm/contracts.py +0 -0
  57. {tpluspy-0.2.3 → tpluspy-0.2.4}/tplus/evm/dev/__init__.py +0 -0
  58. {tpluspy-0.2.3 → tpluspy-0.2.4}/tplus/evm/dev/contracts.py +0 -0
  59. {tpluspy-0.2.3 → tpluspy-0.2.4}/tplus/evm/dev/env.py +0 -0
  60. {tpluspy-0.2.3 → tpluspy-0.2.4}/tplus/evm/dev/token.py +0 -0
  61. {tpluspy-0.2.3 → tpluspy-0.2.4}/tplus/evm/exceptions.py +0 -0
  62. {tpluspy-0.2.3 → tpluspy-0.2.4}/tplus/evm/managers/__init__.py +0 -0
  63. {tpluspy-0.2.3 → tpluspy-0.2.4}/tplus/evm/managers/chaindata.py +0 -0
  64. {tpluspy-0.2.3 → tpluspy-0.2.4}/tplus/evm/managers/credential_manager.py +0 -0
  65. {tpluspy-0.2.3 → tpluspy-0.2.4}/tplus/evm/managers/deposit.py +0 -0
  66. {tpluspy-0.2.3 → tpluspy-0.2.4}/tplus/evm/managers/evm.py +0 -0
  67. {tpluspy-0.2.3 → tpluspy-0.2.4}/tplus/evm/managers/registry.py +0 -0
  68. {tpluspy-0.2.3 → tpluspy-0.2.4}/tplus/evm/managers/settle.py +0 -0
  69. {tpluspy-0.2.3 → tpluspy-0.2.4}/tplus/evm/managers/vault.py +0 -0
  70. {tpluspy-0.2.3 → tpluspy-0.2.4}/tplus/evm/managers/withdraw.py +0 -0
  71. {tpluspy-0.2.3 → tpluspy-0.2.4}/tplus/evm/utils.py +0 -0
  72. {tpluspy-0.2.3 → tpluspy-0.2.4}/tplus/exceptions.py +0 -0
  73. {tpluspy-0.2.3 → tpluspy-0.2.4}/tplus/logger.py +0 -0
  74. {tpluspy-0.2.3 → tpluspy-0.2.4}/tplus/managers/__init__.py +0 -0
  75. {tpluspy-0.2.3 → tpluspy-0.2.4}/tplus/managers/base.py +0 -0
  76. {tpluspy-0.2.3 → tpluspy-0.2.4}/tplus/model/__init__.py +0 -0
  77. {tpluspy-0.2.3 → tpluspy-0.2.4}/tplus/model/approval.py +0 -0
  78. {tpluspy-0.2.3 → tpluspy-0.2.4}/tplus/model/asset_identifier.py +0 -0
  79. {tpluspy-0.2.3 → tpluspy-0.2.4}/tplus/model/batch_order.py +0 -0
  80. {tpluspy-0.2.3 → tpluspy-0.2.4}/tplus/model/cancel_order.py +0 -0
  81. {tpluspy-0.2.3 → tpluspy-0.2.4}/tplus/model/chain_address.py +0 -0
  82. {tpluspy-0.2.3 → tpluspy-0.2.4}/tplus/model/close_all_positions_preview.py +0 -0
  83. {tpluspy-0.2.3 → tpluspy-0.2.4}/tplus/model/config.py +0 -0
  84. {tpluspy-0.2.3 → tpluspy-0.2.4}/tplus/model/interest_rates.py +0 -0
  85. {tpluspy-0.2.3 → tpluspy-0.2.4}/tplus/model/klines.py +0 -0
  86. {tpluspy-0.2.3 → tpluspy-0.2.4}/tplus/model/limit_order.py +0 -0
  87. {tpluspy-0.2.3 → tpluspy-0.2.4}/tplus/model/market.py +0 -0
  88. {tpluspy-0.2.3 → tpluspy-0.2.4}/tplus/model/market_order.py +0 -0
  89. {tpluspy-0.2.3 → tpluspy-0.2.4}/tplus/model/multisig.py +0 -0
  90. {tpluspy-0.2.3 → tpluspy-0.2.4}/tplus/model/order.py +0 -0
  91. {tpluspy-0.2.3 → tpluspy-0.2.4}/tplus/model/order_trigger.py +0 -0
  92. {tpluspy-0.2.3 → tpluspy-0.2.4}/tplus/model/orderbook.py +0 -0
  93. {tpluspy-0.2.3 → tpluspy-0.2.4}/tplus/model/position.py +0 -0
  94. {tpluspy-0.2.3 → tpluspy-0.2.4}/tplus/model/replace_order.py +0 -0
  95. {tpluspy-0.2.3 → tpluspy-0.2.4}/tplus/model/risk_parameters.py +0 -0
  96. {tpluspy-0.2.3 → tpluspy-0.2.4}/tplus/model/settlement.py +0 -0
  97. {tpluspy-0.2.3 → tpluspy-0.2.4}/tplus/model/signed_message.py +0 -0
  98. {tpluspy-0.2.3 → tpluspy-0.2.4}/tplus/model/trades.py +0 -0
  99. {tpluspy-0.2.3 → tpluspy-0.2.4}/tplus/model/types.py +0 -0
  100. {tpluspy-0.2.3 → tpluspy-0.2.4}/tplus/model/user_event.py +0 -0
  101. {tpluspy-0.2.3 → tpluspy-0.2.4}/tplus/model/user_inventory.py +0 -0
  102. {tpluspy-0.2.3 → tpluspy-0.2.4}/tplus/model/user_margin.py +0 -0
  103. {tpluspy-0.2.3 → tpluspy-0.2.4}/tplus/model/user_simulated_margin.py +0 -0
  104. {tpluspy-0.2.3 → tpluspy-0.2.4}/tplus/model/user_solvency.py +0 -0
  105. {tpluspy-0.2.3 → tpluspy-0.2.4}/tplus/model/withdrawal.py +0 -0
  106. {tpluspy-0.2.3 → tpluspy-0.2.4}/tplus/py.typed +0 -0
  107. {tpluspy-0.2.3 → tpluspy-0.2.4}/tplus/types.py +0 -0
  108. {tpluspy-0.2.3 → tpluspy-0.2.4}/tplus/utils/__init__.py +0 -0
  109. {tpluspy-0.2.3 → tpluspy-0.2.4}/tplus/utils/address.py +0 -0
  110. {tpluspy-0.2.3 → tpluspy-0.2.4}/tplus/utils/amount.py +0 -0
  111. {tpluspy-0.2.3 → tpluspy-0.2.4}/tplus/utils/bytes32.py +0 -0
  112. {tpluspy-0.2.3 → tpluspy-0.2.4}/tplus/utils/decimals.py +0 -0
  113. {tpluspy-0.2.3 → tpluspy-0.2.4}/tplus/utils/domain.py +0 -0
  114. {tpluspy-0.2.3 → tpluspy-0.2.4}/tplus/utils/hex.py +0 -0
  115. {tpluspy-0.2.3 → tpluspy-0.2.4}/tplus/utils/limit_order.py +0 -0
  116. {tpluspy-0.2.3 → tpluspy-0.2.4}/tplus/utils/market_order.py +0 -0
  117. {tpluspy-0.2.3 → tpluspy-0.2.4}/tplus/utils/operator.py +0 -0
  118. {tpluspy-0.2.3 → tpluspy-0.2.4}/tplus/utils/replace_order.py +0 -0
  119. {tpluspy-0.2.3 → tpluspy-0.2.4}/tplus/utils/serializers.py +0 -0
  120. {tpluspy-0.2.3 → tpluspy-0.2.4}/tplus/utils/signing.py +0 -0
  121. {tpluspy-0.2.3 → tpluspy-0.2.4}/tplus/utils/timeout.py +0 -0
  122. {tpluspy-0.2.3 → tpluspy-0.2.4}/tplus/utils/user/__init__.py +0 -0
  123. {tpluspy-0.2.3 → tpluspy-0.2.4}/tplus/utils/user/decrypt.py +0 -0
  124. {tpluspy-0.2.3 → tpluspy-0.2.4}/tplus/utils/user/ed_keyfile.py +0 -0
  125. {tpluspy-0.2.3 → tpluspy-0.2.4}/tplus/utils/user/manager.py +0 -0
  126. {tpluspy-0.2.3 → tpluspy-0.2.4}/tplus/utils/user/model.py +0 -0
  127. {tpluspy-0.2.3 → tpluspy-0.2.4}/tplus/utils/user/token_cache.py +0 -0
  128. {tpluspy-0.2.3 → tpluspy-0.2.4}/tplus/utils/user/validate.py +0 -0
  129. {tpluspy-0.2.3 → tpluspy-0.2.4}/tpluspy.egg-info/dependency_links.txt +0 -0
  130. {tpluspy-0.2.3 → tpluspy-0.2.4}/tpluspy.egg-info/entry_points.txt +0 -0
  131. {tpluspy-0.2.3 → tpluspy-0.2.4}/tpluspy.egg-info/not-zip-safe +0 -0
  132. {tpluspy-0.2.3 → tpluspy-0.2.4}/tpluspy.egg-info/requires.txt +0 -0
  133. {tpluspy-0.2.3 → tpluspy-0.2.4}/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.2.3
3
+ Version: 0.2.4
4
4
  Summary: tpluspy: Client utilities for interacting with tplus
5
5
  Home-page: https://github.com/tpluslabs/tpluspy
6
6
  Author: TPlus Labs
@@ -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
@@ -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
@@ -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.2.3",
11
+ version="0.2.4",
12
12
  description="""tpluspy: Client utilities for interacting with tplus""",
13
13
  long_description=long_description,
14
14
  long_description_content_type="text/markdown",
@@ -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 orderbook_client(
55
- self, alias: str | None = None, *, anonymous: bool = False
56
- ) -> "OrderBookClient":
57
- if not self.orderbook_base_url:
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:
91
+ raise click.UsageError(
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."
95
+ )
96
+ return url
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:
58
103
  raise click.UsageError(
59
- "No orderbook base URL specified. "
60
- "Pass --orderbook-base-url or set TPLUS_ORDERBOOK_BASE_URL."
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."
61
107
  )
108
+ return _derive_market_data_base_url(api)
62
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.orderbook_base_url,
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
- if not self.orderbook_base_url:
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
@@ -120,16 +165,10 @@ class CLIContext(dict):
120
165
  return BlockchainClient(base_url=self.blockchain_base_url, insecure_ssl=self.ignore_ssl)
121
166
 
122
167
  def market_data_client(self) -> "MarketDataClient":
123
- if not self.market_data_base_url:
124
- raise click.UsageError(
125
- "No market-data base URL specified. "
126
- "Pass --market-data-base-url or set TPLUS_MARKET_DATA_BASE_URL."
127
- )
128
-
129
168
  from tplus.client.market_data import MarketDataClient
130
169
 
131
170
  return MarketDataClient(
132
- base_url=self.market_data_base_url,
171
+ base_url=self._resolved_market_data_url(),
133
172
  insecure_ssl=self.ignore_ssl,
134
173
  )
135
174
 
@@ -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
- ("TPLUS_ORDERBOOK_BASE_URL", "", "Orderbook service base URL."),
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"),
@@ -0,0 +1,116 @@
1
+ """Static T+ asset metadata used for display-only symbol resolution.
2
+
3
+ The protocol identifies assets by ``AssetIdentifier`` values, not symbols. This
4
+ module is a temporary local snapshot so CLI and onboarding output can show the
5
+ canonical symbol next to known production indexes and chain-address aliases.
6
+
7
+ Source snapshot: ``tplus-risk-params`` ``main:environments/prod/params.json``.
8
+ Do not use this for risk checks; read live registry/market endpoints for trading
9
+ parameters.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ from dataclasses import dataclass
15
+ from typing import Any
16
+
17
+ from tplus.model.asset_identifier import AssetIdentifier
18
+
19
+
20
+ @dataclass(frozen=True)
21
+ class AssetMetadata:
22
+ index: int
23
+ symbol: str
24
+ asset_class: str
25
+ representations: tuple[str, ...]
26
+
27
+
28
+ CANONICAL_ASSETS: dict[int, AssetMetadata] = {
29
+ 0: AssetMetadata(0, "USDC", "USD", ("USDC", "USDT")),
30
+ 1: AssetMetadata(1, "ETH", "ETH", ("WETH",)),
31
+ 2: AssetMetadata(2, "BTC", "BTC", ("WBTC",)),
32
+ 3: AssetMetadata(3, "ARB", "ARB", ("ARB",)),
33
+ 4: AssetMetadata(4, "MON", "MON", ("MON",)),
34
+ 5: AssetMetadata(5, "GOLD", "GOLD", ("XAUT",)),
35
+ 6: AssetMetadata(6, "CIGS", "CIGS", ("CIGS",)),
36
+ }
37
+
38
+
39
+ CHAIN_ASSET_INDEXES: dict[str, int] = {
40
+ # USDC / USDT -> USD class.
41
+ "a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000@000000000000000001": 0,
42
+ "af88d065e77c8cc2239327c5edb3a432268e5831000000000000000000000000@00000000000000a4b1": 0,
43
+ "754704bc059f8c67012fed69bc8a327a5aafb603000000000000000000000000@00000000000000008f": 0,
44
+ "dac17f958d2ee523a2206206994597c13d831ec7000000000000000000000000@000000000000000001": 0,
45
+ "833589fcd6edb6e08f4c7c32d4f71b54bda02913000000000000000000000000@000000000000002105": 0,
46
+ # ETH.
47
+ "82af49447d8a07e3bd95bd0d56f35241523fbab1000000000000000000000000@00000000000000a4b1": 1,
48
+ "c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000@000000000000000001": 1,
49
+ "ee8c0e9f1bffb4eb878d8f15f368a02a35481242000000000000000000000000@00000000000000008f": 1,
50
+ "4200000000000000000000000000000000000006000000000000000000000000@000000000000002105": 1,
51
+ # BTC.
52
+ "2260fac5e5542a773aa44fbcfedf7c193bc2c599000000000000000000000000@000000000000000001": 2,
53
+ "2f2a2543b76a4166549f7aab2e75bef0aefc5b0f000000000000000000000000@00000000000000a4b1": 2,
54
+ "0555e30da8f98308edb960aa94c0db47230d2b9c000000000000000000000000@00000000000000008f": 2,
55
+ # ARB.
56
+ "b50721bcf8d664c30412cfbc6cf7a15145234ad1000000000000000000000000@000000000000000001": 3,
57
+ "912ce59144191c1204e64559fe8253a0e49e6548000000000000000000000000@00000000000000a4b1": 3,
58
+ # MON.
59
+ "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee000000000000000000000000@00000000000000008f": 4,
60
+ # GOLD / XAUT.
61
+ "68749665ff8d2d112fa859aa293f07a622782f38000000000000000000000000@000000000000000001": 5,
62
+ # CIGS.
63
+ "2e9126f66e386879f312287a419b0f70004c3f5b000000000000000000000000@000000000000002105": 6,
64
+ }
65
+
66
+
67
+ def _asset_id_key(asset_id: Any) -> str:
68
+ if isinstance(asset_id, AssetIdentifier):
69
+ return str(asset_id)
70
+ if isinstance(asset_id, int):
71
+ return str(asset_id)
72
+ if isinstance(asset_id, dict):
73
+ return str(AssetIdentifier.model_validate(asset_id))
74
+ return str(asset_id).lower().removeprefix("0x")
75
+
76
+
77
+ def asset_index(asset_id: Any) -> int | None:
78
+ """Return the canonical asset index for a known index/address identifier."""
79
+ key = _asset_id_key(asset_id)
80
+ if key.isnumeric():
81
+ return int(key)
82
+ if "@" in key:
83
+ try:
84
+ key = str(AssetIdentifier(key))
85
+ except ValueError:
86
+ pass
87
+ return CHAIN_ASSET_INDEXES.get(key)
88
+ return None
89
+
90
+
91
+ def get_asset_metadata(asset_id: Any) -> AssetMetadata | None:
92
+ index = asset_index(asset_id)
93
+ if index is None:
94
+ return None
95
+ return CANONICAL_ASSETS.get(index)
96
+
97
+
98
+ def asset_label(asset_id: Any) -> str:
99
+ """Human label for a known asset identifier, falling back without guessing."""
100
+ key = _asset_id_key(asset_id)
101
+ metadata = get_asset_metadata(key)
102
+ if metadata is None:
103
+ return f"asset {key}"
104
+ return f"{metadata.symbol} (asset {key})"
105
+
106
+
107
+ def asset_metadata_dict(asset_id: Any) -> dict[str, Any] | None:
108
+ metadata = get_asset_metadata(asset_id)
109
+ if metadata is None:
110
+ return None
111
+ return {
112
+ "index": metadata.index,
113
+ "symbol": metadata.symbol,
114
+ "asset_class": metadata.asset_class,
115
+ "representations": list(metadata.representations),
116
+ }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tpluspy
3
- Version: 0.2.3
3
+ Version: 0.2.4
4
4
  Summary: tpluspy: Client utilities for interacting with tplus
5
5
  Home-page: https://github.com/tpluslabs/tpluspy
6
6
  Author: TPlus Labs
@@ -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
@@ -2,6 +2,7 @@ README.md
2
2
  pyproject.toml
3
3
  setup.py
4
4
  tplus/__init__.py
5
+ tplus/asset_metadata.py
5
6
  tplus/constants.py
6
7
  tplus/exceptions.py
7
8
  tplus/logger.py
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes