01 · 安装
一个 package,不需 API key。
Preflight verification 使用 IFF 的公开 production API,不需要 owner authentication。
npm install @ifandonlyif/x402-preflight
使用全局 fetch 与 WebCrypto。不持有 wallet key、不执行 payment,也不修改 402 response。
02 · 直接验证
先从比较开始。
这个 Node 20+ 范例只将 requirement 传给 IFF。URL 与 payment field 是 fixture:请改用 client 实际收到的 endpoint 与 x402 v2 envelope。它不会向 provider 请求,也不付款。
import { verify, VerifyRequestError } from "@ifandonlyif/x402-preflight";
// Demonstration data, not a request to buy this service.
const endpoint = "https://api.example.com/v1/quote";
const paymentRequired = {
x402Version: 2,
accepts: [{
scheme: "exact",
network: "eip155:8453",
asset: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
amount: "1000",
payTo: "0x1111111111111111111111111111111111111111",
maxTimeoutSeconds: 60,
}],
};
// Example application policy, NOT wrapFetch's default policy.
function decide(result) {
switch (result?.verdict) {
case "consistent":
return result.matches_last_observed === true
? { state: "consistent", action: "continue_to_own_policy" }
: { state: "unexpected_result", action: "hold" };
case "diverged":
return { state: "diverged", action: "hold" };
case "stale":
return {
state: result.matches_last_observed === true ? "stale_match"
: result.matches_last_observed === false ? "stale_mismatch"
: "stale_unknown",
action: "hold",
};
case "unobserved":
return { state: "unobserved", action: "hold" };
default:
return { state: "unexpected_result", action: "hold" };
}
}
async function preflightDecision(url, requirement) {
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), 3000);
try {
const result = await verify(url, requirement, {
fetch: (input, init) => fetch(input, {
...init, signal: controller.signal, redirect: "error",
}),
});
return decide(result);
} catch (error) {
if (error instanceof VerifyRequestError) {
return error.status === 429
? { state: "iff_rate_limited", action: "retry_later" }
: { state: "iff_request_failed", action: "hold", status: error.status };
}
return {
state: controller.signal.aborted ? "iff_timeout" : "iff_unavailable",
action: "hold",
};
} finally {
clearTimeout(timer);
}
}
// Log the decision only, not the requirement or raw error body.
console.log(await preflightDecision(endpoint, paymentRequired));
// No payment follows, including when the state is consistent.
3 秒 deadline 是 application code:verify() 没有内建 timeout 或 retry。此范例遇到任何 stale 或 unknown result 都会暂停。continue_to_own_policy 只代表比较通过;wallet、budget、merchant 与 payment rule 仍然适用。unknown fixture 通常返回 unobserved。
请使用不含 credentials、query 或 fragment 的完整 public HTTPS endpoint path,以及收到的 envelope(不可用猜测的 price 或 address)。解码 PAYMENT-REQUIRED header 请用 decodePaymentRequiredHeader();browser 需 provider 的 CORS policy 允许读取该 header。
03 · 不强制执行的评估
在既有 policy 旁观察。
在不改变 payment decision 的情况下收集 comparison outcome。Shadow evaluation 是 application-side 对 verify() 的调用,不是 SDK mode 或 wrapFetch() default。
import { verify, VerifyRequestError } from "@ifandonlyif/x402-preflight";
export async function shadowPreflight(url, paymentRequired, record = console.info) {
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), 3000);
let event;
try {
const result = await verify(url, paymentRequired, {
fetch: (input, init) => fetch(input, {
...init, signal: controller.signal, redirect: "error",
}),
});
const verdicts = ["consistent", "diverged", "stale", "unobserved"];
event = {
mode: "shadow",
state: verdicts.includes(result?.verdict) ? result.verdict : "unexpected_result",
matches_last_observed: typeof result?.matches_last_observed === "boolean"
? result.matches_last_observed : null,
};
} catch (error) {
event = {
mode: "shadow",
state: error instanceof VerifyRequestError && error.status === 429
? "iff_rate_limited" : controller.signal.aborted ? "iff_timeout" : "iff_unavailable",
};
} finally {
clearTimeout(timer);
}
// Do not expose the URL, requirement, API error body or wallet details.
try { await record(event); } catch { /* Telemetry must not change policy. */ }
}
// At the point where your client already has the actual requirement:
// void shadowPreflight(exactEndpointUrl, actualPaymentRequired);
// Keep the existing payment decision separate; this helper never authorizes it.
只进行一次 IFF comparison request,不向 provider request。IFF 通过 HTTPS 接收提交的 requirement;helper 只限制写入 log 的内容。启用 DISCOVERY_ENABLED 时,unobserved URL 可能保留为 discovery candidate;requirement 不会存储。
04 · Payment wrapper
了解 wrapper 放行的内容。
封装未付款的 base fetch,再交给 payment library。封装已启用 payment 的 fetch 可能使比较太晚执行。
import {
wrapFetch, X402DivergenceError, X402StaleMismatchError,
} from "@ifandonlyif/x402-preflight";
const preflightFetch = wrapFetch(fetch, {
onDiverged: "throw",
onStale: "strict",
onUnobserved: "warn",
});
export async function fetchWithPreflight(url, init) {
try {
return await preflightFetch(url, { ...init, redirect: "error" });
} catch (error) {
if (error instanceof X402DivergenceError) {
console.warn("IFF requirement diverged; payment flow stopped.");
} else if (error instanceof X402StaleMismatchError) {
console.warn("IFF stale evidence mismatched; payment flow stopped.");
}
// Do not turn a rejected preflight into a bare-fetch/payment retry.
throw error;
}
}
// Give fetchWithPreflight to your payment library as its base fetch.
// This snippet alone does not send a request or enable payments.
onDiverged- 预设为
throw。mismatch 会在 payment logic 继续前停止 wrapper。 onStale- 预设为
strict。stale mismatch 会 throw;stale matching evidence 会警告。 onUnobserved- 预设为
warn。missing evidence 会显示,但不标为 unsafe。
在 v0.2.0,IFF network error、timeout、429 或其他非成功 response 都会原样放行原始 402,且不警告。非 402 与无法解码的 402 也会放行。没有内建 deadline 或自动 retry。若服务不可用时必须暂停流程,请使用直接验证范例。
比较获接受时,原始 response body 仍可读取;divergence 或 stale-mismatch error 会 reject fetch promise。stale result 缺少 matches_last_observed 时只警告。wrapper 不验证 API result 内的 cryptographic proof。
05 · MCP
通过 MCP 连接 agent。
三种各自独立的 MCP 接入方式。
公开 HTTP alpha
13 个匿名、只读 tools(Streamable HTTP),用于 evidence、fingerprint、transparency log 与 receipt check。返回 stateless JSON,无 authentication、OAuth 或 SSE session。verification 在托管服务执行。不付款,也不公开 submit_discovery_candidate。
https://iff-mcp-api-production.up.railway.app/mcp/iff
公开 npm command
package 包含 stdio command iff-x402-preflight-mcp,只有一个 tool:verify_x402_endpoint。
npx --package=@ifandonlyif/x402-preflight \
iff-x402-preflight-mcp
本地 Go 内部 alpha 预览
完整 Go stdio server 是内部 alpha。源代码与二进制文件未发行,也不在 npm package、托管 HTTP service 或公开 Apostille repository 中。
iff-mcp:15 个 tools,用于公开 evidence、requirement comparison、transparency log data,以及本地验证 STH、inclusion、consistency 与 Service Receipt。不付款。apostille-mcp:20 个 tools,用于 offline verification,以及可选的 registration、本地签名、submission 与私有 ERC-8004 流程。authenticated action 需明确配置与 operator 指定的 key file。信任 verification 需独立取得且完全匹配的 issuer/key pin。
06 · Verdict 与失败处理
四种 state,没有隐藏 score。
SDK 返回 API vocabulary,不加 trust label。
consistent收到的 requirement 与 comparison window 相符。
divergedamount 或 payee fingerprint 与 observation 不同。
staleevidence 存在但已超出 freshness window。
unobservedIFF 没有可供比较的 fingerprint observation。
| 结果 | 直接验证范例 | 预设 wrapFetch |
|---|---|---|
consistent | 仅继续交由你自身的 policy 决定。 | 返回原始 402。 |
diverged | 暂停;检查观测到的变更。 | 掷出 X402DivergenceError. |
stale + match | 暂停,等候新鲜的 evidence。 | 警告并返回原始 402。 |
stale + mismatch | 暂停;stale 不会消除 mismatch。 | 掷出 X402StaleMismatchError. |
unobserved | 暂停;没有可比较的 evidence 并非风险 verdict。 | 警告并返回原始 402。 |
| IFF timeout/网络失败 | 暂停;回报 verification 不可用。 | 失败后返回原始 402;没有内建 timeout。 |
IFF 429 | 依你的 rate-limit policy 稍后重试。 | 返回原始 402,不会警告。 |
429、network error 与 IFF 5xx 不是 divergence 的证据。请勿连续重试;直接使用 HTTP API 时遵循 Retry-After,或采用有上限的 backoff、jitter 与 retry 次数限制。VerifyRequestError 提供 status 与 body,不含 header。400/422 请先修正再重试。这些范例不执行自动 retry。
强制套用 policy 前,请以 fixture 重播全部七种情况(含两种 stale),并将 API outage 与 endpoint evidence 分开判读。查看 v0.2.0 版本的实作.
07 · 界线
evidence 是输入,不是保证。
- SDK 绝不提交 paid canary transaction。
- match 不保证 payment 后 delivery,也不是 payment-safety 主张。没有 composite score。
- monitor signature 显示 provenance 与 tamper evidence,不是 attestation,也不证明 trusted execution。
- Service Receipt v2 是 core preflight flow 以外的实验性 direct-API 展示(local verifier,英文)。它不是 x402 payment receipt,不证明 payment、service delivery、caller identity、remote execution、result correctness 或 safety。
- payment policy 与风险由 application 负责。
问题或安全性回报: [email protected]