1use crate::AvatarInfo;
2use alloy_primitives::{Address, TxHash, U256};
3use serde::{Deserialize, Deserializer, Serialize};
4
5#[derive(Debug, Clone, Serialize, Deserialize)]
7pub struct JsonRpcRequest<T = serde_json::Value> {
8 pub jsonrpc: String,
9 pub id: serde_json::Value, pub method: String,
11 pub params: T,
12}
13
14impl<T> JsonRpcRequest<T> {
15 pub fn new(id: impl Into<serde_json::Value>, method: String, params: T) -> Self {
16 Self {
17 jsonrpc: "2.0".to_string(),
18 id: id.into(),
19 method,
20 params,
21 }
22 }
23}
24
25#[derive(Debug, Clone, Serialize, Deserialize)]
27pub struct JsonRpcError {
28 pub code: i32,
29 pub message: String,
30 #[serde(skip_serializing_if = "Option::is_none")]
31 pub data: Option<serde_json::Value>,
32}
33
34#[derive(Debug, Clone, Serialize, Deserialize)]
36pub struct JsonRpcResponse<T = serde_json::Value> {
37 pub jsonrpc: String,
38 pub id: serde_json::Value,
39 #[serde(skip_serializing_if = "Option::is_none")]
40 pub result: Option<T>,
41 #[serde(skip_serializing_if = "Option::is_none")]
42 pub error: Option<JsonRpcError>,
43}
44
45impl<T> JsonRpcResponse<T> {
46 pub fn success(id: impl Into<serde_json::Value>, result: T) -> Self {
47 Self {
48 jsonrpc: "2.0".to_string(),
49 id: id.into(),
50 result: Some(result),
51 error: None,
52 }
53 }
54
55 pub fn error(id: impl Into<serde_json::Value>, error: JsonRpcError) -> Self {
56 Self {
57 jsonrpc: "2.0".to_string(),
58 id: id.into(),
59 result: None,
60 error: Some(error),
61 }
62 }
63}
64
65#[derive(Debug, Clone, Serialize, Deserialize)]
68pub struct CirclesQueryResponse {
69 pub columns: Vec<String>,
70 pub rows: Vec<Vec<serde_json::Value>>,
71}
72
73#[derive(Debug, Clone, Serialize, Deserialize)]
76pub struct QueryResponse<T = serde_json::Value> {
77 pub result: T,
78 #[serde(skip_serializing_if = "Option::is_none")]
79 pub error: Option<serde_json::Value>,
80}
81
82impl<T> QueryResponse<T> {
83 pub fn success(result: T) -> Self {
84 Self {
85 result,
86 error: None,
87 }
88 }
89
90 pub fn error(error: serde_json::Value) -> Self {
91 Self {
92 result: unsafe { std::mem::zeroed() }, error: Some(error),
94 }
95 }
96}
97
98#[derive(Debug, Clone, Serialize, Deserialize)]
100pub struct SafeQueryResponse<T> {
101 #[serde(skip_serializing_if = "Option::is_none")]
102 pub result: Option<T>,
103 #[serde(skip_serializing_if = "Option::is_none")]
104 pub error: Option<serde_json::Value>,
105}
106
107impl<T> SafeQueryResponse<T> {
108 pub fn success(result: T) -> Self {
109 Self {
110 result: Some(result),
111 error: None,
112 }
113 }
114
115 pub fn error(error: serde_json::Value) -> Self {
116 Self {
117 result: None,
118 error: Some(error),
119 }
120 }
121}
122
123#[derive(Debug, Clone, Serialize, Deserialize)]
125#[serde(rename_all = "camelCase")]
126pub struct InvitationOriginResponse {
127 pub address: Address,
128 pub invitation_type: String,
129 pub inviter: Option<Address>,
130 pub proxy_inviter: Option<Address>,
131 pub escrow_amount: Option<String>,
132 pub block_number: u64,
133 pub timestamp: u64,
134 pub transaction_hash: TxHash,
135 pub version: u32,
136}
137
138#[derive(Debug, Clone, Serialize, Deserialize)]
140#[serde(rename_all = "camelCase")]
141pub struct TrustInvitation {
142 pub address: Address,
143 pub source: String,
144 pub balance: String,
145 pub avatar_info: Option<AvatarInfo>,
146}
147
148#[derive(Debug, Clone, Serialize, Deserialize)]
150#[serde(rename_all = "camelCase")]
151pub struct EscrowInvitation {
152 pub address: Address,
153 pub source: String,
154 pub escrowed_amount: String,
155 pub escrow_days: u32,
156 pub block_number: u64,
157 pub timestamp: u64,
158 pub avatar_info: Option<AvatarInfo>,
159}
160
161#[derive(Debug, Clone, Serialize, Deserialize)]
163#[serde(rename_all = "camelCase")]
164pub struct AtScaleInvitation {
165 pub address: Address,
166 pub source: String,
167 pub block_number: u64,
168 pub timestamp: u64,
169 pub origin_inviter: Option<Address>,
170}
171
172#[derive(Debug, Clone, Serialize, Deserialize)]
174#[serde(rename_all = "camelCase")]
175pub struct AllInvitationsResponse {
176 pub address: Address,
177 pub trust_invitations: Vec<TrustInvitation>,
178 pub escrow_invitations: Vec<EscrowInvitation>,
179 pub at_scale_invitations: Vec<AtScaleInvitation>,
180}
181
182#[derive(Debug, Clone, Serialize, Deserialize)]
184#[serde(rename_all = "camelCase")]
185pub struct InvitedAccountInfo {
186 pub address: Address,
187 pub status: String,
188 pub block_number: u64,
189 pub timestamp: u64,
190 pub avatar_info: Option<AvatarInfo>,
191}
192
193#[derive(Debug, Clone, Serialize, Deserialize)]
195#[serde(rename_all = "camelCase")]
196pub struct InvitationsFromResponse {
197 pub address: Address,
198 pub accepted: bool,
199 pub results: Vec<InvitedAccountInfo>,
200}
201
202#[derive(Debug, Clone, Serialize, Deserialize)]
204#[serde(untagged)]
205pub enum Balance {
206 Raw(U256),
207 TimeCircles(f64),
208}
209
210#[derive(Debug, Clone, Serialize)]
212pub struct TokenBalanceResponse {
213 #[serde(rename = "tokenAddress")]
214 pub token_address: Address,
215 #[serde(rename = "tokenId")]
216 pub token_id: Address,
217 pub balance: Balance,
218 #[serde(default, rename = "staticAttoCircles")]
220 pub static_atto_circles: Option<U256>,
221 #[serde(default, rename = "staticCircles")]
222 pub static_circles: Option<f64>,
223 #[serde(default, rename = "tokenType", skip_serializing_if = "Option::is_none")]
224 pub token_type: Option<String>,
225 #[serde(default, skip_serializing_if = "Option::is_none")]
226 pub version: Option<u32>,
227 #[serde(
228 default,
229 rename = "attoCircles",
230 skip_serializing_if = "Option::is_none"
231 )]
232 pub atto_circles: Option<U256>,
233 #[serde(default, skip_serializing_if = "Option::is_none")]
234 pub circles: Option<f64>,
235 #[serde(default, rename = "attoCrc", skip_serializing_if = "Option::is_none")]
236 pub atto_crc: Option<U256>,
237 #[serde(default, skip_serializing_if = "Option::is_none")]
238 pub crc: Option<f64>,
239 #[serde(default, rename = "isErc20")]
240 pub is_erc20: bool,
241 #[serde(default, rename = "isErc1155")]
242 pub is_erc1155: bool,
243 #[serde(default, rename = "isWrapped")]
244 pub is_wrapped: bool,
245 #[serde(default, rename = "isInflationary")]
246 pub is_inflationary: bool,
247 #[serde(default, rename = "isGroup")]
248 pub is_group: bool,
249 #[serde(rename = "tokenOwner")]
250 pub token_owner: Address,
251}
252
253#[derive(Debug, Clone, Deserialize)]
254struct TokenBalanceResponseWire {
255 #[serde(default, rename = "tokenAddress", alias = "token_address")]
256 token_address: Option<Address>,
257 #[serde(rename = "tokenId", alias = "token_id")]
258 token_id: Address,
259 #[serde(default)]
260 balance: Option<Balance>,
261 #[serde(default, rename = "staticAttoCircles")]
262 static_atto_circles: Option<U256>,
263 #[serde(default, rename = "staticCircles")]
264 static_circles: Option<f64>,
265 #[serde(default, rename = "tokenType", alias = "token_type")]
266 token_type: Option<String>,
267 #[serde(default)]
268 version: Option<u32>,
269 #[serde(default, rename = "attoCircles")]
270 atto_circles: Option<U256>,
271 #[serde(default)]
272 circles: Option<f64>,
273 #[serde(default, rename = "attoCrc")]
274 atto_crc: Option<U256>,
275 #[serde(default)]
276 crc: Option<f64>,
277 #[serde(default, rename = "isErc20", alias = "is_erc20")]
278 is_erc20: bool,
279 #[serde(default, rename = "isErc1155", alias = "is_erc1155")]
280 is_erc1155: bool,
281 #[serde(default, rename = "isWrapped", alias = "is_wrapped")]
282 is_wrapped: bool,
283 #[serde(default, rename = "isInflationary", alias = "is_inflationary")]
284 is_inflationary: bool,
285 #[serde(default, rename = "isGroup", alias = "is_group")]
286 is_group: bool,
287 #[serde(rename = "tokenOwner", alias = "token_owner")]
288 token_owner: Address,
289}
290
291impl<'de> Deserialize<'de> for TokenBalanceResponse {
292 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
293 where
294 D: Deserializer<'de>,
295 {
296 let wire = TokenBalanceResponseWire::deserialize(deserializer)?;
297 let balance = wire
298 .balance
299 .or_else(|| wire.atto_circles.map(Balance::Raw))
300 .or_else(|| wire.circles.map(Balance::TimeCircles))
301 .ok_or_else(|| serde::de::Error::missing_field("balance / attoCircles / circles"))?;
302
303 Ok(Self {
304 token_address: wire.token_address.unwrap_or(wire.token_id),
305 token_id: wire.token_id,
306 balance,
307 static_atto_circles: wire.static_atto_circles,
308 static_circles: wire.static_circles,
309 token_type: wire.token_type,
310 version: wire.version,
311 atto_circles: wire.atto_circles,
312 circles: wire.circles,
313 atto_crc: wire.atto_crc,
314 crc: wire.crc,
315 is_erc20: wire.is_erc20,
316 is_erc1155: wire.is_erc1155,
317 is_wrapped: wire.is_wrapped,
318 is_inflationary: wire.is_inflationary,
319 is_group: wire.is_group,
320 token_owner: wire.token_owner,
321 })
322 }
323}
324
325#[cfg(test)]
326mod tests {
327 use super::*;
328 use serde_json::json;
329
330 #[test]
331 fn invitation_origin_response_deserializes_plugin_shape() {
332 let value = json!({
333 "address": "0xde374ece6fa50e781e81aac78e811b33d16912c7",
334 "invitationType": "v2_at_scale",
335 "inviter": "0x1234567890abcdef1234567890abcdef12345678",
336 "proxyInviter": "0xabcdef1234567890abcdef1234567890abcdef12",
337 "escrowAmount": null,
338 "blockNumber": 36500000,
339 "timestamp": 1704240000,
340 "transactionHash": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
341 "version": 2
342 });
343
344 let response: InvitationOriginResponse =
345 serde_json::from_value(value).expect("deserialize invitation origin");
346
347 assert_eq!(response.invitation_type, "v2_at_scale");
348 assert_eq!(response.version, 2);
349 assert_eq!(response.block_number, 36_500_000);
350 assert!(response.inviter.is_some());
351 assert!(response.proxy_inviter.is_some());
352 }
353
354 #[test]
355 fn all_invitations_response_deserializes_plugin_shape() {
356 let value = json!({
357 "address": "0xde374ece6fa50e781e81aac78e811b33d16912c7",
358 "trustInvitations": [{
359 "address": "0x1234567890abcdef1234567890abcdef12345678",
360 "source": "trust",
361 "balance": "150.5",
362 "avatarInfo": null
363 }],
364 "escrowInvitations": [{
365 "address": "0xabcdef1234567890abcdef1234567890abcdef12",
366 "source": "escrow",
367 "escrowedAmount": "100000000000000000000",
368 "escrowDays": 7,
369 "blockNumber": 43645581,
370 "timestamp": 1765725505,
371 "avatarInfo": null
372 }],
373 "atScaleInvitations": [{
374 "address": "0xde374ece6fa50e781e81aac78e811b33d16912c7",
375 "source": "atScale",
376 "blockNumber": 43260668,
377 "timestamp": 1763742205,
378 "originInviter": null
379 }]
380 });
381
382 let response: AllInvitationsResponse =
383 serde_json::from_value(value).expect("deserialize all invitations");
384
385 assert_eq!(response.trust_invitations.len(), 1);
386 assert_eq!(response.escrow_invitations.len(), 1);
387 assert_eq!(response.at_scale_invitations.len(), 1);
388 assert_eq!(response.trust_invitations[0].balance, "150.5");
389 assert_eq!(response.escrow_invitations[0].escrow_days, 7);
390 }
391}
392
393#[derive(Debug, Clone, Serialize, Deserialize)]
395#[serde(rename_all = "camelCase")]
396pub struct TransactionHistoryRow {
397 pub block_number: u64,
398 pub timestamp: u64,
399 pub transaction_index: u32,
400 pub log_index: u32,
401 pub transaction_hash: TxHash,
402 pub version: u32,
403 pub from: Address,
404 pub to: Address,
405 pub id: String,
406 pub token_address: Address,
407 pub value: String,
408 #[serde(default)]
409 pub circles: Option<f64>,
410 #[serde(default)]
411 pub atto_circles: Option<U256>,
412 #[serde(default)]
413 pub static_circles: Option<f64>,
414 #[serde(default)]
415 pub static_atto_circles: Option<U256>,
416 #[serde(default)]
417 pub crc: Option<f64>,
418 #[serde(default)]
419 pub atto_crc: Option<U256>,
420}