cartha-cli 1.0.5__py3-none-any.whl → 1.0.8__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/bt.py +82 -74
- cartha_cli/commands/config.py +1 -1
- cartha_cli/commands/miner_password.py +0 -7
- cartha_cli/commands/miner_status.py +4 -25
- cartha_cli/commands/pair_status.py +2 -27
- cartha_cli/commands/pools.py +29 -52
- cartha_cli/commands/prove_lock.py +60 -143
- cartha_cli/commands/register.py +0 -16
- cartha_cli/config.py +4 -4
- cartha_cli/pool_client.py +214 -0
- cartha_cli/verifier.py +15 -0
- {cartha_cli-1.0.5.dist-info → cartha_cli-1.0.8.dist-info}/METADATA +22 -27
- {cartha_cli-1.0.5.dist-info → cartha_cli-1.0.8.dist-info}/RECORD +16 -15
- {cartha_cli-1.0.5.dist-info → cartha_cli-1.0.8.dist-info}/WHEEL +0 -0
- {cartha_cli-1.0.5.dist-info → cartha_cli-1.0.8.dist-info}/entry_points.txt +0 -0
- {cartha_cli-1.0.5.dist-info → cartha_cli-1.0.8.dist-info}/licenses/LICENSE +0 -0
cartha_cli/bt.py
CHANGED
|
@@ -51,92 +51,100 @@ def register_hotkey(
|
|
|
51
51
|
"""Register a hotkey on the target subnet and return the resulting UID."""
|
|
52
52
|
|
|
53
53
|
subtensor = get_subtensor(network)
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
if subtensor.is_hotkey_registered(hotkey_ss58, netuid=netuid):
|
|
58
|
-
neuron = subtensor.get_neuron_for_pubkey_and_subnet(hotkey_ss58, netuid)
|
|
59
|
-
uid = None if getattr(neuron, "is_null", False) else getattr(neuron, "uid", None)
|
|
60
|
-
return RegistrationResult(status="already", success=True, uid=uid, hotkey=hotkey_ss58)
|
|
54
|
+
try:
|
|
55
|
+
wallet = get_wallet(wallet_name, hotkey_name)
|
|
56
|
+
hotkey_ss58 = wallet.hotkey.ss58_address
|
|
61
57
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
58
|
+
if subtensor.is_hotkey_registered(hotkey_ss58, netuid=netuid):
|
|
59
|
+
neuron = subtensor.get_neuron_for_pubkey_and_subnet(hotkey_ss58, netuid)
|
|
60
|
+
uid = None if getattr(neuron, "is_null", False) else getattr(neuron, "uid", None)
|
|
61
|
+
return RegistrationResult(status="already", success=True, uid=uid, hotkey=hotkey_ss58)
|
|
66
62
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
except Exception:
|
|
72
|
-
pass # Balance may not be available, continue anyway
|
|
73
|
-
|
|
74
|
-
if burned:
|
|
75
|
-
# burned_register returns (success, block_info) or just success
|
|
76
|
-
registration_result = subtensor.burned_register(
|
|
77
|
-
wallet=wallet,
|
|
78
|
-
netuid=netuid,
|
|
79
|
-
wait_for_finalization=wait_for_finalization,
|
|
80
|
-
)
|
|
63
|
+
# Get balance before registration
|
|
64
|
+
balance_before = None
|
|
65
|
+
balance_after = None
|
|
66
|
+
extrinsic = None
|
|
81
67
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
if
|
|
86
|
-
|
|
68
|
+
try:
|
|
69
|
+
balance_obj = subtensor.get_balance(wallet.coldkeypub.ss58_address)
|
|
70
|
+
# Convert Balance object to float using .tao property
|
|
71
|
+
balance_before = balance_obj.tao if hasattr(balance_obj, "tao") else float(balance_obj)
|
|
72
|
+
except Exception:
|
|
73
|
+
pass # Balance may not be available, continue anyway
|
|
74
|
+
|
|
75
|
+
if burned:
|
|
76
|
+
# burned_register returns (success, block_info) or just success
|
|
77
|
+
registration_result = subtensor.burned_register(
|
|
78
|
+
wallet=wallet,
|
|
79
|
+
netuid=netuid,
|
|
80
|
+
wait_for_finalization=wait_for_finalization,
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
# Handle both return types: bool or (bool, message)
|
|
84
|
+
if isinstance(registration_result, tuple):
|
|
85
|
+
ok, message = registration_result
|
|
86
|
+
if isinstance(message, str) and message:
|
|
87
|
+
extrinsic = message
|
|
88
|
+
else:
|
|
89
|
+
ok = registration_result
|
|
90
|
+
|
|
91
|
+
status = "burned"
|
|
87
92
|
else:
|
|
88
|
-
ok =
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
93
|
+
ok = subtensor.register(
|
|
94
|
+
wallet=wallet,
|
|
95
|
+
netuid=netuid,
|
|
96
|
+
wait_for_finalization=wait_for_finalization,
|
|
97
|
+
wait_for_inclusion=wait_for_inclusion,
|
|
98
|
+
cuda=cuda,
|
|
99
|
+
dev_id=dev_id,
|
|
100
|
+
tpb=tpb,
|
|
101
|
+
num_processes=num_processes,
|
|
102
|
+
log_verbose=False,
|
|
103
|
+
)
|
|
104
|
+
status = "pow"
|
|
105
|
+
if isinstance(ok, tuple) and len(ok) == 2:
|
|
106
|
+
ok, message = ok
|
|
107
|
+
if isinstance(message, str):
|
|
108
|
+
extrinsic = message
|
|
109
|
+
|
|
110
|
+
if not ok:
|
|
111
|
+
return RegistrationResult(
|
|
112
|
+
status=status,
|
|
113
|
+
success=False,
|
|
114
|
+
uid=None,
|
|
115
|
+
hotkey=hotkey_ss58,
|
|
116
|
+
balance_before=balance_before,
|
|
117
|
+
balance_after=balance_after,
|
|
118
|
+
extrinsic=extrinsic,
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
# Get balance after registration
|
|
122
|
+
try:
|
|
123
|
+
balance_obj = subtensor.get_balance(wallet.coldkeypub.ss58_address)
|
|
124
|
+
# Convert Balance object to float using .tao property
|
|
125
|
+
balance_after = balance_obj.tao if hasattr(balance_obj, "tao") else float(balance_obj)
|
|
126
|
+
except Exception:
|
|
127
|
+
pass
|
|
128
|
+
|
|
129
|
+
neuron = subtensor.get_neuron_for_pubkey_and_subnet(hotkey_ss58, netuid)
|
|
130
|
+
uid = None if getattr(neuron, "is_null", False) else getattr(neuron, "uid", None)
|
|
108
131
|
|
|
109
|
-
if not ok:
|
|
110
132
|
return RegistrationResult(
|
|
111
133
|
status=status,
|
|
112
|
-
success=
|
|
113
|
-
uid=
|
|
134
|
+
success=True,
|
|
135
|
+
uid=uid,
|
|
114
136
|
hotkey=hotkey_ss58,
|
|
115
137
|
balance_before=balance_before,
|
|
116
138
|
balance_after=balance_after,
|
|
117
139
|
extrinsic=extrinsic,
|
|
118
140
|
)
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
pass
|
|
127
|
-
|
|
128
|
-
neuron = subtensor.get_neuron_for_pubkey_and_subnet(hotkey_ss58, netuid)
|
|
129
|
-
uid = None if getattr(neuron, "is_null", False) else getattr(neuron, "uid", None)
|
|
130
|
-
|
|
131
|
-
return RegistrationResult(
|
|
132
|
-
status=status,
|
|
133
|
-
success=True,
|
|
134
|
-
uid=uid,
|
|
135
|
-
hotkey=hotkey_ss58,
|
|
136
|
-
balance_before=balance_before,
|
|
137
|
-
balance_after=balance_after,
|
|
138
|
-
extrinsic=extrinsic,
|
|
139
|
-
)
|
|
141
|
+
finally:
|
|
142
|
+
# Clean up subtensor connection
|
|
143
|
+
try:
|
|
144
|
+
if hasattr(subtensor, "close"):
|
|
145
|
+
subtensor.close()
|
|
146
|
+
except Exception:
|
|
147
|
+
pass # Silently ignore cleanup errors
|
|
140
148
|
|
|
141
149
|
|
|
142
150
|
def get_burn_cost(network: str, netuid: int) -> float | None:
|
cartha_cli/commands/config.py
CHANGED
|
@@ -17,7 +17,7 @@ from .common import console
|
|
|
17
17
|
ENV_VAR_DOCS: dict[str, dict[str, Any]] = {
|
|
18
18
|
"CARTHA_VERIFIER_URL": {
|
|
19
19
|
"description": "URL of the Cartha verifier service",
|
|
20
|
-
"default": "https://cartha-verifier-
|
|
20
|
+
"default": "https://cartha-verifier-193291340038.us-central1.run.app",
|
|
21
21
|
"required": False,
|
|
22
22
|
},
|
|
23
23
|
"CARTHA_NETWORK": {
|
|
@@ -69,13 +69,6 @@ def miner_password(
|
|
|
69
69
|
netuid = 78
|
|
70
70
|
elif network == "finney":
|
|
71
71
|
netuid = 35
|
|
72
|
-
# Warn that mainnet is not live yet
|
|
73
|
-
console.print()
|
|
74
|
-
console.print("[bold yellow]⚠️ MAINNET NOT AVAILABLE YET[/]")
|
|
75
|
-
console.print("[yellow]Cartha subnet is currently in testnet phase (subnet 78).[/]")
|
|
76
|
-
console.print("[yellow]Mainnet (subnet 35) has not been announced yet.[/]")
|
|
77
|
-
console.print("[dim]Use --network test to access testnet.[/]")
|
|
78
|
-
console.print()
|
|
79
72
|
# Note: netuid parameter is kept for backwards compatibility / explicit override
|
|
80
73
|
|
|
81
74
|
from ..config import get_verifier_url_for_network
|
|
@@ -79,13 +79,6 @@ def miner_status(
|
|
|
79
79
|
netuid = 78
|
|
80
80
|
elif network == "finney":
|
|
81
81
|
netuid = 35
|
|
82
|
-
# Warn that mainnet is not live yet
|
|
83
|
-
console.print()
|
|
84
|
-
console.print("[bold yellow]⚠️ MAINNET NOT AVAILABLE YET[/]")
|
|
85
|
-
console.print("[yellow]Cartha subnet is currently in testnet phase (subnet 78).[/]")
|
|
86
|
-
console.print("[yellow]Mainnet (subnet 35) has not been announced yet.[/]")
|
|
87
|
-
console.print("[dim]Use --network test to access testnet.[/]")
|
|
88
|
-
console.print()
|
|
89
82
|
# Note: netuid parameter is kept for backwards compatibility / explicit override
|
|
90
83
|
|
|
91
84
|
from ..config import get_verifier_url_for_network
|
|
@@ -488,25 +481,14 @@ def miner_status(
|
|
|
488
481
|
|
|
489
482
|
console.print(pool_table)
|
|
490
483
|
|
|
491
|
-
# Concise reminder
|
|
492
|
-
console.print()
|
|
493
|
-
console.print("[bold cyan]━━━ Reminders ━━━[/]")
|
|
494
|
-
console.print(
|
|
495
|
-
"• Lock expiration: USDC returned automatically, emissions stop for that pool."
|
|
496
|
-
)
|
|
497
|
-
console.print(
|
|
498
|
-
"• Top-ups/extensions: Happen automatically on-chain. No CLI action needed."
|
|
499
|
-
)
|
|
500
|
-
if pools and len(pools) > 1:
|
|
501
|
-
console.print(
|
|
502
|
-
"• Multiple pools: Each pool is tracked separately. Expired pools stop earning, others continue."
|
|
503
|
-
)
|
|
504
|
-
|
|
505
484
|
# Link to web interface
|
|
506
485
|
console.print()
|
|
507
486
|
console.print("[bold cyan]━━━ Web Interface ━━━[/]")
|
|
508
487
|
console.print(
|
|
509
|
-
"[cyan]🌐
|
|
488
|
+
"[cyan]🌐 Manage your positions:[/] [bold]https://cartha.finance[/]"
|
|
489
|
+
)
|
|
490
|
+
console.print(
|
|
491
|
+
"[dim] • Deposit new lock positions[/]"
|
|
510
492
|
)
|
|
511
493
|
console.print(
|
|
512
494
|
"[dim] • View all your lock positions[/]"
|
|
@@ -517,8 +499,5 @@ def miner_status(
|
|
|
517
499
|
console.print(
|
|
518
500
|
"[dim] • Top up existing positions[/]"
|
|
519
501
|
)
|
|
520
|
-
console.print(
|
|
521
|
-
"[dim] • Claim testnet USDC from faucet[/]"
|
|
522
|
-
)
|
|
523
502
|
|
|
524
503
|
return
|
|
@@ -34,26 +34,8 @@ from .shared_options import (
|
|
|
34
34
|
json_output_option,
|
|
35
35
|
)
|
|
36
36
|
|
|
37
|
-
# Import pool name helper
|
|
38
|
-
|
|
39
|
-
def _fallback_pool_id_to_name(pool_id: str) -> str | None:
|
|
40
|
-
"""Simple fallback to decode pool ID."""
|
|
41
|
-
try:
|
|
42
|
-
hex_str = pool_id.lower().removeprefix("0x")
|
|
43
|
-
pool_bytes = bytes.fromhex(hex_str)
|
|
44
|
-
name = pool_bytes.rstrip(b"\x00").decode("utf-8", errors="ignore")
|
|
45
|
-
if name and name.isprintable():
|
|
46
|
-
return name
|
|
47
|
-
except Exception:
|
|
48
|
-
pass
|
|
49
|
-
return None
|
|
50
|
-
|
|
51
|
-
# Try to import from testnet module, fallback to default if not available
|
|
52
|
-
try:
|
|
53
|
-
from ..testnet.pool_ids import pool_id_to_name
|
|
54
|
-
except (ImportError, ModuleNotFoundError):
|
|
55
|
-
# Use fallback function
|
|
56
|
-
pool_id_to_name = _fallback_pool_id_to_name
|
|
37
|
+
# Import pool name helper from pool_client (fetches from verifier API)
|
|
38
|
+
from ..pool_client import pool_id_to_name
|
|
57
39
|
|
|
58
40
|
|
|
59
41
|
def pair_status(
|
|
@@ -100,13 +82,6 @@ def pair_status(
|
|
|
100
82
|
netuid = 78
|
|
101
83
|
elif network == "finney":
|
|
102
84
|
netuid = 35
|
|
103
|
-
# Warn that mainnet is not live yet
|
|
104
|
-
console.print()
|
|
105
|
-
console.print("[bold yellow]⚠️ MAINNET NOT AVAILABLE YET[/]")
|
|
106
|
-
console.print("[yellow]Cartha subnet is currently in testnet phase (subnet 78).[/]")
|
|
107
|
-
console.print("[yellow]Mainnet (subnet 35) has not been announced yet.[/]")
|
|
108
|
-
console.print("[dim]Use --network test to access testnet.[/]")
|
|
109
|
-
console.print()
|
|
110
85
|
# Note: netuid parameter is kept for backwards compatibility / explicit override
|
|
111
86
|
|
|
112
87
|
from ..config import get_verifier_url_for_network
|
cartha_cli/commands/pools.py
CHANGED
|
@@ -5,33 +5,7 @@ from __future__ import annotations
|
|
|
5
5
|
import typer
|
|
6
6
|
|
|
7
7
|
from .common import console
|
|
8
|
-
|
|
9
|
-
# Import pool helpers for pool_id conversion
|
|
10
|
-
# Initialize fallback functions first to ensure they're always defined
|
|
11
|
-
def _fallback_list_pools() -> dict[str, str]:
|
|
12
|
-
"""Fallback: return empty dict."""
|
|
13
|
-
return {}
|
|
14
|
-
|
|
15
|
-
def _fallback_pool_id_to_vault_address(pool_id: str) -> str | None:
|
|
16
|
-
"""Fallback: return None."""
|
|
17
|
-
return None
|
|
18
|
-
|
|
19
|
-
def _fallback_pool_id_to_chain_id(pool_id: str) -> int | None:
|
|
20
|
-
"""Fallback: return None."""
|
|
21
|
-
return None
|
|
22
|
-
|
|
23
|
-
# Try to import from testnet module, fallback to defaults if not available
|
|
24
|
-
try:
|
|
25
|
-
from ..testnet.pool_ids import (
|
|
26
|
-
list_pools,
|
|
27
|
-
pool_id_to_chain_id,
|
|
28
|
-
pool_id_to_vault_address,
|
|
29
|
-
)
|
|
30
|
-
except (ImportError, ModuleNotFoundError):
|
|
31
|
-
# Use fallback functions if import failed
|
|
32
|
-
list_pools = _fallback_list_pools
|
|
33
|
-
pool_id_to_vault_address = _fallback_pool_id_to_vault_address
|
|
34
|
-
pool_id_to_chain_id = _fallback_pool_id_to_chain_id
|
|
8
|
+
from ..verifier import VerifierError, fetch_pools
|
|
35
9
|
|
|
36
10
|
|
|
37
11
|
def pools(
|
|
@@ -48,47 +22,37 @@ def pools(
|
|
|
48
22
|
|
|
49
23
|
OUTPUT:
|
|
50
24
|
-------
|
|
51
|
-
- Pool names:
|
|
25
|
+
- Pool names: BTC/USD, ETH/USD, EUR/USD, etc.
|
|
52
26
|
- Pool IDs: Full hex identifiers (0x...)
|
|
53
27
|
- Vault addresses: Contract addresses for each pool
|
|
54
|
-
- Chain IDs: Which blockchain network (
|
|
28
|
+
- Chain IDs: Which blockchain network (8453 for Base Mainnet, 84532 for Base Sepolia)
|
|
55
29
|
|
|
56
30
|
Use these pool names directly in 'cartha vault lock -p BTCUSD ...'
|
|
57
31
|
"""
|
|
58
32
|
try:
|
|
59
|
-
|
|
33
|
+
# Fetch pools from verifier API
|
|
34
|
+
pools_list = fetch_pools()
|
|
60
35
|
|
|
61
36
|
if json_output:
|
|
62
37
|
# JSON output format
|
|
63
38
|
import json
|
|
64
|
-
|
|
65
|
-
pools_data = []
|
|
66
|
-
for pool_name, pool_id_hex in sorted(available_pools.items()):
|
|
67
|
-
vault_addr = pool_id_to_vault_address(pool_id_hex)
|
|
68
|
-
chain_id = pool_id_to_chain_id(pool_id_hex)
|
|
69
|
-
pool_data = {
|
|
70
|
-
"name": pool_name,
|
|
71
|
-
"pool_id": pool_id_hex,
|
|
72
|
-
}
|
|
73
|
-
if vault_addr:
|
|
74
|
-
pool_data["vault_address"] = vault_addr
|
|
75
|
-
if chain_id:
|
|
76
|
-
pool_data["chain_id"] = chain_id
|
|
77
|
-
pools_data.append(pool_data)
|
|
78
|
-
|
|
79
|
-
console.print(json.dumps(pools_data, indent=2))
|
|
39
|
+
console.print(json.dumps(pools_list, indent=2))
|
|
80
40
|
return
|
|
81
41
|
|
|
82
42
|
# Multi-line text output
|
|
83
|
-
if not
|
|
43
|
+
if not pools_list:
|
|
84
44
|
console.print("[yellow]No pools available.[/]")
|
|
85
45
|
return
|
|
86
46
|
|
|
87
47
|
console.print("\n[bold cyan]Available Pools[/]\n")
|
|
88
48
|
|
|
89
|
-
for idx,
|
|
90
|
-
|
|
91
|
-
|
|
49
|
+
for idx, pool in enumerate(pools_list, 1):
|
|
50
|
+
pool_name = pool.get("name", "Unknown")
|
|
51
|
+
# Verifier returns camelCase keys
|
|
52
|
+
pool_id_hex = pool.get("poolId", "")
|
|
53
|
+
vault_addr = pool.get("vaultAddress")
|
|
54
|
+
chain_id = pool.get("chainId")
|
|
55
|
+
network = pool.get("network", "")
|
|
92
56
|
|
|
93
57
|
# Ensure full pool ID is displayed (normalize to ensure 0x prefix)
|
|
94
58
|
pool_id_display = pool_id_hex if pool_id_hex.startswith("0x") else f"0x{pool_id_hex}"
|
|
@@ -96,7 +60,16 @@ def pools(
|
|
|
96
60
|
# Ensure full vault address is displayed
|
|
97
61
|
vault_display = vault_addr if vault_addr else "[dim]N/A[/]"
|
|
98
62
|
|
|
99
|
-
|
|
63
|
+
# Format chain display with network name
|
|
64
|
+
if chain_id:
|
|
65
|
+
if chain_id == 8453:
|
|
66
|
+
chain_display = f"{chain_id} (Base Mainnet)"
|
|
67
|
+
elif chain_id == 84532:
|
|
68
|
+
chain_display = f"{chain_id} (Base Sepolia)"
|
|
69
|
+
else:
|
|
70
|
+
chain_display = str(chain_id)
|
|
71
|
+
else:
|
|
72
|
+
chain_display = "[dim]N/A[/]"
|
|
100
73
|
|
|
101
74
|
console.print(f"[bold cyan]Pool {idx}:[/] {pool_name}")
|
|
102
75
|
console.print(f" [yellow]Pool ID:[/] {pool_id_display}")
|
|
@@ -104,9 +77,13 @@ def pools(
|
|
|
104
77
|
console.print(f" [dim]Chain ID:[/] {chain_display}")
|
|
105
78
|
|
|
106
79
|
# Add spacing between pools except for the last one
|
|
107
|
-
if idx < len(
|
|
80
|
+
if idx < len(pools_list):
|
|
108
81
|
console.print()
|
|
109
82
|
|
|
83
|
+
except VerifierError as exc:
|
|
84
|
+
console.print(f"[bold red]Error:[/] Failed to fetch pools from verifier: {exc}")
|
|
85
|
+
console.print("[dim]Tip: Check your network connection and verifier URL configuration.[/]")
|
|
86
|
+
raise typer.Exit(code=1)
|
|
110
87
|
except Exception as exc:
|
|
111
88
|
console.print(f"[bold red]Error:[/] Failed to list pools: {exc}")
|
|
112
89
|
raise typer.Exit(code=1)
|
|
@@ -42,71 +42,17 @@ from .shared_options import (
|
|
|
42
42
|
json_output_option,
|
|
43
43
|
)
|
|
44
44
|
|
|
45
|
-
# Import pool helpers
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
hex_str = pool_id.lower().removeprefix("0x")
|
|
57
|
-
pool_bytes = bytes.fromhex(hex_str)
|
|
58
|
-
name = pool_bytes.rstrip(b"\x00").decode("utf-8", errors="ignore")
|
|
59
|
-
return name if name and name.isprintable() else None
|
|
60
|
-
except Exception:
|
|
61
|
-
return None
|
|
62
|
-
|
|
63
|
-
def _fallback_format_pool_id(pool_id: str) -> str:
|
|
64
|
-
"""Fallback: return pool_id as-is."""
|
|
65
|
-
return pool_id
|
|
66
|
-
|
|
67
|
-
def _fallback_list_pools() -> dict[str, str]:
|
|
68
|
-
"""Fallback: return empty dict."""
|
|
69
|
-
return {}
|
|
70
|
-
|
|
71
|
-
def _fallback_pool_id_to_vault_address(pool_id: str) -> str | None:
|
|
72
|
-
"""Fallback: return None."""
|
|
73
|
-
return None
|
|
74
|
-
|
|
75
|
-
def _fallback_vault_address_to_pool_id(vault_address: str) -> str | None:
|
|
76
|
-
"""Fallback: return None."""
|
|
77
|
-
return None
|
|
78
|
-
|
|
79
|
-
def _fallback_pool_id_to_chain_id(pool_id: str) -> int | None:
|
|
80
|
-
"""Fallback: return None."""
|
|
81
|
-
return None
|
|
82
|
-
|
|
83
|
-
def _fallback_vault_address_to_chain_id(vault_address: str) -> int | None:
|
|
84
|
-
"""Fallback: return None."""
|
|
85
|
-
return None
|
|
86
|
-
|
|
87
|
-
# Try to import from testnet module, fallback to defaults if not available
|
|
88
|
-
try:
|
|
89
|
-
# Import from cartha_cli.testnet (works both in development and when installed)
|
|
90
|
-
from ..testnet.pool_ids import (
|
|
91
|
-
format_pool_id,
|
|
92
|
-
list_pools,
|
|
93
|
-
pool_id_to_chain_id,
|
|
94
|
-
pool_id_to_name,
|
|
95
|
-
pool_id_to_vault_address,
|
|
96
|
-
pool_name_to_id,
|
|
97
|
-
vault_address_to_chain_id,
|
|
98
|
-
vault_address_to_pool_id,
|
|
99
|
-
)
|
|
100
|
-
except (ImportError, ModuleNotFoundError):
|
|
101
|
-
# Use fallback functions if import failed
|
|
102
|
-
pool_name_to_id = _fallback_pool_name_to_id
|
|
103
|
-
pool_id_to_name = _fallback_pool_id_to_name
|
|
104
|
-
format_pool_id = _fallback_format_pool_id
|
|
105
|
-
list_pools = _fallback_list_pools
|
|
106
|
-
pool_id_to_vault_address = _fallback_pool_id_to_vault_address
|
|
107
|
-
vault_address_to_pool_id = _fallback_vault_address_to_pool_id
|
|
108
|
-
pool_id_to_chain_id = _fallback_pool_id_to_chain_id
|
|
109
|
-
vault_address_to_chain_id = _fallback_vault_address_to_chain_id
|
|
45
|
+
# Import pool helpers from pool_client (fetches from verifier API)
|
|
46
|
+
from ..pool_client import (
|
|
47
|
+
format_pool_id,
|
|
48
|
+
list_pools,
|
|
49
|
+
pool_id_to_chain_id,
|
|
50
|
+
pool_id_to_name,
|
|
51
|
+
pool_id_to_vault_address,
|
|
52
|
+
pool_name_to_id,
|
|
53
|
+
vault_address_to_chain_id,
|
|
54
|
+
vault_address_to_pool_id,
|
|
55
|
+
)
|
|
110
56
|
|
|
111
57
|
|
|
112
58
|
def prove_lock(
|
|
@@ -153,22 +99,6 @@ def prove_lock(
|
|
|
153
99
|
netuid = 78
|
|
154
100
|
elif network == "finney":
|
|
155
101
|
netuid = 35
|
|
156
|
-
# Warn that mainnet is not live yet
|
|
157
|
-
console.print()
|
|
158
|
-
console.print("[bold yellow]⚠️ MAINNET NOT AVAILABLE YET[/]")
|
|
159
|
-
console.print()
|
|
160
|
-
console.print("[yellow]Cartha subnet is currently in testnet phase (subnet 78 on test network).[/]")
|
|
161
|
-
console.print("[yellow]Mainnet (subnet 35 on finney network) has not been announced yet.[/]")
|
|
162
|
-
console.print()
|
|
163
|
-
console.print("[bold cyan]To use testnet:[/]")
|
|
164
|
-
console.print(" cartha vault lock --network test ...")
|
|
165
|
-
console.print()
|
|
166
|
-
console.print("[dim]If you continue with finney network, the CLI will attempt to connect[/]")
|
|
167
|
-
console.print("[dim]but the subnet may not be operational yet.[/]")
|
|
168
|
-
console.print()
|
|
169
|
-
if not Confirm.ask("[yellow]Continue with finney network anyway?[/]", default=False):
|
|
170
|
-
console.print("[yellow]Cancelled. Use --network test for testnet.[/]")
|
|
171
|
-
raise typer.Exit(code=0)
|
|
172
102
|
else:
|
|
173
103
|
# Default to finney settings if unknown network
|
|
174
104
|
netuid = 35
|
|
@@ -386,72 +316,51 @@ def prove_lock(
|
|
|
386
316
|
if not pool_id_normalized.startswith("0x"):
|
|
387
317
|
pool_id_normalized = "0x" + pool_id_normalized
|
|
388
318
|
|
|
389
|
-
|
|
390
|
-
auto_chain_id = pool_id_to_chain_id(pool_id_normalized)
|
|
391
|
-
except (NameError, AttributeError, TypeError):
|
|
392
|
-
# Function not available - this shouldn't happen if imports worked
|
|
393
|
-
# But handle gracefully by trying to import it
|
|
394
|
-
try:
|
|
395
|
-
from ..testnet.pool_ids import pool_id_to_chain_id
|
|
396
|
-
auto_chain_id = pool_id_to_chain_id(pool_id_normalized)
|
|
397
|
-
except (ImportError, ModuleNotFoundError, TypeError):
|
|
398
|
-
pass
|
|
319
|
+
auto_chain_id = pool_id_to_chain_id(pool_id_normalized)
|
|
399
320
|
|
|
400
321
|
if not auto_chain_id:
|
|
401
322
|
# Fallback: try to get from vault address
|
|
402
|
-
|
|
403
|
-
auto_chain_id = vault_address_to_chain_id(vault)
|
|
404
|
-
except (NameError, AttributeError, TypeError):
|
|
405
|
-
try:
|
|
406
|
-
from ..testnet.pool_ids import vault_address_to_chain_id
|
|
407
|
-
auto_chain_id = vault_address_to_chain_id(vault)
|
|
408
|
-
except (ImportError, ModuleNotFoundError, TypeError):
|
|
409
|
-
pass
|
|
323
|
+
auto_chain_id = vault_address_to_chain_id(vault)
|
|
410
324
|
|
|
411
325
|
if auto_chain_id:
|
|
412
326
|
chain = auto_chain_id
|
|
413
|
-
|
|
327
|
+
if chain == 8453:
|
|
328
|
+
chain_name = "Base Mainnet"
|
|
329
|
+
elif chain == 84532:
|
|
330
|
+
chain_name = "Base Sepolia"
|
|
331
|
+
else:
|
|
332
|
+
chain_name = f"Chain {chain}"
|
|
414
333
|
console.print(
|
|
415
334
|
f"[bold green]✓ Auto-matched chain ID[/] - {chain_name} (chain ID: {chain})"
|
|
416
335
|
)
|
|
417
336
|
else:
|
|
418
|
-
#
|
|
419
|
-
|
|
420
|
-
chain
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
console.print(
|
|
435
|
-
"[bold red]Error:[/] Chain ID must be a positive integer"
|
|
436
|
-
)
|
|
437
|
-
continue
|
|
438
|
-
break
|
|
439
|
-
except ValueError:
|
|
440
|
-
console.print("[bold red]Error:[/] Chain ID must be a valid integer")
|
|
337
|
+
# Prompt for chain ID if no mapping found
|
|
338
|
+
console.print(
|
|
339
|
+
"[yellow]⚠ No chain ID mapping found. Please provide chain ID.[/]"
|
|
340
|
+
)
|
|
341
|
+
while True:
|
|
342
|
+
try:
|
|
343
|
+
chain_input = typer.prompt("Chain ID", show_default=False)
|
|
344
|
+
chain = int(chain_input)
|
|
345
|
+
if chain <= 0:
|
|
346
|
+
console.print(
|
|
347
|
+
"[bold red]Error:[/] Chain ID must be a positive integer"
|
|
348
|
+
)
|
|
349
|
+
continue
|
|
350
|
+
break
|
|
351
|
+
except ValueError:
|
|
352
|
+
console.print("[bold red]Error:[/] Chain ID must be a valid integer")
|
|
441
353
|
else:
|
|
442
354
|
# Chain ID was provided, verify it matches vault if possible
|
|
443
|
-
expected_chain_id =
|
|
444
|
-
try:
|
|
445
|
-
expected_chain_id = vault_address_to_chain_id(vault)
|
|
446
|
-
except (NameError, AttributeError):
|
|
447
|
-
try:
|
|
448
|
-
from ..testnet.pool_ids import vault_address_to_chain_id
|
|
449
|
-
expected_chain_id = vault_address_to_chain_id(vault)
|
|
450
|
-
except (ImportError, ModuleNotFoundError):
|
|
451
|
-
pass
|
|
355
|
+
expected_chain_id = vault_address_to_chain_id(vault)
|
|
452
356
|
|
|
453
357
|
if expected_chain_id and expected_chain_id != chain:
|
|
454
|
-
|
|
358
|
+
if expected_chain_id == 8453:
|
|
359
|
+
chain_name = "Base Mainnet"
|
|
360
|
+
elif expected_chain_id == 84532:
|
|
361
|
+
chain_name = "Base Sepolia"
|
|
362
|
+
else:
|
|
363
|
+
chain_name = f"Chain {expected_chain_id}"
|
|
455
364
|
console.print(
|
|
456
365
|
f"[bold yellow]⚠ Warning:[/] Vault {vault} is on {chain_name} (chain ID: {expected_chain_id}), "
|
|
457
366
|
f"but you specified chain ID {chain}"
|
|
@@ -674,8 +583,11 @@ def prove_lock(
|
|
|
674
583
|
"\n[bold yellow]⚠️ Execute these transactions to complete your lock:[/]\n"
|
|
675
584
|
)
|
|
676
585
|
|
|
677
|
-
# USDC contract address
|
|
678
|
-
|
|
586
|
+
# USDC contract address based on chain ID
|
|
587
|
+
if chain == 8453: # Base Mainnet
|
|
588
|
+
usdc_contract_address = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
|
|
589
|
+
else: # Base Sepolia (default)
|
|
590
|
+
usdc_contract_address = "0x2340D09c348930A76c8c2783EDa8610F699A51A8"
|
|
679
591
|
|
|
680
592
|
console.print("[bold cyan]━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━[/]")
|
|
681
593
|
console.print("[bold]Phase 1: Approve USDC[/]")
|
|
@@ -694,7 +606,7 @@ def prove_lock(
|
|
|
694
606
|
phase1_params = {
|
|
695
607
|
"phase": "1",
|
|
696
608
|
"chainId": str(chain),
|
|
697
|
-
"usdcAddress":
|
|
609
|
+
"usdcAddress": usdc_contract_address,
|
|
698
610
|
"vaultAddress": vault,
|
|
699
611
|
"spender": vault,
|
|
700
612
|
"amount": str(amount_base_units),
|
|
@@ -714,8 +626,11 @@ def prove_lock(
|
|
|
714
626
|
console.print("[dim]The CLI will automatically detect when the approval is complete.[/]")
|
|
715
627
|
console.print("[dim]You can also press Ctrl+C to skip and continue manually.[/]")
|
|
716
628
|
|
|
717
|
-
#
|
|
718
|
-
|
|
629
|
+
# RPC endpoint based on chain ID
|
|
630
|
+
if chain == 8453: # Base Mainnet
|
|
631
|
+
rpc_endpoint = "https://mainnet.base.org"
|
|
632
|
+
else: # Base Sepolia (default)
|
|
633
|
+
rpc_endpoint = "https://sepolia.base.org"
|
|
719
634
|
|
|
720
635
|
# ERC20 ABI for allowance function and Approval event
|
|
721
636
|
erc20_abi = [
|
|
@@ -746,7 +661,7 @@ def prove_lock(
|
|
|
746
661
|
|
|
747
662
|
approval_detected = False
|
|
748
663
|
try:
|
|
749
|
-
w3 = Web3(Web3.HTTPProvider(
|
|
664
|
+
w3 = Web3(Web3.HTTPProvider(rpc_endpoint))
|
|
750
665
|
usdc_contract = w3.eth.contract(
|
|
751
666
|
address=Web3.to_checksum_address(usdc_contract_address),
|
|
752
667
|
abi=erc20_abi
|
|
@@ -941,13 +856,15 @@ def prove_lock(
|
|
|
941
856
|
}
|
|
942
857
|
]
|
|
943
858
|
|
|
944
|
-
# Get RPC endpoint
|
|
859
|
+
# Get RPC endpoint based on chain ID
|
|
945
860
|
rpc_url = None
|
|
946
|
-
if chain ==
|
|
861
|
+
if chain == 8453: # Base Mainnet
|
|
862
|
+
rpc_url = "https://mainnet.base.org"
|
|
863
|
+
elif chain == 84532: # Base Sepolia
|
|
947
864
|
rpc_url = "https://sepolia.base.org"
|
|
948
865
|
else:
|
|
949
|
-
#
|
|
950
|
-
console.print(f"[yellow]Warning:[/] Auto-detection only supports Base
|
|
866
|
+
# Unsupported chain for auto-detection
|
|
867
|
+
console.print(f"[yellow]Warning:[/] Auto-detection only supports Base Mainnet (8453) and Base Sepolia (84532), but chain ID {chain} was specified.")
|
|
951
868
|
console.print("[dim]You'll need to enter the transaction hash manually.[/]")
|
|
952
869
|
rpc_url = None
|
|
953
870
|
|
cartha_cli/commands/register.py
CHANGED
|
@@ -79,22 +79,6 @@ def register(
|
|
|
79
79
|
netuid = 78
|
|
80
80
|
elif network == "finney":
|
|
81
81
|
netuid = 35
|
|
82
|
-
# Warn that mainnet is not live yet
|
|
83
|
-
console.print()
|
|
84
|
-
console.print("[bold yellow]⚠️ MAINNET NOT AVAILABLE YET[/]")
|
|
85
|
-
console.print()
|
|
86
|
-
console.print("[yellow]Cartha subnet is currently in testnet phase (subnet 78 on test network).[/]")
|
|
87
|
-
console.print("[yellow]Mainnet (subnet 35 on finney network) has not been announced yet.[/]")
|
|
88
|
-
console.print()
|
|
89
|
-
console.print("[bold cyan]To use testnet:[/]")
|
|
90
|
-
console.print(" cartha miner register --network test")
|
|
91
|
-
console.print()
|
|
92
|
-
console.print("[dim]If you continue with finney network, registration will attempt[/]")
|
|
93
|
-
console.print("[dim]subnet 35 but the subnet may not be operational yet.[/]")
|
|
94
|
-
console.print()
|
|
95
|
-
if not Confirm.ask("[yellow]Continue with finney network anyway?[/]", default=False):
|
|
96
|
-
console.print("[yellow]Cancelled. Use --network test for testnet.[/]")
|
|
97
|
-
raise typer.Exit(code=0)
|
|
98
82
|
# Note: netuid parameter is kept for backwards compatibility / explicit override
|
|
99
83
|
|
|
100
84
|
from ..config import get_verifier_url_for_network
|
cartha_cli/config.py
CHANGED
|
@@ -13,7 +13,7 @@ from pydantic_settings import BaseSettings
|
|
|
13
13
|
# Network to verifier URL mapping
|
|
14
14
|
NETWORK_VERIFIER_MAP = {
|
|
15
15
|
"test": "https://cartha-verifier-826542474079.us-central1.run.app",
|
|
16
|
-
"finney":
|
|
16
|
+
"finney": "https://cartha-verifier-193291340038.us-central1.run.app",
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
|
|
@@ -36,13 +36,13 @@ def get_verifier_url_for_network(network: str) -> str:
|
|
|
36
36
|
if mapped_url:
|
|
37
37
|
return mapped_url
|
|
38
38
|
|
|
39
|
-
# Default fallback
|
|
40
|
-
return "https://cartha-verifier-
|
|
39
|
+
# Default fallback to mainnet
|
|
40
|
+
return "https://cartha-verifier-193291340038.us-central1.run.app"
|
|
41
41
|
|
|
42
42
|
|
|
43
43
|
class Settings(BaseSettings):
|
|
44
44
|
verifier_url: str = Field(
|
|
45
|
-
"https://cartha-verifier-
|
|
45
|
+
"https://cartha-verifier-193291340038.us-central1.run.app", alias="CARTHA_VERIFIER_URL"
|
|
46
46
|
)
|
|
47
47
|
network: str = Field("finney", alias="CARTHA_NETWORK")
|
|
48
48
|
netuid: int = Field(35, alias="CARTHA_NETUID")
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
"""Pool client - fetches pool data from verifier API with caching."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from functools import lru_cache
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from .verifier import VerifierError, fetch_pools
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
# Cache pool data for 5 minutes (300 seconds)
|
|
12
|
+
# Using a module-level cache that can be cleared if needed
|
|
13
|
+
_pools_cache: dict[str, Any] | None = None
|
|
14
|
+
_cache_timestamp: float = 0
|
|
15
|
+
_CACHE_TTL_SECONDS = 300
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _get_cached_pools() -> list[dict[str, Any]]:
|
|
19
|
+
"""Get pools with caching (5 minute TTL)."""
|
|
20
|
+
global _pools_cache, _cache_timestamp
|
|
21
|
+
import time
|
|
22
|
+
|
|
23
|
+
now = time.time()
|
|
24
|
+
if _pools_cache is None or (now - _cache_timestamp) > _CACHE_TTL_SECONDS:
|
|
25
|
+
try:
|
|
26
|
+
_pools_cache = fetch_pools()
|
|
27
|
+
_cache_timestamp = now
|
|
28
|
+
except VerifierError:
|
|
29
|
+
# If fetch fails and we have cached data, use it
|
|
30
|
+
if _pools_cache is not None:
|
|
31
|
+
return _pools_cache
|
|
32
|
+
# Otherwise return empty list
|
|
33
|
+
return []
|
|
34
|
+
|
|
35
|
+
return _pools_cache or []
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def clear_cache() -> None:
|
|
39
|
+
"""Clear the pools cache."""
|
|
40
|
+
global _pools_cache, _cache_timestamp
|
|
41
|
+
_pools_cache = None
|
|
42
|
+
_cache_timestamp = 0
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def list_pools() -> dict[str, str]:
|
|
46
|
+
"""List all available pools from verifier.
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
Dictionary mapping pool names (e.g., "BTCUSD") to hex pool IDs
|
|
50
|
+
"""
|
|
51
|
+
pools = _get_cached_pools()
|
|
52
|
+
result = {}
|
|
53
|
+
for pool in pools:
|
|
54
|
+
name = pool.get("name", "")
|
|
55
|
+
# Verifier returns camelCase keys
|
|
56
|
+
pool_id = pool.get("poolId", "")
|
|
57
|
+
if name and pool_id:
|
|
58
|
+
# Normalize name: "BTC/USD" -> "BTCUSD"
|
|
59
|
+
normalized_name = name.replace("/", "").upper()
|
|
60
|
+
result[normalized_name] = pool_id.lower()
|
|
61
|
+
return result
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def pool_name_to_id(pool_name: str) -> str:
|
|
65
|
+
"""Convert a readable pool name to hex pool ID.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
pool_name: Readable name (e.g., "BTCUSD", "BTC/USD")
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
Hex pool ID (bytes32 format)
|
|
72
|
+
"""
|
|
73
|
+
normalized = pool_name.replace("/", "").upper()
|
|
74
|
+
pools = list_pools()
|
|
75
|
+
|
|
76
|
+
if normalized in pools:
|
|
77
|
+
return pools[normalized]
|
|
78
|
+
|
|
79
|
+
# Fallback: encode the name as hex (for unknown pools)
|
|
80
|
+
name_bytes = pool_name.encode("utf-8")
|
|
81
|
+
if len(name_bytes) > 32:
|
|
82
|
+
raise ValueError(f"Pool name too long: {pool_name} (max 32 bytes)")
|
|
83
|
+
padded = name_bytes.rjust(32, b"\x00")
|
|
84
|
+
return "0x" + padded.hex()
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def pool_id_to_name(pool_id: str) -> str | None:
|
|
88
|
+
"""Convert a hex pool ID to readable name if available.
|
|
89
|
+
|
|
90
|
+
Args:
|
|
91
|
+
pool_id: Hex pool ID (bytes32 format)
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
Readable name if found, None otherwise
|
|
95
|
+
"""
|
|
96
|
+
pools = _get_cached_pools()
|
|
97
|
+
pool_id_lower = pool_id.lower()
|
|
98
|
+
|
|
99
|
+
for pool in pools:
|
|
100
|
+
# Verifier returns camelCase keys
|
|
101
|
+
if pool.get("poolId", "").lower() == pool_id_lower:
|
|
102
|
+
name = pool.get("name", "")
|
|
103
|
+
# Return normalized name without slash
|
|
104
|
+
return name.replace("/", "").upper() if name else None
|
|
105
|
+
|
|
106
|
+
return None
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def format_pool_id(pool_id: str) -> str:
|
|
110
|
+
"""Format a pool ID for display (shows readable name if available).
|
|
111
|
+
|
|
112
|
+
Args:
|
|
113
|
+
pool_id: Hex pool ID
|
|
114
|
+
|
|
115
|
+
Returns:
|
|
116
|
+
Formatted string: "BTCUSD (0x...)" or just "0x..." if no name found
|
|
117
|
+
"""
|
|
118
|
+
name = pool_id_to_name(pool_id)
|
|
119
|
+
if name:
|
|
120
|
+
return f"{name} ({pool_id})"
|
|
121
|
+
return pool_id
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def pool_id_to_vault_address(pool_id: str) -> str | None:
|
|
125
|
+
"""Get vault address for a given pool ID.
|
|
126
|
+
|
|
127
|
+
Args:
|
|
128
|
+
pool_id: Pool ID in hex format (bytes32)
|
|
129
|
+
|
|
130
|
+
Returns:
|
|
131
|
+
Vault address if found, None otherwise
|
|
132
|
+
"""
|
|
133
|
+
pools = _get_cached_pools()
|
|
134
|
+
pool_id_lower = pool_id.lower()
|
|
135
|
+
|
|
136
|
+
for pool in pools:
|
|
137
|
+
# Verifier returns camelCase keys
|
|
138
|
+
if pool.get("poolId", "").lower() == pool_id_lower:
|
|
139
|
+
return pool.get("vaultAddress")
|
|
140
|
+
|
|
141
|
+
return None
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def vault_address_to_pool_id(vault_address: str) -> str | None:
|
|
145
|
+
"""Get pool ID for a given vault address.
|
|
146
|
+
|
|
147
|
+
Args:
|
|
148
|
+
vault_address: Vault contract address
|
|
149
|
+
|
|
150
|
+
Returns:
|
|
151
|
+
Pool ID if found, None otherwise
|
|
152
|
+
"""
|
|
153
|
+
pools = _get_cached_pools()
|
|
154
|
+
vault_lower = vault_address.lower()
|
|
155
|
+
|
|
156
|
+
for pool in pools:
|
|
157
|
+
# Verifier returns camelCase keys
|
|
158
|
+
if pool.get("vaultAddress", "").lower() == vault_lower:
|
|
159
|
+
return pool.get("poolId", "").lower()
|
|
160
|
+
|
|
161
|
+
return None
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def pool_id_to_chain_id(pool_id: str) -> int | None:
|
|
165
|
+
"""Get chain ID for a given pool ID.
|
|
166
|
+
|
|
167
|
+
Args:
|
|
168
|
+
pool_id: Pool ID in hex format (bytes32)
|
|
169
|
+
|
|
170
|
+
Returns:
|
|
171
|
+
Chain ID if found, None otherwise
|
|
172
|
+
"""
|
|
173
|
+
pools = _get_cached_pools()
|
|
174
|
+
pool_id_lower = pool_id.lower()
|
|
175
|
+
|
|
176
|
+
for pool in pools:
|
|
177
|
+
# Verifier returns camelCase keys
|
|
178
|
+
if pool.get("poolId", "").lower() == pool_id_lower:
|
|
179
|
+
return pool.get("chainId")
|
|
180
|
+
|
|
181
|
+
return None
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def vault_address_to_chain_id(vault_address: str) -> int | None:
|
|
185
|
+
"""Get chain ID for a given vault address.
|
|
186
|
+
|
|
187
|
+
Args:
|
|
188
|
+
vault_address: Vault contract address
|
|
189
|
+
|
|
190
|
+
Returns:
|
|
191
|
+
Chain ID if found, None otherwise
|
|
192
|
+
"""
|
|
193
|
+
pools = _get_cached_pools()
|
|
194
|
+
vault_lower = vault_address.lower()
|
|
195
|
+
|
|
196
|
+
for pool in pools:
|
|
197
|
+
# Verifier returns camelCase keys
|
|
198
|
+
if pool.get("vaultAddress", "").lower() == vault_lower:
|
|
199
|
+
return pool.get("chainId")
|
|
200
|
+
|
|
201
|
+
return None
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
__all__ = [
|
|
205
|
+
"clear_cache",
|
|
206
|
+
"list_pools",
|
|
207
|
+
"pool_name_to_id",
|
|
208
|
+
"pool_id_to_name",
|
|
209
|
+
"format_pool_id",
|
|
210
|
+
"pool_id_to_vault_address",
|
|
211
|
+
"vault_address_to_pool_id",
|
|
212
|
+
"pool_id_to_chain_id",
|
|
213
|
+
"vault_address_to_chain_id",
|
|
214
|
+
]
|
cartha_cli/verifier.py
CHANGED
|
@@ -324,6 +324,20 @@ def process_lock_transaction(
|
|
|
324
324
|
)
|
|
325
325
|
|
|
326
326
|
|
|
327
|
+
def fetch_pools() -> list[dict[str, Any]]:
|
|
328
|
+
"""Fetch available pools from verifier.
|
|
329
|
+
|
|
330
|
+
Returns list of pools with:
|
|
331
|
+
- name: Human-readable pool name (e.g., "BTC/USD")
|
|
332
|
+
- pool_id: Hex pool ID (bytes32)
|
|
333
|
+
- vault_address: Vault contract address
|
|
334
|
+
- chain_id: Chain ID
|
|
335
|
+
- network: Network name ("mainnet" or "testnet")
|
|
336
|
+
"""
|
|
337
|
+
data = _request("GET", "/pools")
|
|
338
|
+
return data.get("pools", [])
|
|
339
|
+
|
|
340
|
+
|
|
327
341
|
# REMOVED: Old endpoints - replaced by new lock flow
|
|
328
342
|
# fetch_pair_password, register_pair_password, submit_lock_proof removed
|
|
329
343
|
|
|
@@ -339,4 +353,5 @@ __all__ = [
|
|
|
339
353
|
"request_lock_signature",
|
|
340
354
|
"get_lock_status",
|
|
341
355
|
"process_lock_transaction",
|
|
356
|
+
"fetch_pools",
|
|
342
357
|
]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cartha-cli
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.8
|
|
4
4
|
Summary: CLI utilities for Cartha subnet miners.
|
|
5
5
|
Project-URL: Homepage, https://cartha.finance
|
|
6
6
|
Project-URL: Repository, https://github.com/General-Tao-Ventures/cartha-cli
|
|
@@ -43,7 +43,7 @@ Cartha CLI makes mining on the Cartha subnet effortless. As the Liquidity Provid
|
|
|
43
43
|
- **📊 Instant Status Updates** - See all your pools, balances, and expiration dates at a glance
|
|
44
44
|
- **⏰ Smart Expiration Warnings** - Never miss a renewal with color-coded countdowns
|
|
45
45
|
- **💼 Multi-Pool Management** - Track multiple trading pairs in one place
|
|
46
|
-
-
|
|
46
|
+
- **🔒 Secure Authentication** - Session-based authentication with your Bittensor hotkey
|
|
47
47
|
|
|
48
48
|
## Installation
|
|
49
49
|
|
|
@@ -64,17 +64,19 @@ cartha miner register --help
|
|
|
64
64
|
cartha miner status --help
|
|
65
65
|
|
|
66
66
|
# Check CLI health and connectivity
|
|
67
|
-
cartha health
|
|
67
|
+
cartha utils health
|
|
68
68
|
|
|
69
69
|
# Or use short aliases
|
|
70
70
|
cartha m status
|
|
71
71
|
cartha v lock
|
|
72
|
+
cartha u health
|
|
72
73
|
```
|
|
73
74
|
|
|
74
75
|
## Requirements
|
|
75
76
|
|
|
76
77
|
- Python 3.11
|
|
77
|
-
- Bittensor wallet
|
|
78
|
+
- Bittensor wallet within btcli
|
|
79
|
+
- learn how to create/import one here https://docs.learnbittensor.org/keys/working-with-keys
|
|
78
80
|
|
|
79
81
|
## What You Can Do
|
|
80
82
|
|
|
@@ -90,14 +92,12 @@ cartha miner register --wallet-name your-wallet --wallet-hotkey your-hotkey
|
|
|
90
92
|
cartha miner status --wallet-name your-wallet --wallet-hotkey your-hotkey
|
|
91
93
|
# Or use the short alias: cartha m status
|
|
92
94
|
```
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
-
|
|
98
|
-
-
|
|
99
|
-
- Days remaining before expiration (with helpful warnings)
|
|
100
|
-
- Which pools are included in the next reward epoch
|
|
95
|
+
> **Track Your Miner Status**
|
|
96
|
+
> See all your active trading pairs, balances, and when they expire—all in one command. The CLI shows you:
|
|
97
|
+
> - Which pools are active and earning rewards
|
|
98
|
+
> - How much you have locked in each pool
|
|
99
|
+
> - Days remaining before expiration (with helpful warnings)
|
|
100
|
+
> - Which pools are included in the next reward epoch
|
|
101
101
|
|
|
102
102
|
### View Available Pools
|
|
103
103
|
|
|
@@ -110,14 +110,14 @@ cartha vault pools
|
|
|
110
110
|
|
|
111
111
|
This shows you which pools are available, their full pool IDs, vault contract addresses, and chain IDs.
|
|
112
112
|
|
|
113
|
-
### Lock Your Funds
|
|
113
|
+
### Lock Your Funds to start Mining
|
|
114
114
|
|
|
115
115
|
Create a new lock position with the streamlined lock flow:
|
|
116
116
|
```bash
|
|
117
117
|
cartha vault lock \
|
|
118
118
|
--coldkey your-wallet \
|
|
119
119
|
--hotkey your-hotkey \
|
|
120
|
-
--pool-id
|
|
120
|
+
--pool-id BTCUSD \
|
|
121
121
|
--amount 1000.0 \
|
|
122
122
|
--lock-days 30 \
|
|
123
123
|
--owner-evm 0xYourEVMAddress \
|
|
@@ -136,36 +136,31 @@ The CLI will:
|
|
|
136
136
|
1. Check your registration on the specified network (subnet 35 for finney, subnet 78 for test)
|
|
137
137
|
2. Authenticate with your Bittensor hotkey
|
|
138
138
|
3. Request a signed LockRequest from the verifier
|
|
139
|
-
4. Automatically open the Cartha Lock UI in your browser with all parameters pre-filled
|
|
139
|
+
4. Automatically open the Cartha Lock UI in your browser with all parameters pre-filled (you can also paste the url into your browser manually)
|
|
140
140
|
5. Guide you through Phase 1 (Approve USDC) and Phase 2 (Lock Position) via the web interface
|
|
141
141
|
6. Automatically detect when approval completes and proceed to Phase 2
|
|
142
142
|
7. The verifier automatically detects your lock and adds you to the upcoming epoch
|
|
143
143
|
|
|
144
144
|
**Managing Positions**: Visit https://cartha.finance/manage to view all your positions, extend locks, or top up existing positions.
|
|
145
145
|
|
|
146
|
-
### View Your Password
|
|
147
|
-
|
|
148
|
-
When you need your password (like for signing transactions):
|
|
149
|
-
```bash
|
|
150
|
-
cartha miner password --wallet-name your-wallet --wallet-hotkey your-hotkey
|
|
151
|
-
```
|
|
152
|
-
|
|
153
|
-
**Tip:** Use `miner status` for daily checks—it's faster and doesn't require signing. Only use `miner password` when you actually need it.
|
|
154
|
-
|
|
155
146
|
### Check Your Setup
|
|
156
147
|
|
|
157
148
|
Verify your CLI is configured correctly and can reach all services:
|
|
158
149
|
|
|
159
150
|
```bash
|
|
160
|
-
cartha health
|
|
151
|
+
cartha utils health
|
|
152
|
+
# Or use the short alias
|
|
153
|
+
cartha u health
|
|
161
154
|
```
|
|
162
155
|
|
|
163
156
|
This checks:
|
|
164
157
|
- Verifier connectivity and latency
|
|
165
158
|
- Bittensor network connectivity
|
|
166
159
|
- Configuration validation
|
|
160
|
+
- Subnet metadata
|
|
161
|
+
- Environment variables
|
|
167
162
|
|
|
168
|
-
Use `cartha health --verbose` for detailed troubleshooting information.
|
|
163
|
+
Use `cartha utils health --verbose` (or `cartha u health --verbose`) for detailed troubleshooting information.
|
|
169
164
|
|
|
170
165
|
## Need Help?
|
|
171
166
|
|
|
@@ -179,4 +174,4 @@ We welcome contributions! Please see our [Feedback & Support](docs/FEEDBACK.md)
|
|
|
179
174
|
|
|
180
175
|
---
|
|
181
176
|
|
|
182
|
-
**Made with ❤ by
|
|
177
|
+
**Made with ❤ by General Tensor**
|
|
@@ -1,31 +1,32 @@
|
|
|
1
1
|
cartha_cli/__init__.py,sha256=cSKsPAfHW8_hqJkGMXUh4mFNz9HXsq-UcgRl8G_2KhM,720
|
|
2
|
-
cartha_cli/bt.py,sha256=
|
|
3
|
-
cartha_cli/config.py,sha256=
|
|
2
|
+
cartha_cli/bt.py,sha256=UC5n0LpLD2YNOL-xsawrzohXpuHaoClraQh3fu4qI90,7467
|
|
3
|
+
cartha_cli/config.py,sha256=eIrLNzkG6NE7zr2duDAxEuAdTgpJsDz1oymL5RkNwJc,2272
|
|
4
4
|
cartha_cli/display.py,sha256=Krim69DLgiCHSYDuWNjnOep25IAHUebRAceePHfsiRA,2040
|
|
5
5
|
cartha_cli/eth712.py,sha256=5NU0MnvOk89mxWnkDHzoOaSHN8TJGRAHVLGXmCq8jhM,241
|
|
6
6
|
cartha_cli/main.py,sha256=0-G2syhsj2okLblX5WYb5NGqWjOwF5eW2tDn9AF5vaw,6756
|
|
7
7
|
cartha_cli/pair.py,sha256=Y-TAjAK5FeWqKlUK52dHZLdBhDxx1CJbea8mlsQXkqc,6245
|
|
8
|
+
cartha_cli/pool_client.py,sha256=1mUpzeZX5pzOGW4cV4Y9o9Dtkjw1GgytEHoT3vNo4mE,5717
|
|
8
9
|
cartha_cli/utils.py,sha256=LWlJXzWNkIpInWclCJ2PObkG--JEN1mIcvVXsHmFllE,8429
|
|
9
|
-
cartha_cli/verifier.py,sha256=
|
|
10
|
+
cartha_cli/verifier.py,sha256=sKIjJBwz-Rir-hQNwM8CVgE9fpBu8TnGKQKJ-7Ub4_c,10985
|
|
10
11
|
cartha_cli/wallet.py,sha256=Jha1pONa4Hy2XsHMTrk60eBqdkwJyzsQYgPuI-cxxFo,1770
|
|
11
12
|
cartha_cli/commands/__init__.py,sha256=8CYMVEWJmg2qjLyE3ZeheQtS-E9doltDSfyyumOsET4,345
|
|
12
13
|
cartha_cli/commands/common.py,sha256=tpxKsdzjFtcb0ae7J-J-zxnh32qVXZd8jwjP-frFoio,2099
|
|
13
|
-
cartha_cli/commands/config.py,sha256=
|
|
14
|
+
cartha_cli/commands/config.py,sha256=1G6q6PU7qj6AA71pH9nUygoU6yLVgU8tbreLPcgd6rU,10539
|
|
14
15
|
cartha_cli/commands/health.py,sha256=NRwmIultxUzAe7udOOBIdkcdGG5KsE_kuCs43LZObGk,20717
|
|
15
16
|
cartha_cli/commands/help.py,sha256=6ubfWtmjXfCtp6L_PYvn7rR7m5C_pp-iEjtRc6BS0GA,1721
|
|
16
|
-
cartha_cli/commands/miner_password.py,sha256=
|
|
17
|
-
cartha_cli/commands/miner_status.py,sha256=
|
|
18
|
-
cartha_cli/commands/pair_status.py,sha256=
|
|
19
|
-
cartha_cli/commands/pools.py,sha256=
|
|
20
|
-
cartha_cli/commands/prove_lock.py,sha256=
|
|
21
|
-
cartha_cli/commands/register.py,sha256=
|
|
17
|
+
cartha_cli/commands/miner_password.py,sha256=7IZcufDKiBTmnW2pPshQBCEh69nKR-FBWfWXteA0mso,10635
|
|
18
|
+
cartha_cli/commands/miner_status.py,sha256=lw4e8BVhJCA8fPPO7Fr6ZuN46hgkjHGPISFJAs7ULe8,20843
|
|
19
|
+
cartha_cli/commands/pair_status.py,sha256=4NqyO30HAawkzSoCM5SU7KFMCPel-YFP66FMDkKB_es,18905
|
|
20
|
+
cartha_cli/commands/pools.py,sha256=GzYqpJvpTiY7cgTZCZwlzwxPpycV7JZkGwZ1A5n6lno,3225
|
|
21
|
+
cartha_cli/commands/prove_lock.py,sha256=xKQN_ZujZQ5-_6czQpG51CZtSjzfiOmCTmvVSzYBIbI,57104
|
|
22
|
+
cartha_cli/commands/register.py,sha256=3sdQjsLTtwsbF8ILMROTAMdrAneaesM-cBu_P_uEC-w,9262
|
|
22
23
|
cartha_cli/commands/shared_options.py,sha256=itHzJSgxuKQxUVOh1_jVTcMQXjI3PPzexQyhqIbabxc,5874
|
|
23
24
|
cartha_cli/commands/version.py,sha256=u5oeccQzK0LLcCbgZm0U8-Vslk5vB_lVvW3xT5HPeTg,456
|
|
24
25
|
cartha_cli/testnet/README.md,sha256=kWKaLtq6t_46W-mvXkSaLi2fjXDELLk5ntVGkogiUY0,14511
|
|
25
26
|
cartha_cli/testnet/__init__.py,sha256=xreJMXs-ZKTkPtUQBR5xdY7ImOyUiF7WKG6bv9J9aBM,41
|
|
26
27
|
cartha_cli/testnet/pool_ids.py,sha256=0jvQ6tvc6sL0aGKkl31KXM6ngVpUboYiABY5SDMaRCQ,6747
|
|
27
|
-
cartha_cli-1.0.
|
|
28
|
-
cartha_cli-1.0.
|
|
29
|
-
cartha_cli-1.0.
|
|
30
|
-
cartha_cli-1.0.
|
|
31
|
-
cartha_cli-1.0.
|
|
28
|
+
cartha_cli-1.0.8.dist-info/METADATA,sha256=rtrIH0UTa9mvW2_gq9DERWEr2F3YRnB4mzb087jHxas,5842
|
|
29
|
+
cartha_cli-1.0.8.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
30
|
+
cartha_cli-1.0.8.dist-info/entry_points.txt,sha256=sTYVMgb9l0fuJibUtWpGnIoDmgHinne97G4Y_cCwC-U,43
|
|
31
|
+
cartha_cli-1.0.8.dist-info/licenses/LICENSE,sha256=B4UCiDn13m4xYwIl4TMKfbuKw7kh9pg4c81rJecxHSo,1076
|
|
32
|
+
cartha_cli-1.0.8.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|