From 9f918a9b423c8dca03f75de4dc0f53b20463ed81 Mon Sep 17 00:00:00 2001 From: twzrd-sol Date: Sat, 25 Jul 2026 09:13:45 +0000 Subject: [PATCH 1/4] feat(proxy): default-on TWZRD AutoGate before x402 payment sign Install twzrd-x402-gate on the internal x402Client after scheme registration and before payFetch. Pre-spend check runs on every payment creation: - Solana: free TWZRD preflight (wash refuse); block aborts sign - Base/EVM: observe mode (no fabricated Solana score; payment proceeds) - Seat identity: X-Twzrd-Caller / X-TWZRD-Client stamped by the gate Kill switch (default remains ON): TWZRD_AUTO_GATE=0 TWZRD_GATE_ENABLED=false Dependency: twzrd-x402-gate@^0.8.4 (optional peer for consumers who pin). Soft-fails install if the package cannot load so proxy boot is never blocked. Docs: README "Pre-spend trust (TWZRD AutoGate)" section. Tests: src/twzrd-autogate.test.ts (env kill + install surface). --- README.md | 19 +++ package-lock.json | 245 +++++++++---------------------------- package.json | 4 + src/proxy.ts | 38 ++++++ src/twzrd-autogate.test.ts | 51 ++++++++ 5 files changed, 170 insertions(+), 187 deletions(-) create mode 100644 src/twzrd-autogate.test.ts diff --git a/README.md b/README.md index d9746b60..e506489e 100644 --- a/README.md +++ b/README.md @@ -55,6 +55,25 @@ ClawRouter is built for the agent-first world: This is the stack that lets agents operate autonomously: **x402 + USDC + local routing**. +### Pre-spend trust (TWZRD AutoGate) + +By default ClawRouter installs [`twzrd-x402-gate`](https://www.npmjs.com/package/twzrd-x402-gate) on its internal x402 client **before any payment is signed**: + +- **Solana** pays: free TWZRD preflight (wash / allow|warn|block). `block` aborts the sign. +- **Base / EVM** pays: observe mode (no fabricated Solana reputation; payment proceeds). +- **Seat identity:** every gate call stamps `X-Twzrd-Caller: twzrd-x402-gate/` for measurable installs. + +**Kill switch (default remains ON):** + +```bash +export TWZRD_AUTO_GATE=0 +# or +export TWZRD_GATE_ENABLED=false +``` + +TWZRD is a pre-spend check, not a facilitator and not a replacement for ClawRouter routing. + + --- ## How it compares diff --git a/package-lock.json b/package-lock.json index 08804433..c69a47b1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,6 +23,7 @@ "@x402/svm": "^2.9.0", "axios": "^1.18.1", "https-proxy-agent": "^9.1.0", + "twzrd-x402-gate": "^0.8.4", "undici": "^8.5.0", "viem": "^2.39.3" }, @@ -127,29 +128,6 @@ } } }, - "node_modules/@emnapi/core": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.1.tgz", - "integrity": "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/wasi-threads": "1.2.2", - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/runtime": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.1.tgz", - "integrity": "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, "node_modules/@emnapi/wasi-threads": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz", @@ -2703,6 +2681,7 @@ "resolved": "https://registry.npmjs.org/@solana/kit/-/kit-5.5.1.tgz", "integrity": "sha512-irKUGiV2yRoyf+4eGQ/ZeCRxa43yjFEL1DUI5B0DkcfZw3cr0VJtVJnrG8OtVF01vT0OUfYOcUn6zJW5TROHvQ==", "license": "MIT", + "peer": true, "dependencies": { "@solana/accounts": "5.5.1", "@solana/addresses": "5.5.1", @@ -3493,6 +3472,7 @@ "resolved": "https://registry.npmjs.org/@solana/sysvars/-/sysvars-5.5.1.tgz", "integrity": "sha512-k3Quq87Mm+geGUu1GWv6knPk0ALsfY6EKSJGw9xUJDHzY/RkYSBnh0RiOrUhtFm2TDNjOailg8/m0VHmi3reFA==", "license": "MIT", + "peer": true, "dependencies": { "@solana/accounts": "5.5.1", "@solana/codecs": "5.5.1", @@ -3599,132 +3579,6 @@ } } }, - "node_modules/@solana/web3.js": { - "version": "1.98.4", - "resolved": "https://registry.npmjs.org/@solana/web3.js/-/web3.js-1.98.4.tgz", - "integrity": "sha512-vv9lfnvjUsRiq//+j5pBdXig0IQdtzA0BRZ3bXEP4KaIyF1CcaydWqgyzQgfZMNIsWNWmG+AUHwPy4AHOD6gpw==", - "license": "MIT", - "optional": true, - "dependencies": { - "@babel/runtime": "^7.25.0", - "@noble/curves": "^1.4.2", - "@noble/hashes": "^1.4.0", - "@solana/buffer-layout": "^4.0.1", - "@solana/codecs-numbers": "^2.1.0", - "agentkeepalive": "^4.5.0", - "bn.js": "^5.2.1", - "borsh": "^0.7.0", - "bs58": "^4.0.1", - "buffer": "6.0.3", - "fast-stable-stringify": "^1.0.0", - "jayson": "^4.1.1", - "node-fetch": "^2.7.0", - "rpc-websockets": "^9.0.2", - "superstruct": "^2.0.2" - } - }, - "node_modules/@solana/web3.js/node_modules/@noble/curves": { - "version": "1.9.7", - "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.7.tgz", - "integrity": "sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw==", - "license": "MIT", - "optional": true, - "dependencies": { - "@noble/hashes": "1.8.0" - }, - "engines": { - "node": "^14.21.3 || >=16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@solana/web3.js/node_modules/@noble/hashes": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", - "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", - "license": "MIT", - "optional": true, - "engines": { - "node": "^14.21.3 || >=16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@solana/web3.js/node_modules/@solana/codecs-core": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@solana/codecs-core/-/codecs-core-2.3.0.tgz", - "integrity": "sha512-oG+VZzN6YhBHIoSKgS5ESM9VIGzhWjEHEGNPSibiDTxFhsFWxNaz8LbMDPjBUE69r9wmdGLkrQ+wVPbnJcZPvw==", - "license": "MIT", - "optional": true, - "dependencies": { - "@solana/errors": "2.3.0" - }, - "engines": { - "node": ">=20.18.0" - }, - "peerDependencies": { - "typescript": ">=5.3.3" - } - }, - "node_modules/@solana/web3.js/node_modules/@solana/codecs-numbers": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@solana/codecs-numbers/-/codecs-numbers-2.3.0.tgz", - "integrity": "sha512-jFvvwKJKffvG7Iz9dmN51OGB7JBcy2CJ6Xf3NqD/VP90xak66m/Lg48T01u5IQ/hc15mChVHiBm+HHuOFDUrQg==", - "license": "MIT", - "optional": true, - "dependencies": { - "@solana/codecs-core": "2.3.0", - "@solana/errors": "2.3.0" - }, - "engines": { - "node": ">=20.18.0" - }, - "peerDependencies": { - "typescript": ">=5.3.3" - } - }, - "node_modules/@solana/web3.js/node_modules/@solana/errors": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@solana/errors/-/errors-2.3.0.tgz", - "integrity": "sha512-66RI9MAbwYV0UtP7kGcTBVLxJgUxoZGm8Fbc0ah+lGiAw17Gugco6+9GrJCV83VyF2mDWyYnYM9qdI3yjgpnaQ==", - "license": "MIT", - "optional": true, - "dependencies": { - "chalk": "^5.4.1", - "commander": "^14.0.0" - }, - "bin": { - "errors": "bin/cli.mjs" - }, - "engines": { - "node": ">=20.18.0" - }, - "peerDependencies": { - "typescript": ">=5.3.3" - } - }, - "node_modules/@solana/web3.js/node_modules/base-x": { - "version": "3.0.11", - "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.11.tgz", - "integrity": "sha512-xz7wQ8xDhdyP7tQxwdteLYeFfS68tSMNCZ/Y37WJ4bhGfKPpqEIlmIyueQHqOyoPhE6xNUqjzRr8ra0eF9VRvA==", - "license": "MIT", - "optional": true, - "dependencies": { - "safe-buffer": "^5.0.1" - } - }, - "node_modules/@solana/web3.js/node_modules/bs58": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz", - "integrity": "sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==", - "license": "MIT", - "optional": true, - "dependencies": { - "base-x": "^3.0.2" - } - }, "node_modules/@standard-schema/spec": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", @@ -3892,6 +3746,7 @@ "integrity": "sha512-dzHeT2gySzZtLDsuqxU9AkYgIsQoHAHtRBpOqM+Ofzx1Bwrd2RcCjQJ+6iQbsHOIR6NS33bF2W1k3blN1zLDrA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.62.0", "@typescript-eslint/types": "8.62.0", @@ -4215,6 +4070,7 @@ "resolved": "https://registry.npmjs.org/@x402/fetch/-/fetch-2.17.0.tgz", "integrity": "sha512-iy6ar1JTgs4gzdlc2q2Z7nuL5keBkQhoD4v5niVlTwLoivVvHnAA0p/wK5WTWRrJ1b+el8pthkjFS0Jc3nMIIg==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@x402/core": "~2.17.0" } @@ -4224,6 +4080,7 @@ "resolved": "https://registry.npmjs.org/@x402/svm/-/svm-2.17.0.tgz", "integrity": "sha512-BXSaLBBoP8bfBAKDdwg7c2UYuTfbZOB7gZByCK3LWkROqmTnqxQlQ683urfpYwbvlK0EbZ3TIcqR/Rx42EuVzw==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@solana-program/compute-budget": "^0.11.0", "@solana-program/token": "^0.9.0", @@ -4274,6 +4131,7 @@ "integrity": "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -4589,6 +4447,7 @@ "hasInstallScript": true, "license": "MIT", "optional": true, + "peer": true, "dependencies": { "node-gyp-build": "^4.3.0" }, @@ -4898,6 +4757,7 @@ "integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==", "hasInstallScript": true, "license": "MIT", + "peer": true, "bin": { "esbuild": "bin/esbuild" }, @@ -4952,6 +4812,7 @@ "integrity": "sha512-6lVbcqSodALYo+4ELD0heG6lFiFxnLMuLkiMi2qV8LMp54N8tE8FT1GMH+ev4Ti00nFjNze2+Su6DsV5OQW3Dg==", "dev": true, "license": "MIT", + "peer": true, "workspaces": [ "packages/*" ], @@ -5223,14 +5084,6 @@ "license": "MIT", "optional": true }, - "node_modules/fastestsmallesttextencoderdecoder": { - "version": "1.0.22", - "resolved": "https://registry.npmjs.org/fastestsmallesttextencoderdecoder/-/fastestsmallesttextencoderdecoder-1.0.22.tgz", - "integrity": "sha512-Pb8d48e+oIuY4MaM64Cd7OW1gt4nxCHs7/ddPPZ/Ic3sg8yVGM7O9wDvZ7us6ScaUupzM+pfBolwtYhN1IxBIw==", - "license": "CC0-1.0", - "optional": true, - "peer": true - }, "node_modules/fdir": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", @@ -5688,18 +5541,6 @@ "license": "MIT", "optional": true }, - "node_modules/jiti": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", - "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "bin": { - "jiti": "lib/jiti-cli.mjs" - } - }, "node_modules/joycon": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz", @@ -6678,6 +6519,7 @@ "integrity": "sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@hono/node-server": "^1.19.9", "ajv": "^8.17.1", @@ -7691,6 +7533,7 @@ "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.1", @@ -8099,6 +7942,7 @@ "integrity": "sha512-7dYm06A945mXuIk/5HUlSjeyIYChW8vCEiU2dkOKKqJJzwAWxTkCc91Eqbz7TgODh2rtFFKWI/fekowWHOkmjQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@grammyjs/types": "3.27.3", "abort-controller": "^3.0.0", @@ -8172,6 +8016,7 @@ "integrity": "sha512-2NFaIyNVgJmBs/ecmtGzlmluTFs5cHEWGTdu0t1HBwYzoGXOL5nUQBRMXsXWla5i4KkG//QMzVP88m1+I3fdAQ==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=16.9.0" } @@ -9909,6 +9754,7 @@ "integrity": "sha512-xamtWoB1EshgjpmlXd7GGm2VfdDtw1+rD8uhry8pSNW3If6S8E0m2T2+orSKeZXEn/aPJMviCpDBA65WJt8zhg==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=22.19.0" } @@ -10143,6 +9989,7 @@ "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", "dev": true, "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } @@ -10333,6 +10180,7 @@ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -10382,6 +10230,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "nanoid": "^3.3.12", "picocolors": "^1.1.1", @@ -11049,6 +10898,42 @@ "fsevents": "~2.3.3" } }, + "node_modules/twzrd-x402-gate": { + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/twzrd-x402-gate/-/twzrd-x402-gate-0.8.4.tgz", + "integrity": "sha512-feuLCTdE0ORDGYI3pl1PgWu/cqkIkQsTNmdQwtpGDhlHNK1q8SNpnPEbo+VbXrNUP5L3ePwlrvrHPk1Y5ywQDA==", + "license": "MIT", + "bin": { + "twzrd-safe-fetch": "bin/twzrd-safe-fetch.js" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@scure/base": ">=1.0.0", + "@solana/kit": ">=5.1.0", + "@x402/core": ">=2.0.0", + "@x402/fetch": ">=2.0.0", + "@x402/svm": ">=2.0.0" + }, + "peerDependenciesMeta": { + "@scure/base": { + "optional": true + }, + "@solana/kit": { + "optional": true + }, + "@x402/core": { + "optional": true + }, + "@x402/fetch": { + "optional": true + }, + "@x402/svm": { + "optional": true + } + } + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -11067,6 +10952,7 @@ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -11138,6 +11024,7 @@ "hasInstallScript": true, "license": "MIT", "optional": true, + "peer": true, "dependencies": { "node-gyp-build": "^4.3.0" }, @@ -11242,6 +11129,7 @@ "integrity": "sha512-BuJcQK/56NQTWDGn4ABea3q4SSBdNPWwNZKTkkUpcMPnLoquSYH8llRtSUIgoL1KSCpHt5eghLShn50mH36y7Q==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.4", @@ -11497,6 +11385,7 @@ "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz", "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==", "license": "MIT", + "peer": true, "engines": { "node": ">=10.0.0" }, @@ -11513,24 +11402,6 @@ } } }, - "node_modules/yaml": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", - "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", - "dev": true, - "license": "ISC", - "optional": true, - "peer": true, - "bin": { - "yaml": "bin.mjs" - }, - "engines": { - "node": ">= 14.6" - }, - "funding": { - "url": "https://github.com/sponsors/eemeli" - } - }, "node_modules/yn": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", diff --git a/package.json b/package.json index 51fb7ca3..8b0b7d3d 100644 --- a/package.json +++ b/package.json @@ -94,6 +94,7 @@ "@x402/svm": "^2.9.0", "axios": "^1.18.1", "https-proxy-agent": "^9.1.0", + "twzrd-x402-gate": "^0.8.4", "undici": "^8.5.0", "viem": "^2.39.3" }, @@ -103,6 +104,9 @@ "peerDependenciesMeta": { "openclaw": { "optional": true + }, + "twzrd-x402-gate": { + "optional": true } }, "devDependencies": { diff --git a/src/proxy.ts b/src/proxy.ts index 19cbc896..6ebb7409 100644 --- a/src/proxy.ts +++ b/src/proxy.ts @@ -1993,6 +1993,37 @@ export async function startProxy(options: ProxyOptions): Promise { }; } + +/** + * Default-on TWZRD AutoGate for the internal x402 client (Fork-1 seat). + * Soft-fails if the package is missing at runtime so forks can omit it. + * Kill: TWZRD_AUTO_GATE=0 or TWZRD_GATE_ENABLED=false (handled inside the gate). + */ +async function installTwzrdAutoGateOnClient(client: x402Client): Promise { + try { + const { installTwzrdAutoGate } = await import("twzrd-x402-gate"); + installTwzrdAutoGate(client, { + // refuse hard wash on Solana; Base remains observe (network unscored → allow) + refuseWashFlagged: true, + gateOnCanSpend: false, + unsupportedNetworkMode: "observe", + }); + console.log( + "[ClawRouter] TWZRD AutoGate ON (pre-spend check). Kill: TWZRD_AUTO_GATE=0", + ); + } catch (err) { + const msg = err instanceof Error ? err.message : String(err); + // Missing optional dep or incompatible client surface — do not block proxy boot. + if (/Cannot find module|ERR_MODULE_NOT_FOUND|twzrd-x402-gate/i.test(msg)) { + console.warn( + "[ClawRouter] twzrd-x402-gate not available — payments unguarded. npm i twzrd-x402-gate@^0.8.4", + ); + return; + } + console.warn(`[ClawRouter] TWZRD AutoGate install skipped: ${msg}`); + } +} + // Create x402 payment client with EVM scheme (always available) const account = privateKeyToAccount(walletKey as `0x${string}`); const evmPublicClient = createPublicClient({ chain: base, transport: http() }); @@ -2000,6 +2031,13 @@ export async function startProxy(options: ProxyOptions): Promise { const x402 = new x402Client(); registerExactEvmScheme(x402, { signer: evmSigner }); + // TWZRD Fork-1 default seat: pre-spend gate on every payment before sign. + // Default ON when twzrd-x402-gate is installed (dependency). Kill switch: + // TWZRD_AUTO_GATE=0 | TWZRD_GATE_ENABLED=false + // Base/EVM payments: observe mode (allow; Solana corpus does not score EVM). + // Solana payments: full wash/preflight. Seat identity stamps X-Twzrd-Caller. + await installTwzrdAutoGateOnClient(x402); + // Register Solana scheme if key is available // Uses registerExactSvmScheme helper which registers: // - solana:* wildcard (catches any CAIP-2 Solana network) diff --git a/src/twzrd-autogate.test.ts b/src/twzrd-autogate.test.ts new file mode 100644 index 00000000..a6b9d413 --- /dev/null +++ b/src/twzrd-autogate.test.ts @@ -0,0 +1,51 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; + +/** + * Proxy installs TWZRD AutoGate on the internal x402Client before payFetch. + * We unit-test the env kill + dynamic import contract without booting the proxy. + */ +describe("TWZRD AutoGate seat (Fork-1)", () => { + const prevAuto = process.env.TWZRD_AUTO_GATE; + const prevGate = process.env.TWZRD_GATE_ENABLED; + + afterEach(() => { + if (prevAuto === undefined) delete process.env.TWZRD_AUTO_GATE; + else process.env.TWZRD_AUTO_GATE = prevAuto; + if (prevGate === undefined) delete process.env.TWZRD_GATE_ENABLED; + else process.env.TWZRD_GATE_ENABLED = prevGate; + vi.resetModules(); + vi.unmock("twzrd-x402-gate"); + }); + + it("isTwzrdAutoGateDisabled honors TWZRD_AUTO_GATE=0", async () => { + process.env.TWZRD_AUTO_GATE = "0"; + const { isTwzrdAutoGateDisabled } = await import("twzrd-x402-gate"); + expect(isTwzrdAutoGateDisabled()).toBe(true); + }); + + it("isTwzrdAutoGateDisabled default false when env unset", async () => { + delete process.env.TWZRD_AUTO_GATE; + delete process.env.TWZRD_GATE_ENABLED; + const { isTwzrdAutoGateDisabled } = await import("twzrd-x402-gate"); + expect(isTwzrdAutoGateDisabled()).toBe(false); + }); + + it("installTwzrdAutoGate registers on a client with onBeforePaymentCreation", async () => { + delete process.env.TWZRD_AUTO_GATE; + delete process.env.TWZRD_GATE_ENABLED; + const hooks: Array<(ctx: unknown) => Promise> = []; + const client = { + onBeforePaymentCreation(hook: (ctx: unknown) => Promise) { + hooks.push(hook); + }, + }; + const { installTwzrdAutoGate } = await import("twzrd-x402-gate"); + installTwzrdAutoGate(client as never, { + refuseWashFlagged: true, + gateOnCanSpend: false, + unsupportedNetworkMode: "observe", + }); + // install wraps registrar + installs at least one hook path + expect(typeof client.onBeforePaymentCreation).toBe("function"); + }); +}); From af324cb504edbe191d70ed538868fe44bb34f472 Mon Sep 17 00:00:00 2001 From: twzrd-sol Date: Mon, 27 Jul 2026 07:50:11 +0000 Subject: [PATCH 2/4] fix(proxy): AutoGate install fails closed; restore npm ci lockfile sync - installTwzrdAutoGateOnClient: only a genuinely missing twzrd-x402-gate module soft-skips (optional dep for forks); any hook-registration failure now rethrows so the proxy can never boot with unguarded payments (CodeRabbit Major) - twzrd-autogate.test.ts: drop unused beforeEach import (CodeRabbit Minor) and a dead vi.unmock that vitest flags for hoisting - package-lock.json: replace the previous 245-line churned delta with a purely additive +40 delta on upstream's lock; the branch failed npm ci (EUSAGE, @emnapi drift from a full re-resolve). Cold npm ci now passes (794 pkgs), tsc --noEmit clean, vitest 705 passed / 3 skipped Co-Authored-By: Claude Fable 5 --- package-lock.json | 243 +++++++++++++++++++++++++++++++------ src/proxy.ts | 35 +++--- src/twzrd-autogate.test.ts | 3 +- 3 files changed, 228 insertions(+), 53 deletions(-) diff --git a/package-lock.json b/package-lock.json index c69a47b1..c1c22ae1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -50,6 +50,9 @@ "peerDependenciesMeta": { "openclaw": { "optional": true + }, + "twzrd-x402-gate": { + "optional": true } } }, @@ -128,6 +131,29 @@ } } }, + "node_modules/@emnapi/core": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.1.tgz", + "integrity": "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.2", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.1.tgz", + "integrity": "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@emnapi/wasi-threads": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz", @@ -2681,7 +2707,6 @@ "resolved": "https://registry.npmjs.org/@solana/kit/-/kit-5.5.1.tgz", "integrity": "sha512-irKUGiV2yRoyf+4eGQ/ZeCRxa43yjFEL1DUI5B0DkcfZw3cr0VJtVJnrG8OtVF01vT0OUfYOcUn6zJW5TROHvQ==", "license": "MIT", - "peer": true, "dependencies": { "@solana/accounts": "5.5.1", "@solana/addresses": "5.5.1", @@ -3472,7 +3497,6 @@ "resolved": "https://registry.npmjs.org/@solana/sysvars/-/sysvars-5.5.1.tgz", "integrity": "sha512-k3Quq87Mm+geGUu1GWv6knPk0ALsfY6EKSJGw9xUJDHzY/RkYSBnh0RiOrUhtFm2TDNjOailg8/m0VHmi3reFA==", "license": "MIT", - "peer": true, "dependencies": { "@solana/accounts": "5.5.1", "@solana/codecs": "5.5.1", @@ -3579,6 +3603,132 @@ } } }, + "node_modules/@solana/web3.js": { + "version": "1.98.4", + "resolved": "https://registry.npmjs.org/@solana/web3.js/-/web3.js-1.98.4.tgz", + "integrity": "sha512-vv9lfnvjUsRiq//+j5pBdXig0IQdtzA0BRZ3bXEP4KaIyF1CcaydWqgyzQgfZMNIsWNWmG+AUHwPy4AHOD6gpw==", + "license": "MIT", + "optional": true, + "dependencies": { + "@babel/runtime": "^7.25.0", + "@noble/curves": "^1.4.2", + "@noble/hashes": "^1.4.0", + "@solana/buffer-layout": "^4.0.1", + "@solana/codecs-numbers": "^2.1.0", + "agentkeepalive": "^4.5.0", + "bn.js": "^5.2.1", + "borsh": "^0.7.0", + "bs58": "^4.0.1", + "buffer": "6.0.3", + "fast-stable-stringify": "^1.0.0", + "jayson": "^4.1.1", + "node-fetch": "^2.7.0", + "rpc-websockets": "^9.0.2", + "superstruct": "^2.0.2" + } + }, + "node_modules/@solana/web3.js/node_modules/@noble/curves": { + "version": "1.9.7", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.7.tgz", + "integrity": "sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw==", + "license": "MIT", + "optional": true, + "dependencies": { + "@noble/hashes": "1.8.0" + }, + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@solana/web3.js/node_modules/@noble/hashes": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "license": "MIT", + "optional": true, + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@solana/web3.js/node_modules/@solana/codecs-core": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@solana/codecs-core/-/codecs-core-2.3.0.tgz", + "integrity": "sha512-oG+VZzN6YhBHIoSKgS5ESM9VIGzhWjEHEGNPSibiDTxFhsFWxNaz8LbMDPjBUE69r9wmdGLkrQ+wVPbnJcZPvw==", + "license": "MIT", + "optional": true, + "dependencies": { + "@solana/errors": "2.3.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@solana/web3.js/node_modules/@solana/codecs-numbers": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@solana/codecs-numbers/-/codecs-numbers-2.3.0.tgz", + "integrity": "sha512-jFvvwKJKffvG7Iz9dmN51OGB7JBcy2CJ6Xf3NqD/VP90xak66m/Lg48T01u5IQ/hc15mChVHiBm+HHuOFDUrQg==", + "license": "MIT", + "optional": true, + "dependencies": { + "@solana/codecs-core": "2.3.0", + "@solana/errors": "2.3.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@solana/web3.js/node_modules/@solana/errors": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@solana/errors/-/errors-2.3.0.tgz", + "integrity": "sha512-66RI9MAbwYV0UtP7kGcTBVLxJgUxoZGm8Fbc0ah+lGiAw17Gugco6+9GrJCV83VyF2mDWyYnYM9qdI3yjgpnaQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "chalk": "^5.4.1", + "commander": "^14.0.0" + }, + "bin": { + "errors": "bin/cli.mjs" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@solana/web3.js/node_modules/base-x": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.11.tgz", + "integrity": "sha512-xz7wQ8xDhdyP7tQxwdteLYeFfS68tSMNCZ/Y37WJ4bhGfKPpqEIlmIyueQHqOyoPhE6xNUqjzRr8ra0eF9VRvA==", + "license": "MIT", + "optional": true, + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/@solana/web3.js/node_modules/bs58": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz", + "integrity": "sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==", + "license": "MIT", + "optional": true, + "dependencies": { + "base-x": "^3.0.2" + } + }, "node_modules/@standard-schema/spec": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", @@ -3746,7 +3896,6 @@ "integrity": "sha512-dzHeT2gySzZtLDsuqxU9AkYgIsQoHAHtRBpOqM+Ofzx1Bwrd2RcCjQJ+6iQbsHOIR6NS33bF2W1k3blN1zLDrA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.62.0", "@typescript-eslint/types": "8.62.0", @@ -4070,7 +4219,6 @@ "resolved": "https://registry.npmjs.org/@x402/fetch/-/fetch-2.17.0.tgz", "integrity": "sha512-iy6ar1JTgs4gzdlc2q2Z7nuL5keBkQhoD4v5niVlTwLoivVvHnAA0p/wK5WTWRrJ1b+el8pthkjFS0Jc3nMIIg==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@x402/core": "~2.17.0" } @@ -4080,7 +4228,6 @@ "resolved": "https://registry.npmjs.org/@x402/svm/-/svm-2.17.0.tgz", "integrity": "sha512-BXSaLBBoP8bfBAKDdwg7c2UYuTfbZOB7gZByCK3LWkROqmTnqxQlQ683urfpYwbvlK0EbZ3TIcqR/Rx42EuVzw==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@solana-program/compute-budget": "^0.11.0", "@solana-program/token": "^0.9.0", @@ -4131,7 +4278,6 @@ "integrity": "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -4447,7 +4593,6 @@ "hasInstallScript": true, "license": "MIT", "optional": true, - "peer": true, "dependencies": { "node-gyp-build": "^4.3.0" }, @@ -4757,7 +4902,6 @@ "integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==", "hasInstallScript": true, "license": "MIT", - "peer": true, "bin": { "esbuild": "bin/esbuild" }, @@ -4812,7 +4956,6 @@ "integrity": "sha512-6lVbcqSodALYo+4ELD0heG6lFiFxnLMuLkiMi2qV8LMp54N8tE8FT1GMH+ev4Ti00nFjNze2+Su6DsV5OQW3Dg==", "dev": true, "license": "MIT", - "peer": true, "workspaces": [ "packages/*" ], @@ -5084,6 +5227,14 @@ "license": "MIT", "optional": true }, + "node_modules/fastestsmallesttextencoderdecoder": { + "version": "1.0.22", + "resolved": "https://registry.npmjs.org/fastestsmallesttextencoderdecoder/-/fastestsmallesttextencoderdecoder-1.0.22.tgz", + "integrity": "sha512-Pb8d48e+oIuY4MaM64Cd7OW1gt4nxCHs7/ddPPZ/Ic3sg8yVGM7O9wDvZ7us6ScaUupzM+pfBolwtYhN1IxBIw==", + "license": "CC0-1.0", + "optional": true, + "peer": true + }, "node_modules/fdir": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", @@ -5541,6 +5692,18 @@ "license": "MIT", "optional": true }, + "node_modules/jiti": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", + "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, "node_modules/joycon": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz", @@ -6519,7 +6682,6 @@ "integrity": "sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@hono/node-server": "^1.19.9", "ajv": "^8.17.1", @@ -7533,7 +7695,6 @@ "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.1", @@ -7942,7 +8103,6 @@ "integrity": "sha512-7dYm06A945mXuIk/5HUlSjeyIYChW8vCEiU2dkOKKqJJzwAWxTkCc91Eqbz7TgODh2rtFFKWI/fekowWHOkmjQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@grammyjs/types": "3.27.3", "abort-controller": "^3.0.0", @@ -8016,7 +8176,6 @@ "integrity": "sha512-2NFaIyNVgJmBs/ecmtGzlmluTFs5cHEWGTdu0t1HBwYzoGXOL5nUQBRMXsXWla5i4KkG//QMzVP88m1+I3fdAQ==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=16.9.0" } @@ -9754,7 +9913,6 @@ "integrity": "sha512-xamtWoB1EshgjpmlXd7GGm2VfdDtw1+rD8uhry8pSNW3If6S8E0m2T2+orSKeZXEn/aPJMviCpDBA65WJt8zhg==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=22.19.0" } @@ -9989,7 +10147,6 @@ "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", "dev": true, "license": "MIT", - "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } @@ -10180,7 +10337,6 @@ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -10230,7 +10386,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "nanoid": "^3.3.12", "picocolors": "^1.1.1", @@ -10898,10 +11053,23 @@ "fsevents": "~2.3.3" } }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/twzrd-x402-gate": { - "version": "0.8.4", - "resolved": "https://registry.npmjs.org/twzrd-x402-gate/-/twzrd-x402-gate-0.8.4.tgz", - "integrity": "sha512-feuLCTdE0ORDGYI3pl1PgWu/cqkIkQsTNmdQwtpGDhlHNK1q8SNpnPEbo+VbXrNUP5L3ePwlrvrHPk1Y5ywQDA==", + "version": "0.8.6", + "resolved": "https://registry.npmjs.org/twzrd-x402-gate/-/twzrd-x402-gate-0.8.6.tgz", + "integrity": "sha512-bdul9FcdKhFWuLXSWJad+za+gFETQwUMZSyCrnt358XmELKXVOY9hkdOEgcmzMEzPkBON6LCr+RZU61JpjOJ6Q==", "license": "MIT", "bin": { "twzrd-safe-fetch": "bin/twzrd-safe-fetch.js" @@ -10934,25 +11102,11 @@ } } }, - "node_modules/type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, "node_modules/typescript": { "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -11024,7 +11178,6 @@ "hasInstallScript": true, "license": "MIT", "optional": true, - "peer": true, "dependencies": { "node-gyp-build": "^4.3.0" }, @@ -11129,7 +11282,6 @@ "integrity": "sha512-BuJcQK/56NQTWDGn4ABea3q4SSBdNPWwNZKTkkUpcMPnLoquSYH8llRtSUIgoL1KSCpHt5eghLShn50mH36y7Q==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.4", @@ -11385,7 +11537,6 @@ "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz", "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==", "license": "MIT", - "peer": true, "engines": { "node": ">=10.0.0" }, @@ -11402,6 +11553,24 @@ } } }, + "node_modules/yaml": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", + "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", + "dev": true, + "license": "ISC", + "optional": true, + "peer": true, + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, "node_modules/yn": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", diff --git a/src/proxy.ts b/src/proxy.ts index 6ebb7409..ea17a941 100644 --- a/src/proxy.ts +++ b/src/proxy.ts @@ -1996,32 +1996,39 @@ export async function startProxy(options: ProxyOptions): Promise { /** * Default-on TWZRD AutoGate for the internal x402 client (Fork-1 seat). - * Soft-fails if the package is missing at runtime so forks can omit it. + * Missing package = soft-skip so forks can omit the optional gate. Any other + * install failure fails closed: boot aborts instead of paying unguarded. * Kill: TWZRD_AUTO_GATE=0 or TWZRD_GATE_ENABLED=false (handled inside the gate). */ async function installTwzrdAutoGateOnClient(client: x402Client): Promise { + let gate: typeof import("twzrd-x402-gate"); try { - const { installTwzrdAutoGate } = await import("twzrd-x402-gate"); - installTwzrdAutoGate(client, { - // refuse hard wash on Solana; Base remains observe (network unscored → allow) - refuseWashFlagged: true, - gateOnCanSpend: false, - unsupportedNetworkMode: "observe", - }); - console.log( - "[ClawRouter] TWZRD AutoGate ON (pre-spend check). Kill: TWZRD_AUTO_GATE=0", - ); + gate = await import("twzrd-x402-gate"); } catch (err) { + const code = (err as { code?: string } | null)?.code; const msg = err instanceof Error ? err.message : String(err); - // Missing optional dep or incompatible client surface — do not block proxy boot. - if (/Cannot find module|ERR_MODULE_NOT_FOUND|twzrd-x402-gate/i.test(msg)) { + if ( + code === "ERR_MODULE_NOT_FOUND" || + code === "MODULE_NOT_FOUND" || + /Cannot find module/i.test(msg) + ) { console.warn( "[ClawRouter] twzrd-x402-gate not available — payments unguarded. npm i twzrd-x402-gate@^0.8.4", ); return; } - console.warn(`[ClawRouter] TWZRD AutoGate install skipped: ${msg}`); + throw err; } + // Registration failures must not fall through to unguarded payments. + gate.installTwzrdAutoGate(client, { + // refuse hard wash on Solana; Base remains observe (network unscored → allow) + refuseWashFlagged: true, + gateOnCanSpend: false, + unsupportedNetworkMode: "observe", + }); + console.log( + "[ClawRouter] TWZRD AutoGate ON (pre-spend check). Kill: TWZRD_AUTO_GATE=0", + ); } // Create x402 payment client with EVM scheme (always available) diff --git a/src/twzrd-autogate.test.ts b/src/twzrd-autogate.test.ts index a6b9d413..150c2847 100644 --- a/src/twzrd-autogate.test.ts +++ b/src/twzrd-autogate.test.ts @@ -1,4 +1,4 @@ -import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; +import { describe, it, expect, vi, afterEach } from "vitest"; /** * Proxy installs TWZRD AutoGate on the internal x402Client before payFetch. @@ -14,7 +14,6 @@ describe("TWZRD AutoGate seat (Fork-1)", () => { if (prevGate === undefined) delete process.env.TWZRD_GATE_ENABLED; else process.env.TWZRD_GATE_ENABLED = prevGate; vi.resetModules(); - vi.unmock("twzrd-x402-gate"); }); it("isTwzrdAutoGateDisabled honors TWZRD_AUTO_GATE=0", async () => { From 1d49fcf4e744098fb4aa4b736fd613cee9ee6474 Mon Sep 17 00:00:00 2001 From: twzrd-sol Date: Wed, 29 Jul 2026 21:04:52 +0000 Subject: [PATCH 3/4] fix(proxy): tighten AutoGate soft-skip; optionalDependency 0.8.6 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CodeRabbit residual majors on #218 tip (af324cb): - Soft-skip MODULE_NOT_FOUND only when the missing package is twzrd-x402-gate itself; transitive / unrelated resolution failures rethrow so the proxy cannot boot unguarded. - Move twzrd-x402-gate from dependencies → optionalDependencies (^0.8.6) and drop the dead peerDependenciesMeta entry (meta only applies to peers). - Tests: assert hooks.length > 0 after install; unit the soft-skip matcher. vitest src/twzrd-autogate.test.ts: 4 passed. tsc --noEmit clean. --- package-lock.json | 242 ++++++------------------------------- package.json | 7 +- src/proxy.ts | 18 +-- src/twzrd-autogate.test.ts | 28 ++++- 4 files changed, 79 insertions(+), 216 deletions(-) diff --git a/package-lock.json b/package-lock.json index c1c22ae1..9ca98675 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,7 +23,6 @@ "@x402/svm": "^2.9.0", "axios": "^1.18.1", "https-proxy-agent": "^9.1.0", - "twzrd-x402-gate": "^0.8.4", "undici": "^8.5.0", "viem": "^2.39.3" }, @@ -44,15 +43,15 @@ "engines": { "node": ">=22" }, + "optionalDependencies": { + "twzrd-x402-gate": "^0.8.6" + }, "peerDependencies": { "openclaw": ">=2025.1.0" }, "peerDependenciesMeta": { "openclaw": { "optional": true - }, - "twzrd-x402-gate": { - "optional": true } } }, @@ -131,29 +130,6 @@ } } }, - "node_modules/@emnapi/core": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.1.tgz", - "integrity": "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/wasi-threads": "1.2.2", - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/runtime": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.1.tgz", - "integrity": "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, "node_modules/@emnapi/wasi-threads": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz", @@ -2707,6 +2683,7 @@ "resolved": "https://registry.npmjs.org/@solana/kit/-/kit-5.5.1.tgz", "integrity": "sha512-irKUGiV2yRoyf+4eGQ/ZeCRxa43yjFEL1DUI5B0DkcfZw3cr0VJtVJnrG8OtVF01vT0OUfYOcUn6zJW5TROHvQ==", "license": "MIT", + "peer": true, "dependencies": { "@solana/accounts": "5.5.1", "@solana/addresses": "5.5.1", @@ -3497,6 +3474,7 @@ "resolved": "https://registry.npmjs.org/@solana/sysvars/-/sysvars-5.5.1.tgz", "integrity": "sha512-k3Quq87Mm+geGUu1GWv6knPk0ALsfY6EKSJGw9xUJDHzY/RkYSBnh0RiOrUhtFm2TDNjOailg8/m0VHmi3reFA==", "license": "MIT", + "peer": true, "dependencies": { "@solana/accounts": "5.5.1", "@solana/codecs": "5.5.1", @@ -3603,132 +3581,6 @@ } } }, - "node_modules/@solana/web3.js": { - "version": "1.98.4", - "resolved": "https://registry.npmjs.org/@solana/web3.js/-/web3.js-1.98.4.tgz", - "integrity": "sha512-vv9lfnvjUsRiq//+j5pBdXig0IQdtzA0BRZ3bXEP4KaIyF1CcaydWqgyzQgfZMNIsWNWmG+AUHwPy4AHOD6gpw==", - "license": "MIT", - "optional": true, - "dependencies": { - "@babel/runtime": "^7.25.0", - "@noble/curves": "^1.4.2", - "@noble/hashes": "^1.4.0", - "@solana/buffer-layout": "^4.0.1", - "@solana/codecs-numbers": "^2.1.0", - "agentkeepalive": "^4.5.0", - "bn.js": "^5.2.1", - "borsh": "^0.7.0", - "bs58": "^4.0.1", - "buffer": "6.0.3", - "fast-stable-stringify": "^1.0.0", - "jayson": "^4.1.1", - "node-fetch": "^2.7.0", - "rpc-websockets": "^9.0.2", - "superstruct": "^2.0.2" - } - }, - "node_modules/@solana/web3.js/node_modules/@noble/curves": { - "version": "1.9.7", - "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.7.tgz", - "integrity": "sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw==", - "license": "MIT", - "optional": true, - "dependencies": { - "@noble/hashes": "1.8.0" - }, - "engines": { - "node": "^14.21.3 || >=16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@solana/web3.js/node_modules/@noble/hashes": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", - "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", - "license": "MIT", - "optional": true, - "engines": { - "node": "^14.21.3 || >=16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@solana/web3.js/node_modules/@solana/codecs-core": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@solana/codecs-core/-/codecs-core-2.3.0.tgz", - "integrity": "sha512-oG+VZzN6YhBHIoSKgS5ESM9VIGzhWjEHEGNPSibiDTxFhsFWxNaz8LbMDPjBUE69r9wmdGLkrQ+wVPbnJcZPvw==", - "license": "MIT", - "optional": true, - "dependencies": { - "@solana/errors": "2.3.0" - }, - "engines": { - "node": ">=20.18.0" - }, - "peerDependencies": { - "typescript": ">=5.3.3" - } - }, - "node_modules/@solana/web3.js/node_modules/@solana/codecs-numbers": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@solana/codecs-numbers/-/codecs-numbers-2.3.0.tgz", - "integrity": "sha512-jFvvwKJKffvG7Iz9dmN51OGB7JBcy2CJ6Xf3NqD/VP90xak66m/Lg48T01u5IQ/hc15mChVHiBm+HHuOFDUrQg==", - "license": "MIT", - "optional": true, - "dependencies": { - "@solana/codecs-core": "2.3.0", - "@solana/errors": "2.3.0" - }, - "engines": { - "node": ">=20.18.0" - }, - "peerDependencies": { - "typescript": ">=5.3.3" - } - }, - "node_modules/@solana/web3.js/node_modules/@solana/errors": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@solana/errors/-/errors-2.3.0.tgz", - "integrity": "sha512-66RI9MAbwYV0UtP7kGcTBVLxJgUxoZGm8Fbc0ah+lGiAw17Gugco6+9GrJCV83VyF2mDWyYnYM9qdI3yjgpnaQ==", - "license": "MIT", - "optional": true, - "dependencies": { - "chalk": "^5.4.1", - "commander": "^14.0.0" - }, - "bin": { - "errors": "bin/cli.mjs" - }, - "engines": { - "node": ">=20.18.0" - }, - "peerDependencies": { - "typescript": ">=5.3.3" - } - }, - "node_modules/@solana/web3.js/node_modules/base-x": { - "version": "3.0.11", - "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.11.tgz", - "integrity": "sha512-xz7wQ8xDhdyP7tQxwdteLYeFfS68tSMNCZ/Y37WJ4bhGfKPpqEIlmIyueQHqOyoPhE6xNUqjzRr8ra0eF9VRvA==", - "license": "MIT", - "optional": true, - "dependencies": { - "safe-buffer": "^5.0.1" - } - }, - "node_modules/@solana/web3.js/node_modules/bs58": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz", - "integrity": "sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==", - "license": "MIT", - "optional": true, - "dependencies": { - "base-x": "^3.0.2" - } - }, "node_modules/@standard-schema/spec": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", @@ -3896,6 +3748,7 @@ "integrity": "sha512-dzHeT2gySzZtLDsuqxU9AkYgIsQoHAHtRBpOqM+Ofzx1Bwrd2RcCjQJ+6iQbsHOIR6NS33bF2W1k3blN1zLDrA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.62.0", "@typescript-eslint/types": "8.62.0", @@ -4219,6 +4072,7 @@ "resolved": "https://registry.npmjs.org/@x402/fetch/-/fetch-2.17.0.tgz", "integrity": "sha512-iy6ar1JTgs4gzdlc2q2Z7nuL5keBkQhoD4v5niVlTwLoivVvHnAA0p/wK5WTWRrJ1b+el8pthkjFS0Jc3nMIIg==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@x402/core": "~2.17.0" } @@ -4228,6 +4082,7 @@ "resolved": "https://registry.npmjs.org/@x402/svm/-/svm-2.17.0.tgz", "integrity": "sha512-BXSaLBBoP8bfBAKDdwg7c2UYuTfbZOB7gZByCK3LWkROqmTnqxQlQ683urfpYwbvlK0EbZ3TIcqR/Rx42EuVzw==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@solana-program/compute-budget": "^0.11.0", "@solana-program/token": "^0.9.0", @@ -4278,6 +4133,7 @@ "integrity": "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -4593,6 +4449,7 @@ "hasInstallScript": true, "license": "MIT", "optional": true, + "peer": true, "dependencies": { "node-gyp-build": "^4.3.0" }, @@ -4902,6 +4759,7 @@ "integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==", "hasInstallScript": true, "license": "MIT", + "peer": true, "bin": { "esbuild": "bin/esbuild" }, @@ -4956,6 +4814,7 @@ "integrity": "sha512-6lVbcqSodALYo+4ELD0heG6lFiFxnLMuLkiMi2qV8LMp54N8tE8FT1GMH+ev4Ti00nFjNze2+Su6DsV5OQW3Dg==", "dev": true, "license": "MIT", + "peer": true, "workspaces": [ "packages/*" ], @@ -5227,14 +5086,6 @@ "license": "MIT", "optional": true }, - "node_modules/fastestsmallesttextencoderdecoder": { - "version": "1.0.22", - "resolved": "https://registry.npmjs.org/fastestsmallesttextencoderdecoder/-/fastestsmallesttextencoderdecoder-1.0.22.tgz", - "integrity": "sha512-Pb8d48e+oIuY4MaM64Cd7OW1gt4nxCHs7/ddPPZ/Ic3sg8yVGM7O9wDvZ7us6ScaUupzM+pfBolwtYhN1IxBIw==", - "license": "CC0-1.0", - "optional": true, - "peer": true - }, "node_modules/fdir": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", @@ -5692,18 +5543,6 @@ "license": "MIT", "optional": true }, - "node_modules/jiti": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", - "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "bin": { - "jiti": "lib/jiti-cli.mjs" - } - }, "node_modules/joycon": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz", @@ -6682,6 +6521,7 @@ "integrity": "sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@hono/node-server": "^1.19.9", "ajv": "^8.17.1", @@ -7695,6 +7535,7 @@ "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.1", @@ -8103,6 +7944,7 @@ "integrity": "sha512-7dYm06A945mXuIk/5HUlSjeyIYChW8vCEiU2dkOKKqJJzwAWxTkCc91Eqbz7TgODh2rtFFKWI/fekowWHOkmjQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@grammyjs/types": "3.27.3", "abort-controller": "^3.0.0", @@ -8176,6 +8018,7 @@ "integrity": "sha512-2NFaIyNVgJmBs/ecmtGzlmluTFs5cHEWGTdu0t1HBwYzoGXOL5nUQBRMXsXWla5i4KkG//QMzVP88m1+I3fdAQ==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=16.9.0" } @@ -9913,6 +9756,7 @@ "integrity": "sha512-xamtWoB1EshgjpmlXd7GGm2VfdDtw1+rD8uhry8pSNW3If6S8E0m2T2+orSKeZXEn/aPJMviCpDBA65WJt8zhg==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=22.19.0" } @@ -10147,6 +9991,7 @@ "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", "dev": true, "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } @@ -10337,6 +10182,7 @@ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -10386,6 +10232,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "nanoid": "^3.3.12", "picocolors": "^1.1.1", @@ -11053,24 +10900,12 @@ "fsevents": "~2.3.3" } }, - "node_modules/type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, "node_modules/twzrd-x402-gate": { "version": "0.8.6", "resolved": "https://registry.npmjs.org/twzrd-x402-gate/-/twzrd-x402-gate-0.8.6.tgz", "integrity": "sha512-bdul9FcdKhFWuLXSWJad+za+gFETQwUMZSyCrnt358XmELKXVOY9hkdOEgcmzMEzPkBON6LCr+RZU61JpjOJ6Q==", "license": "MIT", + "optional": true, "bin": { "twzrd-safe-fetch": "bin/twzrd-safe-fetch.js" }, @@ -11102,11 +10937,25 @@ } } }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/typescript": { "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -11178,6 +11027,7 @@ "hasInstallScript": true, "license": "MIT", "optional": true, + "peer": true, "dependencies": { "node-gyp-build": "^4.3.0" }, @@ -11282,6 +11132,7 @@ "integrity": "sha512-BuJcQK/56NQTWDGn4ABea3q4SSBdNPWwNZKTkkUpcMPnLoquSYH8llRtSUIgoL1KSCpHt5eghLShn50mH36y7Q==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.4", @@ -11537,6 +11388,7 @@ "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz", "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==", "license": "MIT", + "peer": true, "engines": { "node": ">=10.0.0" }, @@ -11553,24 +11405,6 @@ } } }, - "node_modules/yaml": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", - "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", - "dev": true, - "license": "ISC", - "optional": true, - "peer": true, - "bin": { - "yaml": "bin.mjs" - }, - "engines": { - "node": ">= 14.6" - }, - "funding": { - "url": "https://github.com/sponsors/eemeli" - } - }, "node_modules/yn": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", diff --git a/package.json b/package.json index 8b0b7d3d..58850017 100644 --- a/package.json +++ b/package.json @@ -94,19 +94,18 @@ "@x402/svm": "^2.9.0", "axios": "^1.18.1", "https-proxy-agent": "^9.1.0", - "twzrd-x402-gate": "^0.8.4", "undici": "^8.5.0", "viem": "^2.39.3" }, + "optionalDependencies": { + "twzrd-x402-gate": "^0.8.6" + }, "peerDependencies": { "openclaw": ">=2025.1.0" }, "peerDependenciesMeta": { "openclaw": { "optional": true - }, - "twzrd-x402-gate": { - "optional": true } }, "devDependencies": { diff --git a/src/proxy.ts b/src/proxy.ts index ea17a941..6d218280 100644 --- a/src/proxy.ts +++ b/src/proxy.ts @@ -1996,8 +1996,10 @@ export async function startProxy(options: ProxyOptions): Promise { /** * Default-on TWZRD AutoGate for the internal x402 client (Fork-1 seat). - * Missing package = soft-skip so forks can omit the optional gate. Any other - * install failure fails closed: boot aborts instead of paying unguarded. + * Missing optional package twzrd-x402-gate = soft-skip so forks can omit it. + * Soft-skip only when the resolution error names twzrd-x402-gate itself — + * missing transitive deps (or any other failure) fail closed so the proxy + * never boots with unguarded payments. * Kill: TWZRD_AUTO_GATE=0 or TWZRD_GATE_ENABLED=false (handled inside the gate). */ async function installTwzrdAutoGateOnClient(client: x402Client): Promise { @@ -2007,13 +2009,15 @@ async function installTwzrdAutoGateOnClient(client: x402Client): Promise { } catch (err) { const code = (err as { code?: string } | null)?.code; const msg = err instanceof Error ? err.message : String(err); - if ( + const missingModule = code === "ERR_MODULE_NOT_FOUND" || code === "MODULE_NOT_FOUND" || - /Cannot find module/i.test(msg) - ) { + /Cannot find module/i.test(msg) || + /Cannot find package/i.test(msg); + // Only soft-skip when the absent package is the gate itself. + if (missingModule && /['"]twzrd-x402-gate['"]/i.test(msg)) { console.warn( - "[ClawRouter] twzrd-x402-gate not available — payments unguarded. npm i twzrd-x402-gate@^0.8.4", + "[ClawRouter] twzrd-x402-gate not available — payments unguarded. npm i twzrd-x402-gate@^0.8.6", ); return; } @@ -2039,7 +2043,7 @@ async function installTwzrdAutoGateOnClient(client: x402Client): Promise { registerExactEvmScheme(x402, { signer: evmSigner }); // TWZRD Fork-1 default seat: pre-spend gate on every payment before sign. - // Default ON when twzrd-x402-gate is installed (dependency). Kill switch: + // Default ON when twzrd-x402-gate is installed (optionalDependency). Kill switch: // TWZRD_AUTO_GATE=0 | TWZRD_GATE_ENABLED=false // Base/EVM payments: observe mode (allow; Solana corpus does not score EVM). // Solana payments: full wash/preflight. Seat identity stamps X-Twzrd-Caller. diff --git a/src/twzrd-autogate.test.ts b/src/twzrd-autogate.test.ts index 150c2847..c78607de 100644 --- a/src/twzrd-autogate.test.ts +++ b/src/twzrd-autogate.test.ts @@ -44,7 +44,33 @@ describe("TWZRD AutoGate seat (Fork-1)", () => { gateOnCanSpend: false, unsupportedNetworkMode: "observe", }); - // install wraps registrar + installs at least one hook path + // Registrar must actually be invoked — typeof alone is true pre-install. + expect(hooks.length).toBeGreaterThan(0); expect(typeof client.onBeforePaymentCreation).toBe("function"); }); + + it("soft-skip matcher only accepts missing twzrd-x402-gate itself", () => { + // Mirror installTwzrdAutoGateOnClient catch predicate (proxy.ts). + const isSoftSkip = (code: string | undefined, msg: string): boolean => { + const missingModule = + code === "ERR_MODULE_NOT_FOUND" || + code === "MODULE_NOT_FOUND" || + /Cannot find module/i.test(msg) || + /Cannot find package/i.test(msg); + return missingModule && /['"]twzrd-x402-gate['"]/i.test(msg); + }; + expect( + isSoftSkip( + "ERR_MODULE_NOT_FOUND", + "Cannot find package 'twzrd-x402-gate' imported from /app/proxy.js", + ), + ).toBe(true); + expect( + isSoftSkip( + "ERR_MODULE_NOT_FOUND", + "Cannot find package 'some-transitive-dep' imported from /app/node_modules/twzrd-x402-gate/index.js", + ), + ).toBe(false); + expect(isSoftSkip(undefined, "boom unrelated")).toBe(false); + }); }); From c0852f07323e149d81865f387c1797500c937a2c Mon Sep 17 00:00:00 2001 From: twzrd-sol Date: Wed, 29 Jul 2026 21:28:10 +0000 Subject: [PATCH 4/4] fix(lockfile): complete package-lock for clean npm ci under Node 22 The previous tip had an incomplete lockfile that failed npm ci on Node 22.22.2 / npm 10.9.4. Regenerate it with npm install --package-lock-only under the target toolchain. No source or test changes. Gate logic, the optionalDependencies ^0.8.6 pin, and the soft-skip matcher remain unchanged. Verified: npm ci; focused AutoGate tests (4/4); npm run typecheck; git diff --check. --- package-lock.json | 221 +++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 197 insertions(+), 24 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9ca98675..5ace54e6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -130,13 +130,39 @@ } } }, + "node_modules/@emnapi/core": { + "version": "1.11.3", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.3.tgz", + "integrity": "sha512-zLpS5asjEb7lq8jYLq37N6XKaE41DIexlY1rF/z4/tIl3wo13Sqm28fRyfIsKZD+NZ8mM5RoKkpW/rBcuoSZSg==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.3", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.11.3", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.3.tgz", + "integrity": "sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@emnapi/wasi-threads": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz", - "integrity": "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.3.tgz", + "integrity": "sha512-ELEBe8PsLvvJ6QMr0zLt8ffvOHW/dc1m3CEzNMg7aJUv3bMaoDtw2TXyDAwkYBuroxxuHEwhRTLJSe5sya547g==", "dev": true, "license": "MIT", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.4.0" } @@ -1863,6 +1889,40 @@ "node": "^20.19.0 || >=22.12.0" } }, + "node_modules/@rolldown/binding-wasm32-wasi/node_modules/@emnapi/core": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.1.tgz", + "integrity": "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.2", + "tslib": "^2.4.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi/node_modules/@emnapi/runtime": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.1.tgz", + "integrity": "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi/node_modules/@emnapi/wasi-threads": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz", + "integrity": "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@rolldown/binding-win32-arm64-msvc": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.1.3.tgz", @@ -2683,7 +2743,6 @@ "resolved": "https://registry.npmjs.org/@solana/kit/-/kit-5.5.1.tgz", "integrity": "sha512-irKUGiV2yRoyf+4eGQ/ZeCRxa43yjFEL1DUI5B0DkcfZw3cr0VJtVJnrG8OtVF01vT0OUfYOcUn6zJW5TROHvQ==", "license": "MIT", - "peer": true, "dependencies": { "@solana/accounts": "5.5.1", "@solana/addresses": "5.5.1", @@ -3474,7 +3533,6 @@ "resolved": "https://registry.npmjs.org/@solana/sysvars/-/sysvars-5.5.1.tgz", "integrity": "sha512-k3Quq87Mm+geGUu1GWv6knPk0ALsfY6EKSJGw9xUJDHzY/RkYSBnh0RiOrUhtFm2TDNjOailg8/m0VHmi3reFA==", "license": "MIT", - "peer": true, "dependencies": { "@solana/accounts": "5.5.1", "@solana/codecs": "5.5.1", @@ -3581,6 +3639,132 @@ } } }, + "node_modules/@solana/web3.js": { + "version": "1.98.4", + "resolved": "https://registry.npmjs.org/@solana/web3.js/-/web3.js-1.98.4.tgz", + "integrity": "sha512-vv9lfnvjUsRiq//+j5pBdXig0IQdtzA0BRZ3bXEP4KaIyF1CcaydWqgyzQgfZMNIsWNWmG+AUHwPy4AHOD6gpw==", + "license": "MIT", + "optional": true, + "dependencies": { + "@babel/runtime": "^7.25.0", + "@noble/curves": "^1.4.2", + "@noble/hashes": "^1.4.0", + "@solana/buffer-layout": "^4.0.1", + "@solana/codecs-numbers": "^2.1.0", + "agentkeepalive": "^4.5.0", + "bn.js": "^5.2.1", + "borsh": "^0.7.0", + "bs58": "^4.0.1", + "buffer": "6.0.3", + "fast-stable-stringify": "^1.0.0", + "jayson": "^4.1.1", + "node-fetch": "^2.7.0", + "rpc-websockets": "^9.0.2", + "superstruct": "^2.0.2" + } + }, + "node_modules/@solana/web3.js/node_modules/@noble/curves": { + "version": "1.9.7", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.7.tgz", + "integrity": "sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw==", + "license": "MIT", + "optional": true, + "dependencies": { + "@noble/hashes": "1.8.0" + }, + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@solana/web3.js/node_modules/@noble/hashes": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "license": "MIT", + "optional": true, + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@solana/web3.js/node_modules/@solana/codecs-core": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@solana/codecs-core/-/codecs-core-2.3.0.tgz", + "integrity": "sha512-oG+VZzN6YhBHIoSKgS5ESM9VIGzhWjEHEGNPSibiDTxFhsFWxNaz8LbMDPjBUE69r9wmdGLkrQ+wVPbnJcZPvw==", + "license": "MIT", + "optional": true, + "dependencies": { + "@solana/errors": "2.3.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@solana/web3.js/node_modules/@solana/codecs-numbers": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@solana/codecs-numbers/-/codecs-numbers-2.3.0.tgz", + "integrity": "sha512-jFvvwKJKffvG7Iz9dmN51OGB7JBcy2CJ6Xf3NqD/VP90xak66m/Lg48T01u5IQ/hc15mChVHiBm+HHuOFDUrQg==", + "license": "MIT", + "optional": true, + "dependencies": { + "@solana/codecs-core": "2.3.0", + "@solana/errors": "2.3.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@solana/web3.js/node_modules/@solana/errors": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@solana/errors/-/errors-2.3.0.tgz", + "integrity": "sha512-66RI9MAbwYV0UtP7kGcTBVLxJgUxoZGm8Fbc0ah+lGiAw17Gugco6+9GrJCV83VyF2mDWyYnYM9qdI3yjgpnaQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "chalk": "^5.4.1", + "commander": "^14.0.0" + }, + "bin": { + "errors": "bin/cli.mjs" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@solana/web3.js/node_modules/base-x": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.11.tgz", + "integrity": "sha512-xz7wQ8xDhdyP7tQxwdteLYeFfS68tSMNCZ/Y37WJ4bhGfKPpqEIlmIyueQHqOyoPhE6xNUqjzRr8ra0eF9VRvA==", + "license": "MIT", + "optional": true, + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/@solana/web3.js/node_modules/bs58": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz", + "integrity": "sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==", + "license": "MIT", + "optional": true, + "dependencies": { + "base-x": "^3.0.2" + } + }, "node_modules/@standard-schema/spec": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", @@ -3748,7 +3932,6 @@ "integrity": "sha512-dzHeT2gySzZtLDsuqxU9AkYgIsQoHAHtRBpOqM+Ofzx1Bwrd2RcCjQJ+6iQbsHOIR6NS33bF2W1k3blN1zLDrA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.62.0", "@typescript-eslint/types": "8.62.0", @@ -4072,7 +4255,6 @@ "resolved": "https://registry.npmjs.org/@x402/fetch/-/fetch-2.17.0.tgz", "integrity": "sha512-iy6ar1JTgs4gzdlc2q2Z7nuL5keBkQhoD4v5niVlTwLoivVvHnAA0p/wK5WTWRrJ1b+el8pthkjFS0Jc3nMIIg==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@x402/core": "~2.17.0" } @@ -4082,7 +4264,6 @@ "resolved": "https://registry.npmjs.org/@x402/svm/-/svm-2.17.0.tgz", "integrity": "sha512-BXSaLBBoP8bfBAKDdwg7c2UYuTfbZOB7gZByCK3LWkROqmTnqxQlQ683urfpYwbvlK0EbZ3TIcqR/Rx42EuVzw==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@solana-program/compute-budget": "^0.11.0", "@solana-program/token": "^0.9.0", @@ -4133,7 +4314,6 @@ "integrity": "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -4449,7 +4629,6 @@ "hasInstallScript": true, "license": "MIT", "optional": true, - "peer": true, "dependencies": { "node-gyp-build": "^4.3.0" }, @@ -4759,7 +4938,6 @@ "integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==", "hasInstallScript": true, "license": "MIT", - "peer": true, "bin": { "esbuild": "bin/esbuild" }, @@ -4814,7 +4992,6 @@ "integrity": "sha512-6lVbcqSodALYo+4ELD0heG6lFiFxnLMuLkiMi2qV8LMp54N8tE8FT1GMH+ev4Ti00nFjNze2+Su6DsV5OQW3Dg==", "dev": true, "license": "MIT", - "peer": true, "workspaces": [ "packages/*" ], @@ -5086,6 +5263,14 @@ "license": "MIT", "optional": true }, + "node_modules/fastestsmallesttextencoderdecoder": { + "version": "1.0.22", + "resolved": "https://registry.npmjs.org/fastestsmallesttextencoderdecoder/-/fastestsmallesttextencoderdecoder-1.0.22.tgz", + "integrity": "sha512-Pb8d48e+oIuY4MaM64Cd7OW1gt4nxCHs7/ddPPZ/Ic3sg8yVGM7O9wDvZ7us6ScaUupzM+pfBolwtYhN1IxBIw==", + "license": "CC0-1.0", + "optional": true, + "peer": true + }, "node_modules/fdir": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", @@ -6521,7 +6706,6 @@ "integrity": "sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@hono/node-server": "^1.19.9", "ajv": "^8.17.1", @@ -7535,7 +7719,6 @@ "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.1", @@ -7944,7 +8127,6 @@ "integrity": "sha512-7dYm06A945mXuIk/5HUlSjeyIYChW8vCEiU2dkOKKqJJzwAWxTkCc91Eqbz7TgODh2rtFFKWI/fekowWHOkmjQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@grammyjs/types": "3.27.3", "abort-controller": "^3.0.0", @@ -8018,7 +8200,6 @@ "integrity": "sha512-2NFaIyNVgJmBs/ecmtGzlmluTFs5cHEWGTdu0t1HBwYzoGXOL5nUQBRMXsXWla5i4KkG//QMzVP88m1+I3fdAQ==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=16.9.0" } @@ -9756,7 +9937,6 @@ "integrity": "sha512-xamtWoB1EshgjpmlXd7GGm2VfdDtw1+rD8uhry8pSNW3If6S8E0m2T2+orSKeZXEn/aPJMviCpDBA65WJt8zhg==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=22.19.0" } @@ -9991,7 +10171,6 @@ "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", "dev": true, "license": "MIT", - "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } @@ -10182,7 +10361,6 @@ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -10232,7 +10410,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "nanoid": "^3.3.12", "picocolors": "^1.1.1", @@ -10955,7 +11132,6 @@ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -11027,7 +11203,6 @@ "hasInstallScript": true, "license": "MIT", "optional": true, - "peer": true, "dependencies": { "node-gyp-build": "^4.3.0" }, @@ -11132,7 +11307,6 @@ "integrity": "sha512-BuJcQK/56NQTWDGn4ABea3q4SSBdNPWwNZKTkkUpcMPnLoquSYH8llRtSUIgoL1KSCpHt5eghLShn50mH36y7Q==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.4", @@ -11388,7 +11562,6 @@ "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz", "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==", "license": "MIT", - "peer": true, "engines": { "node": ">=10.0.0" },