# tenbin-sdk

**tenbin-sdk** is the official asynchronous Rust SDK for interacting with the Tenbin platform. It provides a clean, testable, production-grade interface for both REST and WebSocket APIs, with explicit authentication via user-supplied signing logic.

---

## Status & Stability

- Versioning: `0.x`
- Stability policy: APIs may evolve, but breaking changes will be deliberate and documented
- Primary entry point: `TenbinClient`
- Advanced / lower-level APIs: `rest`, `ws` modules (exposed but not yet stable)

---

## Features

- Async, `tokio`-based
- REST and WebSocket support
- Explicit authentication via a pluggable `PayloadSigner`
- Transport abstractions for testability
- No panics in runtime paths
- Clear, typed error model (`ClientError`)
- Full rustdoc coverage enforced via `#![deny(missing_docs)]`

---

## Installation

Add the crate to your `Cargo.toml`:

```toml
[dependencies]
tenbin-sdk = "0.1"
```

---

## Quick Start

```rust
use tenbin_sdk::{ClientConfig, TenbinClient};
use url::Url;

let http = Url::parse("http://localhost:8080").unwrap();
let ws = Url::parse("ws://localhost:8080/stream").unwrap();

let config = ClientConfig::new(http, ws);
let client = TenbinClient::new(config).unwrap();

// REST API is always available
let _rest = &client.rest;

// WebSocket is optional and async
// let session = client.connect_ws().await?;
```

---

## REST Usage

The REST client is available immediately after construction via `client.rest`.

```rust,no_run
use tenbin_sdk::{ClientConfig, TenbinClient};
use url::Url;
use uuid::Uuid;

# async fn run() -> Result<(), tenbin_sdk::ClientError> {
let http = Url::parse("http://localhost:8080").unwrap();
let ws = Url::parse("ws://localhost:8080/stream").unwrap();

let config = ClientConfig::new(http, ws);
let client = TenbinClient::new(config)?;

// Example REST call
// let response = client.rest.get_order(Uuid::now_v7()).await?;
# Ok(())
# }
```

---

## WebSocket Usage

WebSocket connections are established explicitly and require authentication via a challenge–response flow.

### Implement a `PayloadSigner`

```rust
use tenbin_sdk::signing::PayloadSigner;
use alloy::primitives::Address;
use async_trait::async_trait;

struct MySigner;

#[async_trait]
impl PayloadSigner for MySigner {
    async fn sign_hash(&self, digest: &B256) -> Result<Signature, ClientError> {
        // TODO: Implement signing algorithm
    }
}
```

### Connect and authenticate

```rust,no_run
use tenbin_sdk::{ClientConfig, TenbinClient};
use tenbin_sdk::signing::SimpleSigner;
use alloy::primitives::Address;
use async_trait::async_trait;
use url::Url;

# async fn run() -> Result<(), tenbin_sdk::ClientError> {
let http = Url::parse("http://localhost:8080").unwrap();
let ws = Url::parse("ws://localhost:8080/stream").unwrap();

let config = ClientConfig::new(http, ws);
let client = TenbinClient::new(config)?;

let payer = Address::from([1u8; 20]);
let signer = SimpleSigner {
    pk: [u8; 64]
};

let mut session = client.connect_ws().await?;
session.authenticate_and_subscribe(payer, &signer).await?;

let event = session.next_event().await?;
println!("event: {event:?}");

# Ok(())
# }
```

---

## Error Handling

All fallible operations return `Result<T, ClientError>`.

```rust,no_run
use tenbin_sdk::{ClientConfig, TenbinClient, ClientError};
use url::Url;

# async fn run() {
let http = Url::parse("http://localhost:8080").unwrap();
let ws = Url::parse("ws://localhost:8080/stream").unwrap();

let config = ClientConfig::new(http, ws);

match TenbinClient::new(config) {
    Ok(client) => {
        let _ = client;
    }
    Err(err) => match err {
        ClientError::Config(msg) => eprintln!("config error: {msg}"),
        ClientError::Auth(msg) => eprintln!("auth error: {msg}"),
        ClientError::Protocol(msg) => eprintln!("protocol error: {msg}"),
        other => eprintln!("other error: {other:?}"),
    },
}
# }
```

---

## JSON & Wire Format

- Domain enums (e.g. order types, order status) are serialized as strings
- Serialization rules are explicit via `serde` attributes
- No implicit numeric encoding is used

---

## Testing & Coverage

LLVM coverage can be generated via:

```bash
./coverage.sh
```

---

## License

See the `LICENSE` file in the repository.

---

## Contributing

Issues and PRs are welcome. Please include:
- a clear problem statement
- a minimal reproduction (if relevant)
- tests for behavior changes
