1use std::collections::HashMap;
7
8use once_cell::sync::Lazy;
9
10pub type FieldMapping = HashMap<&'static str, &'static str>;
12
13pub type MethodFieldMappings = HashMap<&'static str, FieldMapping>;
15
16pub static CEL_FIELD_MAPPINGS: Lazy<MethodFieldMappings> = Lazy::new(|| {
18 let mut mappings = HashMap::new();
19
20 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 let prediction_activity_fields = HashMap::from([
33 ("activityId", "a.id"),
34 ("amount", "a.amt"),
35 ("assetIds", "a.as"),
36 ("blockNumber", "a.bn"),
37 ("conditionId", "a.cid"),
38 ("eventSlug", "a.es"),
39 ("logIndex", "a.li"),
40 ("marketIcon", "a.mi"),
41 ("marketId", "a.mid"),
42 ("marketQuestion", "a.mq"),
43 ("outcome", "a.oc"),
44 ("outcomes", "a.ocs"),
45 ("price", "a.p"),
46 ("quantity", "a.q"),
47 ("seqIndex", "a.seq"),
48 ("source", "a.src"),
49 ("taker", "a.tk"),
50 ("takerAge", "a.ta"),
51 ("takerImage", "a.ti"),
52 ("takerName", "a.tn"),
53 ("takerOrderHash", "a.toh"),
54 ("takerPseudonym", "a.tp"),
55 ("takerTags", "a.tt"),
56 ("timestamp", "a.ts"),
57 ("tokenId", "a.tid"),
58 ("txHash", "a.tx"),
59 ("type", "a.ty"),
60 ("wallet", "a.w"),
61 ("participantRole", "a.rl"),
62 ("rawType", "a.rt"),
63 ("rawTokenId", "a.rtid"),
64 ("rawOutcome", "a.ro"),
65 ("rawPrice", "a.rp"),
66 ("pricePerShare", "a.pps"),
67 ("amountInUsd", "a.aiu"),
68 ("collateralAmount", "a.ca"),
69 ("collateralAmountInUsd", "a.cau"),
70 ("collateralTokenSymbol", "a.cs"),
71 ("collateralTokenAddress", "a.cad"),
72 ("counterparty", "a.cp"),
73 ("feeAmount", "a.fa"),
74 ("feeAmountInUsd", "a.fau"),
75 ("feePayer", "a.fp"),
76 ("feeRecipient", "a.fr"),
77 ("economicOutcome", "a.eo"),
78 ("economicAmount", "a.ea"),
79 ("economicAmountInUsd", "a.eau"),
80 ("rawAmount", "a.ra"),
81 ("economicTokenId", "a.etid"),
82 ("economicPrice", "a.ep"),
83 ("feeTokenSymbol", "a.fs"),
84 ("feeTokenAddress", "a.fad"),
85 ]);
86 mappings.insert(
87 "subscribe_prediction_event_activities",
88 prediction_activity_fields.clone(),
89 );
90 mappings.insert(
91 "subscribe_prediction_token_activities",
92 prediction_activity_fields,
93 );
94
95 mappings.insert(
97 "subscribe_token_candles",
98 HashMap::from([
99 ("open", "o"),
100 ("close", "c"),
101 ("high", "h"),
102 ("low", "l"),
103 ("volume", "v"),
104 ("resolution", "r"),
105 ("time", "t"),
106 ("number", "n"),
107 ]),
108 );
109
110 mappings.insert(
112 "subscribe_token_stats",
113 HashMap::from([
114 ("address", "a"),
115 ("timestamp", "t"),
116 ("buys1m", "b1m"),
117 ("sells1m", "s1m"),
118 ("buyers1m", "be1m"),
119 ("sellers1m", "se1m"),
120 ("buyVolumeInUsd1m", "bviu1m"),
121 ("sellVolumeInUsd1m", "sviu1m"),
122 ("price1m", "p1m"),
123 ("openInUsd1m", "oiu1m"),
124 ("closeInUsd1m", "ciu1m"),
125 ("buys5m", "b5m"),
126 ("sells5m", "s5m"),
127 ("buyers5m", "be5m"),
128 ("sellers5m", "se5m"),
129 ("buyVolumeInUsd5m", "bviu5m"),
130 ("sellVolumeInUsd5m", "sviu5m"),
131 ("price5m", "p5m"),
132 ("openInUsd5m", "oiu5m"),
133 ("closeInUsd5m", "ciu5m"),
134 ("buys15m", "b15m"),
135 ("sells15m", "s15m"),
136 ("buyers15m", "be15m"),
137 ("sellers15m", "se15m"),
138 ("buyVolumeInUsd15m", "bviu15m"),
139 ("sellVolumeInUsd15m", "sviu15m"),
140 ("price15m", "p15m"),
141 ("openInUsd15m", "oiu15m"),
142 ("closeInUsd15m", "ciu15m"),
143 ("buys30m", "b30m"),
144 ("sells30m", "s30m"),
145 ("buyers30m", "be30m"),
146 ("sellers30m", "se30m"),
147 ("buyVolumeInUsd30m", "bviu30m"),
148 ("sellVolumeInUsd30m", "sviu30m"),
149 ("price30m", "p30m"),
150 ("openInUsd30m", "oiu30m"),
151 ("closeInUsd30m", "ciu30m"),
152 ("buys1h", "b1h"),
153 ("sells1h", "s1h"),
154 ("buyers1h", "be1h"),
155 ("sellers1h", "se1h"),
156 ("buyVolumeInUsd1h", "bviu1h"),
157 ("sellVolumeInUsd1h", "sviu1h"),
158 ("price1h", "p1h"),
159 ("openInUsd1h", "oiu1h"),
160 ("closeInUsd1h", "ciu1h"),
161 ("buys4h", "b4h"),
162 ("sells4h", "s4h"),
163 ("buyers4h", "be4h"),
164 ("sellers4h", "se4h"),
165 ("buyVolumeInUsd4h", "bviu4h"),
166 ("sellVolumeInUsd4h", "sviu4h"),
167 ("price4h", "p4h"),
168 ("openInUsd4h", "oiu4h"),
169 ("closeInUsd4h", "ciu4h"),
170 ("buys24h", "b24h"),
171 ("sells24h", "s24h"),
172 ("buyers24h", "be24h"),
173 ("sellers24h", "se24h"),
174 ("buyVolumeInUsd24h", "bviu24h"),
175 ("sellVolumeInUsd24h", "sviu24h"),
176 ("price24h", "p24h"),
177 ("price", "p"),
178 ("openInUsd24h", "oiu24h"),
179 ("closeInUsd24h", "ciu24h"),
180 ]),
181 );
182
183 mappings.insert(
185 "subscribe_token_holders",
186 HashMap::from([
187 ("tokenAddress", "a"),
188 ("holders", "h"),
189 ("top100Amount", "t100a"),
190 ("top10Amount", "t10a"),
191 ("top100Holders", "t100h"),
192 ("top10Holders", "t10h"),
193 ("top100Ratio", "t100r"),
194 ("top10Ratio", "t10r"),
195 ("timestamp", "ts"),
196 ]),
197 );
198
199 mappings.insert(
201 "subscribe_new_token",
202 HashMap::from([
203 ("tokenAddress", "a"),
204 ("name", "n"),
205 ("symbol", "s"),
206 ("decimals", "dec"),
207 ("imageUrl", "iu"),
208 ("description", "de"),
209 ("createdAtMs", "cts"),
210 ("coingeckoCoinId", "cgi"),
211 ("socialMedia.twitter", "sm.tw"),
212 ("socialMedia.telegram", "sm.tg"),
213 ("socialMedia.website", "sm.w"),
214 ("socialMedia.tiktok", "sm.tt"),
215 ("socialMedia.discord", "sm.dc"),
216 ("socialMedia.facebook", "sm.fb"),
217 ("socialMedia.github", "sm.gh"),
218 ("socialMedia.instagram", "sm.ig"),
219 ("socialMedia.linkedin", "sm.li"),
220 ("socialMedia.medium", "sm.md"),
221 ("socialMedia.reddit", "sm.rd"),
222 ("socialMedia.youtube", "sm.yt"),
223 ("socialMedia.bitbucket", "sm.bb"),
224 ("launchFrom.programAddress", "lf.pa"),
225 ("launchFrom.protocolFamily", "lf.pf"),
226 ("launchFrom.protocolName", "lf.pn"),
227 ("migratedTo.programAddress", "mt.pa"),
228 ("migratedTo.protocolFamily", "mt.pf"),
229 ("migratedTo.protocolName", "mt.pn"),
230 ]),
231 );
232
233 mappings.insert(
235 "subscribe_token_supply",
236 HashMap::from([("tokenAddress", "a"), ("supply", "s"), ("timestamp", "ts")]),
237 );
238
239 mappings.insert(
241 "subscribe_dex_pool_balance",
242 HashMap::from([
243 ("poolAddress", "a"),
244 ("tokenAAddress", "taa"),
245 ("tokenALiquidityInUsd", "taliu"),
246 ("tokenBAddress", "tba"),
247 ("tokenBLiquidityInUsd", "tbliu"),
248 ]),
249 );
250
251 mappings.insert(
253 "subscribe_token_liquidity",
254 HashMap::from([
255 ("tokenAddress", "a"),
256 ("metricType", "t"),
257 ("value", "v"),
258 ("timestamp", "ts"),
259 ]),
260 );
261
262 mappings.insert(
264 "subscribe_new_tokens_metadata",
265 HashMap::from([
266 ("tokenAddress", "a"),
267 ("name", "n"),
268 ("decimals", "dec"),
269 ("symbol", "s"),
270 ("imageUrl", "iu"),
271 ("description", "de"),
272 ("createdAtMs", "cts"),
273 ("coingeckoCoinId", "cgi"),
274 ("socialMedia.twitter", "sm.tw"),
275 ("socialMedia.telegram", "sm.tg"),
276 ("socialMedia.website", "sm.w"),
277 ("socialMedia.tiktok", "sm.tt"),
278 ("socialMedia.discord", "sm.dc"),
279 ("socialMedia.facebook", "sm.fb"),
280 ("socialMedia.github", "sm.gh"),
281 ("socialMedia.instagram", "sm.ig"),
282 ("socialMedia.linkedin", "sm.li"),
283 ("socialMedia.medium", "sm.md"),
284 ("socialMedia.reddit", "sm.rd"),
285 ("socialMedia.youtube", "sm.yt"),
286 ("socialMedia.bitbucket", "sm.bb"),
287 ("launchFrom.programAddress", "lf.pa"),
288 ("launchFrom.protocolFamily", "lf.pf"),
289 ("launchFrom.protocolName", "lf.pn"),
290 ("migratedTo.programAddress", "mt.pa"),
291 ("migratedTo.protocolFamily", "mt.pf"),
292 ("migratedTo.protocolName", "mt.pn"),
293 ]),
294 );
295
296 mappings.insert(
298 "subscribe_new_tokens",
299 HashMap::from([
300 ("tokenAddress", "a"),
301 ("name", "n"),
302 ("decimals", "dec"),
303 ("symbol", "s"),
304 ("imageUrl", "iu"),
305 ("description", "de"),
306 ("createdAtMs", "cts"),
307 ("coingeckoCoinId", "cgi"),
308 ("socialMedia.twitter", "sm.tw"),
309 ("socialMedia.telegram", "sm.tg"),
310 ("socialMedia.website", "sm.w"),
311 ("socialMedia.tiktok", "sm.tt"),
312 ("socialMedia.discord", "sm.dc"),
313 ("socialMedia.facebook", "sm.fb"),
314 ("socialMedia.github", "sm.gh"),
315 ("socialMedia.instagram", "sm.ig"),
316 ("socialMedia.linkedin", "sm.li"),
317 ("socialMedia.medium", "sm.md"),
318 ("socialMedia.reddit", "sm.rd"),
319 ("socialMedia.youtube", "sm.yt"),
320 ("socialMedia.bitbucket", "sm.bb"),
321 ("launchFrom.programAddress", "lf.pa"),
322 ("launchFrom.protocolFamily", "lf.pf"),
323 ("launchFrom.protocolName", "lf.pn"),
324 ("migratedTo.programAddress", "mt.pa"),
325 ("migratedTo.protocolFamily", "mt.pf"),
326 ("migratedTo.protocolName", "mt.pn"),
327 ]),
328 );
329
330 mappings.insert(
332 "subscribe_token_trades",
333 HashMap::from([
334 ("tokenAddress", "a"),
335 ("timestamp", "t"),
336 ("kind", "k"),
337 ("buyAmount", "ba"),
338 ("buyAmountInUsd", "baiu"),
339 ("buyTokenAddress", "btma"),
340 ("buyTokenName", "btn"),
341 ("buyTokenSymbol", "bts"),
342 ("buyWalletAddress", "bwa"),
343 ("sellAmount", "sa"),
344 ("sellAmountInUsd", "saiu"),
345 ("sellTokenAddress", "stma"),
346 ("sellTokenName", "stn"),
347 ("sellTokenSymbol", "sts"),
348 ("sellWalletAddress", "swa"),
349 ("txHash", "h"),
350 ]),
351 );
352
353 mappings.insert(
355 "subscribe_wallet_pnl",
356 HashMap::from([
357 ("walletAddress", "a"),
358 ("tokenAddress", "ta"),
359 ("tokenPriceInUsd", "tpiu"),
360 ("timestamp", "t"),
361 ("opentime", "ot"),
362 ("lasttime", "lt"),
363 ("closetime", "ct"),
364 ("buyAmount", "ba"),
365 ("buyAmountInUsd", "baiu"),
366 ("buyCount", "bs"),
367 ("buyCount30d", "bs30d"),
368 ("buyCount7d", "bs7d"),
369 ("sellAmount", "sa"),
370 ("sellAmountInUsd", "saiu"),
371 ("sellCount", "ss"),
372 ("sellCount30d", "ss30d"),
373 ("sellCount7d", "ss7d"),
374 ("heldDurationTimestamp", "hdts"),
375 ("averageBuyPriceInUsd", "abpiu"),
376 ("averageSellPriceInUsd", "aspiu"),
377 ("unrealizedProfitInUsd", "upiu"),
378 ("unrealizedProfitRatio", "upr"),
379 ("realizedProfitInUsd", "rpiu"),
380 ("realizedProfitRatio", "rpr"),
381 ("totalRealizedProfitInUsd", "trpiu"),
382 ("totalRealizedProfitRatio", "trr"),
383 ]),
384 );
385
386 mappings.insert(
388 "subscribe_wallet_trade",
389 HashMap::from([
390 ("tokenAddress", "a"),
391 ("timestamp", "t"),
392 ("kind", "k"),
393 ("buyAmount", "ba"),
394 ("buyAmountInUsd", "baiu"),
395 ("buyTokenAddress", "btma"),
396 ("buyTokenName", "btn"),
397 ("buyTokenSymbol", "bts"),
398 ("buyWalletAddress", "bwa"),
399 ("sellAmount", "sa"),
400 ("sellAmountInUsd", "saiu"),
401 ("sellTokenAddress", "stma"),
402 ("sellTokenName", "stn"),
403 ("sellTokenSymbol", "sts"),
404 ("sellWalletAddress", "swa"),
405 ("txHash", "h"),
406 ]),
407 );
408
409 mappings.insert(
411 "subscribe_token_max_liquidity",
412 HashMap::from([
413 ("tokenAddress", "a"),
414 ("poolAddress", "p"),
415 ("liquidityInUsd", "liu"),
416 ("liquidityInNative", "lin"),
417 ("timestamp", "ts"),
418 ]),
419 );
420
421 mappings.insert(
423 "subscribe_token_total_liquidity",
424 HashMap::from([
425 ("tokenAddress", "a"),
426 ("liquidityInUsd", "liu"),
427 ("liquidityInNative", "lin"),
428 ("poolCount", "pc"),
429 ("timestamp", "ts"),
430 ]),
431 );
432
433 mappings
434});
435
436pub fn get_field_mappings(method_name: &str) -> Option<&FieldMapping> {
438 CEL_FIELD_MAPPINGS.get(method_name)
439}
440
441pub fn replace_filter_fields(filter: &str, method_name: &str) -> String {
445 if filter.is_empty() {
446 return filter.to_string();
447 }
448
449 let field_mappings = match get_field_mappings(method_name) {
450 Some(m) => m,
451 None => return filter.to_string(),
452 };
453
454 let mut result = filter.to_string();
455
456 let mut entries: Vec<_> = field_mappings.iter().collect();
458 entries.sort_by_key(|entry| std::cmp::Reverse(entry.0.len()));
459
460 for (long_field, short_field) in entries {
461 let pattern1 = format!(r"\bmeta\.{}\b", regex::escape(long_field));
464 if let Ok(re) = regex::Regex::new(&pattern1) {
465 result = re
466 .replace_all(&result, format!("meta.{}", short_field))
467 .to_string();
468 }
469
470 let pattern2 = format!(r"\b{}\b", regex::escape(long_field));
472 if let Ok(re) = regex::Regex::new(&pattern2) {
473 result = re
474 .replace_all(&result, format!("meta.{}", short_field))
475 .to_string();
476 }
477 }
478
479 result
480}
481
482pub fn get_available_fields(method_name: &str) -> Vec<&'static str> {
484 match get_field_mappings(method_name) {
485 Some(mappings) => mappings.keys().copied().collect(),
486 None => Vec::new(),
487 }
488}
489
490#[cfg(test)]
491mod tests {
492 use super::*;
493
494 #[test]
495 fn test_get_field_mappings() {
496 let mappings = get_field_mappings("subscribe_token_candles");
497 assert!(mappings.is_some());
498 let mappings = mappings.unwrap();
499 assert_eq!(mappings.get("open"), Some(&"o"));
500 assert_eq!(mappings.get("close"), Some(&"c"));
501 }
502
503 #[test]
504 fn test_replace_filter_fields() {
505 let filter = "open > 100 && close < 200";
506 let result = replace_filter_fields(filter, "subscribe_token_candles");
507 assert!(result.contains("meta.o"));
508 assert!(result.contains("meta.c"));
509 }
510
511 #[test]
512 fn test_get_available_fields() {
513 let fields = get_available_fields("subscribe_token_candles");
514 assert!(fields.contains(&"open"));
515 assert!(fields.contains(&"close"));
516 assert!(fields.contains(&"high"));
517 assert!(fields.contains(&"low"));
518 }
519
520 #[test]
521 fn test_prediction_activity_filter_fields_cover_full_payload() {
522 let mappings = get_field_mappings("subscribe_prediction_event_activities").unwrap();
523 assert_eq!(mappings.get("marketIcon"), Some(&"a.mi"));
524 assert_eq!(mappings.get("marketQuestion"), Some(&"a.mq"));
525 assert_eq!(mappings.get("source"), Some(&"a.src"));
526 assert_eq!(mappings.get("takerAge"), Some(&"a.ta"));
527 assert_eq!(mappings.get("takerImage"), Some(&"a.ti"));
528 assert_eq!(mappings.get("takerName"), Some(&"a.tn"));
529 assert_eq!(mappings.get("takerOrderHash"), Some(&"a.toh"));
530 assert_eq!(mappings.get("takerPseudonym"), Some(&"a.tp"));
531 assert_eq!(mappings.get("takerTags"), Some(&"a.tt"));
532 assert_eq!(mappings.get("wallet"), Some(&"a.w"));
533 assert_eq!(mappings.get("participantRole"), Some(&"a.rl"));
534 assert_eq!(mappings.get("amountInUsd"), Some(&"a.aiu"));
535 assert_eq!(mappings.get("pricePerShare"), Some(&"a.pps"));
536 assert_eq!(mappings.get("feeAmountInUsd"), Some(&"a.fau"));
537 assert_eq!(mappings.get("economicTokenId"), Some(&"a.etid"));
538
539 let filter = "marketIcon != '' && takerTags.contains('kol') && participantRole == 'maker' && amountInUsd != ''";
540 let result = replace_filter_fields(filter, "subscribe_prediction_event_activities");
541 assert!(result.contains("meta.a.mi"));
542 assert!(result.contains("meta.a.tt"));
543 assert!(result.contains("meta.a.rl"));
544 assert!(result.contains("meta.a.aiu"));
545 }
546}