77
88import click
99
10+ from gittensor .miner .broadcast import broadcast_predictions
11+
1012from .help import StyledCommand
1113from .helpers import (
1214 _is_interactive ,
2426 print_network_header ,
2527 print_success ,
2628 print_warning ,
29+ resolve_netuid_from_contract ,
2730 resolve_network ,
2831 success_panel ,
2932 validate_issue_id ,
@@ -137,6 +140,10 @@ def issues_predict(
137140 ws_endpoint , network_name = resolve_network (network , rpc_url )
138141 effective_wallet , effective_hotkey = _resolve_wallet_identity (wallet_name , wallet_hotkey )
139142
143+ netuid = resolve_netuid_from_contract (ws_endpoint , contract_addr )
144+ if netuid is None :
145+ handle_exception (as_json , 'Could not resolve netuid from contract.' )
146+
140147 if not as_json :
141148 print_network_header (network_name , contract_addr )
142149 console .print (f'Wallet: { effective_wallet } /{ effective_hotkey } \n ' )
@@ -173,17 +180,7 @@ def issues_predict(
173180 print_warning ('Prediction cancelled' )
174181 return
175182
176- # 7) Interactive mode: verify miner first to avoid wasting manual input.
177- if is_interactive_mode :
178- _resolve_registered_miner_hotkey (
179- wallet_name = effective_wallet ,
180- wallet_hotkey = effective_hotkey ,
181- ws_endpoint = ws_endpoint ,
182- contract_addr = contract_addr ,
183- as_json = as_json ,
184- )
185-
186- # 8) Collect predictions by mode; validate PR membership for non-interactive modes.
183+ # 7) Collect predictions by mode; validate PR membership for non-interactive modes.
187184 try :
188185 if is_interactive_mode :
189186 predictions = _collect_predictions_interactive (pull_requests )
@@ -193,39 +190,51 @@ def issues_predict(
193190 except (click .ClickException , click .BadParameter ) as e :
194191 handle_exception (as_json , str (e ))
195192
196- # 9) Single/batch modes: verify miner after prediction payload validation.
197- if not is_interactive_mode :
198- _resolve_registered_miner_hotkey (
199- wallet_name = effective_wallet ,
200- wallet_hotkey = effective_hotkey ,
201- ws_endpoint = ws_endpoint ,
202- contract_addr = contract_addr ,
203- as_json = as_json ,
204- )
205-
206- payload = build_prediction_payload (
207- issue_id = issue_id ,
208- repository = repo_full_name ,
209- predictions = predictions ,
210- )
211-
212- # 10) Emit machine output or interactive confirmation flow.
213- if as_json :
214- emit_json (payload , pretty = True )
215- broadcast_predictions_stub (payload )
216- return
193+ payload = {
194+ 'issue_id' : issue_id ,
195+ 'repository' : repo_full_name ,
196+ 'predictions' : dict (predictions ),
197+ 'github_access_token' : '***' ,
198+ }
217199
218- if is_interactive_mode :
200+ # 8) Confirmation prompt (interactive only).
201+ if not as_json and is_interactive_mode :
219202 lines = format_prediction_lines (predictions )
220203 confirm_panel (lines , title = 'Prediction Confirmation' )
221204 skip_confirm = yes or not _is_interactive ()
222205 if not skip_confirm and not click .confirm ('Proceed?' , default = True ):
223206 print_warning ('Prediction cancelled' )
224207 return
225208
226- success_panel (json_mod .dumps (payload , indent = 2 ), title = 'Prediction Payload' )
227- print_success ('Prediction prepared (TODO: broadcast)' )
228- broadcast_predictions_stub (payload )
209+ # 9) Verify miner registration before broadcasting.
210+ _resolve_registered_miner_hotkey (
211+ wallet_name = effective_wallet ,
212+ wallet_hotkey = effective_hotkey ,
213+ ws_endpoint = ws_endpoint ,
214+ contract_addr = contract_addr ,
215+ as_json = as_json ,
216+ )
217+
218+ # 10) Show payload and broadcast to validators.
219+ if as_json :
220+ emit_json (payload , pretty = True )
221+
222+ if not as_json :
223+ success_panel (json_mod .dumps (payload , indent = 2 ), title = 'Prediction Synapse' )
224+
225+ with loading_context ('Broadcasting predictions to validators...' , as_json ):
226+ results = broadcast_predictions (
227+ payload = payload ,
228+ wallet_name = effective_wallet ,
229+ wallet_hotkey = effective_hotkey ,
230+ ws_endpoint = ws_endpoint ,
231+ netuid = netuid ,
232+ )
233+
234+ if as_json :
235+ emit_json (results , pretty = True )
236+ else :
237+ _print_broadcast_results (results )
229238
230239
231240def validate_probability (value : float , param_hint : str = 'probability' ) -> float :
@@ -285,9 +294,7 @@ def _resolve_issue_context(
285294 """Load and validate on-chain issue context for prediction."""
286295 try :
287296 with loading_context ('Reading issues from contract...' , as_json ):
288- issue = fetch_issue_from_contract (
289- ws_endpoint , contract_addr , issue_id , require_active = True , verbose = verbose
290- )
297+ issue = fetch_issue_from_contract (ws_endpoint , contract_addr , issue_id , verbose = verbose )
291298 except click .ClickException as e :
292299 handle_exception (as_json , str (e ))
293300
@@ -361,22 +368,22 @@ def format_prediction_lines(predictions: dict[int, float]) -> str:
361368 return '\n ' .join (lines )
362369
363370
364- def build_prediction_payload (
365- issue_id : int ,
366- repository : str ,
367- predictions : dict [int , float ],
368- ) -> dict [str , object ]:
369- """Build validated payload for future network broadcast."""
370- return {
371- 'issue_id' : issue_id ,
372- 'repository' : repository ,
373- 'predictions' : dict (predictions ),
374- }
375-
371+ def _print_broadcast_results (results : dict [str , object ]) -> None :
372+ """Print broadcast results in human-readable format."""
373+ if results .get ('error' ):
374+ print_error (str (results ['error' ]))
375+ return
376+ if results .get ('success' ):
377+ print_success (f'Prediction accepted by { results ["accepted" ]} /{ results ["total_validators" ]} validator(s)' )
378+ else :
379+ print_error (
380+ f'Prediction rejected or unreachable: { results ["rejected" ]} /{ results ["total_validators" ]} validator(s)'
381+ )
376382
377- def broadcast_predictions_stub (payload : dict [str , object ]) -> None :
378- """Broadcast integration seam (stub)."""
379- pass
383+ for r in results .get ('results' , []):
384+ status = 'accepted' if r ['accepted' ] else 'rejected'
385+ reason = f' ({ r ["rejection_reason" ]} )' if r .get ('rejection_reason' ) else ''
386+ console .print (f' { r ["validator" ]} ... { status } { reason } ' )
380387
381388
382389def _collect_predictions_interactive (prs : list [dict ]) -> dict [int , float ]:
0 commit comments