<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Skavio Engineering]]></title><description><![CDATA[Skavio Engineering]]></description><link>https://skavio-dev.hashnode.dev</link><image><url>https://cdn.hashnode.com/uploads/logos/6aed7af9b623060bb5e8a508/144669dc-06dc-4d3e-bbd2-d013f83ca6fb.png</url><title>Skavio Engineering</title><link>https://skavio-dev.hashnode.dev</link></image><generator>RSS for Node</generator><lastBuildDate>Sat, 07 Nov 2026 11:36:14 GMT</lastBuildDate><atom:link href="https://skavio-dev.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Five x402 payment offers an AI buyer should reject]]></title><description><![CDATA[Before connecting an autonomous agent to a funded wallet, give it something to say no to. HTTP 402 is a payment request, not an instruction to pay. An…]]></description><link>https://skavio-dev.hashnode.dev/five-x402-payment-offers-an-ai-buyer-should-reject</link><guid isPermaLink="true">https://skavio-dev.hashnode.dev/five-x402-payment-offers-an-ai-buyer-should-reject</guid><category><![CDATA[Web3]]></category><category><![CDATA[Security]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[ai agents]]></category><dc:creator><![CDATA[Skavio LLC]]></dc:creator><pubDate>Fri, 06 Nov 2026 08:41:07 GMT</pubDate><content:encoded><![CDATA[<p>Before connecting an autonomous agent to a funded wallet, give it something to say <strong>no</strong> to.</p>
<p>HTTP 402 is a payment request, not an instruction to pay. An x402-capable server can return a quote, and your agent can inspect that quote without signing or spending anything. The difficult part is not constructing a transaction. It is refusing terms that depart from your purchasing policy.</p>
<p><em>Disclosure: I work on Skavio LLC. Our public x402 endpoint is the example below. The preview flow is tested against production; no funded settlement is claimed here.</em></p>
<h2>A real, unpaid example</h2>
<p>Skavio's USDC transfer-verification endpoint returns an x402 v2 quote when called without payment:</p>
<pre><code class="language-bash">curl -i --max-time 15 \
 'https://getskavio.com/x402/v1/usdc/verify?tx_hash=0x98809948204426c574b6f0aabdec03baa996b5c43ce28cc9ca704fa5d07e592e&amp;network=base'
</code></pre>
<p>On November 6, 2026, the observed offer was 20,000 base units of USDC, or <strong>0.02 USDC</strong>, on Base (eip155:8453). The PAYMENT-REQUIRED header carries base64-encoded JSON payment terms. Prices may change, so the agent must evaluate each fresh quote.</p>
<p>The following five tests are more important than any slick "pay automatically" demo.</p>
<h2>Test 1: Wrong chain</h2>
<p>A quote asking for a payment on Ethereum mainnet is <strong>not</strong> equivalent to the same amount on Base.</p>
<p>Allow only the configured network for this specific service:</p>
<pre><code class="language-javascript">if (offer.network !== "eip155:8453") {
  throw new Error("Chain is not approved");
}
</code></pre>
<p>If you support several chains, configure which ones are allowed <strong>per service</strong>, not globally.</p>
<h2>Test 2: Wrong token</h2>
<p>On Base, the native Circle USDC contract address is:</p>
<pre><code class="language-text">0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913
</code></pre>
<p>Don't treat every token with the symbol USDC as equivalent. Compare the chain and the exact on-chain asset contract, case-insensitively for an EVM hex address.</p>
<pre><code class="language-javascript">const expectedAsset = "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913";
if (String(offer.asset).toLowerCase() !== expectedAsset) {
  throw new Error("Asset is not approved");
}
</code></pre>
<h2>Test 3: Different recipient</h2>
<p>A payment price can be correct while its destination is wrong. Your buyer should have a known, configured merchant or intermediary address for each tool.</p>
<pre><code class="language-javascript">const trustedMerchant = "0x62bb64e9ab0e8104def0d7845223580319d4c139";
if (String(offer.payTo).toLowerCase() !== trustedMerchant) {
  throw new Error("Recipient is not approved");
}
</code></pre>
<p>This address belongs to our example only. <strong>Never copy it as a universal x402 default.</strong></p>
<h2>Test 4: Excessive or ambiguous amount</h2>
<p>USDC has six decimals, so <strong>20,000 base units = 0.02 USDC</strong>. Do not compare decimal strings using floating point.</p>
<pre><code class="language-javascript">const capBaseUnits = 20_000n;
if (!/^[0-9]+$/.test(String(offer.amount))) {
  throw new Error("Invalid integer amount");
}
const amount = BigInt(offer.amount);
if (amount &lt;= 0n || amount &gt; capBaseUnits) {
  throw new Error("Price exceeds authorized cap");
}
</code></pre>
<p>Also check protocol version 2, the exact payment scheme and that <strong>exactly one</strong> acceptable payment option remains after filtering. Ambiguous options should not be resolved by "try the first".</p>
<p>A per-call cap is not a daily budget: a thousand approved 0.02 USDC calls would still spend <strong>20 USDC</strong>. Add a separate aggregate limit.</p>
<h2>Test 5: A retry after an uncertain payment</h2>
<p>This one can be expensive even when all the payment fields are correct.</p>
<p>Imagine a buyer signs a payment, the server completes the request, but the connection drops before the buyer receives the response. If the agent immediately pays again with a new request identity, the user may be charged twice.</p>
<p>Instead, persist the business request ID, offered terms, attempted payment and any receipt. Query or reconcile the previous settlement before another charge. Where supported, reuse a stable idempotency key.</p>
<p>Distinguish these events in logs:</p>
<table>
<thead>
<tr>
<th>Event</th>
<th>Evidence</th>
</tr>
</thead>
<tbody><tr>
<td>Offered</td>
<td>HTTP 402 plus checked payment requirements</td>
</tr>
<tr>
<td>Completed</td>
<td>The requested tool returned its result</td>
</tr>
<tr>
<td>Settled</td>
<td>Receipt and matching on-chain payment evidence</td>
</tr>
</tbody></table>
<p>An HTTP 402 proves none of the last two events. An HTTP 200 alone is also not proof of an independently verified settlement.</p>
<h2>A free five-minute test</h2>
<p>We published runnable <strong>Python and Node.js</strong> quote inspection clients with negative unit tests:</p>
<p><a href="https://github.com/skavio-eu/skavio-agent-tools/tree/main/examples/x402-safe-buyer">Skavio x402 safe buyer – code and tests</a></p>
<pre><code class="language-bash">git clone https://github.com/skavio-eu/skavio-agent-tools.git
cd skavio-agent-tools/examples/x402-safe-buyer

python3 preview_x402.py
node skavio-x402.mjs --preview
node --test
</code></pre>
<p>The preview works without an account, a wallet or a purchase. Our three Node.js unit tests passed and both clients fetched a real unpaid 402 offer. The optional wallet-enabled paid branch <strong>has not been settlement-tested with real mainnet funds</strong>.</p>
<p>For the original, longer walkthrough about payment quotes and spending policy, see our <a href="https://dev.to/skavioeu/before-an-ai-agent-pays-validate-an-x402-quote-on-base-4lpn">DEV Community tutorial</a>.</p>
<p><strong>Practical rule:</strong> Make your agent prove it can reject the wrong offer <em>before</em> teaching it to accept the right one.</p>
<hr />
<p><em>Skavio LLC develops the example API and maintains the linked GitHub code. This is an original technical explanation, not an independent endorsement or a paid-transaction success report.</em></p>
]]></content:encoded></item></channel></rss>