cartha-cli 1.0.0__py3-none-any.whl
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.
- cartha_cli/__init__.py +34 -0
- cartha_cli/bt.py +206 -0
- cartha_cli/commands/__init__.py +25 -0
- cartha_cli/commands/common.py +76 -0
- cartha_cli/commands/config.py +294 -0
- cartha_cli/commands/health.py +463 -0
- cartha_cli/commands/help.py +49 -0
- cartha_cli/commands/miner_password.py +283 -0
- cartha_cli/commands/miner_status.py +524 -0
- cartha_cli/commands/pair_status.py +484 -0
- cartha_cli/commands/pools.py +121 -0
- cartha_cli/commands/prove_lock.py +1260 -0
- cartha_cli/commands/register.py +274 -0
- cartha_cli/commands/shared_options.py +235 -0
- cartha_cli/commands/version.py +15 -0
- cartha_cli/config.py +75 -0
- cartha_cli/display.py +62 -0
- cartha_cli/eth712.py +7 -0
- cartha_cli/main.py +237 -0
- cartha_cli/pair.py +201 -0
- cartha_cli/utils.py +274 -0
- cartha_cli/verifier.py +342 -0
- cartha_cli/wallet.py +59 -0
- cartha_cli-1.0.0.dist-info/METADATA +180 -0
- cartha_cli-1.0.0.dist-info/RECORD +28 -0
- cartha_cli-1.0.0.dist-info/WHEEL +4 -0
- cartha_cli-1.0.0.dist-info/entry_points.txt +2 -0
- cartha_cli-1.0.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
"""Register command."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import bittensor as bt
|
|
6
|
+
import typer
|
|
7
|
+
from rich.prompt import Confirm
|
|
8
|
+
from rich.table import Table
|
|
9
|
+
|
|
10
|
+
from ..bt import (
|
|
11
|
+
RegistrationResult,
|
|
12
|
+
get_burn_cost,
|
|
13
|
+
get_subtensor,
|
|
14
|
+
get_wallet,
|
|
15
|
+
register_hotkey,
|
|
16
|
+
)
|
|
17
|
+
from ..config import settings
|
|
18
|
+
from ..display import display_clock_and_countdown
|
|
19
|
+
from ..verifier import VerifierError
|
|
20
|
+
from .common import (
|
|
21
|
+
console,
|
|
22
|
+
handle_unexpected_exception,
|
|
23
|
+
handle_wallet_exception,
|
|
24
|
+
)
|
|
25
|
+
from .shared_options import (
|
|
26
|
+
wallet_name_option,
|
|
27
|
+
wallet_hotkey_option,
|
|
28
|
+
network_option,
|
|
29
|
+
netuid_option,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def register(
|
|
34
|
+
wallet_name: str | None = wallet_name_option(required=False),
|
|
35
|
+
wallet_hotkey: str | None = wallet_hotkey_option(required=False),
|
|
36
|
+
network: str = network_option(),
|
|
37
|
+
netuid: int = netuid_option(),
|
|
38
|
+
burned: bool = typer.Option(
|
|
39
|
+
True,
|
|
40
|
+
"--burned/--pow",
|
|
41
|
+
help="Burned registration by default; pass --pow to run PoW registration.",
|
|
42
|
+
),
|
|
43
|
+
cuda: bool = typer.Option(
|
|
44
|
+
False, "--cuda", help="Enable CUDA for PoW registration."
|
|
45
|
+
),
|
|
46
|
+
) -> None:
|
|
47
|
+
"""Register your hotkey on the Cartha subnet (subnet 35 on finney, subnet 78 on testnet).
|
|
48
|
+
|
|
49
|
+
USAGE:
|
|
50
|
+
------
|
|
51
|
+
Interactive mode (recommended): 'cartha miner register' (will prompt for wallet)
|
|
52
|
+
With arguments: 'cartha miner register -w cold -wh hot'
|
|
53
|
+
|
|
54
|
+
ALIASES:
|
|
55
|
+
--------
|
|
56
|
+
Wallet: --wallet-name, --coldkey, -w | --wallet-hotkey, --hotkey, -wh
|
|
57
|
+
Network: --network, -n
|
|
58
|
+
|
|
59
|
+
REGISTRATION OPTIONS:
|
|
60
|
+
---------------------
|
|
61
|
+
--burned (default): Register using burned TAO
|
|
62
|
+
--pow: Register using Proof of Work
|
|
63
|
+
--cuda: Enable CUDA for PoW registration
|
|
64
|
+
|
|
65
|
+
After registration, use 'cartha vault lock' to create lock positions.
|
|
66
|
+
|
|
67
|
+
⚠️ Note: Password generation is no longer supported. The new lock flow uses
|
|
68
|
+
session tokens instead of passwords.
|
|
69
|
+
"""
|
|
70
|
+
|
|
71
|
+
assert wallet_name is not None # nosec - enforced by Typer prompt
|
|
72
|
+
assert wallet_hotkey is not None # nosec - enforced by Typer prompt
|
|
73
|
+
|
|
74
|
+
# Auto-map netuid and verifier URL based on network
|
|
75
|
+
if network == "test":
|
|
76
|
+
netuid = 78
|
|
77
|
+
elif network == "finney":
|
|
78
|
+
netuid = 35
|
|
79
|
+
# Warn that mainnet is not live yet
|
|
80
|
+
console.print()
|
|
81
|
+
console.print("[bold yellow]⚠️ MAINNET NOT AVAILABLE YET[/]")
|
|
82
|
+
console.print()
|
|
83
|
+
console.print("[yellow]Cartha subnet is currently in testnet phase (subnet 78 on test network).[/]")
|
|
84
|
+
console.print("[yellow]Mainnet (subnet 35 on finney network) has not been announced yet.[/]")
|
|
85
|
+
console.print()
|
|
86
|
+
console.print("[bold cyan]To use testnet:[/]")
|
|
87
|
+
console.print(" cartha miner register --network test")
|
|
88
|
+
console.print()
|
|
89
|
+
console.print("[dim]If you continue with finney network, registration will attempt[/]")
|
|
90
|
+
console.print("[dim]subnet 35 but the subnet may not be operational yet.[/]")
|
|
91
|
+
console.print()
|
|
92
|
+
if not Confirm.ask("[yellow]Continue with finney network anyway?[/]", default=False):
|
|
93
|
+
console.print("[yellow]Cancelled. Use --network test for testnet.[/]")
|
|
94
|
+
raise typer.Exit(code=0)
|
|
95
|
+
# Note: netuid parameter is kept for backwards compatibility / explicit override
|
|
96
|
+
|
|
97
|
+
from ..config import get_verifier_url_for_network
|
|
98
|
+
expected_verifier_url = get_verifier_url_for_network(network)
|
|
99
|
+
if settings.verifier_url != expected_verifier_url:
|
|
100
|
+
settings.verifier_url = expected_verifier_url
|
|
101
|
+
|
|
102
|
+
subtensor = None
|
|
103
|
+
try:
|
|
104
|
+
# Initialize subtensor and wallet to get info before registration
|
|
105
|
+
try:
|
|
106
|
+
subtensor = get_subtensor(network)
|
|
107
|
+
wallet = get_wallet(wallet_name, wallet_hotkey)
|
|
108
|
+
except bt.KeyFileError as exc:
|
|
109
|
+
handle_wallet_exception(
|
|
110
|
+
wallet_name=wallet_name, wallet_hotkey=wallet_hotkey, exc=exc
|
|
111
|
+
)
|
|
112
|
+
except typer.Exit:
|
|
113
|
+
raise
|
|
114
|
+
except Exception as exc:
|
|
115
|
+
handle_unexpected_exception("Failed to initialize wallet/subtensor", exc)
|
|
116
|
+
|
|
117
|
+
hotkey_ss58 = wallet.hotkey.ss58_address
|
|
118
|
+
coldkey_ss58 = wallet.coldkeypub.ss58_address
|
|
119
|
+
|
|
120
|
+
# Check if already registered
|
|
121
|
+
if subtensor.is_hotkey_registered(hotkey_ss58, netuid=netuid):
|
|
122
|
+
neuron = subtensor.get_neuron_for_pubkey_and_subnet(hotkey_ss58, netuid)
|
|
123
|
+
uid = (
|
|
124
|
+
None if getattr(neuron, "is_null", False) else getattr(neuron, "uid", None)
|
|
125
|
+
)
|
|
126
|
+
if uid is not None:
|
|
127
|
+
console.print(f"[bold yellow]Hotkey already registered[/]. UID: {uid}")
|
|
128
|
+
raise typer.Exit(code=0)
|
|
129
|
+
|
|
130
|
+
# Get registration cost and balance
|
|
131
|
+
registration_cost = None
|
|
132
|
+
balance = None
|
|
133
|
+
|
|
134
|
+
if burned:
|
|
135
|
+
try:
|
|
136
|
+
registration_cost = get_burn_cost(network, netuid)
|
|
137
|
+
except Exception as exc:
|
|
138
|
+
# Log warning but continue - cost may not be available on all networks
|
|
139
|
+
console.print(
|
|
140
|
+
f"[bold yellow]Warning: Could not fetch registration cost[/]: {exc}"
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
try:
|
|
144
|
+
balance_obj = subtensor.get_balance(coldkey_ss58)
|
|
145
|
+
# Convert Balance object to float using .tao property
|
|
146
|
+
balance = balance_obj.tao if hasattr(balance_obj, "tao") else float(balance_obj)
|
|
147
|
+
except Exception:
|
|
148
|
+
pass
|
|
149
|
+
|
|
150
|
+
# Display registration summary table (like btcli)
|
|
151
|
+
console.print(f"[bold]Using the wallet path from config:[/] {wallet.path}")
|
|
152
|
+
|
|
153
|
+
summary_table = Table(title="Registration Summary")
|
|
154
|
+
summary_table.add_column("Field", style="cyan")
|
|
155
|
+
summary_table.add_column("Value", style="yellow")
|
|
156
|
+
|
|
157
|
+
summary_table.add_row("Netuid", str(netuid))
|
|
158
|
+
if burned:
|
|
159
|
+
if registration_cost is not None:
|
|
160
|
+
summary_table.add_row("Cost", f"τ {registration_cost:.4f}")
|
|
161
|
+
else:
|
|
162
|
+
summary_table.add_row("Cost", "Unable to fetch")
|
|
163
|
+
summary_table.add_row("Hotkey", hotkey_ss58)
|
|
164
|
+
summary_table.add_row("Coldkey", coldkey_ss58)
|
|
165
|
+
summary_table.add_row("Network", network)
|
|
166
|
+
|
|
167
|
+
console.print(summary_table)
|
|
168
|
+
|
|
169
|
+
# Display balance and cost (already converted to float above)
|
|
170
|
+
if balance is not None:
|
|
171
|
+
console.print(f"\n[bold]Your balance is:[/] {balance:.4f} τ")
|
|
172
|
+
|
|
173
|
+
if registration_cost is not None:
|
|
174
|
+
console.print(
|
|
175
|
+
f"[bold]The cost to register by recycle is[/] {registration_cost:.4f} τ"
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
# Display clock and countdown
|
|
179
|
+
console.print()
|
|
180
|
+
display_clock_and_countdown()
|
|
181
|
+
|
|
182
|
+
# Confirmation prompt
|
|
183
|
+
if not typer.confirm("\nDo you want to continue?", default=False):
|
|
184
|
+
console.print("[bold yellow]Registration cancelled.[/]")
|
|
185
|
+
raise typer.Exit(code=0)
|
|
186
|
+
|
|
187
|
+
console.print("\n[bold cyan]Registering...[/]")
|
|
188
|
+
|
|
189
|
+
try:
|
|
190
|
+
result: RegistrationResult = register_hotkey(
|
|
191
|
+
network=network,
|
|
192
|
+
wallet_name=wallet_name,
|
|
193
|
+
hotkey_name=wallet_hotkey,
|
|
194
|
+
netuid=netuid,
|
|
195
|
+
burned=burned,
|
|
196
|
+
cuda=cuda,
|
|
197
|
+
)
|
|
198
|
+
except bt.KeyFileError as exc:
|
|
199
|
+
handle_wallet_exception(
|
|
200
|
+
wallet_name=wallet_name, wallet_hotkey=wallet_hotkey, exc=exc
|
|
201
|
+
)
|
|
202
|
+
except typer.Exit:
|
|
203
|
+
raise
|
|
204
|
+
except Exception as exc:
|
|
205
|
+
handle_unexpected_exception("Registration failed unexpectedly", exc)
|
|
206
|
+
|
|
207
|
+
if result.status == "already":
|
|
208
|
+
console.print(f"[bold yellow]Hotkey already registered[/]. UID: {result.uid}")
|
|
209
|
+
raise typer.Exit(code=0)
|
|
210
|
+
|
|
211
|
+
if not result.success:
|
|
212
|
+
console.print("[bold red]Registration failed.[/]")
|
|
213
|
+
raise typer.Exit(code=1)
|
|
214
|
+
|
|
215
|
+
# Display extrinsic if available
|
|
216
|
+
if result.extrinsic:
|
|
217
|
+
console.print(
|
|
218
|
+
f"[bold green]✔ Your extrinsic has been included as[/] [cyan]{result.extrinsic}[/]"
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
# Display balance update if available (already converted to float in register_hotkey)
|
|
222
|
+
if result.balance_before is not None and result.balance_after is not None:
|
|
223
|
+
console.print(
|
|
224
|
+
f"[bold]Balance:[/] {result.balance_before:.4f} τ -> {result.balance_after:.4f} τ"
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
# Display success message with UID
|
|
228
|
+
if result.status == "burned":
|
|
229
|
+
console.print(
|
|
230
|
+
"[bold green]✔ Registered on netuid[/] "
|
|
231
|
+
f"[cyan]{netuid}[/] [bold green]with UID[/] [cyan]{result.uid}[/]"
|
|
232
|
+
)
|
|
233
|
+
else:
|
|
234
|
+
console.print(
|
|
235
|
+
"[bold green]✔ Registered on netuid[/] "
|
|
236
|
+
f"[cyan]{netuid}[/] [bold green]with UID[/] [cyan]{result.uid}[/]"
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
if result.uid is not None:
|
|
240
|
+
slot_uid = str(result.uid)
|
|
241
|
+
console.print()
|
|
242
|
+
console.print(
|
|
243
|
+
"[bold green]✓ Registration complete![/] "
|
|
244
|
+
f"Hotkey: {result.hotkey}, Slot UID: {slot_uid}"
|
|
245
|
+
)
|
|
246
|
+
console.print()
|
|
247
|
+
console.print(
|
|
248
|
+
"[bold cyan]Next steps:[/]"
|
|
249
|
+
)
|
|
250
|
+
console.print(
|
|
251
|
+
" • Use [green]cartha vault lock[/] to create a lock position"
|
|
252
|
+
)
|
|
253
|
+
console.print(
|
|
254
|
+
" • Use [green]cartha miner status[/] to check your miner status"
|
|
255
|
+
)
|
|
256
|
+
console.print()
|
|
257
|
+
console.print(
|
|
258
|
+
"[dim]Note: The new lock flow uses session tokens instead of passwords. "
|
|
259
|
+
"Password generation is no longer supported.[/]"
|
|
260
|
+
)
|
|
261
|
+
else:
|
|
262
|
+
console.print(
|
|
263
|
+
"[bold yellow]UID not yet available[/] (node may still be syncing)."
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
raise typer.Exit(code=0)
|
|
267
|
+
finally:
|
|
268
|
+
# Clean up subtensor connection
|
|
269
|
+
if subtensor is not None:
|
|
270
|
+
try:
|
|
271
|
+
if hasattr(subtensor, "close"):
|
|
272
|
+
subtensor.close()
|
|
273
|
+
except Exception:
|
|
274
|
+
pass
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
"""Shared option definitions for consistent CLI interface.
|
|
2
|
+
|
|
3
|
+
This module provides reusable typer.Option definitions with comprehensive aliases
|
|
4
|
+
to ensure consistency across all CLI commands.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import typer
|
|
8
|
+
from ..config import settings
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def wallet_name_option(required: bool = True):
|
|
12
|
+
"""Coldkey wallet name option with consistent aliases.
|
|
13
|
+
|
|
14
|
+
Aliases: --wallet-name, --wallet.name, --coldkey, -w
|
|
15
|
+
"""
|
|
16
|
+
if required:
|
|
17
|
+
return typer.Option(
|
|
18
|
+
...,
|
|
19
|
+
"--wallet-name",
|
|
20
|
+
"--wallet.name",
|
|
21
|
+
"--coldkey",
|
|
22
|
+
"-w",
|
|
23
|
+
prompt="Coldkey wallet name",
|
|
24
|
+
help="Coldkey wallet name (aliases: --wallet-name, --wallet.name, --coldkey, -w)",
|
|
25
|
+
show_default=False,
|
|
26
|
+
)
|
|
27
|
+
else:
|
|
28
|
+
return typer.Option(
|
|
29
|
+
None,
|
|
30
|
+
"--wallet-name",
|
|
31
|
+
"--wallet.name",
|
|
32
|
+
"--coldkey",
|
|
33
|
+
"-w",
|
|
34
|
+
help="Coldkey wallet name (aliases: --wallet-name, --wallet.name, --coldkey, -w)",
|
|
35
|
+
show_default=False,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def wallet_hotkey_option(required: bool = True):
|
|
40
|
+
"""Hotkey name option with consistent aliases.
|
|
41
|
+
|
|
42
|
+
Aliases: --wallet-hotkey, --wallet.hotkey, --hotkey, -wh
|
|
43
|
+
"""
|
|
44
|
+
if required:
|
|
45
|
+
return typer.Option(
|
|
46
|
+
...,
|
|
47
|
+
"--wallet-hotkey",
|
|
48
|
+
"--wallet.hotkey",
|
|
49
|
+
"--hotkey",
|
|
50
|
+
"-wh",
|
|
51
|
+
prompt="Hotkey name",
|
|
52
|
+
help="Hotkey name (aliases: --wallet-hotkey, --wallet.hotkey, --hotkey, -wh)",
|
|
53
|
+
show_default=False,
|
|
54
|
+
)
|
|
55
|
+
else:
|
|
56
|
+
return typer.Option(
|
|
57
|
+
None,
|
|
58
|
+
"--wallet-hotkey",
|
|
59
|
+
"--wallet.hotkey",
|
|
60
|
+
"--hotkey",
|
|
61
|
+
"-wh",
|
|
62
|
+
help="Hotkey name (aliases: --wallet-hotkey, --wallet.hotkey, --hotkey, -wh)",
|
|
63
|
+
show_default=False,
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def pool_id_option():
|
|
68
|
+
"""Pool ID option with aliases.
|
|
69
|
+
|
|
70
|
+
Aliases: --pool-id, --pool, --poolid, -p
|
|
71
|
+
"""
|
|
72
|
+
return typer.Option(
|
|
73
|
+
None,
|
|
74
|
+
"--pool-id",
|
|
75
|
+
"--pool",
|
|
76
|
+
"--poolid",
|
|
77
|
+
"-p",
|
|
78
|
+
help="Pool name (e.g., BTCUSD, ETHUSD) or hex ID (0x...) (aliases: --pool-id, --pool, --poolid, -p)",
|
|
79
|
+
show_default=False,
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def chain_id_option():
|
|
84
|
+
"""Chain ID option with aliases.
|
|
85
|
+
|
|
86
|
+
Aliases: --chain-id, --chain, --chainid
|
|
87
|
+
"""
|
|
88
|
+
return typer.Option(
|
|
89
|
+
None,
|
|
90
|
+
"--chain-id",
|
|
91
|
+
"--chain",
|
|
92
|
+
"--chainid",
|
|
93
|
+
help="EVM chain ID (auto-detected from pool if not provided) (aliases: --chain-id, --chain, --chainid)",
|
|
94
|
+
show_default=False,
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def vault_address_option():
|
|
99
|
+
"""Vault contract address option with aliases.
|
|
100
|
+
|
|
101
|
+
Aliases: --vault-address, --vault
|
|
102
|
+
"""
|
|
103
|
+
return typer.Option(
|
|
104
|
+
None,
|
|
105
|
+
"--vault-address",
|
|
106
|
+
"--vault",
|
|
107
|
+
help="Vault contract address (auto-detected from pool if not provided) (aliases: --vault-address, --vault)",
|
|
108
|
+
show_default=False,
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def owner_evm_option():
|
|
113
|
+
"""Owner EVM address option with aliases.
|
|
114
|
+
|
|
115
|
+
Aliases: --owner-evm, --owner, --evm-address, --evm, -e
|
|
116
|
+
"""
|
|
117
|
+
return typer.Option(
|
|
118
|
+
None,
|
|
119
|
+
"--owner-evm",
|
|
120
|
+
"--owner",
|
|
121
|
+
"--evm-address",
|
|
122
|
+
"--evm",
|
|
123
|
+
"-e",
|
|
124
|
+
help="EVM address that will own the lock position (aliases: --owner-evm, --owner, --evm-address, --evm, -e)",
|
|
125
|
+
show_default=False,
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def amount_option():
|
|
130
|
+
"""Amount option.
|
|
131
|
+
|
|
132
|
+
Aliases: --amount, -a
|
|
133
|
+
"""
|
|
134
|
+
return typer.Option(
|
|
135
|
+
None,
|
|
136
|
+
"--amount",
|
|
137
|
+
"-a",
|
|
138
|
+
help="Lock amount in USDC (e.g., 250.5). Auto-detects if normalized USDC or base units (>1e9) (alias: -a)",
|
|
139
|
+
show_default=False,
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def lock_days_option():
|
|
144
|
+
"""Lock days option with aliases.
|
|
145
|
+
|
|
146
|
+
Aliases: --lock-days, --days, -d
|
|
147
|
+
"""
|
|
148
|
+
return typer.Option(
|
|
149
|
+
None,
|
|
150
|
+
"--lock-days",
|
|
151
|
+
"--days",
|
|
152
|
+
"-d",
|
|
153
|
+
help="Lock duration in days (e.g., 365) (aliases: --lock-days, --days, -d)",
|
|
154
|
+
show_default=False,
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def network_option():
|
|
159
|
+
"""Network option with netuid auto-mapping.
|
|
160
|
+
|
|
161
|
+
Aliases: --network, -n
|
|
162
|
+
Maps: test → netuid 78, finney → netuid 35
|
|
163
|
+
"""
|
|
164
|
+
return typer.Option(
|
|
165
|
+
settings.network,
|
|
166
|
+
"--network",
|
|
167
|
+
"-n",
|
|
168
|
+
help="Bittensor network (test or finney). Auto-maps to correct netuid (test=78, finney=35)"
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def netuid_option():
|
|
173
|
+
"""Netuid option."""
|
|
174
|
+
return typer.Option(
|
|
175
|
+
settings.netuid,
|
|
176
|
+
"--netuid",
|
|
177
|
+
help="Subnet netuid"
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def slot_option():
|
|
182
|
+
"""Slot UID option with aliases.
|
|
183
|
+
|
|
184
|
+
Aliases: --slot, --uid, -u
|
|
185
|
+
"""
|
|
186
|
+
return typer.Option(
|
|
187
|
+
None,
|
|
188
|
+
"--slot",
|
|
189
|
+
"--uid",
|
|
190
|
+
"-u",
|
|
191
|
+
help="Subnet UID assigned to the miner (aliases: --slot, --uid, -u). If not provided, will auto-fetch or prompt.",
|
|
192
|
+
show_default=False,
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def auto_fetch_uid_option():
|
|
197
|
+
"""Auto-fetch UID option."""
|
|
198
|
+
return typer.Option(
|
|
199
|
+
True,
|
|
200
|
+
"--auto-fetch-uid/--no-auto-fetch-uid",
|
|
201
|
+
help="Automatically fetch UID from Bittensor network (default: enabled).",
|
|
202
|
+
show_default=False,
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def tx_hash_option():
|
|
207
|
+
"""Transaction hash option with aliases.
|
|
208
|
+
|
|
209
|
+
Aliases: --tx-hash, --tx, --transaction
|
|
210
|
+
"""
|
|
211
|
+
return typer.Option(
|
|
212
|
+
None,
|
|
213
|
+
"--tx-hash",
|
|
214
|
+
"--tx",
|
|
215
|
+
"--transaction",
|
|
216
|
+
help="Transaction hash (aliases: --tx-hash, --tx, --transaction)",
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def json_output_option():
|
|
221
|
+
"""JSON output option."""
|
|
222
|
+
return typer.Option(
|
|
223
|
+
False,
|
|
224
|
+
"--json",
|
|
225
|
+
help="Emit responses as JSON."
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def refresh_option():
|
|
230
|
+
"""Refresh option for triggering manual processing."""
|
|
231
|
+
return typer.Option(
|
|
232
|
+
False,
|
|
233
|
+
"--refresh",
|
|
234
|
+
help="If position not found, manually trigger verifier to process a lock transaction.",
|
|
235
|
+
)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""Version command."""
|
|
2
|
+
|
|
3
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
4
|
+
|
|
5
|
+
from .common import console
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def version_command() -> None:
|
|
9
|
+
"""Print the CLI version."""
|
|
10
|
+
try:
|
|
11
|
+
console.print(f"[bold white]cartha-cli[/] {version('cartha-cli')}")
|
|
12
|
+
except PackageNotFoundError: # pragma: no cover
|
|
13
|
+
console.print("[bold white]cartha-cli[/] 0.0.0")
|
|
14
|
+
console.print("[dim]Cartha is the Liquidity Provider for 0xMarkets DEX[/]")
|
|
15
|
+
|
cartha_cli/config.py
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"""Runtime configuration for the Cartha CLI."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
from functools import lru_cache
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from pydantic import Field
|
|
10
|
+
from pydantic_settings import BaseSettings
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
# Network to verifier URL mapping
|
|
14
|
+
NETWORK_VERIFIER_MAP = {
|
|
15
|
+
"test": "https://cartha-verifier-826542474079.us-central1.run.app",
|
|
16
|
+
"finney": None, # No mainnet verifier yet - use default or env var
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def get_verifier_url_for_network(network: str) -> str:
|
|
21
|
+
"""Get the appropriate verifier URL for a given network.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
network: Network name ("test" or "finney")
|
|
25
|
+
|
|
26
|
+
Returns:
|
|
27
|
+
Verifier URL for the network
|
|
28
|
+
"""
|
|
29
|
+
# Check if user set CARTHA_VERIFIER_URL explicitly
|
|
30
|
+
env_url = os.getenv("CARTHA_VERIFIER_URL")
|
|
31
|
+
if env_url:
|
|
32
|
+
return env_url
|
|
33
|
+
|
|
34
|
+
# Auto-map based on network
|
|
35
|
+
mapped_url = NETWORK_VERIFIER_MAP.get(network)
|
|
36
|
+
if mapped_url:
|
|
37
|
+
return mapped_url
|
|
38
|
+
|
|
39
|
+
# Default fallback (testnet for now since no mainnet)
|
|
40
|
+
return "https://cartha-verifier-826542474079.us-central1.run.app"
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class Settings(BaseSettings):
|
|
44
|
+
verifier_url: str = Field(
|
|
45
|
+
"https://cartha-verifier-826542474079.us-central1.run.app", alias="CARTHA_VERIFIER_URL"
|
|
46
|
+
)
|
|
47
|
+
network: str = Field("finney", alias="CARTHA_NETWORK")
|
|
48
|
+
netuid: int = Field(35, alias="CARTHA_NETUID")
|
|
49
|
+
evm_private_key: str | None = Field(None, alias="CARTHA_EVM_PK")
|
|
50
|
+
# Retry configuration
|
|
51
|
+
retry_max_attempts: int = Field(3, alias="CARTHA_RETRY_MAX_ATTEMPTS")
|
|
52
|
+
retry_backoff_factor: float = Field(1.5, alias="CARTHA_RETRY_BACKOFF_FACTOR")
|
|
53
|
+
# Note: retry_on_status cannot be set via env var easily (would need JSON parsing)
|
|
54
|
+
# For now, it's hardcoded but can be overridden programmatically
|
|
55
|
+
retry_on_status: list[int] = Field(default_factory=lambda: [500, 502, 503, 504])
|
|
56
|
+
# Frontend lock UI URL
|
|
57
|
+
lock_ui_url: str = Field(
|
|
58
|
+
"https://cartha.finance", alias="CARTHA_LOCK_UI_URL"
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
model_config = {
|
|
62
|
+
"env_file": ".env",
|
|
63
|
+
"env_file_encoding": "utf-8",
|
|
64
|
+
"env_nested_delimiter": "__",
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@lru_cache(maxsize=1)
|
|
69
|
+
def get_settings(**overrides: Any) -> Settings:
|
|
70
|
+
return Settings(**overrides)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
settings = get_settings()
|
|
74
|
+
|
|
75
|
+
__all__ = ["settings", "Settings", "get_settings"]
|
cartha_cli/display.py
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"""Display and formatting utilities for the Cartha CLI."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from datetime import UTC, datetime
|
|
6
|
+
|
|
7
|
+
from rich import box
|
|
8
|
+
from rich.console import Console
|
|
9
|
+
from rich.table import Table
|
|
10
|
+
|
|
11
|
+
from .utils import format_countdown, get_local_timezone, get_next_epoch_freeze_time
|
|
12
|
+
|
|
13
|
+
console = Console()
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def get_clock_table() -> Table:
|
|
17
|
+
"""Create a table with current time (UTC and local) and countdown to next epoch freeze.
|
|
18
|
+
|
|
19
|
+
Returns:
|
|
20
|
+
Table with clock and countdown information
|
|
21
|
+
"""
|
|
22
|
+
now_utc = datetime.now(tz=UTC)
|
|
23
|
+
local_tz = get_local_timezone()
|
|
24
|
+
now_local = now_utc.astimezone(local_tz)
|
|
25
|
+
|
|
26
|
+
# Format current time
|
|
27
|
+
utc_str = now_utc.strftime("%Y-%m-%d %H:%M:%S UTC")
|
|
28
|
+
local_str = now_local.strftime("%Y-%m-%d %H:%M:%S %Z")
|
|
29
|
+
|
|
30
|
+
# Calculate next epoch freeze
|
|
31
|
+
next_freeze_utc = get_next_epoch_freeze_time(now_utc)
|
|
32
|
+
next_freeze_local = next_freeze_utc.astimezone(local_tz)
|
|
33
|
+
|
|
34
|
+
# Calculate countdown
|
|
35
|
+
time_until_freeze = (next_freeze_utc - now_utc).total_seconds()
|
|
36
|
+
countdown_str = format_countdown(time_until_freeze)
|
|
37
|
+
|
|
38
|
+
# Format next freeze times
|
|
39
|
+
next_freeze_utc_str = next_freeze_utc.strftime("%Y-%m-%d %H:%M:%S UTC")
|
|
40
|
+
next_freeze_local_str = next_freeze_local.strftime("%Y-%m-%d %H:%M:%S %Z")
|
|
41
|
+
|
|
42
|
+
# Create table
|
|
43
|
+
clock_table = Table(show_header=False, box=box.SIMPLE)
|
|
44
|
+
clock_table.add_column(style="cyan")
|
|
45
|
+
clock_table.add_column(style="yellow")
|
|
46
|
+
|
|
47
|
+
clock_table.add_row("Current time (UTC)", utc_str)
|
|
48
|
+
clock_table.add_row("Current time (Local)", local_str)
|
|
49
|
+
clock_table.add_row("", "") # Spacer
|
|
50
|
+
clock_table.add_row("Next epoch freeze (UTC)", next_freeze_utc_str)
|
|
51
|
+
clock_table.add_row("Next epoch freeze (Local)", next_freeze_local_str)
|
|
52
|
+
clock_table.add_row("Countdown", countdown_str)
|
|
53
|
+
|
|
54
|
+
return clock_table
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def display_clock_and_countdown() -> None:
|
|
58
|
+
"""Display the clock table with current time and countdown."""
|
|
59
|
+
clock_table = get_clock_table()
|
|
60
|
+
console.print(clock_table)
|
|
61
|
+
console.print() # Empty line after table
|
|
62
|
+
|
cartha_cli/eth712.py
ADDED