繁中

TypeScript SDK · v0.2.0

在 payment wrapper 看到 requirement 前先驗證它。

將應用程式收到的 x402 v2 requirement 與 IFF 的獨立 observation 比較。 將 consistent、diverged、stale 與 unobserved 作為你自身 payment policy 的個別輸入。

  • Node 20+
  • 現代瀏覽器
  • ES module
  • TypeScript type
從 npm 安裝
npm install @ifandonlyif/x402-preflight

包含 browser/Node library、TypeScript declaration 與單一 tool 的 MCP command。

  1. 01收到 402你的 payment client 收到 requirement。
  2. 02解碼SDK 讀取 x402 v2 envelope。
  3. 03比較IFF 將它與已簽署的 observation 比較。
  4. 04套用 policy是否繼續由你的程式碼決定。

01 · 安裝

一個 package,不需 API key。

Preflight verification 使用 IFF 的公開 production API,不需要 owner authentication。

終端機
npm install @ifandonlyif/x402-preflight
Runtime contract

使用全域 fetch 與 WebCrypto。不持有 wallet key、不執行 payment,也不修改 402 response。

02 · 直接驗證

先從比較開始。

這個 Node 20+ 範例只將 requirement 傳給 IFF。URL 與 payment field 是 fixture:請改用 client 實際收到的 endpoint 與 x402 v2 envelope。它不會向 provider 請求,也不付款。

payTo 變更時該比較什麼

另存為 preflight.mjs · 以 node preflight.mjs 執行
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.
有界且明確的 policy

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。

可重用的 observation helper · 不會呼叫 payment client
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 可能使比較太晚執行。

TypeScript
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。
重要:此 wrapper 對 IFF error 採 fail-open。

在 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。

無需 global install 即可執行
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。

閱讀 Apostille MCP security model。

06 · Verdict 與失敗處理

四種 state,沒有隱藏 score。

SDK 回傳 API vocabulary,不加 trust label。

consistent

收到的 requirement 與 comparison window 相符。

diverged

amount 或 payee fingerprint 與 observation 不同。

stale

evidence 存在但已超出 freshness window。

unobserved

IFF 沒有可供比較的 fingerprint observation。

應用程式範例與 v0.2.0 wrapper 預設行為比較
結果直接驗證範例預設 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]