Simulate before you transact. PreFlight dry-runs blockchain transactions before they are broadcast and returns either a raw simulation result or an agent-friendly proceed/review/abort assessment.
PreFlight is pay-per-use for production paths: paid REST and paid MCP tool calls settle a small USDC payment through x402. The free /preview path exists for the web UI and local smoke testing.
| Surface | Payment | Purpose |
|---|---|---|
GET /health |
Free | Health check |
POST /preview |
Free | UI/local preview path |
POST /simulate |
x402 paid | REST simulation API |
POST /mcp initialize / tools/list |
Free | MCP discovery |
POST /mcp tools/call preflight_simulate |
x402 paid | Agent simulation tool |
POST /mcp tools/call preflight_assess |
x402 paid | Agent decision tool |
Supported transaction simulation chains:
- Ethereum mainnet:
chainId1 - Ethereum Sepolia:
chainId11155111 - Base mainnet:
chainId8453 - Polygon:
chainId137
- PreFlight simulates transactions; it does not broadcast them.
- x402 payment can run on Base Sepolia for testing. Transaction simulation supports Ethereum mainnet, Ethereum Sepolia, Base mainnet, and Polygon.
- USD gas cost is estimated from CoinGecko when available and falls back to approximate prices if unavailable.
- The
preflight_assessdecision is rule-based. It is a safety pre-check, not a full contract audit.
npm install
cp .env.example .env
npm startMinimum free-preview config:
TENDERLY_NODE_ACCESS_KEY=
PORT=3000
NODE_ENV=developmentPaid x402 config:
WALLET_ADDRESS= # receiving wallet
PRICE_PER_REQUEST=0.01
PAYMENT_NETWORK=base-sepolia
PAYMENT_FACILITATOR_URL= # optional; blank uses package defaultPaid smoke-test config:
PAYER_PRIVATE_KEY= # funded test wallet private key; never use a main wallet
PREFLIGHT_URL=http://localhost:3000For Base Sepolia x402 tests, the payer needs test USDC and gas on Base Sepolia.
Request:
{
"from": "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
"to": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"data": "0x",
"value": "0",
"chainId": 1
}Response:
{
"success": true,
"gasEstimate": 21000,
"nativeToken": "ETH",
"gasCostNative": "0.00042000",
"gasCostUSD": "1.0000",
"logs": [],
"revertReason": null,
"simulatedAt": "2026-05-17T00:00:00.000Z"
}{
"from": "0x1ad4C9d7CEa32B44cd54260A52C57B6Ce4e0D6eB",
"to": "0x8E159C52Dc2823cB6728E26898A2bABC286BBAc7",
"data": "0x",
"value": "1000000000000000",
"chainId": 11155111
}This simulates a native Sepolia ETH transfer of 0.001 ETH. It does not broadcast the transfer.
Validation errors return 400 on /preview. Unpaid /simulate returns 402 before validation.
Takes the same transaction arguments and returns the same simulation object as text content in the MCP tool result. It is paid only when called through tools/call; initialize and tools/list are free.
Takes the same transaction arguments and returns:
{
"decision": "proceed",
"riskLevel": "low",
"reason": "The transaction simulation succeeded and the estimated cost is within the review thresholds.",
"recommendation": "Proceed if the transaction intent and recipient are correct.",
"simulation": {
"success": true,
"gasEstimate": 21000,
"nativeToken": "ETH",
"gasCostNative": "0.00042000",
"gasCostUSD": "1.0000",
"logs": [],
"revertReason": null,
"simulatedAt": "2026-05-17T00:00:00.000Z"
}
}Assessment rules:
- Failed simulation:
decision="abort",riskLevel="high" - Missing
gasEstimate:decision="review",riskLevel="medium" gasEstimate > 500000:decision="review",riskLevel="medium"gasCostUSD > 10:decision="review",riskLevel="medium"- Otherwise:
decision="proceed",riskLevel="low"
Run syntax checks:
node --check src/services/preflight.js
node --check src/routes/simulate.js
node --check src/mcp/server.js
node --check src/middleware/payment.jsRun deterministic assessment-rule tests:
npm run test:rulesRun paid REST smoke test:
npm run test:paidRun paid MCP assessment smoke test:
npm run test:mcp:simulate
npm run test:mcp:assesstest.http contains manual cases for:
GET /health- Free
/previewsuccess/fail - Free
/previewvalidation failures - Unpaid
/simulatereturning402 - Free MCP
initialize - Free MCP
tools/list - Unpaid MCP
preflight_simulatereturning402 - Unpaid MCP
preflight_assessreturning402
PreFlight's hosted /mcp endpoint is x402-paid. Claude Desktop does not pay x402 directly, so the demo uses a local MCP stdio bridge that pays the x402 challenge and forwards results back to Claude.
Architecture:
Claude Desktop -> local MCP bridge -> x402-paid PreFlight /mcp -> Tenderly
Start PreFlight:
npm startValidate the paid MCP path:
npm run test:mcp:assessAdd this to Claude Desktop config:
{
"mcpServers": {
"preflight": {
"command": "node",
"args": [
"D:\\br br patapim\\Preflight\\scripts\\claude-preflight-bridge.mjs"
],
"cwd": "D:\\br br patapim\\Preflight"
}
}
}Restart Claude Desktop after editing the config.
Example Claude prompt:
Use PreFlight to assess this transaction before broadcast:
from: 0x1ad4C9d7CEa32B44cd54260A52C57B6Ce4e0D6eB
to: 0x8E159C52Dc2823cB6728E26898A2bABC286BBAc7
data: 0x
value: 1000000000000000
chainId: 11155111
Tell me whether I should proceed, review, or abort.
Important: Claude is not paying x402 directly. The local MCP bridge pays using the configured test wallet.
src/
index.js Express app and route mounting
config.js Environment configuration
routes/simulate.js REST preview/simulate route handler
services/tenderly.js Tenderly RPC integration
services/ethPrice.js Native token USD pricing
services/preflight.js Shared pricing and assessment logic
middleware/payment.js x402 payment gating for paid REST/MCP calls
mcp/server.js MCP tools
public/index.html Vanilla web UI
scripts/ Paid and deterministic smoke tests
railway.toml is configured for Railway. Set the environment variables in Railway, use PAYMENT_NETWORK=base for production, and keep test private keys out of production environments.