1use std::collections::HashMap;
7use std::fmt::{self, Display};
8
9use alloy::primitives::Address;
10use alloy::rpc::types::TransactionRequest;
11use serde::de::{MapAccess, Visitor};
12use serde::ser::SerializeSeq;
13use serde::{Deserialize, Deserializer, Serialize};
14use serde_json::Number;
15use serde_json::Value;
16
17use crate::cacao::Cacao;
18use crate::error::Result;
19
20#[derive(Debug, Clone, Serialize, Deserialize)]
21#[serde(untagged)]
22pub enum Id {
23 String(String),
24 Number(Number),
25 U128(u128),
26}
27
28impl From<String> for Id {
29 fn from(value: String) -> Self {
30 Id::String(value)
31 }
32}
33
34impl From<u128> for Id {
35 fn from(value: u128) -> Self {
36 Id::String(value.to_string())
37 }
38}
39
40impl Id {
41 #[allow(dead_code)]
42 pub fn to_u128(&self) -> Result<u128> {
43 match self {
44 Id::String(s) => Ok(s.parse::<u128>()?),
45 Id::Number(n) => n.as_u128().ok_or("number too big".into()),
46 Id::U128(n) => Ok(*n),
47 }
48 }
49}
50
51impl PartialEq for Id {
52 fn eq(&self, other: &Self) -> bool {
53 self.to_u128().unwrap().eq(&other.to_u128().unwrap())
54 }
55}
56
57#[derive(Clone, Debug, Serialize, Deserialize)]
58pub struct JsonRpcRequest<ParamType = Value> {
59 pub jsonrpc: String,
60 pub method: JsonRpcMethod,
61 #[serde(skip_serializing_if = "Option::is_none")]
62 pub params: Option<ParamType>,
63 pub id: Id,
64}
65
66impl Display for JsonRpcRequest {
67 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
68 write!(f, "{}", serde_json::to_string(self).unwrap())
69 }
70}
71
72#[derive(Serialize, Deserialize, Debug)]
73pub struct JsonRpcResponse<ResultType = Value> {
74 #[allow(dead_code)]
75 pub jsonrpc: String,
76 #[serde(default)]
77 pub result: Option<ResultType>,
78 #[serde(default)]
79 pub error: Option<JsonRpcError>,
80 #[serde(default)]
81 #[allow(dead_code)]
82 pub id: Option<Id>,
83}
84
85#[derive(Serialize, Deserialize, Debug)]
86#[allow(dead_code)]
87pub struct JsonRpcError {
88 code: i64,
89 message: String,
90 #[serde(default)]
91 data: Option<Value>,
92}
93
94#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
95#[repr(u16)]
96#[serde(try_from = "u16", into = "u16")]
97pub enum IrnTag {
98 UnsupportedMethod = 0,
100
101 PairingDelete = 1000,
102 PairingDeleteResponse = 1001,
103
104 PairingPing = 1002,
105 PairingPingResponse = 1003,
106
107 PairingExtend = 1004,
108 PairingExtendResponse = 1005,
109
110 SessionPropose = 1100,
112 SessionProposeApproveResponse = 1101,
113 SessionProposeRejectResponse = 1120,
114 SessionProposeAutoRejectResponse = 1121,
115
116 SessionSettle = 1102,
117 SessionSettleResponse = 1103,
118
119 SessionUpdate = 1104,
120 SessionUpdateResponse = 1105,
121
122 SessionExtend = 1106,
123 SessionExtendResponse = 1107,
124
125 SessionRequest = 1108,
126 SessionRequestResponse = 1109,
127
128 SessionEvent = 1110,
129 SessionEventResponse = 1111,
130
131 SessionDelete = 1112,
132 SessionDeleteResponse = 1113,
133
134 SessionPing = 1114,
135 SessionPingResponse = 1115,
136
137 SessionAuthenticate = 1116,
138 SessionAuthenticateApproveResponse = 1117,
139 SessionAuthenticateRejectResponse = 1118,
140 SessionAuthenticateAutoRejectResponse = 1119,
141}
142
143impl From<IrnTag> for u16 {
144 fn from(tag: IrnTag) -> Self {
145 tag as u16
146 }
147}
148
149impl TryFrom<u16> for IrnTag {
150 type Error = String;
151
152 fn try_from(value: u16) -> Result<Self, Self::Error> {
153 match value {
154 0 => Ok(IrnTag::UnsupportedMethod),
155
156 1000 => Ok(IrnTag::PairingDelete),
157 1001 => Ok(IrnTag::PairingDeleteResponse),
158
159 1002 => Ok(IrnTag::PairingPing),
160 1003 => Ok(IrnTag::PairingPingResponse),
161
162 1004 => Ok(IrnTag::PairingExtend),
163 1005 => Ok(IrnTag::PairingExtendResponse),
164
165 1100 => Ok(IrnTag::SessionPropose),
166 1101 => Ok(IrnTag::SessionProposeApproveResponse),
167 1120 => Ok(IrnTag::SessionProposeRejectResponse),
168 1121 => Ok(IrnTag::SessionProposeAutoRejectResponse),
169
170 1102 => Ok(IrnTag::SessionSettle),
171 1103 => Ok(IrnTag::SessionSettleResponse),
172
173 1104 => Ok(IrnTag::SessionUpdate),
174 1105 => Ok(IrnTag::SessionUpdateResponse),
175
176 1106 => Ok(IrnTag::SessionExtend),
177 1107 => Ok(IrnTag::SessionExtendResponse),
178
179 1108 => Ok(IrnTag::SessionRequest),
180 1109 => Ok(IrnTag::SessionRequestResponse),
181
182 1110 => Ok(IrnTag::SessionEvent),
183 1111 => Ok(IrnTag::SessionEventResponse),
184
185 1112 => Ok(IrnTag::SessionDelete),
186 1113 => Ok(IrnTag::SessionDeleteResponse),
187
188 1114 => Ok(IrnTag::SessionPing),
189 1115 => Ok(IrnTag::SessionPingResponse),
190
191 1116 => Ok(IrnTag::SessionAuthenticate),
192 1117 => Ok(IrnTag::SessionAuthenticateApproveResponse),
193
194 1118 => Ok(IrnTag::SessionAuthenticateRejectResponse),
195 1119 => Ok(IrnTag::SessionAuthenticateAutoRejectResponse),
196
197 _ => Err(format!("{:?}", crate::Error::InvalidIrnTag(value))),
198 }
199 }
200}
201
202#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
203pub enum JsonRpcMethod {
204 #[serde(rename = "irn_publish")]
205 IrnPublish,
206
207 #[serde(rename = "irn_subscribe")]
208 IrnSubscribe,
209
210 #[serde(rename = "irn_subscription")]
211 IrnSubscription,
212
213 #[serde(rename = "irn_fetchMessages")]
214 IrnFetchMessages,
215}
216
217#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
218pub struct IrnPublishParams {
219 pub topic: String,
220 pub message: String,
221 pub ttl: u64,
222 pub prompt: bool,
223 pub tag: IrnTag,
224}
225
226#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
227pub struct IrnSubscriptionParams {
228 pub id: Id,
229 pub data: IrnSubscriptionData,
230}
231
232#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
233pub struct IrnSubscriptionData {
234 pub topic: String,
235 pub message: String,
236 #[serde(rename = "publishedAt")]
237 pub published_at: Id,
238 pub tag: IrnTag,
239}
240
241#[derive(Debug, Default, Serialize, Deserialize)]
242pub struct IrnFetchMessageResult {
243 #[serde(rename = "hasMore")]
244 pub has_more: bool,
245 pub messages: Vec<EncryptedMessage>,
246}
247
248#[derive(Debug, Serialize, Deserialize)]
249pub struct EncryptedMessage {
250 pub topic: String,
251 pub message: String,
252 pub tag: u16,
253 #[serde(skip_serializing_if = "Option::is_none")]
254 pub ttl: Option<u64>,
255 #[serde(skip_serializing_if = "Option::is_none")]
256 pub prompt: Option<bool>,
257 #[serde(rename = "publishedAt")]
258 #[serde(skip_serializing_if = "Option::is_none")]
259 pub published_at: Option<u64>,
260 #[serde(skip_serializing_if = "Option::is_none")]
262 pub attestation: Option<String>,
263}
264
265impl EncryptedMessage {
266 pub fn new(topic: String, message: String, tag: IrnTag, ttl: u64) -> Self {
267 Self {
268 topic,
269 message,
270 tag: tag as u16,
271 ttl: Some(ttl),
272 prompt: Some(false),
273 published_at: None,
274 attestation: None,
275 }
276 }
277}
278
279#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
280pub struct SessionProposeParams {
281 #[serde(rename = "requiredNamespaces")]
282 pub required_namespaces: HashMap<String, Namespace>,
283 #[serde(rename = "optionalNamespaces")]
284 pub optional_namespaces: HashMap<String, Namespace>,
285 pub relays: Vec<Relay>,
286 #[serde(rename = "pairingTopic")]
287 pub pairing_topic: String,
288 pub proposer: Participant,
289 #[serde(rename = "expiryTimestamp")]
290 pub expiry_timestamp: u64,
291}
292
293#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
294pub struct SessionProposeResponse {
295 pub relay: Relay,
296 #[serde(rename = "responderPublicKey")]
297 pub responder_public_key: String,
298}
299
300#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
301pub struct Namespace {
302 #[serde(skip_serializing_if = "Option::is_none")]
303 pub accounts: Option<Vec<String>>,
304 pub chains: Vec<String>,
305 pub events: Vec<String>,
306 pub methods: Vec<String>,
307}
308
309#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
310pub struct Relay {
311 pub protocol: String,
312}
313
314#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
315pub struct SessionAuthenticateParams {
316 #[serde(rename = "authPayload")]
317 pub auth_payload: AuthPayload,
318 pub requester: Participant,
319 #[serde(rename = "expiryTimestamp")]
320 pub expiry_timestamp: u64,
321}
322
323#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
324pub struct AuthPayload {
325 #[serde(rename = "type")]
326 pub payload_type: String,
327 pub chains: Vec<String>,
328 pub statement: String,
329 pub aud: String,
330 pub domain: String,
331 pub version: String,
332 pub nonce: String,
333 pub iat: String,
334 pub resources: Vec<String>,
335}
336
337#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
338pub struct Participant {
339 #[serde(rename = "publicKey")]
340 pub public_key: String,
341 pub metadata: Metadata,
342}
343
344#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
345pub struct Metadata {
346 pub name: String,
347 pub description: String,
348 pub url: String,
349 pub icons: Vec<String>,
350}
351
352#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
353pub struct SessionAuthenticateResponse {
354 pub cacaos: Vec<Cacao>,
355 pub responder: Participant,
356}
357
358#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
359pub struct SessionSettleParams {
360 pub controller: Participant,
361 pub expiry: u64,
362 pub namespaces: HashMap<String, Namespace>,
363 pub relay: Relay,
364 #[serde(rename = "sessionProperties")]
365 pub session_properties: Option<SessionSettleProperties>,
366}
367
368#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
369pub struct SessionSettleProperties {
370 pub capabilities: String,
371}
372
373#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
374pub struct SessionRequestParams {
375 #[serde(rename = "sessionId")]
376 #[serde(skip_serializing_if = "Option::is_none")]
377 pub session_id: Option<String>,
378 #[serde(skip_serializing_if = "Option::is_none")]
379 pub scope: Option<String>,
380 pub request: SessionRequestObject,
381 #[serde(rename = "chainId")]
382 pub chain_id: String,
383}
384
385#[derive(Clone, Debug, Serialize, PartialEq)]
386pub struct SessionRequestObject {
387 pub method: SessionRequestMethod,
388 pub params: SessionRequestData,
389 #[serde(rename = "expiryTimestamp")]
390 pub expiry_timestamp: u64,
391}
392
393#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
394pub enum SessionRequestMethod {
395 #[serde(rename = "personal_sign")]
396 PersonalSign,
397 #[serde(rename = "eth_sendTransaction")]
398 EthSendTransaction,
399 #[serde(rename = "eth_signTypedData_v4")]
400 EthSignTypedDataV4,
401}
402
403#[derive(Clone, Debug, PartialEq)]
404pub enum SessionRequestData {
405 EthSendTransaction(Box<TransactionRequest>),
406 PersonalSign { message: String, account: Address },
407 EthSignTypedDataV4 { account: Address, typed_data: Value },
408}
409
410impl Serialize for SessionRequestData {
411 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
412 where
413 S: serde::Serializer,
414 {
415 match self {
416 SessionRequestData::EthSendTransaction(tx) => {
417 let mut seq = serializer.serialize_seq(Some(1))?;
418 seq.serialize_element(tx)?;
419 seq.end()
420 }
421 SessionRequestData::PersonalSign { message, account } => {
422 let mut seq = serializer.serialize_seq(Some(2))?;
423 seq.serialize_element(&message)?;
424 seq.serialize_element(&account)?;
425 seq.end()
426 }
427 SessionRequestData::EthSignTypedDataV4 {
428 account,
429 typed_data,
430 } => {
431 let mut seq = serializer.serialize_seq(Some(2))?;
432 seq.serialize_element(account)?;
433 seq.serialize_element(typed_data)?;
434 seq.end()
435 }
436 }
437 }
438}
439
440impl<'de> Deserialize<'de> for SessionRequestObject {
441 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
442 where
443 D: Deserializer<'de>,
444 {
445 #[derive(Deserialize)]
446 #[serde(field_identifier, rename_all = "camelCase")]
447 enum Field {
448 Method,
449 Params,
450 ExpiryTimestamp,
451 }
452
453 struct SessionRequestVisitor;
454
455 impl<'de> Visitor<'de> for SessionRequestVisitor {
456 type Value = SessionRequestObject;
457
458 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
459 formatter.write_str("SessionRequestObject with method, params, and expiryTimestamp")
460 }
461
462 fn visit_map<V>(
463 self,
464 mut map: V,
465 ) -> Result<SessionRequestObject, V::Error>
466 where
467 V: MapAccess<'de>,
468 {
469 use serde_json::Value;
470
471 let mut method: Option<SessionRequestMethod> = None;
472 let mut raw_params: Option<Value> = None;
473 let mut expiry: Option<u64> = None;
474
475 while let Some(key) = map.next_key()? {
476 match key {
477 Field::Method => method = Some(map.next_value()?),
478 Field::Params => raw_params = Some(map.next_value()?),
479 Field::ExpiryTimestamp => {
480 expiry = Some(map.next_value()?)
481 }
482 }
483 }
484
485 let method = method
486 .ok_or_else(|| serde::de::Error::missing_field("method"))?;
487 let raw_params = raw_params
488 .ok_or_else(|| serde::de::Error::missing_field("params"))?;
489 let expiry = expiry.ok_or_else(|| {
490 serde::de::Error::missing_field("expiryTimestamp")
491 })?;
492
493 let params = match method {
495 SessionRequestMethod::EthSendTransaction => {
496 let tx: [Box<TransactionRequest>; 1] =
497 serde_json::from_value(raw_params)
498 .map_err(serde::de::Error::custom)?;
499 SessionRequestData::EthSendTransaction(tx[0].clone())
500 }
501 SessionRequestMethod::PersonalSign => {
502 let (message, account) =
503 serde_json::from_value(raw_params)
504 .map_err(serde::de::Error::custom)?;
505 SessionRequestData::PersonalSign { message, account }
506 }
507 SessionRequestMethod::EthSignTypedDataV4 => {
508 if let Ok((account, typed_data)) =
509 serde_json::from_value(raw_params.clone())
510 {
511 SessionRequestData::EthSignTypedDataV4 {
512 account,
513 typed_data,
514 }
515 } else {
516 let (account, typed_data_str): (Address, String) =
518 serde_json::from_value(raw_params)
519 .map_err(serde::de::Error::custom)?;
520 let typed_data: Value =
521 serde_json::from_str(&typed_data_str)
522 .map_err(serde::de::Error::custom)?;
523 SessionRequestData::EthSignTypedDataV4 {
524 account,
525 typed_data,
526 }
527 }
528 }
529 };
530
531 Ok(SessionRequestObject {
532 method,
533 params,
534 expiry_timestamp: expiry,
535 })
536 }
537 }
538
539 deserializer.deserialize_map(SessionRequestVisitor)
540 }
541}
542
543#[cfg(test)]
544mod tests {
545 use super::*;
546
547 #[test]
548 fn test_decode_id() {
549 let result = serde_json::from_str::<Id>("\"1234\"").unwrap();
550 println!("result {result:?}");
551
552 let result = serde_json::from_str::<Id>("1234").unwrap();
553 println!("result {result:?}");
554 }
555
556 #[test]
557 fn test_decode_fetch_messages() {
558 let resp = "{\"id\":1744205603590064025,\"jsonrpc\":\"2.0\",\"result\":{\"hasMore\":false,\"messages\":[{\"attestation\":\"eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3NDQyMDkxOTAsImlkIjoiZWRhYjVmMjQyNTNmZGUyYTVkYzI4NTcyZmRkNGE4NTViYTJhZjRkOGM2ZmQxMjQxN2NlMWUwNDMzODY5MjcwMCIsIm9yaWdpbiI6Imh0dHBzOi8vYXBwa2l0LWxhYi5yZW93bi5jb20iLCJpc1NjYW0iOmZhbHNlLCJpc1ZlcmlmaWVkIjp0cnVlfQ.zeNQehxpiQ0JqVBHTlkxgDwValU2NhGEsUMT5irJgiM92d1M361ifoxRIJfh1_EF2dVBqyr6zLV4cbp3g4jLSQ\",\"message\":\"ALW+zRyGiw5kj8hlp/Xv1jQ3vGFNcFNsfp4PyyVPbkcbMohD0sGhUejguhlNRA0fXnCfzQ9/hdI5xcmy1SubXts/Db6Kt1cStgOgIXSGReS3jP2AgldAv5WR77dfXDytJzYmg6af9lbKMYCuI+4qWyuaswKvsO4cyeRaT1jVsITT6uy+z2A50H0uBuNcnE4YGKia8yxlZYfLj0dbJ1Q/zX8d9DF/YLBQzUhLOD6P9ajCDUTNs7S24lDtB1xglkKHyxG4/cSktAGmvu7QWcYzkzeLmGcqxK7TxO3+N8baOMf4bq0n2tJjFCQPrs31d/mrQc1e7d7GwmndZjvbWRL+ab+B2INkfs6cEkLTtwu10TXGAScQCrrVtBR/apIiGFpg27YD+KbbM7hCmFoOIxJc1DXH8psf+MjDhwLqQRVxMe26DYbL50jwqQmDu71qvi4DHZgRyAq/wWSIHe+BGqfZKzK45zK7cz72WsZhuEjOqmDK0gZePbHzWaVvI9uApzpVXGVPOwp4eeZOBYbED+Oucq6CovHRjVIw0CZitjQH14yv0XNIFZ1U1/byB/jWgvQwW42O3v9M3cmljeXTeuEaao+bngEv6zN7CDpGMbiQ4gDTxihtpTv7Mgl+9LpyhrtY79QJ6XMx9wkWuxkbsIrQalFgJuhcJEMfULjLSe49gzIC91gSJ5rVKd1ej82yIsDHUn6roKFb0McQXJNa5vb+Wh1cfn46LeX+m6XBRFXGKqr6xXs4dujZg2TeDA9XyNT4B99hXGQl4BkNmAv6BYjH6pB63Fr7f+10iyf9SrfXlYVR9mLTqsVVd53sVuKWW6esKhgPHc2+Fd30+LRMTmhG/IebxRaRBpBvjjh8lkVFowQn37TruR4sVX0NB/UsqX2U9Ns9A1LLMVI3+QycUmU/aDPR3fxmn1OF2abskgjvWLr3lqVVZGPf3nFgxMfU4u/+xsztwuw2Pc5lFos06z+dY0sZaRpB4b59Qb79KT/1CFodRjfGDcM6eRbaj2XcMfn46HkczxKl494aiu6KwLIzx5ChpCt9EMyO6SV/dhEhvL/cwcWAZsUmft5yPSa24J5bCtTetcL7fGMrjyyJYsQBFMgdQJC7YtlUcrPm5NIhJu8DYjef86sr8m38t2vixFGifC55RQhlKdG12cUrtY9TVQMJrEBdLsmuBbSFVa9oSzbh4yNvPRciLkaUAo5A5QfBlfZmbLZutE9v9IsjmIqZ78xys6Q93Hx5keDk3xdM1/1and9v5kjVMvmpQX1vqtlebIJY5sqM3BZVt9Tyxg6eSl+fKI+Jug+K19T9b1dGsfqByIf/js9pEAATxbV+e8I2CAtxNlipfHJleamZY1mVe74sokGMbM/Tz7MYYXp8MePKPXH+Te6yFMtvHfHdPi1Qmte+w9VLEin2PuLWt/DNX277YrOvhBYuRx30L9DWAXSIcN3qnxI6GpL45dX2rf8pyMdO1vdsPQG/Sr1EnU8jTi+ul182Zyen04kOhtqtuoYV2Sh73k2/6AVRWPtFqhGUI0cUBrdVnE1PAFgRaYMDmTTMYFnmz/PXptuP/T14x5F0wdoFNOUw88opmRrIsqQgtRYIwMa5303aHhB7H+aY7Hg3/y2RNOxklXUMikwI/8bHw0l2jVS69egUe1JMVk51sG8IxG45lf/q7zUEm5BcJ6QJ21OTqZqMJHPDEO0r0okc1C17/rylIh9c0+2zUdqJa7N0sV9Qd9oGNciKswoHLeNhHdQQrEyEFjmQr5TV9MM8HaWVIrRf5SGgsAmSd+GHUyz1sNt8DzlOQja2qKEZIgNZYBE6f667dX5WTmNcQ9zZje45uVKRkO8YGkBT0m1b4iD+hgHuYQdkLQMHvfsDiZo=\",\"publishedAt\":1744205591202,\"tag\":1100,\"topic\":\"74818ca920a949b9adad9ed0c5bdfa1f5e6ea5e5b1a18e71a7ba1ac9923295e5\"},{\"attestation\":\"eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3NDQyMDkxOTAsImlkIjoiZGU0MjhhYjU0NzhiZWJkOGU5M2M3OGFhNTVjYTNkYTEwZWViYzE0MTY4MTgwYWJkNTYxYmRhZmVmYjMxMmZkOSIsIm9yaWdpbiI6Imh0dHBzOi8vYXBwa2l0LWxhYi5yZW93bi5jb20iLCJpc1NjYW0iOmZhbHNlLCJpc1ZlcmlmaWVkIjp0cnVlfQ.EJMNd8iO0Uvr1owRbomGuC3One2K6pfKZa3nvIl4MpI00EkHL86Tdo63MJXKtGK65ixFZqhx-L__M8kXbhFoJA\",\"message\":\"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\",\"publishedAt\":1744205591323,\"tag\":1116,\"topic\":\"74818ca920a949b9adad9ed0c5bdfa1f5e6ea5e5b1a18e71a7ba1ac9923295e5\"}]}}";
559 let result = serde_json::from_str::<
560 JsonRpcResponse<IrnFetchMessageResult>,
561 >(resp)
562 .unwrap();
563 println!("result {result:?}");
564 }
565}