@@ -98,6 +98,19 @@ pub struct Args {
9898
9999impl Args {
100100 pub fn get ( & self , locator : & locator:: Args ) -> Result < Network , Error > {
101+ self . resolve ( locator, /* require_rpc */ true )
102+ }
103+
104+ /// Resolve the network for commands that only need the passphrase
105+ /// (e.g. `tx sign`, `tx hash`) and never contact an RPC server.
106+ ///
107+ /// When only a passphrase is supplied, the returned `Network` has an empty
108+ /// `rpc_url`; callers of this method MUST NOT use `rpc_client()`.
109+ pub fn get_no_rpc ( & self , locator : & locator:: Args ) -> Result < Network , Error > {
110+ self . resolve ( locator, /* require_rpc */ false )
111+ }
112+
113+ fn resolve ( & self , locator : & locator:: Args , require_rpc : bool ) -> Result < Network , Error > {
101114 match (
102115 self . network . as_deref ( ) ,
103116 self . rpc_url . clone ( ) ,
@@ -108,6 +121,13 @@ impl Args {
108121 Ok ( DEFAULTS . get ( DEFAULT_NETWORK_KEY ) . unwrap ( ) . into ( ) )
109122 }
110123 ( _, Some ( _) , None ) => Err ( Error :: MissingNetworkPassphrase ) ,
124+ // Signing-only commands don't need an RPC URL, so accept a
125+ // passphrase on its own and leave `rpc_url` empty.
126+ ( _, None , Some ( network_passphrase) ) if !require_rpc => Ok ( Network {
127+ rpc_url : String :: new ( ) ,
128+ rpc_headers : Vec :: new ( ) ,
129+ network_passphrase,
130+ } ) ,
111131 ( _, None , Some ( _) ) => Err ( Error :: MissingRpcUrl ) ,
112132 ( Some ( network) , None , None ) => Ok ( locator. read_network ( network) ?) ,
113133 ( _, Some ( rpc_url) , Some ( network_passphrase) ) => {
@@ -549,6 +569,77 @@ mod tests {
549569 assert_eq ! ( network. rpc_url, "https://soroban-testnet.stellar.org" ) ;
550570 }
551571
572+ #[ test]
573+ fn test_get_no_rpc_accepts_passphrase_only ( ) {
574+ use super :: super :: locator;
575+
576+ let args = Args {
577+ rpc_url : None ,
578+ rpc_headers : Vec :: new ( ) ,
579+ network_passphrase : Some ( "specified manually" . to_string ( ) ) ,
580+ network : None ,
581+ } ;
582+
583+ let network = args
584+ . get_no_rpc ( & locator:: Args :: default ( ) )
585+ . expect ( "passphrase-only network should resolve for signing-only commands" ) ;
586+ assert_eq ! ( network. network_passphrase, "specified manually" ) ;
587+ assert_eq ! ( network. rpc_url, "" ) ;
588+ assert ! ( network. rpc_headers. is_empty( ) ) ;
589+ }
590+
591+ #[ test]
592+ fn test_get_no_rpc_still_requires_passphrase_when_rpc_given ( ) {
593+ use super :: super :: locator;
594+
595+ let args = Args {
596+ rpc_url : Some ( "https://example.com" . to_string ( ) ) ,
597+ rpc_headers : Vec :: new ( ) ,
598+ network_passphrase : None ,
599+ network : None ,
600+ } ;
601+
602+ let err = args. get_no_rpc ( & locator:: Args :: default ( ) ) . expect_err (
603+ "rpc without passphrase should still error, even for signing-only commands" ,
604+ ) ;
605+ assert ! ( matches!( err, Error :: MissingNetworkPassphrase ) ) ;
606+ }
607+
608+ #[ test]
609+ fn test_get_no_rpc_preserves_rpc_url_when_both_given ( ) {
610+ use super :: super :: locator;
611+
612+ let args = Args {
613+ rpc_url : Some ( "https://example.com" . to_string ( ) ) ,
614+ rpc_headers : Vec :: new ( ) ,
615+ network_passphrase : Some ( "specified manually" . to_string ( ) ) ,
616+ network : None ,
617+ } ;
618+
619+ let network = args. get_no_rpc ( & locator:: Args :: default ( ) ) . unwrap ( ) ;
620+ assert_eq ! ( network. rpc_url, "https://example.com" ) ;
621+ assert_eq ! ( network. network_passphrase, "specified manually" ) ;
622+ }
623+
624+ #[ test]
625+ fn test_get_strict_still_requires_rpc_url_with_passphrase_only ( ) {
626+ use super :: super :: locator;
627+
628+ let args = Args {
629+ rpc_url : None ,
630+ rpc_headers : Vec :: new ( ) ,
631+ network_passphrase : Some ( "specified manually" . to_string ( ) ) ,
632+ network : None ,
633+ } ;
634+
635+ // The strict resolver used by RPC commands must keep rejecting a
636+ // passphrase-only invocation.
637+ let err = args
638+ . get ( & locator:: Args :: default ( ) )
639+ . expect_err ( "strict get() must still require an rpc-url with passphrase-only args" ) ;
640+ assert ! ( matches!( err, Error :: MissingRpcUrl ) ) ;
641+ }
642+
552643 #[ tokio:: test]
553644 async fn test_user_config_default_overrides_automatic_testnet ( ) {
554645 use super :: super :: locator;
0 commit comments