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 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 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 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 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 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 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}