Skip to main content

chainstream_sdk/stream/
fields.rs

1//! CEL field mappings for stream filter expressions
2//!
3//! This module provides field name mappings used to convert human-readable
4//! field names to their short form for server-side CEL filter expressions.
5
6use std::collections::HashMap;
7
8use once_cell::sync::Lazy;
9
10/// Field mapping type: maps long field names to short field names
11pub type FieldMapping = HashMap<&'static str, &'static str>;
12
13/// Method field mappings type: maps subscription method names to their field mappings
14pub type MethodFieldMappings = HashMap<&'static str, FieldMapping>;
15
16/// CEL field mappings organized by subscription method
17pub static CEL_FIELD_MAPPINGS: Lazy<MethodFieldMappings> = Lazy::new(|| {
18    let mut mappings = HashMap::new();
19
20    // Wallet balance subscription fields
21    mappings.insert(
22        "subscribe_wallet_balance",
23        HashMap::from([
24            ("walletAddress", "a"),
25            ("tokenAddress", "ta"),
26            ("tokenPriceInUsd", "tpiu"),
27            ("balance", "b"),
28            ("timestamp", "t"),
29        ]),
30    );
31
32    // Token candle subscription fields
33    mappings.insert(
34        "subscribe_token_candles",
35        HashMap::from([
36            ("open", "o"),
37            ("close", "c"),
38            ("high", "h"),
39            ("low", "l"),
40            ("volume", "v"),
41            ("resolution", "r"),
42            ("time", "t"),
43            ("number", "n"),
44        ]),
45    );
46
47    // Token stats subscription fields
48    mappings.insert(
49        "subscribe_token_stats",
50        HashMap::from([
51            ("address", "a"),
52            ("timestamp", "t"),
53            ("buys1m", "b1m"),
54            ("sells1m", "s1m"),
55            ("buyers1m", "be1m"),
56            ("sellers1m", "se1m"),
57            ("buyVolumeInUsd1m", "bviu1m"),
58            ("sellVolumeInUsd1m", "sviu1m"),
59            ("price1m", "p1m"),
60            ("openInUsd1m", "oiu1m"),
61            ("closeInUsd1m", "ciu1m"),
62            ("buys5m", "b5m"),
63            ("sells5m", "s5m"),
64            ("buyers5m", "be5m"),
65            ("sellers5m", "se5m"),
66            ("buyVolumeInUsd5m", "bviu5m"),
67            ("sellVolumeInUsd5m", "sviu5m"),
68            ("price5m", "p5m"),
69            ("openInUsd5m", "oiu5m"),
70            ("closeInUsd5m", "ciu5m"),
71            ("buys15m", "b15m"),
72            ("sells15m", "s15m"),
73            ("buyers15m", "be15m"),
74            ("sellers15m", "se15m"),
75            ("buyVolumeInUsd15m", "bviu15m"),
76            ("sellVolumeInUsd15m", "sviu15m"),
77            ("price15m", "p15m"),
78            ("openInUsd15m", "oiu15m"),
79            ("closeInUsd15m", "ciu15m"),
80            ("buys30m", "b30m"),
81            ("sells30m", "s30m"),
82            ("buyers30m", "be30m"),
83            ("sellers30m", "se30m"),
84            ("buyVolumeInUsd30m", "bviu30m"),
85            ("sellVolumeInUsd30m", "sviu30m"),
86            ("price30m", "p30m"),
87            ("openInUsd30m", "oiu30m"),
88            ("closeInUsd30m", "ciu30m"),
89            ("buys1h", "b1h"),
90            ("sells1h", "s1h"),
91            ("buyers1h", "be1h"),
92            ("sellers1h", "se1h"),
93            ("buyVolumeInUsd1h", "bviu1h"),
94            ("sellVolumeInUsd1h", "sviu1h"),
95            ("price1h", "p1h"),
96            ("openInUsd1h", "oiu1h"),
97            ("closeInUsd1h", "ciu1h"),
98            ("buys4h", "b4h"),
99            ("sells4h", "s4h"),
100            ("buyers4h", "be4h"),
101            ("sellers4h", "se4h"),
102            ("buyVolumeInUsd4h", "bviu4h"),
103            ("sellVolumeInUsd4h", "sviu4h"),
104            ("price4h", "p4h"),
105            ("openInUsd4h", "oiu4h"),
106            ("closeInUsd4h", "ciu4h"),
107            ("buys24h", "b24h"),
108            ("sells24h", "s24h"),
109            ("buyers24h", "be24h"),
110            ("sellers24h", "se24h"),
111            ("buyVolumeInUsd24h", "bviu24h"),
112            ("sellVolumeInUsd24h", "sviu24h"),
113            ("price24h", "p24h"),
114            ("price", "p"),
115            ("openInUsd24h", "oiu24h"),
116            ("closeInUsd24h", "ciu24h"),
117        ]),
118    );
119
120    // Token holder subscription fields
121    mappings.insert(
122        "subscribe_token_holders",
123        HashMap::from([
124            ("tokenAddress", "a"),
125            ("holders", "h"),
126            ("top100Amount", "t100a"),
127            ("top10Amount", "t10a"),
128            ("top100Holders", "t100h"),
129            ("top10Holders", "t10h"),
130            ("top100Ratio", "t100r"),
131            ("top10Ratio", "t10r"),
132            ("timestamp", "ts"),
133        ]),
134    );
135
136    // New token subscription fields
137    mappings.insert(
138        "subscribe_new_token",
139        HashMap::from([
140            ("tokenAddress", "a"),
141            ("name", "n"),
142            ("symbol", "s"),
143            ("createdAtMs", "cts"),
144        ]),
145    );
146
147    // Token supply subscription fields
148    mappings.insert(
149        "subscribe_token_supply",
150        HashMap::from([
151            ("tokenAddress", "a"),
152            ("supply", "s"),
153            ("timestamp", "ts"),
154        ]),
155    );
156
157    // DEX pool balance subscription fields
158    mappings.insert(
159        "subscribe_dex_pool_balance",
160        HashMap::from([
161            ("poolAddress", "a"),
162            ("tokenAAddress", "taa"),
163            ("tokenALiquidityInUsd", "taliu"),
164            ("tokenBAddress", "tba"),
165            ("tokenBLiquidityInUsd", "tbliu"),
166        ]),
167    );
168
169    // Token liquidity subscription fields
170    mappings.insert(
171        "subscribe_token_liquidity",
172        HashMap::from([
173            ("tokenAddress", "a"),
174            ("metricType", "t"),
175            ("value", "v"),
176            ("timestamp", "ts"),
177        ]),
178    );
179
180    // New token metadata subscription fields
181    mappings.insert(
182        "subscribe_new_tokens_metadata",
183        HashMap::from([
184            ("tokenAddress", "a"),
185            ("name", "n"),
186            ("symbol", "s"),
187            ("imageUrl", "iu"),
188            ("description", "de"),
189            ("socialMedia", "sm"),
190            ("createdAtMs", "cts"),
191        ]),
192    );
193
194    // Token trade subscription fields
195    mappings.insert(
196        "subscribe_token_trades",
197        HashMap::from([
198            ("tokenAddress", "a"),
199            ("timestamp", "t"),
200            ("kind", "k"),
201            ("buyAmount", "ba"),
202            ("buyAmountInUsd", "baiu"),
203            ("buyTokenAddress", "btma"),
204            ("buyTokenName", "btn"),
205            ("buyTokenSymbol", "bts"),
206            ("buyWalletAddress", "bwa"),
207            ("sellAmount", "sa"),
208            ("sellAmountInUsd", "saiu"),
209            ("sellTokenAddress", "stma"),
210            ("sellTokenName", "stn"),
211            ("sellTokenSymbol", "sts"),
212            ("sellWalletAddress", "swa"),
213            ("txHash", "h"),
214        ]),
215    );
216
217    // Wallet token PnL subscription fields
218    mappings.insert(
219        "subscribe_wallet_pnl",
220        HashMap::from([
221            ("walletAddress", "a"),
222            ("tokenAddress", "ta"),
223            ("tokenPriceInUsd", "tpiu"),
224            ("timestamp", "t"),
225            ("opentime", "ot"),
226            ("lasttime", "lt"),
227            ("closetime", "ct"),
228            ("buyAmount", "ba"),
229            ("buyAmountInUsd", "baiu"),
230            ("buyCount", "bs"),
231            ("buyCount30d", "bs30d"),
232            ("buyCount7d", "bs7d"),
233            ("sellAmount", "sa"),
234            ("sellAmountInUsd", "saiu"),
235            ("sellCount", "ss"),
236            ("sellCount30d", "ss30d"),
237            ("sellCount7d", "ss7d"),
238            ("heldDurationTimestamp", "hdts"),
239            ("averageBuyPriceInUsd", "abpiu"),
240            ("averageSellPriceInUsd", "aspiu"),
241            ("unrealizedProfitInUsd", "upiu"),
242            ("unrealizedProfitRatio", "upr"),
243            ("realizedProfitInUsd", "rpiu"),
244            ("realizedProfitRatio", "rpr"),
245            ("totalRealizedProfitInUsd", "trpiu"),
246            ("totalRealizedProfitRatio", "trr"),
247        ]),
248    );
249
250    // Wallet trade subscription fields
251    mappings.insert(
252        "subscribe_wallet_trade",
253        HashMap::from([
254            ("tokenAddress", "a"),
255            ("timestamp", "t"),
256            ("kind", "k"),
257            ("buyAmount", "ba"),
258            ("buyAmountInUsd", "baiu"),
259            ("buyTokenAddress", "btma"),
260            ("buyTokenName", "btn"),
261            ("buyTokenSymbol", "bts"),
262            ("buyWalletAddress", "bwa"),
263            ("sellAmount", "sa"),
264            ("sellAmountInUsd", "saiu"),
265            ("sellTokenAddress", "stma"),
266            ("sellTokenName", "stn"),
267            ("sellTokenSymbol", "sts"),
268            ("sellWalletAddress", "swa"),
269            ("txHash", "h"),
270        ]),
271    );
272
273    // Token max liquidity subscription fields
274    mappings.insert(
275        "subscribe_token_max_liquidity",
276        HashMap::from([
277            ("tokenAddress", "a"),
278            ("poolAddress", "p"),
279            ("liquidityInUsd", "liu"),
280            ("liquidityInNative", "lin"),
281            ("timestamp", "ts"),
282        ]),
283    );
284
285    // Token total liquidity subscription fields
286    mappings.insert(
287        "subscribe_token_total_liquidity",
288        HashMap::from([
289            ("tokenAddress", "a"),
290            ("liquidityInUsd", "liu"),
291            ("liquidityInNative", "lin"),
292            ("poolCount", "pc"),
293            ("timestamp", "ts"),
294        ]),
295    );
296
297    mappings
298});
299
300/// Get field mappings for a specific subscription method
301pub fn get_field_mappings(method_name: &str) -> Option<&FieldMapping> {
302    CEL_FIELD_MAPPINGS.get(method_name)
303}
304
305/// Replace long field names with short field names in filter expressions
306/// Automatically adds meta. prefix if not present
307pub fn replace_filter_fields(filter: &str, method_name: &str) -> String {
308    if filter.is_empty() {
309        return filter.to_string();
310    }
311
312    let field_mappings = match get_field_mappings(method_name) {
313        Some(m) => m,
314        None => return filter.to_string(),
315    };
316
317    let mut result = filter.to_string();
318
319    // Replace all long field names with short field names
320    for (long_field, short_field) in field_mappings {
321        // Handle two cases: with and without meta. prefix
322        // Pattern 1: fieldName (without meta. prefix)
323        let pattern1 = format!(r"\b{}\b", regex::escape(long_field));
324        if let Ok(re) = regex::Regex::new(&pattern1) {
325            result = re
326                .replace_all(&result, format!("meta.{}", short_field))
327                .to_string();
328        }
329
330        // Pattern 2: meta.fieldName (with meta. prefix)
331        let pattern2 = format!(r"\bmeta\.{}\b", regex::escape(long_field));
332        if let Ok(re) = regex::Regex::new(&pattern2) {
333            result = re
334                .replace_all(&result, format!("meta.{}", short_field))
335                .to_string();
336        }
337    }
338
339    result
340}
341
342/// Get available field names for a specific subscription method
343pub fn get_available_fields(method_name: &str) -> Vec<&'static str> {
344    match get_field_mappings(method_name) {
345        Some(mappings) => mappings.keys().copied().collect(),
346        None => Vec::new(),
347    }
348}
349
350#[cfg(test)]
351mod tests {
352    use super::*;
353
354    #[test]
355    fn test_get_field_mappings() {
356        let mappings = get_field_mappings("subscribe_token_candles");
357        assert!(mappings.is_some());
358        let mappings = mappings.unwrap();
359        assert_eq!(mappings.get("open"), Some(&"o"));
360        assert_eq!(mappings.get("close"), Some(&"c"));
361    }
362
363    #[test]
364    fn test_replace_filter_fields() {
365        let filter = "open > 100 && close < 200";
366        let result = replace_filter_fields(filter, "subscribe_token_candles");
367        assert!(result.contains("meta.o"));
368        assert!(result.contains("meta.c"));
369    }
370
371    #[test]
372    fn test_get_available_fields() {
373        let fields = get_available_fields("subscribe_token_candles");
374        assert!(fields.contains(&"open"));
375        assert!(fields.contains(&"close"));
376        assert!(fields.contains(&"high"));
377        assert!(fields.contains(&"low"));
378    }
379}