@helm-protocol/ttt-mcp 0.1.8 → 0.2.1
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/LICENSE +18 -17
- package/README.md +42 -1
- package/dist/auth.js +34 -225
- package/dist/index.js +51 -22499
- package/dist/tools.js +6 -8
- package/package.json +4 -4
package/LICENSE
CHANGED
|
@@ -1,21 +1,22 @@
|
|
|
1
|
-
|
|
1
|
+
Business Source License 1.1
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Parameters
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
5
|
+
Licensor: Heime-Jorgen (Peter Jang)
|
|
6
|
+
Licensed Work: @helm-protocol/ttt-mcp
|
|
7
|
+
Change Date: 2029-05-28
|
|
8
|
+
Change License: Apache License, Version 2.0
|
|
11
9
|
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
Additional Use Grant:
|
|
11
|
+
The following uses are permitted without a commercial license:
|
|
12
|
+
- Non-commercial use (research, education, personal projects)
|
|
13
|
+
- Commercial use generating less than $10,000 USD/year in revenue
|
|
14
|
+
- Individual MEV bots or arbitrage bots operated for personal account
|
|
14
15
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
16
|
+
For all other commercial use, a license is required. See:
|
|
17
|
+
https://kenosian.com/pricing
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
Business Source License 1.1 terms apply. The full text is available at:
|
|
22
|
+
https://mariadb.com/bsl11/
|
package/README.md
CHANGED
|
@@ -144,6 +144,47 @@ Config file locations:
|
|
|
144
144
|
- Node.js >= 18
|
|
145
145
|
- Network access for time synthesis (HTTPS to time.nist.gov, time.google.com, time.cloudflare.com)
|
|
146
146
|
|
|
147
|
+
## TypeScript: MEV Bot Integration
|
|
148
|
+
|
|
149
|
+
```typescript
|
|
150
|
+
import { McpClient } from "@modelcontextprotocol/sdk/client/mcp.js";
|
|
151
|
+
|
|
152
|
+
// Generate a Proof of Time for a transaction
|
|
153
|
+
const result = await client.callTool({
|
|
154
|
+
name: "pot_generate",
|
|
155
|
+
arguments: {
|
|
156
|
+
txHash: "0xabc123...",
|
|
157
|
+
chainId: 8453,
|
|
158
|
+
poolAddress: "0xdef456..."
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
// Returns: { potHash, timestamp, stratum, grg_shards }
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
## Python: Hedge Fund Audit
|
|
165
|
+
|
|
166
|
+
```python
|
|
167
|
+
import subprocess, json
|
|
168
|
+
|
|
169
|
+
result = subprocess.run(
|
|
170
|
+
["npx", "-y", "@helm-protocol/ttt-mcp"],
|
|
171
|
+
input=json.dumps({
|
|
172
|
+
"tool": "pot_verify",
|
|
173
|
+
"potHash": "0x...",
|
|
174
|
+
"expectedChainId": 8453
|
|
175
|
+
}),
|
|
176
|
+
capture_output=True, text=True
|
|
177
|
+
)
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
## Rate Limits & Pricing
|
|
181
|
+
|
|
182
|
+
```
|
|
183
|
+
Free Tier: 100 calls/day per IP — no API key needed
|
|
184
|
+
Paid Tier: Set TTT_API_KEY env var — unlimited
|
|
185
|
+
Commercial: peter@kenosian.com (hedge funds, DEX protocols, OTC desks)
|
|
186
|
+
```
|
|
187
|
+
|
|
147
188
|
## Learn More
|
|
148
189
|
|
|
149
190
|
- [OpenTTT SDK](https://www.npmjs.com/package/openttt) — The underlying SDK
|
|
@@ -152,4 +193,4 @@ Config file locations:
|
|
|
152
193
|
|
|
153
194
|
## License
|
|
154
195
|
|
|
155
|
-
|
|
196
|
+
BSL-1.1
|
package/dist/auth.js
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __create = Object.create;
|
|
3
2
|
var __defProp = Object.defineProperty;
|
|
4
3
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
4
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
7
5
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
6
|
var __export = (target, all) => {
|
|
9
7
|
for (var name in all)
|
|
@@ -17,241 +15,52 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
17
15
|
}
|
|
18
16
|
return to;
|
|
19
17
|
};
|
|
20
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
-
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
-
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
-
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
-
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
-
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
-
mod
|
|
27
|
-
));
|
|
28
18
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
19
|
var auth_exports = {};
|
|
30
20
|
__export(auth_exports, {
|
|
31
|
-
|
|
32
|
-
|
|
21
|
+
checkRateLimit: () => checkRateLimit,
|
|
22
|
+
resolveApiKey: () => resolveApiKey
|
|
33
23
|
});
|
|
34
24
|
module.exports = __toCommonJS(auth_exports);
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
const
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
"[ttt-mcp] HELM_AUTH_SERVER_URL must use HTTPS (got: " + HELM_AUTH_SERVER_URL + "). Refusing to start to protect API keys in transit."
|
|
44
|
-
);
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
const CLIENT_VERSION = "0.2.0";
|
|
48
|
-
const authCache = /* @__PURE__ */ new Map();
|
|
49
|
-
function getCached(apiKey, toolName) {
|
|
50
|
-
const key = `${apiKey}:${toolName}`;
|
|
51
|
-
const entry = authCache.get(key);
|
|
52
|
-
if (!entry) return null;
|
|
53
|
-
if (Date.now() > entry.expiresAt) {
|
|
54
|
-
authCache.delete(key);
|
|
55
|
-
return null;
|
|
56
|
-
}
|
|
57
|
-
return entry;
|
|
58
|
-
}
|
|
59
|
-
function setCached(apiKey, toolName, allowed, planTier, cacheTtlMs) {
|
|
60
|
-
const key = `${apiKey}:${toolName}`;
|
|
61
|
-
authCache.set(key, {
|
|
62
|
-
allowed,
|
|
63
|
-
planTier,
|
|
64
|
-
expiresAt: Date.now() + cacheTtlMs
|
|
65
|
-
});
|
|
66
|
-
}
|
|
67
|
-
const CIRCUIT_FAILURE_THRESHOLD = 3;
|
|
68
|
-
const CIRCUIT_OPEN_DURATION_MS = 5 * 60 * 1e3;
|
|
69
|
-
const circuit = {
|
|
70
|
-
state: "CLOSED",
|
|
71
|
-
consecutiveFailures: 0,
|
|
72
|
-
openedAt: 0
|
|
73
|
-
};
|
|
74
|
-
function circuitRecordSuccess() {
|
|
75
|
-
circuit.consecutiveFailures = 0;
|
|
76
|
-
circuit.state = "CLOSED";
|
|
77
|
-
}
|
|
78
|
-
function circuitRecordFailure() {
|
|
79
|
-
circuit.consecutiveFailures += 1;
|
|
80
|
-
if (circuit.consecutiveFailures >= CIRCUIT_FAILURE_THRESHOLD) {
|
|
81
|
-
circuit.state = "OPEN";
|
|
82
|
-
circuit.openedAt = Date.now();
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
function circuitCanAttempt() {
|
|
86
|
-
if (circuit.state === "CLOSED") return true;
|
|
87
|
-
if (circuit.state === "OPEN") {
|
|
88
|
-
if (Date.now() - circuit.openedAt >= CIRCUIT_OPEN_DURATION_MS) {
|
|
89
|
-
circuit.state = "HALF_OPEN";
|
|
90
|
-
return true;
|
|
91
|
-
}
|
|
92
|
-
return false;
|
|
93
|
-
}
|
|
94
|
-
return true;
|
|
25
|
+
const FREE_TIER_LIMIT = parseInt(process.env.FREE_TIER_LIMIT ?? "100", 10);
|
|
26
|
+
const buckets = /* @__PURE__ */ new Map();
|
|
27
|
+
function nextMidnightUtc() {
|
|
28
|
+
const now = /* @__PURE__ */ new Date();
|
|
29
|
+
const midnight = new Date(
|
|
30
|
+
Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate() + 1)
|
|
31
|
+
);
|
|
32
|
+
return midnight.getTime();
|
|
95
33
|
}
|
|
96
|
-
function
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
const body = JSON.stringify({
|
|
100
|
-
api_key: apiKey,
|
|
101
|
-
tool_name: toolName,
|
|
102
|
-
request_id: requestId,
|
|
103
|
-
timestamp_ms: Date.now(),
|
|
104
|
-
client_version: CLIENT_VERSION
|
|
105
|
-
});
|
|
106
|
-
const url = new URL("/v1/auth/verify", serverUrl);
|
|
107
|
-
const options = {
|
|
108
|
-
hostname: url.hostname,
|
|
109
|
-
port: url.port || 443,
|
|
110
|
-
path: url.pathname,
|
|
111
|
-
method: "POST",
|
|
112
|
-
headers: {
|
|
113
|
-
"Content-Type": "application/json",
|
|
114
|
-
"Content-Length": Buffer.byteLength(body),
|
|
115
|
-
"X-Request-ID": requestId
|
|
116
|
-
}
|
|
117
|
-
};
|
|
118
|
-
const req = https.request(options, (res) => {
|
|
119
|
-
let data = "";
|
|
120
|
-
res.on("data", (chunk) => {
|
|
121
|
-
data += chunk.toString();
|
|
122
|
-
});
|
|
123
|
-
res.on("end", () => {
|
|
124
|
-
try {
|
|
125
|
-
const parsed = JSON.parse(data);
|
|
126
|
-
resolve(parsed);
|
|
127
|
-
} catch {
|
|
128
|
-
reject(new Error("Auth server returned non-JSON response"));
|
|
129
|
-
}
|
|
130
|
-
});
|
|
131
|
-
});
|
|
132
|
-
req.setTimeout(3e3, () => {
|
|
133
|
-
req.destroy(new Error("Auth server request timed out (3000ms)"));
|
|
134
|
-
});
|
|
135
|
-
req.on("error", (err) => {
|
|
136
|
-
reject(err);
|
|
137
|
-
});
|
|
138
|
-
req.write(body);
|
|
139
|
-
req.end();
|
|
140
|
-
});
|
|
141
|
-
}
|
|
142
|
-
async function serverSideGate(apiKey, toolName) {
|
|
143
|
-
if (!HELM_AUTH_SERVER_URL) {
|
|
144
|
-
return null;
|
|
145
|
-
}
|
|
146
|
-
const cached = getCached(apiKey, toolName);
|
|
147
|
-
if (cached !== null) {
|
|
148
|
-
if (!cached.allowed) {
|
|
149
|
-
return `Access denied by Helm Auth Server (plan: ${cached.planTier}, cached)`;
|
|
150
|
-
}
|
|
151
|
-
return null;
|
|
152
|
-
}
|
|
153
|
-
if (!circuitCanAttempt()) {
|
|
154
|
-
return "Helm Auth Server is temporarily unreachable. Access denied (fail-closed). Please retry in a few minutes.";
|
|
155
|
-
}
|
|
156
|
-
try {
|
|
157
|
-
const resp = await callAuthServer(HELM_AUTH_SERVER_URL, apiKey, toolName);
|
|
158
|
-
const ttl = typeof resp.cache_ttl_ms === "number" && resp.cache_ttl_ms > 0 ? resp.cache_ttl_ms : 3e5;
|
|
159
|
-
setCached(apiKey, toolName, resp.allowed, resp.plan_tier ?? "unknown", ttl);
|
|
160
|
-
circuitRecordSuccess();
|
|
161
|
-
if (!resp.allowed) {
|
|
162
|
-
const reason = resp.reason ? ` Reason: ${resp.reason}` : "";
|
|
163
|
-
return `Access denied by Helm Auth Server (plan: ${resp.plan_tier}).${reason}`;
|
|
164
|
-
}
|
|
165
|
-
return null;
|
|
166
|
-
} catch {
|
|
167
|
-
circuitRecordFailure();
|
|
168
|
-
return "Helm Auth Server is unreachable. Access denied (fail-closed). Please retry later.";
|
|
34
|
+
function checkRateLimit(apiKey, clientIp) {
|
|
35
|
+
if (apiKey && apiKey.trim().length > 0) {
|
|
36
|
+
return { allowed: true, remaining: -1, tier: "paid" };
|
|
169
37
|
}
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
return { valid: false, error: "Invalid key format" };
|
|
177
|
-
}
|
|
178
|
-
if (HELM_PUBLIC_KEY_B64 === "PLACEHOLDER_REPLACE_BEFORE_DEPLOY") {
|
|
179
|
-
if (process.env.HELM_DEV_MODE === "true") {
|
|
180
|
-
return { valid: true, tier: 0, orgId: "dev-mode" };
|
|
181
|
-
}
|
|
182
|
-
return { valid: false, error: "Server misconfiguration: HELM_PUBLIC_KEY not set" };
|
|
38
|
+
const bucketKey = `ip:${clientIp}`;
|
|
39
|
+
const now = Date.now();
|
|
40
|
+
let entry = buckets.get(bucketKey);
|
|
41
|
+
if (!entry || now >= entry.resetAt) {
|
|
42
|
+
entry = { count: 0, resetAt: nextMidnightUtc() };
|
|
43
|
+
buckets.set(bucketKey, entry);
|
|
183
44
|
}
|
|
184
|
-
|
|
185
|
-
const parts = jwt.split(".");
|
|
186
|
-
if (parts.length !== 3) {
|
|
187
|
-
return { valid: false, error: "Malformed JWT" };
|
|
188
|
-
}
|
|
189
|
-
try {
|
|
190
|
-
const payload = JSON.parse(
|
|
191
|
-
Buffer.from(parts[1], "base64url").toString("utf8")
|
|
192
|
-
);
|
|
193
|
-
if (typeof payload.exp !== "number" || payload.exp < Math.floor(Date.now() / 1e3)) {
|
|
194
|
-
return { valid: false, error: "Key expired" };
|
|
195
|
-
}
|
|
196
|
-
if (payload.iss !== "helm-protocol") {
|
|
197
|
-
return { valid: false, error: "Invalid issuer" };
|
|
198
|
-
}
|
|
199
|
-
const publicKey = (0, import_crypto.createPublicKey)({
|
|
200
|
-
key: Buffer.from(HELM_PUBLIC_KEY_B64, "base64"),
|
|
201
|
-
format: "der",
|
|
202
|
-
type: "spki"
|
|
203
|
-
});
|
|
204
|
-
const sigInput = Buffer.from(`${parts[0]}.${parts[1]}`);
|
|
205
|
-
const sig = Buffer.from(parts[2], "base64url");
|
|
206
|
-
const isValid = (0, import_crypto.verify)(null, sigInput, publicKey, sig);
|
|
207
|
-
if (!isValid) {
|
|
208
|
-
return { valid: false, error: "Invalid signature" };
|
|
209
|
-
}
|
|
210
|
-
if (opts?.redisClient && payload.type === "short" && payload.jti) {
|
|
211
|
-
const redisKey = `helm:jti:${payload.jti}`;
|
|
212
|
-
const ttl = Math.max(
|
|
213
|
-
1,
|
|
214
|
-
payload.exp - Math.floor(Date.now() / 1e3)
|
|
215
|
-
);
|
|
216
|
-
const isNew = await opts.redisClient.set(redisKey, "1", {
|
|
217
|
-
NX: true,
|
|
218
|
-
EX: ttl
|
|
219
|
-
});
|
|
220
|
-
if (isNew === null) {
|
|
221
|
-
opts.onReplayDetected?.(payload.jti);
|
|
222
|
-
return { valid: false, error: "Replay detected" };
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
if (opts?.toolName) {
|
|
226
|
-
const gateError = await serverSideGate(apiKey, opts.toolName);
|
|
227
|
-
if (gateError !== null) {
|
|
228
|
-
return { valid: false, error: gateError };
|
|
229
|
-
}
|
|
230
|
-
}
|
|
45
|
+
if (entry.count >= FREE_TIER_LIMIT) {
|
|
231
46
|
return {
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
47
|
+
allowed: false,
|
|
48
|
+
remaining: 0,
|
|
49
|
+
tier: "free"
|
|
235
50
|
};
|
|
236
|
-
} catch {
|
|
237
|
-
return { valid: false, error: "Verification error" };
|
|
238
51
|
}
|
|
52
|
+
entry.count += 1;
|
|
53
|
+
return {
|
|
54
|
+
allowed: true,
|
|
55
|
+
remaining: FREE_TIER_LIMIT - entry.count,
|
|
56
|
+
tier: "free"
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
function resolveApiKey(headerValue) {
|
|
60
|
+
return headerValue?.trim() || process.env.TTT_API_KEY?.trim() || void 0;
|
|
239
61
|
}
|
|
240
|
-
const AUTH_ERROR_MESSAGE = `This tool requires a Helm Protocol API key.
|
|
241
|
-
|
|
242
|
-
To get access:
|
|
243
|
-
1. Contact enterprise@helmprotocol.io
|
|
244
|
-
2. Add the key to your MCP config:
|
|
245
|
-
{ "env": { "HELM_API_KEY": "hk-..." } }
|
|
246
|
-
|
|
247
|
-
Optional server-side validation:
|
|
248
|
-
Set HELM_AUTH_SERVER_URL=https://auth.helm-protocol.com for additional
|
|
249
|
-
server-side gate (quota enforcement, revocation, plan tier checks).
|
|
250
|
-
HTTPS is required \u2014 HTTP URLs are rejected at startup.
|
|
251
|
-
|
|
252
|
-
Free tools available without a key: pot_health, pot_stats`;
|
|
253
62
|
// Annotate the CommonJS export names for ESM import in node:
|
|
254
63
|
0 && (module.exports = {
|
|
255
|
-
|
|
256
|
-
|
|
64
|
+
checkRateLimit,
|
|
65
|
+
resolveApiKey
|
|
257
66
|
});
|