@@ -13,6 +13,15 @@ use mina_node_account::{AccountPublicKey, AccountSecretKey};
1313use mina_p2p_messages:: v2:: MinaBaseSignedCommandStableV2 ;
1414use mina_signer:: { CompressedPubKey , Keypair , Signer } ;
1515
16+ use super :: super :: Network ;
17+
18+ fn network_to_network_id ( network : & Network ) -> mina_signer:: NetworkId {
19+ match network {
20+ Network :: Mainnet => mina_signer:: NetworkId :: MAINNET ,
21+ Network :: Devnet => mina_signer:: NetworkId :: TESTNET ,
22+ }
23+ }
24+
1625#[ derive( Debug , clap:: Args ) ]
1726pub struct Send {
1827 /// Path to encrypted sender key file
@@ -57,35 +66,16 @@ pub struct Send {
5766 #[ arg( long) ]
5867 pub fee_payer : Option < AccountPublicKey > ,
5968
60- /// Network to use for signing (mainnet or testnet)
61- #[ arg( long, default_value = "testnet" ) ]
62- pub network : NetworkArg ,
63-
6469 /// Node RPC endpoint
6570 #[ arg( long, default_value = "http://localhost:3000" ) ]
6671 pub node : String ,
6772}
6873
69- #[ derive( Debug , Clone , clap:: ValueEnum ) ]
70- pub enum NetworkArg {
71- Mainnet ,
72- Testnet ,
73- }
74-
75- impl From < NetworkArg > for mina_signer:: NetworkId {
76- fn from ( network : NetworkArg ) -> Self {
77- match network {
78- NetworkArg :: Mainnet => mina_signer:: NetworkId :: MAINNET ,
79- NetworkArg :: Testnet => mina_signer:: NetworkId :: TESTNET ,
80- }
81- }
82- }
83-
8474impl Send {
85- pub fn run ( self ) -> Result < ( ) > {
75+ pub fn run ( self , network : Network ) -> Result < ( ) > {
8676 // Check node is synced and on the correct network
8777 println ! ( "Checking node status..." ) ;
88- self . check_node_status ( ) ?;
78+ self . check_node_status ( & network ) ?;
8979
9080 // Load the sender's secret key
9181 let sender_key = AccountSecretKey :: from_encrypted_file ( & self . from , & self . password )
@@ -115,11 +105,14 @@ impl Send {
115105 . map_err ( |_| anyhow:: anyhow!( "Invalid receiver public key" ) ) ?;
116106
117107 // Fetch nonce from node if not provided
108+ // Note: GraphQL API expects nonce to be account_nonce, but we need to sign
109+ // with account_nonce for the first transaction from a new account
118110 let nonce = if let Some ( nonce) = self . nonce {
119111 nonce
120112 } else {
121113 println ! ( "Fetching nonce from node..." ) ;
122- self . fetch_nonce ( & fee_payer_pk) ?
114+ let current_nonce = self . fetch_nonce ( & fee_payer_pk) ?;
115+ current_nonce
123116 } ;
124117
125118 println ! ( "Using nonce: {}" , nonce) ;
@@ -144,7 +137,7 @@ impl Send {
144137
145138 // Sign the transaction
146139 println ! ( "Signing transaction..." ) ;
147- let network_id = self . network . clone ( ) . into ( ) ;
140+ let network_id = network_to_network_id ( & network ) ;
148141 let signed_command = self . sign_transaction ( payload, & sender_key, network_id) ?;
149142
150143 // Submit to node
@@ -160,8 +153,11 @@ impl Send {
160153 Ok ( ( ) )
161154 }
162155
163- fn check_node_status ( & self ) -> Result < ( ) > {
164- let client = reqwest:: blocking:: Client :: new ( ) ;
156+ fn check_node_status ( & self , network : & Network ) -> Result < ( ) > {
157+ let client = reqwest:: blocking:: Client :: builder ( )
158+ . timeout ( std:: time:: Duration :: from_secs ( 30 ) )
159+ . build ( )
160+ . context ( "Failed to create HTTP client" ) ?;
165161 let url = format ! ( "{}/graphql" , self . node) ;
166162
167163 // GraphQL query to check sync status and network ID
@@ -213,16 +209,16 @@ impl Send {
213209 . context ( "Network ID not found in GraphQL response" ) ?;
214210
215211 // Expected network ID based on selected network
216- let expected_network = match self . network {
217- NetworkArg :: Mainnet => "mina:mainnet" ,
218- NetworkArg :: Testnet => "mina:devnet" , // devnet is used for testnet
212+ let expected_network = match network {
213+ Network :: Mainnet => "mina:mainnet" ,
214+ Network :: Devnet => "mina:devnet" ,
219215 } ;
220216
221217 if !network_id. contains ( expected_network) {
222218 anyhow:: bail!(
223219 "Network mismatch: node is on '{}' but you selected {:?}. Use --network to specify the correct network." ,
224220 network_id,
225- self . network
221+ network
226222 ) ;
227223 }
228224
@@ -232,7 +228,10 @@ impl Send {
232228 }
233229
234230 fn fetch_nonce ( & self , sender_pk : & CompressedPubKey ) -> Result < u32 > {
235- let client = reqwest:: blocking:: Client :: new ( ) ;
231+ let client = reqwest:: blocking:: Client :: builder ( )
232+ . timeout ( std:: time:: Duration :: from_secs ( 30 ) )
233+ . build ( )
234+ . context ( "Failed to create HTTP client" ) ?;
236235 let url = format ! ( "{}/graphql" , self . node) ;
237236
238237 // GraphQL query to fetch account information
@@ -285,7 +284,8 @@ impl Send {
285284 // Create signer and sign the transaction
286285 let mut signer = mina_signer:: create_legacy ( network_id) ;
287286 let kp: Keypair = sender_key. clone ( ) . into ( ) ;
288- let signature = signer. sign ( & kp, & payload_to_sign, false ) ;
287+ // Use packed=true for OCaml/TypeScript compatibility (required by Mina protocol)
288+ let signature = signer. sign ( & kp, & payload_to_sign, true ) ;
289289
290290 Ok ( SignedCommand {
291291 payload,
@@ -295,7 +295,10 @@ impl Send {
295295 }
296296
297297 fn submit_transaction ( & self , signed_command : SignedCommand ) -> Result < String > {
298- let client = reqwest:: blocking:: Client :: new ( ) ;
298+ let client = reqwest:: blocking:: Client :: builder ( )
299+ . timeout ( std:: time:: Duration :: from_secs ( 120 ) )
300+ . build ( )
301+ . context ( "Failed to create HTTP client" ) ?;
299302 let url = format ! ( "{}/graphql" , self . node) ;
300303
301304 // Convert to v2 types for easier field extraction
@@ -317,6 +320,13 @@ impl Send {
317320
318321 let fee_payer_pk = signed_cmd_v2. payload . common . fee_payer_pk . to_string ( ) ;
319322
323+ // Build memo field - omit if empty
324+ let memo_field = if self . memo . is_empty ( ) {
325+ String :: new ( )
326+ } else {
327+ format ! ( r#"memo: "{}""# , self . memo)
328+ } ;
329+
320330 // Build GraphQL mutation
321331 let mutation = format ! (
322332 r#"mutation {{
@@ -326,7 +336,7 @@ impl Send {
326336 to: "{}"
327337 amount: "{}"
328338 fee: "{}"
329- memo: "{}"
339+ {}
330340 nonce: "{}"
331341 validUntil: "{}"
332342 }}
@@ -345,7 +355,7 @@ impl Send {
345355 receiver_pk,
346356 amount,
347357 * * * signed_cmd_v2. payload. common. fee,
348- signed_cmd_v2 . payload . common . memo . to_base58check ( ) ,
358+ memo_field ,
349359 * * signed_cmd_v2. payload. common. nonce,
350360 signed_cmd_v2. payload. common. valid_until. as_u32( ) ,
351361 sig_field,
0 commit comments