Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
ab7a773
first push
Feb 20, 2026
0d3da2f
Merge branch 'test' into merge-preds
anderdc Feb 24, 2026
fc7fe0c
refactor for merge predict
anderdc Feb 25, 2026
a974cd3
Add AffineIO/affine-cortex and AffineIO/affinetes to master repo list…
MkDev11 Feb 26, 2026
fe1156c
fix: correct missing f-string and off-by-one retry guard (#237)
Nicknamess96 Feb 26, 2026
419c2ad
chore: add dev branch to sn36 repo as additional acceptable branch (#…
bittoby Feb 26, 2026
b9ce097
fix: resolve retry logic inconsistencies in github_api_tools (#195)
eren-karakus0 Feb 26, 2026
e0e8108
add ui (#241)
anderdc Feb 27, 2026
f7a505d
Merge branch 'test' into merge-preds
anderdc Feb 27, 2026
846b8ed
chugging away
anderdc Feb 27, 2026
b146734
integrated baseline into validator forward
anderdc Mar 1, 2026
53c137b
Merge branch 'test' into merge-preds
anderdc Mar 3, 2026
d2e560a
cleanup
anderdc Mar 4, 2026
5eddfbd
patch cli, bugs in broadcast
anderdc Mar 5, 2026
54c6510
broadcast rejection fix
anderdc Mar 5, 2026
9fcbe81
adjust db path
anderdc Mar 5, 2026
21aada8
updates
anderdc Mar 5, 2026
36301f2
handler update
anderdc Mar 6, 2026
97008be
update sqlite path
Mar 6, 2026
92b3b1a
patch volume storage
Mar 6, 2026
3087df2
fix tests, integrate downstream db storage
Mar 6, 2026
4afbfc2
Merge branch 'test' into merge-preds
Mar 6, 2026
1459252
new pr template for weight changes
Mar 7, 2026
534c7f1
update major version
Mar 7, 2026
d945b06
Merge branch 'test' into merge-preds
Mar 7, 2026
1757a08
small tweaks
Mar 7, 2026
8a54c6c
style: auto-format with ruff
anderdc Mar 7, 2026
4b3c662
patch rejection bug
Mar 7, 2026
d96b015
patch
Mar 7, 2026
54aa99f
failing test fix
Mar 7, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ HOTKEY_NAME=default
WANDB_API_KEY=
# for issue bounties api calls
GITTENSOR_VALIDATOR_PAT=
# Optional custom name for wandb logging
# Optional custom name for wandb logging
WANDB_VALIDATOR_NAME=vali

# ******* MINER VARIABLES *******
Expand Down
52 changes: 52 additions & 0 deletions .github/PULL_REQUEST_TEMPLATE/weight_adjustment.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
## Weight Adjustment PR

### Changes Summary

| Metric | Gold | Silver | Bronze | Total |
| -------------------- | ---- | ------ | ------ | ----- |
| Repositories Added | 0 | 0 | 0 | 0 |
| Repositories Removed | 0 | 0 | 0 | 0 |
| Weights Modified | 0 | 0 | 0 | 0 |
| Net Weight Change | 0 | 0 | 0 | 0 |

### Added Repositories

<!-- Delete this section if none -->

| Repository | Tier | Branch | Weight |
| ---------- | ------ | ------ | ------ |
| owner/repo | silver | main | 20.00 |

### Removed Repositories

<!-- Delete this section if none -->

| Repository | Tier | Reason |
| ---------- | ------ | ------ |
| owner/repo | silver ||

### Justification

<!-- Explain why these weight changes are being made -->

### Additional Acceptable Branches

<!--
If this PR adds entries to additional_acceptable_branches for any repository,
you MUST provide a link to a MERGED pull request in the target repository
that demonstrates the contributor follows the workflow/pattern used in that repo.
Without this proof, the additional branch will not be approved.
Example:
- `feature-branch` for `owner/repo`: https://github.com/owner/repo/pull/123 (merged)
-->

- [ ] No additional_acceptable_branches changes in this PR
- [ ] Proof of merged PR(s) provided above for all additional_acceptable_branches entries

### Checklist

- [ ] Changes summary table is filled in accurately
- [ ] Net weight changes are justified in the Justification section
- [ ] Added repositories have correct tier, branch, and initial weight
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@ wandb

*.log

# Merge predictions local DB
merge-prediction-data/
gt-merge-preds.db
gt-merge-preds.db-wal
gt-merge-preds.db-shm

CLAUDE.md
.claude/
.vscode/
Expand Down
1 change: 1 addition & 0 deletions docker-compose.vali.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ services:
volumes:
# 'ro' = readonly
- ${WALLET_PATH}:/root/.bittensor/wallets:ro
- ./merge-prediction-data:/app/data
# optional: uncomment this if you are running validator database
# networks:
# - gittensor_network
Expand Down
2 changes: 1 addition & 1 deletion gittensor/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,6 @@
# DEALINGS IN THE SOFTWARE.

# NOTE: bump this value when updating the codebase
__version__ = '4.0.0'
__version__ = '5.0.0'
version_split = __version__.split('.')
__spec_version__ = (1000 * int(version_split[0])) + (10 * int(version_split[1])) + (1 * int(version_split[2]))
2 changes: 1 addition & 1 deletion gittensor/classes.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

from gittensor.constants import MIN_TOKEN_SCORE_FOR_BASE_SCORE
from gittensor.utils.utils import parse_repo_name
from gittensor.validator.configurations.tier_config import Tier, TierConfig, TierStats
from gittensor.validator.oss_contributions.tier_config import Tier, TierConfig, TierStats

GITHUB_DOMAIN = 'https://github.com/'

Expand Down
26 changes: 14 additions & 12 deletions gittensor/cli/issue_commands/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import click
from rich.console import Console
from rich.panel import Panel
from substrateinterface import SubstrateInterface

from gittensor.cli.issue_commands.tables import build_pr_table
from gittensor.constants import CONTRACT_ADDRESS
Expand All @@ -43,7 +44,6 @@
'Cancelled': 'dim',
}

ISSUE_BOUNTY_ELIGIBLE_STATUSES = {'registered', 'active'}

# Default paths
GITTENSOR_DIR = Path.home() / '.gittensor'
Expand Down Expand Up @@ -193,16 +193,21 @@ def print_issue_submission_table(
console.print(f'Showing {len(pull_requests)} submissions{suffix}')


def verify_miner_registration(ws_endpoint: str, contract_addr: str, hotkey_ss58: str) -> bool:
"""Return whether the hotkey is registered on the subnet configured by the contract netuid."""
import bittensor as bt
from substrateinterface import SubstrateInterface
def resolve_netuid_from_contract(ws_endpoint: str, contract_addr: str) -> Optional[int]:
"""Read the subnet netuid stored in the on-chain contract."""

substrate = SubstrateInterface(url=ws_endpoint)
packed = _read_contract_packed_storage(substrate, contract_addr)
netuid = None
if packed and packed.get('netuid') is not None:
netuid = int(packed['netuid'])
return int(packed['netuid'])
return None


def verify_miner_registration(ws_endpoint: str, contract_addr: str, hotkey_ss58: str) -> bool:
"""Return whether the hotkey is registered on the subnet configured by the contract netuid."""
import bittensor as bt

netuid = resolve_netuid_from_contract(ws_endpoint, contract_addr)
if netuid is None:
return False

Expand Down Expand Up @@ -796,21 +801,18 @@ def fetch_issue_from_contract(
ws_endpoint: str,
contract_addr: str,
issue_id: int,
require_active: bool = False,
verbose: bool = False,
) -> Dict[str, Any]:
"""Resolve an on-chain issue and validate bountied/active status."""
"""Resolve an on-chain issue and validate bountied status."""
issues = read_issues_from_contract(ws_endpoint, contract_addr, verbose)
issue = next((i for i in issues if i.get('id') == issue_id), None)
if not issue:
raise click.ClickException(f'Issue ID {issue_id} not found on-chain.')

status = issue.get('status') or ''
status_normalized = str(status).strip().lower()
if status_normalized not in ISSUE_BOUNTY_ELIGIBLE_STATUSES:
if status_normalized not in {'registered', 'active'}:
raise click.ClickException(f'Issue #{issue_id} is not in a bountied state (status: {status}).')
if require_active and status_normalized != 'active':
raise click.ClickException(f'Issue #{issue_id} is not active (status: {status}).')

repo = issue.get('repository_full_name', '')
issue_number = issue.get('issue_number', 0)
Expand Down
115 changes: 61 additions & 54 deletions gittensor/cli/issue_commands/predict.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

import click

from gittensor.miner.broadcast import broadcast_predictions

from .help import StyledCommand
from .helpers import (
_is_interactive,
Expand All @@ -24,6 +26,7 @@
print_network_header,
print_success,
print_warning,
resolve_netuid_from_contract,
resolve_network,
success_panel,
validate_issue_id,
Expand Down Expand Up @@ -137,6 +140,10 @@ def issues_predict(
ws_endpoint, network_name = resolve_network(network, rpc_url)
effective_wallet, effective_hotkey = _resolve_wallet_identity(wallet_name, wallet_hotkey)

netuid = resolve_netuid_from_contract(ws_endpoint, contract_addr)
if netuid is None:
handle_exception(as_json, 'Could not resolve netuid from contract.')

if not as_json:
print_network_header(network_name, contract_addr)
console.print(f'Wallet: {effective_wallet}/{effective_hotkey}\n')
Expand Down Expand Up @@ -173,17 +180,7 @@ def issues_predict(
print_warning('Prediction cancelled')
return

# 7) Interactive mode: verify miner first to avoid wasting manual input.
if is_interactive_mode:
_resolve_registered_miner_hotkey(
wallet_name=effective_wallet,
wallet_hotkey=effective_hotkey,
ws_endpoint=ws_endpoint,
contract_addr=contract_addr,
as_json=as_json,
)

# 8) Collect predictions by mode; validate PR membership for non-interactive modes.
# 7) Collect predictions by mode; validate PR membership for non-interactive modes.
try:
if is_interactive_mode:
predictions = _collect_predictions_interactive(pull_requests)
Expand All @@ -193,39 +190,51 @@ def issues_predict(
except (click.ClickException, click.BadParameter) as e:
handle_exception(as_json, str(e))

# 9) Single/batch modes: verify miner after prediction payload validation.
if not is_interactive_mode:
_resolve_registered_miner_hotkey(
wallet_name=effective_wallet,
wallet_hotkey=effective_hotkey,
ws_endpoint=ws_endpoint,
contract_addr=contract_addr,
as_json=as_json,
)

payload = build_prediction_payload(
issue_id=issue_id,
repository=repo_full_name,
predictions=predictions,
)

# 10) Emit machine output or interactive confirmation flow.
if as_json:
emit_json(payload, pretty=True)
broadcast_predictions_stub(payload)
return
payload = {
'issue_id': issue_id,
'repository': repo_full_name,
'predictions': dict(predictions),
'github_access_token': '***',
}

if is_interactive_mode:
# 8) Confirmation prompt (interactive only).
if not as_json and is_interactive_mode:
lines = format_prediction_lines(predictions)
confirm_panel(lines, title='Prediction Confirmation')
skip_confirm = yes or not _is_interactive()
if not skip_confirm and not click.confirm('Proceed?', default=True):
print_warning('Prediction cancelled')
return

success_panel(json_mod.dumps(payload, indent=2), title='Prediction Payload')
print_success('Prediction prepared (TODO: broadcast)')
broadcast_predictions_stub(payload)
# 9) Verify miner registration before broadcasting.
_resolve_registered_miner_hotkey(
wallet_name=effective_wallet,
wallet_hotkey=effective_hotkey,
ws_endpoint=ws_endpoint,
contract_addr=contract_addr,
as_json=as_json,
)

# 10) Show payload and broadcast to validators.
if as_json:
emit_json(payload, pretty=True)

if not as_json:
success_panel(json_mod.dumps(payload, indent=2), title='Prediction Synapse')

with loading_context('Broadcasting predictions to validators...', as_json):
results = broadcast_predictions(
payload=payload,
wallet_name=effective_wallet,
wallet_hotkey=effective_hotkey,
ws_endpoint=ws_endpoint,
netuid=netuid,
)

if as_json:
emit_json(results, pretty=True)
else:
_print_broadcast_results(results)


def validate_probability(value: float, param_hint: str = 'probability') -> float:
Expand Down Expand Up @@ -285,9 +294,7 @@ def _resolve_issue_context(
"""Load and validate on-chain issue context for prediction."""
try:
with loading_context('Reading issues from contract...', as_json):
issue = fetch_issue_from_contract(
ws_endpoint, contract_addr, issue_id, require_active=True, verbose=verbose
)
issue = fetch_issue_from_contract(ws_endpoint, contract_addr, issue_id, verbose=verbose)
except click.ClickException as e:
handle_exception(as_json, str(e))

Expand Down Expand Up @@ -361,22 +368,22 @@ def format_prediction_lines(predictions: dict[int, float]) -> str:
return '\n'.join(lines)


def build_prediction_payload(
issue_id: int,
repository: str,
predictions: dict[int, float],
) -> dict[str, object]:
"""Build validated payload for future network broadcast."""
return {
'issue_id': issue_id,
'repository': repository,
'predictions': dict(predictions),
}

def _print_broadcast_results(results: dict[str, object]) -> None:
"""Print broadcast results in human-readable format."""
if results.get('error'):
print_error(str(results['error']))
return
if results.get('success'):
print_success(f'Prediction accepted by {results["accepted"]}/{results["total_validators"]} validator(s)')
else:
print_error(
f'Prediction rejected or unreachable: {results["rejected"]}/{results["total_validators"]} validator(s)'
)

def broadcast_predictions_stub(payload: dict[str, object]) -> None:
"""Broadcast integration seam (stub)."""
pass
for r in results.get('results', []):
status = 'accepted' if r['accepted'] else 'rejected'
reason = f' ({r["rejection_reason"]})' if r.get('rejection_reason') else ''
console.print(f' {r["validator"]}... {status}{reason}')


def _collect_predictions_interactive(prs: list[dict]) -> dict[int, float]:
Expand Down
4 changes: 1 addition & 3 deletions gittensor/cli/issue_commands/submissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,7 @@ def issues_submissions(

try:
with loading_context('Fetching issue from contract...', as_json):
issue = fetch_issue_from_contract(
ws_endpoint, contract_addr, issue_id, require_active=False, verbose=verbose
)
issue = fetch_issue_from_contract(ws_endpoint, contract_addr, issue_id, verbose=verbose)
except click.ClickException as e:
handle_exception(as_json, str(e))

Expand Down
23 changes: 22 additions & 1 deletion gittensor/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,4 +139,25 @@
# =============================================================================
CONTRACT_ADDRESS = '5FWNdk8YNtNcHKrAx2krqenFrFAZG7vmsd2XN2isJSew3MrD'
ISSUES_TREASURY_UID = 111 # UID of the smart contract neuron, if set to RECYCLE_UID then it's disabled
ISSUES_TREASURY_EMISSION_SHARE = 0.15 # % of emissions routed to funding issues treasury
ISSUES_TREASURY_EMISSION_SHARE = 0.15 # % of emissions allocated to funding issues treasury

# =============================================================================
# Merge Predictions
# =============================================================================
PREDICTIONS_EMISSIONS_SHARE = 0.15 # % of emissions allocated to prediction competition

PREDICTIONS_EMA_BETA = 0.1 # EMA decay rate for predictions record
PREDICTIONS_CORRECTNESS_EXPONENT = 3 # exponent on correctness to harshly punish incorrect predictions
PREDICTIONS_TIMELINESS_EXPONENT = 1.8 # curve for early prediction bonus. higher = sharper curve. 1.0 = linear
PREDICTIONS_MAX_TIMELINESS_BONUS = 0.75 # max bonus for earliest predictions
PREDICTIONS_MAX_CONSENSUS_BONUS = 0.25 # max bonus for pre-convergence predictions
PREDICTIONS_MAX_ORDER_BONUS = 0.75 # max bonus for first correct predictor (applies to merged PR only)
PREDICTIONS_ORDER_CORRECTNESS_THRESHOLD = 0.66 # min raw correctness to qualify for order bonus
# variance threshold for full rewards
# if variance across predictions never exceeds this threshold, the solution must be 'obvious'
PREDICTIONS_CONSENSUS_VARIANCE_TARGET = 0.2

# Cooldown & Limits
PREDICTIONS_COOLDOWN_SECONDS = 900 # 15 min cooldown per miner per PR re-prediction
PREDICTIONS_MIN_VALUE = 0.0
PREDICTIONS_MAX_VALUE = 1.0
Loading