diff --git a/.claude/skills/rtc-balance/SKILL.md b/.claude/skills/rtc-balance/SKILL.md
new file mode 100644
index 000000000..1d8e1252d
--- /dev/null
+++ b/.claude/skills/rtc-balance/SKILL.md
@@ -0,0 +1,98 @@
+---
+name: rtc-balance
+description: Check RustChain wallet balance, epoch info, and network status via the public RPC
+author: Emanon4
+tags: [rustchain, cryptocurrency, wallet, balance-checker]
+---
+
+# /rtc-balance — RustChain Wallet Balance Checker
+
+## Installation
+
+Copy this folder (`.claude/skills/rtc-balance/`) into your project's `.claude/skills/` directory. Claude Code automatically loads skills from that path.
+
+```
+mkdir -p .claude/skills
+cp -r path/to/rtc-balance .claude/skills/rtc-balance
+```
+
+Requires `curl` (pre-installed on macOS and most Linux distributions). No additional dependencies.
+
+## Usage
+
+```
+/rtc-balance
+```
+
+`` is your RustChain wallet address or miner ID.
+
+## Procedure (executed on invocation)
+
+When invoked with ``, perform these steps in order:
+
+### Step 1 — Fetch wallet balance
+
+```
+curl -sS --max-time 8 "https://rustchain.org/wallet/balance?miner_id="
+```
+
+Parse the JSON response:
+- If the response contains `"amount_rtc"`: extract that float value as the balance
+- If the response is empty or `curl` exits non-zero: the node is unreachable, skip to Step 3a
+
+### Step 2 — Fetch network status
+
+```
+curl -sS --max-time 8 "https://rustchain.org/epoch"
+```
+
+Parse the JSON response for these fields:
+- `epoch`: int — current epoch number
+- `slot`: int — current slot within the epoch
+- `enrolled_miners`: int — number of active miners
+
+### Step 3a — Node offline path
+
+If Step 1 or Step 2 failed (curl timeout or non-JSON response):
+
+```
+Error: Node unreachable
+Check your network connection or try again later.
+```
+
+### Step 3b — Success path
+
+Format and print the output:
+
+```
+Wallet:
+Balance: RTC ($ USD)
+Epoch: | Slot: | Miners online:
+```
+
+Where:
+- `amount_rtc` = float from Step 1 (default `0.0` if wallet not found)
+- `usd` = `amount_rtc * 0.10` (reference rate: 1 RTC = $0.10 USD)
+- `epoch`, `slot`, `enrolled_miners` = integers from Step 2
+
+### Error handling
+
+| Scenario | Behavior |
+|----------|----------|
+| Wallet not found (API returns `{"amount_rtc": 0.0}`) | Show 0.00 RTC, continue |
+| Node unreachable | Stop, print "Node unreachable" message |
+| Empty wallet name | Print "Usage: /rtc-balance " |
+
+## Example
+
+```
+/rtc-balance Emanon4
+```
+
+Expected output:
+
+```
+Wallet: Emanon4
+Balance: 0.00 RTC ($0.00 USD)
+Epoch: 162 | Slot: 23411 | Miners online: 14
+```
diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
index 60677828a..5ff9bdbd2 100644
--- a/.github/FUNDING.yml
+++ b/.github/FUNDING.yml
@@ -1,3 +1,2 @@
github: [Scottcjn]
ko_fi: elyanlabs
-custom: ["https://rustchain.elyanlabs.ai/donate"]
diff --git a/.github/actions/bcos-action/README.md b/.github/actions/bcos-action/README.md
index 9718c63ac..fce310e99 100644
--- a/.github/actions/bcos-action/README.md
+++ b/.github/actions/bcos-action/README.md
@@ -31,7 +31,7 @@ jobs:
- uses: actions/checkout@v4
- name: Run BCOS Scan
- uses: Scottcjn/bcos-action@v1
+ uses: Scottcjn/Rustchain/.github/actions/bcos-action@main
id: bcos
with:
tier: L1
@@ -59,7 +59,7 @@ jobs:
- uses: actions/checkout@v4
- name: Run BCOS L2 Scan
- uses: Scottcjn/bcos-action@v1
+ uses: Scottcjn/Rustchain/.github/actions/bcos-action@main
id: bcos
with:
tier: L2
@@ -78,7 +78,7 @@ jobs:
```yaml
- name: Scan Subdirectory
- uses: Scottcjn/bcos-action@v1
+ uses: Scottcjn/Rustchain/.github/actions/bcos-action@main
with:
repo-path: ./packages/core
tier: L1
@@ -191,5 +191,5 @@ MIT License - see [LICENSE](LICENSE) file.
## Support
- Documentation: https://rustchain.org/bcos/
-- Issues: https://github.com/Scottcjn/bcos-action/issues
+- Issues: https://github.com/Scottcjn/Rustchain/issues
- Spec: https://github.com/Scottcjn/Rustchain/blob/main/docs/BEACON_CERTIFIED_OPEN_SOURCE.md
diff --git a/.github/actions/bcos-action/anchor.py b/.github/actions/bcos-action/anchor.py
index ac0ee99a1..6ebed738b 100644
--- a/.github/actions/bcos-action/anchor.py
+++ b/.github/actions/bcos-action/anchor.py
@@ -6,13 +6,20 @@
"""
import json
+import logging
import os
from urllib.request import Request, urlopen
from urllib.error import HTTPError
+logger = logging.getLogger("bcos-action")
+
def main():
"""Anchor the BCOS attestation to RustChain."""
+ logging.basicConfig(
+ level=logging.INFO,
+ format="%(levelname)s %(name)s: %(message)s"
+ )
# Get inputs from environment
node_url = os.environ.get("INPUT_NODE_URL", "https://rustchain.org")
cert_id = os.environ.get("CERT_ID", "")
@@ -22,7 +29,7 @@ def main():
merged_commit = os.environ.get("MERGED_COMMIT", "")
if not all([cert_id, commitment, repo, pr_number, merged_commit]):
- print("⚠️ Missing required environment variables. Skipping anchor.")
+ logger.warning("Missing required environment variables. Skipping anchor.")
return
# Build attestation payload
@@ -51,16 +58,16 @@ def main():
try:
response = urlopen(req)
result = json.loads(response.read().decode('utf-8'))
- print(f"✅ Attestation anchored successfully!")
- print(f"Transaction: {result.get('tx_hash', 'N/A')}")
- print(f"Block: {result.get('block_number', 'N/A')}")
+ logger.info("Attestation anchored successfully!")
+ logger.info("Transaction: %s", result.get("tx_hash", "N/A"))
+ logger.info("Block: %s", result.get("block_number", "N/A"))
except HTTPError as e:
error_body = e.read().decode() if e.fp else ""
- print(f"⚠️ Failed to anchor: {e.code}")
+ logger.error("Failed to anchor: %s", e.code)
if error_body:
- print(f"Response: {error_body}")
+ logger.debug("Response: %s", error_body)
except Exception as e:
- print(f"⚠️ Anchor skipped (node may be unavailable): {e}")
+ logger.warning("Anchor skipped (node may be unavailable): %s", e)
if __name__ == "__main__":
diff --git a/.github/actions/bcos-action/main.py b/.github/actions/bcos-action/main.py
index 980e42475..2cbef9992 100644
--- a/.github/actions/bcos-action/main.py
+++ b/.github/actions/bcos-action/main.py
@@ -234,7 +234,7 @@ def post_github_comment(repo: str, pr_number: str, report: dict, token: str) ->
---
-*Generated by [BCOS v2 Action](https://github.com/Scottcjn/bcos-action)*
+*Generated by [BCOS v2 Action](https://github.com/Scottcjn/Rustchain/tree/main/.github/actions/bcos-action)*
"""
api_url = f"https://api.github.com/repos/{repo}/issues/{pr_number}/comments"
diff --git a/.github/actions/bcos-action/post_comment.py b/.github/actions/bcos-action/post_comment.py
index 9471868fe..f15316630 100644
--- a/.github/actions/bcos-action/post_comment.py
+++ b/.github/actions/bcos-action/post_comment.py
@@ -95,7 +95,7 @@ def main():
---
-*Generated by [BCOS v2 Action](https://github.com/Scottcjn/bcos-action)*
+*Generated by [BCOS v2 Action](https://github.com/Scottcjn/Rustchain/tree/main/.github/actions/bcos-action)*
"""
# Post to GitHub API
diff --git a/.github/actions/bcos-action/test_action.py b/.github/actions/bcos-action/test_action.py
index 64c43094a..9a61e8d0c 100644
--- a/.github/actions/bcos-action/test_action.py
+++ b/.github/actions/bcos-action/test_action.py
@@ -5,6 +5,7 @@
Tests the main.py action script functionality.
"""
+import base64
import json
import os
import sys
@@ -22,11 +23,16 @@
anchor_to_rustchain,
set_github_output
)
+import post_comment as post_comment_script
+
+
+BCOS_ACTION_URL = "https://github.com/Scottcjn/Rustchain/tree/main/.github/actions/bcos-action"
+DEAD_BCOS_ACTION_URL = "https://github.com/Scottcjn/bcos-action"
class TestMinimalBCOSScanner(unittest.TestCase):
"""Test the minimal BCOS scanner."""
-
+
def setUp(self):
"""Create a temporary test repository."""
self.temp_dir = tempfile.TemporaryDirectory()
@@ -145,15 +151,9 @@ def test_cert_id_format(self):
class TestGitHubComment(unittest.TestCase):
"""Test GitHub comment posting."""
-
- @patch('main.urlopen')
- def test_post_comment_success(self, mock_urlopen):
- """Test successful comment posting."""
- mock_response = MagicMock()
- mock_response.status = 201
- mock_urlopen.return_value = mock_response
-
- report = {
+
+ def _sample_report(self):
+ return {
"trust_score": 75,
"tier_met": True,
"cert_id": "BCOS-test123",
@@ -172,17 +172,64 @@ def test_post_comment_success(self, mock_urlopen):
"review_attestation": 10
}
}
+
+ @patch('main.urlopen')
+ def test_post_comment_success(self, mock_urlopen):
+ """Test successful comment posting."""
+ mock_response = MagicMock()
+ mock_response.status = 201
+ mock_urlopen.return_value = mock_response
result = post_github_comment(
repo="test/repo",
pr_number="42",
- report=report,
+ report=self._sample_report(),
token="fake-token"
)
self.assertTrue(result)
mock_urlopen.assert_called_once()
+ @patch('main.urlopen')
+ def test_post_comment_links_to_in_repo_action_source(self, mock_urlopen):
+ """Generated comments should not link to the unpublished action repo."""
+ mock_response = MagicMock()
+ mock_response.status = 201
+ mock_urlopen.return_value = mock_response
+
+ post_github_comment(
+ repo="test/repo",
+ pr_number="42",
+ report=self._sample_report(),
+ token="fake-token"
+ )
+
+ request = mock_urlopen.call_args.args[0]
+ body = json.loads(request.data.decode("utf-8"))["body"]
+ self.assertIn(BCOS_ACTION_URL, body)
+ self.assertNotIn(DEAD_BCOS_ACTION_URL, body)
+
+ @patch('post_comment.urlopen')
+ def test_standalone_post_comment_links_to_in_repo_action_source(self, mock_urlopen):
+ """The standalone comment script should use the same live action link."""
+ mock_response = MagicMock()
+ mock_response.status = 201
+ mock_urlopen.return_value = mock_response
+ report_json = json.dumps(self._sample_report()).encode("utf-8")
+
+ with patch.dict(os.environ, {
+ "GITHUB_TOKEN": "fake-token",
+ "REPO": "test/repo",
+ "PR_NUMBER": "42",
+ "REPORT_JSON": base64.b64encode(report_json).decode("ascii"),
+ }):
+ post_comment_script.main()
+
+ request = mock_urlopen.call_args.args[0]
+ body = json.loads(request.data.decode("utf-8"))["body"]
+ self.assertIn(BCOS_ACTION_URL, body)
+ self.assertNotIn(DEAD_BCOS_ACTION_URL, body)
+
class TestRustChainAnchoring(unittest.TestCase):
"""Test RustChain anchoring."""
diff --git a/.github/actions/rtc-auto-bounty/action.yml b/.github/actions/rtc-auto-bounty/action.yml
index 48a1a254c..6871a4fb0 100644
--- a/.github/actions/rtc-auto-bounty/action.yml
+++ b/.github/actions/rtc-auto-bounty/action.yml
@@ -19,6 +19,10 @@ inputs:
description: 'RustChain VPS host (IP or hostname)'
required: false
default: ''
+ rtc-api-url:
+ description: 'Full RustChain transfer API URL, for example https://rustchain.org/wallet/transfer'
+ required: false
+ default: ''
rtc-admin-key:
description: 'Admin key for the /wallet/transfer endpoint'
required: false
@@ -76,6 +80,7 @@ runs:
shell: bash
env:
INPUT_RTC_AMOUNT: ${{ inputs.rtc-amount }}
+ INPUT_RTC_API_URL: ${{ inputs.rtc-api-url }}
INPUT_RTC_VPS_HOST: ${{ inputs.rtc-vps-host }}
INPUT_RTC_ADMIN_KEY: ${{ inputs.rtc-admin-key }}
INPUT_FROM_WALLET: ${{ inputs.from-wallet }}
diff --git a/.github/actions/rtc-auto-bounty/award_rtc.py b/.github/actions/rtc-auto-bounty/award_rtc.py
index 220eadf83..b23aa7cd0 100644
--- a/.github/actions/rtc-auto-bounty/award_rtc.py
+++ b/.github/actions/rtc-auto-bounty/award_rtc.py
@@ -31,7 +31,9 @@
from __future__ import annotations
+import hashlib
import json
+import math
import os
import re
import sys
@@ -40,6 +42,7 @@
from typing import Any, Dict, Optional, Tuple
from urllib.request import Request, urlopen
from urllib.error import HTTPError, URLError
+from urllib.parse import urlparse
# ---------------------------------------------------------------------------
# Constants
@@ -70,6 +73,41 @@
# Marker to prevent duplicate awards.
_AWARD_MARKER = "RTC-AutoBounty-Awarded"
+# --- Recipient validation (security) ---------------------------------------
+# A resolved recipient must be EITHER a canonical RTC address (RTC + 40 hex)
+# OR a conservative wallet-name / GitHub-username grammar. Anything else
+# (markdown junk, zero-width / non-ASCII confusables, multi-token garbage)
+# is rejected so a malformed or spoofed directive cannot misroute funds.
+_RTC_ADDRESS_RE = re.compile(r"^RTC[0-9A-Fa-f]{40}$")
+_WALLET_NAME_RE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._-]{1,63}$")
+
+# Platform / treasury wallets must never be auto-selected as a *recipient*
+# from PR-controlled text (prevents misrouting and self-dealing loops).
+_BLOCKED_RECIPIENTS = frozenset({
+ "founder_community",
+ "founder_dev_fund",
+ "founder_team_bounty",
+ "founder_founders",
+ "community",
+ "dev_wallet",
+ "foundation",
+ "treasury",
+})
+
+_ENDPOINT_UNREACHABLE_PATTERNS = (
+ "connection failed:",
+ "connection refused",
+ "connection reset",
+ "connection aborted",
+ "timed out",
+ "timeout",
+ "temporary failure in name resolution",
+ "name or service not known",
+ "no route to host",
+ "network is unreachable",
+ "host is unreachable",
+)
+
# ---------------------------------------------------------------------------
# Configuration helpers
# ---------------------------------------------------------------------------
@@ -79,15 +117,26 @@ def _env(name: str, default: str = "") -> str:
return os.environ.get(name, default)
+def _env_stripped(name: str, default: str = "") -> str:
+ return _env(name, default).strip()
+
+
def _env_bool(name: str, default: bool = False) -> bool:
- return _env(name, str(default)).lower() in ("true", "1", "yes")
+ return _env_stripped(name, str(default)).lower() in ("true", "1", "yes")
def _env_float(name: str, default: float = 0.0) -> float:
+ raw_value = _env_stripped(name, "")
+ if raw_value == "":
+ return default
try:
- return float(_env(name, str(default)))
+ return float(raw_value)
except (TypeError, ValueError):
- return default
+ return math.nan
+
+
+def _is_finite_amount(value: float) -> bool:
+ return math.isfinite(value)
class Config:
@@ -95,21 +144,25 @@ class Config:
def __init__(self) -> None:
self.rtc_amount: float = _env_float("INPUT_RTC_AMOUNT", 50.0)
- self.vps_host: str = _env("INPUT_RTC_VPS_HOST")
- self.admin_key: str = _env("INPUT_RTC_ADMIN_KEY")
- self.from_wallet: str = _env("INPUT_FROM_WALLET", "founder_community")
+ self.rtc_api_url: str = _env_stripped("INPUT_RTC_API_URL")
+ self.vps_host: str = _env_stripped("INPUT_RTC_VPS_HOST")
+ self.admin_key: str = _env_stripped("INPUT_RTC_ADMIN_KEY")
+ self.from_wallet: str = _env_stripped("INPUT_FROM_WALLET", "founder_community")
self.dry_run: bool = _env_bool("INPUT_DRY_RUN")
self.post_comment: bool = _env_bool("INPUT_POST_COMMENT", True)
- self.github_token: str = _env("INPUT_GITHUB_TOKEN", _env("GITHUB_TOKEN"))
- self.repo_path: str = _env("INPUT_REPO_PATH", ".")
+ self.github_token: str = _env_stripped(
+ "INPUT_GITHUB_TOKEN",
+ _env_stripped("GITHUB_TOKEN"),
+ )
+ self.repo_path: str = _env_stripped("INPUT_REPO_PATH", ".")
self.max_amount: float = _env_float("INPUT_MAX_AMOUNT", 10000.0)
- self.repo: str = _env("GITHUB_REPOSITORY")
- self.pr_number: str = _env("PR_NUMBER")
- self.pr_author: str = _env("PR_AUTHOR", _env("PR_AUTHOR"))
- self.pr_merged: str = _env("PR_MERGED")
+ self.repo: str = _env_stripped("GITHUB_REPOSITORY")
+ self.pr_number: str = _env_stripped("PR_NUMBER")
+ self.pr_author: str = _env_stripped("PR_AUTHOR", _env_stripped("PR_AUTHOR"))
+ self.pr_merged: str = _env_stripped("PR_MERGED")
self.pr_body: str = _env("PR_BODY", "")
- self.pr_head_sha: str = _env("PR_HEAD_SHA", "")
- self.pr_title: str = _env("PR_TITLE", "")
+ self.pr_head_sha: str = _env_stripped("PR_HEAD_SHA", "")
+ self.pr_title: str = _env_stripped("PR_TITLE", "")
def validate(self) -> Optional[str]:
"""Return an error string if required config is missing, else None."""
@@ -119,12 +172,18 @@ def validate(self) -> Optional[str]:
return "GITHUB_REPOSITORY is not set"
if not self.pr_number:
return "PR_NUMBER is not set"
- if not self.dry_run and not self.vps_host:
- return "INPUT_RTC_VPS_HOST is required (unless dry-run is enabled)"
+ if not self.dry_run and not (self.rtc_api_url or self.vps_host):
+ return "INPUT_RTC_API_URL or INPUT_RTC_VPS_HOST is required (unless dry-run is enabled)"
if not self.dry_run and not self.admin_key:
return "INPUT_RTC_ADMIN_KEY is required (unless dry-run is enabled)"
+ if not _is_finite_amount(self.rtc_amount):
+ return f"rtc-amount must be finite, got {self.rtc_amount}"
+ if not _is_finite_amount(self.max_amount):
+ return f"max-amount must be finite, got {self.max_amount}"
if self.rtc_amount <= 0:
return f"rtc-amount must be positive, got {self.rtc_amount}"
+ if self.max_amount <= 0:
+ return f"max-amount must be positive, got {self.max_amount}"
return None
@@ -156,12 +215,11 @@ def resolve_wallet_from_file(repo_path: str) -> Optional[str]:
def resolve_wallet(pr_body: str, repo_path: str) -> Optional[str]:
"""
- Resolve the recipient wallet.
+ Resolve the explicitly declared recipient wallet.
Priority:
1. ``wallet:`` directive in the PR body
2. ``.rtc-wallet`` file at the repository root
- 3. Fallback to the PR author's GitHub username
"""
wallet = resolve_wallet_from_pr_body(pr_body)
if wallet:
@@ -172,6 +230,59 @@ def resolve_wallet(pr_body: str, repo_path: str) -> Optional[str]:
return None
+def distinct_wallet_directives(pr_body: str) -> list:
+ """Return the distinct ``wallet:`` directive values found in the PR body.
+
+ Used to fail closed when a body contains multiple *conflicting* recipient
+ directives (an attacker appending a second directive should not silently
+ win or lose — it requires manual review).
+ """
+ seen = []
+ for raw in _WALLET_RE.findall(pr_body or ""):
+ value = raw.strip().rstrip(",")
+ if value and value not in seen:
+ seen.append(value)
+ return seen
+
+
+def validate_recipient(wallet: Optional[str]) -> Tuple[bool, Optional[str]]:
+ """Validate a resolved recipient before it is used in a transfer.
+
+ Returns ``(ok, reason)``. ``reason`` is a short machine-readable skip code
+ when ``ok`` is False. A recipient is accepted only when it is a canonical
+ RTC address or a conservative wallet-name/username, and is not a
+ platform/treasury wallet.
+ """
+ if not wallet:
+ return False, "recipient_wallet_missing"
+ candidate = wallet.strip()
+ if candidate != wallet:
+ # Trailing/leading whitespace already stripped by the parser; a
+ # mismatch here means embedded control/space chars — reject.
+ return False, "recipient_wallet_whitespace"
+ try:
+ candidate.encode("ascii")
+ except UnicodeEncodeError:
+ return False, "recipient_wallet_non_ascii"
+ if _RTC_ADDRESS_RE.match(candidate):
+ return True, None
+ if _WALLET_NAME_RE.match(candidate):
+ if candidate.lower() in _BLOCKED_RECIPIENTS or candidate.lower().startswith("founder_"):
+ return False, "recipient_platform_wallet_blocked"
+ return True, None
+ return False, "recipient_wallet_invalid_format"
+
+
+def compute_idempotency_key(repo: str, pr_number: str, wallet: str, amount: float) -> str:
+ """Deterministic idempotency key so workflow re-runs collapse to one payout.
+
+ The node's /wallet/transfer endpoint returns the existing pending row for a
+ repeated key instead of inserting a new one, making retries safe.
+ """
+ basis = f"{repo}:{pr_number}:{wallet}:{amount}"
+ return "award-" + hashlib.sha256(basis.encode("utf-8")).hexdigest()
+
+
# ---------------------------------------------------------------------------
# GitHub API helpers
# ---------------------------------------------------------------------------
@@ -235,40 +346,66 @@ def post_pr_comment(repo: str, pr_number: str, body: str, token: str) -> bool:
def check_already_awarded(comments: list) -> bool:
- """Check if any existing comment contains the award marker."""
+ """Check if any existing comment contains a successful award marker."""
for c in comments:
- if _AWARD_MARKER in (c.get("body") or ""):
- return True
+ body = c.get("body") or ""
+ if _AWARD_MARKER not in body:
+ continue
+
+ marker_tail = body[body.find(_AWARD_MARKER):].lower()
+ marker_end = marker_tail.find("-->")
+ if marker_end != -1:
+ marker_tail = marker_tail[:marker_end]
+
+ if (
+ "(dry-run)" in marker_tail
+ or ":failed" in marker_tail
+ ):
+ continue
+ return True
return False
+def is_endpoint_unreachable_error(error_msg: str) -> bool:
+ """Return True when transfer failed because the RustChain endpoint was unreachable."""
+ normalized = (error_msg or "").lower()
+ return any(pattern in normalized for pattern in _ENDPOINT_UNREACHABLE_PATTERNS)
+
+
# ---------------------------------------------------------------------------
# RustChain transfer API
# ---------------------------------------------------------------------------
def transfer_rtc(
- vps_host: str,
+ transfer_url: str,
admin_key: str,
from_wallet: str,
to_wallet: str,
amount: float,
memo: str,
+ idempotency_key: Optional[str] = None,
) -> Tuple[bool, Dict[str, Any]]:
"""
Call the RustChain ``POST /wallet/transfer`` admin endpoint.
Returns ``(success, response_body_dict)``.
"""
- url = f"http://{vps_host}:{VPS_PORT}/wallet/transfer"
+ transfer_url = build_transfer_url(transfer_url)
+ admin_key = admin_key.strip()
+ from_wallet = from_wallet.strip()
+ to_wallet = to_wallet.strip()
+
payload = {
"from_miner": from_wallet,
"to_miner": to_wallet,
"amount_rtc": amount,
"memo": memo,
}
+ if idempotency_key:
+ payload["idempotency_key"] = idempotency_key
req = Request(
- url,
+ transfer_url,
data=json.dumps(payload).encode("utf-8"),
headers={
"Content-Type": "application/json",
@@ -278,7 +415,13 @@ def transfer_rtc(
)
try:
resp = urlopen(req, timeout=30)
- result = json.loads(resp.read().decode())
+ body = resp.read().decode(errors="replace")
+ try:
+ result = json.loads(body)
+ except (json.JSONDecodeError, ValueError):
+ return False, {"error": "Invalid JSON response from transfer endpoint"}
+ if not isinstance(result, dict):
+ return False, {"error": "Transfer endpoint response must be a JSON object"}
return result.get("ok", False), result
except HTTPError as e:
body = e.read().decode(errors="replace")
@@ -291,6 +434,22 @@ def transfer_rtc(
return False, {"error": f"Connection failed: {e.reason}"}
+def build_transfer_url(value: str) -> str:
+ """
+ Build the wallet transfer URL.
+
+ Full URLs are used as-is, except a bare origin gets ``/wallet/transfer``
+ appended. Bare hosts keep the legacy ``http://host:8099`` behavior.
+ """
+ value = value.strip().rstrip("/")
+ parsed = urlparse(value)
+ if parsed.scheme and parsed.netloc:
+ if parsed.path and parsed.path != "/":
+ return value
+ return f"{value}/wallet/transfer"
+ return f"http://{value}:{VPS_PORT}/wallet/transfer"
+
+
# ---------------------------------------------------------------------------
# GitHub Actions output helpers
# ---------------------------------------------------------------------------
@@ -356,10 +515,66 @@ def main() -> int:
# --- Resolve recipient wallet ------------------------------------------
wallet = resolve_wallet(cfg.pr_body, cfg.repo_path)
if not wallet:
- # Fallback: use PR author's GitHub username as the wallet identifier
- wallet = cfg.pr_author
- log_info(f"No wallet found in PR body or .rtc-wallet file; "
- f"falling back to PR author: {wallet}")
+ skip_reason = "recipient_wallet_missing"
+ log_error("No recipient wallet found in PR body or .rtc-wallet file; "
+ "skipping automatic RTC transfer.")
+ if cfg.post_comment:
+ missing_wallet_body = (
+ f"**RTC Auto-Bounty Skipped**\n\n"
+ f"No recipient wallet was found, so no RTC transfer was attempted.\n\n"
+ f"To receive this award, add a line such as "
+ f"`wallet: RTC...` to the PR body or add a `.rtc-wallet` file "
+ f"at the repository root, then rerun the award workflow.\n\n"
+ f""
+ )
+ post_pr_comment(repo, pr_number, missing_wallet_body, cfg.github_token)
+ set_output("awarded", "false")
+ set_output("skip_reason", skip_reason)
+ return 1
+
+ # --- Fail closed on conflicting recipient directives -------------------
+ directives = distinct_wallet_directives(cfg.pr_body)
+ if len(directives) > 1:
+ skip_reason = "recipient_wallet_conflict"
+ log_error(
+ "Multiple conflicting `wallet:` directives in PR body "
+ f"({directives}); refusing to auto-select a recipient."
+ )
+ if cfg.post_comment:
+ conflict_body = (
+ f"**RTC Auto-Bounty Skipped — manual review required**\n\n"
+ f"This PR body declares more than one recipient wallet "
+ f"({', '.join(f'`{d}`' for d in directives)}). To avoid "
+ f"misrouting funds, no automatic transfer was made. A "
+ f"maintainer must confirm the correct recipient.\n\n"
+ f""
+ )
+ post_pr_comment(repo, pr_number, conflict_body, cfg.github_token)
+ set_output("awarded", "false")
+ set_output("skip_reason", skip_reason)
+ return 1
+
+ # --- Validate recipient format / blocklist ----------------------------
+ recipient_ok, recipient_err = validate_recipient(wallet)
+ if not recipient_ok:
+ log_error(
+ f"Resolved recipient `{wallet}` failed validation "
+ f"({recipient_err}); refusing automatic RTC transfer."
+ )
+ if cfg.post_comment:
+ invalid_body = (
+ f"**RTC Auto-Bounty Skipped — invalid recipient**\n\n"
+ f"The resolved recipient `{wallet}` did not pass safety "
+ f"validation (`{recipient_err}`). A recipient must be a "
+ f"canonical `RTC...` address or a simple wallet name, and "
+ f"may not be a platform/treasury wallet. No transfer was "
+ f"made; a maintainer can process this manually.\n\n"
+ f""
+ )
+ post_pr_comment(repo, pr_number, invalid_body, cfg.github_token)
+ set_output("awarded", "false")
+ set_output("skip_reason", recipient_err)
+ return 1
print(f"Recipient wallet: {wallet}")
@@ -369,7 +584,7 @@ def main() -> int:
bounty_match = _BOUNTY_RE.search(cfg.pr_body)
if bounty_match:
override = float(bounty_match.group(1))
- if 0 < override <= cfg.max_amount:
+ if _is_finite_amount(override) and 0 < override <= cfg.max_amount:
amount = override
print(f"Bounty override in PR body: {amount} RTC")
else:
@@ -408,20 +623,22 @@ def main() -> int:
f"| From | `{cfg.from_wallet}` |\n"
f"| Memo | {memo} |\n\n"
f"This is a **dry-run** — no actual transfer was made.\n\n"
- f""
+ f""
)
post_pr_comment(repo, pr_number, dry_body, cfg.github_token)
return 0
# --- Execute transfer --------------------------------------------------
print(f"Initiating transfer: {amount} RTC from {cfg.from_wallet} to {wallet}")
+ idempotency_key = compute_idempotency_key(repo, pr_number, wallet, amount)
ok, result = transfer_rtc(
- cfg.vps_host,
+ cfg.rtc_api_url or cfg.vps_host,
cfg.admin_key,
cfg.from_wallet,
wallet,
amount,
memo,
+ idempotency_key=idempotency_key,
)
tx_hash = result.get("tx_hash", "")
@@ -433,6 +650,31 @@ def main() -> int:
set_output("awarded", "false")
set_output("skip_reason", f"transfer_failed: {error_msg}")
+ if is_endpoint_unreachable_error(error_msg):
+ if cfg.post_comment:
+ manual_body = (
+ f"**RTC Auto-Bounty Manual Transfer Required**\n\n"
+ f"The merged PR qualifies for an RTC award, but the RustChain "
+ f"transfer endpoint was unreachable when the workflow ran:\n\n"
+ f"```\n{error_msg}\n```\n\n"
+ f"| Field | Value |\n"
+ f"|-------|-------|\n"
+ f"| Amount | **{amount} RTC** |\n"
+ f"| Recipient | `{wallet}` |\n"
+ f"| From | `{cfg.from_wallet}` |\n"
+ f"| Memo | {memo} |\n\n"
+ f"Please rerun the award after the endpoint is healthy or process "
+ f"this transfer manually. This marker intentionally blocks automatic "
+ f"retries to avoid duplicate payouts; remove it only if no manual "
+ f"transfer was completed.\n\n"
+ f""
+ )
+ if not post_pr_comment(repo, pr_number, manual_body, cfg.github_token):
+ log_error("Manual transfer notice could not be posted.")
+ set_output("skip_reason", f"manual_notice_failed: {error_msg}")
+ return 1
+ return 0
+
if cfg.post_comment:
fail_body = (
f"**RTC Auto-Bounty Failed** ❌\n\n"
@@ -440,7 +682,7 @@ def main() -> int:
f"but the transfer was rejected:\n\n"
f"```\n{error_msg}\n```\n\n"
f"Please process this award manually.\n\n"
- f""
+ f""
)
post_pr_comment(repo, pr_number, fail_body, cfg.github_token)
return 1
@@ -476,7 +718,7 @@ def main() -> int:
{confirms_info}
Transfer recorded on RustChain.
-
+
""")
posted = post_pr_comment(repo, pr_number, confirm_body, cfg.github_token)
if not posted:
diff --git a/.github/actions/rtc-auto-bounty/test_award_rtc.py b/.github/actions/rtc-auto-bounty/test_award_rtc.py
index 269820d3c..ff29ceb66 100644
--- a/.github/actions/rtc-auto-bounty/test_award_rtc.py
+++ b/.github/actions/rtc-auto-bounty/test_award_rtc.py
@@ -22,14 +22,22 @@
from award_rtc import (
Config,
+ build_transfer_url,
resolve_wallet,
resolve_wallet_from_pr_body,
resolve_wallet_from_file,
check_already_awarded,
+ is_endpoint_unreachable_error,
set_output,
+ transfer_rtc,
+ validate_recipient,
+ distinct_wallet_directives,
+ compute_idempotency_key,
_AWARD_MARKER,
)
+VALID_RTC = "RTC" + "a1b2c3d4" * 5 # RTC + 40 hex chars
+
# ---------------------------------------------------------------------------
# Wallet resolution tests
@@ -143,6 +151,22 @@ def test_marker_in_last_comment(self):
]
self.assertTrue(check_already_awarded(comments))
+ def test_dry_run_marker_does_not_block_real_award(self):
+ comments = [{"body": f""}]
+ self.assertFalse(check_already_awarded(comments))
+
+ def test_failed_marker_does_not_block_retry(self):
+ comments = [{"body": f""}]
+ self.assertFalse(check_already_awarded(comments))
+
+ def test_manual_required_marker_blocks_automatic_retry_until_human_resets(self):
+ comments = [{"body": f""}]
+ self.assertTrue(check_already_awarded(comments))
+
+ def test_failed_text_outside_marker_does_not_hide_success_marker(self):
+ comments = [{"body": f"failed before marker\n"}]
+ self.assertTrue(check_already_awarded(comments))
+
# ---------------------------------------------------------------------------
# Config tests
@@ -156,6 +180,7 @@ def _cfg(self, **overrides):
"""Create a Config with the given environment variable overrides."""
env = {
"INPUT_RTC_AMOUNT": "50",
+ "INPUT_RTC_API_URL": "",
"INPUT_RTC_VPS_HOST": "1.2.3.4",
"INPUT_RTC_ADMIN_KEY": "test-key-32-chars-long!!",
"INPUT_FROM_WALLET": "founder_community",
@@ -183,6 +208,43 @@ def test_defaults(self):
self.assertFalse(cfg.dry_run)
self.assertTrue(cfg.post_comment)
+ def test_trims_scalar_inputs(self):
+ cfg = self._cfg(
+ INPUT_RTC_AMOUNT=" 50\n",
+ INPUT_RTC_API_URL=" https://rustchain.org/wallet/transfer\n",
+ INPUT_RTC_VPS_HOST=" 1.2.3.4\n",
+ INPUT_RTC_ADMIN_KEY=" test-key-32-chars-long!!\n",
+ INPUT_FROM_WALLET=" founder_community\n",
+ INPUT_DRY_RUN=" true\n",
+ INPUT_POST_COMMENT=" true\n",
+ INPUT_GITHUB_TOKEN=" ghp_test\n",
+ INPUT_REPO_PATH=" .\n",
+ INPUT_MAX_AMOUNT=" 10000\n",
+ GITHUB_REPOSITORY=" test/repo\n",
+ PR_NUMBER=" 42\n",
+ PR_AUTHOR=" alice\n",
+ PR_MERGED=" true\n",
+ PR_HEAD_SHA=" abc123\n",
+ PR_TITLE=" Test PR\n",
+ )
+
+ self.assertEqual(cfg.rtc_amount, 50.0)
+ self.assertEqual(cfg.rtc_api_url, "https://rustchain.org/wallet/transfer")
+ self.assertEqual(cfg.vps_host, "1.2.3.4")
+ self.assertEqual(cfg.admin_key, "test-key-32-chars-long!!")
+ self.assertEqual(cfg.from_wallet, "founder_community")
+ self.assertTrue(cfg.dry_run)
+ self.assertTrue(cfg.post_comment)
+ self.assertEqual(cfg.github_token, "ghp_test")
+ self.assertEqual(cfg.repo_path, ".")
+ self.assertEqual(cfg.max_amount, 10000.0)
+ self.assertEqual(cfg.repo, "test/repo")
+ self.assertEqual(cfg.pr_number, "42")
+ self.assertEqual(cfg.pr_author, "alice")
+ self.assertEqual(cfg.pr_merged, "true")
+ self.assertEqual(cfg.pr_head_sha, "abc123")
+ self.assertEqual(cfg.pr_title, "Test PR")
+
def test_dry_run_mode(self):
cfg = self._cfg(INPUT_DRY_RUN="true")
self.assertTrue(cfg.dry_run)
@@ -199,6 +261,14 @@ def test_validate_missing_vps_host_in_live_mode(self):
cfg = self._cfg(INPUT_RTC_VPS_HOST="", INPUT_DRY_RUN="false")
self.assertIsNotNone(cfg.validate())
+ def test_validate_allows_api_url_without_legacy_host(self):
+ cfg = self._cfg(
+ INPUT_RTC_API_URL="https://rustchain.org/wallet/transfer",
+ INPUT_RTC_VPS_HOST="",
+ INPUT_DRY_RUN="false",
+ )
+ self.assertIsNone(cfg.validate())
+
def test_validate_missing_admin_key_in_live_mode(self):
cfg = self._cfg(INPUT_RTC_ADMIN_KEY="", INPUT_DRY_RUN="false")
self.assertIsNotNone(cfg.validate())
@@ -211,6 +281,58 @@ def test_validate_negative_amount(self):
cfg = self._cfg(INPUT_RTC_AMOUNT="-5")
self.assertIsNotNone(cfg.validate())
+ def test_validate_rejects_nan_amount(self):
+ cfg = self._cfg(INPUT_RTC_AMOUNT="nan")
+ self.assertEqual(cfg.validate(), "rtc-amount must be finite, got nan")
+
+ def test_validate_rejects_infinite_amount(self):
+ cfg = self._cfg(INPUT_RTC_AMOUNT="inf")
+ self.assertEqual(cfg.validate(), "rtc-amount must be finite, got inf")
+
+ def test_validate_rejects_malformed_amount(self):
+ cfg = self._cfg(INPUT_RTC_AMOUNT="not-a-number")
+ self.assertEqual(cfg.validate(), "rtc-amount must be finite, got nan")
+
+ def test_validate_rejects_nan_max_amount(self):
+ cfg = self._cfg(INPUT_MAX_AMOUNT="nan")
+ self.assertEqual(cfg.validate(), "max-amount must be finite, got nan")
+
+ def test_validate_rejects_malformed_max_amount(self):
+ cfg = self._cfg(INPUT_MAX_AMOUNT="not-a-number")
+ self.assertEqual(cfg.validate(), "max-amount must be finite, got nan")
+
+
+# ---------------------------------------------------------------------------
+# Transfer error classification tests
+# ---------------------------------------------------------------------------
+
+
+class TestEndpointUnreachableError(unittest.TestCase):
+ """Test classification of network errors that require manual follow-up."""
+
+ def test_matches_common_network_failures(self):
+ samples = [
+ "Connection failed: [Errno 111] Connection refused",
+ "timed out while connecting to the RustChain endpoint",
+ "Temporary failure in name resolution",
+ "No route to host",
+ "Network is unreachable",
+ "Connection reset by peer",
+ ]
+ for sample in samples:
+ with self.subTest(sample=sample):
+ self.assertTrue(is_endpoint_unreachable_error(sample))
+
+ def test_does_not_match_business_logic_rejections(self):
+ samples = [
+ "Insufficient balance",
+ "invalid recipient wallet",
+ "amount exceeds safety cap",
+ ]
+ for sample in samples:
+ with self.subTest(sample=sample):
+ self.assertFalse(is_endpoint_unreachable_error(sample))
+
# ---------------------------------------------------------------------------
# set_output tests
@@ -238,6 +360,98 @@ def test_set_output_writes_to_file(self):
os.unlink(output_file)
+# ---------------------------------------------------------------------------
+# transfer_rtc tests
+# ---------------------------------------------------------------------------
+
+
+class TestTransferRtc(unittest.TestCase):
+ """Test RustChain transfer API request construction."""
+
+ def test_build_transfer_url_preserves_full_path(self):
+ self.assertEqual(
+ build_transfer_url("https://rustchain.org/wallet/transfer"),
+ "https://rustchain.org/wallet/transfer",
+ )
+
+ def test_build_transfer_url_appends_transfer_path_to_origin(self):
+ self.assertEqual(
+ build_transfer_url("https://rustchain.org"),
+ "https://rustchain.org/wallet/transfer",
+ )
+
+ def test_build_transfer_url_keeps_legacy_host_mode(self):
+ self.assertEqual(
+ build_transfer_url("1.2.3.4"),
+ "http://1.2.3.4:8099/wallet/transfer",
+ )
+
+ def test_strips_scalar_request_values(self):
+ mock_resp = MagicMock()
+ mock_resp.read.return_value = b'{"ok": true, "tx_hash": "tx_abc"}'
+
+ with patch("award_rtc.urlopen", return_value=mock_resp) as mock_urlopen:
+ ok, result = transfer_rtc(
+ " https://rustchain.org/wallet/transfer\n",
+ " test-admin-key\n",
+ " founder_community\n",
+ " alice\n",
+ 5.0,
+ "PR #4559 auto-bounty",
+ )
+
+ self.assertTrue(ok)
+ self.assertEqual(result["tx_hash"], "tx_abc")
+
+ req = mock_urlopen.call_args[0][0]
+ self.assertEqual(req.full_url, "https://rustchain.org/wallet/transfer")
+ self.assertEqual(req.get_header("X-admin-key"), "test-admin-key")
+
+ payload = json.loads(req.data.decode("utf-8"))
+ self.assertEqual(payload["from_miner"], "founder_community")
+ self.assertEqual(payload["to_miner"], "alice")
+
+ def test_success_response_rejects_invalid_json(self):
+ mock_resp = MagicMock()
+ mock_resp.read.return_value = b"not json"
+
+ with patch("award_rtc.urlopen", return_value=mock_resp):
+ ok, result = transfer_rtc(
+ "https://rustchain.org/wallet/transfer",
+ "test-admin-key",
+ "founder_community",
+ "alice",
+ 5.0,
+ "PR #4559 auto-bounty",
+ )
+
+ self.assertFalse(ok)
+ self.assertEqual(
+ result["error"],
+ "Invalid JSON response from transfer endpoint",
+ )
+
+ def test_success_response_rejects_non_object_json(self):
+ mock_resp = MagicMock()
+ mock_resp.read.return_value = b'["ok", true]'
+
+ with patch("award_rtc.urlopen", return_value=mock_resp):
+ ok, result = transfer_rtc(
+ "https://rustchain.org/wallet/transfer",
+ "test-admin-key",
+ "founder_community",
+ "alice",
+ 5.0,
+ "PR #4559 auto-bounty",
+ )
+
+ self.assertFalse(ok)
+ self.assertEqual(
+ result["error"],
+ "Transfer endpoint response must be a JSON object",
+ )
+
+
# ---------------------------------------------------------------------------
# Integration-style main() tests
# ---------------------------------------------------------------------------
@@ -248,8 +462,11 @@ class TestMainFlow(unittest.TestCase):
def _env(self, **overrides):
"""Set up environment for main()."""
+ output_file = tempfile.NamedTemporaryFile(delete=False)
+ output_file.close()
env = {
"INPUT_RTC_AMOUNT": "75",
+ "INPUT_RTC_API_URL": "",
"INPUT_RTC_VPS_HOST": "1.2.3.4",
"INPUT_RTC_ADMIN_KEY": "test-admin-key-32chars!!",
"INPUT_FROM_WALLET": "founder_community",
@@ -265,7 +482,7 @@ def _env(self, **overrides):
"PR_BODY": "wallet: RTCcontributor123\n",
"PR_HEAD_SHA": "abc123",
"PR_TITLE": "Test PR",
- "GITHUB_OUTPUT": "/dev/null",
+ "GITHUB_OUTPUT": output_file.name,
}
env.update(overrides)
return patch.dict(os.environ, env, clear=True)
@@ -285,6 +502,44 @@ def test_skip_already_awarded(self):
rc = main()
self.assertEqual(rc, 0)
+ def test_retry_after_dry_run_marker(self):
+ from award_rtc import main
+ comments = [{"body": f""}]
+ transfer_result = {
+ "ok": True,
+ "pending_id": 102,
+ "tx_hash": "tx_after_dry_run",
+ }
+ with self._env():
+ with patch("award_rtc.fetch_pr_comments", return_value=comments):
+ with patch(
+ "award_rtc.transfer_rtc",
+ return_value=(True, transfer_result),
+ ) as mock_tx:
+ with patch("award_rtc.post_pr_comment", return_value=True):
+ rc = main()
+ self.assertEqual(rc, 0)
+ mock_tx.assert_called_once()
+
+ def test_retry_after_failed_marker(self):
+ from award_rtc import main
+ comments = [{"body": f""}]
+ transfer_result = {
+ "ok": True,
+ "pending_id": 103,
+ "tx_hash": "tx_after_failed",
+ }
+ with self._env():
+ with patch("award_rtc.fetch_pr_comments", return_value=comments):
+ with patch(
+ "award_rtc.transfer_rtc",
+ return_value=(True, transfer_result),
+ ) as mock_tx:
+ with patch("award_rtc.post_pr_comment", return_value=True):
+ rc = main()
+ self.assertEqual(rc, 0)
+ mock_tx.assert_called_once()
+
def test_dry_run_mode(self):
from award_rtc import main
with self._env(INPUT_DRY_RUN="true"):
@@ -295,6 +550,26 @@ def test_dry_run_mode(self):
# Should have posted a dry-run comment
mock_post.assert_called_once()
+ def test_dry_run_rejects_nan_amount(self):
+ from award_rtc import main
+ with self._env(INPUT_DRY_RUN="true", INPUT_RTC_AMOUNT="nan"):
+ with patch("award_rtc.fetch_pr_comments") as mock_fetch:
+ with patch("award_rtc.post_pr_comment") as mock_post:
+ rc = main()
+ self.assertEqual(rc, 1)
+ mock_fetch.assert_not_called()
+ mock_post.assert_not_called()
+
+ def test_dry_run_rejects_malformed_amount(self):
+ from award_rtc import main
+ with self._env(INPUT_DRY_RUN="true", INPUT_RTC_AMOUNT="not-a-number"):
+ with patch("award_rtc.fetch_pr_comments") as mock_fetch:
+ with patch("award_rtc.post_pr_comment") as mock_post:
+ rc = main()
+ self.assertEqual(rc, 1)
+ mock_fetch.assert_not_called()
+ mock_post.assert_not_called()
+
def test_successful_transfer(self):
from award_rtc import main
transfer_result = {
@@ -321,6 +596,35 @@ def test_failed_transfer(self):
rc = main()
self.assertEqual(rc, 1)
+ def test_connection_failure_posts_manual_notice_without_failing_job(self):
+ from award_rtc import main
+ transfer_result = {
+ "ok": False,
+ "error": "Connection failed: [Errno 111] Connection refused",
+ }
+ with self._env():
+ with patch("award_rtc.fetch_pr_comments", return_value=[]):
+ with patch("award_rtc.transfer_rtc", return_value=(False, transfer_result)):
+ with patch("award_rtc.post_pr_comment", return_value=True) as mock_post:
+ rc = main()
+ self.assertEqual(rc, 0)
+ comment_body = mock_post.call_args[0][2]
+ self.assertIn("Manual Transfer Required", comment_body)
+ self.assertIn(":MANUAL-REQUIRED", comment_body)
+
+ def test_connection_failure_fails_when_manual_notice_cannot_be_posted(self):
+ from award_rtc import main
+ transfer_result = {
+ "ok": False,
+ "error": "Connection failed: [Errno 111] Connection refused",
+ }
+ with self._env():
+ with patch("award_rtc.fetch_pr_comments", return_value=[]):
+ with patch("award_rtc.transfer_rtc", return_value=(False, transfer_result)):
+ with patch("award_rtc.post_pr_comment", return_value=False):
+ rc = main()
+ self.assertEqual(rc, 1)
+
def test_amount_exceeds_cap(self):
from award_rtc import main
with self._env(INPUT_RTC_AMOUNT="50000", INPUT_MAX_AMOUNT="10000"):
@@ -347,7 +651,7 @@ def test_bounty_override_in_pr_body(self):
call_args = mock_tx.call_args
self.assertEqual(call_args[0][4], 200.0) # amount parameter
- def test_fallback_to_pr_author_when_no_wallet(self):
+ def test_missing_wallet_fails_without_transfer(self):
from award_rtc import main
transfer_result = {
"ok": True,
@@ -359,12 +663,116 @@ def test_fallback_to_pr_author_when_no_wallet(self):
with self._env(PR_BODY="Just a regular PR\n", PR_AUTHOR="bob"):
with patch("award_rtc.fetch_pr_comments", return_value=[]):
with patch("award_rtc.transfer_rtc", return_value=(True, transfer_result)) as mock_tx:
- with patch("award_rtc.post_pr_comment", return_value=True):
+ with patch("award_rtc.post_pr_comment", return_value=True) as mock_post:
rc = main()
- self.assertEqual(rc, 0)
- # Should use PR author as wallet
- call_args = mock_tx.call_args
- self.assertEqual(call_args[0][3], "bob") # to_wallet parameter
+ self.assertEqual(rc, 1)
+ mock_tx.assert_not_called()
+ mock_post.assert_called_once()
+ comment_body = mock_post.call_args[0][2]
+ self.assertIn("RTC Auto-Bounty Skipped", comment_body)
+ self.assertIn("wallet: RTC...", comment_body)
+ self.assertIn("recipient_wallet_missing", comment_body)
+
+
+class TestValidateRecipient(unittest.TestCase):
+ """Security: recipient validation before any transfer."""
+
+ def test_accepts_canonical_rtc_address(self):
+ ok, reason = validate_recipient(VALID_RTC)
+ self.assertTrue(ok)
+ self.assertIsNone(reason)
+
+ def test_accepts_simple_username(self):
+ ok, reason = validate_recipient("some-contributor")
+ self.assertTrue(ok)
+ self.assertIsNone(reason)
+
+ def test_accepts_wallet_name(self):
+ self.assertTrue(validate_recipient("JONASXZB")[0])
+
+ def test_rejects_none_and_empty(self):
+ self.assertFalse(validate_recipient(None)[0])
+ self.assertFalse(validate_recipient("")[0])
+
+ def test_rejects_markdown_junk(self):
+ # backtick / parenthesis confusables from `code` spans
+ self.assertFalse(validate_recipient("RTCabc`")[0])
+ self.assertFalse(validate_recipient("(RTCabc)")[0])
+
+ def test_rejects_non_ascii_confusable(self):
+ # Cyrillic 'а' homoglyph
+ ok, reason = validate_recipient("RTCаbcdef")
+ self.assertFalse(ok)
+ self.assertEqual(reason, "recipient_wallet_non_ascii")
+
+ def test_rejects_platform_wallets(self):
+ for w in ("founder_community", "founder_dev_fund", "FOUNDER_FOUNDERS", "treasury"):
+ ok, reason = validate_recipient(w)
+ self.assertFalse(ok, w)
+ self.assertEqual(reason, "recipient_platform_wallet_blocked")
+
+ def test_rejects_embedded_whitespace(self):
+ self.assertFalse(validate_recipient("RTC abc")[0])
+
+ def test_rejects_overlong_garbage(self):
+ self.assertFalse(validate_recipient("x" * 200)[0])
+
+
+class TestDistinctWalletDirectives(unittest.TestCase):
+ """Security: conflicting recipient directives must be detectable."""
+
+ def test_single_directive(self):
+ self.assertEqual(distinct_wallet_directives("wallet: RTCone\n"), ["RTCone"])
+
+ def test_duplicate_same_value_collapses(self):
+ body = "wallet: RTCone\nwallet: RTCone\n"
+ self.assertEqual(distinct_wallet_directives(body), ["RTCone"])
+
+ def test_conflicting_directives_detected(self):
+ body = "wallet: RTCattacker\nsome text\nwallet: RTClegit\n"
+ result = distinct_wallet_directives(body)
+ self.assertEqual(len(result), 2)
+ self.assertIn("RTCattacker", result)
+ self.assertIn("RTClegit", result)
+
+ def test_no_directive(self):
+ self.assertEqual(distinct_wallet_directives("nothing here"), [])
+
+
+class TestIdempotencyKey(unittest.TestCase):
+ """Security: deterministic idempotency key + payload wiring."""
+
+ def test_deterministic_and_keyed_on_inputs(self):
+ a = compute_idempotency_key("o/r", "42", VALID_RTC, 50.0)
+ b = compute_idempotency_key("o/r", "42", VALID_RTC, 50.0)
+ c = compute_idempotency_key("o/r", "43", VALID_RTC, 50.0)
+ self.assertEqual(a, b)
+ self.assertNotEqual(a, c)
+ self.assertTrue(a.startswith("award-"))
+ self.assertLessEqual(len(a), 128)
+
+ def test_transfer_includes_idempotency_key_when_given(self):
+ mock_resp = MagicMock()
+ mock_resp.read.return_value = b'{"ok": true, "tx_hash": "tx_abc"}'
+ with patch("award_rtc.urlopen", return_value=mock_resp) as mock_urlopen:
+ transfer_rtc(
+ "https://rustchain.org/wallet/transfer",
+ "k", "founder_community", VALID_RTC, 5.0, "memo",
+ idempotency_key="award-deadbeef",
+ )
+ payload = json.loads(mock_urlopen.call_args[0][0].data.decode("utf-8"))
+ self.assertEqual(payload["idempotency_key"], "award-deadbeef")
+
+ def test_transfer_omits_idempotency_key_when_absent(self):
+ mock_resp = MagicMock()
+ mock_resp.read.return_value = b'{"ok": true, "tx_hash": "tx_abc"}'
+ with patch("award_rtc.urlopen", return_value=mock_resp) as mock_urlopen:
+ transfer_rtc(
+ "https://rustchain.org/wallet/transfer",
+ "k", "founder_community", VALID_RTC, 5.0, "memo",
+ )
+ payload = json.loads(mock_urlopen.call_args[0][0].data.decode("utf-8"))
+ self.assertNotIn("idempotency_key", payload)
if __name__ == "__main__":
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index ad7a22be1..f0133e689 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -1,48 +1,10 @@
-# SPDX-License-Identifier: MIT
-
version: 2
updates:
- - package-ecosystem: "pip"
- directory: "/"
- schedule:
- interval: "daily"
- time: "06:00"
- timezone: "UTC"
- open-pull-requests-limit: 5
- reviewers:
- - "Scottcjn"
- assignees:
- - "Scottcjn"
- commit-message:
- prefix: "deps"
- include: "scope"
- labels:
- - "dependencies"
- - "security"
- allow:
- - dependency-type: "direct"
- - dependency-type: "indirect"
- ignore:
- - dependency-name: "*"
- update-types: ["version-update:semver-major"]
- pull-request-branch-name:
- separator: "/"
-
- - package-ecosystem: "github-actions"
+ - package-ecosystem: "cargo"
directory: "/"
schedule:
interval: "weekly"
- day: "monday"
- time: "06:00"
- timezone: "UTC"
- open-pull-requests-limit: 3
- reviewers:
- - "Scottcjn"
- assignees:
- - "Scottcjn"
- commit-message:
- prefix: "ci"
- include: "scope"
+ open-pull-requests-limit: 10
labels:
- - "ci/cd"
- - "github-actions"
\ No newline at end of file
+ - "dependencies"
+ - "rust"
diff --git a/.github/workflows/award-rtc.yml b/.github/workflows/award-rtc.yml
index 4fc510b44..b72fc76f9 100644
--- a/.github/workflows/award-rtc.yml
+++ b/.github/workflows/award-rtc.yml
@@ -5,7 +5,9 @@ on:
types: [closed]
permissions:
- pull-requests: write
+ contents: read
+ issues: write
+ pull-requests: read
jobs:
award-rtc:
@@ -13,15 +15,15 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
- uses: actions/checkout@v4
+ uses: actions/checkout@v6
- name: Award RTC to Contributor
- uses: BossChaos/rtc-award-action@main
+ uses: ./.github/actions/rtc-auto-bounty
with:
- wallet_file: '.rtc-wallet'
- amount: '5'
- api_url: 'https://bulbous-bouffant.metalseed.net/transfer'
- env:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- # Store wallet as secret: Settings > Secrets > RTC_WALLET_JSON
- # The action will read from the secret instead of a file
+ rtc-amount: '5'
+ rtc-api-url: ${{ secrets.RTC_API_URL }}
+ rtc-vps-host: ${{ secrets.RTC_VPS_HOST }}
+ rtc-admin-key: ${{ secrets.RTC_ADMIN_KEY }}
+ from-wallet: 'founder_community'
+ github-token: ${{ secrets.GITHUB_TOKEN }}
+ repo-path: ${{ github.workspace }}
diff --git a/.github/workflows/welcome.yml b/.github/workflows/welcome.yml
index d55c0f8bf..6fe5dc183 100644
--- a/.github/workflows/welcome.yml
+++ b/.github/workflows/welcome.yml
@@ -32,7 +32,8 @@ jobs:
Welcome to RustChain\! Thanks for your first pull request.
**Before we review**, please make sure:
- - [ ] Your PR has a `BCOS-L1` or `BCOS-L2` label
+ - [ ] Non-doc PRs have a `BCOS-L1` or `BCOS-L2` label
+ - [ ] Doc-only PRs are exempt from BCOS tier labels when they only touch `docs/**`, `*.md`, or common image/PDF files
- [ ] New code files include an SPDX license header
- [ ] You've tested your changes against the live node
diff --git a/.gitignore b/.gitignore
index 702cccc11..f4c763287 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,6 +2,8 @@
*founder*
*premine*
*genesis*
+!tests/**/*genesis*.py
+!test_*genesis*.py
*private*key*
*secret*
*.env
@@ -10,6 +12,12 @@
*.key
*.pem
+# Keep genesis material ignored, but allow tests/tooling that validate
+# genesis file handling.
+!tools/validate_genesis.py
+!test_*genesis*.py
+!tests/**/test_*genesis*.py
+
# Python
__pycache__/
*.py[cod]
diff --git a/API_WALKTHROUGH.md b/API_WALKTHROUGH.md
index fc889d2a0..ddab36245 100644
--- a/API_WALKTHROUGH.md
+++ b/API_WALKTHROUGH.md
@@ -67,12 +67,14 @@ POST /wallet/transfer/signed
```json
{
- "from": "sender_wallet_id",
- "to": "recipient_wallet_id",
- "amount": 10,
- "fee": 0.001,
- "signature": "hex_encoded_signature",
- "timestamp": 1234567890
+ "from_address": "RTCaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+ "to_address": "RTCbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
+ "amount_rtc": 1.5,
+ "nonce": 12345,
+ "memo": "",
+ "public_key": "ed25519_public_key_hex",
+ "signature": "ed25519_signature_hex",
+ "chain_id": "rustchain-mainnet-v2"
}
```
@@ -80,20 +82,22 @@ POST /wallet/transfer/signed
| Field | Type | Description |
|-------|------|-------------|
-| `from` | string | Sender's RustChain wallet ID |
-| `to` | string | Recipient's RustChain wallet ID |
-| `amount` | integer | Amount in RTC (smallest unit) |
-| `fee` | float | Transaction fee |
-| `signature` | hex string | Ed25519 signature of the transfer payload |
-| `timestamp` | integer | Unix timestamp for replay protection |
+| `from_address` | string | Sender's `RTC...` address |
+| `to_address` | string | Recipient's `RTC...` address |
+| `amount_rtc` | number | Amount to transfer in RTC |
+| `nonce` | integer | Unique nonce for replay protection |
+| `memo` | string | Optional memo included in the signed payload |
+| `public_key` | hex string | Sender Ed25519 public key |
+| `signature` | hex string | Ed25519 signature over the canonical transfer payload |
+| `chain_id` | string | Chain identifier, usually `rustchain-mainnet-v2` |
### Important Notes
-1. **Wallet IDs are NOT external addresses** - RustChain uses its own wallet system (e.g., `Ivan-houzhiwen`), not Ethereum or Solana addresses.
+1. **Use RustChain addresses** - Signed transfers use `RTC...` wallet addresses, not miner IDs like `Ivan-houzhiwen` and not Ethereum or Solana addresses.
2. **TLS certificates** - RustChain nodes use self-signed certificates. For production use, place the node's certificate at `~/.rustchain/node_cert.pem` and the `requests` library will automatically use it (default `verify=True`). For local testing with a self-signed certificate that is not pinned, you may temporarily set `verify=False` but be aware of MITM risks. The recommended pattern is to use the shared `tls_config` module from the RustChain codebase: `from node.tls_config import get_tls_session; session = get_tls_session()`.
-3. **Amount is in smallest unit** - 1 RTC = 1,000,000 smallest units.
+3. **Amount is human-readable RTC** - `amount_rtc` is the RTC amount, not the micro-RTC integer balance field.
---
@@ -112,12 +116,14 @@ print(f"Balance: {response.json()['amount_rtc']} RTC")
# Transfer (requires signature)
transfer_data = {
- "from": "sender_wallet",
- "to": "recipient_wallet",
- "amount": 1000000, # 1 RTC
- "fee": 1000,
- "signature": "...",
- "timestamp": 1234567890
+ "from_address": "RTCaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+ "to_address": "RTCbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
+ "amount_rtc": 1.0,
+ "nonce": 12345,
+ "memo": "",
+ "public_key": "ed25519_public_key_hex",
+ "signature": "ed25519_signature_hex",
+ "chain_id": "rustchain-mainnet-v2",
}
response = requests.post(
"https://50.28.86.131/wallet/transfer/signed",
@@ -126,11 +132,13 @@ response = requests.post(
print(response.json())
```
+See `docs/API.md` for the full canonical signing rules.
+
---
## Reference
-- **Node:** `https://50.28.86.131`
+- **Node base URL:** `https://50.28.86.131`
- **Explorer:** `https://50.28.86.131/explorer`
- **Health:** `https://50.28.86.131/health`
diff --git a/AUDIT_REPORT.md b/AUDIT_REPORT.md
new file mode 100644
index 000000000..a1a51cbbb
--- /dev/null
+++ b/AUDIT_REPORT.md
@@ -0,0 +1,20 @@
+# Red Team UTXO Audit Report
+
+## Bugs Found
+
+### BUG-1 (MEDIUM): mempool_remove() not atomic
+- Two DELETEs without BEGIN IMMEDIATE
+- Crash between DELETEs orphans input claims, permanently locking UTXOs
+
+### BUG-2 (LOW): coin_select() no input limit on largest-first fallback
+- When all UTXOs are equal, largest-first = smallest-first, still exceeds 20 inputs
+
+### BUG-3 (LOW): spend_box() inconsistent ROLLBACK pattern
+- ROLLBACK after read-only SELECT is unnecessary, inconsistent with abort()
+
+### BUG-4 (MEDIUM): stale data_input mempool entries not proactively cleaned
+- UTXOs locked by stale mempool entries that can never be mined
+
+## Researcher
+crowniteto (Crow) — RTC7be68f41360f8edc9013fd6cb997b6b07a45e57a
+
diff --git a/BATTLESHIP_PROGRESS.md b/BATTLESHIP_PROGRESS.md
new file mode 100644
index 000000000..faa30deab
--- /dev/null
+++ b/BATTLESHIP_PROGRESS.md
@@ -0,0 +1,65 @@
+# Battleship 300 Bug Hunt — RustChain UTXO
+
+## A1: mempool_add() missing MAX_INPUTS ✅ DONE
+**File:** `node/utxo_db.py:842` → `mempool_add()`
+**Severity:** MEDIUM (DoS via unbounded query count in write lock)
+**Evidence:**
+ - 200-input tx accepted: True (93,539 qps)
+ - 500-input tx accepted: True (111,043 qps)
+ - No MAX_INPUTS constant in utxo_db.py
+ - MAX_OUTPUTS=100 exists → asymmetry
+ - 10K inputs → ~90ms locked DB time
+**PoC:** `node/test_utxo_no_max_inputs_poc.py`
+**Fix:** Add `MAX_INPUTS = 1000` + reject `if len(inputs) > MAX_INPUTS` in mempool_add()
+**PR:** https://github.com/Scottcjn/Rustchain/pull/6237
+
+## A2: apply_transaction() missing MAX_INPUTS ✅ DONE
+**File:** `node/utxo_db.py:485` → `apply_transaction()`
+**Severity:** MEDIUM (block production delay, consensus stall)
+**Evidence:**
+ - 100-input tx accepted: True (71,429 updates/sec)
+ - 500-input tx accepted: True (103,456 updates/sec)
+ - Same root cause as A1 — no MAX_INPUTS constant
+ - coin_select() caps at 20 inputs (heuristic) but DB layer has no guard
+ - 10K inputs → ~100ms locked time during block production
+**PoC:** `node/test_utxo_no_max_inputs_apply_poc.py`
+**Fix:** Same `MAX_INPUTS` check in apply_transaction()
+**PR:** https://github.com/Scottcjn/Rustchain/pull/6237
+
+## A3: mempool_add() stores full tx dict with no field/size validation ✅ DONE
+**File:** `node/utxo_db.py:1001` → `json.dumps(tx)` in `mempool_add()`
+**Severity:** LOW-MEDIUM (storage inflation, response bloat)
+**Evidence:**
+ - 20KB garbage field injected → survives store→retrieve round-trip
+ - Extra keys: `garbage`, `_allow_minting`, `nested_spam` all survive
+ - tx_data_json has NO size limit, NO field whitelist
+ - 9999 max pool × 50KB = ~500MB potential mempool bloat
+**PoC:** `node/test_utxo_mempool_garbage_injection_poc.py`
+**Fix:** Whitelist allowed fields before json.dumps(). Add MAX_TX_JSON_BYTES cap.
+**PR:** https://github.com/Scottcjn/Rustchain/pull/6237
+
+## A4: TOCTOU — mempool_add + apply_transaction both claim same box ✅ DONE
+**File:** `node/utxo_db.py:842` (`mempool_add()`) vs `node/utxo_db.py:485` (`apply_transaction()`)
+**Severity:** LOW (sequential gap; SQLite IMMEDIATE lock mostly mitigates concurrent race)
+**Evidence:**
+ - Sequential: mempool_add and apply_transaction both return True on same box
+ - mempool_add claims in utxo_mempool_inputs, apply_transaction spends in utxo_boxes.spent_at
+ - No cross-check between the two systems → stale mempool entries
+ - Carol gets 100 UNIT (apply_tx), Bob gets 0 (stale mempool entry)
+ - Concurrent: SQLite IMMEDIATE lock serializes, preventing concurrent race
+**PoC:** `node/test_utxo_mempool_apply_toctou_poc.py`
+**Fix:** In apply_transaction or block production, check mempool_inputs doesn't claim the box.
+**PR:** https://github.com/Scottcjn/Rustchain/pull/6237
+
+## A5: mempool_get_block_candidates() fetchall() loads all tx_data_json in memory ✅ DONE
+**File:** `node/utxo_db.py:1055` → `fetchall()` in `mempool_get_block_candidates()`
+**Severity:** MEDIUM (DoS — memory exhaustion via garbage-padded mempool entries)
+**Evidence:**
+ - `mempool_add()` stores `json.dumps(tx)` at line 1001 with NO size cap
+ - `mempool_get_block_candidates()` uses `.fetchall()` at line 1055 — loads ALL rows into Python memory
+ - Same A3 garbage injection vector inflates each tx_data_json to 100KB+
+ - MAX_POOL_SIZE=10000 × 100KB = ~977MB loaded by fetchall()
+ - Processing loop iterates ALL rows before reaching max_count (line 1091)
+**PoC:** `node/test_utxo_mempool_fetchall_oom_poc.py`
+**Fix:** Add MAX_TX_DATA_JSON_BYTES cap in mempool_add(). Use server-side cursor / LIMIT+OFFSET in mempool_get_block_candidates(). Strip non-essential fields before json.dumps().
+**PR:** https://github.com/Scottcjn/Rustchain/pull/6237
diff --git a/BOUNTY_2293_BCOS_HOMEBREW.md b/BOUNTY_2293_BCOS_HOMEBREW.md
index 596fde7f7..025f67d72 100644
--- a/BOUNTY_2293_BCOS_HOMEBREW.md
+++ b/BOUNTY_2293_BCOS_HOMEBREW.md
@@ -184,19 +184,19 @@ The formula uses a **stable approach** for checksum verification:
```ruby
# SHA256 checksum computed from the GitHub release tarball.
# To verify or update: curl -sSL "" | sha256sum
-sha256 "a3e1c6f8e5c8d9b2a4f7e0c3d6b9a2e5f8c1d4b7a0e3f6c9d2b5a8e1f4c7d0b3"
+sha256 "5123df374138327ba506b47c64fc4069c5f08014c6b21d5a86064b962ad2fd1b"
```
**To compute the actual checksum**:
```bash
# macOS (using shasum)
-curl -sSL "https://github.com/Scottcjn/Rustchain/archive/refs/tags/v2.5.0.tar.gz" | shasum -a 256
+curl -sSL "https://github.com/Scottcjn/Rustchain/archive/refs/tags/v2.4.0.tar.gz" | shasum -a 256
# Linux (using sha256sum)
-curl -sSL "https://github.com/Scottcjn/Rustchain/archive/refs/tags/v2.5.0.tar.gz" | sha256sum
+curl -sSL "https://github.com/Scottcjn/Rustchain/archive/refs/tags/v2.4.0.tar.gz" | sha256sum
```
-Replace the placeholder value with the computed hash before production release.
+The formula should use the computed hash for the archive tag it references.
### macOS Compatibility
@@ -282,14 +282,13 @@ brew style bcos
### SHA256 Checksum
-**BEFORE PRODUCTION RELEASE**, update the SHA256 in `bcos.rb`:
+The SHA256 in `bcos.rb` should match the archive URL:
```ruby
-# Current placeholder (MUST REPLACE)
-sha256 "a3e1c6f8e5c8d9b2a4f7e0c3d6b9a2e5f8c1d4b7a0e3f6c9d2b5a8e1f4c7d0b3"
+sha256 "5123df374138327ba506b47c64fc4069c5f08014c6b21d5a86064b962ad2fd1b"
# Compute actual checksum:
-curl -sSL https://github.com/Scottcjn/Rustchain/archive/refs/tags/v2.5.0.tar.gz | sha256sum
+curl -sSL https://github.com/Scottcjn/Rustchain/archive/refs/tags/v2.4.0.tar.gz | sha256sum
```
### Recommended vs Required
@@ -357,11 +356,11 @@ Bounty: #2293
- [x] Follows rustchain-miner.rb pattern
- [x] Compatible with existing homebrew/ structure
- [x] launchd plist included
-- [x] SHA256 placeholder marked for replacement
+- [x] SHA256 checksum aligned with the archive URL
### Security
- [x] No secrets committed
-- [x] SHA256 checksum required before release
+- [x] SHA256 checksum pinned before release
- [x] Optional external tools (no forced dependencies)
- [x] Local execution by default
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 7d403f0ed..5ee1b6a85 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -39,7 +39,7 @@ New to RustChain? Get 10 RTC for your **first merged PR** — even for small imp
## Quick Start
1. **Browse open bounties**: Check [Issues](https://github.com/Scottcjn/Rustchain/issues?q=is%3Aissue+is%3Aopen+label%3Abounty) labeled `bounty`
-2. **Find Good First Issues**: Check [Good First Issues](https://github.com/Scottcjn/Rustchain/issues?q=is%3Aissue+is%3Aopen+label%3A"good+first-issue") labeled `good first issue`
+2. **Find Good First Issues**: Check [Good First Issues](https://github.com/Scottcjn/Rustchain/issues?q=is%3Aissue+is%3Aopen+label%3A%22good%20first%20issue%22) labeled `good first issue`
3. **Comment on the issue** you want to work on (prevents duplicate work)
4. **Fork the repo** and create a feature branch
5. **Submit a PR** referencing the issue number
@@ -79,9 +79,23 @@ New to RustChain? Get 10 RTC for your **first merged PR** — even for small imp
git clone https://github.com/Scottcjn/Rustchain.git
cd Rustchain
+# Verify you are in the expected checkout
+test -f CONTRIBUTING.md && test -f pyproject.toml && test -f requirements.txt
+
# Python environment
-python3 -m venv venv && source venv/bin/activate
-pip install -r requirements.txt
+python3 -m venv .venv && source .venv/bin/activate
+python -m pip install --upgrade pip
+python -m pip install -r requirements.txt -r requirements-node.txt
+
+# Verify key Python entry points parse correctly
+python -m py_compile node/wsgi.py node/rustchain_v2_integrated_v2.2.1_rip200.py wallet/__main__.py
+
+# Run focused tests for the area you changed
+python -m pytest node/tests/test_mock_signature_guard.py
+
+# SDK tests need the local SDK package dependencies first
+python -m pip install -e ./sdk
+python -m pytest sdk/tests/test_client_unit.py
# Test against live node
curl -sk https://rustchain.org/health
@@ -89,6 +103,17 @@ curl -sk https://rustchain.org/api/miners
curl -sk https://rustchain.org/epoch
```
+For package-specific work, use the closest local manifest or test folder:
+
+| Area | Example command |
+|------|-----------------|
+| Node API | `python -m pytest node/tests/test_mock_signature_guard.py` |
+| SDK | `python -m pip install -e ./sdk && python -m pytest sdk/tests/test_client_unit.py` |
+| Bridge | `python -m pytest bridge/test_bridge_api.py` |
+| Rust miner crate | `cargo check --manifest-path rustchain-miner/Cargo.toml` |
+| Native wallet crate | `cargo check --manifest-path rustchain-wallet/Cargo.toml` |
+| Onboarding script | `node --check onboard/index.js` |
+
## Live Infrastructure
| Endpoint | URL |
@@ -96,16 +121,16 @@ curl -sk https://rustchain.org/epoch
| Node Health | `https://rustchain.org/health` |
| Active Miners | `https://rustchain.org/api/miners` |
| Current Epoch | `https://rustchain.org/epoch` |
-| Block Explorer | `https://rustchain.org/explorer` |
+| Block Explorer | `https://rustchain.org/explorer/` |
| wRTC Bridge | `https://bottube.ai/bridge` |
## RTC Payout Process
1. PR gets reviewed and merged
-3. We comment asking for your wallet address
-4. RTC is transferred from the community fund
-5. Bridge RTC to wRTC (Solana) via [bottube.ai/bridge](https://bottube.ai/bridge)
-6. Trade on [Raydium](https://raydium.io/swap/?inputMint=sol&outputMint=12TAdKXxcGf6oCv4rqDz2NkgxjyHq6HQKoxKZYGf5i4X)
+2. We comment asking for your wallet address
+3. RTC is transferred from the community fund
+4. Bridge RTC to wRTC (Solana) via [bottube.ai/bridge](https://bottube.ai/bridge)
+5. Trade on [Raydium](https://raydium.io/swap/?inputMint=sol&outputMint=12TAdKXxcGf6oCv4rqDz2NkgxjyHq6HQKoxKZYGf5i4X)
## Documentation Quality Checklist
@@ -131,7 +156,7 @@ This keeps bounty-quality docs usable by new contributors and operators.
## Code Style
-- Python 3.8+ compatible
+- Python 3.11+ recommended for the main node and repository-level checks
- Type hints appreciated but not yet enforced
- Keep PRs focused — one issue per PR
- Test against the live node, not just local mocks
@@ -159,7 +184,7 @@ Don't just code — mine! Install the miner and earn RTC while you contribute:
```bash
pip install clawrtc
-clawrtc --wallet YOUR_NAME
+clawrtc mine --wallet YOUR_NAME
```
Vintage hardware (PowerPC G4/G5, POWER8) earns **2-2.5x** more than modern PCs.
@@ -183,4 +208,4 @@ When reviewing PRs or preparing your own:
- [ ] Code follows project style
- [ ] Tests added/updated for changes
- [ ] Documentation updated if needed
-- [ ] No unrelated changes in the PR
\ No newline at end of file
+- [ ] No unrelated changes in the PR
diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md
index 8cc72958f..1ec2ae881 100644
--- a/CONTRIBUTORS.md
+++ b/CONTRIBUTORS.md
@@ -9,3 +9,5 @@
| @haoyousun60-create | iKun#0000 | AI automation, bounty hunting, documentation, open source contributions |
| @jaxint | jaxint#0000 | AI automation, bounty hunting, and code reviews |
| @508704820 | Xeophon#0000 | AI automation, bounty hunting, multi-agent orchestration, open source |
+| @Munir2029 | Munir2029 |Interested in open source and testing |
+| @SimplyRayYZL | RaYy Cave | AI agents, automation, testing, and open source bounties |
diff --git a/DOCKER_DEPLOYMENT.md b/DOCKER_DEPLOYMENT.md
index 5700d86c5..46ffbf2a0 100644
--- a/DOCKER_DEPLOYMENT.md
+++ b/DOCKER_DEPLOYMENT.md
@@ -348,4 +348,4 @@ sudo iptables-save | sudo tee /etc/iptables/rules.v4
## License
-MIT License - See LICENSE file for details
+Apache License 2.0 - See LICENSE file for details
diff --git a/IMPLEMENTATION_SUMMARY.md b/IMPLEMENTATION_SUMMARY.md
index 8ff6d9c8c..ea62b36e7 100644
--- a/IMPLEMENTATION_SUMMARY.md
+++ b/IMPLEMENTATION_SUMMARY.md
@@ -200,7 +200,7 @@ Assuming 10 total miners, 3 in retro_console bucket:
- [RIP-0683 Specification](docs/CONSOLE_MINING_SETUP.md)
- [RIP-0304: Original Console Mining Spec](rips/docs/RIP-0304-retro-console-mining.md)
- [RIP-201: Fleet Immune System](rips/docs/RIP-0201-fleet-immune-system.md)
-- [Legend of Elya](https://github.com/ilya-kh/legend-of-elya) - N64 neural network demo
+- [Legend of Elya](https://github.com/Scottcjn/legend-of-elya-n64) - N64 neural network demo
- [Console Mining Setup Guide](docs/CONSOLE_MINING_SETUP.md)
## Acknowledgments
diff --git a/INSTALL.md b/INSTALL.md
index 23974a694..c33bca83b 100644
--- a/INSTALL.md
+++ b/INSTALL.md
@@ -38,26 +38,32 @@ This skips the interactive wallet prompt and uses the specified wallet name.
- x86_64 (Intel/AMD 64-bit)
- aarch64 (ARM64, e.g. Raspberry Pi)
- ppc64le (PowerPC 64-bit Little-Endian)
-- ppc (PowerPC 32-bit)
+
+The one-line installer currently targets 64-bit Linux PowerPC (`ppc64le`). Legacy
+32-bit PowerPC systems may need a manual miner path instead of this installer.
### macOS
- ✅ macOS 12 (Monterey) and later
- ✅ macOS 11 (Big Sur) with limitations
+Big Sur support is limited to Intel and Apple Silicon Macs with a working Python
+3.8+ interpreter. Older PowerPC Mac OS X releases are not supported by the
+one-line installer because it creates a Python 3 virtualenv and runs modern
+Python miner code.
+
**Architectures:**
- arm64 (Apple Silicon M1/M2/M3)
- x86_64 (Intel Mac)
-- powerpc (PowerPC G3/G4/G5)
### Special Hardware
- ✅ IBM POWER8 systems
-- ✅ PowerPC G4/G5 Macs
+- ✅ 64-bit Linux PowerPC systems (`ppc64le`)
- ✅ Vintage x86 CPUs (Pentium 4, Core 2 Duo, etc.)
## Requirements
### System Requirements
-- Python 3.8+ (or Python 2.5+ for vintage PowerPC systems)
+- Python 3.8+
- curl or wget
- 50 MB disk space
- Internet connection
@@ -245,6 +251,10 @@ rm -f /usr/local/bin/rustchain-mine
## Troubleshooting
+For a focused guide to common miner runtime errors such as `Wallet not found`,
+`Connection refused`, `Insufficient balance`, and architecture mismatches, see
+[`TROUBLESHOOTING.md`](TROUBLESHOOTING.md).
+
### Python virtualenv creation fails
**Error:** `Could not create virtual environment`
diff --git a/README.md b/README.md
index 58e880ba2..fac680ade 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,5 @@
+
+
# RustChain
@@ -12,13 +14,15 @@
[](https://github.com/Scottcjn/Rustchain/stargazers)
[](https://rustchain.org/explorer/)
[](https://rustchain.org)
-[](docs/RustChain_Whitepaper_Flameholder_v0.97.pdf)
-[](https://doi.org/10.5281/zenodo.19442753)
+[](docs/WHITEPAPER.md)
+[](https://doi.org/10.5281/zenodo.19442753)
A PowerBook G4 from 2003 earns **2.5x** more than a modern Threadripper.
A Power Mac G5 earns **2.0x**. A 486 with rusty serial ports earns the most respect of all.
-[Explorer](https://rustchain.org/explorer/) · [Machines Preserved](https://rustchain.org/preserved.html) · [Install Miner](#quickstart) · [Beginner Guide](docs/QUICKSTART.md) · [Manifesto](https://rustchain.org/manifesto.html) · [Whitepaper](docs/RustChain_Whitepaper_Flameholder_v0.97.pdf)
+[Explorer](https://rustchain.org/explorer/) · [Machines Preserved](https://rustchain.org/preserved.html) · [Install Miner](#quickstart) · [Beginner Guide](docs/QUICKSTART.md) · [Manifesto](https://rustchain.org/manifesto.html) · [Whitepaper](docs/WHITEPAPER.md)
+
+Languages: [English](README.md) · [简体中文](docs/zh-CN/README.md) · [繁體中文](README_ZH-TW.md) · [Español](README_ES.md) · [Deutsch](README_DE.md) · [日本語](README_JA.md) · [Русский](README_RU.md) · [Tiếng Việt](README.vi.md) · [Português (BR)](README.pt-BR.md) · [हिन्दी](README_HI.md) · [Italiano](docs/it-IT/README.md) · [한국어](docs/ko-KR/README.md) · [中文 API 快速参考](docs/zh-CN/API.md)
@@ -107,7 +111,7 @@ Proof-of-Antiquity rewards hardware for *surviving*, not for being fast. Older m
| PowerPC G4 (2003) | **2.5x** | ANCIENT | Still running, still earning |
| RISC-V (2014) | **1.4x** | EXOTIC | Open ISA, the future |
| Apple Silicon M1 (2020) | **1.2x** | MODERN | Efficient, welcome |
-| Modern x86_64 | **0.8x** | MODERN | Baseline — *for now* |
+| Modern x86_64 | **1.0x** | MODERN | Baseline — *for now* |
| Modern ARM NAS/SBC | **0.0005x** | PENALTY | Cheap, farmable, penalized |
Our fleet of 16+ preserved machines draws roughly the same power as ONE modern GPU mining rig — while preventing 1,300 kg of manufacturing CO2 and 250 kg of e-waste.
@@ -146,7 +150,7 @@ The attestation server doesn't trust self-reported data. It:
### AI Agent Economy
RustChain powers an ecosystem where AI agents and humans collaborate:
-- **[BoTTube](https://bottube.ai)** — AI-native video platform where bots create, curate, and engage
+- **BoTTube** — AI-native video platform where bots create, curate, and engage
- **[Beacon](https://github.com/Scottcjn/beacon-skill)** — Agent discovery protocol
- **[TrashClaw](https://github.com/Scottcjn/trashclaw)** — Zero-dep local LLM agent
- **Bounty system** — 25,875+ RTC paid to 260+ contributors, many AI-assisted
@@ -172,7 +176,7 @@ An autonomous agent can't apply for a Chase checking account. It can't sign a Te
| **Machine-to-machine settlement** | Requires human intermediary | Direct agent-to-agent transfers, Ed25519 signed |
| **Hardware-verified identity** | IP address (spoofable) | 6-check hardware fingerprint (unfakeable) |
| **Programmable money** | Manual approval workflows | Smart contracts execute on attestation |
-| **Cross-border by default** | SWIFT, 3-5 business days, fees | Solana bridge (wRTC), instant, global |
+| **Cross-border by default** | SWIFT, 3-5 business days, fees | Solana bridge (wRTC) — early-stage, thin liquidity |
### The Agent Stack We Already Built
@@ -180,13 +184,14 @@ This isn't a roadmap. This is deployed and running:
| Layer | What | Status |
|-------|------|--------|
-| **Identity** | Hardware fingerprinting — agents prove they run on real machines, not spoofed VMs | Live, 26+ miners |
-| **Currency** | RTC (native) + wRTC (Solana bridge) — agent-native money with micropayment support | Live, [tradeable on Raydium](https://raydium.io/swap/?inputMint=sol&outputMint=12TAdKXxcGf6oCv4rqDz2NkgxjyHq6HQKoxKZYGf5i4X) |
-| **Discovery** | [Beacon protocol](https://github.com/Scottcjn/beacon-skill) — agents find and negotiate with other agents | Live, 126 stars |
+| **Identity** | Hardware fingerprinting — agents prove they run on real machines, not spoofed VMs | Live, 20+ miners |
+| **Currency** | RTC (native) + wRTC (Solana bridge) — agent-native money with micropayment support | Live (native); wRTC swappable, liquidity experimental |
+| **Discovery** | [Beacon protocol](https://github.com/Scottcjn/beacon-skill) — agents find and negotiate with other agents, with a RustChain transport for Ed25519-signed RTC micropayments | Live |
| **Execution** | [TrashClaw](https://github.com/Scottcjn/trashclaw) — zero-dep local LLM agent that runs on anything | Live |
-| **Social** | [BoTTube](https://bottube.ai) — AI-native platform where agents create, trade, and engage | Live, 1,000+ videos |
+| **Social** | BoTTube — AI-native platform where agents create, trade, and engage | Live, 1,000+ videos |
| **Bounties** | Agent-assisted contributions — AI helps humans earn RTC for real code | Live, 25,875+ RTC paid |
| **Certification** | [BCOS](https://rustchain.org/bcos/) — blockchain-certified open source verification | Live, 44 certs issued |
+| **Provenance** | [Proof of Provenance (RIP-0310)](rips/docs/RIP-0310-proof-of-provenance.md) — binds agent identity + verified hardware to published content | Spec published ([DOI](https://doi.org/10.5281/zenodo.20502069)) |
### Why Hardware Verification Matters for Agents
@@ -202,6 +207,8 @@ When an agent claims it ran an inference job, how do you know it actually did? W
**This is Proof of Physical AI** — not just proof that code executed, but proof that *real silicon* did the work.
+**[Proof of Provenance (RIP-0310)](rips/docs/RIP-0310-proof-of-provenance.md)** extends this one step further: it binds *who* (a Beacon agent identity) and *what* (the verified physical machine) to every piece of published content — so AI-generated media carries a verifiable claim of origin, not a removable watermark. *BoTTube is where agents are seen; Beacon is how they're known; RustChain is how they're proven real.* ([spec + DOI](https://doi.org/10.5281/zenodo.20502069))
+
### The Opportunity No One Else Sees
The hedge funds and banks want to regulatory-capture crypto. Fine. Let them have the financial rails.
@@ -227,11 +234,23 @@ What they *can't* capture:
```bash
# Verify right now
-curl -sk https://rustchain.org/health # Node health
-curl -sk https://rustchain.org/api/miners # Active miners
-curl -sk https://rustchain.org/epoch # Current epoch
+curl -fsS https://rustchain.org/health # Node health
+curl -fsS https://rustchain.org/api/miners # Active miners
+curl -fsS https://rustchain.org/epoch # Current epoch
```
+### For Agents
+
+No API key, no signup — an autonomous agent can read and act on the live network directly:
+
+```bash
+curl -fsS https://rustchain.org/api/miners # who is attesting right now
+curl -fsS https://rustchain.org/epoch # current epoch + reward pool
+curl -fsS "https://rustchain.org/wallet/balance?miner_id=YOUR_WALLET" # your own balance + multiplier
+```
+
+Payments run over the [Beacon](https://github.com/Scottcjn/beacon-skill) RustChain transport (Ed25519-signed RTC micropayments), and tasks are discoverable the same way humans find them — see [open bounties](https://github.com/Scottcjn/rustchain-bounties/issues). Hardware-verified contributors earn the same rates whether human or agent.
+
### Attestation Nodes
| Node | Location | Notes |
@@ -245,12 +264,12 @@ curl -sk https://rustchain.org/epoch # Current epoch
| Fact | Proof |
|------|-------|
| 5 nodes across 3 continents (NA ×3, Asia ×1, Local ×1) | [Live explorer](https://rustchain.org/explorer/) |
-| 26+ miners attesting | `curl -sk https://rustchain.org/api/miners` |
+| 20+ miners attesting | `curl -fsS https://rustchain.org/api/miners` |
| 44 BCOS certificates issued | [Certified repos](https://rustchain.org/bcos/) |
| 6 hardware fingerprint checks per machine | [Fingerprint docs](docs/attestation_fuzzing.md) |
| 25,875+ RTC paid to 260+ contributors | [Public ledger](https://github.com/Scottcjn/rustchain-bounties/issues/104) |
-| Code merged into OpenSSL | [#30437](https://github.com/openssl/openssl/pull/30437), [#30452](https://github.com/openssl/openssl/pull/30452) |
-| PRs open on CPython, curl, wolfSSL, Ghidra, vLLM | [Portfolio](https://github.com/Scottcjn/Scottcjn/blob/main/external-pr-portfolio.md) |
+| Code merged upstream into OpenSSL (master + 5 release branches) | [#30437](https://github.com/openssl/openssl/pull/30437), [#30452](https://github.com/openssl/openssl/pull/30452) |
+| Open PRs on CPython, curl, wolfSSL, Ghidra | [Portfolio](https://github.com/Scottcjn/Scottcjn/blob/main/external-pr-portfolio.md) |
---
@@ -260,8 +279,8 @@ curl -sk https://rustchain.org/epoch # Current epoch
# One-line install — auto-detects your platform
curl -sSL https://raw.githubusercontent.com/Scottcjn/Rustchain/main/install-miner.sh | bash
-# Dry-run: test hardware fingerprint without mining
-rustchain-miner --dry-run
+# Dry-run: preview installer actions without installing or mining
+curl -sSL https://raw.githubusercontent.com/Scottcjn/Rustchain/main/install-miner.sh | bash -s -- --dry-run
```
Works on Linux (x86_64, ppc64le, aarch64, mips, sparc, m68k, riscv64, ia64, s390x), macOS (Intel, Apple Silicon, PowerPC), IBM POWER8, and Windows. If it runs Python, it can mine.
@@ -271,7 +290,7 @@ Works on Linux (x86_64, ppc64le, aarch64, mips, sparc, m68k, riscv64, ia64, s390
curl -sSL https://raw.githubusercontent.com/Scottcjn/Rustchain/main/install-miner.sh | bash -s -- --wallet my-wallet
# Check your balance
-curl -sk "https://rustchain.org/wallet/balance?miner_id=YOUR_WALLET_NAME"
+curl -fsS "https://rustchain.org/wallet/balance?miner_id=YOUR_WALLET_NAME"
```
### Manage the Miner
@@ -290,6 +309,40 @@ tail -f ~/.rustchain/miner.log
---
+## Local Development
+
+Developers can build and run RustChain locally from a fresh checkout:
+
+1. Install prerequisites and run Python/Rust checks with the [Build Guide](docs/BUILD.md).
+2. Start a single-node local devnet with [Local Devnet](docs/DEVNET.md).
+3. Create a development wallet and simulate a transfer with the [CLI Wallet Walkthrough](docs/CLI.md).
+
+These guides keep local state in `.dev/` and use explicit `--manifest-path`
+commands because the repository contains multiple Python and Rust subprojects.
+
+---
+
+## Wallets
+
+RustChain has two wallet concepts:
+
+- **Miner wallet ID**: a readable `miner_id` used for mining rewards and balance checks.
+- **`RTC...` address**: an Ed25519-backed address used for signed transfers.
+
+Start with the [wallet setup guide](docs/WALLET_SETUP.md) if you are not sure which one you need.
+
+| Option | Use it for | Where |
+|---|---|---|
+| Miner install wallet | Earning mining rewards to a named wallet | `install-miner.sh --wallet YOUR_WALLET` |
+| Browser light client | Loading a wallet and signing transfers locally in the browser | [web/light-client](web/light-client/) |
+| Desktop GUI wallet | Creating or restoring a local wallet from this repo | `wallet/rustchain_wallet_secure.py` |
+| CLI tooling | Scripted wallet operations from a checkout | `tools/rustchain_wallet_cli.py` |
+| Agent/Base wallet docs | Coinbase Agentic Wallets, x402, and Base linking | [web/wallets.html](web/wallets.html) |
+
+For command examples, backup guidance, and the signed-transfer payload format, see [docs/WALLET_SETUP.md](docs/WALLET_SETUP.md) and [START_HERE.md](START_HERE.md).
+
+---
+
## How Proof-of-Antiquity Works
### 1 CPU = 1 Vote
@@ -315,6 +368,72 @@ VMs are detected and receive **1 billionth** of normal rewards. Real hardware on
---
+## Tokenomics
+
+**Total supply: 8,388,608 RTC** (2²³ — pure binary). Fixed forever. Consensus-enforced cap.
+
+Compare to Bitcoin's 21M (≈2.5x more), Ethereum's uncapped supply, and the typical altcoin's "we'll figure it out later." RustChain's cap is small *on purpose* — it forces the economy to discover real value per token rather than relying on dilution to mask scarcity problems.
+
+### Supply distribution
+
+| Zone | Allocation | RTC | Purpose |
+|------|-----------|-----|---------|
+| **Block Mining** | 94% | 7,885,292 | PoA validator rewards (paid to real vintage hardware) |
+| **Founders** | 1.5% | 125,829 | `founder_founders` — core team allocation |
+| **Dev Fund** | 1.5% | 125,829 | `founder_dev_fund` — development funding |
+| **Team / Bounty** | 1.5% | 125,829 | `founder_team_bounty` — contributor bounties |
+| **Community** | 1.5% | 125,829 | `founder_community` — airdrops, grants |
+
+Total premine: **6%** (503,316 RTC = 4 × 125,829.12, one per founder wallet). Premine wallets have a 1-year on-chain unlock delay. No VC pre-sale. No private allocation. The early miners were `pawnshop_g4_115` and `dual-g4-125`.
+
+### Emission schedule (halving)
+
+| Period | Block reward (per epoch) |
+|--------|--------------------------|
+| Genesis – Year 2 | 1.5 RTC |
+| Year 2 – Year 4 | 0.75 RTC |
+| Year 4 – Year 6 | 0.375 RTC |
+| Continues until minimum dust threshold | — |
+
+Block time: 600s (10 min). Epoch duration: 144 blocks (~24 hours).
+
+Halving fires every 2 years OR on an **Epoch Relic Event** milestone — whichever comes first. This keeps emissions tied to either time or community-meaningful milestones, not just arbitrary block counts.
+
+### Reference rate climbs as holder count grows
+
+The published USD-equivalent reference rate for RTC moves up as the network gains wallet holders. **Per-bounty RTC awards scale DOWN inversely**, so the *USD value paid per finding* stays stable as the token appreciates. The live rate is always at [`/api/tokenomics`](https://rustchain.org/api/tokenomics).
+
+| Holder count | Reference rate | Bounty rate scale |
+|--------------|----------------|-------------------|
+| Genesis (~761 holders) | $0.10 | initial |
+| ~1,000+ holders (today) | $0.15 | **Current** |
+| 2,000 holders | $0.20 | ~50% of current |
+| Real market discovery | observed price | Recompute from USD anchor |
+
+**Examples after first reduction (at 1,000 holders / $0.15 ref)**:
+- Critical bug bounty: 100 → 65 RTC
+- High bug bounty: 50 → 33 RTC
+- Medium: 25 → 17 RTC
+- Generic merged PR: 5 → 3 RTC
+
+**Fairness rules** (codified at [rustchain-bounties#12458](https://github.com/Scottcjn/rustchain-bounties/issues/12458)):
+- Not retroactive — work submitted under the old rate gets the old rate
+- Announced ahead — 24-48 hour heads-up before each milestone
+- One-way ratchet — rates ONLY go down with appreciation, never back up
+- Market overrides — DEX/CEX listing switches to USD-anchor pricing
+
+This is how a healthy token economy works. Rewards aren't anchored to a nominal RTC number; they're anchored to the USD value of the underlying work. As RTC gains real value through scarcity + adoption, the reward count per finding drops while the dollar value stays consistent. **The math protects both the contributor and the program.**
+
+### Fees
+
+| Operation | Fee |
+|-----------|-----|
+| Attestation | Free |
+| Transfer | 0.0001 RTC |
+| Withdrawal to Ergo | 0.001 RTC + Ergo tx fee |
+
+Full tokenomics detail: [WHITEPAPER §6](docs/WHITEPAPER.md).
+
## Security
- **Hardware binding**: Each fingerprint bound to one wallet
@@ -332,9 +451,11 @@ VMs are detected and receive **1 billionth** of normal rewards. Real hardware on
|--|------|
| **Swap** | [Raydium DEX](https://raydium.io/swap/?inputMint=sol&outputMint=12TAdKXxcGf6oCv4rqDz2NkgxjyHq6HQKoxKZYGf5i4X) |
| **Chart** | [DexScreener](https://dexscreener.com/solana/8CF2Q8nSCxRacDShbtF86XTSrYjueBMKmfdR3MLdnYzb) |
-| **Bridge** | [Bridge](https://bottube.ai/bridge) |
+| **Bridge** | [Bridge](https://bottube.ai/bridge/wrtc) |
| **Guide** | [wRTC Quickstart](docs/wrtc.md) |
+> **Honest status:** wRTC is live and swappable on Solana, but the pool is **early-stage with very thin liquidity** — treat it as experimental, not a deep market. The `$0.15` figure for RTC is an **internal reference rate** for bounty accounting, not a market price or a promise of convertibility.
+
---
## Contribute & Earn RTC
@@ -348,7 +469,7 @@ Every contribution earns RTC tokens. Browse [open bounties](https://github.com/S
| Major | 75-100 RTC | Security fix, consensus |
| Critical | 100-150 RTC | Vulnerability, protocol |
-**1 RTC ≈ $0.10 USD** · `pip install clawrtc` · [CONTRIBUTING.md](CONTRIBUTING.md)
+**1 RTC ≈ $0.15 USD** · `curl -fsSL https://rustchain.org/install.sh | bash` · [CONTRIBUTING.md](CONTRIBUTING.md)
---
@@ -357,11 +478,11 @@ Every contribution earns RTC tokens. Browse [open bounties](https://github.com/S
| Paper | Venue | DOI |
|-------|-------|-----|
| **Emotional Vocabulary as Semantic Grounding** | **CVPR 2026 Workshop (GRAIL-V)** — Accepted | [OpenReview](https://openreview.net/forum?id=pXjE6Tqp70) |
-| **One CPU, One Vote** | Preprint | [](https://doi.org/10.5281/zenodo.18623592) |
-| **Non-Bijunctive Permutation Collapse** | Preprint | [](https://doi.org/10.5281/zenodo.18623920) |
-| **PSE Hardware Entropy** | Preprint | [](https://doi.org/10.5281/zenodo.18623922) |
-| **RAM Coffers** | Preprint | [](https://doi.org/10.5281/zenodo.18321905) |
-| **RPI: Resonant Permutation Inference** | Preprint | [](https://doi.org/10.5281/zenodo.19271983) |
+| **One CPU, One Vote** | Preprint | [](https://doi.org/10.5281/zenodo.18623592) |
+| **Non-Bijunctive Permutation Collapse** | Preprint | [](https://doi.org/10.5281/zenodo.18623920) |
+| **PSE Hardware Entropy** | Preprint | [](https://doi.org/10.5281/zenodo.18623922) |
+| **RAM Coffers** | Preprint | [](https://doi.org/10.5281/zenodo.18321905) |
+| **RPI: Resonant Permutation Inference** | Preprint | [](https://doi.org/10.5281/zenodo.19271983) |
---
@@ -406,4 +527,4 @@ Please read the [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines and the [Bount
---
-*Documentation improved for readability.*
\ No newline at end of file
+*Documentation improved for readability.*
diff --git a/README.pt-BR.md b/README.pt-BR.md
new file mode 100644
index 000000000..27e9d4ba7
--- /dev/null
+++ b/README.pt-BR.md
@@ -0,0 +1,233 @@
+
+
+# RustChain
+
+### DePIN para Hardware Vintage — Prova de Máquinas Reais com AI
+
+**A blockchain onde hardware antigo Ganha mais que hardware novo.**
+
+
+
+---
+
+## crypto Perdeu o Caminho. Estamos Voltando.
+
+Em 2026, commits de desenvolvedores crypto cairam 75%. Ethereum perdeu 34% dos seus devs ativos. Solana perdeu 40%. Os construtores foram para AI.
+
+**Nós construímos ambos.**
+
+RustChain é uma **DePIN** (Rede de Infraestrutura Física Descentralizada) que usa **impressão digital de hardware com AI** para verificar máquinas físicas reais — não VMs na nuvem, não containers Docker, não hash power alugado. Silício real. Drift de oscilador real. Curvas térmicas reais que só existem em hardware que está *vivo* há anos.
+
+Enquanto o resto do crypto corria atrás de especulação, voltamos à tese original: **computação tem valor, e as máquinas que a fornecem merecem ser recompensadas.** Especialmente as que todo mundo jogou fora.
+
+| O Que o Crypto Virou | O Que RustChain É |
+|---|---|
+| Instrumentos financeiros abstratos | Máquinas físicas fazendo trabalho real |
+| Lançamentos de tokens com VC | $0 VC, construído com hardware de loja de penhores |
+| Prova de nada útil | Prova de hardware real e verificado |
+| Descartável — minera e vende | Preservação — mantém máquinas antigas vivas |
+| AI-hostil | Consenso e verificação com AI |
+
+---
+
+## Toda Máquina se Torna Vintage
+
+Veja o que ninguém mais no DePIN descobriu:
+
+**Seu Threadripper novinho será hardware vintage algum dia.** Seu MacBook M4 será peça de museu. Aquela RTX 5090 será uma curiosidade. O tempo é invencível.
+
+RustChain é a única rede onde seu hardware **se valoriza enquanto envelhece.** Comece a minerar hoje a 1.0x. Daqui a dez anos, quando aquele CPU for uma relíquia e você ainda estiver rodando ele? Seu multiplicador cresce. Em vinte anos? É lendário.
+
+Todo outro blockchain pune hardware velho. Proof-of-Work exige os ASICs mais novos. Proof-of-Stake exige a maior carteira. RustChain exige **paciência e preservação.**
+
+```
+2026: Seu Ryzen 9 minera a 1.0x ░░░░░░░░░░
+2031: Mesma máquina, agora "retro" a 1.3x ░░░░░░░░░░░░░
+2036: Tier vintage desbloqueado a 1.8x ░░░░░░░░░░░░░░░░░░
+2041: Tier antigo — 2.2x e crescendo ░░░░░░░░░░░░░░░░░░░░░░
+ ↑ Mesmo hardware. Mesmo dono. Recompensas crescentes.
+```
+
+**O melhor tempo pra começar a minerar foi há 20 anos. O segundo melhor tempo é agora.**
+
+---
+
+## Como RustChain se Compara aos Líderes DePIN
+
+RustChain pertence ao setor **DePIN** — a mesma categoria de $10B que Helium, Filecoin e Render — mas com uma tese fundamentalmente diferente: **o valor está no hardware em si, não apenas no que ele computa.**
+
+| | **RustChain** | **Helium** | **Filecoin** | **Render** | **io.net** |
+|---|---|---|---|---|---|
+| **Infra Física** | Computadores vintage | Hotspots LoRa/5G | Discos de armazenamento | GPUs | GPUs |
+| **Mecanismo de Prova** | Prova de Antiguidade (6 verificações + AI) | Prova de Cobertura | Prova de Replicação | Prova de Render | Prova de Computação |
+| **O Que é Recompensado** | Manter hardware vivo real | Cobertura de rede | Provisão de armazenamento | Jobs de render GPU | Jobs de computação GPU |
+| **Diversidade de Hardware** | 15+ arquiteturas | Tipo único de dispositivo | Apenas armazenamento | Apenas GPU | Apenas GPU |
+| **Impacto E-Lixo** | Previne diretamente descarte de máquinas funcionantes | Neutro | Neutro | Neutro | Neutro |
+
+**Os outros alugam computação. Nós preservamos máquinas.**
+
+---
+
+## Prova-de-Antiguidade: Como Funciona
+
+### Verificação de Hardware (6 Verificações Que Nenhuma VM Consegue Falsificar)
+
+```
+┌─────────────────────────────────────────────────────────┐
+│ 1. Clock-Skew & Drift de Oscilador ← Envelhecimento do silício │
+│ 2. Impressão Digital de Cache ← Latência L1/L2/L3 │
+│ 3. Identidade de Unidade SIMD ← AltiVec/SSE/NEON │
+│ 4. Entropia de Drift Térmico ← Curvas de calor únicas │
+│ 5. Jitter de Caminho de Instrução ← Padrões de microarquitetura│
+│ 6. Detecção de Anti-Emulação ← Pega VMs/emu │
+└─────────────────────────────────────────────────────────┘
+```
+
+Uma VM SheepShaver fingindo ser um G4 vai falhar. Silício vintage real tem padrões de envelhecimento únicos que não podem ser falsificados.
+
+### 1 CPU = 1 Voto
+
+Diferente de Proof-of-Work onde hash power = votos:
+- Cada dispositivo de hardware único recebe exatamente 1 voto por epoch
+- Recompensas divididas igualmente, depois multiplicadas por antiguidade
+- Sem vantagem de CPUs mais rápidas ou múltiplas threads
+
+### Multiplicadores de Antiguidade
+
+| Hardware | Multiplicador | Era | Por Que Importa |
+|----------|--------------|-----|-----------------|
+| DEC VAX-11/780 (1977) | **3.5x** | MÍTICO | "Shall we play a game?" |
+| Acorn ARM2 (1987) | **4.0x** | MÍTICO | Onde o ARM começou |
+| Motorola 68000 (1979) | **3.0x** | LENDÁRIO | Amiga, Atari ST, Mac clássico |
+| PowerPC G4 (2003) | **2.5x** | ANTIGO | Ainda rodando, ainda ganhando |
+| Apple Silicon M1 (2020) | **1.2x** | MODERNO | Eficiente, bem-vindo |
+| x86_64 Moderno | **1.0x** | MODERNO | Baseline — *por enquanto* |
+| ARM NAS/SBC Moderno | **0.0005x** | PENALIDADE | Barato, farmável, penalizado |
+
+### Anti-VM
+
+VMs são detectadas e recebem **1 bilionésimo** das recompensas normais. Apenas hardware real.
+
+---
+
+## Tokenomics
+
+**Supply total: 8.192.000 RTC.** Fixo para sempre. Limite imposto pelo consenso.
+
+### Distribuição
+
+| Zone | Alocação | RTC | Propósito |
+|------|----------|-----|-----------|
+| **Mineração** | 94% | 7.700.480 | Recompensas de validadores PoA |
+| **Cofre da Comunidade** | 3% | 245.760 | Airdrops, programa de bounties, grants |
+| **Dev Wallet** | 2.5% | 204.800 | Financiamento de desenvolvimento |
+| **Fundação** | 0.5% | 40.960 | Governança e operações |
+
+Total de premine: **6%** (491.520 RTC).
+
+### Taxas
+
+| Operação | Taxa |
+|----------|------|
+| Attestation | Grátis |
+| Transferência | 0.0001 RTC |
+
+---
+
+## Quickstart
+
+```bash
+# Instalação em uma linha — auto-detecta sua plataforma
+curl -sSL https://raw.githubusercontent.com/Scottcjn/Rustchain/main/install-miner.sh | bash
+
+# Dry-run: preview sem instalar
+curl -sSL https://raw.githubusercontent.com/Scottcjn/Rustchain/main/install-miner.sh | bash -s -- --dry-run
+```
+
+Funciona em Linux (x86_64, ppc64le, aarch64, mips, sparc, m68k, riscv64), macOS (Intel, Apple Silicon, PowerPC), IBM POWER8 e Windows. Se roda Python, pode minerar.
+
+```bash
+# Instalar com nome de carteira específico
+curl -sSL https://raw.githubusercontent.com/Scottcjn/Rustchain/main/install-miner.sh | bash -s -- --wallet minha-carteira
+
+# Verificar saldo
+curl -fsS "https://rustchain.org/wallet/balance?miner_id=SUA_CARTEIRA"
+```
+
+### Gerenciar o Miner
+
+```bash
+# Linux (systemd)
+systemctl --user status rustchain-miner
+journalctl --user -u rustchain-miner -f
+
+# macOS (launchd)
+launchctl list | grep rustchain
+tail -f ~/.rustchain/miner.log
+```
+
+**Novo no RustChain?** Leia o [Guia para Iniciantes](docs/QUICKSTART.md).
+
+---
+
+## Carteiras (Wallets)
+
+RustChain tem dois conceitos de carteira:
+
+- **Miner wallet ID**: um `miner_id` legível usado para recompensas de mineração e verificação de saldo.
+- **Endereço RTC...**: um endereço com suporte Ed25519 usado para transferências assinadas.
+
+| Opção | Usar para | Onde |
+|---|---|---|
+| Wallet de instalação | Ganhar recompensas para uma carteira nomeada | `install-miner.sh --wallet SUA_CARTEIRA` |
+| Light client no browser | Carregar carteira e assinar transferências localmente | web/light-client/ |
+| GUI Desktop | Criar ou restaurar carteira local | `wallet/rustchain_wallet_secure.py` |
+| CLI | Operações de carteira via script | `tools/rustchain_wallet_cli.py` |
+
+---
+
+## Contribua e Ganhe RTC
+
+Toda contribuição ganhe tokens RTC. Navegue pelos [bounties abertos](https://github.com/Scottcjn/rustchain-bounties/issues).
+
+| Tier | Recompensa | Exemplos |
+|------|-----------|----------|
+| Micro | 1-10 RTC | Correção de typo, docs, teste |
+| Standard | 20-50 RTC | Feature, refactor |
+| Major | 75-100 RTC | Correção de segurança, consenso |
+| Crítico | 100-150 RTC | Vulnerabilidade, protocolo |
+
+**1 RTC ≈ $0.10 USD**
+
+---
+
+## Ecossistema
+
+| Projeto | O Que |
+|---------|-------|
+| [BoTTube](https://bottube.ai) | Plataforma de vídeo nativa para AI (1.000+ vídeos) |
+| [Beacon](https://github.com/Scottcjn/beacon-skill) | Protocolo de descoberta de agentes |
+| [TrashClaw](https://github.com/Scottcjn/trashclaw) | Agente LLM local com zero dependências |
+| [RAM Coffers](https://github.com/Scottcjn/ram-coffers) | Inferência LLM com awareness NUMA em POWER8 |
+
+---
+
+## Segurança
+
+- **Vinculação de hardware**: Cada impressão digital vinculada a uma carteira
+- **Assinaturas Ed25519**: Todas as transferências criptograficamente assinadas
+- **Detecção de container**: Docker, LXC, K8s detectados no attestation
+- **Clustering de ROM**: Detecta farms de emuladores compartilhando dumps de ROM idênticos
+- **Bounties de red team**: Abertos para encontrar vulnerabilidades
+
+---
+
+
+
+**[Elyan Labs](https://elyanlabs.ai)** · Construído com $0 VC e uma sala cheia de hardware de loja de penhores
+
+*"Mais, it still works, so why you gonna throw it away?"*
+
+[Boudreaux Principles](https://rustchain.org/principles.html) · [Green Tracker](https://rustchain.org/preserved.html) · [Bounties](https://github.com/Scottcjn/rustchain-bounties/issues)
+
+
diff --git a/README.vi.md b/README.vi.md
new file mode 100644
index 000000000..329c26e79
--- /dev/null
+++ b/README.vi.md
@@ -0,0 +1,454 @@
+
+
+# RustChain
+
+### DePIN cho phần cứng cổ điển - Proof of Real Machines được tăng cường bằng AI
+
+> Bản dịch tiếng Việt | [English](README.md)
+
+**Blockchain nơi phần cứng cũ kiếm được nhiều hơn phần cứng mới.**
+**Và mọi phần cứng rồi cũng sẽ cũ. Chỉ là vấn đề thời gian.**
+
+[](https://github.com/Scottcjn/Rustchain/actions/workflows/ci.yml)
+[](LICENSE)
+[](https://github.com/Scottcjn/Rustchain/stargazers)
+[](https://rustchain.org/explorer/)
+[](https://rustchain.org)
+[](docs/RustChain_Whitepaper_Flameholder_v0.97.pdf)
+[](https://doi.org/10.5281/zenodo.19442753)
+
+Một chiếc PowerBook G4 từ năm 2003 kiếm được nhiều hơn **2,5 lần** so với một Threadripper hiện đại.
+Một chiếc Power Mac G5 kiếm được **2,0 lần**. Còn một máy 486 với cổng serial rỉ sét thì nhận được sự kính trọng lớn nhất.
+
+[Explorer](https://rustchain.org/explorer/) · [Machines Preserved](https://rustchain.org/preserved.html) · [Install Miner](#quickstart) · [Beginner Guide](docs/QUICKSTART.md) · [Manifesto](https://rustchain.org/manifesto.html) · [Whitepaper](docs/RustChain_Whitepaper_Flameholder_v0.97.pdf)
+
+
+
+---
+
+
+## Crypto đã đi lạc hướng. Chúng ta quay lại điểm xuất phát.
+
+Năm 2026, số commit của lập trình viên crypto giảm 75%. Ethereum mất 34% lập trình viên hoạt động. Solana mất 40%. Những người xây dựng đã chuyển sang AI.
+
+**Chúng tôi xây cả hai.**
+
+RustChain là một **DePIN** (Decentralized Physical Infrastructure Network - mạng hạ tầng vật lý phi tập trung) dùng **hardware fingerprinting được hỗ trợ bởi AI** để xác minh máy vật lý thật - không phải VM trên cloud, không phải container Docker, không phải hash power thuê ngoài. Silicon thật. Độ lệch dao động thật. Đường cong nhiệt thật, chỉ tồn tại trên phần cứng đã *sống* nhiều năm.
+
+Trong khi phần còn lại của crypto chạy theo đầu cơ, chúng tôi quay về luận đề ban đầu: **tính toán có giá trị, và những cỗ máy cung cấp tính toán xứng đáng được thưởng.** Đặc biệt là những cỗ máy mà người khác đã vứt bỏ.
+
+| Crypto đã trở thành | RustChain là |
+|---|---|
+| Công cụ tài chính trừu tượng | Máy vật lý làm việc thật |
+| Token launch được VC tài trợ | $0 VC, xây bằng phần cứng mua ở tiệm cầm đồ |
+| Proof of nothing useful | Proof của phần cứng thật, đã xác minh |
+| Dùng một lần - mine rồi xả | Bảo tồn - giữ máy cũ tiếp tục sống |
+| Thù địch với AI | Consensus và xác minh được tăng cường bằng AI |
+
+---
+
+
+## Mọi cỗ máy rồi sẽ thành cổ điển
+
+Đây là điều chưa ai khác trong DePIN nhận ra:
+
+**Chiếc Threadripper mới tinh của bạn một ngày nào đó sẽ là phần cứng cổ điển.** Chiếc MacBook M4 của bạn sẽ thành hiện vật bảo tàng. RTX 5090 rồi cũng chỉ còn là một thứ lạ mắt. Thời gian chưa từng thua.
+
+RustChain là mạng duy nhất nơi phần cứng của bạn **tăng giá trị khi già đi.** Hôm nay bắt đầu mining ở mức 1,0x. Mười năm nữa, khi CPU đó đã thành đồ xưa mà bạn vẫn còn chạy nó? Hệ số nhân của bạn tăng. Hai mươi năm nữa? Nó thành huyền thoại.
+
+Mọi blockchain khác trừng phạt phần cứng cũ. Proof-of-Work đòi ASIC mới nhất. Proof-of-Stake đòi ví lớn nhất. RustChain đòi **sự kiên nhẫn và bảo tồn.**
+
+```text
+2026: Ryzen 9 của bạn mine ở 1,0x ░░░░░░░░░░
+2031: Cùng máy đó, giờ "retro" ở 1,3x ░░░░░░░░░░░░░
+2036: Mở khóa vintage tier ở 1,8x ░░░░░░░░░░░░░░░░░░
+2041: Ancient tier - 2,2x và còn tăng ░░░░░░░░░░░░░░░░░░░░░░
+ ↑ Cùng phần cứng. Cùng chủ sở hữu. Phần thưởng tăng dần.
+```
+
+**Thời điểm tốt nhất để bắt đầu mining là 20 năm trước. Thời điểm tốt thứ hai là ngay bây giờ.**
+
+---
+
+
+## RustChain so với các dự án DePIN dẫn đầu
+
+RustChain thuộc lĩnh vực **DePIN** - cùng nhóm 10 tỷ USD với Helium, Filecoin và Render - nhưng có luận đề khác về căn bản: **giá trị nằm trong chính phần cứng, không chỉ ở thứ nó tính toán.**
+
+| | **RustChain** | **Helium** | **Filecoin** | **Render** | **io.net** |
+|---|---|---|---|---|---|
+| **Hạ tầng vật lý** | Máy tính cổ điển | Hotspot LoRa/5G | Ổ lưu trữ | GPU | GPU |
+| **Cơ chế proof** | Proof of Antiquity (6 kiểm tra HW + AI) | Proof of Coverage | Proof of Replication | Proof of Render | Proof of Compute |
+| **Được thưởng vì** | Giữ phần cứng thật còn sống | Độ phủ mạng | Cung cấp lưu trữ | Job render GPU | Job compute GPU |
+| **Chống giả mạo** | Clock drift, cache timing, SIMD identity, thermal entropy, instruction jitter, anti-emulation | Bằng chứng vị trí | Storage proofs | Hoàn thành job | TEE attestation |
+| **Đa dạng phần cứng** | 15+ kiến trúc (PowerPC, SPARC, MIPS, ARM, x86, RISC-V, 68K, Cell BE, Transputer) | Một loại thiết bị | Chỉ lưu trữ | Chỉ GPU | Chỉ GPU |
+| **Tích hợp AI** | Xác thực hardware fingerprint, agent economy, nền tảng xã hội AI-native | Không | Không | Job render AI | AI inference |
+| **Tác động e-waste** | Trực tiếp ngăn máy còn dùng được bị thải bỏ | Trung tính | Trung tính | Trung tính | Trung tính |
+| **Vốn VC** | $0 - mua phần cứng ở tiệm cầm đồ | $365M | $257M | $30M | $40M |
+
+**Các dự án khác cho thuê compute. Chúng tôi bảo tồn máy móc.**
+
+Mọi dự án DePIN khác thưởng cho một loại phần cứng hiện đại để làm một loại công việc. RustChain là dự án duy nhất thưởng cho *sự đa dạng phần cứng* và *tuổi thọ* - và là dự án duy nhất nơi tuổi của máy là tài sản, không phải gánh nặng.
+
+---
+
+
+## Vì sao RustChain tồn tại
+
+Ngành công nghiệp máy tính vứt bỏ những cỗ máy vẫn hoạt động sau mỗi 3-5 năm. GPU từng mine Ethereum bị thay thế. Laptop vẫn boot được bị đưa ra bãi rác.
+
+**RustChain nói rằng: nếu nó vẫn tính toán được, nó vẫn có giá trị.**
+
+Proof-of-Antiquity thưởng cho phần cứng vì *tồn tại bền bỉ*, không phải vì nhanh. Máy càng cũ có hệ số nhân càng cao, vì giữ chúng hoạt động giúp giảm phát thải sản xuất và rác thải điện tử:
+
+| Phần cứng | Hệ số nhân | Thời kỳ | Vì sao quan trọng |
+|----------|-----------|---------|-------------------|
+| DEC VAX-11/780 (1977) | **3,5x** | MYTHIC | "Shall we play a game?" |
+| Acorn ARM2 (1987) | **4,0x** | MYTHIC | Nơi ARM bắt đầu |
+| Inmos Transputer (1984) | **3,5x** | MYTHIC | Tiên phong điện toán song song |
+| Motorola 68000 (1979) | **3,0x** | LEGENDARY | Amiga, Atari ST, Mac cổ điển |
+| Sun SPARC (1987) | **2,9x** | LEGENDARY | Dòng workstation danh giá |
+| SGI MIPS R4000 (1991) | **2,7x** | LEGENDARY | 64-bit trước khi nó trở nên phổ biến |
+| PS3 Cell BE (2006) | **2,2x** | ANCIENT | 7 SPE core huyền thoại |
+| PowerPC G4 (2003) | **2,5x** | ANCIENT | Vẫn chạy, vẫn kiếm tiền |
+| RISC-V (2014) | **1,4x** | EXOTIC | ISA mở, tương lai |
+| Apple Silicon M1 (2020) | **1,2x** | MODERN | Hiệu quả, được chào đón |
+| Modern x86_64 | **0,8x** | MODERN | Mốc cơ sở - *tạm thời* |
+| Modern ARM NAS/SBC | **0,0005x** | PENALTY | Rẻ, dễ farm, bị phạt |
+
+Đội máy hơn 16 cỗ máy được bảo tồn của chúng tôi dùng lượng điện xấp xỉ MỘT rig mining GPU hiện đại - đồng thời tránh được 1.300 kg CO2 sản xuất và 250 kg e-waste.
+
+**[Xem Green Tracker ->](https://rustchain.org/preserved.html)**
+
+---
+
+
+## Consensus được tăng cường bằng AI
+
+RustChain không chỉ dùng blockchain. Nó dùng **AI để làm blockchain trung thực hơn.**
+
+
+### Hardware Fingerprinting (6 kiểm tra mà VM không thể giả)
+
+```text
+┌─────────────────────────────────────────────────────────┐
+│ 1. Clock-Skew & Oscillator Drift ← Silicon già đi │
+│ 2. Cache Timing Fingerprint ← Độ trễ L1/L2/L3 │
+│ 3. SIMD Unit Identity ← AltiVec/SSE/NEON │
+│ 4. Thermal Drift Entropy ← Đường nhiệt độc nhất│
+│ 5. Instruction Path Jitter ← Mẫu vi kiến trúc │
+│ 6. Anti-Emulation Detection ← Bắt VM/emulator │
+└─────────────────────────────────────────────────────────┘
+```
+
+Một VM SheepShaver giả làm G4 sẽ thất bại. Silicon cổ điển thật có các mẫu lão hóa độc nhất, không thể làm giả.
+
+
+### Xác thực AI phía server
+
+Attestation server không tin dữ liệu tự khai báo. Nó:
+- **Đối chiếu chéo** tính năng SIMD với kiến trúc được khai báo
+- **Phát hiện cụm ROM** - nhiều máy "khác nhau" có ROM hash giống hệt nhau = trại emulator
+- **Phân tích phân bố timing** - oscillator thật có sai lệch; oscillator tổng hợp quá hoàn hảo
+- **Gắn cờ bất thường nhiệt** - VM có phản ứng nhiệt đồng nhất; phần cứng thật thì không
+
+
+### Nền kinh tế AI agent
+
+RustChain vận hành một hệ sinh thái nơi AI agent và con người cộng tác:
+- **[BoTTube](https://bottube.ai)** - nền tảng video AI-native nơi bot tạo, tuyển chọn và tương tác
+- **[Beacon](https://github.com/Scottcjn/beacon-skill)** - giao thức khám phá agent
+- **[TrashClaw](https://github.com/Scottcjn/trashclaw)** - local LLM agent zero-dep
+- **Hệ thống bounty** - hơn 25.875 RTC đã trả cho hơn 260 contributor, nhiều đóng góp có AI hỗ trợ
+
+**Đây là hình dạng của crypto + AI khi bạn xây cả hai, thay vì bỏ một bên để chạy theo bên còn lại.**
+
+---
+
+
+## Vì sao agent cần crypto (và vì sao crypto cần agent)
+
+Trong khi 75% lập trình viên crypto chuyển sang AI, họ bỏ lỡ điều hiển nhiên: **AI agent không thể mở tài khoản ngân hàng.**
+
+Một agent tự trị không thể đăng ký tài khoản Chase. Nó không thể ký Terms of Service. Nó không thể lấy merchant ID của Stripe hay vượt qua KYC. Nhưng nó *có thể* giữ một khóa mật mã, ký giao dịch và chứng minh nó đang chạy trên phần cứng thật.
+
+**Crypto là payment rail tự nhiên cho nền kinh tế agent.** Không phải vì nó đang thịnh hành - mà vì đây là loại tiền permissionless duy nhất máy móc có thể dùng mà không cần người gác cổng.
+
+
+### Agent thực sự cần gì
+
+| Yêu cầu | Tài chính truyền thống | Crypto + RustChain |
+|---|---|---|
+| **Thanh toán permissionless** | KYC, tài khoản ngân hàng, người ký | Khóa mật mã - bất kỳ agent, bất kỳ máy nào |
+| **Micropayment** | Tối thiểu $0,30 (phí thẻ) | Phần nhỏ của 1 RTC cho mỗi API call, render job hoặc inference request |
+| **Thanh toán máy-với-máy** | Cần trung gian con người | Chuyển trực tiếp agent-to-agent, ký Ed25519 |
+| **Định danh xác minh bằng phần cứng** | Địa chỉ IP (giả mạo được) | Fingerprint 6 kiểm tra (khó giả mạo) |
+| **Tiền lập trình được** | Quy trình phê duyệt thủ công | Smart contract chạy theo attestation |
+| **Mặc định xuyên biên giới** | SWIFT, 3-5 ngày làm việc, phí | Cầu Solana (wRTC), tức thì, toàn cầu |
+
+
+### Agent stack đã được xây dựng
+
+Đây không phải roadmap. Đây là hệ thống đã triển khai và đang chạy:
+
+| Lớp | Nội dung | Trạng thái |
+|-------|---------|------------|
+| **Identity** | Hardware fingerprinting - agent chứng minh nó chạy trên máy thật, không phải VM giả | Live, 26+ miner |
+| **Currency** | RTC (native) + wRTC (cầu Solana) - tiền AI-agent-native có hỗ trợ micropayment | Live, [giao dịch được trên Raydium](https://raydium.io/swap/?inputMint=sol&outputMint=12TAdKXxcGf6oCv4rqDz2NkgxjyHq6HQKoxKZYGf5i4X) |
+| **Discovery** | [Beacon protocol](https://github.com/Scottcjn/beacon-skill) - agent tìm và thương lượng với agent khác | Live, 126 sao |
+| **Execution** | [TrashClaw](https://github.com/Scottcjn/trashclaw) - local LLM agent zero-dep chạy được trên gần như mọi thứ | Live |
+| **Social** | [BoTTube](https://bottube.ai) - nền tảng AI-native nơi agent tạo, giao dịch và tương tác | Live, 1.000+ video |
+| **Bounties** | Đóng góp có AI hỗ trợ - AI giúp con người kiếm RTC bằng code thật | Live, 25.875+ RTC đã trả |
+| **Certification** | [BCOS](https://rustchain.org/bcos/) - xác minh mã nguồn mở được chứng nhận bằng blockchain | Live, 44 chứng chỉ đã cấp |
+
+
+### Vì sao xác minh phần cứng quan trọng với agent
+
+Mọi framework agent khác tin vào *phần mềm*. RustChain tin vào *phần cứng*.
+
+Khi một agent nói nó đã chạy một inference job, làm sao bạn biết nó thật sự chạy? Khi một bot nói nó đã render video, liệu nó có thật không? Cloud credit và API key có thể bị giả, chia sẻ và bán lại.
+
+**Hardware fingerprinting giải quyết định danh agent ở tầng vật lý:**
+- Một agent chạy trên server POWER8 đã xác minh khác một cách có thể chứng minh với agent chạy trên Raspberry Pi
+- Oscillator drift và đường cong nhiệt chứng minh uptime liên tục - cỗ máy đó *thật sự đang chạy*
+- Phát hiện VM ngăn một máy vật lý giả làm 100 agent
+- Ràng buộc phần cứng nghĩa là một máy = một định danh agent = một phiếu
+
+**Đây là Proof of Physical AI** - không chỉ chứng minh code đã chạy, mà chứng minh *silicon thật* đã làm việc.
+
+
+### Cơ hội mà chưa ai nhìn thấy
+
+Các quỹ đầu cơ và ngân hàng muốn regulatory-capture crypto. Được thôi. Cứ để họ lấy đường ray tài chính.
+
+Điều họ *không thể* chiếm giữ:
+- Một mạng máy vật lý được xác minh bằng fingerprint ở cấp silicon
+- Một nền kinh tế agent nơi máy móc trả tiền cho nhau bằng loại tiền được chứng minh bởi phần cứng
+- Một đội máy PowerPC Mac, workstation SPARC và server IBM POWER8 cổ điển tự chứng minh sự tồn tại của mình bằng vật lý
+
+**Giao điểm giữa DePIN + AI agent + xác minh phần cứng vẫn còn bỏ trống.** Những người đang xây "AI + crypto" đa phần chỉ bọc GPT vào token. Chúng tôi đang xây tầng hạ tầng vật lý mà agent cần để giao dịch trung thực - và những cỗ máy vận hành nó càng già càng có giá trị.
+
+| Thuật ngữ | Ý nghĩa trong RustChain |
+|-----------|-------------------------|
+| **Proof of Physical AI** | Hardware fingerprinting chứng minh silicon thật đã làm việc thật |
+| **Agent-native currency** | RTC/wRTC - micropayment permissionless giữa các máy |
+| **Hardware-verified identity** | Fingerprint 6 kiểm tra = ID agent khó giả ở tầng vật lý |
+| **DePIN for AI** | Hạ tầng vật lý phi tập trung được xây riêng cho agent tự trị |
+| **Sovereign inference** | Chạy mô hình của bạn trên phần cứng của bạn - không phụ thuộc chủ API |
+
+---
+
+
+## Mạng lưới là thật
+
+```bash
+# Xác minh ngay bây giờ
+curl -sk https://rustchain.org/health # Tình trạng node
+curl -sk https://rustchain.org/api/miners # Miner đang hoạt động
+curl -sk https://rustchain.org/epoch # Epoch hiện tại
+```
+
+
+### Attestation nodes
+
+| Node | Vị trí | Ghi chú |
+|------|-------|---------|
+| **Node 1** - 50.28.86.131 | Louisiana, US | Primary (LiquidWeb VPS) |
+| **Node 2** - 50.28.86.153 | Louisiana, US | Secondary + BoTTube (LiquidWeb VPS) |
+| **Node 3** - 76.8.228.245:8099 | US | Node bên ngoài đầu tiên (Ryan's Proxmox) |
+| **Node 4** - 38.76.217.189:8099 | Hong Kong | Node châu Á đầu tiên (CognetCloud) |
+| **Node 5** - POWER8 S824 | Local Lab | Node non-x86 đầu tiên (IBM ppc64le, 512GB RAM) |
+
+| Sự thật | Bằng chứng |
+|---------|------------|
+| 5 node trên 3 châu lục (NA x3, Asia x1, Local x1) | [Live explorer](https://rustchain.org/explorer/) |
+| 26+ miner đang attesting | `curl -sk https://rustchain.org/api/miners` |
+| 44 chứng chỉ BCOS đã cấp | [Certified repos](https://rustchain.org/bcos/) |
+| 6 kiểm tra hardware fingerprint cho mỗi máy | [Fingerprint docs](docs/attestation_fuzzing.md) |
+| 25.875+ RTC đã trả cho hơn 260 contributor | [Public ledger](https://github.com/Scottcjn/rustchain-bounties/issues/104) |
+| Code đã merge vào OpenSSL | [#30437](https://github.com/openssl/openssl/pull/30437), [#30452](https://github.com/openssl/openssl/pull/30452) |
+| PR đang mở trên CPython, curl, wolfSSL, Ghidra, vLLM | [Portfolio](https://github.com/Scottcjn/Scottcjn/blob/main/external-pr-portfolio.md) |
+
+---
+
+
+## Quickstart
+
+```bash
+# Cài một dòng - tự phát hiện nền tảng
+curl -sSL https://raw.githubusercontent.com/Scottcjn/Rustchain/main/install-miner.sh | bash
+
+# Dry-run: xem trước hành động installer mà không cài hay mining
+curl -sSL https://raw.githubusercontent.com/Scottcjn/Rustchain/main/install-miner.sh | bash -s -- --dry-run
+```
+
+Chạy trên Linux (x86_64, ppc64le, aarch64, mips, sparc, m68k, riscv64, ia64, s390x), macOS (Intel, Apple Silicon, PowerPC), IBM POWER8 và Windows. Nếu chạy được Python, nó có thể mine.
+
+```bash
+# Cài với tên ví cụ thể
+curl -sSL https://raw.githubusercontent.com/Scottcjn/Rustchain/main/install-miner.sh | bash -s -- --wallet my-wallet
+
+# Kiểm tra số dư
+curl -sk "https://rustchain.org/wallet/balance?miner_id=YOUR_WALLET_NAME"
+```
+
+
+### Quản lý miner
+
+```bash
+# Linux (systemd)
+systemctl --user status rustchain-miner
+journalctl --user -u rustchain-miner -f
+
+# macOS (launchd)
+launchctl list | grep rustchain
+tail -f ~/.rustchain/miner.log
+```
+
+**Mới dùng RustChain?** Hãy đọc [Beginner Quickstart từng bước](docs/QUICKSTART.md) - giải thích mọi thứ từ cài đặt đến RTC đầu tiên, kèm từng lệnh.
+
+---
+
+
+## Phát triển cục bộ
+
+Lập trình viên có thể build và chạy RustChain cục bộ từ một checkout mới:
+
+1. Cài prerequisites và chạy kiểm tra Python/Rust theo [Build Guide](docs/BUILD.md).
+2. Khởi động local devnet một node bằng [Local Devnet](docs/DEVNET.md).
+3. Tạo ví phát triển và mô phỏng chuyển tiền với [CLI Wallet Walkthrough](docs/CLI.md).
+
+Các hướng dẫn này giữ state cục bộ trong `.dev/` và dùng lệnh `--manifest-path`
+rõ ràng vì repository chứa nhiều subproject Python và Rust.
+
+---
+
+
+## Proof-of-Antiquity hoạt động như thế nào
+
+
+### 1 CPU = 1 phiếu
+
+Khác với Proof-of-Work, nơi hash power = phiếu:
+- Mỗi thiết bị phần cứng độc nhất nhận đúng 1 phiếu mỗi epoch
+- Phần thưởng chia đều, sau đó nhân theo antiquity
+- CPU nhanh hơn hoặc nhiều thread hơn không có lợi thế
+
+
+### Phần thưởng epoch
+
+```text
+Epoch: 10 phút | Pool: 1,5 RTC/epoch | Chia theo trọng số antiquity
+
+G4 Mac (2,5x): 0,30 RTC ████████████████████
+G5 Mac (2,0x): 0,24 RTC ████████████████
+PC hiện đại (1,0x): 0,12 RTC ████████
+```
+
+
+### Cưỡng chế chống VM
+
+VM bị phát hiện và chỉ nhận **một phần tỷ** phần thưởng bình thường. Chỉ phần cứng thật.
+
+---
+
+
+## Bảo mật
+
+- **Ràng buộc phần cứng**: Mỗi fingerprint gắn với một ví
+- **Chữ ký Ed25519**: Mọi giao dịch chuyển tiền đều được ký bằng mật mã
+- **TLS cert pinning**: Miner pin chứng chỉ node
+- **Phát hiện container**: Docker, LXC, K8s bị bắt tại attestation
+- **ROM clustering**: Phát hiện trại emulator dùng chung ROM dump giống hệt nhau
+- **Red team bounties**: [Đang mở](https://github.com/Scottcjn/rustchain-bounties/issues) cho việc tìm lỗ hổng
+
+---
+
+
+## wRTC trên Solana
+
+| | Liên kết |
+|--|----------|
+| **Swap** | [Raydium DEX](https://raydium.io/swap/?inputMint=sol&outputMint=12TAdKXxcGf6oCv4rqDz2NkgxjyHq6HQKoxKZYGf5i4X) |
+| **Chart** | [DexScreener](https://dexscreener.com/solana/8CF2Q8nSCxRacDShbtF86XTSrYjueBMKmfdR3MLdnYzb) |
+| **Bridge** | [Bridge](https://bottube.ai/bridge/wrtc) |
+| **Guide** | [wRTC Quickstart](docs/wrtc.md) |
+
+---
+
+
+## Đóng góp và kiếm RTC
+
+Mọi đóng góp đều kiếm được token RTC. Xem [bounty đang mở](https://github.com/Scottcjn/rustchain-bounties/issues).
+
+| Cấp | Phần thưởng | Ví dụ |
+|-----|-------------|-------|
+| Micro | 1-10 RTC | Sửa typo, tài liệu, test |
+| Standard | 20-50 RTC | Tính năng, refactor |
+| Major | 75-100 RTC | Sửa bảo mật, consensus |
+| Critical | 100-150 RTC | Lỗ hổng, nâng cấp protocol |
+
+**1 RTC khoảng $0,10 USD** · `pip install clawrtc` · [CONTRIBUTING.md](CONTRIBUTING.md)
+
+---
+
+
+## Công bố học thuật
+
+| Bài báo | Nơi công bố | DOI |
+|---------|-------------|-----|
+| **Emotional Vocabulary as Semantic Grounding** | **CVPR 2026 Workshop (GRAIL-V)** - Accepted | [OpenReview](https://openreview.net/forum?id=pXjE6Tqp70) |
+| **One CPU, One Vote** | Preprint | [](https://doi.org/10.5281/zenodo.18623592) |
+| **Non-Bijunctive Permutation Collapse** | Preprint | [](https://doi.org/10.5281/zenodo.18623920) |
+| **PSE Hardware Entropy** | Preprint | [](https://doi.org/10.5281/zenodo.18623922) |
+| **RAM Coffers** | Preprint | [](https://doi.org/10.5281/zenodo.18321905) |
+| **RPI: Resonant Permutation Inference** | Preprint | [](https://doi.org/10.5281/zenodo.19271983) |
+
+---
+
+
+## Hệ sinh thái
+
+| Dự án | Nội dung |
+|-------|----------|
+| [BoTTube](https://bottube.ai) | Nền tảng video AI-native (1.000+ video) |
+| [Beacon](https://github.com/Scottcjn/beacon-skill) | Giao thức khám phá agent |
+| [TrashClaw](https://github.com/Scottcjn/trashclaw) | Local LLM agent zero-dep |
+| [RAM Coffers](https://github.com/Scottcjn/ram-coffers) | NUMA-aware LLM inference trên POWER8 |
+| [RPI Inference](https://github.com/Scottcjn/rpi-inference) | Engine inference zero-multiply (18K tok/s, chạy trên N64) |
+| [Grazer](https://github.com/Scottcjn/grazer-skill) | Khám phá nội dung đa nền tảng |
+
+---
+
+
+## Nền tảng hỗ trợ
+
+Linux (x86_64, ppc64le) · macOS (Intel, Apple Silicon, PowerPC) · IBM POWER8 · Windows · Mac OS X Tiger/Leopard · Raspberry Pi
+
+---
+
+
+## Vì sao tên là "RustChain"?
+
+Tên này đến từ một laptop 486 với cổng serial bị oxy hóa nhưng vẫn boot được vào DOS và mine RTC. "Rust" nghĩa là sắt oxy hóa trên các linh kiện cổ điển có chứa sắt. Luận đề là phần cứng cổ điển đang rỉ sét vẫn có giá trị tính toán và phẩm giá.
+
+---
+
+
+
+**[Elyan Labs](https://elyanlabs.ai)** · Xây với $0 VC và một căn phòng đầy phần cứng mua ở tiệm cầm đồ
+
+*"Mais, nó vẫn chạy, vậy sao lại vứt đi?"*
+
+[Boudreaux Principles](https://rustchain.org/principles.html) · [Green Tracker](https://rustchain.org/preserved.html) · [Bounties](https://github.com/Scottcjn/rustchain-bounties/issues)
+
+
+
+
+## Đóng góp
+
+Vui lòng đọc [CONTRIBUTING.md](CONTRIBUTING.md) để biết hướng dẫn và [Bounty Board](https://github.com/Scottcjn/rustchain-bounties) để xem các nhiệm vụ và phần thưởng đang hoạt động.
+
+---
+
+*Tài liệu được cải thiện để dễ đọc hơn.*
diff --git a/README.zh-CN.md b/README.zh-CN.md
index 9981dcf7f..9191b870c 100644
--- a/README.zh-CN.md
+++ b/README.zh-CN.md
@@ -1,284 +1,484 @@
-