polymarket 0.1.0

Rust SDK for Polymarket prediction market - CLOB trading, on-chain operations, and WebSocket streaming
Documentation
use alloy::signers::local::PrivateKeySigner;
use std::str::FromStr;

use crate::clob::{ClobError, Result};

/// Wallet signer for CLOB operations
///
/// Wraps a private key signer and provides convenience methods
/// for address and chain ID access.
#[derive(Clone)]
pub struct Signer {
    signer: PrivateKeySigner,
    chain_id: u64,
}

impl Signer {
    /// Create a new signer from a private key and chain ID
    ///
    /// # Arguments
    /// * `private_key` - Hex-encoded private key (with or without 0x prefix)
    /// * `chain_id` - Chain ID (137 for Polygon, 80002 for Amoy)
    pub fn new(private_key: &str, chain_id: u64) -> Result<Self> {
        let signer = PrivateKeySigner::from_str(private_key)
            .map_err(|e| ClobError::InvalidPrivateKey(e.to_string()))?;

        Ok(Self { signer, chain_id })
    }

    /// Get the wallet address (checksummed)
    pub fn address(&self) -> String {
        // alloy::primitives::Address's Display trait automatically checksums
        self.signer.address().to_string()
    }

    /// Get the chain ID
    pub fn chain_id(&self) -> u64 {
        self.chain_id
    }

    /// Get a reference to the underlying signer
    pub fn inner(&self) -> &PrivateKeySigner {
        &self.signer
    }

    /// Sign a message hash
    ///
    /// # Arguments
    /// * `message_hash` - The message hash to sign (32 bytes)
    ///
    /// # Returns
    /// Hex-encoded signature with "0x" prefix
    pub async fn sign_hash(&self, message_hash: &[u8; 32]) -> Result<String> {
        use alloy::signers::Signer as AlloySigner;
        use alloy::primitives::FixedBytes;

        let hash: &FixedBytes<32> = message_hash.into();
        let signature = self
            .signer
            .sign_hash(hash)
            .await
            .map_err(|e| ClobError::SigningError(e.to_string()))?;

        Ok(format!("0x{}", hex::encode(signature.as_bytes())))
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_signer_creation() {
        let private_key = "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80";
        let signer = Signer::new(private_key, 137);

        assert!(signer.is_ok());
        let s = signer.unwrap();
        assert_eq!(s.chain_id(), 137);
        assert!(s.address().starts_with("0x"));
    }

    #[test]
    fn test_invalid_private_key() {
        let invalid_key = "invalid";
        let result = Signer::new(invalid_key, 137);

        assert!(result.is_err());
    }

    #[tokio::test]
    async fn test_sign_hash() {
        let private_key = "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80";
        let signer = Signer::new(private_key, 137).unwrap();

        let message_hash = [0u8; 32];
        let signature = signer.sign_hash(&message_hash).await;

        assert!(signature.is_ok());
        let sig = signature.unwrap();
        assert!(sig.starts_with("0x"));
    }
}