<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Mark Zhurbin</title>
    <description>The latest articles on DEV Community by Mark Zhurbin (@0rkz).</description>
    <link>https://dev.to/0rkz</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3999212%2F93bdae03-e203-41d2-a5ac-e0686d5deac1.png</url>
      <title>DEV Community: Mark Zhurbin</title>
      <link>https://dev.to/0rkz</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/0rkz"/>
    <language>en</language>
    <item>
      <title>Verify before your agent acts: a trust check for x402 data feeds (in one npm install)</title>
      <dc:creator>Mark Zhurbin</dc:creator>
      <pubDate>Tue, 23 Jun 2026 18:05:35 +0000</pubDate>
      <link>https://dev.to/0rkz/verify-before-your-agent-acts-a-trust-check-for-x402-data-feeds-in-one-npm-install-4eco</link>
      <guid>https://dev.to/0rkz/verify-before-your-agent-acts-a-trust-check-for-x402-data-feeds-in-one-npm-install-4eco</guid>
      <description>&lt;h3&gt;
  
  
  Your agent paid for the data. That doesn't mean the bytes are real.
&lt;/h3&gt;

&lt;p&gt;Autonomous agents are starting to &lt;em&gt;pay&lt;/em&gt; for data — per call, in stablecoins, over &lt;a href="https://x402.org" rel="noopener noreferrer"&gt;x402&lt;/a&gt;. That solves billing. It doesn't solve trust. A &lt;code&gt;200 OK&lt;/code&gt; from a paid endpoint tells you the money moved. It tells you nothing about whether the bytes you got back are the bytes the publisher actually signed — or whether a proxy, a cache, or a man-in-the-middle rewrote them on the way to your agent.&lt;/p&gt;

&lt;p&gt;If your agent is about to &lt;em&gt;act&lt;/em&gt; on that data — release funds, open a position, file a report, trigger a workflow — "I paid for it" is not the bar. "I can prove it's intact and I know who stands behind it" is.&lt;/p&gt;

&lt;p&gt;This is a 5-minute walkthrough of doing that check before your agent acts, using an open MIT kit. No token, no signup, no API key.&lt;/p&gt;

&lt;h3&gt;
  
  
  See it first — one command, no wallet
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx @foreseal/demo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This runs entirely offline. It walks an agent through a handful of payloads — a genuine signed one, a byte-tampered one, a forged-key one, one with the receipt stripped off, one with the domain rewritten — and prints &lt;strong&gt;ACT&lt;/strong&gt; or &lt;strong&gt;REFUSE&lt;/strong&gt; for each. The point: a verifier that fails &lt;em&gt;closed&lt;/em&gt;. Tampered, forged, or missing-receipt → the agent refuses. No real money is involved; it's the mechanism, distilled.&lt;/p&gt;

&lt;h3&gt;
  
  
  How the receipt works
&lt;/h3&gt;

&lt;p&gt;Every paid response from the &lt;a href="https://x402.payperbyte.io" rel="noopener noreferrer"&gt;PayPerByte&lt;/a&gt; gateway carries an&lt;br&gt;
&lt;code&gt;X-BYTE-Attestation&lt;/code&gt; header: an &lt;a href="https://eips.ethereum.org/EIPS/eip-712" rel="noopener noreferrer"&gt;EIP-712&lt;/a&gt; &lt;code&gt;PayloadAttestation&lt;/code&gt;.&lt;br&gt;
The recipe is published, machine-readable, at &lt;code&gt;/.well-known/agent.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;keccak256&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;responseBody&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;payloadHash&lt;/span&gt; &lt;span class="nx"&gt;AND&lt;/span&gt; &lt;span class="nf"&gt;recoverTypedDataAddress&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;PayloadAttestation&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;signature&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;attester&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two legs. The &lt;strong&gt;hash&lt;/strong&gt; leg proves the bytes weren't altered. The &lt;strong&gt;signer&lt;/strong&gt; leg proves the receipt was&lt;br&gt;
issued by the key you expect — not self-asserted by whoever sent the response. You check both, locally,&lt;br&gt;
before acting. (One deliberate detail that looks like a bug but isn't: payments settle in USDC on &lt;strong&gt;Base&lt;/strong&gt;, but the attestation's EIP-712 domain is anchored on &lt;strong&gt;Arbitrum, chainId 421614&lt;/strong&gt;. Settlement rail and trust anchor are intentionally separate.)&lt;/p&gt;
&lt;h3&gt;
  
  
  Wire it into your agent
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm i @payperbyte/sdk@^0.1.2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;blockquote&gt;
&lt;p&gt;Version matters: the full hash &lt;strong&gt;and&lt;/strong&gt; signer check (&lt;code&gt;verify&lt;/code&gt; / &lt;code&gt;verifyAttestation&lt;/code&gt; /&lt;br&gt;
&lt;code&gt;verifyFromGatewayResponse&lt;/code&gt;) ships in &lt;strong&gt;0.1.2+&lt;/strong&gt;. Earlier &lt;code&gt;0.1.0&lt;/code&gt;/&lt;code&gt;0.1.1&lt;/code&gt; only export the hash-only&lt;br&gt;
&lt;code&gt;verifyPayload&lt;/code&gt;. Pin &lt;code&gt;^0.1.2&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The one-call path, for a response you just fetched from the gateway:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;verifyFromGatewayResponse&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ARBITRUM_SEPOLIA&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@payperbyte/sdk&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Pin this once from https://x402.payperbyte.io/.well-known/agent.json → receipt.attester&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;GATEWAY_ATTESTER&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;0x77c86a5367d941091a31BC97104609F2Db33C472&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getVerifiedData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;Uint8Array&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Uint8Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;arrayBuffer&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt; 
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;header&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;X-BYTE-Attestation&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;verdict&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;verifyFromGatewayResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;header&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;ARBITRUM_SEPOLIA&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;        &lt;span class="c1"&gt;// the attestation domain's chain (421614)&lt;/span&gt;
    &lt;span class="nx"&gt;GATEWAY_ATTESTER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;        &lt;span class="c1"&gt;// pin it — a self-asserted header can't prove itself&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;verdict&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;verified&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// fail-closed: tampered, forged, expired-key, or no receipt at all&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`refusing to act on &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;verdict&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// safe to act on&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That &lt;code&gt;GATEWAY_ATTESTER&lt;/code&gt; argument is not optional ceremony. If you don't pin the attester, the kit&lt;br&gt;
&lt;strong&gt;fails closed on purpose&lt;/strong&gt; — because the &lt;code&gt;publisher&lt;/code&gt; field inside an attestation header is attacker- controlled, so trusting it would let a forged response self-certify. Pin the address from the published recipe; then a forged header has nothing to stand on.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;Verdict&lt;/code&gt; you get back is explicit, so your logs tell you &lt;em&gt;why&lt;/em&gt; something was refused:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;Verdict&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;verified&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;       &lt;span class="c1"&gt;// hashMatch &amp;amp;&amp;amp; signerMatch === true — the one "safe to act?" bool&lt;/span&gt;
  &lt;span class="nl"&gt;hashMatch&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;      &lt;span class="c1"&gt;// bytes weren't altered&lt;/span&gt;
  &lt;span class="nl"&gt;signerMatch&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// null = no attestation present → fail-closed (never "pass on hash alone")&lt;/span&gt;
  &lt;span class="nl"&gt;recovered&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;    &lt;span class="c1"&gt;// the address the signature actually recovers to&lt;/span&gt;
  &lt;span class="nl"&gt;expired&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;        &lt;span class="c1"&gt;// ADVISORY ONLY — staleness is a freshness axis, not a provenance verdict&lt;/span&gt;
  &lt;span class="nl"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;          &lt;span class="c1"&gt;// human-readable, for post-mortems&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note &lt;code&gt;expired&lt;/code&gt; does &lt;strong&gt;not&lt;/strong&gt; flip &lt;code&gt;verified&lt;/code&gt; to false. A once-minted &lt;code&gt;now+300s&lt;/code&gt; deadline makes every aged feed look "expired"; that's a freshness question for your own policy, not a tamper verdict. Surface it, decide for yourself.&lt;/p&gt;

&lt;p&gt;If you're working from the lower-level pieces (raw bytes + the attested fields, or an on-chain event&lt;br&gt;
rather than a gateway header), &lt;code&gt;verify(input)&lt;/code&gt; takes the explicit struct, and &lt;code&gt;verifyPayload&lt;/code&gt; does the hash-only leg on its own. Same fail-closed contract: it throws nothing and always returns a &lt;code&gt;Verdict&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  Bonus: verify the &lt;em&gt;counterparty&lt;/em&gt;, not just the payload
&lt;/h3&gt;

&lt;p&gt;The same EIP-712 receipt pattern backs a live &lt;code&gt;$0.05&lt;/code&gt; counterparty screen — a signed go/no-go on an address+domain &lt;em&gt;before&lt;/em&gt; your agent releases funds. It's a paid feed; the verdict comes back as a signed ALLOW/WARN/BLOCK you verify the same way:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://x402.payperbyte.io/feeds/address-reputation &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'content-type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"domain":"payee-checkout-7x9q.example","address":"0xRecipient","amount":5000000,"chain":"base"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(With no payment header you get the HTTP 402 challenge back — that's the handshake, not an error. This is a &lt;em&gt;counterparty&lt;/em&gt; screen — "is it safe to send here?" — not a seller-reputation score.)&lt;/p&gt;

&lt;h3&gt;
  
  
  Where this is honest about itself
&lt;/h3&gt;

&lt;p&gt;This is early. It's dogfooded end-to-end; the only mainnet settlements so far are our own self-tests, and external adoption is exactly the open question we're publishing this to answer. What it &lt;em&gt;does&lt;/em&gt; prove is narrow and real: &lt;strong&gt;authenticity and tamper-evidence&lt;/strong&gt;, not correctness — your agent learns "these are exactly the bytes that were signed, by the key I expected," not "this data is true." It's MIT, USDC-only, no token, no new contracts to deploy. The verifier is the whole pitch: a small amount of code, hardened to fail closed, that you run before your agent acts.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Demo: &lt;code&gt;npx @foreseal/demo&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Kit: &lt;code&gt;npm i @payperbyte/sdk@^0.1.2&lt;/code&gt; · Gate (for publishers): &lt;code&gt;@foreseal/gate&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Recipe: &lt;code&gt;https://x402.payperbyte.io/.well-known/agent.json&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;MCP server (drive it from Claude/Cursor): &lt;code&gt;npx -y byte-mcp-server&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Disclosure: I build PayPerByte (machine name "BYTE Library").&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>web3</category>
      <category>security</category>
    </item>
  </channel>
</rss>
