Skip to main content

circle_developer_controlled_wallets/models/
signing.rs

1//! Signing resource models for the Circle Developer-Controlled Wallets API.
2//!
3//! Contains request parameters and response types for message and transaction
4//! signing endpoints.
5
6use super::common::Blockchain;
7
8/// Request body for signing a plain or hex-encoded message.
9#[derive(Debug, Clone, serde::Serialize)]
10#[serde(rename_all = "camelCase")]
11pub struct SignMessageRequest {
12    /// Source wallet ID.
13    #[serde(skip_serializing_if = "Option::is_none")]
14    pub wallet_id: Option<String>,
15    /// Blockchain network (required when wallet_id is absent).
16    #[serde(skip_serializing_if = "Option::is_none")]
17    pub blockchain: Option<Blockchain>,
18    /// Wallet address (required when wallet_id is absent).
19    #[serde(skip_serializing_if = "Option::is_none")]
20    pub wallet_address: Option<String>,
21    /// Message to sign (UTF-8 string or hex string when encoded_by_hex is true).
22    pub message: String,
23    /// When true, `message` is treated as a hex-encoded byte array.
24    #[serde(skip_serializing_if = "Option::is_none")]
25    pub encoded_by_hex: Option<bool>,
26    /// Optional memo for record-keeping.
27    #[serde(skip_serializing_if = "Option::is_none")]
28    pub memo: Option<String>,
29    /// Encrypted entity secret ciphertext.
30    pub entity_secret_ciphertext: String,
31}
32
33/// Inner data of a sign-message response.
34#[derive(Debug, Clone, serde::Deserialize)]
35#[serde(rename_all = "camelCase")]
36pub struct SignatureData {
37    /// Hex-encoded signature.
38    pub signature: String,
39}
40
41/// Response wrapper for sign-message and sign-typed-data endpoints.
42#[derive(Debug, Clone, serde::Deserialize)]
43pub struct SignatureResponse {
44    /// Response data.
45    pub data: SignatureData,
46}
47
48/// Request body for signing an EIP-712 typed data payload.
49#[derive(Debug, Clone, serde::Serialize)]
50#[serde(rename_all = "camelCase")]
51pub struct SignTypedDataRequest {
52    /// Source wallet ID.
53    #[serde(skip_serializing_if = "Option::is_none")]
54    pub wallet_id: Option<String>,
55    /// Blockchain network (required when wallet_id is absent).
56    #[serde(skip_serializing_if = "Option::is_none")]
57    pub blockchain: Option<Blockchain>,
58    /// Wallet address (required when wallet_id is absent).
59    #[serde(skip_serializing_if = "Option::is_none")]
60    pub wallet_address: Option<String>,
61    /// JSON-encoded EIP-712 typed data object.
62    pub typed_data: String,
63    /// Encrypted entity secret ciphertext.
64    pub entity_secret_ciphertext: String,
65}
66
67/// Request body for signing a raw transaction.
68#[derive(Debug, Clone, serde::Serialize)]
69#[serde(rename_all = "camelCase")]
70pub struct SignTransactionRequest {
71    /// Source wallet ID.
72    #[serde(skip_serializing_if = "Option::is_none")]
73    pub wallet_id: Option<String>,
74    /// Blockchain network (required when wallet_id is absent).
75    #[serde(skip_serializing_if = "Option::is_none")]
76    pub blockchain: Option<Blockchain>,
77    /// Wallet address (required when wallet_id is absent).
78    #[serde(skip_serializing_if = "Option::is_none")]
79    pub wallet_address: Option<String>,
80    /// Hex-encoded raw unsigned transaction.
81    #[serde(skip_serializing_if = "Option::is_none")]
82    pub raw_transaction: Option<String>,
83    /// Structured transaction object (alternative to raw_transaction).
84    #[serde(skip_serializing_if = "Option::is_none")]
85    pub transaction: Option<serde_json::Value>,
86    /// Encrypted entity secret ciphertext.
87    pub entity_secret_ciphertext: String,
88}
89
90/// Inner data of a sign-transaction response.
91#[derive(Debug, Clone, serde::Deserialize)]
92#[serde(rename_all = "camelCase")]
93pub struct SignTransactionData {
94    /// Hex-encoded signature.
95    pub signature: String,
96    /// Hex-encoded signed transaction.
97    pub signed_transaction: String,
98    /// Transaction hash (available after broadcast).
99    #[serde(skip_serializing_if = "Option::is_none")]
100    pub tx_hash: Option<String>,
101}
102
103/// Response wrapper for the sign-transaction endpoint.
104#[derive(Debug, Clone, serde::Deserialize)]
105pub struct SignTransactionResponse {
106    /// Response data.
107    pub data: SignTransactionData,
108}
109
110#[cfg(test)]
111mod tests {
112    use super::*;
113
114    #[test]
115    fn signature_response_deserializes() -> Result<(), Box<dyn std::error::Error>> {
116        let json = r#"{"data": {"signature": "0xabcdef1234"}}"#;
117        let resp: SignatureResponse = serde_json::from_str(json)?;
118        assert_eq!(resp.data.signature, "0xabcdef1234");
119        Ok(())
120    }
121
122    #[test]
123    fn sign_transaction_response_deserializes() -> Result<(), Box<dyn std::error::Error>> {
124        let json = r#"{
125            "data": {
126                "signature": "0xsig",
127                "signedTransaction": "0xsigned",
128                "txHash": "0xhash"
129            }
130        }"#;
131        let resp: SignTransactionResponse = serde_json::from_str(json)?;
132        assert_eq!(resp.data.signature, "0xsig");
133        assert_eq!(resp.data.signed_transaction, "0xsigned");
134        assert_eq!(resp.data.tx_hash.as_deref(), Some("0xhash"));
135        Ok(())
136    }
137
138    #[test]
139    fn sign_message_request_serializes() -> Result<(), Box<dyn std::error::Error>> {
140        let req = SignMessageRequest {
141            wallet_id: Some("wallet-1".to_string()),
142            blockchain: None,
143            wallet_address: None,
144            message: "Hello, World!".to_string(),
145            encoded_by_hex: None,
146            memo: None,
147            entity_secret_ciphertext: "cipher".to_string(),
148        };
149        let json = serde_json::to_string(&req)?;
150        assert!(json.contains("walletId"));
151        assert!(json.contains("entitySecretCiphertext"));
152        assert!(json.contains("Hello, World!"));
153        Ok(())
154    }
155
156    #[test]
157    fn sign_typed_data_request_serializes() -> Result<(), Box<dyn std::error::Error>> {
158        let req = SignTypedDataRequest {
159            wallet_id: Some("wallet-1".to_string()),
160            blockchain: None,
161            wallet_address: None,
162            typed_data: r#"{"types":{}}"#.to_string(),
163            entity_secret_ciphertext: "cipher".to_string(),
164        };
165        let json = serde_json::to_string(&req)?;
166        assert!(json.contains("typedData"));
167        Ok(())
168    }
169}