openmina_node_native/graphql/
user_command.rs

1use 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)]
19// pub struct InputGraphQLSendPayment {
20//     pub input: InputGraphQLPayment,
21//     pub signature: UserCommandSignature,
22// }
23
24#[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    // Note: either raw_signature or scalar + field must be provided
50    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    // TODO: add the account type
132    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    // TODO: add the account type
141    pub receiver: String,
142    // TODO: add the account type
143    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}