Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions ptn_cli/ptncli.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@
from ptn_cli.src.commands.asset import (
select as select_asset
)
from ptn_cli.src.commands.pop import (
generate_tree as pop_generate_tree
)

_epilog = "Made with [bold red]:heart:[/bold red] by The Proprieτary Trading Neτwork"

Expand All @@ -38,19 +41,36 @@ class PTNOptions:
"--prompt",
help="Whether to prompt for confirmation",
)
data_file = typer.Option(
None,
"--path",
help="Path to the data.json file",
)
hotkey_opt = typer.Option(
None,
"--hotkey",
help="Miner's hotkey",
)
output_path = typer.Option(
None,
"--output",
help="Path where the tree.json file will be saved",
)



class PTNCLIManager(CLIManager):

collateral_app: typer.Typer
asset_app: typer.Typer
pop_app: typer.Typer

def __init__(self):
super().__init__()

self.collateral_app = typer.Typer(epilog=_epilog)
self.asset_app = typer.Typer(epilog=_epilog)
self.pop_app = typer.Typer(epilog=_epilog)

self.app.add_typer(
self.collateral_app,
Expand All @@ -64,6 +84,12 @@ def __init__(self):
short_help="Asset command for choosing asset",
no_args_is_help=True
)
self.app.add_typer(
self.pop_app,
name="pop",
short_help="Proof-of-Portfolio commands",
no_args_is_help=True
)

self.collateral_app.command(
"list", rich_help_panel="Collateral Management"
Expand All @@ -79,6 +105,10 @@ def __init__(self):
"select", rich_help_panel="Asset class selection"
)(self.asset_select)

self.pop_app.command(
"generate-tree", rich_help_panel="Proof-of-Portfolio"
)(self.pop_generate_tree)

def collateral_list(
self,
wallet_name: Optional[str] = Options.wallet_name,
Expand Down Expand Up @@ -240,6 +270,31 @@ def asset_select(
)
)

def pop_generate_tree(
self,
data_file: Optional[str] = PTNOptions.data_file,
hotkey: Optional[str] = PTNOptions.hotkey_opt,
output_path: Optional[str] = PTNOptions.output_path,
quiet: bool = Options.quiet,
verbose: bool = Options.verbose,
json_output: bool = Options.json_output,
):
"""
Generate a merkle tree for a miner using proof-of-portfolio
"""
self.verbosity_handler(quiet, verbose, json_output)

return self._run_command(
pop_generate_tree.generate_tree(
data_file,
hotkey,
output_path,
quiet,
verbose,
json_output
)
)


def main():
manager = PTNCLIManager()
Expand Down
1 change: 1 addition & 0 deletions ptn_cli/src/commands/pop/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Pop commands for proof-of-portfolio integration
99 changes: 99 additions & 0 deletions ptn_cli/src/commands/pop/generate_tree.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
from typing import Optional
import sys
import json
from pathlib import Path
from proof_of_portfolio.signal_processor import (
generate_validator_trees,
get_validator_tree_hashes,
)


async def generate_tree(
signals_dir: Optional[str] = None,
hotkey: Optional[str] = None,
output_path: Optional[str] = None,
quiet: bool = False,
verbose: bool = False,
json_output: bool = False,
):
"""
Generate merkle trees for miners using processed signals from mining directory
"""

if not signals_dir:
current_dir = Path.cwd()
potential_dirs = [
current_dir / "mining" / "processed_signals",
current_dir / "processed_signals",
Path("./mining/processed_signals"),
]

for dir_path in potential_dirs:
if dir_path.exists() and dir_path.is_dir():
signals_dir = str(dir_path)
if not quiet:
print(f"Found signals directory: {signals_dir}")
break

if not signals_dir:
if not quiet:
print("Error: No processed signals directory found.", file=sys.stderr)
print(
"Please provide --signals-dir path or place files in ./mining/processed_signals/",
file=sys.stderr,
)
return False

signals_path = Path(signals_dir)
if not signals_path.exists() or not signals_path.is_dir():
if not quiet:
print(
f"Error: Signals directory not found at {signals_dir}", file=sys.stderr
)
return False

try:
validator_trees = generate_validator_trees(
signals_dir=signals_dir, hotkey=hotkey, output_dir=output_path, quiet=quiet
)

if not validator_trees:
return False

validator_hashes = get_validator_tree_hashes(validator_trees)

if json_output:
result = {
"validator_trees": len(validator_trees),
"validators": {
validator_key: {
"order_count": data["order_count"],
"output_path": data["output_path"],
"tree_hash": data["tree_hash"],
}
for validator_key, data in validator_trees.items()
},
}
print(json.dumps(result, indent=2))
else:
print("\nExpected Tree Hashes:")
print("-" * 80)
for validator_key, tree_hash in validator_hashes.items():
data = validator_trees[validator_key]
print(f"Validator: {validator_key}")
print(f"Hash: {tree_hash}")
print(f"Orders: {data['order_count']}")
print(f"Output: {data['output_path']}")
print("-" * 80)

return True

except Exception as e:
if not quiet:
import traceback

print(f"Error generating trees: {str(e)}", file=sys.stderr)
if verbose:
print(f"Full traceback:", file=sys.stderr)
traceback.print_exc()
return False
19 changes: 6 additions & 13 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,7 @@ name = "ptn-cli"
version = "1.0.3"
description = "Proprietary Trading Network CLI"
readme = "README.md"
authors = [
{name = "taoshi.io" }
]
authors = [{ name = "taoshi.io" }]
license = { file = "LICENSE" }
scripts = { ptncli = "ptn_cli.ptncli:main" }
requires-python = ">=3.9,<3.14"
Expand All @@ -19,7 +17,7 @@ dependencies = [
"async-substrate-interface>=1.4.2",
"aiohttp~=3.10.2",
"backoff~=2.2.1",
"click<8.2.0", # typer.testing.CliRunner(mix_stderr=) is broken in click 8.2.0+
"click<8.2.0", # typer.testing.CliRunner(mix_stderr=) is broken in click 8.2.0+
"GitPython>=3.0.0",
"netaddr~=1.3.0",
"numpy>=2.0.1,<3.0.0",
Expand All @@ -32,17 +30,12 @@ dependencies = [
"bittensor-wallet>=3.0.7",
"plotille>=5.0.0",
"plotly>=6.0.0",
"proof-of-portfolio>=0.0.14",
]

[project.optional-dependencies]
cuda = [
"torch>=1.13.1,<3.0",
]
dev = [
"pytest",
"pytest-asyncio",
"ruff==0.11.5",
]
cuda = ["torch>=1.13.1,<3.0"]
dev = ["pytest", "pytest-asyncio", "ruff==0.11.5"]

[project.urls]
# more details can be found here
Expand All @@ -62,7 +55,7 @@ classifiers = [
"Topic :: Scientific/Engineering",
"Topic :: Scientific/Engineering :: Mathematics",
"Topic :: Scientific/Engineering :: Artificial Intelligence",
"Topic :: Utilities"
"Topic :: Utilities",
]

[tool.setuptools]
Expand Down