-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathserver.mjs
More file actions
101 lines (91 loc) · 3.61 KB
/
Copy pathserver.mjs
File metadata and controls
101 lines (91 loc) · 3.61 KB
1
2
3
4
5
6
7
8
9
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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
/**
* Cloud Run entrypoint.
*
* The agent is the product; this is only the sleeve that lets Google Cloud run
* it and lets a judge watch it work. Two endpoints and no framework:
*
* GET / health + what this is
* POST /run run the agent once and stream back the audit trail
* GET /run same, for a judge who wants to click a link
*
* Every payment the agent makes is reported with the payer address and a
* block-explorer URL, because "trust our logs" is not evidence.
*/
import { createServer } from "node:http";
import { DEFAULT_POLICY, run } from "./src/agent.mjs";
import { formatUnits } from "./src/x402.mjs";
const PORT = Number(process.env.PORT || 8080);
const API = (process.env.PREDGE_API || "https://x402-api-production-266e.up.railway.app").replace(/\/$/, "");
/** Routes the agent may buy. Kept explicit — an agent with an open-ended shopping list is a liability. */
const ROUTES = (process.env.AGENT_ROUTES || "/v1/whales/latest?limit=5").split(",").map((r) => API + r.trim());
/** Lazily import the wallet so the service still boots (and reports why) without credentials. */
async function loadWallet() {
const mod = await import("./src/wallet.mjs");
return mod.createWallet();
}
function json(res, status, body) {
const payload = JSON.stringify(body, (_k, v) => (typeof v === "bigint" ? v.toString() : v), 2);
res.writeHead(status, { "content-type": "application/json; charset=utf-8", "cache-control": "no-store" });
res.end(payload);
}
async function handleRun(res) {
const events = [];
const onEvent = (event, detail) => {
const entry = { at: new Date().toISOString(), event, ...detail };
events.push(entry);
console.log(JSON.stringify(entry)); // Cloud Logging picks up structured stdout
};
let wallet;
try {
wallet = await loadWallet();
} catch (e) {
// Fail loudly and specifically: a demo that silently runs without a wallet
// would "succeed" while proving nothing.
return json(res, 503, {
ok: false,
error: "wallet unavailable",
detail: e.message,
hint: "set the Circle Agent Wallet credentials — see README",
});
}
const started = Date.now();
const out = await run({ wallet, routes: ROUTES, policy: DEFAULT_POLICY, onEvent });
const payments = out.results.filter((r) => r.payment).map((r) => r.payment);
json(res, 200, {
ok: out.results.every((r) => r.ok),
agent: { address: await wallet.address(), policy: describePolicy(DEFAULT_POLICY) },
spent_usdc: out.spent,
payments, // payer + txHash + explorerUrl — the proof, not the claim
results: out.results,
events,
took_ms: Date.now() - started,
});
}
function describePolicy(p) {
return {
network: p.network,
max_per_call_usdc: formatUnits(p.maxAmountAtomic),
session_budget_usdc: formatUnits(p.sessionBudgetAtomic),
asset_allowlist: p.allowedAssets,
};
}
createServer(async (req, res) => {
const url = new URL(req.url, `http://${req.headers.host}`);
try {
if (url.pathname === "/run") return await handleRun(res);
if (url.pathname === "/") {
return json(res, 200, {
service: "predge-agentpay",
what: "An AI agent that buys verifiable market intelligence and pays for it itself, per call, in USDC.",
try_it: "POST /run (or GET /run)",
api: API,
routes: ROUTES,
policy: describePolicy(DEFAULT_POLICY),
});
}
json(res, 404, { error: "not found" });
} catch (e) {
console.error(e);
json(res, 500, { error: "internal", detail: e.message });
}
}).listen(PORT, () => console.log(JSON.stringify({ event: "listening", port: PORT, api: API })));