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]