WDK CLI: A Self-Custodial Wallet in Your Terminal (and in Your Agent)
By the end of this post, you'll understand what a CLI is, what MCP is, why WDK ships both on top of the same wallet, and exactly where each one belongs in a real product.
Before start
If this is the first time you hear about WDK, or even digital wallets, have a look at our previous posts:
- Introduction to WDK: Tether's Wallet Development Kit: the vision, the core principles, and the module system everything else depends on.
- Gasless Transactions in WDK: the paymaster and has sponsorship pattern, chain by chain across EVM, Solana, TON and Tron.
- Protocol modules: for swaps, bridges, lending, and fiat on-ramps.
The problem with giving a wallet to an agent
A language model can write a beautiful transaction. But it cannot send one. It produces text, and text does not move money.
So the question is not "can an AI use a wallet". The question is: what does a wallet need to expose so that a model can operate it, and what must it refuse to expose so that a human stays in control?
The WDK CLI answers both halves. And the second half is the interesting one.
What a CLI is, if you've never used one
A CLI (command-line interface) is an application without buttons. Instead of tapping a screen, you type a sentence into a terminal and press 'Enter'.
The mapping to an app you already know is direct:
- Tap "Create wallet" →
wdk wallet create --name alice --words 12 - Tap "Receive" to see your address →
wdk get address --network ethereum - Fill in the send form →
wdk send --network ethereum --to 0x... --amount 0.0001
Every command follows the same shape: program →noun → verb → options. wdk is the program, wallet is what you're talking about, create is what to do, --name alice is the detail. Once you see that pattern, wdk get balance, wdk token add, and wdk mcp setup read themselves.
Installation is one line, and it needs Node.js 22.18.0 or later:
npm install -g @tetherto/[email protected]Install the scoped @tetherto/wdk-cli package. The unscoped wdk-cli on npm is a different project.
Three binaries, one wallet
That single install puts three executables on your machine, and understanding the split is what makes everything below make sense.
wdk→ what you type. Runs, prints, exits.wdk-daemon→ a background process that holds the unlocked wallet in memory.wdk-mcp→ the same wallet operations, exposed as structured tools for an AI client.
Your seed phrase lives encrypted on disk, in wallets/NAME/seed.enc AES-256-GCM with a scrypt-derived key, owner-only file permissions on macOS and Linux.
wdk wallet unlock --name alice --ttl 5 opens the safe for five minutes. During those five minutes, the daemon holds the derived key, and any command can sign without asking again. Then it closes by itself.
One detail worth internalizing: the TTL is absolute. Using the wallet does not extend it. Five minutes from unlock, the session is gone, whether you were in the middle of something or not. That's deliberate; an activity-refreshed timer silently extends exposure every time you touch it.
What MCP actually is
MCP (Model Context Protocol) is an open standard that solves the "text can't move money" problem in a general way.
An MCP server publishes a list of tools: named functions with typed parameters and descriptions. An MCP client (QVAC, Claude Desktop, Claude Code, Cursor, VS Code, OpenClaw...) reads that list and shows it to the model. When you type "how much ETH do I have?", the model doesn't guess, and it doesn't hallucinate a number; it picks the get_balance tool, fills in { network: "ethereum" }, the server actually runs it, and the real result comes back.
The analogy that holds up: a menu in a restaurant. The model never walks into the kitchen. It reads a menu of specific dishes and orders one. And critically, whatever isn't on the menu doesn't exist for the model.
Hold on to that, because it's the whole security story.
The CLI & MCP connection: same wallet, two doors
Here is the idea this entire post rests on.
A CLI is a menu of operations for you. The MCP server is a subset of that menu for a model: eleven tools, over the same wallet.
Not a copy of the wallet. The same one. wdk balance get typed in your terminal and get_balance called from Claude Desktop both travel to the same wdk-daemon, reading the same encrypted seed, inside the same unlock session you started.
Two doors into one room. But they are not the same size, and only one of them has a key.
The agent's door opens onto reading and spending: addresses, balances, history, transfers, module methods, MoonPay URLs. It does not open onto creating a wallet, importing or exporting a seed, changing your default, or editing your config. Most of all, it cannot unlock anything. wdk wallet unlock is the human authorization moment, and it stays on your side of the wall.
Which means connecting an agent to a real self-custodial wallet is three deliberate steps, not one:
wdk wallet create --name agent-dev --words 12
wdk wallet unlock --name agent-dev --ttl 5
wdk mcp setup --ai-tool claude-desktopCreate. Unlock. Wire. Then restart the client; setup will tell you what that client needs.
The five-minute TTL is not decoration. It is absolute from the moment you unlock; agent activity does not extend it. When it expires, the door closes on its own.
What you are actually agreeing to. An unlocked wallet behaves like a local hot wallet for the duration of the session. Read, sign, and send all happen without asking for the passphrase again. On Unix-like systems, the daemon socket is owner-only, which stops other OS users; not other processes running as you. The eleven-tool surface is a tool boundary, not an operating-system sandbox; an AI client that also has shell access can simply run wdk directly.
And the preview step is a convention, not a control. send_token defaults to dryRun: true, and the recommended flow is preview, show the user the exact network, token, recipient, amount, and fee, then execute after confirmation. But the daemon does not enforce that sequence; a direct call with dryRun: false broadcasts. call_method has no dry run at all.
So: dev wallet, testnet funds, short TTL, and wdk wallet lock when you're done. The rest of this post assumes exactly that setup.
The nine tools (and the ones deliberately missing)
wdk-mcp registers nine tools:
Tool | Wallet required | Purpose |
|---|---|---|
| No | List networks, mainnets or testnets |
| No | List registered tokens |
| No | Read one registry entry |
| Yes | Derive addresses across networks |
| Yes | Read balances, aggregate with USD estimates |
| Yes | Indexer-backed transfer history |
| Yes | Preview or execute a transfer |
| Yes | Create a signed MoonPay buy URL |
| Yes | Create a signed MoonPay sell URL |
Now look at whatnot. No wallet create. No import. No export. No unlock. No lock. No config set, no network create, no token add.
Those exist only as commands a person types. Which produces the sentence that matters most in this post:
wdk wallet unlock is deliberately the human authorization moment. An agent cannot open the wallet. It can only operate a wallet that a person already opened, for the duration that person chose, and wdk wallet lock ends it.
The read/write split runs one level deeper too. send_token defaults to dryRun: true. The recommended flow is: preview, show the user the network, token, recipient, amount, and estimated fee, get explicit confirmation, and only then call again with dryRun: false.
If that discipline sounds familiar, it should. It's the same rule from the protocol modules post: quote first, then execute. There, it protected a user from a surprise. Here, it protects a user from an actor that has no judgment at all.
The honest part, so this doesn't read as a security pitch. The nine-tool surface is a boundary of the tool menu, not an operating-system sandbox. An AI client that also has shell access can invokewdkdirectly. And while a wallet is unlocked, any process running as the same OS user that can reach the daemon socket can request a signature without the passphrase — the socket is owner-only, but there is no per-program credential. The daemon does not enforce the preview-and-confirm flow either; that's client behaviour.
The real trust boundary is the operating-system account, not the prompt. Use a dedicated wallet with limited funds, keep the TTL short, and don't run unreviewed code under that account while the wallet is open. The full picture is in the security model, and it's worth reading before you unlock anything funded.
Try it in four minutes
Use Sepolia and a dedicated wallet. The commands below are testnet; nothing here should be your first experience with mainnet.
1. Create and unlock.
wdk wallet create --name agent-dev --words 12
wdk wallet unlock --name agent-dev --ttl 5The CLI prompts for a passphrase with hidden input, then shows the generated seed phrase once. Record it offline. Never paste it into a ticket, a screenshot, or an AI transcript.
2. Use it as a human.
wdk get address --network sepolia --wallet agent-dev
wdk get balance --network sepolia --wallet agent-dev
wdk send --network sepolia --to 0xYOUR_ADDRESS --amount 0.0001 --wallet agent-dev --dry-run--dry-run estimates the fee and returns a transaction summary without broadcasting. Drop the flag, and it broadcasts immediately (an unlocked CLI wallet does not ask twice).
3. Hand the same wallet to an agent.
wdk mcp setup --ai-tool claude-desktop
wdk mcp list4. Talk to it. Restart the client and ask, in plain language: "What's my Sepolia address?" · "What's my balance?" · "Send 0.0001 ETH to 0x…". The last one comes back as a preview first.
5. Finish with what it can't do. Ask the agent to unlock your other wallet. It can't — that tool doesn't exist. Then run wdk wallet lock --name agent-dev and ask for the balance again. It fails. The session was the permission, and you just took it back.
So what is the CLI actually for?
Fair question, and it deserves a straight answer rather than an inflated list.
- A large share of CLI usage is evaluation and prototyping, and that's fine. Most product CLIs are exactly that. Nobody ships
stripe listenorsupabase startto production, and nobody thinks they're pointless. Their job is to make the first hour cheap. Three uses do outlive that first hour: - Debugging an app you already shipped. Your React Native app doesn't show a USD₮ balance on Polygon. Four suspects: your code, the wallet module, the RPC provider, the indexer. Import the same seed into the CLI and ask directly. If the CLI sees the balance and your app doesn't, the bug is yours — three layers eliminated in thirty seconds. That's an independent oracle over the same wallet, and it's worth more here than in most domains because blockchain failures are silent. You don't get an exception. You get a zero.
- Local operations that don't justify building a UI. Check the hot wallet before a release, pay three suppliers in USD₮, verify a transaction landed. A shell script with
--jsoncovers it. The alternative is an internal dashboard nobody maintains. With the standing caveat: dedicated wallet, limited funds, and nevercreateorexportinside CI. - A test bench for module authors. If you're a protocol building a WDK module,
wdk module addandwdk method callare where you exercise it before any app exists. That's the other end ofcreate-wdk-modulefrom the last post.
And the strategic reason, which is the real one: none of the above explains why this CLI exists now. This does, giving an agent a self-custodial wallet requires an encrypted seed store, a process that holds the session, a human authorization moment, and an MCP server wired to all three. Someone has to build those four pieces and put them on the user's machine.
The CLI isn't a wrapper around the MCP server. It's a prerequisite for the MCP server. The human-only half of the CLI is what makes the agent half safe.
Can the CLI be a feature inside my app?
Sometimes. The line is sharper than yes or no, and drawing it gives you a map.
Start with what's usually true: the CLI is already built with WDK. Everything the wdk binary does, the SDK does inside your process. If your app is a server or a phone, shelling out to the CLI means wrapping a library in an executable to call it from outside; you pay for a process, a socket, and an interactive prompt, and gain nothing.
The reasons are specific, and each one names a place:
- Wallets are named files under one OS account. There is no notion of "user 84,912."
- Unlocking is an interactive prompt. There is no one to ask during an HTTP request.
- The trust boundary is a shared OS user. While a wallet is unlocked, any process running as that user that can reach the daemon socket can request signing or sending, without another passphrase.
- There is no Node.js inside an iOS or Android sandbox.
Read those again and notice what they rule out: multi-tenant backends and mobile apps. They don't rule out an app that runs on one person's machine, as long as that person is present.
Imagine a desktop sales assistant app for informal merchants where the CLI is a core component. It creates and encrypts the seller's wallet, unlocks it through the local daemon, derives the Sepolia address, and locks it again. The SDK then signs the escrow operations, and the app refuses to start if the address the CLI derived doesn't match the SDK identity.
Look at how the work is divided. The CLI owns the wallet lifecycle: creation, encryption at rest, the human unlock moment, the lock at the end. The SDK owns the hot path: signing, sending, everything that happens per transaction. The team didn't reimplement passphrase handling and encrypted seed storage, and they didn't shell out for every signature either.
Every objection above is satisfied by that shape. One merchant. One machine. One wallet. The OS user is the person. The prompt has someone to answer it. And it ships as a desktop binary, not a phone app.
So the map:
Use the SDK when the code runs on a server for many users, or on a phone. Both, usually.
The CLI can be a component when the code runs on one operator's own machine, and that operator is the wallet's owner: desktop tools, terminal POS, treasury scripts, local agents, CI jobs...
Where it runs | Whose wallet | What you use |
|---|---|---|
Mobile or desktop app | The end user's | SDK |
Your backend, with agents | Your product's or treasury's | MCP Toolkit |
A developer's laptop | Theirs | CLI |
An MCP client on the user's machine | Theirs, already created | wdk-mcp |
Also, that last row is the nuance. If you're building an MCP client (a desktop assistant, a coding agent, a local bot) your app can gain wallet capabilities by declaring a stdio server pointing at wdk-mcp, without writing any blockchain code. But you're not embedding the CLI. You're connecting to a wallet the user already has, that your app didn't create, can't unlock, and doesn't own.
The CLI isn't something you ship to production. It's something that gets you to production faster. When it's time for your users to have wallets, you change layers: SDK inside the app, MCP Toolkit inside the backend.
Why WDK for this
Self-custodial. The usual way to give an agent money is exchange or custodian API keys: someone else's account, someone else's limits, someone else's freeze button. Here the seed never leaves your infrastructure.
One seed, thirteen chains. An agent needing six integrations for six chains is an agent you won't maintain.
Gasless matters more for agents than for humans. Tell a person to buy ETH before sending USD₮, and they get annoyed. Tell an agent, and it stalls; it has to discover it needs gas, acquire it, wait for confirmation, and retry. The entire gasless series turns out to be a prerequisite for unsupervised agents, not just a UX improvement.
Human confirmation is already in the protocol. You don't invent the approval flow: dryRun: true by default in the CLI, elicitations in the Toolkit.
Key Insights
- One wallet, two doors, but not the same door twice. The CLI and
wdk-mcproute to the same local daemon, the same encrypted seed, the same unlock session. The model's door is narrower by design. - The missing tools are the design. No
create,import,export,unlock,lock,delete,rename, ordefaultover MCP, and no persistent configuration either.wdk wallet unlockis the human authorization moment, deliberately kept on your side. - That boundary is a tool surface, not a sandbox. An AI client with its own shell access can just run
wdk. The eleven tools constrain the MCP door, not the operating system. - Dry run is a default, not a control.
send_tokenstarts atdryRun: true, and the recommended flow is preview: show the user the exact network, token, recipient, amount and fee, then execute. But the daemon enforces none of it: a direct call withdryRun: falsebroadcasts, andcall_methodhas no dry run at all. If you want a guardrail, you build it. - The trust boundary is the OS account, not the prompt. Owner-only socket permissions stop other users, not code running as you. An unlocked wallet is a local hot wallet for the session; no passphrase asked again. Dedicated wallet, short TTL (it's absolute from unlock; agent activity doesn't extend it), funds you can afford to lose.
- Pick the layer by where the code runs and whose wallet it is. SDK in-process for servers and mobile. MCP Toolkit when you need to build the MCP server yourself: your own tool selection, your own custom tools. CLI when the code runs on one operator's own machine, as that operator: your laptop, or a desktop product like Minkaly.
What's next
Follow WDK's official X account for announcements, and join us on Discord to ask questions and get help directly from the team.
Ready to explore? WDK CLI → Get Started with the CLI → Use the MCP Server → Security Model → MCP Toolkit → GitHub
Receive the latest WDK news
Click to subscribe to the WDK newsletter and stay updated!
Latest Articles & Guides



