Conversation
WalkthroughThis change replaces the prior “Neuron” surface with a new “Dendron” and “Weights” interface, adds ABIs (DendronManager, Weights), revises GlobalStaking ABI, updates CLI commands and wrappers accordingly, adds HETU and AMM workflows, revises config defaults, introduces docs (intro), and pins Python to 3.12.4. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant U as User (CLI)
participant W as Wallet/Keystore
participant RPC as JSON-RPC Node
participant GS as GlobalStaking
participant SM as SubnetManager
participant DM as DendronManager
participant WT as WHETU (ERC20)
rect rgba(230,245,255,0.5)
note over U: Stake and Allocate
U->>W: Load keystore (sender)
U->>RPC: Connect (HTTP)
U->>GS: getStakeInfo(user)
GS-->>U: totalStaked, totalAllocated, totalCost
U->>SM: getRegistrationCost()/params
SM-->>U: costs/thresholds
U->>WT: allowance(user, GS)
alt Insufficient allowance
U->>WT: approve(GS, amount)
WT-->>U: receipt
end
U->>GS: addGlobalStake / allocateToSubnet(...)
GS-->>U: receipt
end
rect rgba(235,255,235,0.5)
note over U: Register Dendron
U->>DM: registerNeuronWithStakeAllocation(netuid, stake, role, service)
DM-->>U: receipt + events
end
sequenceDiagram
autonumber
participant U as User (CLI)
participant RPC as JSON-RPC Node
participant SM as SubnetManager
participant AMM as Subnet AMM
participant WT as WHETU
participant AT as ALPHA
rect rgba(255,245,230,0.5)
note over U: AMM address resolution
U->>RPC: Connect
alt netuid provided
U->>SM: getSubnetInfo(netuid)
SM-->>U: ... , ammPoolAddress, ...
U->>AMM: Bind at ammPoolAddress
else direct contract
U->>AMM: Bind at contract
end
end
rect rgba(245,235,255,0.5)
note over U: Approvals and Swap
U->>AMM: getTokenAddresses()
U->>WT: allowance(user, AMM)
U->>AT: allowance(user, AMM)
alt Insufficient allowance
U->>WT: approve(AMM, amount)
U->>AT: approve(AMM, amount)
end
U->>AMM: swap / addLiquidity / withdraw
AMM-->>U: receipt
end
sequenceDiagram
autonumber
participant V as Validator (CLI)
participant W as Wallet/Keystore
participant RPC as JSON-RPC
participant WC as Weights Contract
rect rgba(240,255,240,0.5)
note over V: Set Weights
V->>W: Decrypt keystore (sender)
V->>RPC: Connect
V->>WC: setWeights(netuid, weights[]) (signed tx)
WC-->>V: receipt (WeightsSet)
end
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Poem
Tip 🔌 Remote MCP (Model Context Protocol) integration is now available!Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats. ✨ Finishing Touches
🧪 Generate unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Actionable comments posted: 17
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (7)
hetu_pycli/src/commands/tx.py (2)
10-49: Prevent precision loss and invalid recipient address when sending fundsTwo issues can lead to loss of funds or failed tx:
- value is a float; converting floats to wei can introduce rounding errors.
- to address isn’t validated or checksummed.
Apply the following diff:
@@ -from hetu_pycli.src.commands.wallet import load_keystore, get_wallet_path +from hetu_pycli.src.commands.wallet import load_keystore, get_wallet_path +from decimal import Decimal @@ def send( - w3 = Web3(Web3.HTTPProvider(rpc_url)) + w3 = Web3(Web3.HTTPProvider(rpc_url)) + if not w3.is_connected(): + print(f"[red]Failed to connect to RPC: {rpc_url}") + raise typer.Exit(1) @@ - tx = { - "to": to, - "value": w3.to_wei(value, "ether"), + if not Web3.is_address(to): + print(f"[red]Invalid recipient address: {to}") + raise typer.Exit(1) + to_addr = Web3.to_checksum_address(to) + try: + value_wei = Web3.to_wei(Decimal(str(value)), "ether") + except Exception as e: + print(f"[red]Invalid amount '{value}': {e}") + raise typer.Exit(1) + + tx = { + "to": to_addr, + "value": int(value_wei), "gas": 21000, - "gasPrice": w3.eth.gas_price, + # Prefer legacy gasPrice for simple value transfers; consider EIP-1559 below. + "gasPrice": w3.eth.gas_price, "nonce": nonce, "chainId": w3.eth.chain_id, }Optional: Support EIP-1559 fee fields when baseFeePerGas is present, falling back to gasPrice if not. I can provide a patch if desired.
51-78: Mirror the safety fixes in send-dk (RPC connectivity, address check, Decimal for amount)The direct-key path needs the same guardrails.
@@ -from web3 import Web3 +from web3 import Web3 +from decimal import Decimal @@ def send_by_direct_key( - w3 = Web3(Web3.HTTPProvider(rpc_url)) + w3 = Web3(Web3.HTTPProvider(rpc_url)) + if not w3.is_connected(): + print(f"[red]Failed to connect to RPC: {rpc_url}") + raise typer.Exit(1) @@ - tx = { - "to": to, - "value": w3.to_wei(value, "ether"), + if not Web3.is_address(to): + print(f"[red]Invalid recipient address: {to}") + raise typer.Exit(1) + to_addr = Web3.to_checksum_address(to) + try: + value_wei = Web3.to_wei(Decimal(str(value)), "ether") + except Exception as e: + print(f"[red]Invalid amount '{value}': {e}") + raise typer.Exit(1) + + tx = { + "to": to_addr, + "value": int(value_wei), "gas": 21000, - "gasPrice": w3.eth.gas_price, + "gasPrice": w3.eth.gas_price, "nonce": nonce, "chainId": w3.eth.chain_id, }Optional: Normalize private_key to accept both with/without 0x and validate hex length. I can add that if you want.
hetu_pycli/cli.py (1)
24-56: Map thechainCLI option to thejson_rpcconfig keyThe
load_confighelper currently copies all CLI args straight into the config dict under their own names—but downstream code readsjson_rpc, notchain. As a result, passing--chainhas no effect on RPC calls. We need to translatechainintojson_rpcduring config loading.Please update
hetu_pycli/config.pyin theload_configfunction:• After line 52 (inside the
for k, v in cli_args.items():loop), handlechainexplicitly:for k, v in cli_args.items(): - if v is not None: - config[k] = v + if v is not None: + if k == "chain": + # Map the CLI “chain” flag into the API URL key + config["json_rpc"] = v + else: + config[k] = vThis ensures that:
config["json_rpc"]is overridden whenever--chainis passed,- The old
chainkey can be dropped or left unused.hetu_pycli/src/hetu/subnet.py (1)
456-468: update_subnet_params: dict-to-tuple relies on insertion order; specify schemaRelying on dict.values() assumes the CLI user picked the same order as the Solidity tuple. This is brittle and will silently send wrong fields.
- Define an explicit ordered schema (e.g., ["paramA","paramB","paramC"]) and map by keys with validation, erroring if missing.
- Alternatively, require a JSON array with documented ordering.
I can provide a patch if you share the expected tuple field order.
hetu_pycli/src/hetu/staking.py (2)
120-166: Stake amount unit handling and symbols
- You convert the stake amount via to_wei(amount, "ether"), implicitly assuming WHETU has 18 decimals. Use the token’s decimals instead to avoid incorrect raw values.
- Mixed unit labels: messages say “HETU” while the balance source is WHETU.
Apply this diff (and add “from decimal import Decimal” at top-level imports):
- decimals = whetu.decimals() - whetu_balance_human = whetu_balance_raw / (10 ** decimals) - amount_wei = staking.web3.to_wei(amount, "ether") + decimals = whetu.decimals() + whetu_balance_human = whetu_balance_raw / (10 ** decimals) + amount_raw = int(Decimal(str(amount)) * (10 ** decimals)) @@ - print(f"[green]Current WHETU balance: {whetu_balance_human} HETU") - print(f"[green]Amount to stake: {amount} HETU") + print(f"[green]Current WHETU balance: {whetu_balance_human} WHETU") + print(f"[green]Amount to stake: {amount} WHETU") @@ - if whetu_balance_raw < amount_wei: + if whetu_balance_raw < amount_raw: @@ - current_allowance = whetu.allowance(from_address, contract) + current_allowance = whetu.allowance(from_address, contract) @@ - if current_allowance < amount_wei: + if current_allowance < amount_raw: @@ - approve_tx = whetu.contract.functions.approve(contract, amount_wei).build_transaction( + approve_tx = whetu.contract.functions.approve(contract, amount_raw).build_transaction( @@ - tx = staking.contract.functions.addGlobalStake(amount_wei).build_transaction( + tx = staking.contract.functions.addGlobalStake(amount_raw).build_transaction( @@ - print(f"[green]Successfully added {amount} HETU to global stake") + print(f"[green]Successfully added {amount} WHETU to global stake")Additional import required near the top of the file:
from decimal import Decimal
218-264: remove_stake: amount conversion assumes 18 decimals; align with WHETU.decimals()Same unit issue as add_stake. Also consider reusing the WHETU decimals fetched via staking.hetuToken().
Sketch of changes:
- Load WHETU via staking.hetuToken()
- decimals = whetu.decimals()
- amount_raw = int(Decimal(str(amount)) * (10 ** decimals))
- Use amount_raw in removeGlobalStake
I can draft the exact diff if desired.
hetu_pycli/src/hetu/wrapper/dendron_mgr.py (1)
1-2: Update or remove outdated commentThe comment on lines 1-2 states "Do not edit this file directly, it is generated from the ABI" but the file has been manually edited with renamed methods and added compatibility shims. Either update the comment to reflect that this is now a manually maintained wrapper with compatibility methods, or remove the misleading instruction.
-# NeuronMgr contract class generated from [ABI](contracts/NeuronManager.abi) -# Do not edit this file directly, it is generated from the ABI. +# DendronMgr contract wrapper class +# This file contains both auto-generated methods from the ABI and manually added compatibility shims
🧹 Nitpick comments (30)
.python-version (1)
1-2: Align Python version documentation and metadataAll core tooling is already targeting Python 3.12.x:
- .python-version is pinned to 3.12.4.
- pyproject.toml specifies
python = "^3.12".- GitHub Actions (
.github/workflows/ci.yml) usesactions/setup-python@v5withpython-version: '3.12'.Recommended next steps:
- Add a “Requires Python 3.12+” callout in README.md (and CONTRIBUTING.md if you introduce one) so contributors and consumers know the minimum interpreter.
- If you ship packaging metadata via setup.cfg or setup.py, consider adding a
python_requires = >=3.12line (or the equivalent Trove classifier) to keep everything in sync.hetu_pycli/src/hetu/wrapper/weights.py (2)
4-8: Be flexible with provider input (URL string vs Provider instance).Accepting a URL string improves ergonomics across CLI/SDK usage.
Apply:
class Weights: def __init__(self, contract_address, provider, abi): self.contract_address = contract_address - self.web3 = Web3(provider) + # Allow either a provider instance or an RPC URL string + if isinstance(provider, str): + self.web3 = Web3(Web3.HTTPProvider(provider)) + else: + self.web3 = Web3(provider) self.contract = self.web3.eth.contract(address=contract_address, abi=abi)
18-26: Fix docstring and normalize inputs for the view method.Clarify return type and use checksum addresses to avoid subtle issues.
Apply:
def weights(self, netuid, validator, dest): """ Call weights(netuid, validator, dest) :param netuid: uint16 (solidity name: 'netuid') :param validator: address (solidity name: 'validator') :param dest: address (solidity name: 'dest') - :return: [{'internalType': 'uint256', 'name': '', 'type': 'uint256'}] + :return: int (uint256) """ - return self.contract.functions.weights(netuid, validator, dest).call() + return self.contract.functions.weights( + int(netuid), + self.web3.to_checksum_address(validator), + self.web3.to_checksum_address(dest), + ).call()hetu_pycli/config.py (1)
16-21: Refactor contract address configuration for multi-network support and overridesVerification confirms that
neuron_addressis no longer referenced anywhere (search returned no hits), so it can be safely removed. However,dendron_addressandweights_addressare actively consumed in both the CLI commands (README.md) and in the code (hetu_pycli/src/hetu/dendron.pyandhetu_pycli/src/hetu/weights.py), so their defaults should not remain hardcoded unconditionally.• Location to update:
- hetu_pycli/config.py (lines 16–21): currently hardcodes all addresses in a single dict.
• Suggested approach:
- Split addresses into per-network config files, e.g.:
- config.local.yml (use localhost or zero addresses)
- config.testnet.yml
- config.mainnet.yml
- Load the appropriate file based on an environment variable (e.g.
HETU_NETWORK) or a CLI flag.- Allow individual overrides via environment variables or
hetucli c set <key> <address>(already documented in README).- Remove the old
neuron_addressentry entirely since it is unused.This ensures that:
- Developers won’t accidentally point at mainnet contracts when running locally or on testnet.
- Users can override any single address without forking the repo.
hetu_pycli/src/commands/wallet.py (1)
17-33: Gracefully handle a missing wallet directory in load_keystore.os.listdir raises if the directory doesn’t exist. Add an early check and clearer message.
Apply:
def load_keystore(address_or_name, wallet_path): # Support lookup by wallet name or address # First try to find by name, otherwise iterate all files to match address field file_path = os.path.join(wallet_path, f"{address_or_name}.json") + if not os.path.exists(wallet_path): + print(f"[red]Wallet path not found: {wallet_path}") + raise typer.Exit(1) if os.path.exists(file_path): with open(file_path, "r") as f: return json.load(f) # fallback: iterate all files to find by address for f in os.listdir(wallet_path):introduction.md (4)
155-159: Clarify that stake thresholds are examples unless enforced on-chainThe specific minimums (500 HETU for miners, 200 HETU for validators) read as normative policy. If these are illustrative rather than hard-coded contract constants, please label them as examples and reference the relevant on-chain constants (e.g., MIN_STAKE_TO_PARTICIPATE or MIN_SUBNET_ALLOCATION) to avoid drift.
Would you like me to scan the repo for these constants in ABIs/wrappers and wire a cross-reference?
Apply this minimal copy edit if they’re examples:
- **Miners**: Require 500 HETU minimum stake allocation to ensure sufficient economic commitment - **Validators**: Require 200 HETU minimum stake allocation, reflecting their different role and responsibility level + **Miners**: Require a minimum subnet allocation of 500 HETU (example; refer to on-chain policy/constants) + **Validators**: Require a minimum subnet allocation of 200 HETU (example; refer to on-chain policy/constants)
160-167: Tighten grammar and list lead-in for the registration requirementsThe section header and lead sentence can be streamlined; minor grammar nits in bullets.
-Users register through the CLI by providing comprehensive information: +Users register through the CLI by providing: - **Wallet credentials and stake amount**: Ensuring sufficient economic commitment and identity verification + - **Wallet credentials and desired stake amount**: Ensures sufficient economic commitment and identity verification - **Network endpoint information**: Including Xylem endpoints for communication and Cambium endpoints for monitoring + - **Network endpoint information**: Include Xylem endpoints (communication) and Cambium endpoints (monitoring) - **Role selection**: Choosing between miner (computational contributor) or validator (network validator) based on capabilities + - **Role selection**: Choose miner (compute) or validator (network) based on capabilities - **Subnet identification**: Specifying the target subnet using its unique netuid identifier + - **Subnet identification**: Specify the target subnet by its netuid - **Technical specifications**: Providing network configuration details for seamless integration + - **Technical specifications**: Provide network configuration details for seamless integration
3-6: Add scope/disclaimer note to prevent policy and tokenomics driftA short upfront disclaimer helps readers distinguish stable interfaces from change-prone parameters (fees, thresholds, addresses).
## Overview Hetu is a decentralized protocol designed to create a network of specialized subnets where participants can stake HETU tokens, register as nodes (miners or validators), and contribute to the network's computational capabilities. The protocol establishes an economic incentive structure that rewards participants for contributing computational resources while maintaining network security through staking mechanisms. + +Note: +- Values such as stake thresholds, fees, and addresses may change via governance or contract upgrades. Treat figures herein as illustrative unless explicitly referenced to on-chain constants or config defaults. +- This document is informational and not financial advice.
177-188: Resolve heading punctuation/spacing and ensure consistent casingMinor formatting: remove double spaces and use consistent casing (e.g., “smart contracts” lowercased mid-sentence).
-## Technical Architecture and Smart Contract Design - -### Core Smart Contracts +## Technical Architecture and smart contract design + +### Core smart contractshetu_pycli/src/hetu/wrapper/global_staking.py (1)
78-85: Signature change safe internally—no internal callers remain
- I ran the suggested search across the repo and found no call sites passing two arguments to
getAvailableStake—only the method definition itself was matched—so the signature shrink won’t break any internal code.- If you maintain external consumers that still invoke the old two-parameter form, consider adding an optional deprecation shim in
hetu_pycli/src/hetu/wrapper/global_staking.py:def getAvailableStake_DEPRECATED(self, user, netuid): # netuid ignored per new ABI; kept for backward compatibility return self.getAvailableStake(user)hetu_pycli/cli.py (1)
78-84: Wiring new groups LGTM; consider a temporary alias for backward compatibilityThe dendron and weights groups replace the previous neuron surface. Consider adding a hidden alias (neuron → dendron) for one release cycle to avoid breaking existing docs/scripts.
Example:
# After registering dendron_app app.add_typer(dendron_app, name="neuron", hidden=True, no_args_is_help=True)README.md (4)
132-136: Inconsistent wallet subcommand alias (“w import” vs “wallet import”)Earlier sections use “hetucli wallet …” but this line uses “hetucli w import …”. Unless “w” is an intentional alias wired in the CLI, this will 404 for users.
If “w” is not an alias, replace it:
-hetucli w import <private-key> --name test0 +hetucli wallet import <private-key> --name test0
139-152: Duplicate activation instructions; streamline for clarityThese lines repeat the activation steps already shown above. Consider keeping one concise, linear flow to reduce confusion.
Apply this minimal dedupe:
-# Activate the subnet using the returned netuid -hetucli subnet activate-subnet --netuid <returned_netuid> --sender test0 - -# Check subnet status -hetucli subnet subnet-info --netuid <netuid> - -# Activate the subnet -hetucli subnet activate-subnet --netuid <netuid> --sender <wallet_name> - -# Verify activation status -hetucli subnet subnet-info --netuid <netuid> +# Activate the subnet and verify status +hetucli subnet activate-subnet --netuid <returned_netuid> --sender test0 +hetucli subnet subnet-info --netuid <returned_netuid>
107-112: Emphasize security caveat for send-dkYou already note it’s not recommended. Consider explicitly warning about shell history, process list leakage, and suggesting env-vars or a keystore workflow.
Example wording update:
-# Direct private key usage (not recommended, private key will be exposed in command history) +# Direct private key usage (strongly discouraged: exposes key in shell history/process list; prefer keystore or env-var)
196-204: Consistency: “wallet balance” line placementIn the WHETU section, “wallet balance <wallet_name>” is mixed with whetu subcommands. Consider moving it to Wallet section or preface with “to verify balance”.
hetu_pycli/src/hetu/subnet.py (4)
196-207: Avoid float rounding for token math; use Decimal for human formattingDividing by 10**decimals and formatting as float will lose precision for large values or 18 decimals.
Use Decimal with context:
+ from decimal import Decimal, getcontext + getcontext().prec = 50 - lock_cost_human = lock_cost_raw / (10 ** decimals) - lock_cost_str = f"{lock_cost_human:,.{decimals}f}".rstrip('0').rstrip('.') + lock_cost_human = Decimal(lock_cost_raw) / (Decimal(10) ** Decimal(decimals)) + lock_cost_str = f"{lock_cost_human:.6f}".rstrip('0').rstrip('.') # cap to 6 dp for readabilityRepeat similarly for balance formatting below.
209-215: Deposit guidance should round up to required amount and avoid float driftUsing a raw float could suggest an amount slightly below required due to rounding.
Consider:
- print(f"[yellow]Please deposit more WHETU using: hetucli whetu deposit --sender {sender} --value {lock_cost_human}") + # round up to 6 dp for CLI convenience + need_str = f"{lock_cost_human:.6f}".rstrip('0').rstrip('.') + print(f"[yellow]Please deposit more WHETU using: hetucli whetu deposit --sender {sender} --value {need_str}")
224-247: Consider approving “exact needed” allowance and/or idempotent top-upsApproving exactly lock_cost_raw is fine, but if an approval already exists slightly below required, users will need to re-approve the whole amount. Approving delta (lockCost - currentAllowance) reduces gas/UX bumps; also consider EIP-20 allowance reset pattern for non-compliant tokens.
Example:
- if current_allowance < lock_cost_raw: + required_delta = lock_cost_raw - current_allowance + if required_delta > 0: print(f"[yellow]Insufficient allowance. Approving {lock_cost_str} WHETU...") - approve_tx = whetu.contract.functions.approve(contract, lock_cost_raw).build_transaction( + approve_tx = whetu.contract.functions.approve(contract, required_delta).build_transaction( {
272-281: Created netuid inference may be racy under concurrencyAssuming created_netuid == next_netuid_before can be wrong if concurrent registrations occur. Prefer deriving from an emitted event or by reading getNextNetuid() after receipt and subtracting one.
Safer fallback:
- # Get the created subnet ID - it should be the current nextNetuid - created_netuid = next_netuid_before + # Best-effort derive created netuid + try: + next_after = subnet_mgr.getNextNetuid() + created_netuid = max(0, next_after - 1) + except Exception: + created_netuid = next_netuid_before # fallbackIf the contract emits a “SubnetCreated”/similar event, parse receipt.logs instead for an authoritative netuid.
hetu_pycli/src/hetu/staking.py (2)
41-69: total_staked: defaulting to contract owner may be surprisingIf no user is supplied, you query staking.owner() and show that account’s totals. That’s an admin address, not the caller’s wallet, and may confuse users.
Consider:
- require --user explicitly, or
- default to a “sender” wallet name supplied via config/flag, not owner().
95-109: DRY: repeated wallet-name-to-address resolutionThe same pattern appears across multiple commands. Extract a helper, e.g., resolve_user_address(ctx, user_or_name), to reduce bugs and keep messaging uniform.
I can provide a small utility function and replace call sites in a follow-up patch.
Also applies to: 325-338, 356-369, 387-400, 466-479
contracts/GlobalStaking.abi (1)
208-237: Gas/UX: consider exposing allocateToSubnetWithMinThreshold in CLIThe ABI includes allocateToSubnetWithMinThreshold. If relevant, add a CLI surface to let validators set a min threshold to reduce slippage-like effects during allocation.
Happy to sketch a new command if you want it in this PR.
Also applies to: 239-260
hetu_pycli/src/hetu/weights.py (3)
96-119: Validate sum/normalization of weights (optional, contract-dependent)If the contract expects weights to sum to a fixed scale (e.g., 1_000_000 per validator), add a preflight sum check to catch user mistakes early.
Example:
contract_weights = [] + total = 0 for weight in weights_data: @@ - contract_weights.append([dest, int(weight_value)]) + w = int(weight_value) + contract_weights.append([dest, w]) + total += w + + if total == 0: + print("[red]Total weight must be > 0") + raise typer.Exit(1) +# Optional strict mode: +# if total != 1_000_000: +# print(f"[red]Total weight must sum to 1_000_000, got {total}") +# raise typer.Exit(1)
136-141: Gas is fixed at 300k; may underflow for larger weight setsFor long weight arrays, 300k may be insufficient. Consider eth_estimateGas with a safety multiplier, and allow --gas override.
Illustrative change:
- tx = weights_contract.contract.functions.setWeights(netuid, new_weights).build_transaction( - { - "from": from_address, - "nonce": nonce, - "gas": 300000, - "gasPrice": weights_contract.web3.eth.gas_price, - } - ) + fn = weights_contract.contract.functions.setWeights(netuid, new_weights) + est = weights_contract.web3.eth.estimate_gas({"from": from_address, "to": weights_contract.contract.address, "data": fn._encode_transaction_data()}) + gas_limit = int(est * 1.2) # 20% headroom + tx = fn.build_transaction({ + "from": from_address, + "nonce": nonce, + "gas": gas_limit, + "gasPrice": weights_contract.web3.eth.gas_price, + })Also applies to: 298-305
155-190: get_weights: nice UX; consider optional wallet-name resolution for validatorNot required, but symmetry with other commands would let users pass a wallet name for validator.
If interested, reuse the resolve helper proposed in staking.py.
hetu_pycli/src/hetu/wrapper/dendron_mgr.py (1)
25-27: Inconsistent return type documentationThe return type documentation shows a complex tuple structure, but the actual return is documented in line 25 as a single tuple. Consider making the documentation clearer by showing it's a tuple type rather than a list with one element.
:param account: address (solidity name: 'account') - :return: [{'components': [{'internalType': 'address', 'name': 'account', 'type': 'address'}, {'internalType': 'uint16', 'name': 'netuid', 'type': 'uint16'}, {'internalType': 'bool', 'name': 'isActive', 'type': 'bool'}, {'internalType': 'bool', 'name': 'isValidator', 'type': 'bool'}, {'internalType': 'uint256', 'name': 'stake', 'type': 'uint256'}, {'internalType': 'uint64', 'name': 'registrationBlock', 'type': 'uint64'}, {'internalType': 'uint256', 'name': 'lastUpdate', 'type': 'uint256'}, {'internalType': 'string', 'name': 'axonEndpoint', 'type': 'string'}, {'internalType': 'uint32', 'name': 'axonPort', 'type': 'uint32'}, {'internalType': 'string', 'name': 'prometheusEndpoint', 'type': 'string'}, {'internalType': 'uint32', 'name': 'prometheusPort', 'type': 'uint32'}], 'internalType': 'struct SubnetTypes.DendronInfo', 'name': '', 'type': 'tuple'}] + :return: SubnetTypes.DendronInfo tuple containing (account, netuid, isActive, isValidator, stake, registrationBlock, lastUpdate, axonEndpoint, axonPort, prometheusEndpoint, prometheusPort)hetu_pycli/src/hetu/amm.py (2)
63-63: Add comment documenting AMM pool address indexThe magic number
3for accessing the AMM pool address from subnet info should be documented for clarity.# Get subnet info to extract AMM pool address subnet_info = subnet_mgr.getSubnetInfo(netuid) + # AMM Pool address is at index 3 in the SubnetInfo tuple amm_pool_address = subnet_info[3] # AMM Pool address is at index 3
594-595: Typo in comment: "10 HETU" should be dynamicThe comment says "10 HETU" but the actual value used for simulation is dynamic (10.0 is hardcoded). Consider making the comment match the code.
# Simulate swap hetu_amount_wei = hetu_token.web3.to_wei(10.0, "ether") simulated_alpha = amm.simSwapHETUForAlpha(hetu_amount_wei) simulated_alpha_ether = hetu_token.web3.from_wei(simulated_alpha, "ether") - print(f"\n[green]Swap Simulation (10 HETU):") + print(f"\n[green]Swap Simulation (10.0 HETU):")contracts/DendronManager.abi (1)
287-296: Align ABI function name with wrapper callThe Python wrapper invokes
getDendronCount(netuid), but the ABI still defines this entry asgetNeuronCount. To ensure the contract functions resolve correctly at runtime, update the ABI incontracts/DendronManager.abi:--- a/contracts/DendronManager.abi +++ b/contracts/DendronManager.abi @@ lines 287-296 - "name": "getNeuronCount", + "name": "getDendronCount", "outputs": [ { "internalType": "uint256", "name": "", "type": "uint256" } ],• File: contracts/DendronManager.abi (lines ~287)
• Change"getNeuronCount"→"getDendronCount"This optional refactor brings the ABI in line with your
DendronMgr.getDendronCountcall and prevents “function not found” errors.hetu_pycli/src/hetu/dendron.py (1)
366-367: Unused decimals variableThe
decimalsvariable is retrieved but never used in the approval flow.current_allowance = whetu.allowance(from_address, staking_contract) - decimals = whetu.decimals() if current_allowance < required_stake_wei:Or if you want to use it for display:
current_allowance = whetu.allowance(from_address, staking_contract) decimals = whetu.decimals() if current_allowance < required_stake_wei: print(f"[yellow]Insufficient allowance. Approving {mgr.web3.from_wei(required_stake_wei, 'ether')} WHETU for staking...")
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (21)
.python-version(1 hunks)README.md(5 hunks)contracts/DendronManager.abi(1 hunks)contracts/GlobalStaking.abi(1 hunks)contracts/NeuronManager.abi(0 hunks)contracts/Weights.abi(1 hunks)hetu_pycli/cli.py(2 hunks)hetu_pycli/config.py(1 hunks)hetu_pycli/src/commands/tx.py(2 hunks)hetu_pycli/src/commands/wallet.py(1 hunks)hetu_pycli/src/hetu/amm.py(10 hunks)hetu_pycli/src/hetu/dendron.py(1 hunks)hetu_pycli/src/hetu/hetu.py(1 hunks)hetu_pycli/src/hetu/neuron.py(0 hunks)hetu_pycli/src/hetu/staking.py(9 hunks)hetu_pycli/src/hetu/subnet.py(5 hunks)hetu_pycli/src/hetu/weights.py(1 hunks)hetu_pycli/src/hetu/wrapper/dendron_mgr.py(5 hunks)hetu_pycli/src/hetu/wrapper/global_staking.py(1 hunks)hetu_pycli/src/hetu/wrapper/weights.py(1 hunks)introduction.md(1 hunks)
💤 Files with no reviewable changes (2)
- hetu_pycli/src/hetu/neuron.py
- contracts/NeuronManager.abi
🧰 Additional context used
🧬 Code Graph Analysis (10)
hetu_pycli/src/hetu/hetu.py (1)
hetu_pycli/src/commands/wallet.py (2)
load_keystore(17-32)get_wallet_path(12-14)
hetu_pycli/src/hetu/wrapper/weights.py (2)
hetu_pycli/src/hetu/wrapper/subnet_amm.py (1)
netuid(204-209)hetu_pycli/src/commands/contract.py (1)
call(10-33)
hetu_pycli/src/hetu/amm.py (6)
hetu_pycli/src/hetu/wrapper/subnet_amm.py (15)
netuid(204-209)getAlphaPrice(64-69)getPoolInfo(99-104)getStatistics(106-111)getSwapPreview(113-120)simSwapAlphaForHETU(218-224)simSwapHETUForAlpha(226-232)withdrawLiquidity(318-326)swapAlphaForHETU(262-270)swapHETUForAlpha(272-280)hetuToken(151-156)alphaToken(18-23)getSystemInfo(122-127)getCreatorInfo(71-76)mechanism(183-188)hetu_pycli/src/hetu/subnet.py (2)
get_contract_address(28-35)load_subnet_mgr(18-26)hetu_pycli/src/hetu/wrapper/subnet_mgr.py (2)
getSubnetInfo(63-69)hetuToken(87-92)hetu_pycli/src/commands/wallet.py (2)
get_wallet_path(12-14)load_keystore(17-32)hetu_pycli/src/hetu/erc20.py (1)
load_erc20(18-26)hetu_pycli/src/hetu/wrapper/whetu.py (3)
approve(27-34)balanceOf(36-42)allowance(18-25)
hetu_pycli/src/hetu/weights.py (3)
hetu_pycli/src/hetu/wrapper/weights.py (3)
weights(18-26)Weights(3-26)setWeights(9-16)hetu_pycli/src/commands/wallet.py (2)
load_keystore(17-32)get_wallet_path(12-14)hetu_pycli/src/hetu/dendron.py (1)
get_contract_address(17-24)
hetu_pycli/src/commands/wallet.py (1)
hetu_pycli/src/commands/config.py (1)
get(15-26)
hetu_pycli/src/hetu/subnet.py (4)
hetu_pycli/src/hetu/whetu.py (3)
load_whetu(17-25)decimals(329-340)name(357-368)hetu_pycli/src/commands/wallet.py (2)
get_wallet_path(12-14)load_keystore(17-32)hetu_pycli/src/hetu/wrapper/subnet_mgr.py (5)
getUserSubnets(79-85)getNetworkLockCost(26-31)hetuToken(87-92)getNextNetuid(40-45)networkRateLimit(122-127)hetu_pycli/src/hetu/wrapper/whetu.py (4)
decimals(44-49)balanceOf(36-42)allowance(18-25)name(65-70)
hetu_pycli/cli.py (1)
hetu_pycli/src/hetu/wrapper/weights.py (1)
weights(18-26)
hetu_pycli/src/hetu/staking.py (6)
hetu_pycli/src/hetu/dendron.py (1)
get_contract_address(17-24)hetu_pycli/src/hetu/whetu.py (4)
get_contract_address(27-34)decimals(329-340)load_whetu(17-25)approve(280-326)hetu_pycli/src/hetu/wrapper/global_staking.py (4)
owner(170-175)getStakeInfo(104-110)hetuToken(144-149)getSubnetAllocation(112-119)hetu_pycli/src/commands/wallet.py (2)
get_wallet_path(12-14)load_keystore(17-32)hetu_pycli/src/hetu/erc20.py (1)
load_erc20(18-26)hetu_pycli/src/hetu/wrapper/whetu.py (4)
decimals(44-49)balanceOf(36-42)allowance(18-25)approve(27-34)
hetu_pycli/src/hetu/dendron.py (7)
hetu_pycli/src/hetu/wrapper/dendron_mgr.py (10)
DendronMgr(6-190)getDendronInfo(20-27)getSubnetDendronCount(29-35)getSubnetDendrons(37-43)getSubnetValidatorCount(45-51)getSubnetValidators(53-59)isDendron(68-75)isValidator(77-84)dendronList(86-93)dendrons(183-190)hetu_pycli/src/commands/wallet.py (2)
load_keystore(17-32)get_wallet_path(12-14)hetu_pycli/src/hetu/subnet.py (2)
get_contract_address(28-35)load_subnet_mgr(18-26)hetu_pycli/src/hetu/staking.py (2)
get_contract_address(28-35)load_staking(18-26)hetu_pycli/src/hetu/wrapper/subnet_mgr.py (1)
getSubnetParams(71-77)hetu_pycli/src/hetu/wrapper/global_staking.py (2)
getAvailableStake(78-84)getStakeInfo(104-110)hetu_pycli/src/hetu/whetu.py (1)
load_whetu(17-25)
hetu_pycli/src/hetu/wrapper/dendron_mgr.py (2)
hetu_pycli/src/commands/contract.py (1)
call(10-33)hetu_pycli/src/hetu/dendron.py (1)
dendrons(206-219)
🪛 LanguageTool
introduction.md
[grammar] ~154-~154: There might be a mistake here.
Context: ... 1. Staking Requirements and Preparation - Miners: Require 500 HETU minimum stake...
(QB_NEW_EN)
[grammar] ~160-~160: There might be a mistake here.
Context: ...ation Process and Technical Requirements Users register through the CLI by provid...
(QB_NEW_EN)
[grammar] ~161-~161: There might be a mistake here.
Context: ... by providing comprehensive information: - Wallet credentials and stake amount: E...
(QB_NEW_EN)
[grammar] ~162-~162: There might be a mistake here.
Context: ...mic commitment and identity verification - Network endpoint information: Includin...
(QB_NEW_EN)
[grammar] ~163-~163: There might be a mistake here.
Context: ...ion and Cambium endpoints for monitoring - Role selection: Choosing between miner...
(QB_NEW_EN)
[grammar] ~164-~164: There might be a mistake here.
Context: ...network validator) based on capabilities - Subnet identification: Specifying the ...
(QB_NEW_EN)
[grammar] ~165-~165: There might be a mistake here.
Context: ...ubnet using its unique netuid identifier - Technical specifications: Providing ne...
(QB_NEW_EN)
[grammar] ~169-~169: There might be a mistake here.
Context: ...r role. The activation process involves: - Smart contract verification: Ensuring ...
(QB_NEW_EN)
[grammar] ~177-~177: There might be a mistake here.
Context: ...ontract Design ### Core Smart Contracts The protocol consists of several interco...
(QB_NEW_EN)
[grammar] ~188-~188: There might be a mistake here.
Context: ...idity. ### Network Interaction Patterns The protocol implements several key inte...
(QB_NEW_EN)
🔇 Additional comments (13)
.python-version (1)
1-1: Pin to Python 3.12.4 looks good.This helps avoid “works on my machine” interpreter drift across devs and CI.
contracts/Weights.abi (2)
1-98: ABI structure is minimal and consistent with intended usage.
- Event WeightsSet has netuid and validator indexed; weights as non-indexed tuple[] — good.
- setWeights is nonpayable and weights getter is view — expected.
No issues from an ABI perspective.
1-98: Sanity Check Passed: The ABI contract surface matches expectations
setWeightsisnonpayableweights()is aviewreturninguint256WeightsSetevent inputs are correctly indexed (first two) with the third as atuple[]of{ dest: address, weight: uint256 }No further changes required.
hetu_pycli/src/commands/tx.py (2)
16-16: Help text improvement LGTMClearer that RPC falls back to config. Consistent with existing resolution: rpc or config["json_rpc"].
57-57: Help text improvement LGTMSame as above for send-dk; aligns with config semantics.
hetu_pycli/cli.py (1)
9-9: Adding hetu_app entrypoint LGTMNew native HETU group import is straightforward and consistent with how other Typer apps are wired.
hetu_pycli/src/hetu/subnet.py (1)
566-579: Nice addition: network_rate_limit read endpointThis complements the other network telemetry getters. No issues.
hetu_pycli/src/hetu/amm.py (4)
36-71: LGTM! Well-structured AMM contract address resolutionThe
get_amm_contract_addressfunction provides a clean implementation for resolving AMM addresses either directly or through subnet info. The mutually exclusive parameter validation and comprehensive error handling are appropriate.
72-104: Enhanced alpha price display with exchange ratesGood improvement to the alpha price display with both raw and formatted values, plus clear exchange rate calculations. The conditional formatting for 1:1 exchange is user-friendly.
281-285: Consistent recipient defaulting patternGood implementation of defaulting the recipient to the sender address when not specified, with clear user notification. This pattern is consistently applied across withdraw_liquidity, swap_alpha_for_hetu, and swap_hetu_for_alpha commands.
Also applies to: 340-344, 399-403
427-521: Comprehensive token approval implementationThe
approve_tokenscommand is well-implemented with proper token address resolution, separate approval flows for HETU and ALPHA, and appropriate transaction handling.contracts/DendronManager.abi (1)
1-711: LGTM! Well-structured DendronManager ABIThe ABI provides a comprehensive interface for dendron/neuron management with appropriate events, errors, and functions. The structure is clean and follows Ethereum ABI standards.
hetu_pycli/src/hetu/dendron.py (1)
293-296: Further Inspection ofsubnet_paramsUsageTo accurately verify the hardcoded indices, we need to see how
subnet_paramsis obtained and where its layout is defined. I’ve requested the snippet around lines 250–350 ofdendron.pyto trace the call that populates this list and locate the source (ABI or wrapper) dictating its ordering. Once we have that context, we can map each index to its intended field and confirm—or correct—the positions forbaseBurnCost,validatorThreshold, anddendronThreshold.
| { | ||
| "inputs": [ | ||
| { | ||
| "internalType": "address", | ||
| "name": "user", | ||
| "type": "address" | ||
| } | ||
| ], | ||
| "name": "getStakeInfo", | ||
| "outputs": [ | ||
| { | ||
| "components": [ | ||
| { | ||
| "internalType": "uint256", | ||
| "name": "totalStaked", | ||
| "type": "uint256" | ||
| }, | ||
| { | ||
| "internalType": "uint256", | ||
| "name": "totalAllocated", | ||
| "type": "uint256" | ||
| }, | ||
| { | ||
| "internalType": "uint256", | ||
| "name": "totalCost", | ||
| "type": "uint256" | ||
| }, | ||
| { | ||
| "internalType": "uint256", | ||
| "name": "lastUpdateBlock", | ||
| "type": "uint256" | ||
| } | ||
| ], | ||
| "internalType": "struct IGlobalStaking.StakeInfo", | ||
| "name": "", | ||
| "type": "tuple" | ||
| } | ||
| ], | ||
| "stateMutability": "view", | ||
| "type": "function" | ||
| }, |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
ABI changed: CLI still calls removed methods (effective/locked); align commands
Per this ABI, getEffectiveStake and getLockedStake are not present; getSubnetAllocation returns (allocated, cost, lastUpdateBlock) and getStakeInfo returns (totalStaked, totalAllocated, totalCost, lastUpdateBlock). Update the CLI accordingly (see staking.py comments).
Run this script to confirm absence in the ABI and find CLI call sites:
Also applies to: 430-469
🏁 Script executed:
#!/bin/bash
# Verify ABI does not contain removed methods
jq -r '.[].name // empty' contracts/GlobalStaking.abi | grep -E 'getEffectiveStake|getLockedStake' || echo "OK: methods not present"
# Show CLI references (expect matches in staking.py only)
rg -nP -C2 '\bgetEffectiveStake\b|\bgetLockedStake\b' --type=pyLength of output: 2731
Critical: ABI methods removed – update CLI and wrapper accordingly
The getEffectiveStake and getLockedStake functions no longer exist in contracts/GlobalStaking.abi, but the Python CLI and wrapper still call them, which will break at runtime. Please update both layers to align with the new ABI methods (getSubnetAllocation and getStakeInfo).
Affected locations:
-
hetu_pycli/src/hetu/staking.py
• Line 370:staking.getEffectiveStake(user, netuid)
• Line 401:staking.getLockedStake(user, netuid)
→ Remove or replace these commands. Usestaking.getStakeInfo(user)(and/or a newgetSubnetAllocation(user)wrapper) and format the fields:
–totalStaked,totalAllocated,totalCost,lastUpdateBlock
– orallocated,cost,lastUpdateBlockas appropriate. -
hetu_pycli/src/hetu/wrapper/global_staking.py
• MethodsgetEffectiveStake(lines 86–93) andgetLockedStake(lines 95–102) still call the removed ABI functions.
→ Remove or refactor these wrappers. You can instead expose:
–getStakeInfo(self, user)which wraps the newgetStakeInfoABI call
– (Optionally)getSubnetAllocation(self, user)for the 3-tuple ABI return
Please ensure the CLI commands reference only existing ABI functions and that the wrapper layer maps to those correctly.
🤖 Prompt for AI Agents
In contracts/GlobalStaking.abi (around lines 388–428) and the Python code,
update hetu_pycli/src/hetu/staking.py (lines ~370 and ~401) and
hetu_pycli/src/hetu/wrapper/global_staking.py (methods at lines ~86–102): the
ABI no longer has getEffectiveStake/getLockedStake so remove calls to those
functions and replace with wrappers that call the new ABI methods—implement
getStakeInfo(self, user) in the wrapper to call the new getStakeInfo ABI and
return/format totalStaked, totalAllocated, totalCost, lastUpdateBlock (and
optionally add getSubnetAllocation(self, user) if needed to wrap the other ABI),
then update the CLI commands in staking.py to use staking.getStakeInfo(user)
(and/or staking.getSubnetAllocation(user)) and map the returned tuple fields to
the expected output instead of calling the removed methods.
| "chain": "ws://127.0.0.1:9090", | ||
| "json_rpc": "http://161.97.161.133:18545", | ||
| "network": "local", |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
Default endpoints suggest a remote HTTP RPC while network is set to 'local'.
This may surprise users by hitting a remote node by default. Consider:
- Keeping localhost defaults for “local” profile and introducing a “testnet/mainnet” profile, or
- Sourcing endpoints from HETU_NETWORK env var and switching addresses accordingly.
At minimum, document this clearly in README and CLI help.
To spot potential mismatches, run:
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Current defaults:"
rg -n 'DEFAULT_CONFIG\s*=\s*{' -n -n hetu_pycli/config.py -n
rg -n '\"network\":\s*\"local\"' hetu_pycli/config.py || true
rg -n '\"json_rpc\":' hetu_pycli/config.py || trueLength of output: 235
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "🔍 Searching for DEFAULT_CONFIG block"
rg -n 'DEFAULT_CONFIG\s*=\s*\{' -C2 hetu_pycli/config.py || true
echo ""
echo "🔍 Verifying the “network” default"
rg -n '"network":\s*"local"' hetu_pycli/config.py || true
echo ""
echo "🔍 Verifying the “json_rpc” default"
rg -n '"json_rpc":' hetu_pycli/config.py || trueLength of output: 697
Align default JSON-RPC endpoint with “local” network or document the mismatch
The DEFAULT_CONFIG in hetu_pycli/config.py currently sets:
- chain:
ws://127.0.0.1:9090 - json_rpc:
http://161.97.161.133:18545 - network:
"local"
Because the JSON-RPC URL points at a remote host while the network is labeled “local,” users invoking the CLI with no overrides may unexpectedly hit a public node. To prevent confusion or inadvertent exposure, please address this in one of the following ways:
• Switch the default JSON-RPC URL to http://127.0.0.1:18545 for the local profile (i.e. keep both endpoints on localhost).
• Introduce named profiles (e.g. “local”, “testnet”, “mainnet”) each with its own endpoint set, and default to the truly local profile.
• Source endpoints from a HETU_NETWORK (or similar) environment variable and map “local”→localhost, “testnet”→public RPC, etc.
At minimum, add clear documentation:
- In
README.md, describe that the CLI’s “local” network profile expects a local node and that the JSON-RPC default is remote unless overridden. - In the CLI’s help text (
--help), note that--json-rpcmust be specified to point elsewhere.
Locations to update:
hetu_pycli/config.pylines 9–11 (default JSON-RPC value and/or profile logic)- Documentation in
README.mdand CLI help output
🤖 Prompt for AI Agents
In hetu_pycli/config.py around lines 9–11 the DEFAULT_CONFIG's json_rpc points
to a remote host while network is "local"; change the json_rpc default to
"http://127.0.0.1:18545" so both endpoints are localhost for the local profile,
and update README.md to state that the CLI defaults assume a local node and that
--json-rpc can be used to target a different RPC, and add a brief note to the
CLI --help output indicating the same and recommending explicit override for
non-local networks.
| # Load keystore by wallet name | ||
| try: | ||
| keystore = load_keystore(wallet_name, wallet_path) | ||
| address = keystore.get("address") | ||
| print(f"[yellow]Wallet '{wallet_name}' address: {address}") | ||
| except Exception: | ||
| print(f"[red]Wallet not found: {wallet_name}") | ||
| raise typer.Exit(1) |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Harden balance: validate keystore address and handle RPC failures.
Avoid double-printing errors, ensure address exists and is valid, checksum it, and guard RPC calls.
Apply:
- # Load keystore by wallet name
- try:
- keystore = load_keystore(wallet_name, wallet_path)
- address = keystore.get("address")
- print(f"[yellow]Wallet '{wallet_name}' address: {address}")
- except Exception:
- print(f"[red]Wallet not found: {wallet_name}")
- raise typer.Exit(1)
+ # Load keystore by wallet name
+ try:
+ keystore = load_keystore(wallet_name, wallet_path)
+ except Exception as e:
+ print(f"[red]Wallet not found or invalid: {wallet_name} ({e})")
+ raise typer.Exit(1)
+ address = keystore.get("address")
+ if not address or not Web3.is_address(address):
+ print("[red]Keystore missing a valid 'address' field.")
+ raise typer.Exit(1)
+ address = Web3.to_checksum_address(address)
+ print(f"[yellow]Wallet '{wallet_name}' address: {address}")
@@
- bal = w3.eth.get_balance(address)
+ try:
+ bal = w3.eth.get_balance(address)
+ except Exception as e:
+ print(f"[red]Failed to fetch balance from {rpc_url}: {e}")
+ raise typer.Exit(1)
ether = w3.from_wei(bal, 'ether')
ether_str = f"{ether:,.18f}".rstrip('0').rstrip('.')
print(f"[cyan]Balance: {ether_str} HETU")
print(f"[cyan]Address: {address}")Also applies to: 158-163
🤖 Prompt for AI Agents
In hetu_pycli/src/commands/wallet.py around lines 149-156 (and similarly
158-163), the keystore load block double-prints errors and does not validate the
address or guard RPC calls; change the except to exit once without printing
twice, verify keystore contains an "address" and that it is a valid address
(convert/normalize to checksum form) before printing, and wrap any subsequent
RPC calls in try/except to handle connection or RPC failures gracefully
(log/print a clear one-time error and exit with typer.Exit(1) on RPC errors).
Ensure you import and use a reliable checksum/address validator and only print
the wallet address after validation.
| # 检查是否是钱包名称并转换为地址 | ||
| address = account | ||
| config = ctx.obj | ||
| wallet_path = get_wallet_path(config) | ||
| if not (address.startswith('0x') and len(address) == 42): | ||
| try: | ||
| keystore = load_keystore(account, wallet_path) | ||
| address = keystore.get("address") | ||
| print(f"[yellow]Converted wallet name '{account}' to address: {address}") | ||
| except Exception: | ||
| print(f"[red]Wallet not found: {account}") | ||
| raise typer.Exit(1) | ||
| account = address | ||
|
|
There was a problem hiding this comment.
🛠️ Refactor suggestion
Replace repeated Chinese comments with English
Multiple locations contain Chinese comments (检查是否是钱包名称并转换为地址) that should be in English for consistency with the rest of the codebase.
- # 检查是否是钱包名称并转换为地址
+ # Check if it's a wallet name and convert to addressApply this change to lines 50, 141, 172, and 493.
Also applies to: 141-154, 172-185, 493-506
🤖 Prompt for AI Agents
In hetu_pycli/src/hetu/dendron.py around lines 50-63, 141-154, 172-185, and
493-506 the inline comment "检查是否是钱包名称并转换为地址" appears in Chinese; replace each
occurrence with an English comment such as "Check if input is a wallet name and
convert to address" (or similar concise phrasing consistent with project style)
and keep surrounding logic unchanged so only the comment text is modified for
consistency.
| # 由于 ABI 中没有 canRegisterDendron 函数,我们显示基本信息 | ||
| print(f"[yellow]Checking registration eligibility for user {user} on subnet {netuid}") | ||
| print(f"[yellow]Validator role: {is_validator_role}") | ||
| print(f"[yellow]Note: canRegisterDendron function not available in current ABI") | ||
| print(f"[yellow]Please check subnet status and user stake allocation manually") |
There was a problem hiding this comment.
Remove placeholder message for canRegisterDendron
The function displays a message that canRegisterDendron is not available in the ABI, but this seems to be a placeholder implementation. Consider either implementing actual eligibility checks or removing this command if it's not functional.
Either implement actual registration eligibility checks:
- # 由于 ABI 中没有 canRegisterDendron 函数,我们显示基本信息
- print(f"[yellow]Checking registration eligibility for user {user} on subnet {netuid}")
- print(f"[yellow]Validator role: {is_validator_role}")
- print(f"[yellow]Note: canRegisterDendron function not available in current ABI")
- print(f"[yellow]Please check subnet status and user stake allocation manually")
+ # Check if user is already registered
+ is_registered = mgr.isDendron(netuid, user)
+ if is_registered:
+ print(f"[red]User {user} is already registered as a dendron in subnet {netuid}")
+ return
+
+ # Check stake availability
+ from hetu_pycli.src.hetu.staking import load_staking
+ staking_contract = get_contract_address(ctx, "staking_address", None)
+ staking = load_staking(staking_contract, rpc)
+ available_stake = staking.getAvailableStake(user)
+
+ print(f"[green]User {user} can register: Not currently registered")
+ print(f"[green]Available stake: {mgr.web3.from_wei(available_stake, 'ether')} HETU")
+ print(f"[green]Validator role requested: {is_validator_role}")Or remove the command entirely if it's not needed.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| # 由于 ABI 中没有 canRegisterDendron 函数,我们显示基本信息 | |
| print(f"[yellow]Checking registration eligibility for user {user} on subnet {netuid}") | |
| print(f"[yellow]Validator role: {is_validator_role}") | |
| print(f"[yellow]Note: canRegisterDendron function not available in current ABI") | |
| print(f"[yellow]Please check subnet status and user stake allocation manually") | |
| # Check if user is already registered | |
| is_registered = mgr.isDendron(netuid, user) | |
| if is_registered: | |
| print(f"[red]User {user} is already registered as a dendron in subnet {netuid}") | |
| return | |
| # Check stake availability | |
| from hetu_pycli.src.hetu.staking import load_staking | |
| staking_contract = get_contract_address(ctx, "staking_address", None) | |
| staking = load_staking(staking_contract, rpc) | |
| available_stake = staking.getAvailableStake(user) | |
| print(f"[green]User {user} can register: Not currently registered") | |
| print(f"[green]Available stake: {mgr.web3.from_wei(available_stake, 'ether')} HETU") | |
| print(f"[green]Validator role requested: {is_validator_role}") |
🤖 Prompt for AI Agents
In hetu_pycli/src/hetu/dendron.py around lines 237 to 241, the command prints a
placeholder message stating canRegisterDendron is not available in the ABI;
remove this non-functional placeholder and either implement the actual
eligibility check or delete the command: if implementing, query the
contract/subnet for registration eligibility (e.g., check user's stake, subnet
status, validator role via available ABI methods or on-chain calls), compute and
print a clear eligible/not-eligible result with reasons; if deleting, remove the
command and any references to it and update CLI help/tests accordingly.
| # 检查是否是钱包名称并转换为地址 | ||
| address = sender | ||
| config = ctx.obj | ||
| wallet_path = get_wallet_path(config) | ||
| if not (address.startswith('0x') and len(address) == 42): | ||
| try: | ||
| keystore = load_keystore(sender, wallet_path) | ||
| address = keystore.get("address") | ||
| print(f"[yellow]Wallet '{sender}' address: {address}") | ||
| except Exception: | ||
| print(f"[red]Wallet not found: {sender}") | ||
| raise typer.Exit(1) |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Use Web3 address validation instead of prefix/length check; unify error path
The current check uses startswith('0x') and len==42, which can pass malformed addresses. Prefer Web3.is_address and avoid double-printing errors from load_keystore.
Apply this diff:
- # 检查是否是钱包名称并转换为地址
- address = sender
+ # Resolve wallet name to address if needed
+ address = sender
config = ctx.obj
wallet_path = get_wallet_path(config)
- if not (address.startswith('0x') and len(address) == 42):
+ if not Web3.is_address(address):
try:
keystore = load_keystore(sender, wallet_path)
address = keystore.get("address")
- print(f"[yellow]Wallet '{sender}' address: {address}")
- except Exception:
- print(f"[red]Wallet not found: {sender}")
+ if not Web3.is_address(address):
+ print(f"[red]Resolved address from wallet '{sender}' is invalid: {address}")
+ raise typer.Exit(1)
+ print(f"[yellow]Wallet '{sender}' address: {address}")
+ except Exception:
+ # load_keystore already prints an error; exit here to avoid duplication
raise typer.Exit(1)📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| # 检查是否是钱包名称并转换为地址 | |
| address = sender | |
| config = ctx.obj | |
| wallet_path = get_wallet_path(config) | |
| if not (address.startswith('0x') and len(address) == 42): | |
| try: | |
| keystore = load_keystore(sender, wallet_path) | |
| address = keystore.get("address") | |
| print(f"[yellow]Wallet '{sender}' address: {address}") | |
| except Exception: | |
| print(f"[red]Wallet not found: {sender}") | |
| raise typer.Exit(1) | |
| # Resolve wallet name to address if needed | |
| address = sender | |
| config = ctx.obj | |
| wallet_path = get_wallet_path(config) | |
| if not Web3.is_address(address): | |
| try: | |
| keystore = load_keystore(sender, wallet_path) | |
| address = keystore.get("address") | |
| if not Web3.is_address(address): | |
| print(f"[red]Resolved address from wallet '{sender}' is invalid: {address}") | |
| raise typer.Exit(1) | |
| print(f"[yellow]Wallet '{sender}' address: {address}") | |
| except Exception: | |
| # load_keystore already prints an error; exit here to avoid duplication | |
| raise typer.Exit(1) |
🤖 Prompt for AI Agents
In hetu_pycli/src/hetu/subnet.py around lines 110 to 121, replace the manual
startswith/len check with Web3 address validation: import Web3 and use
Web3.is_address(sender) to detect valid addresses and
Web3.to_checksum_address(...) to normalize; if sender is not a valid address,
attempt to load_keystore(sender, wallet_path) and if that call fails or returns
no "address" then raise typer.Exit(1) with a single error path (do not print the
error twice); ensure exceptions from load_keystore are caught only to produce
the unified exit (no prior print), and when you get an address normalize it to
checksum form before assigning to address.
| from hetu_pycli.src.hetu.whetu import load_whetu | ||
| whetu_contract = get_contract_address(ctx, "whetu_address", None) | ||
| whetu = load_whetu(whetu_contract, rpc) | ||
|
|
||
| # Get decimals and convert to human readable format | ||
| decimals = whetu.decimals() | ||
| lock_cost_human = lock_cost_raw / (10 ** decimals) | ||
| lock_cost_str = f"{lock_cost_human:,.{decimals}f}".rstrip('0').rstrip('.') | ||
| print(f"[green]Network lock cost: {lock_cost_str} WHETU") |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Subnet register: using config whetu_address instead of the contract’s hetuToken; risk of wrong balance/allowance checks
You correctly read whetuToken() into whetu_token_address but then ignore it and load WHETU from config. This can silently check/approve the wrong token.
Apply this diff:
- from hetu_pycli.src.hetu.whetu import load_whetu
- whetu_contract = get_contract_address(ctx, "whetu_address", None)
- whetu = load_whetu(whetu_contract, rpc)
+ from hetu_pycli.src.hetu.whetu import load_whetu
+ whetu = load_whetu(whetu_token_address, rpc)📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| from hetu_pycli.src.hetu.whetu import load_whetu | |
| whetu_contract = get_contract_address(ctx, "whetu_address", None) | |
| whetu = load_whetu(whetu_contract, rpc) | |
| # Get decimals and convert to human readable format | |
| decimals = whetu.decimals() | |
| lock_cost_human = lock_cost_raw / (10 ** decimals) | |
| lock_cost_str = f"{lock_cost_human:,.{decimals}f}".rstrip('0').rstrip('.') | |
| print(f"[green]Network lock cost: {lock_cost_str} WHETU") | |
| from hetu_pycli.src.hetu.whetu import load_whetu | |
| whetu = load_whetu(whetu_token_address, rpc) | |
| # Get decimals and convert to human readable format | |
| decimals = whetu.decimals() | |
| lock_cost_human = lock_cost_raw / (10 ** decimals) | |
| lock_cost_str = f"{lock_cost_human:,.{decimals}f}".rstrip('0').rstrip('.') | |
| print(f"[green]Network lock cost: {lock_cost_str} WHETU") |
🤖 Prompt for AI Agents
In hetu_pycli/src/hetu/subnet.py around lines 191 to 199, the code loads the
WHETU contract using the config key whetu_address instead of the token address
returned by the contract call (whetu_token_address), which can cause incorrect
balance/allowance checks; replace the line that sets whetu_contract to use the
previously read whetu_token_address (or a safe fallback) when calling load_whetu
so the actual hetuToken contract address is loaded, and keep the rest of the
decimals/formatting logic unchanged.
| def getDendronCount(self, netuid): | ||
| """ | ||
| Call batchUpdateStakeAllocations(netuid, accounts, newStakes) | ||
| Call getDendronCount(netuid) | ||
| :param netuid: uint16 (solidity name: 'netuid') | ||
| :param accounts: address[] (solidity name: 'accounts') | ||
| :param newStakes: uint256[] (solidity name: 'newStakes') | ||
| :return: [] | ||
| """ | ||
| return self.contract.functions.batchUpdateStakeAllocations(netuid, accounts, newStakes).call() | ||
|
|
||
| def canRegisterNeuron(self, user, netuid, isValidatorRole): | ||
| """ | ||
| Call canRegisterNeuron(user, netuid, isValidatorRole) | ||
| :param user: address (solidity name: 'user') | ||
| :param netuid: uint16 (solidity name: 'netuid') | ||
| :param isValidatorRole: bool (solidity name: 'isValidatorRole') | ||
| :return: [{'internalType': 'bool', 'name': '', 'type': 'bool'}] | ||
| """ | ||
| return self.contract.functions.canRegisterNeuron(user, netuid, isValidatorRole).call() | ||
|
|
||
| def deregisterNeuron(self, netuid): | ||
| """ | ||
| Call deregisterNeuron(netuid) | ||
| :param netuid: uint16 (solidity name: 'netuid') | ||
| :return: [] | ||
| """ | ||
| return self.contract.functions.deregisterNeuron(netuid).call() | ||
|
|
||
| def distributeRewards(self, netuid, accounts, amounts): | ||
| """ | ||
| Call distributeRewards(netuid, accounts, amounts) | ||
| :param netuid: uint16 (solidity name: 'netuid') | ||
| :param accounts: address[] (solidity name: 'accounts') | ||
| :param amounts: uint256[] (solidity name: 'amounts') | ||
| :return: [] | ||
| :return: [{'internalType': 'uint256', 'name': '', 'type': 'uint256'}] | ||
| """ | ||
| return self.contract.functions.distributeRewards(netuid, accounts, amounts).call() | ||
| return self.contract.functions.getDendronCount(netuid).call() |
There was a problem hiding this comment.
Inconsistent method name mapping
The getDendronCount method calls getDendronCount on the contract, but based on the pattern in other methods and the DendronManager.abi, this should likely call getNeuronCount for compatibility with the underlying contract.
def getDendronCount(self, netuid):
"""
Call getDendronCount(netuid)
:param netuid: uint16 (solidity name: 'netuid')
:return: [{'internalType': 'uint256', 'name': '', 'type': 'uint256'}]
"""
- return self.contract.functions.getDendronCount(netuid).call()
+ return self.contract.functions.getNeuronCount(netuid).call()📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| def getDendronCount(self, netuid): | |
| """ | |
| Call batchUpdateStakeAllocations(netuid, accounts, newStakes) | |
| Call getDendronCount(netuid) | |
| :param netuid: uint16 (solidity name: 'netuid') | |
| :param accounts: address[] (solidity name: 'accounts') | |
| :param newStakes: uint256[] (solidity name: 'newStakes') | |
| :return: [] | |
| """ | |
| return self.contract.functions.batchUpdateStakeAllocations(netuid, accounts, newStakes).call() | |
| def canRegisterNeuron(self, user, netuid, isValidatorRole): | |
| """ | |
| Call canRegisterNeuron(user, netuid, isValidatorRole) | |
| :param user: address (solidity name: 'user') | |
| :param netuid: uint16 (solidity name: 'netuid') | |
| :param isValidatorRole: bool (solidity name: 'isValidatorRole') | |
| :return: [{'internalType': 'bool', 'name': '', 'type': 'bool'}] | |
| """ | |
| return self.contract.functions.canRegisterNeuron(user, netuid, isValidatorRole).call() | |
| def deregisterNeuron(self, netuid): | |
| """ | |
| Call deregisterNeuron(netuid) | |
| :param netuid: uint16 (solidity name: 'netuid') | |
| :return: [] | |
| """ | |
| return self.contract.functions.deregisterNeuron(netuid).call() | |
| def distributeRewards(self, netuid, accounts, amounts): | |
| """ | |
| Call distributeRewards(netuid, accounts, amounts) | |
| :param netuid: uint16 (solidity name: 'netuid') | |
| :param accounts: address[] (solidity name: 'accounts') | |
| :param amounts: uint256[] (solidity name: 'amounts') | |
| :return: [] | |
| :return: [{'internalType': 'uint256', 'name': '', 'type': 'uint256'}] | |
| """ | |
| return self.contract.functions.distributeRewards(netuid, accounts, amounts).call() | |
| return self.contract.functions.getDendronCount(netuid).call() | |
| def getDendronCount(self, netuid): | |
| """ | |
| Call getDendronCount(netuid) | |
| :param netuid: uint16 (solidity name: 'netuid') | |
| :return: [{'internalType': 'uint256', 'name': '', 'type': 'uint256'}] | |
| """ | |
| return self.contract.functions.getNeuronCount(netuid).call() |
🤖 Prompt for AI Agents
hetu_pycli/src/hetu/wrapper/dendron_mgr.py lines 12-18: the method
getDendronCount currently calls self.contract.functions.getDendronCount(netuid)
but per ABI and project naming patterns it should call getNeuronCount for
compatibility with the underlying contract; update the function call to
self.contract.functions.getNeuronCount(netuid).call(), adjust the docstring to
reflect getNeuronCount and keep the same parameters and return handling.
| def setWeights(self, netuid, newWeights): | ||
| """ | ||
| Call setWeights(netuid, newWeights) | ||
| :param netuid: uint16 (solidity name: 'netuid') | ||
| :param newWeights: Weight[] (solidity name: 'newWeights') | ||
| :return: [] | ||
| """ | ||
| return self.contract.functions.setWeights(netuid, newWeights).call() | ||
|
|
There was a problem hiding this comment.
Critical: setWeights is state-changing but uses .call(); this never persists on-chain.
Use a transaction path. Either build and return a tx for external signing or send via .transact when an unlocked account is available.
Apply:
- def setWeights(self, netuid, newWeights):
+ def setWeights(self, netuid, newWeights, tx_params: dict | None = None, build_only: bool = True):
"""
- Call setWeights(netuid, newWeights)
+ Build or send setWeights(netuid, newWeights)
:param netuid: uint16 (solidity name: 'netuid')
:param newWeights: Weight[] (solidity name: 'newWeights')
- :return: []
+ :param tx_params: dict with at least 'from' when building/sending
+ :param build_only: if True, returns unsigned tx dict; if False, sends via .transact
+ :return: dict (unsigned tx) when build_only=True, else tx hash hex string
"""
- return self.contract.functions.setWeights(netuid, newWeights).call()
+ # Normalize inputs and checksum addresses
+ fn = self.contract.functions.setWeights(
+ int(netuid),
+ [
+ (self.web3.to_checksum_address(w["dest"]), int(w["weight"]))
+ for w in newWeights
+ ],
+ )
+ if build_only:
+ if not tx_params or "from" not in tx_params:
+ raise ValueError("tx_params with a 'from' address is required to build the transaction")
+ from_addr = self.web3.to_checksum_address(tx_params["from"])
+ base = {"from": from_addr}
+ base["nonce"] = tx_params.get("nonce", self.web3.eth.get_transaction_count(from_addr))
+ # Optional EIP-1559 fields passthrough
+ for k in ("gas", "chainId", "maxFeePerGas", "maxPriorityFeePerGas", "gasPrice", "value"):
+ if k in tx_params:
+ base[k] = tx_params[k]
+ return fn.build_transaction(base)
+ else:
+ if not tx_params:
+ raise ValueError("tx_params required to send transaction (must include 'from').")
+ return fn.transact(tx_params)Note: Returning a built tx aligns with your existing sign_tx flow.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| def setWeights(self, netuid, newWeights): | |
| """ | |
| Call setWeights(netuid, newWeights) | |
| :param netuid: uint16 (solidity name: 'netuid') | |
| :param newWeights: Weight[] (solidity name: 'newWeights') | |
| :return: [] | |
| """ | |
| return self.contract.functions.setWeights(netuid, newWeights).call() | |
| def setWeights(self, netuid, newWeights, tx_params: dict | None = None, build_only: bool = True): | |
| """ | |
| Build or send setWeights(netuid, newWeights) | |
| :param netuid: uint16 (solidity name: 'netuid') | |
| :param newWeights: Weight[] (solidity name: 'newWeights') | |
| :param tx_params: dict with at least 'from' when building/sending | |
| :param build_only: if True, returns unsigned tx dict; if False, sends via .transact | |
| :return: dict (unsigned tx) when build_only=True, else tx hash hex string | |
| """ | |
| # Normalize inputs and checksum addresses | |
| fn = self.contract.functions.setWeights( | |
| int(netuid), | |
| [ | |
| (self.web3.to_checksum_address(w["dest"]), int(w["weight"])) | |
| for w in newWeights | |
| ], | |
| ) | |
| if build_only: | |
| if not tx_params or "from" not in tx_params: | |
| raise ValueError("tx_params with a 'from' address is required to build the transaction") | |
| from_addr = self.web3.to_checksum_address(tx_params["from"]) | |
| base = {"from": from_addr} | |
| base["nonce"] = tx_params.get("nonce", self.web3.eth.get_transaction_count(from_addr)) | |
| # Optional EIP-1559 fields passthrough | |
| for k in ("gas", "chainId", "maxFeePerGas", "maxPriorityFeePerGas", "gasPrice", "value"): | |
| if k in tx_params: | |
| base[k] = tx_params[k] | |
| return fn.build_transaction(base) | |
| else: | |
| if not tx_params: | |
| raise ValueError("tx_params required to send transaction (must include 'from').") | |
| return fn.transact(tx_params) |
🤖 Prompt for AI Agents
In hetu_pycli/src/hetu/wrapper/weights.py around lines 9 to 17, the setWeights
method currently uses .call() which is read-only and does not persist state;
change it to produce a state-changing transaction instead — either build and
return a transaction dict (using
contract.functions.setWeights(...).buildTransaction(...) with appropriate
nonce/gas/from fields) so the caller can sign/send via your existing sign_tx
flow, or if an unlocked account is expected, send directly with .transact(...)
and return the tx hash; ensure required tx fields (from, nonce, gas, gasPrice or
maxFee/maxPriority) are set.
| ```bash | ||
| # Check subnet status | ||
| hetucli subnet subnet-info --netuid <netuid> | ||
|
|
||
| # Activate the subnet | ||
| hetucli subnet activate-subnet --netuid <netuid> --sender <wallet_name> | ||
|
|
||
| # Verify activation status | ||
| hetucli subnet subnet-info --netuid <netuid> | ||
| ``` |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Broken code fences: nested ```bash block breaks rendering
There is an opening bash at Line 131 and a second bash at Line 143 without closing the first block. This will render incorrectly on GitHub and can hide content beneath.
Apply this diff to close the first fence before starting the “Check subnet status” block:
-# Activate the subnet using the returned netuid
-hetucli subnet activate-subnet --netuid <returned_netuid> --sender test0
-
-
-```bash
+# Activate the subnet using the returned netuid
+hetucli subnet activate-subnet --netuid <returned_netuid> --sender test0
+```
+
+```bash
# Check subnet status
hetucli subnet subnet-info --netuid <netuid>🤖 Prompt for AI Agents
In README.md around lines 143 to 152, the markdown has nested/open code-fence
issues: close the previous ```bash fence before starting the “Check subnet
status” block by inserting the missing activation command line(s) and a closing
``` on the prior block, then open a fresh ```bash block for the "Check subnet
status" snippet so each fenced block is properly terminated and rendered.
Summary by CodeRabbit
New Features
Refactor
Documentation
Chores