Skip to main content

blokli_client/api/v1/graphql/
txs.rs

1use super::{DateTime, Hex32, schema};
2use crate::{
3    api::TxReceipt,
4    errors::{BlokliClientError, ErrorKind},
5};
6
7#[derive(cynic::QueryVariables)]
8pub struct TransactionsVariables {
9    pub id: cynic::Id,
10}
11
12#[derive(cynic::QueryVariables)]
13pub struct SendTransactionVariables {
14    pub raw_transaction: String,
15}
16
17#[derive(cynic::QueryVariables)]
18pub struct ConfirmTransactionVariables {
19    pub raw_transaction: String,
20    pub confirmations: i32,
21}
22
23#[derive(cynic::QueryFragment, Debug)]
24#[cynic(graphql_type = "QueryRoot", variables = "TransactionsVariables")]
25pub struct QueryTransaction {
26    #[arguments(id: $id)]
27    pub transaction: Option<TransactionResult>,
28}
29
30#[derive(cynic::QueryFragment, Debug)]
31#[cynic(graphql_type = "SubscriptionRoot", variables = "TransactionsVariables")]
32pub struct SubscribeTransaction {
33    #[arguments(id: $id)]
34    pub transaction_updated: Transaction,
35}
36
37#[derive(cynic::QueryFragment, Debug)]
38#[cynic(graphql_type = "MutationRoot", variables = "ConfirmTransactionVariables")]
39pub struct MutateConfirmTransaction {
40    #[arguments(input: { rawTransaction: $raw_transaction }, confirmations: $confirmations)]
41    pub send_transaction_sync: SendTransactionSyncResult,
42}
43
44#[derive(cynic::QueryFragment, Debug)]
45#[cynic(graphql_type = "MutationRoot", variables = "SendTransactionVariables")]
46pub struct MutateTrackTransaction {
47    #[arguments(input: { rawTransaction: $raw_transaction })]
48    pub send_transaction_async: SendTransactionAsyncResult,
49}
50
51#[derive(cynic::QueryFragment, Debug)]
52#[cynic(graphql_type = "MutationRoot", variables = "SendTransactionVariables")]
53pub struct MutateSendTransaction {
54    #[arguments(input: { rawTransaction: $raw_transaction })]
55    pub send_transaction: SendTransactionResult,
56}
57
58/// Safe module execution details associated with a transaction.
59#[derive(cynic::QueryFragment, Debug, Clone, PartialEq, Eq)]
60#[cfg_attr(feature = "serde", derive(serde::Serialize))]
61pub struct SafeExecution {
62    /// Whether the safe module execution succeeded.
63    pub success: bool,
64    /// Safe transaction hash, when safe execution data is available.
65    #[cynic(rename = "safeTxHash")]
66    pub safe_tx_hash: Option<Hex32>,
67    /// Revert reason reported by safe execution, when available.
68    #[cynic(rename = "revertReason")]
69    pub revert_reason: Option<String>,
70}
71
72/// Transaction tracked by Blokli.
73#[derive(cynic::QueryFragment, Debug, Clone, PartialEq, Eq)]
74#[cfg_attr(feature = "serde", derive(serde::Serialize))]
75pub struct Transaction {
76    /// Blokli tracking id.
77    pub id: cynic::Id,
78    /// Current transaction status.
79    pub status: TransactionStatus,
80    /// Timestamp at which Blokli accepted or observed the submission.
81    pub submitted_at: DateTime,
82    /// On-chain transaction hash.
83    pub transaction_hash: Hex32,
84    /// Safe execution details for safe module transactions.
85    #[cynic(rename = "safeExecution")]
86    pub safe_execution: Option<SafeExecution>,
87}
88
89/// Error returned for an unknown or invalid Blokli transaction tracking id.
90#[derive(cynic::QueryFragment, Debug)]
91pub struct InvalidTransactionIdError {
92    /// GraphQL concrete type name.
93    pub __typename: String,
94    /// Stable Blokli error code.
95    pub code: String,
96    /// Human-readable error message from Blokli.
97    pub message: String,
98}
99
100impl From<InvalidTransactionIdError> for BlokliClientError {
101    fn from(value: InvalidTransactionIdError) -> Self {
102        ErrorKind::BlokliError {
103            kind: "invalid transaction id",
104            code: value.code,
105            message: value.message,
106        }
107        .into()
108    }
109}
110
111#[derive(cynic::InlineFragments, Debug)]
112pub enum TransactionResult {
113    Transaction(Transaction),
114    InvalidTransactionIdError(InvalidTransactionIdError),
115    #[cynic(fallback)]
116    Unknown,
117}
118
119impl From<TransactionResult> for Result<Transaction, BlokliClientError> {
120    fn from(value: TransactionResult) -> Self {
121        match value {
122            TransactionResult::Transaction(t) => Ok(t),
123            TransactionResult::InvalidTransactionIdError(e) => Err(e.into()),
124            TransactionResult::Unknown => Err(ErrorKind::NoData.into()),
125        }
126    }
127}
128
129/// Transaction lifecycle state reported by Blokli.
130#[derive(cynic::Enum, Clone, Copy, Debug, PartialEq, Eq)]
131pub enum TransactionStatus {
132    /// Transaction reached the configured confirmation requirement.
133    #[cynic(rename = "CONFIRMED")]
134    Confirmed,
135    /// Transaction is being processed by Blokli.
136    #[cynic(rename = "PENDING")]
137    Pending,
138    /// Transaction executed and reverted.
139    #[cynic(rename = "REVERTED")]
140    Reverted,
141    /// Blokli failed to submit the transaction.
142    #[cynic(rename = "SUBMISSION_FAILED")]
143    SubmissionFailed,
144    /// Transaction was submitted to the chain.
145    #[cynic(rename = "SUBMITTED")]
146    Submitted,
147    /// Transaction tracking timed out.
148    #[cynic(rename = "TIMEOUT")]
149    Timeout,
150    /// Blokli rejected the transaction before submission.
151    #[cynic(rename = "VALIDATION_FAILED")]
152    ValidationFailed,
153}
154
155/// Timeout returned by a synchronous transaction operation.
156#[derive(cynic::QueryFragment, Debug)]
157pub struct TimeoutError {
158    /// GraphQL concrete type name.
159    pub __typename: String,
160    /// Stable Blokli error code.
161    pub code: String,
162    /// Human-readable error message from Blokli.
163    pub message: String,
164}
165
166impl From<TimeoutError> for BlokliClientError {
167    fn from(value: TimeoutError) -> Self {
168        ErrorKind::BlokliError {
169            kind: "timeout",
170            code: value.code,
171            message: value.message,
172        }
173        .into()
174    }
175}
176
177/// Successful immediate transaction submission result.
178#[derive(cynic::QueryFragment, Debug)]
179pub struct SendTransactionSuccess {
180    /// GraphQL concrete type name.
181    pub __typename: String,
182    /// On-chain transaction hash.
183    pub transaction_hash: Hex32,
184}
185
186/// RPC error returned by Blokli while submitting a transaction.
187#[derive(cynic::QueryFragment, Debug)]
188pub struct RpcError {
189    /// GraphQL concrete type name.
190    pub __typename: String,
191    /// Stable Blokli error code.
192    pub code: String,
193    /// Human-readable error message from Blokli.
194    pub message: String,
195}
196
197impl From<RpcError> for BlokliClientError {
198    fn from(value: RpcError) -> Self {
199        ErrorKind::BlokliError {
200            kind: "rpc error",
201            code: value.code,
202            message: value.message,
203        }
204        .into()
205    }
206}
207
208/// Error returned when a transaction calls a function that Blokli policy rejects.
209#[derive(cynic::QueryFragment, Debug)]
210pub struct FunctionNotAllowedError {
211    /// GraphQL concrete type name.
212    pub __typename: String,
213    /// Stable Blokli error code.
214    pub code: String,
215    /// Contract address containing the rejected function.
216    pub contract_address: String,
217    /// Rejected function selector.
218    pub function_selector: String,
219    /// Human-readable error message from Blokli.
220    pub message: String,
221}
222
223impl From<FunctionNotAllowedError> for BlokliClientError {
224    fn from(value: FunctionNotAllowedError) -> Self {
225        ErrorKind::BlokliError {
226            kind: "function not allowed",
227            code: value.code,
228            message: value.message,
229        }
230        .into()
231    }
232}
233
234/// Error returned when a transaction targets a contract that Blokli policy rejects.
235#[derive(cynic::QueryFragment, Debug)]
236pub struct ContractNotAllowedError {
237    /// GraphQL concrete type name.
238    pub __typename: String,
239    /// Stable Blokli error code.
240    pub code: String,
241    /// Rejected contract address.
242    pub contract_address: String,
243    /// Human-readable error message from Blokli.
244    pub message: String,
245}
246
247impl From<ContractNotAllowedError> for BlokliClientError {
248    fn from(value: ContractNotAllowedError) -> Self {
249        ErrorKind::BlokliError {
250            kind: "contract not allowed",
251            code: value.code,
252            message: value.message,
253        }
254        .into()
255    }
256}
257
258#[derive(cynic::InlineFragments, Debug)]
259pub enum SendTransactionAsyncResult {
260    Transaction(Transaction),
261    ContractNotAllowedError(ContractNotAllowedError),
262    FunctionNotAllowedError(FunctionNotAllowedError),
263    RpcError(RpcError),
264    #[cynic(fallback)]
265    Unknown,
266}
267
268impl From<SendTransactionAsyncResult> for Result<Transaction, BlokliClientError> {
269    fn from(value: SendTransactionAsyncResult) -> Self {
270        match value {
271            SendTransactionAsyncResult::Transaction(t) => Ok(t),
272            SendTransactionAsyncResult::ContractNotAllowedError(e) => Err(e.into()),
273            SendTransactionAsyncResult::FunctionNotAllowedError(e) => Err(e.into()),
274            SendTransactionAsyncResult::RpcError(e) => Err(e.into()),
275            SendTransactionAsyncResult::Unknown => Err(ErrorKind::NoData.into()),
276        }
277    }
278}
279
280#[derive(cynic::InlineFragments, Debug)]
281pub enum SendTransactionResult {
282    SendTransactionSuccess(SendTransactionSuccess),
283    ContractNotAllowedError(ContractNotAllowedError),
284    FunctionNotAllowedError(FunctionNotAllowedError),
285    RpcError(RpcError),
286    #[cynic(fallback)]
287    Unknown,
288}
289
290impl From<SendTransactionResult> for Result<TxReceipt, BlokliClientError> {
291    fn from(value: SendTransactionResult) -> Self {
292        match value {
293            SendTransactionResult::SendTransactionSuccess(t) => {
294                let hash = t.transaction_hash.0.to_lowercase();
295                Ok(hex::decode(hash.trim_start_matches("0x"))
296                    .map_err(|_| ErrorKind::ParseError)?
297                    .try_into()
298                    .map_err(|_| ErrorKind::ParseError)?)
299            }
300            SendTransactionResult::ContractNotAllowedError(e) => Err(e.into()),
301            SendTransactionResult::FunctionNotAllowedError(e) => Err(e.into()),
302            SendTransactionResult::RpcError(e) => Err(e.into()),
303            SendTransactionResult::Unknown => Err(ErrorKind::NoData.into()),
304        }
305    }
306}
307
308#[derive(cynic::InlineFragments, Debug)]
309pub enum SendTransactionSyncResult {
310    Transaction(Transaction),
311    ContractNotAllowedError(ContractNotAllowedError),
312    FunctionNotAllowedError(FunctionNotAllowedError),
313    RpcError(RpcError),
314    TimeoutError(TimeoutError),
315    #[cynic(fallback)]
316    Unknown,
317}
318
319impl From<SendTransactionSyncResult> for Result<Transaction, BlokliClientError> {
320    fn from(value: SendTransactionSyncResult) -> Self {
321        match value {
322            SendTransactionSyncResult::Transaction(t) => Ok(t),
323            SendTransactionSyncResult::ContractNotAllowedError(e) => Err(e.into()),
324            SendTransactionSyncResult::FunctionNotAllowedError(e) => Err(e.into()),
325            SendTransactionSyncResult::RpcError(e) => Err(e.into()),
326            SendTransactionSyncResult::TimeoutError(e) => Err(e.into()),
327            SendTransactionSyncResult::Unknown => Err(ErrorKind::NoData.into()),
328        }
329    }
330}