@@ -62,7 +62,8 @@ impl PbsMuxes {
6262 . load (
6363 & mux. id ,
6464 chain,
65- default_pbs. ssv_api_url . clone ( ) ,
65+ default_pbs. ssv_node_api_url . clone ( ) ,
66+ default_pbs. ssv_public_api_url . clone ( ) ,
6667 default_pbs. rpc_url . clone ( ) ,
6768 http_timeout,
6869 )
@@ -212,7 +213,8 @@ impl MuxKeysLoader {
212213 & self ,
213214 mux_id : & str ,
214215 chain : Chain ,
215- ssv_api_url : Url ,
216+ ssv_node_api_url : Url ,
217+ ssv_public_api_url : Url ,
216218 rpc_url : Option < Url > ,
217219 http_timeout : Duration ,
218220 ) -> eyre:: Result < Vec < BlsPublicKey > > {
@@ -258,7 +260,8 @@ impl MuxKeysLoader {
258260 }
259261 NORegistry :: SSV => {
260262 fetch_ssv_pubkeys (
261- ssv_api_url,
263+ ssv_node_api_url,
264+ ssv_public_api_url,
262265 chain,
263266 U256 :: from ( * node_operator_id) ,
264267 http_timeout,
@@ -391,11 +394,62 @@ async fn fetch_lido_registry_keys(
391394}
392395
393396async fn fetch_ssv_pubkeys (
394- mut api_url : Url ,
397+ node_url : Url ,
398+ public_url : Url ,
395399 chain : Chain ,
396400 node_operator_id : U256 ,
397401 http_timeout : Duration ,
398402) -> eyre:: Result < Vec < BlsPublicKey > > {
403+ // Try the node API first
404+ match fetch_ssv_pubkeys_from_ssv_node ( node_url. clone ( ) , node_operator_id, http_timeout) . await {
405+ Ok ( pubkeys) => Ok ( pubkeys) ,
406+ Err ( e) => {
407+ // Fall back to public API
408+ warn ! (
409+ "failed to fetch pubkeys from SSV node API at {node_url}: {e}; falling back to public API" ,
410+ ) ;
411+ fetch_ssv_pubkeys_from_public_api ( public_url, chain, node_operator_id, http_timeout)
412+ . await
413+ }
414+ }
415+ }
416+
417+ /// Ensures that the SSV API URL has a trailing slash
418+ fn ensure_ssv_api_url ( url : & mut Url ) -> eyre:: Result < ( ) > {
419+ // Validate the URL - this appends a trailing slash if missing as efficiently as
420+ // possible
421+ if !url. path ( ) . ends_with ( '/' ) {
422+ match url. path_segments_mut ( ) {
423+ Ok ( mut segments) => segments. push ( "" ) , // Analogous to a trailing slash
424+ Err ( _) => bail ! ( "SSV API URL is not a valid base URL" ) ,
425+ } ;
426+ }
427+ Ok ( ( ) )
428+ }
429+
430+ /// Fetches SSV pubkeys from the user's SSV node
431+ async fn fetch_ssv_pubkeys_from_ssv_node (
432+ mut url : Url ,
433+ node_operator_id : U256 ,
434+ http_timeout : Duration ,
435+ ) -> eyre:: Result < Vec < BlsPublicKey > > {
436+ ensure_ssv_api_url ( & mut url) ?;
437+ let route = "validators" ;
438+ let url = url. join ( route) . wrap_err ( "failed to construct SSV API URL" ) ?;
439+
440+ let response = request_ssv_pubkeys_from_ssv_node ( url, node_operator_id, http_timeout) . await ?;
441+ let pubkeys = response. data . into_iter ( ) . map ( |v| v. public_key ) . collect :: < Vec < BlsPublicKey > > ( ) ;
442+ Ok ( pubkeys)
443+ }
444+
445+ /// Fetches SSV pubkeys from the public SSV network API with pagination
446+ async fn fetch_ssv_pubkeys_from_public_api (
447+ mut url : Url ,
448+ chain : Chain ,
449+ node_operator_id : U256 ,
450+ http_timeout : Duration ,
451+ ) -> eyre:: Result < Vec < BlsPublicKey > > {
452+ ensure_ssv_api_url ( & mut url) ?;
399453 const MAX_PER_PAGE : usize = 100 ;
400454
401455 let chain_name = match chain {
@@ -409,22 +463,13 @@ async fn fetch_ssv_pubkeys(
409463 let mut page = 1 ;
410464 let mut expected_total: Option < usize > = None ;
411465
412- // Validate the URL - this appends a trailing slash if missing as efficiently as
413- // possible
414- if !api_url. path ( ) . ends_with ( '/' ) {
415- match api_url. path_segments_mut ( ) {
416- Ok ( mut segments) => segments. push ( "" ) , // Analogous to a trailing slash
417- Err ( _) => bail ! ( "SSV API URL is not a valid base URL" ) ,
418- } ;
419- }
420-
421466 loop {
422467 let route = format ! (
423468 "{chain_name}/validators/in_operator/{node_operator_id}?perPage={MAX_PER_PAGE}&page={page}" ,
424469 ) ;
425- let url = api_url . join ( & route) . wrap_err ( "failed to construct SSV API URL" ) ?;
470+ let url = url . join ( & route) . wrap_err ( "failed to construct SSV API URL" ) ?;
426471
427- let response = fetch_ssv_pubkeys_from_url ( url, http_timeout) . await ?;
472+ let response = request_ssv_pubkeys_from_public_api ( url, http_timeout) . await ?;
428473 let fetched = response. validators . len ( ) ;
429474 if expected_total. is_none ( ) && fetched > 0 {
430475 expected_total = Some ( response. pagination . total ) ;
0 commit comments