<?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: Alexander</title>
    <description>The latest articles on DEV Community by Alexander (@summusforge).</description>
    <link>https://dev.to/summusforge</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%2F3925582%2F8d6061b3-7818-4c31-8ebe-ee48eff3a07a.png</url>
      <title>DEV Community: Alexander</title>
      <link>https://dev.to/summusforge</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/summusforge"/>
    <language>en</language>
    <item>
      <title>veil-cli: a terminal wallet that makes you understand before you sign</title>
      <dc:creator>Alexander</dc:creator>
      <pubDate>Tue, 02 Jun 2026 11:39:24 +0000</pubDate>
      <link>https://dev.to/summusforge/veil-cli-a-terminal-wallet-that-makes-you-understand-before-you-sign-2003</link>
      <guid>https://dev.to/summusforge/veil-cli-a-terminal-wallet-that-makes-you-understand-before-you-sign-2003</guid>
      <description>&lt;p&gt;Most Ethereum wallets show you a hex string and a gas estimate. You click confirm.&lt;/p&gt;

&lt;p&gt;That's not enough if you care about what you're actually signing.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;veil-cli&lt;/code&gt; is an open-source, terminal-first security tool for EVM transactions. The goal: before your private key touches anything, you should see a decoded function name, a risk score, and a simulated balance diff. This post covers how it was built — from the first decode command to a full send pipeline.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa9jk6bjray8xh0qo7vf5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa9jk6bjray8xh0qo7vf5.png" alt="Terminal screenshot" width="729" height="389"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Part 1: Decode, simulate, and score before you sign
&lt;/h2&gt;

&lt;p&gt;The core problem is that raw calldata is opaque. &lt;code&gt;0xa9059cbb000000000000000000000000...&lt;/code&gt; means nothing to a human. Wallets render it as "contract interaction" and move on.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;veil decode&lt;/code&gt; resolves the ABI from three sources in order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Etherscan&lt;/strong&gt; — if the contract is verified and you have an API key&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sourcify&lt;/strong&gt; — decentralized, no key required&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;4byte.directory&lt;/strong&gt; — fallback by selector hash, covers most common functions
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;veil decode 0xabc123... &lt;span class="nt"&gt;--chain&lt;/span&gt; mainnet
veil decode 0xa9059cbb... &lt;span class="nt"&gt;--address&lt;/span&gt; 0xdAC17F... &lt;span class="nt"&gt;--chain&lt;/span&gt; mainnet
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;veil risk&lt;/code&gt; runs a heuristics pass on the destination contract: proxy detection, bytecode size checks, EOA detection, plus GoPlus Security API data for honeypot flags, blacklists, and high sell tax.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;veil simulate&lt;/code&gt; forks the chain locally with Anvil and shows you balance diffs before the transaction is broadcast. One thing that surprised me: transactions that would revert on-chain still pass RPC validation — the node accepts them. You only find out after paying gas. The local fork catches it for free.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stack:&lt;/strong&gt; TypeScript, viem, Commander.js, Ink, Foundry/Anvil, GoPlus Security API.&lt;/p&gt;




&lt;h2&gt;
  
  
  Part 2: Keystore v3 with zero new dependencies
&lt;/h2&gt;

&lt;p&gt;A security tool that handles private keys needs a reasonable storage model. We implemented Ethereum keystore v3 — the same format used by geth, MetaMask, and MyCrypto.&lt;/p&gt;

&lt;p&gt;The format is three steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Derive a key from the password using &lt;code&gt;scrypt&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Encrypt the private key with &lt;code&gt;AES-128-CTR&lt;/code&gt; using the first 16 bytes&lt;/li&gt;
&lt;li&gt;Compute a MAC over the last 16 bytes + ciphertext to detect wrong passwords&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Everything is in &lt;code&gt;node:crypto&lt;/code&gt; — except &lt;code&gt;keccak256&lt;/code&gt; for the MAC, which we pulled from &lt;code&gt;viem&lt;/code&gt; since it was already a dependency. No new packages.&lt;/p&gt;

&lt;p&gt;Two things worth noting from the implementation:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;crypto.scryptSync()&lt;/code&gt; blocks the event loop for 1–2 seconds&lt;/strong&gt; with &lt;code&gt;N=131072&lt;/code&gt;. Acceptable for a CLI, but we switched to the async version anyway. The default &lt;code&gt;maxmem&lt;/code&gt; of 32MB in Node isn't enough for these parameters — we had to raise it to 160MB. That one wasn't obvious from the docs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The MAC comparison uses &lt;code&gt;crypto.timingSafeEqual()&lt;/code&gt;&lt;/strong&gt; instead of a plain string comparison. Is a timing attack against a local CLI keystore a realistic threat? Probably not. But if you're writing a security tool, it's hard to justify doing it the wrong way.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;veil wallet create    &lt;span class="c"&gt;# generates a key, encrypts with password, writes to ~/.veil/wallets/&lt;/span&gt;
veil wallet import    &lt;span class="c"&gt;# same flow, bring your own private key&lt;/span&gt;
veil wallet list      &lt;span class="c"&gt;# shows all wallets and their addresses&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output is a standard keystore v3 file — importable into MetaMask or any compatible client.&lt;/p&gt;




&lt;h2&gt;
  
  
  Part 3: Sign only after you understand
&lt;/h2&gt;

&lt;p&gt;This is where everything comes together. &lt;code&gt;veil send&lt;/code&gt; runs a full pipeline before your key is ever unlocked:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;tx.json → decode → risk check → simulate → summary → confirm → password → broadcast
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's what the summary looks like before the prompt appears:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;┌ Transaction summary ─────────────────────────────────
│ From    0xYourWallet
│ To      0xUniswapRouter
│ Value   0.5 ETH
│ Method  swapExactETHForTokens
│ Risk    ✔ LOW
│ Gas     ~142,381
└──────────────────────────────────────────────────────

  ETH   -0.500000
  PEPE  +2,184,112.000000

? Sign and broadcast? › (y/N)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your key is never unlocked until the last step. By the time you type your password, the transaction is already boring.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why tx.json?
&lt;/h3&gt;

&lt;p&gt;Instead of constructing a transaction inline, &lt;code&gt;veil send&lt;/code&gt; takes a file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;veil send tx.json &lt;span class="nt"&gt;--chain&lt;/span&gt; mainnet
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That means a transaction can be built, reviewed, and version-controlled before signing is even part of the process. &lt;code&gt;veil tx build&lt;/code&gt; is an interactive wizard that generates the file. A Gnosis Safe export or &lt;code&gt;cast calldata&lt;/code&gt; output works just as well.&lt;/p&gt;

&lt;h3&gt;
  
  
  The uncomfortable reality of unverified contracts
&lt;/h3&gt;

&lt;p&gt;Many contracts are not verified. No source code on Etherscan, nothing on Sourcify, selector unknown to 4byte.directory.&lt;/p&gt;

&lt;p&gt;Most tools handle this by showing nothing useful — or by proceeding as if the decode succeeded. Neither is honest.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;veil send&lt;/code&gt; doesn't block the pipeline on an unreadable contract. Risk check and simulation still run. But instead of pretending the decode worked, it shows the raw calldata and makes the gap explicit. You see what the tool knows and what it doesn't. The decision stays with you.&lt;/p&gt;

&lt;h3&gt;
  
  
  What the risk check still misses
&lt;/h3&gt;

&lt;p&gt;The current engine catches obvious things: honeypot flags, unverified source, high sell tax, blacklisted addresses. It misses subtler issues — an upgradeable proxy pointing at a new implementation, a multisig with recently changed signers.&lt;/p&gt;

&lt;p&gt;That's an open problem. Detecting those cases on-chain requires either event log indexing or off-chain data sources. Haven't found a clean solution yet.&lt;/p&gt;




&lt;h2&gt;
  
  
  The goal
&lt;/h2&gt;

&lt;p&gt;The goal isn't to make signing harder.&lt;/p&gt;

&lt;p&gt;The goal is to move understanding before authorization.&lt;/p&gt;

&lt;p&gt;By the time the password prompt appears, the transaction should already be boring.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;veil tx build          &lt;span class="c"&gt;# interactive wizard → tx.json&lt;/span&gt;
veil send tx.json      &lt;span class="c"&gt;# full pipeline, sign only at the end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Current state
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;veil decode &amp;lt;tx-hash|calldata&amp;gt;   &lt;span class="c"&gt;# decode any transaction&lt;/span&gt;
veil approvals &amp;lt;address&amp;gt;         &lt;span class="c"&gt;# scan active ERC-20/721 approvals&lt;/span&gt;
veil simulate &amp;lt;tx.json&amp;gt;          &lt;span class="c"&gt;# local fork, balance diffs&lt;/span&gt;
veil risk &amp;lt;address&amp;gt;              &lt;span class="c"&gt;# contract risk score&lt;/span&gt;
veil wallet create/import/list   &lt;span class="c"&gt;# encrypted local keystore&lt;/span&gt;
veil tx build                    &lt;span class="c"&gt;# interactive tx.json builder&lt;/span&gt;
veil send &amp;lt;tx.json&amp;gt;              &lt;span class="c"&gt;# full pipeline, sign last&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Not published to npm yet — install from source for now.&lt;/p&gt;

&lt;p&gt;github.com/summusforge-lab/veil-cli&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally posted as a series on r/ethdev.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ethereum</category>
      <category>security</category>
      <category>cli</category>
      <category>web3</category>
    </item>
  </channel>
</rss>
