-
Notifications
You must be signed in to change notification settings - Fork 0
Getting Started
cargo add cryptohopperOr in your Cargo.toml:
[dependencies]
cryptohopper = "0.1.0-alpha.1"
tokio = { version = "1", features = ["rt-multi-thread", "macros"] }Requires Rust 1.74 or newer (for async fn in traits, used internally) and a tokio runtime.
use cryptohopper::Client;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let token = std::env::var("CRYPTOHOPPER_TOKEN")?;
let client = Client::new(token)?;
let me = client.user.get().await?;
println!("Logged in as: {}", me["email"]);
let ticker = client
.exchange
.ticker("binance", "BTC/USDT")
.await?;
println!("BTC/USDT: {}", ticker["last"]);
Ok(())
}Client is cheaply cloneable (Arc internally), so you can pass it to spawned tasks without wrapping it in another Arc.
Every request needs an OAuth2 bearer token (the AWS API Gateway in front of the production API rejects every unauthenticated call as of today). Create one via Developer → Create App on cryptohopper.com and complete the consent flow. The token is a 40-character opaque string.
For local dev:
export CRYPTOHOPPER_TOKEN=<your-token>For production, load from your secret store at startup. dotenv works for dev, but in CI use the runner's secret-injection mechanism.
The SDK returns a typed cryptohopper::Error whose code field is a cryptohopper::ErrorCode enum. Match on it directly — no string-comparison gymnastics:
use cryptohopper::{Client, ErrorCode};
match client.hoppers.get("999999").await {
Ok(hopper) => println!("got: {hopper:?}"),
Err(err) => match err.code {
ErrorCode::NotFound => {
// Expected; ignore.
}
ErrorCode::Unauthorized => {
refresh_token().await?;
// retry
}
ErrorCode::RateLimited => {
// SDK already retried; back off harder
if let Some(ms) = err.retry_after_ms {
tokio::time::sleep(std::time::Duration::from_millis(ms)).await;
}
}
ErrorCode::Forbidden => {
eprintln!("blocked from IP: {:?}", err.ip_address);
}
ErrorCode::Other(ref code) => {
// Server returned a code the SDK doesn't know about yet —
// pass through cleanly.
tracing::warn!("unknown cryptohopper code: {code}");
}
_ => return Err(err.into()),
},
}The Other(String) variant catches any new server-side codes the SDK predates. You don't need to bump SDK versions to handle new error types.
The SDK error implements std::error::Error, so ? works naturally with anyhow::Result or any custom error type built with thiserror:
use anyhow::Result;
async fn list_open_positions(client: &cryptohopper::Client, hopper_id: &str) -> Result<Vec<serde_json::Value>> {
let raw = client.hoppers.positions(hopper_id).await?;
Ok(raw.as_array().cloned().unwrap_or_default())
}For library code, define your own error type:
#[derive(thiserror::Error, Debug)]
pub enum MyError {
#[error("cryptohopper: {0}")]
Cryptohopper(#[from] cryptohopper::Error),
#[error("missing field: {0}")]
MissingField(&'static str),
}#[from] auto-converts the SDK error.
use std::time::Duration;
use cryptohopper::Client;
let client = Client::builder()
.api_key(std::env::var("CRYPTOHOPPER_TOKEN")?)
.app_key(std::env::var("CRYPTOHOPPER_APP_KEY").unwrap_or_default())
.base_url("https://api.staging.cryptohopper.com/v1")
.timeout(Duration::from_secs(60))
.max_retries(5)
.user_agent("my-app/1.0")
.build()?;The builder is the full-control entry point. Client::new(api_key) is a shortcut for Client::builder().api_key(...).build() when defaults are fine.
Client is Send + Sync + Clone. Spawn it across tasks:
use futures::stream::{FuturesUnordered, StreamExt};
let mut tasks = FuturesUnordered::new();
for id in hopper_ids {
let client = client.clone();
tasks.push(tokio::spawn(async move {
client.hoppers.get(&id).await
}));
}
while let Some(res) = tasks.next().await {
match res? {
Ok(hopper) => process(hopper),
Err(err) => tracing::warn!("hopper fetch failed: {err}"),
}
}See Rate Limits for guidance on capping concurrency at the API quota.
Error: Cryptohopper(Error { code: ValidationError, ... }) with message: "api_key must not be empty" — you passed an empty string. Most often: std::env::var("CRYPTOHOPPER_TOKEN").unwrap_or_default() returns "" when unset. Use ? to fail loudly:
let token = std::env::var("CRYPTOHOPPER_TOKEN")
.map_err(|_| anyhow::anyhow!("CRYPTOHOPPER_TOKEN is not set"))?;code: Unauthorized on every call — token is wrong, expired, or revoked. Visit the app page in the Cryptohopper dashboard to confirm.
code: Forbidden on endpoints that used to work — IP allowlisting on the OAuth app blocked your current IP. The error includes ip_address:
if let Err(err) = client.hoppers.list().await {
if matches!(err.code, ErrorCode::Forbidden) {
eprintln!("blocked from {:?}", err.ip_address);
}
}error[E0277]: ? operator can only be applied to values that implement Try — usually means you're inside a function that returns something other than Result. Either change the function signature to Result<T, E> or pattern-match on the SDK call's return.
Hangs in tests — your tests don't have a tokio runtime. Use #[tokio::test] instead of #[test]:
#[tokio::test]
async fn it_lists_hoppers() {
let client = Client::new("test-token").unwrap();
// ...
}Response shapes are returned as serde_json::Value because the Cryptohopper API hasn't been frozen into stable models yet. To layer typed parsing on top, use serde::Deserialize:
use serde::Deserialize;
#[derive(Deserialize)]
struct Hopper {
id: u64,
name: String,
exchange: String,
enabled: bool,
}
let raw = client.hoppers.get(&id).await?;
let hopper: Hopper = serde_json::from_value(raw)?;Future SDK versions may ship typed response structs as a feature flag — file an issue if you'd benefit.
- Authentication — bearer flow, app keys, IP whitelisting, custom HTTP clients
- Error Handling — every variant, pattern-match recipes, retry wrappers
- Rate Limits — auto-retry, customising back-off, concurrency caps
Pages
Other SDKs
Resources