openmina_node_native/graphql/
user_command.rs1use std::str::FromStr;
2
3use juniper::{GraphQLInputObject, GraphQLObject};
4use ledger::scan_state::{
5 currency::{Amount, Fee, Magnitude, Nonce, Slot},
6 transaction_logic::{signed_command, Memo},
7};
8use mina_p2p_messages::{
9 bigint::BigInt,
10 v2::{self, TokenIdKeyHash},
11};
12use mina_signer::CompressedPubKey;
13use node::account::AccountPublicKey;
14use o1_utils::field_helpers::FieldHelpers;
15
16use super::zkapp::GraphQLFailureReason;
17
18#[derive(GraphQLInputObject, Debug)]
25pub struct InputGraphQLPayment {
26 pub from: String,
27 pub to: String,
28 pub amount: String,
29 pub valid_until: Option<String>,
30 pub fee: String,
31 pub memo: Option<String>,
32 pub nonce: Option<String>,
33}
34
35#[derive(GraphQLInputObject, Debug)]
36pub struct InputGraphQLDelegation {
37 pub from: String,
38 pub to: String,
39 pub valid_until: Option<String>,
40 pub fee: String,
41 pub memo: Option<String>,
42 pub nonce: Option<String>,
43}
44
45#[derive(GraphQLInputObject, Debug, Clone)]
46pub struct UserCommandSignature {
47 pub field: Option<String>,
48 pub scalar: Option<String>,
49 pub raw_signature: Option<String>,
51}
52
53impl TryFrom<UserCommandSignature> for mina_signer::Signature {
54 type Error = super::ConversionError;
55
56 fn try_from(value: UserCommandSignature) -> Result<Self, Self::Error> {
57 let UserCommandSignature {
58 field,
59 scalar,
60 raw_signature,
61 } = value;
62
63 if let Some(raw_signature) = raw_signature {
64 let sig_parts_len = raw_signature
65 .len()
66 .checked_div(2)
67 .ok_or(super::ConversionError::InvalidLength)?;
68 let (rx_hex, s_hex) = raw_signature.split_at(sig_parts_len);
69
70 let rx_bytes = hex::decode(rx_hex).map_err(|_| super::ConversionError::InvalidHex)?;
71 let s_bytes = hex::decode(s_hex).map_err(|_| super::ConversionError::InvalidHex)?;
72
73 let rx = mina_signer::BaseField::from_bytes(&rx_bytes)?;
74 let s = mina_signer::ScalarField::from_bytes(&s_bytes)?;
75
76 Ok(Self { rx, s })
77 } else if let (Some(field), Some(scalar)) = (field, scalar) {
78 let sig = Self {
79 rx: BigInt::from_decimal(&field)?
80 .try_into()
81 .map_err(|_| super::ConversionError::InvalidBigInt)?,
82 s: BigInt::from_decimal(&scalar)?
83 .try_into()
84 .map_err(|_| super::ConversionError::InvalidBigInt)?,
85 };
86
87 Ok(sig)
88 } else {
89 Err(super::ConversionError::MissingField(
90 "Either raw_signature or scalar + field must be provided".to_string(),
91 ))
92 }
93 }
94}
95
96impl TryFrom<&UserCommandSignature> for mina_signer::Signature {
97 type Error = super::ConversionError;
98
99 fn try_from(value: &UserCommandSignature) -> Result<Self, Self::Error> {
100 value.clone().try_into()
101 }
102}
103
104impl UserCommandSignature {
105 pub fn validate(&self) -> Result<(), super::Error> {
106 if self.raw_signature.is_some() || (self.scalar.is_some() && self.field.is_some()) {
107 Ok(())
108 } else {
109 Err(super::Error::Custom(
110 "Either raw_signature or scalar + field must be provided".to_string(),
111 ))
112 }
113 }
114}
115
116#[derive(GraphQLObject, Debug)]
117pub struct GraphQLSendPaymentResponse {
118 pub payment: GraphQLUserCommand,
119}
120
121#[derive(GraphQLObject, Debug)]
122pub struct GraphQLSendDelegationResponse {
123 pub delegation: GraphQLUserCommand,
124}
125
126#[derive(GraphQLObject, Debug)]
127pub struct GraphQLUserCommand {
128 pub amount: String,
129 pub fee: String,
130 pub failure_reason: Option<GraphQLFailureReason>,
131 pub fee_payer: String,
133 pub fee_token: String,
134 pub hash: String,
135 pub id: String,
136 pub is_delegation: bool,
137 pub kind: String,
138 pub memo: String,
139 pub nonce: String,
140 pub receiver: String,
142 pub source: String,
144 pub token: String,
145 pub valid_until: String,
146}
147
148impl TryFrom<v2::MinaBaseUserCommandStableV2> for GraphQLSendPaymentResponse {
149 type Error = super::ConversionError;
150 fn try_from(value: v2::MinaBaseUserCommandStableV2) -> Result<Self, Self::Error> {
151 if let v2::MinaBaseUserCommandStableV2::SignedCommand(ref signed_cmd) = value {
152 if let v2::MinaBaseSignedCommandPayloadBodyStableV2::Payment(ref payment) =
153 signed_cmd.payload.body
154 {
155 let res = GraphQLSendPaymentResponse {
156 payment: GraphQLUserCommand {
157 amount: payment.amount.to_string(),
158 fee: signed_cmd.payload.common.fee.to_string(),
159 failure_reason: None,
160 fee_payer: signed_cmd.payload.common.fee_payer_pk.to_string(),
161 fee_token: TokenIdKeyHash::default().to_string(),
162 hash: signed_cmd.hash()?.to_string(),
163 id: signed_cmd.to_base64()?,
164 is_delegation: false,
165 kind: "PAYMENT".to_string(),
166 memo: signed_cmd.payload.common.memo.to_base58check(),
167 nonce: signed_cmd.payload.common.nonce.to_string(),
168 receiver: payment.receiver_pk.to_string(),
169 source: signed_cmd.payload.common.fee_payer_pk.to_string(),
170 token: TokenIdKeyHash::default().to_string(),
171 valid_until: signed_cmd.payload.common.valid_until.as_u32().to_string(),
172 },
173 };
174 Ok(res)
175 } else {
176 Err(super::ConversionError::WrongVariant)
177 }
178 } else {
179 Err(super::ConversionError::WrongVariant)
180 }
181 }
182}
183
184impl TryFrom<v2::MinaBaseUserCommandStableV2> for GraphQLSendDelegationResponse {
185 type Error = super::ConversionError;
186 fn try_from(value: v2::MinaBaseUserCommandStableV2) -> Result<Self, Self::Error> {
187 if let v2::MinaBaseUserCommandStableV2::SignedCommand(ref signed_cmd) = value {
188 if let v2::MinaBaseSignedCommandPayloadBodyStableV2::StakeDelegation(ref delegation) =
189 signed_cmd.payload.body
190 {
191 let v2::MinaBaseStakeDelegationStableV2::SetDelegate { new_delegate } = delegation;
192 let res = GraphQLSendDelegationResponse {
193 delegation: GraphQLUserCommand {
194 amount: "0".to_string(),
195 fee: signed_cmd.payload.common.fee.to_string(),
196 failure_reason: None,
197 fee_payer: signed_cmd.payload.common.fee_payer_pk.to_string(),
198 fee_token: TokenIdKeyHash::default().to_string(),
199 hash: signed_cmd.hash()?.to_string(),
200 id: signed_cmd.to_base64()?,
201 is_delegation: true,
202 kind: "STAKE_DELEGATION".to_string(),
203 memo: signed_cmd.payload.common.memo.to_base58check(),
204 nonce: signed_cmd.payload.common.nonce.to_string(),
205 receiver: new_delegate.to_string(),
206 source: signed_cmd.payload.common.fee_payer_pk.to_string(),
207 token: TokenIdKeyHash::default().to_string(),
208 valid_until: signed_cmd.payload.common.valid_until.as_u32().to_string(),
209 },
210 };
211 Ok(res)
212 } else {
213 Err(super::ConversionError::WrongVariant)
214 }
215 } else {
216 Err(super::ConversionError::WrongVariant)
217 }
218 }
219}
220
221impl InputGraphQLPayment {
222 pub fn create_user_command(
223 &self,
224 infered_nonce: Nonce,
225 signature: UserCommandSignature,
226 ) -> Result<v2::MinaBaseUserCommandStableV2, super::ConversionError> {
227 let infered_nonce = infered_nonce.incr();
228
229 let nonce = if let Some(nonce) = &self.nonce {
230 let input_nonce = Nonce::from_u32(
231 nonce
232 .parse::<u32>()
233 .map_err(|_| super::ConversionError::InvalidBigInt)?,
234 );
235
236 if input_nonce.is_zero() || input_nonce > infered_nonce {
237 return Err(super::ConversionError::Custom(
238 "Provided nonce is zero or greater than infered nonce".to_string(),
239 ));
240 } else {
241 input_nonce
242 }
243 } else {
244 infered_nonce
245 };
246
247 let valid_until = if let Some(valid_until) = &self.valid_until {
248 Some(Slot::from_u32(
249 valid_until
250 .parse::<u32>()
251 .map_err(|_| super::ConversionError::InvalidBigInt)?,
252 ))
253 } else {
254 None
255 };
256
257 let memo = if let Some(memo) = &self.memo {
258 Memo::from_str(memo)
259 .map_err(|_| super::ConversionError::Custom("Invalid memo".to_string()))?
260 } else {
261 Memo::empty()
262 };
263
264 let from: CompressedPubKey = AccountPublicKey::from_str(&self.from)?
265 .try_into()
266 .map_err(|_| super::ConversionError::InvalidBigInt)?;
267
268 let signature = signature.try_into()?;
269
270 let sc: signed_command::SignedCommand = signed_command::SignedCommand {
271 payload: signed_command::SignedCommandPayload::create(
272 Fee::from_u64(
273 self.fee
274 .parse::<u64>()
275 .map_err(|_| super::ConversionError::InvalidBigInt)?,
276 ),
277 from.clone(),
278 nonce,
279 valid_until,
280 memo,
281 signed_command::Body::Payment(signed_command::PaymentPayload {
282 receiver_pk: AccountPublicKey::from_str(&self.to)?
283 .try_into()
284 .map_err(|_| super::ConversionError::InvalidBigInt)?,
285 amount: Amount::from_u64(
286 self.amount
287 .parse::<u64>()
288 .map_err(|_| super::ConversionError::InvalidBigInt)?,
289 ),
290 }),
291 ),
292 signer: from.clone(),
293 signature,
294 };
295
296 Ok(v2::MinaBaseUserCommandStableV2::SignedCommand(sc.into()))
297 }
298}
299
300impl InputGraphQLDelegation {
301 pub fn create_user_command(
302 &self,
303 infered_nonce: Nonce,
304 signature: UserCommandSignature,
305 ) -> Result<v2::MinaBaseUserCommandStableV2, super::ConversionError> {
306 let infered_nonce = infered_nonce.incr();
307
308 let nonce = if let Some(nonce) = &self.nonce {
309 let input_nonce = Nonce::from_u32(
310 nonce
311 .parse::<u32>()
312 .map_err(|_| super::ConversionError::InvalidBigInt)?,
313 );
314
315 if input_nonce.is_zero() || input_nonce > infered_nonce {
316 return Err(super::ConversionError::Custom(
317 "Provided nonce is zero or greater than infered nonce".to_string(),
318 ));
319 } else {
320 input_nonce
321 }
322 } else {
323 infered_nonce
324 };
325
326 let valid_until = if let Some(valid_until) = &self.valid_until {
327 Some(Slot::from_u32(
328 valid_until
329 .parse::<u32>()
330 .map_err(|_| super::ConversionError::InvalidBigInt)?,
331 ))
332 } else {
333 None
334 };
335
336 let memo = if let Some(memo) = &self.memo {
337 Memo::from_str(memo)
338 .map_err(|_| super::ConversionError::Custom("Invalid memo".to_string()))?
339 } else {
340 Memo::empty()
341 };
342
343 let from: CompressedPubKey = AccountPublicKey::from_str(&self.from)?
344 .try_into()
345 .map_err(|_| super::ConversionError::InvalidBigInt)?;
346
347 let signature = signature.try_into()?;
348
349 let sc: signed_command::SignedCommand = signed_command::SignedCommand {
350 payload: signed_command::SignedCommandPayload::create(
351 Fee::from_u64(
352 self.fee
353 .parse::<u64>()
354 .map_err(|_| super::ConversionError::InvalidBigInt)?,
355 ),
356 from.clone(),
357 nonce,
358 valid_until,
359 memo,
360 signed_command::Body::StakeDelegation(
361 signed_command::StakeDelegationPayload::SetDelegate {
362 new_delegate: AccountPublicKey::from_str(&self.to)?
363 .try_into()
364 .map_err(|_| super::ConversionError::InvalidBigInt)?,
365 },
366 ),
367 ),
368 signer: from.clone(),
369 signature,
370 };
371
372 Ok(v2::MinaBaseUserCommandStableV2::SignedCommand(sc.into()))
373 }
374}