Skip to main content

chainstream_sdk/stream/
models.rs

1//! Data models for the ChainStream WebSocket Stream API
2//!
3//! This module contains all the data structures used by the stream API
4//! for real-time data subscriptions.
5
6use serde::{Deserialize, Serialize};
7
8// Re-export Resolution from openapi for convenience
9pub use crate::openapi::types::Resolution;
10
11/// Token activity type
12#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
13#[serde(rename_all = "snake_case")]
14pub enum TokenActivityType {
15    Sell,
16    Buy,
17    AddLiquidity,
18    RemoveLiquidity,
19}
20
21/// Channel type for subscriptions
22#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
23#[serde(rename_all = "snake_case")]
24pub enum ChannelType {
25    New,
26    #[serde(rename = "trending")]
27    Hot,
28    UsStocks,
29    Completed,
30    Graduated,
31}
32
33/// Metric type for measurements
34#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
35#[serde(rename_all = "snake_case")]
36pub enum MetricType {
37    LiquidityInUsd,
38    MigratedRatio,
39}
40
41/// Ranking type for token rankings
42#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
43#[serde(rename_all = "snake_case")]
44pub enum RankingType {
45    New,
46    #[serde(rename = "trending")]
47    Hot,
48    Stocks,
49    #[serde(rename = "completed")]
50    FinalStretch,
51    #[serde(rename = "graduated")]
52    Migrated,
53}
54
55/// DEX (Decentralized Exchange) type
56#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
57#[serde(rename_all = "snake_case")]
58pub enum Dex {
59    PumpFun,
60    RaydiumLaunchpad,
61    MeteoraDynamicBoundingCurve,
62    BonkFun,
63    BoopFun,
64    MoonitFun,
65}
66
67/// Token activity data
68#[derive(Debug, Clone, Serialize, Deserialize)]
69#[serde(rename_all = "camelCase")]
70pub struct TokenActivity {
71    pub address: String,
72    pub price_usd: String,
73    pub amount: String,
74    #[serde(rename = "type")]
75    pub activity_type: TokenActivityType,
76    pub tx_hash: String,
77    pub timestamp: i64,
78}
79
80/// Token statistics with multi-timeframe data
81#[derive(Debug, Clone, Serialize, Deserialize, Default)]
82#[serde(rename_all = "camelCase")]
83pub struct TokenStat {
84    pub address: String,
85    pub timestamp: i64,
86
87    // 1-minute data
88    #[serde(skip_serializing_if = "Option::is_none")]
89    pub buys_1m: Option<i32>,
90    #[serde(skip_serializing_if = "Option::is_none")]
91    pub sells_1m: Option<i32>,
92    #[serde(skip_serializing_if = "Option::is_none")]
93    pub buyers_1m: Option<i32>,
94    #[serde(skip_serializing_if = "Option::is_none")]
95    pub sellers_1m: Option<i32>,
96    #[serde(skip_serializing_if = "Option::is_none")]
97    pub buy_volume_in_usd_1m: Option<String>,
98    #[serde(skip_serializing_if = "Option::is_none")]
99    pub sell_volume_in_usd_1m: Option<String>,
100    #[serde(skip_serializing_if = "Option::is_none")]
101    pub price_1m: Option<String>,
102    #[serde(skip_serializing_if = "Option::is_none")]
103    pub open_in_usd_1m: Option<String>,
104    #[serde(skip_serializing_if = "Option::is_none")]
105    pub close_in_usd_1m: Option<String>,
106
107    // 5-minute data
108    #[serde(skip_serializing_if = "Option::is_none")]
109    pub buys_5m: Option<i32>,
110    #[serde(skip_serializing_if = "Option::is_none")]
111    pub sells_5m: Option<i32>,
112    #[serde(skip_serializing_if = "Option::is_none")]
113    pub buyers_5m: Option<i32>,
114    #[serde(skip_serializing_if = "Option::is_none")]
115    pub sellers_5m: Option<i32>,
116    #[serde(skip_serializing_if = "Option::is_none")]
117    pub buy_volume_in_usd_5m: Option<String>,
118    #[serde(skip_serializing_if = "Option::is_none")]
119    pub sell_volume_in_usd_5m: Option<String>,
120    #[serde(skip_serializing_if = "Option::is_none")]
121    pub price_5m: Option<String>,
122    #[serde(skip_serializing_if = "Option::is_none")]
123    pub open_in_usd_5m: Option<String>,
124    #[serde(skip_serializing_if = "Option::is_none")]
125    pub close_in_usd_5m: Option<String>,
126
127    // 15-minute data
128    #[serde(skip_serializing_if = "Option::is_none")]
129    pub buys_15m: Option<i32>,
130    #[serde(skip_serializing_if = "Option::is_none")]
131    pub sells_15m: Option<i32>,
132    #[serde(skip_serializing_if = "Option::is_none")]
133    pub buyers_15m: Option<i32>,
134    #[serde(skip_serializing_if = "Option::is_none")]
135    pub sellers_15m: Option<i32>,
136    #[serde(skip_serializing_if = "Option::is_none")]
137    pub buy_volume_in_usd_15m: Option<String>,
138    #[serde(skip_serializing_if = "Option::is_none")]
139    pub sell_volume_in_usd_15m: Option<String>,
140    #[serde(skip_serializing_if = "Option::is_none")]
141    pub price_15m: Option<String>,
142    #[serde(skip_serializing_if = "Option::is_none")]
143    pub open_in_usd_15m: Option<String>,
144    #[serde(skip_serializing_if = "Option::is_none")]
145    pub close_in_usd_15m: Option<String>,
146
147    // 30-minute data
148    #[serde(skip_serializing_if = "Option::is_none")]
149    pub buys_30m: Option<i32>,
150    #[serde(skip_serializing_if = "Option::is_none")]
151    pub sells_30m: Option<i32>,
152    #[serde(skip_serializing_if = "Option::is_none")]
153    pub buyers_30m: Option<i32>,
154    #[serde(skip_serializing_if = "Option::is_none")]
155    pub sellers_30m: Option<i32>,
156    #[serde(skip_serializing_if = "Option::is_none")]
157    pub buy_volume_in_usd_30m: Option<String>,
158    #[serde(skip_serializing_if = "Option::is_none")]
159    pub sell_volume_in_usd_30m: Option<String>,
160    #[serde(skip_serializing_if = "Option::is_none")]
161    pub price_30m: Option<String>,
162    #[serde(skip_serializing_if = "Option::is_none")]
163    pub open_in_usd_30m: Option<String>,
164    #[serde(skip_serializing_if = "Option::is_none")]
165    pub close_in_usd_30m: Option<String>,
166
167    // 1-hour data
168    #[serde(skip_serializing_if = "Option::is_none")]
169    pub buys_1h: Option<i32>,
170    #[serde(skip_serializing_if = "Option::is_none")]
171    pub sells_1h: Option<i32>,
172    #[serde(skip_serializing_if = "Option::is_none")]
173    pub buyers_1h: Option<i32>,
174    #[serde(skip_serializing_if = "Option::is_none")]
175    pub sellers_1h: Option<i32>,
176    #[serde(skip_serializing_if = "Option::is_none")]
177    pub buy_volume_in_usd_1h: Option<String>,
178    #[serde(skip_serializing_if = "Option::is_none")]
179    pub sell_volume_in_usd_1h: Option<String>,
180    #[serde(skip_serializing_if = "Option::is_none")]
181    pub price_1h: Option<String>,
182    #[serde(skip_serializing_if = "Option::is_none")]
183    pub open_in_usd_1h: Option<String>,
184    #[serde(skip_serializing_if = "Option::is_none")]
185    pub close_in_usd_1h: Option<String>,
186
187    // 4-hour data
188    #[serde(skip_serializing_if = "Option::is_none")]
189    pub buys_4h: Option<i32>,
190    #[serde(skip_serializing_if = "Option::is_none")]
191    pub sells_4h: Option<i32>,
192    #[serde(skip_serializing_if = "Option::is_none")]
193    pub buyers_4h: Option<i32>,
194    #[serde(skip_serializing_if = "Option::is_none")]
195    pub sellers_4h: Option<i32>,
196    #[serde(skip_serializing_if = "Option::is_none")]
197    pub buy_volume_in_usd_4h: Option<String>,
198    #[serde(skip_serializing_if = "Option::is_none")]
199    pub sell_volume_in_usd_4h: Option<String>,
200    #[serde(skip_serializing_if = "Option::is_none")]
201    pub price_4h: Option<String>,
202    #[serde(skip_serializing_if = "Option::is_none")]
203    pub open_in_usd_4h: Option<String>,
204    #[serde(skip_serializing_if = "Option::is_none")]
205    pub close_in_usd_4h: Option<String>,
206
207    // 24-hour data
208    #[serde(skip_serializing_if = "Option::is_none")]
209    pub buys_24h: Option<i32>,
210    #[serde(skip_serializing_if = "Option::is_none")]
211    pub sells_24h: Option<i32>,
212    #[serde(skip_serializing_if = "Option::is_none")]
213    pub buyers_24h: Option<i32>,
214    #[serde(skip_serializing_if = "Option::is_none")]
215    pub sellers_24h: Option<i32>,
216    #[serde(skip_serializing_if = "Option::is_none")]
217    pub buy_volume_in_usd_24h: Option<String>,
218    #[serde(skip_serializing_if = "Option::is_none")]
219    pub sell_volume_in_usd_24h: Option<String>,
220    #[serde(skip_serializing_if = "Option::is_none")]
221    pub price_24h: Option<String>,
222    #[serde(skip_serializing_if = "Option::is_none")]
223    pub open_in_usd_24h: Option<String>,
224    #[serde(skip_serializing_if = "Option::is_none")]
225    pub close_in_usd_24h: Option<String>,
226
227    // Current price
228    #[serde(skip_serializing_if = "Option::is_none")]
229    pub price: Option<String>,
230}
231
232/// Token holder information
233#[derive(Debug, Clone, Serialize, Deserialize)]
234#[serde(rename_all = "camelCase")]
235pub struct TokenHolder {
236    pub token_address: String,
237    #[serde(skip_serializing_if = "Option::is_none")]
238    pub holders: Option<i32>,
239    #[serde(skip_serializing_if = "Option::is_none")]
240    pub top100_amount: Option<String>,
241    #[serde(skip_serializing_if = "Option::is_none")]
242    pub top10_amount: Option<String>,
243    #[serde(skip_serializing_if = "Option::is_none")]
244    pub top100_holders: Option<i32>,
245    #[serde(skip_serializing_if = "Option::is_none")]
246    pub top10_holders: Option<i32>,
247    #[serde(skip_serializing_if = "Option::is_none")]
248    pub top100_ratio: Option<String>,
249    #[serde(skip_serializing_if = "Option::is_none")]
250    pub top10_ratio: Option<String>,
251    #[serde(skip_serializing_if = "Option::is_none")]
252    pub creators_holders: Option<i32>,
253    #[serde(skip_serializing_if = "Option::is_none")]
254    pub creators_amount: Option<String>,
255    #[serde(skip_serializing_if = "Option::is_none")]
256    pub creators_ratio: Option<String>,
257    pub timestamp: i64,
258}
259
260/// Wallet balance data
261#[derive(Debug, Clone, Serialize, Deserialize)]
262#[serde(rename_all = "camelCase")]
263pub struct WalletBalance {
264    pub wallet_address: String,
265    pub token_address: String,
266    pub token_price_in_usd: String,
267    pub balance: String,
268    pub timestamp: i64,
269}
270
271/// Wallet profit and loss data
272#[derive(Debug, Clone, Serialize, Deserialize)]
273#[serde(rename_all = "camelCase")]
274pub struct WalletPnl {
275    pub wallet_address: String,
276    pub buys: i32,
277    pub buy_amount: String,
278    pub buy_amount_in_usd: String,
279    pub average_buy_price_in_usd: String,
280    pub sell_amount: String,
281    pub sell_amount_in_usd: String,
282    pub sells: i32,
283    pub wins: i32,
284    pub win_ratio: String,
285    pub pnl_in_usd: String,
286    pub average_pnl_in_usd: String,
287    pub pnl_ratio: String,
288    pub profitable_days: i32,
289    pub losing_days: i32,
290    pub tokens: i32,
291    pub resolution: String,
292}
293
294/// DEX protocol information
295#[derive(Debug, Clone, Serialize, Deserialize)]
296#[serde(rename_all = "camelCase")]
297pub struct DexProtocol {
298    #[serde(skip_serializing_if = "Option::is_none")]
299    pub program_address: Option<String>,
300    #[serde(skip_serializing_if = "Option::is_none")]
301    pub protocol_family: Option<String>,
302    #[serde(skip_serializing_if = "Option::is_none")]
303    pub protocol_name: Option<String>,
304}
305
306/// New token data
307#[derive(Debug, Clone, Serialize, Deserialize)]
308#[serde(rename_all = "camelCase")]
309pub struct NewToken {
310    pub token_address: String,
311    pub name: String,
312    pub symbol: String,
313    #[serde(skip_serializing_if = "Option::is_none")]
314    pub decimals: Option<i32>,
315    #[serde(skip_serializing_if = "Option::is_none")]
316    pub launch_from: Option<DexProtocol>,
317    pub created_at_ms: i64,
318}
319
320/// Token supply data
321#[derive(Debug, Clone, Serialize, Deserialize)]
322#[serde(rename_all = "camelCase")]
323pub struct TokenSupply {
324    pub token_address: String,
325    #[serde(skip_serializing_if = "Option::is_none")]
326    pub supply: Option<String>,
327    #[serde(skip_serializing_if = "Option::is_none")]
328    pub market_cap_in_usd: Option<String>,
329    pub timestamp: i64,
330}
331
332/// DEX pool balance data
333#[derive(Debug, Clone, Serialize, Deserialize)]
334#[serde(rename_all = "camelCase")]
335pub struct DexPoolBalance {
336    pub pool_address: String,
337    pub token_a_address: String,
338    pub token_a_liquidity_in_usd: String,
339    pub token_b_address: String,
340    pub token_b_liquidity_in_usd: String,
341}
342
343/// Token liquidity data
344#[derive(Debug, Clone, Serialize, Deserialize)]
345#[serde(rename_all = "camelCase")]
346pub struct TokenLiquidity {
347    pub token_address: String,
348    pub metric_type: MetricType,
349    pub value: String,
350    pub timestamp: i64,
351}
352
353/// Token max liquidity data (max liquidity in a single pool)
354#[derive(Debug, Clone, Serialize, Deserialize)]
355#[serde(rename_all = "camelCase")]
356pub struct TokenMaxLiquidity {
357    pub token_address: String,
358    pub pool_address: String,
359    pub liquidity_in_usd: String,
360    pub liquidity_in_native: String,
361    pub timestamp: i64,
362}
363
364/// Token total liquidity data (total liquidity across all pools)
365#[derive(Debug, Clone, Serialize, Deserialize)]
366#[serde(rename_all = "camelCase")]
367pub struct TokenTotalLiquidity {
368    pub token_address: String,
369    pub liquidity_in_usd: String,
370    pub liquidity_in_native: String,
371    pub pool_count: i32,
372    pub timestamp: i64,
373}
374
375/// Token bonding curve data
376#[derive(Debug, Clone, Serialize, Deserialize)]
377#[serde(rename_all = "camelCase")]
378pub struct TokenBondingCurve {
379    #[serde(skip_serializing_if = "Option::is_none")]
380    pub token_address: Option<String>,
381    #[serde(skip_serializing_if = "Option::is_none")]
382    pub progress_ratio: Option<String>,
383}
384
385/// Social media links
386#[derive(Debug, Clone, Serialize, Deserialize, Default)]
387#[serde(rename_all = "camelCase")]
388pub struct SocialMedia {
389    #[serde(skip_serializing_if = "Option::is_none")]
390    pub twitter: Option<String>,
391    #[serde(skip_serializing_if = "Option::is_none")]
392    pub telegram: Option<String>,
393    #[serde(skip_serializing_if = "Option::is_none")]
394    pub website: Option<String>,
395    #[serde(skip_serializing_if = "Option::is_none")]
396    pub tiktok: Option<String>,
397    #[serde(skip_serializing_if = "Option::is_none")]
398    pub discord: Option<String>,
399    #[serde(skip_serializing_if = "Option::is_none")]
400    pub facebook: Option<String>,
401    #[serde(skip_serializing_if = "Option::is_none")]
402    pub github: Option<String>,
403    #[serde(skip_serializing_if = "Option::is_none")]
404    pub instagram: Option<String>,
405    #[serde(skip_serializing_if = "Option::is_none")]
406    pub linkedin: Option<String>,
407    #[serde(skip_serializing_if = "Option::is_none")]
408    pub medium: Option<String>,
409    #[serde(skip_serializing_if = "Option::is_none")]
410    pub reddit: Option<String>,
411    #[serde(skip_serializing_if = "Option::is_none")]
412    pub youtube: Option<String>,
413    #[serde(skip_serializing_if = "Option::is_none")]
414    pub bitbucket: Option<String>,
415}
416
417/// Token metadata
418#[derive(Debug, Clone, Serialize, Deserialize)]
419#[serde(rename_all = "camelCase")]
420pub struct TokenMetadata {
421    pub token_address: String,
422    #[serde(skip_serializing_if = "Option::is_none")]
423    pub name: Option<String>,
424    #[serde(skip_serializing_if = "Option::is_none")]
425    pub decimals: Option<i32>,
426    #[serde(skip_serializing_if = "Option::is_none")]
427    pub symbol: Option<String>,
428    #[serde(skip_serializing_if = "Option::is_none")]
429    pub image_url: Option<String>,
430    #[serde(skip_serializing_if = "Option::is_none")]
431    pub description: Option<String>,
432    #[serde(skip_serializing_if = "Option::is_none")]
433    pub social_media: Option<SocialMedia>,
434    #[serde(skip_serializing_if = "Option::is_none")]
435    pub created_at_ms: Option<i64>,
436    #[serde(skip_serializing_if = "Option::is_none")]
437    pub launch_from: Option<DexProtocol>,
438    #[serde(skip_serializing_if = "Option::is_none")]
439    pub migrated_to: Option<DexProtocol>,
440}
441
442/// Token candlestick data
443#[derive(Debug, Clone, Serialize, Deserialize)]
444#[serde(rename_all = "camelCase")]
445pub struct TokenCandle {
446    pub open: String,
447    pub close: String,
448    pub high: String,
449    pub low: String,
450    pub volume: String,
451    pub resolution: String,
452    pub time: i64,
453    pub number: i32,
454}
455
456/// Trade activity data
457#[derive(Debug, Clone, Serialize, Deserialize)]
458#[serde(rename_all = "camelCase")]
459pub struct TradeActivity {
460    pub token_address: String,
461    pub timestamp: i64,
462    pub kind: String,
463    pub buy_amount: String,
464    pub buy_amount_in_usd: String,
465    pub buy_token_address: String,
466    pub buy_token_name: String,
467    pub buy_token_symbol: String,
468    pub buy_wallet_address: String,
469    pub sell_amount: String,
470    pub sell_amount_in_usd: String,
471    pub sell_token_address: String,
472    pub sell_token_name: String,
473    pub sell_token_symbol: String,
474    pub sell_wallet_address: String,
475    pub tx_hash: String,
476}
477
478/// Wallet token profit and loss data
479#[derive(Debug, Clone, Serialize, Deserialize)]
480#[serde(rename_all = "camelCase")]
481pub struct WalletTokenPnl {
482    pub wallet_address: String,
483    pub token_address: String,
484    pub token_price_in_usd: String,
485    pub timestamp: i64,
486    pub open_time: i64,
487    pub last_time: i64,
488    pub close_time: i64,
489    pub buy_amount: String,
490    pub buy_amount_in_usd: String,
491    pub buy_count: i32,
492    pub buy_count_30d: i32,
493    pub buy_count_7d: i32,
494    pub sell_amount: String,
495    pub sell_amount_in_usd: String,
496    pub sell_count: i32,
497    pub sell_count_30d: i32,
498    pub sell_count_7d: i32,
499    pub held_duration_timestamp: i64,
500    pub average_buy_price_in_usd: String,
501    pub average_sell_price_in_usd: String,
502    pub unrealized_profit_in_usd: String,
503    pub unrealized_profit_ratio: String,
504    pub realized_profit_in_usd: String,
505    pub realized_profit_ratio: String,
506    pub total_realized_profit_in_usd: String,
507    pub total_realized_profit_ratio: String,
508}
509
510/// Ranking token list data
511#[derive(Debug, Clone, Serialize, Deserialize)]
512#[serde(rename_all = "camelCase")]
513pub struct RankingTokenList {
514    #[serde(skip_serializing_if = "Option::is_none")]
515    pub metadata: Option<TokenMetadata>,
516    #[serde(skip_serializing_if = "Option::is_none")]
517    pub holder: Option<TokenHolder>,
518    #[serde(skip_serializing_if = "Option::is_none")]
519    pub supply: Option<TokenSupply>,
520    #[serde(skip_serializing_if = "Option::is_none")]
521    pub stat: Option<TokenStat>,
522    #[serde(skip_serializing_if = "Option::is_none")]
523    pub bonding_curve: Option<TokenBondingCurve>,
524}