fuel_gql_client/client/schema/tx/
transparent_tx.rs

1use crate::{
2    client::schema::{
3        contract::ContractIdFragment,
4        schema,
5        tx::{
6            transparent_receipt::Receipt,
7            TransactionStatus,
8            TxIdArgs,
9        },
10        Address,
11        AssetId,
12        Bytes32,
13        ConnectionArgs,
14        ConversionError,
15        HexString,
16        MessageId,
17        PageInfo,
18        Salt,
19        TransactionId,
20        TxPointer,
21        UtxoId,
22        U64,
23    },
24    fuel_tx::field::ReceiptsRoot,
25};
26use core::convert::{
27    TryFrom,
28    TryInto,
29};
30use fuel_vm::fuel_tx::StorageSlot;
31use itertools::Itertools;
32
33/// Retrieves the transaction in opaque form
34#[derive(cynic::QueryFragment, Debug)]
35#[cynic(
36    schema_path = "./assets/schema.sdl",
37    graphql_type = "Query",
38    variables = "TxIdArgs"
39)]
40pub struct TransactionQuery {
41    #[arguments(id: $id)]
42    pub transaction: Option<Transaction>,
43}
44
45#[derive(cynic::QueryFragment, Debug)]
46#[cynic(
47    schema_path = "./assets/schema.sdl",
48    graphql_type = "Query",
49    variables = "ConnectionArgs"
50)]
51pub struct TransactionsQuery {
52    #[arguments(after: $after, before: $before, first: $first, last: $last)]
53    pub transactions: TransactionConnection,
54}
55
56#[derive(cynic::QueryFragment, Debug)]
57#[cynic(schema_path = "./assets/schema.sdl")]
58pub struct TransactionConnection {
59    pub edges: Vec<TransactionEdge>,
60    pub page_info: PageInfo,
61}
62
63#[derive(cynic::QueryFragment, Debug)]
64#[cynic(schema_path = "./assets/schema.sdl")]
65pub struct TransactionEdge {
66    pub cursor: String,
67    pub node: Transaction,
68}
69
70#[derive(cynic::QueryFragment, Debug)]
71#[cynic(schema_path = "./assets/schema.sdl")]
72pub struct Transaction {
73    pub gas_limit: Option<U64>,
74    pub gas_price: Option<U64>,
75    pub id: TransactionId,
76    pub tx_pointer: Option<TxPointer>,
77    pub input_asset_ids: Option<Vec<AssetId>>,
78    pub input_contracts: Option<Vec<ContractIdFragment>>,
79    pub inputs: Option<Vec<Input>>,
80    pub is_script: bool,
81    pub is_create: bool,
82    pub is_mint: bool,
83    pub outputs: Vec<Output>,
84    pub maturity: Option<U64>,
85    pub receipts_root: Option<Bytes32>,
86    pub status: Option<TransactionStatus>,
87    pub witnesses: Option<Vec<HexString>>,
88    pub receipts: Option<Vec<Receipt>>,
89    pub script: Option<HexString>,
90    pub script_data: Option<HexString>,
91    pub salt: Option<Salt>,
92    pub storage_slots: Option<Vec<HexString>>,
93    pub bytecode_witness_index: Option<i32>,
94    pub bytecode_length: Option<U64>,
95}
96
97impl TryFrom<Transaction> for fuel_vm::prelude::Transaction {
98    type Error = ConversionError;
99
100    fn try_from(tx: Transaction) -> Result<Self, Self::Error> {
101        let tx = if tx.is_script {
102            let mut script = fuel_vm::prelude::Transaction::script(
103                tx.gas_price
104                    .ok_or_else(|| {
105                        ConversionError::MissingField("gas_price".to_string())
106                    })?
107                    .into(),
108                tx.gas_limit
109                    .ok_or_else(|| {
110                        ConversionError::MissingField("gas_limit".to_string())
111                    })?
112                    .into(),
113                tx.maturity
114                    .ok_or_else(|| ConversionError::MissingField("maturity".to_string()))?
115                    .into(),
116                tx.script
117                    .ok_or_else(|| ConversionError::MissingField("script".to_string()))?
118                    .into(),
119                tx.script_data
120                    .ok_or_else(|| {
121                        ConversionError::MissingField("script_data".to_string())
122                    })?
123                    .into(),
124                tx.inputs
125                    .ok_or_else(|| ConversionError::MissingField("inputs".to_string()))?
126                    .into_iter()
127                    .map(TryInto::try_into)
128                    .collect::<Result<Vec<::fuel_vm::fuel_tx::Input>, ConversionError>>(
129                    )?,
130                tx.outputs
131                    .into_iter()
132                    .map(TryInto::try_into)
133                    .collect::<Result<Vec<::fuel_vm::fuel_tx::Output>, ConversionError>>(
134                    )?,
135                tx.witnesses
136                    .ok_or_else(|| {
137                        ConversionError::MissingField("witnesses".to_string())
138                    })?
139                    .into_iter()
140                    .map(|w| w.0 .0.into())
141                    .collect(),
142            );
143            *script.receipts_root_mut() = tx
144                .receipts_root
145                .ok_or_else(|| {
146                    ConversionError::MissingField("receipts_root".to_string())
147                })?
148                .into();
149            script.into()
150        } else if tx.is_create {
151            let create = fuel_vm::prelude::Transaction::create(
152                tx.gas_price
153                    .ok_or_else(|| {
154                        ConversionError::MissingField("gas_price".to_string())
155                    })?
156                    .into(),
157                tx.gas_limit
158                    .ok_or_else(|| {
159                        ConversionError::MissingField("gas_limit".to_string())
160                    })?
161                    .into(),
162                tx.maturity
163                    .ok_or_else(|| ConversionError::MissingField("maturity".to_string()))?
164                    .into(),
165                tx.bytecode_witness_index
166                    .ok_or_else(|| {
167                        ConversionError::MissingField(
168                            "bytecode_witness_index".to_string(),
169                        )
170                    })?
171                    .try_into()?,
172                tx.salt
173                    .ok_or_else(|| ConversionError::MissingField("salt".to_string()))?
174                    .into(),
175                tx.storage_slots
176                    .ok_or_else(|| {
177                        ConversionError::MissingField("storage_slots".to_string())
178                    })?
179                    .into_iter()
180                    .map(|slot| {
181                        if slot.0 .0.len() != 64 {
182                            return Err(ConversionError::BytesLength)
183                        }
184                        let key = &slot.0 .0[0..32];
185                        let value = &slot.0 .0[32..];
186                        Ok(StorageSlot::new(
187                            // unwrap is safe because length is checked
188                            ::fuel_vm::fuel_types::Bytes32::try_from(key)
189                                .map_err(|_| ConversionError::BytesLength)?,
190                            ::fuel_vm::fuel_types::Bytes32::try_from(value)
191                                .map_err(|_| ConversionError::BytesLength)?,
192                        ))
193                    })
194                    .try_collect()?,
195                tx.inputs
196                    .ok_or_else(|| ConversionError::MissingField("inputs".to_string()))?
197                    .into_iter()
198                    .map(TryInto::try_into)
199                    .collect::<Result<Vec<::fuel_vm::fuel_tx::Input>, ConversionError>>(
200                    )?,
201                tx.outputs
202                    .into_iter()
203                    .map(TryInto::try_into)
204                    .collect::<Result<Vec<::fuel_vm::fuel_tx::Output>, ConversionError>>(
205                    )?,
206                tx.witnesses
207                    .ok_or_else(|| {
208                        ConversionError::MissingField("witnesses".to_string())
209                    })?
210                    .into_iter()
211                    .map(|w| w.0 .0.into())
212                    .collect(),
213            );
214            create.into()
215        } else {
216            let tx_pointer: fuel_vm::prelude::TxPointer = tx
217                .tx_pointer
218                .ok_or_else(|| ConversionError::MissingField("tx_pointer".to_string()))?
219                .into();
220            let mint = fuel_vm::prelude::Transaction::mint(
221                tx_pointer,
222                tx.outputs
223                    .into_iter()
224                    .map(TryInto::try_into)
225                    .collect::<Result<Vec<::fuel_vm::fuel_tx::Output>, ConversionError>>(
226                    )?,
227            );
228            mint.into()
229        };
230
231        // This `match` block is added here to enforce compilation error if a new variant
232        // is added into the `fuel_tx::Transaction` enum.
233        //
234        // If you face a compilation error, please update the code above and add a new variant below.
235        match tx {
236            fuel_vm::prelude::Transaction::Script(_) => {}
237            fuel_vm::prelude::Transaction::Create(_) => {}
238            fuel_vm::prelude::Transaction::Mint(_) => {}
239        };
240
241        Ok(tx)
242    }
243}
244
245#[derive(cynic::InlineFragments, Debug)]
246#[cynic(schema_path = "./assets/schema.sdl")]
247pub enum Input {
248    InputCoin(InputCoin),
249    InputContract(InputContract),
250    InputMessage(InputMessage),
251    #[cynic(fallback)]
252    Unknown,
253}
254
255#[derive(cynic::QueryFragment, Debug)]
256#[cynic(schema_path = "./assets/schema.sdl")]
257pub struct InputCoin {
258    pub utxo_id: UtxoId,
259    pub owner: Address,
260    pub amount: U64,
261    pub asset_id: AssetId,
262    pub tx_pointer: TxPointer,
263    pub witness_index: i32,
264    pub maturity: U64,
265    pub predicate: HexString,
266    pub predicate_data: HexString,
267}
268
269#[derive(cynic::QueryFragment, Debug)]
270#[cynic(schema_path = "./assets/schema.sdl")]
271pub struct InputContract {
272    pub utxo_id: UtxoId,
273    pub balance_root: Bytes32,
274    pub state_root: Bytes32,
275    pub tx_pointer: TxPointer,
276    pub contract: ContractIdFragment,
277}
278
279#[derive(cynic::QueryFragment, Debug)]
280#[cynic(schema_path = "./assets/schema.sdl")]
281pub struct InputMessage {
282    message_id: MessageId,
283    sender: Address,
284    recipient: Address,
285    amount: U64,
286    nonce: U64,
287    witness_index: i32,
288    data: HexString,
289    predicate: HexString,
290    predicate_data: HexString,
291}
292
293impl TryFrom<Input> for ::fuel_vm::fuel_tx::Input {
294    type Error = ConversionError;
295
296    fn try_from(input: Input) -> Result<::fuel_vm::fuel_tx::Input, Self::Error> {
297        Ok(match input {
298            Input::InputCoin(coin) => {
299                if coin.predicate.0 .0.is_empty() {
300                    ::fuel_vm::fuel_tx::Input::CoinSigned {
301                        utxo_id: coin.utxo_id.into(),
302                        owner: coin.owner.into(),
303                        amount: coin.amount.into(),
304                        asset_id: coin.asset_id.into(),
305                        tx_pointer: coin.tx_pointer.into(),
306                        witness_index: coin.witness_index.try_into()?,
307                        maturity: coin.maturity.into(),
308                    }
309                } else {
310                    ::fuel_vm::fuel_tx::Input::CoinPredicate {
311                        utxo_id: coin.utxo_id.into(),
312                        owner: coin.owner.into(),
313                        amount: coin.amount.into(),
314                        asset_id: coin.asset_id.into(),
315                        maturity: coin.maturity.into(),
316                        tx_pointer: coin.tx_pointer.into(),
317                        predicate: coin.predicate.into(),
318                        predicate_data: coin.predicate_data.into(),
319                    }
320                }
321            }
322            Input::InputContract(contract) => ::fuel_vm::fuel_tx::Input::Contract {
323                utxo_id: contract.utxo_id.into(),
324                balance_root: contract.balance_root.into(),
325                state_root: contract.state_root.into(),
326                tx_pointer: contract.tx_pointer.into(),
327                contract_id: contract.contract.id.into(),
328            },
329            Input::InputMessage(message) => {
330                if message.predicate.0 .0.is_empty() {
331                    ::fuel_vm::fuel_tx::Input::MessageSigned {
332                        message_id: message.message_id.into(),
333                        sender: message.sender.into(),
334                        recipient: message.recipient.into(),
335                        amount: message.amount.into(),
336                        nonce: message.nonce.into(),
337                        witness_index: message.witness_index.try_into()?,
338                        data: message.data.into(),
339                    }
340                } else {
341                    ::fuel_vm::fuel_tx::Input::MessagePredicate {
342                        message_id: message.message_id.into(),
343                        sender: message.sender.into(),
344                        recipient: message.recipient.into(),
345                        amount: message.amount.into(),
346                        nonce: message.nonce.into(),
347                        data: message.data.into(),
348                        predicate: message.predicate.into(),
349                        predicate_data: message.predicate_data.into(),
350                    }
351                }
352            }
353            Input::Unknown => return Err(Self::Error::UnknownVariant("Input")),
354        })
355    }
356}
357
358#[derive(cynic::InlineFragments, Debug)]
359#[cynic(schema_path = "./assets/schema.sdl")]
360pub enum Output {
361    CoinOutput(CoinOutput),
362    ContractOutput(ContractOutput),
363    MessageOutput(MessageOutput),
364    ChangeOutput(ChangeOutput),
365    VariableOutput(VariableOutput),
366    ContractCreated(ContractCreated),
367    #[cynic(fallback)]
368    Unknown,
369}
370
371#[derive(cynic::QueryFragment, Debug)]
372#[cynic(schema_path = "./assets/schema.sdl")]
373pub struct CoinOutput {
374    pub to: Address,
375    pub amount: U64,
376    pub asset_id: AssetId,
377}
378
379#[derive(cynic::QueryFragment, Debug)]
380#[cynic(schema_path = "./assets/schema.sdl")]
381pub struct MessageOutput {
382    pub recipient: Address,
383    pub amount: U64,
384}
385
386#[derive(cynic::QueryFragment, Debug)]
387#[cynic(schema_path = "./assets/schema.sdl")]
388pub struct ChangeOutput {
389    pub to: Address,
390    pub amount: U64,
391    pub asset_id: AssetId,
392}
393
394#[derive(cynic::QueryFragment, Debug)]
395#[cynic(schema_path = "./assets/schema.sdl")]
396pub struct VariableOutput {
397    pub to: Address,
398    pub amount: U64,
399    pub asset_id: AssetId,
400}
401
402#[derive(cynic::QueryFragment, Debug)]
403#[cynic(schema_path = "./assets/schema.sdl")]
404pub struct ContractOutput {
405    pub input_index: i32,
406    pub balance_root: Bytes32,
407    pub state_root: Bytes32,
408}
409
410#[derive(cynic::QueryFragment, Debug)]
411#[cynic(schema_path = "./assets/schema.sdl")]
412pub struct ContractCreated {
413    contract: ContractIdFragment,
414    state_root: Bytes32,
415}
416
417impl TryFrom<Output> for ::fuel_vm::fuel_tx::Output {
418    type Error = ConversionError;
419
420    fn try_from(value: Output) -> Result<Self, Self::Error> {
421        Ok(match value {
422            Output::CoinOutput(coin) => Self::Coin {
423                to: coin.to.into(),
424                amount: coin.amount.into(),
425                asset_id: coin.asset_id.into(),
426            },
427            Output::ContractOutput(contract) => Self::Contract {
428                input_index: contract.input_index.try_into()?,
429                balance_root: contract.balance_root.into(),
430                state_root: contract.state_root.into(),
431            },
432            Output::MessageOutput(message) => Self::Message {
433                recipient: message.recipient.into(),
434                amount: message.amount.into(),
435            },
436            Output::ChangeOutput(change) => Self::Change {
437                to: change.to.into(),
438                amount: change.amount.into(),
439                asset_id: change.asset_id.into(),
440            },
441            Output::VariableOutput(variable) => Self::Variable {
442                to: variable.to.into(),
443                amount: variable.amount.into(),
444                asset_id: variable.asset_id.into(),
445            },
446            Output::ContractCreated(contract) => Self::ContractCreated {
447                contract_id: contract.contract.id.into(),
448                state_root: contract.state_root.into(),
449            },
450            Output::Unknown => return Err(Self::Error::UnknownVariant("Output")),
451        })
452    }
453}