Skip to content

Commit 4325767

Browse files
committed
Enhance CLI with wallet management and transaction features
Introduces comprehensive wallet management functionality including: - Named wallet support with keystore management - Improved transaction handling with both keystore and direct key options - New config management commands with shortcuts - Enhanced balance checking with name/address resolution - Transaction signing capabilities Adds json_rpc endpoint to config and improves CLI usability with: - Short command aliases (w, c, conf) - Consistent help behavior across commands - Better error handling and user feedback - More intuitive wallet operations using names instead of just addresses
1 parent 5c2c171 commit 4325767

File tree

8 files changed

+340
-43
lines changed

8 files changed

+340
-43
lines changed

.github/workflows/ci.yml

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [ main ]
6+
pull_request:
7+
branches: [ main ]
8+
9+
jobs:
10+
build:
11+
runs-on: ubuntu-latest
12+
steps:
13+
- uses: actions/checkout@v4
14+
- name: Set up Python
15+
uses: actions/setup-python@v5
16+
with:
17+
python-version: '3.12'
18+
- name: Install Poetry
19+
run: |
20+
pip install poetry
21+
- name: Install dependencies
22+
run: |
23+
poetry install --no-interaction --no-root
24+
- name: Lint with ruff
25+
run: |
26+
poetry run pip install ruff
27+
poetry run ruff hetu_pycli
28+
- name: Run tests (if any)
29+
run: |
30+
poetry run pytest || echo 'No tests found.'

CHANGELOG.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# Changelog
2+
3+
## [Unreleased]
4+
### Added
5+
- Support for importing and exporting wallet private keys
6+
- Improved error handling and user feedback for wallet commands
7+
- Support for signing transactions with wallet private keys
8+
- New command for checking wallet balance
9+
- Support for sending transactions using wallet private keys
10+
- New command for listing all wallets
11+
- Support for importing new wallets with a private key
12+
- New command for exporting wallet private keys
13+
- New config command to show current settings in JSON format
14+
- New command to set configuration values
15+
- New command to reset configuration to default values

contrib/contributing.md

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# Contributing to Hetu chain CLI
2+
We want to make contributing to this project as easy and transparent as
3+
possible.
4+
5+
We have an official Twitter where you can follow the latest updates and announcements:
6+
7+
* [Official Hetu protocol](https://x.com/hetu_protocol)
8+
* [Open an Issue](https://github.com/hetu-project/hetu-pycli/issues)
9+
10+
## Issues
11+
We use GitHub issues to track bugs and improvements. Please ensure your description is
12+
clear and has sufficient instructions to be able to reproduce the issue.
13+
14+
When submitting a bug report, please include the following information:
15+
1. Python version
16+
2. Network or Chain which was being used to interact with Subtensor
17+
3. Steps to reproduce the issue
18+
4. Expected behavior and actual behavior
19+
5. Any error messages or stack traces encountered
20+
21+
## Pull Requests
22+
We welcome your pull requests. To ensure a smooth and efficient review process, please follow these guidelines:
23+
24+
### Bug fixes & Improvements:
25+
26+
1. Fork the repo and create your branch from `main`.
27+
2. If you've added code that should be tested, add tests.
28+
3. If you've changed APIs, update the documentation.
29+
4. Ensure the test suite passes.
30+
5. Make sure your code lints. We use ruff for linting.
31+
6. Provide detailed explanation what your PR fixes, alternate designs, and possible ripple effects.
32+
33+
### Feature Requests
34+
We welcome feature requests and suggestions for improving the Hetu chain CLI. To submit a feature request, please follow these guidelines:
35+
36+
1. If your feature request doesn't already exist, create a new issue on GitHub with the label "enhancement" or "feature request".
37+
3. Provide a clear and descriptive title for the feature request.
38+
4. Explain the feature you'd like to see added, why it would be useful, and how it could be implemented. Be as specific as possible.
39+
5. If applicable, include examples, screenshots, or mockups to help illustrate your feature request.
40+
6. Be patient and understanding. The maintainers will review your feature request and provide feedback.

hetu_pycli/cli.py

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from hetu_pycli.src.commands.wallet import wallet_app
44
from hetu_pycli.src.commands.tx import tx_app
55
from hetu_pycli.src.commands.contract import contract_app
6+
from hetu_pycli.src.commands.config import config_app
67
from hetu_pycli.config import load_config, ensure_config_file
78
from hetu_pycli.version import __version__
89

@@ -11,15 +12,13 @@
1112
no_args_is_help=True,
1213
)
1314

14-
1515
@app.callback()
1616
def main_callback(
17+
ctx: typer.Context,
1718
version: bool = typer.Option(
1819
None,
1920
"--version",
20-
callback=lambda v: (print(__version__), raise_exit())
21-
if v
22-
else None,
21+
callback=lambda v: (print(__version__), raise_exit()) if v else None,
2322
is_eager=True,
2423
help="Show version and exit.",
2524
),
@@ -44,16 +43,27 @@ def main_callback(
4443
wallet_path=wallet_path,
4544
)
4645
config_obj = load_config(config, cli_args)
47-
typer.Context.obj = config_obj
46+
ctx.obj = config_obj
4847

4948

5049
def raise_exit():
5150
raise typer.Exit()
5251

5352

54-
app.add_typer(wallet_app, name="wallet", help="Wallet management")
55-
app.add_typer(tx_app, name="tx", help="Transfer & transaction")
56-
app.add_typer(contract_app, name="contract", help="Contract operations")
53+
app.add_typer(
54+
wallet_app,
55+
name="wallet",
56+
help="Wallet management",
57+
no_args_is_help=True,
58+
)
59+
app.add_typer(wallet_app, name="w", hidden=True, no_args_is_help=True)
60+
app.add_typer(tx_app, name="tx", help="Transfer & transaction", no_args_is_help=True)
61+
app.add_typer(
62+
contract_app, name="contract", help="Contract operations", no_args_is_help=True
63+
)
64+
app.add_typer(config_app, name="config", help="Config management", no_args_is_help=True)
65+
app.add_typer(config_app, name="c", hidden=True, no_args_is_help=True)
66+
app.add_typer(config_app, name="conf", hidden=True, no_args_is_help=True)
5767

5868
if __name__ == "__main__":
5969
app()

hetu_pycli/config.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
DEFAULT_CONFIG_PATH = os.path.expanduser("~/.hetucli/config.yml")
66
DEFAULT_CONFIG = {
77
"chain": "ws://127.0.0.1:8545",
8+
"json_rpc": "http://127.0.0.1:8545",
89
"network": "local",
910
"no_cache": False,
1011
"wallet_hotkey": "hotkey-user1",

hetu_pycli/src/commands/config.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import typer
2+
import yaml
3+
import json
4+
from rich import print
5+
from hetu_pycli.config import DEFAULT_CONFIG_PATH, load_config, ensure_config_file
6+
7+
config_app = typer.Typer(help="Config file management commands")
8+
9+
@config_app.command()
10+
def show(ctx: typer.Context):
11+
"""Displays settings from the config file."""
12+
typer.echo(json.dumps(ctx.obj, indent=2, ensure_ascii=False))
13+
14+
@config_app.command()
15+
def get(
16+
key: str = typer.Argument(None, help="Config key (optional, show all if omitted)"),
17+
):
18+
"""Show current config value(s)"""
19+
ensure_config_file()
20+
config = load_config()
21+
if key:
22+
value = config.get(key, None)
23+
print(f"[cyan]{key}: {value}")
24+
else:
25+
for k, v in config.items():
26+
print(f"[cyan]{k}: {v}")
27+
28+
29+
@config_app.command()
30+
def set(
31+
key: str = typer.Argument(..., help="Config key to set/update"),
32+
value: str = typer.Argument(..., help="Value to set"),
33+
):
34+
"""Set or update a config value"""
35+
ensure_config_file()
36+
path = DEFAULT_CONFIG_PATH
37+
config = load_config()
38+
config[key] = value
39+
with open(path, "w") as f:
40+
yaml.safe_dump(config, f)
41+
print(f"[green]Set {key} = {value}")
42+
43+
44+
@config_app.command()
45+
def clear(key: str = typer.Argument(..., help="Config key to clear")):
46+
"""Clear a specific config key"""
47+
ensure_config_file()
48+
path = DEFAULT_CONFIG_PATH
49+
config = load_config()
50+
if key in config:
51+
del config[key]
52+
with open(path, "w") as f:
53+
yaml.safe_dump(config, f)
54+
print(f"[yellow]Cleared {key}")
55+
else:
56+
print(f"[red]Key not found: {key}")

hetu_pycli/src/commands/tx.py

Lines changed: 55 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,67 @@
22
from web3 import Web3
33
from eth_account import Account
44
from rich import print
5+
from hetu_pycli.src.commands.wallet import load_keystore, get_wallet_path
6+
import getpass
57

68
tx_app = typer.Typer(help="Transfer and transaction commands")
79

8-
910
@tx_app.command()
1011
def send(
12+
ctx: typer.Context,
13+
sender: str = typer.Option(..., help="Wallet name or address (local keystore)"),
14+
to: str = typer.Option(..., help="Recipient address"),
15+
value: float = typer.Option(..., help="Transfer amount (HETU)"),
16+
rpc: str = typer.Option(None, help="Ethereum node RPC URL"),
17+
wallet_path: str = typer.Option(None, help="Wallet path (default from config)"),
18+
password: str = typer.Option(None, help="Password for keystore (prompt if not set)"),
19+
):
20+
"""Send HETU transfer using local keystore by wallet name or address."""
21+
config = getattr(ctx, "obj", None) or {}
22+
rpc_url = rpc or config.get("json_rpc")
23+
if not rpc_url:
24+
print("[red]No RPC URL provided or found in config.")
25+
raise typer.Exit(1)
26+
wallet_path = wallet_path or get_wallet_path(config)
27+
keystore = load_keystore(sender, wallet_path)
28+
if not password:
29+
password = getpass.getpass("Keystore password: ")
30+
try:
31+
privkey = Account.decrypt(keystore, password)
32+
acct = Account.from_key(privkey)
33+
except Exception as e:
34+
print(f"[red]Failed to unlock keystore: {e}")
35+
raise typer.Exit(1)
36+
w3 = Web3(Web3.HTTPProvider(rpc_url))
37+
nonce = w3.eth.get_transaction_count(acct.address)
38+
tx = {
39+
"to": to,
40+
"value": w3.to_wei(value, "ether"),
41+
"gas": 21000,
42+
"gasPrice": w3.eth.gas_price,
43+
"nonce": nonce,
44+
"chainId": w3.eth.chain_id,
45+
}
46+
signed = acct.sign_transaction(tx)
47+
tx_hash = w3.eth.send_raw_transaction(signed.raw_transaction)
48+
print(f"[green]Transaction sent: {tx_hash.hex()}")
49+
50+
51+
@tx_app.command(name="send-dk")
52+
def send_by_direct_key(
53+
ctx: typer.Context,
1154
private_key: str = typer.Option(..., help="Sender private key"),
1255
to: str = typer.Option(..., help="Recipient address"),
13-
value: float = typer.Option(..., help="Transfer amount (ETH)"),
14-
rpc: str = typer.Option(..., help="Ethereum node RPC URL"),
56+
value: float = typer.Option(..., help="Transfer amount (HETU)"),
57+
rpc: str = typer.Option(None, help="Ethereum node RPC URL"),
1558
):
16-
"""Send ETH transfer"""
17-
w3 = Web3(Web3.HTTPProvider(rpc))
59+
"""Send HETU transfer directly by private key."""
60+
config = getattr(ctx, "obj", {}) or {}
61+
rpc_url = rpc or config.get("json_rpc")
62+
if not rpc_url:
63+
print("[red]No RPC URL provided or found in config.")
64+
raise typer.Exit(1)
65+
w3 = Web3(Web3.HTTPProvider(rpc_url))
1866
acct = Account.from_key(private_key)
1967
nonce = w3.eth.get_transaction_count(acct.address)
2068
tx = {
@@ -26,5 +74,5 @@ def send(
2674
"chainId": w3.eth.chain_id,
2775
}
2876
signed = acct.sign_transaction(tx)
29-
tx_hash = w3.eth.send_raw_transaction(signed.rawTransaction)
30-
print(f"[green]Transaction sent: {tx_hash.hex()}")
77+
tx_hash = w3.eth.send_raw_transaction(signed.raw_transaction)
78+
print(f"[green]Transaction sent: {tx_hash.hex()}")

0 commit comments

Comments
 (0)