Skip to content

Commit 6d3ef3f

Browse files
authored
Merge pull request #2 from anu-m03/api
Api
2 parents f876679 + 1d2a191 commit 6d3ef3f

File tree

28 files changed

+1459
-292
lines changed

28 files changed

+1459
-292
lines changed

apps/backend/.data/logs.jsonl

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{"id":"6e346965-e789-4669-a770-e905559a397a","timestamp":1771343000368,"type":"SWARM_START","runId":"5549e732-09cf-4868-a9ff-7da53747a22a","payload":{"tx":{"chainId":8453,"from":"0xaaa","to":"0x0000000000000000000000000000000000000000","data":"0x","value":"2000000000000000000"},"runId":"5549e732-09cf-4868-a9ff-7da53747a22a"},"level":"INFO"}
2+
{"id":"e769b63d-9433-4e26-b968-f5204eaeabfb","timestamp":1771343000369,"type":"AGENT_REPORTS","runId":"5549e732-09cf-4868-a9ff-7da53747a22a","payload":{"reports":[{"agentId":"sentinel-7dbc246e","agentType":"SENTINEL","timestamp":1771343000368,"riskScore":70,"confidenceBps":8500,"severity":"HIGH","reasons":["Transaction targets zero address","Plain ETH transfer with high value and no calldata"],"evidence":{"zeroAddress":true,"highValueNoData":true},"recommendation":"REVIEW"},{"agentId":"scam-ba29b285","agentType":"SCAM","timestamp":1771343000368,"riskScore":5,"confidenceBps":5500,"severity":"LOW","reasons":["No scam indicators detected"],"evidence":{},"recommendation":"ALLOW"},{"agentId":"mev-c0988863","agentType":"MEV","timestamp":1771343000368,"riskScore":5,"confidenceBps":5000,"severity":"LOW","reasons":["No MEV risk detected"],"evidence":{},"recommendation":"ALLOW"},{"agentId":"liq-47a7f78d","agentType":"LIQUIDATION","timestamp":1771343000369,"riskScore":5,"confidenceBps":4000,"severity":"LOW","reasons":["No lending risk detected"],"evidence":{},"recommendation":"ALLOW"},{"agentId":"coord-e871db43","agentType":"COORDINATOR","timestamp":1771343000369,"riskScore":29,"confidenceBps":5750,"severity":"HIGH","reasons":["SENTINEL: score 70 (HIGH)"],"evidence":{"peerCount":4,"severityCounts":{"LOW":3,"MEDIUM":0,"HIGH":1,"CRITICAL":0},"avgScore":29},"recommendation":"ALLOW"}]},"level":"INFO"}
3+
{"id":"19bafafb-c49b-49e4-bbcc-a4289da00b41","timestamp":1771343000369,"type":"CONSENSUS","runId":"5549e732-09cf-4868-a9ff-7da53747a22a","payload":{"decision":{"runId":"5549e732-09cf-4868-a9ff-7da53747a22a","timestamp":1771343000369,"finalSeverity":"HIGH","finalRiskScore":21,"decision":"ALLOW","threshold":{"approvalsRequired":2,"criticalBlockEnabled":true},"approvingAgents":[{"agentId":"scam-ba29b285","riskScore":5,"confidenceBps":5500,"reasonHash":"e1ae697e917bdc20"},{"agentId":"mev-c0988863","riskScore":5,"confidenceBps":5000,"reasonHash":"ef447bf5eec9a74f"},{"agentId":"liq-47a7f78d","riskScore":5,"confidenceBps":4000,"reasonHash":"4630f0c56999a0c4"}],"dissentingAgents":[{"agentId":"sentinel-7dbc246e","reason":"Transaction targets zero address; Plain ETH transfer with high value and no calldata"}],"notes":["Approvals: 3/4 (required 2)"]}},"level":"INFO"}
4+
{"id":"94abc094-c5c8-41f3-a4f3-75fc3d942752","timestamp":1771343000369,"type":"INTENT","runId":"5549e732-09cf-4868-a9ff-7da53747a22a","payload":{"intent":{"intentId":"4227387c-611a-4da2-807b-7d83acac74e9","runId":"5549e732-09cf-4868-a9ff-7da53747a22a","action":"EXECUTE_TX","chainId":8453,"to":"0x0000000000000000000000000000000000000000","value":"2000000000000000000","data":"0x","meta":{"finalSeverity":"HIGH","finalRiskScore":21,"timestamp":1771343000369}}},"level":"INFO"}
5+
{"id":"f414433e-9456-4e7c-9290-d0d03914ba55","timestamp":1771343000369,"type":"SWARM_END","runId":"5549e732-09cf-4868-a9ff-7da53747a22a","payload":{"runId":"5549e732-09cf-4868-a9ff-7da53747a22a","decision":"ALLOW"},"level":"INFO"}
6+
{"id":"4dc90534-1713-4cc3-bb46-973ccf360dfa","timestamp":1771343000582,"type":"GOVERNANCE_VOTE","payload":{"proposalId":"proposal-002","recommendation":"AGAINST","confidenceBps":7500,"summary":"Summary: Upgrade proxy to v2.1 — emergency patch Emergency upgrade to fix a critical vulnerability in the proxy contract. The admin key will be rotated and the quorum threshold adjusted from 3/5 to 4/7. Immedi..."},"level":"INFO"}
7+
{"id":"c3586e9a-cc5b-46cd-9cf8-f1bfee71e3c9","timestamp":1771343126538,"type":"SWARM_START","runId":"8c6e82b4-0aaa-4f84-bb39-3932add59123","payload":{"tx":{"chainId":8453,"from":"0xaaa","to":"0x0000000000000000000000000000000000000000","data":"0x","value":"2000000000000000000"},"runId":"8c6e82b4-0aaa-4f84-bb39-3932add59123"},"level":"INFO"}
8+
{"id":"1d4f1f4a-7964-43c3-99aa-387a8d1a915c","timestamp":1771343126539,"type":"AGENT_REPORTS","runId":"8c6e82b4-0aaa-4f84-bb39-3932add59123","payload":{"reports":[{"agentId":"sentinel-1dc31a5f","agentType":"SENTINEL","timestamp":1771343126539,"riskScore":70,"confidenceBps":8500,"severity":"HIGH","reasons":["Transaction targets zero address","Plain ETH transfer with high value and no calldata"],"evidence":{"zeroAddress":true,"highValueNoData":true},"recommendation":"REVIEW"},{"agentId":"scam-5148bfbe","agentType":"SCAM","timestamp":1771343126539,"riskScore":5,"confidenceBps":5500,"severity":"LOW","reasons":["No scam indicators detected"],"evidence":{},"recommendation":"ALLOW"},{"agentId":"mev-fa3d94d6","agentType":"MEV","timestamp":1771343126539,"riskScore":5,"confidenceBps":5000,"severity":"LOW","reasons":["No MEV risk detected"],"evidence":{},"recommendation":"ALLOW"},{"agentId":"liq-eb561ba8","agentType":"LIQUIDATION","timestamp":1771343126539,"riskScore":5,"confidenceBps":4000,"severity":"LOW","reasons":["No lending risk detected"],"evidence":{},"recommendation":"ALLOW"},{"agentId":"coord-ae69d24c","agentType":"COORDINATOR","timestamp":1771343126539,"riskScore":29,"confidenceBps":5750,"severity":"HIGH","reasons":["SENTINEL: score 70 (HIGH)"],"evidence":{"peerCount":4,"severityCounts":{"LOW":3,"MEDIUM":0,"HIGH":1,"CRITICAL":0},"avgScore":29},"recommendation":"ALLOW"}]},"level":"INFO"}
9+
{"id":"6e429b37-ce27-4f25-9ba6-faac0ea206e3","timestamp":1771343126540,"type":"CONSENSUS","runId":"8c6e82b4-0aaa-4f84-bb39-3932add59123","payload":{"decision":{"runId":"8c6e82b4-0aaa-4f84-bb39-3932add59123","timestamp":1771343126540,"finalSeverity":"HIGH","finalRiskScore":21,"decision":"ALLOW","threshold":{"approvalsRequired":2,"criticalBlockEnabled":true},"approvingAgents":[{"agentId":"scam-5148bfbe","riskScore":5,"confidenceBps":5500,"reasonHash":"e1ae697e917bdc20"},{"agentId":"mev-fa3d94d6","riskScore":5,"confidenceBps":5000,"reasonHash":"ef447bf5eec9a74f"},{"agentId":"liq-eb561ba8","riskScore":5,"confidenceBps":4000,"reasonHash":"4630f0c56999a0c4"}],"dissentingAgents":[{"agentId":"sentinel-1dc31a5f","reason":"Transaction targets zero address; Plain ETH transfer with high value and no calldata"}],"notes":["Approvals: 3/4 (required 2)"]}},"level":"INFO"}
10+
{"id":"7567ac1e-432f-4dab-9489-d077d7e60769","timestamp":1771343126540,"type":"INTENT","runId":"8c6e82b4-0aaa-4f84-bb39-3932add59123","payload":{"intent":{"intentId":"18403edf-fd49-4295-990b-9627c87dfb23","runId":"8c6e82b4-0aaa-4f84-bb39-3932add59123","action":"EXECUTE_TX","chainId":8453,"to":"0x0000000000000000000000000000000000000000","value":"2000000000000000000","data":"0x","meta":{"finalSeverity":"HIGH","finalRiskScore":21,"timestamp":1771343126540}}},"level":"INFO"}
11+
{"id":"3beb2f0b-146a-4fc3-a5d0-3425be3d122c","timestamp":1771343126540,"type":"SWARM_END","runId":"8c6e82b4-0aaa-4f84-bb39-3932add59123","payload":{"runId":"8c6e82b4-0aaa-4f84-bb39-3932add59123","decision":"ALLOW"},"level":"INFO"}
12+
{"id":"f0b77d60-6a64-499b-ba5c-d0b61576f459","timestamp":1771343133545,"type":"GOVERNANCE_VOTE","payload":{"proposalId":"proposal-002","recommendation":"AGAINST","confidenceBps":7500,"summary":"Summary: Upgrade proxy to v2.1 — emergency patch Emergency upgrade to fix a critical vulnerability in the proxy contract. The admin key will be rotated and the quorum threshold adjusted from 3/5 to 4/7. Immedi..."},"level":"INFO"}

apps/backend/.env.example

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# AgentSafe Backend env configuration
2+
# Copy this file to .env and fill in values
3+
4+
# ─── Server ──────────────────────────────────────────────
5+
PORT=4000
6+
7+
# ─── QuickNode RPC (Base chain) ──────────────────────────
8+
# Leave empty to run in "disabled" mode (no live chain data)
9+
QUICKNODE_RPC_URL=
10+
11+
# ─── Kite AI ─────────────────────────────────────────────
12+
# Leave empty to run with deterministic stubs
13+
KITE_BASE_URL=
14+
KITE_API_KEY=
15+
16+
# ─── Log Store ───────────────────────────────────────────
17+
# Directory for JSONL log files (defaults to .data/ in cwd)
18+
LOG_STORE_PATH=

apps/backend/README.md

Lines changed: 99 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,110 @@
11
# @agent-safe/backend
22

3-
Node.js + Express backend API for the AgentSafe swarm agent orchestrator.
3+
Express + TypeScript backend for **AgentSafe** — SwarmGuard orchestrator,
4+
governance recommender, and audit log API.
5+
6+
## Quick start
7+
8+
```bash
9+
cp .env.example .env # fill in optional keys
10+
pnpm install
11+
pnpm dev # tsx watch — http://localhost:4000
12+
```
413

514
## Endpoints
615

7-
| Method | Path | Description |
8-
|--------|------|-------------|
9-
| GET | `/health` | Health check |
10-
| POST | `/api/swarm/evaluate-tx` | Evaluate a transaction through SwarmGuard |
11-
| GET | `/api/swarm/logs` | Fetch audit log of swarm decisions |
12-
| GET | `/api/governance/proposals` | Fetch governance proposals (Snapshot stub) |
13-
| POST | `/api/governance/recommend` | Analyse a proposal and return recommendation |
16+
| Method | Path | Description |
17+
|--------|-------------------------------|-------------|
18+
| GET | `/health` | Rich health check (QuickNode + Kite AI status) |
19+
| GET | `/status` | Quick liveness probe |
20+
| POST | `/api/swarm/evaluate-tx` | Evaluate a transaction through SwarmGuard |
21+
| GET | `/api/swarm/logs` | Fetch audit log (`?runId=...&limit=100`) |
22+
| GET | `/api/governance/proposals` | List mock governance proposals |
23+
| GET | `/api/governance/proposals/:id` | Get single proposal |
24+
| POST | `/api/governance/recommend` | Get vote recommendation for a proposal |
25+
26+
## Architecture
27+
28+
```
29+
src/
30+
├── index.ts Express entry point
31+
├── middleware/logger.ts Request logger with requestId
32+
├── routes/
33+
│ ├── health.ts /health + integration status
34+
│ ├── swarm.ts /api/swarm/*
35+
│ └── governance.ts /api/governance/*
36+
├── agents/ V2 heuristic agents
37+
│ ├── sentinel.ts Approval / zero-addr / calldata checks
38+
│ ├── scamDetector.ts Contract reputation checks
39+
│ ├── mevWatcher.ts DEX swap sandwich risk
40+
│ ├── liquidationPredictor.ts Health factor monitoring
41+
│ ├── coordinator.ts Weighted aggregation
42+
│ └── defender.ts Defensive action stub
43+
├── orchestrator/
44+
│ ├── swarmRunner.ts Full pipeline: agents → consensus → intent
45+
│ ├── consensus.ts MVP consensus rules (critical-block, approval count)
46+
│ ├── intent.ts Maps consensus decision → ActionIntent
47+
│ └── governanceRunner.ts Proposal evaluation via Kite AI + policies
48+
├── governance/
49+
│ ├── proposals.ts Reads mockProposals.json
50+
│ └── mockProposals.json 3 sample proposals
51+
├── storage/
52+
│ └── logStore.ts JSONL-backed audit log
53+
└── services/
54+
├── rpc/quicknode.ts QuickNode RPC wrapper (graceful degradation)
55+
└── agents/kite.ts Kite AI summarise / classify (stub fallback)
56+
```
57+
58+
## Agents (V2)
1459

15-
## Agents
60+
All agents export `evaluateTx(ctx, tx): Promise<AgentRiskReportV2>` and run
61+
**deterministic heuristics** — no LLM calls in the hot path.
1662

17-
| Agent | File | Purpose |
18-
|-------|------|---------|
19-
| Sentinel | `agents/sentinel.ts` | Approval & activity monitoring |
20-
| MEV Watcher | `agents/mevWatcher.ts` | Sandwich attack detection |
21-
| Liquidation Predictor | `agents/liquidationPredictor.ts` | Health factor tracking |
22-
| Scam Detector | `agents/scamDetector.ts` | Contract reputation checks |
23-
| Coordinator | `agents/coordinator.ts` | Consensus aggregation |
24-
| Defender | `agents/defender.ts` | Defensive action execution |
63+
| Agent | Key heuristics |
64+
|--------------|----------------|
65+
| Sentinel | Zero-address, empty calldata + high value, approve()/setApprovalForAll(), MAX_UINT |
66+
| Scam | contractVerified, contractAge, phishing labels, honeypot flag |
67+
| MEV | SWAP kind, DEX selector detection, high value, slippage > 300 bps |
68+
| Liquidation | healthFactor thresholds (< 1.05 critical, < 1.2 high), collateral ratio |
69+
| Coordinator | Weighted average of peer reports, severity counts |
2570

26-
## Run
71+
## Consensus rules
72+
73+
1. **Critical block** — any CRITICAL severity with ≥ 7 000 bps confidence ⇒ BLOCK
74+
2. **Approval count** — ALLOW or (LOW/MEDIUM + ≥ 6 000 bps) counts as approval; need ≥ 2 ⇒ ALLOW
75+
3. Otherwise ⇒ REVIEW_REQUIRED
76+
77+
## Environment variables
78+
79+
| Variable | Required | Default | Notes |
80+
|--------------------|----------|---------|-------|
81+
| `PORT` | No | 4000 | HTTP listen port |
82+
| `QUICKNODE_RPC_URL`| No | | Base-chain RPC; omit for disabled mode |
83+
| `KITE_BASE_URL` | No | | Kite AI endpoint; omit for stub mode |
84+
| `KITE_API_KEY` | No | | Bearer token for Kite AI |
85+
| `LOG_STORE_PATH` | No | `.data` | Directory for JSONL logs |
86+
87+
## Example curl commands
2788

2889
```bash
29-
pnpm dev # starts on http://localhost:4000
90+
# Health
91+
curl http://localhost:4000/health | jq
92+
93+
# Evaluate transaction
94+
curl -X POST http://localhost:4000/api/swarm/evaluate-tx \
95+
-H 'Content-Type: application/json' \
96+
-d '{"chainId":8453,"from":"0xYou","to":"0xTarget","data":"0x095ea7b3000000000000000000000000spenderffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","value":"0"}' \
97+
| jq
98+
99+
# Fetch logs
100+
curl 'http://localhost:4000/api/swarm/logs?limit=10' | jq
101+
102+
# List proposals
103+
curl http://localhost:4000/api/governance/proposals | jq
104+
105+
# Get vote recommendation
106+
curl -X POST http://localhost:4000/api/governance/recommend \
107+
-H 'Content-Type: application/json' \
108+
-d '{"proposalId":"proposal-002"}' \
109+
| jq
30110
```
Lines changed: 64 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,71 @@
1-
import type { AgentRiskReport, SwarmConsensusDecision } from '@agent-safe/shared';
2-
import { CONSENSUS_THRESHOLD } from '@agent-safe/shared';
1+
import type { AgentRiskReportV2, Severity, Recommendation } from '@agent-safe/shared';
2+
import crypto from 'node:crypto';
33

44
/**
5-
* Coordinator Agent – aggregates individual agent reports and produces consensus.
6-
* TODO: Implement weighted voting, configurable thresholds.
5+
* Coordinator Agent – aggregates individual agent reports and produces a summary report.
6+
* Does not perform independent analysis; reflects swarm consensus.
77
*/
8-
export async function runCoordinatorAgent(
9-
reports: AgentRiskReport[],
10-
): Promise<SwarmConsensusDecision> {
11-
// TODO: Implement weighted voting based on agent confidence
12-
// TODO: Configurable consensus threshold
13-
// TODO: Handle conflicting agent outputs
14-
15-
const highRiskCount = reports.filter(
16-
(r) => r.risk_level === 'HIGH' || r.risk_level === 'CRITICAL',
17-
).length;
18-
19-
const riskScore = Math.round(
20-
(reports.reduce((sum, r) => {
21-
const levelScore = { LOW: 10, MEDIUM: 40, HIGH: 75, CRITICAL: 95 }[r.risk_level];
22-
return sum + levelScore * r.confidence;
23-
}, 0) /
24-
reports.length) *
25-
1,
26-
);
27-
28-
const consensusMet = highRiskCount >= CONSENSUS_THRESHOLD;
8+
export async function evaluateTx(
9+
_ctx: unknown,
10+
_tx: unknown,
11+
peerReports: AgentRiskReportV2[],
12+
): Promise<AgentRiskReportV2> {
13+
if (peerReports.length === 0) {
14+
return {
15+
agentId: `coord-${crypto.randomUUID().slice(0, 8)}`,
16+
agentType: 'COORDINATOR',
17+
timestamp: Date.now(),
18+
riskScore: 0,
19+
confidenceBps: 0,
20+
severity: 'LOW',
21+
reasons: ['No peer reports to aggregate'],
22+
evidence: {},
23+
recommendation: 'ALLOW',
24+
};
25+
}
26+
27+
// Weighted average risk score (weight = confidence)
28+
let totalWeight = 0;
29+
let weightedScore = 0;
30+
const severityCounts: Record<Severity, number> = { LOW: 0, MEDIUM: 0, HIGH: 0, CRITICAL: 0 };
31+
const reasons: string[] = [];
32+
33+
for (const r of peerReports) {
34+
const w = r.confidenceBps / 10_000;
35+
weightedScore += r.riskScore * w;
36+
totalWeight += w;
37+
severityCounts[r.severity]++;
38+
if (r.riskScore > 30) {
39+
reasons.push(`${r.agentType}: score ${r.riskScore} (${r.severity})`);
40+
}
41+
}
42+
43+
const avgScore = totalWeight > 0 ? Math.round(weightedScore / totalWeight) : 0;
44+
45+
let severity: Severity = 'LOW';
46+
if (severityCounts.CRITICAL > 0) severity = 'CRITICAL';
47+
else if (severityCounts.HIGH > 0) severity = 'HIGH';
48+
else if (severityCounts.MEDIUM > 0) severity = 'MEDIUM';
49+
50+
let recommendation: Recommendation = 'ALLOW';
51+
if (avgScore >= 70) recommendation = 'BLOCK';
52+
else if (avgScore >= 40) recommendation = 'REVIEW';
53+
54+
if (reasons.length === 0) reasons.push('All peer agents report low risk');
2955

3056
return {
31-
final_decision: consensusMet ? 'BLOCK' : 'ALLOW',
32-
risk_score: riskScore,
33-
consensus: `${highRiskCount}/${reports.length} agents`,
34-
summary: consensusMet
35-
? 'Multiple agents flagged high risk – transaction blocked.'
36-
: 'Risk level acceptable – transaction allowed.',
37-
actions: consensusMet ? ['BLOCK_TX'] : ['ALLOW'],
38-
agent_reports: reports,
39-
timestamp: new Date().toISOString(),
57+
agentId: `coord-${crypto.randomUUID().slice(0, 8)}`,
58+
agentType: 'COORDINATOR',
59+
timestamp: Date.now(),
60+
riskScore: avgScore,
61+
confidenceBps: Math.round(totalWeight / peerReports.length * 10_000),
62+
severity,
63+
reasons,
64+
evidence: {
65+
peerCount: peerReports.length,
66+
severityCounts,
67+
avgScore,
68+
},
69+
recommendation,
4070
};
4171
}
Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,28 @@
1-
import type { SwarmConsensusDecision } from '@agent-safe/shared';
1+
import type { SwarmConsensusDecisionV2, ActionIntent, ConsensusDecision } from '@agent-safe/shared';
22

33
/**
4-
* Defender Agent – executes defensive actions when allowed.
5-
* TODO: Implement approval revocation, swap prevention, repayment triggers.
4+
* Defender Agent – executes defensive actions when the swarm consensus
5+
* is BLOCK or REVIEW. Currently simulates execution; real integration
6+
* would submit a UserOp to revoke approvals or cancel pending txns.
67
*/
78
export async function runDefenderAgent(
8-
decision: SwarmConsensusDecision,
9-
): Promise<{ executed: boolean; action: string }> {
10-
// TODO: Revoke approvals via UserOp
11-
// TODO: Prevent swap execution
12-
// TODO: Trigger repayment on lending positions (stretch)
9+
decision: SwarmConsensusDecisionV2,
10+
intent?: ActionIntent | null,
11+
): Promise<{ executed: boolean; action: string; txHash?: string }> {
12+
const shouldAct: ConsensusDecision[] = ['BLOCK', 'REVIEW_REQUIRED'];
1313

14-
if (decision.final_decision === 'EXECUTE_DEFENSE') {
15-
console.log('[DefenderAgent] Would execute defensive action:', decision.actions);
16-
return {
17-
executed: false, // Stub – no real execution
18-
action: 'STUB_DEFENSE_ACTION',
19-
};
14+
if (!shouldAct.includes(decision.decision)) {
15+
return { executed: false, action: 'NO_ACTION_NEEDED' };
2016
}
2117

18+
console.log(
19+
`[DefenderAgent] Would execute defensive action: ${intent?.action ?? 'N/A'}`,
20+
);
21+
22+
// In production this would build + submit a UserOp via EntryPoint.
2223
return {
23-
executed: false,
24-
action: 'NO_ACTION_NEEDED',
24+
executed: false, // stub – no real on-chain tx
25+
action: intent?.action ?? 'BLOCK_TX',
26+
txHash: undefined,
2527
};
2628
}

0 commit comments

Comments
 (0)