@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 +58 -158
- package/dist/api-client.d.ts +17 -0
- package/dist/api-client.d.ts.map +1 -1
- package/dist/api-client.js +144 -9
- package/dist/api-client.js.map +1 -1
- package/dist/composite-tools.d.ts +64 -0
- package/dist/composite-tools.d.ts.map +1 -1
- package/dist/composite-tools.js +479 -230
- package/dist/composite-tools.js.map +1 -1
- package/dist/index.js +9 -226
- package/dist/index.js.map +1 -1
- package/dist/resources.d.ts +44 -5
- package/dist/resources.d.ts.map +1 -1
- package/dist/resources.js +61 -5
- package/dist/resources.js.map +1 -1
- package/dist/server.d.ts +15 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +264 -0
- package/dist/server.js.map +1 -0
- package/dist/tool-runtime.d.ts +46 -0
- package/dist/tool-runtime.d.ts.map +1 -1
- package/dist/tool-runtime.js +265 -52
- package/dist/tool-runtime.js.map +1 -1
- package/dist/tools.d.ts +139 -75
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +412 -107
- package/dist/tools.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,41 +1,24 @@
|
|
|
1
1
|
# @sharpe-terminal/mcp-server
|
|
2
2
|
|
|
3
|
-
|
|
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
|
-
##
|
|
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
|
-
|
|
15
|
+
Or add it to Claude:
|
|
16
16
|
|
|
17
17
|
```bash
|
|
18
|
-
|
|
19
|
-
sharpe-mcp
|
|
18
|
+
claude mcp add sharpe -- npx -y @sharpe-terminal/mcp-server
|
|
20
19
|
```
|
|
21
20
|
|
|
22
|
-
|
|
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
|
-
###
|
|
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
|
|
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
|
-
|
|
46
|
+
## Configuration
|
|
61
47
|
|
|
62
|
-
|
|
63
|
-
|
|
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
|
-
|
|
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
|
-
|
|
57
|
+
## What you get
|
|
69
58
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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
|
-
|
|
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
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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
|
-
|
|
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
|
package/dist/api-client.d.ts
CHANGED
|
@@ -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
|
package/dist/api-client.d.ts.map
CHANGED
|
@@ -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;
|
|
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"}
|
package/dist/api-client.js
CHANGED
|
@@ -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
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
};
|
package/dist/api-client.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api-client.js","sourceRoot":"","sources":["../src/api-client.ts"],"names":[],"mappings":"AAAA,MAAM,gBAAgB,GAAG,IAAI,GAAG,CAAC,CAAC,WAAW,EAAE,WAAW,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC;
|
|
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;
|
|
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"}
|