From c3a13d97fcc3b3296f66bf480e5e33aa50b7c987 Mon Sep 17 00:00:00 2001 From: Pim Feltkamp Date: Mon, 27 Apr 2026 10:41:13 +0200 Subject: [PATCH 1/2] Fix: send access-token header instead of Authorization: Bearer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Critical: every authenticated request was being rejected by the AWS API Gateway in front of api.cryptohopper.com/v1/*. The gateway routes the `Authorization` header into a SigV4 parser and returns `405 Missing Authentication Token` — there's no Bearer-token authz on these routes. Cryptohopper's Public API v1 uses `access-token: ` instead. Confirmed by: - https://www.cryptohopper.com/api-documentation/how-the-api-works explicitly says: 'access-token - Your access token received with the Oauth2 authentication' - cryptohopper-ios-sdk/Cryptohopper-iOS-SDK/SharedModels/ConfigModels/ HopperAPIRequest.swift:248 sends `access-token` for v1 calls (and `Authorization: Bearer` only for the V2 admin API) - cryptohopper-android-sdk/sdk/src/.../HopperAPIRequest.kt:331 same - cryptohopper/code-samples/curl/README.md uses `-H "access-token: [ACCESS TOKEN]"` Bump to 0.4.0-alpha.2. No public-API change — `client.user.get()`, `client.hoppers.list()`, etc. keep their signatures. Only the wire-level header sent on each request changes. Test suite updated to assert the new header; 67/67 pass. Co-Authored-By: Claude Opus 4.7 (1M context) --- CHANGELOG.md | 11 ++++++++++- package.json | 2 +- src/client.ts | 5 ++++- src/version.ts | 2 +- test/client.test.ts | 3 ++- 5 files changed, 18 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fc407e4..f76fa9d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,16 @@ All notable changes to `@cryptohopper/sdk` are documented in this file. The format is loosely based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). -## 0.4.0-alpha.1 — Unreleased +## 0.4.0-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 (it routes `Authorization` to a SigV4 parser and returns `405 Missing Authentication Token`). Cryptohopper's Public API v1 uses a different header — `access-token: ` — confirmed by the official [API documentation](https://www.cryptohopper.com/api-documentation/how-the-api-works) and the legacy `cryptohopper-ios-sdk` / `cryptohopper-android-sdk` (which also send `access-token` for v1 calls). Switching the SDK to send `access-token` instead. The `Authorization` header is no longer set. +- The `appKey` → `x-api-app-key` header is unchanged; that one was always correct. + +### Compatibility +No public-API change. The fix is purely in the request-builder and is invisible to callers — `client.user.get()`, `client.hoppers.list()`, etc. all keep their existing signatures and behaviour. Only the wire-level header sent on each request changes. + +## 0.4.0-alpha.1 — 2026-04-25 Adds four more API domains: `social`, `tournaments`, `webhooks`, `app`. This is the final A-wave — all 14 remaining public domains are now covered. diff --git a/package.json b/package.json index 12714ee..e3c8719 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@cryptohopper/sdk", - "version": "0.4.0-alpha.1", + "version": "0.4.0-alpha.2", "description": "Official Node.js SDK for the Cryptohopper API", "type": "module", "main": "./dist/index.js", diff --git a/src/client.ts b/src/client.ts index 1521883..7c2ba8b 100644 --- a/src/client.ts +++ b/src/client.ts @@ -157,7 +157,10 @@ export class CryptohopperClient { const url = this.buildUrl(path, options.query); const ua = `cryptohopper-sdk/${CURRENT_VERSION}${this.userAgentSuffix ? ` ${this.userAgentSuffix}` : ""}`; const headers: Record = { - Authorization: `Bearer ${this.apiKey}`, + // 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. + "access-token": this.apiKey, "User-Agent": ua, Accept: "application/json", }; diff --git a/src/version.ts b/src/version.ts index 26dd551..651cb33 100644 --- a/src/version.ts +++ b/src/version.ts @@ -1 +1 @@ -export const CURRENT_VERSION = "0.4.0-alpha.1"; +export const CURRENT_VERSION = "0.4.0-alpha.2"; diff --git a/test/client.test.ts b/test/client.test.ts index 46d9211..50f59cd 100644 --- a/test/client.test.ts +++ b/test/client.test.ts @@ -17,7 +17,8 @@ describe("CryptohopperClient transport", () => { const req = mock.requests[0]!; expect(req.url).toBe("https://api.cryptohopper.com/v1/user/get"); expect(req.method).toBe("GET"); - expect(req.headers["authorization"]).toBe("Bearer ch_abc"); + expect(req.headers["access-token"]).toBe("ch_abc"); + expect(req.headers["authorization"]).toBeUndefined(); expect(req.headers["user-agent"]).toBe(`cryptohopper-sdk/${CURRENT_VERSION}`); expect(req.headers["x-api-app-key"]).toBeUndefined(); }); From 183843c84a69cc381a2470b038d36660753f7759 Mon Sep 17 00:00:00 2001 From: Pim Feltkamp Date: Mon, 27 Apr 2026 13:21:12 +0200 Subject: [PATCH 2/2] =?UTF-8?q?Drop=20'public=20=E2=80=94=20no=20auth=20re?= =?UTF-8?q?quired'=20README=20claims=20(same=20bug=20surface)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Exchange and Marketplace sections claimed those 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 labels were misleading. Replaces them with comments that match reality. --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a47de12..c01c4f1 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ await ch.hoppers.configGet(42); await ch.hoppers.configUpdate(42, { /* config fields */ }); await ch.hoppers.panic(42); -// Exchange (public — no auth required) +// Exchange — market data (still requires a real token; the gateway has no anonymous routes) await ch.exchange.ticker({ exchange: "binance", market: "BTC/USDT" }); await ch.exchange.candles({ exchange: "binance", market: "BTC/USDT", timeframe: "1h" }); await ch.exchange.orderbook({ exchange: "binance", market: "BTC/USDT" }); @@ -74,7 +74,7 @@ await ch.backtest.list(); await ch.backtest.cancel(1); await ch.backtest.limits(); -// Marketplace (public — no auth required) +// Marketplace — same auth requirement as everything else await ch.market.signals({ type: "buy" }); await ch.market.signal(99); await ch.market.items({ type: "strategy" });