SAFE CLIENT / EXACT PAYMENT POLICY

Make the wallet match every term.
Abort when one field drifts.

This reference handoff uses the official x402 v2 client policy interface to admit one Andromalius product only. It fixes the resource, scheme, Base network, native-USDC asset, receiving wallet, atomic amount, and timeout before a separate wallet adapter can sign.

01 / PIN THE QUALIFIED CLIENT GENERATION

Start from exact dependencies.

These versions match the currently qualified Andromalius merchant generation. Review and requalify deliberate upgrades before using them in a protected release job.

npm install --save-exact \
  @x402/core@2.21.0 \
  @x402/evm@2.21.0 \
  @x402/fetch@2.21.0 \
  viem@2.55.8

02 / KEEP AUTHORITY OUTSIDE THE EXAMPLE

Inject a reviewed signer. Never paste wallet material.

The referenced isolated-signer.js module is intentionally not provided. Your organization must supply a reviewed, low-balance signer boundary with its own approval and cumulative-spend controls.

03 / FILTER BEFORE SIGNING

Allow one exact challenge audit.

The policy returns no payment option when any security-relevant field differs. The pre-creation hook separately binds the top-level resource URL before the wallet is asked to create a payment payload. The request body is a complete fixed synthetic challenge, so copying the example does not leave a placeholder that can consume a payment and then fail input validation. Andromalius audits the supplied JSON without fetching merchant.example.

import { x402Client, x402HTTPClient } from "@x402/core/client";
import { ExactEvmScheme } from "@x402/evm/exact/client";
import { wrapFetchWithPayment } from "@x402/fetch";
import { signer } from "./isolated-signer.js";

const allowed = Object.freeze({
  resource: "https://api.andromalius.io/v1/audit/challenge",
  scheme: "exact",
  network: "eip155:8453",
  asset: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
  payTo: "0x2ff7D9d400b27659F67EFd9E43D779D2bE5053c3",
  amount: "10000",
  maxTimeoutSeconds: 300,
});

const sameAddress = (left: string, right: string) =>
  left.toLowerCase() === right.toLowerCase();

const client = x402Client.fromConfig({
  schemes: [
    { network: allowed.network, client: new ExactEvmScheme(signer) },
  ],
  policies: [
    (version, requirements) =>
      version === 2
        ? requirements.filter(
            (requirement) =>
              requirement.scheme === allowed.scheme &&
              requirement.network === allowed.network &&
              sameAddress(requirement.asset, allowed.asset) &&
              sameAddress(requirement.payTo, allowed.payTo) &&
              requirement.amount === allowed.amount &&
              requirement.maxTimeoutSeconds <= allowed.maxTimeoutSeconds,
          )
        : [],
  ],
});

client.onBeforePaymentCreation(async ({ paymentRequired }) => {
  if (paymentRequired.resource.url !== allowed.resource) {
    return { abort: true, reason: "Resource URL is not allowlisted" };
  }
  return undefined;
});

const fetchWithPayment = wrapFetchWithPayment(fetch, client);
const response = await fetchWithPayment(allowed.resource, {
  method: "POST",
  headers: { "content-type": "application/json" },
  body: JSON.stringify({
    paymentRequired: {
      x402Version: 2,
      resource: {
        url: "https://merchant.example/v1/resource",
        description: "Fixed synthetic resource",
        mimeType: "application/json",
      },
      accepts: [
        {
          scheme: allowed.scheme,
          network: allowed.network,
          asset: allowed.asset,
          amount: allowed.amount,
          payTo: allowed.payTo,
          maxTimeoutSeconds: allowed.maxTimeoutSeconds,
          extra: {},
        },
      ],
    },
    expected: {
      scheme: allowed.scheme,
      network: allowed.network,
      asset: allowed.asset,
      payTo: allowed.payTo,
      maxAmountAtomic: allowed.amount,
      resourceUrl: "https://merchant.example/v1/resource",
      maxTimeoutSeconds: allowed.maxTimeoutSeconds,
    },
  }),
});

if (response.status !== 200) throw new Error("Paid report was not delivered");

const receipt = new x402HTTPClient(client).getPaymentSettleResponse(
  (name) => response.headers.get(name),
);
if (!receipt?.success || receipt.network !== allowed.network) {
  throw new Error("Settlement receipt is missing or inconsistent; do not retry");
}

const report = await response.json();

04 / TREAT AMBIGUITY AS RECONCILIATION

One attempt. One receipt. No blind retry.

A missing response does not prove that no payment occurred. Freeze the attempt and reconcile the wallet, facilitator, and Base transaction before deciding whether another call is safe.

This page neither connects a wallet nor authorizes a payment. The example is a reference boundary, not custody software or a substitute for independent review. Terms · Privacy · Acceptable use