<?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: TobiasBond</title>
    <description>The latest articles on DEV Community by TobiasBond (@tobiasbond).</description>
    <link>https://dev.to/tobiasbond</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3798468%2Fefa65006-7c20-4782-899b-f802726168f1.jpeg</url>
      <title>DEV Community: TobiasBond</title>
      <link>https://dev.to/tobiasbond</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/tobiasbond"/>
    <language>en</language>
    <item>
      <title>Anchor Error Codes: Complete Reference Guide (100–4200)</title>
      <dc:creator>TobiasBond</dc:creator>
      <pubDate>Mon, 09 Mar 2026 00:37:00 +0000</pubDate>
      <link>https://dev.to/tobiasbond/anchor-error-codes-complete-reference-guide-100-4200-1887</link>
      <guid>https://dev.to/tobiasbond/anchor-error-codes-complete-reference-guide-100-4200-1887</guid>
      <description>&lt;p&gt;If you've built on Solana with Anchor, you've seen errors like &lt;br&gt;
&lt;code&gt;Error Code: 2003&lt;/code&gt; with no clear explanation of what went wrong &lt;br&gt;
or how to fix it.&lt;/p&gt;

&lt;p&gt;This guide covers every official Anchor error code — what it &lt;br&gt;
means, why it happens, and how to fix it.&lt;/p&gt;


&lt;h2&gt;
  
  
  How Anchor Error Codes Are Structured
&lt;/h2&gt;

&lt;p&gt;Anchor errors follow a numbered system across 6 categories:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;100–103&lt;/strong&gt; — Instruction errors&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;1000–1002&lt;/strong&gt; — IDL errors
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;1500&lt;/strong&gt; — Event errors&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;2000–2029&lt;/strong&gt; — Constraint errors (most common)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;3000–3017&lt;/strong&gt; — Account errors&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;4000–4200&lt;/strong&gt; — State errors&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  Constraint Errors (2000–2029) — The Ones You'll Hit Most
&lt;/h2&gt;
&lt;h3&gt;
  
  
  2000 — ConstraintMut
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Message:&lt;/strong&gt; &lt;code&gt;A mut constraint was violated&lt;/code&gt;&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Cause:&lt;/strong&gt; You're trying to modify an account not marked as &lt;code&gt;mut&lt;/code&gt;&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Fix:&lt;/strong&gt; Add &lt;code&gt;#[account(mut)]&lt;/code&gt; to the account in your instruction context&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nd"&gt;#[derive(Accounts)]&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;MyInstruction&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nv"&gt;'info&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;#[account(mut)]&lt;/span&gt;  &lt;span class="c1"&gt;// &amp;lt;- add this&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;my_account&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Account&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nv"&gt;'info&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;MyData&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  2001 — ConstraintHasOne
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Message:&lt;/strong&gt; &lt;code&gt;A has_one constraint was violated&lt;/code&gt;&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Cause:&lt;/strong&gt; A field on your account doesn't match the expected pubkey&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Fix:&lt;/strong&gt; Make sure the account's field matches the signer or related account&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nd"&gt;#[account(has_one&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="nd"&gt;authority)]&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;data_account&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Account&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nv"&gt;'info&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;MyData&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;authority&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Signer&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nv"&gt;'info&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  2002 — ConstraintSigner
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Message:&lt;/strong&gt; &lt;code&gt;A signer constraint was violated&lt;/code&gt;&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Cause:&lt;/strong&gt; Account expected to sign but didn't&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Fix:&lt;/strong&gt; Ensure the account is a &lt;code&gt;Signer&lt;/code&gt; type and the client passes it as a signer&lt;/p&gt;


&lt;h3&gt;
  
  
  2003 — ConstraintRaw
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Message:&lt;/strong&gt; &lt;code&gt;A raw constraint was violated&lt;/code&gt;&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Cause:&lt;/strong&gt; Custom &lt;code&gt;constraint&lt;/code&gt; expression evaluated to false&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Fix:&lt;/strong&gt; Debug your &lt;code&gt;constraint =&lt;/code&gt; expression — add logs to check values&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nd"&gt;#[account(constraint&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="nd"&gt;some_account&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nd"&gt;value&lt;/span&gt; &lt;span class="err"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="err"&gt;@&lt;/span&gt; &lt;span class="nd"&gt;MyError::InvalidValue)]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  2006 — ConstraintOwner
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Message:&lt;/strong&gt; &lt;code&gt;An owner constraint was violated&lt;/code&gt;&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Cause:&lt;/strong&gt; Account is owned by a different program than expected&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Fix:&lt;/strong&gt; Verify you're passing the correct account owned by your program&lt;/p&gt;


&lt;h3&gt;
  
  
  2012 — ConstraintSeeds
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Message:&lt;/strong&gt; &lt;code&gt;A seeds constraint was violated&lt;/code&gt;&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Cause:&lt;/strong&gt; PDA address doesn't match the provided seeds + bump&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Fix:&lt;/strong&gt; Make sure seeds and bump on-chain match exactly what you used to derive the PDA client-side&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="c1"&gt;// On-chain&lt;/span&gt;
&lt;span class="nd"&gt;#[account(seeds&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="err"&gt;[&lt;/span&gt;&lt;span class="s"&gt;b"vault"&lt;/span&gt;&lt;span class="nd"&gt;,&lt;/span&gt; &lt;span class="nd"&gt;user&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nd"&gt;key()&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nd"&gt;as_ref()]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bump&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;

&lt;span class="c1"&gt;// Client-side — must match exactly&lt;/span&gt;
&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;vault&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PublicKey&lt;/span&gt;&lt;span class="nf"&gt;.findProgramAddressSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Buffer&lt;/span&gt;&lt;span class="nf"&gt;.from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"vault"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="py"&gt;.publicKey&lt;/span&gt;&lt;span class="nf"&gt;.toBuffer&lt;/span&gt;&lt;span class="p"&gt;()],&lt;/span&gt;
  &lt;span class="n"&gt;programId&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Account Errors (3000–3017)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  3000 — AccountDiscriminatorAlreadySet
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Message:&lt;/strong&gt; &lt;code&gt;Account discriminator already set&lt;/code&gt;&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Cause:&lt;/strong&gt; Trying to initialize an account that's already initialized&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Fix:&lt;/strong&gt; Check if account exists before calling init instruction&lt;/p&gt;




&lt;h3&gt;
  
  
  3001 — AccountDiscriminatorNotFound
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Message:&lt;/strong&gt; &lt;code&gt;Account discriminator not found&lt;/code&gt;&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Cause:&lt;/strong&gt; Account data is empty — account probably doesn't exist&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Fix:&lt;/strong&gt; Confirm the account is created and has data before fetching&lt;/p&gt;




&lt;h3&gt;
  
  
  3003 — AccountDidNotDeserialize
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Message:&lt;/strong&gt; &lt;code&gt;Failed to deserialize the account&lt;/code&gt;&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Cause:&lt;/strong&gt; Account data doesn't match your struct layout&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Fix:&lt;/strong&gt; Check if the account was initialized with a different version of your program&lt;/p&gt;




&lt;h3&gt;
  
  
  3007 — AccountNotInitialized
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Message:&lt;/strong&gt; &lt;code&gt;The given account is not initialized&lt;/code&gt;&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Cause:&lt;/strong&gt; Account has no data / wrong account passed&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Fix:&lt;/strong&gt; Make sure you're passing the right account address&lt;/p&gt;




&lt;h3&gt;
  
  
  3012 — AccountNotMutable
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Message:&lt;/strong&gt; &lt;code&gt;The given account is not mutable&lt;/code&gt;&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Cause:&lt;/strong&gt; Trying to write to an account without marking it mutable&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Fix:&lt;/strong&gt; Pass the account with &lt;code&gt;isMutable: true&lt;/code&gt; on the client side&lt;/p&gt;




&lt;h2&gt;
  
  
  Instruction Errors (100–103)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  100 — InstructionMissing
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Cause:&lt;/strong&gt; 8-byte discriminator not found — wrong instruction or corrupted data&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Fix:&lt;/strong&gt; Verify you're calling the right instruction name&lt;/p&gt;

&lt;h3&gt;
  
  
  101 — InstructionFallbackNotFound
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Cause:&lt;/strong&gt; Fallback function called but not defined  &lt;/p&gt;

&lt;h3&gt;
  
  
  102 — InstructionDidNotDeserialize
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Cause:&lt;/strong&gt; Instruction data couldn't be deserialized — argument mismatch&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Fix:&lt;/strong&gt; Check that client-side arguments match the instruction signature exactly&lt;/p&gt;

&lt;h3&gt;
  
  
  103 — InstructionDidNotSerialize
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Cause:&lt;/strong&gt; Return value couldn't be serialized&lt;/p&gt;




&lt;h2&gt;
  
  
  State Errors (4000–4200)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  4000 — IdlInstructionStub
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Cause:&lt;/strong&gt; IDL instruction called but not implemented  &lt;/p&gt;

&lt;h3&gt;
  
  
  4100 — EventInstructionStub
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Cause:&lt;/strong&gt; Event CPI called but handler not implemented&lt;/p&gt;




&lt;h2&gt;
  
  
  Quick Reference — Top 10 Errors
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Code&lt;/th&gt;
&lt;th&gt;Name&lt;/th&gt;
&lt;th&gt;One-Line Fix&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;2000&lt;/td&gt;
&lt;td&gt;ConstraintMut&lt;/td&gt;
&lt;td&gt;Add &lt;code&gt;#[account(mut)]&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2001&lt;/td&gt;
&lt;td&gt;ConstraintHasOne&lt;/td&gt;
&lt;td&gt;Field must match related account pubkey&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2002&lt;/td&gt;
&lt;td&gt;ConstraintSigner&lt;/td&gt;
&lt;td&gt;Account must sign the transaction&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2003&lt;/td&gt;
&lt;td&gt;ConstraintRaw&lt;/td&gt;
&lt;td&gt;Custom constraint expression is false&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2006&lt;/td&gt;
&lt;td&gt;ConstraintOwner&lt;/td&gt;
&lt;td&gt;Wrong program owns this account&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2012&lt;/td&gt;
&lt;td&gt;ConstraintSeeds&lt;/td&gt;
&lt;td&gt;Seeds/bump mismatch between client and program&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3000&lt;/td&gt;
&lt;td&gt;AccountDiscriminatorAlreadySet&lt;/td&gt;
&lt;td&gt;Account already initialized&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3001&lt;/td&gt;
&lt;td&gt;AccountDiscriminatorNotFound&lt;/td&gt;
&lt;td&gt;Account doesn't exist yet&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3007&lt;/td&gt;
&lt;td&gt;AccountNotInitialized&lt;/td&gt;
&lt;td&gt;Wrong account address passed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3012&lt;/td&gt;
&lt;td&gt;AccountNotMutable&lt;/td&gt;
&lt;td&gt;Pass account as mutable on client&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Full Reference PDF
&lt;/h2&gt;

&lt;p&gt;This article covers the most common errors. I put together a &lt;br&gt;
complete PDF handbook covering all 59 Anchor error codes with &lt;br&gt;
full explanations, root causes, fixes, and code examples:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://tobiasbond.gumroad.com/l/gtainv" rel="noopener noreferrer"&gt;Anchor Error Handbook — Complete Reference (100–4200)&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Useful if you want everything in one searchable document you &lt;br&gt;
can keep open while building.&lt;/p&gt;

</description>
      <category>solana</category>
      <category>rust</category>
      <category>blockchain</category>
      <category>webdev</category>
    </item>
    <item>
      <title>How I Built a Cost Proxy to Stop OpenClaw from Burning My API Budget</title>
      <dc:creator>TobiasBond</dc:creator>
      <pubDate>Sat, 28 Feb 2026 15:31:52 +0000</pubDate>
      <link>https://dev.to/tobiasbond/how-i-built-a-cost-proxy-to-stop-openclaw-from-burning-my-api-budget-25i3</link>
      <guid>https://dev.to/tobiasbond/how-i-built-a-cost-proxy-to-stop-openclaw-from-burning-my-api-budget-25i3</guid>
      <description>&lt;p&gt;If you've been in the OpenClaw community for more than a week, you've seen the posts.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;"$3,600/month on API calls."&lt;/em&gt;&lt;br&gt;
&lt;em&gt;"Woke up to a $200 bill from a heartbeat loop running all night."&lt;/em&gt;&lt;br&gt;
&lt;em&gt;"I have zero visibility into what my agent is spending."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;OpenClaw is one of the most exciting open-source projects of 2026 — 210K+ GitHub stars, a personal AI agent that actually &lt;em&gt;does&lt;/em&gt; things. But the moment you give an AI agent unrestricted access to paid APIs, you're playing with fire.&lt;/p&gt;

&lt;p&gt;I kept seeing these horror stories and realized nobody had built a proper solution. There are a few monitoring tools out there (ClawMetry gives you read-only stats, Tokscale is CLI-only), but nothing that actually &lt;em&gt;stops&lt;/em&gt; the bleeding in real time.&lt;/p&gt;

&lt;p&gt;So I built TokPinch.&lt;/p&gt;
&lt;h2&gt;
  
  
  What TokPinch Does
&lt;/h2&gt;

&lt;p&gt;TokPinch is a transparent proxy that sits between OpenClaw and your LLM provider (Anthropic, OpenAI). Every API request passes through it.&lt;/p&gt;

&lt;p&gt;Setup is literally one line in your OpenClaw config:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;ANTHROPIC_BASE_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;http://localhost:4100/v1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. Your agent doesn't know TokPinch exists. But now you have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Real-time cost tracking&lt;/strong&gt; — every request logged with model, tokens, cost, and session&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Budget enforcement&lt;/strong&gt; — set daily/monthly limits that actually block requests when exceeded&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Loop detection&lt;/strong&gt; — catches runaway agents (rapid fire, repeated content, cost spirals, heartbeat storms) and pauses them automatically&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Smart model routing&lt;/strong&gt; — automatically downgrades cheap tasks (heartbeats, short messages) from Opus to Haiku, saving 10-50%&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Telegram/email alerts&lt;/strong&gt; — get notified the second something goes wrong&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A dashboard that doesn't suck&lt;/strong&gt; — dark mode, real-time WebSocket updates, cost charts, budget gauges&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Architecture
&lt;/h2&gt;

&lt;p&gt;I wanted TokPinch to be fast, self-hosted, and have zero dependencies on external services.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;OpenClaw → TokPinch (localhost:4100) → Anthropic/OpenAI
                ↓
         SQLite (metadata only)
                ↓
         React Dashboard + WebSocket
                ↓
         Telegram/Email Alerts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The tech stack:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;TypeScript&lt;/strong&gt; — end to end, because life is too short for runtime type errors&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fastify&lt;/strong&gt; — fastest Node.js HTTP framework, perfect for a proxy&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SQLite&lt;/strong&gt; (better-sqlite3) — zero config, WAL mode for concurrent reads, file-based so it deploys anywhere&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;React 18 + Vite + Tailwind&lt;/strong&gt; — for the dashboard&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Docker&lt;/strong&gt; — multi-stage build, runs as non-root with read-only filesystem&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The key design decision: &lt;strong&gt;TokPinch never stores API keys or message content.&lt;/strong&gt; Keys pass through in headers and are discarded immediately. Only metadata hits the database (model name, token counts, cost, timestamp, session ID). This is documented in our &lt;a href="https://github.com/TobieTom/tokpinch/blob/main/SECURITY.md" rel="noopener noreferrer"&gt;SECURITY.md&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building the Loop Detector
&lt;/h2&gt;

&lt;p&gt;This was the most interesting engineering challenge. OpenClaw agents can get stuck in loops — the infamous heartbeat bug, where the agent sends the same message repeatedly, burning through your budget at 20+ requests per minute.&lt;/p&gt;

&lt;p&gt;I implemented four detection rules:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Rapid fire&lt;/strong&gt; — more than 20 requests per minute from the same session&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Repeated content&lt;/strong&gt; — same message hash appearing 5+ times in 5 minutes (uses djb2 hash on first 200 chars)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cost spiral&lt;/strong&gt; — more than $2 spent in a 5-minute window&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Heartbeat storm&lt;/strong&gt; — 10+ heartbeat-pattern messages in 10 minutes&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;When any rule triggers, TokPinch pauses that session with exponential backoff (starting at 5 minutes, doubling up to 30 minutes). The agent gets a clear error message, and you get a Telegram alert.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;🔄 Loop detected! Session loop-test sent identical content 6 times 
in 5 minutes. Spending $0.0000. Paused for 5 minute(s).
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The circular buffer approach keeps memory usage constant — 100 slots per session, O(1) lookups.&lt;/p&gt;

&lt;h2&gt;
  
  
  Smart Model Routing
&lt;/h2&gt;

&lt;p&gt;This is the feature that saves real money. Not every API call needs the most expensive model.&lt;/p&gt;

&lt;p&gt;When OpenClaw sends a heartbeat ping or a short message like "hi" to Claude Opus ($15/MTok input), TokPinch intercepts it and routes to Haiku ($0.80/MTok input) instead. The response quality for trivial tasks is identical, but the cost drops by ~95%.&lt;/p&gt;

&lt;p&gt;The routing rules are configurable:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Route to cheap model when:&lt;/strong&gt; message is under 200 tokens, no tools/images/documents, system prompt under 500 tokens&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Never downgrade when:&lt;/strong&gt; user explicitly set the model, images or documents are present, more than 5 tools are being used&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;During testing, a request to &lt;code&gt;claude-opus-4&lt;/code&gt; with just "hi" was correctly routed to &lt;code&gt;claude-haiku-4-5&lt;/code&gt;, confirmed in the server logs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;🔀 Routed: claude-opus-4 → claude-haiku-4-5-20251001 (low_token_chat, saved ~$0.0037)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Security: Built for the OpenClaw Crisis
&lt;/h2&gt;

&lt;p&gt;Security isn't an afterthought — it's a feature. OpenClaw has had a rough security track record: one-click RCE, 824+ malicious skills on ClawHub, 42,000+ exposed instances. TokPinch sitting in the API request path means it &lt;em&gt;must&lt;/em&gt; be bulletproof.&lt;/p&gt;

&lt;p&gt;What we did:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;API keys are &lt;strong&gt;never stored or logged&lt;/strong&gt; — pino logger has redact paths for all auth headers&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zero message content on disk&lt;/strong&gt; — the &lt;code&gt;requests&lt;/code&gt; table schema literally has no column for it&lt;/li&gt;
&lt;li&gt;Docker runs as &lt;strong&gt;non-root with read-only filesystem&lt;/strong&gt; and &lt;code&gt;no-new-privileges&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;JWT auth with &lt;strong&gt;auto-generated 512-bit secrets&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rate limiting&lt;/strong&gt; on every endpoint (proxy, API, and login)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Content-Security-Policy&lt;/strong&gt;, X-Frame-Options, HSTS headers on all responses&lt;/li&gt;
&lt;li&gt;Test endpoints &lt;strong&gt;auto-disabled in production&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The full audit is in &lt;a href="https://github.com/TobieTom/tokpinch/blob/main/SECURITY.md" rel="noopener noreferrer"&gt;SECURITY.md&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Dashboard
&lt;/h2&gt;

&lt;p&gt;I wanted the dashboard to feel like a proper product, not a developer afterthought. Dark theme (zinc-950 base), JetBrains Mono for numbers, Outfit for headings, real-time WebSocket updates, Framer Motion animations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Overview&lt;/strong&gt; — 4 stat cards, cost-over-time chart, model breakdown, budget gauges, live request feed&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sessions&lt;/strong&gt; — every session with cost, request count, tokens, and the most-used model. Expandable rows showing individual requests.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Budget&lt;/strong&gt; — arc gauges showing spend vs. limit, status badges (ACTIVE/WARNING/PAUSED/OVERRIDE), one-click resume after manual review.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Alerts&lt;/strong&gt; — all budget warnings, loop detections, and daily digests with filter tabs and delivery status.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Learned
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Native modules on Windows are painful.&lt;/strong&gt; better-sqlite3 needs to be compiled for your exact Node.js version. Switching Node versions (via nvm) breaks the binary. Solution: always &lt;code&gt;npm rebuild&lt;/code&gt; after version changes.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Streaming proxies are tricky.&lt;/strong&gt; SSE (Server-Sent Events) responses from Anthropic need to be intercepted without buffering — you want zero-latency passthrough while still accumulating usage data from the final event. The &lt;code&gt;SSEInterceptor&lt;/code&gt; Transform stream handles this.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Test with real money before shipping.&lt;/strong&gt; Mock tests proved the code worked. Real Anthropic API calls found two critical bugs: a wrong default model ID in routing rules, and a missing &lt;code&gt;anthropic-version&lt;/code&gt; header that the proxy wasn't injecting. Both would have broken every user's setup.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Security documentation is a feature.&lt;/strong&gt; In the OpenClaw ecosystem where trust is low (malicious skills, exposed instances), having a thorough SECURITY.md that explains exactly how API keys are handled makes people comfortable using your tool.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Try It
&lt;/h2&gt;

&lt;p&gt;TokPinch is 100% free and open source (MIT licensed).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Quick start:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-p&lt;/span&gt; 4100:4100 &lt;span class="nt"&gt;-v&lt;/span&gt; tokpinch-data:/app/data &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;DASHBOARD_PASSWORD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;yourpassword &lt;span class="se"&gt;\&lt;/span&gt;
  tokpinch/tokpinch
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then add one line to your OpenClaw config:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;ANTHROPIC_BASE_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;http://localhost:4100/v1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open &lt;code&gt;http://localhost:4100/dashboard&lt;/code&gt; and watch your costs in real time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Links:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GitHub: &lt;a href="https://github.com/TobieTom/tokpinch" rel="noopener noreferrer"&gt;github.com/TobieTom/tokpinch&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Landing page: &lt;a href="https://tokpinch.vercel.app" rel="noopener noreferrer"&gt;tokpinch.vercel.app&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Cloud version waitlist: &lt;a href="https://tokpinch.vercel.app/#waitlist" rel="noopener noreferrer"&gt;tokpinch.vercel.app/#waitlist&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you're running OpenClaw or any AI agent with paid API access, give TokPinch a try. Star the repo if it's useful, and open an issue if you find bugs.&lt;/p&gt;

&lt;p&gt;Built by &lt;a href="https://github.com/TobieTom" rel="noopener noreferrer"&gt;TobieTom&lt;/a&gt; 🇳🇬&lt;/p&gt;




&lt;p&gt;&lt;em&gt;What features would you want to see next? Drop a comment below or open a GitHub issue.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>agents</category>
      <category>ai</category>
      <category>api</category>
      <category>showdev</category>
    </item>
  </channel>
</rss>
