polymarket-client-sdk 0.3.0

Polymarket CLOB (Central Limit Order Book) API client SDK
Documentation
#![cfg(feature = "bridge")]
#![allow(clippy::unwrap_used, reason = "tests can panic on unwrap")]

mod deposit {
    use httpmock::{Method::POST, MockServer};
    use polymarket_client_sdk::bridge::{
        Client,
        types::{DepositAddresses, DepositRequest, DepositResponse},
    };
    use reqwest::StatusCode;
    use serde_json::json;

    #[tokio::test]
    async fn deposit_should_succeed() -> anyhow::Result<()> {
        let server = MockServer::start();
        let client = Client::new(&server.base_url())?;

        let mock = server.mock(|when, then| {
            when.method(POST)
                .path("/deposit")
                .header("Content-Type", "application/json")
                .json_body(json!({
                    "address": "0x56687bf447db6ffa42ffe2204a05edaa20f55839"
                }));
            then.status(StatusCode::CREATED).json_body(json!({
                "address": {
                    "evm": "0x23566f8b2E82aDfCf01846E54899d110e97AC053",
                    "svm": "CrvTBvzryYxBHbWu2TiQpcqD5M7Le7iBKzVmEj3f36Jb",
                    "btc": "bc1q8eau83qffxcj8ht4hsjdza3lha9r3egfqysj3g"
                },
                "note": "Only certain chains and tokens are supported. See /supported-assets for details."
            }));
        });

        let request = DepositRequest::builder()
            .address(alloy::primitives::address!(
                "56687bf447db6ffa42ffe2204a05edaa20f55839"
            ))
            .build();

        let response = client.deposit(&request).await?;

        let expected = DepositResponse::builder()
            .address(
                DepositAddresses::builder()
                    .evm("0x23566f8b2E82aDfCf01846E54899d110e97AC053")
                    .svm("CrvTBvzryYxBHbWu2TiQpcqD5M7Le7iBKzVmEj3f36Jb")
                    .btc("bc1q8eau83qffxcj8ht4hsjdza3lha9r3egfqysj3g")
                    .build(),
            )
            .note(
                "Only certain chains and tokens are supported. See /supported-assets for details."
                    .to_owned(),
            )
            .build();

        assert_eq!(response, expected);
        mock.assert();

        Ok(())
    }

    #[tokio::test]
    async fn deposit_without_note_should_succeed() -> anyhow::Result<()> {
        let server = MockServer::start();
        let client = Client::new(&server.base_url())?;

        let mock = server.mock(|when, then| {
            when.method(POST).path("/deposit");
            then.status(StatusCode::CREATED).json_body(json!({
                "address": {
                    "evm": "0x23566f8b2E82aDfCf01846E54899d110e97AC053",
                    "svm": "CrvTBvzryYxBHbWu2TiQpcqD5M7Le7iBKzVmEj3f36Jb",
                    "btc": "bc1q8eau83qffxcj8ht4hsjdza3lha9r3egfqysj3g"
                }
            }));
        });

        let request = DepositRequest::builder()
            .address(alloy::primitives::address!(
                "56687bf447db6ffa42ffe2204a05edaa20f55839"
            ))
            .build();

        let response = client.deposit(&request).await?;

        assert!(response.note.is_none());
        assert_eq!(
            response.address.evm,
            "0x23566f8b2E82aDfCf01846E54899d110e97AC053"
        );
        mock.assert();

        Ok(())
    }

    #[tokio::test]
    async fn deposit_bad_request_should_fail() -> anyhow::Result<()> {
        let server = MockServer::start();
        let client = Client::new(&server.base_url())?;

        let mock = server.mock(|when, then| {
            when.method(POST).path("/deposit");
            then.status(StatusCode::BAD_REQUEST)
                .json_body(json!({"error": "Invalid address"}));
        });

        let request = DepositRequest::builder()
            .address(alloy::primitives::address!(
                "0000000000000000000000000000000000000000"
            ))
            .build();

        let result = client.deposit(&request).await;

        result.unwrap_err();
        mock.assert();

        Ok(())
    }
}

mod supported_assets {
    use httpmock::{Method::GET, MockServer};
    use polymarket_client_sdk::bridge::{
        Client,
        types::{SupportedAsset, SupportedAssetsResponse, Token},
    };
    use reqwest::StatusCode;
    use rust_decimal_macros::dec;
    use serde_json::json;

    #[tokio::test]
    async fn supported_assets_should_succeed() -> anyhow::Result<()> {
        let server = MockServer::start();
        let client = Client::new(&server.base_url())?;

        let mock = server.mock(|when, then| {
            when.method(GET).path("/supported-assets");
            then.status(StatusCode::OK).json_body(json!({
                "supportedAssets": [
                    {
                        "chainId": "1",
                        "chainName": "Ethereum",
                        "token": {
                            "name": "USD Coin",
                            "symbol": "USDC",
                            "address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
                            "decimals": 6
                        },
                        "minCheckoutUsd": 45.0
                    },
                    {
                        "chainId": "137",
                        "chainName": "Polygon",
                        "token": {
                            "name": "Bridged USDC",
                            "symbol": "USDC.e",
                            "address": "0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174",
                            "decimals": 6
                        },
                        "minCheckoutUsd": 10.0
                    }
                ]
            }));
        });

        let response = client.supported_assets().await?;

        let expected = SupportedAssetsResponse::builder()
            .supported_assets(vec![
                SupportedAsset::builder()
                    .chain_id("1")
                    .chain_name("Ethereum")
                    .token(
                        Token::builder()
                            .name("USD Coin")
                            .symbol("USDC")
                            .address("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48")
                            .decimals(6_u8)
                            .build(),
                    )
                    .min_checkout_usd(dec!(45))
                    .build(),
                SupportedAsset::builder()
                    .chain_id("137")
                    .chain_name("Polygon")
                    .token(
                        Token::builder()
                            .name("Bridged USDC")
                            .symbol("USDC.e")
                            .address("0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174")
                            .decimals(6_u8)
                            .build(),
                    )
                    .min_checkout_usd(dec!(10))
                    .build(),
            ])
            .build();

        assert_eq!(response, expected);
        mock.assert();

        Ok(())
    }

    #[tokio::test]
    async fn supported_assets_empty_should_succeed() -> anyhow::Result<()> {
        let server = MockServer::start();
        let client = Client::new(&server.base_url())?;

        let mock = server.mock(|when, then| {
            when.method(GET).path("/supported-assets");
            then.status(StatusCode::OK)
                .json_body(json!({"supportedAssets": []}));
        });

        let response = client.supported_assets().await?;

        assert!(response.supported_assets.is_empty());
        mock.assert();

        Ok(())
    }

    #[tokio::test]
    async fn supported_assets_server_error_should_fail() -> anyhow::Result<()> {
        let server = MockServer::start();
        let client = Client::new(&server.base_url())?;

        let mock = server.mock(|when, then| {
            when.method(GET).path("/supported-assets");
            then.status(StatusCode::INTERNAL_SERVER_ERROR)
                .json_body(json!({"error": "Internal server error"}));
        });

        let result = client.supported_assets().await;

        result.unwrap_err();
        mock.assert();

        Ok(())
    }
}

mod client {
    use polymarket_client_sdk::bridge::Client;

    #[test]
    fn default_client_should_have_correct_host() {
        let client = Client::default();
        assert_eq!(client.host().as_str(), "https://bridge.polymarket.com/");
    }

    #[test]
    fn custom_host_should_succeed() -> anyhow::Result<()> {
        let client = Client::new("https://custom.bridge.api")?;
        assert_eq!(client.host().as_str(), "https://custom.bridge.api/");
        Ok(())
    }

    #[test]
    fn invalid_host_should_fail() {
        let result = Client::new("not a valid url");
        result.unwrap_err();
    }
}