From 1705a81c4b943b5b9e26bfb8c397ca348621437d Mon Sep 17 00:00:00 2001 From: Pim Feltkamp Date: Mon, 27 Apr 2026 11:02:56 +0200 Subject: [PATCH 1/3] Fix: send access-token header instead of Authorization: Bearer Critical: every authenticated request was being rejected by the AWS API Gateway in front of api.cryptohopper.com/v1/*. Switching to access-token: as documented in the official Cryptohopper API docs and used by the legacy iOS/Android SDKs. Bump to 0.1.0.pre.alpha.2. Co-Authored-By: Claude Opus 4.7 (1M context) --- CHANGELOG.md | 10 +++++++++- lib/cryptohopper/client.rb | 5 ++++- lib/cryptohopper/version.rb | 2 +- spec/cryptohopper/client_spec.rb | 12 ++++++++++-- 4 files changed, 24 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 094c42d..6651cb5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,15 @@ All notable changes to the `cryptohopper` gem are documented in this file. The format is loosely based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). -## 0.1.0.pre.alpha.1 — Unreleased +## 0.1.0.pre.alpha.2 — Unreleased + +### Fixed +- **Critical: every authenticated request was rejected by the API gateway.** The transport sent `Authorization: Bearer `, which the AWS API Gateway in front of `api.cryptohopper.com/v1/*` rejects (`405 Missing Authentication Token`). Cryptohopper's Public API v1 uses `access-token: ` — confirmed by the official [API documentation](https://www.cryptohopper.com/api-documentation/how-the-api-works) and the legacy iOS/Android SDKs. Switching to send `access-token`. The `Authorization` header is no longer set. + +### Compatibility +No public-API change. `client.user.get`, `client.hoppers.list`, etc. keep their signatures. + +## 0.1.0.pre.alpha.1 — 2026-04-24 Initial release. Launches at full surface parity with the other SDKs at 0.4.0 — all 18 public API domains from day one. diff --git a/lib/cryptohopper/client.rb b/lib/cryptohopper/client.rb index ca50bdd..03d4086 100644 --- a/lib/cryptohopper/client.rb +++ b/lib/cryptohopper/client.rb @@ -136,7 +136,10 @@ def build_request(method, uri, body) end req = klass.new(uri.request_uri) - req["Authorization"] = "Bearer #{@api_key}" + # Cryptohopper Public API v1 uses `access-token: `, not the + # OAuth2-conventional `Authorization: Bearer `. The gateway + # in front of the API rejects Bearer with a SigV4 parse error. + req["access-token"] = @api_key req["Accept"] = "application/json" req["User-Agent"] = user_agent_header # Ruby treats empty strings as truthy, so a literal `if @app_key` diff --git a/lib/cryptohopper/version.rb b/lib/cryptohopper/version.rb index ac1603e..1a28c4d 100644 --- a/lib/cryptohopper/version.rb +++ b/lib/cryptohopper/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module Cryptohopper - VERSION = "0.1.0.pre.alpha.1" + VERSION = "0.1.0.pre.alpha.2" end diff --git a/spec/cryptohopper/client_spec.rb b/spec/cryptohopper/client_spec.rb index bcadfcb..4b4b744 100644 --- a/spec/cryptohopper/client_spec.rb +++ b/spec/cryptohopper/client_spec.rb @@ -23,11 +23,11 @@ def build_client(**opts) end describe "transport" do - it "sends Authorization + User-Agent headers and unwraps {data}" do + it "sends access-token + User-Agent headers and unwraps {data}" do stub_request(:get, "https://api.cryptohopper.com/v1/user/get") .with( headers: { - "Authorization" => "Bearer ch_test", + "access-token" => "ch_test", "Accept" => "application/json", "User-Agent" => "cryptohopper-sdk-ruby/#{Cryptohopper::VERSION}" } @@ -39,6 +39,14 @@ def build_client(**opts) expect(out).to eq({ "hello" => "world" }) end + it "does not send an Authorization header" do + stub_request(:get, "https://api.cryptohopper.com/v1/user/get") + .to_return(status: 200, body: '{"data":{}}') + build_client.send(:_request, "GET", "/user/get") + expect(WebMock).to have_requested(:get, "https://api.cryptohopper.com/v1/user/get") + .with { |req| req.headers["Authorization"].nil? } + end + it "sends x-api-app-key when app_key is provided" do stub_request(:get, "https://api.cryptohopper.com/v1/user/get") .with(headers: { "x-api-app-key" => "client_123" }) From a009815a51ec4bb7b0dd9ae2edfdc18939be3023 Mon Sep 17 00:00:00 2001 From: Pim Feltkamp Date: Mon, 27 Apr 2026 13:24:47 +0200 Subject: [PATCH 2/3] Drop 'public, no auth' README claim (same bug surface) The Exchange section claimed the endpoints accepted anonymous calls. The auth-header fix in this PR establishes that EVERY endpoint on api.cryptohopper.com/v1/* requires a real token (the AWS API Gateway has no anonymous routes), so the README label was misleading. Replaces it with a comment that matches reality. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 33111b0..5fe513c 100644 --- a/README.md +++ b/README.md @@ -67,7 +67,7 @@ ch.hoppers.buy(hopper_id: 42, market: "BTC/USDT", amount: 0.001) ch.hoppers.config_update(42, strategy_id: 99) ch.hoppers.panic(42) -# Exchange (public, no auth) +# Exchange — market data (still requires a real token; the gateway has no anonymous routes) ch.exchange.ticker(exchange: "binance", market: "BTC/USDT") ch.exchange.candles(exchange: "binance", market: "BTC/USDT", timeframe: "1h") From f1b8d6731688983c67693dffd4eb536260f5adeb Mon Sep 17 00:00:00 2001 From: Pim Feltkamp Date: Tue, 28 Apr 2026 03:21:36 +0200 Subject: [PATCH 3/3] test: parenthesize have_requested call to silence Lint/AmbiguousBlockAssociation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit RuboCop's `Lint/AmbiguousBlockAssociation` flagged the `.with { … }` block — without explicit parens, Ruby's parser binds the block to the inner method, not the outer `expect(…).to(…)`. RuboCop is conservative here: the binding actually was correct, but the warning is still correctable. Wrapping the matcher in parens makes the association explicit and lets `--fail-level W` pass. Co-Authored-By: Claude Opus 4.7 (1M context) --- spec/cryptohopper/client_spec.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/spec/cryptohopper/client_spec.rb b/spec/cryptohopper/client_spec.rb index 4b4b744..9433fc9 100644 --- a/spec/cryptohopper/client_spec.rb +++ b/spec/cryptohopper/client_spec.rb @@ -43,8 +43,10 @@ def build_client(**opts) stub_request(:get, "https://api.cryptohopper.com/v1/user/get") .to_return(status: 200, body: '{"data":{}}') build_client.send(:_request, "GET", "/user/get") - expect(WebMock).to have_requested(:get, "https://api.cryptohopper.com/v1/user/get") - .with { |req| req.headers["Authorization"].nil? } + expect(WebMock).to( + have_requested(:get, "https://api.cryptohopper.com/v1/user/get") + .with { |req| req.headers["Authorization"].nil? } + ) end it "sends x-api-app-key when app_key is provided" do