@tabai/sdk 0.2.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/LICENSE +23 -0
- package/README.md +401 -0
- package/bin/tab.mjs +23 -0
- package/dist/_shared/abi.d.ts +150 -0
- package/dist/_shared/abi.d.ts.map +1 -0
- package/dist/_shared/abi.js +197 -0
- package/dist/_shared/abi.js.map +1 -0
- package/dist/_shared/chains.d.ts +118 -0
- package/dist/_shared/chains.d.ts.map +1 -0
- package/dist/_shared/chains.js +89 -0
- package/dist/_shared/chains.js.map +1 -0
- package/dist/_shared/hex.d.ts +35 -0
- package/dist/_shared/hex.d.ts.map +1 -0
- package/dist/_shared/hex.js +40 -0
- package/dist/_shared/hex.js.map +1 -0
- package/dist/_shared/index.d.ts +14 -0
- package/dist/_shared/index.d.ts.map +1 -0
- package/dist/_shared/index.js +14 -0
- package/dist/_shared/index.js.map +1 -0
- package/dist/_shared/keccak256.d.ts +29 -0
- package/dist/_shared/keccak256.d.ts.map +1 -0
- package/dist/_shared/keccak256.js +145 -0
- package/dist/_shared/keccak256.js.map +1 -0
- package/dist/_shared/result.d.ts +78 -0
- package/dist/_shared/result.d.ts.map +1 -0
- package/dist/_shared/result.js +61 -0
- package/dist/_shared/result.js.map +1 -0
- package/dist/cli/client-config.d.ts +155 -0
- package/dist/cli/client-config.d.ts.map +1 -0
- package/dist/cli/client-config.js +382 -0
- package/dist/cli/client-config.js.map +1 -0
- package/dist/cli/connect.d.ts +76 -0
- package/dist/cli/connect.d.ts.map +1 -0
- package/dist/cli/connect.js +158 -0
- package/dist/cli/connect.js.map +1 -0
- package/dist/cli/doctor.d.ts +57 -0
- package/dist/cli/doctor.d.ts.map +1 -0
- package/dist/cli/doctor.js +253 -0
- package/dist/cli/doctor.js.map +1 -0
- package/dist/cli/index.d.ts +13 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +13 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/main.d.ts +45 -0
- package/dist/cli/main.d.ts.map +1 -0
- package/dist/cli/main.js +371 -0
- package/dist/cli/main.js.map +1 -0
- package/dist/errors.d.ts +29 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +37 -0
- package/dist/errors.js.map +1 -0
- package/dist/http/client-402.d.ts +243 -0
- package/dist/http/client-402.d.ts.map +1 -0
- package/dist/http/client-402.js +515 -0
- package/dist/http/client-402.js.map +1 -0
- package/dist/http/headers.d.ts +173 -0
- package/dist/http/headers.d.ts.map +1 -0
- package/dist/http/headers.js +284 -0
- package/dist/http/headers.js.map +1 -0
- package/dist/http/index.d.ts +15 -0
- package/dist/http/index.d.ts.map +1 -0
- package/dist/http/index.js +15 -0
- package/dist/http/index.js.map +1 -0
- package/dist/http/metering-claim.d.ts +82 -0
- package/dist/http/metering-claim.d.ts.map +1 -0
- package/dist/http/metering-claim.js +99 -0
- package/dist/http/metering-claim.js.map +1 -0
- package/dist/index.d.ts +48 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +51 -0
- package/dist/index.js.map +1 -0
- package/dist/logger.d.ts +40 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +50 -0
- package/dist/logger.js.map +1 -0
- package/dist/mcp/assets.d.ts +31 -0
- package/dist/mcp/assets.d.ts.map +1 -0
- package/dist/mcp/assets.js +78 -0
- package/dist/mcp/assets.js.map +1 -0
- package/dist/mcp/index.d.ts +19 -0
- package/dist/mcp/index.d.ts.map +1 -0
- package/dist/mcp/index.js +19 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/mcp/json-schema.d.ts +86 -0
- package/dist/mcp/json-schema.d.ts.map +1 -0
- package/dist/mcp/json-schema.js +215 -0
- package/dist/mcp/json-schema.js.map +1 -0
- package/dist/mcp/json.d.ts +43 -0
- package/dist/mcp/json.d.ts.map +1 -0
- package/dist/mcp/json.js +69 -0
- package/dist/mcp/json.js.map +1 -0
- package/dist/mcp/registry-client.d.ts +88 -0
- package/dist/mcp/registry-client.d.ts.map +1 -0
- package/dist/mcp/registry-client.js +158 -0
- package/dist/mcp/registry-client.js.map +1 -0
- package/dist/mcp/schemas.d.ts +82 -0
- package/dist/mcp/schemas.d.ts.map +1 -0
- package/dist/mcp/schemas.js +493 -0
- package/dist/mcp/schemas.js.map +1 -0
- package/dist/mcp/server.d.ts +97 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +285 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/settings.d.ts +90 -0
- package/dist/mcp/settings.d.ts.map +1 -0
- package/dist/mcp/settings.js +160 -0
- package/dist/mcp/settings.js.map +1 -0
- package/dist/mcp/toolset.d.ts +231 -0
- package/dist/mcp/toolset.d.ts.map +1 -0
- package/dist/mcp/toolset.js +760 -0
- package/dist/mcp/toolset.js.map +1 -0
- package/dist/payments/abi.d.ts +9 -0
- package/dist/payments/abi.d.ts.map +1 -0
- package/dist/payments/abi.js +17 -0
- package/dist/payments/abi.js.map +1 -0
- package/dist/payments/config.d.ts +199 -0
- package/dist/payments/config.d.ts.map +1 -0
- package/dist/payments/config.js +259 -0
- package/dist/payments/config.js.map +1 -0
- package/dist/payments/index.d.ts +13 -0
- package/dist/payments/index.d.ts.map +1 -0
- package/dist/payments/index.js +13 -0
- package/dist/payments/index.js.map +1 -0
- package/dist/payments/kuru.d.ts +191 -0
- package/dist/payments/kuru.d.ts.map +1 -0
- package/dist/payments/kuru.js +377 -0
- package/dist/payments/kuru.js.map +1 -0
- package/dist/payments/monad.d.ts +69 -0
- package/dist/payments/monad.d.ts.map +1 -0
- package/dist/payments/monad.js +306 -0
- package/dist/payments/monad.js.map +1 -0
- package/dist/payments/permit2.d.ts +118 -0
- package/dist/payments/permit2.d.ts.map +1 -0
- package/dist/payments/permit2.js +366 -0
- package/dist/payments/permit2.js.map +1 -0
- package/dist/payments/registry.d.ts +119 -0
- package/dist/payments/registry.d.ts.map +1 -0
- package/dist/payments/registry.js +199 -0
- package/dist/payments/registry.js.map +1 -0
- package/dist/payments/strategy.d.ts +80 -0
- package/dist/payments/strategy.d.ts.map +1 -0
- package/dist/payments/strategy.js +103 -0
- package/dist/payments/strategy.js.map +1 -0
- package/dist/proxy/hooks.d.ts +90 -0
- package/dist/proxy/hooks.d.ts.map +1 -0
- package/dist/proxy/hooks.js +35 -0
- package/dist/proxy/hooks.js.map +1 -0
- package/dist/proxy/index.d.ts +9 -0
- package/dist/proxy/index.d.ts.map +1 -0
- package/dist/proxy/index.js +9 -0
- package/dist/proxy/index.js.map +1 -0
- package/dist/proxy/proxy.d.ts +156 -0
- package/dist/proxy/proxy.d.ts.map +1 -0
- package/dist/proxy/proxy.js +366 -0
- package/dist/proxy/proxy.js.map +1 -0
- package/dist/server/adapters/express.d.ts +89 -0
- package/dist/server/adapters/express.d.ts.map +1 -0
- package/dist/server/adapters/express.js +215 -0
- package/dist/server/adapters/express.js.map +1 -0
- package/dist/server/adapters/hono.d.ts +52 -0
- package/dist/server/adapters/hono.d.ts.map +1 -0
- package/dist/server/adapters/hono.js +61 -0
- package/dist/server/adapters/hono.js.map +1 -0
- package/dist/server/adapters/next.d.ts +52 -0
- package/dist/server/adapters/next.d.ts.map +1 -0
- package/dist/server/adapters/next.js +56 -0
- package/dist/server/adapters/next.js.map +1 -0
- package/dist/server/index.d.ts +30 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +30 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/metering.d.ts +209 -0
- package/dist/server/metering.d.ts.map +1 -0
- package/dist/server/metering.js +365 -0
- package/dist/server/metering.js.map +1 -0
- package/dist/server/post-paid.d.ts +355 -0
- package/dist/server/post-paid.d.ts.map +1 -0
- package/dist/server/post-paid.js +512 -0
- package/dist/server/post-paid.js.map +1 -0
- package/dist/x402/client.d.ts +203 -0
- package/dist/x402/client.d.ts.map +1 -0
- package/dist/x402/client.js +337 -0
- package/dist/x402/client.js.map +1 -0
- package/dist/x402/hub.d.ts +79 -0
- package/dist/x402/hub.d.ts.map +1 -0
- package/dist/x402/hub.js +164 -0
- package/dist/x402/hub.js.map +1 -0
- package/dist/x402/index.d.ts +27 -0
- package/dist/x402/index.d.ts.map +1 -0
- package/dist/x402/index.js +27 -0
- package/dist/x402/index.js.map +1 -0
- package/dist/x402/proxy.d.ts +162 -0
- package/dist/x402/proxy.d.ts.map +1 -0
- package/dist/x402/proxy.js +198 -0
- package/dist/x402/proxy.js.map +1 -0
- package/dist/x402/server.d.ts +162 -0
- package/dist/x402/server.d.ts.map +1 -0
- package/dist/x402/server.js +306 -0
- package/dist/x402/server.js.map +1 -0
- package/dist/x402/wire.d.ts +104 -0
- package/dist/x402/wire.d.ts.map +1 -0
- package/dist/x402/wire.js +265 -0
- package/dist/x402/wire.js.map +1 -0
- package/package.json +61 -0
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Buy now, pay later for x402 APIs: a Tab Service fronts a pay-per-request
|
|
3
|
+
* upstream, pays it with the Service operator's own key, and meters the Agent's
|
|
4
|
+
* Open Tab for what it paid.
|
|
5
|
+
*
|
|
6
|
+
* ## The shape
|
|
7
|
+
*
|
|
8
|
+
* ```text
|
|
9
|
+
* Agent --(Tab-Agent)--> fronting proxy --(request)--> x402 upstream
|
|
10
|
+
* <--(402, PAYMENT-REQUIRED)--
|
|
11
|
+
* operator signs EIP-3009 for the quoted amount
|
|
12
|
+
* --(PAYMENT-SIGNATURE)-->
|
|
13
|
+
* <--(200, PAYMENT-RESPONSE)--
|
|
14
|
+
* post-paid plugin meters upstream price + margin onto the Open Tab
|
|
15
|
+
* Agent <--(200, Tab-Charge-*)--
|
|
16
|
+
* ```
|
|
17
|
+
*
|
|
18
|
+
* The Agent never signs and never holds the upstream's currency. It buys on
|
|
19
|
+
* credit, from a Service it already has a tab with, and settles later exactly
|
|
20
|
+
* as it settles every other Tab charge. The Service is the one taking the
|
|
21
|
+
* upstream's price risk, which is what the margin is for.
|
|
22
|
+
*
|
|
23
|
+
* ## The price is known only after the upstream's 402
|
|
24
|
+
*
|
|
25
|
+
* `tabPostPaid` asks `priceOf(request)` once the response exists, which is
|
|
26
|
+
* after the forward. So the forward records what it paid, keyed by the request
|
|
27
|
+
* object, and {@link X402UpstreamPricing.priceOf} reads it back for the same
|
|
28
|
+
* object. `createTabProxy` hands its `fetchImpl` that request for this reason.
|
|
29
|
+
*
|
|
30
|
+
* ## Before the operator's funds move
|
|
31
|
+
*
|
|
32
|
+
* An upstream paid for a delivery the Agent then cannot be metered for is the
|
|
33
|
+
* operator's loss. `preflight` runs between the upstream's `402` and the
|
|
34
|
+
* signature, with the quoted amount in hand, so a Service can simulate the
|
|
35
|
+
* delivery against `TabBook` and refuse an Agent with no headroom before paying.
|
|
36
|
+
* A refusal is answered at its category's status, `402` for `LIMIT`, and the
|
|
37
|
+
* upstream is not paid.
|
|
38
|
+
*/
|
|
39
|
+
import { causeOf, ok } from "../_shared/index.js";
|
|
40
|
+
import { tabError } from "../errors.js";
|
|
41
|
+
import { TAB_HEADER } from "../http/headers.js";
|
|
42
|
+
import { defaultLogger } from "../logger.js";
|
|
43
|
+
import { createTabProxy, errorResponse } from "../proxy/proxy.js";
|
|
44
|
+
import { createX402Client } from "./client.js";
|
|
45
|
+
/** `TabBook.recordDelivery` takes the unit count as a `uint32`. */
|
|
46
|
+
const MAX_UNITS = 4294967295n;
|
|
47
|
+
export function createX402UpstreamPricing(options) {
|
|
48
|
+
const book = new WeakMap();
|
|
49
|
+
const bps = options.margin?.bps ?? 0n;
|
|
50
|
+
const flat = options.margin?.flatBaseUnits ?? 0n;
|
|
51
|
+
const unitBaseUnits = options.unitBaseUnits ?? 1n;
|
|
52
|
+
if (unitBaseUnits <= 0n)
|
|
53
|
+
throw new RangeError("unitBaseUnits must be a positive count of Asset base units");
|
|
54
|
+
const marginOf = (upstreamAmount) => (upstreamAmount * bps) / 10000n + flat;
|
|
55
|
+
return {
|
|
56
|
+
tool: options.tool,
|
|
57
|
+
unitBaseUnits,
|
|
58
|
+
amountFor: (upstreamAmount) => upstreamAmount + marginOf(upstreamAmount),
|
|
59
|
+
record(request, payment) {
|
|
60
|
+
const margin = marginOf(payment.amount);
|
|
61
|
+
const charge = { upstreamAmount: payment.amount, margin, amount: payment.amount + margin, payment };
|
|
62
|
+
book.set(request, charge);
|
|
63
|
+
return charge;
|
|
64
|
+
},
|
|
65
|
+
chargeOf: (request) => book.get(request),
|
|
66
|
+
priceOf(request) {
|
|
67
|
+
const charge = book.get(request);
|
|
68
|
+
if (charge === undefined || charge.amount <= 0n)
|
|
69
|
+
return undefined;
|
|
70
|
+
// Rounded up, so a coarse unit never leaves the Service short of what it
|
|
71
|
+
// paid the upstream. A charge past `uint32` units is not priced at all:
|
|
72
|
+
// the delivery would revert on chain, and refusing here means the
|
|
73
|
+
// Service learns why from its own log instead of from a mined failure.
|
|
74
|
+
const units = (charge.amount + unitBaseUnits - 1n) / unitBaseUnits;
|
|
75
|
+
if (units > MAX_UNITS)
|
|
76
|
+
return undefined;
|
|
77
|
+
return { tool: options.tool, units: Number(units), unitPrice: unitBaseUnits };
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Builds the fronting proxy: `createTabProxy` with a forward that pays.
|
|
83
|
+
*
|
|
84
|
+
* Everything `createTabProxy` does, hooks, header hygiene, the metering plugin
|
|
85
|
+
* around the forward, streaming of the response, holds. The one thing it gives
|
|
86
|
+
* up is streaming of the *request* body: an upstream that answers `402` has to
|
|
87
|
+
* be sent the same body twice, so a streamed body is read into memory before
|
|
88
|
+
* the first attempt.
|
|
89
|
+
*/
|
|
90
|
+
export function createX402FrontedProxy(options) {
|
|
91
|
+
const logger = options.logger ?? defaultLogger;
|
|
92
|
+
const payments = [];
|
|
93
|
+
const { signer, pricing, asset, upstreamPayment, preflight, onRefused, onPayment, maxUpstreamAmount, nowSeconds, fetchImpl, ...proxyOptions } = options;
|
|
94
|
+
const paying = {
|
|
95
|
+
signer: upstreamPayment?.signer ?? signer,
|
|
96
|
+
chainId: upstreamPayment?.chainId ?? asset.chainId,
|
|
97
|
+
asset: upstreamPayment?.asset ?? asset.address,
|
|
98
|
+
};
|
|
99
|
+
const quoteFor = (request, requirement, required) => {
|
|
100
|
+
const upstreamAmount = BigInt(requirement.amount);
|
|
101
|
+
const claimed = request.headers.get(TAB_HEADER.agent)?.trim().toLowerCase();
|
|
102
|
+
return {
|
|
103
|
+
agent: claimed !== undefined && /^0x[0-9a-f]{40}$/.test(claimed) ? claimed : undefined,
|
|
104
|
+
tool: pricing.tool,
|
|
105
|
+
amount: pricing.amountFor(upstreamAmount),
|
|
106
|
+
upstreamAmount,
|
|
107
|
+
requirement,
|
|
108
|
+
required,
|
|
109
|
+
};
|
|
110
|
+
};
|
|
111
|
+
const payingFetch = async (url, init, request) => {
|
|
112
|
+
const buffered = await bufferBody(init.body);
|
|
113
|
+
if (!buffered.ok)
|
|
114
|
+
return errorResponse(buffered.error);
|
|
115
|
+
let refused;
|
|
116
|
+
const client = createX402Client({
|
|
117
|
+
signer: paying.signer,
|
|
118
|
+
chainId: paying.chainId,
|
|
119
|
+
asset: paying.asset,
|
|
120
|
+
...(maxUpstreamAmount === undefined ? {} : { maxAmount: maxUpstreamAmount }),
|
|
121
|
+
...(fetchImpl === undefined ? {} : { fetchImpl }),
|
|
122
|
+
...(nowSeconds === undefined ? {} : { nowSeconds }),
|
|
123
|
+
logger,
|
|
124
|
+
authorise: async (requirement, required) => {
|
|
125
|
+
if (preflight === undefined)
|
|
126
|
+
return ok(undefined);
|
|
127
|
+
const quote = quoteFor(request, requirement, required);
|
|
128
|
+
const verdict = await preflight(quote, request);
|
|
129
|
+
if (!verdict.ok)
|
|
130
|
+
refused = { error: verdict.error, quote };
|
|
131
|
+
return verdict;
|
|
132
|
+
},
|
|
133
|
+
});
|
|
134
|
+
const { body: _body, duplex: _duplex, ...rest } = init;
|
|
135
|
+
const result = await client.fetch(url, {
|
|
136
|
+
...rest,
|
|
137
|
+
headers: init.headers,
|
|
138
|
+
...(buffered.value === undefined ? {} : { body: buffered.value }),
|
|
139
|
+
});
|
|
140
|
+
if (!result.ok) {
|
|
141
|
+
if (refused !== undefined) {
|
|
142
|
+
const override = call(() => onRefused?.(refused?.error, refused?.quote, request), logger);
|
|
143
|
+
if (override !== undefined)
|
|
144
|
+
return override;
|
|
145
|
+
}
|
|
146
|
+
logger.warn("the fronted upstream could not be paid, so the request was not delivered", {
|
|
147
|
+
url,
|
|
148
|
+
code: result.error.code,
|
|
149
|
+
message: result.error.message,
|
|
150
|
+
});
|
|
151
|
+
return errorResponse(result.error);
|
|
152
|
+
}
|
|
153
|
+
if (result.value.payment !== undefined) {
|
|
154
|
+
pricing.record(request, result.value.payment);
|
|
155
|
+
payments.push(result.value.payment);
|
|
156
|
+
call(() => onPayment?.(result.value.payment, request), logger);
|
|
157
|
+
}
|
|
158
|
+
return result.value.response;
|
|
159
|
+
};
|
|
160
|
+
const proxy = createTabProxy({ ...proxyOptions, logger, fetchImpl: payingFetch });
|
|
161
|
+
return Object.assign(proxy, { pricing, payments: () => [...payments] });
|
|
162
|
+
}
|
|
163
|
+
/** Runs a consumer callback without letting it throw into the forward. */
|
|
164
|
+
function call(fn, logger) {
|
|
165
|
+
try {
|
|
166
|
+
return fn();
|
|
167
|
+
}
|
|
168
|
+
catch (error) {
|
|
169
|
+
logger.warn("a fronted-proxy callback threw and was ignored", causeOf(error));
|
|
170
|
+
return undefined;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Reads a streamed request body into bytes so it can be sent twice.
|
|
175
|
+
*
|
|
176
|
+
* Anything that is not a stream is passed through as it is: a string, a byte
|
|
177
|
+
* view, a `URLSearchParams`, a `FormData`, or nothing.
|
|
178
|
+
*/
|
|
179
|
+
async function bufferBody(body) {
|
|
180
|
+
if (body === undefined || body === null || typeof body !== "object")
|
|
181
|
+
return ok(body);
|
|
182
|
+
const candidate = body;
|
|
183
|
+
if (typeof candidate.getReader !== "function" && typeof candidate[Symbol.asyncIterator] !== "function")
|
|
184
|
+
return ok(body);
|
|
185
|
+
try {
|
|
186
|
+
const bytes = await new Response(body).arrayBuffer();
|
|
187
|
+
return ok(new Uint8Array(bytes));
|
|
188
|
+
}
|
|
189
|
+
catch (error) {
|
|
190
|
+
return {
|
|
191
|
+
ok: false,
|
|
192
|
+
error: tabError("VALIDATION", "REQUEST_BODY_UNREADABLE", "the request body could not be read for forwarding", {
|
|
193
|
+
cause: causeOf(error),
|
|
194
|
+
}),
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
//# sourceMappingURL=proxy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"proxy.js","sourceRoot":"","sources":["../../src/x402/proxy.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Buy now, pay later for x402 APIs: a Tab Service fronts a pay-per-request\n * upstream, pays it with the Service operator's own key, and meters the Agent's\n * Open Tab for what it paid.\n *\n * ## The shape\n *\n * ```text\n * Agent --(Tab-Agent)--> fronting proxy --(request)--> x402 upstream\n * <--(402, PAYMENT-REQUIRED)--\n * operator signs EIP-3009 for the quoted amount\n * --(PAYMENT-SIGNATURE)-->\n * <--(200, PAYMENT-RESPONSE)--\n * post-paid plugin meters upstream price + margin onto the Open Tab\n * Agent <--(200, Tab-Charge-*)--\n * ```\n *\n * The Agent never signs and never holds the upstream's currency. It buys on\n * credit, from a Service it already has a tab with, and settles later exactly\n * as it settles every other Tab charge. The Service is the one taking the\n * upstream's price risk, which is what the margin is for.\n *\n * ## The price is known only after the upstream's 402\n *\n * `tabPostPaid` asks `priceOf(request)` once the response exists, which is\n * after the forward. So the forward records what it paid, keyed by the request\n * object, and {@link X402UpstreamPricing.priceOf} reads it back for the same\n * object. `createTabProxy` hands its `fetchImpl` that request for this reason.\n *\n * ## Before the operator's funds move\n *\n * An upstream paid for a delivery the Agent then cannot be metered for is the\n * operator's loss. `preflight` runs between the upstream's `402` and the\n * signature, with the quoted amount in hand, so a Service can simulate the\n * delivery against `TabBook` and refuse an Agent with no headroom before paying.\n * A refusal is answered at its category's status, `402` for `LIMIT`, and the\n * upstream is not paid.\n */\n\nimport { causeOf, ok, type Address, type Bytes32, type Result, type TabError } from \"../_shared/index.js\";\n\nimport { tabError } from \"../errors.js\";\nimport { TAB_HEADER } from \"../http/headers.js\";\nimport { defaultLogger, type Logger } from \"../logger.js\";\nimport type { AssetRef } from \"../payments/strategy.js\";\nimport { createTabProxy, errorResponse, type ProxyFetch, type TabProxy, type TabProxyOptions } from \"../proxy/proxy.js\";\nimport type { MeteredRequest, PriceQuote } from \"../server/post-paid.js\";\nimport { createX402Client, type X402Fetch, type X402PaymentReceipt, type X402Signer } from \"./client.js\";\nimport type { PaymentRequired, PaymentRequirements } from \"./wire.js\";\n\n/** What the Service adds on top of the upstream's price. Both parts optional; both default to nothing. */\nexport interface X402UpstreamMargin {\n /** Basis points of the upstream amount, rounded down. `500n` is five percent. */\n readonly bps?: bigint;\n /** A flat count of Asset base units per call. */\n readonly flatBaseUnits?: bigint;\n}\n\nexport interface X402UpstreamPricingOptions {\n /** The tool key the fronted calls are metered under, as the applied price list holds it. */\n readonly tool: Bytes32;\n readonly margin?: X402UpstreamMargin;\n /**\n * What one unit of this tool costs on chain, as `ServiceRegistry` holds it.\n *\n * A fronted call's price is not known until the upstream answers, and\n * `TabBook.recordDelivery` refuses any unit price that is not the one in the\n * applied price list. So the varying amount rides in the **unit count**, not\n * in the unit price: a Service publishes a small fixed unit and a call\n * consumes as many of them as it cost. One base unit, the default, makes the\n * charge exact; a coarser unit rounds up, never down, so the Service is\n * never left short by its own rounding.\n *\n * The published price and this number must be the same, or every fronted\n * delivery reverts `PriceListChangedMidCall` and the Service pays the\n * upstream for work it bills nobody.\n */\n readonly unitBaseUnits?: bigint;\n}\n\n/** One fronted call's economics: what the operator paid and what the Agent is metered. */\nexport interface X402UpstreamCharge {\n readonly upstreamAmount: bigint;\n readonly margin: bigint;\n /** `upstreamAmount + margin`: what lands on the Open Tab. */\n readonly amount: bigint;\n readonly payment: X402PaymentReceipt;\n}\n\n/**\n * The price book a fronting proxy writes and its post-paid plugin reads.\n *\n * Built once and shared: the plugin takes `priceOf` at construction, and the\n * proxy takes the whole object so it can `record` into it. Keyed weakly by the\n * request, so nothing is retained after the request is gone.\n */\nexport interface X402UpstreamPricing {\n readonly tool: Bytes32;\n /** For `tabPostPaid({ priceOf })`. Nothing when no upstream payment was made for this request. */\n priceOf(request: MeteredRequest): PriceQuote | undefined;\n chargeOf(request: MeteredRequest): X402UpstreamCharge | undefined;\n record(request: MeteredRequest, payment: X402PaymentReceipt): X402UpstreamCharge;\n /** The Agent's price for an upstream amount, margin applied. */\n amountFor(upstreamAmount: bigint): bigint;\n /** What one unit costs on chain: the unit price every quote carries. */\n readonly unitBaseUnits: bigint;\n}\n\n/** `TabBook.recordDelivery` takes the unit count as a `uint32`. */\nconst MAX_UNITS = 4_294_967_295n;\n\nexport function createX402UpstreamPricing(options: X402UpstreamPricingOptions): X402UpstreamPricing {\n const book = new WeakMap<object, X402UpstreamCharge>();\n const bps = options.margin?.bps ?? 0n;\n const flat = options.margin?.flatBaseUnits ?? 0n;\n const unitBaseUnits = options.unitBaseUnits ?? 1n;\n if (unitBaseUnits <= 0n) throw new RangeError(\"unitBaseUnits must be a positive count of Asset base units\");\n const marginOf = (upstreamAmount: bigint): bigint => (upstreamAmount * bps) / 10_000n + flat;\n return {\n tool: options.tool,\n unitBaseUnits,\n amountFor: (upstreamAmount) => upstreamAmount + marginOf(upstreamAmount),\n record(request, payment) {\n const margin = marginOf(payment.amount);\n const charge: X402UpstreamCharge = { upstreamAmount: payment.amount, margin, amount: payment.amount + margin, payment };\n book.set(request, charge);\n return charge;\n },\n chargeOf: (request) => book.get(request),\n priceOf(request) {\n const charge = book.get(request);\n if (charge === undefined || charge.amount <= 0n) return undefined;\n // Rounded up, so a coarse unit never leaves the Service short of what it\n // paid the upstream. A charge past `uint32` units is not priced at all:\n // the delivery would revert on chain, and refusing here means the\n // Service learns why from its own log instead of from a mined failure.\n const units = (charge.amount + unitBaseUnits - 1n) / unitBaseUnits;\n if (units > MAX_UNITS) return undefined;\n return { tool: options.tool, units: Number(units), unitPrice: unitBaseUnits };\n },\n };\n}\n\n/** What `preflight` is told before the operator signs. */\nexport interface X402PreflightQuote {\n /** The Agent named by `Tab-Agent`, when the request named one. */\n readonly agent: Address | undefined;\n readonly tool: Bytes32;\n /** What the Agent will be metered: upstream amount plus margin. */\n readonly amount: bigint;\n readonly upstreamAmount: bigint;\n readonly requirement: PaymentRequirements;\n readonly required: PaymentRequired;\n}\n\n/**\n * Where an upstream is paid, when that is not the Service's own chain and Asset.\n *\n * The API Hub takes USDC on Monad Mainnet and nothing else, and a Service on\n * Testnet meters in a Testnet Asset, so the two sides of a fronted call can sit\n * on different chains. This names the paying side. The signer is the one that\n * holds funds there, and defaults to the proxy's own; the amount the Agent is\n * metered is the upstream's base units passed through `pricing`, which is right\n * only when both Assets are six-decimal dollar stablecoins, and a Service that\n * fronts anything else states its own conversion in `pricing`.\n */\nexport interface X402UpstreamPayment {\n readonly chainId: bigint;\n readonly asset: Address;\n readonly signer?: X402Signer;\n}\n\nexport interface X402FrontedProxyOptions extends Omit<TabProxyOptions, \"fetchImpl\"> {\n /** The Service operator's key: what pays the upstream. */\n readonly signer: X402Signer;\n readonly pricing: X402UpstreamPricing;\n /** The Asset the Service meters in. The upstream is paid in it, on its chain, unless `upstreamPayment` says otherwise. */\n readonly asset: Pick<AssetRef, \"chainId\" | \"address\">;\n /** The chain, Asset and key the upstream is paid with, where they differ from the Service's own. */\n readonly upstreamPayment?: X402UpstreamPayment;\n /** The `fetch` the paying client sends with. Defaults to the host's. */\n readonly fetchImpl?: X402Fetch<Response>;\n /** Refuse any upstream price above this many atomic units. Omitted sets no ceiling. */\n readonly maxUpstreamAmount?: bigint;\n /** Runs after the upstream's 402 and before the signature. An `err` stops the payment. */\n readonly preflight?: (quote: X402PreflightQuote, request: Request) => Promise<Result<void>> | Result<void>;\n /** Replaces the response a `preflight` refusal is answered with. */\n readonly onRefused?: (error: TabError, quote: X402PreflightQuote, request: Request) => Response | undefined;\n readonly onPayment?: (receipt: X402PaymentReceipt, request: Request) => void;\n /** Seconds since the epoch, for `validBefore`. A test passes a fixed clock. */\n readonly nowSeconds?: () => number;\n}\n\nexport interface X402FrontedProxy extends TabProxy {\n readonly pricing: X402UpstreamPricing;\n /** Every upstream payment made, oldest first. */\n payments(): readonly X402PaymentReceipt[];\n}\n\n/**\n * Builds the fronting proxy: `createTabProxy` with a forward that pays.\n *\n * Everything `createTabProxy` does, hooks, header hygiene, the metering plugin\n * around the forward, streaming of the response, holds. The one thing it gives\n * up is streaming of the *request* body: an upstream that answers `402` has to\n * be sent the same body twice, so a streamed body is read into memory before\n * the first attempt.\n */\nexport function createX402FrontedProxy(options: X402FrontedProxyOptions): X402FrontedProxy {\n const logger = options.logger ?? defaultLogger;\n const payments: X402PaymentReceipt[] = [];\n const { signer, pricing, asset, upstreamPayment, preflight, onRefused, onPayment, maxUpstreamAmount, nowSeconds, fetchImpl, ...proxyOptions } = options;\n const paying = {\n signer: upstreamPayment?.signer ?? signer,\n chainId: upstreamPayment?.chainId ?? asset.chainId,\n asset: upstreamPayment?.asset ?? asset.address,\n };\n\n const quoteFor = (request: Request, requirement: PaymentRequirements, required: PaymentRequired): X402PreflightQuote => {\n const upstreamAmount = BigInt(requirement.amount);\n const claimed = request.headers.get(TAB_HEADER.agent)?.trim().toLowerCase();\n return {\n agent: claimed !== undefined && /^0x[0-9a-f]{40}$/.test(claimed) ? (claimed as Address) : undefined,\n tool: pricing.tool,\n amount: pricing.amountFor(upstreamAmount),\n upstreamAmount,\n requirement,\n required,\n };\n };\n\n const payingFetch: ProxyFetch = async (url, init, request) => {\n const buffered = await bufferBody(init.body);\n if (!buffered.ok) return errorResponse(buffered.error);\n\n let refused: { error: TabError; quote: X402PreflightQuote } | undefined;\n const client = createX402Client<Response>({\n signer: paying.signer,\n chainId: paying.chainId,\n asset: paying.asset,\n ...(maxUpstreamAmount === undefined ? {} : { maxAmount: maxUpstreamAmount }),\n ...(fetchImpl === undefined ? {} : { fetchImpl }),\n ...(nowSeconds === undefined ? {} : { nowSeconds }),\n logger,\n authorise: async (requirement, required) => {\n if (preflight === undefined) return ok(undefined);\n const quote = quoteFor(request, requirement, required);\n const verdict = await preflight(quote, request);\n if (!verdict.ok) refused = { error: verdict.error, quote };\n return verdict;\n },\n });\n\n const { body: _body, duplex: _duplex, ...rest } = init;\n const result = await client.fetch(url, {\n ...rest,\n headers: init.headers,\n ...(buffered.value === undefined ? {} : { body: buffered.value }),\n });\n\n if (!result.ok) {\n if (refused !== undefined) {\n const override = call(() => onRefused?.(refused?.error as TabError, refused?.quote as X402PreflightQuote, request), logger);\n if (override !== undefined) return override;\n }\n logger.warn(\"the fronted upstream could not be paid, so the request was not delivered\", {\n url,\n code: result.error.code,\n message: result.error.message,\n });\n return errorResponse(result.error);\n }\n\n if (result.value.payment !== undefined) {\n pricing.record(request, result.value.payment);\n payments.push(result.value.payment);\n call(() => onPayment?.(result.value.payment as X402PaymentReceipt, request), logger);\n }\n return result.value.response;\n };\n\n const proxy = createTabProxy({ ...proxyOptions, logger, fetchImpl: payingFetch });\n return Object.assign(proxy, { pricing, payments: () => [...payments] });\n}\n\n/** Runs a consumer callback without letting it throw into the forward. */\nfunction call<T>(fn: () => T, logger: Logger): T | undefined {\n try {\n return fn();\n } catch (error) {\n logger.warn(\"a fronted-proxy callback threw and was ignored\", causeOf(error));\n return undefined;\n }\n}\n\n/**\n * Reads a streamed request body into bytes so it can be sent twice.\n *\n * Anything that is not a stream is passed through as it is: a string, a byte\n * view, a `URLSearchParams`, a `FormData`, or nothing.\n */\nasync function bufferBody(body: unknown): Promise<Result<unknown>> {\n if (body === undefined || body === null || typeof body !== \"object\") return ok(body);\n const candidate = body as { getReader?: unknown; [Symbol.asyncIterator]?: unknown };\n if (typeof candidate.getReader !== \"function\" && typeof candidate[Symbol.asyncIterator] !== \"function\") return ok(body);\n try {\n const bytes = await new Response(body as ReadableStream).arrayBuffer();\n return ok(new Uint8Array(bytes));\n } catch (error) {\n return {\n ok: false,\n error: tabError(\"VALIDATION\", \"REQUEST_BODY_UNREADABLE\", \"the request body could not be read for forwarding\", {\n cause: causeOf(error),\n }),\n };\n }\n}\n"]}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The x402 server side: build what a `402` must carry, and take a signed
|
|
3
|
+
* payment through a facilitator.
|
|
4
|
+
*
|
|
5
|
+
* ## The flow, and why the order is fixed
|
|
6
|
+
*
|
|
7
|
+
* x402's default `authorization` flow is verify, then resource, then settle:
|
|
8
|
+
* the facilitator confirms the signature and the balance before the work is
|
|
9
|
+
* done, and moves the funds only after the work succeeded. {@link handlePrepaidRequest}
|
|
10
|
+
* runs exactly that. It never settles a payment for a response that was not a
|
|
11
|
+
* delivery, for the same reason the post-paid plugin never meters one: an
|
|
12
|
+
* Agent is not charged for a call that failed.
|
|
13
|
+
*
|
|
14
|
+
* ## What a Tab Service does with this
|
|
15
|
+
*
|
|
16
|
+
* A Tab Service delivers on credit and refuses on `LimitExceeded` alone. With
|
|
17
|
+
* this module, that refusal can also carry a `PAYMENT-REQUIRED` naming the same
|
|
18
|
+
* charge as an x402 `exact` requirement, so an Agent that has no headroom and
|
|
19
|
+
* would rather not settle first can pay for the one call. When it does, the
|
|
20
|
+
* call is prepaid in full, the facilitator moves the Asset to the Service's
|
|
21
|
+
* Collection address, and nothing lands on the Open Tab, because nothing is
|
|
22
|
+
* owed.
|
|
23
|
+
*
|
|
24
|
+
* ## Official and hand-rolled
|
|
25
|
+
*
|
|
26
|
+
* The facilitator client is `@x402/core`'s `HTTPFacilitatorClient`, wrapped so
|
|
27
|
+
* a thrown `VerifyError` or `SettleError` comes back as a `Result` carrying the
|
|
28
|
+
* facilitator's reason. Requirement construction and the request handling are
|
|
29
|
+
* this package's own, because the reference server middleware gates every
|
|
30
|
+
* request on payment, which is the model Tab exists to replace.
|
|
31
|
+
*
|
|
32
|
+
* Specification: x402-specification-v2.md sections 5, 6.1 and 7, transports-v2/http.md.
|
|
33
|
+
*/
|
|
34
|
+
import { type FacilitatorClient } from "@x402/core/server";
|
|
35
|
+
import { type Address, type Result, type TabError } from "../_shared/index.js";
|
|
36
|
+
import { type Logger } from "../logger.js";
|
|
37
|
+
import type { AssetRef } from "../payments/strategy.js";
|
|
38
|
+
import { type PaymentPayload, type PaymentRequired, type PaymentRequirements, type ResourceInfo, type SettleResponse, type SupportedResponse, type VerifyResponse } from "./wire.js";
|
|
39
|
+
/** Monad's facilitator, which settles `exact` and `upto` on Mainnet and Testnet. */
|
|
40
|
+
export declare const MONAD_FACILITATOR_URL = "https://x402-facilitator.molandak.org";
|
|
41
|
+
/** How long an authorization stays valid when the Service does not say. */
|
|
42
|
+
export declare const DEFAULT_MAX_TIMEOUT_SECONDS = 300;
|
|
43
|
+
/**
|
|
44
|
+
* What verifies and settles payments.
|
|
45
|
+
*
|
|
46
|
+
* The reference interface, re-exported so a fake in a test and the HTTP client
|
|
47
|
+
* in production are the same type. The methods throw, as the reference does;
|
|
48
|
+
* {@link verifyPayment} and {@link settlePayment} are the zero-throw doors.
|
|
49
|
+
*/
|
|
50
|
+
export type X402FacilitatorClient = FacilitatorClient;
|
|
51
|
+
export interface X402FacilitatorOptions {
|
|
52
|
+
/** Defaults to {@link MONAD_FACILITATOR_URL}. */
|
|
53
|
+
readonly url?: string;
|
|
54
|
+
/** Per-request timeout. Defaults to the reference client's 90 seconds. */
|
|
55
|
+
readonly timeoutMs?: number;
|
|
56
|
+
}
|
|
57
|
+
/** The reference HTTP client over a facilitator's `/verify`, `/settle` and `/supported`. */
|
|
58
|
+
export declare function createX402Facilitator(options?: X402FacilitatorOptions): X402FacilitatorClient;
|
|
59
|
+
export interface ExactRequirementOptions {
|
|
60
|
+
readonly chainId: bigint;
|
|
61
|
+
/** The token. `symbol` picks the EIP-712 domain when `extra` is not given. */
|
|
62
|
+
readonly asset: Pick<AssetRef, "address" | "symbol">;
|
|
63
|
+
/** Atomic units of the token. */
|
|
64
|
+
readonly amount: bigint;
|
|
65
|
+
/** Where the facilitator sends the funds: the Service's Collection address for the Asset. */
|
|
66
|
+
readonly payTo: Address;
|
|
67
|
+
readonly maxTimeoutSeconds?: number;
|
|
68
|
+
/**
|
|
69
|
+
* The requirement's `extra`. Must carry the token's EIP-712 domain `name` and
|
|
70
|
+
* `version`, which EIP-3009 signing needs. Defaults to USDC's when the Asset's
|
|
71
|
+
* symbol is `USDC`, and is required otherwise.
|
|
72
|
+
*/
|
|
73
|
+
readonly extra?: Readonly<Record<string, unknown>>;
|
|
74
|
+
}
|
|
75
|
+
/** One `exact` requirement, EIP-3009, on an EVM chain. */
|
|
76
|
+
export declare function exactRequirementFor(options: ExactRequirementOptions): Result<PaymentRequirements>;
|
|
77
|
+
export interface PaymentRequiredOptions {
|
|
78
|
+
readonly resource: ResourceInfo;
|
|
79
|
+
readonly accepts: readonly PaymentRequirements[];
|
|
80
|
+
/** Why payment is being asked for, for a person reading the header. */
|
|
81
|
+
readonly error?: string;
|
|
82
|
+
}
|
|
83
|
+
/** The object a `402` carries in `PAYMENT-REQUIRED`. */
|
|
84
|
+
export declare const paymentRequiredFor: (options: PaymentRequiredOptions) => PaymentRequired;
|
|
85
|
+
/** The `PAYMENT-REQUIRED` header, ready to merge onto a response. */
|
|
86
|
+
export declare function paymentRequiredHeaders(required: PaymentRequired): Result<Record<string, string>>;
|
|
87
|
+
/** The `PAYMENT-RESPONSE` header, ready to merge onto a response. */
|
|
88
|
+
export declare function paymentResponseHeaders(settlement: SettleResponse): Result<Record<string, string>>;
|
|
89
|
+
/** `POST /verify`, as a `Result`. An invalid payment is a `LIMIT` error naming the facilitator's reason. */
|
|
90
|
+
export declare function verifyPayment(facilitator: X402FacilitatorClient, payload: PaymentPayload, requirements: PaymentRequirements): Promise<Result<VerifyResponse>>;
|
|
91
|
+
/** `POST /settle`, as a `Result`. A failed settlement carries the facilitator's reason and any broadcast hash. */
|
|
92
|
+
export declare function settlePayment(facilitator: X402FacilitatorClient, payload: PaymentPayload, requirements: PaymentRequirements): Promise<Result<SettleResponse>>;
|
|
93
|
+
/** `GET /supported`, as a `Result`. */
|
|
94
|
+
export declare const facilitatorSupports: (facilitator: X402FacilitatorClient) => Promise<Result<SupportedResponse>>;
|
|
95
|
+
/**
|
|
96
|
+
* Checks that what the client says it accepted is what this server requires.
|
|
97
|
+
*
|
|
98
|
+
* The facilitator verifies the signature against the server's requirements, so
|
|
99
|
+
* a mismatch would be caught there; catching it here saves the round trip and
|
|
100
|
+
* names the field. `extra` and `maxTimeoutSeconds` are not compared: they are
|
|
101
|
+
* the server's inputs to signing and the facilitator reads them from the
|
|
102
|
+
* server's copy, never the client's.
|
|
103
|
+
*/
|
|
104
|
+
export declare function matchAccepted(accepted: PaymentRequirements, requirements: PaymentRequirements): Result<void>;
|
|
105
|
+
/** The response, reduced to what the billability decision needs. */
|
|
106
|
+
export interface X402DeliveredResponse {
|
|
107
|
+
readonly status: number;
|
|
108
|
+
}
|
|
109
|
+
export interface PrepaidRequestOptions {
|
|
110
|
+
readonly facilitator: X402FacilitatorClient;
|
|
111
|
+
/** The decoded `PAYMENT-SIGNATURE`. */
|
|
112
|
+
readonly payload: PaymentPayload;
|
|
113
|
+
/** What this call costs, built by the server and never taken from the client. */
|
|
114
|
+
readonly requirements: PaymentRequirements;
|
|
115
|
+
readonly resource: ResourceInfo;
|
|
116
|
+
/** The work. Runs only after the payment verified. */
|
|
117
|
+
readonly deliver: () => Promise<Response> | Response;
|
|
118
|
+
/** Whether a response is a delivery worth settling for. Defaults to `status < 400`. */
|
|
119
|
+
readonly billable?: (response: X402DeliveredResponse) => boolean;
|
|
120
|
+
readonly logger?: Logger;
|
|
121
|
+
}
|
|
122
|
+
export type PrepaidOutcome =
|
|
123
|
+
/** Verified, delivered, settled. The response carries `PAYMENT-RESPONSE`. */
|
|
124
|
+
{
|
|
125
|
+
readonly kind: "paid";
|
|
126
|
+
readonly response: Response;
|
|
127
|
+
readonly settlement: SettleResponse;
|
|
128
|
+
readonly payer: string | undefined;
|
|
129
|
+
}
|
|
130
|
+
/** The payment did not verify. The response is a `402` carrying `PAYMENT-REQUIRED` with the reason. */
|
|
131
|
+
| {
|
|
132
|
+
readonly kind: "rejected";
|
|
133
|
+
readonly response: Response;
|
|
134
|
+
readonly error: TabError;
|
|
135
|
+
}
|
|
136
|
+
/** The work was not a delivery, so nothing was settled and the handler's own response goes out. */
|
|
137
|
+
| {
|
|
138
|
+
readonly kind: "not-delivered";
|
|
139
|
+
readonly response: Response;
|
|
140
|
+
}
|
|
141
|
+
/** The handler threw. Nothing was settled. */
|
|
142
|
+
| {
|
|
143
|
+
readonly kind: "handler-failed";
|
|
144
|
+
readonly thrown: unknown;
|
|
145
|
+
readonly error: TabError;
|
|
146
|
+
}
|
|
147
|
+
/** Delivered, but the facilitator would not settle. The response is a `402` carrying the failed `PAYMENT-RESPONSE`. */
|
|
148
|
+
| {
|
|
149
|
+
readonly kind: "settlement-failed";
|
|
150
|
+
readonly response: Response;
|
|
151
|
+
readonly error: TabError;
|
|
152
|
+
};
|
|
153
|
+
/**
|
|
154
|
+
* Takes one prepaid request through verify, deliver, settle.
|
|
155
|
+
*
|
|
156
|
+
* Never throws and never rejects. A thrown handler is an outcome, not an
|
|
157
|
+
* exception, so a host adapter can re-raise it the way its framework expects.
|
|
158
|
+
*/
|
|
159
|
+
export declare function handlePrepaidRequest(options: PrepaidRequestOptions): Promise<PrepaidOutcome>;
|
|
160
|
+
/** A `402` carrying `PAYMENT-REQUIRED` and a JSON body naming the error. */
|
|
161
|
+
export declare function paymentRequiredResponse(required: PaymentRequired, error: TabError, extraHeaders?: Readonly<Record<string, string>>): Response;
|
|
162
|
+
//# sourceMappingURL=server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/x402/server.ts"],"names":[],"mappings":"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"}
|