Skip to main content

celestia_grpc/
tx.rs

1use serde::{Deserialize, Serialize};
2
3use celestia_types::hash::Hash;
4
5use crate::Error;
6use crate::grpc::{AsyncGrpcCall, TxPriority};
7
8pub use celestia_proto::cosmos::tx::v1beta1::SignDoc;
9
10/// A result of correctly submitted transaction.
11#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
12#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
13pub struct TxInfo {
14    /// Hash of the transaction.
15    pub hash: Hash,
16    /// Height at which transaction was submitted.
17    pub height: u64,
18}
19
20/// Broadcasted, but still not confirmed transaction.
21#[derive(Debug)]
22pub struct SubmittedTx {
23    broadcasted_tx: BroadcastedTx,
24    confirm_tx: AsyncGrpcCall<TxInfo>,
25}
26
27impl SubmittedTx {
28    pub(crate) fn new(tx: BroadcastedTx, confirm_tx: AsyncGrpcCall<TxInfo>) -> SubmittedTx {
29        SubmittedTx {
30            broadcasted_tx: tx,
31            confirm_tx,
32        }
33    }
34
35    /// Get reference to [`BroadcastedTx`]
36    pub fn tx_ref(&self) -> &BroadcastedTx {
37        &self.broadcasted_tx
38    }
39
40    /// Confirm the transaction and return [`TxInfo`]
41    pub async fn confirm(self) -> Result<TxInfo, Error> {
42        self.confirm_tx.await
43    }
44}
45
46/// A transaction that was broadcasted
47#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
48#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
49pub struct BroadcastedTx {
50    /// Broadcasted bytes
51    pub tx: Vec<u8>,
52    /// Transaction hash
53    pub hash: Hash,
54    /// Transaction sequence
55    pub sequence: u64,
56}
57
58const DEFAULT_CONFIRMATION_INTERVAL_MS: u64 = 500;
59
60fn default_confirmation_interval_ms() -> u64 {
61    DEFAULT_CONFIRMATION_INTERVAL_MS
62}
63
64/// Configuration for the transaction.
65#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
66#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
67pub struct TxConfig {
68    /// Custom gas limit for the transaction (in `utia`). By default, client will
69    /// query gas estimation service to get estimate gas limit.
70    pub gas_limit: Option<u64>,
71    /// Custom gas price for fee calculation. By default, client will query gas
72    /// estimation service to get gas price estimate.
73    pub gas_price: Option<f64>,
74    /// Memo for the transaction
75    pub memo: Option<String>,
76    /// Priority of the transaction, used with gas estimation service
77    pub priority: TxPriority,
78    /// Interval between confirmation polling attempts, in milliseconds.
79    /// Defaults to 500ms.
80    #[serde(default = "default_confirmation_interval_ms")]
81    pub confirmation_interval_ms: u64,
82}
83
84impl TxConfig {
85    /// Attach gas limit to this config.
86    pub fn with_gas_limit(mut self, gas_limit: u64) -> Self {
87        self.gas_limit = Some(gas_limit);
88        self
89    }
90
91    /// Attach gas price to this config.
92    pub fn with_gas_price(mut self, gas_price: f64) -> Self {
93        self.gas_price = Some(gas_price);
94        self
95    }
96
97    /// Attach memo to this config.
98    pub fn with_memo(mut self, memo: impl Into<String>) -> Self {
99        self.memo = Some(memo.into());
100        self
101    }
102
103    /// Specify transaction priority to be used when using gas estimation service
104    pub fn with_priority(mut self, priority: TxPriority) -> Self {
105        self.priority = priority;
106        self
107    }
108
109    /// Specify the confirmation polling interval in milliseconds.
110    pub fn with_confirmation_interval_ms(mut self, confirmation_interval_ms: u64) -> Self {
111        self.confirmation_interval_ms = confirmation_interval_ms;
112        self
113    }
114}
115
116impl Default for TxConfig {
117    fn default() -> Self {
118        TxConfig {
119            gas_limit: None,
120            gas_price: None,
121            memo: None,
122            priority: TxPriority::default(),
123            confirmation_interval_ms: DEFAULT_CONFIRMATION_INTERVAL_MS,
124        }
125    }
126}
127
128#[cfg(all(target_arch = "wasm32", feature = "wasm-bindgen"))]
129pub use wbg::*;
130
131#[cfg(all(target_arch = "wasm32", feature = "wasm-bindgen"))]
132mod wbg {
133    use super::{BroadcastedTx, TxConfig, TxInfo, TxPriority};
134    use js_sys::{BigInt, Uint8Array};
135    use lumina_utils::make_object;
136    use wasm_bindgen::{JsCast, prelude::*};
137
138    #[wasm_bindgen(typescript_custom_section)]
139    const _: &str = "
140    /**
141     * Transaction info
142     */
143    export interface TxInfo {
144      /**
145       * Hash of the transaction.
146       */
147      hash: string;
148      /**
149       * Height at which transaction was submitted.
150       */
151      height: bigint;
152    }
153
154    /**
155     * A transaction that was broadcasted
156     */
157    export interface BroadcastedTx {
158      /**
159       * Broadcasted bytes
160       */
161      tx: Uint8Array;
162      /**
163       * Transaction hash
164       */
165      hash: string;
166      /**
167       * Transaction sequence
168       */
169      sequence: bigint;
170    }
171
172    /**
173     * Transaction config.
174     */
175    export interface TxConfig {
176      /**
177       * Custom gas limit for the transaction (in `utia`). By default, client will
178       * query gas estimation service to get estimate gas limit.
179       */
180      gasLimit?: bigint; // utia
181      /**
182       * Custom gas price for fee calculation. By default, client will query gas
183       * estimation service to get gas price estimate.
184       */
185      gasPrice?: number;
186      /**
187       * Memo for the transaction
188       */
189      memo?: string;
190      /**
191       * Priority of the transaction, used with gas estimation service
192       */
193      priority?: TxPriority;
194      /**
195       * Interval between confirmation polling attempts, in milliseconds.
196       */
197      confirmationIntervalMs?: bigint;
198    }
199    ";
200
201    #[wasm_bindgen]
202    extern "C" {
203        /// TxInfo exposed to javascript
204        #[wasm_bindgen(typescript_type = "TxInfo")]
205        pub type JsTxInfo;
206
207        /// BroadcastedTx exposed to javascript
208        #[wasm_bindgen(typescript_type = "BroadcastedTx")]
209        pub type JsBroadcastedTx;
210
211        #[wasm_bindgen(method, getter, js_name = tx)]
212        pub fn tx(this: &JsBroadcastedTx) -> Vec<u8>;
213
214        #[wasm_bindgen(method, getter, js_name = hash)]
215        pub fn hash(this: &JsBroadcastedTx) -> String;
216
217        #[wasm_bindgen(method, getter, js_name = sequence)]
218        pub fn sequence(this: &JsBroadcastedTx) -> BigInt;
219
220        /// TxConfig exposed to javascript
221        #[wasm_bindgen(typescript_type = "TxConfig")]
222        pub type JsTxConfig;
223
224        #[wasm_bindgen(method, getter, js_name = gasLimit)]
225        pub fn gas_limit(this: &JsTxConfig) -> Option<u64>;
226
227        #[wasm_bindgen(method, getter, js_name = gasPrice)]
228        pub fn gas_price(this: &JsTxConfig) -> Option<f64>;
229
230        #[wasm_bindgen(method, getter, js_name = memo)]
231        pub fn memo(this: &JsTxConfig) -> Option<String>;
232
233        #[wasm_bindgen(method, getter, js_name = priority)]
234        pub fn priority(this: &JsTxConfig) -> Option<TxPriority>;
235
236        #[wasm_bindgen(method, getter, js_name = confirmationIntervalMs)]
237        pub fn confirmation_interval_ms(this: &JsTxConfig) -> Option<u64>;
238    }
239
240    impl From<TxInfo> for JsTxInfo {
241        fn from(value: TxInfo) -> JsTxInfo {
242            let obj = make_object!(
243                "hash" => value.hash.to_string().into(),
244                "height" => BigInt::from(value.height)
245            );
246
247            obj.unchecked_into()
248        }
249    }
250
251    impl From<BroadcastedTx> for JsBroadcastedTx {
252        fn from(value: BroadcastedTx) -> JsBroadcastedTx {
253            let tx_bytes = Uint8Array::from(value.tx.as_slice());
254            let obj = make_object!(
255                "tx" => tx_bytes.into(),
256                "hash" => value.hash.to_string().into(),
257                "sequence" => BigInt::from(value.sequence)
258            );
259
260            obj.unchecked_into()
261        }
262    }
263
264    impl TryFrom<JsBroadcastedTx> for BroadcastedTx {
265        type Error = crate::Error;
266
267        fn try_from(value: JsBroadcastedTx) -> Result<BroadcastedTx, Self::Error> {
268            Ok(BroadcastedTx {
269                tx: value.tx(),
270                hash: value.hash().parse()?,
271                sequence: value.sequence().try_into().map_err(|i| {
272                    crate::Error::InvalidBroadcastedTx(format!("invalid sequence: {i}"))
273                })?,
274            })
275        }
276    }
277
278    impl From<JsTxConfig> for TxConfig {
279        fn from(value: JsTxConfig) -> TxConfig {
280            TxConfig {
281                gas_limit: value.gas_limit(),
282                gas_price: value.gas_price(),
283                memo: value.memo(),
284                priority: value.priority().unwrap_or_default(),
285                confirmation_interval_ms: value
286                    .confirmation_interval_ms()
287                    .unwrap_or(TxConfig::default().confirmation_interval_ms),
288            }
289        }
290    }
291}
292
293impl From<SubmittedTx> for BroadcastedTx {
294    fn from(value: SubmittedTx) -> Self {
295        value.broadcasted_tx
296    }
297}
298
299impl From<SubmittedTx> for AsyncGrpcCall<TxInfo> {
300    fn from(value: SubmittedTx) -> Self {
301        value.confirm_tx
302    }
303}
304
305impl From<SubmittedTx> for (BroadcastedTx, AsyncGrpcCall<TxInfo>) {
306    fn from(
307        SubmittedTx {
308            broadcasted_tx,
309            confirm_tx,
310        }: SubmittedTx,
311    ) -> Self {
312        (broadcasted_tx, confirm_tx)
313    }
314}