@@ -12,6 +12,8 @@ use crate::{
1212const DEFAULT_BASE_URL : & str = "https://api.icpswap.com" ;
1313/// `ICPSwap` token info responses are small JSON objects; keep the cap tight for cycle costs.
1414const 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 ) ]
1719struct 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
3236fn 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