cartha-cli 1.0.2__py3-none-any.whl → 1.0.3__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/commands/pair_status.py +1 -1
- cartha_cli/commands/pools.py +5 -25
- cartha_cli/commands/prove_lock.py +11 -40
- cartha_cli/testnet/README.md +446 -0
- cartha_cli/testnet/__init__.py +2 -0
- cartha_cli/testnet/pool_ids.py +208 -0
- {cartha_cli-1.0.2.dist-info → cartha_cli-1.0.3.dist-info}/METADATA +1 -1
- {cartha_cli-1.0.2.dist-info → cartha_cli-1.0.3.dist-info}/RECORD +11 -8
- {cartha_cli-1.0.2.dist-info → cartha_cli-1.0.3.dist-info}/WHEEL +0 -0
- {cartha_cli-1.0.2.dist-info → cartha_cli-1.0.3.dist-info}/entry_points.txt +0 -0
- {cartha_cli-1.0.2.dist-info → cartha_cli-1.0.3.dist-info}/licenses/LICENSE +0 -0
|
@@ -50,7 +50,7 @@ def _fallback_pool_id_to_name(pool_id: str) -> str | None:
|
|
|
50
50
|
|
|
51
51
|
# Try to import from testnet module, fallback to default if not available
|
|
52
52
|
try:
|
|
53
|
-
from
|
|
53
|
+
from ..testnet.pool_ids import pool_id_to_name
|
|
54
54
|
except (ImportError, ModuleNotFoundError):
|
|
55
55
|
# Use fallback function
|
|
56
56
|
pool_id_to_name = _fallback_pool_id_to_name
|
cartha_cli/commands/pools.py
CHANGED
|
@@ -22,36 +22,16 @@ def _fallback_pool_id_to_chain_id(pool_id: str) -> int | None:
|
|
|
22
22
|
|
|
23
23
|
# Try to import from testnet module, fallback to defaults if not available
|
|
24
24
|
try:
|
|
25
|
-
from
|
|
25
|
+
from ..testnet.pool_ids import (
|
|
26
26
|
list_pools,
|
|
27
27
|
pool_id_to_chain_id,
|
|
28
28
|
pool_id_to_vault_address,
|
|
29
29
|
)
|
|
30
30
|
except (ImportError, ModuleNotFoundError):
|
|
31
|
-
#
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
# Try adding parent directory to path
|
|
36
|
-
testnet_dir = Path(__file__).parent.parent.parent / "testnet"
|
|
37
|
-
if testnet_dir.exists():
|
|
38
|
-
sys.path.insert(0, str(testnet_dir.parent))
|
|
39
|
-
try:
|
|
40
|
-
from testnet.pool_ids import (
|
|
41
|
-
list_pools,
|
|
42
|
-
pool_id_to_chain_id,
|
|
43
|
-
pool_id_to_vault_address,
|
|
44
|
-
)
|
|
45
|
-
except (ImportError, ModuleNotFoundError):
|
|
46
|
-
# Use fallback functions
|
|
47
|
-
list_pools = _fallback_list_pools
|
|
48
|
-
pool_id_to_vault_address = _fallback_pool_id_to_vault_address
|
|
49
|
-
pool_id_to_chain_id = _fallback_pool_id_to_chain_id
|
|
50
|
-
else:
|
|
51
|
-
# Use fallback functions if testnet directory doesn't exist
|
|
52
|
-
list_pools = _fallback_list_pools
|
|
53
|
-
pool_id_to_vault_address = _fallback_pool_id_to_vault_address
|
|
54
|
-
pool_id_to_chain_id = _fallback_pool_id_to_chain_id
|
|
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
|
|
55
35
|
|
|
56
36
|
|
|
57
37
|
def pools(
|
|
@@ -86,7 +86,8 @@ def _fallback_vault_address_to_chain_id(vault_address: str) -> int | None:
|
|
|
86
86
|
|
|
87
87
|
# Try to import from testnet module, fallback to defaults if not available
|
|
88
88
|
try:
|
|
89
|
-
from
|
|
89
|
+
# Import from cartha_cli.testnet (works both in development and when installed)
|
|
90
|
+
from ..testnet.pool_ids import (
|
|
90
91
|
format_pool_id,
|
|
91
92
|
list_pools,
|
|
92
93
|
pool_id_to_chain_id,
|
|
@@ -97,45 +98,15 @@ try:
|
|
|
97
98
|
vault_address_to_pool_id,
|
|
98
99
|
)
|
|
99
100
|
except (ImportError, ModuleNotFoundError):
|
|
100
|
-
#
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
from testnet.pool_ids import (
|
|
110
|
-
format_pool_id,
|
|
111
|
-
list_pools,
|
|
112
|
-
pool_id_to_chain_id,
|
|
113
|
-
pool_id_to_name,
|
|
114
|
-
pool_id_to_vault_address,
|
|
115
|
-
pool_name_to_id,
|
|
116
|
-
vault_address_to_chain_id,
|
|
117
|
-
vault_address_to_pool_id,
|
|
118
|
-
)
|
|
119
|
-
except (ImportError, ModuleNotFoundError):
|
|
120
|
-
# Use fallback functions
|
|
121
|
-
pool_name_to_id = _fallback_pool_name_to_id
|
|
122
|
-
pool_id_to_name = _fallback_pool_id_to_name
|
|
123
|
-
format_pool_id = _fallback_format_pool_id
|
|
124
|
-
list_pools = _fallback_list_pools
|
|
125
|
-
pool_id_to_vault_address = _fallback_pool_id_to_vault_address
|
|
126
|
-
vault_address_to_pool_id = _fallback_vault_address_to_pool_id
|
|
127
|
-
pool_id_to_chain_id = _fallback_pool_id_to_chain_id
|
|
128
|
-
vault_address_to_chain_id = _fallback_vault_address_to_chain_id
|
|
129
|
-
else:
|
|
130
|
-
# Use fallback functions if testnet directory doesn't exist
|
|
131
|
-
pool_name_to_id = _fallback_pool_name_to_id
|
|
132
|
-
pool_id_to_name = _fallback_pool_id_to_name
|
|
133
|
-
format_pool_id = _fallback_format_pool_id
|
|
134
|
-
list_pools = _fallback_list_pools
|
|
135
|
-
pool_id_to_vault_address = _fallback_pool_id_to_vault_address
|
|
136
|
-
vault_address_to_pool_id = _fallback_vault_address_to_pool_id
|
|
137
|
-
pool_id_to_chain_id = _fallback_pool_id_to_chain_id
|
|
138
|
-
vault_address_to_chain_id = _fallback_vault_address_to_chain_id
|
|
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
|
|
139
110
|
|
|
140
111
|
|
|
141
112
|
def prove_lock(
|
|
@@ -0,0 +1,446 @@
|
|
|
1
|
+
# Cartha CLI - Testnet Setup Guide
|
|
2
|
+
|
|
3
|
+
This guide will help you set up and use the Cartha CLI on the public testnet with real vault contracts.
|
|
4
|
+
|
|
5
|
+
## Prerequisites
|
|
6
|
+
|
|
7
|
+
- Python 3.11
|
|
8
|
+
- Bittensor wallet (for subnet registration)
|
|
9
|
+
- Access to the testnet verifier URL
|
|
10
|
+
- Testnet TAO (required for subnet registration)
|
|
11
|
+
- EVM wallet (MetaMask or similar) with testnet USDC for locking
|
|
12
|
+
|
|
13
|
+
## Step 0: Set Up Your EVM Wallet for Base Sepolia Testnet
|
|
14
|
+
|
|
15
|
+
Before you can lock funds, you need to configure your EVM wallet (MetaMask, Coinbase Wallet, etc.) to connect to Base Sepolia Testnet and get testnet tokens.
|
|
16
|
+
|
|
17
|
+
### 0.1: Add Base Sepolia Testnet to Your Wallet
|
|
18
|
+
|
|
19
|
+
**For MetaMask:**
|
|
20
|
+
|
|
21
|
+
1. Open MetaMask and click the network dropdown (usually shows "Ethereum Mainnet")
|
|
22
|
+
2. Click "Add Network" or "Add a network manually"
|
|
23
|
+
3. Enter the following network details:
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
Network Name: Base Sepolia
|
|
27
|
+
RPC URL: https://sepolia.base.org
|
|
28
|
+
Chain ID: 84532
|
|
29
|
+
Currency Symbol: ETH
|
|
30
|
+
Block Explorer URL: https://sepolia.basescan.org
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
4. Click "Save" to add the network
|
|
34
|
+
5. Switch to the "Base Sepolia" network
|
|
35
|
+
|
|
36
|
+
**For Other Wallets:**
|
|
37
|
+
|
|
38
|
+
- **Coinbase Wallet**: Go to Settings → Networks → Add Network, then enter the details above
|
|
39
|
+
- **WalletConnect-compatible wallets**: Use the same network details when connecting
|
|
40
|
+
|
|
41
|
+
**Quick Add (MetaMask):**
|
|
42
|
+
|
|
43
|
+
You can also use the [Chainlist](https://chainlist.org/) website:
|
|
44
|
+
1. Visit https://chainlist.org/
|
|
45
|
+
2. Search for "Base Sepolia"
|
|
46
|
+
3. Click "Connect Wallet" and approve the connection
|
|
47
|
+
4. Click "Add to MetaMask" and confirm
|
|
48
|
+
|
|
49
|
+
### 0.2: Get Testnet ETH for Gas Fees
|
|
50
|
+
|
|
51
|
+
You'll need testnet ETH on Base Sepolia to pay for transaction gas fees. Get it from the official Optimism Superchain faucet:
|
|
52
|
+
|
|
53
|
+
1. **Visit the Optimism Superchain Faucet**: https://console.optimism.io/faucet
|
|
54
|
+
2. **Connect your wallet** (MetaMask or WalletConnect)
|
|
55
|
+
3. **Select "Base Sepolia"** from the network dropdown
|
|
56
|
+
4. **Enter your wallet address** (or it will auto-detect from your connected wallet)
|
|
57
|
+
5. **Click "Request Tokens"** or similar button
|
|
58
|
+
6. **Wait for confirmation** - You should receive testnet ETH within a few minutes
|
|
59
|
+
|
|
60
|
+
**Note**: The faucet may have rate limits (e.g., once per 24 hours per address). If you need more testnet ETH, you may need to wait or use a different address.
|
|
61
|
+
|
|
62
|
+
**Alternative Faucets** (if the main faucet is unavailable):
|
|
63
|
+
- Check Base Sepolia documentation for additional faucet options
|
|
64
|
+
- Join the Cartha Discord/Telegram for community faucet links
|
|
65
|
+
|
|
66
|
+
### 0.3: Get Testnet USDC Tokens
|
|
67
|
+
|
|
68
|
+
Testnet USDC is required to lock funds in the Cartha vaults. You can claim testnet USDC from the Cartha faucet.
|
|
69
|
+
|
|
70
|
+
**To Get Testnet USDC:**
|
|
71
|
+
|
|
72
|
+
1. **Visit the Cartha Faucet**: https://cartha.finance/faucet
|
|
73
|
+
2. **Connect your wallet** (MetaMask, Coinbase Wallet, Talisman, or WalletConnect)
|
|
74
|
+
3. **Make sure you're on Base Sepolia network** (Chain ID: 84532)
|
|
75
|
+
4. **Click "Claim USDC"** button
|
|
76
|
+
5. **Approve the transaction** in your wallet
|
|
77
|
+
6. **Wait for confirmation** - You should receive 1,000,000 testnet USDC within a few minutes
|
|
78
|
+
|
|
79
|
+
**Faucet Details**:
|
|
80
|
+
- **Claim Amount**: 1,000,000 USDC per claim
|
|
81
|
+
- **Cooldown**: 24 hours between claims (per wallet address)
|
|
82
|
+
- **Network**: Base Sepolia (Chain ID: 84532)
|
|
83
|
+
|
|
84
|
+
**Note**: The faucet has a 24-hour cooldown period. After claiming, you'll need to wait 24 hours before you can claim again from the same wallet address.
|
|
85
|
+
|
|
86
|
+
**Testnet USDC Contract Address** (for reference):
|
|
87
|
+
- Base Sepolia: `0x2340D09c348930A76c8c2783EDa8610F699A51A8`
|
|
88
|
+
|
|
89
|
+
You can verify you received USDC by:
|
|
90
|
+
- Checking your wallet balance (should show USDC)
|
|
91
|
+
- Viewing your address on [BaseScan Sepolia](https://sepolia.basescan.org/)
|
|
92
|
+
|
|
93
|
+
### Getting Testnet TAO
|
|
94
|
+
|
|
95
|
+
You'll need testnet TAO to register your hotkey to the subnet. Get testnet TAO from the faucet:
|
|
96
|
+
|
|
97
|
+
🔗 **Testnet TAO Faucet**: <https://app.minersunion.ai/testnet-faucet>
|
|
98
|
+
|
|
99
|
+
Simply visit the faucet and request testnet TAO to your wallet address. You'll need TAO in your wallet to pay for subnet registration.
|
|
100
|
+
|
|
101
|
+
## Installation
|
|
102
|
+
|
|
103
|
+
Install the Cartha CLI from PyPI:
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
pip install cartha-cli
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Verify the installation:
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
cartha --help
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## Testnet Configuration
|
|
116
|
+
|
|
117
|
+
### Environment Variables
|
|
118
|
+
|
|
119
|
+
Set the following environment variables:
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
# Required: Testnet verifier URL
|
|
123
|
+
export CARTHA_VERIFIER_URL="https://cartha-verifier-826542474079.us-central1.run.app"
|
|
124
|
+
|
|
125
|
+
# Required: Bittensor network configuration
|
|
126
|
+
export CARTHA_NETWORK="test" # Use "test" for testnet
|
|
127
|
+
export CARTHA_NETUID=78 # Testnet subnet UID
|
|
128
|
+
|
|
129
|
+
# Optional: Custom wallet path
|
|
130
|
+
export BITTENSOR_WALLET_PATH="/path/to/wallet"
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### Verify Configuration
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
# Check CLI can access verifier
|
|
137
|
+
cartha --help
|
|
138
|
+
|
|
139
|
+
# Test verifier connectivity
|
|
140
|
+
curl "${CARTHA_VERIFIER_URL}/health"
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## Testnet Workflow
|
|
144
|
+
|
|
145
|
+
### Step 1: Verify Your EVM Wallet Setup
|
|
146
|
+
|
|
147
|
+
Before proceeding, make sure you have:
|
|
148
|
+
|
|
149
|
+
- ✅ Base Sepolia network added to your wallet
|
|
150
|
+
- ✅ Testnet ETH in your wallet (for gas fees)
|
|
151
|
+
- ✅ Testnet USDC in your wallet (contact team if needed)
|
|
152
|
+
|
|
153
|
+
You can verify your balances:
|
|
154
|
+
- Check ETH balance in your wallet
|
|
155
|
+
- Check USDC balance (should show as a token in your wallet)
|
|
156
|
+
- View on [BaseScan Sepolia](https://sepolia.basescan.org/address/YOUR_ADDRESS) to see all tokens
|
|
157
|
+
|
|
158
|
+
### Step 2: Register Your Hotkey
|
|
159
|
+
|
|
160
|
+
Register your hotkey to the testnet subnet:
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
cartha miner register \
|
|
164
|
+
--wallet-name <your-wallet-name> \
|
|
165
|
+
--wallet-hotkey <your-hotkey-name> \
|
|
166
|
+
--network test \
|
|
167
|
+
--netuid 78
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
This will:
|
|
171
|
+
|
|
172
|
+
- Register your hotkey to subnet 78 (testnet)
|
|
173
|
+
- Fetch your slot UID
|
|
174
|
+
- Display your registration details
|
|
175
|
+
|
|
176
|
+
**Save the output** - you'll need your slot UID.
|
|
177
|
+
|
|
178
|
+
### Step 3: Lock Funds Using Cartha Lock UI
|
|
179
|
+
|
|
180
|
+
Use the streamlined lock flow with the Cartha Lock UI:
|
|
181
|
+
|
|
182
|
+
```bash
|
|
183
|
+
cartha vault lock \
|
|
184
|
+
--coldkey <your-coldkey-name> \
|
|
185
|
+
--hotkey <your-hotkey-name> \
|
|
186
|
+
--pool-id BTCUSD \
|
|
187
|
+
--amount 100.0 \
|
|
188
|
+
--lock-days 30 \
|
|
189
|
+
--owner-evm 0xYourEVMAddress
|
|
190
|
+
|
|
191
|
+
# Note: --chain-id and --vault-address are optional - CLI auto-matches them from pool-id!
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
This command will:
|
|
195
|
+
|
|
196
|
+
1. **Check Registration**: Verify your hotkey is registered on the subnet
|
|
197
|
+
2. **Authenticate**: Sign a challenge message with your Bittensor hotkey to get a session token
|
|
198
|
+
3. **Request Signature**: Get an EIP-712 LockRequest signature from the verifier
|
|
199
|
+
4. **Open Cartha Lock UI**: Automatically opens the web interface in your browser with all parameters pre-filled
|
|
200
|
+
5. **Phase 1 - Approve USDC**: Connect your wallet and approve USDC spending through the frontend. The CLI automatically detects when approval completes
|
|
201
|
+
6. **Phase 2 - Lock Position**: Lock your USDC in the vault contract through the frontend
|
|
202
|
+
7. **Auto-Detection**: The verifier automatically detects your `LockCreated` event and adds you to the upcoming epoch
|
|
203
|
+
|
|
204
|
+
**Important Notes**:
|
|
205
|
+
- Make sure you're connected to **Base Sepolia** network in your wallet (not Mainnet!)
|
|
206
|
+
- You'll need testnet USDC in your EVM wallet (contact team if you don't have any)
|
|
207
|
+
- The frontend supports MetaMask, Coinbase Wallet, Talisman, and WalletConnect
|
|
208
|
+
- Make sure the wallet you connect matches the `--owner-evm` address specified in the CLI
|
|
209
|
+
- The frontend includes wallet validation to prevent using the wrong address
|
|
210
|
+
|
|
211
|
+
**Transaction Flow**:
|
|
212
|
+
1. **Approve USDC**: First transaction approves the vault to spend your USDC (handled in frontend)
|
|
213
|
+
2. **Lock Position**: Second transaction locks your USDC in the vault (handled in frontend)
|
|
214
|
+
3. Both transactions require gas fees (paid in testnet ETH)
|
|
215
|
+
|
|
216
|
+
**Managing Existing Positions**:
|
|
217
|
+
- Visit https://cartha.finance/manage to view all your locks
|
|
218
|
+
- Use "Extend" or "Top Up" buttons to modify existing positions
|
|
219
|
+
|
|
220
|
+
### Step 4: Check Miner Status
|
|
221
|
+
|
|
222
|
+
Verify your miner status (no authentication required):
|
|
223
|
+
|
|
224
|
+
```bash
|
|
225
|
+
cartha miner status \
|
|
226
|
+
--wallet-name <your-wallet-name> \
|
|
227
|
+
--wallet-hotkey <your-hotkey-name>
|
|
228
|
+
|
|
229
|
+
# Or with explicit slot UID
|
|
230
|
+
cartha miner status \
|
|
231
|
+
--wallet-name <your-wallet-name> \
|
|
232
|
+
--wallet-hotkey <your-hotkey-name> \
|
|
233
|
+
--slot <your-slot-uid>
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
This will show:
|
|
237
|
+
|
|
238
|
+
- Miner state and pool information
|
|
239
|
+
- All active pools with amounts and expiration dates
|
|
240
|
+
- Days remaining countdown (with warnings for expiring pools)
|
|
241
|
+
|
|
242
|
+
## Pool IDs and Vault Addresses
|
|
243
|
+
|
|
244
|
+
Pool IDs can be specified as either:
|
|
245
|
+
- **Human-readable names**: `BTCUSD`, `EUR/USD`, `ETH/USD`, etc.
|
|
246
|
+
- **Hex strings**: `0x...` (32 bytes)
|
|
247
|
+
|
|
248
|
+
The CLI automatically converts readable names to hex format and matches them to the correct vault address.
|
|
249
|
+
|
|
250
|
+
### Available Testnet Pools (Base Sepolia)
|
|
251
|
+
|
|
252
|
+
| Pool Name | Pool ID (hex) | Vault Address |
|
|
253
|
+
|-----------|---------------|---------------|
|
|
254
|
+
| BTCUSD | `0xee62665949c883f9e0f6f002eac32e00bd59dfe6c34e92a91c37d6a8322d6489` | `0x471D86764B7F99b894ee38FcD3cEFF6EAB321b69` |
|
|
255
|
+
| ETH/USD | `0x0b43555ace6b39aae1b894097d0a9fc17f504c62fea598fa206cc6f5088e6e45` | `0xdB74B44957A71c95406C316f8d3c5571FA588248` |
|
|
256
|
+
| EUR/USD | `0xa9226449042e36bf6865099eec57482aa55e3ad026c315a0e4a692b776c318ca` | `0x3C4dAfAC827140B8a031d994b7e06A25B9f27BAD` |
|
|
257
|
+
|
|
258
|
+
**Note**: When using `cartha vault lock`, you can simply specify `--pool-id BTCUSD` and the CLI will automatically:
|
|
259
|
+
- Match the correct vault address for that pool
|
|
260
|
+
- Match the correct chain ID (Base Sepolia: 84532)
|
|
261
|
+
|
|
262
|
+
You don't need to manually specify `--vault-address` or `--chain-id` unless you want to override them.
|
|
263
|
+
|
|
264
|
+
See `testnet/pool_ids.py` for the complete pool mappings and helper functions.
|
|
265
|
+
|
|
266
|
+
## Common Commands
|
|
267
|
+
|
|
268
|
+
### Check CLI Version
|
|
269
|
+
|
|
270
|
+
```bash
|
|
271
|
+
cartha version
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
### View Help
|
|
275
|
+
|
|
276
|
+
```bash
|
|
277
|
+
cartha --help
|
|
278
|
+
cartha miner register --help
|
|
279
|
+
cartha vault lock --help
|
|
280
|
+
cartha miner status --help
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
### Register (Burned Registration)
|
|
284
|
+
|
|
285
|
+
```bash
|
|
286
|
+
cartha miner register \
|
|
287
|
+
--wallet-name <name> \
|
|
288
|
+
--wallet-hotkey <hotkey> \
|
|
289
|
+
--network test \
|
|
290
|
+
--netuid 78 \
|
|
291
|
+
--burned
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
## Troubleshooting
|
|
295
|
+
|
|
296
|
+
### "Verifier URL not found"
|
|
297
|
+
|
|
298
|
+
**Problem**: CLI can't connect to verifier
|
|
299
|
+
|
|
300
|
+
**Solution**:
|
|
301
|
+
|
|
302
|
+
```bash
|
|
303
|
+
# Verify environment variable is set
|
|
304
|
+
echo $CARTHA_VERIFIER_URL
|
|
305
|
+
|
|
306
|
+
# Test verifier connectivity
|
|
307
|
+
curl "${CARTHA_VERIFIER_URL}/health"
|
|
308
|
+
|
|
309
|
+
# If using a different URL, update it
|
|
310
|
+
export CARTHA_VERIFIER_URL="https://cartha-verifier-826542474079.us-central1.run.app"
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
### "Hotkey not registered"
|
|
314
|
+
|
|
315
|
+
**Problem**: Hotkey is not registered on the subnet
|
|
316
|
+
|
|
317
|
+
**Solution**:
|
|
318
|
+
|
|
319
|
+
- Register your hotkey first using `cartha miner register`
|
|
320
|
+
- Verify you're using the correct network (`test`) and netuid (`78`)
|
|
321
|
+
- Check that you have testnet TAO in your wallet
|
|
322
|
+
|
|
323
|
+
### "Wallet not found"
|
|
324
|
+
|
|
325
|
+
**Problem**: CLI can't find your Bittensor wallet
|
|
326
|
+
|
|
327
|
+
**Solution**:
|
|
328
|
+
|
|
329
|
+
```bash
|
|
330
|
+
# Check default wallet location
|
|
331
|
+
ls ~/.bittensor/wallets/
|
|
332
|
+
|
|
333
|
+
# Or set custom path
|
|
334
|
+
export BITTENSOR_WALLET_PATH="/path/to/wallet"
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
### "Network error"
|
|
338
|
+
|
|
339
|
+
**Problem**: Can't connect to Bittensor network
|
|
340
|
+
|
|
341
|
+
**Solution**:
|
|
342
|
+
|
|
343
|
+
- Verify `CARTHA_NETWORK` is set to `"test"` for testnet
|
|
344
|
+
- Check your internet connection
|
|
345
|
+
- Try using a VPN if network is blocked
|
|
346
|
+
|
|
347
|
+
### "Transaction failed"
|
|
348
|
+
|
|
349
|
+
**Problem**: MetaMask transaction failed
|
|
350
|
+
|
|
351
|
+
**Solution**:
|
|
352
|
+
|
|
353
|
+
- **Check Network**: Make sure you're on **Base Sepolia** network (not Mainnet or other networks)
|
|
354
|
+
- **Check Gas**: Ensure you have enough testnet ETH for gas fees
|
|
355
|
+
- **Check USDC**: Ensure you have enough testnet USDC in your wallet
|
|
356
|
+
- **Check Approval**: Make sure you've approved the vault to spend USDC (first transaction)
|
|
357
|
+
- **Verify Transaction Data**: Check that the transaction data matches what the CLI displayed
|
|
358
|
+
- **Check Network Congestion**: Base Sepolia may be slower than mainnet - wait a bit and retry
|
|
359
|
+
|
|
360
|
+
### "Insufficient funds" or "Not enough ETH"
|
|
361
|
+
|
|
362
|
+
**Problem**: Don't have enough testnet ETH for gas
|
|
363
|
+
|
|
364
|
+
**Solution**:
|
|
365
|
+
|
|
366
|
+
- Visit https://console.optimism.io/faucet
|
|
367
|
+
- Select "Base Sepolia" network
|
|
368
|
+
- Request testnet ETH to your wallet address
|
|
369
|
+
- Wait a few minutes for the transaction to complete
|
|
370
|
+
- Retry your transaction
|
|
371
|
+
|
|
372
|
+
### "USDC balance is zero" or "No USDC found"
|
|
373
|
+
|
|
374
|
+
**Problem**: Don't have testnet USDC tokens
|
|
375
|
+
|
|
376
|
+
**Solution**:
|
|
377
|
+
|
|
378
|
+
- Visit the Cartha faucet at https://cartha.finance/faucet
|
|
379
|
+
- Connect your wallet and claim 1,000,000 testnet USDC
|
|
380
|
+
- Note: There's a 24-hour cooldown between claims
|
|
381
|
+
- Verify receipt on [BaseScan Sepolia](https://sepolia.basescan.org/)
|
|
382
|
+
|
|
383
|
+
### "Wrong wallet connected" or "Wallet address mismatch"
|
|
384
|
+
|
|
385
|
+
**Problem**: The frontend shows a warning that the connected wallet doesn't match the required owner address
|
|
386
|
+
|
|
387
|
+
**Solution**:
|
|
388
|
+
|
|
389
|
+
1. **Disconnect your current wallet** using the "Disconnect" button in the frontend
|
|
390
|
+
2. **Connect the correct wallet** that matches the `--owner-evm` address you specified in the CLI
|
|
391
|
+
3. **Verify the address** - The frontend will show "Required Owner" vs "Connected Wallet" to help you identify the mismatch
|
|
392
|
+
4. If you need to use a different address, restart the CLI command with the correct `--owner-evm` address
|
|
393
|
+
|
|
394
|
+
**Note**: The frontend includes automatic wallet validation to prevent this issue. Always ensure you connect the wallet that matches the address specified in the CLI command.
|
|
395
|
+
|
|
396
|
+
## Testing Your Setup
|
|
397
|
+
|
|
398
|
+
### Complete Testnet Checklist
|
|
399
|
+
|
|
400
|
+
Before starting, make sure you have:
|
|
401
|
+
|
|
402
|
+
- [ ] Python 3.11 installed
|
|
403
|
+
- [ ] `uv` package manager installed
|
|
404
|
+
- [ ] Bittensor wallet set up
|
|
405
|
+
- [ ] MetaMask (or other EVM wallet) installed
|
|
406
|
+
- [ ] Base Sepolia network added to MetaMask
|
|
407
|
+
- [ ] Testnet ETH in your wallet (from faucet)
|
|
408
|
+
- [ ] Testnet USDC in your wallet (from team)
|
|
409
|
+
- [ ] Testnet TAO in your Bittensor wallet (for registration)
|
|
410
|
+
|
|
411
|
+
### Quick Test
|
|
412
|
+
|
|
413
|
+
```bash
|
|
414
|
+
# 1. Register your hotkey
|
|
415
|
+
cartha miner register --wallet-name test --wallet-hotkey test --network test --netuid 78
|
|
416
|
+
|
|
417
|
+
# 2. Check miner status (no authentication needed)
|
|
418
|
+
cartha miner status --wallet-name test --wallet-hotkey test
|
|
419
|
+
|
|
420
|
+
# 3. Lock funds (interactive flow)
|
|
421
|
+
# Note: Make sure you're on Base Sepolia network in MetaMask!
|
|
422
|
+
# Chain ID and vault address are auto-detected from pool-id - no need to specify!
|
|
423
|
+
cartha vault lock \
|
|
424
|
+
--coldkey test \
|
|
425
|
+
--hotkey test \
|
|
426
|
+
--pool-id BTCUSD \
|
|
427
|
+
--amount 100.0 \
|
|
428
|
+
--lock-days 30 \
|
|
429
|
+
--owner-evm 0xYourEVMAddress
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
**Important**:
|
|
433
|
+
- Chain ID (84532 for Base Sepolia) and vault address are **automatically detected** from the pool ID
|
|
434
|
+
- You only need to specify `--pool-id BTCUSD` (or `ETH/USD`, `EUR/USD`)
|
|
435
|
+
- The CLI will show you the auto-matched values before proceeding
|
|
436
|
+
|
|
437
|
+
## Next Steps
|
|
438
|
+
|
|
439
|
+
- Check the [Main README](../README.md) for advanced usage
|
|
440
|
+
- Review [Validator Setup](../../cartha-validator/docs/TESTNET_SETUP.md) if running a validator
|
|
441
|
+
- Provide feedback via [GitHub Issues](https://github.com/your-org/cartha-cli/issues)
|
|
442
|
+
|
|
443
|
+
## Additional Resources
|
|
444
|
+
|
|
445
|
+
- [CLI README](../README.md) - Full CLI documentation
|
|
446
|
+
- `testnet/pool_ids.py` - Pool ID helper functions for converting between readable names and hex format
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
"""Helper functions for generating and displaying human-readable pool IDs.
|
|
2
|
+
|
|
3
|
+
Pool IDs are bytes32 (32 bytes = 64 hex chars), so we can encode readable text
|
|
4
|
+
in hex format. This module provides helpers to convert between readable names
|
|
5
|
+
and hex pool IDs.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
# Predefined pool mappings (readable name -> hex pool ID)
|
|
11
|
+
# Base Sepolia testnet pool IDs from vault contracts (keccak256 hashes)
|
|
12
|
+
# Pool IDs are bytes32 (keccak256 hashes: 32 bytes = 64 hex chars + 0x prefix = 66 chars)
|
|
13
|
+
POOL_MAPPINGS: dict[str, str] = {
|
|
14
|
+
"BTCUSD": "0xee62665949c883f9e0f6f002eac32e00bd59dfe6c34e92a91c37d6a8322d6489", # BTC/USD (keccak256("BTC/USD"))
|
|
15
|
+
"ETHUSD": "0x0b43555ace6b39aae1b894097d0a9fc17f504c62fea598fa206cc6f5088e6e45", # ETH/USD (keccak256("ETH/USD"))
|
|
16
|
+
"EURUSD": "0xa9226449042e36bf6865099eec57482aa55e3ad026c315a0e4a692b776c318ca", # EUR/USD (keccak256("EUR/USD"))
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
# Reverse mapping (hex -> readable name)
|
|
20
|
+
POOL_NAMES: dict[str, str] = {v.lower(): k for k, v in POOL_MAPPINGS.items()}
|
|
21
|
+
|
|
22
|
+
# Pool ID to Vault Address mapping (Base Sepolia testnet)
|
|
23
|
+
# Each pool ID has its own dedicated vault address
|
|
24
|
+
VAULT_ADDRESSES: dict[str, str] = {
|
|
25
|
+
"BTCUSD": "0x471D86764B7F99b894ee38FcD3cEFF6EAB321b69", # BTC/USD Vault
|
|
26
|
+
"ETHUSD": "0xdB74B44957A71c95406C316f8d3c5571FA588248", # ETH/USD Vault
|
|
27
|
+
"EURUSD": "0x3C4dAfAC827140B8a031d994b7e06A25B9f27BAD", # EUR/USD Vault
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
# Pool ID (hex) to Vault Address mapping
|
|
31
|
+
POOL_ID_TO_VAULT: dict[str, str] = {
|
|
32
|
+
POOL_MAPPINGS["BTCUSD"].lower(): VAULT_ADDRESSES["BTCUSD"],
|
|
33
|
+
POOL_MAPPINGS["ETHUSD"].lower(): VAULT_ADDRESSES["ETHUSD"],
|
|
34
|
+
POOL_MAPPINGS["EURUSD"].lower(): VAULT_ADDRESSES["EURUSD"],
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
# Vault Address to Chain ID mapping (Base Sepolia testnet)
|
|
38
|
+
# All testnet vaults are on Base Sepolia (chain ID 84532)
|
|
39
|
+
VAULT_TO_CHAIN_ID: dict[str, int] = {
|
|
40
|
+
VAULT_ADDRESSES["BTCUSD"].lower(): 84532, # Base Sepolia
|
|
41
|
+
VAULT_ADDRESSES["ETHUSD"].lower(): 84532, # Base Sepolia
|
|
42
|
+
VAULT_ADDRESSES["EURUSD"].lower(): 84532, # Base Sepolia
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
# Pool ID (hex) to Chain ID mapping
|
|
46
|
+
POOL_ID_TO_CHAIN_ID: dict[str, int] = {
|
|
47
|
+
POOL_MAPPINGS["BTCUSD"].lower(): 84532, # Base Sepolia
|
|
48
|
+
POOL_MAPPINGS["ETHUSD"].lower(): 84532, # Base Sepolia
|
|
49
|
+
POOL_MAPPINGS["EURUSD"].lower(): 84532, # Base Sepolia
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def pool_id_to_vault_address(pool_id: str) -> str | None:
|
|
54
|
+
"""Get vault address for a given pool ID.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
pool_id: Pool ID in hex format (bytes32)
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
Vault address if found, None otherwise
|
|
61
|
+
"""
|
|
62
|
+
pool_id_lower = pool_id.lower().strip()
|
|
63
|
+
if not pool_id_lower.startswith("0x"):
|
|
64
|
+
pool_id_lower = "0x" + pool_id_lower
|
|
65
|
+
|
|
66
|
+
return POOL_ID_TO_VAULT.get(pool_id_lower)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def vault_address_to_pool_id(vault_address: str) -> str | None:
|
|
70
|
+
"""Get pool ID for a given vault address.
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
vault_address: Vault contract address
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
Pool ID if found, None otherwise
|
|
77
|
+
"""
|
|
78
|
+
vault_lower = vault_address.lower().strip()
|
|
79
|
+
if not vault_lower.startswith("0x"):
|
|
80
|
+
vault_lower = "0x" + vault_lower
|
|
81
|
+
|
|
82
|
+
# Reverse lookup
|
|
83
|
+
for pool_id, vault in POOL_ID_TO_VAULT.items():
|
|
84
|
+
if vault.lower() == vault_lower:
|
|
85
|
+
return pool_id
|
|
86
|
+
return None
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def pool_id_to_chain_id(pool_id: str) -> int | None:
|
|
90
|
+
"""Get chain ID for a given pool ID.
|
|
91
|
+
|
|
92
|
+
Args:
|
|
93
|
+
pool_id: Pool ID in hex format (bytes32)
|
|
94
|
+
|
|
95
|
+
Returns:
|
|
96
|
+
Chain ID if found, None otherwise
|
|
97
|
+
"""
|
|
98
|
+
pool_id_lower = pool_id.lower().strip()
|
|
99
|
+
if not pool_id_lower.startswith("0x"):
|
|
100
|
+
pool_id_lower = "0x" + pool_id_lower
|
|
101
|
+
|
|
102
|
+
return POOL_ID_TO_CHAIN_ID.get(pool_id_lower)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def vault_address_to_chain_id(vault_address: str) -> int | None:
|
|
106
|
+
"""Get chain ID for a given vault address.
|
|
107
|
+
|
|
108
|
+
Args:
|
|
109
|
+
vault_address: Vault contract address
|
|
110
|
+
|
|
111
|
+
Returns:
|
|
112
|
+
Chain ID if found, None otherwise
|
|
113
|
+
"""
|
|
114
|
+
vault_lower = vault_address.lower().strip()
|
|
115
|
+
if not vault_lower.startswith("0x"):
|
|
116
|
+
vault_lower = "0x" + vault_lower
|
|
117
|
+
|
|
118
|
+
return VAULT_TO_CHAIN_ID.get(vault_lower)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def pool_name_to_id(pool_name: str) -> str:
|
|
122
|
+
"""Convert a readable pool name to hex pool ID.
|
|
123
|
+
|
|
124
|
+
Args:
|
|
125
|
+
pool_name: Readable name (e.g., "USDEUR", "XAUUSD")
|
|
126
|
+
|
|
127
|
+
Returns:
|
|
128
|
+
Hex pool ID (bytes32 format)
|
|
129
|
+
|
|
130
|
+
Examples:
|
|
131
|
+
>>> pool_name_to_id("USDEUR")
|
|
132
|
+
'0x0000000000000000000000000000000000000000000000000000000000555344455552'
|
|
133
|
+
"""
|
|
134
|
+
pool_name_upper = pool_name.upper()
|
|
135
|
+
if pool_name_upper in POOL_MAPPINGS:
|
|
136
|
+
return POOL_MAPPINGS[pool_name_upper]
|
|
137
|
+
|
|
138
|
+
# If not in predefined mappings, encode the name as hex
|
|
139
|
+
# Pad to 32 bytes (64 hex chars), right-padded (data at end, zeros at beginning)
|
|
140
|
+
name_bytes = pool_name.encode("utf-8")
|
|
141
|
+
if len(name_bytes) > 32:
|
|
142
|
+
raise ValueError(f"Pool name too long: {pool_name} (max 32 bytes)")
|
|
143
|
+
|
|
144
|
+
# Right-pad with zeros to 32 bytes (data at end, zeros at beginning)
|
|
145
|
+
padded = name_bytes.rjust(32, b"\x00")
|
|
146
|
+
hex_id = "0x" + padded.hex()
|
|
147
|
+
# Validate length
|
|
148
|
+
if len(hex_id) != 66: # 0x + 64 hex chars
|
|
149
|
+
raise ValueError(f"Generated pool_id has incorrect length: {len(hex_id)} (expected 66)")
|
|
150
|
+
return hex_id
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def pool_id_to_name(pool_id: str) -> str | None:
|
|
154
|
+
"""Convert a hex pool ID to readable name if available.
|
|
155
|
+
|
|
156
|
+
Args:
|
|
157
|
+
pool_id: Hex pool ID (bytes32 format)
|
|
158
|
+
|
|
159
|
+
Returns:
|
|
160
|
+
Readable name if found, None otherwise
|
|
161
|
+
|
|
162
|
+
Examples:
|
|
163
|
+
>>> pool_id_to_name("0x0000000000000000000000000000000000000000000000000000000000555344455552")
|
|
164
|
+
'USDEUR'
|
|
165
|
+
"""
|
|
166
|
+
pool_id_lower = pool_id.lower()
|
|
167
|
+
if pool_id_lower in POOL_NAMES:
|
|
168
|
+
return POOL_NAMES[pool_id_lower]
|
|
169
|
+
|
|
170
|
+
# Try to decode from hex
|
|
171
|
+
try:
|
|
172
|
+
# Remove 0x prefix
|
|
173
|
+
hex_str = pool_id_lower.removeprefix("0x")
|
|
174
|
+
# Convert to bytes
|
|
175
|
+
pool_bytes = bytes.fromhex(hex_str)
|
|
176
|
+
# Try to decode as UTF-8 (remove trailing zeros)
|
|
177
|
+
name = pool_bytes.rstrip(b"\x00").decode("utf-8", errors="ignore")
|
|
178
|
+
if name and name.isprintable():
|
|
179
|
+
return name
|
|
180
|
+
except Exception:
|
|
181
|
+
pass
|
|
182
|
+
|
|
183
|
+
return None
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def format_pool_id(pool_id: str) -> str:
|
|
187
|
+
"""Format a pool ID for display (shows readable name if available).
|
|
188
|
+
|
|
189
|
+
Args:
|
|
190
|
+
pool_id: Hex pool ID
|
|
191
|
+
|
|
192
|
+
Returns:
|
|
193
|
+
Formatted string: "USDEUR (0x...)" or just "0x..." if no name found
|
|
194
|
+
"""
|
|
195
|
+
name = pool_id_to_name(pool_id)
|
|
196
|
+
if name:
|
|
197
|
+
return f"{name} ({pool_id})"
|
|
198
|
+
return pool_id
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def list_pools() -> dict[str, str]:
|
|
202
|
+
"""List all predefined pools.
|
|
203
|
+
|
|
204
|
+
Returns:
|
|
205
|
+
Dictionary mapping readable names to hex pool IDs
|
|
206
|
+
"""
|
|
207
|
+
return POOL_MAPPINGS.copy()
|
|
208
|
+
|
|
@@ -15,14 +15,17 @@ cartha_cli/commands/health.py,sha256=NRwmIultxUzAe7udOOBIdkcdGG5KsE_kuCs43LZObGk
|
|
|
15
15
|
cartha_cli/commands/help.py,sha256=6ubfWtmjXfCtp6L_PYvn7rR7m5C_pp-iEjtRc6BS0GA,1721
|
|
16
16
|
cartha_cli/commands/miner_password.py,sha256=7cbcyrJ9KzCyJ68_174U_CXjBUt9BaYhgKAycRpv7AE,11078
|
|
17
17
|
cartha_cli/commands/miner_status.py,sha256=tWlCWcuwm9NEd4USuCLp_6qObikX2odc2e7m6Y_vNrU,21873
|
|
18
|
-
cartha_cli/commands/pair_status.py,sha256=
|
|
19
|
-
cartha_cli/commands/pools.py,sha256=
|
|
20
|
-
cartha_cli/commands/prove_lock.py,sha256=
|
|
18
|
+
cartha_cli/commands/pair_status.py,sha256=LoU-fA1MXuWZGHNmeKPMjwvNw30Yn094ZD1EC0-WXVc,19978
|
|
19
|
+
cartha_cli/commands/pools.py,sha256=P02SEp23a7OdrgNj6AaiJTnQ9K_QrI300DzucA6HH8A,3884
|
|
20
|
+
cartha_cli/commands/prove_lock.py,sha256=ihcZLrYcc9Pr4X75K9cgaDEod0Sjjyj58sAUPAhRQEo,60268
|
|
21
21
|
cartha_cli/commands/register.py,sha256=sxbYO0V4NucOKLZpaFoVnhFDHLSLDHREoMtN9DjyLsM,10227
|
|
22
22
|
cartha_cli/commands/shared_options.py,sha256=itHzJSgxuKQxUVOh1_jVTcMQXjI3PPzexQyhqIbabxc,5874
|
|
23
23
|
cartha_cli/commands/version.py,sha256=u5oeccQzK0LLcCbgZm0U8-Vslk5vB_lVvW3xT5HPeTg,456
|
|
24
|
-
cartha_cli
|
|
25
|
-
cartha_cli
|
|
26
|
-
cartha_cli
|
|
27
|
-
cartha_cli-1.0.
|
|
28
|
-
cartha_cli-1.0.
|
|
24
|
+
cartha_cli/testnet/README.md,sha256=kWKaLtq6t_46W-mvXkSaLi2fjXDELLk5ntVGkogiUY0,14511
|
|
25
|
+
cartha_cli/testnet/__init__.py,sha256=xreJMXs-ZKTkPtUQBR5xdY7ImOyUiF7WKG6bv9J9aBM,41
|
|
26
|
+
cartha_cli/testnet/pool_ids.py,sha256=0jvQ6tvc6sL0aGKkl31KXM6ngVpUboYiABY5SDMaRCQ,6747
|
|
27
|
+
cartha_cli-1.0.3.dist-info/METADATA,sha256=5c0z7KdAKD3ZejILXjQF9GI6WvIUcqxUdmgv-qhm1eY,5794
|
|
28
|
+
cartha_cli-1.0.3.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
29
|
+
cartha_cli-1.0.3.dist-info/entry_points.txt,sha256=sTYVMgb9l0fuJibUtWpGnIoDmgHinne97G4Y_cCwC-U,43
|
|
30
|
+
cartha_cli-1.0.3.dist-info/licenses/LICENSE,sha256=B4UCiDn13m4xYwIl4TMKfbuKw7kh9pg4c81rJecxHSo,1076
|
|
31
|
+
cartha_cli-1.0.3.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|