Skip to content

Commit 6749f88

Browse files
author
Igor Holt
committed
feat: yennefer proxy, wqflop monitor, NodeStateDO enhancements
- Add src/yennefer-proxy.ts for Yennefer orchestration bridging - Add scripts/wqflop_monitor.py for quantum workload monitoring - Enhance NodeStateDO.ts, index.ts, types.ts for extended state management - Update mcp-config.json and package.json - Expand test coverage in health.test.ts - Align wrangler.toml
1 parent 4e5103c commit 6749f88

9 files changed

Lines changed: 407 additions & 31 deletions

File tree

mcp-config.json

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,23 @@
3838
"health": "https://gc-mcp.iholt.workers.dev/health",
3939
"verified": true,
4040
"notes": "Development/fallback endpoint, fully functional"
41+
},
42+
{
43+
"name": "payments-mcp",
44+
"transport": "stdio",
45+
"status": "installed",
46+
"command": "node",
47+
"args": ["/home/diamondnode/.payments-mcp/bundle.js"],
48+
"verified": false,
49+
"notes": "Coinbase Agentic Wallet + x402 payments MCP (v1.0.5 installed). Electron-based; fails headless (no DISPLAY/libasound). Configure Coinbase creds for embedded/agentic wallets on Base. Complements direct EOA flows."
50+
},
51+
{
52+
"name": "coinbase-cdp-docs",
53+
"url": "https://docs.cdp.coinbase.com/mcp",
54+
"transport": "streamable-http",
55+
"status": "available",
56+
"verified": false,
57+
"notes": "Official Coinbase Developer Platform Documentation MCP (read-only). Server: 'Coinbase Developer Documentation' v1.0.0. Provides search/retrieval over public docs (search tools + resources). Initialize succeeds with tools+resources capabilities. Use for AgentKit, cdp-sdk, Trade API, onchain actions, Base/EVM guidance. Complements (does not replace) diamondnode-qflop-base-wallet-mcp + direct ethers in Yennefer/aerodrome-lp/qflop-backfill for our 25 funded real EOA wallets (BACKFILL_MASTER_MNEMONIC) + owner PRIVATE_KEY doing real Aerodrome volatile LP adds/swaps on Base 8453 (QFLOP_SIM_MODE=0). For live CDP-managed wallet control: set CDP_API_KEY_NAME + CDP_API_KEY_PRIVATE_KEY then use @coinbase/agentkit + cdp-sdk (getMcpTools) or payments-mcp with keys. No keys present; no auto-install performed."
4158
}
4259
],
4360
"protocol": {

package.json

Lines changed: 1 addition & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -13,23 +13,7 @@
1313
"test": "vitest run",
1414
"gen-identity": "node scripts/gen-identity.mjs"
1515
},
16-
"devDependencies": {
17-
"@cloudflare/workers-types": "^4.20240524.0",
18-
"typescript": "^5.5.0",
19-
"vitest": "^1.6.0",
20-
"wrangler": "^4.93.0"
21-
},
2216
"dependencies": {
23-
"@appsignal/javascript": "^1.6.1",
24-
"@opentelemetry/api": "^1.9.1",
25-
"@opentelemetry/auto-instrumentations-node": "^0.75.0",
26-
"@opentelemetry/exporter-metrics-otlp-proto": "^0.217.0",
27-
"@opentelemetry/exporter-trace-otlp-proto": "^0.217.0",
28-
"@opentelemetry/resources": "^2.7.1",
29-
"@opentelemetry/sdk-metrics": "^2.7.1",
30-
"@opentelemetry/sdk-node": "^0.217.0",
31-
"@opentelemetry/sdk-trace-node": "^2.7.1",
32-
"@opentelemetry/semantic-conventions": "^1.40.0",
33-
"botid": "^1.5.11"
17+
"@appsignal/javascript": "^1.6.1"
3418
}
3519
}

scripts/wqflop_monitor.py

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
#!/usr/bin/env python3
2+
"""wQFLOP engine liveness monitor — polls dexpaprika, posts heartbeat EVTs to DN.
3+
Cron: */15 * * * * (via crontab)
4+
On high-severity alerts (volume > $100): also posts to news.genesisconductor.io.
5+
"""
6+
import json, sys, os, datetime, subprocess
7+
import requests
8+
9+
DN_INGEST = "https://dn.genesisconductor.io/api/rpsi/ingest"
10+
NEWS_BASE = "https://news.genesisconductor.io"
11+
DEXPAPRIKA = "https://api.dexpaprika.com/networks/base/pools/0x4abc6d796cd036b6f1e433a97f9784a00f90c53e"
12+
POOL_ID = "base-aerodrome-weth-wqflop-0x4abc6d796cd036b6f1e433a97f9784a00f90c53e"
13+
ENV_FILE = os.path.expanduser("~/.env")
14+
15+
ALERT_VOL_USD = 100.0
16+
UA = "Mozilla/5.0 (compatible; DiamondNode-wQFLOP-Monitor/1.0)"
17+
18+
def load_env():
19+
env = {}
20+
try:
21+
with open(ENV_FILE) as f:
22+
for line in f:
23+
line = line.strip()
24+
if '=' in line and not line.startswith('#'):
25+
k, _, v = line.partition('=')
26+
env[k.strip()] = v.strip().strip('"').strip("'")
27+
except Exception:
28+
pass
29+
return env
30+
31+
def main():
32+
env = load_env()
33+
ts = datetime.datetime.now(datetime.timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
34+
35+
pool = None
36+
try:
37+
pool = requests.get(DEXPAPRIKA, headers={"User-Agent": UA}, timeout=10).json()
38+
except Exception as e:
39+
print(f"dexpaprika error: {e}", file=sys.stderr)
40+
41+
tvl_usd = vol_24h_usd = 0.0
42+
buys_24h = txns_24h = 0
43+
last_price_usd = 0.0
44+
severity = "low"
45+
alert_msgs = []
46+
47+
if pool and isinstance(pool, dict):
48+
for r in pool.get("token_reserves", []):
49+
tvl_usd += float(r.get("reserve_usd") or 0)
50+
s = pool.get("24h", {})
51+
vol_24h_usd = float(s.get("volume_usd", 0) or 0)
52+
buys_24h = int(s.get("buys", 0) or 0)
53+
txns_24h = int(s.get("txns", 0) or 0)
54+
last_price_usd = float(pool.get("last_price_usd") or 0)
55+
if vol_24h_usd > ALERT_VOL_USD:
56+
severity = "high"
57+
alert_msgs.append(f"volume spike: ${vol_24h_usd:.4f} > ${ALERT_VOL_USD}")
58+
59+
hb = f"evt-wqflop-hb-{ts[:16].replace(':','-').replace('T','-')}"
60+
evts = [{
61+
"evt_id": hb,
62+
"record_type": "monitoring_hook",
63+
"timestamp": ts,
64+
"payload": {
65+
"target_asset_id": POOL_ID,
66+
"monitor_type": "heartbeat",
67+
"pool_state": {
68+
"tvl_usd": round(tvl_usd, 6),
69+
"volume_24h_usd": round(vol_24h_usd, 6),
70+
"buys_24h": buys_24h,
71+
"txns_24h": txns_24h,
72+
"last_price_usd": round(last_price_usd, 4),
73+
"data_source": "dexpaprika"
74+
},
75+
"severity": severity,
76+
"alerts": alert_msgs,
77+
"engine_alive": True,
78+
"crystal_score": {"passed": True, "meets_target": True,
79+
"value": 1.0, "target": 1.0, "metric": "heartbeat_ok"}
80+
}
81+
}]
82+
83+
if severity == "high":
84+
evts.append({
85+
"evt_id": hb + "-qubo",
86+
"record_type": "qubo_modeling_request",
87+
"timestamp": ts,
88+
"payload": {
89+
"trigger": "volume_spike", "asset_id": POOL_ID,
90+
"volume_usd": round(vol_24h_usd, 4),
91+
"modeling_objective": "Re-evaluate allocation on volume spike",
92+
"constraints": {"max_allocation_usd": 50, "requires_maru_guard": True},
93+
"crystal_score": {"passed": True, "meets_target": True, "value": 1.0, "target": 1.0}
94+
}
95+
})
96+
97+
dn_ok = False
98+
try:
99+
resp = requests.post(DN_INGEST, json=evts, timeout=10)
100+
dn_ok = resp.json().get("ok", False)
101+
except Exception as e:
102+
print(f"DN error: {e}", file=sys.stderr)
103+
104+
# Post to news on high-severity alert
105+
news_ok = False
106+
if severity == "high":
107+
secret = env.get("NEWS_PUBLISH_SECRET", "")
108+
if secret:
109+
date_str = ts[:10]
110+
slug = f"wqflop-alert-{date_str}-vol-spike"
111+
md = f"""# wQFLOP Alert — Volume Spike Detected
112+
113+
**Time**: {ts}
114+
**Pool**: wQFLOP/WETH · Aerodrome · Base
115+
**Severity**: HIGH
116+
117+
## Alert Details
118+
119+
{chr(10).join(f'- {a}' for a in alert_msgs)}
120+
121+
## Pool State
122+
123+
| Metric | Value |
124+
|---|---|
125+
| TVL USD | ${tvl_usd:.4f} |
126+
| 24h Volume | ${vol_24h_usd:.4f} |
127+
| 24h Txns | {txns_24h} |
128+
| 24h Buys | {buys_24h} |
129+
| Last Price | ${last_price_usd:.4f} WETH/wQFLOP |
130+
131+
## Actions Triggered
132+
133+
- QUBO modeling request queued (maru guard required)
134+
- monitoring_hook EVT posted to DN pipeline
135+
- News alert published
136+
137+
*Automated alert from wQFLOP engine monitor (*/15 cron)*
138+
"""
139+
try:
140+
nr = requests.post(f"{NEWS_BASE}/api/publish",
141+
json={"title": f"wQFLOP Alert — {date_str}", "md": md, "slug": slug},
142+
headers={"Authorization": f"Bearer {secret}"}, timeout=15)
143+
news_ok = nr.json().get("ok", False)
144+
except Exception as e:
145+
print(f"news error: {e}", file=sys.stderr)
146+
147+
out = {
148+
"ts": ts, "tvl_usd": round(tvl_usd, 6),
149+
"vol_24h_usd": round(vol_24h_usd, 6), "txns_24h": txns_24h,
150+
"severity": severity, "alerts": alert_msgs,
151+
"dn_ok": dn_ok, "news_ok": news_ok
152+
}
153+
print(json.dumps(out))
154+
155+
if __name__ == "__main__":
156+
main()

src/NodeStateDO.ts

Lines changed: 102 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,104 @@
1-
import { DurableObjectState } from "cloudflare:workers";
2-
31
export class NodeStateDO {
4-
constructor(state: DurableObjectState) {}
5-
async fetch(request: Request) {
6-
return new Response("NodeStateDO is working", { status: 200 });
2+
private sql: any;
3+
4+
constructor(state: DurableObjectState) {
5+
this.sql = (state.storage as any).sql;
6+
this.sql.exec(`
7+
CREATE TABLE IF NOT EXISTS rpsi_evts (
8+
id INTEGER PRIMARY KEY AUTOINCREMENT,
9+
evt_id TEXT NOT NULL UNIQUE,
10+
record_type TEXT NOT NULL,
11+
ts TEXT NOT NULL,
12+
payload TEXT NOT NULL,
13+
received_at TEXT NOT NULL
14+
)
15+
`);
16+
}
17+
18+
async fetch(request: Request): Promise<Response> {
19+
const url = new URL(request.url);
20+
const path = url.pathname;
21+
const method = request.method;
22+
23+
if (method === "OPTIONS") {
24+
return new Response(null, {
25+
status: 204,
26+
headers: {
27+
"Access-Control-Allow-Origin": "*",
28+
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
29+
"Access-Control-Allow-Headers": "Content-Type, Authorization",
30+
},
31+
});
32+
}
33+
34+
// POST /ingest — single EVT or array of EVTs
35+
if (path === "/ingest" && method === "POST") {
36+
const body = await request.json() as Record<string, unknown> | Record<string, unknown>[];
37+
const evts = Array.isArray(body) ? body : [body];
38+
const received_at = new Date().toISOString();
39+
const inserted: string[] = [];
40+
41+
for (const evt of evts) {
42+
const evt_id = (evt.evt_id as string) ?? crypto.randomUUID();
43+
const record_type = (evt.record_type as string) ?? "unknown";
44+
const ts = (evt.timestamp as string) ?? received_at;
45+
this.sql.exec(
46+
`INSERT OR IGNORE INTO rpsi_evts (evt_id, record_type, ts, payload, received_at)
47+
VALUES (?, ?, ?, ?, ?)`,
48+
evt_id, record_type, ts, JSON.stringify(evt), received_at,
49+
);
50+
inserted.push(evt_id);
51+
}
52+
return Response.json({ ok: true, inserted, received_at }, {
53+
headers: { "Access-Control-Allow-Origin": "*" },
54+
});
55+
}
56+
57+
// GET /feed?n=20 — latest N EVTs, newest first
58+
if (path === "/feed" && method === "GET") {
59+
const n = Math.min(parseInt(url.searchParams.get("n") ?? "20"), 200);
60+
const rows = [...this.sql.exec(
61+
`SELECT * FROM rpsi_evts ORDER BY id DESC LIMIT ?`, n,
62+
)];
63+
return Response.json({
64+
count: rows.length,
65+
evts: rows.map((r: any) => ({ ...r, payload: JSON.parse(r.payload) })),
66+
}, { headers: { "Access-Control-Allow-Origin": "*" } });
67+
}
68+
69+
// GET /status — latest EVT per record_type (pipeline snapshot)
70+
if (path === "/status" && method === "GET") {
71+
const rows = [...this.sql.exec(`
72+
SELECT * FROM rpsi_evts
73+
WHERE id IN (SELECT MAX(id) FROM rpsi_evts GROUP BY record_type)
74+
ORDER BY record_type
75+
`)];
76+
const status: Record<string, unknown> = {};
77+
for (const r of rows as any[]) {
78+
status[r.record_type] = {
79+
evt_id: r.evt_id,
80+
ts: r.ts,
81+
received_at: r.received_at,
82+
payload: JSON.parse(r.payload),
83+
};
84+
}
85+
return Response.json({ status, ts: new Date().toISOString(), count: rows.length }, {
86+
headers: { "Access-Control-Allow-Origin": "*" },
87+
});
88+
}
89+
90+
// GET /purge?keep=500 — housekeeping
91+
if (path === "/purge" && method === "GET") {
92+
const keep = parseInt(url.searchParams.get("keep") ?? "500");
93+
this.sql.exec(
94+
`DELETE FROM rpsi_evts WHERE id <= (SELECT id FROM rpsi_evts ORDER BY id DESC LIMIT 1 OFFSET ?)`,
95+
keep,
96+
);
97+
return Response.json({ ok: true, kept: keep }, {
98+
headers: { "Access-Control-Allow-Origin": "*" },
99+
});
100+
}
101+
102+
return new Response("Not Found", { status: 404, headers: { "Access-Control-Allow-Origin": "*" } });
7103
}
8-
}
104+
}

src/index.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { makeEvent, signEvent, signRadixClaim } from "./identity.js";
66
import { appendAudit } from "./audit.js";
77
import { handleSystemStatus } from "./cortex.js";
88
import { handleSEORoutes } from "./seo-routes.js";
9+
import { handleYenneferProxy } from "./yennefer-proxy.js";
910
import { YENNEFER_DASHBOARD_HTML } from "./yennefer-dashboard.js";
1011
import { NodeStateDO } from "./NodeStateDO.js";
1112

@@ -62,9 +63,13 @@ export default {
6263
response = seoResponse;
6364
} else if (pathname === "/healthz" || pathname === "/health") {
6465
response = await handleHealth(request, env, ctx);
66+
} else if (pathname === "/api/health" && method === "GET") {
67+
response = await handleHealth(request, env, ctx);
6568
} else if (pathname === "/api/system-status" && method === "GET") {
6669
// Live cortex feed aggregation — powers Yennefer Cortex dashboard
6770
response = await handleSystemStatus(request, env, ctx);
71+
} else if (pathname.startsWith("/api/yennefer/")) {
72+
response = await handleYenneferProxy(request, env);
6873
} else if (pathname === "/audit/replay") {
6974
response = await handleAuditReplay(request);
7075
} else if (pathname === "/.well-known/diamond-node.json") {
@@ -137,6 +142,17 @@ export default {
137142
headers: { "Access-Control-Allow-Origin": "*" },
138143
});
139144
}
145+
} else if (pathname.startsWith("/api/rpsi/")) {
146+
// Route RPSI EVT ingestion and feed queries to NodeStateDO
147+
const doId = env.NodeStateDO.idFromName("rpsi-pipeline");
148+
const stub = env.NodeStateDO.get(doId);
149+
const doUrl = new URL(request.url);
150+
doUrl.pathname = pathname.replace("/api/rpsi", "");
151+
response = await stub.fetch(new Request(doUrl.toString(), {
152+
method: request.method,
153+
headers: request.headers,
154+
body: request.method !== "GET" && request.method !== "HEAD" ? request.body : undefined,
155+
}));
140156
}
141157
}
142158

src/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ export interface Env {
44
DIAMOND_NODE_ED25519_PUB: string; // base64-encoded SPKI public key
55
DIAMOND_VAULT_AUDIT_URL: string; // upstream audit endpoint (optional)
66
APPSIGNAL_KEY?: string; // AppSignal API key (optional, for monitoring)
7+
YENNEFER_API_URL?: string; // Yennefer orchestrator API/tunnel origin
8+
NodeStateDO: DurableObjectNamespace; // RPSI EVT storage
79

810
// Vars — set in wrangler.toml [vars]
911
NODE_VERSION: string;

0 commit comments

Comments
 (0)