Skip to content

Comparison

Pim Feltkamp edited this page Apr 27, 2026 · 2 revisions

Cross-language comparison

Same operations, all 9 official Cryptohopper SDKs side-by-side. Use this page as a quick mapping when porting between languages, or when picking which SDK to start with — every SDK exposes the same 18 API domains with the same error taxonomy and retry contract, just in idiomatic shape per language.

The CLI is included at the bottom for reference.

Construct a client

Language Snippet
Node const ch = new CryptohopperClient({ apiKey: token, timeoutMs: 30_000, maxRetries: 3 });
Python with CryptohopperClient(api_key=token, timeout=30, max_retries=3) as ch:
Go ch, _ := cryptohopper.NewClient(token, cryptohopper.WithTimeout(30*time.Second), cryptohopper.WithMaxRetries(3))
Ruby ch = Cryptohopper::Client.new(api_key: token, timeout: 30, max_retries: 3)
Rust let ch = Client::builder().api_key(token).timeout(Duration::from_secs(30)).max_retries(3).build()?;
PHP $ch = new Client(apiKey: $token, timeout: 30, maxRetries: 3);
Dart final ch = CryptohopperClient(apiKey: token, timeout: const Duration(seconds: 30), maxRetries: 3);
Swift let ch = try Client(apiKey: token, timeout: 30, maxRetries: 3)
CLI cryptohopper login (token persists in ~/.cryptohopper/config.json)

All nine accept identical option semantics: timeout is per-request, maxRetries covers HTTP 429 (Retry-After honoured automatically; set 0 to disable).

Fetch the authenticated user

Language Snippet
Node const me = await ch.user.get();
Python me = ch.user.get()
Go me, err := ch.User.Get(ctx)
Ruby me = ch.user.get
Rust let me = ch.user.get().await?;
PHP $me = $ch->user->get();
Dart final me = await ch.user.get();
Swift let me = try await client.user.get()
CLI cryptohopper whoami

List your hoppers

Language Snippet
Node const hoppers = await ch.hoppers.list({ exchange: "binance" });
Python hoppers = ch.hoppers.list(exchange="binance")
Go hoppers, _ := ch.Hoppers.List(ctx, &cryptohopper.HoppersListOptions{Exchange: "binance"})
Ruby hoppers = ch.hoppers.list(exchange: "binance")
Rust let hoppers = ch.hoppers.list(Some("binance")).await?;
PHP $hoppers = $ch->hoppers->list(exchange: "binance");
Dart final hoppers = await ch.hoppers.list(exchange: 'binance');
Swift let hoppers = try await client.hoppers.list(exchange: "binance")
CLI cryptohopper hoppers list --exchange binance

Fetch a ticker

Language Snippet
Node const t = await ch.exchange.ticker({ exchange: "binance", market: "BTC/USDT" });
Python t = ch.exchange.ticker(exchange="binance", market="BTC/USDT")
Go t, _ := ch.Exchange.Ticker(ctx, "binance", "BTC/USDT")
Ruby t = ch.exchange.ticker(exchange: "binance", market: "BTC/USDT")
Rust let t = ch.exchange.ticker(&json!({"exchange":"binance","market":"BTC/USDT"})).await?;
PHP $t = $ch->exchange->ticker(exchange: "binance", market: "BTC/USDT");
Dart final t = await ch.exchange.ticker(exchange: 'binance', market: 'BTC/USDT');
Swift let t = try await client.exchange.ticker(exchange: "binance", market: "BTC/USDT")
CLI cryptohopper ticker binance BTC/USDT

Although /exchange/ticker is conceptually "public market data," the AWS API Gateway in front of the production API rejects every call without a valid OAuth bearer today (it returns 405 Missing Authentication Token). Practical result: pass a real token to every SDK call. See Authentication.

Submit a backtest

Language Snippet
Node const bt = await ch.backtest.create({ hopper_id: 42, start_date: "2026-01-01", end_date: "2026-04-01" });
Python bt = ch.backtest.create({"hopper_id": 42, "start_date": "2026-01-01", "end_date": "2026-04-01"})
Go bt, _ := ch.Backtest.Create(ctx, map[string]any{"hopper_id":42, "start_date":"2026-01-01", "end_date":"2026-04-01"})
Ruby bt = ch.backtest.create(hopper_id: 42, start_date: "2026-01-01", end_date: "2026-04-01")
Rust let bt = ch.backtest.create(&json!({"hopper_id":42,"start_date":"2026-01-01","end_date":"2026-04-01"})).await?;
PHP $bt = $ch->backtest->create(["hopper_id" => 42, "start_date" => "2026-01-01", "end_date" => "2026-04-01"]);
Dart final bt = await ch.backtest.create({"hopper_id": 42, "start_date": "2026-01-01", "end_date": "2026-04-01"});
Swift let bt = try await client.backtest.create(["hopper_id": 42, "start_date": "2026-01-01", "end_date": "2026-04-01"])
CLI cryptohopper backtest new 42 --from 2026-01-01 --to 2026-04-01

Catch a typed error

Same code in every SDK, just with the language's idiomatic catch syntax.

Language Snippet
Node try { ... } catch (e) { if (e instanceof CryptohopperError && e.code === "RATE_LIMITED") { ... e.retryAfterMs ... } }
Python try: ... except CryptohopperError as e: if e.code == "RATE_LIMITED": ... e.retry_after_ms ...
Go var ce *cryptohopper.Error; if errors.As(err, &ce) && ce.Code == "RATE_LIMITED" { ... ce.RetryAfter ... }
Ruby rescue Cryptohopper::Error => e; case e.code in "RATE_LIMITED" then ... e.retry_after_ms ...
Rust Err(e) if e.code == ErrorCode::RateLimited => { ... e.retry_after_ms ... }
PHP catch (CryptohopperException $e) { if ($e->getErrorCode() === "RATE_LIMITED") { ... $e->getRetryAfterMs() ... } }
Dart on CryptohopperException catch (e) { if (e.code == "RATE_LIMITED") { ... e.retryAfterMs ... } }
Swift catch let e as CryptohopperError where e.code == .rateLimited { ... e.retryAfterMs ... }
CLI exit code 3 (rate limited); --json exposes error.code + error.retry_after_ms

The error code strings (RATE_LIMITED, UNAUTHORIZED, FORBIDDEN, NOT_FOUND, VALIDATION_ERROR, SERVER_ERROR, SERVICE_UNAVAILABLE, DEVICE_UNAUTHORIZED, NETWORK_ERROR, TIMEOUT, UNKNOWN) are stable across every SDK. The Rust enum and the Swift enum carry the same values via as_str() / rawValue.

Inject a custom HTTP transport

For proxies, mTLS, request logging, etc.

Language Snippet
Node new CryptohopperClient({ apiKey, fetch: customFetch }) (any function matching globalThis.fetch)
Python CryptohopperClient(api_key=token, http_client=httpx.Client(proxy=..., verify=...))
Go cryptohopper.NewClient(token, cryptohopper.WithHTTPClient(&http.Client{Transport: customRT}))
Ruby not exposed today — the SDK uses Ruby's stdlib Net::HTTP directly
Rust Client::builder().http_client(reqwest::Client::builder().proxy(...).build()?).build()?
PHP new Client(apiKey: $token, httpClient: new GuzzleClient(['proxy' => ...]))
Dart CryptohopperClient(apiKey: token, httpClient: customClient) (any http.Client)
Swift Client(apiKey: token, httpClient: customClient) (any HTTPClient)
CLI not exposed; honours HTTPS_PROXY / NO_PROXY env vars via Node's native fetch

Disable retries / handle 429 yourself

Language Snippet
Node new CryptohopperClient({ apiKey, maxRetries: 0 })
Python CryptohopperClient(api_key=token, max_retries=0)
Go cryptohopper.NewClient(token, cryptohopper.WithMaxRetries(0))
Ruby Cryptohopper::Client.new(api_key: token, max_retries: 0)
Rust Client::builder().api_key(token).max_retries(0).build()?
PHP new Client(apiKey: $token, maxRetries: 0)
Dart CryptohopperClient(apiKey: token, maxRetries: 0)
Swift try Client(apiKey: token, maxRetries: 0)
CLI not exposed — the underlying Node SDK retries 429 transparently

Authentication is required on every endpoint

The AWS API Gateway in front of the production API rejects every call without a valid OAuth bearer token (returns 405 Missing Authentication Token). This holds even on routes the API conceptually treats as "public market data" like /exchange/ticker and /market/homepage. Practical result: every SDK call (and every CLI subcommand, including cryptohopper ticker) requires a real token. See Authentication for how to issue one.

Picking an SDK

  • Already in a Node.js codebase? → Node.
  • Server-side Python (FastAPI, Django, Airflow)? → Python.
  • High-performance backend, want tight binary size, deploying to cloud functions? → Go or Rust.
  • Web-app side (Rails, Sinatra)? → Ruby.
  • WordPress / Laravel / Symfony / Magento integration? → PHP.
  • Mobile (Flutter cross-platform)? → Dart. (The legacy cryptohopper-android-sdk targets Android-only via Kotlin and predates this suite.)
  • Native iOS / macOS / Linux server-side Swift? → Swift. (The legacy cryptohopper-ios-sdk targets iOS-only via CocoaPods and predates this suite.)
  • One-off scripts, CI automation, terminal use? → CLI.

All nine are MIT-licensed, share the same error taxonomy and retry semantics, and cover all 18 public API domains.

See also