aignt-jupiter-client 0.3.0

Jupiter API Client
Documentation
use crate::client::JupiterApiClient;
use httpmock::prelude::*;

#[tokio::test]
async fn test_new_client() {
    let client = JupiterApiClient::new("test-key".to_string());
    assert!(client.is_ok());
}

#[tokio::test]
async fn test_get_prices_v3_exact_sample() {
    let server = MockServer::start();
    let api_key = "test-key".to_string();

    let sample_response = r#"{
        "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v": {
            "createdAt": "2024-06-05T08:55:25.527Z",
            "liquidity": 4235561.009987704,
            "usdPrice": 0.9997073145154354,
            "blockId": 395374169,
            "decimals": 6,
            "priceChange24h": 0.0003915640587189741
        },
        "So11111111111111111111111111111111111111112": {
            "createdAt": "2024-06-05T08:55:25.527Z",
            "liquidity": 151608370.92457405,
            "usdPrice": 127.02993218743363,
            "blockId": 395374172,
            "decimals": 9,
            "priceChange24h": -2.231717089336497
        }
    }"#;

    let mock = server.mock(|when, then| {
        when.method(GET)
            .path("/price/v3")
            .header("x-api-key", "test-key")
            .query_param("ids", "So11111111111111111111111111111111111111112,EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v");
        then.status(200)
            .header("content-type", "application/json")
            .body(sample_response);
    });

    let mut client = JupiterApiClient::new(api_key).unwrap();
    client.base_url = server.base_url();

    let ids = vec![
        "So11111111111111111111111111111111111111112".to_string(),
        "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v".to_string(),
    ];
    let result = client.get_prices(&ids).await;

    mock.assert();
    assert!(result.is_ok());

    let prices = result.unwrap();
    assert_eq!(prices.len(), 2);

    let sol = prices.get("So11111111111111111111111111111111111111112").unwrap();
    assert!((sol.usd_price - 127.02993218743363).abs() < f64::EPSILON * 100.0);
    assert_eq!(sol.decimals, 9);
    assert_eq!(sol.created_at.as_deref(), Some("2024-06-05T08:55:25.527Z"));
    assert!((sol.liquidity.unwrap() - 151608370.92457405).abs() < f64::EPSILON * 100000.0);
    assert_eq!(sol.block_id, Some(395374172));
    assert!((sol.price_change_24h.unwrap() - (-2.231717089336497)).abs() < f64::EPSILON * 100.0);

    let usdc = prices.get("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v").unwrap();
    assert!((usdc.usd_price - 0.9997073145154354).abs() < f64::EPSILON);
    assert_eq!(usdc.decimals, 6);
    assert_eq!(usdc.created_at.as_deref(), Some("2024-06-05T08:55:25.527Z"));
    assert!((usdc.liquidity.unwrap() - 4235561.009987704).abs() < f64::EPSILON * 1000.0);
    assert_eq!(usdc.block_id, Some(395374169));
    assert!((usdc.price_change_24h.unwrap() - 0.0003915640587189741).abs() < f64::EPSILON);
}

#[tokio::test]
async fn test_get_prices_with_api_key() {
    let server = MockServer::start();
    let api_key = "test-api-key".to_string();

    let mock = server.mock(|when, then| {
        when.method(GET)
            .path("/price/v3")
            .header("x-api-key", "test-api-key");
        then.status(200)
            .header("content-type", "application/json")
            .body("{}");
    });

    let mut client = JupiterApiClient::new(api_key).unwrap();
    client.base_url = server.base_url();

    let _ = client.get_prices(&["token".to_string()]).await;

    mock.assert();
}

#[tokio::test]
async fn test_get_prices_v3_with_missing_usd_price() {
    let server = MockServer::start();
    let api_key = "test-key".to_string();

    let sample_response = r#"{
        "GPPEV4tZHesL1gztLTV31Zym6NhoQpUcrFZVjYpDpump": {
            "createdAt": "2026-02-05T14:45:49Z",
            "liquidity": 2711.8305892749604,
            "usdPrice": 0.0000025286445588138423,
            "blockId": 398243425,
            "decimals": 6
        },
        "So11111111111111111111111111111111111111112": {
            "createdAt": "2024-06-05T08:55:25.527Z",
            "liquidity": 624230374.7176507,
            "usdPrice": 76.66694161338336,
            "blockId": 402386707,
            "decimals": 9,
            "priceChange24h": -4.47394415683684
        },
        "SupEmNudsbx5yeKipe7ipAy2P24weAUER15o5uYtv7t": {
            "createdAt": "2025-07-21T07:20:44Z",
            "liquidity": 18.22394062640557,
            "blockId": 397779922,
            "decimals": 9
        },
        "pumpCmXqMfrsAkQ5r49WcJnRayYRqmXz6ae8H7H9Dfn": {
            "createdAt": "2025-07-10T13:10:28Z",
            "liquidity": 20474386.960526194,
            "usdPrice": 0.0017541202126736364,
            "blockId": 402386655,
            "decimals": 6,
            "priceChange24h": -9.104137129050581
        }
    }"#;

    let mock = server.mock(|when, then| {
        when.method(GET)
            .path("/price/v3")
            .header("x-api-key", "test-key");
        then.status(200)
            .header("content-type", "application/json")
            .body(sample_response);
    });

    let mut client = JupiterApiClient::new(api_key).unwrap();
    client.base_url = server.base_url();

    let ids = vec![
        "GPPEV4tZHesL1gztLTV31Zym6NhoQpUcrFZVjYpDpump".to_string(),
        "So11111111111111111111111111111111111111112".to_string(),
        "SupEmNudsbx5yeKipe7ipAy2P24weAUER15o5uYtv7t".to_string(),
        "pumpCmXqMfrsAkQ5r49WcJnRayYRqmXz6ae8H7H9Dfn".to_string(),
    ];
    let result = client.get_prices(&ids).await;

    mock.assert();
    assert!(result.is_ok());

    let prices = result.unwrap();
    assert_eq!(prices.len(), 4);

    // Token with usdPrice but no priceChange24h
    let gppev = prices.get("GPPEV4tZHesL1gztLTV31Zym6NhoQpUcrFZVjYpDpump").unwrap();
    assert!((gppev.usd_price - 0.0000025286445588138423).abs() < f64::EPSILON);
    assert_eq!(gppev.decimals, 6);
    assert_eq!(gppev.created_at.as_deref(), Some("2026-02-05T14:45:49Z"));
    assert!((gppev.liquidity.unwrap() - 2711.8305892749604).abs() < f64::EPSILON);
    assert_eq!(gppev.block_id, Some(398243425));
    assert!(gppev.price_change_24h.is_none());

    // Token with all fields present
    let sol = prices.get("So11111111111111111111111111111111111111112").unwrap();
    assert!((sol.usd_price - 76.66694161338336).abs() < f64::EPSILON * 100.0);
    assert_eq!(sol.decimals, 9);
    assert_eq!(sol.created_at.as_deref(), Some("2024-06-05T08:55:25.527Z"));
    assert!((sol.liquidity.unwrap() - 624230374.7176507).abs() < f64::EPSILON * 100000.0);
    assert_eq!(sol.block_id, Some(402386707));
    assert!((sol.price_change_24h.unwrap() - (-4.47394415683684)).abs() < f64::EPSILON * 100.0);

    // Token with no usdPrice and no priceChange24h
    let sup = prices.get("SupEmNudsbx5yeKipe7ipAy2P24weAUER15o5uYtv7t").unwrap();
    assert!((sup.usd_price - 0.0).abs() < f64::EPSILON);
    assert_eq!(sup.decimals, 9);
    assert_eq!(sup.created_at.as_deref(), Some("2025-07-21T07:20:44Z"));
    assert!((sup.liquidity.unwrap() - 18.22394062640557).abs() < f64::EPSILON);
    assert_eq!(sup.block_id, Some(397779922));
    assert!(sup.price_change_24h.is_none());

    // Token with usdPrice and priceChange24h
    let pump = prices.get("pumpCmXqMfrsAkQ5r49WcJnRayYRqmXz6ae8H7H9Dfn").unwrap();
    assert!((pump.usd_price - 0.0017541202126736364).abs() < f64::EPSILON);
    assert_eq!(pump.decimals, 6);
    assert_eq!(pump.created_at.as_deref(), Some("2025-07-10T13:10:28Z"));
    assert!((pump.liquidity.unwrap() - 20474386.960526194).abs() < 1e-3);
    assert_eq!(pump.block_id, Some(402386655));
    assert!((pump.price_change_24h.unwrap() - (-9.104137129050581)).abs() < f64::EPSILON * 100.0);
}