Skip to main content

xmtp_content_types/
wallet_send_calls.rs

1use std::collections::HashMap;
2
3use crate::{CodecError, ContentCodec};
4use serde::{Deserialize, Serialize};
5use xmtp_proto::xmtp::mls::message_contents::{ContentTypeId, EncodedContent};
6
7pub struct WalletSendCallsCodec {}
8
9impl WalletSendCallsCodec {
10    const AUTHORITY_ID: &'static str = "xmtp.org";
11    pub const TYPE_ID: &'static str = "walletSendCalls";
12    pub const MAJOR_VERSION: u32 = 1;
13    pub const MINOR_VERSION: u32 = 0;
14}
15
16impl WalletSendCallsCodec {
17    fn fallback(content: &WalletSendCalls) -> Option<String> {
18        let json = serde_json::to_string(content).unwrap_or_else(|_| "{}".to_string());
19        Some(format!("[Transaction request generated]: {}", json))
20    }
21}
22
23impl ContentCodec<WalletSendCalls> for WalletSendCallsCodec {
24    fn content_type() -> ContentTypeId {
25        ContentTypeId {
26            authority_id: Self::AUTHORITY_ID.to_string(),
27            type_id: Self::TYPE_ID.to_string(),
28            version_major: Self::MAJOR_VERSION,
29            version_minor: Self::MINOR_VERSION,
30        }
31    }
32
33    fn encode(content: WalletSendCalls) -> Result<EncodedContent, CodecError> {
34        let json = serde_json::to_vec(&content)
35            .map_err(|e| CodecError::Encode(format!("JSON encode error: {e}")))?;
36
37        Ok(EncodedContent {
38            r#type: Some(Self::content_type()),
39            parameters: HashMap::new(),
40            fallback: Self::fallback(&content),
41            compression: None,
42            content: json,
43        })
44    }
45
46    fn decode(encoded: EncodedContent) -> Result<WalletSendCalls, CodecError> {
47        serde_json::from_slice(&encoded.content)
48            .map_err(|e| CodecError::Decode(format!("JSON decode error: {e}")))
49    }
50
51    fn should_push() -> bool {
52        true
53    }
54}
55
56#[derive(Debug, Serialize, Deserialize, Clone)]
57pub struct WalletSendCalls {
58    pub version: String,
59    #[serde(rename = "chainId")]
60    pub chain_id: String,
61    pub from: String,
62    pub calls: Vec<WalletCall>,
63    #[serde(skip_serializing_if = "Option::is_none")]
64    pub capabilities: Option<HashMap<String, String>>,
65}
66
67#[derive(Debug, Serialize, Deserialize, Clone)]
68pub struct WalletCall {
69    #[serde(skip_serializing_if = "Option::is_none")]
70    pub to: Option<String>,
71    #[serde(skip_serializing_if = "Option::is_none")]
72    pub data: Option<String>,
73    #[serde(skip_serializing_if = "Option::is_none")]
74    pub value: Option<String>,
75    #[serde(skip_serializing_if = "Option::is_none")]
76    pub gas: Option<String>,
77    #[serde(skip_serializing_if = "Option::is_none")]
78    pub metadata: Option<WalletCallMetadata>,
79}
80
81#[derive(Debug, Serialize, Deserialize, Clone)]
82pub struct WalletCallMetadata {
83    pub description: String,
84    #[serde(rename = "transactionType")]
85    pub transaction_type: String,
86    #[serde(flatten)]
87    pub extra: HashMap<String, String>,
88}
89
90#[cfg(test)]
91pub(crate) mod tests {
92    use super::*;
93    use crate::ContentCodec;
94
95    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
96    #[cfg_attr(not(target_arch = "wasm32"), test)]
97    fn test_encode_decode_wallet_send_calls() {
98        let params = WalletSendCalls {
99            version: "1".to_string(),
100            chain_id: "0x1".to_string(),
101            from: "0xsender".to_string(),
102            calls: vec![WalletCall {
103                to: Some("0xrecipient".to_string()),
104                data: Some("0xdeadbeef".to_string()),
105                value: Some("0x0".to_string()),
106                gas: Some("0x5208".to_string()),
107                metadata: Some(WalletCallMetadata {
108                    description: "Send funds".to_string(),
109                    transaction_type: "transfer".to_string(),
110                    extra: HashMap::from([("note".to_string(), "test".to_string())]),
111                }),
112            }],
113            capabilities: Some(HashMap::from([("foo".to_string(), "bar".to_string())])),
114        };
115
116        let encoded = WalletSendCallsCodec::encode(params.clone()).unwrap();
117        let decoded = WalletSendCallsCodec::decode(encoded).unwrap();
118
119        assert_eq!(decoded.version, params.version);
120        assert_eq!(decoded.chain_id, params.chain_id);
121        assert_eq!(decoded.from, params.from);
122        assert_eq!(decoded.calls.len(), 1);
123        assert_eq!(
124            decoded.calls[0].metadata.as_ref().unwrap().transaction_type,
125            "transfer"
126        );
127    }
128}