Skip to main content

pyra_privy/
models.rs

1use serde::{Deserialize, Serialize};
2
3// ─── Wallet types ─────────────────────────────────────────────────────
4
5/// A Privy-managed wallet.
6#[derive(Debug, Clone, Deserialize)]
7pub struct Wallet {
8    pub id: String,
9    pub address: String,
10    pub chain_type: String,
11    #[serde(default)]
12    pub policy_ids: Vec<String>,
13    #[serde(default)]
14    pub additional_signers: Vec<WalletSigner>,
15}
16
17/// A signer attached to a wallet.
18#[derive(Debug, Clone, Deserialize)]
19pub struct WalletSigner {
20    pub signer_id: String,
21    #[serde(default)]
22    pub override_policy_ids: Vec<String>,
23}
24
25/// Response from GET /v1/wallets.
26#[derive(Debug, Deserialize)]
27pub struct WalletsResponse {
28    pub data: Vec<Wallet>,
29}
30
31// ─── Sign transaction types ───────────────────────────────────────────
32
33/// Response from POST /v1/wallets/{id}/rpc (signTransaction).
34#[derive(Debug, Deserialize)]
35pub struct SignTransactionResponse {
36    #[serde(default)]
37    pub method: String,
38    pub data: SignTransactionData,
39}
40
41/// Data field from the signTransaction response.
42#[derive(Debug, Deserialize)]
43pub struct SignTransactionData {
44    pub signed_transaction: String,
45    #[serde(default)]
46    pub encoding: String,
47}
48
49// ─── Policy types ─────────────────────────────────────────────────────
50
51/// Request body for POST /v1/policies.
52#[derive(Debug, Serialize)]
53pub struct CreatePolicyRequest {
54    pub version: String,
55    pub name: String,
56    pub chain_type: String,
57    pub rules: Vec<PolicyRule>,
58}
59
60/// A single rule within a delegation policy.
61#[derive(Debug, Clone, Serialize)]
62pub struct PolicyRule {
63    pub name: String,
64    pub method: String,
65    pub conditions: Vec<PolicyCondition>,
66    pub action: String,
67}
68
69/// A condition within a policy rule.
70#[derive(Debug, Clone, Serialize)]
71pub struct PolicyCondition {
72    pub field_source: String,
73    pub field: String,
74    pub operator: String,
75    pub value: serde_json::Value,
76}
77
78/// Response from POST /v1/policies.
79#[derive(Debug, Deserialize)]
80pub struct CreatePolicyResponse {
81    pub id: String,
82}
83
84/// Response from GET /v1/policies/{id}.
85#[derive(Debug, Deserialize, Default)]
86#[serde(default)]
87pub struct GetPolicyResponse {
88    pub id: String,
89    pub name: String,
90    pub rules: Vec<PolicyRuleResponse>,
91}
92
93/// A rule as returned by GET /v1/policies/{id}.
94#[derive(Debug, Deserialize, Default)]
95#[serde(default)]
96pub struct PolicyRuleResponse {
97    pub id: String,
98    pub name: String,
99    pub method: String,
100    pub conditions: Vec<PolicyConditionResponse>,
101    pub action: String,
102}
103
104/// A condition as returned by GET /v1/policies/{id}.
105#[derive(Debug, Deserialize, Default)]
106#[serde(default)]
107pub struct PolicyConditionResponse {
108    pub field_source: String,
109    pub field: String,
110    pub operator: String,
111    pub value: serde_json::Value,
112}
113
114// ─── JWT types ────────────────────────────────────────────────────────
115
116/// Privy Identity Token claims.
117#[derive(Debug, Serialize, Deserialize)]
118pub struct PrivyClaims {
119    pub aud: String,
120    pub exp: u64,
121    pub iss: String,
122    /// User ID (Privy DID, e.g. "did:privy:abc123").
123    pub sub: String,
124    pub sid: String,
125    pub iat: u64,
126    #[serde(default)]
127    pub linked_accounts: Option<String>,
128    #[serde(default)]
129    pub custom_metadata: Option<String>,
130}
131
132/// Parsed linked account from identity token.
133#[derive(Debug, Clone, Deserialize)]
134#[serde(tag = "type", rename_all = "snake_case")]
135pub enum LinkedAccount {
136    Email {
137        address: String,
138    },
139    Wallet {
140        address: String,
141        #[serde(default)]
142        chain_type: Option<String>,
143    },
144    Phone {
145        #[serde(rename = "phoneNumber")]
146        phone_number: String,
147    },
148    Google {
149        email: String,
150        #[serde(default)]
151        name: Option<String>,
152    },
153    Apple {
154        email: String,
155    },
156    #[serde(other)]
157    Unknown,
158}
159
160impl PrivyClaims {
161    /// Extract the primary email from linked_accounts.
162    pub fn get_email(&self) -> Option<String> {
163        let accounts_json = self.linked_accounts.as_ref()?;
164        let accounts: Vec<LinkedAccount> = serde_json::from_str(accounts_json).ok()?;
165        accounts.into_iter().find_map(|acc| match acc {
166            LinkedAccount::Email { address } => Some(address),
167            LinkedAccount::Google { email, .. } => Some(email),
168            LinkedAccount::Apple { email } => Some(email),
169            _ => None,
170        })
171    }
172
173    /// Extract all emails from linked_accounts.
174    pub fn get_all_emails(&self) -> Vec<String> {
175        let Some(accounts_json) = self.linked_accounts.as_ref() else {
176            return Vec::new();
177        };
178        let Ok(accounts) = serde_json::from_str::<Vec<LinkedAccount>>(accounts_json) else {
179            return Vec::new();
180        };
181        accounts
182            .into_iter()
183            .filter_map(|acc| match acc {
184                LinkedAccount::Email { address } => Some(address),
185                LinkedAccount::Google { email, .. } => Some(email),
186                LinkedAccount::Apple { email } => Some(email),
187                _ => None,
188            })
189            .collect()
190    }
191
192    /// Extract wallet addresses from linked_accounts.
193    pub fn get_wallet_addresses(&self) -> Vec<String> {
194        let Some(accounts_json) = self.linked_accounts.as_ref() else {
195            return Vec::new();
196        };
197        let Ok(accounts) = serde_json::from_str::<Vec<LinkedAccount>>(accounts_json) else {
198            return Vec::new();
199        };
200        accounts
201            .into_iter()
202            .filter_map(|acc| match acc {
203                LinkedAccount::Wallet { address, .. } => Some(address),
204                _ => None,
205            })
206            .collect()
207    }
208}