@@ -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 {
@@ -408,22 +462,13 @@ async fn fetch_ssv_pubkeys(
408462 let mut pubkeys: Vec < BlsPublicKey > = vec ! [ ] ;
409463 let mut page = 1 ;
410464
411- // Validate the URL - this appends a trailing slash if missing as efficiently as
412- // possible
413- if !api_url. path ( ) . ends_with ( '/' ) {
414- match api_url. path_segments_mut ( ) {
415- Ok ( mut segments) => segments. push ( "" ) , // Analogous to a trailing slash
416- Err ( _) => bail ! ( "SSV API URL is not a valid base URL" ) ,
417- } ;
418- }
419-
420465 loop {
421466 let route = format ! (
422467 "{chain_name}/validators/in_operator/{node_operator_id}?perPage={MAX_PER_PAGE}&page={page}" ,
423468 ) ;
424- let url = api_url . join ( & route) . wrap_err ( "failed to construct SSV API URL" ) ?;
469+ let url = url . join ( & route) . wrap_err ( "failed to construct SSV API URL" ) ?;
425470
426- let response = fetch_ssv_pubkeys_from_url ( url, http_timeout) . await ?;
471+ let response = request_ssv_pubkeys_from_public_api ( url, http_timeout) . await ?;
427472 let fetched = response. validators . len ( ) ;
428473 pubkeys. extend (
429474 response. validators . into_iter ( ) . map ( |v| v. pubkey ) . collect :: < Vec < BlsPublicKey > > ( ) ,
0 commit comments