@runtab/mcp 0.0.1

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.
Files changed (82) hide show
  1. package/LICENSE +21 -0
  2. package/dist/bootstrap.d.ts +18 -0
  3. package/dist/bootstrap.js +125 -0
  4. package/dist/cli-config.d.ts +11 -0
  5. package/dist/cli-config.js +59 -0
  6. package/dist/cli.d.ts +2 -0
  7. package/dist/cli.js +33 -0
  8. package/dist/control-plane-origin.d.ts +4 -0
  9. package/dist/control-plane-origin.js +27 -0
  10. package/dist/detect.d.ts +10 -0
  11. package/dist/detect.js +74 -0
  12. package/dist/durable-mcp-payment.d.ts +40 -0
  13. package/dist/durable-mcp-payment.js +105 -0
  14. package/dist/durable-payment-fetch.d.ts +19 -0
  15. package/dist/durable-payment-fetch.js +117 -0
  16. package/dist/eip3009-authorization.d.ts +59 -0
  17. package/dist/eip3009-authorization.js +142 -0
  18. package/dist/errors.d.ts +4 -0
  19. package/dist/errors.js +7 -0
  20. package/dist/fetch-wire.d.ts +46 -0
  21. package/dist/fetch-wire.js +143 -0
  22. package/dist/fetch-wrapper.d.ts +24 -0
  23. package/dist/fetch-wrapper.js +70 -0
  24. package/dist/fetch.d.ts +3 -0
  25. package/dist/fetch.js +1 -0
  26. package/dist/index.d.ts +6 -0
  27. package/dist/index.js +4 -0
  28. package/dist/origin-context.d.ts +5 -0
  29. package/dist/origin-context.js +15 -0
  30. package/dist/paid-fetch-server.d.ts +49 -0
  31. package/dist/paid-fetch-server.js +104 -0
  32. package/dist/payment-authorization-state.d.ts +48 -0
  33. package/dist/payment-authorization-state.js +150 -0
  34. package/dist/payment-client.d.ts +4 -0
  35. package/dist/payment-client.js +14 -0
  36. package/dist/payment-correlation.d.ts +9 -0
  37. package/dist/payment-correlation.js +36 -0
  38. package/dist/payment-envelope-capacity.d.ts +2 -0
  39. package/dist/payment-envelope-capacity.js +28 -0
  40. package/dist/payment-envelope-disk.d.ts +5 -0
  41. package/dist/payment-envelope-disk.js +97 -0
  42. package/dist/payment-envelope-journal.d.ts +54 -0
  43. package/dist/payment-envelope-journal.js +116 -0
  44. package/dist/payment-envelope-lock.d.ts +5 -0
  45. package/dist/payment-envelope-lock.js +88 -0
  46. package/dist/payment-envelope-model.d.ts +32 -0
  47. package/dist/payment-envelope-model.js +123 -0
  48. package/dist/payment-envelope-store.d.ts +31 -0
  49. package/dist/payment-envelope-store.js +210 -0
  50. package/dist/payment-envelope.d.ts +17 -0
  51. package/dist/payment-envelope.js +145 -0
  52. package/dist/payment-idempotency.d.ts +2 -0
  53. package/dist/payment-idempotency.js +8 -0
  54. package/dist/payment-observation-reporter.d.ts +31 -0
  55. package/dist/payment-observation-reporter.js +151 -0
  56. package/dist/payment-profile.d.ts +3 -0
  57. package/dist/payment-profile.js +4 -0
  58. package/dist/payment-request-fingerprint.d.ts +5 -0
  59. package/dist/payment-request-fingerprint.js +54 -0
  60. package/dist/payment-settlement-observation.d.ts +9 -0
  61. package/dist/payment-settlement-observation.js +74 -0
  62. package/dist/payment-signal.d.ts +2 -0
  63. package/dist/payment-signal.js +8 -0
  64. package/dist/payment-target-network.d.ts +16 -0
  65. package/dist/payment-target-network.js +178 -0
  66. package/dist/payment-target-policy.d.ts +10 -0
  67. package/dist/payment-target-policy.js +75 -0
  68. package/dist/proxy.d.ts +47 -0
  69. package/dist/proxy.js +72 -0
  70. package/dist/remote-signer-http.d.ts +16 -0
  71. package/dist/remote-signer-http.js +182 -0
  72. package/dist/remote-signer.d.ts +34 -0
  73. package/dist/remote-signer.js +157 -0
  74. package/dist/resource-url.d.ts +5 -0
  75. package/dist/resource-url.js +36 -0
  76. package/dist/routing.d.ts +13 -0
  77. package/dist/routing.js +42 -0
  78. package/dist/runtime.d.ts +13 -0
  79. package/dist/runtime.js +92 -0
  80. package/dist/upstream.d.ts +43 -0
  81. package/dist/upstream.js +38 -0
  82. package/package.json +65 -0
@@ -0,0 +1,117 @@
1
+ import { x402HTTPClient } from "@x402/core/client";
2
+ import { newPaymentEnvelope } from "./payment-envelope.js";
3
+ import { PaymentEnvelopeJournal, PaymentIdempotencyRequiredError, PaymentReconciliationRequiredError, } from "./payment-envelope-journal.js";
4
+ import { fingerprintPaymentRequest } from "./payment-request-fingerprint.js";
5
+ const MAX_PAYMENT_REQUIRED_BODY_BYTES = 65_536;
6
+ async function boundedJson(response) {
7
+ if (!response.body)
8
+ return undefined;
9
+ const reader = response.body.getReader();
10
+ const chunks = [];
11
+ let length = 0;
12
+ try {
13
+ while (true) {
14
+ const { done, value } = await reader.read();
15
+ if (done)
16
+ break;
17
+ length += value.byteLength;
18
+ if (length > MAX_PAYMENT_REQUIRED_BODY_BYTES) {
19
+ await reader.cancel().catch(() => undefined);
20
+ throw new Error("Payment-required response is too large");
21
+ }
22
+ chunks.push(value);
23
+ }
24
+ }
25
+ finally {
26
+ reader.releaseLock();
27
+ }
28
+ const bytes = new Uint8Array(length);
29
+ let offset = 0;
30
+ for (const chunk of chunks) {
31
+ bytes.set(chunk, offset);
32
+ offset += chunk.byteLength;
33
+ }
34
+ const text = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
35
+ return text ? JSON.parse(text) : undefined;
36
+ }
37
+ async function paymentRequired(response, client) {
38
+ const getHeader = (name) => response.headers.get(name);
39
+ try {
40
+ const required = client.getPaymentRequiredResponse(getHeader);
41
+ void response.body?.cancel().catch(() => undefined);
42
+ return required;
43
+ }
44
+ catch {
45
+ const body = await boundedJson(response);
46
+ return client.getPaymentRequiredResponse(getHeader, body);
47
+ }
48
+ }
49
+ export function createDurablePaymentFetch(options) {
50
+ const httpClient = new x402HTTPClient(options.client);
51
+ const journal = new PaymentEnvelopeJournal({
52
+ address: options.address,
53
+ ...(options.authorizationState ? { authorizationState: options.authorizationState } : {}),
54
+ ...(options.nowSeconds ? { nowSeconds: options.nowSeconds } : {}),
55
+ paymentProfile: options.paymentProfile,
56
+ signer: options.signer,
57
+ store: options.store,
58
+ });
59
+ async function processPaidResponse(response, parsed, idempotencyKey, requestFingerprint) {
60
+ const result = await httpClient.processPaymentResult(parsed.payload, (name) => response.headers.get(name), response.status);
61
+ if (result.settleResponse) {
62
+ const observed = await journal.recordObservation(idempotencyKey, requestFingerprint, parsed, result.settleResponse);
63
+ if (observed) {
64
+ await journal.reconcileObserved(idempotencyKey, requestFingerprint, observed, parsed);
65
+ }
66
+ }
67
+ return response;
68
+ }
69
+ async function sendEnvelope(request, envelope, idempotencyKey, requestFingerprint) {
70
+ const parsed = await journal.parse(envelope);
71
+ await journal.reconcileObserved(idempotencyKey, requestFingerprint, envelope, parsed);
72
+ request.headers.set("PAYMENT-SIGNATURE", envelope.paymentSignature);
73
+ request.headers.set("Access-Control-Expose-Headers", "PAYMENT-RESPONSE,X-PAYMENT-RESPONSE");
74
+ const response = await options.fetch(request);
75
+ return processPaidResponse(response, parsed, idempotencyKey, requestFingerprint);
76
+ }
77
+ return async (input, init) => {
78
+ const request = new Request(input, init);
79
+ const paymentRequest = request.clone();
80
+ const requestFingerprint = await fingerprintPaymentRequest(request.clone());
81
+ const idempotencyKey = options.idempotencyKey();
82
+ if (idempotencyKey) {
83
+ const existing = await journal.load(idempotencyKey, requestFingerprint);
84
+ if (existing) {
85
+ return sendEnvelope(paymentRequest, existing, idempotencyKey, requestFingerprint);
86
+ }
87
+ }
88
+ const response = await options.fetch(request);
89
+ if (response.status !== 402)
90
+ return response;
91
+ if (!idempotencyKey) {
92
+ await response.body?.cancel().catch(() => undefined);
93
+ throw new PaymentIdempotencyRequiredError();
94
+ }
95
+ const required = await paymentRequired(response, httpClient);
96
+ const hookHeaders = await httpClient.handlePaymentRequired(required);
97
+ if (hookHeaders) {
98
+ const hookRequest = paymentRequest.clone();
99
+ for (const [name, value] of Object.entries(hookHeaders))
100
+ hookRequest.headers.set(name, value);
101
+ const hookResponse = await options.fetch(hookRequest);
102
+ if (hookResponse.status !== 402)
103
+ return hookResponse;
104
+ await hookResponse.body?.cancel().catch(() => undefined);
105
+ }
106
+ const envelope = await journal.getOrCreate(idempotencyKey, requestFingerprint, async () => {
107
+ const payload = await options.client.createPaymentPayload(required);
108
+ const headers = httpClient.encodePaymentSignatureHeader(payload);
109
+ const paymentSignature = headers["PAYMENT-SIGNATURE"];
110
+ if (!paymentSignature)
111
+ throw new Error("The x402 payment header is invalid");
112
+ return newPaymentEnvelope(payload, paymentSignature, options.signer);
113
+ });
114
+ return sendEnvelope(paymentRequest, envelope, idempotencyKey, requestFingerprint);
115
+ };
116
+ }
117
+ export { PaymentIdempotencyRequiredError, PaymentReconciliationRequiredError };
@@ -0,0 +1,59 @@
1
+ import type { PaymentProfile } from "./payment-profile.js";
2
+ export declare const MAX_USDC_AMOUNT_ATOMIC: bigint;
3
+ export interface SignerRequest {
4
+ domain: Record<string, unknown>;
5
+ message: Record<string, unknown>;
6
+ primaryType: string;
7
+ types: Record<string, unknown>;
8
+ }
9
+ export declare class InvalidEip3009AuthorizationError extends Error {
10
+ constructor();
11
+ }
12
+ export declare function parseExactEip3009Authorization(value: SignerRequest, options: {
13
+ address: `0x${string}`;
14
+ nowSeconds: number;
15
+ paymentProfile: PaymentProfile;
16
+ }): {
17
+ amount: string;
18
+ asset: `0x${string}`;
19
+ network: "eip155:8453" | "eip155:42161" | "eip155:84532";
20
+ payTo: `0x${string}`;
21
+ typedData: {
22
+ domain: {
23
+ chainId: number;
24
+ name: string;
25
+ verifyingContract: `0x${string}`;
26
+ version: string;
27
+ };
28
+ message: {
29
+ from: `0x${string}`;
30
+ nonce: `0x${string}`;
31
+ to: `0x${string}`;
32
+ validAfter: bigint;
33
+ validBefore: bigint;
34
+ value: bigint;
35
+ };
36
+ primaryType: "TransferWithAuthorization";
37
+ types: {
38
+ TransferWithAuthorization: readonly [{
39
+ readonly name: "from";
40
+ readonly type: "address";
41
+ }, {
42
+ readonly name: "to";
43
+ readonly type: "address";
44
+ }, {
45
+ readonly name: "value";
46
+ readonly type: "uint256";
47
+ }, {
48
+ readonly name: "validAfter";
49
+ readonly type: "uint256";
50
+ }, {
51
+ readonly name: "validBefore";
52
+ readonly type: "uint256";
53
+ }, {
54
+ readonly name: "nonce";
55
+ readonly type: "bytes32";
56
+ }];
57
+ };
58
+ };
59
+ };
@@ -0,0 +1,142 @@
1
+ import { getAddress } from "viem";
2
+ import { ARBITRUM_NETWORK, ARBITRUM_USDC, BASE_NETWORK, BASE_SEPOLIA_NETWORK, BASE_SEPOLIA_USDC, BASE_USDC, } from "./routing.js";
3
+ const MAX_AUTHORIZATION_LIFETIME_SECONDS = 600;
4
+ // Mirrors numeric(20,0) cap cents at 10,000 atomic USDC units per cent.
5
+ const MAX_CAP_USD_CENTS = 10n ** 20n - 1n;
6
+ const ATOMIC_UNITS_PER_CENT = 10000n;
7
+ export const MAX_USDC_AMOUNT_ATOMIC = MAX_CAP_USD_CENTS * ATOMIC_UNITS_PER_CENT;
8
+ const AUTHORIZATION_TYPES = [
9
+ { name: "from", type: "address" },
10
+ { name: "to", type: "address" },
11
+ { name: "value", type: "uint256" },
12
+ { name: "validAfter", type: "uint256" },
13
+ { name: "validBefore", type: "uint256" },
14
+ { name: "nonce", type: "bytes32" },
15
+ ];
16
+ export class InvalidEip3009AuthorizationError extends Error {
17
+ constructor() {
18
+ super("The EIP-3009 authorization is invalid.");
19
+ this.name = "InvalidEip3009AuthorizationError";
20
+ }
21
+ }
22
+ function record(value) {
23
+ return typeof value === "object" && value !== null && !Array.isArray(value);
24
+ }
25
+ function exactRecord(value, sortedKeys) {
26
+ if (!record(value))
27
+ throw new InvalidEip3009AuthorizationError();
28
+ const keys = Object.keys(value).sort();
29
+ if (keys.length !== sortedKeys.length || keys.some((key, index) => key !== sortedKeys[index])) {
30
+ throw new InvalidEip3009AuthorizationError();
31
+ }
32
+ return value;
33
+ }
34
+ function unsigned(value) {
35
+ if (typeof value === "bigint" && value >= 0n)
36
+ return value.toString();
37
+ if (typeof value === "string" && /^(0|[1-9][0-9]*)$/.test(value))
38
+ return value;
39
+ throw new InvalidEip3009AuthorizationError();
40
+ }
41
+ function address(value) {
42
+ if (typeof value !== "string")
43
+ throw new InvalidEip3009AuthorizationError();
44
+ try {
45
+ return getAddress(value);
46
+ }
47
+ catch {
48
+ throw new InvalidEip3009AuthorizationError();
49
+ }
50
+ }
51
+ function network(chainIdValue, paymentProfile) {
52
+ const chainId = typeof chainIdValue === "number" && Number.isSafeInteger(chainIdValue)
53
+ ? String(chainIdValue)
54
+ : unsigned(chainIdValue);
55
+ if (paymentProfile === "mainnet" && chainId === "8453") {
56
+ return { asset: getAddress(BASE_USDC), chainId: 8453, name: "USD Coin", network: BASE_NETWORK };
57
+ }
58
+ if (paymentProfile === "mainnet" && chainId === "42161") {
59
+ return {
60
+ asset: getAddress(ARBITRUM_USDC),
61
+ chainId: 42161,
62
+ name: "USD Coin",
63
+ network: ARBITRUM_NETWORK,
64
+ };
65
+ }
66
+ if (paymentProfile === "base_sepolia_integration" && chainId === "84532") {
67
+ return {
68
+ asset: getAddress(BASE_SEPOLIA_USDC),
69
+ chainId: 84532,
70
+ name: "USDC",
71
+ network: BASE_SEPOLIA_NETWORK,
72
+ };
73
+ }
74
+ throw new InvalidEip3009AuthorizationError();
75
+ }
76
+ export function parseExactEip3009Authorization(value, options) {
77
+ const request = exactRecord(value, ["domain", "message", "primaryType", "types"]);
78
+ if (request.primaryType !== "TransferWithAuthorization") {
79
+ throw new InvalidEip3009AuthorizationError();
80
+ }
81
+ const domain = exactRecord(request.domain, ["chainId", "name", "verifyingContract", "version"]);
82
+ const selectedNetwork = network(domain.chainId, options.paymentProfile);
83
+ const asset = address(domain.verifyingContract);
84
+ if (domain.name !== selectedNetwork.name ||
85
+ domain.version !== "2" ||
86
+ asset !== selectedNetwork.asset) {
87
+ throw new InvalidEip3009AuthorizationError();
88
+ }
89
+ const types = exactRecord(request.types, ["TransferWithAuthorization"]);
90
+ if (JSON.stringify(types.TransferWithAuthorization) !== JSON.stringify(AUTHORIZATION_TYPES)) {
91
+ throw new InvalidEip3009AuthorizationError();
92
+ }
93
+ const message = exactRecord(request.message, [
94
+ "from",
95
+ "nonce",
96
+ "to",
97
+ "validAfter",
98
+ "validBefore",
99
+ "value",
100
+ ]);
101
+ const from = address(message.from);
102
+ const payTo = address(message.to);
103
+ const amount = unsigned(message.value);
104
+ const validAfter = unsigned(message.validAfter);
105
+ const validBefore = unsigned(message.validBefore);
106
+ const nonce = message.nonce;
107
+ if (from !== getAddress(options.address) ||
108
+ amount === "0" ||
109
+ BigInt(amount) > MAX_USDC_AMOUNT_ATOMIC ||
110
+ validAfter !== "0" ||
111
+ typeof nonce !== "string" ||
112
+ !/^0x[0-9a-fA-F]{64}$/.test(nonce) ||
113
+ BigInt(validBefore) <= BigInt(options.nowSeconds) ||
114
+ BigInt(validBefore) > BigInt(options.nowSeconds + MAX_AUTHORIZATION_LIFETIME_SECONDS)) {
115
+ throw new InvalidEip3009AuthorizationError();
116
+ }
117
+ const authorizationNonce = nonce;
118
+ return {
119
+ amount,
120
+ asset,
121
+ network: selectedNetwork.network,
122
+ payTo,
123
+ typedData: {
124
+ domain: {
125
+ chainId: selectedNetwork.chainId,
126
+ name: selectedNetwork.name,
127
+ verifyingContract: asset,
128
+ version: "2",
129
+ },
130
+ message: {
131
+ from,
132
+ nonce: authorizationNonce,
133
+ to: payTo,
134
+ validAfter: BigInt(validAfter),
135
+ validBefore: BigInt(validBefore),
136
+ value: BigInt(amount),
137
+ },
138
+ primaryType: "TransferWithAuthorization",
139
+ types: { TransferWithAuthorization: AUTHORIZATION_TYPES },
140
+ },
141
+ };
142
+ }
@@ -0,0 +1,4 @@
1
+ export declare class SignerNotConfiguredError extends Error {
2
+ readonly code = "SIGNER_NOT_CONFIGURED";
3
+ constructor();
4
+ }
package/dist/errors.js ADDED
@@ -0,0 +1,7 @@
1
+ export class SignerNotConfiguredError extends Error {
2
+ code = "SIGNER_NOT_CONFIGURED";
3
+ constructor() {
4
+ super("SIGNER_NOT_CONFIGURED: Agent signing is not configured for this agent.");
5
+ this.name = "SignerNotConfiguredError";
6
+ }
7
+ }
@@ -0,0 +1,46 @@
1
+ export declare const PAID_FETCH_INPUT_SCHEMA: {
2
+ additionalProperties: boolean;
3
+ properties: {
4
+ body: {
5
+ maxLength: number;
6
+ type: "string";
7
+ };
8
+ headers: {
9
+ additionalProperties: {
10
+ type: "string";
11
+ };
12
+ maxProperties: number;
13
+ type: "object";
14
+ };
15
+ idempotencyKey: {
16
+ maxLength: number;
17
+ pattern: string;
18
+ type: "string";
19
+ };
20
+ method: {
21
+ enum: string[];
22
+ type: "string";
23
+ };
24
+ url: {
25
+ maxLength: number;
26
+ type: "string";
27
+ };
28
+ };
29
+ required: string[];
30
+ type: "object";
31
+ };
32
+ export interface ParsedFetchRequest {
33
+ body?: string;
34
+ headers?: Record<string, string>;
35
+ idempotencyKey: string;
36
+ method: string;
37
+ url: string;
38
+ }
39
+ export declare function parsePaidFetchRequest(value: unknown, options?: {
40
+ allowDevelopmentLoopback?: boolean;
41
+ }): ParsedFetchRequest;
42
+ export declare function readBoundedResponse(response: Response): Promise<{
43
+ body: string;
44
+ truncated: boolean;
45
+ }>;
46
+ export declare function readResponseHeaders(response: Response): Record<string, string>;
@@ -0,0 +1,143 @@
1
+ import { validatePaymentTarget } from "./payment-target-policy.js";
2
+ const MAX_BODY_BYTES = 1_048_576;
3
+ const MAX_HEADERS = 32;
4
+ const MAX_RESPONSE_BYTES = 262_144;
5
+ const MAX_URL_LENGTH = 2_048;
6
+ const IDEMPOTENCY_KEY_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/;
7
+ const METHODS = new Set(["DELETE", "GET", "HEAD", "PATCH", "POST", "PUT"]);
8
+ const FORBIDDEN_HEADERS = new Set(["connection", "content-length", "host", "transfer-encoding"]);
9
+ export const PAID_FETCH_INPUT_SCHEMA = {
10
+ additionalProperties: false,
11
+ properties: {
12
+ body: { maxLength: MAX_BODY_BYTES, type: "string" },
13
+ headers: {
14
+ additionalProperties: { type: "string" },
15
+ maxProperties: MAX_HEADERS,
16
+ type: "object",
17
+ },
18
+ idempotencyKey: {
19
+ maxLength: 128,
20
+ pattern: IDEMPOTENCY_KEY_PATTERN.source,
21
+ type: "string",
22
+ },
23
+ method: { enum: [...METHODS], type: "string" },
24
+ url: { maxLength: MAX_URL_LENGTH, type: "string" },
25
+ },
26
+ required: ["idempotencyKey", "url"],
27
+ type: "object",
28
+ };
29
+ function record(value) {
30
+ return typeof value === "object" && value !== null && !Array.isArray(value);
31
+ }
32
+ function parseHeaders(value) {
33
+ if (value === undefined)
34
+ return undefined;
35
+ if (!record(value) || Object.keys(value).length > MAX_HEADERS)
36
+ throw new Error("headers");
37
+ const headers = {};
38
+ let totalBytes = 0;
39
+ for (const [name, headerValue] of Object.entries(value)) {
40
+ const normalized = name.toLowerCase();
41
+ if (!/^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/.test(name) ||
42
+ FORBIDDEN_HEADERS.has(normalized) ||
43
+ typeof headerValue !== "string" ||
44
+ /[\r\n]/.test(headerValue)) {
45
+ throw new Error("headers");
46
+ }
47
+ totalBytes += Buffer.byteLength(name) + Buffer.byteLength(headerValue);
48
+ if (totalBytes > 32_768)
49
+ throw new Error("headers");
50
+ headers[name] = headerValue;
51
+ }
52
+ return headers;
53
+ }
54
+ export function parsePaidFetchRequest(value, options = {}) {
55
+ if (!record(value))
56
+ throw new Error("request");
57
+ const keys = Object.keys(value);
58
+ if (keys.some((key) => !["body", "headers", "idempotencyKey", "method", "url"].includes(key))) {
59
+ throw new Error("request");
60
+ }
61
+ if (typeof value.idempotencyKey !== "string" ||
62
+ !IDEMPOTENCY_KEY_PATTERN.test(value.idempotencyKey)) {
63
+ throw new Error("idempotencyKey");
64
+ }
65
+ if (typeof value.url !== "string" ||
66
+ value.url.length === 0 ||
67
+ value.url.length > MAX_URL_LENGTH) {
68
+ throw new Error("url");
69
+ }
70
+ let url;
71
+ try {
72
+ url = new URL(value.url);
73
+ }
74
+ catch {
75
+ throw new Error("url");
76
+ }
77
+ if ((url.protocol !== "http:" && url.protocol !== "https:") ||
78
+ url.username.length > 0 ||
79
+ url.password.length > 0) {
80
+ throw new Error("url");
81
+ }
82
+ const method = value.method === undefined ? "GET" : value.method;
83
+ if (typeof method !== "string" || method !== method.toUpperCase() || !METHODS.has(method)) {
84
+ throw new Error("method");
85
+ }
86
+ if (value.body !== undefined &&
87
+ (typeof value.body !== "string" ||
88
+ Buffer.byteLength(value.body) > MAX_BODY_BYTES ||
89
+ method === "GET" ||
90
+ method === "HEAD")) {
91
+ throw new Error("body");
92
+ }
93
+ const headers = parseHeaders(value.headers);
94
+ return {
95
+ ...(value.body === undefined ? {} : { body: value.body }),
96
+ ...(headers === undefined ? {} : { headers }),
97
+ idempotencyKey: value.idempotencyKey,
98
+ method,
99
+ url: validatePaymentTarget(url.toString(), options),
100
+ };
101
+ }
102
+ export async function readBoundedResponse(response) {
103
+ if (!response.body)
104
+ return { body: "", truncated: false };
105
+ const reader = response.body.getReader();
106
+ const chunks = [];
107
+ let length = 0;
108
+ let truncated = false;
109
+ while (true) {
110
+ const { done, value } = await reader.read();
111
+ if (done)
112
+ break;
113
+ const remaining = MAX_RESPONSE_BYTES - length;
114
+ if (value.byteLength > remaining) {
115
+ if (remaining > 0)
116
+ chunks.push(value.subarray(0, remaining));
117
+ length += Math.max(remaining, 0);
118
+ truncated = true;
119
+ await reader.cancel();
120
+ break;
121
+ }
122
+ chunks.push(value);
123
+ length += value.byteLength;
124
+ }
125
+ const bytes = new Uint8Array(length);
126
+ let offset = 0;
127
+ for (const chunk of chunks) {
128
+ bytes.set(chunk, offset);
129
+ offset += chunk.byteLength;
130
+ }
131
+ return { body: new TextDecoder().decode(bytes), truncated };
132
+ }
133
+ export function readResponseHeaders(response) {
134
+ const headers = {};
135
+ let count = 0;
136
+ for (const [name, value] of response.headers) {
137
+ if (count >= 64)
138
+ break;
139
+ headers[name] = value.slice(0, 4_096);
140
+ count += 1;
141
+ }
142
+ return headers;
143
+ }
@@ -0,0 +1,24 @@
1
+ import type { readPaymentAuthorizationState } from "./payment-authorization-state.js";
2
+ import type { PaymentProfile } from "./payment-profile.js";
3
+ import { type PaymentTargetLookup } from "./payment-target-network.js";
4
+ import { TabRemoteSigner } from "./remote-signer.js";
5
+ interface LeashFetchOptions {
6
+ address: `0x${string}`;
7
+ allowDevelopmentLoopback?: boolean;
8
+ apiBaseUrl: string;
9
+ apiKey: string;
10
+ authorizationState?: typeof readPaymentAuthorizationState;
11
+ clientName?: string | (() => string);
12
+ fetch?: typeof globalThis.fetch;
13
+ idempotencyKey?: () => string | undefined;
14
+ nowSeconds?: () => number;
15
+ paymentProfile: PaymentProfile;
16
+ paymentStateDirectory?: string;
17
+ lookup?: PaymentTargetLookup;
18
+ signer?: TabRemoteSigner;
19
+ }
20
+ type FetchInput = Request | string | URL;
21
+ export declare function createTabFetch(options: LeashFetchOptions): ((input: FetchInput, init?: RequestInit) => Promise<Response>) & {
22
+ close: () => Promise<void>;
23
+ };
24
+ export {};
@@ -0,0 +1,70 @@
1
+ import { createDurablePaymentFetch } from "./durable-payment-fetch.js";
2
+ import { currentPaymentOrigin, withPaymentOrigin, withPaymentResourceUrl, } from "./origin-context.js";
3
+ import { createLeashPaymentClient } from "./payment-client.js";
4
+ import { defaultPaymentStateDirectory, PaymentEnvelopeStore } from "./payment-envelope-store.js";
5
+ import { currentPaymentIdempotencyKey } from "./payment-idempotency.js";
6
+ import { createPinnedPaymentFetch } from "./payment-target-network.js";
7
+ import { safePaymentRequestInit, validatePaymentTarget } from "./payment-target-policy.js";
8
+ import { TabRemoteSigner } from "./remote-signer.js";
9
+ function requestUrl(input) {
10
+ return input instanceof Request ? input.url : input.toString();
11
+ }
12
+ function requestName(input, init) {
13
+ const method = init?.method ?? (input instanceof Request ? input.method : "GET");
14
+ const rawUrl = input instanceof Request ? input.url : input.toString();
15
+ let safeUrl = "Invalid URL";
16
+ try {
17
+ const url = new URL(rawUrl);
18
+ url.username = "";
19
+ url.password = "";
20
+ url.search = "";
21
+ url.hash = "";
22
+ safeUrl = url.toString();
23
+ }
24
+ catch {
25
+ // The underlying fetch reports invalid input without persisting the raw value as receipt origin.
26
+ }
27
+ return `${method.toUpperCase()} ${safeUrl}`;
28
+ }
29
+ export function createTabFetch(options) {
30
+ const baseFetch = options.fetch ?? globalThis.fetch;
31
+ const allowDevelopmentLoopback = options.allowDevelopmentLoopback === true;
32
+ const targetPolicy = createPinnedPaymentFetch({
33
+ allowDevelopmentLoopback,
34
+ fetch: baseFetch,
35
+ ...(options.lookup ? { lookup: options.lookup } : {}),
36
+ });
37
+ const signer = options.signer ??
38
+ new TabRemoteSigner({
39
+ address: options.address,
40
+ apiBaseUrl: options.apiBaseUrl,
41
+ apiKey: options.apiKey,
42
+ fetch: baseFetch,
43
+ origin: currentPaymentOrigin,
44
+ paymentProfile: options.paymentProfile,
45
+ });
46
+ const paidFetch = createDurablePaymentFetch({
47
+ address: options.address,
48
+ ...(options.authorizationState ? { authorizationState: options.authorizationState } : {}),
49
+ client: createLeashPaymentClient(signer, options.paymentProfile),
50
+ fetch: targetPolicy.fetch,
51
+ idempotencyKey: options.idempotencyKey ?? currentPaymentIdempotencyKey,
52
+ ...(options.nowSeconds ? { nowSeconds: options.nowSeconds } : {}),
53
+ paymentProfile: options.paymentProfile,
54
+ signer,
55
+ store: new PaymentEnvelopeStore(options.address, options.paymentStateDirectory ?? defaultPaymentStateDirectory()),
56
+ });
57
+ const leashFetch = (input, init) => {
58
+ validatePaymentTarget(requestUrl(input), {
59
+ allowDevelopmentLoopback,
60
+ });
61
+ return withPaymentOrigin({
62
+ clientName: typeof options.clientName === "function"
63
+ ? options.clientName()
64
+ : (options.clientName ?? "leash-fetch"),
65
+ toolName: requestName(input, init),
66
+ transport: "http",
67
+ }, () => withPaymentResourceUrl(requestUrl(input), () => paidFetch(input, safePaymentRequestInit(init))));
68
+ };
69
+ return Object.assign(leashFetch, { close: () => targetPolicy.close() });
70
+ }
@@ -0,0 +1,3 @@
1
+ export { createTabFetch } from "./fetch-wrapper.js";
2
+ export type CreateTabFetchOptions = Parameters<typeof import("./fetch-wrapper.js").createTabFetch>[0];
3
+ export type { PaymentProfile } from "./payment-profile.js";
package/dist/fetch.js ADDED
@@ -0,0 +1 @@
1
+ export { createTabFetch } from "./fetch-wrapper.js";
@@ -0,0 +1,6 @@
1
+ export { createTabFetch } from "./fetch-wrapper.js";
2
+ export type CreateTabFetchOptions = Parameters<typeof import("./fetch-wrapper.js").createTabFetch>[0];
3
+ export { InvalidControlPlaneOriginError, validateControlPlaneOrigin, } from "./control-plane-origin.js";
4
+ export type { PaymentProfile } from "./payment-profile.js";
5
+ export { PaymentTargetPolicyError, validatePaymentTarget } from "./payment-target-policy.js";
6
+ export { RemoteSignerError, TabRemoteSigner } from "./remote-signer.js";
package/dist/index.js ADDED
@@ -0,0 +1,4 @@
1
+ export { createTabFetch } from "./fetch-wrapper.js";
2
+ export { InvalidControlPlaneOriginError, validateControlPlaneOrigin, } from "./control-plane-origin.js";
3
+ export { PaymentTargetPolicyError, validatePaymentTarget } from "./payment-target-policy.js";
4
+ export { RemoteSignerError, TabRemoteSigner } from "./remote-signer.js";
@@ -0,0 +1,5 @@
1
+ import type { PaymentOrigin } from "./remote-signer.js";
2
+ export declare function currentPaymentOrigin(): PaymentOrigin | undefined;
3
+ export declare function withPaymentOrigin<T>(origin: PaymentOrigin, action: () => T): T;
4
+ export declare function currentPaymentResourceUrl(): string | undefined;
5
+ export declare function withPaymentResourceUrl<T>(resourceUrl: string, action: () => T): T;
@@ -0,0 +1,15 @@
1
+ import { AsyncLocalStorage } from "node:async_hooks";
2
+ const paymentOrigin = new AsyncLocalStorage();
3
+ const paymentResourceUrl = new AsyncLocalStorage();
4
+ export function currentPaymentOrigin() {
5
+ return paymentOrigin.getStore();
6
+ }
7
+ export function withPaymentOrigin(origin, action) {
8
+ return paymentOrigin.run(origin, action);
9
+ }
10
+ export function currentPaymentResourceUrl() {
11
+ return paymentResourceUrl.getStore();
12
+ }
13
+ export function withPaymentResourceUrl(resourceUrl, action) {
14
+ return paymentResourceUrl.run(resourceUrl, action);
15
+ }