Skip to content

Commit b2f18ec

Browse files
feat(backend): Ignore stale ICPSwap token prices
1 parent 2c4b944 commit b2f18ec

File tree

1 file changed

+64
-3
lines changed
  • src/backend/src/exchange/providers/icpswap

1 file changed

+64
-3
lines changed

src/backend/src/exchange/providers/icpswap/mod.rs

Lines changed: 64 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ use crate::{
1212
const DEFAULT_BASE_URL: &str = "https://api.icpswap.com";
1313
/// `ICPSwap` token info responses are small JSON objects; keep the cap tight for cycle costs.
1414
const MAX_RESPONSE_BYTES: u64 = 8_192;
15+
/// Pools with TVL at or below this threshold are considered stale and their prices are discarded.
16+
const MIN_TVL_USD: f64 = 10.0;
1517

1618
#[derive(Debug, Deserialize)]
1719
struct IcpSwapEnvelope {
@@ -27,6 +29,8 @@ struct IcpSwapTokenBody {
2729
price: String,
2830
#[serde(rename = "priceChange24H")]
2931
price_change_24h: String,
32+
#[serde(rename = "tvlUSD")]
33+
tvl_usd: String,
3034
}
3135

3236
fn exchange_data_from_icpswap_envelope(
@@ -41,6 +45,10 @@ fn exchange_data_from_icpswap_envelope(
4145
if !price.is_finite() || price <= 0.0 {
4246
return None;
4347
}
48+
let tvl: f64 = token.tvl_usd.parse().ok()?;
49+
if !tvl.is_finite() || tvl <= MIN_TVL_USD {
50+
return None;
51+
}
4452
let price_24h_change_pct = token
4553
.price_change_24h
4654
.parse()
@@ -138,7 +146,7 @@ mod tests {
138146

139147
#[test]
140148
fn parse_icpswap_body_success() {
141-
let json = br#"{"code":0,"message":null,"data":{"tokenLedgerId":"ryjl3-tyaaa-aaaaa-aaaba-cai","tokenName":"x","tokenSymbol":"X","price":"1.25","priceChange24H":"-2.5","tvlUSD":"0","tvlUSDChange24H":"0","txCount24H":"0","volumeUSD24H":"0","volumeUSD7D":"0","totalVolumeUSD":"0","priceLow24H":"0","priceHigh24H":"0","priceLow7D":"0","priceHigh7D":"0","priceLow30D":"0","priceHigh30D":"0"}}"#;
149+
let json = br#"{"code":0,"message":null,"data":{"tokenLedgerId":"ryjl3-tyaaa-aaaaa-aaaba-cai","tokenName":"x","tokenSymbol":"X","price":"1.25","priceChange24H":"-2.5","tvlUSD":"50000","tvlUSDChange24H":"0","txCount24H":"0","volumeUSD24H":"0","volumeUSD7D":"0","totalVolumeUSD":"0","priceLow24H":"0","priceHigh24H":"0","priceLow7D":"0","priceHigh7D":"0","priceLow30D":"0","priceHigh30D":"0"}}"#;
142150
let parsed: IcpSwapEnvelope = serde_json::from_slice(json).unwrap();
143151
let d = exchange_data_from_icpswap_envelope(parsed, 99).unwrap();
144152
assert_eq!(d.timestamp_ns, 99);
@@ -156,7 +164,7 @@ mod tests {
156164
#[test]
157165
fn parse_icpswap_body_filters_non_finite_price_change() {
158166
let json =
159-
br#"{"code":0,"data":{"tokenLedgerId":"x","price":"1.0","priceChange24H":"NaN"}}"#;
167+
br#"{"code":0,"data":{"tokenLedgerId":"x","price":"1.0","priceChange24H":"NaN","tvlUSD":"100"}}"#;
160168
let parsed: IcpSwapEnvelope = serde_json::from_slice(json).unwrap();
161169
let d = exchange_data_from_icpswap_envelope(parsed, 0).unwrap();
162170
assert_eq!(d.price, Some(1.0));
@@ -165,8 +173,61 @@ mod tests {
165173

166174
#[test]
167175
fn parse_icpswap_body_rejects_bad_price() {
168-
let json = br#"{"code":0,"data":{"tokenLedgerId":"x","price":"0","priceChange24H":"0"}}"#;
176+
let json = br#"{"code":0,"data":{"tokenLedgerId":"x","price":"0","priceChange24H":"0","tvlUSD":"100"}}"#;
177+
let parsed: IcpSwapEnvelope = serde_json::from_slice(json).unwrap();
178+
assert!(exchange_data_from_icpswap_envelope(parsed, 0).is_none());
179+
}
180+
181+
#[test]
182+
fn parse_icpswap_body_rejects_stale_pool() {
183+
let json = br#"{"code":0,"data":{"tokenLedgerId":"x","price":"1.25","priceChange24H":"-2.5","tvlUSD":"1.78"}}"#;
184+
let parsed: IcpSwapEnvelope = serde_json::from_slice(json).unwrap();
185+
assert!(exchange_data_from_icpswap_envelope(parsed, 0).is_none());
186+
}
187+
188+
#[test]
189+
fn parse_icpswap_body_rejects_zero_tvl() {
190+
let json = br#"{"code":0,"data":{"tokenLedgerId":"x","price":"1.25","priceChange24H":"-2.5","tvlUSD":"0"}}"#;
191+
let parsed: IcpSwapEnvelope = serde_json::from_slice(json).unwrap();
192+
assert!(exchange_data_from_icpswap_envelope(parsed, 0).is_none());
193+
}
194+
195+
#[test]
196+
fn parse_icpswap_body_rejects_tvl_at_threshold() {
197+
let json = br#"{"code":0,"data":{"tokenLedgerId":"x","price":"1.25","priceChange24H":"-2.5","tvlUSD":"10"}}"#;
198+
let parsed: IcpSwapEnvelope = serde_json::from_slice(json).unwrap();
199+
assert!(exchange_data_from_icpswap_envelope(parsed, 0).is_none());
200+
}
201+
202+
#[test]
203+
fn parse_icpswap_body_accepts_tvl_above_threshold() {
204+
let json = br#"{"code":0,"data":{"tokenLedgerId":"x","price":"1.25","priceChange24H":"-2.5","tvlUSD":"10.01"}}"#;
205+
let parsed: IcpSwapEnvelope = serde_json::from_slice(json).unwrap();
206+
let d = exchange_data_from_icpswap_envelope(parsed, 0).unwrap();
207+
assert_eq!(d.price, Some(1.25));
208+
}
209+
210+
#[test]
211+
fn parse_icpswap_body_rejects_nan_tvl() {
212+
let json = br#"{"code":0,"data":{"tokenLedgerId":"x","price":"1.25","priceChange24H":"-2.5","tvlUSD":"NaN"}}"#;
169213
let parsed: IcpSwapEnvelope = serde_json::from_slice(json).unwrap();
170214
assert!(exchange_data_from_icpswap_envelope(parsed, 0).is_none());
171215
}
216+
217+
#[test]
218+
fn parse_icpswap_body_rejects_negative_tvl() {
219+
let json = br#"{"code":0,"data":{"tokenLedgerId":"x","price":"1.25","priceChange24H":"-2.5","tvlUSD":"-500"}}"#;
220+
let parsed: IcpSwapEnvelope = serde_json::from_slice(json).unwrap();
221+
assert!(exchange_data_from_icpswap_envelope(parsed, 0).is_none());
222+
}
223+
224+
#[test]
225+
fn parse_icpswap_body_rejects_missing_tvl() {
226+
let json = br#"{"code":0,"data":{"tokenLedgerId":"x","price":"1.25","priceChange24H":"-2.5"}}"#;
227+
let parsed: Result<IcpSwapEnvelope, _> = serde_json::from_slice(json);
228+
assert!(parsed
229+
.ok()
230+
.and_then(|e| exchange_data_from_icpswap_envelope(e, 0))
231+
.is_none());
232+
}
172233
}

0 commit comments

Comments
 (0)