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