Skip to main content

playtron_core/
sui.rs

1use std::{fmt, time::Duration};
2
3use futures_util::StreamExt;
4use uuid::Uuid;
5use zbus::{proxy::PropertyStream, Connection};
6
7use crate::dbus::wallet_interface::WalletInterfaceProxy;
8
9#[derive(Debug, Clone, PartialEq)]
10pub enum SuiError {
11    DbusConnectionFailed(zbus::Error),
12    DbusMessageFailed(zbus::fdo::Error),
13    DbusInvalidArgument(zbus::Error),
14    GetAddressError(zbus::Error),
15    UserCancelled,
16    Error(String),
17    Timeout,
18}
19
20impl fmt::Display for SuiError {
21    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
22        match self {
23            SuiError::DbusConnectionFailed(e) => write!(f, "D-Bus connection failed: {e}"),
24            SuiError::DbusMessageFailed(e) => write!(f, "D-Bus message failed: {e}"),
25            SuiError::DbusInvalidArgument(e) => write!(f, "Invalid D-Bus argument: {e}"),
26            SuiError::GetAddressError(e) => write!(f, "Failed to get wallet address: {e}"),
27            SuiError::UserCancelled => write!(f, "User cancelled the operation"),
28            SuiError::Error(msg) => write!(f, "{}", msg),
29            SuiError::Timeout => write!(f, "Timeout"),
30        }
31    }
32}
33
34#[derive(Clone, Debug)]
35pub struct TransactionSuccessPayload {
36    pub result: String,
37    pub completed_at: u64,
38}
39
40pub struct Sui {
41    connection: Connection,
42}
43
44impl Sui {
45    pub async fn new() -> Result<Self, SuiError> {
46        let connection = Connection::session()
47            .await
48            .map_err(SuiError::DbusConnectionFailed)?;
49
50        Ok(Sui { connection })
51    }
52
53    pub async fn get_address(&self) -> Result<String, SuiError> {
54        let proxy = self.get_proxy().await?;
55        proxy.address().await.map_err(SuiError::GetAddressError)
56    }
57
58    pub async fn receive_address_changed(&self) -> Result<PropertyStream<String>, SuiError> {
59        let proxy = self.get_proxy().await?;
60        Ok(proxy.receive_address_changed().await)
61    }
62
63    pub async fn sign_message(
64        &self,
65        provider_app_id: String,
66        msg: String,
67    ) -> Result<String, SuiError> {
68        let proxy = self.get_proxy().await?;
69        let msg_id = Uuid::new_v4().to_string();
70
71        // Set up both signal streams before calling
72        let mut signed_stream = proxy
73            .receive_on_message_sign_success()
74            .await
75            .map_err(SuiError::DbusConnectionFailed)?;
76
77        let mut cancelled_stream = proxy
78            .receive_on_message_sign_cancelled()
79            .await
80            .map_err(SuiError::DbusConnectionFailed)?;
81
82        // Trigger the signing
83        proxy
84            .sign_message(provider_app_id, msg_id.clone(), msg)
85            .await
86            .map_err(SuiError::DbusMessageFailed)?;
87
88        let result = tokio::time::timeout(Duration::from_secs(180), async {
89            loop {
90                tokio::select! {
91                    Some(success) = signed_stream.next() => {
92                        let args = success.args().map_err(SuiError::DbusInvalidArgument)?;
93                        if args.msg_id == msg_id {
94                            return Ok(args.signature);
95                        }
96                    }
97                    Some(cancelled) = cancelled_stream.next() => {
98                        let args = cancelled.args().map_err(SuiError::DbusInvalidArgument)?;
99                        if args.msg_id == msg_id {
100                            let err = if args.error.is_empty() {
101                                SuiError::UserCancelled
102                            } else {
103                                SuiError::Error(args.error)
104                            };
105
106                            return Err(err);
107                        }
108                    }
109                    else => break,
110                }
111            }
112
113            Err(SuiError::Timeout)
114        })
115        .await;
116
117        match result {
118            Ok(Ok(payload)) => Ok(payload),
119            Ok(Err(e)) => Err(e),
120            Err(_) => Err(SuiError::Timeout),
121        }
122    }
123
124    pub async fn sign_and_execute_transaction(
125        &self,
126        provider_app_id: String,
127        tx_bytes: String,
128    ) -> Result<TransactionSuccessPayload, SuiError> {
129        let proxy = self.get_proxy().await?;
130        let tx_id = Uuid::new_v4().to_string();
131
132        // Set up signal streams before triggering the call
133        let mut success_stream = proxy
134            .receive_on_transaction_success()
135            .await
136            .map_err(SuiError::DbusConnectionFailed)?;
137
138        let mut cancelled_stream = proxy
139            .receive_on_transaction_cancelled()
140            .await
141            .map_err(SuiError::DbusConnectionFailed)?;
142
143        // Send the D-Bus call
144        proxy
145            .sign_and_execute_transaction(provider_app_id, tx_id.clone(), tx_bytes)
146            .await
147            .map_err(SuiError::DbusMessageFailed)?;
148
149        let result = tokio::time::timeout(Duration::from_secs(180), async {
150            loop {
151                tokio::select! {
152                    Some(success) = success_stream.next() => {
153                        let args = success.args().map_err(SuiError::DbusInvalidArgument)?;
154                        if args.tx_id == tx_id {
155                            return Ok(TransactionSuccessPayload {
156                                result: args.result,
157                                completed_at: args.completed_at,
158                            });
159                        }
160                    }
161                    Some(cancelled) = cancelled_stream.next() => {
162                        let args = cancelled.args().map_err(SuiError::DbusInvalidArgument)?;
163                        if args.tx_id == tx_id {
164                            let err = if args.error.is_empty() {
165                                SuiError::UserCancelled
166                            } else {
167                                SuiError::Error(args.error)
168                            };
169
170                            return Err(err);
171                        }
172                    }
173                    else => break,
174                }
175            }
176
177            Err(SuiError::Timeout)
178        })
179        .await;
180
181        match result {
182            Ok(Ok(payload)) => Ok(payload),
183            Ok(Err(e)) => Err(e),
184            Err(_) => Err(SuiError::Timeout),
185        }
186    }
187
188    pub async fn sign_transaction(
189        &self,
190        provider_app_id: String,
191        tx_bytes: String,
192    ) -> Result<String, SuiError> {
193        let proxy = self.get_proxy().await?;
194        let tx_id = Uuid::new_v4().to_string();
195
196        // Set up signal streams before triggering the call
197        let mut success_stream = proxy
198            .receive_on_transaction_sign_success()
199            .await
200            .map_err(SuiError::DbusConnectionFailed)?;
201
202        let mut cancelled_stream = proxy
203            .receive_on_transaction_sign_cancelled()
204            .await
205            .map_err(SuiError::DbusConnectionFailed)?;
206
207        // Send the D-Bus call
208        proxy
209            .sign_transaction(provider_app_id, tx_id.clone(), tx_bytes)
210            .await
211            .map_err(SuiError::DbusMessageFailed)?;
212
213        let result = tokio::time::timeout(Duration::from_secs(180), async {
214            loop {
215                tokio::select! {
216                    Some(success) = success_stream.next() => {
217                        let args = success.args().map_err(SuiError::DbusInvalidArgument)?;
218                        if args.tx_id == tx_id {
219                            return Ok(args.signature);
220                        }
221                    }
222                    Some(cancelled) = cancelled_stream.next() => {
223                        let args = cancelled.args().map_err(SuiError::DbusInvalidArgument)?;
224                        if args.tx_id == tx_id {
225                            let err = if args.error.is_empty() {
226                                SuiError::UserCancelled
227                            } else {
228                                SuiError::Error(args.error)
229                            };
230
231                            return Err(err);
232                        }
233                    }
234                    else => break,
235                }
236            }
237
238            Err(SuiError::Timeout)
239        })
240        .await;
241
242        match result {
243            Ok(Ok(payload)) => Ok(payload),
244            Ok(Err(e)) => Err(e),
245            Err(_) => Err(SuiError::Timeout),
246        }
247    }
248
249    async fn get_proxy(&self) -> Result<WalletInterfaceProxy<'_>, SuiError> {
250        WalletInterfaceProxy::builder(&self.connection)
251            .build()
252            .await
253            .map_err(SuiError::DbusConnectionFailed)
254    }
255}