# Feedback
If you encounter incorrect, outdated, or confusing documentation on any page, submit feedback:
POST https://docs.walletsuite.io/feedback
```json
{
"path": "/current-page-path",
"feedback": "Description of the issue"
}
```
Only submit feedback when you have something specific and actionable to report.
# Choose Your Setup
Source: https://docs.walletsuite.io/ai-agents/choose-your-setup
Pick the WalletSuite MCP setup that matches how you want to use it.
Most users should start with a local stdio server installed through `npx`. It is the fastest path, requires the least setup, and keeps the MCP server colocated with the host process.
## Recommendation table
| Your situation | Recommended setup | Why |
| ------------------------------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------- |
| You want the fastest install | `npx` over stdio | Lowest setup overhead and best default path |
| You need a reusable HTTP endpoint | Docker image over HTTP | Best when your client or workflow prefers a URL-based MCP server |
| You need local signing and local broadcast | Enable OWS | Adds wallet creation, signing, and send tools that run through the local OWS vault — keys stay on the operator host |
## What stdio means
With stdio, your MCP host starts WalletSuite MCP as a local subprocess on the same host. This is the simplest option and the best fit for:
* Agent Frameworks: **LangChain**, **CrewAI**, and **generic MCP runtimes** that launch local subprocesses.
* Desktop & IDE Hosts: **Claude Desktop**, Claude Code, Cursor, VS Code, Codex, and Windsurf.
Use stdio when you want the least operational overhead.
## What HTTP means
With HTTP, WalletSuite MCP runs as a separate local service and your MCP host connects to it by URL.
**Choose HTTP when**:
* your client prefers HTTP servers
* you want one local endpoint that multiple tools can point at
* you are testing local deployment behavior
For most users, **stdio is still simpler**.
## What Docker is for
Docker is the **recommended self-hosting path** when you want HTTP. It gives you:
* A repeatable local runtime
* A clean separation between the client and the MCP server
* A straightforward path to local HTTP testing
Use Docker if you want an HTTP endpoint without managing a local package runtime yourself.
The current documented image is:
```
walletsuite/mcp-server:latest
```
See [Self-Hosting Over HTTP](/ai-agents/self-hosting-over-http) for the exact pull and run commands.
## What OWS is for
**OWS (Open Wallet Standard)** adds optional local signing and broadcast capabilities.
Leave it off unless you need:
* Local wallet creation
* Detached signing of raw unsigned transaction hex
* Local **sign-and-send** through a configured RPC URL
## Advanced Configuration: Stateful vs. Stateless HTTP
WalletSuite MCP supports both `stateless` and `stateful` HTTP modes, but most users should stay with the default `stateless` mode.
* **Stateless**: Best for simpler request-per-call behavior and lower operational complexity.
* **Stateful**: Use this only if you specifically need session-oriented behavior or resumability testing.
## Best path by audience
### Agent frameworks and runtimes
Use:
* `npx` over stdio first
* HTTP only when the framework prefers a URL-based MCP server
This path fits LangChain, CrewAI, and other MCP runtimes that orchestrate tools programmatically.
### Self-hosters
Use:
* HTTP
* the `walletsuite/mcp-server:latest` Docker image first
This path is best when you want a reusable endpoint instead of a host-managed local process.
### Advanced local signers
Use:
* OWS
* owner mode first
Add broadcast only after you have confirmed the local signing flow and RPC configuration.
## Next steps
* Use [Quickstart](/ai-agents/quickstart) if you are ready to install now
* Use [Install Guides](/ai-agents/install-guides) for host-specific steps
* Use [Self-Hosting Over HTTP](/ai-agents/self-hosting-over-http) if you need an endpoint
* Use [OWS Local Signing](/ai-agents/ows-local-signing) if you need signing or broadcast
# Install Guides
Source: https://docs.walletsuite.io/ai-agents/install-guides
Framework- and host-specific installation instructions for WalletSuite MCP Server.
Choose the framework or host where you want to run WalletSuite MCP. All paths require Node.js 22 or newer for the local `npx` setup.
Claude Desktop reads MCP servers from `claude_desktop_config.json`.
Common locations:
* macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
* Windows: `%APPDATA%\\Claude\\claude_desktop_config.json`
If the file does not exist yet, create it.
### Add WalletSuite MCP
```json theme={null}
{
"mcpServers": {
"walletsuite": {
"command": "npx",
"args": ["-y", "@walletsuite/mcp-server"],
"env": {
"WALLETSUITE_API_KEY": "your-key",
"MCP_BANDS": "read"
}
}
}
}
```
After saving, fully restart Claude Desktop so it reloads MCP servers.
### Verify
* `What's the ETH price right now?`
* `Check balances for 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045`
* `Show me recent transactions for 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045 on ethereum`
### Troubleshooting
* **Authentication is required** — API key is missing or empty. Check `WALLETSUITE_API_KEY`.
* **Server does not appear** — verify JSON is valid, file is in the correct location, and Claude Desktop was fully restarted.
* **`npx` is not found** — install Node.js 22 or newer.
Claude Code supports both local stdio and remote HTTP MCP servers.
### stdio via npx (Recommended)
```bash theme={null}
claude mcp add walletsuite --env WALLETSUITE_API_KEY=your-key --env MCP_BANDS=read -- npx -y @walletsuite/mcp-server
```
Add `--scope project` (a Claude CLI flag, not a WalletSuite MCP option) to register the server in the project-level `.mcp.json` so your team shares the same config.
On Windows, wrap with `cmd /c`:
```bash theme={null}
claude mcp add walletsuite --env WALLETSUITE_API_KEY=your-key --env MCP_BANDS=read -- cmd /c npx -y @walletsuite/mcp-server
```
### HTTP endpoint
If you have an HTTP endpoint from [Self-Hosting Over HTTP](/ai-agents/self-hosting-over-http):
```bash theme={null}
claude mcp add --transport http walletsuite http://localhost:3000/mcp
```
Set `WALLETSUITE_API_KEY` on the server process, not in the client config.
### Verify
```bash theme={null}
claude mcp list
```
Then try: `What's the ETH price right now?`
LangChain's `MultiServerMCPClient` supports both `stdio` and `http` transports.
### stdio setup
```ts theme={null}
import { MultiServerMCPClient } from "@langchain/mcp-adapters";
const client = new MultiServerMCPClient({
walletsuite: {
transport: "stdio",
command: "npx",
args: ["-y", "@walletsuite/mcp-server"],
env: {
WALLETSUITE_API_KEY: "your-key",
MCP_BANDS: "read"
}
}
});
```
```python theme={null}
from langchain_mcp_adapters.client import MultiServerMCPClient
client = MultiServerMCPClient(
{
"walletsuite": {
"transport": "stdio",
"command": "npx",
"args": ["-y", "@walletsuite/mcp-server"],
"env": {
"WALLETSUITE_API_KEY": "your-key",
"MCP_BANDS": "read",
},
}
}
)
```
### HTTP alternative
Start the server with [Self-Hosting Over HTTP](/ai-agents/self-hosting-over-http), then:
```ts theme={null}
const client = new MultiServerMCPClient({
walletsuite: {
transport: "http",
url: "http://localhost:3000/mcp"
}
});
```
```python theme={null}
client = MultiServerMCPClient(
{
"walletsuite": {
"transport": "http",
"url": "http://localhost:3000/mcp",
}
}
)
```
### What to test first
Start with read-only: price lookup, balance checks, asset resolution, transaction history. Add `prepare` band only when needed.
CrewAI supports `MCPServerStdio` and `MCPServerHTTP` in the agent `mcps` list.
### stdio setup
```python theme={null}
from crewai import Agent
from crewai.mcp import MCPServerStdio
agent = Agent(
role="Wallet Analyst",
goal="Use wallet data tools inside a CrewAI workflow",
backstory="Agent with access to WalletSuite MCP tools",
mcps=[
MCPServerStdio(
command="npx",
args=["-y", "@walletsuite/mcp-server"],
env={
"WALLETSUITE_API_KEY": "your-key",
"MCP_BANDS": "read",
},
)
],
)
```
### HTTP alternative
Start the server with [Self-Hosting Over HTTP](/ai-agents/self-hosting-over-http), then:
```python theme={null}
from crewai import Agent
from crewai.mcp import MCPServerHTTP
agent = Agent(
role="Wallet Analyst",
goal="Use a shared WalletSuite MCP endpoint",
backstory="Agent with access to WalletSuite MCP tools",
mcps=[
MCPServerHTTP(
url="http://localhost:3000/mcp",
streamable=True,
)
],
)
```
### What to test first
Start with read-only tasks: price lookup, balance checks, asset resolution, transaction history.
Use this when your framework supports MCP but does not have a dedicated guide above.
### stdio settings
```json theme={null}
{
"command": "npx",
"args": ["-y", "@walletsuite/mcp-server"],
"env": {
"WALLETSUITE_API_KEY": "your-key",
"MCP_BANDS": "read"
}
}
```
### HTTP alternative
Start the server with [Self-Hosting Over HTTP](/ai-agents/self-hosting-over-http), then point the runtime at `http://localhost:3000/mcp`. Set `WALLETSUITE_API_KEY` on the server process.
### Validation
Test with read-only operations first: price lookup, balance checks, transaction history, asset resolution. Expand the band only when needed.
## Next steps
* [Security & Trust](/ai-agents/security-model) — understand the non-custodial model before enabling more capability
* [OWS Local Signing](/ai-agents/ows-local-signing) — enable local signing and broadcast
* [Tool Reference](/ai-agents/tool-reference) — full WalletSuite MCP tool surface
# Overview
Source: https://docs.walletsuite.io/ai-agents/overview
Governed wallet infrastructure for AI agents. Band-filtered access, policy-gated signing, and structured error recovery across multiple blockchains.
WalletSuite MCP is the governed wallet layer for AI agents. It exposes wallet operations — balances, transaction preparation, signing, and broadcast — as MCP tools bounded by band filtering, policy gates, and a hash-chained audit trail. Signing is optional and stays local to the customer's infrastructure.
WalletSuite MCP is non-custodial. We never hold your keys or funds. Signing happens locally via OWS with policy controls.
## Architecture positioning
WalletSuite MCP is a non-custodial orchestration layer connecting:
* AI agents and MCP-compatible frameworks such as LangChain, CrewAI, and custom agent runtimes
* MCP-compatible hosts such as Claude Desktop and Claude Code
* any MCP runtime that can launch a local server process or connect to an MCP URL
* the WalletSuite API for wallet data, asset data, and transaction preparation
* local signing through OWS on the operator-controlled host
In practice, that means:
* signing is local to the host running OWS
* keys never leave that host
* WalletSuite MCP never has custody of funds or private keys
**Regulated or compliance-bound team?** Audit trail and policy gates are enforced outside the LLM's reach, non-custodial by architecture, and verifiable end-to-end. See [Security Diligence](/security/diligence) for the procurement-ready posture.
## What you can do
With WalletSuite MCP connected, an AI client can:
* Check a native balance for an address on a specific chain
* Return a broader portfolio view with native and token balances
* Look up token prices by symbol or contract
* Estimate transfer fees before sending
* Resolve an asset symbol or name into the exact token contract you need
* Check transaction status and recent transaction history
* Prepare transactions for later signing or execution
* Optionally create a local wallet, sign raw transactions, and broadcast them through OWS
## Safe by default
WalletSuite MCP is intentionally conservative:
* The default band is `read`, so a new install starts with read-only tools
* `prepare`, `sign`, and `broadcast` are opt-in
* Local signing is disabled unless you explicitly enable OWS
* WalletSuite does not take custody of your private keys
* Secrets are provided through environment variables, not through tool arguments
## What you need
Before you install WalletSuite MCP, make sure you have:
* WalletSuite API key — see [Credentials & Authentication](/getting-started/prerequisites/credentials-and-authentication)
* An MCP-compatible framework or runtime such as LangChain, CrewAI, Claude Desktop, Claude Code, or any custom agent host
* Node.js with `npx` available if you want the simplest local stdio setup
Frameworks can integrate as long as they support MCP over `stdio` or `http`. Use the local `npx` path when the framework launches MCP subprocesses directly, or use the HTTP setup when the framework prefers a URL-based MCP server.
## Recommended default path
For most users, the best starting point is:
1. Install WalletSuite MCP as a local stdio server with `npx`
2. Set `WALLETSUITE_API_KEY`
3. Keep `MCP_BANDS=read` or `read,prepare` until you need more
## What this is not
WalletSuite MCP is not:
* A hosted wallet user interface
* A browser extension
* A shared remote signing service
If you need local wallet creation and signing, enable OWS and keep those keys local.
## Next steps
* Start with [Quickstart](/ai-agents/quickstart) if you want the fastest path
* Read [Choose Your Setup](/ai-agents/choose-your-setup) if you are deciding between stdio, HTTP, and OWS
* Read [Security & Trust](/ai-agents/security-model) if you want to understand the non-custodial model before enabling more capability
* Go straight to an [Install Guide](/ai-agents/install-guides) if you already know your framework or MCP host
# OWS Local Signing
Source: https://docs.walletsuite.io/ai-agents/ows-local-signing
Enable optional local wallet creation, signing, and local broadcast through Open Wallet Standard.
By default:
* OWS is disabled
* signing tools are not exposed
* broadcast tools are not exposed
When OWS is enabled, WalletSuite MCP remains non-custodial. Keys stay in the local OWS vault.
OWS is the operator-side trust anchor for signing:
* keys stay on the operator-controlled host
* WalletSuite MCP never receives private keys
* signing happens locally, with owner or policy-based control depending on mode
## Vault location
By default, OWS uses this local vault path:
```
~/.ows
```
To use a different vault directory, set:
```bash theme={null}
OWS_VAULT_PATH=/path/to/your/ows-vault
```
Use the same `OWS_VAULT_PATH` value for:
* the WalletSuite MCP server process
* the OWS agent bootstrap helper, if you use agent mode
#### Enable owner mode
Set:
```bash theme={null}
OWS_ENABLED=true
OWS_VAULT_PATH=~/.ows
OWS_AUTH_MODE=owner
OWS_PASSPHRASE=your-passphrase
```
If you also want local broadcast with `send_transaction`, add the chain RPC URLs you need:
```bash theme={null}
OWS_ETHEREUM_RPC_URL=https://...
OWS_TRON_RPC_URL=https://...
```
#### What tools appear
Owner mode can expose:
* `create_wallet`
* `create_custom_policy` — register a scoped OWS policy (chain allowlist + expiry)
* `create_agent_api_key` — mint an agent token bound to a wallet and optional policy; token is written to a mode-`0600` file and never returned in chat
* `get_wallet_address`
* `sign_transaction`
* `send_transaction` when the broadcast band is enabled
`create_wallet`, `create_custom_policy`, and `create_agent_api_key` are owner-exclusive — they are not registered when the server runs in agent mode.
#### Create a wallet
After the server starts in owner mode, ask the MCP host:
```
Create a wallet called treasury
```
`create_wallet` creates a local OWS wallet, derives the supported addresses, and returns wallet metadata. It does not return the mnemonic or private keys.
#### Signing vs sending
Use `sign_transaction` when you want detached signing of a raw unsigned transaction hex blob.
Use `send_transaction` only when:
* the user has explicitly confirmed the transaction
* you set `confirmBroadcast=true`
* the matching chain RPC URL is configured
`send_transaction` is the destructive path because it broadcasts an on-chain transaction.
#### Policy controls
Owner mode is direct local control: the user unlocks the local vault and signs from that host.
Agent mode is delegated local control: a pre-created OWS token is bound to the wallet and its policy.
In the current default WalletSuite MCP setup, that policy gates agent-mode signing by:
* allowed chains
* an expiry window
If the request falls outside policy, OWS rejects the signing operation before the key is used.
#### Why agent mode exists
Agent mode lets one process sign with a pre-created scoped token instead of a passphrase. This is useful when you want a narrower automation flow after the wallet has already been provisioned by an owner.
This is the default policy-based signing path for automation. The token is local, scoped, and time-bounded by the OWS policy it is tied to.
**Running automation on production infra?** Agent mode is the pattern when one MCP deployment represents one identity — a trading bot, a treasury service, a scheduled rebalancer — and you want it signing with a scoped, time-bounded token instead of an interactive passphrase. Rotate tokens on your schedule; bind each to a policy (chain allowlist + spend limit + expiry) that matches what that identity is allowed to do.
#### Step 1: create the agent token
From the published npm package:
```bash theme={null}
OWS_VAULT_PATH=~/.ows
npx -y --package @walletsuite/mcp-server walletsuite-mcp-ows-bootstrap-agent --token-file ~/.walletsuite/ows-agent-token
```
The token-file workflow is the recommended path because it keeps the raw token out of terminal history and gives the file restrictive permissions.
#### Step 2: start the server in agent mode
```bash theme={null}
OWS_ENABLED=true
OWS_VAULT_PATH=~/.ows
OWS_AUTH_MODE=agent
OWS_AGENT_TOKEN=$(cat ~/.walletsuite/ows-agent-token)
```
#### What tools appear
Agent mode can expose:
* `get_wallet_address`
* `sign_transaction`
* `send_transaction` when the broadcast band is enabled
`create_wallet` remains owner-only.
## Broadcast requirements
`send_transaction` only works when all of the following are true:
* OWS is enabled
* the `broadcast` band is active
* `confirmBroadcast=true` is supplied
* the matching RPC URL is set for the chain
* the input is raw unsigned transaction hex
## Verifying OWS status
When OWS is working, startup logs report one of these states:
* disabled
* owner mode
* agent mode
If `sign_transaction` is missing from the visible tools, OWS is not enabled for that MCP instance or the active band set does not include `sign`.
## Common issues
### `OWS_LOAD_FAILED` or `Failed to load @open-wallet-standard/core`
The server dynamically imports `@open-wallet-standard/core` when OWS is enabled, and the import threw at runtime. Common causes: the package is not installed in the runtime's `node_modules`, a platform-specific build artifact is missing or incompatible with the current Node.js / OS / architecture, or the dependency is being loaded from a stale install. Reinstall the MCP server (or the `@open-wallet-standard/core` dependency) on the target platform with Node.js 22 or newer and retry.
### `OWS_WALLET_EXISTS`
The requested wallet name already exists in the local vault. Choose a different `walletName` or reuse the existing wallet.
### `OWS_WALLET_NOT_FOUND`
The named wallet does not exist in the selected vault. Check `walletName`, verify `OWS_VAULT_PATH`, or create the wallet first in owner mode.
### `OWS_ACCOUNT_NOT_FOUND`
The wallet exists, but it does not expose an account for the requested chain. Use a wallet with support for the selected OWS chain.
### `OWS_AGENT_TOKEN_EXPIRED`
Regenerate the token, update `OWS_AGENT_TOKEN`, and restart the server.
### `OWS_AGENT_TOKEN_INVALID`
The configured token does not exist in the vault. Generate a new agent token and update `OWS_AGENT_TOKEN`.
### `OWS_CREDENTIAL_INVALID`
The passphrase or token does not match the vault. Double-check `OWS_PASSPHRASE` or `OWS_AGENT_TOKEN`.
### `OWS_POLICY_DENIED`
The current OWS policy rejected the signing request. Use an allowed chain or generate a token with a policy that permits the operation.
### `OWS_INVALID_UNSIGNED_TX`
The input is not valid raw unsigned transaction hex. Pass raw unsigned transaction hex to `sign_transaction` or `send_transaction`.
### `OWS_RPC_URL_REQUIRED`
`send_transaction` was called without the matching RPC URL environment variable for the selected chain.
### `OWS_OWNER_MODE_REQUIRED`
You tried to run an owner-only flow, such as wallet creation or agent-token bootstrap, while the server was configured for agent mode. Restart with `OWS_AUTH_MODE=owner` and `OWS_PASSPHRASE`.
### `OWS_OPERATION_FAILED`
OWS returned a generic failure. Check server logs, verify the selected vault path and credentials, and retry with the simplest working flow first.
### Signing tools are missing
Check:
* `OWS_ENABLED=true`
* `MCP_BANDS` includes `sign`
* `MCP_BANDS` includes `broadcast` if you expect `send_transaction`
# Quickstart
Source: https://docs.walletsuite.io/ai-agents/quickstart
The fastest way to connect WalletSuite MCP to an MCP-compatible client or framework.
WalletSuite MCP runs locally in your MCP host. You do not need to clone a repo or build the server from source to get started.
WalletSuite MCP is non-custodial — we never hold your keys or funds. You choose where signing happens:
* **Self-hosted (OWS vault)** — keys stay on your infrastructure, signed in place. This is the default for the `npx` setup below.
* **Hosted (secure enclave, coming soon)** — hardware-attested enclaves with a level of public verifiability the category hasn't seen: every deployment ships with cryptographic proof you can verify yourself, anchored to a public transparency log. Zero local setup.
See [Choose Your Setup](/ai-agents/choose-your-setup) for the trade-offs, and the [Security model](/ai-agents/security-model) for the custody and approval model.
## 1. Get your API key
WalletSuite API key — see [Credentials & Authentication](/getting-started/prerequisites/credentials-and-authentication).
## 2. Add WalletSuite MCP to your client or framework
Use the local stdio `npx` setup first. It is the recommended default path for most users.
```json theme={null}
{
"mcpServers": {
"walletsuite": {
"command": "npx",
"args": ["-y", "@walletsuite/mcp-server"],
"env": {
"WALLETSUITE_API_KEY": "your-key",
"MCP_BANDS": "read"
}
}
}
}
```
Notes:
* `WALLETSUITE_API_KEY` is required
* `MCP_BANDS` is optional; if you omit it, WalletSuite MCP starts in `read`
* `read` is the safest default for a new install
* Node.js 22 or newer is required for the local `npx` path
* this process runs locally on the host you run it from; WalletSuite does not receive your private keys
Your MCP framework or host may expect this config in a different file or under a different top-level key. If so, use the matching page in [Install Guides](/ai-agents/install-guides).
Framework note:
* if your framework supports MCP over `stdio`, this `npx` configuration is the simplest path
* if your framework prefers MCP over `http`, use [Self-Hosting Over HTTP](/ai-agents/self-hosting-over-http) instead
## 3. Restart or reload your MCP host
After you save the config:
* restart the host application, or
* reload MCP servers in the host settings if the host supports that workflow
## 4. Test it with prompts
Once the server is connected, try prompts like:
* `What's the ETH price right now?`
* `Check balances for 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045`
* `Show me recent transactions for 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045 on ethereum`
* `Resolve USDC on ethereum so I can prepare a transfer`
## 5. Expand only when you need to
Start narrow, then widen the tool surface only if your workflow needs it.
Common [band](/core-concepts/band-filtering) choices:
* `read`: balances, prices, fee quotes, asset resolution, transaction queries
* `read,prepare`: adds transaction-preparation tools
* `read,prepare,sign`: adds local OWS signing tools when OWS is enabled
* `full`: enables all four band slots: `read`, `prepare`, `sign`, and `broadcast`
`full` does not enable OWS by itself. The `sign` and `broadcast` tools still require `OWS_ENABLED=true`, and `send_transaction` also needs the matching RPC configuration.
Before you enable signing or broadcast, read [Security & Trust](/ai-agents/security-model) and [OWS Local Signing](/ai-agents/ows-local-signing).
## Key concepts
Skim these core ideas — they're what makes WalletSuite MCP different:
Four-tier tool visibility: read, prepare, sign, broadcast. Each caller gets only what its role requires.
Spend limits, chain allowlists, counterparty screening. Evaluated before any key is touched.
Category, code, and required action on every error. Agents recover programmatically.
## Where to go next
* Use [Choose Your Setup](/ai-agents/choose-your-setup) if you are deciding between stdio, HTTP, or OWS
* Read [Security & Trust](/ai-agents/security-model) if you want the custody and approval model first
* Use [Install Guides](/ai-agents/install-guides) for framework- or host-specific instructions
* Use [OWS Local Signing](/ai-agents/ows-local-signing) if you need local signing or broadcast
* Use [Tool Reference](/ai-agents/tool-reference) if you want a complete surface overview
## See it working
End-to-end recipes for the common agent stacks:
* [LangChain agent wallet](/recipes/langchain-agent-wallet) — give a LangChain agent a governed wallet
* [CrewAI multi-agent](/recipes/crewai-multi-agent) — coordinate multiple agents, each with a scoped token
* [x402 micropayments](/recipes/x402-micropayments) — agent-to-merchant payments on the x402 rail
* [Counterparty screening](/recipes/counterparty-screening) — pre-send risk check before broadcast
# Security & Trust
Source: https://docs.walletsuite.io/ai-agents/security-model
Why WalletSuite MCP is safe to use with wallet data and optional local signing.
WalletSuite MCP is designed to be useful without taking custody of your keys or exposing more capability than necessary.
In wallets, the core trust question is who controls the keys. With WalletSuite MCP, the user controls the keys through the local OWS vault.
> WalletSuite MCP is non-custodial. We never hold your keys or funds. Signing happens locally via OWS with policy controls.
## Trust model
WalletSuite MCP keeps the orchestration core private and replaces source-code visibility with three explicit architectural guarantees:
**Non-custodial by design.** The server never stores or transmits private keys. A fresh install uses only the WalletSuite API key — no local signing, no broadcast. Enabling OWS keeps keys in an encrypted vault on the host you control; signing stays under user or policy control.
**Architecture transparency.** The `prepare → sign → broadcast` flow is [publicly documented](/core-concepts/transaction-lifecycle). Responsibilities between WalletSuite MCP, the WalletSuite API, OWS, and the caller are explicit. Policy boundaries for signing are stated in [Policy Gates](/core-concepts/policy-gates).
**Minimal public surface.** WalletSuite exposes the npm package, Docker image, MCP tool interfaces, and these docs. Internal orchestration details that aren't required for safe integration are not disclosed.
## How signing works
WalletSuite MCP separates transaction preparation from signing:
```text theme={null}
User or AI host -> WalletSuite MCP -> prepare transaction
User or AI host -> OWS on the operator's host -> sign locally
Signed transaction -> chain RPC -> broadcast
```
WalletSuite MCP prepares and orchestrates. OWS performs local signing. Wallet keys remain on the host that runs OWS — your infrastructure, never WalletSuite's.
## Separation of responsibilities
| Component | Responsibility |
| -------------------- | ------------------------------------------------------- |
| WalletSuite MCP | MCP orchestration and tool exposure |
| WalletSuite API | wallet data, prices, balances, and transfer preparation |
| OWS | local key storage and local signing |
| User or policy owner | final approval of sensitive actions |
## Policy-based signing
OWS adds the signing control layer.
In owner mode:
* the user controls signing directly through the local vault passphrase
* wallet creation is available
In agent mode:
* signing uses a pre-created scoped OWS token
* the current default WalletSuite MCP policy limits signing to the allowed chains in that wallet policy and an expiry window
* policy denial blocks the signing request before the key is used
This means WalletSuite MCP can prepare and route requests, but the final signing boundary still sits inside OWS.
## What WalletSuite does not do
WalletSuite does not:
* store private keys
* custody funds
* sign transactions on your behalf
* move funds without an enabled signing path and explicit approval
## Attack model
If WalletSuite MCP is misconfigured or compromised on its own:
* an attacker still does not get your private keys
* an attacker still cannot sign transactions unless local OWS signing is enabled and reachable
* an attacker still cannot broadcast through `send_transaction` unless the broadcast band is enabled, the RPC URL is configured, and the request is explicitly confirmed
This matters because the trust boundary is not "the server is perfect." The trust boundary is that key custody and signing stay local.
## What WalletSuite does not control
WalletSuite does not control:
* the contents of your local OWS vault
* the approval decision to sign when OWS is configured for local signing
* the host where the vault, environment variables, and MCP server run
Those boundaries stay with the user or operator.
## What risks still exist
Non-custodial does not mean zero risk.
Important remaining risks are:
* enabling broader bands than you need
* approving or automating a transaction you did not inspect carefully
* running on a compromised host
* leaking environment secrets such as API keys, passphrases, or agent tokens
The mitigation is to keep bands narrow, use OWS deliberately, scope agent tokens, and review destructive actions carefully.
## Bands control tool exposure
WalletSuite MCP exposes tools through bands:
| Band | What it allows |
| ----------- | ------------------------------------------------------------------------------ |
| `read` | balances, prices, fee quotes, asset resolution, transaction status and history |
| `prepare` | transaction-preparation tools |
| `sign` | local wallet lookup and detached signing through OWS |
| `broadcast` | local sign-and-send through OWS |
The default is `read`.
This matters because the best security control for AI tooling is often not a runtime warning. It is not exposing a more powerful tool in the first place.
## Why the default should stay narrow
Read-only tools answer many wallet questions without creating transfer payloads or exposing any signing surface.
A safe rollout pattern is:
1. start with `read`
2. move to `read,prepare` only when you need transaction preparation
3. add `sign` only when you are ready to manage a local OWS wallet
4. add `broadcast` only when you are ready to execute on-chain actions
## Detached signing vs local broadcast
`sign_transaction` and `send_transaction` are intentionally separate.
### `sign_transaction`
Use this when you want detached signing of raw unsigned transaction hex.
This is still sensitive, but it does not submit anything on-chain by itself.
### `send_transaction`
Use this only for explicit user-approved broadcasts.
`send_transaction` is the destructive path because it signs and broadcasts the transaction. WalletSuite MCP requires:
* the broadcast band
* `confirmBroadcast=true`
* the matching RPC URL for the selected chain
## Secrets stay out of tool arguments
Sensitive values are supplied through environment variables, not through tool arguments.
Examples:
* `WALLETSUITE_API_KEY`
* `OWS_PASSPHRASE`
* `OWS_AGENT_TOKEN`
* `OWS_ETHEREUM_RPC_URL`
* `OWS_TRON_RPC_URL`
This keeps secrets out of normal user prompts and out of tool argument history.
## Local signing boundaries
When OWS is enabled:
* keys stay local
* signing happens inside OWS
* the MCP tool surface only receives wallet names, chain ids, and raw unsigned transaction hex where applicable
WalletSuite MCP does not expose mnemonics or private keys through the tool responses.
## Verifiable audit trail
Every `sign_transaction` and `send_transaction` call appends a structured JSON receipt to a local log file at `~/.walletsuite/audit-trail.jsonl` (override with `WALLETSUITE_AUDIT_PATH`). Receipts are linked by a SHA-256 hash chain: each entry records the previous entry's hash, so tampering with any line invalidates every line after it. Secrets are redacted before write (API keys, mnemonics, bearer tokens) and transaction hex is truncated.
The audit trail is local by default — it stays on the host that runs the MCP server, under operator control, and can be shipped to a SIEM or archived on a schedule. See [Audit Trail](/security/audit-trail) for the full receipt schema, hash-chain verification, and SIEM integration guidance.
## Why WalletSuite MCP is closed-source
WalletSuite MCP is distributed as a product surface through the published npm package and Docker image, while the core implementation remains private.
Trust comes from:
* non-custodial architecture
* local signing boundaries
* explicit capability controls through bands and OWS modes
* clear operational documentation
Closed source does not change the custody model: WalletSuite still does not hold your keys.
## Practical guidance
Use these defaults unless you have a reason not to:
* `MCP_BANDS=read` for a new install
* no OWS on day one
* no broadcast until the local signing flow is proven
If you need more capability later, add it deliberately instead of enabling the entire surface at once.
# Self-Host Signing
Source: https://docs.walletsuite.io/ai-agents/self-host-signing
Deploy WalletSuite MCP with local OWS signing for full key control on your infrastructure.
**Time:** 15-20 minutes.
## What You'll Build
A self-hosted MCP server that can:
* Create wallets with BIP-39 mnemonics (stored encrypted in a local vault)
* Sign transactions locally for Ethereum and Tron
* Enforce policy gates (chain allowlists, expiry)
* Broadcast signed transactions to chain RPCs
* Run as a Docker container or Node.js process
## Architecture
```
Your Infrastructure
|
|-- MCP Server (HTTP, port 3000)
| |-- Read/Prepare tools -> WalletSuite API
| \-- Sign/Broadcast tools -> OWS Vault (local)
|
|-- OWS Vault (~/.ows, AES-256-GCM encrypted)
|
\-- Chain RPCs (Ethereum, Tron)
```
Keys never leave your infrastructure. The MCP server orchestrates, OWS signs.
Self-host signing is the path for regulated, audit-driven, or compliance-bound organizations that require key custody on their own infrastructure. See [Security Diligence](/security/diligence) for the procurement-ready security posture.
## Prerequisites
* WalletSuite API key — see [Credentials & Authentication](/getting-started/prerequisites/credentials-and-authentication)
* Docker (recommended) or Node.js 22+
* Ethereum and/or Tron RPC URL (for broadcasting)
## Step 1 — Configure Environment
Create an `.env` file:
```env theme={null}
# Required
WALLETSUITE_API_KEY=your-api-key
# Transport
MCP_TRANSPORT=http
MCP_HTTP_MODE=stateless
MCP_BANDS=full
# OWS — Owner mode for initial setup
OWS_ENABLED=true
OWS_AUTH_MODE=owner
OWS_PASSPHRASE=your-secure-passphrase
OWS_VAULT_PATH=~/.ows
# Chain RPCs (required for broadcasting)
OWS_ETHEREUM_RPC_URL=https://your-ethereum-rpc.com
OWS_TRON_RPC_URL=https://your-tron-rpc.com
```
Do not commit the `.env` file to version control. Add it to `.gitignore`.
## Step 2 — Start the Server
```bash theme={null}
docker run --name walletsuite-mcp --rm -d \
--env-file .env \
-p 3000:3000 \
-v ~/.ows:/home/mcp/.ows \
walletsuite/mcp-server:latest
```
The `-v` flag mounts your local vault into the container so keys persist across restarts.
Verify health:
```bash theme={null}
curl http://localhost:3000/health
```
```bash theme={null}
npx -y @walletsuite/mcp-server
```
The server reads `.env` from the current directory. Health check at `http://localhost:3000/health`.
## Step 3 — Create a Wallet (Owner Mode)
Connect to the MCP server and ask:
```
Create a wallet called treasury
```
The `create_wallet` tool generates a BIP-39 mnemonic, derives addresses for Ethereum and Tron, and stores the key material encrypted in the vault.
The mnemonic is shown once during creation. Save it securely — it cannot be retrieved later.
You will see the derived addresses:
```json theme={null}
{
"wallet": {
"name": "treasury",
"accounts": [
{ "chainId": "eip155:1", "address": "0x1234..." },
{ "chainId": "tron:mainnet", "address": "T1234..." }
]
}
}
```
## Step 4 — Test Signing
There are two shapes to test. Start with the detached path — it exercises the full signing flow without touching the chain.
### Detached signing (safe; no on-chain side effect)
```
Prepare a transfer of 0.001 ETH from treasury to 0xdef... and sign it
```
The agent will:
1. Call `prepare_serialized_unsigned_tx` → builds the unsigned transaction hex
2. Show you a review (amount, recipient, estimated fee)
3. Call `sign_transaction` → signs locally and returns the signature. Nothing is broadcast.
### Sign and broadcast (destructive; sends on-chain)
`send_transaction` is the destructive path — it signs **and** broadcasts. It requires `confirmBroadcast=true`, the matching chain RPC URL, and produces a real on-chain transaction hash that cannot be undone once confirmed. Use a testnet wallet and funded testnet address before running this against mainnet.
```
Prepare a transfer of 0.001 ETH from treasury to 0xdef... and broadcast it
```
The agent will prepare the transaction, show you the review, and then call `send_transaction` with `confirmBroadcast=true`. The response is the resulting transaction hash.
## Step 5 — Create a Policy and Agent Key
For production, switch from owner mode to agent mode with a restricted policy.
### Create a policy
```
Create a policy called "eth-only-90d" allowing Ethereum only, expiring in 90 days
```
### Create an agent key bound to that policy
```
Create an agent API key for wallet treasury with policy eth-only-90d
```
The token is written to `~/.walletsuite/ows-agent-token` (mode `0600`).
### Switch to agent mode
Update your `.env`:
```env theme={null}
OWS_AUTH_MODE=agent
OWS_AGENT_TOKEN=
# Remove OWS_PASSPHRASE
```
Restart the server. The agent can now sign only on Ethereum and only until the policy expires. It cannot create wallets, keys, or policies.
## Step 6 — Connect Clients
Point any MCP-compatible client at the HTTP endpoint:
```json theme={null}
{
"mcpServers": {
"walletsuite": {
"type": "http",
"url": "http://localhost:3000/mcp"
}
}
}
```
Or with Claude Code:
```bash theme={null}
claude mcp add --transport http walletsuite http://localhost:3000/mcp
```
## Production Recommendations
* **Use agent mode** — never run owner mode in production. Owner mode is for setup only.
* **Set restrictive policies** — chain allowlists + short expiry windows.
* **Back up the vault** — `~/.ows` contains your encrypted keys. Losing it means losing access.
* **Use HTTPS RPCs** — HTTP is allowed only for localhost.
* **Monitor the audit trail** — `~/.walletsuite/audit-trail.jsonl` logs every signing operation.
* **Run the production checklist** — [Production Checklist](/production-checklist).
## Related
* [OWS Local Signing](/ai-agents/ows-local-signing) — full OWS reference (owner mode, agent mode, troubleshooting)
* [Self-Hosting Over HTTP](/ai-agents/self-hosting-over-http) — HTTP transport details (stateless vs stateful, Docker vs Node.js)
* [Choose Your Setup](/ai-agents/choose-your-setup) — stdio vs HTTP decision guide
* [Policy Gates](/core-concepts/policy-gates) — constraining what agents can sign
* [Key Management](/security/key-management) — vault encryption and key lifecycle
# Self-Hosting Over HTTP
Source: https://docs.walletsuite.io/ai-agents/self-hosting-over-http
Run WalletSuite MCP as a local HTTP endpoint instead of a host-managed stdio process.
Self-hosting over HTTP is the right choice when you want WalletSuite MCP to run as a reusable local service instead of being launched separately by each MCP host.
Use HTTP when:
* your client prefers or requires a URL-based MCP server
* you want one local endpoint that multiple tools can reuse
* you are testing local deployment behavior or HTTP integrations
* you operate production infrastructure (trading engine, vault manager, treasury ops, bot fleet) and want WalletSuite running alongside it as a long-lived local service, pinned at startup to its own [OWS agent token](/ai-agents/ows-local-signing) — one deployment represents one identity
For most users, stdio is still simpler. If you do not specifically need an HTTP endpoint, use one of the [Install Guides](/ai-agents/install-guides) instead.
These examples use the published `walletsuite/mcp-server:latest` image.
Authentication note:
* `WALLETSUITE_API_KEY` must be set on the HTTP server process itself
* MCP clients that connect by URL do not forward that key automatically to the server
### 1. Create an env file
```bash theme={null}
printf 'WALLETSUITE_API_KEY=your-key\nMCP_BANDS=read\nMCP_HTTP_MODE=stateless\n' > .env.walletsuite-mcp
```
Add OWS variables only if you need local signing or broadcast.
### 2. Start the server
```bash theme={null}
docker run --name walletsuite-mcp --rm -d --env-file .env.walletsuite-mcp -p 3000:3000 walletsuite/mcp-server:latest
```
### 3. Verify health
```bash theme={null}
curl http://localhost:3000/health
```
### 4. Connect a client
If your MCP host supports HTTP MCP servers directly, point it at:
```
http://localhost:3000/mcp
```
Generic HTTP config:
```json theme={null}
{
"mcpServers": {
"walletsuite": {
"type": "http",
"url": "http://localhost:3000/mcp"
}
}
}
```
Claude Code example:
```bash theme={null}
claude mcp add --transport http walletsuite-http http://localhost:3000/mcp
```
Use this when you want an HTTP server without Docker but still want to install from the published package instead of from source.
This path requires Node.js 22 or newer.
### Start the server
```bash theme={null}
MCP_TRANSPORT=http MCP_HTTP_MODE=stateless MCP_BANDS=read WALLETSUITE_API_KEY=your-key PORT=3000 npx -y @walletsuite/mcp-server
```
### Verify health
```bash theme={null}
curl http://localhost:3000/health
```
### Connect a client
Point your MCP host at:
```
http://localhost:3000/mcp
```
## HTTP modes
WalletSuite MCP supports two HTTP runtime modes:
### `stateless`
This is the default and the best choice for most local HTTP setups.
Use it when:
* you want simpler request-per-call behavior
* you do not need session-oriented behavior
* you want the least operational complexity
### `stateful`
Use this only when you specifically need session-oriented behavior or resumability testing.
Most users do not need it for day-to-day wallet queries or transfer preparation.
If you use `stateful`, the main tuning knobs are:
* `MCP_SESSION_TTL_MS`
* `MCP_MAX_SESSIONS`
* `MCP_SHUTDOWN_TIMEOUT_MS`
## Key environment variables
| Variable | Default | What it does |
| ------------------------- | ----------- | -------------------------------------------------------- |
| `MCP_TRANSPORT=http` | none | Starts the server in HTTP mode |
| `MCP_HTTP_MODE` | `stateless` | Chooses `stateless` or `stateful` |
| `PORT` | `3000` | Sets the HTTP port |
| `MCP_BANDS` | `read` | Controls which tools are exposed |
| `WALLETSUITE_API_KEY` | none | Authenticates the server to WalletSuite |
| `MCP_SESSION_TTL_MS` | `1800000` | Stateful mode only. Session time-to-live in milliseconds |
| `MCP_MAX_SESSIONS` | `1000` | Stateful mode only. Maximum active sessions |
| `MCP_SHUTDOWN_TIMEOUT_MS` | `5000` | Graceful shutdown timeout in milliseconds |
## OWS with HTTP
If you want local signing on top of HTTP, add the same OWS environment variables you would use in stdio mode:
* `OWS_ENABLED=true`
* `OWS_AUTH_MODE=owner` or `agent`
* `OWS_PASSPHRASE` or `OWS_AGENT_TOKEN`
* chain RPC URLs if you want local broadcast
## When not to use HTTP
Do not use HTTP just because it sounds more flexible. Stay with stdio if:
* your host already supports local stdio servers well
* you only need one host on one machine
* you do not want to run or monitor a separate local service
## Roadmap: hosted deployment (Q2–Q3 2026)
A hosted WalletSuite deployment is planned for consumer-scale platforms and agent SaaS — the shape where a single backend serves many end-user agents. Signing runs inside a hardware-isolated Trusted Execution Environment (TEE) with per-tenant AEAD key isolation, KMS-managed envelope encryption, and remote attestation for integrity guarantees — architecturally distinct from self-hosted OWS and not a drop-in replacement for this page's pattern. [Contact us](mailto:contact@walletsuite.io) for early-access details.
## Next steps
* Use [OWS Local Signing](/ai-agents/ows-local-signing) if you want local signing on top of your HTTP deployment
* Use [Troubleshooting](/ai-agents/troubleshooting) if the server starts but your host cannot connect
# Supported Chains
Source: https://docs.walletsuite.io/ai-agents/supported-chains
Current chain support for WalletSuite MCP.
WalletSuite MCP support depends on the operation type.
* Read and prepare operations follow the broader WalletSuite backend-supported chain surface.
* Local OWS signing ships on the chains with the highest production demand and expands as customers need them.
These chains are currently supported for the read and prepare surface:
| Chain ID | Chain Name | Family | Native Symbol |
| -------------- | ---------------------- | -------- | ------------- |
| `base` | Base | Ethereum | ETH |
| `linea` | Linea | Ethereum | ETH |
| `mantle` | Mantle | Ethereum | MNT |
| `ethereum` | Ethereum | Ethereum | ETH |
| `classic` | Ethereum Classic | Ethereum | ETC |
| `tron` | Tron | Tron | TRX |
| `vechain` | VeChain | Ethereum | VET |
| `viction` | Viction | Ethereum | VIC |
| `thundertoken` | ThunderCore | Ethereum | TT |
| `harmony` | Harmony | Ethereum | ONE |
| `oasis` | Oasis | Ethereum | ROSE |
| `smartchain` | Smart Chain | Ethereum | BNB |
| `polygon` | Polygon | Ethereum | POL |
| `rootstock` | Rootstock | Ethereum | RBTC |
| `optimism` | Optimism | Ethereum | ETH |
| `polygonzkevm` | Polygon zkEVM | Ethereum | ETH |
| `zksync` | Zksync | Ethereum | ETH |
| `scroll` | Scroll | Ethereum | ETH |
| `arbitrum` | Arbitrum | Ethereum | ETH |
| `arbitrumnova` | Arbitrum Nova | Ethereum | ETH |
| `avalanchec` | Avalanche C-Chain | Ethereum | AVAX |
| `xdai` | xDai | Ethereum | xDAI |
| `fantom` | Fantom | Ethereum | FTM |
| `celo` | Celo | Ethereum | CELO |
| `ronin` | Ronin | Ethereum | RON |
| `cronos` | Cronos Chain | Ethereum | CRO |
| `kavaevm` | KavaEvm | Ethereum | KAVA |
| `kcc` | KuCoin Community Chain | Ethereum | KCS |
| `metis` | Metis | Ethereum | METIS |
| `aurora` | Aurora | Ethereum | ETH |
| `evmos` | Evmos | Ethereum | EVMOS |
| `moonriver` | Moonriver | Ethereum | MOVR |
| `moonbeam` | Moonbeam | Ethereum | GLMR |
| `kaia` | Kaia | Ethereum | KAIA |
| `opbnb` | OpBNB | Ethereum | BNB |
| `neon` | Neon | Ethereum | NEON |
| `manta` | Manta Pacific | Ethereum | ETH |
| `merlin` | Merlin | Ethereum | BTC |
| `blast` | Blast | Ethereum | ETH |
| `sonic` | Sonic | Ethereum | S |
| `plasma` | Plasma | Ethereum | XPL |
| `monad` | Monad | Ethereum | MON |
| `megaeth` | MegaETH | Ethereum | ETH |
| `flare` | Flare | Ethereum | FLR |
| `xdc` | XDC Network | Ethereum | XDC |
| `unichain` | Unichain | Ethereum | ETH |
| `berachain` | Berachain | Ethereum | BERA |
| `chiliz` | Chiliz Chain | Ethereum | CHZ |
| `lumia` | Lumia | Ethereum | LUMIA |
| `hyperevm` | HyperEVM | Ethereum | HYPE |
| `lisk` | Lisk | Ethereum | ETH |
| `mocachain` | MocaChain | Ethereum | MOCA |
| `abstract` | Abstract | Ethereum | ETH |
| `mantrachain` | MantraChain | Ethereum | OM |
| `haqq` | HAQQ Network | Ethereum | ISLM |
| `plume` | Plume | Ethereum | ETH |
| `fuse` | Fuse | Ethereum | FUSE |
| `core` | Core | Ethereum | CORE |
| `bittorrent` | BitTorrent Chain | Ethereum | BTT |
| `mode` | Mode | Ethereum | ETH |
| `taiko` | Taiko | Ethereum | ETH |
| `shibarium` | Shibarium | Ethereum | BONE |
| `ink` | Ink | Ethereum | ETH |
| `bob` | BOB | Ethereum | ETH |
| `hemi` | Hemi | Ethereum | ETH |
| `apechain` | ApeChain | Ethereum | APE |
| `fraxtal` | Fraxtal | Ethereum | frxETH |
| `superseed` | Superseed | Ethereum | ETH |
| `everclear` | Everclear | Ethereum | ETH |
| `worldchain` | World Chain | Ethereum | ETH |
| `soneium` | Soneium | Ethereum | ETH |
| `kroma` | Kroma | Ethereum | ETH |
| `zora` | Zora | Ethereum | ETH |
| `swell` | Swell | Ethereum | ETH |
| `hashkey` | HashKey Chain | Ethereum | HSK |
| `cronoszkevm` | Cronos zkEVM | Ethereum | zkCRO |
| `morph` | Morph | Ethereum | ETH |
| `lens` | Lens Network | Ethereum | GHO |
| `zircuit` | Zircuit | Ethereum | ETH |
OWS signing ships on the chains with the highest production demand. Additional chains are added as customers need them.
| Chain ID | Chain Name | Family | Native Symbol |
| ---------- | ---------- | -------- | ------------- |
| `ethereum` | Ethereum | Ethereum | ETH |
| `tron` | Tron | Tron | TRX |
Use this table when you are configuring:
* `get_wallet_address`
* `sign_transaction`
* `send_transaction`
If you are preparing a transaction on a chain outside this table, you can still use the read and prepare tools, but local OWS signing will not be available for that chain.
# Tool Reference
Source: https://docs.walletsuite.io/ai-agents/tool-reference
The public WalletSuite MCP tool surface.
## Bands
| Band | What it includes | Typical use |
| ----------- | ------------------------------------------------------------------- | ------------------------------------------------ |
| `read` | balances, prices, fee quotes, asset resolution, transaction queries | safe default |
| `prepare` | transaction preparation tools | prepare transactions before signing or execution |
| `sign` | local OWS wallet and signing tools | local detached signing |
| `broadcast` | local OWS sign-and-send | execute on-chain actions |
If OWS is disabled, the sign and broadcast tools are not exposed even if the band set includes them.
## Token-transfer pattern
When you are working with token transfers, use this order:
1. Call `resolve_asset` with the chain and a token symbol, name, or contract
2. Choose the intended candidate
3. Pass the selected `tokenContract` into the transaction-preparation step
WalletSuite MCP does not guess token contracts from symbols or names.
## Read Tools
### `get_balance`
**What it does:** Returns the native balance for one address on one chain.
**Band:** `read`
**Required inputs:**
* `address`
* `chain`
**Optional inputs:**
* `fiat` (defaults to `USD`)
**Returns:** Native asset balance and valuation details for the selected chain.
**Example prompt:** `Check the native balance for 0x... on ethereum.`
### `get_all_balances`
**What it does:** Returns native and token balances for one address on one chain.
**Band:** `read`
**Required inputs:**
* `address`
* `chain`
**Optional inputs:**
* `fiat` (defaults to `USD`)
* `assetIds`
* `includeNative` (defaults to `true`)
**Returns:** A broader portfolio view for the selected chain.
**Example prompt:** `Show all balances for 0x... on ethereum.`
### `get_price`
**What it does:** Returns the current price for a token by symbol or by contract address.
**Band:** `read`
**Required inputs:**
* either `symbol`
* or both `contractAddress` and `chain`
**Optional inputs:**
* `base` (defaults to `USD`)
**Returns:** Current token price data for the requested pair.
**Example prompt:** `What's the price of ETH?`
### `get_fee_quote`
**What it does:** Estimates the network fee for a native or token transfer before execution.
**Band:** `read`
**Required inputs:**
* `chain`
* `from`
* `to`
* `amountWei`
**Optional inputs:**
* `tokenContract`
* `memo`
* `fiat`
**Returns:** Estimated network fee details for the proposed transfer.
**Example prompt:** `Estimate the fee to send 1000000 smallest-unit USDC from 0x... to 0x... on ethereum.`
### `resolve_asset`
**What it does:** Resolves a token symbol, name, or exact contract into one or more chain-specific asset candidates.
**Band:** `read`
**Required inputs:**
* `chain`
* exactly one of `tokenContract`, `symbol`, or `name`
**Optional inputs:**
* `limit` (defaults to `5`, max `10`)
**Returns:** A candidate list plus a resolution status of `unique`, `ambiguous`, or `not_found`.
**Example prompt:** `Resolve USDC on ethereum so I can prepare a transfer.`
### `get_tx_status`
**What it does:** Returns the status of one transaction hash on one chain.
**Band:** `read`
**Required inputs:**
* `hash`
* `chain`
**Returns:** A transaction status such as `PENDING`, `SUCCEEDED`, `FAILED`, or `UNKNOWN`.
**Example prompt:** `What's the status of transaction 0x... on ethereum?`
### `get_tx_history`
**What it does:** Returns recent transaction history for one address on one chain.
**Band:** `read`
**Required inputs:**
* `address`
* `chain`
**Optional inputs:**
* `limit` (defaults to `10`, max `50`)
**Returns:** A trimmed list of recent transactions for the address.
**Example prompt:** `Show me the last 10 transactions for 0x... on ethereum.`
## Prepare Tools
### `prepare_transfer`
**What it does:** Prepares a structured transfer request for later execution.
**Band:** `prepare`
**Required inputs:**
* `chain`
* `from`
* `to`
* either `amount` or `amountWei`
**Optional inputs:**
* `symbol`
* `tokenContract`
* `nonce`
* `maxPriorityFeePerGasWei`
* `priorityFeeMultiplier`
**Important rules:**
* provide either `amount` or `amountWei`, never both
* token transfers require `tokenContract`
* if you only know the token symbol or name, resolve it first with `resolve_asset`
**Returns:** A structured prepared transfer result for the requested transaction.
**Example prompt:** `Prepare a transfer of 1 ETH from 0x... to 0x... on ethereum.`
### `prepare_serialized_unsigned_tx`
**What it does:** Prepares signing-ready raw unsigned transaction hex for local OWS signing or local OWS broadcast.
**Band:** `prepare`
**Required inputs:**
* `chain`
* `to`
* either `walletName` or `from`
* either `amount` or `amountWei`
**Optional inputs:**
* `walletName`
* `from`
* `symbol`
* `tokenContract`
* `nonce`
* `maxPriorityFeePerGasWei`
* `priorityFeeMultiplier`
**Important rules:**
* provide either `amount` or `amountWei`, never both
* token transfers require `tokenContract`
* if you only know the token symbol or name, resolve it first with `resolve_asset`
* currently supports the OWS signing chains: `ethereum` and `tron`
**Returns:** Signing-ready raw unsigned transaction hex plus a review summary.
**Example prompt:** `Prepare a signing-ready transaction to send 1 ETH from my local wallet to 0x... on ethereum.`
### `prepare_onramp`
**What it does:** Prepares a MoonPay on-ramp widget URL for funding a destination address or a local OWS wallet on a supported chain.
**Band:** `prepare`
**Required inputs:**
* `chain`
* exactly one of `address` or `walletName`
**Optional inputs:**
* `currencyCode` (defaults to the chain-native asset)
* `baseCurrencyCode` (required if `baseCurrencyAmount` or `quoteCurrencyAmount` is provided)
* one of `baseCurrencyAmount` or `quoteCurrencyAmount`
* `lockAmount` (only valid with `baseCurrencyAmount`)
* `redirectUrl` (HTTPS)
**Returns:** A MoonPay widget URL plus the resolved destination address and next-action hint.
**Notes:**
* Requires MoonPay credentials on the server. If unconfigured, the call returns a structured `not_available` error.
* If `walletName` is provided, the tool resolves the chain-specific OWS address before building the widget URL.
**Example prompt:** `Prepare an on-ramp to fund my treasury wallet with 500 USD on ethereum.`
## OWS Sign Tools
### `create_wallet`
**What it does:** Creates a new local OWS wallet and derives addresses for the supported OWS chains.
**Band:** `sign`
**Required inputs:**
* `walletName`
**Availability:** Owner mode only.
**Returns:** Wallet metadata, derived addresses, and default policy metadata.
**Example prompt:** `Create a wallet called treasury.`
### `get_wallet_address`
**What it does:** Returns the address for a named local OWS wallet on a supported OWS chain.
**Band:** `sign`
**Required inputs:**
* `walletName`
* `chain`
**Returns:** The local wallet address for the selected chain.
**Example prompt:** `Get the ethereum address for my local wallet named treasury.`
### `sign_transaction`
**What it does:** Signs a raw unsigned transaction hex blob with a local OWS wallet.
**Band:** `sign`
**Required inputs:**
* `walletName`
* `chain`
* `unsignedTxHex`
**Returns:** A signature and recovery id when available.
**Important note:** This works with raw unsigned transaction hex.
**Example prompt:** `Sign this raw unsigned ethereum transaction hex with my treasury wallet.`
### `create_custom_policy`
**What it does:** Registers a local OWS custom policy with declarative chain and expiry constraints, and returns the resulting `policyId` for later agent-key binding.
**Band:** `sign`
**Required inputs:**
* `policyName`
* at least one of `allowedChains` or `expiresAt`
**Optional inputs:**
* `allowedChains` (defaults to all OWS signing chains)
* `expiresAt` (ISO 8601; defaults to 30 days from now)
* `maxPerTxNative`, `dailyNativeBudget` — accepted in schema for forward compatibility; currently return an upstream-blocked error on calls that set them
**Availability:** Owner mode only.
**Returns:** The new policy's id, name, allowed chains, and expiry.
**Example prompt:** `Create a local OWS policy called trader-90d limited to ethereum and tron, expiring in 90 days.`
### `create_agent_api_key`
**What it does:** Mints a local OWS agent API key bound to a wallet (and optionally a specific policy), writes the secret token to a mode-0600 file, and returns metadata only. The raw token is never returned in chat.
**Band:** `sign`
**Required inputs:**
* `walletName`
**Optional inputs:**
* `policyId` (defaults to the wallet's default policy)
* `tokenFile` (defaults to `~/.walletsuite/ows-agent-token`)
* `expiryMode` — one of `policy_default` (default), `one_year`, or `custom`
* `policy_default` inherits the bound policy's expiry exactly as stored (wallet default policy: 90 days; custom policy without explicit `expiresAt`: 30 days)
* `one_year` caps at 365 days from creation, but never later than the bound policy's expiry
* `custom` requires an explicit `expiresAt` that must stay within the bound policy's expiry
* `expiresAt` — required only when `expiryMode=custom`
**Availability:** Owner mode only.
**Returns:** Key metadata (`keyId`, `keyName`, `walletId`, `policyId`, `expiryMode`, `expiresAt`), the token file path, and a `nextAction` hint to restart the server in agent mode with `OWS_AGENT_TOKEN` set from the file.
**Example prompt:** `Create an agent API key for my treasury wallet bound to the trader-90d policy.`
## OWS Broadcast Tools
### `send_transaction`
**What it does:** Signs and broadcasts a raw unsigned transaction hex blob with a local OWS wallet.
**Band:** `broadcast`
**Required inputs:**
* `walletName`
* `chain`
* `unsignedTxHex`
* `confirmBroadcast=true`
**Returns:** The resulting transaction hash.
**Important notes:**
* this is the destructive path
* explicit confirmation is required
* the matching RPC URL must be configured
* this works with raw unsigned transaction hex
**Example prompt:** `Broadcast this unsigned ethereum transaction with my treasury wallet.`
# Troubleshooting
Source: https://docs.walletsuite.io/ai-agents/troubleshooting
Common WalletSuite MCP issues and how to fix them.
Use this page when WalletSuite MCP is installed but something is not working as expected.
## Authentication errors
See [Credentials & Authentication](/getting-started/prerequisites/credentials-and-authentication) for the header contract, tier rules, and full error taxonomy.
### Symptom: `Authentication is required`
This usually means `WALLETSUITE_API_KEY` is missing.
Check:
* the MCP host config file
* environment variable spelling
* whether the config was reloaded after editing
Fix:
* add a valid `WALLETSUITE_API_KEY`
* restart or reload the MCP host
### Symptom: requests fail even though the server starts
This often means the API key is present but invalid for the current environment.
Fix:
* verify the key in WalletSuite
* replace the key and reload the host
## Missing tools
### Symptom: only read tools are visible
This is normal if you did not set `MCP_BANDS`.
Fix:
* use `MCP_BANDS=read,prepare` if you need transaction preparation tools
* add `sign` and `broadcast` only when you also plan to enable OWS
### Symptom: signing tools are missing
Check:
* `OWS_ENABLED=true`
* `MCP_BANDS` includes `sign`
### Symptom: `send_transaction` is missing
Check:
* `OWS_ENABLED=true`
* `MCP_BANDS` includes `broadcast`
## OWS issues
### Symptom: `OWS_LOAD_FAILED` or `Failed to load @open-wallet-standard/core`
The local OWS binary is not available for your platform.
Fix:
* verify the host is using a supported macOS or Linux build
* reinstall dependencies if needed
### Symptom: `OWS_WALLET_EXISTS`
The requested wallet name already exists in the local vault.
Fix:
* choose a different `walletName`, or
* use the existing wallet instead of creating a new one
### Symptom: `OWS_WALLET_NOT_FOUND`
OWS could not find the named wallet.
Fix:
* verify `walletName`
* verify `OWS_VAULT_PATH` if you are using a non-default vault location
* create the wallet first in owner mode if it does not exist yet
### Symptom: `OWS_ACCOUNT_NOT_FOUND`
The wallet exists, but it does not expose an account for the selected OWS chain.
Fix:
* verify the requested chain
* use a wallet with support for that chain
### Symptom: `OWS_CREDENTIAL_INVALID`
The configured passphrase or token does not match the local vault.
Fix:
* verify `OWS_PASSPHRASE` in owner mode
* verify `OWS_AGENT_TOKEN` in agent mode
* restart the server after making changes
### Symptom: `OWS_AGENT_TOKEN_EXPIRED`
The token must be regenerated by the owner bootstrap flow.
Fix:
1. generate a fresh token
2. update `OWS_AGENT_TOKEN`
3. restart the server
### Symptom: `OWS_AGENT_TOKEN_INVALID`
OWS could not find the configured agent token.
Fix:
1. generate a new token from the owner bootstrap flow
2. update `OWS_AGENT_TOKEN`
3. restart the server
### Symptom: `OWS_POLICY_DENIED`
OWS policy rejected the signing request.
Fix:
* retry with an allowed chain
* use a token whose policy allows the required operation
### Symptom: `OWS_INVALID_UNSIGNED_TX`
The transaction input is not valid raw unsigned transaction hex.
Fix:
* pass raw unsigned transaction hex only
* do not pass JSON objects
### Symptom: `OWS_RPC_URL_REQUIRED`
You called `send_transaction` without the matching chain RPC URL.
Fix:
* set `OWS_ETHEREUM_RPC_URL` for ethereum
* set `OWS_TRON_RPC_URL` for tron
### Symptom: `OWS_OWNER_MODE_REQUIRED`
You tried to perform an owner-only operation while running in agent mode.
Fix:
* restart with `OWS_AUTH_MODE=owner`
* set `OWS_PASSPHRASE`
### Symptom: `OWS_OPERATION_FAILED`
OWS returned a generic failure that did not map to a more specific code.
Fix:
* check the server logs
* verify vault path, credentials, and input data
* retry with the simplest working owner-mode flow first
## HTTP connection issues
### Symptom: the client cannot connect to the HTTP server
Check:
* that the server is running
* `http://localhost:3000/health`
* the full MCP path `http://localhost:3000/mcp`
### Symptom: port already in use
Another local process is already using the selected port.
Fix:
* stop the conflicting process, or
* choose another port, for example `PORT=3001`
### Symptom: confusion between stateless and stateful mode
Most users should stay with `MCP_HTTP_MODE=stateless`.
Only switch to `stateful` if you specifically need session-oriented behavior.
## Host config issues
### Symptom: the MCP host ignores the config
This often means the file location or top-level JSON shape is wrong for the host.
Fix:
* use the host-specific page in [Install Guides](/ai-agents/install-guides)
* validate the JSON
* reload the host
### Symptom: the host still uses old settings
Fix:
* restart the application, or
* reload MCP servers from the host UI or CLI if the host supports it
## Feature availability issues
### Symptom: a tool exists but calls return a not-available error
Some backend features are feature-gated.
Fix:
* confirm whether the capability is enabled for your plan
* fall back to the currently available read and prepare surface when possible
## Still stuck?
If the server starts but behavior is still off:
1. reduce the config back to the simplest working shape
2. keep `MCP_BANDS=read`
3. disable OWS
4. verify one read-only prompt first
5. add HTTP, `prepare`, or OWS only after the base setup works
If the problem persists, contact the WalletSuite team at [support@walletsuite.io](mailto:support@walletsuite.io) — include your MCP host, band config, a recent stderr snippet, and the chain you were hitting.
# Build approve transaction
Source: https://docs.walletsuite.io/api-reference/approvals/build-approve-transaction
/openapi.json post /api/approvals/build
# Check if approval is sufficient
Source: https://docs.walletsuite.io/api-reference/approvals/check-if-approval-is-sufficient
/openapi.json post /api/approvals/check
# Get allowance
Source: https://docs.walletsuite.io/api-reference/approvals/get-allowance
/openapi.json post /api/approvals/allowance
# Get asset restrictions
Source: https://docs.walletsuite.io/api-reference/asset-status/get-asset-restrictions
/openapi.json get /api/assets/{id}/restrictions
# Get asset status
Source: https://docs.walletsuite.io/api-reference/asset-status/get-asset-status
/openapi.json get /api/assets/{id}/status
# Get single asset by id
Source: https://docs.walletsuite.io/api-reference/assets/get-single-asset-by-id
/openapi.json get /api/assets/{id}
Returns metadata for a single asset id if present
# List or resolve supported assets
Source: https://docs.walletsuite.io/api-reference/assets/list-or-resolve-supported-assets
/openapi.json get /api/assets
Returns active assets. Without symbol or name, returns the full list. With symbol or name, resolves matching candidates in deterministic order.
# Get balances for native + selected token assets
Source: https://docs.walletsuite.io/api-reference/balances/get-balances-for-native-+-selected-token-assets
/openapi.json get /api/balances/{address}
Returns native + token balances in parallel, skips zero balances, and includes total fiat value.
If assetIds is omitted, uses the default supported set:
- EVM (ethereum): USDT + USDC
- TRON: USDT
# Get native balance
Source: https://docs.walletsuite.io/api-reference/balances/get-native-balance
/openapi.json get /api/balance/{address}
Returns the native balance for the given address on the specified chain. One RPC call; fiat valuation is optional.
# Latest indexed blocks
Source: https://docs.walletsuite.io/api-reference/blocks/latest-indexed-blocks
/openapi.json get /api/blocks/latest
Returns the newest blocks that indexer has already stored.
# EVM fee estimate for L2 chains
Source: https://docs.walletsuite.io/api-reference/chain-specific/evm-fee-estimate-for-l2-chains
/openapi.json post /api/chain/evm/estimate-l2-fee
# EVM token metadata by contract
Source: https://docs.walletsuite.io/api-reference/chain-specific/evm-token-metadata-by-contract
/openapi.json post /api/chain/evm/token-metadata
# Tron account resources
Source: https://docs.walletsuite.io/api-reference/chain-specific/tron-account-resources
/openapi.json get /api/chain/tron/resources/{address}
# Get chain by key
Source: https://docs.walletsuite.io/api-reference/chains/get-chain-by-key
/openapi.json get /api/chains/{chain}
Returns metadata for a single blockchain network available under your API plan.
# Get chain fee policy
Source: https://docs.walletsuite.io/api-reference/chains/get-chain-fee-policy
/openapi.json get /api/chains/{chain}/fee-policy
# Get chains by SLIP-0044 id
Source: https://docs.walletsuite.io/api-reference/chains/get-chains-by-slip-0044-id
/openapi.json get /api/chains/slip/{slipId}
Returns chains matching the given SLIP-0044 (BIP-44) id under your API plan.
# List supported chains
Source: https://docs.walletsuite.io/api-reference/chains/list-supported-chains
/openapi.json get /api/chains
Returns blockchain networks available under your API plan. Optionally filter results by chain family.
# Transfer fee quote
Source: https://docs.walletsuite.io/api-reference/fees/transfer-fee-quote
/openapi.json post /api/fees/quote
Returns a fee estimate for a transaction. For token transfers pass tokenContract and amountWei as token smallest units.
# Chains info
Source: https://docs.walletsuite.io/api-reference/info/chains-info
/openapi.json get /api/info/chains
Returns chain families and chain counts per family.
# General info summary
Source: https://docs.walletsuite.io/api-reference/info/general-info-summary
/openapi.json get /api/info
Returns summary counts for assets and supported chains.
# Resolve address to name
Source: https://docs.walletsuite.io/api-reference/name-service/resolve-address-to-name
/openapi.json post /api/names/reverse
# Resolve name to address
Source: https://docs.walletsuite.io/api-reference/name-service/resolve-name-to-address
/openapi.json post /api/names/resolve
# Build NFT transfer transaction
Source: https://docs.walletsuite.io/api-reference/nfts/build-nft-transfer-transaction
/openapi.json post /api/nfts/transfer/build
# Get NFT item metadata
Source: https://docs.walletsuite.io/api-reference/nfts/get-nft-item-metadata
/openapi.json get /api/nfts/item
# Get NFT owners
Source: https://docs.walletsuite.io/api-reference/nfts/get-nft-owners
/openapi.json get /api/nfts/owners
# List NFT collections by owner address
Source: https://docs.walletsuite.io/api-reference/nfts/list-nft-collections-by-owner-address
/openapi.json get /api/nfts/collections/{address}
# Get price by contract address
Source: https://docs.walletsuite.io/api-reference/prices/get-price-by-contract-address
/openapi.json get /api/price/by-contract/{address}
Checks if the contract is a supported asset, then uses its symbol to return the price. If missing, refreshes first.
# Get price by symbol
Source: https://docs.walletsuite.io/api-reference/prices/get-price-by-symbol
/openapi.json get /api/price/{symbol}
Returns the latest price from DB; if missing, refreshes from provider and returns it.
# Build claim rewards transaction
Source: https://docs.walletsuite.io/api-reference/staking/build-claim-rewards-transaction
/openapi.json post /api/staking/claim-rewards/build
# Build delegate transaction
Source: https://docs.walletsuite.io/api-reference/staking/build-delegate-transaction
/openapi.json post /api/staking/delegate/build
# Build undelegate transaction
Source: https://docs.walletsuite.io/api-reference/staking/build-undelegate-transaction
/openapi.json post /api/staking/undelegate/build
# Get staking APR
Source: https://docs.walletsuite.io/api-reference/staking/get-staking-apr
/openapi.json get /api/staking/apr
# Get staking positions by address
Source: https://docs.walletsuite.io/api-reference/staking/get-staking-positions-by-address
/openapi.json get /api/staking/positions/{address}
# List validators
Source: https://docs.walletsuite.io/api-reference/staking/list-validators
/openapi.json get /api/staking/validators
# Build swap transaction
Source: https://docs.walletsuite.io/api-reference/swaps/build-swap-transaction
/openapi.json post /api/swaps/build
# Swap quote
Source: https://docs.walletsuite.io/api-reference/swaps/swap-quote
/openapi.json post /api/swaps/quote
# Swap route by quoteId
Source: https://docs.walletsuite.io/api-reference/swaps/swap-route-by-quoteid
/openapi.json post /api/swaps/route
# Swap supported tokens
Source: https://docs.walletsuite.io/api-reference/swaps/swap-supported-tokens
/openapi.json get /api/swaps/tokens
# Live transaction status
Source: https://docs.walletsuite.io/api-reference/transactions/live-transaction-status
/openapi.json get /api/txs/status/{hash}
Checks a transaction by hash via node RPC (Ethereum) or FullNode APIs (Tron) and returns a normalized status.
# Prepare transfer signing payload
Source: https://docs.walletsuite.io/api-reference/transactions/prepare-transfer-signing-payload
/openapi.json post /api/txs/prepare-sign
Prepares signing payload for TRANSFER_NATIVE or TRANSFER_TOKEN, calculates nonce (EVM), calldata (EVM ERC20), fee params, and runs a simulation check.
Amount can be specified in two ways (mutually exclusive):
- `amountWei` — raw amount in smallest units (wei / sun)
- `amount` — human-readable amount (e.g. "1.5" for 1.5 ETH); backend resolves decimals automatically
When using `amount`, decimals are resolved from the chain (native transfers) or from the asset registry for the provided `tokenContract` (token transfers).
For `TRANSFER_TOKEN`, `tokenContract` is required.
`symbol` is optional and only acts as an auxiliary decimal hint after the token is identified by `tokenContract`. Returns 400 if decimals cannot be resolved.
# Send signed transaction
Source: https://docs.walletsuite.io/api-reference/transactions/send-signed-transaction
/openapi.json post /api/txs/send
Submits a signed raw transaction to the blockchain network and returns its transaction hash/txid.
- Ethereum (EVM): rawSignedTx must be 0x-prefixed hex
- Tron: rawSignedTx must be hex (0x prefix allowed)
# Transaction history
Source: https://docs.walletsuite.io/api-reference/transactions/transaction-history
/openapi.json get /api/txs/history/{address}
Returns recent transactions for the given address on a selected chain. Mocked data only for Pilot tier initially (no indexer).
# Deep Links
Source: https://docs.walletsuite.io/build-with-ai/deep-links
Chat with WalletSuite docs in Claude or ChatGPT with preloaded context.
Useful for troubleshooting, code generation, or exploring a topic with AI assistance. Available on every docs page.
## How to use
On any docs page:
1. Click the **Copy page** button (top right)
2. Choose **Chat with Claude** or **Chat with ChatGPT**
This opens a new AI chat session with the full page content already loaded. Ask follow-up questions, request code examples, or troubleshoot issues — the AI has the relevant docs context without you needing to paste anything.
## Copy as Markdown
You can also copy any page as Markdown for embedding in your own prompts, READMEs, or internal docs. Use the **Copy page** button and select **Copy as Markdown**.
# Docs MCP Server
Source: https://docs.walletsuite.io/build-with-ai/docs-mcp-server
Query WalletSuite docs from Claude, Cursor, VS Code, or any MCP client.
Search and read the docs from inside your development environment, no browser tab required.
## Connect to the MCP server
1. Go to [Connectors](https://claude.ai/settings/connectors) in Claude settings
2. Select **Add custom connector**
3. Add the server:
* Name: `WalletSuite`
* URL: `https://docs.walletsuite.io/mcp`
4. Select **Add**
In any chat, click the attachments button (plus icon) and select the WalletSuite connector, then ask your question.
```bash theme={null}
claude mcp add --transport http WalletSuite https://docs.walletsuite.io/mcp
```
Verify:
```bash theme={null}
claude mcp list
```
Open the command palette (`Cmd+Shift+P`), search "Open MCP settings", and add:
```json theme={null}
{
"mcpServers": {
"WalletSuite": {
"url": "https://docs.walletsuite.io/mcp"
}
}
}
```
Test by asking Cursor: "What tools do you have available?"
Create `.vscode/mcp.json` in your project:
```json theme={null}
{
"servers": {
"WalletSuite": {
"type": "http",
"url": "https://docs.walletsuite.io/mcp"
}
}
}
```
## Available tools
Connected AI clients get access to two tools:
| Tool | What It Does |
| ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `search_wallet_suite` | Search across the WalletSuite knowledge base — docs, code examples, API references, guides |
| `query_docs_filesystem_wallet_suite` | Read-only shell-like queries against a virtual filesystem of all docs pages. Supports `cat`, `head`, `rg`, `tree`, `ls`, `grep`, and other standard text utilities |
**Typical workflow:** Use `search_wallet_suite` for broad queries ("how does band filtering work"). Use `query_docs_filesystem_wallet_suite` to read specific pages by path (`head -80 /core-concepts/band-filtering.mdx`) or grep for exact terms (`rg -C 3 "MCP_BANDS" /`).
## LLM feed files
Two continuously updated files for ingesting WalletSuite docs into custom GPTs, RAG pipelines, or any LLM application:
| File | URL | Use Case |
| --------------- | ------------------------------------------------------------------------------ | ------------------------------------------------------------- |
| `llms.txt` | [docs.walletsuite.io/llms.txt](https://docs.walletsuite.io/llms.txt) | Concise page index — good for smaller models or quick context |
| `llms-full.txt` | [docs.walletsuite.io/llms-full.txt](https://docs.walletsuite.io/llms-full.txt) | Full content of all pages — ideal for complete indexing |
Both files are auto-generated and always reflect the latest published docs.
# Changelog
Source: https://docs.walletsuite.io/changelog
What changed and when across WalletSuite docs and products.
## 2026-04-16 — Documentation Update
**Core Concepts** filled with production content:
* [Band Filtering](/core-concepts/band-filtering) — 4-tier access control model with multi-agent patterns and tool annotations
**Use Case Guides** added:
* [Give an AI Agent a Wallet](/getting-started/use-case-guides/give-ai-agent-a-wallet) — end-to-end guide from install to first query in under 5 minutes
**Resources:**
* [Production Checklist](/production-checklist) — pre-go-live validation across API keys, bands, OWS, policies, error handling, monitoring, and security
## 2026-04-01 — MCP Server v1.0
**Initial public release:**
* 7 read tools: `get_balance`, `get_all_balances`, `get_price`, `get_fee_quote`, `resolve_asset`, `get_tx_status`, `get_tx_history`
* 3 prepare tools: `prepare_transfer`, `prepare_serialized_unsigned_tx`, `prepare_onramp`
* 6 OWS signing tools (optional, requires `OWS_ENABLED=true`): `get_wallet_address`, `sign_transaction`, `send_transaction` (both modes); `create_wallet`, `create_custom_policy`, `create_agent_api_key` (owner mode only)
* OWS local signing chains: Ethereum and Tron
* Owner mode and agent mode with policy gates
* stdio and HTTP (stateless/stateful) transport
* 9 install guides: Claude Desktop, Claude Code, Cursor, VS Code, Codex, Windsurf, LangChain, CrewAI, generic MCP client
* Non-custodial architecture — keys never leave the local device
* Band filtering with 4 tiers: read, prepare, sign, broadcast
* Structured error taxonomy with 6 categories and programmatic recovery
* Multi-chain support for read/prepare operations
# Architecture
Source: https://docs.walletsuite.io/core-concepts/architecture
How WalletSuite connects AI agents to wallet infrastructure through governed MCP.
WalletSuite is an intent-driven MCP server that translates natural language wallet requests into blockchain operations. It sits between AI models and the WalletSuite REST API, orchestrating queries, transaction preparation, and optional local signing.
## System Context
```
┌──────────────────┐ ┌─────────────────────────┐ ┌────────────────────┐
│ │ │ │ │ │
│ LLM / Agent │─────▶│ WalletSuite MCP │─────▶│ WalletSuite API │
│ (Claude, GPT, │ MCP │ Server │ HTTP │ (REST API) │
│ custom agent) │◀─────│ │◀─────│ │
│ │ │ │ │ │
└──────────────────┘ └──────────┬──────────────┘ └────────┬───────────┘
│ │
┌─────────▼──────────┐ ┌────────▼───────────┐
│ OWS Signing Layer │ │ Blockchain RPCs │
│ (optional) │ │ (multi-chain) │
│ Local keys, │ │ │
│ policy-gated │ │ │
└────────────────────┘ └────────────────────┘
```
The MCP server runs locally on the host that executes your agent runtime. It calls the WalletSuite REST API for blockchain data and transaction preparation. If OWS local signing is enabled, it also manages a local encrypted key vault for signing and broadcasting.
## Design Principle
Every MCP tool maps to a user outcome, not a REST endpoint. The server orchestrates multiple API calls behind a single tool when needed. For example, a token transfer requires resolving the asset first, then preparing the transaction — the agent handles this as two clear steps, not raw API calls.
## Component Layers
### Transport Layer
Handles MCP protocol communication between the AI host and the server.
| Transport | How It Works | When to Use |
| ------------------- | -------------------------------------------- | -------------------------------------------- |
| **stdio** (default) | Runs as a child process of the MCP host | Claude Desktop, Claude Code, Cursor, VS Code |
| **HTTP stateless** | Each request creates a new server instance | Docker, serverless, auto-scaling |
| **HTTP stateful** | Sessions persist via `Mcp-Session-Id` header | SSE streaming, resumable sessions |
See [Choose Your Setup](/ai-agents/choose-your-setup) for guidance on which transport to use.
### Tool Handler Layer
Maps MCP tool calls to service operations. Each handler:
1. Validates input using Zod schemas (address format, amount, chain)
2. Calls the service layer
3. Returns a structured MCP response or a structured error
Tools are organized by [band](/core-concepts/band-filtering) — read, prepare, sign, broadcast. Only tools in the active band set are registered.
### Service Layer
Shared business logic behind the tool handlers:
| Service | Responsibility |
| ------------------------ | --------------------------------------------------------------------------------- |
| **WalletSuiteApiClient** | Typed HTTP client for the REST API — retry, timeout, response envelope unwrapping |
| **OWS** | Local key generation, encrypted key storage, policy-gated signing |
| **TxCompiler** | Converts API prepare-sign responses into signing-ready unsigned transaction hex |
| **MoonPay** | Widget URL builder for fiat on-ramp (`prepare_onramp` tool) |
| **AuditTrail** | Append-only JSONL log for sign and broadcast operations |
## Boundary of Responsibility
| Component | Owns | Does Not Own |
| -------------------- | ------------------------------------------------------------------------------------- | --------------------------------------------------------------- |
| **MCP Server** | Tool schema, input validation, error mapping, transport, band filtering | Blockchain interaction, caching, chain-specific tx construction |
| **OWS Signing** | Local key generation, encrypted storage, policy enforcement, sign and broadcast | Transaction preparation, MCP protocol |
| **WalletSuite API** | Chain interaction, fee estimation, tx simulation, broadcast, caching, volume tracking | LLM integration, tool discovery, signing |
| **You (integrator)** | LLM orchestration, user confirmation, wallet UI | API orchestration, fee calculation (handled by API) |
## Two Signing Modes
### External Signing (Default)
The MCP server prepares unsigned transaction payloads. You sign with your own wallet infrastructure (HSM, KMS, browser extension, or any signing solution).
```
Agent → prepare_transfer → unsigned payload → your wallet signs → your infra broadcasts
```
Use `MCP_BANDS=read,prepare`. No OWS needed.
### OWS Local Signing (Opt-In)
Keys generated and stored locally via the Open Wallet Standard. Policy-gated signing with chain allowlists and expiry. Keys never leave the device.
```
Agent → prepare_serialized_unsigned_tx → sign_transaction (OWS) → send_transaction (OWS) → chain
```
Requires `OWS_ENABLED=true`. Band set depends on what you want: `read,prepare,sign` is enough for detached signing (`sign_transaction`); add `broadcast` (or use `full`) to also expose `send_transaction`. `full` is a convenience alias for all four bands, not a requirement for OWS. See [OWS Local Signing](/ai-agents/ows-local-signing).
## What the Server Does Not Do
* Store or transmit private keys
* Cache blockchain data (the API backend handles caching)
* Make signing decisions (OWS policy engine or the user decides)
* Expose internal orchestration logic (tools map to outcomes, not implementation details)
# Band Filtering
Source: https://docs.walletsuite.io/core-concepts/band-filtering
4-tier access control model — read, prepare, sign, broadcast — that governs what AI agents can do.
Tools outside the active band set are never registered in the MCP schema — the LLM cannot discover or call them. Bands are resolved once at server startup; there is no per-request override.
## The 4 Bands
| Band | Tools | What the Agent Can Do |
| ------------------ | ------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- |
| **read** (default) | `get_balance`, `get_all_balances`, `get_price`, `get_fee_quote`, `resolve_asset`, `get_tx_status`, `get_tx_history` | Query balances, prices, fees, assets, transaction status |
| **prepare** | + `prepare_transfer`, `prepare_serialized_unsigned_tx`, `prepare_onramp` | Construct unsigned transaction payloads (no execution) |
| **sign** | + `get_wallet_address`, `sign_transaction`, `create_wallet`\*, `create_agent_api_key`\*, `create_custom_policy`\* | OWS local signing and wallet management |
| **broadcast** | + `send_transaction` | Sign and submit transactions to the blockchain |
\*Owner-mode only. See [OWS Local Signing](/ai-agents/ows-local-signing).
Bands are cumulative — each level includes all tools from the levels above it.
## Configuration
Set the `MCP_BANDS` environment variable:
| Value | Active Bands |
| ------------------- | --------------------- |
| `read` | read |
| `read,prepare` | read + prepare |
| `read,prepare,sign` | read + prepare + sign |
| `full` | all 4 bands |
The default is `read`. Start narrow, expand as needed.
```json theme={null}
{
"mcpServers": {
"walletsuite": {
"command": "npx",
"args": ["-y", "@walletsuite/mcp-server"],
"env": {
"WALLETSUITE_API_KEY": "your-key",
"MCP_BANDS": "read,prepare"
}
}
}
}
```
## How It Works
```
Agent sends tool call
→ MCP Server checks: is this tool in the active band set?
→ YES: execute the tool, return result
→ NO: tool does not exist in the schema — the agent never sees it
```
This is not runtime authorization. The tools literally do not exist in the MCP tool list. An agent configured with `MCP_BANDS=read` has 7 tools in its schema. An agent with `MCP_BANDS=full` has 16 tools in owner mode or 13 in agent mode — the three owner-only tools (`create_wallet`, `create_custom_policy`, `create_agent_api_key`) are not registered when the server runs in agent mode. There is nothing to bypass because the restricted tools were never registered.
## Tool Annotations
Every tool carries advisory metadata that helps MCP clients categorize behavior:
| Band | Read-only | Destructive | Idempotent | Open-world |
| --------- | --------- | ----------- | ---------- | ---------- |
| read | yes | no | yes | yes |
| prepare | no | no | no | yes |
| sign | no | no | yes | no |
| broadcast | no | yes | no | yes |
Read tools are safe and repeatable. Sign tools are idempotent because signing the same payload always produces the same signature. Broadcast tools are destructive because submitting a transaction to the blockchain cannot be undone once confirmed.
## Multi-Agent Pattern
Run multiple MCP server instances with different band configurations to implement role-based access:
| Agent Role | Band Config | Tools Visible | Purpose |
| ---------------- | ------------------------ | ----------------------- | --------------------------------- |
| Monitoring agent | `MCP_BANDS=read` | 7 | Portfolio queries, price alerts |
| Treasury agent | `MCP_BANDS=read,prepare` | 10 | Prepare transactions for review |
| Executor agent | `MCP_BANDS=full` | 16 (owner) / 13 (agent) | Sign and broadcast (policy-gated) |
Each agent sees only the tools appropriate to its role. The executor agent's signing is further constrained by [Policy Gates](/core-concepts/policy-gates) — band filtering controls visibility, policies control behavior.
## Feature-Gated Tools
Some tools within active bands may return `not_available` errors if the backend feature is not enabled for your API key (e.g., swaps, staking, NFTs). This is a separate layer:
* **Bands** control the MCP tool schema (which tools exist)
* **Feature gates** control the backend API (which tools succeed)
A tool gated by the backend returns a structured error with `category: "not_available"` and a message explaining why. See [Structured Errors](/core-concepts/structured-errors) for how to handle these.
## Why Start with Read
Read-only tools answer most wallet questions without creating transfer payloads or exposing any signing surface:
* "What's my ETH balance?" — `get_balance`
* "How much is 1 ETH in USD?" — `get_price`
* "What would the gas fee be for this transfer?" — `get_fee_quote`
* "Show me recent transactions" — `get_tx_history`
A safe rollout pattern:
1. Start with `read` — prove the integration works
2. Add `prepare` when you need transaction construction
3. Add `sign` when you are ready to manage a local OWS wallet
4. Add `broadcast` when you are ready to execute on-chain actions
Each step is a deliberate expansion, not an accident.
# Policy Gates
Source: https://docs.walletsuite.io/core-concepts/policy-gates
Spend limits, chain allowlists, and counterparty screening that control agent signing behavior.
Policy gates are declarative rules enforced by the OWS signing layer before any transaction is signed. They control what a signed transaction is allowed to do — which chains, what expiry window, and (when available) spending limits.
Policies work alongside [band filtering](/core-concepts/band-filtering):
* **Bands** control which tools the agent can see (MCP layer)
* **Policies** control what the visible tools can do (OWS layer)
An agent with `MCP_BANDS=full` but a restrictive policy can see all tools but will be denied by the policy when trying to sign for an unauthorized chain.
## How Policies Work
1. You create a policy with constraints (allowed chains, expiry)
2. You create an agent API key bound to that policy
3. When the agent requests a signature, OWS evaluates the policy before touching any key material
4. If the policy denies the request, signing fails with a `flow` error containing the specific rule that triggered and a `requiredAction` to fix it
All policies bound to an agent key are evaluated with AND semantics — the first denial short-circuits. Policies are stored locally in the OWS vault and cannot be overridden by the MCP server or LLM.
## Available Constraints
| Constraint | What It Controls | Status |
| ------------------- | ---------------------------------------------------------------------------------------- | --------- |
| `allowedChains` | List of CAIP-2 chain IDs the agent can sign for (e.g., `["eip155:1"]` for Ethereum only) | Available |
| `expiresAt` | ISO 8601 timestamp after which the agent key stops working | Available |
| `maxPerTxNative` | Maximum native token value per transaction | Planned |
| `dailyNativeBudget` | Daily cumulative spend cap in native token | Planned |
### Not yet in MCP
Upstream OWS supports additional declarative policy rules that WalletSuite MCP does not currently surface through the `create_custom_policy` tool schema. Notably, `allowed_typed_data_contracts` — which scopes EIP-712 signing to a contract allowlist — is implementable at the OWS layer but is not wired through MCP tool arguments today. Use a direct OWS configuration if you need it before MCP exposes it.
## Creating a Policy
Use the `create_custom_policy` tool in **owner mode** (requires `MCP_BANDS` including `sign`):
```
Create a policy called "eth-only-30d" that allows signing on Ethereum only, expiring in 30 days.
```
The agent calls:
```json theme={null}
{
"name": "create_custom_policy",
"arguments": {
"policyName": "eth-only-30d",
"allowedChains": ["eip155:1"],
"expiresAt": "2026-05-16T00:00:00Z"
}
}
```
Returns a `policyId` — a server-generated UUID — alongside the `policyName` you supplied. Capture the `policyId` from the response; you bind agent keys to the UUID, not to the name.
```json theme={null}
// Example create_custom_policy response
{
"policyId": "2f6d3a1e-94c7-4c8e-9c5a-7b0e1f2a3b4c",
"policyName": "eth-only-30d",
"summary": { "allowedChains": ["eip155:1"], "expiresAt": "2026-05-16T00:00:00Z" }
}
```
## Binding a Policy to an Agent Key
Use `create_agent_api_key` with the `policyId` UUID returned above:
```
Create an agent API key for wallet "treasury" bound to policy 2f6d3a1e-94c7-4c8e-9c5a-7b0e1f2a3b4c.
```
The agent calls:
```json theme={null}
{
"name": "create_agent_api_key",
"arguments": {
"walletName": "treasury",
"policyId": "2f6d3a1e-94c7-4c8e-9c5a-7b0e1f2a3b4c"
}
}
```
`policyId` is the UUID from `create_custom_policy`'s response — **not** the human-readable `policyName`. Passing the name raises `OWS_POLICY_NOT_FOUND`.
The token is written to a local file (mode `0600`) — never returned in chat. Restart the MCP server in agent mode with `OWS_AGENT_TOKEN` sourced from that file.
## What Happens When a Policy Denies
The agent receives a structured error:
```json theme={null}
{
"category": "flow",
"code": "OWS_POLICY_DENIED",
"message": "Policy 'eth-only-30d' denied signing for chain tron:mainnet",
"requiredAction": "This agent key is restricted to chains: eip155:1. Use a different wallet or modify the policy."
}
```
The `requiredAction` tells the agent exactly what to do. See [Structured Errors](/core-concepts/structured-errors) for the full error taxonomy.
## Owner Override
The owner key (OWS owner mode) can always override agent policies. This ensures you always retain full control — WalletSuite cannot unilaterally freeze funds or block signing.
If an agent is denied by a policy, the owner can:
* Modify the policy to allow the operation
* Delete the policy
* Sign directly in owner mode (bypasses all policies)
## Two Layers of Control
| Layer | What It Controls | Where It Runs |
| ----------------------------------------------- | ----------------------------------- | ----------------------- |
| [Band Filtering](/core-concepts/band-filtering) | Which tools exist in the MCP schema | MCP Server (startup) |
| **Policy Gates** | What signed transactions can do | OWS Vault (per-request) |
A complete security setup uses both:
1. Set `MCP_BANDS` to the minimum required level
2. Create a policy with chain restrictions and expiry
3. Create an agent key bound to that policy
4. The agent can only see the tools it needs AND can only sign what the policy allows
## Practical Example
**Scenario:** You want an agent that can prepare and sign Ethereum transfers only, with a 7-day authorization window.
**Setup:**
1. Create a policy:
* `allowedChains`: `["eip155:1"]`
* `expiresAt`: 7 days from now
2. Create an agent key bound to that policy
3. Configure the MCP server:
* `MCP_BANDS=full`
* `OWS_ENABLED=true`
* `OWS_AUTH_MODE=agent`
* `OWS_AGENT_TOKEN` from the token file
**Result:** The agent can prepare and sign Ethereum transactions. If it tries to sign for Tron, the policy denies it. After 7 days, the agent key expires and all signing stops until a new key is created.
See [OWS Local Signing](/ai-agents/ows-local-signing) for the full setup guide.
# Structured Errors
Source: https://docs.walletsuite.io/core-concepts/structured-errors
Category, code, and required action in every error — so agents recover programmatically.
Every error from WalletSuite includes a `category`, `code`, and `message`. Errors in the `flow` category also include a `requiredAction` — a specific instruction for what the caller should do next.
The contract is identical across the REST API, MCP tools, and the SDK — only the wrapper around it differs. Agents and integrators can recover programmatically instead of guessing.
## Error Format
```json theme={null}
{
"category": "validation",
"code": "INVALID_ADDRESS",
"message": "Address does not match expected format for ethereum"
}
```
For `flow` errors, a `requiredAction` is always included:
```json theme={null}
{
"category": "flow",
"code": "OWS_WALLET_NOT_FOUND",
"message": "Wallet 'my-vault' does not exist in the OWS vault",
"requiredAction": "Create a wallet first using create_wallet with walletName 'my-vault'"
}
```
## Error Categories
| Category | Where it appears | Meaning | What the caller should do |
| --------------- | ---------------- | ------------------------------------------------------------------- | ------------------------------------------------ |
| `validation` | SDK + MCP | Bad input — address format, missing field, invalid amount | Fix the input and retry |
| `upstream` | SDK + MCP | Backend API or RPC failure, possibly transient | Retry with exponential backoff |
| `flow` | MCP only | Prerequisite missing — OWS wallet not found, signing not configured | Execute the `requiredAction` first, then retry |
| `auth` | SDK + MCP | API key invalid, missing, or unauthorized | Stop — tell the user to configure credentials |
| `limit` | SDK + MCP | Rate or quota exceeded | Back off, retry after the rate limit window |
| `not_available` | SDK + MCP | Feature not enabled for this API key | Inform the user the feature is not on their plan |
## Common Error Codes
Every error carries a `code`. The **Source** column tells you which layer raised it — and which surfaces can see it:
* **API** — returned by the WalletSuite REST API. Visible to SDK and MCP callers alike.
* **MCP** — raised by MCP tool orchestration (OWS vault, policy engine, Zod pre-validation, tool dispatch). **Not returned by the REST API** — SDK callers do not encounter these.
### Validation Errors
| Code | Source | When It Happens |
| ------------------------ | ------ | ------------------------------------------------------------------------------------------------------------------- |
| `INVALID_ADDRESS` | MCP | Address fails Zod format check for the specified chain (e.g., not 0x-prefixed for EVM) — raised before the API call |
| `INVALID_AMOUNT` | MCP | Human-readable amount is not a positive decimal string |
| `INVALID_AMOUNT_WEI` | MCP | Wei amount is not a positive integer string |
| `MISSING_TOKEN_CONTRACT` | MCP | Token transfer attempted with a symbol but no contract address — use `resolve_asset` first |
| `INVALID_PARAMS` | MCP | Zod schema validation failed on tool input |
| `BAD_REQUEST` | API | Backend returned 400 — check the request parameters |
| `NOT_FOUND` | API | Backend returned 404 — the resource does not exist |
### Flow Errors — MCP only
Flow errors signal prerequisites missing in the local OWS vault, policy engine, or MCP environment. They are raised by MCP orchestration and **never returned by the REST API**, so SDK callers do not encounter them.
| Code | When It Happens | Required Action |
| ------------------------- | -------------------------------------------------------- | ---------------------------------------------------- |
| `OWS_WALLET_NOT_FOUND` | Named wallet does not exist in the vault | Create the wallet with `create_wallet` |
| `OWS_ACCOUNT_NOT_FOUND` | Wallet exists but has no account for the requested chain | Check supported chains or create a new wallet |
| `OWS_REQUIRED` | Sign/broadcast tool called without OWS enabled | Set `OWS_ENABLED=true` and configure OWS |
| `OWS_POLICY_DENIED` | Policy rejected the signing request | Modify the policy or use a different wallet/chain |
| `OWS_RPC_URL_REQUIRED` | `send_transaction` called without the chain RPC URL | Set `OWS_ETHEREUM_RPC_URL` or `OWS_TRON_RPC_URL` |
| `OWS_CREDENTIAL_INVALID` | Passphrase or agent token cannot unlock the vault | Check credentials |
| `OWS_AGENT_TOKEN_EXPIRED` | Agent token has expired | Create a new agent token with `create_agent_api_key` |
| `MOONPAY_CONFIG_REQUIRED` | `prepare_onramp` called without MoonPay env vars | Set `MOONPAY_API_KEY` and `MOONPAY_SECRET_KEY` |
### Auth Errors
| Code | Source | When It Happens |
| -------------- | ------ | ----------------------------------------------------------------------------- |
| `UNAUTHORIZED` | API | API key is missing or invalid (backend returned 401) |
| `FORBIDDEN` | API | API key is valid but not authorized for this operation (backend returned 403) |
### Upstream Errors
| Code | Source | When It Happens |
| -------------------------- | ------ | -------------------------------------------------------------------------- |
| `SERVER_ERROR` | API | Backend returned 500, 502, or 503 |
| `TIMEOUT` | API | Request exceeded the timeout (default 60s) |
| `NETWORK` | API | DNS or connection failure |
| `TX_COMPILER_*` | API | Transaction compilation failed at the backend |
| `SIMULATION_FAILED` | API | Transaction simulation at the backend failed |
| `INVALID_BACKEND_RESPONSE` | MCP | Backend response did not match the expected schema (MCP client-side guard) |
### Limit and Feature Errors
| Code | Source | When It Happens |
| ----------------- | ------ | -------------------------------------------------------------------------------------------------------------------------------- |
| `RATE_LIMITED` | API | Backend returned 429 — too many requests |
| `PLAN_RESTRICTED` | API | Backend returned 403 — feature not enabled on your API key |
| `FEATURE_GATED` | MCP | MCP tool classified as not-available for your plan (surfaces `PLAN_RESTRICTED` to the agent as a `not_available` category error) |
## Consuming errors
The same structured payload reaches the caller through two different wrappers. Pick the sub-section that matches your surface.
### From an AI agent (MCP)
Every MCP tool response surfaces the structured error verbatim. The `category` field tells the agent what class of action to take; `requiredAction` (when present) gives a concrete next step.
A well-behaved agent should:
1. Check `category`
2. If `flow` — execute the `requiredAction`, then retry the original request
3. If `validation` — fix the input based on the `message`, then retry
4. If `upstream` — retry with exponential backoff (3 attempts, then surface to user)
5. If `auth` — stop and ask the user to fix their credentials
6. If `limit` — wait and retry
7. If `not_available` — inform the user
### From the SDK
SDK calls throw an `ApiError` wrapper. The structured payload lives in `err.bodySnippet` as a JSON string — parse it to reach `category`, `code`, and `message`.
SDK callers only see the API-source categories: `validation` (from the backend, not Zod), `auth`, `upstream`, `limit`, and `not_available`. The `flow` category is MCP-only and will not appear in SDK responses.
```typescript theme={null}
import { WalletSuiteSDK, ApiError } from "@walletsuite/wallet-sdk";
try {
const balance = await sdk.api.getNativeBalance(address, "ethereum");
} catch (err) {
if (err instanceof ApiError && err.bodySnippet) {
try {
const payload = JSON.parse(err.bodySnippet);
switch (payload.category) {
case "validation": /* fix input, retry */ break;
case "upstream": /* retry with backoff */ break;
case "auth": /* check credentials */ break;
case "limit": /* wait and retry */ break;
case "not_available": /* inform the user */ break;
}
} catch {
// non-JSON body — transport-level failure (timeout, DNS, etc.)
}
}
}
```
For SDK-specific wrapper fields (`url`, `method`, `status`, `attempt`) and debug-logging behavior, see [SDK Error Handling](/sdk/error-handling).
## Design Principle
Errors are designed so agents can recover without human intervention whenever possible. A `flow` error with a `requiredAction` gives the agent a concrete next step — it does not need to guess or ask the user for help.
Internal error details (`internalMessage`) are logged server-side but never included in the response sent to the LLM. The agent sees only what it needs to act on.
# Transaction Lifecycle
Source: https://docs.walletsuite.io/core-concepts/transaction-lifecycle
End-to-end flow from intent to broadcast — prepare, sign, send, confirm.
A transaction moves through up to 4 stages, each controlled by a different [band](/core-concepts/band-filtering). You only need the stages your integration requires — most use cases need only the first two.
## Stages
### 1. Query (read band)
Check balances, prices, and fees before deciding to transact.
| Tool | What It Returns |
| ------------------ | ------------------------------------------------- |
| `get_balance` | Native token balance for one address on one chain |
| `get_all_balances` | All token balances with fiat valuations |
| `get_price` | Current market price for a token |
| `get_fee_quote` | Estimated network fee for a transfer |
| `resolve_asset` | Token contract address from a symbol or name |
| `get_tx_status` | Status of a submitted transaction |
| `get_tx_history` | Recent transactions for an address |
No keys involved. No state modified.
### 2. Prepare (prepare band)
Construct an unsigned transaction payload. The blockchain is not touched — this builds the data structure you or the agent will later sign.
| Tool | Output | Use Case |
| -------------------------------- | ---------------------------------------------- | --------------------------------------------------- |
| `prepare_transfer` | Unsigned payload (nonce, fee params, calldata) | External signing — you sign with your own wallet |
| `prepare_serialized_unsigned_tx` | Signing-ready hex + human-readable review | OWS signing — feed directly into `sign_transaction` |
| `prepare_onramp` | MoonPay widget URL | Fiat on-ramp to fund a wallet |
**Token transfers are two-step:** First call `resolve_asset` to get the token contract address, then call the prepare tool with that contract. WalletSuite does not guess token contracts from symbols.
**Amount handling:** Provide either `amount` (human-readable, e.g., `"1.5"`) or `amountWei` (smallest units, e.g., `"1500000000000000000"`). Never both — the server rejects ambiguous input.
### 3. Sign (sign band, OWS only)
Sign the prepared transaction locally using the OWS vault. Requires `OWS_ENABLED=true`.
| Tool | What It Does |
| -------------------- | ------------------------------------------------------ |
| `sign_transaction` | Takes unsigned tx hex, returns signature + recovery ID |
| `get_wallet_address` | Resolves the local wallet address for a chain |
Keys stay in the OWS vault. The MCP server never sees private key material. [Policy gates](/core-concepts/policy-gates) are evaluated before any key is touched.
Signing is idempotent — signing the same payload produces the same signature.
### 4. Broadcast (broadcast band, OWS only)
Submit the signed transaction to the blockchain. Once confirmed it cannot be undone.
| Tool | What It Does |
| ------------------ | ------------------------------------------------- |
| `send_transaction` | Signs and broadcasts in one step, returns tx hash |
Requirements:
* `confirmBroadcast: true` (explicit confirmation)
* Matching RPC URL configured (`OWS_ETHEREUM_RPC_URL` or `OWS_TRON_RPC_URL`)
* Broadcast band enabled (`MCP_BANDS=full`)
After broadcast, use `get_tx_status` to monitor confirmation.
## External Signing Flow
If you manage your own keys (no OWS):
```
1. get_fee_quote → estimate the cost
2. prepare_transfer → build unsigned payload
3. Your wallet signs → (outside WalletSuite)
4. Your infra broadcasts → (outside WalletSuite, or via WalletSuite API)
```
Use `MCP_BANDS=read,prepare`. No OWS needed.
## OWS Local Signing Flow
If you use OWS for end-to-end local signing:
```
1. get_fee_quote → estimate the cost
2. prepare_serialized_unsigned_tx → build signing-ready hex + review
3. (Agent presents review to user)
4. sign_transaction → OWS signs locally
OR
send_transaction → OWS signs + broadcasts
5. get_tx_status → confirm on-chain
```
The full flow stays within the MCP session. No browser extension, no QR code, no external wallet.
Example conversation:
```
User: "Send 1 ETH to 0xdef"
Agent: Prepares the transfer, shows review:
"Send 1.0 ETH to 0xdef... Estimated fee: 0.002 ETH ($5.40)"
User: "Confirm"
Agent: Signs and broadcasts via send_transaction
"Sent 1 ETH to 0xdef. Tx: 0xabc... confirmed in block 19234567."
```
## Data Flow Diagram
```
User intent ("Send 1 ETH to 0xdef")
|
v
MCP Server
|
|-- [1] prepare_serialized_unsigned_tx
| |-- POST /api/txs/prepare-sign (backend)
| \-- tx-compiler -> unsigned hex + review
|
|-- [2] send_transaction
| |-- OWS: evaluate policy
| |-- OWS: decrypt key, sign, wipe key
| |-- OWS: broadcast to chain RPC
| \-- return tx hash
|
\-- [3] get_tx_status
|-- GET /api/txs/status/{hash} (backend)
\-- return confirmed / pending / failed
```
## Related
* [Band Filtering](/core-concepts/band-filtering) — controls which stages are available
* [Policy Gates](/core-concepts/policy-gates) — controls what the sign/broadcast stages can do
* [Tool Reference](/ai-agents/tool-reference) — full input/output schemas for every tool
* [OWS Local Signing](/ai-agents/ows-local-signing) — setup guide for stages 3 and 4
# API Reference
Source: https://docs.walletsuite.io/getting-started/api-reference/overview
Authenticate and make your first request against the WalletSuite REST API.
The WalletSuite REST API gives you direct HTTPS access to every wallet operation — balances, prices, fees, transaction preparation, broadcasting, and more.
Prefer typed helpers? Use the [SDK](/sdk/quick-start) instead. Building an AI agent? See the [MCP Quickstart](/ai-agents/quickstart).
## Built for
Teams who need REST access to wallet operations from any language or stack — no SDK dependency, no agent layer. Common shapes: server-side backends handling multi-chain payments, internal treasury or ops dashboards, and embedded Web3 features inside existing fintech or commerce products.
## What you can build
* Native and token balance queries with fiat valuation
* Price, fee-quote, transaction status, and history queries across supported chains
* Asset catalogs and name-service resolution (ENS, UNS)
* NFT ownership and token allowance queries
* Transaction preparation — payload construction, fee estimation, and simulation
* Broadcast pre-signed transactions to supported chains
* Staking and swap metadata queries on supported chains
## Make your first API request
Request a Pilot API key by emailing [contact@walletsuite.io](mailto:contact@walletsuite.io). Once you receive it, store it as an environment variable:
```bash theme={null}
export WALLETSUITE_API_KEY="your-api-key"
```
Pass your key in the `X-API-KEY` header on every request. All requests must use HTTPS.
```bash theme={null}
curl -X GET "https://api.walletsuite.io/api/blocks/latest?chain=ethereum" \
-H "X-API-KEY: $WALLETSUITE_API_KEY"
```
A successful response returns `200 OK` with a JSON body. If you receive `401` or `403`, check that your key is valid and correctly set in the header. If you receive `429`, you have exceeded your rate limit — see [rate limits](/getting-started/prerequisites/rate-limits) for retry guidance.
## Next steps
* Review [Credentials & Authentication](/getting-started/prerequisites/credentials-and-authentication) for a full explanation of the `X-API-KEY` header and error responses
* Check [rate limits](/getting-started/prerequisites/rate-limits) to understand quota enforcement and how to handle 429 responses
* Browse the endpoint groups in the sidebar for the full list of operations
# Quickstart
Source: https://docs.walletsuite.io/getting-started/paths
Three integration paths for WalletSuite — AI Agent Wallet (MCP), SDK, and REST API. Pick the one that fits your stack.
**MCP-native · \~5 min**
Connect Claude, LangChain, CrewAI or any MCP client. Agents get scoped tools and policy-gated signing.
**TypeScript · \~10 min**
Typed helpers for signing, balances, prices, and multi-chain ops. 80+ chains behind one API.
**Any language · \~2 min**
Direct HTTPS. No dependencies, full control. Works with any stack or language.
## Before you begin
All three paths require a WalletSuite API key. See [Credentials & Authentication](/getting-started/prerequisites/credentials-and-authentication) to request a Pilot key.
## Use case guides
Looking for something more specific? Start here:
* [**Give an AI agent a wallet**](/getting-started/use-case-guides/give-ai-agent-a-wallet)
* [**Add a wallet to your application**](/getting-started/use-case-guides/add-wallet-to-backend)
# Credentials & Authentication
Source: https://docs.walletsuite.io/getting-started/prerequisites/credentials-and-authentication
### Getting Your API Key
To request a **Pilot API key**, contact:
📧 [**contact@walletsuite.io**](mailto:contact@walletsuite.io)
Each API key is issued per organization and controls access to specific endpoints and features. See [Rate Limits](/getting-started/prerequisites/rate-limits) for the Pilot key validity window and tier limits.
***
### Authentication Method
* **Type:** `apiKey`
* **Location:** HTTP request header
* **Header name:** `X-API-KEY`
All authenticated requests must include this header. Requests without a valid API key are rejected before reaching business logic.
#### Example
```http theme={null}
X-API-KEY: your-api-key
```
***
### HTTPS Requirement
* All API requests must be sent over HTTPS
* Requests over plain HTTP will fail
* Requests without authentication headers will fail
This requirement applies to all tiers, including Pilot and Production.
***
### Request Scope & Permissions
* All requests are scoped to your organization
* Access is restricted based on your plan and enabled permissions
* Unauthorized access attempts are rejected
***
### Authentication Errors
Authentication can fail for the following reasons:
* Missing `X-API-KEY` header
* Invalid or expired API key
* Insufficient permissions for the requested endpoint
In those cases the API returns an appropriate 4xx HTTP status code (typically `401` or `403`).
***
### Premium Endpoints
Certain endpoints are classified as **Premium** and are not accessible using the Pilot API key by default.
To enable premium access, contact your account manager.
Premium access is enforced at the API level during request authentication.
***
### Securing Your API Keys
WalletSuite API keys grant access to privileged resources. You are responsible for keeping them secure.
**Best practices:**
* Store API keys in environment variables or a secrets manager
* Restrict access to trusted systems only
**Never:**
* Commit API keys to source control (GitHub, GitLab, etc.)
* Expose API keys in frontend or client-side applications
* Share API keys publicly or with unauthorized parties
# Integration Considerations
Source: https://docs.walletsuite.io/getting-started/prerequisites/integration-considerations
This section outlines important operational characteristics of the WalletSuite API to help you design reliable, high-performance integrations.
WalletSuite is built to support a wide range of Web3 use cases and production workloads. Understanding the considerations below will help you get the best results during development and at scale.
***
### Performance & Latency
WalletSuite prioritizes accuracy and reliability while continuously optimizing for speed.
* Some operations may take longer on first execution, depending on the requested data and network conditions
* Subsequent requests for the same resources typically return faster
* Results are continuously refreshed in the background to maintain accuracy
Latency may vary depending on blockchain network, endpoint type, and request complexity.
***
### Data Preparation & Optimization
WalletSuite proactively prepares and indexes on-chain data to ensure optimal performance across supported networks.
* Frequently accessed data is optimized for fast retrieval
* The platform continuously processes new blockchain activity to keep results up-to-date
If you have specific performance requirements or high-volume use cases, our team can help guide best practices for optimal integration.
***
### Error Handling & Reliability
WalletSuite APIs are built for high availability and stability.
Occasionally, requests may be affected by **external blockchain network conditions**, such as temporary congestion, delayed finality, or upstream node availability. These conditions are outside of WalletSuite’s control and may impact request execution or response times.
* Such situations are typically short lived
* Retrying the request will often succeed once network conditions normalize
* Standard HTTP status codes are used to clearly communicate request outcomes
For optimal reliability, we recommend implementing **retry logic with exponential backoff** when interacting with the API.
***
### Data Freshness
WalletSuite balances performance and data freshness.
* Responses may be served from optimized caches to ensure low latency
* Data is refreshed regularly to reflect the latest on-chain state
* Cached data is never retained beyond defined freshness windows
Repeated requests for the same resource will return the most up-to-date data available.
***
### Feedback & Support
We actively improve the WalletSuite platform based on real-world usage.
If you have:
* Feedback on existing endpoints
* Feature requests
* Questions about your integration
Please reach out to:
📧 [**support@walletsuite.io**](mailto:support@walletsuite.io)
***
# Rate Limits
Source: https://docs.walletsuite.io/getting-started/prerequisites/rate-limits
Rate limits are applied **per API key** and vary depending on the key tier and subscription plan. The same per-key limit applies to every integration path — direct REST, SDK, and MCP requests all count against it.
Your key automatically promotes from Pilot to Production when your trial converts — no rotation required. The `X-API-KEY` header stays the same; quota and tool bands expand in place.
***
### Pilot Tier Limits
For evaluation and integration testing. No charge during the Pilot window.
* **2,000 operations per month**
* **All EVM chains and Tron mainnet**
* Transaction history and advanced endpoints require a paid plan
* Fair-use rate limits apply
Pilot keys are time-bounded: each key is valid for **one month** from issue. Use this window to run integration work, evaluate the platform, and plan a Production rollout. Contact your account manager before expiry to renew or issue a Production key.
***
### What Counts as an Operation
An operation is **one REST API call** to `api.walletsuite.io`. All operations count equally against your monthly quota, and all responses count — success and failure alike.
MCP tools that touch the REST API consume one operation per invocation. Tools that run entirely through the local OWS vault — wallet creation, signing, and broadcast — do not consume any quota.
| Tool | Band | Ops per call |
| -------------------------------- | ----------- | ------------ |
| `get_balance` | `read` | 1 |
| `get_all_balances` | `read` | 1 |
| `get_price` | `read` | 1 |
| `get_fee_quote` | `read` | 1 |
| `resolve_asset` | `read` | 1 |
| `get_tx_status` | `read` | 1 |
| `get_tx_history` | `read` | 1 |
| `prepare_transfer` | `prepare` | 1 |
| `prepare_serialized_unsigned_tx` | `prepare` | 1 |
| `prepare_onramp` | `prepare` | 0 |
| `create_wallet` | `sign` | 0 |
| `get_wallet_address` | `sign` | 0 |
| `sign_transaction` | `sign` | 0 |
| `create_agent_api_key` | `sign` | 0 |
| `create_custom_policy` | `sign` | 0 |
| `send_transaction` | `broadcast` | 0 |
`send_transaction` broadcasts through a local chain RPC you configure (`OWS_ETHEREUM_RPC_URL`, `OWS_TRON_RPC_URL`) — not through WalletSuite — which is why it does not count against your quota.
***
### Production Tier
Production API keys are available with an active subscription.
* Rate limits for Production are defined according to the subscribed plan
* Exact limits are communicated during onboarding
***
### Rate Limit Enforcement
* Rate limits are enforced automatically
* Requests exceeding the monthly quota return **HTTP 429 Too Many Requests**
* Monthly quotas reset automatically at the beginning of each billing cycle
***
### Production Access
To upgrade to a Production API key:
* Contact your **account manager**, or
* Email [**contact@walletsuite.io**](mailto:contact@walletsuite.io)
# Add Wallet Operations to Your Application
Source: https://docs.walletsuite.io/getting-started/use-case-guides/add-wallet-to-backend
Integrate WalletSuite into your backend — balances, prices, fees, and unsigned transaction payloads — via the TypeScript SDK or REST API.
**Time:** 10-15 minutes.
Integrate from TypeScript via the SDK, or from any language via the REST API. Either way, run it server-side — keys and signing stay on your infrastructure. WalletSuite returns unsigned transaction payloads you sign with your existing signer (KMS, HSM, custodian, or a local wallet).
## What Your Application Will Be Able to Do
* Query any wallet's balance across [multiple chains](/ai-agents/supported-chains) with fiat valuations
* Look up token prices in real-time
* Estimate transaction fees before executing
* Prepare unsigned transaction payloads for your signing infrastructure
* Resolve token contract addresses from symbols or names
## Prerequisites
* WalletSuite API key — see [Credentials & Authentication](/getting-started/prerequisites/credentials-and-authentication)
* One of: Node.js 18+ (for the TypeScript SDK), Python 3.9+ with `requests`, or any HTTP client (`curl`, Go's `net/http`, Ruby's `net/http`, etc.)
## Step 1 — Install
```bash TypeScript SDK theme={null}
npm install @walletsuite/wallet-sdk
```
```bash Python theme={null}
pip install requests
```
```bash cURL theme={null}
# No install — curl ships with macOS, Linux, and Windows 10+.
```
## Step 2 — Initialize the Client
```typescript TypeScript SDK theme={null}
import { WalletSuiteSDK } from "@walletsuite/wallet-sdk";
const sdk = new WalletSuiteSDK({
apiKey: process.env.WALLETSUITE_API_KEY!,
env: "prod",
});
```
```python Python theme={null}
import os
import requests
session = requests.Session()
session.headers.update({
"x-api-key": os.environ["WALLETSUITE_API_KEY"],
"content-type": "application/json",
})
BASE_URL = "https://api.walletsuite.io"
```
```bash cURL theme={null}
export WALLETSUITE_API_KEY="your-key-here"
export WALLETSUITE_BASE_URL="https://api.walletsuite.io"
```
Store your API key in an environment variable or secret manager. Do not hardcode it in source files.
## Step 3 — Query a Balance
```typescript theme={null}
const balance = await sdk.api.getNativeBalance("0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045", {
chain: "ethereum",
fiat: "USD",
});
console.log(balance.data);
// { chain: "ethereum", symbol: "ETH", amount: "1234.56", fiatValue: "3950000.00", ... }
```
For all token balances on a chain:
```typescript theme={null}
const balances = await sdk.api.getAssetBalances("0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045", {
chain: "ethereum",
fiat: "USD",
includeNative: true,
});
console.log(balances.data.totalFiatValue); // "4,120,000.00"
console.log(balances.data.assets); // [{ symbol: "ETH", ... }, { symbol: "USDT", ... }]
```
## Step 4 — Get a Price
```typescript theme={null}
// By symbol
const ethPrice = await sdk.api.getPrice("ETH");
console.log(ethPrice.data.value); // "3200.42"
// By contract address
const usdtPrice = await sdk.api.getPriceByContract(
"0xdAC17F958D2ee523a2206206994597C13D831ec7",
{ chain: "ethereum" }
);
```
## Step 5 — Estimate Fees
```typescript theme={null}
const feeQuote = await sdk.api.getFeeQuote({
chain: "ethereum",
fromAddress: "0xabc...",
toAddress: "0xdef...",
amountWei: "1500000000000000000", // 1.5 ETH in wei
});
console.log(feeQuote.data);
// { chain: "ethereum", feeSymbol: "ETH", feeAmount: "0.002", fiatValue: "6.40", ... }
```
## Step 6 — Prepare a Transfer
```typescript TypeScript SDK theme={null}
const prepared = await sdk.api.prepareSign({
chain: "ethereum",
txType: "TRANSFER_NATIVE",
fromAddress: "0xabc...",
toAddress: "0xdef...",
amount: "1.5", // Human-readable ETH
});
console.log(prepared.data);
// { nonce, gasLimit, maxFeePerGas, value, data, estimatedFee, estimatedFeeInUsd, ... }
```
```python Python theme={null}
response = session.post(
f"{BASE_URL}/api/txs/prepare-sign",
json={
"chain": "ethereum",
"txType": "TRANSFER_NATIVE",
"from": "0xabc...",
"to": "0xdef...",
"amount": "1.5", # Human-readable ETH
},
)
prepared = response.json()
# { nonce, gasLimit, maxFeePerGas, value, data, estimatedFee, estimatedFeeInUsd, ... }
```
```bash cURL theme={null}
curl -X POST "$WALLETSUITE_BASE_URL/api/txs/prepare-sign" \
-H "x-api-key: $WALLETSUITE_API_KEY" \
-H "content-type: application/json" \
-d '{
"chain": "ethereum",
"txType": "TRANSFER_NATIVE",
"from": "0xabc...",
"to": "0xdef...",
"amount": "1.5"
}'
# { nonce, gasLimit, maxFeePerGas, value, data, estimatedFee, estimatedFeeInUsd, ... }
```
The response contains everything your signing infrastructure needs: nonce, gas parameters, calldata, and value. Sign this payload with your own key management (HSM, KMS, or any signer) and broadcast via your infrastructure or the WalletSuite API.
### Token Transfers
For ERC-20 token transfers, resolve the contract first:
```typescript theme={null}
// 1. Resolve the token
const asset = await sdk.api.getAssets({
chain: "ethereum",
symbol: "USDT",
});
const tokenContract = asset.data[0].id;
// 2. Prepare the transfer
const prepared = await sdk.api.prepareSign({
chain: "ethereum",
txType: "TRANSFER_TOKEN",
fromAddress: "0xabc...",
toAddress: "0xdef...",
amount: "100",
tokenContract,
});
```
## Error Handling
All SDK errors include a `category` for programmatic handling:
```typescript theme={null}
try {
const balance = await sdk.api.getNativeBalance(address, { chain });
} catch (error) {
switch (error.category) {
case "validation":
// Fix input (bad address, invalid chain)
break;
case "upstream":
// Retry with backoff (WalletSuite API transient error)
break;
case "auth":
// Check API key configuration
break;
case "limit":
// Back off, retry later
break;
case "not_available":
// Feature not enabled on your plan
break;
}
}
```
See [SDK Error Handling](/sdk/error-handling) for the full error taxonomy and handling patterns.
## Architecture Recommendation
Run the SDK server-side, not in the browser. The integration shape:
```text theme={null}
Browser → Your Server → WalletSuite SDK → unsigned tx payload
│
▼
Your signer (KMS / HSM / custodian / local wallet)
│
▼
Signed tx → WalletSuite API or your own RPC → Blockchain
```
This keeps your API key on the server, keeps signing inside your existing key infrastructure, and lets you add your own auth, rate limiting, and business logic between the user and WalletSuite. The SDK never sees keys or signed payloads — it returns typed unsigned payloads with everything your signer needs (nonce, gas parameters, calldata, value).
## Next Steps
* [End-to-End Token Transfer](/sdk/end-to-end-token-transfer-flow) — complete signing and broadcast flow
* [Integration Samples](/sdk/integration-samples) — 20+ code examples
* [SDK Error Handling](/sdk/error-handling) — detailed error patterns
* [API Reference](/getting-started/api-reference/overview) — full REST API with interactive playground
# Give an AI Agent a Wallet
Source: https://docs.walletsuite.io/getting-started/use-case-guides/give-ai-agent-a-wallet
End-to-end guide to giving your AI agent wallet access through WalletSuite MCP.
**Time:** under 5 minutes.
By the end of this guide, your AI agent will be able to check balances, look up prices, estimate fees, and prepare transactions - all through natural language.
This page walks the self-hosted OWS path that ships today. A hosted TEE-backed deployment for consumer-scale and platform operators is planned for Q2–Q3 2026 — see [Self-Hosting Over HTTP → Roadmap](/ai-agents/self-hosting-over-http) or [contact us](mailto:contact@walletsuite.io).
## What Your Agent Will Be Able to Do
* `What's Vitalik's ETH balance?` — queries the blockchain in real-time
* `How much is 1 ETH in USD?` — gets the current market price
* `What would the gas fee be for sending 0.1 ETH?` — estimates the network fee
* `Prepare a transfer of 0.1 ETH to 0x...` — builds an unsigned transaction payload
## Prerequisites
* WalletSuite API key — see [Credentials & Authentication](/getting-started/prerequisites/credentials-and-authentication)
* An MCP-compatible host or framework - Claude Desktop, Claude Code, LangChain, CrewAI, LlamaIndex or any MCP client
* Node.js 22+
## Step 1 — Install (30 seconds)
Add WalletSuite MCP to your client's MCP config:
```json theme={null}
{
"mcpServers": {
"walletsuite": {
"command": "npx",
"args": ["-y", "@walletsuite/mcp-server"],
"env": { "WALLETSUITE_API_KEY": "your-key" }
}
}
}
```
The server starts in `read` mode by default — seven tools for balances, prices, fees, and transaction history. No passphrase, no vault, no signing.
Where to put this config depends on your host:
| Host | Config Location |
| ----------- | -------------------------------- |
| Claude Code | `.mcp.json` in your project root |
For full framework-specific instructions, see [Install Guides](/ai-agents/install-guides).
## Step 2 — Restart Your Host
After saving the config, restart the application or reload MCP servers from the host settings.
## Step 3 — Try It
Ask your agent any of these:
```text theme={null}
What is the ETH balance of 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045?
```
You should see a structured response with the balance in ETH and its USD value.
Try a few more:
* `What's the price of ETH right now?`
* `Show me recent transactions for 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045 on ethereum`
* `Resolve USDC on ethereum` — finds the token contract address
* `What would the gas fee be for sending 1 ETH from 0xabc to 0xdef?`
## Step 4 — Expand Access (Optional)
By default, your agent can only read data (`MCP_BANDS=read`, 7 tools). To allow more:
Add transaction preparation when you want the agent to construct payloads:
```json theme={null}
"MCP_BANDS": "read,prepare"
```
This adds 3 tools — `prepare_transfer`, `prepare_serialized_unsigned_tx`, `prepare_onramp`. The agent can now construct unsigned transaction payloads but cannot sign or broadcast them.
## What Happens Under the Hood
```text theme={null}
Your agent asks a question
→ MCP host sends a tool call to WalletSuite MCP (running locally)
→ WalletSuite MCP calls the WalletSuite REST API
→ API queries the blockchain
→ WalletSuite MCP returns a structured response
→ Your agent reads the response and answers naturally
```
No keys are involved for read operations. The MCP server is an orchestration layer that translates natural language into blockchain queries.
## Production signing
When you're ready to sign and broadcast, enable OWS with the full band set:
```json theme={null}
"MCP_BANDS": "full",
"OWS_ENABLED": "true",
"OWS_AUTH_MODE": "owner",
"OWS_PASSPHRASE": "your-passphrase"
```
Keys are generated and stored locally on the host that runs OWS — WalletSuite never sees them.
The example above uses **owner mode** — an interactive setup where the vault passphrase unlocks the vault for bootstrap and admin operations. For headless or multi-agent deployments, switch the same MCP server to **agent mode** with a scoped token bound to a policy (chain allowlists, spend limits, expiry). The vault passphrase is not required at runtime in agent mode. See [OWS Local Signing](/ai-agents/ows-local-signing) for the full token-issuance and policy-binding flow, and [Policy Gates](/core-concepts/policy-gates) for the policy primitives themselves.
Before enabling signing, read:
* [Band Filtering](/core-concepts/band-filtering) — the access control model
## Next Steps
* [Choose Your Setup](/ai-agents/choose-your-setup) — decide between stdio, HTTP, and Docker deployment
* [Tool Reference](/ai-agents/tool-reference) — complete list of all available tools
* [Security Overview](/security/overview) — the full trust model
* [Security & Trust for AI Agents](/ai-agents/security-model) — agent-scoped custody details
* [Supported Chains](/ai-agents/supported-chains) — full chain list for read/prepare and signing
# WalletSuite
Source: https://docs.walletsuite.io/overview
WalletSuite provides secure, scalable, and programmable wallet infrastructure for AI agents and Web3 applications.
Pick your integration path: AI Agent Wallet (MCP), SDK, or REST API.
Design partners, enterprise, and feedback.
## Two products, one platform
Whether you're building an AI agent or a full-stack Web3 product, WalletSuite is the wallet layer you don't have to build.
POWERED BY MCP
A governed, non-custodial wallet layer for AI agents in production — scoped tools, policy-gated signing, and a tamper-evident audit trail.
* Agents only see tools they're allowed to use
* Every transaction screened before it sends
* Keys never leave your infrastructure
* Tamper-evident audit trail for compliance
[Set up MCP →](/ai-agents/overview)
A typed, non-custodial wallet platform for engineering teams who need multi-chain operations in production without owning the chain infrastructure.
* Generate wallets, prepare, sign, and broadcast through a single entry point
* Keys stay local on your infrastructure — never stored or seen by WalletSuite
* One normalized schema for balances, prices, fees, and history across chains
* Webhooks and websockets for reacting to on-chain activity
[Install the SDK →](/sdk/overview)
## Governance
4-tier access control — read, prepare, sign, broadcast. Each agent gets only the tools its role requires.
Spend limits, chain allowlists, and counterparty screening. Policies are evaluated before any key is touched.
Every error includes a category, code, and required action. Agents recover programmatically, not by guessing.
See also: [Architecture](/core-concepts/architecture) and [Transaction Lifecycle](/core-concepts/transaction-lifecycle) for the deeper system view.
## Security
WalletSuite is fully non-custodial — your users are always in control of their keys. Private keys are never exposed to WalletSuite, your software, or your team. Keys are generated and signed inside the isolated environment you control — never in our infrastructure.
* **Self-hosted** — keys stay on your infrastructure in an encrypted OWS vault
* **Cloud (coming soon)** — keys generated and used exclusively inside hardware-attested secure enclaves, with a level of public verifiability the category hasn't seen: every deployment ships with cryptographic proof you can verify from your own terminal, anchored to a public transparency log.
## Support
[walletsuite.io](https://walletsuite.io/)
[support@walletsuite.io](mailto:support@walletsuite.io)
Issues, discussions, and source
Updates and announcements
# Production Checklist
Source: https://docs.walletsuite.io/production-checklist
Validate your WalletSuite configuration before going live.
## API and Authentication
* [ ] Production API key created (not the Pilot key)
* [ ] API key stored in an environment variable or secret manager — not hardcoded in source
* [ ] API key is not committed to version control
* [ ] Rate limits reviewed and understood — see [Rate Limits](/getting-started/prerequisites/rate-limits)
## Band Filtering
* [ ] `MCP_BANDS` set to the minimum required level for each agent role
* [ ] Read-only agents use `MCP_BANDS=read` (not `full`)
* [ ] Each agent's band level has a documented justification
* [ ] Multi-agent setups use separate MCP instances with different band configs — see [Band Filtering](/core-concepts/band-filtering)
## OWS Local Signing (If Enabled)
* [ ] `OWS_VAULT_PATH` points to a secure, backed-up location (default: `~/.ows`)
* [ ] Owner passphrase stored securely — not in `.env` files committed to git
* [ ] Agent tokens created with restrictive policies:
* [ ] Chain allowlist configured (only the chains the agent needs)
* [ ] Expiry set on every agent token
* Custom policy (no explicit `expiresAt`) defaults to 30 days
* Wallet default policy (created alongside the wallet) defaults to 90 days
* `expiryMode: one_year` preset caps at 1 year but cannot outlive the bound policy
* [ ] Agent token files have mode `0600` (readable only by owner)
* [ ] RPC URLs configured for each signing chain:
* [ ] `OWS_ETHEREUM_RPC_URL` — for Ethereum signing/broadcasting
* [ ] `OWS_TRON_RPC_URL` — for Tron signing/broadcasting
* [ ] All RPC URLs use HTTPS (HTTP allowed only for localhost in development)
* [ ] OWS signing tested in staging before production deployment
See [OWS Local Signing](/ai-agents/ows-local-signing) for the full setup guide.
## Policy Gates (If Using Agent Mode)
* [ ] At least one policy created with chain restrictions — see [Policy Gates](/core-concepts/policy-gates)
* [ ] Policy expiry configured
* [ ] Spend limits configured when available
* [ ] Policy tested: verified that signing for unauthorized chains is denied
* [ ] Policy denial returns a `flow` error with `requiredAction` — verify your agent handles it
## Error Handling
All 6 error categories handled:
* [ ] `validation` — fix input and retry
* [ ] `upstream` — retry with exponential backoff
* [ ] `flow` — execute the `requiredAction` field before retrying
* [ ] `auth` — halt the agent, alert the operator
* [ ] `limit` — back off, retry after the rate limit window
* [ ] `not_available` — inform the user the feature is not enabled
See [Structured Errors](/core-concepts/structured-errors) for the full error taxonomy.
## Monitoring
* [ ] Audit trail location configured and accessible — default: `~/.walletsuite/audit-trail.jsonl`
* [ ] Health endpoint reachable for HTTP deployments — `GET /health`
* [ ] Log output captured — WalletSuite MCP logs structured JSON to stderr
* [ ] Alerts configured for signing failures and policy denials (if applicable)
## Security
* [ ] No private keys, mnemonics, or passphrases in source control or tool arguments
* [ ] No secrets passed as MCP tool parameters — all secrets via environment variables
* [ ] HTTPS enforced for all external URLs
* [ ] Client-side hooks enabled for broadcast confirmation (recommended for Claude Code) — see [Install Guides](/ai-agents/install-guides)
* [ ] Docker deployments use the non-root `mcp` user (default in the published image)
Before going live, review the full trust model and diligence path: [Security Overview](/security/overview) · [Security Diligence](/security/diligence) · [Build & Supply Chain](/security/build).
## Deployment
* [ ] Using the published npm package (`@walletsuite/mcp-server`) or official Docker image
* [ ] Node.js 22+ verified
* [ ] `.env.example` copied and populated with production values
* [ ] Health check passing after deployment — `curl http://localhost:3000/health`
# Counterparty Screening
Source: https://docs.walletsuite.io/recipes/counterparty-screening
Screen transaction counterparties before signing with WalletSuite policy gates.
Coming soon.
# CrewAI Multi-Agent Wallet
Source: https://docs.walletsuite.io/recipes/crewai-multi-agent
Multi-agent wallet pattern with CrewAI and WalletSuite MCP.
Coming soon.
# LangChain Agent with Wallet
Source: https://docs.walletsuite.io/recipes/langchain-agent-wallet
Build a LangChain agent with full wallet access using WalletSuite MCP.
Coming soon.
# x402 Micropayments
Source: https://docs.walletsuite.io/recipes/x402-micropayments
Integrate x402 protocol micropayments with WalletSuite.
Coming soon.
# End to End Token Transfer Flow
Source: https://docs.walletsuite.io/sdk/end-to-end-token-transfer-flow
A complete example of preparing, signing, and sending an ERC20 token transfer on Ethereum using the WalletSuite SDK.
This walkthrough uses a local private key from an imported mnemonic so every step is reproducible. In production, you'll keep the same prepare → sign → broadcast shape, but sign with your existing key-management system (KMS, HSM, custodian) instead of the SDK's private-key helper. See [Bring your own signer](#bring-your-own-signer) below.
#### Initialize
```ts theme={null}
import { WalletSuiteSDK } from "@walletsuite/wallet-sdk";
const sdk = new WalletSuiteSDK({
apiKey: process.env.WALLETSUITE_API_KEY!,
env: "prod",
});
await sdk.init();
```
#### Import wallet and get Ethereum account
```ts theme={null}
const mnemonic = process.env.MNEMONIC as string;
if (!mnemonic) throw new Error("MNEMONIC is required");
const wallet = await sdk.importWallet(mnemonic);
// Default account for Ethereum (m/44'/60'/0'/0/0)
const eth = await wallet.deriveEthereum();
console.log("ETH address:", eth.address);
```
#### Read USDT balance on Ethereum
```ts theme={null}
const usdtContract = "0xdAC17F958D2ee523a2206206994597C13D831ec7";
const before = await sdk.api.getAssetBalances(eth.address, {
chain: "ethereum",
fiat: "USD",
assetIds: [usdtContract],
includeNative: false,
});
console.log("USDT balance (before):", before.data);
```
Response fields
* `amount` is human-readable (decimals applied)
* `smallestUnit` is the raw token integer balance
#### Prepare USDT transfer (fees, nonce, calldata, simulation)
This stage produces a signing payload that your UI can display and confirm.
**Server side actions**
* Resolve nonce if not provided
* Build ERC20 calldata
* Select fee mode (EIP1559 by default, legacy fallback when required)
* Run a simulation check
**Client side actions**
* Display fee information to the user
* If user confirms, sign the payload locally using the SDK signer
```ts theme={null}
const to = process.env.TO as string;
if (!to) throw new Error("TO is required");
// Send 1 USDT
const amountSmallest = "1000000"; // 1 * 10^6
const prepared = await sdk.api.prepareTransferSign({
chain: "ethereum",
txType: "TRANSFER_TOKEN",
from: eth.address,
to,
tokenContract: usdtContract,
amountWei: amountSmallest,
// Optional fee tuning
// priorityFeeMultiplier: 1.0,
// maxPriorityFeePerGasWei: "2000000000",
// Optional explicit nonce (otherwise backend resolves it)
// nonce: "12",
});
```
If the simulation fails, `prepareTransferSign` throws a structured SDK error with backend code `SIMULATION_FAILED`.
#### Sign transaction
Signing happens entirely on the client.
* No network requests are made during signing
* Private keys are provided by the client application at call time
* WalletSuite SDK does not persist keys
The SDK's `signEvmTransaction` / `signTronTransaction` helpers accept a raw private key — useful for tests and integration spikes. If your signer lives in a KMS, HSM, or custodian, skip these helpers: pass `prepared.data` to your signer, take the signed hex back, and continue to the broadcast step. `sendSignedTransaction` accepts any valid signed hex. See [Security Best Practices → Key Management](./security-best-practices#key-management) for backend key-storage guidance.
```ts theme={null}
// Sign locally — no network requests, private key never leaves the client
const signedTx = sdk.signEvmTransaction(prepared.data, eth.privateKeyHex);
// 0x-prefixed signed raw transaction
console.log("signedTx:", signedTx.slice(0, 20) + "...");
```
#### Broadcast signed transaction
```ts theme={null}
const sent = await sdk.api.sendSignedTransaction({
chain: "ethereum",
signedTx,
});
console.log("tx hash:", sent.data.hash);
```
#### Check transaction status
The SDK does not provide any waiting or polling helpers.
After broadcasting the transaction, the client application is responsible for deciding when and how to check the status. A common approach is to wait \~10 seconds (Ethereum) or few seconds other chains before the first check and then poll periodically until a terminal state is reached.
```ts theme={null}
// Example: single status check after a delay handled by the client
const status = await sdk.api.getTransactionStatus(sent.data.hash, "ethereum");
console.log("status:", status.status);
console.log("success:", status.success);
console.log("block:", status.blockNumber ?? "-");
```
#### Read USDT balance again
```ts theme={null}
const after = await sdk.api.getAssetBalances(eth.address, {
chain: "ethereum",
fiat: "USD",
assetIds: [usdtContract],
includeNative: false,
});
console.log("USDT balance (after):", after.data);
```
Notes
* Balance updates depend on chain finality and node/indexer freshness.
* If you transfer the full balance, account level gas costs are paid in ETH and do not affect USDT amount.
**Common failure modes — Simulation failed.**
* Typical causes: insufficient token balance, invalid recipient, token restrictions
* Expected SDK error: a structured error with backend code `SIMULATION_FAILED`
***
## Bring your own signer
The prepare-sign → broadcast surface of the SDK is signer-agnostic. `prepareTransferSign` returns a typed unsigned payload and `sendSignedTransaction` accepts any valid signed hex — how you sign in between is up to you.
### Local private key
For tests, integration spikes, or cases where the private key legitimately belongs in your application's process. `sdk.transfer()` handles the whole prepare → sign → broadcast flow in one call:
```ts theme={null}
const result = await sdk.transfer({
chain: "ethereum",
from: eth.address,
to: "0xRecipient",
amount: "1000000", // smallest unit
privateKey: eth.privateKeyHex,
txType: "TRANSFER_TOKEN", // optional, auto-detected from tokenContract
tokenContract: "0xdAC17F958D2ee523a2206206994597C13D831ec7", // omit for native transfers
priorityFeeMultiplier: 1.0, // optional, default 1.0
});
console.log("tx hash:", result.hash);
```
Use the step-by-step flow above when you need to display fee details to the user before signing, or when you want to split preparation and signing across processes.
### External signer (KMS, HSM, custodian)
The production pattern, equivalent to [External Signing](/core-concepts/architecture#two-signing-modes) in the architecture model. Prepare with `sdk.api.prepareTransferSign`, hand the payload to your existing signer, broadcast with `sdk.api.sendSignedTransaction`. The SDK never sees the key.
```ts theme={null}
// 1. Prepare the unsigned payload with the SDK
const prepared = await sdk.api.prepareTransferSign({
chain: "ethereum",
txType: "TRANSFER_TOKEN",
from: fromAddress,
to: recipient,
tokenContract: "0xdAC17F958D2ee523a2206206994597C13D831ec7",
amountWei: "1000000",
});
// 2. Sign with your own infrastructure — shape varies per signer
const signedTx = await yourSigner.signEvmTransaction(prepared.data);
// 3. Broadcast
const sent = await sdk.api.sendSignedTransaction({
chain: "ethereum",
signedTx,
});
```
See [Add Wallet Operations to Your Application](/getting-started/use-case-guides/add-wallet-to-backend) for a worked backend-integration example.
# Error Handling
Source: https://docs.walletsuite.io/sdk/error-handling
SDK calls throw `ApiError` — a transport-layer wrapper around the response returned by the WalletSuite API. The wrapper carries the HTTP call metadata (URL, method, status, retry attempt). The canonical error contract — `category`, `code`, `message`, `requiredAction` — is documented in [Structured Errors](/core-concepts/structured-errors). Parse `err.bodySnippet` as JSON to reach it.
## `ApiError` fields
| Field | Type | What it carries |
| ------------- | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| `message` | `string` | Human-readable summary (same as `Error.message`) |
| `url` | `string` | Full request URL that failed |
| `method` | `string` | HTTP verb (`GET`, `POST`, …) |
| `status` | `number \| undefined` | HTTP status code. **Undefined** means the request never reached the server — a transport-level failure (timeout, DNS, `ECONNREFUSED`) |
| `bodySnippet` | `string \| undefined` | Truncated raw response body. Contains the structured error JSON when the server returned one |
| `attempt` | `number` | Which retry attempt failed (1-indexed). Useful for correlating with logs |
| `causeErr` | `unknown` | Underlying error object, if any (`fetch` failure cause, etc.) |
## Reading the structured error
Wrap every SDK call in `try / catch`. Check `err.bodySnippet` for the structured payload when the server responded:
```ts theme={null}
import { WalletSuiteSDK, ApiError } from "@walletsuite/wallet-sdk";
try {
await sdk.api.getPriceBySymbol("ETH", "USD");
} catch (err) {
if (err instanceof ApiError && err.bodySnippet) {
try {
const payload = JSON.parse(err.bodySnippet);
// payload: { category, code, message, requiredAction? }
// See /core-concepts/structured-errors for the full taxonomy.
} catch {
// bodySnippet was non-JSON — fall through to transport handling
}
}
}
```
See [Structured Errors → From the SDK](/core-concepts/structured-errors#from-the-sdk) for a switch on `category` that covers every recovery path.
## Transport-level failures
When `err.status` is **undefined**, the request never reached the server. Common causes:
* Network timeout
* DNS resolution failure
* `ECONNREFUSED` from a local proxy
* TLS handshake error
In this case `err.bodySnippet` is also undefined and there is no structured payload to parse — the retry policy should treat it like an `upstream` category error (retry with backoff, then surface to the user).
## Debug logging
Enable `debug: true` during development to log request and response details to stdout:
```ts theme={null}
const sdk = new WalletSuiteSDK({
env: "prod",
apiKey: process.env.WALLETSUITE_API_KEY!,
debug: true,
});
```
Never leave `debug: true` enabled in production — logs may include URLs that carry sensitive path parameters.
## Related
* [Structured Errors](/core-concepts/structured-errors) — canonical `category` / `code` / `requiredAction` taxonomy
* [Security Best Practices → Logging](/sdk/security-best-practices) — what to log and what to redact
# Installation
Source: https://docs.walletsuite.io/sdk/installation
This page explains how to install the WalletSuite SDK using your preferred package manager.
## Prerequisites
Before installing, make sure you have:
* Node.js 18 or later recommended
* A package manager: npm, Yarn, or pnpm
* WalletSuite API key — see [Credentials & Authentication](/getting-started/prerequisites/credentials-and-authentication)
* GitHub personal access token with `read:packages` scope — the SDK is published to GitHub Packages, not the public npm registry
## Configure the registry
The SDK is hosted on **GitHub Packages**, so npm needs to be told where to find it before `install` will work.
Create or update `.npmrc` in your project root:
```ini .npmrc theme={null}
@walletsuite:registry=https://npm.pkg.github.com
//npm.pkg.github.com/:_authToken=${GITHUB_TOKEN}
```
Then export your token in the shell that runs `install`:
```bash theme={null}
export GITHUB_TOKEN=
```
Keep `.npmrc` out of version control if it contains a literal token. Prefer the `${GITHUB_TOKEN}` form above so the token stays in your shell or CI secret store.
## Install
```bash Install with npm theme={null}
npm install @walletsuite/wallet-sdk
```
```bash Install with Yarn theme={null}
yarn add @walletsuite/wallet-sdk
```
```bash Install with pnpm theme={null}
pnpm add @walletsuite/wallet-sdk
```
## Verify Installation
After installing, you should be able to import the SDK in your project.
```ts example.ts theme={null}
import { WalletSuiteSDK } from "@walletsuite/wallet-sdk";
```
If your build system uses ESM or TypeScript, this should work out of the box.
## Next Step
Continue to the [SDK Quick Start](/sdk/quick-start) to make your first authenticated request.
# Integration Samples
Source: https://docs.walletsuite.io/sdk/integration-samples
## Crypto & Wallets
This section demonstrates local key derivation for testing and development. In production, keys live in your existing key-management system (KMS, HSM, or custodian) — use the SDK's prepare → broadcast surface and sign with that infrastructure. See [Bring your own signer](./end-to-end-token-transfer-flow#bring-your-own-signer).
#### Create a new wallet (mnemonic) and derive Ethereum
default path `m/44'/60'/0'/0/0`
```ts theme={null}
import { WalletSuiteSDK } from "@walletsuite/wallet-sdk";
const sdk = new WalletSuiteSDK({
apiKey: process.env.WALLETSUITE_API_KEY!,
env: "prod",
});
await sdk.init();
const { mnemonic, wallet: w } = await sdk.createWallet(); // 12 words by default
const eth = await w.deriveEthereum();
console.log("mnemonic:", mnemonic);
console.log("eth.address:", eth.address);
console.log(
"eth.privateKeyHex (64 hex):",
eth.privateKeyHex.slice(0, 10) + "…",
);
console.log("eth.publicKeyHex (130 hex):", eth.publicKeyHex.slice(0, 12) + "…");
```
Return object shape:
```ts theme={null}
// DerivedKey
{
privateKeyHex: string; // 64 hex chars
publicKeyHex: string; // 130 hex chars (uncompressed secp256k1)
address: string; // 0x… (EVM address)
}
```
Security: never log full private keys in production. Above we truncate for demonstration.
#### Derive Tron
default path `m/44'/195'/0'/0/0`
```ts theme={null}
const tron = await w.deriveTron();
console.log("tron.address:", tron.address);
console.log("tron.privateKeyHex:", tron.privateKeyHex.slice(0, 10) + "…");
console.log("tron.publicKeyHex:", tron.publicKeyHex.slice(0, 12) + "…");
```
Return object shape:
```ts theme={null}
{
privateKeyHex: string; // 64 hex chars
publicKeyHex: string; // 130 hex chars
address: string; // T… (base58) or 41… hex depending on encoding
}
```
***
## API calls through SDK
Assume:
```ts theme={null}
// Using the sdk instance created above:
const api = sdk.api;
```
#### Prices — by symbol
```ts theme={null}
const price = await api.getPriceBySymbol("ETH", "USD");
console.log("symbol:", price.symbol);
console.log("value:", price.value);
console.log("updatedAt:", price.updatedAt);
```
Returned (brief):
```ts theme={null}
{
symbol: string; // e.g. "ETH"
basePair: string; // e.g. "USD" (API supports other basePairs as well e.g. EURO, AED etc.)
value: number; // latest price
updatedAt: string; // ISO timestamp
}
```
#### Prices — by contract address
```ts theme={null}
const p2 = await api.getPriceByContract("0xA0b86991…", "ethereum", "USD");
console.log("symbol:", p2.symbol);
console.log("value:", p2.value);
```
Returned (brief): same shape as above.
#### Blocks — latest indexed block information
```ts theme={null}
const block = await api.getLatestBlocks("ethereum");
console.log("number:", block.number);
console.log("hash:", block.hash);
console.log("timestamp:", block.timestamp);
```
Returned (brief):
```ts theme={null}
{
number: number;
hash: string;
parentHash?: string;
timestamp: number; // unix milliseconds
}
```
#### Balances — native balance
```ts theme={null}
const bal = await api.getNativeBalance("0xYourAddress", "ethereum", "USD");
console.log("ok:", bal.ok);
console.log("amount:", bal.data?.amount);
console.log("symbol:", bal.data?.symbol);
console.log("fiatValue:", bal.data?.fiatValue);
```
Returned (brief):
```ts theme={null}
{
ok: boolean;
code: string;
message: string;
data?: {
chain: string; symbol: string; decimals: number; address: string;
smallestUnit: string | number; amount: number; fiatCurrency?: string; fiatValue?: number;
};
}
```
#### Balances — native plus token balances
```ts theme={null}
const res = await api.getAssetBalances("0xYourAddress", {
chain: "ethereum",
fiat: "USD",
assetIds: [
"0xdAC17F958D2ee523a2206206994597C13D831ec7", // USDT
"0xA0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", // USDC
],
includeNative: true,
});
console.log("totalFiatValue:", res.data.totalFiatValue);
console.log("assets:", res.data.assets);
```
Notes:
* If `assetIds` is omitted, WalletSuite uses a default set:
* Ethereum: USDT and USDC
* Tron: USDT
* Zero balances are omitted from `assets`.
#### Assets — list supported assets
```ts theme={null}
// Default: every supported asset on the chain
const list = await api.listAssets({ chain: "ethereum" });
console.log("count:", list.data?.length);
if (list.data?.[0])
console.log("first asset:", list.data[0].id, list.data[0].symbol);
// Filter: only stablecoins
const stables = await api.listAssets({
chain: "ethereum",
stablecoinOnly: true,
});
// Search by symbol or name (substring match), with a custom page size
const usdc = await api.listAssets({
chain: "ethereum",
symbol: "USDC",
limit: 5,
});
const wrapped = await api.listAssets({
chain: "ethereum",
name: "Wrapped",
limit: 20,
});
// Drop the market-cap floor (defaults to true) to include long-tail assets
const all = await api.listAssets({ chain: "ethereum", requireMcap: false });
```
Filter parameters (all optional):
```ts theme={null}
{
chain?: string; // default "ethereum"
stablecoinOnly?: boolean; // default false
requireMcap?: boolean; // default true; false includes long-tail assets
symbol?: string; // case-insensitive substring match on symbol
name?: string; // case-insensitive substring match on name
limit?: number; // default 10
}
```
Returned (brief):
```ts theme={null}
{
ok: boolean; code: string; message: string;
data?: Array<{
id: string; name: string; symbol: string; type: string; decimals: number;
description?: string; website?: string; explorer?: string; logo?: string;
}>;
}
```
#### Assets — single asset by id
```ts theme={null}
const asset = await api.getAssetById("usdc", "ethereum");
console.log("asset:", asset.data?.name, asset.data?.symbol);
```
Returned (brief): same `TokenInfo` object as in list items.
#### Fees — transfer fee quote
```ts theme={null}
const quote = await api.quoteTransferFee({
chain: "ethereum",
from: "0xYourAddress",
to: "0xRecipient",
amountWei: "10000000000000000", // 0.01 ETH in wei (string recommended)
tokenContract: null,
fiat: "USD",
});
console.log("feeAmount:", quote.data.feeAmount);
console.log("fiatValue:", quote.data.fiatValue);
```
Token transfer example:
```ts theme={null}
const quoteToken = await api.quoteTransferFee({
chain: "ethereum",
from: "0xYourAddress",
to: "0xRecipient",
amountWei: "1000000", // token smallest units (example)
tokenContract: "0xA0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", // USDC
fiat: "USD",
});
console.log("feeSymbol:", quoteToken.data.feeSymbol);
console.log("feeAmount:", quoteToken.data.feeAmount);
```
Tron memo example (optional `memo` field; on Tron this adds a fixed 1 TRX fee + extra bandwidth):
```ts theme={null}
const quoteWithMemo = await api.quoteTransferFee({
chain: "tron",
from: "TYourAddress",
to: "TRecipient",
amountWei: "1000000", // 1 USDT (6 decimals)
tokenContract: "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t", // USDT on Tron
memo: "invoice #1234",
fiat: "USD",
});
```
#### Transactions — live status by tx hash
```ts theme={null}
const tx = await api.getTransactionStatus("0xTransactionHash…", "ethereum");
console.log("status:", tx.status);
console.log("success:", tx.success);
console.log("from→to:", tx.from, "→", tx.to);
```
Returned (brief):
```ts theme={null}
{
status: "PENDING" | "SUCCEEDED" | "FAILED" | "UNKNOWN";
success: boolean;
blockNumber?: number; txIndex?: number; gasUsed?: string;
from?: string; to?: string; value?: string; displayValue?: string;
txType:
| "TRANSFER_NATIVE" | "TRANSFER_TOKEN"
| "CONTRACT_CREATION" | "CONTRACT_CALL"
| "SWAP" | "STAKE" | "UNSTAKE" | "CLAIM_REWARDS"
| "UNKNOWN";
tokenContract?: string; tokenTo?: string; tokenValue?: string; methodId?: string;
blockTimestamp?: number; // unix milliseconds; null until confirmed
}
```
#### Transactions — history by address
```ts theme={null}
const history = await api.getTransactionHistory("0xYourAddress", {
chain: "ethereum",
});
console.log("items:", history.length);
if (history[0])
console.log(
"first:",
history[0].hash,
history[0].txType,
history[0].displayValue,
);
```
Returned (brief):
```ts theme={null}
Array<{
hash: string;
chain: string;
blockNumber: number;
timestamp: string;
from: string;
to: string;
value: string | null;
displayValue: string | null;
txType:
| "TRANSFER_NATIVE"
| "TRANSFER_TOKEN"
| "CONTRACT_CREATION"
| "CONTRACT_CALL"
| "SWAP"
| "STAKE"
| "UNSTAKE"
| "CLAIM_REWARDS"
| "UNKNOWN";
tokenContract?: string | null;
tokenSymbol?: string | null;
tokenDecimals?: number | null;
tokenValue?: string | null;
tokenDisplayValue?: string | null;
direction: "IN" | "OUT" | string;
}>;
```
#### Transactions — send signed transaction
```ts theme={null}
const sent = await api.sendSignedTransaction({
chain: "ethereum",
signedTx: "0xSignedRawTransactionHex",
});
console.log("hash:", sent.data.hash);
```
Notes:
* Ethereum expects `signedTx` as `0x` prefixed raw signed tx hex.
* Tron expects signed tx hex; `0x` prefix is accepted.
#### Transactions — prepare signing payload for transfers
This endpoint prepares an EVM signing payload for:
* `TRANSFER_NATIVE`
* `TRANSFER_TOKEN`
It calculates:
* `nonce` (EVM only) if not provided
* `data` for ERC20 transfers
* fee params (EIP1559 by default when supported; legacy fallback when chain does not support)
* a simulation check using gas estimation
Native transfer example:
```ts theme={null}
const prep = await api.prepareTransferSign({
chain: "ethereum",
txType: "TRANSFER_NATIVE",
from: "0xYourAddress",
to: "0xRecipient",
amountWei: "10000000000000000", // 0.01 ETH
nonce: null,
tokenContract: null,
maxPriorityFeePerGasWei: null,
priorityFeeMultiplier: 1.0,
});
console.log("nonce:", prep.data.nonce);
console.log("fee mode:", prep.data.fee.mode);
```
Token transfer example:
```ts theme={null}
const prepToken = await api.prepareTransferSign({
chain: "ethereum",
txType: "TRANSFER_TOKEN",
from: "0xYourAddress",
to: "0xRecipient",
amountWei: "1000000",
tokenContract: "0xA0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
nonce: null,
maxPriorityFeePerGasWei: "1500000000",
priorityFeeMultiplier: 1.0,
});
console.log("data:", prepToken.data.data); // ERC20 calldata
console.log("fee:", prepToken.data.fee);
```
Human-readable amount (alternative to `amountWei`):
```ts theme={null}
// Native: backend resolves "1.5" using the chain's native decimals (18 for ETH)
const prepNative = await api.prepareTransferSign({
chain: "ethereum",
txType: "TRANSFER_NATIVE",
from: "0xYourAddress",
to: "0xRecipient",
amount: "1.5",
});
// Token: backend resolves "1.5" using the token's decimals (looked up via tokenContract)
// `symbol` is an optional decimal hint; ignored when `amountWei` is set or for native txs.
const prepUsdc = await api.prepareTransferSign({
chain: "ethereum",
txType: "TRANSFER_TOKEN",
from: "0xYourAddress",
to: "0xRecipient",
amount: "1.5",
tokenContract: "0xA0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
symbol: "USDC",
});
```
Notes:
* `amountWei` and `amount` are **mutually exclusive** — provide one or the other (at least one is required).
* `amount` is a human-readable decimal string (e.g. `"1.5"`); the backend converts it to smallest units using the chain (native) or asset registry (token).
* `symbol` is an optional decimal hint paired with `amount` for token transfers; never replaces `tokenContract`.
* `nonce` is optional; if omitted or null, WalletSuite fetches it automatically for EVM.
* For `TRANSFER_TOKEN`, `tokenContract` is required.
* `maxPriorityFeePerGasWei` can be provided to speed up inclusion; otherwise the backend uses a suggested value.
* `priorityFeeMultiplier` is an optional multiplier applied on top of `maxPriorityFeePerGasWei` or suggested tip.
#### Chains — list supported chains
```ts theme={null}
const chains = await api.listChains();
console.log("count:", chains.data?.length);
if (chains.data?.[0])
console.log("first:", chains.data[0].id, chains.data[0].symbol);
// Optional family filter, e.g. only EVM chains
const evm = await api.listChains("ethereum");
```
Returned (brief):
```ts theme={null}
{
ok: boolean;
code: string;
message: string;
data?: Array<{
id: string; // e.g. "ethereum", "tron"
name: string; // human-readable name
symbol: string; // native asset symbol e.g. "ETH", "TRX"
decimals: number;
slipId?: string | null; // SLIP-44 coin type
chainId?: string | null; // EVM chain id (numeric string)
chainFamily?: string | null; // e.g. "ethereum", "tron"
curve?: string | null;
publicKeyType?: string | null;
explorer?: { url?: string; txPath?: string; accountPath?: string } | null;
info?: { url?: string; source?: string; rpc?: string; documentation?: string } | null;
tier?: number | null; // provider tier: 1 = high traffic, 2 = moderate, 3 = niche; null = unclassified
}>;
}
```
Companion methods on the same surface:
```ts theme={null}
await api.getChainByKey("ethereum");
await api.getChainsBySlipId("60");
await api.getInfo();
await api.getInfoChains();
```
#### Chains — fee policy
Read the chain's recommended priority fee tiers before building or signing a transaction. Pair with `quoteTransferFee` for fiat conversion and `prepareTransferSign` to actually use a custom tip.
```ts theme={null}
const policy = await api.getChainFeePolicy("ethereum");
console.log("policy:", policy.data?.policy);
console.log("recommended:", policy.data?.recommendedPriorityFeeWei);
```
Returned (brief):
```ts theme={null}
{
ok: boolean; code: string; message: string;
data?: {
chain: string;
policy: string; // "eip1559" | "legacy" | "resource" (resource = Tron's energy/bandwidth model)
minPriorityFeeWei?: string | null; // smallest unit string
recommendedPriorityFeeWei?: string | null;
fastPriorityFeeWei?: string | null;
};
}
```
#### Approvals — get current allowance
Inspect how many tokens an `owner` has approved a `spender` to move on a given ERC-20 contract.
```ts theme={null}
const allowance = await api.getAllowance({
chain: "ethereum",
owner: "0xYourAddress",
tokenContract: "0xA0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", // USDC
spender: "0xRouterContract",
});
console.log("allowance (smallest unit):", allowance.data?.allowance);
```
Returned (brief):
```ts theme={null}
{
ok: boolean; code: string; message: string;
data?: {
chain: string;
owner: string;
tokenContract: string;
spender: string;
allowance: string; // BigInteger as decimal string
updatedAt: string; // ISO 8601
};
}
```
#### Approvals — check sufficiency
Compare current allowance to the `requiredAmount` for an upcoming spend. The backend returns a single boolean plus the gap so the client doesn't need to compare BigInts.
```ts theme={null}
const check = await api.checkApproval({
chain: "ethereum",
owner: "0xYourAddress",
tokenContract: "0xA0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
spender: "0xRouterContract",
requiredAmount: "1000000", // 1 USDC (6 decimals)
});
if (!check.data?.approved) {
// need to call buildApproveTransaction below first
}
```
Returned (brief):
```ts theme={null}
{
ok: boolean; code: string; message: string;
data?: {
chain: string;
approved: boolean;
currentAllowance: string; // BigInteger as decimal string
requiredAmount: string;
};
}
```
#### Approvals — build approve transaction
Build the unsigned ERC-20 `approve(spender, amount)` calldata. Same prepare → sign → broadcast pattern as `prepareTransferSign`.
```ts theme={null}
const built = await api.buildApproveTransaction({
chain: "ethereum",
owner: "0xYourAddress",
tokenContract: "0xA0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
spender: "0xRouterContract",
amount: "1000000000", // raise allowance to 1,000 USDC (6 decimals)
// optional fee controls:
// nonce: "42",
// maxPriorityFeePerGasWei: "1500000000",
// priorityFeeMultiplier: 1.2,
});
const signedTx = sdk.signEvmTransaction(built.data, eth.privateKeyHex);
const sent = await api.sendSignedTransaction({ chain: "ethereum", signedTx });
console.log("approval tx:", sent.data?.hash);
```
Returned (brief):
```ts theme={null}
{
ok: boolean; code: string; message: string;
data?: {
chain: string;
chainId?: number | null;
from: string;
to: string; // tokenContract — the call target
valueWei: string; // "0" for token approvals
data: string; // 0x-prefixed approve(spender, amount) calldata
nonce?: string | null;
fee: {
mode: "eip1559" | "legacy";
gasLimit?: string | null;
maxPriorityFeePerGas?: string | null;
maxFeePerGas?: string | null;
gasPrice?: string | null;
};
};
}
```
#### Asset status — enablement
Whether an asset is currently enabled on the platform, with a human-readable reason if disabled.
```ts theme={null}
const status = await api.getAssetStatus("usdc", "ethereum");
console.log("status:", status.data?.status);
if (status.data?.reason) console.log("reason:", status.data.reason);
```
Returned (brief):
```ts theme={null}
{
ok: boolean; code: string; message: string;
data?: {
chain: string;
assetId: string;
status: "ENABLED" | "DISABLED" | "BLACKLISTED" | string;
reason?: string | null;
updatedAt: string; // ISO 8601
};
}
```
#### Asset status — per-action restrictions
Buy / sell / transfer / swap allowances per asset. Useful for compliance-aware UIs that hide or disable individual buttons.
```ts theme={null}
const r = await api.getAssetRestrictions("usdc", "ethereum");
console.log("canTransfer:", r.data?.canTransfer);
console.log("canSwap:", r.data?.canSwap);
```
Returned (brief):
```ts theme={null}
{
ok: boolean; code: string; message: string;
data?: {
chain: string;
assetId: string;
canBuy: boolean;
canSell: boolean;
canTransfer: boolean;
canSwap: boolean;
reason?: string | null;
updatedAt: string;
};
}
```
#### Name service — resolve name to address
ENS, TNS, or any chain-specific name service the backend knows about. Names are normalized server-side (trimmed and lowercased).
```ts theme={null}
const r = await api.resolveName({
chain: "ethereum",
name: "vitalik.eth",
});
console.log("address:", r.data?.address);
console.log("resolver:", r.data?.resolver); // e.g. "ENS"
```
Returned (brief):
```ts theme={null}
{
ok: boolean; code: string; message: string;
data?: {
chain: string;
name: string;
address: string;
resolver?: string | null;
updatedAt: string;
};
}
```
#### Name service — reverse resolve
Look up the human-readable name an address advertises (if any).
```ts theme={null}
const r = await api.reverseResolveName({
chain: "ethereum",
address: "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
});
console.log("name:", r.data?.name); // null when no reverse record exists
```
Returned (brief):
```ts theme={null}
{
ok: boolean; code: string; message: string;
data?: {
chain: string;
address: string;
name?: string | null;
updatedAt: string;
};
}
```
#### Chain-specific — EVM token metadata by contract
Look up `name` / `symbol` / `decimals` for any ERC-20 contract, even ones outside the curated `listAssets` set. Useful when you only have a contract address from on-chain logs or a partner integration.
```ts theme={null}
const meta = await api.getEvmTokenMetadata({
chain: "ethereum",
contract: "0xA0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
});
console.log(meta.data?.symbol, meta.data?.decimals);
```
Returned (brief):
```ts theme={null}
{
ok: boolean; code: string; message: string;
data?: {
chain: string;
contract: string;
name?: string | null;
symbol?: string | null;
decimals?: number | null;
verified: boolean;
updatedAt: string;
};
}
```
#### Chain-specific — Tron account resources
Energy and bandwidth used vs available for a Tron address. Decide whether to burn TRX as fee or stake for resources before sending a TRC-20 transfer.
```ts theme={null}
const res = await api.getTronResources("TJRabPrwbZy45sbavfcjinPJC18kjpRTv8");
console.log("energy:", res.data?.energyUsed, "/", res.data?.energyLimit);
console.log(
"bandwidth:",
res.data?.bandwidthUsed,
"/",
res.data?.bandwidthLimit,
);
```
Returned (brief):
```ts theme={null}
{
ok: boolean; code: string; message: string;
data?: {
chain: "tron";
address: string;
energyUsed: number;
energyLimit: number;
bandwidthUsed: number;
bandwidthLimit: number;
updatedAt: string;
};
}
```
#### Chain-specific — EVM L2 fee estimate
L2 chains (Optimism, Base, Arbitrum, etc.) charge an additional L1 data fee that's not part of `gasPrice * gasUsed`. Estimate it before signing.
```ts theme={null}
const l1 = await api.estimateEvmL2Fee({
chain: "base",
from: "0xYourAddress",
to: "0xRecipient",
valueWei: "10000000000000000", // 0.01 ETH
data: null, // or 0x… for contract calls
});
console.log("L1 data fee (wei):", l1.data?.l1FeeWei);
console.log("currency:", l1.data?.currency); // e.g. "ETH"
```
Returned (brief):
```ts theme={null}
{
ok: boolean; code: string; message: string;
data?: {
chain: string;
l1FeeWei: string; // BigInteger as decimal string
currency: string;
};
}
```
#### Staking — APR / APY
Current annual percentage rate and yield for native staking on the given chain. Caches at the source so two consecutive calls return the same numbers.
```ts theme={null}
const apr = await api.getStakingApr("tron");
console.log("APR:", apr.data?.apr, "APY:", apr.data?.apy);
console.log("source:", apr.data?.source);
```
Returned (brief):
```ts theme={null}
{
ok: boolean; code: string; message: string;
data?: {
chain: string;
apr: string; // BigDecimal as string, e.g. "0.0532"
apy: string;
updatedAt: string;
source: string;
};
}
```
#### Staking — list validators
Active set of validators with commission and per-validator APR estimate.
```ts theme={null}
const v = await api.listStakingValidators("tron");
v.data?.validators.forEach((val) =>
console.log(val.id, val.name, "commission:", val.commissionBps / 100, "%"),
);
```
Returned (brief):
```ts theme={null}
{
ok: boolean; code: string; message: string;
data?: {
chain: string;
validators: Array<{
id: string;
name: string;
commissionBps: number; // 100 = 1%
aprEstimate?: string | null; // BigDecimal as string
active: boolean;
}>;
updatedAt: string;
};
}
```
#### Staking — positions by address
Every active and unbonding position for an address. `unfreezeExpireTimeMs` is set during the unstaking cooldown period.
```ts theme={null}
const p = await api.getStakingPositions(
"TJRabPrwbZy45sbavfcjinPJC18kjpRTv8",
"tron",
);
p.data?.positions.forEach((pos) =>
console.log(
pos.validatorId,
"staked:",
pos.stakedAmountWei,
"rewards:",
pos.rewardsWei,
),
);
```
Returned (brief):
```ts theme={null}
{
ok: boolean; code: string; message: string;
data?: {
chain: string;
address: string;
positions: Array<{
chain: string;
address: string;
validatorId: string;
stakedAmountWei: string; // BigInteger as decimal string
rewardsWei: string;
status: string; // e.g. "ACTIVE" | "UNBONDING" | "INACTIVE"
unfreezeExpireTimeMs?: number | null;
updatedAt: string;
}>;
};
}
```
#### Staking — build delegate transaction
Build an unsigned delegate (stake) transaction.
```ts theme={null}
const built = await api.buildStakingDelegateTransaction({
chain: "tron",
from: "TJRabPrwbZy45sbavfcjinPJC18kjpRTv8",
validatorId: "TVj7RNVHy6thbM7BWdSe9G6gXwKhjhdNZS",
amountWei: "1000000000", // 1,000 TRX (sun = 6 decimals)
});
const signedTx = sdk.signTronTransaction(built.data, tron.privateKeyHex);
const sent = await api.sendSignedTransaction({ chain: "tron", signedTx });
```
Returned (brief): same `BuildStakingTxResponseDto` shape as below — feedable directly into `sdk.signEvmTransaction(...)` for EVM staking chains or `sdk.signTronTransaction(...)` for Tron.
```ts theme={null}
{
ok: boolean; code: string; message: string;
data?: {
chain: string;
chainId?: number | null;
from: string;
to: string;
valueWei: string;
data?: string | null;
nonce?: string | null;
fee: {
mode: "eip1559" | "legacy" | "tron" | string;
gasLimit?: string | null;
maxPriorityFeePerGas?: string | null;
maxFeePerGas?: string | null;
gasPrice?: string | null;
};
};
}
```
#### Staking — build undelegate transaction
```ts theme={null}
const built = await api.buildStakingUndelegateTransaction({
chain: "tron",
from: "TJRabPrwbZy45sbavfcjinPJC18kjpRTv8",
validatorId: "TVj7RNVHy6thbM7BWdSe9G6gXwKhjhdNZS",
amountWei: "500000000", // unstake 500 TRX
});
const signedTx = sdk.signTronTransaction(built.data, tron.privateKeyHex);
const sent = await api.sendSignedTransaction({ chain: "tron", signedTx });
```
Returned (brief): same `BuildStakingTxResponseDto` shape as the delegate step above.
#### Staking — build claim rewards transaction
`validatorId` is optional; omit to claim across every position the address holds.
```ts theme={null}
const built = await api.buildStakingClaimRewardsTransaction({
chain: "tron",
from: "TJRabPrwbZy45sbavfcjinPJC18kjpRTv8",
validatorId: "TVj7RNVHy6thbM7BWdSe9G6gXwKhjhdNZS",
});
const signedTx = sdk.signTronTransaction(built.data, tron.privateKeyHex);
const sent = await api.sendSignedTransaction({ chain: "tron", signedTx });
```
Returned (brief): same `BuildStakingTxResponseDto` shape as the delegate step above.
#### NFTs — list collections owned by an address
```ts theme={null}
const cols = await api.listNftCollections("0xYourAddress", "ethereum");
cols.data?.forEach((c) =>
console.log(c.standard, c.symbol ?? "(no symbol)", c.contract),
);
```
Returned (brief):
```ts theme={null}
{
ok: boolean; code: string; message: string;
data?: Array<{
chain: string;
contract: string;
name?: string | null;
symbol?: string | null;
standard: "ERC721" | "ERC1155" | "TRC721" | "TRC1155";
imageUrl?: string | null;
}>;
}
```
#### NFTs — item metadata
Fetch a single NFT's metadata (name, image, attributes) by `contract` + `tokenId`.
```ts theme={null}
const item = await api.getNftItem({
chain: "ethereum",
contract: "0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D", // BAYC
tokenId: "1",
});
console.log("name:", item.data?.name, "image:", item.data?.imageUrl);
```
Returned (brief):
```ts theme={null}
{
ok: boolean; code: string; message: string;
data?: {
chain: string;
contract: string;
tokenId: string;
standard: "ERC721" | "ERC1155" | "TRC721" | "TRC1155";
name?: string | null;
description?: string | null;
imageUrl?: string | null;
attributes: Record;
lastUpdatedAt: string;
};
}
```
#### NFTs — owners of a token
For ERC-721 returns one owner; for ERC-1155 may return many with non-1 quantities.
```ts theme={null}
const owners = await api.getNftOwners({
chain: "ethereum",
contract: "0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D",
tokenId: "1",
});
owners.data?.owners.forEach((o) => console.log(o.owner, "qty:", o.quantity));
```
Returned (brief):
```ts theme={null}
{
ok: boolean; code: string; message: string;
data?: {
chain: string;
contract: string;
tokenId: string;
owners: Array<{ owner: string; quantity: string }>;
};
}
```
#### NFTs — build transfer transaction
Builds the unsigned `transferFrom` (ERC-721) or `safeTransferFrom` (ERC-1155 / TRC-1155) calldata. For 1155 standards, set `amount`.
```ts theme={null}
const built = await api.buildNftTransferTransaction({
chain: "ethereum",
from: "0xYourAddress",
to: "0xRecipient",
contract: "0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D",
tokenId: "1",
standard: "ERC721",
// amount: "1", // required for ERC1155 / TRC1155
});
const signedTx = sdk.signEvmTransaction(built.data, eth.privateKeyHex);
const sent = await api.sendSignedTransaction({ chain: "ethereum", signedTx });
```
Returned (brief):
```ts theme={null}
{
ok: boolean; code: string; message: string;
data?: {
chain: string;
chainId?: number | null;
from: string;
to: string; // contract address — the call target
contract: string;
tokenId: string;
standard: "ERC721" | "ERC1155" | "TRC721" | "TRC1155";
valueWei: string; // "0" for NFT transfers
data: string; // 0x-prefixed transfer calldata
nonce?: string | null;
};
}
```
#### Swaps — list available tokens
```ts theme={null}
const tokens = await api.listSwapTokens("ethereum");
tokens.data?.forEach((t) => console.log(t.symbol, t.token, t.decimals));
```
Returned (brief):
```ts theme={null}
{
ok: boolean; code: string; message: string;
data?: Array<{
chain: string;
token: string; // contract address (or "ETH" for native)
symbol?: string | null;
decimals?: number | null;
}>;
}
```
#### Swaps — quote
Quote a swap and receive a `quoteId` to feed into `getSwapRoute` and `buildSwapTransaction`. `provider` is optional; the backend picks a best provider when omitted.
```ts theme={null}
const quote = await api.getSwapQuote({
chain: "ethereum",
fromAddress: "0xYourAddress",
sellToken: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", // WETH
buyToken: "0xA0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", // USDC
sellAmount: "1000000000000000000", // 1 WETH (18 decimals)
slippageBps: 50, // 0.50%
// provider: "ZERO_X",
});
console.log("quoteId:", quote.data?.quoteId);
console.log("buyAmount:", quote.data?.buyAmount);
console.log("price:", quote.data?.price);
console.log("expiresAt:", quote.data?.expiresAt);
```
Returned (brief):
```ts theme={null}
{
ok: boolean; code: string; message: string;
data?: {
chain: string;
provider:
| "ZERO_X" | "ONE_INCH" | "PANCAKE_SWAP" | "QUICK_SWAP"
| "FLUID_SWAP" | "BALANCER" | "CURVE_FINANCE_SWAP"
| "UNISWAP" | "WOOFI" | "INTERNAL";
quoteId: string; // pass to getSwapRoute / buildSwapTransaction
sellToken: string;
buyToken: string;
sellAmount: string; // BigInteger as decimal string
buyAmount: string;
minBuyAmount: string; // buyAmount after slippage
price: string; // BigDecimal as decimal string
priceImpactBps?: number | null;
estimatedGas?: string | null;
issues: string[]; // backend-flagged warnings (e.g. "low liquidity")
expiresAt: string; // ISO 8601 — quote re-fetch required after this
};
}
```
#### Swaps — route preview
Inspect the multi-hop route the backend will execute, including any approval that will be inserted as a prerequisite. Useful for building UI affordances ("you'll be asked to approve USDC first").
```ts theme={null}
const route = await api.getSwapRoute({
chain: "ethereum",
quoteId: quote.data!.quoteId,
});
route.data?.steps.forEach((s) =>
console.log(s.kind, s.description, s.needsApproval ? "(needs approval)" : ""),
);
```
Returned (brief):
```ts theme={null}
{
ok: boolean; code: string; message: string;
data?: {
chain: string;
provider: SwapProvider; // same union as in getSwapQuote
quoteId: string;
steps: Array<{
kind: string; // e.g. "approval" | "swap"
description: string;
spender?: string | null;
needsApproval: boolean;
approvalToken?: string | null;
approvalAmount?: string | null; // BigInteger as decimal string
callTo?: string | null;
callData?: string | null;
}>;
totalEstimatedGas?: string | null;
warnings: string[];
};
}
```
#### Swaps — build swap transaction
Build the final unsigned swap tx from a `quoteId`. If `getSwapRoute` indicated `needsApproval`, run `buildApproveTransaction` first and broadcast that approval before calling this one.
```ts theme={null}
const built = await api.buildSwapTransaction({
chain: "ethereum",
fromAddress: "0xYourAddress",
quoteId: quote.data!.quoteId,
// optional fee controls:
// nonce: "42",
// maxPriorityFeePerGasWei: "1500000000",
// priorityFeeMultiplier: 1.2,
});
const signedTx = sdk.signEvmTransaction(built.data, eth.privateKeyHex);
const sent = await api.sendSignedTransaction({ chain: "ethereum", signedTx });
console.log("swap tx:", sent.data?.hash);
```
Returned (brief):
```ts theme={null}
{
ok: boolean; code: string; message: string;
data?: {
chain: string;
chainId?: number | null;
from: string;
to: string; // router / aggregator contract
valueWei: string; // non-zero only when selling the native asset
data: string; // 0x-prefixed swap calldata
nonce?: string | null;
fee: {
mode: "eip1559" | "legacy";
gasLimit?: string | null;
maxPriorityFeePerGas?: string | null;
maxFeePerGas?: string | null;
gasPrice?: string | null;
};
};
}
```
# Overview
Source: https://docs.walletsuite.io/sdk/overview
A typed TypeScript SDK for multi-chain wallet operations, signing, and on-chain data.
Everything runs through one class — `WalletSuiteSDK` — so wallet creation, key derivation, transaction lifecycle, and on-chain reads share the same auth, types, and error surface. Signing stays local to your infrastructure.
***
## Built for
Engineering teams who need typed, multi-chain wallet primitives in production — trading systems, treasury ops, fintech and payment backends, or Web3 features embedded into an existing stack. If you already own a signing layer (KMS, HSM, custodian), the SDK composes with it through [Bring your own signer](/sdk/end-to-end-token-transfer-flow#bring-your-own-signer).
***
## What you can build
* Multi-chain wallet creation with BIP-39 mnemonics and BIP-44 derivation
* Full transaction lifecycle — prepare, sign, broadcast — step-by-step or bundled in `sdk.transfer()`
* Native and token balance queries with fiat valuation
* Price, fee-quote, transaction status, and history queries across supported chains
* Asset metadata, name-service resolution (ENS, UNS), NFT ownership, and token allowance checks
* Staking and swap metadata queries on supported chains
* Bring-your-own-signer integration — keep keys in your existing KMS, HSM, or custodian
***
## Non-Custodial
WalletSuite never stores user funds or private keys. All signing happens locally. Key management remains under the integrator's full control.
Integrators are responsible for ensuring secure key handling and compliant usage within their own systems.
***
## Multi-Chain Wallet Operations
* Consistent API across supported chain families
* WASM-based cryptographic implementation compatible with modern runtimes
* Wallet-agnostic — works with any key management or wallet architecture
***
## What's Included
**Environment mapping**
pass `prod` and the SDK resolves the correct endpoints
**Typed API client**
Typed responses for balances, assets, prices, blocks, fees, transaction status and history
**TypeScript & JavaScript**
full typings, ESM + CJS dual export, Node 18+
# Quick Start
Source: https://docs.walletsuite.io/sdk/quick-start
This guide helps you authorize and make your first request with the WalletSuite SDK.
#### Get an API Key
See [Credentials & Authentication](/getting-started/prerequisites/credentials-and-authentication) to request a Pilot API key.
#### Set Your API Key
Store your API key in an environment variable.
```bash theme={null}
export WALLETSUITE_API_KEY="your_api_key"
```
If you are using an `.env` file, add:
```env theme={null}
WALLETSUITE_API_KEY=your_api_key
```
#### Initialize the Client
Create a client instance and pass your API key and environment.
```ts theme={null}
import { WalletSuiteSDK } from "@walletsuite/wallet-sdk"
const sdk = new WalletSuiteSDK({
apiKey: process.env.WALLETSUITE_API_KEY!,
env: "prod",
})
```
Notes:
* The SDK automatically sends the `X-API-KEY` header on every request
* All requests are scoped to your organization
#### Make Your First API Call
Call an endpoint from the SDK client. The exact method names depend on the SDK version, but usage follows this pattern.
```ts theme={null}
async function main() {
const result = await sdk.api.getLatestBlocks("ethereum");
console.log(JSON.stringify(result, null, 2));
}
main().catch(console.error);
```
```json theme={null}
{
"number": 24743234,
"hash": "0x69cc379e94acc8565250e834b36baf263313afada5cd8ab712feed4038408401",
"parentHash": "0xf7620978e51a6a40d256fae31b26cbb008b537bbc1e779aad37a621597fc3ddb",
"timestamp": 1774546199000
}
```
## Next Steps
* Explore the [Integration Samples](/sdk/integration-samples)
* Read [Security Best Practices](/sdk/security-best-practices)
* Review [Rate Limits](/getting-started/prerequisites/rate-limits) for Pilot tier constraints
## See it working
End-to-end recipes backed by the SDK:
* [x402 micropayments](/recipes/x402-micropayments) — build a payment flow on the x402 rail
* [Counterparty screening](/recipes/counterparty-screening) — pre-send risk check before broadcast
* [LangChain agent wallet](/recipes/langchain-agent-wallet) — wire the SDK behind a LangChain agent
* [CrewAI multi-agent](/recipes/crewai-multi-agent) — SDK-powered multi-agent coordination
# Security Best Practices
Source: https://docs.walletsuite.io/sdk/security-best-practices
Security is a shared responsibility. Follow these best practices when using the WalletSuite SDK.
***
## Key Management
Never commit mnemonics or private keys to source control.
* Use KMS or HSM solutions for server-side key storage
* On mobile devices, use secure storage mechanisms
* Avoid exposing keys in frontend applications
***
## Logging & Monitoring
Never log private keys, mnemonics, or API keys.
* Log only minimal diagnostic information
* Sanitize error outputs before persisting logs
***
## Recommended Architecture
* Prefer executing sensitive operations from backend services
* If browser access is required, use a backend proxy and/or request CORS allowlisting
* Treat frontend applications as request initiators only
* Enforce server-side authentication for all API requests
***
## API Key Protection
Keep your WalletSuite API key on the backend only. Call WalletSuite from server-side services and expose only minimal, controlled endpoints to the frontend.
If browser access is required:
* Be aware that secrets in browser bundles can be discovered
* Prefer a backend proxy (`browser → backend → WalletSuite`)
* Apply authentication, rate limiting, and logging on the proxy
* CORS allowlisting can be enabled if direct calls are unavoidable
### Storage Recommendations
* **Backend:** use a secrets manager (AWS / GCP / Azure) or CI/CD secrets
* **Mobile:** use platform-secure storage (Keychain / Secure Enclave)
* **Browser:** avoid; if unavoidable, minimize scope and actively monitor usage
# Audit Trail
Source: https://docs.walletsuite.io/security/audit-trail
Hash-chained JSONL logs for compliance and forensics on every signing operation.
Every `sign_transaction` and `send_transaction` operation produces an audit receipt appended to a local JSONL log. Receipts are linked by a SHA-256 hash chain for tamper detection.
## Location
| Setting | Default | Override |
| ---------- | ---------------------------------- | -------------------------------- |
| Audit file | `~/.walletsuite/audit-trail.jsonl` | `WALLETSUITE_AUDIT_PATH` env var |
The file is append-only and local-only. It does not support concurrent writers.
## Receipt Format
Each line is a JSON object containing:
| Field | Description |
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `receiptId` | Unique receipt identifier |
| `timestamp` | ISO 8601 timestamp |
| `tool` | `sign_transaction` or `send_transaction` |
| `walletName` | OWS wallet used for signing |
| `chain` | Chain ID (e.g., `eip155:1`, `tron:mainnet`) |
| `txDigest` | Truncated transaction hex (first 8 + last 8 chars) |
| `txHash` | On-chain transaction hash (if broadcast succeeded) |
| `approvalStatus` | `confirmed`, `missing`, or `not_required` |
| `counterpartyCheck` | Counterparty screening result. An object, not a scalar: `{ status: "not_performed" }`, `{ status: "performed", provider?, result: "clear" \| "flagged", details? }`, or `{ status: "failed", provider?, errorCode?, errorMessage? }` |
| `policy` | Policy evaluation snapshot: `result` (`allow`/`deny`), `matchedRules`, `policyIds`, `denyReason` |
| `outcome` | `success` or `error` |
| `errorCode` | Error code (if outcome is `error`) |
| `errorMessage` | Error message (if outcome is `error`) |
| `elapsedMs` | Operation duration in milliseconds |
| `contentHash` | SHA-256 hash of the receipt content (excluding chain fields) |
| `prevHash` | Previous receipt's `entryHash` (forms the chain) |
| `entryHash` | SHA-256 over `prevHash + ":" + contentHash` when `prevHash` exists; SHA-256 over `contentHash` alone for the first receipt. The chain link |
## Hash Chain
Each receipt has two hashes: `contentHash` (SHA-256 of the receipt data) and `entryHash` (SHA-256 that links the receipt to the previous one). The first receipt's `entryHash` is just the SHA-256 of its `contentHash`. Every subsequent receipt's `entryHash` is SHA-256 of `prevHash + ":" + contentHash` — `prevHash` first, a literal `:` separator, then the current receipt's `contentHash`. Tampering with any entry invalidates every entry after it.
```
Receipt 1: contentHash = SHA-256(receipt_1_data)
entryHash = SHA-256(contentHash)
Receipt 2: contentHash = SHA-256(receipt_2_data)
entryHash = SHA-256(receipt_1.entryHash + ":" + contentHash)
Receipt 3: contentHash = SHA-256(receipt_3_data)
entryHash = SHA-256(receipt_2.entryHash + ":" + contentHash)
```
To verify integrity, walk the chain and confirm each receipt's `prevHash` equals the preceding receipt's `entryHash`, then recompute `entryHash` using the formula above.
## Secret Redaction
The audit trail automatically redacts sensitive patterns before writing:
* API keys and bearer tokens
* Mnemonics and private keys
* GitHub and AWS credentials
* Multiple patterns matched in a single compiled pass
Transaction hex is truncated to first 8 + last 8 characters to prevent key material leakage from serialized payloads.
## Querying
The file is newline-delimited JSON. Query with standard tools:
```bash theme={null}
# Last 10 entries
tail -10 ~/.walletsuite/audit-trail.jsonl | jq .
# All denied operations
cat ~/.walletsuite/audit-trail.jsonl | jq 'select(.policy.result == "deny")'
# All successful broadcasts
cat ~/.walletsuite/audit-trail.jsonl | jq 'select(.tool == "send_transaction" and .outcome == "success")'
# Signing activity for a specific wallet
cat ~/.walletsuite/audit-trail.jsonl | jq 'select(.walletName == "treasury")'
# Counterparty screening that ran and flagged a recipient
# Note: counterpartyCheck is an object — filter on the nested .status / .result fields.
cat ~/.walletsuite/audit-trail.jsonl | jq 'select(.counterpartyCheck.status == "performed" and .counterpartyCheck.result == "flagged")'
```
## SIEM Integration
Export the JSONL file to your SIEM or log aggregation system:
* **Filebeat / Fluentd:** Tail the JSONL file and forward to Elasticsearch, Datadog, or Splunk
* **CloudWatch Logs:** For Docker deployments, mount the audit path and ship with the CloudWatch agent
* **S3 archival:** Periodically copy the file to S3 for long-term retention
The structured JSON format is compatible with any log ingestion pipeline that supports JSONL.
## Large File Handling
Within a running server process, the previous receipt's `entryHash` is cached in memory — each append links to the cached hash, so the chain stays intact for the lifetime of that process regardless of file size.
The \~100 MB threshold only matters at startup (or the first append after a process restart), when the server bootstraps the cache by reading back from disk. If the existing audit file exceeds \~100 MB at that moment, the server skips reading the previous hash to avoid blocking the Node.js event loop, logs `audit_prev_hash_skipped_large_file`, and the next receipt is written without a `prevHash` link. Subsequent receipts in the same process chain normally.
For high-volume deployments, rotate the audit file off-process (archive and truncate) before it crosses the threshold, or restart the server behind a rotation to keep the bootstrap path cheap.
## Related
* [Key Management](/security/key-management) — what gets signed and how
* [Security Overview](/security/overview) — the full trust model
* [Production Checklist](/production-checklist) — audit trail configuration checklist
# Build & Supply Chain Security
Source: https://docs.walletsuite.io/security/build
How to verify the WalletSuite MCP server you run matches the source you reviewed.
## Signed Releases
Every npm release of `@walletsuite/mcp-server` is published with provenance attestation. Provenance is a Sigstore-backed, OIDC-signed claim linking the tarball to the exact source commit and workflow run that produced it.
```bash theme={null}
npm audit signatures @walletsuite/mcp-server
```
The command verifies the attestation against the public Sigstore transparency log and returns the source repository, commit SHA, and workflow file used to build the package. Verification is independent of WalletSuite infrastructure: the trust anchor is the public transparency log, not any endpoint we control.
## Runtime Response Validation
Every backend response consumed by a tool is validated against a typed contract before it reaches the LLM. A tampered or unexpected response fails schema validation and surfaces as a structured, sanitized error — it never becomes input to agent reasoning. The same validation boundary applied to tool arguments is applied symmetrically to the other side of the API call.
Token amounts are preserved losslessly as strings. Large integer values are never coerced to floats, eliminating a class of precision-loss issues on balances and transfer amounts.
## Prompt Injection and Tool Hygiene
Beyond band filtering, the server assumes the LLM is an untrusted caller:
* Every tool input is validated against a Zod schema before any backend call. Address format, amount format, and mutually exclusive fields are enforced at the boundary.
* Every backend response is validated against a handwritten contract. A tampered or unexpected response fails schema validation and becomes a structured error, not agent input.
* Internal diagnostics are logged server-side and sanitized before reaching the LLM. The model sees a structured, user-safe error; the server retains the detail.
* Logs use operation labels rather than URLs. Wallet addresses, transaction parameters, and response payloads are not written.
## Secret Boundary
Secrets are supplied through environment variables at process launch. They are never passed as tool arguments, never returned in tool responses, and never written to prompt history or audit receipts.
Environment variables are the injection interface, not the storage mechanism. Production deployments are expected to source secrets from a secrets manager — HashiCorp Vault, AWS Secrets Manager, GCP Secret Manager, Kubernetes Secrets, Docker Secrets, or systemd credentials — and materialize them into the process environment only at launch. The server does not integrate with any specific KMS; the standard 12-factor injection path covers every major platform.
| Secret | When required | Runtime exposure |
| --------------------- | ------------------------------------------------- | ----------------------------------------------- |
| `WALLETSUITE_API_KEY` | Always | Read once at startup; never logged |
| `OWS_AGENT_TOKEN` | Headless / production (agent mode) | Read once at startup; never logged |
| `OWS_PASSPHRASE` | Interactive bootstrap and admin only (owner mode) | Read once at startup; never logged |
| Agent token files | Owner-mode bootstrap output | Local file, mode `0600`; never returned in chat |
Production and headless deployments run in agent mode with a scoped, policy-bound token. The vault passphrase is not required at runtime — it is used once by a human operator to bootstrap the vault and issue agent tokens, then does not need to be present in the environment where agents actually sign. This is the recommended production posture.
By default, newly issued agent tokens are written to a local file with owner-only permissions (`0600`). The CLI can print a token to stdout instead, but only when invoked with an explicit, intentionally-named opt-in flag — so a CI job or wrapping script can't capture a token through automation that didn't ask for it.
See [Key Management](/security/key-management) for the owner/agent mode distinction and full credential lifecycle.
## Logging and Redaction
Logs are designed to be useful for forensics without leaking request-identifying material.
* Operation labels replace URLs. Wallet addresses, transaction payloads, and request parameters are not logged.
* Internal diagnostics are retained server-side. The LLM receives a structured, sanitized error; the operator's logs retain the detail.
* Audit receipts use schema-driven redaction: only dynamic text fields are scanned for inline secret patterns. Operational fields — wallet name, chain, identifiers, hashes, policy IDs — are preserved for audit, compliance, and forensic review.
* Transaction hex is truncated before write, so even a serialized payload containing key material would not land in storage intact.
See [Audit Trail](/security/audit-trail) for the receipt schema and hash chain.
## Dependency Surface
Published releases fail on any high or critical advisory against a runtime dependency; the full list is verifiable via `npm view`.
For the broader posture on formal audits and third-party certifications that sit above the build pipeline, see [Security Diligence → Certifications Roadmap](/security/diligence#certifications-roadmap).
## Related
* [Security Overview](/security/overview) — trust model and defense in depth
* [Key Management](/security/key-management) — OWS vault, encryption, key lifecycle
* [Audit Trail](/security/audit-trail) — receipt schema and hash chain
* [Security Diligence](/security/diligence) — how to evaluate WalletSuite without SOC 2
* [Responsible Disclosure](/security/responsible-disclosure) — reporting a vulnerability
# Security Diligence
Source: https://docs.walletsuite.io/security/diligence
How WalletSuite answers security diligence questions today.
There is no customer data on our systems. Keys, signed transactions, and the audit trail all live on the operator's infrastructure. That is a categorical architectural guarantee, not a policy promise — and it is the starting point for every other question on this page.
## Keys Isolation
No WalletSuite employee can access keys, signed transactions, or the audit trail in a self-hosted deployment.
* Keys are generated inside the OWS vault on the customer infrastructure. They never leave.
* Signing happens inside OWS, on the customer infrastructure, against a key that exists in decrypted form only for the duration of the signing call.
* Audit receipts are appended to a JSONL file on the customer disk.
We never see any of it. No log forwarding, no telemetry on key use, no server-side record of signed payloads. The backend API sees chain queries and transaction preparation requests — public blockchain data and the address involved. It cannot access signatures, mnemonics, or vault contents.
The [Trust Model](/security/overview#trust-model) enumerates what each component can access.
## Data Residency
The audit trail, OWS vault, and signed payloads exist only on customer infrastructure — any cloud, on-prem, bare metal, or edge environment the customer chooses. There is no "WalletSuite copy." Whatever network boundary a deployment sits behind, that is where this material stays.
The WalletSuite backend API serves public chain data: balances, fees, metadata, unsigned transaction payloads. Residency requirements that apply to operational secrets or signing material are satisfied by the fact that those never leave customer infrastructure. Requirements that apply to public chain data are satisfied by the fact that it is public.
## Deployment Modes
Today, the only deployment mode is the self-hosted MCP server, which ships with the OWS vault built in. Everything on this page assumes that mode. The "no customer data on our systems" claim holds categorically because there is no other mode.
A hosted offering is on the roadmap. When it ships, customers choose one of two modes: fully self-hosted (today's deployment — the server and vault run on customer infrastructure) or fully hosted (the server and vault both run on WalletSuite infrastructure, with signing inside a hardware-attested enclave).
## Band Filtering as Prompt-Injection Defense
Band filtering is also a prompt-injection defense — enforced at the tool-visibility layer.
Tools outside the active band are never registered with the MCP client. They do not appear in `tools/list`, do not have a callable handler, and cannot be discovered through any in-session negotiation. An agent compromised by a malicious prompt — a poisoned search result, a hostile tool output from another server, a user instruction that was actually an injected command — cannot call a tool that the client does not know exists. It cannot invent `send_transaction` if broadcast is outside its band.
The enforcement point is the tool schema itself, not a runtime permission check. Bands are resolved once, at server startup, and reflected in the `tools/list` response the LLM receives. A model cannot reason around a tool that was never surfaced.
See [Band Filtering](/core-concepts/band-filtering) for the band hierarchy and configuration.
## Verification Path
1. **Architecture** — [Security Overview](/security/overview). Trust boundary, defense in depth, attack model.
2. **Key lifecycle** — [Key Management](/security/key-management). Vault encryption, BIP-39/44 derivation, memory hygiene.
3. **Audit trail** — [Audit Trail](/security/audit-trail). Receipt schema, hash chain, redaction model.
4. **Build provenance** — [Build & Supply Chain](/security/build). Signed releases, runtime response validation, secret boundary.
5. **SDK source** — public at [github.com/walletsuite](https://github.com/walletsuite).
## Incident Response
If you find a vulnerability, [security@walletsuite.io](mailto:security@walletsuite.io) is the canonical contact. The [Responsible Disclosure](/security/responsible-disclosure) page documents acknowledgment SLAs, severity definitions, and scope.
For operational incidents on a customer deployment, the audit trail is the starting point. The hash chain lets an operator prove, after the fact, that no entries were inserted, modified, or removed. Exporting the trail to a SIEM preserves this property across longer retention windows than a single host.
## Certifications Roadmap
Formal certifications are on the plan for procurement readiness. They do not change the architectural guarantees above.
| Certification | Status |
| ---------------------------- | -------------- |
| SOC 2 Type II | Planned — 2026 |
| CCSS Level 2 | Planned |
| Independent penetration test | In progress |
Auditors and target quarters will be published once engagements are signed. Until then, the architecture is the proof.
## Related
* [Security Overview](/security/overview) — trust model and defense in depth
* [Key Management](/security/key-management) — OWS vault and key lifecycle
* [Audit Trail](/security/audit-trail) — tamper-evident receipts
* [Build & Supply Chain](/security/build) — signed releases and runtime validation
* [Responsible Disclosure](/security/responsible-disclosure) — reporting vulnerabilities
# Key Management
Source: https://docs.walletsuite.io/security/key-management
OWS vault, BIP-39/44 derivation, and AES-256-GCM encryption.
WalletSuite uses the Open Wallet Standard (OWS) for local key management. Keys are generated, stored, and used entirely on the host where the agent runtime executes. WalletSuite never receives or transmits private key material.
## OWS Vault
| Property | Detail |
| ---------------------------- | -------------------------------------------------------------------------------------- |
| **Location** | `~/.ows` (configurable via `OWS_VAULT_PATH`) |
| **Encryption** | AES-256-GCM |
| **Key derivation** | BIP-39 mnemonic → BIP-44 hierarchical deterministic paths |
| **Supported signing chains** | Ethereum (`eip155:1`), Tron (`tron:mainnet`) |
| **Read/prepare chains** | [See supported chains](/ai-agents/supported-chains) (signing not required for queries) |
The vault stores encrypted key material. Decryption happens in memory only during a signing operation — the decrypted key is used, then wiped from memory.
## Two Access Modes
### Owner Mode
Direct control of the vault using a passphrase.
| Setting | Value |
| ---------------- | --------------------- |
| `OWS_AUTH_MODE` | `owner` |
| `OWS_PASSPHRASE` | Your vault passphrase |
**Can do:** Create wallets, create agent keys, create policies, sign, broadcast.
**Use for:** Interactive setup, wallet bootstrap, administration.
### Agent Mode
Delegated control using a pre-created scoped token.
| Setting | Value |
| ----------------- | --------------------------------- |
| `OWS_AUTH_MODE` | `agent` |
| `OWS_AGENT_TOKEN` | Token from `create_agent_api_key` |
**Can do:** Get wallet addresses, sign, broadcast — subject to policy constraints.
**Cannot do:** Create wallets, create keys, modify policies.
**Use for:** Headless automation, CI/CD pipelines, production agents.
## Key Lifecycle
### 1. Create
`create_wallet` (owner mode) generates a BIP-39 mnemonic, derives addresses for all supported signing chains, and stores the key material encrypted in the vault.
* The mnemonic is displayed once during creation — it is never stored in plaintext or returned again.
* Derived addresses are returned in the response so you know where to send funds.
* Default derivation path: `m/44'/60'/0'/0/0` (Ethereum), chain-specific paths for other networks.
### 2. Use
`sign_transaction` decrypts the key from the vault, signs the provided unsigned transaction hex, and returns the signature.
* Key material exists in memory only during the signing operation.
* After signing, the decrypted key is wiped from memory.
* The MCP server receives only the signature — never the key.
### 3. Delegate
`create_agent_api_key` (owner mode) creates a scoped token bound to a specific wallet and optional [policy](/core-concepts/policy-gates).
* The token is written to a local file with mode `0600` (owner-readable only).
* The token is never returned in chat or logged.
* Restart the MCP server in agent mode with `OWS_AGENT_TOKEN` sourced from that file.
### 4. Revoke
Delete the agent token file. The agent can no longer sign. The wallet and keys remain in the vault — only the delegation is removed.
## What the MCP Server Never Sees
| Secret | Where It Lives | MCP Server Access |
| ------------ | ---------------------- | ---------------------------------- |
| Private keys | OWS vault (encrypted) | Never |
| Mnemonics | Shown once at creation | Never stored, never returned |
| Passphrases | Environment variable | Read once at startup, never logged |
| Agent tokens | Environment variable | Read once at startup, never logged |
The MCP server operates through the OWS API — it asks OWS to sign, OWS handles key decryption internally, and only the signature comes back.
## Encryption Details
| Layer | Algorithm | Purpose |
| ------------------ | ------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
| Vault encryption | AES-256-GCM | Encrypt key material at rest |
| Agent key wrapping | HKDF-SHA256 → AES-256-GCM | Re-encrypt the wallet secret under a key derived from the agent token — the token is both authentication credential and decryption capability |
| Key derivation | BIP-39 → BIP-44 | Deterministic address generation from mnemonic |
| Signing | secp256k1 ECDSA (Ethereum), chain-specific (Tron) | Transaction signature |
| Memory protection | Zeroize on drop | Wipe key material from memory after use |
### Token-as-capability
When an owner mints an agent API key, OWS decrypts the wallet secret with the owner passphrase and re-encrypts it under a fresh AES-256-GCM key derived via HKDF-SHA256 from the agent token itself. The encrypted copy is stored with the API key record; the raw token is written only to the operator's token file (mode `0600`). At runtime, the agent presents the token and OWS re-derives the decryption key — the token functions as both authentication credential and decryption capability. Revoking a single token invalidates only that wrapped copy; disk access alone cannot decrypt without the token, and the token alone cannot decrypt without disk access.
## Backup
Back up the OWS vault directory (`~/.ows` or your custom `OWS_VAULT_PATH`). The vault contains the encrypted key material — without it, keys cannot be recovered.
The vault is encrypted, so a backup is safe to store in a secure location. The passphrase is required to decrypt it.
If you lose both the vault and the mnemonic, the keys are permanently lost. There is no recovery path. Back up your vault and store your mnemonic securely.
## Related
* [OWS Local Signing](/ai-agents/ows-local-signing) — full setup guide for owner and agent modes
* [Policy Gates](/core-concepts/policy-gates) — constraining what agent keys can do
* [Security Overview](/security/overview) — the full trust model
* [Audit Trail](/security/audit-trail) — logging every signing operation
# Security Overview
Source: https://docs.walletsuite.io/security/overview
Non-custodial architecture and trust model behind WalletSuite.
WalletSuite is non-custodial by design. We never hold your keys, funds, or signing authority. The MCP server is an orchestration layer — it prepares transactions and enforces policy, but private keys stay with you.
## Trust Model
| Component | Trust Level | What It Can Access |
| ------------------- | ---------------------- | ---------------------------------------------------------------------------------------------------------------- |
| **WalletSuite API** | No key access | Blockchain data, transaction preparation, fee estimation |
| **MCP Server** | No key access | Tool orchestration, input validation, band filtering |
| **OWS Vault** | Trusted, operator-held | Key generation, [AES-256-GCM encrypted storage](/security/key-management#ows-vault), signing, policy enforcement |
| **Your LLM** | No key access | MCP tool calls and responses only — never sees key material |
The trust boundary is not "the server is perfect." The trust boundary is that key custody and signing stay local, regardless of what happens to the server.
## Defense in Depth
WalletSuite uses seven layers of security controls:
### 1. Band Filtering
Controls which tools exist in the MCP schema. Tools outside the active band are never registered — the LLM cannot discover or call them. Default is `read` (7 tools, no signing).
See [Band Filtering](/core-concepts/band-filtering).
### 2. Policy Gates
Controls what signed transactions can do. Declarative rules (chain allowlists, expiry) enforced by OWS before any key material is touched. First denial short-circuits.
See [Policy Gates](/core-concepts/policy-gates).
### 3. Structured Errors
Every error includes a `category` and `code`. `flow` errors include a `requiredAction`. Agents recover programmatically — no guessing, no silent failures.
See [Structured Errors](/core-concepts/structured-errors).
### 4. Input Validation
Zod schemas validate all tool inputs before they reach the API:
* Address format checked per chain family (0x-prefixed for EVM, T-prefixed for Tron)
* Amounts validated as positive numeric strings (no scientific notation)
* Mutually exclusive fields enforced (`amount` vs `amountWei`, never both)
* Token contracts validated when provided
* Required fields enforced
### 5. Secret Isolation
Sensitive values are supplied through environment variables, never through tool arguments:
* `WALLETSUITE_API_KEY`
* `OWS_PASSPHRASE`
* `OWS_AGENT_TOKEN`
* `OWS_ETHEREUM_RPC_URL` / `OWS_TRON_RPC_URL`
Secrets stay out of prompt history, tool argument logs, and MCP traffic.
### 6. HTTPS Enforcement
All external URLs are validated as HTTPS before use. HTTP is allowed only for `localhost` in development. This applies to the backend API, chain RPCs, and MoonPay widget URLs.
### 7. Audit Trail
Every `sign_transaction` and `send_transaction` operation appends a receipt to a hash-chained, append-only JSONL log. Each receipt is SHA-256 linked to the previous one, so tampering with any entry invalidates every entry after it. Receipts record the operation, chain, policy decision, and outcome — wallet name is preserved for forensics; sensitive fields are schema-redacted before write.
See [Audit Trail](/security/audit-trail).
## Non-Custodial Architecture
```
User request → MCP Server prepares unsigned tx (no key access)
→ OWS decrypts key from local vault
→ OWS signs the transaction
→ OWS broadcasts via configured RPC
→ Key wiped from memory
```
At no point does the MCP server or the WalletSuite API access private key material. The key exists in decrypted form only inside OWS during the signing operation, then it is wiped from memory.
See [Key Management](/security/key-management) for vault details.
## Attack Model
If the MCP server is compromised:
* An attacker **does not** get private keys (keys are in the OWS vault, not the server)
* An attacker **cannot** sign transactions unless OWS is enabled and the signing band is active
* An attacker **cannot** broadcast unless the broadcast band is enabled, the RPC URL is configured, and `confirmBroadcast=true` is passed
The worst case for a compromised server with `MCP_BANDS=read` is information disclosure (balances, prices, transaction history). No value can be moved.
## What WalletSuite Does Not Do
* Store private keys or mnemonics
* Custody funds
* Sign transactions on your behalf
* Move funds without an explicitly enabled signing path and user/policy approval
* Expose internal orchestration logic
## Remaining Risks
Non-custodial does not mean zero risk. Important risks that remain with the operator:
* Enabling broader bands than needed
* Running on a compromised host
* Leaking environment secrets (API keys, passphrases, agent tokens)
**Mitigation:** Keep bands narrow, scope agent tokens with policies, and protect your environment variables.
## Practical Guidance
Start with these defaults and expand deliberately:
| Setting | Default | When to Change |
| ------------- | -------- | ------------------------------------------- |
| `MCP_BANDS` | `read` | Add `prepare` when you need tx construction |
| `OWS_ENABLED` | `false` | Enable when you need local signing |
| Broadcast | Disabled | Enable only after proving the signing flow |
| Policy | None | Create before issuing agent tokens |
See [Production Checklist](/production-checklist) for the full pre-go-live validation.
## Related
* [Key Management](/security/key-management) — OWS vault, encryption, key lifecycle
* [Audit Trail](/security/audit-trail) — signing and broadcast audit log
* [Security & Trust (AI Agents)](/ai-agents/security-model) — detailed threat model
* [Responsible Disclosure](/security/responsible-disclosure) — reporting vulnerabilities
# Responsible Disclosure
Source: https://docs.walletsuite.io/security/responsible-disclosure
How to report security vulnerabilities to WalletSuite.
## Reporting a Vulnerability
**Email:** [security@walletsuite.io](mailto:security@walletsuite.io)
Include:
* Description of the vulnerability
* Steps to reproduce
* Impact assessment
* Affected component (MCP Server, REST API, SDK, website)
* Your contact information (optional, for follow-up)
## Scope
| In Scope | Out of Scope |
| -------------------------------------------------- | ------------------------------------------------------------- |
| WalletSuite MCP Server (npm package, Docker image) | Social engineering |
| WalletSuite REST API | Denial of service attacks |
| WalletSuite SDK (`@walletsuite/wallet-sdk`) | Issues in third-party dependencies (report to the maintainer) |
| walletsuite.io website | Spam or phishing |
| OWS signing integration | Physical access attacks |
## Response Timeline
| Stage | Timeframe |
| ------------------------------ | ---------------------- |
| Acknowledgment | 48 hours |
| Triage and severity assessment | 5 business days |
| Fix (critical severity) | 72 hours |
| Fix (high severity) | 2 weeks |
| Fix (medium/low severity) | Next scheduled release |
## Policy
* We do not pursue legal action against researchers who follow responsible disclosure.
* We will credit reporters in our changelog (unless you prefer to remain anonymous).
* We ask that you do not publicly disclose the vulnerability until we have released a fix or 90 days have passed, whichever comes first.
## Severity Definitions
| Severity | Definition |
| ------------ | -------------------------------------------------------------- |
| **Critical** | Key material exposure, unauthorized signing, fund loss |
| **High** | Authentication bypass, privilege escalation, data exfiltration |
| **Medium** | Information disclosure, input validation bypass |
| **Low** | Documentation issues, minor misconfigurations |