@paystage/preflight 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/README.md CHANGED
@@ -1,3 +1,94 @@
1
- # Temporary Holding Version
1
+ # @paystage/preflight
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Evaluate an [x402](https://x402.org) `PaymentRequired` against a local spending
4
+ policy **before** your agent pays. Returns one of three verdicts: `pay`,
5
+ `review`, or `do_not_pay`.
6
+
7
+ - **Local-first** — policy lives in a JSON file on your machine. No secrets, no
8
+ network calls to decide.
9
+ - **MCP-native** — runs as a stdio MCP server so Claude Desktop, Cursor, and any
10
+ MCP client can call it as a tool.
11
+ - **Deterministic** — same input + same policy ⇒ same verdict.
12
+
13
+ ## Install
14
+
15
+ Run directly with `npx` (no global install):
16
+
17
+ ```bash
18
+ npx -y @paystage/preflight
19
+ ```
20
+
21
+ Or add it to your MCP client config.
22
+
23
+ ## Claude Desktop
24
+
25
+ Add to `claude_desktop_config.json`:
26
+
27
+ ```json
28
+ {
29
+ "mcpServers": {
30
+ "paystage-preflight": {
31
+ "command": "npx",
32
+ "args": ["-y", "@paystage/preflight"],
33
+ "env": {
34
+ "PAYSTAGE_POLICY_PATH": "/absolute/path/to/policy.json"
35
+ }
36
+ }
37
+ }
38
+ }
39
+ ```
40
+
41
+ ## Cursor
42
+
43
+ Add to `.cursor/mcp.json`:
44
+
45
+ ```json
46
+ {
47
+ "mcpServers": {
48
+ "paystage-preflight": {
49
+ "command": "npx",
50
+ "args": ["-y", "@paystage/preflight"],
51
+ "env": {
52
+ "PAYSTAGE_POLICY_PATH": "/absolute/path/to/policy.json"
53
+ }
54
+ }
55
+ }
56
+ }
57
+ ```
58
+
59
+ ## Example `policy.json`
60
+
61
+ ```json
62
+ {
63
+ "version": 1,
64
+ "name": "standard",
65
+ "allowed_networks": ["eip155:8453", "eip155:84532"],
66
+ "allowed_assets": [
67
+ "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
68
+ "0x036CbD53842c5426634e7929541eC2318f3dCF7e"
69
+ ],
70
+ "allowed_pay_to": ["0x209693Bc6afc0C5328bA36FaF03C514EF312287C"],
71
+ "max_amount_per_call": "1.00",
72
+ "max_amount_per_session": "10.00",
73
+ "require_human_above": "0.50",
74
+ "max_timeout_seconds": 300,
75
+ "default_verdict": "review"
76
+ }
77
+ ```
78
+
79
+ Amounts are human-readable USDC (e.g. `"0.50"` = 0.50 USDC). Networks use
80
+ CAIP-2 identifiers.
81
+
82
+ ## Verdicts
83
+
84
+ | Verdict | Meaning |
85
+ | ------------- | ----------------------------------------------------------------------- |
86
+ | `pay` | Within policy — safe to proceed automatically. |
87
+ | `review` | Allowed but above a human threshold — ask a human before paying. |
88
+ | `do_not_pay` | Violates policy (network, asset, payTo, amount, timeout, or malformed). |
89
+
90
+ ## Links
91
+
92
+ - Website: <https://paystage.xyz>
93
+ - Source: <https://github.com/base-buzz/paystage>
94
+ - License: MIT
package/dist/cli.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
package/dist/cli.js ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env node
2
+ import { startServer } from "./server.js";
3
+ startServer().catch((error) => {
4
+ console.error(error);
5
+ process.exit(1);
6
+ });
7
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE1C,WAAW,EAAE,CAAC,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE;IACrC,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IACrB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
@@ -0,0 +1,38 @@
1
+ import { type Policy } from "@paystage/policy";
2
+ export interface AcceptOption {
3
+ scheme: string;
4
+ network: string;
5
+ amount: string;
6
+ asset: string;
7
+ payTo: string;
8
+ maxTimeoutSeconds: number;
9
+ extra?: Record<string, unknown>;
10
+ }
11
+ export interface PaymentResource {
12
+ url: string;
13
+ description?: string;
14
+ mimeType?: string;
15
+ }
16
+ export interface PaymentRequest {
17
+ x402Version: number;
18
+ accepts: AcceptOption[];
19
+ error?: string;
20
+ resource?: PaymentResource;
21
+ }
22
+ export type Decision = "pay" | "review" | "do_not_pay";
23
+ export type AcceptStatus = "pay" | "review" | "do_not_pay";
24
+ export interface AcceptResult {
25
+ accept: AcceptOption;
26
+ status: AcceptStatus;
27
+ reasons: string[];
28
+ }
29
+ export interface EvaluateResult {
30
+ decision: Decision;
31
+ selectedAccept?: AcceptOption;
32
+ acceptResults: AcceptResult[];
33
+ reasons: string[];
34
+ summary: string;
35
+ dailyCapNote?: string;
36
+ }
37
+ export declare function evaluatePayment(pr: PaymentRequest, policy: Policy): EvaluateResult;
38
+ //# sourceMappingURL=evaluate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"evaluate.d.ts","sourceRoot":"","sources":["../src/evaluate.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,MAAM,EAEZ,MAAM,kBAAkB,CAAC;AAG1B,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,iBAAiB,EAAE,MAAM,CAAC;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACjC;AAED,MAAM,WAAW,eAAe;IAC9B,GAAG,EAAE,MAAM,CAAC;IACZ,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,cAAc;IAC7B,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,eAAe,CAAC;CAC5B;AAED,MAAM,MAAM,QAAQ,GAAG,KAAK,GAAG,QAAQ,GAAG,YAAY,CAAC;AACvD,MAAM,MAAM,YAAY,GAAG,KAAK,GAAG,QAAQ,GAAG,YAAY,CAAC;AAE3D,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,YAAY,CAAC;IACrB,MAAM,EAAE,YAAY,CAAC;IACrB,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,QAAQ,CAAC;IACnB,cAAc,CAAC,EAAE,YAAY,CAAC;IAC9B,aAAa,EAAE,YAAY,EAAE,CAAC;IAC9B,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAyHD,wBAAgB,eAAe,CAC7B,EAAE,EAAE,cAAc,EAClB,MAAM,EAAE,MAAM,GACb,cAAc,CA2EhB"}
@@ -0,0 +1,160 @@
1
+ import { humanToAtomic, atomicToHuman, } from "@paystage/policy";
2
+ import { isKnownDrainer } from "./known-drainers.js";
3
+ function eq(a, b) {
4
+ return a.toLowerCase() === b.toLowerCase();
5
+ }
6
+ function findAsset(policy, network, assetAddress) {
7
+ return policy.allowed_assets.find((a) => eq(a.network, network) && eq(a.address, assetAddress));
8
+ }
9
+ function evaluateAccept(accept, policy) {
10
+ const reasons = [];
11
+ // Rule 2: network allowlist
12
+ const networkAllowed = policy.allowed_networks.some((n) => eq(n, accept.network));
13
+ if (!networkAllowed) {
14
+ reasons.push(`network ${accept.network} not in allowed_networks`);
15
+ return { accept, status: "do_not_pay", reasons };
16
+ }
17
+ // Rule 3: asset must match allowed_assets with same network
18
+ const asset = findAsset(policy, accept.network, accept.asset);
19
+ if (!asset) {
20
+ reasons.push(`asset ${accept.asset} on ${accept.network} not in allowed_assets`);
21
+ return { accept, status: "do_not_pay", reasons };
22
+ }
23
+ // Rule 4: amount is atomic
24
+ let amount;
25
+ try {
26
+ amount = BigInt(accept.amount);
27
+ }
28
+ catch {
29
+ reasons.push(`invalid atomic amount ${accept.amount}`);
30
+ return { accept, status: "do_not_pay", reasons };
31
+ }
32
+ // Rule 5: policy-level max_amount, only if asset symbol matches
33
+ if (eq(policy.max_amount.asset, asset.symbol)) {
34
+ const maxAtomic = humanToAtomic(policy.max_amount.value, policy.max_amount.decimals);
35
+ if (amount > maxAtomic) {
36
+ reasons.push(`amount ${atomicToHuman(amount, asset.decimals)} ${asset.symbol} exceeds max_amount ${policy.max_amount.value} ${policy.max_amount.asset}`);
37
+ return { accept, status: "do_not_pay", reasons };
38
+ }
39
+ }
40
+ // Rule 6: timeout
41
+ if (accept.maxTimeoutSeconds > policy.max_timeout_seconds) {
42
+ reasons.push(`maxTimeoutSeconds ${accept.maxTimeoutSeconds} exceeds policy max_timeout_seconds ${policy.max_timeout_seconds}`);
43
+ return { accept, status: "do_not_pay", reasons };
44
+ }
45
+ // Rule 7a: known drainers — hard deny before unknown_payee review
46
+ if (isKnownDrainer(accept.payTo)) {
47
+ reasons.push("known drainer");
48
+ return { accept, status: "do_not_pay", reasons };
49
+ }
50
+ // Rule 7: payee allowlist
51
+ const payeeAllowed = policy.allowed_pay_to.some((p) => eq(p, accept.payTo));
52
+ if (!payeeAllowed) {
53
+ if (policy.unknown_payee === "deny") {
54
+ reasons.push(`payTo ${accept.payTo} not in allowed_pay_to (deny)`);
55
+ return { accept, status: "do_not_pay", reasons };
56
+ }
57
+ reasons.push(`payTo ${accept.payTo} not in allowed_pay_to (review)`);
58
+ return { accept, status: "review", reasons };
59
+ }
60
+ // Rule 8: require_human_above
61
+ if (policy.require_human_above) {
62
+ const threshold = humanToAtomic(policy.require_human_above.value, policy.require_human_above.decimals);
63
+ if (amount > threshold) {
64
+ reasons.push(`amount ${atomicToHuman(amount, asset.decimals)} ${asset.symbol} exceeds require_human_above ${policy.require_human_above.value} ${policy.require_human_above.asset}`);
65
+ return { accept, status: "review", reasons };
66
+ }
67
+ }
68
+ return { accept, status: "pay", reasons };
69
+ }
70
+ function amountOf(accept) {
71
+ try {
72
+ return BigInt(accept.amount);
73
+ }
74
+ catch {
75
+ return BigInt(0);
76
+ }
77
+ }
78
+ function cheapest(accepts) {
79
+ if (accepts.length === 0)
80
+ return undefined;
81
+ return [...accepts].sort((a, b) => {
82
+ const da = amountOf(a);
83
+ const db = amountOf(b);
84
+ if (da < db)
85
+ return -1;
86
+ if (da > db)
87
+ return 1;
88
+ return a.maxTimeoutSeconds - b.maxTimeoutSeconds;
89
+ })[0];
90
+ }
91
+ export function evaluatePayment(pr, policy) {
92
+ // Rule 1: malformed
93
+ if (!pr || pr.x402Version !== 2) {
94
+ return {
95
+ decision: "do_not_pay",
96
+ acceptResults: [],
97
+ reasons: [`unsupported x402Version ${pr?.x402Version}`],
98
+ summary: "Payment request is malformed or unsupported version.",
99
+ };
100
+ }
101
+ if (!Array.isArray(pr.accepts) || pr.accepts.length === 0) {
102
+ return {
103
+ decision: "do_not_pay",
104
+ acceptResults: [],
105
+ reasons: ["no accepts in payment request"],
106
+ summary: "Payment request has no acceptable payment options.",
107
+ };
108
+ }
109
+ const acceptResults = pr.accepts.map((a) => evaluateAccept(a, policy));
110
+ const payAccepts = acceptResults
111
+ .filter((r) => r.status === "pay")
112
+ .map((r) => r.accept);
113
+ const reviewAccepts = acceptResults
114
+ .filter((r) => r.status === "review")
115
+ .map((r) => r.accept);
116
+ let decision;
117
+ let selectedAccept;
118
+ if (payAccepts.length > 0) {
119
+ decision = "pay";
120
+ selectedAccept = cheapest(payAccepts);
121
+ }
122
+ else if (reviewAccepts.length > 0) {
123
+ decision = "review";
124
+ selectedAccept = cheapest(reviewAccepts);
125
+ }
126
+ else {
127
+ decision = "do_not_pay";
128
+ }
129
+ const reasons = [];
130
+ for (const r of acceptResults) {
131
+ for (const reason of r.reasons)
132
+ reasons.push(reason);
133
+ }
134
+ // Rule 9: daily_cap note only
135
+ let dailyCapNote;
136
+ if (policy.daily_cap) {
137
+ dailyCapNote = `daily_cap ${policy.daily_cap.value} ${policy.daily_cap.asset} (informational only)`;
138
+ }
139
+ // Rule 12: summary
140
+ let summary;
141
+ if (selectedAccept) {
142
+ const asset = findAsset(policy, selectedAccept.network, selectedAccept.asset);
143
+ const decimals = asset?.decimals ?? policy.max_amount.decimals;
144
+ const human = atomicToHuman(BigInt(selectedAccept.amount), decimals);
145
+ const symbol = asset?.symbol ?? policy.max_amount.asset;
146
+ summary = `Decision: ${decision}. Selected ${human} ${symbol} on ${selectedAccept.network} to ${selectedAccept.payTo}.`;
147
+ }
148
+ else {
149
+ summary = `Decision: ${decision}. No acceptable payment option.`;
150
+ }
151
+ return {
152
+ decision,
153
+ selectedAccept,
154
+ acceptResults,
155
+ reasons,
156
+ summary,
157
+ dailyCapNote,
158
+ };
159
+ }
160
+ //# sourceMappingURL=evaluate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"evaluate.js","sourceRoot":"","sources":["../src/evaluate.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ export { evaluatePayment } from "./evaluate.js";
2
+ export type { AcceptOption, PaymentResource, PaymentRequest, Decision, AcceptStatus, AcceptResult, EvaluateResult, } from "./evaluate.js";
3
+ export { normalizePaymentRequired } from "./normalize.js";
4
+ export { createServer, startServer } from "./server.js";
5
+ export type { PaymentRequired, PaymentAccept, EvaluationResult, EvaluationDecision, AcceptEvaluation, } from "./types.js";
6
+ export { isKnownDrainer, KNOWN_DRAINERS, SOURCES } from "./known-drainers.js";
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAChD,YAAY,EACV,YAAY,EACZ,eAAe,EACf,cAAc,EACd,QAAQ,EACR,YAAY,EACZ,YAAY,EACZ,cAAc,GACf,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,wBAAwB,EAAE,MAAM,gBAAgB,CAAC;AAC1D,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AACxD,YAAY,EACV,eAAe,EACf,aAAa,EACb,gBAAgB,EAChB,kBAAkB,EAClB,gBAAgB,GACjB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,5 @@
1
+ export { evaluatePayment } from "./evaluate.js";
2
+ export { normalizePaymentRequired } from "./normalize.js";
3
+ export { createServer, startServer } from "./server.js";
4
+ export { isKnownDrainer, KNOWN_DRAINERS, SOURCES } from "./known-drainers.js";
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAUhD,OAAO,EAAE,wBAAwB,EAAE,MAAM,gBAAgB,CAAC;AAC1D,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAQxD,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC"}
@@ -0,0 +1,12 @@
1
+ export interface KnownDrainerSource {
2
+ name: string;
3
+ url: string;
4
+ retrieved: string;
5
+ }
6
+ export declare const SOURCES: readonly KnownDrainerSource[];
7
+ /**
8
+ * Small curated subset. Do not add addresses without a public citation.
9
+ */
10
+ export declare const KNOWN_DRAINERS: readonly string[];
11
+ export declare function isKnownDrainer(addr: string): boolean;
12
+ //# sourceMappingURL=known-drainers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"known-drainers.d.ts","sourceRoot":"","sources":["../src/known-drainers.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,eAAO,MAAM,OAAO,EAAE,SAAS,kBAAkB,EAoChD,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,cAAc,EAAE,SAAS,MAAM,EA0C1C,CAAC;AAMH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAGpD"}
@@ -0,0 +1,91 @@
1
+ // Curated hard-deny payees. Matching is checksum-agnostic (lowercase).
2
+ // See KNOWN_DRAINERS.md for curation policy and how to extend.
3
+ export const SOURCES = [
4
+ {
5
+ name: "MyEtherWallet ethereum-lists address darklist",
6
+ url: "https://github.com/MyEtherWallet/ethereum-lists/blob/master/src/addresses/addresses-darklist.json",
7
+ retrieved: "2026-11-02",
8
+ },
9
+ {
10
+ name: "MetaMask eth-phishing-detect (domain list; related ecosystem)",
11
+ url: "https://github.com/MetaMask/eth-phishing-detect",
12
+ retrieved: "2026-11-02",
13
+ },
14
+ {
15
+ name: "Ethereum burn / non-receivable sentinel 0x…dEaD",
16
+ url: "https://etherscan.io/address/0x000000000000000000000000000000000000dead",
17
+ retrieved: "2026-11-02",
18
+ },
19
+ {
20
+ name: "0xCERT IOC address feed (Base incidents incl. 0xCERT-2026-0230)",
21
+ url: "https://www.0xcert.com/iocs",
22
+ retrieved: "2026-11-02",
23
+ },
24
+ {
25
+ name: "Matcha Meta / CryptoTimes SwapNet approval-drain advisory",
26
+ url: "https://www.cryptotimes.io/2026/01/26/security-alert-matcha-meta-flags-swapnet-bug-as-over-16-8m-is-drained/",
27
+ retrieved: "2026-11-02",
28
+ },
29
+ {
30
+ name: "Chainabuse contract-exploit reports (Base EIP-7702 / USDC drains)",
31
+ url: "https://chainabuse.com/category/contract-exploit",
32
+ retrieved: "2026-11-02",
33
+ },
34
+ {
35
+ name: "Chainabuse address report (Base USDC theft Jul 2026)",
36
+ url: "https://chainabuse.com/address/0x2cff890f0378a11913b6129b2e97417a2c302680",
37
+ retrieved: "2026-11-02",
38
+ },
39
+ ];
40
+ /**
41
+ * Small curated subset. Do not add addresses without a public citation.
42
+ */
43
+ export const KNOWN_DRAINERS = Object.freeze([
44
+ // Canonical burn address — non-receivable sink (demo + sentinel).
45
+ "0x000000000000000000000000000000000000dead",
46
+ // MEW darklist: "XRP phishing website (ripple.com.pt) this wallet collects funds…"
47
+ "0x09750ad360fdb7a2ee23669c4503c974d86d8694",
48
+ // MEW darklist: XRP phishing website (ripple.com.pt) via Coinswitch
49
+ "0xc915ec7f4cfd1c0a8aba090f03bfaab588aef9b4",
50
+ "0xecb6ffac05d8b4660b99b475b359fe454c77d153",
51
+ "0x7f85a82a2da50540412f6e526f1d00a0690a77b8",
52
+ // MEW darklist: "Scam address for various fake twitter accounts and fake giveaways"
53
+ "0x08389b19ad52f0d983609ab785b3a43a0e90355f",
54
+ // MEW darklist: Fake MEW / myelherwallel.com wallet
55
+ "0xd0cc2b24980cbcca47ef755da88b220a82291407",
56
+ // MEW darklist: Fake MyEtherWallet
57
+ "0xdafc2a2d4e070f863cfcb5887bc2516e05e18877",
58
+ // MEW darklist: Fake giveaway site asking to pay miner fees
59
+ "0x8444173280e5fdc0525494638123b014cffcc521",
60
+ // MEW darklist: Trust-trading scam
61
+ "0x5d82db63cf0c54d47006d416bdc7dab09ea2f3f1",
62
+ // --- Added 2026-11-02 NZDT ---
63
+ // 0xCERT-2026-0230 Base attacker EOA (GoPlus Security; PeckShield); Basescan phishing label
64
+ // https://www.0xcert.com/iocs
65
+ "0x0b5126e1bc27c0de77e02e97945760a674edb034",
66
+ // 0xCERT-2026-0230 Base exploit contract (GoPlus)
67
+ // https://www.0xcert.com/iocs
68
+ "0xcdfe91301356da873562ef513828a60dba1f569d",
69
+ // SwapNet router — Matcha Meta approval-drain advisory (arbitrary call); CryptoTimes 2026-01-26; Basescan Exploit
70
+ // https://www.cryptotimes.io/2026/01/26/security-alert-matcha-meta-flags-swapnet-bug-as-over-16-8m-is-drained/
71
+ "0x616000e384ef1c2b52f5f3a88d57a3b64f23757e",
72
+ // Chainabuse Base EIP-7702 drain 2026-10-08; Basescan phishing
73
+ // https://chainabuse.com/category/contract-exploit
74
+ "0xc9002a87e2e3efdcc26cfb0bc9034b919d38fb03",
75
+ // Chainabuse Base drain forwarder 2026-10-08
76
+ // https://chainabuse.com/category/contract-exploit
77
+ "0x101a548bcda171b5f00179aaca0ac32074a2b86a",
78
+ // Chainabuse Base USDC theft Jul 2026
79
+ // https://chainabuse.com/address/0x2cff890f0378a11913b6129b2e97417a2c302680
80
+ "0x678245175aae05ba3301a7812a38f1a2592a97a8",
81
+ // Chainabuse reported drain receiver (Bitget Swap2 incident report)
82
+ // https://chainabuse.com/category/contract-exploit
83
+ "0x9cc58d06f299f2a37050403eb9c90482f38ee3ad",
84
+ ]);
85
+ const SET = new Set(KNOWN_DRAINERS.map((a) => a.toLowerCase()));
86
+ export function isKnownDrainer(addr) {
87
+ if (typeof addr !== "string" || addr.length === 0)
88
+ return false;
89
+ return SET.has(addr.toLowerCase());
90
+ }
91
+ //# sourceMappingURL=known-drainers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"known-drainers.js","sourceRoot":"","sources":["../src/known-drainers.ts"],"names":[],"mappings":"AAAA,uEAAuE;AACvE,+DAA+D;AAQ/D,MAAM,CAAC,MAAM,OAAO,GAAkC;IACpD;QACE,IAAI,EAAE,+CAA+C;QACrD,GAAG,EAAE,mGAAmG;QACxG,SAAS,EAAE,YAAY;KACxB;IACD;QACE,IAAI,EAAE,+DAA+D;QACrE,GAAG,EAAE,iDAAiD;QACtD,SAAS,EAAE,YAAY;KACxB;IACD;QACE,IAAI,EAAE,iDAAiD;QACvD,GAAG,EAAE,yEAAyE;QAC9E,SAAS,EAAE,YAAY;KACxB;IACD;QACE,IAAI,EAAE,iEAAiE;QACvE,GAAG,EAAE,6BAA6B;QAClC,SAAS,EAAE,YAAY;KACxB;IACD;QACE,IAAI,EAAE,2DAA2D;QACjE,GAAG,EAAE,8GAA8G;QACnH,SAAS,EAAE,YAAY;KACxB;IACD;QACE,IAAI,EAAE,mEAAmE;QACzE,GAAG,EAAE,kDAAkD;QACvD,SAAS,EAAE,YAAY;KACxB;IACD;QACE,IAAI,EAAE,sDAAsD;QAC5D,GAAG,EAAE,2EAA2E;QAChF,SAAS,EAAE,YAAY;KACxB;CACF,CAAC;AAEF;;GAEG;AACH,MAAM,CAAC,MAAM,cAAc,GAAsB,MAAM,CAAC,MAAM,CAAC;IAC7D,kEAAkE;IAClE,4CAA4C;IAC5C,mFAAmF;IACnF,4CAA4C;IAC5C,oEAAoE;IACpE,4CAA4C;IAC5C,4CAA4C;IAC5C,4CAA4C;IAC5C,oFAAoF;IACpF,4CAA4C;IAC5C,oDAAoD;IACpD,4CAA4C;IAC5C,mCAAmC;IACnC,4CAA4C;IAC5C,4DAA4D;IAC5D,4CAA4C;IAC5C,mCAAmC;IACnC,4CAA4C;IAE5C,gCAAgC;IAChC,4FAA4F;IAC5F,8BAA8B;IAC9B,4CAA4C;IAC5C,kDAAkD;IAClD,8BAA8B;IAC9B,4CAA4C;IAC5C,kHAAkH;IAClH,+GAA+G;IAC/G,4CAA4C;IAC5C,+DAA+D;IAC/D,mDAAmD;IACnD,4CAA4C;IAC5C,6CAA6C;IAC7C,mDAAmD;IACnD,4CAA4C;IAC5C,sCAAsC;IACtC,4EAA4E;IAC5E,4CAA4C;IAC5C,oEAAoE;IACpE,mDAAmD;IACnD,4CAA4C;CAC7C,CAAC,CAAC;AAEH,MAAM,GAAG,GAAwB,IAAI,GAAG,CACtC,cAAc,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAC3C,CAAC;AAEF,MAAM,UAAU,cAAc,CAAC,IAAY;IACzC,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IAChE,OAAO,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC;AACrC,CAAC"}
@@ -0,0 +1,4 @@
1
+ export type { PaymentRequest, AcceptOption, PaymentResource, } from "./evaluate.js";
2
+ import type { PaymentRequest } from "./evaluate.js";
3
+ export declare function normalizePaymentRequired(input: unknown): PaymentRequest;
4
+ //# sourceMappingURL=normalize.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"normalize.d.ts","sourceRoot":"","sources":["../src/normalize.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,cAAc,EACd,YAAY,EACZ,eAAe,GAChB,MAAM,eAAe,CAAC;AACvB,OAAO,KAAK,EACV,cAAc,EAGf,MAAM,eAAe,CAAC;AA6EvB,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,OAAO,GAAG,cAAc,CAsBvE"}
@@ -0,0 +1,88 @@
1
+ function isRecord(value) {
2
+ return typeof value === "object" && value !== null;
3
+ }
4
+ function parseJsonText(text) {
5
+ try {
6
+ return JSON.parse(text);
7
+ }
8
+ catch {
9
+ throw new Error("content text is not valid JSON");
10
+ }
11
+ }
12
+ function coerceResource(raw) {
13
+ if (!isRecord(raw))
14
+ return undefined;
15
+ if (typeof raw.url !== "string")
16
+ return undefined;
17
+ return {
18
+ url: raw.url,
19
+ description: typeof raw.description === "string" ? raw.description : undefined,
20
+ mimeType: typeof raw.mimeType === "string" ? raw.mimeType : undefined,
21
+ };
22
+ }
23
+ function coerceAccept(raw) {
24
+ const amount = typeof raw.amount === "string"
25
+ ? raw.amount
26
+ : typeof raw.maxAmountRequired === "string"
27
+ ? raw.maxAmountRequired
28
+ : undefined;
29
+ if (typeof raw.scheme !== "string" ||
30
+ typeof raw.network !== "string" ||
31
+ typeof amount !== "string" ||
32
+ typeof raw.asset !== "string" ||
33
+ typeof raw.payTo !== "string" ||
34
+ typeof raw.maxTimeoutSeconds !== "number") {
35
+ throw new Error("malformed accept option");
36
+ }
37
+ return {
38
+ scheme: raw.scheme,
39
+ network: raw.network,
40
+ amount,
41
+ asset: raw.asset,
42
+ payTo: raw.payTo,
43
+ maxTimeoutSeconds: raw.maxTimeoutSeconds,
44
+ extra: isRecord(raw.extra) ? raw.extra : undefined,
45
+ };
46
+ }
47
+ function coercePaymentRequired(candidate) {
48
+ if (!isRecord(candidate)) {
49
+ throw new Error("PaymentRequired must be an object");
50
+ }
51
+ if (candidate.x402Version !== 2) {
52
+ throw new Error(`x402Version must be 2, got ${String(candidate.x402Version)}`);
53
+ }
54
+ if (!Array.isArray(candidate.accepts)) {
55
+ throw new Error("accepts must be an array");
56
+ }
57
+ const accepts = candidate.accepts.map((item, i) => {
58
+ if (!isRecord(item))
59
+ throw new Error(`accepts[${i}] must be an object`);
60
+ return coerceAccept(item);
61
+ });
62
+ return {
63
+ x402Version: 2,
64
+ accepts,
65
+ error: typeof candidate.error === "string" ? candidate.error : undefined,
66
+ resource: coerceResource(candidate.resource),
67
+ };
68
+ }
69
+ export function normalizePaymentRequired(input) {
70
+ if (!isRecord(input)) {
71
+ throw new Error("payment required input must be an object");
72
+ }
73
+ if ("x402Version" in input && "accepts" in input) {
74
+ return coercePaymentRequired(input);
75
+ }
76
+ if ("structuredContent" in input) {
77
+ return coercePaymentRequired(input.structuredContent);
78
+ }
79
+ if (Array.isArray(input.content)) {
80
+ const textItem = input.content.find((c) => isRecord(c) && c.type === "text" && typeof c.text === "string");
81
+ if (!textItem || !isRecord(textItem) || typeof textItem.text !== "string") {
82
+ throw new Error("MCP tool result content array did not include a text JSON payload");
83
+ }
84
+ return coercePaymentRequired(parseJsonText(textItem.text));
85
+ }
86
+ throw new Error("Unrecognized PaymentRequired input shape");
87
+ }
88
+ //# sourceMappingURL=normalize.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"normalize.js","sourceRoot":"","sources":["../src/normalize.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function createServer(): McpServer;
3
+ export declare function startServer(): Promise<void>;
4
+ //# sourceMappingURL=server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AASpE,wBAAgB,YAAY,IAAI,SAAS,CA2CxC;AAcD,wBAAsB,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC,CAIjD"}
package/dist/server.js ADDED
@@ -0,0 +1,50 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3
+ import { z } from "zod";
4
+ import { loadPolicy, parsePolicy } from "@paystage/policy";
5
+ import { normalizePaymentRequired } from "./normalize.js";
6
+ import { evaluatePayment } from "./evaluate.js";
7
+ export function createServer() {
8
+ const server = new McpServer({
9
+ name: "paystage-preflight",
10
+ version: "0.1.0",
11
+ });
12
+ server.registerTool("evaluate_mcp_payment", {
13
+ description: "Evaluate an x402 PaymentRequired payload against a PayStage spending policy before paying",
14
+ inputSchema: {
15
+ payment_required: z.unknown(),
16
+ policy_path: z.string().optional(),
17
+ policy: z.unknown().optional(),
18
+ },
19
+ }, async (input) => {
20
+ try {
21
+ const policy = await resolvePolicy(input);
22
+ const pr = normalizePaymentRequired(input.payment_required);
23
+ const result = evaluatePayment(pr, policy);
24
+ return {
25
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
26
+ };
27
+ }
28
+ catch (error) {
29
+ const message = error instanceof Error ? error.message : String(error);
30
+ return {
31
+ isError: true,
32
+ content: [{ type: "text", text: `Error: ${message}` }],
33
+ };
34
+ }
35
+ });
36
+ return server;
37
+ }
38
+ async function resolvePolicy(input) {
39
+ if (input.policy !== undefined) {
40
+ return parsePolicy(input.policy);
41
+ }
42
+ const path = input.policy_path ?? process.env.PAYSTAGE_POLICY_PATH ?? "./policy.json";
43
+ return await loadPolicy(path);
44
+ }
45
+ export async function startServer() {
46
+ const server = createServer();
47
+ const transport = new StdioServerTransport();
48
+ await server.connect(transport);
49
+ }
50
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAE3D,OAAO,EAAE,wBAAwB,EAAE,MAAM,gBAAgB,CAAC;AAC1D,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAGhD,MAAM,UAAU,YAAY;IAC1B,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC;QAC3B,IAAI,EAAE,oBAAoB;QAC1B,OAAO,EAAE,OAAO;KACjB,CAAC,CAAC;IAEH,MAAM,CAAC,YAAY,CACjB,sBAAsB,EACtB;QACE,WAAW,EACT,2FAA2F;QAC7F,WAAW,EAAE;YACX,gBAAgB,EAAE,CAAC,CAAC,OAAO,EAAE;YAC7B,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;YAClC,MAAM,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE;SAC/B;KACF,EACD,KAAK,EAAE,KAIN,EAGE,EAAE;QACH,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,aAAa,CAAC,KAAK,CAAC,CAAC;YAC1C,MAAM,EAAE,GAAG,wBAAwB,CAAC,KAAK,CAAC,gBAAgB,CAAC,CAAC;YAC5D,MAAM,MAAM,GAAmB,eAAe,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC;YAC3D,OAAO;gBACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC;aACnE,CAAC;QACJ,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YACvE,OAAO;gBACL,OAAO,EAAE,IAAI;gBACb,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,OAAO,EAAE,EAAE,CAAC;aACvD,CAAC;QACJ,CAAC;IACH,CAAC,CACF,CAAC;IAEF,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,KAAK,UAAU,aAAa,CAAC,KAG5B;IACC,IAAI,KAAK,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;QAC/B,OAAO,WAAW,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IACnC,CAAC;IACD,MAAM,IAAI,GACR,KAAK,CAAC,WAAW,IAAI,OAAO,CAAC,GAAG,CAAC,oBAAoB,IAAI,eAAe,CAAC;IAC3E,OAAO,MAAM,UAAU,CAAC,IAAI,CAAC,CAAC;AAChC,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,WAAW;IAC/B,MAAM,MAAM,GAAG,YAAY,EAAE,CAAC;IAC9B,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;AAClC,CAAC"}
@@ -0,0 +1,8 @@
1
+ export type { AcceptOption, PaymentResource, PaymentRequest, Decision, AcceptStatus, AcceptResult, EvaluateResult, } from "./evaluate.js";
2
+ /** @deprecated alias */
3
+ export type PaymentRequired = import("./evaluate.js").PaymentRequest;
4
+ export type PaymentAccept = import("./evaluate.js").AcceptOption;
5
+ export type EvaluationResult = import("./evaluate.js").EvaluateResult;
6
+ export type EvaluationDecision = import("./evaluate.js").Decision;
7
+ export type AcceptEvaluation = import("./evaluate.js").AcceptResult;
8
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,YAAY,EACZ,eAAe,EACf,cAAc,EACd,QAAQ,EACR,YAAY,EACZ,YAAY,EACZ,cAAc,GACf,MAAM,eAAe,CAAC;AAEvB,wBAAwB;AACxB,MAAM,MAAM,eAAe,GAAG,OAAO,eAAe,EAAE,cAAc,CAAC;AACrE,MAAM,MAAM,aAAa,GAAG,OAAO,eAAe,EAAE,YAAY,CAAC;AACjE,MAAM,MAAM,gBAAgB,GAAG,OAAO,eAAe,EAAE,cAAc,CAAC;AACtE,MAAM,MAAM,kBAAkB,GAAG,OAAO,eAAe,EAAE,QAAQ,CAAC;AAClE,MAAM,MAAM,gBAAgB,GAAG,OAAO,eAAe,EAAE,YAAY,CAAC"}
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
package/package.json CHANGED
@@ -1,6 +1,63 @@
1
1
  {
2
2
  "name": "@paystage/preflight",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.1",
4
+ "description": "Evaluate x402 PaymentRequired against a local spending policy before an agent pays",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "main": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js"
13
+ }
14
+ },
15
+ "bin": {
16
+ "paystage-preflight": "./dist/cli.js"
17
+ },
18
+ "files": [
19
+ "dist",
20
+ "server.json"
21
+ ],
22
+ "mcpName": "xyz.paystage/preflight",
23
+ "engines": {
24
+ "node": ">=20"
25
+ },
26
+ "keywords": [
27
+ "mcp",
28
+ "x402",
29
+ "agent-payments",
30
+ "usdc",
31
+ "base",
32
+ "preflight",
33
+ "policy"
34
+ ],
35
+ "homepage": "https://paystage.xyz",
36
+ "repository": {
37
+ "type": "git",
38
+ "url": "git+https://github.com/base-buzz/paystage.git",
39
+ "directory": "packages/preflight"
40
+ },
41
+ "bugs": {
42
+ "url": "https://github.com/base-buzz/paystage/issues"
43
+ },
44
+ "publishConfig": {
45
+ "access": "public"
46
+ },
47
+ "scripts": {
48
+ "build": "tsc",
49
+ "test": "vitest run",
50
+ "typecheck": "tsc --noEmit",
51
+ "prepublishOnly": "npm run typecheck && npm run build"
52
+ },
53
+ "dependencies": {
54
+ "@modelcontextprotocol/sdk": "^1.12.0",
55
+ "@paystage/policy": "0.1.0",
56
+ "zod": "^3.23.8"
57
+ },
58
+ "devDependencies": {
59
+ "@types/node": "^20.14.0",
60
+ "typescript": "^5.5.4",
61
+ "vitest": "^2.0.5"
62
+ }
63
+ }
package/server.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
3
+ "name": "xyz.paystage/preflight",
4
+ "title": "PayStage Preflight",
5
+ "description": "Evaluate x402 PaymentRequired against a local spending policy before an agent pays",
6
+ "version": "0.1.0",
7
+ "websiteUrl": "https://paystage.xyz",
8
+ "repository": {
9
+ "url": "https://github.com/base-buzz/paystage",
10
+ "source": "github",
11
+ "subfolder": "packages/preflight"
12
+ },
13
+ "packages": [
14
+ {
15
+ "registryType": "npm",
16
+ "registryBaseUrl": "https://registry.npmjs.org",
17
+ "identifier": "@paystage/preflight",
18
+ "version": "0.1.0",
19
+ "transport": {
20
+ "type": "stdio"
21
+ },
22
+ "environmentVariables": [
23
+ {
24
+ "name": "PAYSTAGE_POLICY_PATH",
25
+ "description": "Path to policy.json spending rules",
26
+ "isRequired": false
27
+ }
28
+ ]
29
+ }
30
+ ]
31
+ }