> ## Documentation Index
> Fetch the complete documentation index at: https://docs.eco.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Wallets over stdio

> Let a Sauce MCP server that your agent starts locally sign and send from its own wallets.

A server your agent starts over stdio serves the wallets in a wallet directory, the same one `sauce wallet` uses. The agent can list them, create or import one, and send what the other tools build. Every send is simulated and checked against the wallet's spending policy first. No tool takes or returns a key or a password.

These are hot wallets: the key is on your machine, encrypted under a password the server reads from a source that you, or the agent through `sauce_wallet_enable`, name. Keep small balances in them. An agent that runs commands as you can read the same wallet files and password sources outside the server, and sign with them without the policy: [Agents, wallets and keys](https://docs.eco.com/sauce/security-considerations.md#agents-wallets-and-keys). For real funds, sign with a wallet that keeps its own key ([Sign with a wallet's MCP server](https://docs.eco.com/sauce-sdk/mcp/external-wallets.md)).

## Prerequisites

* The server added to your agent over stdio ([The Sauce MCP server](https://docs.eco.com/sauce-sdk/mcp/overview.md)).
* A wallet directory, created by `sauce wallet new` or by the server itself.
* To send a cook, the release's engine deployed on the chain. `sauce pot --chain <chain> --owner <address>` reports whether it is.

## The wallet directory

| Start the server with | Wallet tools | Directory |
| - | - | - |
| `sauce mcp` | all six, and `sauce_sign` | `~/.sauce/wallets` |
| `sauce mcp --wallets <dir>` | all six, and `sauce_sign` | `<dir>` |
| `sauce mcp --no-wallets` | none; nothing signs | none |
| `sauce mcp --no-stores` | none | none |

On start, the server writes one line to stderr that names the directory and whether it can sign:

```text theme={null}
sauce mcp: serving the wallets in ./wallets; sauce_wallet_enable enables them to sign
```

`sauce_wallet_list` and `sauce_wallet_address` read the directory with no password, because each wallet file names its address in the clear. `sauce_wallet_create`, `sauce_wallet_import`, `sauce_wallet_send` and `sauce_sign` need the wallets **enabled** by a password source. Before they are, those tools are refused with an error naming `sauce_wallet_enable` and its four sources.

## Enable the wallets when the server starts

Give the server a password source as an argument. It reads the password once, before it serves anything:

| Flag | The password |
| - | - |
| `--password-env <NAME>` | The environment variable `NAME`; only that one is read |
| `--password-file <path>` | The first line of a file only you can read (`chmod 600`) |
| `--password-command <command>` | What the command prints, such as a secret manager's CLI |

There is no prompt, because over stdio the protocol owns stdin. In an agent's configuration, add the flag to the entry's `args`:

```json theme={null}
{
  "mcpServers": {
    "sauce": { "command": "sauce", "args": ["mcp", "--password-env", "SAUCE_WALLET_PASSWORD"] }
  }
}
```

With `--password-env`, your client must pass that variable into the server's environment, through the `env` setting of the server entry. Put the password in the client's environment or `.env`, and keep the `.env` out of version control.

A server started this way prints `signing with the wallets in <dir>`. `sauce_wallet_list` then reports the source:

```json theme={null}
{ "dir": "./wallets", "enabled": true, "source": "--password-file ./wallets.password", "wallets": [ … ] }
```

`sauce_wallet_enable` does not replace a source given at start:

```text theme={null}
the wallets are enabled already, by the password source this server was started with (--password-file ./wallets.password): it is its operator's to change
```

## Let the agent enable the wallets

Without a source at start, the agent calls `sauce_wallet_enable` once per server. The call names where the password comes from, never the password itself. Give exactly one of:

| Input | The password |
| - | - |
| `{ "env": "SAUCE_WALLET_PASSWORD" }` | A variable of the server's environment. The input takes the variable's name; a string that is not a variable name (letters, digits and `_`) is refused |
| `{ "file": "~/.sauce/agent.password" }` | The first line of a file only its owner can read |
| `{ "dotenv": ".env", "variable": "SAUCE_WALLET_PASSWORD" }` | One entry of a `.env` file only its owner can read. `variable` defaults to `SAUCE_WALLET_PASSWORD` |
| `{ "generate": true }` | A new random password, written to a new file beside the wallet directory: `<dir>.password`, so `~/.sauce/wallets.password` for the default directory |

Notes on the sources:

* A file that others can read or write is refused.
* `generate` also takes a path instead of `true`. The path must be in a directory that exists, outside the wallet directory. It never writes over an existing file.
* The agent cannot name a shell command as a source. Only you can, with `--password-command` at start.
* The password stays in the server's memory for the life of the process and is never written to disk. With `generate`, the generated file holds it.
* Calling `sauce_wallet_enable` again replaces the source. `dir` names another wallet directory.

A generated password sits in its file in the clear. Whoever can read that file and a wallet file holds that wallet's key, so keep the file out of repositories, backups and synced folders. After a restart, `{ "file": "<that path>" }` enables the same wallets again.

The result of `{ "generate": true }`:

```json theme={null}
{
  "dir": "./wallets",
  "enabled": true,
  "source": "file <scratch>/wallets.password",
  "wallets": [],
  "passwordFile": "<scratch>/wallets.password"
}
```

## Create, import and list

* `sauce_wallet_create { name, kind }` makes a wallet with a fresh key. `kind` is `evm` (default) or `solana`. It returns the wallet's name, kind, address, file and policy, never the key. A name already in use is refused: `wallet agent exists: a wallet is never overwritten (pick another name)`.
* `sauce_wallet_import { name, path, kind }` makes a wallet from a key file on the machine, named by path. The file is a keystore, a Solana keypair file or an EVM private key in hex. It must be a regular file only its owner can read, and it is left unchanged. A keystore must open with the wallets' password.
* `sauce_wallet_list` returns the directory, `enabled`, `source` and every wallet. `sauce_wallet_address { name }` returns one wallet.

```json theme={null}
{
  "name": "agent",
  "kind": "evm",
  "address": "0x4CEC2d39fCeea0AEcdc6EA8676C4A66379d9b95c",
  "path": "wallets/agent.json",
  "policy": null
}
```

There is no export tool. `sauce wallet export` shows a key only at a terminal.

## Send from a wallet

`sauce_wallet_send { wallet, transaction, chain?, rpcUrl? }` signs and sends. `transaction` is one of:

* the whole result of `sauce_tx`, `sauce_pot`, or `sauce_actions` with an owner, or of `sauce_signed_cook` or `sauce_sign`;
* one transaction;
* a list of transactions.

Build the transactions for the wallet's address by passing it as `owner`.

**1. Simulate**

On an EVM chain, every transaction is simulated together in one `eth_simulateV1` block from the wallet. On Solana, each is simulated as signed. If any would revert, nothing is sent.

**2. Hold to the policy**

Before any transaction is sent, each is measured against the wallet's policy file, per transaction and per 24 hours: what it moves out of the wallet and its Pot, and the allowances it leaves other keys. On an EVM chain a transaction is made whole first, with the gas and fees it will be signed with, and signed exactly as measured. On Solana the plan's setup, its rent and fees, is checked before any of it is sent, and a transaction is held to the policy of every wallet unlocked to sign it. What the policy does not allow is refused, with the limit named. A wallet with no policy file has no limits.

**3. Send**

Each transaction is sent in order and simulated again first. Sending stops at the first that does not succeed.

Each sent transaction comes back with its `hash` (a signature on Solana), `status`, `explorer` and `trace`. An EVM cook adds `moves`, `leftInPot` and `summary`.

[Wallets, passwords and spending policy](https://docs.eco.com/sauce-sdk/cli/wallets-and-policy.md) documents the policy file, the ledger and the password rules. The server shares them with `sauce wallet`.

Wallet sends are limited to the supported chains. On a preview chain the call is refused before anything is simulated, signed or sent:

```json theme={null}
{
  "refused": {
    "code": "preview-chain",
    "action": "sending from a wallet",
    "message": "sending from a wallet is refused: it involves arbitrum (Arbitrum One, chain id 42161), a preview chain. …"
  }
}
```

## Sign a cook another account sends

`sauce_sign { wallet, source, chain, args, … }` signs a cook with a wallet, as its Pot's owner, for another account to send and pay the gas for: it is [`sauce sign --wallet`](https://docs.eco.com/sauce-sdk/cli/sign), and returns the signature and `cook`, with `deployPot` while the Pot has no code. `sauce_wallet_send` takes that result, or `cook` alone, and sends it from whichever wallet you name, with exactly its value:

```text theme={null}
sauce_sign        { wallet: "owner", source, chain: "base", args: [...], within: 600 }
  → { pot, typedData, digest, signature, cook: { chainId, to, data, value }, deployPot, nonce, deadline, … }
sauce_wallet_send { wallet: "relayer", transaction: <that result> }
  → { ok: true, sent: [ { what: "Kitchen.deployPot of Pot …", status: "success" }, { what: "signed cook on Pot …", status: "success", moves, … } ] }
sauce_sign        { wallet: "owner", chain: "base", cancel: ["<nonce>"] }
  → { pot, from, nonces, sent: [ { what: "invalidateNonces(…)", status: "success", … } ] }
```

On a local fork of Base with the current contracts, this sequence deployed the owner's Pot and ran its cook from the relayer's wallet, and a cancelled signature's cook was then refused: `not sent — transaction 1 (1 of 1) would revert: NonceUsed()`.

A wallet whose policy sets a `native` or `tokens` limit refuses to sign one, naming the limit: a signed cook runs when someone else sends it, on the chain's state then, so nothing simulated at signing bounds what it moves. `chains` and `to` still apply, with the Pot as the address. For a wallet kept elsewhere, [`sauce_signed_cook`](https://docs.eco.com/sauce-sdk/mcp/tools#sauce_signed_cook) returns the typed data to sign and signs nothing.

## Over HTTP, nothing signs

The HTTP server has no wallet tools, and it refuses the wallet and password flags at start:

```text theme={null}
sauce mcp: --wallets is for stdio: the HTTP server never signs, and serves no wallet tools
sauce mcp: --password-env is for stdio: the HTTP server never signs, and serves no wallet tools
```

## Next steps

* [Local server storage](https://docs.eco.com/sauce-sdk/mcp/local-server.md): the wallet directory and the Pot store.
* [Sign with a wallet's MCP server](https://docs.eco.com/sauce-sdk/mcp/external-wallets.md): sign real funds with a wallet that keeps its key.
* [MCP tools](https://docs.eco.com/sauce-sdk/mcp/tools.md#wallet-tools-stdio-only): every wallet tool's inputs and outputs.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.