@absol-labs/agent 0.9.1 → 0.11.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 +184 -26
- package/dist/capability/invocation-capability.d.ts +18 -6
- package/dist/capability/invocation-capability.d.ts.map +1 -1
- package/dist/capability/invocation-capability.js.map +1 -1
- package/dist/cli/amounts.d.ts +26 -0
- package/dist/cli/amounts.d.ts.map +1 -0
- package/dist/cli/amounts.js +71 -0
- package/dist/cli/amounts.js.map +1 -0
- package/dist/cli/args.d.ts +32 -0
- package/dist/cli/args.d.ts.map +1 -0
- package/dist/cli/args.js +119 -0
- package/dist/cli/args.js.map +1 -0
- package/dist/cli/attesters.d.ts +51 -0
- package/dist/cli/attesters.d.ts.map +1 -0
- package/dist/cli/attesters.js +58 -0
- package/dist/cli/attesters.js.map +1 -0
- package/dist/cli/bin.d.ts +3 -0
- package/dist/cli/bin.d.ts.map +1 -0
- package/dist/cli/bin.js +5 -0
- package/dist/cli/bin.js.map +1 -0
- package/dist/cli/commands/claim.d.ts +13 -0
- package/dist/cli/commands/claim.d.ts.map +1 -0
- package/dist/cli/commands/claim.js +102 -0
- package/dist/cli/commands/claim.js.map +1 -0
- package/dist/cli/commands/close.d.ts +3 -0
- package/dist/cli/commands/close.d.ts.map +1 -0
- package/dist/cli/commands/close.js +55 -0
- package/dist/cli/commands/close.js.map +1 -0
- package/dist/cli/commands/discover.d.ts +3 -0
- package/dist/cli/commands/discover.d.ts.map +1 -0
- package/dist/cli/commands/discover.js +183 -0
- package/dist/cli/commands/discover.js.map +1 -0
- package/dist/cli/commands/invoke.d.ts +3 -0
- package/dist/cli/commands/invoke.d.ts.map +1 -0
- package/dist/cli/commands/invoke.js +142 -0
- package/dist/cli/commands/invoke.js.map +1 -0
- package/dist/cli/commands/mcp.d.ts +3 -0
- package/dist/cli/commands/mcp.d.ts.map +1 -0
- package/dist/cli/commands/mcp.js +113 -0
- package/dist/cli/commands/mcp.js.map +1 -0
- package/dist/cli/commands/open.d.ts +3 -0
- package/dist/cli/commands/open.d.ts.map +1 -0
- package/dist/cli/commands/open.js +331 -0
- package/dist/cli/commands/open.js.map +1 -0
- package/dist/cli/commands/reclaim-unverified.d.ts +3 -0
- package/dist/cli/commands/reclaim-unverified.d.ts.map +1 -0
- package/dist/cli/commands/reclaim-unverified.js +172 -0
- package/dist/cli/commands/reclaim-unverified.js.map +1 -0
- package/dist/cli/commands/reclaim.d.ts +33 -0
- package/dist/cli/commands/reclaim.d.ts.map +1 -0
- package/dist/cli/commands/reclaim.js +308 -0
- package/dist/cli/commands/reclaim.js.map +1 -0
- package/dist/cli/commands/status.d.ts +3 -0
- package/dist/cli/commands/status.d.ts.map +1 -0
- package/dist/cli/commands/status.js +43 -0
- package/dist/cli/commands/status.js.map +1 -0
- package/dist/cli/commands/streams.d.ts +3 -0
- package/dist/cli/commands/streams.d.ts.map +1 -0
- package/dist/cli/commands/streams.js +257 -0
- package/dist/cli/commands/streams.js.map +1 -0
- package/dist/cli/commands/watch.d.ts +3 -0
- package/dist/cli/commands/watch.d.ts.map +1 -0
- package/dist/cli/commands/watch.js +55 -0
- package/dist/cli/commands/watch.js.map +1 -0
- package/dist/cli/config.d.ts +61 -0
- package/dist/cli/config.d.ts.map +1 -0
- package/dist/cli/config.js +140 -0
- package/dist/cli/config.js.map +1 -0
- package/dist/cli/context.d.ts +29 -0
- package/dist/cli/context.d.ts.map +1 -0
- package/dist/cli/context.js +2 -0
- package/dist/cli/context.js.map +1 -0
- package/dist/cli/deps.d.ts +191 -0
- package/dist/cli/deps.d.ts.map +1 -0
- package/dist/cli/deps.js +277 -0
- package/dist/cli/deps.js.map +1 -0
- package/dist/cli/descriptor.d.ts +61 -0
- package/dist/cli/descriptor.d.ts.map +1 -0
- package/dist/cli/descriptor.js +78 -0
- package/dist/cli/descriptor.js.map +1 -0
- package/dist/cli/errors.d.ts +48 -0
- package/dist/cli/errors.d.ts.map +1 -0
- package/dist/cli/errors.js +129 -0
- package/dist/cli/errors.js.map +1 -0
- package/dist/cli/escape-hatch.d.ts +50 -0
- package/dist/cli/escape-hatch.d.ts.map +1 -0
- package/dist/cli/escape-hatch.js +45 -0
- package/dist/cli/escape-hatch.js.map +1 -0
- package/dist/cli/index.d.ts +8 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +7 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/listings.d.ts +15 -0
- package/dist/cli/listings.d.ts.map +1 -0
- package/dist/cli/listings.js +56 -0
- package/dist/cli/listings.js.map +1 -0
- package/dist/cli/mandate.d.ts +66 -0
- package/dist/cli/mandate.d.ts.map +1 -0
- package/dist/cli/mandate.js +61 -0
- package/dist/cli/mandate.js.map +1 -0
- package/dist/cli/output.d.ts +33 -0
- package/dist/cli/output.d.ts.map +1 -0
- package/dist/cli/output.js +34 -0
- package/dist/cli/output.js.map +1 -0
- package/dist/cli/preflight.d.ts +38 -0
- package/dist/cli/preflight.d.ts.map +1 -0
- package/dist/cli/preflight.js +72 -0
- package/dist/cli/preflight.js.map +1 -0
- package/dist/cli/privy.d.ts +83 -0
- package/dist/cli/privy.d.ts.map +1 -0
- package/dist/cli/privy.js +230 -0
- package/dist/cli/privy.js.map +1 -0
- package/dist/cli/progress.d.ts +36 -0
- package/dist/cli/progress.d.ts.map +1 -0
- package/dist/cli/progress.js +40 -0
- package/dist/cli/progress.js.map +1 -0
- package/dist/cli/run.d.ts +25 -0
- package/dist/cli/run.d.ts.map +1 -0
- package/dist/cli/run.js +173 -0
- package/dist/cli/run.js.map +1 -0
- package/dist/cli/scan-range.d.ts +55 -0
- package/dist/cli/scan-range.d.ts.map +1 -0
- package/dist/cli/scan-range.js +57 -0
- package/dist/cli/scan-range.js.map +1 -0
- package/dist/cli/spend-policy.d.ts +60 -0
- package/dist/cli/spend-policy.d.ts.map +1 -0
- package/dist/cli/spend-policy.js +65 -0
- package/dist/cli/spend-policy.js.map +1 -0
- package/dist/cli/status.d.ts +90 -0
- package/dist/cli/status.d.ts.map +1 -0
- package/dist/cli/status.js +252 -0
- package/dist/cli/status.js.map +1 -0
- package/dist/cli/timeout.d.ts +30 -0
- package/dist/cli/timeout.d.ts.map +1 -0
- package/dist/cli/timeout.js +55 -0
- package/dist/cli/timeout.js.map +1 -0
- package/dist/cli/version.d.ts +12 -0
- package/dist/cli/version.d.ts.map +1 -0
- package/dist/cli/version.js +22 -0
- package/dist/cli/version.js.map +1 -0
- package/dist/cli/wallet.d.ts +164 -0
- package/dist/cli/wallet.d.ts.map +1 -0
- package/dist/cli/wallet.js +234 -0
- package/dist/cli/wallet.js.map +1 -0
- package/dist/frameworks/crewai.d.ts +10 -0
- package/dist/frameworks/crewai.d.ts.map +1 -1
- package/dist/frameworks/crewai.js +12 -2
- package/dist/frameworks/crewai.js.map +1 -1
- package/dist/gateway/caller-auth-gateway.d.ts +38 -3
- package/dist/gateway/caller-auth-gateway.d.ts.map +1 -1
- package/dist/gateway/caller-auth-gateway.js +25 -1
- package/dist/gateway/caller-auth-gateway.js.map +1 -1
- package/dist/gateway/http-server.d.ts +53 -0
- package/dist/gateway/http-server.d.ts.map +1 -1
- package/dist/gateway/http-server.js +189 -4
- package/dist/gateway/http-server.js.map +1 -1
- package/dist/gateway/server-entry.d.ts +1 -0
- package/dist/gateway/server-entry.js +1 -0
- package/dist/gateway/server-entry.js.map +1 -1
- package/dist/gateway/x402-mode.d.ts +123 -0
- package/dist/gateway/x402-mode.d.ts.map +1 -0
- package/dist/gateway/x402-mode.js +212 -0
- package/dist/gateway/x402-mode.js.map +1 -0
- package/dist/index.d.ts +6 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -3
- package/dist/index.js.map +1 -1
- package/dist/mandates/env.d.ts +3 -2
- package/dist/mandates/env.d.ts.map +1 -1
- package/dist/mandates/env.js +3 -2
- package/dist/mandates/env.js.map +1 -1
- package/dist/mcp/http-server.d.ts +1 -0
- package/dist/mcp/http-server.js +1 -0
- package/dist/mcp/http-server.js.map +1 -1
- package/dist/mcp/server.d.ts +49 -0
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +533 -25
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/service-invoker.d.ts +97 -0
- package/dist/mcp/service-invoker.d.ts.map +1 -0
- package/dist/mcp/service-invoker.js +167 -0
- package/dist/mcp/service-invoker.js.map +1 -0
- package/dist/mcp/stdio.d.ts +1 -0
- package/dist/mcp/stdio.js +1 -0
- package/dist/mcp/stdio.js.map +1 -1
- package/dist/sdk/client.d.ts +25 -1
- package/dist/sdk/client.d.ts.map +1 -1
- package/dist/sdk/client.js +20 -1
- package/dist/sdk/client.js.map +1 -1
- package/dist/sdk/invoke.d.ts +19 -4
- package/dist/sdk/invoke.d.ts.map +1 -1
- package/dist/sdk/invoke.js +69 -5
- package/dist/sdk/invoke.js.map +1 -1
- package/dist/x402/delivery-oracle.d.ts +108 -0
- package/dist/x402/delivery-oracle.d.ts.map +1 -0
- package/dist/x402/delivery-oracle.js +174 -0
- package/dist/x402/delivery-oracle.js.map +1 -0
- package/dist/x402/facilitator-server-entry.d.ts +3 -0
- package/dist/x402/facilitator-server-entry.d.ts.map +1 -0
- package/dist/x402/facilitator-server-entry.js +27 -0
- package/dist/x402/facilitator-server-entry.js.map +1 -0
- package/dist/x402/facilitator-server.d.ts +73 -0
- package/dist/x402/facilitator-server.d.ts.map +1 -0
- package/dist/x402/facilitator-server.js +305 -0
- package/dist/x402/facilitator-server.js.map +1 -0
- package/dist/x402/facilitator.d.ts +69 -17
- package/dist/x402/facilitator.d.ts.map +1 -1
- package/dist/x402/facilitator.js +76 -33
- package/dist/x402/facilitator.js.map +1 -1
- package/dist/x402/payment-requirements.d.ts +106 -0
- package/dist/x402/payment-requirements.d.ts.map +1 -0
- package/dist/x402/payment-requirements.js +138 -0
- package/dist/x402/payment-requirements.js.map +1 -0
- package/dist/x402/relayer.d.ts +41 -0
- package/dist/x402/relayer.d.ts.map +1 -0
- package/dist/x402/relayer.js +64 -0
- package/dist/x402/relayer.js.map +1 -0
- package/dist/x402/resource-index.d.ts +75 -0
- package/dist/x402/resource-index.d.ts.map +1 -0
- package/dist/x402/resource-index.js +126 -0
- package/dist/x402/resource-index.js.map +1 -0
- package/dist/x402/verified-delivery-facilitator.d.ts +285 -0
- package/dist/x402/verified-delivery-facilitator.d.ts.map +1 -0
- package/dist/x402/verified-delivery-facilitator.js +423 -0
- package/dist/x402/verified-delivery-facilitator.js.map +1 -0
- package/dist/zktls/t2-delivery-proof.d.ts +6 -6
- package/docs/agent-layer.md +14 -9
- package/docs/cli.md +705 -0
- package/docs/crewai.md +6 -2
- package/docs/eliza.md +6 -1
- package/docs/mcp-hosted.md +7 -5
- package/docs/mcp-reference.md +175 -0
- package/docs/quickstart.md +36 -9
- package/docs/threat-model.md +4 -3
- package/docs/x402.md +648 -0
- package/package.json +33 -10
- package/src/capability/invocation-capability.ts +17 -1
- package/src/cli/amounts.ts +102 -0
- package/src/cli/args.ts +150 -0
- package/src/cli/attesters.ts +86 -0
- package/src/cli/bin.ts +5 -0
- package/src/cli/commands/claim.ts +126 -0
- package/src/cli/commands/close.ts +69 -0
- package/src/cli/commands/discover.ts +228 -0
- package/src/cli/commands/invoke.ts +178 -0
- package/src/cli/commands/mcp.ts +135 -0
- package/src/cli/commands/open.ts +397 -0
- package/src/cli/commands/reclaim-unverified.ts +218 -0
- package/src/cli/commands/reclaim.ts +377 -0
- package/src/cli/commands/status.ts +56 -0
- package/src/cli/commands/streams.ts +344 -0
- package/src/cli/commands/watch.ts +70 -0
- package/src/cli/config.ts +208 -0
- package/src/cli/context.ts +30 -0
- package/src/cli/deps.ts +528 -0
- package/src/cli/descriptor.ts +133 -0
- package/src/cli/errors.ts +162 -0
- package/src/cli/escape-hatch.ts +92 -0
- package/src/cli/index.ts +27 -0
- package/src/cli/listings.ts +77 -0
- package/src/cli/mandate.ts +126 -0
- package/src/cli/output.ts +63 -0
- package/src/cli/preflight.ts +88 -0
- package/src/cli/privy.ts +396 -0
- package/src/cli/progress.ts +75 -0
- package/src/cli/run.ts +209 -0
- package/src/cli/scan-range.ts +76 -0
- package/src/cli/spend-policy.ts +139 -0
- package/src/cli/status.ts +381 -0
- package/src/cli/timeout.ts +64 -0
- package/src/cli/version.ts +21 -0
- package/src/cli/wallet.ts +372 -0
- package/src/frameworks/crewai.ts +13 -2
- package/src/gateway/caller-auth-gateway.ts +77 -6
- package/src/gateway/http-server.ts +280 -6
- package/src/gateway/server-entry.ts +1 -0
- package/src/gateway/x402-mode.ts +350 -0
- package/src/index.ts +40 -5
- package/src/mandates/env.ts +3 -2
- package/src/mcp/http-server.ts +1 -0
- package/src/mcp/server.ts +706 -30
- package/src/mcp/service-invoker.ts +285 -0
- package/src/mcp/stdio.ts +1 -0
- package/src/sdk/client.ts +33 -1
- package/src/sdk/invoke.ts +90 -12
- package/src/x402/delivery-oracle.ts +285 -0
- package/src/x402/facilitator-server-entry.ts +35 -0
- package/src/x402/facilitator-server.ts +445 -0
- package/src/x402/facilitator.ts +125 -46
- package/src/x402/payment-requirements.ts +231 -0
- package/src/x402/relayer.ts +122 -0
- package/src/x402/resource-index.ts +228 -0
- package/src/x402/verified-delivery-facilitator.ts +744 -0
|
@@ -12,8 +12,18 @@ import {
|
|
|
12
12
|
CallerAuthGateway,
|
|
13
13
|
DEFAULT_MAX_STATE_STALENESS_SECONDS,
|
|
14
14
|
MAX_CORROBORATING_STREAM_READERS,
|
|
15
|
+
type CallerAuthDecision,
|
|
15
16
|
type CallerAuthDenialReason,
|
|
16
17
|
} from "./caller-auth-gateway.js";
|
|
18
|
+
import {
|
|
19
|
+
createHttpFacilitatorClient,
|
|
20
|
+
createListingPriceQuoter,
|
|
21
|
+
encodeSettleResponseHeader,
|
|
22
|
+
X402_PAYMENT_RESPONSE_HEADER,
|
|
23
|
+
X402_SETTLEMENT_HEADROOM_SECONDS,
|
|
24
|
+
type GatewayX402Mode,
|
|
25
|
+
} from "./x402-mode.js";
|
|
26
|
+
import { discoverServices } from "../discovery/registry.js";
|
|
17
27
|
|
|
18
28
|
/**
|
|
19
29
|
* Reference standalone reverse-proxy gateway server (metrik-agent#63).
|
|
@@ -49,6 +59,12 @@ export interface CallerAuthGatewayServerConfig {
|
|
|
49
59
|
readonly upstreamUrl: string;
|
|
50
60
|
/** Extra request-timeout for the upstream fetch, in ms. Default 30000. */
|
|
51
61
|
readonly upstreamTimeoutMs?: number;
|
|
62
|
+
/**
|
|
63
|
+
* The same {@link GatewayX402Mode} given to the gateway. Required for the
|
|
64
|
+
* x402 path: the gateway VERIFIES a payment, this server SETTLES it once the
|
|
65
|
+
* upstream has produced a response.
|
|
66
|
+
*/
|
|
67
|
+
readonly x402?: GatewayX402Mode;
|
|
52
68
|
}
|
|
53
69
|
|
|
54
70
|
export interface CallerAuthGatewayServer {
|
|
@@ -70,6 +86,7 @@ export function createCallerAuthGatewayServer(
|
|
|
70
86
|
config.gateway,
|
|
71
87
|
upstreamBase,
|
|
72
88
|
upstreamTimeoutMs,
|
|
89
|
+
config.x402,
|
|
73
90
|
).catch((error) => {
|
|
74
91
|
logError("unhandled request error", error);
|
|
75
92
|
if (!res.headersSent) {
|
|
@@ -105,6 +122,7 @@ async function handleRequest(
|
|
|
105
122
|
gateway: CallerAuthGateway,
|
|
106
123
|
upstreamBase: URL,
|
|
107
124
|
upstreamTimeoutMs: number,
|
|
125
|
+
x402: GatewayX402Mode | undefined,
|
|
108
126
|
): Promise<void> {
|
|
109
127
|
const requestUrl = new URL(req.url ?? "/", "http://localhost");
|
|
110
128
|
const method = req.method ?? "GET";
|
|
@@ -117,6 +135,14 @@ async function handleRequest(
|
|
|
117
135
|
});
|
|
118
136
|
|
|
119
137
|
if (!decision.authorized) {
|
|
138
|
+
// The x402 challenge is a PROTOCOL body, not Metrik's `{authorized,reason}`
|
|
139
|
+
// shape: `x402-fetch` parses `{x402Version, accepts}` and nothing else.
|
|
140
|
+
if (decision.x402PaymentRequired !== undefined) {
|
|
141
|
+
res
|
|
142
|
+
.writeHead(402, { "Content-Type": "application/json" })
|
|
143
|
+
.end(JSON.stringify(decision.x402PaymentRequired));
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
120
146
|
writeJsonError(
|
|
121
147
|
res,
|
|
122
148
|
decision.httpStatus ?? 403,
|
|
@@ -143,8 +169,23 @@ async function handleRequest(
|
|
|
143
169
|
upstreamBase,
|
|
144
170
|
);
|
|
145
171
|
|
|
172
|
+
// Bound the upstream by the payment's OWN deadline, not just by the server's
|
|
173
|
+
// timeout. Settlement happens after the upstream returns, so an upstream that
|
|
174
|
+
// outlives the signed authorization would produce its side effects and then
|
|
175
|
+
// be unsettleable — the caller would get the work for free (never the response
|
|
176
|
+
// body, which is only flushed after settlement, but the side effects are
|
|
177
|
+
// real). Aborting early turns that into an ordinary 502.
|
|
178
|
+
const effectiveTimeoutMs = boundedUpstreamTimeoutMs(
|
|
179
|
+
upstreamTimeoutMs,
|
|
180
|
+
decision.x402Payment?.validBefore,
|
|
181
|
+
);
|
|
182
|
+
if (effectiveTimeoutMs <= 0) {
|
|
183
|
+
writeJsonError(res, 402, "x402-authorization-too-short");
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
|
|
146
187
|
const controller = new AbortController();
|
|
147
|
-
const timeout = setTimeout(() => controller.abort(),
|
|
188
|
+
const timeout = setTimeout(() => controller.abort(), effectiveTimeoutMs);
|
|
148
189
|
try {
|
|
149
190
|
const upstreamResponse = await fetch(upstreamUrl, {
|
|
150
191
|
method,
|
|
@@ -153,11 +194,26 @@ async function handleRequest(
|
|
|
153
194
|
signal: controller.signal,
|
|
154
195
|
});
|
|
155
196
|
|
|
156
|
-
res.writeHead(
|
|
157
|
-
upstreamResponse.status,
|
|
158
|
-
headersToObject(upstreamResponse.headers),
|
|
159
|
-
);
|
|
160
197
|
const buffer = Buffer.from(await upstreamResponse.arrayBuffer());
|
|
198
|
+
const responseHeaders = headersToObject(upstreamResponse.headers);
|
|
199
|
+
|
|
200
|
+
// Settle AFTER the upstream produced a response and BEFORE that response is
|
|
201
|
+
// flushed. Buyer-favouring on both edges: a call that never produced a
|
|
202
|
+
// response is never charged for, and a payment that cannot be settled
|
|
203
|
+
// returns `402` instead of handing over the goods for free.
|
|
204
|
+
if (decision.x402Payment !== undefined) {
|
|
205
|
+
const settled = await settleX402(decision, x402);
|
|
206
|
+
if (settled === undefined) {
|
|
207
|
+
writeJsonError(res, 402, "x402-settlement-failed");
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
responseHeaders[X402_PAYMENT_RESPONSE_HEADER] =
|
|
211
|
+
encodeSettleResponseHeader(settled);
|
|
212
|
+
responseHeaders["access-control-expose-headers"] =
|
|
213
|
+
X402_PAYMENT_RESPONSE_HEADER;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
res.writeHead(upstreamResponse.status, responseHeaders);
|
|
161
217
|
res.end(buffer);
|
|
162
218
|
} catch (error) {
|
|
163
219
|
logError("upstream request failed", error);
|
|
@@ -167,6 +223,67 @@ async function handleRequest(
|
|
|
167
223
|
}
|
|
168
224
|
}
|
|
169
225
|
|
|
226
|
+
/**
|
|
227
|
+
* The upstream budget, capped so the signed authorization is still settleable
|
|
228
|
+
* when the call returns. Returns <= 0 when there is no usable window at all.
|
|
229
|
+
*/
|
|
230
|
+
export function boundedUpstreamTimeoutMs(
|
|
231
|
+
upstreamTimeoutMs: number,
|
|
232
|
+
validBeforeSeconds: number | undefined,
|
|
233
|
+
nowSeconds: number = Math.floor(Date.now() / 1000),
|
|
234
|
+
): number {
|
|
235
|
+
if (validBeforeSeconds === undefined) {
|
|
236
|
+
return upstreamTimeoutMs;
|
|
237
|
+
}
|
|
238
|
+
const remainingSeconds =
|
|
239
|
+
validBeforeSeconds - nowSeconds - X402_SETTLEMENT_HEADROOM_SECONDS;
|
|
240
|
+
if (remainingSeconds <= 0) {
|
|
241
|
+
return 0;
|
|
242
|
+
}
|
|
243
|
+
return Math.min(upstreamTimeoutMs, remainingSeconds * 1000);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/** Returns the settlement result, or `undefined` if nothing was settled. */
|
|
247
|
+
async function settleX402(
|
|
248
|
+
decision: CallerAuthDecision,
|
|
249
|
+
x402: GatewayX402Mode | undefined,
|
|
250
|
+
): Promise<
|
|
251
|
+
Awaited<ReturnType<GatewayX402Mode["facilitator"]["settle"]>> | undefined
|
|
252
|
+
> {
|
|
253
|
+
const payment = decision.x402Payment;
|
|
254
|
+
if (payment === undefined) {
|
|
255
|
+
return undefined;
|
|
256
|
+
}
|
|
257
|
+
if (x402 === undefined) {
|
|
258
|
+
// The gateway accepted an x402 payment but this server was given no
|
|
259
|
+
// facilitator to settle it with. Refuse: serving here would be free service.
|
|
260
|
+
logError(
|
|
261
|
+
"x402 settlement",
|
|
262
|
+
new Error(
|
|
263
|
+
"gateway accepted an x402 payment but the server has no x402 facilitator configured",
|
|
264
|
+
),
|
|
265
|
+
);
|
|
266
|
+
return undefined;
|
|
267
|
+
}
|
|
268
|
+
try {
|
|
269
|
+
const settled = await x402.facilitator.settle(
|
|
270
|
+
payment.payload,
|
|
271
|
+
payment.requirements,
|
|
272
|
+
);
|
|
273
|
+
if (settled.success !== true) {
|
|
274
|
+
logError(
|
|
275
|
+
"x402 settlement",
|
|
276
|
+
new Error(settled.errorReason ?? "facilitator reported failure"),
|
|
277
|
+
);
|
|
278
|
+
return undefined;
|
|
279
|
+
}
|
|
280
|
+
return settled;
|
|
281
|
+
} catch (error) {
|
|
282
|
+
logError("x402 settlement", error);
|
|
283
|
+
return undefined;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
170
287
|
function normalizeIncomingHeaders(
|
|
171
288
|
headers: IncomingMessage["headers"],
|
|
172
289
|
): Record<string, string | undefined> {
|
|
@@ -210,7 +327,9 @@ function writeJsonError(
|
|
|
210
327
|
| CallerAuthDenialReason
|
|
211
328
|
| "unauthorized"
|
|
212
329
|
| "internal-error"
|
|
213
|
-
| "upstream-unavailable"
|
|
330
|
+
| "upstream-unavailable"
|
|
331
|
+
| "x402-settlement-failed"
|
|
332
|
+
| "x402-authorization-too-short",
|
|
214
333
|
): void {
|
|
215
334
|
if (res.headersSent) {
|
|
216
335
|
res.end();
|
|
@@ -242,6 +361,22 @@ export interface CallerAuthGatewayEnvConfig {
|
|
|
242
361
|
readonly corroboratingRpcUrls: readonly string[];
|
|
243
362
|
/** Freshness bound for on-chain stream state, in seconds. */
|
|
244
363
|
readonly maxStateStalenessSeconds: number;
|
|
364
|
+
/** Set only when standard-x402 mode is enabled. */
|
|
365
|
+
readonly x402?: CallerAuthGatewayX402EnvConfig;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
export interface CallerAuthGatewayX402EnvConfig {
|
|
369
|
+
/** Per-call price in the asset's base units (micro-USDC). Seller-set, unsigned. */
|
|
370
|
+
readonly pricePerCallBaseUnits: bigint;
|
|
371
|
+
/** A facilitator exposing `/verify` and `/settle` — e.g. Metrik's own. */
|
|
372
|
+
readonly facilitatorUrl: string;
|
|
373
|
+
/** The public origin buyers reach this gateway on. Becomes `resource`. */
|
|
374
|
+
readonly resourceBaseUrl: string;
|
|
375
|
+
/** Listing source used to derive `payTo` from the operator-signed descriptor. */
|
|
376
|
+
readonly registryUrl?: string;
|
|
377
|
+
readonly maxTimeoutSeconds?: number;
|
|
378
|
+
/** Bearer token for the facilitator's `/settle`. Required by Metrik's own. */
|
|
379
|
+
readonly facilitatorToken?: string;
|
|
245
380
|
}
|
|
246
381
|
|
|
247
382
|
/**
|
|
@@ -346,6 +481,8 @@ export function parseCallerAuthGatewayEnvConfig(
|
|
|
346
481
|
);
|
|
347
482
|
}
|
|
348
483
|
|
|
484
|
+
const x402 = parseGatewayX402EnvConfig(env);
|
|
485
|
+
|
|
349
486
|
return {
|
|
350
487
|
host,
|
|
351
488
|
port,
|
|
@@ -360,6 +497,132 @@ export function parseCallerAuthGatewayEnvConfig(
|
|
|
360
497
|
corroboratingRpcUrls,
|
|
361
498
|
maxStateStalenessSeconds,
|
|
362
499
|
...(headerName === undefined ? {} : { headerName }),
|
|
500
|
+
...(x402 === undefined ? {} : { x402 }),
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
/**
|
|
505
|
+
* x402 mode is OFF unless `METRIK_GATEWAY_X402_PRICE_BASE_UNITS` is set. Once
|
|
506
|
+
* on, every other x402 var is required: a half-configured paywall would answer
|
|
507
|
+
* `402` with a quote it cannot settle.
|
|
508
|
+
*
|
|
509
|
+
* Note what is NOT here: `payTo`. It is never configurable — it is resolved at
|
|
510
|
+
* startup from the operator-signed descriptor for this gateway's `serviceRef`
|
|
511
|
+
* (see {@link resolveSignedListingPayee}), and the gateway refuses to start if
|
|
512
|
+
* that lookup fails.
|
|
513
|
+
*/
|
|
514
|
+
export function parseGatewayX402EnvConfig(
|
|
515
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
516
|
+
): CallerAuthGatewayX402EnvConfig | undefined {
|
|
517
|
+
const rawPrice = env.METRIK_GATEWAY_X402_PRICE_BASE_UNITS?.trim();
|
|
518
|
+
if (rawPrice === undefined || rawPrice.length === 0) {
|
|
519
|
+
return undefined;
|
|
520
|
+
}
|
|
521
|
+
let pricePerCallBaseUnits: bigint;
|
|
522
|
+
try {
|
|
523
|
+
pricePerCallBaseUnits = BigInt(rawPrice);
|
|
524
|
+
} catch {
|
|
525
|
+
throw new Error(
|
|
526
|
+
`invalid METRIK_GATEWAY_X402_PRICE_BASE_UNITS: ${rawPrice} (integer base units, e.g. 1000 = 0.001 USDC)`,
|
|
527
|
+
);
|
|
528
|
+
}
|
|
529
|
+
if (pricePerCallBaseUnits <= 0n) {
|
|
530
|
+
throw new Error("METRIK_GATEWAY_X402_PRICE_BASE_UNITS must be positive");
|
|
531
|
+
}
|
|
532
|
+
const facilitatorUrl = requireEnv(env, "METRIK_GATEWAY_X402_FACILITATOR_URL");
|
|
533
|
+
const resourceBaseUrl = requireEnv(
|
|
534
|
+
env,
|
|
535
|
+
"METRIK_GATEWAY_X402_RESOURCE_BASE_URL",
|
|
536
|
+
);
|
|
537
|
+
for (const candidate of [facilitatorUrl, resourceBaseUrl]) {
|
|
538
|
+
// eslint-disable-next-line no-new -- validates the URL is well-formed.
|
|
539
|
+
new URL(candidate);
|
|
540
|
+
}
|
|
541
|
+
const registryUrl = env.METRIK_GATEWAY_X402_REGISTRY_URL;
|
|
542
|
+
const facilitatorToken = env.METRIK_GATEWAY_X402_FACILITATOR_TOKEN?.trim();
|
|
543
|
+
const rawTimeout = env.METRIK_GATEWAY_X402_MAX_TIMEOUT_SECONDS;
|
|
544
|
+
let maxTimeoutSeconds: number | undefined;
|
|
545
|
+
if (rawTimeout !== undefined && rawTimeout.trim().length > 0) {
|
|
546
|
+
maxTimeoutSeconds = Number(rawTimeout);
|
|
547
|
+
if (!Number.isInteger(maxTimeoutSeconds) || maxTimeoutSeconds <= 0) {
|
|
548
|
+
throw new Error(
|
|
549
|
+
`invalid METRIK_GATEWAY_X402_MAX_TIMEOUT_SECONDS: ${rawTimeout}`,
|
|
550
|
+
);
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
return {
|
|
555
|
+
pricePerCallBaseUnits,
|
|
556
|
+
facilitatorUrl,
|
|
557
|
+
resourceBaseUrl,
|
|
558
|
+
...(registryUrl === undefined ? {} : { registryUrl }),
|
|
559
|
+
...(maxTimeoutSeconds === undefined ? {} : { maxTimeoutSeconds }),
|
|
560
|
+
...(facilitatorToken === undefined || facilitatorToken.length === 0
|
|
561
|
+
? {}
|
|
562
|
+
: { facilitatorToken }),
|
|
563
|
+
};
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
export class UnresolvedListingPayeeError extends Error {
|
|
567
|
+
constructor(serviceRef: string) {
|
|
568
|
+
super(
|
|
569
|
+
`no operator-signed listing found for serviceRef ${serviceRef}. ` +
|
|
570
|
+
"x402 mode refuses to start: payTo is derived from the signed descriptor and " +
|
|
571
|
+
"is never taken from configuration, so an unresolved listing has no payee.",
|
|
572
|
+
);
|
|
573
|
+
this.name = "UnresolvedListingPayeeError";
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
/**
|
|
578
|
+
* Resolves `{serviceRef, operator}` from the registry. `discoverServices`
|
|
579
|
+
* re-derives `serviceRef` from the signed content and recovers the operator's
|
|
580
|
+
* EIP-712 signature, dropping anything that fails — so what comes back here is
|
|
581
|
+
* signed, not merely served.
|
|
582
|
+
*/
|
|
583
|
+
export async function resolveSignedListingPayee(
|
|
584
|
+
serviceRef: Hex,
|
|
585
|
+
registryUrl?: string,
|
|
586
|
+
): Promise<{ readonly serviceRef: Hex; readonly operator: Address }> {
|
|
587
|
+
const listings = await discoverServices(
|
|
588
|
+
registryUrl === undefined ? {} : { registryUrl },
|
|
589
|
+
);
|
|
590
|
+
const match = listings.find(
|
|
591
|
+
(listing) => listing.serviceRef.toLowerCase() === serviceRef.toLowerCase(),
|
|
592
|
+
);
|
|
593
|
+
if (match === undefined) {
|
|
594
|
+
throw new UnresolvedListingPayeeError(serviceRef);
|
|
595
|
+
}
|
|
596
|
+
return { serviceRef: match.serviceRef, operator: match.operator };
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
/** Builds the gateway's x402 mode, deriving `payTo` from the signed listing. */
|
|
600
|
+
export async function buildGatewayX402Mode(
|
|
601
|
+
config: CallerAuthGatewayEnvConfig,
|
|
602
|
+
): Promise<GatewayX402Mode | undefined> {
|
|
603
|
+
if (config.x402 === undefined) {
|
|
604
|
+
return undefined;
|
|
605
|
+
}
|
|
606
|
+
const listing = await resolveSignedListingPayee(
|
|
607
|
+
config.serviceRef,
|
|
608
|
+
config.x402.registryUrl,
|
|
609
|
+
);
|
|
610
|
+
return {
|
|
611
|
+
quote: createListingPriceQuoter({
|
|
612
|
+
listing,
|
|
613
|
+
resourceBaseUrl: config.x402.resourceBaseUrl,
|
|
614
|
+
pricePerCallBaseUnits: config.x402.pricePerCallBaseUnits,
|
|
615
|
+
chainId: config.chainId,
|
|
616
|
+
...(config.x402.maxTimeoutSeconds === undefined
|
|
617
|
+
? {}
|
|
618
|
+
: { maxTimeoutSeconds: config.x402.maxTimeoutSeconds }),
|
|
619
|
+
}),
|
|
620
|
+
facilitator: createHttpFacilitatorClient({
|
|
621
|
+
url: config.x402.facilitatorUrl,
|
|
622
|
+
...(config.x402.facilitatorToken === undefined
|
|
623
|
+
? {}
|
|
624
|
+
: { token: config.x402.facilitatorToken }),
|
|
625
|
+
}),
|
|
363
626
|
};
|
|
364
627
|
}
|
|
365
628
|
|
|
@@ -376,6 +639,7 @@ export async function startCallerAuthGatewayServerFromEnv(
|
|
|
376
639
|
env: NodeJS.ProcessEnv = process.env,
|
|
377
640
|
): Promise<CallerAuthGatewayServer & { readonly boundPort: number }> {
|
|
378
641
|
const config = parseCallerAuthGatewayEnvConfig(env);
|
|
642
|
+
const x402 = await buildGatewayX402Mode(config);
|
|
379
643
|
const gateway = new CallerAuthGateway({
|
|
380
644
|
escrowAddress: config.escrowAddress,
|
|
381
645
|
rpcUrl: config.rpcUrl,
|
|
@@ -387,6 +651,7 @@ export async function startCallerAuthGatewayServerFromEnv(
|
|
|
387
651
|
...(config.headerName === undefined
|
|
388
652
|
? {}
|
|
389
653
|
: { headerName: config.headerName }),
|
|
654
|
+
...(x402 === undefined ? {} : { x402 }),
|
|
390
655
|
});
|
|
391
656
|
if (config.corroboratingRpcUrls.length === 0) {
|
|
392
657
|
console.error(
|
|
@@ -397,9 +662,18 @@ export async function startCallerAuthGatewayServerFromEnv(
|
|
|
397
662
|
"one INDEPENDENT provider to close that window.",
|
|
398
663
|
);
|
|
399
664
|
}
|
|
665
|
+
if (x402 !== undefined && config.x402 !== undefined) {
|
|
666
|
+
console.error(
|
|
667
|
+
"[metrik-caller-auth-gateway] standard x402 mode ENABLED: callers with no Metrik " +
|
|
668
|
+
`capability are quoted ${config.x402.pricePerCallBaseUnits} base units per call, ` +
|
|
669
|
+
`settled through ${config.x402.facilitatorUrl}. This is a per-call payment with NO ` +
|
|
670
|
+
"escrow and NO recoverability — the stream path remains the only recoverable one.",
|
|
671
|
+
);
|
|
672
|
+
}
|
|
400
673
|
const server = createCallerAuthGatewayServer({
|
|
401
674
|
gateway,
|
|
402
675
|
upstreamUrl: config.upstreamUrl,
|
|
676
|
+
...(x402 === undefined ? {} : { x402 }),
|
|
403
677
|
});
|
|
404
678
|
const { port } = await server.listen(config.port, config.host);
|
|
405
679
|
return Object.assign(server, { boundPort: port });
|