@sharpe-terminal/mcp-server 1.2.0 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,41 +1,24 @@
1
1
  # @sharpe-terminal/mcp-server
2
2
 
3
- Model Context Protocol (MCP) server for the Sharpe crypto derivatives and market data API. Enables AI agents (Claude, Cursor, Windsurf, etc.) to query real-time crypto market data including funding rates, futures, arbitrage opportunities, narratives, ecosystems, and more.
3
+ MCP server for [Sharpe Terminal](https://www.sharpe.ai): crypto derivatives
4
+ and market data for AI agents. Funding rates across 32 exchanges, futures
5
+ (open interest, liquidations, basis, long/short), arbitrage scanners,
6
+ correlation, listings, narratives, and more, exposed as Model Context
7
+ Protocol tools.
4
8
 
5
- ## Installation
6
-
7
- Requires Node.js 18 or later.
8
-
9
- ### npx (no install)
9
+ ## Quick start
10
10
 
11
11
  ```bash
12
12
  npx @sharpe-terminal/mcp-server
13
13
  ```
14
14
 
15
- ### Global install
15
+ Or add it to Claude:
16
16
 
17
17
  ```bash
18
- npm install -g @sharpe-terminal/mcp-server
19
- sharpe-mcp
18
+ claude mcp add sharpe -- npx -y @sharpe-terminal/mcp-server
20
19
  ```
21
20
 
22
- ## Configuration
23
-
24
- ### Environment Variables
25
-
26
- | Variable | Required | Default | Description |
27
- |---|---|---|---|
28
- | `SHARPE_API_KEY` | No | — | Your Sharpe API key (`sk_live_...`). When set, uses the authenticated `/api/v1/` endpoints with higher rate limits. |
29
- | `SHARPE_API_URL` | No | `https://www.sharpe.ai` | Base URL for authenticated `/api/v1/*` API requests. |
30
- | `SHARPE_FREE_URL` | No | `https://www.sharpe.ai` | Base URL for free `/api/*` fallback requests. |
31
-
32
- Without an API key, the server falls back to free public endpoints when a matching public route exists. Authenticated-only `/api/v1/*` tools require `SHARPE_API_KEY`.
33
-
34
- Custom base URLs must use `https://`, except `http://localhost`/`127.0.0.1`/`::1` for local development. The server refuses API redirects so credentials are not forwarded to unexpected hosts.
35
-
36
- ### Claude Desktop
37
-
38
- Add to your `claude_desktop_config.json` (usually at `~/Library/Application Support/Claude/claude_desktop_config.json` on macOS or `%APPDATA%\Claude\claude_desktop_config.json` on Windows):
21
+ Generic MCP client config:
39
22
 
40
23
  ```json
41
24
  {
@@ -43,151 +26,68 @@ Add to your `claude_desktop_config.json` (usually at `~/Library/Application Supp
43
26
  "sharpe": {
44
27
  "command": "npx",
45
28
  "args": ["-y", "@sharpe-terminal/mcp-server"],
46
- "env": {
47
- "SHARPE_API_KEY": "sk_live_your_key_here"
48
- }
29
+ "env": { "SHARPE_API_KEY": "sk_live_..." }
49
30
  }
50
31
  }
51
32
  }
52
33
  ```
53
34
 
54
- ### Claude Code
35
+ ### Hosted endpoint (no install)
36
+
37
+ The same server runs at `https://www.sharpe.ai/api/mcp` (Streamable HTTP).
38
+ Clients that take a URL need nothing installed; send a key as a header for
39
+ the authenticated routes:
55
40
 
56
41
  ```bash
57
- claude mcp add sharpe -- npx -y @sharpe-terminal/mcp-server
42
+ claude mcp add --transport http sharpe-mcp https://www.sharpe.ai/api/mcp \
43
+ --header "Authorization: Bearer sk_live_..."
58
44
  ```
59
45
 
60
- Or with an API key:
46
+ ## Configuration
61
47
 
62
- ```bash
63
- SHARPE_API_KEY=sk_live_your_key_here claude mcp add sharpe -- npx -y @sharpe-terminal/mcp-server
64
- ```
48
+ | Env var | Required | Purpose |
49
+ | --- | --- | --- |
50
+ | `SHARPE_API_KEY` | No | v1 API key (higher limits). Without it, tools fall back to the free public endpoints. |
51
+ | `SHARPE_API_URL` | No | Override the authenticated API base (HTTPS required outside localhost). |
52
+ | `SHARPE_FREE_URL` | No | Override the free API base (HTTPS required outside localhost). |
65
53
 
66
- ### Cursor
54
+ Get a key at [sharpe.ai/dashboard/api-keys](https://www.sharpe.ai/dashboard/api-keys). Redirects
55
+ are never followed, and keys are never forwarded across hosts.
67
56
 
68
- Add to your `.cursor/mcp.json`:
57
+ ## What you get
69
58
 
70
- ```json
71
- {
72
- "mcpServers": {
73
- "sharpe": {
74
- "command": "npx",
75
- "args": ["-y", "@sharpe-terminal/mcp-server"],
76
- "env": {
77
- "SHARPE_API_KEY": "sk_live_your_key_here"
78
- }
79
- }
80
- }
81
- }
82
- ```
59
+ - 37 endpoint tools: funding rates (current / accumulated / per-coin
60
+ history), funding settlement (dollar value actually paid, long/short
61
+ split, per-window totals), futures charts, arbitrage (spot-perp,
62
+ cross-exchange, basis, calendar spread, carry), correlation matrix,
63
+ market overview, listings, narratives, ecosystems, memecoins, gem finder,
64
+ rug check, token scanner, stablecoins, news, and more.
65
+ - 3 composite workflows: `analyze_coin`, `market_briefing`,
66
+ `find_opportunities` fan out across several endpoints and return a
67
+ formatted briefing.
68
+ - 4 resources and 2 prompts: chart-id catalogs and guided workflows.
83
69
 
84
- ### Local development (from source)
70
+ Paginated endpoints (`get_funding_rates`, `get_gem_finder`) return a
71
+ `pagination` block with the continuation cursor when more rows exist.
85
72
 
86
- ```bash
87
- cd packages/mcp-server
88
- npm install
89
- npm run build
90
- node dist/index.js
91
- ```
73
+ Large responses are truncated to protect the model context window; the
74
+ payload marks `truncated: true` with the total count when that happens.
75
+
76
+ ## Data freshness
77
+
78
+ Current funding rates refresh every 5 minutes; accumulated funding keeps a
79
+ 30-minute refresh, and funding history rows land per settlement. Futures
80
+ datasets refresh every 10 minutes to 6 hours depending on the metric;
81
+ narrative and ecosystem data refreshes every 2 hours. Responses carry the
82
+ API's freshness metadata.
83
+
84
+ ## Links
85
+
86
+ - API docs: https://www.sharpe.ai/docs
87
+ - OpenAPI spec: https://www.sharpe.ai/openapi.json
88
+ - MCP guide: https://www.sharpe.ai/docs/mcp-server
89
+ - Python server + CLI source:
90
+ [`packages/mcp-server-python`](../mcp-server-python). Its first PyPI release
91
+ is pending; do not use `pip` or `uvx` until the package exists on PyPI.
92
92
 
93
- ## Available Tools
94
-
95
- The TypeScript package exposes 41 tools: 38 endpoint tools plus 3 composite workflows (`analyze_coin`, `market_briefing`, `find_opportunities`). The Python `sharpe-terminal-mcp` package mirrors this surface and is the primary distribution for the companion CLI.
96
-
97
- All tools are read-only market-data calls. They advertise MCP annotations for `readOnlyHint`, `destructiveHint=false`, `idempotentHint`, and `openWorldHint`. Endpoint tools return both `structuredContent` (`{ "result": ... }`) and text JSON for older clients. Composite workflow tools return `{ "report": "..." }` in `structuredContent` plus the same markdown report in text content.
98
-
99
- Large list responses are capped at 200 rows before they are returned to the MCP client. Truncated payloads include `truncated`, `total`, and `showing` fields.
100
-
101
- Release checklist and cross-package synchronization notes live in [`../../docs/release-packages.md`](../../docs/release-packages.md).
102
-
103
- ### Composite Workflows
104
-
105
- | Tool | Description |
106
- |---|---|
107
- | `analyze_coin` | Single-coin analysis combining funding, futures OI, and prediction data |
108
- | `market_briefing` | Market overview, narrative leaders, and funding highlights in one briefing |
109
- | `find_opportunities` | Funding-rate and arbitrage opportunity scan across multiple sources |
110
-
111
- ### Derivatives
112
-
113
- | Tool | Description |
114
- |---|---|
115
- | `get_funding_rates` | Perpetual funding rates across exchanges (current, accumulated, or historical) |
116
- | `get_futures_data` | Futures chart data: OI, volume, liquidations, long/short ratios, basis, term structure |
117
- | `get_futures_coins` | List of coins with futures data and exchange capability flags |
118
- | `get_derivatives_overview` | Market-wide derivatives snapshot: total OI, weighted funding, top coins, and exchange counts |
119
-
120
- ### Market Data
121
-
122
- | Tool | Description |
123
- |---|---|
124
- | `get_heatmap` | Market heatmap by coins, narratives, or ecosystems |
125
- | `get_correlation_matrix` | Price correlation matrix for portfolio analysis |
126
- | `get_market_overview` | Total market cap, BTC dominance, top gainers/losers, trending coins |
127
- | `get_price_prediction` | ML-based price predictions with confidence intervals |
128
- | `search_market_cap` | Search coins by name or ticker with market cap data |
129
- | `get_news` | Aggregated crypto news with category and coin filters |
130
- | `get_curated_news` | Curated crypto news highlights |
131
- | `get_gem_finder` | Curated low-cap, high-potential token discovery |
132
- | `get_stablecoins` | Stablecoin overview, detail, peg, chain supply, and yield data |
133
- | `get_mindshare` | Narrative mindshare, token-level attention, and historical snapshots |
134
- | `get_web_traffic` | Attention rankings, search trends, and social/developer snapshots |
135
- | `get_katana` | Katana Network TVL, token, holder, sentiment, pool, and ecosystem analytics |
136
-
137
- ### Screener
138
-
139
- | Tool | Description |
140
- |---|---|
141
- | `get_dexscreener_data` | Live DEX pair screening by category, network, or search phrase |
142
- | `get_dexscreener_security` | Token contract security signals for DEX Screener tokens |
143
- | `get_token_scanner` | Read-only hot token, new runner, alpha drop, AI token, and top-new scans |
144
- | `get_rug_check_security` | Token contract and liquidity risk signals |
145
- | `get_rug_check_trending` | Trending tokens suitable for rug-check review |
146
- | `get_insider_selling` | Insider selling pressure and holder-quality risk signals |
147
- | `get_pump_dump` | Pump-and-dump manipulation risk signals |
148
-
149
- ### Arbitrage
150
-
151
- | Tool | Description |
152
- |---|---|
153
- | `get_arbitrage_spot_perp` | Spot-perpetual funding rate arbitrage opportunities |
154
- | `get_arbitrage_cross_exchange` | Cross-exchange price discrepancy arbitrage |
155
- | `get_arbitrage_dated_futures_basis` | Spot-futures cash-and-carry basis rows |
156
- | `get_arbitrage_futures_calendar_spread` | Near-versus-far dated futures curve rows |
157
- | `get_arbitrage_perp_dated_carry` | Perp funding versus dated futures carry rows |
158
- | `get_arbitrage_cex_spot_transfer` | CEX spot-transfer rows with network, fee, depth, and slippage checks |
159
-
160
- ### Categories
161
-
162
- | Tool | Description |
163
- |---|---|
164
- | `get_narratives` | Narrative-level data: L1, L2, DeFi, AI Agents, DePIN, RWA, etc. |
165
- | `get_ecosystems` | Ecosystem-level data: Ethereum, Solana, Arbitrum, Base, etc. with TVL |
166
- | `get_memecoins` | Memecoin narrative data: Dog, Cat, Frog, AI, Solana, Base, Pump.fun, etc. |
167
- | `get_memecoin_launches` | Recently launched memecoin pairs screened by age, liquidity, volume, and transactions |
168
- | `get_exchange_listings` | New Listings aggregate activity by narrative and venue |
169
- | `get_recent_listings` | Recent token listings feed |
170
- | `get_listing_events` | Canonical listing lifecycle events with source confidence |
171
- | `get_listing_exchanges` | Listing connector coverage and parser status |
172
-
173
- ### System
174
-
175
- | Tool | Description |
176
- |---|---|
177
- | `get_api_coverage` | Available data products, exchanges, timeframes, and chart coverage |
178
-
179
- ## Example Prompts
180
-
181
- Once connected, you can ask your AI agent things like:
182
-
183
- - "What are the current BTC funding rates across exchanges?"
184
- - "Show me the top spot-perp arbitrage opportunities"
185
- - "What's the correlation between BTC, ETH, and SOL over the last 90 days?"
186
- - "Show me the DeFi narrative performance data"
187
- - "Search for Solana in the market cap tracker"
188
- - "What are the latest crypto news about Bitcoin?"
189
- - "Get the futures open interest chart for ETH over the last month"
190
-
191
- ## License
192
-
193
- MIT
93
+ MIT © Sharpe Terminal
@@ -5,9 +5,26 @@ export interface ApiClientOptions {
5
5
  fallbackBaseUrl: string;
6
6
  userAgent: string;
7
7
  fetchImpl?: typeof fetch;
8
+ /** Appended to a keyless 401: how this caller supplies a key. */
9
+ missingKeyHint?: string;
10
+ /** Test seam for retry backoff. */
11
+ sleep?: (ms: number) => Promise<void>;
8
12
  }
13
+ /** How long to wait before retrying, or null when the failure is final. */
14
+ export declare function retryDelayMs(response: Response | null): number | null;
15
+ /**
16
+ * The error an agent reads: status plus the problem detail, suggested action
17
+ * and request id from the API's problem+json body, rather than the raw JSON.
18
+ */
19
+ export declare function describeApiFailure(response: Response, body: string, hint?: string): string;
9
20
  export declare function normalizeBaseUrl(base: string): string;
10
21
  export declare function redactUrl(input: string): string;
11
22
  export declare function buildUrl(base: string, path: string, params: Record<string, unknown>): string;
23
+ /** `{ cursor, has_more, total }` from header-only pagination, or null when absent. */
24
+ export declare function headerPagination(headers: Headers): {
25
+ cursor: string | null;
26
+ has_more: boolean;
27
+ total: number | null;
28
+ } | null;
12
29
  export declare function createApiCaller(options: ApiClientOptions): ApiCaller;
13
30
  //# sourceMappingURL=api-client.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"api-client.d.ts","sourceRoot":"","sources":["../src/api-client.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,SAAS,GAAG,CACtB,OAAO,EAAE,MAAM,EACf,YAAY,EAAE,MAAM,EACpB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAC1B,OAAO,CAAC,OAAO,CAAC,CAAC;AAEtB,MAAM,WAAW,gBAAgB;IAC/B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe,EAAE,MAAM,CAAC;IACxB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;CAC1B;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAsCrD;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAS/C;AAED,wBAAgB,QAAQ,CACtB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC9B,MAAM,CAQR;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE,gBAAgB,GAAG,SAAS,CAgEpE"}
1
+ {"version":3,"file":"api-client.d.ts","sourceRoot":"","sources":["../src/api-client.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,SAAS,GAAG,CACtB,OAAO,EAAE,MAAM,EACf,YAAY,EAAE,MAAM,EACpB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAC1B,OAAO,CAAC,OAAO,CAAC,CAAC;AAEtB,MAAM,WAAW,gBAAgB;IAC/B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe,EAAE,MAAM,CAAC;IACxB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;IACzB,iEAAiE;IACjE,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,mCAAmC;IACnC,KAAK,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACvC;AAuBD,2EAA2E;AAC3E,wBAAgB,YAAY,CAAC,QAAQ,EAAE,QAAQ,GAAG,IAAI,GAAG,MAAM,GAAG,IAAI,CAWrE;AASD;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,QAAQ,EAClB,IAAI,EAAE,MAAM,EACZ,IAAI,CAAC,EAAE,MAAM,GACZ,MAAM,CA+BR;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAsCrD;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAS/C;AAED,wBAAgB,QAAQ,CACtB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC9B,MAAM,CAQR;AAED,sFAAsF;AACtF,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,OAAO,GAAG;IAClD,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,QAAQ,EAAE,OAAO,CAAC;IAClB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB,GAAG,IAAI,CAUP;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE,gBAAgB,GAAG,SAAS,CAoHpE"}
@@ -1,4 +1,76 @@
1
1
  const LOCAL_HTTP_HOSTS = new Set(["localhost", "127.0.0.1", "::1", "[::1]"]);
2
+ const DEFAULT_MISSING_KEY_HINT = "This tool needs an API key: set SHARPE_API_KEY (the free tier works, https://www.sharpe.ai/docs/authentication).";
3
+ /** One retry: transient failures usually clear within a second. */
4
+ const MAX_ATTEMPTS = 2;
5
+ const RETRYABLE_STATUSES = new Set([502, 503, 504]);
6
+ /**
7
+ * A 429 is retried only when the API says the window reopens this soon. A
8
+ * monthly-quota 429 carries a Retry-After of days and fails straight away.
9
+ */
10
+ const MAX_RATE_LIMIT_WAIT_MS = 10_000;
11
+ function retryAfterMs(response) {
12
+ const value = response.headers.get("retry-after");
13
+ if (!value)
14
+ return null;
15
+ const seconds = Number(value);
16
+ if (Number.isFinite(seconds))
17
+ return Math.max(0, seconds * 1000);
18
+ const date = Date.parse(value);
19
+ return Number.isFinite(date) ? Math.max(0, date - Date.now()) : null;
20
+ }
21
+ /** How long to wait before retrying, or null when the failure is final. */
22
+ export function retryDelayMs(response) {
23
+ // Network failure before any response (reset, DNS): retry shortly.
24
+ if (response === null)
25
+ return 500;
26
+ if (RETRYABLE_STATUSES.has(response.status)) {
27
+ return Math.min(retryAfterMs(response) ?? 500, MAX_RATE_LIMIT_WAIT_MS);
28
+ }
29
+ if (response.status === 429) {
30
+ const wait = retryAfterMs(response);
31
+ return wait != null && wait <= MAX_RATE_LIMIT_WAIT_MS ? wait : null;
32
+ }
33
+ return null;
34
+ }
35
+ function isTimeout(error) {
36
+ return (error instanceof Error &&
37
+ (error.name === "TimeoutError" || error.name === "AbortError"));
38
+ }
39
+ /**
40
+ * The error an agent reads: status plus the problem detail, suggested action
41
+ * and request id from the API's problem+json body, rather than the raw JSON.
42
+ */
43
+ export function describeApiFailure(response, body, hint) {
44
+ let detail = body.slice(0, 500);
45
+ try {
46
+ const problem = JSON.parse(body);
47
+ const text = [problem.detail, problem.error, problem.message].find((value) => typeof value === "string" && value !== "");
48
+ if (text) {
49
+ detail = [
50
+ text,
51
+ typeof problem.suggested_action === "string"
52
+ ? problem.suggested_action
53
+ : null,
54
+ typeof problem.request_id === "string"
55
+ ? `(request_id ${problem.request_id})`
56
+ : null,
57
+ ]
58
+ .filter(Boolean)
59
+ .join(" ");
60
+ }
61
+ }
62
+ catch {
63
+ // Not JSON: keep the raw (clipped) body.
64
+ }
65
+ const retryAfter = response.headers.get("retry-after");
66
+ return [
67
+ `API returned ${response.status} ${response.statusText}: ${detail}`,
68
+ retryAfter ? `Retry after ${retryAfter}s.` : null,
69
+ hint ?? null,
70
+ ]
71
+ .filter(Boolean)
72
+ .join(" ");
73
+ }
2
74
  export function normalizeBaseUrl(base) {
3
75
  const normalized = base.trim().replace(/\/+$/, "");
4
76
  if (!normalized)
@@ -54,11 +126,26 @@ export function buildUrl(base, path, params) {
54
126
  }
55
127
  return url.toString();
56
128
  }
129
+ /** `{ cursor, has_more, total }` from header-only pagination, or null when absent. */
130
+ export function headerPagination(headers) {
131
+ const cursor = headers.get("x-next-cursor");
132
+ const totalHeader = headers.get("x-total-count");
133
+ if (cursor == null && totalHeader == null)
134
+ return null;
135
+ const total = totalHeader == null ? Number.NaN : Number(totalHeader);
136
+ return {
137
+ cursor: cursor || null,
138
+ has_more: Boolean(cursor),
139
+ total: Number.isFinite(total) ? total : null,
140
+ };
141
+ }
57
142
  export function createApiCaller(options) {
58
143
  const apiKey = (options.apiKey ?? "").trim();
59
144
  const apiBaseUrl = normalizeBaseUrl(options.apiBaseUrl);
60
145
  const fallbackBaseUrl = normalizeBaseUrl(options.fallbackBaseUrl);
61
146
  const fetchImpl = options.fetchImpl ?? fetch;
147
+ const sleep = options.sleep ??
148
+ ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
62
149
  return async function callApi(apiPath, fallbackPath, args) {
63
150
  const useAuthenticatedApi = apiKey.length > 0;
64
151
  const base = useAuthenticatedApi ? apiBaseUrl : fallbackBaseUrl;
@@ -71,19 +158,48 @@ export function createApiCaller(options) {
71
158
  if (useAuthenticatedApi) {
72
159
  headers.Authorization = `Bearer ${apiKey}`;
73
160
  }
74
- const response = await fetchImpl(url, {
75
- method: "GET",
76
- headers,
77
- redirect: "manual",
78
- signal: AbortSignal.timeout(30_000),
79
- });
80
- if (response.status >= 300 && response.status < 400) {
161
+ let response;
162
+ for (let attempt = 1;; attempt += 1) {
163
+ let attemptResponse = null;
164
+ try {
165
+ attemptResponse = await fetchImpl(url, {
166
+ method: "GET",
167
+ headers,
168
+ redirect: "manual",
169
+ signal: AbortSignal.timeout(30_000),
170
+ });
171
+ }
172
+ catch (error) {
173
+ // A timeout already cost the caller 30s; only fast failures retry.
174
+ if (attempt >= MAX_ATTEMPTS || isTimeout(error))
175
+ throw error;
176
+ }
177
+ const delay = attempt < MAX_ATTEMPTS ? retryDelayMs(attemptResponse) : null;
178
+ if (attemptResponse && delay === null) {
179
+ response = attemptResponse;
180
+ break;
181
+ }
182
+ // Release the discarded attempt's connection before retrying.
183
+ await attemptResponse?.body?.cancel().catch(() => undefined);
184
+ await sleep(delay ?? 0);
185
+ }
186
+ // 304 Not Modified is excluded: it is a cache-validation response, not a
187
+ // redirect, carries no Location, and never forwards credentials anywhere.
188
+ // This client sends no If-None-Match today, so a 304 is unexpected here;
189
+ // let it fall through to the generic !ok handling below instead of
190
+ // misreporting it as a credential-protecting redirect refusal.
191
+ if (response.status >= 300 &&
192
+ response.status < 400 &&
193
+ response.status !== 304) {
81
194
  const location = response.headers.get("location") ?? "unknown";
82
195
  throw new Error(`Sharpe API redirected ${redactUrl(url)} to ${location}. Refusing to follow redirects to protect credentials; set SHARPE_API_URL/SHARPE_FREE_URL to the canonical HTTPS host.`);
83
196
  }
84
197
  if (!response.ok) {
85
198
  const body = await response.text().catch(() => "");
86
- throw new Error(`API returned ${response.status} ${response.statusText}: ${body.slice(0, 500)}`);
199
+ const hint = response.status === 401 && !useAuthenticatedApi
200
+ ? (options.missingKeyHint ?? DEFAULT_MISSING_KEY_HINT)
201
+ : undefined;
202
+ throw new Error(describeApiFailure(response, body, hint));
87
203
  }
88
204
  let data;
89
205
  try {
@@ -92,12 +208,31 @@ export function createApiCaller(options) {
92
208
  catch {
93
209
  throw new Error(`API returned invalid JSON for ${apiPath}`);
94
210
  }
211
+ // The free funding book keeps its body a bare array and carries the page
212
+ // cursor only in X-Next-Cursor / X-Total-Count headers. Wrap it in the v1
213
+ // pagination shape so the runtime reattaches the cursor instead of
214
+ // silently dropping it (2026-09-24 audit).
215
+ if (Array.isArray(data)) {
216
+ const pagination = headerPagination(response.headers);
217
+ return pagination ? { data, pagination } : data;
218
+ }
95
219
  if (data &&
96
220
  typeof data === "object" &&
97
221
  !Array.isArray(data) &&
98
222
  "data" in data &&
99
223
  "meta" in data) {
100
- return data.data;
224
+ const envelope = data;
225
+ // Sibling fields riding alongside `data`/`meta` -- a pagination cursor,
226
+ // or an aggregate block like funding/settlement's `totals` -- carry
227
+ // state the caller needs. Unwrapping to `.data` alone silently
228
+ // discarded them: paginated results could never be continued
229
+ // (2026-07-27 audit), and an aggregate would have to be recomputed
230
+ // client-side from a truncated page instead of read once.
231
+ const { data: inner, meta, ...siblings } = envelope;
232
+ void meta;
233
+ return Object.keys(siblings).length > 0
234
+ ? { data: inner, ...siblings }
235
+ : inner;
101
236
  }
102
237
  return data;
103
238
  };
@@ -1 +1 @@
1
- {"version":3,"file":"api-client.js","sourceRoot":"","sources":["../src/api-client.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"api-client.js","sourceRoot":"","sources":["../src/api-client.ts"],"names":[],"mappings":"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"}
@@ -6,7 +6,71 @@
6
6
  export declare function fmtUsd(value: number | null | undefined): string;
7
7
  export declare function fmtPct(value: number | null | undefined): string;
8
8
  export declare function fmtRate(value: number | null | undefined): string;
9
+ /**
10
+ * A funding row's own settlement interval. Returns null rather than a default
11
+ * when the venue publishes none — see src/products/funding-rate/lib/
12
+ * CALCULATIONS.md: a stored rate is never implicitly an 8-hour rate.
13
+ */
14
+ export declare function intervalHours(r: Record<string, unknown>): number | null;
15
+ /** APR for one observation: `rate x (8760 / interval_hours)`. */
16
+ export declare function aprVal(r: Record<string, unknown>): number;
17
+ /**
18
+ * Minimum open interest (USD) for a market to rank in market_briefing and
19
+ * find_opportunities, and the per-leg OI and volume floor sent to the
20
+ * cross-exchange scanner. Without it every extreme was a dust market (ESIM
21
+ * on KuCoin: $33K of OI at -16,862% APR, 2026-09-24 audit). Unknown OI fails
22
+ * the floor, the same rule the cross-exchange API applies.
23
+ */
24
+ export declare const LIQUIDITY_FLOOR_USD = 1000000;
25
+ /** FUNDING_RATE_FRESHNESS_SLA_SECONDS in src/products/funding-rate/lib/freshness.ts. */
26
+ export declare const FUNDING_FRESHNESS_SLA_MS: number;
27
+ /**
28
+ * A funding row past its freshness SLA. The free endpoint serves stale rows
29
+ * flagged `is_stale: true` rather than dropping them, so rankings skip them;
30
+ * a row without the flag is judged from its `updated_at`.
31
+ */
32
+ export declare function isStaleRow(row: Record<string, unknown>, nowMs?: number): boolean;
33
+ /** A venue whose latest reading trails the newest by more than this is not current OI. */
34
+ export declare const OI_FRESHNESS_WINDOW_MS: number;
35
+ interface VenueOi {
36
+ exchange: string;
37
+ value: number;
38
+ at: number;
39
+ }
40
+ /**
41
+ * Venues the API names in `openInterestExcludedVenues`: their rows are
42
+ * shown per venue but never summed (MEXC, whose figure moves 7.00x its own
43
+ * trade flow), keyed to the reason the payload gives.
44
+ */
45
+ export declare function excludedOiVenues(payload: unknown): Map<string, string>;
46
+ /**
47
+ * Each venue's latest open-interest reading, split into venues that are
48
+ * current (within OI_FRESHNESS_WINDOW_MS of the newest reading), venues
49
+ * that stopped reporting, and venues the API excludes from totals. Totals
50
+ * sum every current venue: summing only the ten listed understated BTC by
51
+ * 9.4% (2026-09-24 audit).
52
+ */
53
+ export declare function summarizeVenueOi(rows: unknown[], excludedVenues?: ReadonlyMap<string, string>): {
54
+ current: VenueOi[];
55
+ stale: VenueOi[];
56
+ excluded: VenueOi[];
57
+ };
9
58
  type ApiCaller = (apiPath: string, fallbackPath: string, args: Record<string, unknown>) => Promise<unknown>;
59
+ /**
60
+ * Find the prediction row for a ticker in the endpoint's own coverage list.
61
+ *
62
+ * The price-prediction endpoint is keyed by CoinGecko slug ("bitcoin",
63
+ * "avalanche-2", "matic-network"), never by ticker, and a lowercased ticker is
64
+ * not a slug. Guessing one either 404s or — far worse — resolves to whichever
65
+ * unrelated asset happens to own that slug, and tickers are shared across
66
+ * thousands of assets in this dataset. So the guess is a silent wrong-coin
67
+ * risk, never a convenience: it is resolve-or-say-so here.
68
+ *
69
+ * The list response (`GET .../price-prediction/data` with no `coin`) carries
70
+ * both the ticker (`coin`) and the `slug` for every covered asset, so an exact
71
+ * ticker match against it is the endpoint's own documented contract.
72
+ */
73
+ export declare function findPredictionRow(payload: unknown, ticker: string): Record<string, unknown> | null;
10
74
  export declare function analyzeCoin(callApi: ApiCaller, coin: string): Promise<string>;
11
75
  export declare function marketBriefing(callApi: ApiCaller): Promise<string>;
12
76
  export declare function findOpportunities(callApi: ApiCaller): Promise<string>;
@@ -1 +1 @@
1
- {"version":3,"file":"composite-tools.d.ts","sourceRoot":"","sources":["../src/composite-tools.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH,wBAAgB,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAW/D;AAED,wBAAgB,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAG/D;AAED,wBAAgB,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAGhE;AAgBD,KAAK,SAAS,GAAG,CACf,OAAO,EAAE,MAAM,EACf,YAAY,EAAE,MAAM,EACpB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAC1B,OAAO,CAAC,OAAO,CAAC,CAAC;AAuCtB,wBAAsB,WAAW,CAC/B,OAAO,EAAE,SAAS,EAClB,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,MAAM,CAAC,CAgNjB;AAID,wBAAsB,cAAc,CAAC,OAAO,EAAE,SAAS,GAAG,OAAO,CAAC,MAAM,CAAC,CA6LxE;AAID,wBAAsB,iBAAiB,CAAC,OAAO,EAAE,SAAS,GAAG,OAAO,CAAC,MAAM,CAAC,CAiM3E"}
1
+ {"version":3,"file":"composite-tools.d.ts","sourceRoot":"","sources":["../src/composite-tools.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAOH,wBAAgB,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAW/D;AAED,wBAAgB,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAG/D;AAED,wBAAgB,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAGhE;AAID;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,GAAG,IAAI,CAGvE;AAMD,iEAAiE;AACjE,wBAAgB,MAAM,CAAC,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAGzD;AAED;;;;;;GAMG;AACH,eAAO,MAAM,mBAAmB,UAAY,CAAC;AAM7C,wFAAwF;AACxF,eAAO,MAAM,wBAAwB,QAAiB,CAAC;AAEvD;;;;GAIG;AACH,wBAAgB,UAAU,CACxB,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC5B,KAAK,SAAa,GACjB,OAAO,CAMT;AAuCD,0FAA0F;AAC1F,eAAO,MAAM,sBAAsB,QAAqB,CAAC;AAEzD,UAAU,OAAO;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,EAAE,EAAE,MAAM,CAAC;CACZ;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,OAAO,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAetE;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,OAAO,EAAE,EACf,cAAc,GAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAa,GACtD;IACD,OAAO,EAAE,OAAO,EAAE,CAAC;IACnB,KAAK,EAAE,OAAO,EAAE,CAAC;IACjB,QAAQ,EAAE,OAAO,EAAE,CAAC;CACrB,CA4BA;AAuBD,KAAK,SAAS,GAAG,CACf,OAAO,EAAE,MAAM,EACf,YAAY,EAAE,MAAM,EACpB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAC1B,OAAO,CAAC,OAAO,CAAC,CAAC;AAsBtB;;;;;;;;;;;;;GAaG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,OAAO,EAChB,MAAM,EAAE,MAAM,GACb,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAchC;AAID,wBAAsB,WAAW,CAC/B,OAAO,EAAE,SAAS,EAClB,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,MAAM,CAAC,CA8QjB;AAID,wBAAsB,cAAc,CAAC,OAAO,EAAE,SAAS,GAAG,OAAO,CAAC,MAAM,CAAC,CA4PxE;AAuBD,wBAAsB,iBAAiB,CAAC,OAAO,EAAE,SAAS,GAAG,OAAO,CAAC,MAAM,CAAC,CAuL3E"}