@helm-protocol/ttt-mcp 0.2.2 → 0.3.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 +185 -179
- package/dist/index.js +166 -167
- package/dist/tools.js +277 -9
- package/package.json +23 -3
package/README.md
CHANGED
|
@@ -4,291 +4,298 @@
|
|
|
4
4
|
|
|
5
5
|
**MCP Server for OpenTTT — Proof of Time tools for AI agents**
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
> Who was first?
|
|
9
|
-
>
|
|
10
|
-
> OpenTTT answers this with cryptographic Proof of Time — synthesized from
|
|
11
|
-
> multiple independent time sources, verified through GRG integrity shards,
|
|
12
|
-
> and signed with Ed25519 for non-repudiation.
|
|
7
|
+
---
|
|
13
8
|
|
|
14
|
-
##
|
|
9
|
+
## The Problem: Workflow Amnesia
|
|
15
10
|
|
|
16
|
-
|
|
17
|
-
|
|
11
|
+
Large Claude Code workflows — 20-agent Dynamic Workflows, multi-day multi-session projects, 100K+ token contexts — all face the same failure mode: **context compression erases action history.**
|
|
12
|
+
|
|
13
|
+
Agent B has no memory of what Agent A decided. Agent A resumes after compression with no record of its own prior steps. Duplicate work. Lost decisions. State corruption.
|
|
14
|
+
|
|
15
|
+
**ttt-mcp is the external nervous system that survives context compression.**
|
|
16
|
+
|
|
17
|
+
Every workflow step is anchored to a cryptographic timestamp on an **external server** — physically separate from Claude's context window. When compression happens, agents query their exact action history through the MCP tools and resume with full causal context.
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
Claude workflow → [context compressed] → agents call pot_query(eventId)
|
|
21
|
+
→ external server returns full timeline
|
|
22
|
+
→ workflow resumes, zero lost state
|
|
18
23
|
```
|
|
19
24
|
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Mathematical Guarantee
|
|
28
|
+
|
|
29
|
+
| Layer | Mechanism | Guarantee |
|
|
30
|
+
|-------|-----------|-----------|
|
|
31
|
+
| **Identity** | SHA-3 eventId (256-bit) | Collision probability 2⁻²⁵⁶ ≈ 0 — practically 100% exact step recall |
|
|
32
|
+
| **Ordering** | TTTPS causal timestamps | Total order on events — tamper-proof sequence proof |
|
|
33
|
+
| **Causal chain** | prevEventId DAG | O(depth) traversal — depth ~100 for 1B-token workflows |
|
|
34
|
+
| **Fingerprint** | Multi-layer cryptographic pipeline | Formally bounded tamper-evident step identity |
|
|
35
|
+
| **Non-repudiation** | Ed25519 signature | Cryptographic proof of who acted when |
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## Quick Start
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
# Claude Desktop
|
|
20
43
|
```json
|
|
21
|
-
// claude_desktop_config.json
|
|
22
44
|
{
|
|
23
45
|
"mcpServers": {
|
|
24
46
|
"ttt": {
|
|
25
47
|
"command": "npx",
|
|
26
|
-
"args": ["@helm-protocol/ttt-mcp"]
|
|
48
|
+
"args": ["-y", "@helm-protocol/ttt-mcp"]
|
|
27
49
|
}
|
|
28
50
|
}
|
|
29
51
|
}
|
|
30
52
|
```
|
|
31
53
|
|
|
32
|
-
|
|
54
|
+
Add `TTT_API_KEY` for unlimited calls (free tier: 100 calls/day per IP).
|
|
55
|
+
|
|
56
|
+
---
|
|
33
57
|
|
|
34
58
|
## Tools
|
|
35
59
|
|
|
36
60
|
| Tool | Description |
|
|
37
61
|
|------|-------------|
|
|
38
|
-
| `pot_generate` |
|
|
39
|
-
| `pot_verify` | Verify a Proof of Time using its hash and
|
|
40
|
-
| `pot_query` |
|
|
62
|
+
| `pot_generate` | Stamp a workflow step with eventId + prevEventId (builds causal chain) |
|
|
63
|
+
| `pot_verify` | Verify a Proof of Time using its hash and integrity shards |
|
|
64
|
+
| `pot_query` | O(1) exact lookup by eventId — call this after context compression |
|
|
65
|
+
| `pot_graph` | Traverse full causal DAG — backward + forward chain from any step |
|
|
41
66
|
| `pot_stats` | Get turbo/full mode statistics for a time period |
|
|
42
|
-
| `pot_health` | Check system health: time sources,
|
|
67
|
+
| `pot_health` | Check system health: time sources, uptime, current mode |
|
|
68
|
+
| `pot_checkpoint` | Create a compressed rollup checkpoint of workflow history |
|
|
69
|
+
|
|
70
|
+
---
|
|
43
71
|
|
|
44
72
|
## Tool Parameters
|
|
45
73
|
|
|
46
74
|
### pot_generate
|
|
47
75
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
| Parameter | Type | Required | Description |
|
|
51
|
-
|-----------|------|----------|-------------|
|
|
52
|
-
| txHash | string | Yes | Transaction hash (hex with 0x prefix) |
|
|
53
|
-
| chainId | number | Yes | Chain ID (e.g. 8453 for Base, 84532 for Base Sepolia) |
|
|
54
|
-
| poolAddress | string | Yes | DEX pool contract address |
|
|
55
|
-
|
|
56
|
-
### pot_verify
|
|
57
|
-
|
|
58
|
-
Verify a Proof of Time using its hash and GRG shards. Returns validity, mode (turbo/full), and timestamp.
|
|
76
|
+
Stamp a workflow step with a cryptographic timestamp. For Claude Code: use `eventId` + `prevEventId`. For DeFi: use `txHash` + `chainId` + `poolAddress`. One of `eventId` or `txHash` is required.
|
|
59
77
|
|
|
60
78
|
| Parameter | Type | Required | Description |
|
|
61
79
|
|-----------|------|----------|-------------|
|
|
62
|
-
|
|
|
63
|
-
|
|
|
64
|
-
|
|
|
65
|
-
|
|
|
80
|
+
| eventId | string | Either/or | Workflow step identifier. E.g. `"refactor_auth_step1"` |
|
|
81
|
+
| prevEventId | string | No | Previous step's eventId — links steps into a causal chain |
|
|
82
|
+
| txHash | string | Either/or | Transaction hash (DeFi, hex with 0x prefix) |
|
|
83
|
+
| chainId | number | No | EVM chain ID (DeFi) |
|
|
84
|
+
| poolAddress | string | No | DEX pool contract address (DeFi) |
|
|
66
85
|
|
|
67
86
|
### pot_query
|
|
68
87
|
|
|
69
|
-
Query Proof of Time
|
|
88
|
+
Query Proof of Time records. Use `eventId` for O(1) exact lookup after context compression.
|
|
70
89
|
|
|
71
90
|
| Parameter | Type | Required | Description |
|
|
72
91
|
|-----------|------|----------|-------------|
|
|
92
|
+
| eventId | string | No | Exact step lookup — collision probability 2⁻²⁵⁶ |
|
|
73
93
|
| startTime | number | No | Start time (unix ms). Default: 24h ago |
|
|
74
94
|
| endTime | number | No | End time (unix ms). Default: now |
|
|
75
95
|
| limit | number | No | Max entries to return. Default: 100, max: 1000 |
|
|
76
96
|
|
|
77
|
-
###
|
|
97
|
+
### pot_graph
|
|
78
98
|
|
|
79
|
-
|
|
99
|
+
Traverse the causal chain from any step. Returns backward chain (ancestors) and forward chain (descendants).
|
|
80
100
|
|
|
81
101
|
| Parameter | Type | Required | Description |
|
|
82
102
|
|-----------|------|----------|-------------|
|
|
83
|
-
|
|
|
84
|
-
|
|
85
|
-
### pot_health
|
|
103
|
+
| eventId | string | Yes | Step to traverse from |
|
|
104
|
+
| depth | number | No | Max backward depth. Default: 10, max: 100 |
|
|
86
105
|
|
|
87
|
-
|
|
106
|
+
### pot_verify
|
|
88
107
|
|
|
89
108
|
| Parameter | Type | Required | Description |
|
|
90
109
|
|-----------|------|----------|-------------|
|
|
91
|
-
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
```typescript
|
|
96
|
-
// In your AI agent's tool call:
|
|
97
|
-
const pot = await pot_generate({
|
|
98
|
-
txHash: "0xabc123...",
|
|
99
|
-
chainId: 84532,
|
|
100
|
-
poolAddress: "0xdef456..."
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
// pot.potHash — unique Proof of Time hash
|
|
104
|
-
// pot.grgShards — GRG integrity shards for verification
|
|
105
|
-
// pot.timestamp — synthesized nanosecond timestamp
|
|
106
|
-
// pot.mode — "turbo" (honest) or "full" (requires full verification)
|
|
107
|
-
|
|
108
|
-
const verification = await pot_verify({
|
|
109
|
-
potHash: pot.potHash,
|
|
110
|
-
grgShards: pot.grgShards
|
|
111
|
-
});
|
|
112
|
-
// verification.valid — true if integrity shards reconstruct correctly
|
|
113
|
-
```
|
|
110
|
+
| potHash | string | Yes | PoT hash to verify (hex with 0x prefix) |
|
|
111
|
+
| grgShards | string[] | Yes | Array of hex-encoded cryptographic integrity shards |
|
|
112
|
+
| chainId | number | Yes | EVM chain ID |
|
|
113
|
+
| poolAddress | string | Yes | Uniswap V4 pool address |
|
|
114
114
|
|
|
115
|
-
|
|
115
|
+
### pot_stats
|
|
116
116
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
4. **Adaptive Mode** — Honest builders get `turbo` mode (fast, profitable); tampered sequences get `full` mode (slow, costly) — natural economic selection
|
|
117
|
+
| Parameter | Type | Required | Description |
|
|
118
|
+
|-----------|------|----------|-------------|
|
|
119
|
+
| period | `"day"` \| `"week"` \| `"month"` | Yes | Time period for statistics |
|
|
121
120
|
|
|
122
|
-
|
|
121
|
+
### pot_health
|
|
123
122
|
|
|
124
|
-
|
|
123
|
+
No parameters.
|
|
125
124
|
|
|
126
|
-
|
|
127
|
-
{
|
|
128
|
-
"mcpServers": {
|
|
129
|
-
"ttt": {
|
|
130
|
-
"command": "npx",
|
|
131
|
-
"args": ["@helm-protocol/ttt-mcp"]
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
```
|
|
125
|
+
### pot_checkpoint
|
|
136
126
|
|
|
137
|
-
|
|
138
|
-
- **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
|
|
139
|
-
- **Windows**: `%APPDATA%\Claude\claude_desktop_config.json`
|
|
140
|
-
- **Linux**: `~/.config/Claude/claude_desktop_config.json`
|
|
127
|
+
Creates a compressed rollup checkpoint of workflow history.
|
|
141
128
|
|
|
142
|
-
|
|
129
|
+
**Use when:** Approaching context limit, before long tasks, or every ~100 events.
|
|
143
130
|
|
|
144
|
-
|
|
145
|
-
|
|
131
|
+
| Parameter | Type | Required | Description |
|
|
132
|
+
|-----------|------|----------|-------------|
|
|
133
|
+
| fromEventId | string | No | Start of range — first eventId in the causal chain to include |
|
|
134
|
+
| toEventId | string | No | End of range — last eventId in the causal chain to include |
|
|
135
|
+
| startTime | number | No | Unix ms. Default: 1 hour ago |
|
|
136
|
+
| endTime | number | No | Unix ms. Default: now |
|
|
137
|
+
| maxTokens | number | No | Approximate max tokens for rollup output. Default: 2000 |
|
|
138
|
+
|
|
139
|
+
**Returns:**
|
|
140
|
+
- `checkpointId` — unique checkpoint identifier
|
|
141
|
+
- `rollupSummary` — compressed event history (depth-adaptive: full/compact/minimal/rollup)
|
|
142
|
+
- `chainIntact` — whether the causal chain is unbroken
|
|
143
|
+
- `nextCheckpointHint` — recommended events before next checkpoint
|
|
144
|
+
|
|
145
|
+
**Depth-adaptive compression:**
|
|
146
|
+
|
|
147
|
+
| Depth | Format | ~Tokens |
|
|
148
|
+
|-------|--------|---------|
|
|
149
|
+
| 1–5 | Full entry | ~200/event |
|
|
150
|
+
| 6–20 | Compact (id+hash+ts) | ~80/event |
|
|
151
|
+
| 21–50 | Minimal (id+ts) | ~30/event |
|
|
152
|
+
| 51+ | Rollup string | ~10/event |
|
|
146
153
|
|
|
147
154
|
---
|
|
148
155
|
|
|
149
156
|
## Use Cases
|
|
150
157
|
|
|
151
|
-
### 1.
|
|
158
|
+
### 1. Claude Code Workflow — Amnesia Prevention
|
|
152
159
|
|
|
153
|
-
**Problem**:
|
|
160
|
+
**Problem**: A 20-agent Dynamic Workflow refactors a 500K-line codebase over hours. After each context compression, agents have no memory of what they already processed. Duplicate work. State corruption.
|
|
154
161
|
|
|
155
|
-
**Solution**:
|
|
162
|
+
**Solution**: Each agent stamps its steps with `pot_generate(eventId, prevEventId)`. After compression, it calls `pot_query(eventId)` to recover its exact action history — what ran, when, in what order — from the external server. The server is outside Claude's context window; compression never touches it.
|
|
156
163
|
|
|
157
164
|
```typescript
|
|
158
|
-
//
|
|
159
|
-
const pot = await client.callTool({
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
165
|
+
// Agent starts a workflow step
|
|
166
|
+
const pot = await client.callTool({
|
|
167
|
+
name: "pot_generate",
|
|
168
|
+
arguments: {
|
|
169
|
+
eventId: "refactor_auth_module_step3",
|
|
170
|
+
prevEventId: "refactor_auth_module_step2"
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
// pot.potHash — cryptographic proof this step happened at this time
|
|
167
174
|
|
|
168
|
-
|
|
175
|
+
// After context compression, agent recovers its history:
|
|
176
|
+
const history = await client.callTool({
|
|
177
|
+
name: "pot_query",
|
|
178
|
+
arguments: { eventId: "refactor_auth_module_step3" }
|
|
179
|
+
});
|
|
180
|
+
// history.local[0] — exact record: timestamp, prevEventId, potHash
|
|
181
|
+
// history.found: true — O(1) lookup, collision probability 2⁻²⁵⁶
|
|
169
182
|
|
|
170
|
-
|
|
183
|
+
// Traverse full causal chain:
|
|
184
|
+
const chain = await client.callTool({
|
|
185
|
+
name: "pot_graph",
|
|
186
|
+
arguments: { eventId: "refactor_auth_module_step3", depth: 20 }
|
|
187
|
+
});
|
|
188
|
+
// chain.backwardChain — all ancestor steps in chronological order
|
|
189
|
+
// chain.forwardChain — steps that follow this one
|
|
190
|
+
```
|
|
171
191
|
|
|
172
|
-
**
|
|
192
|
+
**Before a long task or every ~100 events — create a checkpoint:**
|
|
173
193
|
|
|
174
194
|
```typescript
|
|
175
|
-
//
|
|
176
|
-
const
|
|
177
|
-
|
|
178
|
-
|
|
195
|
+
// Compress workflow history before context fills up — by causal range:
|
|
196
|
+
const checkpoint = await client.callTool({
|
|
197
|
+
name: "pot_checkpoint",
|
|
198
|
+
arguments: {
|
|
199
|
+
fromEventId: "refactor_auth_module_step1",
|
|
200
|
+
toEventId: "refactor_auth_module_step3"
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
// checkpoint.checkpointId — store this; resume from it after compression
|
|
204
|
+
// checkpoint.rollupSummary — depth-adaptive compressed history (10–200 tokens/event)
|
|
205
|
+
// checkpoint.chainIntact: true — causal chain verified unbroken
|
|
206
|
+
// checkpoint.nextCheckpointHint: 87 — suggested events before next checkpoint
|
|
207
|
+
|
|
208
|
+
// Or compress by time window with a token budget:
|
|
209
|
+
const checkpoint = await client.callTool({
|
|
210
|
+
name: "pot_checkpoint",
|
|
211
|
+
arguments: {
|
|
212
|
+
startTime: Date.now() - 3_600_000, // last 1 hour
|
|
213
|
+
maxTokens: 1500
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
// After context compression, restore from checkpoint instead of re-querying all events:
|
|
218
|
+
const history = await client.callTool({
|
|
219
|
+
name: "pot_query",
|
|
220
|
+
arguments: { eventId: checkpoint.checkpointId }
|
|
221
|
+
});
|
|
222
|
+
// Full causal context restored in a single call
|
|
179
223
|
```
|
|
180
224
|
|
|
181
|
-
**Outcome**:
|
|
225
|
+
**Outcome**: Zero duplicate work. Full workflow timeline recoverable even after complete context resets.
|
|
182
226
|
|
|
183
227
|
---
|
|
184
228
|
|
|
185
|
-
###
|
|
229
|
+
### 2. MEV Bot — Transaction Ordering Proof
|
|
186
230
|
|
|
187
|
-
**Problem**:
|
|
231
|
+
**Problem**: You got front-run. You can't prove it — mempool timestamps are per-node, unsigned, non-authoritative.
|
|
188
232
|
|
|
189
|
-
**Solution**: `pot_generate`
|
|
233
|
+
**Solution**: Call `pot_generate` before every submission. The PoT receipt is cryptographically signed by three independent time sources (NIST, Google, Cloudflare), anchored on Base Sepolia TTT ERC-1155. If front-running occurs, you have a timestamped, on-chain record predating the attacker's block inclusion.
|
|
190
234
|
|
|
191
235
|
```typescript
|
|
192
|
-
const
|
|
236
|
+
const pot = await client.callTool({
|
|
193
237
|
name: "pot_generate",
|
|
194
|
-
arguments: { txHash:
|
|
238
|
+
arguments: { txHash: pendingTxHash, chainId: 8453, poolAddress: "0x..." }
|
|
195
239
|
});
|
|
196
|
-
//
|
|
197
|
-
// audit.uncertainty: +/- ms bound (required field in RTS 25 record)
|
|
198
|
-
// audit.confidence: fraction of sources that agreed
|
|
199
|
-
// audit.ed25519_sig: non-repudiation signature
|
|
200
|
-
// Export to your compliance system — same format, every trade
|
|
240
|
+
// pot.potHash — your evidence, timestamped by NIST+Google+Cloudflare
|
|
201
241
|
```
|
|
202
242
|
|
|
203
|
-
**Outcome**: MiFIR-grade audit trail at ~$0.04/1K calls (DEX tier). Replaces $50K+ hardware setup. IETF standardized via `draft-helmprotocol-tttps-00`.
|
|
204
|
-
|
|
205
243
|
---
|
|
206
244
|
|
|
207
|
-
###
|
|
245
|
+
### 3. DEX Protocol — Sandwich Deterrence
|
|
208
246
|
|
|
209
|
-
**
|
|
210
|
-
|
|
211
|
-
**Solution**: Stamp every LP action (add liquidity, remove liquidity, fee harvest) with a PoT receipt. The receipt chain creates an unforgeable causal timeline: each action's potHash references the previous, anchored on Base Sepolia. Legally defensible for tax documentation, insurance claims, and protocol dispute resolution.
|
|
212
|
-
|
|
213
|
-
```typescript
|
|
214
|
-
// On liquidity add
|
|
215
|
-
const entryPot = await client.callTool({ name: "pot_generate", arguments: { txHash: addLiqTx, chainId: 8453 } });
|
|
216
|
-
|
|
217
|
-
// On liquidity remove
|
|
218
|
-
const exitPot = await client.callTool({ name: "pot_generate", arguments: { txHash: removeLiqTx, chainId: 8453 } });
|
|
219
|
-
|
|
220
|
-
// Verify the causal chain
|
|
221
|
-
const chain = await client.callTool({ name: "pot_verify", arguments: { potHash: exitPot.potHash, precedingHash: entryPot.potHash } });
|
|
222
|
-
// chain.valid: true means exit cryptographically followed entry
|
|
223
|
-
```
|
|
247
|
+
**Solution**: Integrate `TTTHookSimple` (Uniswap V4 hook, Base Sepolia: `0x8C633b05b833a476925F7d9818da6E215760F2c7`). Honest builders get `turbo` mode. Tampered sequences get `full` mode (exponential backoff). Economics, not governance.
|
|
224
248
|
|
|
225
249
|
---
|
|
226
250
|
|
|
227
|
-
###
|
|
251
|
+
### 4. Hedge Fund / Prop Desk — MiFIR Art.22c Compliance
|
|
228
252
|
|
|
229
|
-
**Problem**:
|
|
253
|
+
**Problem**: MiFIR Article 22c / RTS 25 requires microsecond-precision UTC-synchronized timestamps. Hardware PTP appliances cost $50K–$500K.
|
|
230
254
|
|
|
231
|
-
**Solution**:
|
|
255
|
+
**Solution**: `pot_generate` produces an Ed25519-signed timestamp with uncertainty bound and multi-source attestation. Structurally compatible with RTS 25 audit record requirements. One API call per trade.
|
|
232
256
|
|
|
233
257
|
```typescript
|
|
234
|
-
|
|
235
|
-
const signalPot = await client.callTool({ name: "pot_generate", arguments: { txHash: signalId } });
|
|
236
|
-
|
|
237
|
-
// Agent B (executor) — references Agent A's pot
|
|
238
|
-
const execPot = await client.callTool({
|
|
258
|
+
const audit = await client.callTool({
|
|
239
259
|
name: "pot_generate",
|
|
240
|
-
arguments: { txHash:
|
|
260
|
+
arguments: { txHash: tradeHash, chainId: 8453 }
|
|
241
261
|
});
|
|
242
|
-
|
|
243
|
-
//
|
|
244
|
-
|
|
262
|
+
// audit.timestamp: nanosecond precision
|
|
263
|
+
// audit.uncertainty: ±ms bound (RTS 25 required field)
|
|
264
|
+
// audit.confidence: fraction of sources that agreed
|
|
245
265
|
```
|
|
246
266
|
|
|
247
|
-
**Outcome**:
|
|
267
|
+
**Outcome**: MiFIR-grade audit trail. IETF standardized via `draft-helmprotocol-tttps-00`.
|
|
248
268
|
|
|
249
269
|
---
|
|
250
270
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
```typescript
|
|
254
|
-
import { McpClient } from "@modelcontextprotocol/sdk/client/mcp.js";
|
|
255
|
-
|
|
256
|
-
// Generate a Proof of Time for a transaction
|
|
257
|
-
const result = await client.callTool({
|
|
258
|
-
name: "pot_generate",
|
|
259
|
-
arguments: {
|
|
260
|
-
txHash: "0xabc123...",
|
|
261
|
-
chainId: 8453,
|
|
262
|
-
poolAddress: "0xdef456..."
|
|
263
|
-
}
|
|
264
|
-
});
|
|
265
|
-
// Returns: { potHash, timestamp, stratum, grg_shards }
|
|
266
|
-
```
|
|
271
|
+
### 5. Multi-Agent Coordination — Causal Order Proof
|
|
267
272
|
|
|
268
|
-
|
|
273
|
+
**Problem**: When multiple AI agents interact in a pipeline, the causal order matters for debugging and audit. Agent logs are unverifiable.
|
|
269
274
|
|
|
270
|
-
|
|
271
|
-
import subprocess, json
|
|
275
|
+
**Solution**: Each agent stamps its action with `pot_generate`. The potHash chain is independently verifiable. `pot_graph` reconstructs who did what and in what order.
|
|
272
276
|
|
|
273
|
-
|
|
274
|
-
["npx", "-y", "@helm-protocol/ttt-mcp"],
|
|
275
|
-
input=json.dumps({
|
|
276
|
-
"tool": "pot_verify",
|
|
277
|
-
"potHash": "0x...",
|
|
278
|
-
"expectedChainId": 8453
|
|
279
|
-
}),
|
|
280
|
-
capture_output=True, text=True
|
|
281
|
-
)
|
|
282
|
-
```
|
|
277
|
+
---
|
|
283
278
|
|
|
284
279
|
## Rate Limits & Pricing
|
|
285
280
|
|
|
286
281
|
```
|
|
287
282
|
Free Tier: 100 calls/day per IP — no API key needed
|
|
288
|
-
|
|
289
|
-
|
|
283
|
+
BOT Tier: $199/mo — unlimited, SLA
|
|
284
|
+
DEX Tier: $499/mo — unlimited, priority support
|
|
285
|
+
FUND Tier: $2K+/mo — enterprise, dedicated infra
|
|
290
286
|
```
|
|
291
287
|
|
|
288
|
+
Contact: heime.jorgen@proton.me
|
|
289
|
+
|
|
290
|
+
---
|
|
291
|
+
|
|
292
|
+
## Requirements
|
|
293
|
+
|
|
294
|
+
- Node.js >= 18
|
|
295
|
+
- Network access for time synthesis (HTTPS to time.nist.gov, time.google.com, time.cloudflare.com)
|
|
296
|
+
|
|
297
|
+
---
|
|
298
|
+
|
|
292
299
|
## Learn More
|
|
293
300
|
|
|
294
301
|
- [OpenTTT SDK](https://www.npmjs.com/package/openttt) — The underlying SDK
|
|
@@ -299,7 +306,6 @@ Commercial: peter@kenosian.com (hedge funds, DEX protocols, OTC desks)
|
|
|
299
306
|
|
|
300
307
|
BSL-1.1 — free for non-commercial use.
|
|
301
308
|
|
|
302
|
-
**Commercial use** (production bots, hedge funds, prop desks) requires a license.
|
|
303
|
-
→ [kenosian.com/pricing](https://kenosian.com/pricing)
|
|
309
|
+
**Commercial use** (production bots, hedge funds, prop desks) requires a license.
|
|
304
310
|
|
|
305
311
|
Change Date: 2029-05-28 → Apache 2.0
|
package/dist/index.js
CHANGED
|
@@ -7,188 +7,183 @@ var import_http = require("http");
|
|
|
7
7
|
var import_zod = require("zod");
|
|
8
8
|
var import_tools = require("./tools");
|
|
9
9
|
var import_auth = require("./auth");
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
isError: true
|
|
36
|
-
};
|
|
10
|
+
async function restoreDAGFromRedis() {
|
|
11
|
+
try {
|
|
12
|
+
await Promise.race([
|
|
13
|
+
import_tools.redis.connect(),
|
|
14
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error("timeout")), 3e3))
|
|
15
|
+
]);
|
|
16
|
+
} catch {
|
|
17
|
+
return 0;
|
|
18
|
+
}
|
|
19
|
+
let cursor = "0";
|
|
20
|
+
let restored = 0;
|
|
21
|
+
const entries = [];
|
|
22
|
+
try {
|
|
23
|
+
do {
|
|
24
|
+
const [next, keys] = await import_tools.redis.scan(cursor, "MATCH", "dag:*", "COUNT", "200");
|
|
25
|
+
cursor = next;
|
|
26
|
+
if (keys.length === 0) continue;
|
|
27
|
+
const values = await import_tools.redis.mget(...keys);
|
|
28
|
+
for (const v of values) {
|
|
29
|
+
if (!v) continue;
|
|
30
|
+
try {
|
|
31
|
+
const entry = JSON.parse(v);
|
|
32
|
+
if (entry.eventId) entries.push(entry);
|
|
33
|
+
} catch {
|
|
34
|
+
}
|
|
37
35
|
}
|
|
38
|
-
}
|
|
36
|
+
} while (cursor !== "0");
|
|
37
|
+
} catch {
|
|
38
|
+
return 0;
|
|
39
|
+
}
|
|
40
|
+
entries.sort((a, b) => a.createdAt - b.createdAt);
|
|
41
|
+
for (const e of entries) {
|
|
39
42
|
try {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
43
|
+
(0, import_tools.restoreDagEntry)({
|
|
44
|
+
eventId: e.eventId,
|
|
45
|
+
prevEventId: e.prevEventId,
|
|
46
|
+
potHash: e.potHash,
|
|
47
|
+
timestamp: e.timestamp,
|
|
48
|
+
stratum: e.stratum,
|
|
49
|
+
mode: e.mode,
|
|
50
|
+
createdAt: e.createdAt,
|
|
51
|
+
chainId: e.chainId,
|
|
52
|
+
poolAddress: e.poolAddress
|
|
53
|
+
});
|
|
54
|
+
restored++;
|
|
55
|
+
} catch {
|
|
50
56
|
}
|
|
51
57
|
}
|
|
52
|
-
)
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
};
|
|
58
|
+
if (restored > 0) {
|
|
59
|
+
console.error(`[ttt-mcp] DAG restored from Redis: ${restored} entries`);
|
|
60
|
+
}
|
|
61
|
+
return restored;
|
|
62
|
+
}
|
|
63
|
+
function buildMcpServer() {
|
|
64
|
+
const s = new import_mcp.McpServer({ name: "ttt-mcp", version: "0.1.0" });
|
|
65
|
+
s.tool(
|
|
66
|
+
"pot_generate",
|
|
67
|
+
"Generate a cryptographic Proof of Time timestamp. For Claude Code workflows: use eventId + prevEventId to build a causal chain. For DeFi: use txHash + chainId + poolAddress. Either eventId or txHash is required.",
|
|
68
|
+
{
|
|
69
|
+
eventId: import_zod.z.string().optional().describe("Workflow step identifier (Claude Code). E.g. 'refactor_auth_step1'"),
|
|
70
|
+
prevEventId: import_zod.z.string().optional().describe("Previous step's eventId \u2014 links steps into a causal chain"),
|
|
71
|
+
txHash: import_zod.z.string().optional().describe("Transaction hash (DeFi, hex with 0x prefix)"),
|
|
72
|
+
chainId: import_zod.z.number().optional().describe("EVM chain ID (DeFi, e.g. 8453 for Base)"),
|
|
73
|
+
poolAddress: import_zod.z.string().optional().describe("DEX pool contract address (DeFi)")
|
|
74
|
+
},
|
|
75
|
+
async (args) => {
|
|
76
|
+
try {
|
|
77
|
+
const result = await (0, import_tools.potGenerate)(args);
|
|
78
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
79
|
+
} catch (err) {
|
|
80
|
+
return { content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
|
|
76
81
|
}
|
|
77
82
|
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
83
|
+
);
|
|
84
|
+
s.tool(
|
|
85
|
+
"pot_verify",
|
|
86
|
+
"Verify a Proof of Time using its hash and integrity shards. Returns validity, mode (turbo/full), and timestamp.",
|
|
87
|
+
{
|
|
88
|
+
potHash: import_zod.z.string().describe("PoT hash to verify (hex with 0x prefix)"),
|
|
89
|
+
grgShards: import_zod.z.array(import_zod.z.string()).describe("Array of hex-encoded cryptographic integrity shards"),
|
|
90
|
+
chainId: import_zod.z.number().describe("EVM chain ID (e.g. 84532 for Base Sepolia)"),
|
|
91
|
+
poolAddress: import_zod.z.string().describe("Uniswap V4 pool address (0x-prefixed)")
|
|
92
|
+
},
|
|
93
|
+
async (args) => {
|
|
94
|
+
try {
|
|
95
|
+
const result = await (0, import_tools.potVerify)(args);
|
|
96
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
97
|
+
} catch (err) {
|
|
98
|
+
return { content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
|
|
99
|
+
}
|
|
89
100
|
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
return {
|
|
106
|
-
content: [{ type: "text", text: JSON.stringify({
|
|
107
|
-
error: "rate_limit_exceeded",
|
|
108
|
-
message: `Free tier: ${import_auth.FREE_TIER_LIMIT} calls/day. Set TTT_API_KEY env var for unlimited access. \u2192 kenosian.com/pricing`,
|
|
109
|
-
remaining: 0,
|
|
110
|
-
tier: "free"
|
|
111
|
-
}) }],
|
|
112
|
-
isError: true
|
|
113
|
-
};
|
|
101
|
+
);
|
|
102
|
+
s.tool(
|
|
103
|
+
"pot_query",
|
|
104
|
+
"Query Proof of Time records. Use eventId for exact O(1) lookup of a specific workflow step (collision probability 2^-256). Use startTime/endTime for time-range queries.",
|
|
105
|
+
{
|
|
106
|
+
eventId: import_zod.z.string().optional().describe("Exact eventId lookup \u2014 call this at workflow start to restore action history after context compression"),
|
|
107
|
+
startTime: import_zod.z.number().optional().describe("Start time (unix ms). Default: 24h ago"),
|
|
108
|
+
endTime: import_zod.z.number().optional().describe("End time (unix ms). Default: now"),
|
|
109
|
+
limit: import_zod.z.number().optional().describe("Max entries to return. Default: 100, max: 1000")
|
|
110
|
+
},
|
|
111
|
+
async (args) => {
|
|
112
|
+
try {
|
|
113
|
+
const result = await (0, import_tools.potQuery)(args);
|
|
114
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
115
|
+
} catch (err) {
|
|
116
|
+
return { content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
|
|
114
117
|
}
|
|
115
118
|
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
119
|
+
);
|
|
120
|
+
s.tool(
|
|
121
|
+
"pot_graph",
|
|
122
|
+
"Traverse the causal chain of workflow steps. Given an eventId, returns the full backward chain (ancestors via prevEventId) and forward chain (steps that follow). Use after context compression to reconstruct the complete workflow timeline.",
|
|
123
|
+
{
|
|
124
|
+
eventId: import_zod.z.string().describe("The workflow step to start traversal from"),
|
|
125
|
+
depth: import_zod.z.number().optional().describe("Max backward traversal depth. Default: 10, max: 100")
|
|
126
|
+
},
|
|
127
|
+
async (args) => {
|
|
128
|
+
try {
|
|
129
|
+
const result = await (0, import_tools.potGraph)(args);
|
|
130
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
131
|
+
} catch (err) {
|
|
132
|
+
return { content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
|
|
133
|
+
}
|
|
127
134
|
}
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
const rl = (0, import_auth.checkRateLimit)(apiKey, "stdio");
|
|
140
|
-
if (!rl.allowed) {
|
|
141
|
-
return {
|
|
142
|
-
content: [{ type: "text", text: JSON.stringify({
|
|
143
|
-
error: "rate_limit_exceeded",
|
|
144
|
-
message: `Free tier: ${import_auth.FREE_TIER_LIMIT} calls/day. Set TTT_API_KEY env var for unlimited access. \u2192 kenosian.com/pricing`,
|
|
145
|
-
remaining: 0,
|
|
146
|
-
tier: "free"
|
|
147
|
-
}) }],
|
|
148
|
-
isError: true
|
|
149
|
-
};
|
|
135
|
+
);
|
|
136
|
+
s.tool(
|
|
137
|
+
"pot_stats",
|
|
138
|
+
"Get PoT statistics: total swaps, turbo/full counts, and turbo ratio for a given period.",
|
|
139
|
+
{ period: import_zod.z.enum(["day", "week", "month"]).describe("Time period for statistics") },
|
|
140
|
+
async (args) => {
|
|
141
|
+
try {
|
|
142
|
+
const result = await (0, import_tools.potStats)(args);
|
|
143
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
144
|
+
} catch (err) {
|
|
145
|
+
return { content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
|
|
150
146
|
}
|
|
151
147
|
}
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
content: [{ type: "text", text:
|
|
161
|
-
|
|
162
|
-
|
|
148
|
+
);
|
|
149
|
+
s.tool(
|
|
150
|
+
"pot_health",
|
|
151
|
+
"Check PoT system health: time source status, subgraph sync, server uptime, and current mode.",
|
|
152
|
+
{},
|
|
153
|
+
async () => {
|
|
154
|
+
try {
|
|
155
|
+
const result = await (0, import_tools.potHealth)();
|
|
156
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
157
|
+
} catch (err) {
|
|
158
|
+
return { content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
|
|
159
|
+
}
|
|
163
160
|
}
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
isError: true
|
|
181
|
-
}
|
|
161
|
+
);
|
|
162
|
+
s.tool(
|
|
163
|
+
"pot_checkpoint",
|
|
164
|
+
"Create a compressed rollup checkpoint of workflow history. Call this periodically to prevent token explosion when recovering from context compression. Returns checkpointId, compressed event history, chainIntact status, and nextCheckpointHint (recommended events before next checkpoint).",
|
|
165
|
+
{
|
|
166
|
+
fromEventId: import_zod.z.string().optional().describe("Start of range by eventId (optional, use with toEventId)"),
|
|
167
|
+
toEventId: import_zod.z.string().optional().describe("End of range by eventId (optional, use with fromEventId)"),
|
|
168
|
+
startTime: import_zod.z.number().optional().describe("Unix ms start time (optional, default: 1h ago)"),
|
|
169
|
+
endTime: import_zod.z.number().optional().describe("Unix ms end time (optional, default: now)"),
|
|
170
|
+
maxTokens: import_zod.z.number().optional().describe("Approximate max tokens for rollup (default: 2000)")
|
|
171
|
+
},
|
|
172
|
+
async (args) => {
|
|
173
|
+
try {
|
|
174
|
+
const result = await (0, import_tools.potCheckpoint)(args);
|
|
175
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
176
|
+
} catch (err) {
|
|
177
|
+
return { content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
|
|
178
|
+
}
|
|
182
179
|
}
|
|
183
|
-
|
|
184
|
-
|
|
180
|
+
);
|
|
181
|
+
return s;
|
|
182
|
+
}
|
|
185
183
|
async function main() {
|
|
184
|
+
await restoreDAGFromRedis();
|
|
186
185
|
const port = process.env.PORT ? parseInt(process.env.PORT, 10) : null;
|
|
187
186
|
if (port) {
|
|
188
|
-
const transport = new import_streamableHttp.StreamableHTTPServerTransport({
|
|
189
|
-
sessionIdGenerator: void 0
|
|
190
|
-
});
|
|
191
|
-
await server.connect(transport);
|
|
192
187
|
const httpServer = (0, import_http.createServer)(async (req, res) => {
|
|
193
188
|
if (req.method === "GET" && (req.url === "/health" || req.url === "/ping")) {
|
|
194
189
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
@@ -209,7 +204,7 @@ async function main() {
|
|
|
209
204
|
res.end(
|
|
210
205
|
JSON.stringify({
|
|
211
206
|
error: "rate_limit_exceeded",
|
|
212
|
-
message: "Free tier
|
|
207
|
+
message: "Free tier: 100 calls/day reached. Contact heime.jorgen@proton.me for commercial access.",
|
|
213
208
|
tier: "free"
|
|
214
209
|
})
|
|
215
210
|
);
|
|
@@ -233,6 +228,9 @@ async function main() {
|
|
|
233
228
|
}
|
|
234
229
|
}
|
|
235
230
|
try {
|
|
231
|
+
const reqServer = buildMcpServer();
|
|
232
|
+
const transport = new import_streamableHttp.StreamableHTTPServerTransport({ sessionIdGenerator: void 0 });
|
|
233
|
+
await reqServer.connect(transport);
|
|
236
234
|
await transport.handleRequest(req, res);
|
|
237
235
|
} catch (err) {
|
|
238
236
|
if (!res.headersSent) {
|
|
@@ -245,8 +243,9 @@ async function main() {
|
|
|
245
243
|
console.error(`[ttt-mcp] OpenTTT MCP Server (HTTP) on port ${port}`);
|
|
246
244
|
});
|
|
247
245
|
} else {
|
|
246
|
+
const stdioServer = buildMcpServer();
|
|
248
247
|
const transport = new import_stdio.StdioServerTransport();
|
|
249
|
-
await
|
|
248
|
+
await stdioServer.connect(transport);
|
|
250
249
|
console.error("[ttt-mcp] OpenTTT MCP Server running on stdio");
|
|
251
250
|
}
|
|
252
251
|
}
|
package/dist/tools.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
2
3
|
var __defProp = Object.defineProperty;
|
|
3
4
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
5
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
8
|
var __export = (target, all) => {
|
|
7
9
|
for (var name in all)
|
|
@@ -15,24 +17,52 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
15
17
|
}
|
|
16
18
|
return to;
|
|
17
19
|
};
|
|
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
|
+
));
|
|
18
28
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
29
|
var tools_exports = {};
|
|
20
30
|
__export(tools_exports, {
|
|
31
|
+
potCheckpoint: () => potCheckpoint,
|
|
21
32
|
potGenerate: () => potGenerate,
|
|
33
|
+
potGraph: () => potGraph,
|
|
22
34
|
potHealth: () => potHealth,
|
|
23
35
|
potQuery: () => potQuery,
|
|
24
36
|
potStats: () => potStats,
|
|
25
|
-
potVerify: () => potVerify
|
|
37
|
+
potVerify: () => potVerify,
|
|
38
|
+
redis: () => redis,
|
|
39
|
+
restoreDagEntry: () => restoreDagEntry
|
|
26
40
|
});
|
|
27
41
|
module.exports = __toCommonJS(tools_exports);
|
|
28
42
|
var import_openttt = require("openttt");
|
|
29
43
|
var import_telemetry = require("./telemetry");
|
|
44
|
+
var import_ioredis = __toESM(require("ioredis"));
|
|
45
|
+
const GrgPipeline = (
|
|
46
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
47
|
+
require("openttt").GrgPipeline ?? null
|
|
48
|
+
);
|
|
49
|
+
const redis = new import_ioredis.default(process.env.REDIS_URL ?? "redis://127.0.0.1:6379", {
|
|
50
|
+
lazyConnect: true,
|
|
51
|
+
enableOfflineQueue: false,
|
|
52
|
+
maxRetriesPerRequest: 1
|
|
53
|
+
});
|
|
54
|
+
redis.on("error", () => {
|
|
55
|
+
});
|
|
30
56
|
const timeSynth = new import_openttt.TimeSynthesis();
|
|
31
57
|
const adaptiveSwitch = new import_openttt.AdaptiveSwitch();
|
|
32
58
|
const potSigner = new import_openttt.PotSigner();
|
|
33
59
|
const startedAt = Date.now();
|
|
34
60
|
const POT_LOG_MAX = 1e4;
|
|
35
61
|
const potLog = [];
|
|
62
|
+
const potByEventId = /* @__PURE__ */ new Map();
|
|
63
|
+
const potByPrevEventId = /* @__PURE__ */ new Map();
|
|
64
|
+
const evictedEventIds = /* @__PURE__ */ new Set();
|
|
65
|
+
const EVICTED_MAX = 1e3;
|
|
36
66
|
function bigintReplacer(_key, value) {
|
|
37
67
|
return typeof value === "bigint" ? value.toString() : value;
|
|
38
68
|
}
|
|
@@ -40,26 +70,181 @@ function serialize(obj) {
|
|
|
40
70
|
return JSON.parse(JSON.stringify(obj, bigintReplacer));
|
|
41
71
|
}
|
|
42
72
|
const SUBGRAPH_URL = "https://api.studio.thegraph.com/query/1744392/openttt-base-sepolia/v0.1.0";
|
|
73
|
+
function compressEntry(entry, depth, depthThreshold) {
|
|
74
|
+
const t = depthThreshold ?? { compact: 5, minimal: 20, rollup: 50 };
|
|
75
|
+
if (depth <= t.compact) return entry;
|
|
76
|
+
if (depth <= t.minimal) return {
|
|
77
|
+
// compact
|
|
78
|
+
eventId: entry.eventId,
|
|
79
|
+
potHash: entry.potHash,
|
|
80
|
+
timestamp: entry.timestamp,
|
|
81
|
+
prevEventId: entry.prevEventId
|
|
82
|
+
};
|
|
83
|
+
if (depth <= t.rollup) return {
|
|
84
|
+
// minimal
|
|
85
|
+
eventId: entry.eventId,
|
|
86
|
+
timestamp: entry.timestamp
|
|
87
|
+
};
|
|
88
|
+
return `${entry.eventId ?? "?"}@${entry.timestamp}`;
|
|
89
|
+
}
|
|
90
|
+
function depthThresholdFromTokens(maxTokens, entryCount) {
|
|
91
|
+
const charBudget = maxTokens * 4;
|
|
92
|
+
const fullBudget = charBudget * 0.6;
|
|
93
|
+
const compactBudget = charBudget * 0.25;
|
|
94
|
+
const minimalBudget = charBudget * 0.1;
|
|
95
|
+
const fullDepth = Math.max(1, Math.floor(fullBudget / 300));
|
|
96
|
+
const compactDepth = fullDepth + Math.max(0, Math.floor(compactBudget / 120));
|
|
97
|
+
const minimalDepth = compactDepth + Math.max(0, Math.floor(minimalBudget / 60));
|
|
98
|
+
return {
|
|
99
|
+
compact: Math.min(fullDepth, entryCount),
|
|
100
|
+
minimal: Math.min(compactDepth, entryCount),
|
|
101
|
+
rollup: Math.min(minimalDepth, entryCount)
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
function restoreDagEntry(entry) {
|
|
105
|
+
if (potByEventId.has(entry.eventId)) return;
|
|
106
|
+
const e = {
|
|
107
|
+
potHash: entry.potHash,
|
|
108
|
+
timestamp: entry.timestamp,
|
|
109
|
+
stratum: entry.stratum,
|
|
110
|
+
mode: entry.mode,
|
|
111
|
+
chainId: entry.chainId ?? void 0,
|
|
112
|
+
poolAddress: entry.poolAddress ?? void 0,
|
|
113
|
+
eventId: entry.eventId,
|
|
114
|
+
prevEventId: entry.prevEventId ?? void 0,
|
|
115
|
+
createdAt: entry.createdAt
|
|
116
|
+
};
|
|
117
|
+
if (potLog.length >= POT_LOG_MAX) {
|
|
118
|
+
const evicted = potLog.shift();
|
|
119
|
+
if (evicted.eventId) {
|
|
120
|
+
potByEventId.delete(evicted.eventId);
|
|
121
|
+
evictedEventIds.add(evicted.eventId);
|
|
122
|
+
if (evictedEventIds.size > EVICTED_MAX) {
|
|
123
|
+
const first = evictedEventIds.values().next().value;
|
|
124
|
+
if (first !== void 0) evictedEventIds.delete(first);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
if (evicted.prevEventId) {
|
|
128
|
+
const siblings = potByPrevEventId.get(evicted.prevEventId);
|
|
129
|
+
if (siblings) {
|
|
130
|
+
const filtered = siblings.filter((s) => s !== evicted);
|
|
131
|
+
if (filtered.length === 0) potByPrevEventId.delete(evicted.prevEventId);
|
|
132
|
+
else potByPrevEventId.set(evicted.prevEventId, filtered);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
potLog.push(e);
|
|
137
|
+
potByEventId.set(entry.eventId, e);
|
|
138
|
+
if (entry.prevEventId) {
|
|
139
|
+
const bucket = potByPrevEventId.get(entry.prevEventId) ?? [];
|
|
140
|
+
bucket.push(e);
|
|
141
|
+
potByPrevEventId.set(entry.prevEventId, bucket);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
43
144
|
async function potGenerate(args) {
|
|
145
|
+
if (!args.eventId && !args.txHash) {
|
|
146
|
+
throw new Error("Either eventId (Claude Code) or txHash (DeFi) is required");
|
|
147
|
+
}
|
|
44
148
|
(0, import_telemetry.telemetryIncrement)("pot_generate");
|
|
45
|
-
|
|
149
|
+
let pot;
|
|
150
|
+
let isOfflineFallback = false;
|
|
151
|
+
try {
|
|
152
|
+
pot = await timeSynth.generateProofOfTime();
|
|
153
|
+
} catch {
|
|
154
|
+
isOfflineFallback = true;
|
|
155
|
+
const nowMs = BigInt(Date.now());
|
|
156
|
+
pot = {
|
|
157
|
+
timestamp: nowMs * 1000000n,
|
|
158
|
+
stratum: 16,
|
|
159
|
+
uncertainty: 999999999,
|
|
160
|
+
confidence: 0,
|
|
161
|
+
sources: 0,
|
|
162
|
+
nonce: Buffer.from(crypto.getRandomValues(new Uint8Array(16))).toString("hex"),
|
|
163
|
+
expiresAt: (nowMs + 300000n) * 1000000n,
|
|
164
|
+
sourceReadings: []
|
|
165
|
+
};
|
|
166
|
+
}
|
|
46
167
|
const potHash = import_openttt.TimeSynthesis.getOnChainHash(pot);
|
|
47
|
-
|
|
48
|
-
|
|
168
|
+
let grgShards = [];
|
|
169
|
+
let currentMode;
|
|
170
|
+
if (args.txHash && args.chainId != null && args.poolAddress) {
|
|
171
|
+
const txData = new TextEncoder().encode(args.txHash);
|
|
172
|
+
grgShards = GrgPipeline.processForward(txData, args.chainId, args.poolAddress).map((s) => Buffer.from(s).toString("hex"));
|
|
173
|
+
const syntheticBlock = {
|
|
174
|
+
timestamp: Number(pot.timestamp / 1000000n),
|
|
175
|
+
// ns → ms
|
|
176
|
+
txs: [args.txHash],
|
|
177
|
+
data: new TextEncoder().encode(args.txHash)
|
|
178
|
+
};
|
|
179
|
+
const syntheticTTTRecord = {
|
|
180
|
+
time: Number(pot.timestamp / 1000000n),
|
|
181
|
+
txOrder: [args.txHash],
|
|
182
|
+
grgPayload: []
|
|
183
|
+
};
|
|
184
|
+
currentMode = adaptiveSwitch.verifyBlock(syntheticBlock, syntheticTTTRecord, args.chainId, args.poolAddress);
|
|
185
|
+
} else {
|
|
186
|
+
currentMode = adaptiveSwitch.getCurrentMode();
|
|
187
|
+
}
|
|
49
188
|
const signature = potSigner.signPot(potHash);
|
|
50
189
|
const entry = {
|
|
51
190
|
potHash,
|
|
52
191
|
timestamp: pot.timestamp.toString(),
|
|
53
192
|
stratum: pot.stratum,
|
|
54
|
-
mode:
|
|
193
|
+
mode: currentMode,
|
|
55
194
|
chainId: args.chainId,
|
|
56
195
|
poolAddress: args.poolAddress,
|
|
196
|
+
eventId: args.eventId,
|
|
197
|
+
prevEventId: args.prevEventId,
|
|
57
198
|
createdAt: Date.now()
|
|
58
199
|
};
|
|
200
|
+
if (potLog.length >= POT_LOG_MAX) {
|
|
201
|
+
const evicted = potLog.shift();
|
|
202
|
+
if (evicted.eventId) {
|
|
203
|
+
potByEventId.delete(evicted.eventId);
|
|
204
|
+
evictedEventIds.add(evicted.eventId);
|
|
205
|
+
if (evictedEventIds.size > EVICTED_MAX) {
|
|
206
|
+
const first = evictedEventIds.values().next().value;
|
|
207
|
+
if (first !== void 0) evictedEventIds.delete(first);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
if (evicted.prevEventId) {
|
|
211
|
+
const siblings = potByPrevEventId.get(evicted.prevEventId);
|
|
212
|
+
if (siblings) {
|
|
213
|
+
const filtered = siblings.filter((e) => e !== evicted);
|
|
214
|
+
if (filtered.length === 0) potByPrevEventId.delete(evicted.prevEventId);
|
|
215
|
+
else potByPrevEventId.set(evicted.prevEventId, filtered);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
59
219
|
potLog.push(entry);
|
|
60
|
-
if (
|
|
220
|
+
if (args.eventId) potByEventId.set(args.eventId, entry);
|
|
221
|
+
if (args.prevEventId) {
|
|
222
|
+
const bucket = potByPrevEventId.get(args.prevEventId) ?? [];
|
|
223
|
+
bucket.push(entry);
|
|
224
|
+
potByPrevEventId.set(args.prevEventId, bucket);
|
|
225
|
+
}
|
|
226
|
+
if (args.eventId) {
|
|
227
|
+
redis.setex(
|
|
228
|
+
`dag:${args.eventId}`,
|
|
229
|
+
90 * 86400,
|
|
230
|
+
JSON.stringify({
|
|
231
|
+
eventId: args.eventId,
|
|
232
|
+
prevEventId: args.prevEventId ?? null,
|
|
233
|
+
potHash,
|
|
234
|
+
timestamp: pot.timestamp.toString(),
|
|
235
|
+
stratum: pot.stratum,
|
|
236
|
+
mode: currentMode,
|
|
237
|
+
createdAt: entry.createdAt,
|
|
238
|
+
chainId: args.chainId ?? null,
|
|
239
|
+
poolAddress: args.poolAddress ?? null
|
|
240
|
+
})
|
|
241
|
+
).catch(() => {
|
|
242
|
+
});
|
|
243
|
+
}
|
|
61
244
|
return serialize({
|
|
62
245
|
potHash,
|
|
246
|
+
eventId: args.eventId ?? null,
|
|
247
|
+
prevEventId: args.prevEventId ?? null,
|
|
63
248
|
timestamp: pot.timestamp.toString(),
|
|
64
249
|
stratum: pot.stratum,
|
|
65
250
|
uncertainty: pot.uncertainty,
|
|
@@ -67,7 +252,8 @@ async function potGenerate(args) {
|
|
|
67
252
|
sources: pot.sources,
|
|
68
253
|
nonce: pot.nonce,
|
|
69
254
|
expiresAt: pot.expiresAt.toString(),
|
|
70
|
-
|
|
255
|
+
...isOfflineFallback && { mode: "local" },
|
|
256
|
+
...grgShards.length > 0 && { grgShards },
|
|
71
257
|
signature: {
|
|
72
258
|
issuerPubKey: signature.issuerPubKey,
|
|
73
259
|
signature: signature.signature,
|
|
@@ -81,7 +267,7 @@ async function potVerify(args) {
|
|
|
81
267
|
let valid = false;
|
|
82
268
|
let reconstructedSize = 0;
|
|
83
269
|
try {
|
|
84
|
-
const recovered =
|
|
270
|
+
const recovered = GrgPipeline.processInverse(shards, 0, args.chainId, args.poolAddress);
|
|
85
271
|
valid = recovered.length > 0;
|
|
86
272
|
reconstructedSize = recovered.length;
|
|
87
273
|
} catch {
|
|
@@ -98,6 +284,16 @@ async function potVerify(args) {
|
|
|
98
284
|
}
|
|
99
285
|
async function potQuery(args) {
|
|
100
286
|
(0, import_telemetry.telemetryIncrement)("pot_query");
|
|
287
|
+
if (args.eventId) {
|
|
288
|
+
const entry = potByEventId.get(args.eventId);
|
|
289
|
+
return serialize({
|
|
290
|
+
local: entry ? [entry] : [],
|
|
291
|
+
subgraph: [],
|
|
292
|
+
found: !!entry,
|
|
293
|
+
totalLocal: potLog.length,
|
|
294
|
+
query: { eventId: args.eventId }
|
|
295
|
+
});
|
|
296
|
+
}
|
|
101
297
|
const limit = Math.min(args.limit ?? 100, 1e3);
|
|
102
298
|
const now = Date.now();
|
|
103
299
|
const startTime = args.startTime ?? now - 864e5;
|
|
@@ -140,6 +336,35 @@ async function potQuery(args) {
|
|
|
140
336
|
query: { startTime, endTime, limit }
|
|
141
337
|
});
|
|
142
338
|
}
|
|
339
|
+
async function potGraph(args) {
|
|
340
|
+
(0, import_telemetry.telemetryIncrement)("pot_graph");
|
|
341
|
+
const maxDepth = Math.min(args.depth ?? 10, 100);
|
|
342
|
+
const backwardChain = [];
|
|
343
|
+
let cursor = potByEventId.get(args.eventId);
|
|
344
|
+
let d = 0;
|
|
345
|
+
while (cursor && d < maxDepth) {
|
|
346
|
+
backwardChain.unshift(cursor);
|
|
347
|
+
cursor = cursor.prevEventId ? potByEventId.get(cursor.prevEventId) : void 0;
|
|
348
|
+
d++;
|
|
349
|
+
}
|
|
350
|
+
const forwardChain = potByPrevEventId.get(args.eventId) ?? [];
|
|
351
|
+
const found = potByEventId.has(args.eventId);
|
|
352
|
+
const chainBroken = backwardChain.some((e) => e.eventId && evictedEventIds.has(e.eventId)) || cursor?.prevEventId != null && potByEventId.get(cursor.prevEventId) == null;
|
|
353
|
+
const chainRoot = backwardChain.length > 0 ? backwardChain[0] : null;
|
|
354
|
+
const isServerRestart = chainBroken && chainRoot !== null && chainRoot.prevEventId != null && !potByEventId.has(chainRoot.prevEventId) && !evictedEventIds.has(chainRoot.prevEventId);
|
|
355
|
+
const brokenAt = chainBroken ? isServerRestart ? "server_restart" : backwardChain[0]?.eventId ?? null : null;
|
|
356
|
+
const compressedBackward = backwardChain.map((e, i) => compressEntry(e, i + 1));
|
|
357
|
+
return serialize({
|
|
358
|
+
eventId: args.eventId,
|
|
359
|
+
found,
|
|
360
|
+
backwardChain: compressedBackward,
|
|
361
|
+
forwardChain,
|
|
362
|
+
chainLength: backwardChain.length + forwardChain.length,
|
|
363
|
+
reachableDepth: backwardChain.length,
|
|
364
|
+
chainBroken,
|
|
365
|
+
brokenAt
|
|
366
|
+
});
|
|
367
|
+
}
|
|
143
368
|
async function potStats(args) {
|
|
144
369
|
(0, import_telemetry.telemetryIncrement)("pot_stats");
|
|
145
370
|
const now = Date.now();
|
|
@@ -220,11 +445,54 @@ async function potHealth() {
|
|
|
220
445
|
}
|
|
221
446
|
});
|
|
222
447
|
}
|
|
448
|
+
async function potCheckpoint(args) {
|
|
449
|
+
(0, import_telemetry.telemetryIncrement)("pot_checkpoint");
|
|
450
|
+
const now = Date.now();
|
|
451
|
+
const startTime = args.startTime ?? now - 36e5;
|
|
452
|
+
const endTime = args.endTime ?? now;
|
|
453
|
+
let entries;
|
|
454
|
+
if (args.fromEventId && args.toEventId) {
|
|
455
|
+
entries = [];
|
|
456
|
+
let cursor = potByEventId.get(args.fromEventId);
|
|
457
|
+
const maxDepth = 1e3;
|
|
458
|
+
let d = 0;
|
|
459
|
+
while (cursor && d < maxDepth) {
|
|
460
|
+
entries.push(cursor);
|
|
461
|
+
if (cursor.eventId === args.toEventId) break;
|
|
462
|
+
const nexts = potByPrevEventId.get(cursor.eventId ?? "") ?? [];
|
|
463
|
+
cursor = nexts[0];
|
|
464
|
+
d++;
|
|
465
|
+
}
|
|
466
|
+
} else {
|
|
467
|
+
entries = potLog.filter((e) => e.createdAt >= startTime && e.createdAt <= endTime);
|
|
468
|
+
}
|
|
469
|
+
const eventCount = entries.length;
|
|
470
|
+
const depthThreshold = args.maxTokens ? depthThresholdFromTokens(args.maxTokens, entries.length) : void 0;
|
|
471
|
+
const compressed = entries.map((e, i) => compressEntry(e, i + 1, depthThreshold));
|
|
472
|
+
const chainIntact = !entries.some((e) => e.eventId && evictedEventIds.has(e.eventId));
|
|
473
|
+
const nextCheckpointHint = Math.max(10, 100 - eventCount % 100);
|
|
474
|
+
const checkpointId = `ckpt_${now}_${eventCount}`;
|
|
475
|
+
const firstTs = entries[0]?.timestamp ?? null;
|
|
476
|
+
const lastTs = entries[entries.length - 1]?.timestamp ?? null;
|
|
477
|
+
return serialize({
|
|
478
|
+
checkpointId,
|
|
479
|
+
eventCount,
|
|
480
|
+
chainIntact,
|
|
481
|
+
nextCheckpointHint,
|
|
482
|
+
rollup: compressed,
|
|
483
|
+
summary: `${eventCount} events from ${firstTs ?? "?"} to ${lastTs ?? "?"}`,
|
|
484
|
+
generatedAt: now
|
|
485
|
+
});
|
|
486
|
+
}
|
|
223
487
|
// Annotate the CommonJS export names for ESM import in node:
|
|
224
488
|
0 && (module.exports = {
|
|
489
|
+
potCheckpoint,
|
|
225
490
|
potGenerate,
|
|
491
|
+
potGraph,
|
|
226
492
|
potHealth,
|
|
227
493
|
potQuery,
|
|
228
494
|
potStats,
|
|
229
|
-
potVerify
|
|
495
|
+
potVerify,
|
|
496
|
+
redis,
|
|
497
|
+
restoreDagEntry
|
|
230
498
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@helm-protocol/ttt-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "MCP Server for OpenTTT — Proof of Time tools for AI agents",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -13,7 +13,23 @@
|
|
|
13
13
|
"scripts": {
|
|
14
14
|
"build": "esbuild index.ts tools.ts telemetry.ts auth.ts --platform=node --target=node18 --format=cjs --outdir=dist",
|
|
15
15
|
"start": "node dist/index.js",
|
|
16
|
-
"dev": "npx ts-node index.ts"
|
|
16
|
+
"dev": "npx ts-node index.ts",
|
|
17
|
+
"test": "jest --forceExit"
|
|
18
|
+
},
|
|
19
|
+
"jest": {
|
|
20
|
+
"preset": "ts-jest",
|
|
21
|
+
"testEnvironment": "node",
|
|
22
|
+
"testMatch": ["**/__tests__/**/*.test.ts"],
|
|
23
|
+
"moduleNameMapper": {
|
|
24
|
+
"^openttt$": "<rootDir>/node_modules/openttt/dist/index.js"
|
|
25
|
+
},
|
|
26
|
+
"transform": {
|
|
27
|
+
"^.+\\.tsx?$": ["ts-jest", {
|
|
28
|
+
"tsconfig": {
|
|
29
|
+
"module": "CommonJS"
|
|
30
|
+
}
|
|
31
|
+
}]
|
|
32
|
+
}
|
|
17
33
|
},
|
|
18
34
|
"keywords": [
|
|
19
35
|
"mcp",
|
|
@@ -36,12 +52,16 @@
|
|
|
36
52
|
},
|
|
37
53
|
"dependencies": {
|
|
38
54
|
"@modelcontextprotocol/sdk": "^1.12.1",
|
|
39
|
-
"
|
|
55
|
+
"ioredis": "^5.11.0",
|
|
56
|
+
"openttt": "^0.2.13",
|
|
40
57
|
"zod": "^3.25.0"
|
|
41
58
|
},
|
|
42
59
|
"devDependencies": {
|
|
60
|
+
"@types/jest": "^30.0.0",
|
|
43
61
|
"@types/node": "^20.11.19",
|
|
44
62
|
"esbuild": "^0.27.4",
|
|
63
|
+
"jest": "^30.4.2",
|
|
64
|
+
"ts-jest": "^29.4.11",
|
|
45
65
|
"typescript": "^5.3.3"
|
|
46
66
|
},
|
|
47
67
|
"mcpName": "io.github.Helm-Protocol/openttt-pot"
|