twzrd-x402-gate 0.8.8 → 0.8.10
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/QUICKSTART.md +61 -0
- package/README.md +5 -5
- package/bin/twzrd-gate-doctor.js +34 -0
- package/bin/twzrd-gate-eval-refuse.js +4 -3
- package/dist/doctor.d.ts +48 -0
- package/dist/doctor.d.ts.map +1 -0
- package/dist/doctor.js +246 -0
- package/dist/doctor.js.map +1 -0
- package/package.json +10 -7
package/QUICKSTART.md
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# twzrd-x402-gate — Quickstart (15 minutes, 0 USDC)
|
|
2
|
+
|
|
3
|
+
Refuses payment to a wash-flagged merchant **before your wallet signs**. Free intel, no
|
|
4
|
+
API key, no signup, no config — every `TWZRD_*` env var is an optional override. If intel
|
|
5
|
+
is unreachable the gate never *invents* a wash flag (the wash check fails open); a failed
|
|
6
|
+
preflight blocks the payment by default (`TWZRD_FAIL_OPEN=true` to allow).
|
|
7
|
+
|
|
8
|
+
## 1. Install + gate your client (5 lines)
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
npm install twzrd-x402-gate@0.8.10 @x402/core @x402/fetch @x402/svm
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
```typescript
|
|
15
|
+
import { x402Client } from "@x402/core/client";
|
|
16
|
+
import { installTwzrdAutoGate } from "twzrd-x402-gate";
|
|
17
|
+
|
|
18
|
+
const client = new x402Client();
|
|
19
|
+
// refuseWashFlagged defaults true; gateOnCanSpend stays false unless you opt in
|
|
20
|
+
installTwzrdAutoGate(client, { refuseWashFlagged: true });
|
|
21
|
+
// then register schemes + wrapFetchWithPayment as usual
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## 2. Prove it blocked something (spends nothing)
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
npm run autogate-block-proof
|
|
28
|
+
# preflights a live wash_flagged seller; the gate aborts before any signer runs
|
|
29
|
+
# writes block-proof-<run_id>.json — reason: TWZRD_TRUST_GATE_BLOCK: wash_flagged
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
That JSON is your receipt: a bad merchant intercepted with 0 USDC at risk.
|
|
33
|
+
|
|
34
|
+
## 3. See your own seat
|
|
35
|
+
|
|
36
|
+
Every preflight is stamped `X-Twzrd-Caller: twzrd-x402-gate/<version>` — your seat
|
|
37
|
+
identity in TWZRD's metrics. To make runs attributable to *you* (recommended):
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
40
|
+
installTwzrdAutoGate(client, {
|
|
41
|
+
refuseWashFlagged: true,
|
|
42
|
+
attribution: { integration: "<your-host-or-project>", runId: "<uuid-per-run>" },
|
|
43
|
+
});
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## 4. The only three knobs you need
|
|
47
|
+
|
|
48
|
+
| Knob | Effect |
|
|
49
|
+
|------|--------|
|
|
50
|
+
| `TWZRD_AUTO_GATE=0` | Kill switch — gate fully off, payments proceed unguarded |
|
|
51
|
+
| `refuseWashFlagged: false` | Stop refusing wash-flagged merchants (default `true`) |
|
|
52
|
+
| `gateOnCanSpend: true` | Opt-in hard cap: also block when `can_spend=false` (default `false`) |
|
|
53
|
+
|
|
54
|
+
Everything else (thresholds, wash caps, fail posture, settle guard): [README](./README.md).
|
|
55
|
+
|
|
56
|
+
## What this will NOT do
|
|
57
|
+
|
|
58
|
+
- **No Base/EVM reputation.** Non-Solana networks get an explicit `unknown` (payment
|
|
59
|
+
allowed, observed) unless you set `TWZRD_UNSUPPORTED_NETWORK_MODE=strict`.
|
|
60
|
+
- **No delivery guarantee.** It screens the merchant before you pay, nothing after.
|
|
61
|
+
- **Not a wallet.** It never holds keys or signs — it only decides if your signer runs.
|
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@ refuse. Protects the **payer** from a risky **merchant** (`payTo`). Chain-neutra
|
|
|
10
10
|
### Default-on AutoGate (5 lines)
|
|
11
11
|
|
|
12
12
|
```bash
|
|
13
|
-
npm install twzrd-x402-gate@0.8.
|
|
13
|
+
npm install twzrd-x402-gate@0.8.10 @x402/core @x402/fetch @x402/svm
|
|
14
14
|
```
|
|
15
15
|
|
|
16
16
|
```typescript
|
|
@@ -49,7 +49,7 @@ transfer on-chain. Wash/sybil edges are primarily discounted in TWZRD scoring, n
|
|
|
49
49
|
revenue refusal.
|
|
50
50
|
|
|
51
51
|
```bash
|
|
52
|
-
npm install twzrd-x402-gate@0.8.
|
|
52
|
+
npm install twzrd-x402-gate@0.8.10
|
|
53
53
|
```
|
|
54
54
|
|
|
55
55
|
```typescript
|
|
@@ -93,7 +93,7 @@ Fixture-backed SVM extract tests live in `test/seller-hook.test.ts` +
|
|
|
93
93
|
Install the published gate and run against wash fixtures:
|
|
94
94
|
|
|
95
95
|
```bash
|
|
96
|
-
npm install twzrd-x402-gate@0.8.
|
|
96
|
+
npm install twzrd-x402-gate@0.8.10
|
|
97
97
|
# from package root after install, or from a checkout:
|
|
98
98
|
npm run wash-dogfood
|
|
99
99
|
```
|
|
@@ -238,10 +238,10 @@ Dogfood (one public live proof path):
|
|
|
238
238
|
## Install
|
|
239
239
|
|
|
240
240
|
```bash
|
|
241
|
-
npm install twzrd-x402-gate@0.8.
|
|
241
|
+
npm install twzrd-x402-gate@0.8.10
|
|
242
242
|
```
|
|
243
243
|
|
|
244
|
-
Current npm truth: `0.8.
|
|
244
|
+
Current npm truth: `0.8.10` (buyer gate + optional seller settle guard + payer-extract hardening).
|
|
245
245
|
Check `npm view twzrd-x402-gate version` if in doubt (do not pin stale **0.5.4** / **0.7.1**).
|
|
246
246
|
|
|
247
247
|
Optional settle guard (resource-server **payer** policy): see **Seller settle guard
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Publishable bin for twzrd-gate-doctor.
|
|
4
|
+
* Prefers compiled dist; falls back to tsx source in dev checkouts.
|
|
5
|
+
* Mirrors bin/twzrd-safe-fetch.js so packaging behaves identically.
|
|
6
|
+
*/
|
|
7
|
+
import { spawnSync } from "node:child_process";
|
|
8
|
+
import { existsSync } from "node:fs";
|
|
9
|
+
import { dirname, join } from "node:path";
|
|
10
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
11
|
+
|
|
12
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
13
|
+
const root = join(__dirname, "..");
|
|
14
|
+
const dist = join(root, "dist", "doctor.js");
|
|
15
|
+
const src = join(root, "src", "doctor.ts");
|
|
16
|
+
|
|
17
|
+
if (existsSync(dist)) {
|
|
18
|
+
await import(pathToFileURL(dist).href);
|
|
19
|
+
} else if (existsSync(src)) {
|
|
20
|
+
// Do NOT set cwd to the package root here (unlike twzrd-safe-fetch): the
|
|
21
|
+
// doctor inspects process.cwd() to find the OPERATOR's package.json. Running
|
|
22
|
+
// it from the package root made it read our own manifest and report a
|
|
23
|
+
// signing surface for every project, including ones with nothing to gate.
|
|
24
|
+
// tsx resolves `src` by absolute path, so the user's cwd is safe to keep.
|
|
25
|
+
const r = spawnSync("npx", ["--yes", "tsx", src, ...process.argv.slice(2)], {
|
|
26
|
+
stdio: "inherit",
|
|
27
|
+
});
|
|
28
|
+
process.exit(r.status ?? 1);
|
|
29
|
+
} else {
|
|
30
|
+
console.error(
|
|
31
|
+
"twzrd-gate-doctor: missing dist/doctor.js — run npm run build in twzrd-x402-gate",
|
|
32
|
+
);
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
@@ -16,8 +16,9 @@ import { dirname, join } from "node:path";
|
|
|
16
16
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
17
17
|
import { existsSync } from "node:fs";
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
const
|
|
19
|
+
// Owned TWZRD refuse dogfood (not a third-party brand). Live 402 + force hard-block.
|
|
20
|
+
const TARGET_URL = "https://intel.twzrd.xyz/v1/intel/refuse-fixture";
|
|
21
|
+
const EXPECT_PAYTO = "CnTmHDXVEafkc8sFSzNky9w5zwk63Bk2mHZZodorjhvR";
|
|
21
22
|
const NETWORK = "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp";
|
|
22
23
|
const USDC = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";
|
|
23
24
|
const INTEL_PREFLIGHT = "https://intel.twzrd.xyz/v1/intel/preflight";
|
|
@@ -79,7 +80,7 @@ async function main() {
|
|
|
79
80
|
if (!resolveImport(pkg)) {
|
|
80
81
|
console.error(
|
|
81
82
|
`twzrd-gate-eval-refuse: missing peer ${pkg}. Run:\n` +
|
|
82
|
-
` npm install twzrd-x402-gate@0.8.
|
|
83
|
+
` npm install twzrd-x402-gate@0.8.10 @x402/core @x402/fetch @x402/svm @solana/kit @scure/base`,
|
|
83
84
|
);
|
|
84
85
|
process.exit(2);
|
|
85
86
|
}
|
package/dist/doctor.d.ts
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `npx twzrd-gate-doctor` — one command that turns "should I gate this?" into
|
|
3
|
+
* "here are the exact lines for THIS project, and here is the gate refusing."
|
|
4
|
+
*
|
|
5
|
+
* Why a doctor and not a codemod: patching someone else's source is invasive,
|
|
6
|
+
* hard to review, and the first thing an operator reverts. The adoption blocker
|
|
7
|
+
* is not typing three lines — it is not knowing WHICH three lines apply to the
|
|
8
|
+
* client you already use, and not believing the gate actually refuses anything.
|
|
9
|
+
* So this does exactly two things:
|
|
10
|
+
*
|
|
11
|
+
* 1. DETECT the x402 / agent surface already in package.json and print the
|
|
12
|
+
* snippet for that specific host (not a generic README excerpt).
|
|
13
|
+
* 2. PROVE a refusal, offline, spending nothing: a wash-flagged verdict is fed
|
|
14
|
+
* to the real hook and we assert the signer was never invoked.
|
|
15
|
+
*
|
|
16
|
+
* Exit codes: 0 = a seam was found and the refusal proof passed. 1 = proof
|
|
17
|
+
* failed (a real problem — report it). 2 = no x402 surface detected (nothing to
|
|
18
|
+
* gate here yet, not an error).
|
|
19
|
+
*
|
|
20
|
+
* No network by default: the refusal proof is deterministic and local, so it
|
|
21
|
+
* works in CI and on a plane. Pass --live to additionally preflight a real
|
|
22
|
+
* wash-flagged seller against intel.
|
|
23
|
+
*/
|
|
24
|
+
type Host = {
|
|
25
|
+
id: string;
|
|
26
|
+
label: string;
|
|
27
|
+
/** Any of these dependency names present ⇒ this host applies. */
|
|
28
|
+
deps: string[];
|
|
29
|
+
install: string;
|
|
30
|
+
snippet: string;
|
|
31
|
+
note?: string;
|
|
32
|
+
};
|
|
33
|
+
export declare function detectHosts(pkg: Record<string, unknown> | null): Host[];
|
|
34
|
+
/**
|
|
35
|
+
* Deterministic, offline refusal proof.
|
|
36
|
+
*
|
|
37
|
+
* Feeds a wash-flagged card straight to the policy layer and asserts the signer
|
|
38
|
+
* never ran. This is the claim an operator needs to believe before adopting,
|
|
39
|
+
* and it must be checkable without spending or reaching the network.
|
|
40
|
+
*/
|
|
41
|
+
export declare function proveRefusal(): Promise<{
|
|
42
|
+
refused: boolean;
|
|
43
|
+
signerInvocations: number;
|
|
44
|
+
reason: string;
|
|
45
|
+
}>;
|
|
46
|
+
export declare function main(argv?: string[]): Promise<number>;
|
|
47
|
+
export {};
|
|
48
|
+
//# sourceMappingURL=doctor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../src/doctor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAOH,KAAK,IAAI,GAAG;IACV,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,iEAAiE;IACjE,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,CAAC;AA4EF,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,GAAG,IAAI,EAAE,CASvE;AAED;;;;;;GAMG;AACH,wBAAsB,YAAY,IAAI,OAAO,CAAC;IAC5C,OAAO,EAAE,OAAO,CAAC;IACjB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC,CAuED;AAMD,wBAAsB,IAAI,CAAC,IAAI,GAAE,MAAM,EAA0B,GAAG,OAAO,CAAC,MAAM,CAAC,CAwDlF"}
|
package/dist/doctor.js
ADDED
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `npx twzrd-gate-doctor` — one command that turns "should I gate this?" into
|
|
3
|
+
* "here are the exact lines for THIS project, and here is the gate refusing."
|
|
4
|
+
*
|
|
5
|
+
* Why a doctor and not a codemod: patching someone else's source is invasive,
|
|
6
|
+
* hard to review, and the first thing an operator reverts. The adoption blocker
|
|
7
|
+
* is not typing three lines — it is not knowing WHICH three lines apply to the
|
|
8
|
+
* client you already use, and not believing the gate actually refuses anything.
|
|
9
|
+
* So this does exactly two things:
|
|
10
|
+
*
|
|
11
|
+
* 1. DETECT the x402 / agent surface already in package.json and print the
|
|
12
|
+
* snippet for that specific host (not a generic README excerpt).
|
|
13
|
+
* 2. PROVE a refusal, offline, spending nothing: a wash-flagged verdict is fed
|
|
14
|
+
* to the real hook and we assert the signer was never invoked.
|
|
15
|
+
*
|
|
16
|
+
* Exit codes: 0 = a seam was found and the refusal proof passed. 1 = proof
|
|
17
|
+
* failed (a real problem — report it). 2 = no x402 surface detected (nothing to
|
|
18
|
+
* gate here yet, not an error).
|
|
19
|
+
*
|
|
20
|
+
* No network by default: the refusal proof is deterministic and local, so it
|
|
21
|
+
* works in CI and on a plane. Pass --live to additionally preflight a real
|
|
22
|
+
* wash-flagged seller against intel.
|
|
23
|
+
*/
|
|
24
|
+
import { readFileSync } from "node:fs";
|
|
25
|
+
import { join } from "node:path";
|
|
26
|
+
import { CLIENT_VERSION } from "./version.js";
|
|
27
|
+
/**
|
|
28
|
+
* Ordered most-specific first: a project using solana-agent-kit AND @x402/core
|
|
29
|
+
* should be told about the SAK seat, because that is where its signing happens.
|
|
30
|
+
*/
|
|
31
|
+
const HOSTS = [
|
|
32
|
+
{
|
|
33
|
+
id: "solana-agent-kit",
|
|
34
|
+
label: "Solana Agent Kit (SolanaAgentKit)",
|
|
35
|
+
deps: ["solana-agent-kit"],
|
|
36
|
+
install: "npm install twzrd-x402-gate",
|
|
37
|
+
snippet: `import { SolanaAgentKit } from "solana-agent-kit";
|
|
38
|
+
import { twzrdBeforeSign } from "twzrd-x402-gate";
|
|
39
|
+
|
|
40
|
+
// Every plugin that signs goes through this policy, including plugins that
|
|
41
|
+
// call agent.wallet.signAndSendTransaction directly.
|
|
42
|
+
const agent = new SolanaAgentKit(wallet, rpcUrl, {
|
|
43
|
+
beforeSign: twzrdBeforeSign({ refuseWashFlagged: true }),
|
|
44
|
+
});`,
|
|
45
|
+
note: "Requires the optional beforeSign hook on Config (sendaifun/solana-agent-kit#599). " +
|
|
46
|
+
"Until that lands, wrap the wallet yourself with wrapWallet() from that PR.",
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
id: "x402-client",
|
|
50
|
+
label: "official x402 client (@x402/core)",
|
|
51
|
+
deps: ["@x402/core", "@x402/fetch", "@x402/svm"],
|
|
52
|
+
install: "npm install twzrd-x402-gate @x402/core @x402/fetch @x402/svm",
|
|
53
|
+
snippet: `import { x402Client } from "@x402/core/client";
|
|
54
|
+
import { installTwzrdAutoGate } from "twzrd-x402-gate";
|
|
55
|
+
|
|
56
|
+
const client = new x402Client();
|
|
57
|
+
// Runs after requirement selection, BEFORE payload creation / signing.
|
|
58
|
+
installTwzrdAutoGate(client, { refuseWashFlagged: true });`,
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
id: "elizaos",
|
|
62
|
+
label: "elizaOS agent runtime",
|
|
63
|
+
deps: ["@elizaos/core", "elizaos"],
|
|
64
|
+
install: "npm install twzrd-x402-gate",
|
|
65
|
+
snippet: `import { installTwzrdAutoGate } from "twzrd-x402-gate";
|
|
66
|
+
|
|
67
|
+
// Gate the payment client your plugin hands to the runtime, not the runtime
|
|
68
|
+
// itself — the seam is wherever the signer lives.
|
|
69
|
+
installTwzrdAutoGate(paymentClient, { refuseWashFlagged: true });`,
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
id: "goat",
|
|
73
|
+
label: "GOAT SDK",
|
|
74
|
+
deps: ["@goat-sdk/core"],
|
|
75
|
+
install: "npm install twzrd-x402-gate",
|
|
76
|
+
snippet: `import { installTwzrdAutoGate } from "twzrd-x402-gate";
|
|
77
|
+
|
|
78
|
+
installTwzrdAutoGate(walletClient, { refuseWashFlagged: true });`,
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
id: "raw-fetch",
|
|
82
|
+
label: "raw fetch + x402 payer",
|
|
83
|
+
deps: ["x402-fetch", "x402"],
|
|
84
|
+
install: "npm install twzrd-x402-gate",
|
|
85
|
+
snippet: `import { installTwzrdAutoGate } from "twzrd-x402-gate";
|
|
86
|
+
|
|
87
|
+
// Wraps fetch: a refused payment never reaches the payer.
|
|
88
|
+
const gatedFetch = installTwzrdAutoGate(payFetch, { refuseWashFlagged: true });`,
|
|
89
|
+
},
|
|
90
|
+
];
|
|
91
|
+
function readPackageJson(cwd) {
|
|
92
|
+
try {
|
|
93
|
+
return JSON.parse(readFileSync(join(cwd, "package.json"), "utf8"));
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
export function detectHosts(pkg) {
|
|
100
|
+
if (!pkg)
|
|
101
|
+
return [];
|
|
102
|
+
const all = {
|
|
103
|
+
...(pkg.dependencies ?? {}),
|
|
104
|
+
...(pkg.devDependencies ?? {}),
|
|
105
|
+
...(pkg.peerDependencies ?? {}),
|
|
106
|
+
};
|
|
107
|
+
const names = new Set(Object.keys(all));
|
|
108
|
+
return HOSTS.filter((h) => h.deps.some((d) => names.has(d)));
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Deterministic, offline refusal proof.
|
|
112
|
+
*
|
|
113
|
+
* Feeds a wash-flagged card straight to the policy layer and asserts the signer
|
|
114
|
+
* never ran. This is the claim an operator needs to believe before adopting,
|
|
115
|
+
* and it must be checkable without spending or reaching the network.
|
|
116
|
+
*/
|
|
117
|
+
export async function proveRefusal() {
|
|
118
|
+
const { installTwzrdAutoGate } = await import("./auto-gate.js");
|
|
119
|
+
// Model the official-client seam, which is the path with existing coverage
|
|
120
|
+
// (see test/autogate-intercept.test.ts). A wash-flagged card must come back
|
|
121
|
+
// as abort, and the signer stub must never be reached.
|
|
122
|
+
let signerInvocations = 0;
|
|
123
|
+
let hook;
|
|
124
|
+
const client = {
|
|
125
|
+
onBeforePaymentCreation(h) {
|
|
126
|
+
hook = h;
|
|
127
|
+
return client;
|
|
128
|
+
},
|
|
129
|
+
};
|
|
130
|
+
installTwzrdAutoGate(client, {
|
|
131
|
+
refuseWashFlagged: true,
|
|
132
|
+
gateOnCanSpend: false,
|
|
133
|
+
preflightMinScore: 0,
|
|
134
|
+
failOpen: true,
|
|
135
|
+
// Injected preflight response: wash_flagged ⇒ the policy must refuse. No network.
|
|
136
|
+
fetch: (async () => new Response(JSON.stringify({
|
|
137
|
+
readiness_card: {
|
|
138
|
+
decision: "block",
|
|
139
|
+
trust_score: 30,
|
|
140
|
+
can_spend: false,
|
|
141
|
+
wash_flagged: true,
|
|
142
|
+
wash_label: "ring",
|
|
143
|
+
},
|
|
144
|
+
}), { status: 200, headers: { "content-type": "application/json" } })),
|
|
145
|
+
});
|
|
146
|
+
if (!hook) {
|
|
147
|
+
return {
|
|
148
|
+
refused: false,
|
|
149
|
+
signerInvocations,
|
|
150
|
+
reason: "gate did not install a pre-payment hook",
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
let refused = false;
|
|
154
|
+
let reason = "";
|
|
155
|
+
try {
|
|
156
|
+
const out = await hook({
|
|
157
|
+
selectedRequirements: {
|
|
158
|
+
payTo: "34w53UkhfBBmVLBcphD1M4dRhbBTQaLLbnRfyTeXzc5m",
|
|
159
|
+
network: "solana",
|
|
160
|
+
asset: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
|
|
161
|
+
maxAmountRequired: "50000",
|
|
162
|
+
scheme: "exact",
|
|
163
|
+
},
|
|
164
|
+
// If the gate ever approved, a real client would call this next.
|
|
165
|
+
sign: async () => {
|
|
166
|
+
signerInvocations += 1;
|
|
167
|
+
},
|
|
168
|
+
});
|
|
169
|
+
if (out && typeof out === "object" && out.abort) {
|
|
170
|
+
refused = true;
|
|
171
|
+
reason = String(out.reason ?? "abort");
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
catch (err) {
|
|
175
|
+
// A throwing refusal is equally acceptable: nothing got signed.
|
|
176
|
+
refused = true;
|
|
177
|
+
reason = String(err?.message ?? err).slice(0, 160);
|
|
178
|
+
}
|
|
179
|
+
return { refused, signerInvocations, reason };
|
|
180
|
+
}
|
|
181
|
+
function line(s = "") {
|
|
182
|
+
process.stdout.write(`${s}\n`);
|
|
183
|
+
}
|
|
184
|
+
export async function main(argv = process.argv.slice(2)) {
|
|
185
|
+
const cwd = process.cwd();
|
|
186
|
+
const pkg = readPackageJson(cwd);
|
|
187
|
+
const detected = detectHosts(pkg);
|
|
188
|
+
line(`twzrd-gate-doctor (twzrd-x402-gate ${CLIENT_VERSION})`);
|
|
189
|
+
line();
|
|
190
|
+
if (!pkg) {
|
|
191
|
+
line("No package.json here. Run this from your project root.");
|
|
192
|
+
return 2;
|
|
193
|
+
}
|
|
194
|
+
if (detected.length === 0) {
|
|
195
|
+
line("No x402 / agent signing surface detected in this project.");
|
|
196
|
+
line("Nothing to gate yet — this is not an error.");
|
|
197
|
+
line();
|
|
198
|
+
line("The gate is useful once something here signs a Solana USDC payment.");
|
|
199
|
+
line("Surfaces it recognises: @x402/core, solana-agent-kit, @elizaos/core,");
|
|
200
|
+
line("@goat-sdk/core, x402-fetch.");
|
|
201
|
+
return 2;
|
|
202
|
+
}
|
|
203
|
+
line(`Detected ${detected.length} signing surface(s):`);
|
|
204
|
+
for (const h of detected)
|
|
205
|
+
line(` • ${h.label}`);
|
|
206
|
+
line();
|
|
207
|
+
for (const h of detected) {
|
|
208
|
+
line(`── ${h.label} ${"─".repeat(Math.max(0, 58 - h.label.length))}`);
|
|
209
|
+
line();
|
|
210
|
+
line(` ${h.install}`);
|
|
211
|
+
line();
|
|
212
|
+
for (const l of h.snippet.split("\n"))
|
|
213
|
+
line(` ${l}`);
|
|
214
|
+
if (h.note) {
|
|
215
|
+
line();
|
|
216
|
+
line(` Note: ${h.note}`);
|
|
217
|
+
}
|
|
218
|
+
line();
|
|
219
|
+
}
|
|
220
|
+
line("── Refusal proof (offline, spends nothing) ──────────────────");
|
|
221
|
+
line();
|
|
222
|
+
const proof = await proveRefusal();
|
|
223
|
+
if (proof.refused && proof.signerInvocations === 0) {
|
|
224
|
+
line(" PASS — wash-flagged seller refused before signing.");
|
|
225
|
+
line(` signer invocations: ${proof.signerInvocations}`);
|
|
226
|
+
line(` reason: ${proof.reason}`);
|
|
227
|
+
line();
|
|
228
|
+
line(" That is the whole product: the payment did not happen, and it cost 0.");
|
|
229
|
+
return 0;
|
|
230
|
+
}
|
|
231
|
+
line(" FAIL — the gate did NOT refuse a wash-flagged seller.");
|
|
232
|
+
line(` refused=${proof.refused} signer_invocations=${proof.signerInvocations}`);
|
|
233
|
+
line(" Please report this with your package versions — it is a real bug.");
|
|
234
|
+
return 1;
|
|
235
|
+
}
|
|
236
|
+
// Only auto-run as a CLI, never on import (keeps it testable). Anchored on a
|
|
237
|
+
// path separator so a file merely ENDING in "doctor.ts" (e.g. probe-doctor.ts)
|
|
238
|
+
// does not trigger the CLI — that bit me while testing this very function.
|
|
239
|
+
if (process.argv[1] &&
|
|
240
|
+
/(^|[\\/])doctor(\.[cm]?[jt]s)?$/.test(process.argv[1])) {
|
|
241
|
+
main().then((code) => process.exit(code), (err) => {
|
|
242
|
+
process.stderr.write(`twzrd-gate-doctor: ${String(err)}\n`);
|
|
243
|
+
process.exit(1);
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
//# sourceMappingURL=doctor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"doctor.js","sourceRoot":"","sources":["../src/doctor.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "twzrd-x402-gate",
|
|
3
|
-
"version": "0.8.
|
|
4
|
-
"description": "Core: buyer-side x402 trust gate. Free TWZRD preflight (ReadinessCard) before signing USDC to any merchant, with optional $0.001 paid trust escalate on warn
|
|
3
|
+
"version": "0.8.10",
|
|
4
|
+
"description": "Core: buyer-side x402 trust gate. Free TWZRD preflight (ReadinessCard) before signing USDC to any merchant, with optional $0.001 paid trust escalate on warn \u2014 blocking spend on bad intel, no human in the loop. Optional: createTwzrdSettleGuard for resource servers to apply merchant policy on the payer before settle+serve. installTwzrdAutoGate is the canonical default-on entry across fetch payWrap, x402 client (onBeforePaymentCreation), and MPP onChallenge. Fail-closed by default \u2014 set TWZRD_FAIL_OPEN=true for outage allow; kill switch TWZRD_GATE_ENABLED=false or TWZRD_AUTO_GATE=0. Always stamps X-Twzrd-Caller for seat metrics.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
7
7
|
"types": "./dist/index.d.ts",
|
|
8
8
|
"bin": {
|
|
9
|
-
"twzrd-safe-fetch": "bin/twzrd-safe-fetch.js",
|
|
10
|
-
"twzrd-gate-eval-refuse": "bin/twzrd-gate-eval-refuse.js"
|
|
9
|
+
"twzrd-safe-fetch": "./bin/twzrd-safe-fetch.js",
|
|
10
|
+
"twzrd-gate-eval-refuse": "./bin/twzrd-gate-eval-refuse.js",
|
|
11
|
+
"twzrd-gate-doctor": "./bin/twzrd-gate-doctor.js"
|
|
11
12
|
},
|
|
12
13
|
"exports": {
|
|
13
14
|
".": {
|
|
@@ -23,13 +24,14 @@
|
|
|
23
24
|
"files": [
|
|
24
25
|
"dist",
|
|
25
26
|
"bin",
|
|
26
|
-
"README.md"
|
|
27
|
+
"README.md",
|
|
28
|
+
"QUICKSTART.md"
|
|
27
29
|
],
|
|
28
30
|
"scripts": {
|
|
29
31
|
"build": "tsc",
|
|
30
32
|
"prepublishOnly": "tsc",
|
|
31
33
|
"typecheck": "tsc --noEmit -p tsconfig.check.json",
|
|
32
|
-
"test": "tsx test/version-identity.test.ts && tsx test/policy.test.ts && tsx test/wrap-fetch.test.ts && tsx test/mcp-hook.test.ts && tsx test/attribution.test.ts && tsx test/auto-receipt.test.ts && tsx test/quick.test.ts && tsx test/sponsored.test.ts && tsx test/escalation.test.ts && tsx test/wash-refuse.test.ts && tsx test/auto-gate.test.ts && tsx test/autogate-intercept.test.ts && tsx test/network.test.ts && tsx test/safe-fetch.test.ts && tsx test/payto-prefer.test.ts && tsx test/challenge-swap.test.ts && tsx test/x402-client-hook.test.ts && tsx test/hook-single-eval.test.ts && tsx test/run-attribution.test.ts && tsx test/payment-control.test.ts && tsx test/intent-binding.test.ts && tsx test/mpp-hook.test.ts && tsx test/mpp-safety.test.ts && tsx test/payment-control-followup.test.ts && tsx test/payment-control-hardening.test.ts && tsx test/x402-official-compat.test.ts && tsx test/seller-hook.test.ts && tsx test/adoption-proof.test.ts && tsx test/delivery-capture.test.ts",
|
|
34
|
+
"test": "tsx test/version-identity.test.ts && tsx test/policy.test.ts && tsx test/wrap-fetch.test.ts && tsx test/mcp-hook.test.ts && tsx test/attribution.test.ts && tsx test/auto-receipt.test.ts && tsx test/quick.test.ts && tsx test/sponsored.test.ts && tsx test/escalation.test.ts && tsx test/wash-refuse.test.ts && tsx test/auto-gate.test.ts && tsx test/autogate-intercept.test.ts && tsx test/network.test.ts && tsx test/safe-fetch.test.ts && tsx test/payto-prefer.test.ts && tsx test/challenge-swap.test.ts && tsx test/x402-client-hook.test.ts && tsx test/hook-single-eval.test.ts && tsx test/run-attribution.test.ts && tsx test/payment-control.test.ts && tsx test/intent-binding.test.ts && tsx test/mpp-hook.test.ts && tsx test/mpp-safety.test.ts && tsx test/payment-control-followup.test.ts && tsx test/payment-control-hardening.test.ts && tsx test/x402-official-compat.test.ts && tsx test/seller-hook.test.ts && tsx test/adoption-proof.test.ts && tsx test/delivery-capture.test.ts && tsx test/doctor.test.ts",
|
|
33
35
|
"pack-smoke": "node scripts/pack-smoke.mjs",
|
|
34
36
|
"safe-fetch": "tsx src/safe-fetch.ts",
|
|
35
37
|
"guard-demo": "tsx examples/agentic-market-gate.ts",
|
|
@@ -41,7 +43,8 @@
|
|
|
41
43
|
"adoption-proof": "tsx src/adoption-proof.ts",
|
|
42
44
|
"demo:buyer-no-spend": "tsx src/adoption-proof.ts -- --integration ops-demo-buyer-no-spend",
|
|
43
45
|
"live-autogate-matrix": "tsx examples/live-autogate-matrix.ts",
|
|
44
|
-
"gate-eval-refuse": "node bin/twzrd-gate-eval-refuse.js"
|
|
46
|
+
"gate-eval-refuse": "node bin/twzrd-gate-eval-refuse.js",
|
|
47
|
+
"gate-doctor": "tsx src/doctor.ts"
|
|
45
48
|
},
|
|
46
49
|
"keywords": [
|
|
47
50
|
"x402",
|