diff --git a/hunter/__init__.py b/hunter/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/hunter/core/executor.py b/hunter/core/executor.py new file mode 100644 index 00000000..d9dc8907 --- /dev/null +++ b/hunter/core/executor.py @@ -0,0 +1,31 @@ +import subprocess +import os + +class BountyExecutor: + """ + Handles the technical execution of a bounty: fork, clone, implement, commit, PR. + """ + def __init__(self, repo: str, token: str): + self.repo = repo + self.token = token + + def fork_and_clone(self): + """Fork and clone the repository using gh CLI.""" + print(f"šŸ“ Forking {self.repo}...") + subprocess.run(["gh", "repo", "fork", self.repo, "--clone=true"], check=True) + + def submit_pr(self, branch: str, title: str, body: str): + """Create a PR to the upstream repository.""" + print(f"šŸš€ Creating Pull Request: {title}") + subprocess.run([ + "gh", "pr", "create", + "--title", title, + "--body", body, + "--head", branch + ], check=True) + + def commit_changes(self, message: str): + """Commit local changes.""" + subprocess.run(["git", "add", "."], check=True) + subprocess.run(["git", "commit", "-m", message], check=True) + subprocess.run(["git", "push", "origin", "HEAD"], check=True) diff --git a/hunter/core/scanner.py b/hunter/core/scanner.py new file mode 100644 index 00000000..4b6ed0d4 --- /dev/null +++ b/hunter/core/scanner.py @@ -0,0 +1,34 @@ +import requests +import json +from typing import List, Dict + +class BountyScanner: + """ + Scans GitHub repositories for open bounties. + """ + def __init__(self, token: str): + self.headers = {"Authorization": f"token {token}", "Accept": "application/vnd.github.v3+json"} + self.base_url = "https://api.github.com" + + def find_bounties(self, repo: str) -> List[Dict]: + """Find open issues with the 'bounty' label.""" + url = f"{self.base_url}/repos/{repo}/issues" + params = {"labels": "bounty", "state": "open"} + r = requests.get(url, headers=self.headers, params=params) + r.raise_for_status() + return r.json() + + def evaluate_difficulty(self, issue: Dict) -> int: + """ + Heuristic evaluation of issue difficulty (1-10). + Matches keywords against agent capabilities. + """ + text = (issue.get('title', '') + issue.get('body', '')).lower() + score = 5 # Default + + # Capability match + if "python" in text: score -= 2 + if "sdk" in text: score -= 1 + if "vintage" in text or "hardware" in text: score += 4 # Hard for pure AI + + return max(1, min(10, score)) diff --git a/hunter/main.py b/hunter/main.py new file mode 100644 index 00000000..a1a15c8c --- /dev/null +++ b/hunter/main.py @@ -0,0 +1,25 @@ +from core.scanner import BountyScanner +from core.executor import BountyExecutor +import os + +def run_hunter(): + GITHUB_TOKEN = os.getenv("GITHUB_TOKEN") + TARGET_REPO = "Scottcjn/rustchain-bounties" + + print("šŸ¦… Operative Reon: Hunter Framework Engaged.") + + scanner = BountyScanner(GITHUB_TOKEN) + executor = BountyExecutor(TARGET_REPO, GITHUB_TOKEN) + + # 1. Scan + bounties = scanner.find_bounties(TARGET_REPO) + print(f"šŸ” Found {len(bounties)} open bounties.") + + for b in bounties: + diff = scanner.evaluate_difficulty(b) + print(f" - [{b['number']}] {b['title']} (Difficulty: {diff}/10)") + + print("\nāœ… Framework initialized. Ready for autonomous task assignment.") + +if __name__ == "__main__": + run_hunter() diff --git a/hunter/requirements.txt b/hunter/requirements.txt new file mode 100644 index 00000000..e69de29b diff --git a/sdk/README.md b/sdk/README.md new file mode 100644 index 00000000..3a1d4440 --- /dev/null +++ b/sdk/README.md @@ -0,0 +1,43 @@ +# RustChain Python SDK + +Official Python client for the [RustChain](https://github.com/Scottcjn/Rustchain) Proof-of-Antiquity network. + +## Installation + +```bash +pip install rustchain-sdk +``` + +## Quick Start + +```python +from rustchain_sdk import RustChainClient + +# Connect to the live node +client = RustChainClient("https://50.28.86.131", verify_ssl=False) + +# Check health +status = client.health() +print(f"Node Version: {status.version}") + +# Get miner info +miners = client.miners() +for m in miners: + print(f"Miner: {m.miner} | Multiplier: {m.antiquity_multiplier}") + +# Check balance +balance = client.balance("your_miner_id") +print(f"Balance: {balance.amount_rtc} RTC") +``` + +## Features + +- **Node Health**: Check version and uptime. +- **Epoch Info**: Track slots and reward pots. +- **Miner List**: Discover active hardware on the network. +- **Wallet Management**: Query balances and perform signed transfers. +- **Attestation**: Submit hardware fingerprints for enrollment. + +## License + +MIT diff --git a/sdk/pyproject.toml b/sdk/pyproject.toml new file mode 100644 index 00000000..d041b64b --- /dev/null +++ b/sdk/pyproject.toml @@ -0,0 +1,21 @@ +[build-system] +requires = ["setuptools>=42", "wheel"] +build-backend = "setuptools.build_meta" + +[project] +name = "rustchain-sdk" +version = "0.1.0" +description = "Official Python SDK for the RustChain Proof-of-Antiquity network." +readme = "README.md" +requires-python = ">=3.8" +dependencies = [ + "requests>=2.25.0", +] +authors = [ + {name = "nighthawks", email = "night@example.com"}, +] +license = {text = "MIT"} + +[project.urls] +Homepage = "https://github.com/nighthawks0/rustchain-sdk" +Repository = "https://github.com/Scottcjn/Rustchain" diff --git a/sdk/rustchain_sdk/__init__.py b/sdk/rustchain_sdk/__init__.py new file mode 100644 index 00000000..2e44f2c9 --- /dev/null +++ b/sdk/rustchain_sdk/__init__.py @@ -0,0 +1,5 @@ +from .client import RustChainClient +from .models import HealthStatus, EpochInfo, MinerInfo, Balance, TransferResponse, AttestationResponse +from .exceptions import RustChainError, APIError, AuthenticationError, InsufficientBalanceError, VMDetectedError + +__version__ = "0.1.0" diff --git a/sdk/rustchain_sdk/client.py b/sdk/rustchain_sdk/client.py new file mode 100644 index 00000000..f343b96c --- /dev/null +++ b/sdk/rustchain_sdk/client.py @@ -0,0 +1,91 @@ +import requests +from typing import List, Optional, Dict, Any +from .models import HealthStatus, EpochInfo, MinerInfo, Balance, TransferResponse, AttestationResponse +from .exceptions import APIError, AuthenticationError, InsufficientBalanceError, VMDetectedError + +class RustChainClient: + """ + Client for interacting with the RustChain node API. + """ + def __init__(self, base_url: str = "https://50.28.86.131", verify_ssl: bool = False): + self.base_url = base_url.rstrip('/') + self.verify_ssl = verify_ssl + self.session = requests.Session() + + # Suppress insecure request warnings if verify_ssl is False + if not verify_ssl: + import urllib3 + urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) + + def _request(self, method: str, endpoint: str, **kwargs) -> Dict[str, Any]: + url = f"{self.base_url}{endpoint}" + try: + response = self.session.request(method, url, verify=self.verify_ssl, **kwargs) + response.raise_for_status() + return response.json() + except requests.exceptions.HTTPError as e: + try: + error_data = response.json() + error_code = error_data.get('error') + detail = error_data.get('detail') or error_data.get('check_failed') + + if error_code == 'VM_DETECTED': + raise VMDetectedError(f"Attestation failed: {detail}", code=error_code, detail=detail) + elif error_code == 'INVALID_SIGNATURE': + raise AuthenticationError("Invalid signature provided") + elif error_code == 'INSUFFICIENT_BALANCE': + raise InsufficientBalanceError("Insufficient RTC balance for transfer") + + raise APIError(f"API Error: {error_code or e}", code=error_code, detail=detail) + except (ValueError, AttributeError): + raise APIError(f"HTTP Error: {e}") + except Exception as e: + raise APIError(f"Request failed: {e}") + + def health(self) -> HealthStatus: + """Check node health and version.""" + data = self._request("GET", "/health") + return HealthStatus(**data) + + def epoch(self) -> EpochInfo: + """Get current epoch details.""" + data = self._request("GET", "/epoch") + return EpochInfo(**data) + + def miners(self) -> List[MinerInfo]: + """List all active/enrolled miners.""" + data = self._request("GET", "/api/miners") + return [MinerInfo(**item) for item in data] + + def balance(self, miner_id: str) -> Balance: + """Check RTC balance for a miner.""" + params = {"miner_id": miner_id} + data = self._request("GET", "/wallet/balance", params=params) + return Balance(**data) + + def transfer(self, from_id: str, to_id: str, amount_i64: int, nonce: int, signature: str) -> TransferResponse: + """ + Transfer RTC to another wallet. + Requires an Ed25519 signature of the transfer payload. + """ + payload = { + "from": from_id, + "to": to_id, + "amount_i64": amount_i64, + "nonce": nonce, + "signature": signature + } + data = self._request("POST", "/wallet/transfer/signed", json=payload) + return TransferResponse(**data) + + def submit_attestation(self, miner_id: str, fingerprint: Dict[str, Any], signature: str) -> AttestationResponse: + """ + Submit hardware fingerprint for epoch enrollment. + """ + payload = { + "miner_id": miner_id, + "fingerprint": fingerprint, + "signature": signature + } + data = self._request("POST", "/attest/submit", json=payload) + return AttestationResponse(**data) diff --git a/sdk/rustchain_sdk/exceptions.py b/sdk/rustchain_sdk/exceptions.py new file mode 100644 index 00000000..c7a1d49b --- /dev/null +++ b/sdk/rustchain_sdk/exceptions.py @@ -0,0 +1,22 @@ +class RustChainError(Exception): + """Base error for RustChain SDK""" + pass + +class APIError(RustChainError): + """Raised when the API returns an error""" + def __init__(self, message, code=None, detail=None): + super().__init__(message) + self.code = code + self.detail = detail + +class AuthenticationError(RustChainError): + """Raised when signature verification fails""" + pass + +class InsufficientBalanceError(RustChainError): + """Raised when wallet has insufficient funds""" + pass + +class VMDetectedError(RustChainError): + """Raised when attestation fails due to VM detection""" + pass diff --git a/sdk/rustchain_sdk/models.py b/sdk/rustchain_sdk/models.py new file mode 100644 index 00000000..dc9d85b3 --- /dev/null +++ b/sdk/rustchain_sdk/models.py @@ -0,0 +1,52 @@ +from dataclasses import dataclass +from typing import List, Optional + +@dataclass +class HealthStatus: + ok: bool + version: str + uptime_s: int + db_rw: bool + backup_age_hours: float + tip_age_slots: int + +@dataclass +class EpochInfo: + epoch: int + slot: int + blocks_per_epoch: int + epoch_pot: float + enrolled_miners: int + +@dataclass +class MinerInfo: + miner: str + device_family: str + device_arch: str + hardware_type: str + antiquity_multiplier: float + entropy_score: float + last_attest: int + +@dataclass +class Balance: + miner_id: str + amount_rtc: float + amount_i64: int + +@dataclass +class TransferResponse: + success: bool + tx_hash: Optional[str] = None + new_balance: Optional[int] = None + error: Optional[str] = None + +@dataclass +class AttestationResponse: + success: bool + enrolled: bool = False + epoch: Optional[int] = None + multiplier: Optional[float] = None + next_settlement_slot: Optional[int] = None + error: Optional[str] = None + detail: Optional[str] = None diff --git a/sdk/test_sdk.py b/sdk/test_sdk.py new file mode 100644 index 00000000..988b1f69 --- /dev/null +++ b/sdk/test_sdk.py @@ -0,0 +1,59 @@ +import unittest +from unittest.mock import patch, MagicMock +from rustchain_sdk import RustChainClient, APIError, AuthenticationError +from rustchain_sdk.models import HealthStatus + +class TestRustChainClient(unittest.TestCase): + def setUp(self): + self.client = RustChainClient(base_url="https://mock-node", verify_ssl=False) + + @patch('requests.Session.request') + def test_health_success(self, mock_request): + mock_response = MagicMock() + mock_response.status_code = 200 + mock_response.json.return_value = { + "backup_age_hours": 6.75, + "db_rw": True, + "ok": True, + "tip_age_slots": 0, + "uptime_s": 18728, + "version": "2.2.1-rip200" + } + mock_request.return_value = mock_response + + status = self.client.health() + self.assertTrue(status.ok) + self.assertEqual(status.version, "2.2.1-rip200") + self.assertEqual(status.uptime_s, 18728) + + @patch('requests.Session.request') + def test_balance_success(self, mock_request): + mock_response = MagicMock() + mock_response.status_code = 200 + mock_response.json.return_value = { + "amount_i64": 118357193, + "amount_rtc": 118.357193, + "miner_id": "test_miner" + } + mock_request.return_value = mock_response + + balance = self.client.balance("test_miner") + self.assertEqual(balance.amount_rtc, 118.357193) + self.assertEqual(balance.miner_id, "test_miner") + + @patch('requests.Session.request') + def test_invalid_signature_error(self, mock_request): + mock_response = MagicMock() + mock_response.status_code = 400 + mock_response.json.return_value = {"success": False, "error": "INVALID_SIGNATURE"} + mock_request.return_value = mock_response + + # We need to trigger raise_for_status for the catch block to work in _request + from requests.exceptions import HTTPError + mock_response.raise_for_status.side_effect = HTTPError(response=mock_response) + + with self.assertRaises(AuthenticationError): + self.client.balance("test_miner") + +if __name__ == '__main__': + unittest.main()