@odingard/guardian-x402 0.0.0-stage → 0.1.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.
- package/LICENSE +201 -0
- package/README.md +269 -2
- package/dist/axios.js +85 -0
- package/dist/chunk-C2QTXTL6.js +1443 -0
- package/dist/chunk-DJIAG2IA.js +58 -0
- package/dist/fetch.js +62 -0
- package/dist/index.js +57 -0
- package/dist/mcp.js +60 -0
- package/dist-dts/axios.d.ts +9 -0
- package/dist-dts/constants.d.ts +58 -0
- package/dist-dts/context.d.ts +56 -0
- package/dist-dts/fetch.d.ts +9 -0
- package/dist-dts/guard.d.ts +209 -0
- package/dist-dts/index.d.ts +32 -0
- package/dist-dts/ledger.d.ts +84 -0
- package/dist-dts/mcp.d.ts +30 -0
- package/dist-dts/terms.d.ts +79 -0
- package/dist-dts/util.d.ts +51 -0
- package/dist-dts/version.d.ts +14 -0
- package/package.json +89 -4
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// src/context.ts
|
|
2
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
3
|
+
var requestStorage = new AsyncLocalStorage();
|
|
4
|
+
var flowStorage = new AsyncLocalStorage();
|
|
5
|
+
|
|
6
|
+
// src/version.ts
|
|
7
|
+
import { readFileSync } from "node:fs";
|
|
8
|
+
import { createRequire } from "node:module";
|
|
9
|
+
import { dirname, join } from "node:path";
|
|
10
|
+
var TESTED_X402_MAJOR = 2;
|
|
11
|
+
var TESTED_X402_MINOR = 30;
|
|
12
|
+
var TESTED_X402_RANGE = "~2.30.0";
|
|
13
|
+
var require2 = createRequire(import.meta.url);
|
|
14
|
+
var checked = /* @__PURE__ */ new Map();
|
|
15
|
+
function installedVersion(name) {
|
|
16
|
+
let dir;
|
|
17
|
+
try {
|
|
18
|
+
dir = dirname(require2.resolve(name));
|
|
19
|
+
} catch {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
for (let i = 0; i < 8; i += 1) {
|
|
23
|
+
try {
|
|
24
|
+
const pkg = JSON.parse(readFileSync(join(dir, "package.json"), "utf8"));
|
|
25
|
+
if (pkg.name === name && typeof pkg.version === "string") {
|
|
26
|
+
return pkg.version;
|
|
27
|
+
}
|
|
28
|
+
} catch {
|
|
29
|
+
}
|
|
30
|
+
const parent = dirname(dir);
|
|
31
|
+
if (parent === dir) break;
|
|
32
|
+
dir = parent;
|
|
33
|
+
}
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
function isSupportedX402Version(version) {
|
|
37
|
+
const parts = typeof version === "string" ? version.match(/^(\d+)\.(\d+)\.(\d+)$/) : null;
|
|
38
|
+
if (!parts) return false;
|
|
39
|
+
return Number(parts[1]) === TESTED_X402_MAJOR && Number(parts[2]) === TESTED_X402_MINOR;
|
|
40
|
+
}
|
|
41
|
+
function assertSupportedX402(name, readVersion = installedVersion) {
|
|
42
|
+
const cached = checked.get(name);
|
|
43
|
+
if (cached && readVersion === installedVersion) return cached;
|
|
44
|
+
const version = readVersion(name);
|
|
45
|
+
if (!isSupportedX402Version(version)) {
|
|
46
|
+
throw new Error(
|
|
47
|
+
`guardian-x402 supports ${name} ${TESTED_X402_RANGE}; found ${version ?? "none"}.`
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
if (readVersion === installedVersion) checked.set(name, version);
|
|
51
|
+
return version;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export {
|
|
55
|
+
requestStorage,
|
|
56
|
+
flowStorage,
|
|
57
|
+
assertSupportedX402
|
|
58
|
+
};
|
package/dist/fetch.js
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import {
|
|
2
|
+
assertSupportedX402,
|
|
3
|
+
requestStorage
|
|
4
|
+
} from "./chunk-DJIAG2IA.js";
|
|
5
|
+
|
|
6
|
+
// src/fetch.ts
|
|
7
|
+
import { randomBytes } from "node:crypto";
|
|
8
|
+
import { decodePaymentResponseHeader } from "@x402/core/http";
|
|
9
|
+
import { wrapFetchWithPayment } from "@x402/fetch";
|
|
10
|
+
function readSettle(headers) {
|
|
11
|
+
const raw = headers.get("X-PAYMENT-RESPONSE");
|
|
12
|
+
if (!raw) return null;
|
|
13
|
+
try {
|
|
14
|
+
return decodePaymentResponseHeader(raw);
|
|
15
|
+
} catch {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
function guardedFetch(guard, fetchImpl = globalThis.fetch) {
|
|
20
|
+
assertSupportedX402("@x402/fetch");
|
|
21
|
+
const paying = wrapFetchWithPayment(fetchImpl, guard.httpClient());
|
|
22
|
+
const guarded = async (input, init) => {
|
|
23
|
+
const isRequest = typeof Request !== "undefined" && input instanceof Request;
|
|
24
|
+
let originalUrl = "";
|
|
25
|
+
try {
|
|
26
|
+
originalUrl = isRequest ? input.url : new URL(String(input)).href;
|
|
27
|
+
} catch {
|
|
28
|
+
originalUrl = "";
|
|
29
|
+
}
|
|
30
|
+
const method = (init?.method ?? (isRequest ? input.method : "GET")).toUpperCase();
|
|
31
|
+
const ctx = {
|
|
32
|
+
id: `x4r_${randomBytes(8).toString("hex")}`,
|
|
33
|
+
info: {
|
|
34
|
+
transport: "http",
|
|
35
|
+
method,
|
|
36
|
+
originalUrl,
|
|
37
|
+
finalUrl: null
|
|
38
|
+
},
|
|
39
|
+
signedEntries: [],
|
|
40
|
+
versions: /* @__PURE__ */ new Map()
|
|
41
|
+
};
|
|
42
|
+
return requestStorage.run(ctx, async () => {
|
|
43
|
+
let response;
|
|
44
|
+
try {
|
|
45
|
+
response = await paying(input, init);
|
|
46
|
+
} catch (error) {
|
|
47
|
+
if (error instanceof Error && error.message === "Payment already attempted") {
|
|
48
|
+
await guard.markSignedNotSent(ctx);
|
|
49
|
+
}
|
|
50
|
+
throw error;
|
|
51
|
+
}
|
|
52
|
+
if (ctx.signedEntries.some((id) => ctx.versions.get(id) === 1)) {
|
|
53
|
+
await guard.recordV1Settlement(ctx, readSettle(response.headers));
|
|
54
|
+
}
|
|
55
|
+
return response;
|
|
56
|
+
});
|
|
57
|
+
};
|
|
58
|
+
return guarded;
|
|
59
|
+
}
|
|
60
|
+
export {
|
|
61
|
+
guardedFetch
|
|
62
|
+
};
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BASE_MAINNET,
|
|
3
|
+
BASE_SEPOLIA,
|
|
4
|
+
EIP3009_TYPES,
|
|
5
|
+
GuardedX402Client,
|
|
6
|
+
PAY_ABOVE_CAP_TOOL,
|
|
7
|
+
PAY_TOOL,
|
|
8
|
+
PINNED_ASSETS,
|
|
9
|
+
X402Guard,
|
|
10
|
+
X402GuardRefused,
|
|
11
|
+
createX402Guard,
|
|
12
|
+
readTerms
|
|
13
|
+
} from "./chunk-C2QTXTL6.js";
|
|
14
|
+
import "./chunk-DJIAG2IA.js";
|
|
15
|
+
|
|
16
|
+
// src/index.ts
|
|
17
|
+
var AUTHORIZATION_STATE_ABI = [
|
|
18
|
+
{
|
|
19
|
+
type: "function",
|
|
20
|
+
name: "authorizationState",
|
|
21
|
+
stateMutability: "view",
|
|
22
|
+
inputs: [
|
|
23
|
+
{ name: "authorizer", type: "address" },
|
|
24
|
+
{ name: "nonce", type: "bytes32" }
|
|
25
|
+
],
|
|
26
|
+
outputs: [{ name: "", type: "bool" }]
|
|
27
|
+
}
|
|
28
|
+
];
|
|
29
|
+
function authorizationStateReader(readContractFor) {
|
|
30
|
+
return async ({ network, asset, from, nonce }) => {
|
|
31
|
+
const read = readContractFor(network);
|
|
32
|
+
if (!read) throw new Error(`no reader for ${network}`);
|
|
33
|
+
const used = await read({
|
|
34
|
+
address: asset,
|
|
35
|
+
abi: AUTHORIZATION_STATE_ABI,
|
|
36
|
+
functionName: "authorizationState",
|
|
37
|
+
args: [from, nonce]
|
|
38
|
+
});
|
|
39
|
+
if (typeof used !== "boolean") throw new Error("authorizationState is not a boolean");
|
|
40
|
+
return used;
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
export {
|
|
44
|
+
AUTHORIZATION_STATE_ABI,
|
|
45
|
+
BASE_MAINNET,
|
|
46
|
+
BASE_SEPOLIA,
|
|
47
|
+
EIP3009_TYPES,
|
|
48
|
+
GuardedX402Client,
|
|
49
|
+
PAY_ABOVE_CAP_TOOL,
|
|
50
|
+
PAY_TOOL,
|
|
51
|
+
PINNED_ASSETS,
|
|
52
|
+
X402Guard,
|
|
53
|
+
X402GuardRefused,
|
|
54
|
+
authorizationStateReader,
|
|
55
|
+
createX402Guard,
|
|
56
|
+
readTerms
|
|
57
|
+
};
|
package/dist/mcp.js
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import {
|
|
2
|
+
GuardedX402Client,
|
|
3
|
+
X402GuardRefused
|
|
4
|
+
} from "./chunk-C2QTXTL6.js";
|
|
5
|
+
import {
|
|
6
|
+
assertSupportedX402,
|
|
7
|
+
requestStorage
|
|
8
|
+
} from "./chunk-DJIAG2IA.js";
|
|
9
|
+
|
|
10
|
+
// src/mcp.ts
|
|
11
|
+
import { randomBytes } from "node:crypto";
|
|
12
|
+
import { x402MCPClient } from "@x402/mcp";
|
|
13
|
+
var GuardedX402MCPClient = class extends x402MCPClient {
|
|
14
|
+
serverUrl;
|
|
15
|
+
constructor(guard, mcpClient, options = {}) {
|
|
16
|
+
const paymentClient = guard.client;
|
|
17
|
+
super(mcpClient, paymentClient, {
|
|
18
|
+
autoPayment: true,
|
|
19
|
+
// A local check only: never approves anything by itself and never
|
|
20
|
+
// asks Guardian. The decision is the core hook's.
|
|
21
|
+
onPaymentRequested: () => paymentClient instanceof GuardedX402Client && paymentClient.guard === guard,
|
|
22
|
+
...options.maxRequestTimeoutSeconds !== void 0 ? { maxRequestTimeoutSeconds: options.maxRequestTimeoutSeconds } : {}
|
|
23
|
+
});
|
|
24
|
+
this.serverUrl = options.serverUrl ?? null;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Refused: an `onPaymentRequired` hook can hand x402 a ready-made payment
|
|
28
|
+
* that skips the guard.
|
|
29
|
+
*/
|
|
30
|
+
onPaymentRequired(_hook) {
|
|
31
|
+
throw new X402GuardRefused(
|
|
32
|
+
"GUARD_SEALED",
|
|
33
|
+
"onPaymentRequired hooks are refused on a guarded MCP client."
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
async callTool(name, args = {}, options) {
|
|
37
|
+
const ctx = {
|
|
38
|
+
id: `x4r_${randomBytes(8).toString("hex")}`,
|
|
39
|
+
info: {
|
|
40
|
+
transport: "mcp",
|
|
41
|
+
method: "tools/call",
|
|
42
|
+
originalUrl: `mcp://tool/${name}`,
|
|
43
|
+
finalUrl: null,
|
|
44
|
+
toolName: name,
|
|
45
|
+
serverUrl: this.serverUrl
|
|
46
|
+
},
|
|
47
|
+
signedEntries: [],
|
|
48
|
+
versions: /* @__PURE__ */ new Map()
|
|
49
|
+
};
|
|
50
|
+
return requestStorage.run(ctx, () => super.callTool(name, args, options));
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
function guardedMcpClient(guard, mcpClient, options = {}) {
|
|
54
|
+
assertSupportedX402("@x402/mcp");
|
|
55
|
+
return new GuardedX402MCPClient(guard, mcpClient, options);
|
|
56
|
+
}
|
|
57
|
+
export {
|
|
58
|
+
GuardedX402MCPClient,
|
|
59
|
+
guardedMcpClient
|
|
60
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { AxiosInstance } from "axios";
|
|
2
|
+
import type { X402Guard } from "./guard.js";
|
|
3
|
+
/**
|
|
4
|
+
* The official `wrapAxiosWithPayment` on the given instance, built on the
|
|
5
|
+
* guard's client. Use only the returned instance: calls made on the
|
|
6
|
+
* original instance carry no original URL and every payment they attempt
|
|
7
|
+
* is refused.
|
|
8
|
+
*/
|
|
9
|
+
export declare function guardedAxios(guard: X402Guard, instance: AxiosInstance): AxiosInstance;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/** Guardian tool names the guard proposes (`tool.invoke`, record transport). */
|
|
2
|
+
export declare const PAY_TOOL = "x402.pay";
|
|
3
|
+
export declare const PAY_ABOVE_CAP_TOOL = "x402.pay.above_cap";
|
|
4
|
+
export declare const BASE_SEPOLIA = "eip155:84532";
|
|
5
|
+
export declare const BASE_MAINNET = "eip155:8453";
|
|
6
|
+
export type SupportedNetwork = typeof BASE_SEPOLIA | typeof BASE_MAINNET;
|
|
7
|
+
export type PinnedAsset = Readonly<{
|
|
8
|
+
network: SupportedNetwork;
|
|
9
|
+
chainId: number;
|
|
10
|
+
networkName: string;
|
|
11
|
+
/** Lowercase token contract address. */
|
|
12
|
+
asset: string;
|
|
13
|
+
/** EIP-712 domain name and version of the token. */
|
|
14
|
+
name: string;
|
|
15
|
+
version: string;
|
|
16
|
+
decimals: number;
|
|
17
|
+
symbol: string;
|
|
18
|
+
}>;
|
|
19
|
+
/**
|
|
20
|
+
* The only assets the guard pays in. Values are the guard's own pins, not
|
|
21
|
+
* read from the 402 response.
|
|
22
|
+
*/
|
|
23
|
+
export declare const PINNED_ASSETS: readonly PinnedAsset[];
|
|
24
|
+
/** x402 v1 network names the guard maps. Nothing else is mapped. */
|
|
25
|
+
export declare const V1_NETWORK_NAMES: Readonly<Record<string, SupportedNetwork>>;
|
|
26
|
+
/** The EIP-3009 typed-data types, exactly as signed. */
|
|
27
|
+
export declare const EIP3009_TYPES: Readonly<{
|
|
28
|
+
TransferWithAuthorization: readonly (Readonly<{
|
|
29
|
+
name: "from";
|
|
30
|
+
type: "address";
|
|
31
|
+
}> | Readonly<{
|
|
32
|
+
name: "to";
|
|
33
|
+
type: "address";
|
|
34
|
+
}> | Readonly<{
|
|
35
|
+
name: "value";
|
|
36
|
+
type: "uint256";
|
|
37
|
+
}> | Readonly<{
|
|
38
|
+
name: "validAfter";
|
|
39
|
+
type: "uint256";
|
|
40
|
+
}> | Readonly<{
|
|
41
|
+
name: "validBefore";
|
|
42
|
+
type: "uint256";
|
|
43
|
+
}> | Readonly<{
|
|
44
|
+
name: "nonce";
|
|
45
|
+
type: "bytes32";
|
|
46
|
+
}>)[];
|
|
47
|
+
}>;
|
|
48
|
+
export declare const DEFAULT_MAX_TIMEOUT_SECONDS_LIMIT = 300;
|
|
49
|
+
export declare const DEFAULT_SIGNING_WINDOW_MS = 30000;
|
|
50
|
+
export declare const DEFAULT_CLAIM_POLL_INTERVAL_MS = 2000;
|
|
51
|
+
export declare const DEFAULT_GUARDIAN_CALL_TIMEOUT_MS = 15000;
|
|
52
|
+
export declare const MAX_SIGNING_WINDOW_MS = 120000;
|
|
53
|
+
export declare const MAX_CLAIM_POLL_INTERVAL_MS = 60000;
|
|
54
|
+
export declare const MAX_GUARDIAN_CALL_TIMEOUT_MS = 60000;
|
|
55
|
+
/** How long past `validBefore` reconcile waits before reading the nonce. */
|
|
56
|
+
export declare const RECONCILE_MARGIN_MS = 300000;
|
|
57
|
+
export declare function pinnedAsset(network: string, asset: string): PinnedAsset | undefined;
|
|
58
|
+
export declare function pinnedAssetsOn(network: string): PinnedAsset[];
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
|
+
import type { CanonicalTerms, PaymentRequestInfo } from "./terms.js";
|
|
3
|
+
import type { PinnedAsset } from "./constants.js";
|
|
4
|
+
/** One request the agent made through a guarded transport. */
|
|
5
|
+
export type RequestContext = {
|
|
6
|
+
readonly id: string;
|
|
7
|
+
readonly info: PaymentRequestInfo;
|
|
8
|
+
/** Ledger ids of authorizations signed while serving this request. */
|
|
9
|
+
readonly signedEntries: string[];
|
|
10
|
+
/** x402 version of each signed flow, by ledger id. */
|
|
11
|
+
readonly versions: Map<string, number>;
|
|
12
|
+
};
|
|
13
|
+
export type SigningToken = {
|
|
14
|
+
readonly terms: CanonicalTerms;
|
|
15
|
+
readonly pinned: PinnedAsset;
|
|
16
|
+
/** Snapshot of the selected entry at decision time. */
|
|
17
|
+
readonly requirementSnapshot: unknown;
|
|
18
|
+
readonly requestId: string;
|
|
19
|
+
readonly receiptReference: string;
|
|
20
|
+
readonly expiresAtMs: number;
|
|
21
|
+
used: boolean;
|
|
22
|
+
};
|
|
23
|
+
/** One `createPaymentPayload` call. */
|
|
24
|
+
export type PaymentFlow = {
|
|
25
|
+
readonly id: string;
|
|
26
|
+
readonly paymentRequired: unknown;
|
|
27
|
+
readonly request: PaymentRequestInfo | null;
|
|
28
|
+
readonly requestContext: RequestContext | null;
|
|
29
|
+
/** Policy-time refusal reasons, first one wins in errors. */
|
|
30
|
+
readonly dropped: {
|
|
31
|
+
code: string;
|
|
32
|
+
detail: string;
|
|
33
|
+
}[];
|
|
34
|
+
/** The live entry x402 selected (a reference, not a copy). */
|
|
35
|
+
selectedRequirements?: unknown;
|
|
36
|
+
decided: boolean;
|
|
37
|
+
ledgerId?: string;
|
|
38
|
+
/** Set once Guardian's execute redeemed the decision. */
|
|
39
|
+
redeemed?: {
|
|
40
|
+
requestId: string;
|
|
41
|
+
receiptReference: string;
|
|
42
|
+
};
|
|
43
|
+
/** Set when `execute` was sent but its outcome is unknown (may be redeemed). */
|
|
44
|
+
executeOutcomeUnknown?: {
|
|
45
|
+
requestId: string;
|
|
46
|
+
};
|
|
47
|
+
token?: SigningToken;
|
|
48
|
+
signed?: {
|
|
49
|
+
nonce: string;
|
|
50
|
+
from: string;
|
|
51
|
+
validBefore: number;
|
|
52
|
+
};
|
|
53
|
+
signAttempted: boolean;
|
|
54
|
+
};
|
|
55
|
+
export declare const requestStorage: AsyncLocalStorage<RequestContext>;
|
|
56
|
+
export declare const flowStorage: AsyncLocalStorage<PaymentFlow>;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { X402Guard } from "./guard.js";
|
|
2
|
+
/**
|
|
3
|
+
* The official `wrapFetchWithPayment`, built on the guard's client. Every
|
|
4
|
+
* payment it makes is decided by Guardian before the wallet signs.
|
|
5
|
+
*
|
|
6
|
+
* The guard records the URL and method the agent asked for (before any
|
|
7
|
+
* redirect); a payment whose resource host differs is refused.
|
|
8
|
+
*/
|
|
9
|
+
export declare function guardedFetch(guard: X402Guard, fetchImpl?: typeof globalThis.fetch): typeof globalThis.fetch;
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import { type GuardianClient } from "@odingard/guardian-sdk";
|
|
2
|
+
import { x402Client, x402HTTPClient, type PaymentCreatedContext, type PaymentCreationContext, type PaymentPolicy, type PaymentResponseContext, type SelectPaymentRequirements, type SpendControls } from "@x402/core/client";
|
|
3
|
+
import type { PaymentPayload, PaymentRequired, SettleResponse } from "@x402/core/types";
|
|
4
|
+
import { type SupportedNetwork } from "./constants.js";
|
|
5
|
+
import { requestStorage, type PaymentFlow, type RequestContext } from "./context.js";
|
|
6
|
+
import { type AuthorizationStateReader, type BudgetConfig, type LedgerEntry, type X402GuardStore } from "./ledger.js";
|
|
7
|
+
import { type CanonicalTerms, type PaymentRequestInfo } from "./terms.js";
|
|
8
|
+
import { X402GuardRefused, type RefusalCode } from "./util.js";
|
|
9
|
+
/** The wallet the guard signs with. A viem local account fits. */
|
|
10
|
+
export type X402WalletSigner = {
|
|
11
|
+
readonly address: `0x${string}`;
|
|
12
|
+
signTypedData(message: {
|
|
13
|
+
domain: Record<string, unknown>;
|
|
14
|
+
types: Record<string, unknown>;
|
|
15
|
+
primaryType: string;
|
|
16
|
+
message: Record<string, unknown>;
|
|
17
|
+
}): Promise<`0x${string}`>;
|
|
18
|
+
};
|
|
19
|
+
/** Local limits for one pinned asset, in its atomic units. */
|
|
20
|
+
export type AssetLimit = Readonly<{
|
|
21
|
+
network: SupportedNetwork;
|
|
22
|
+
asset: string;
|
|
23
|
+
/** Above this, the payment needs a person's approval. */
|
|
24
|
+
askAbove: string;
|
|
25
|
+
/** Above this, the payment is refused locally. */
|
|
26
|
+
max: string;
|
|
27
|
+
/** Optional: the only recipients allowed for this asset. */
|
|
28
|
+
payTo?: readonly string[];
|
|
29
|
+
}>;
|
|
30
|
+
export type X402GuardConfig = Readonly<{
|
|
31
|
+
/** A Guardian client (`createGuardianClient`). */
|
|
32
|
+
guardian: GuardianClient;
|
|
33
|
+
/** The tool connection holding the `x402.pay` and `x402.pay.above_cap` entries. */
|
|
34
|
+
connectionId: string;
|
|
35
|
+
/** The wallet. The guard never exposes it; it hands x402 a wrapped signer. */
|
|
36
|
+
signer: X402WalletSigner;
|
|
37
|
+
/**
|
|
38
|
+
* Networks the guard pays on. Default: Base Sepolia only. Base mainnet
|
|
39
|
+
* (`eip155:8453`) must be named here to be used.
|
|
40
|
+
*/
|
|
41
|
+
enabledNetworks?: readonly SupportedNetwork[];
|
|
42
|
+
/** Per-asset `askAbove` and `max`. An asset with none: every payment needs a person. */
|
|
43
|
+
limits?: readonly AssetLimit[];
|
|
44
|
+
/** Local budgets. Need `store`. */
|
|
45
|
+
budgets?: readonly BudgetConfig[];
|
|
46
|
+
/** Durable store with compare-and-set, for budgets and live authorizations. */
|
|
47
|
+
store?: X402GuardStore;
|
|
48
|
+
/** On-chain `authorizationState` reader, used by `reconcile()` to release budgets. */
|
|
49
|
+
authorizationState?: AuthorizationStateReader;
|
|
50
|
+
/** x402's own spend controls. Cannot be turned off. */
|
|
51
|
+
spendControls?: SpendControls;
|
|
52
|
+
paymentRequirementsSelector?: SelectPaymentRequirements;
|
|
53
|
+
/** Highest `maxTimeoutSeconds` accepted. Default 300. */
|
|
54
|
+
maxTimeoutSecondsLimit?: number;
|
|
55
|
+
/** How long a Guardian request stays open (an ASK expires after it). Default 120 s, max 300 s. */
|
|
56
|
+
requestTtlMs?: number;
|
|
57
|
+
/** How long a signing token lasts after `execute`. Default 30 s, max 120 s. */
|
|
58
|
+
signingWindowMs?: number;
|
|
59
|
+
/** How often `claim` is polled while a person decides. Default 2 s, max 60 s. */
|
|
60
|
+
claimPollIntervalMs?: number;
|
|
61
|
+
/** Per-call limit on each Guardian call. Default 15 s, max 60 s. */
|
|
62
|
+
guardianCallTimeoutMs?: number;
|
|
63
|
+
/**
|
|
64
|
+
* Optional receipt verification keys (from `fetchReceiptVerificationKeys`).
|
|
65
|
+
* When given, the receipt checked for an above-cap payment must verify
|
|
66
|
+
* offline.
|
|
67
|
+
*/
|
|
68
|
+
receiptVerificationKeys?: unknown;
|
|
69
|
+
/** Called with each local record. Records never hold keys or signatures. */
|
|
70
|
+
onRecord?: (record: X402GuardRecord) => void;
|
|
71
|
+
/** Clock, for tests. */
|
|
72
|
+
now?: () => number;
|
|
73
|
+
}>;
|
|
74
|
+
export type X402GuardRecord = Readonly<{
|
|
75
|
+
at: number;
|
|
76
|
+
event: "refused" | "redeemed" | "signed" | "redeemed-not-signed" | "signed-not-sent" | "settlement" | "released" | "spent";
|
|
77
|
+
flowId?: string;
|
|
78
|
+
tool?: string;
|
|
79
|
+
requestId?: string;
|
|
80
|
+
receiptReference?: string;
|
|
81
|
+
code?: RefusalCode;
|
|
82
|
+
guardianReasonCode?: string;
|
|
83
|
+
detail?: string;
|
|
84
|
+
terms?: CanonicalTerms;
|
|
85
|
+
reviewReasons?: readonly string[];
|
|
86
|
+
approvedByPerson?: boolean;
|
|
87
|
+
/** `execute` was sent but its outcome is unknown; the decision may be spent. */
|
|
88
|
+
outcomeUnknown?: boolean;
|
|
89
|
+
nonce?: string;
|
|
90
|
+
from?: string;
|
|
91
|
+
validBefore?: number;
|
|
92
|
+
network?: string;
|
|
93
|
+
transaction?: string;
|
|
94
|
+
success?: boolean;
|
|
95
|
+
errorReason?: string;
|
|
96
|
+
}>;
|
|
97
|
+
/**
|
|
98
|
+
* Builds and guards the official x402 client. Create one with
|
|
99
|
+
* `createX402Guard`, then use its `client` with the fetch, axios or MCP
|
|
100
|
+
* builders.
|
|
101
|
+
*/
|
|
102
|
+
export declare class X402Guard {
|
|
103
|
+
readonly client: GuardedX402Client;
|
|
104
|
+
private readonly config;
|
|
105
|
+
private readonly ledger;
|
|
106
|
+
private readonly durable;
|
|
107
|
+
private readonly startedAtMs;
|
|
108
|
+
private readonly now;
|
|
109
|
+
private readonly termsOptions;
|
|
110
|
+
private readonly requestTtlMs;
|
|
111
|
+
private readonly signingWindowMs;
|
|
112
|
+
private readonly pollMs;
|
|
113
|
+
private readonly callTimeoutMs;
|
|
114
|
+
private readonly walletAddress;
|
|
115
|
+
private readonly wallet;
|
|
116
|
+
private readonly log;
|
|
117
|
+
/** PaymentRequired object → the request it answered (set by the HTTP hook). */
|
|
118
|
+
private readonly pending;
|
|
119
|
+
constructor(config: X402GuardConfig);
|
|
120
|
+
/** Local records (newest last). No keys, signatures or payment payloads. */
|
|
121
|
+
records(): readonly X402GuardRecord[];
|
|
122
|
+
/** @internal */
|
|
123
|
+
record(r: Omit<X402GuardRecord, "at">): void;
|
|
124
|
+
/** Authorizations the guard signed that may still be settled. */
|
|
125
|
+
liveAuthorizations(): Promise<LedgerEntry[]>;
|
|
126
|
+
/**
|
|
127
|
+
* Releases budget held by authorizations whose `validBefore` has passed
|
|
128
|
+
* and whose nonce the chain shows unused. Needs `authorizationState`.
|
|
129
|
+
*/
|
|
130
|
+
reconcile(): Promise<{
|
|
131
|
+
released: number;
|
|
132
|
+
spent: number;
|
|
133
|
+
}>;
|
|
134
|
+
/**
|
|
135
|
+
* An x402 HTTP client for this guard. Its only `onPaymentRequired` hook
|
|
136
|
+
* records which request the 402 answered; it never returns headers.
|
|
137
|
+
*/
|
|
138
|
+
httpClient(): x402HTTPClient;
|
|
139
|
+
/** @internal */
|
|
140
|
+
requestInfoFor(paymentRequired: object): {
|
|
141
|
+
info: PaymentRequestInfo | null;
|
|
142
|
+
ctx: RequestContext | null;
|
|
143
|
+
};
|
|
144
|
+
/** @internal Fetch: "Payment already attempted" after the payload was signed. */
|
|
145
|
+
markSignedNotSent(ctx: RequestContext): Promise<void>;
|
|
146
|
+
/** @internal v1 settlement, read by the transport wrapper. */
|
|
147
|
+
recordV1Settlement(ctx: RequestContext, settle: SettleResponse | null): Promise<void>;
|
|
148
|
+
private recordSettlement;
|
|
149
|
+
/** @internal Drops entries the guard cannot pay before x402 selects one. */
|
|
150
|
+
policy: PaymentPolicy;
|
|
151
|
+
/** @internal Pre-check before x402 runs: refuse what nothing can pay. */
|
|
152
|
+
precheck(paymentRequired: unknown, request: PaymentRequestInfo | null): void;
|
|
153
|
+
/** @internal The decision point: x402's onBeforePaymentCreation. */
|
|
154
|
+
beforePaymentCreation: (ctx: PaymentCreationContext) => Promise<void | {
|
|
155
|
+
abort: true;
|
|
156
|
+
reason: string;
|
|
157
|
+
}>;
|
|
158
|
+
private limitFor;
|
|
159
|
+
private decide;
|
|
160
|
+
/** The longest a flow can hold a reservation before it signs. */
|
|
161
|
+
private longestFlowMs;
|
|
162
|
+
private guardianCall;
|
|
163
|
+
private askGuardian;
|
|
164
|
+
/** The receipt must show a person approved this exact request. */
|
|
165
|
+
private checkApprovedByPerson;
|
|
166
|
+
/** @internal The only signer x402 gets: EIP-3009 typed data, nothing else. */
|
|
167
|
+
readonly guardedSigner: Readonly<{
|
|
168
|
+
address: `0x${string}`;
|
|
169
|
+
signTypedData: X402WalletSigner["signTypedData"];
|
|
170
|
+
signTransaction: () => Promise<`0x${string}`>;
|
|
171
|
+
}>;
|
|
172
|
+
private readonly recordedRefusals;
|
|
173
|
+
/** @internal Records a refusal once. */
|
|
174
|
+
recordRefusal(err: X402GuardRefused, flowId?: string): void;
|
|
175
|
+
private signGuarded;
|
|
176
|
+
private signChecked;
|
|
177
|
+
private checkTypedData;
|
|
178
|
+
/** @internal */
|
|
179
|
+
afterPaymentCreation: (ctx: PaymentCreatedContext) => Promise<void>;
|
|
180
|
+
/** @internal v2 only (x402 skips this hook for v1). */
|
|
181
|
+
paymentResponse: (ctx: PaymentResponseContext) => Promise<void>;
|
|
182
|
+
/** @internal Called when a flow ends. */
|
|
183
|
+
endFlow(flow: PaymentFlow): Promise<void>;
|
|
184
|
+
/** @internal */
|
|
185
|
+
newFlow(paymentRequired: unknown): PaymentFlow;
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* The official `x402Client`, built and sealed by the guard. Every payment
|
|
189
|
+
* runs in its own flow; the guard's hooks and wrapped signer read it.
|
|
190
|
+
*/
|
|
191
|
+
export declare class GuardedX402Client extends x402Client {
|
|
192
|
+
/** @internal */
|
|
193
|
+
readonly guard: X402Guard;
|
|
194
|
+
private sealed;
|
|
195
|
+
constructor(guard: X402Guard, config: X402GuardConfig);
|
|
196
|
+
register(...args: Parameters<x402Client["register"]>): x402Client;
|
|
197
|
+
registerV1(...args: Parameters<x402Client["registerV1"]>): x402Client;
|
|
198
|
+
registerExtension(...args: Parameters<x402Client["registerExtension"]>): x402Client;
|
|
199
|
+
/**
|
|
200
|
+
* Refused once sealed: a failure hook can return its own payload, which
|
|
201
|
+
* x402 would send in place of a guard refusal.
|
|
202
|
+
*/
|
|
203
|
+
onPaymentCreationFailure(...args: Parameters<x402Client["onPaymentCreationFailure"]>): x402Client;
|
|
204
|
+
setSpendControls(controls: SpendControls | false): x402Client;
|
|
205
|
+
createPaymentPayload(paymentRequired: PaymentRequired): Promise<PaymentPayload>;
|
|
206
|
+
}
|
|
207
|
+
/** Builds the guard and its guarded x402 client. */
|
|
208
|
+
export declare function createX402Guard(config: X402GuardConfig): X402Guard;
|
|
209
|
+
export { requestStorage };
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export { createX402Guard, GuardedX402Client, X402Guard, type AssetLimit, type X402GuardConfig, type X402GuardRecord, type X402WalletSigner, } from "./guard.js";
|
|
2
|
+
export { BASE_MAINNET, BASE_SEPOLIA, EIP3009_TYPES, PAY_ABOVE_CAP_TOOL, PAY_TOOL, PINNED_ASSETS, type PinnedAsset, type SupportedNetwork, } from "./constants.js";
|
|
3
|
+
export { type AuthorizationStateReader, type BudgetConfig, type LedgerEntry, type X402GuardStore, } from "./ledger.js";
|
|
4
|
+
export { readTerms, type CanonicalTerms, type PaymentRequestInfo } from "./terms.js";
|
|
5
|
+
export { X402GuardRefused, type RefusalCode } from "./util.js";
|
|
6
|
+
/** ABI fragment for EIP-3009 `authorizationState(address,bytes32)`. */
|
|
7
|
+
export declare const AUTHORIZATION_STATE_ABI: readonly [{
|
|
8
|
+
readonly type: "function";
|
|
9
|
+
readonly name: "authorizationState";
|
|
10
|
+
readonly stateMutability: "view";
|
|
11
|
+
readonly inputs: readonly [{
|
|
12
|
+
readonly name: "authorizer";
|
|
13
|
+
readonly type: "address";
|
|
14
|
+
}, {
|
|
15
|
+
readonly name: "nonce";
|
|
16
|
+
readonly type: "bytes32";
|
|
17
|
+
}];
|
|
18
|
+
readonly outputs: readonly [{
|
|
19
|
+
readonly name: "";
|
|
20
|
+
readonly type: "bool";
|
|
21
|
+
}];
|
|
22
|
+
}];
|
|
23
|
+
/**
|
|
24
|
+
* An `authorizationState` reader from a `readContract` function (a viem
|
|
25
|
+
* public client's, for example) per network. Used by `reconcile()`.
|
|
26
|
+
*/
|
|
27
|
+
export declare function authorizationStateReader(readContractFor: (network: string) => ((args: {
|
|
28
|
+
address: `0x${string}`;
|
|
29
|
+
abi: typeof AUTHORIZATION_STATE_ABI;
|
|
30
|
+
functionName: "authorizationState";
|
|
31
|
+
args: readonly [`0x${string}`, `0x${string}`];
|
|
32
|
+
}) => Promise<unknown>) | undefined): import("./ledger.js").AuthorizationStateReader;
|