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.802 / 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.
- No recovery phrase, wallet key, credential, or payment signature belongs in source.
- Use a dedicated low-balance wallet and an independent cumulative budget.
- Do not expose signing material to models, pull requests, logs, or Andromalius.
- Do not add a recovery hook that automatically retries an ambiguous payment.
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