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#[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 ::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 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}