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([("tokenAddress", "a"), ("supply", "s"), ("timestamp", "ts")]),
151    );
152
153    // DEX pool balance subscription fields
154    mappings.insert(
155        "subscribe_dex_pool_balance",
156        HashMap::from([
157            ("poolAddress", "a"),
158            ("tokenAAddress", "taa"),
159            ("tokenALiquidityInUsd", "taliu"),
160            ("tokenBAddress", "tba"),
161            ("tokenBLiquidityInUsd", "tbliu"),
162        ]),
163    );
164
165    // Token liquidity subscription fields
166    mappings.insert(
167        "subscribe_token_liquidity",
168        HashMap::from([
169            ("tokenAddress", "a"),
170            ("metricType", "t"),
171            ("value", "v"),
172            ("timestamp", "ts"),
173        ]),
174    );
175
176    // New token metadata subscription fields
177    mappings.insert(
178        "subscribe_new_tokens_metadata",
179        HashMap::from([
180            ("tokenAddress", "a"),
181            ("name", "n"),
182            ("symbol", "s"),
183            ("imageUrl", "iu"),
184            ("description", "de"),
185            ("socialMedia", "sm"),
186            ("createdAtMs", "cts"),
187        ]),
188    );
189
190    // Token trade subscription fields
191    mappings.insert(
192        "subscribe_token_trades",
193        HashMap::from([
194            ("tokenAddress", "a"),
195            ("timestamp", "t"),
196            ("kind", "k"),
197            ("buyAmount", "ba"),
198            ("buyAmountInUsd", "baiu"),
199            ("buyTokenAddress", "btma"),
200            ("buyTokenName", "btn"),
201            ("buyTokenSymbol", "bts"),
202            ("buyWalletAddress", "bwa"),
203            ("sellAmount", "sa"),
204            ("sellAmountInUsd", "saiu"),
205            ("sellTokenAddress", "stma"),
206            ("sellTokenName", "stn"),
207            ("sellTokenSymbol", "sts"),
208            ("sellWalletAddress", "swa"),
209            ("txHash", "h"),
210        ]),
211    );
212
213    // Wallet token PnL subscription fields
214    mappings.insert(
215        "subscribe_wallet_pnl",
216        HashMap::from([
217            ("walletAddress", "a"),
218            ("tokenAddress", "ta"),
219            ("tokenPriceInUsd", "tpiu"),
220            ("timestamp", "t"),
221            ("opentime", "ot"),
222            ("lasttime", "lt"),
223            ("closetime", "ct"),
224            ("buyAmount", "ba"),
225            ("buyAmountInUsd", "baiu"),
226            ("buyCount", "bs"),
227            ("buyCount30d", "bs30d"),
228            ("buyCount7d", "bs7d"),
229            ("sellAmount", "sa"),
230            ("sellAmountInUsd", "saiu"),
231            ("sellCount", "ss"),
232            ("sellCount30d", "ss30d"),
233            ("sellCount7d", "ss7d"),
234            ("heldDurationTimestamp", "hdts"),
235            ("averageBuyPriceInUsd", "abpiu"),
236            ("averageSellPriceInUsd", "aspiu"),
237            ("unrealizedProfitInUsd", "upiu"),
238            ("unrealizedProfitRatio", "upr"),
239            ("realizedProfitInUsd", "rpiu"),
240            ("realizedProfitRatio", "rpr"),
241            ("totalRealizedProfitInUsd", "trpiu"),
242            ("totalRealizedProfitRatio", "trr"),
243        ]),
244    );
245
246    // Wallet trade subscription fields
247    mappings.insert(
248        "subscribe_wallet_trade",
249        HashMap::from([
250            ("tokenAddress", "a"),
251            ("timestamp", "t"),
252            ("kind", "k"),
253            ("buyAmount", "ba"),
254            ("buyAmountInUsd", "baiu"),
255            ("buyTokenAddress", "btma"),
256            ("buyTokenName", "btn"),
257            ("buyTokenSymbol", "bts"),
258            ("buyWalletAddress", "bwa"),
259            ("sellAmount", "sa"),
260            ("sellAmountInUsd", "saiu"),
261            ("sellTokenAddress", "stma"),
262            ("sellTokenName", "stn"),
263            ("sellTokenSymbol", "sts"),
264            ("sellWalletAddress", "swa"),
265            ("txHash", "h"),
266        ]),
267    );
268
269    // Token max liquidity subscription fields
270    mappings.insert(
271        "subscribe_token_max_liquidity",
272        HashMap::from([
273            ("tokenAddress", "a"),
274            ("poolAddress", "p"),
275            ("liquidityInUsd", "liu"),
276            ("liquidityInNative", "lin"),
277            ("timestamp", "ts"),
278        ]),
279    );
280
281    // Token total liquidity subscription fields
282    mappings.insert(
283        "subscribe_token_total_liquidity",
284        HashMap::from([
285            ("tokenAddress", "a"),
286            ("liquidityInUsd", "liu"),
287            ("liquidityInNative", "lin"),
288            ("poolCount", "pc"),
289            ("timestamp", "ts"),
290        ]),
291    );
292
293    mappings
294});
295
296/// Get field mappings for a specific subscription method
297pub fn get_field_mappings(method_name: &str) -> Option<&FieldMapping> {
298    CEL_FIELD_MAPPINGS.get(method_name)
299}
300
301/// Replace long field names with short field names in filter expressions
302/// Automatically adds meta. prefix if not present
303pub fn replace_filter_fields(filter: &str, method_name: &str) -> String {
304    if filter.is_empty() {
305        return filter.to_string();
306    }
307
308    let field_mappings = match get_field_mappings(method_name) {
309        Some(m) => m,
310        None => return filter.to_string(),
311    };
312
313    let mut result = filter.to_string();
314
315    // Replace all long field names with short field names
316    for (long_field, short_field) in field_mappings {
317        // Handle two cases: with and without meta. prefix
318        // Pattern 1: fieldName (without meta. prefix)
319        let pattern1 = format!(r"\b{}\b", regex::escape(long_field));
320        if let Ok(re) = regex::Regex::new(&pattern1) {
321            result = re
322                .replace_all(&result, format!("meta.{}", short_field))
323                .to_string();
324        }
325
326        // Pattern 2: meta.fieldName (with meta. prefix)
327        let pattern2 = format!(r"\bmeta\.{}\b", regex::escape(long_field));
328        if let Ok(re) = regex::Regex::new(&pattern2) {
329            result = re
330                .replace_all(&result, format!("meta.{}", short_field))
331                .to_string();
332        }
333    }
334
335    result
336}
337
338/// Get available field names for a specific subscription method
339pub fn get_available_fields(method_name: &str) -> Vec<&'static str> {
340    match get_field_mappings(method_name) {
341        Some(mappings) => mappings.keys().copied().collect(),
342        None => Vec::new(),
343    }
344}
345
346#[cfg(test)]
347mod tests {
348    use super::*;
349
350    #[test]
351    fn test_get_field_mappings() {
352        let mappings = get_field_mappings("subscribe_token_candles");
353        assert!(mappings.is_some());
354        let mappings = mappings.unwrap();
355        assert_eq!(mappings.get("open"), Some(&"o"));
356        assert_eq!(mappings.get("close"), Some(&"c"));
357    }
358
359    #[test]
360    fn test_replace_filter_fields() {
361        let filter = "open > 100 && close < 200";
362        let result = replace_filter_fields(filter, "subscribe_token_candles");
363        assert!(result.contains("meta.o"));
364        assert!(result.contains("meta.c"));
365    }
366
367    #[test]
368    fn test_get_available_fields() {
369        let fields = get_available_fields("subscribe_token_candles");
370        assert!(fields.contains(&"open"));
371        assert!(fields.contains(&"close"));
372        assert!(fields.contains(&"high"));
373        assert!(fields.contains(&"low"));
374    }
375}