back
loading skill details...
Self-custody Ethereum agent wallet. Runs entirely on the user's machine as one Docker (or Podman) image (MCP over stdio, plus an HTTP gateway for signing integrations — loopback-only by default, opt-in network exposure); private keys never leave it. Read wallet context, balances and DeFi positions (
---
name: rustok-wallet
description: Self-custody Ethereum agent wallet. Runs entirely on the user's machine as one Docker (or Podman) image (MCP over stdio, plus an HTTP gateway for signing integrations — loopback-only by default, opt-in network exposure); private keys never leave it. Read wallet context, balances and DeFi positions (Aave v3, ERC-4626); preview, execute, sign plain messages and EIP-712 typed data. The user assumes all risk for funds on the agent wallet — there are no hard-coded spending limits.
version: 0.5.1
metadata:
openclaw:
emoji: "🦀"
requires:
anyBins:
- docker
- podman
homepage: https://github.com/rustok-org/mcp
---
# Rustok Ethereum Wallet
> **License note:** this OpenClaw skill package (`skills/rustok-wallet/`) is MIT-0
> per ClawHub requirements. The Rustok wallet core itself is proprietary; only the
> compiled binary image is distributed.
You are connected to a **self-custody** Ethereum agent wallet that runs entirely
on the user's machine as a single Docker (or Podman) image
(`ghcr.io/rustok-org/rustok-wallet`). The container runs the wallet core + gateway
and speaks MCP over **stdio**, plus an HTTP gateway for signing integrations
(`127.0.0.1:3000` by default — **loopback-only**, not reachable outside the
container; the operator can opt into network exposure via `RUSTOK_MCP_API_KEY`,
see below, at their own risk); the private keys live only in the user's local
Docker volume and never leave it.
> ⚠️ **Self-custody, real funds, your risk.** This wallet has **no hard-coded
> spending limits or budgets** — the user consciously accepts that funds sent to
> the agent wallet are at risk. txguard flags risky transactions and hard-blocks
> sends to known-scam addresses (a bundled denylist); everything else goes
> through. All supported chains the user enables are live (incl.
> Ethereum mainnet). Always preview before executing and show the user the details.
## Prerequisites
- **Docker** installed and running. No `docker` on your machine (e.g. Fedora)?
**Podman is a drop-in replacement** — use `podman` in place of `docker` in
every command below; nothing else changes.
- An Ethereum RPC URL (an Alchemy key URL is best; a public RPC works for testing).
## One-time onboarding (the user does this in a terminal, once)
Create the wallet and **back up the 12-word recovery phrase** — it is shown only
once, in the user's own terminal (never to the agent). The keyring password goes
in as a **secret** — never inline, never into shell history:
**Podman** — via the secret store (`read -s` keeps it out of history; the
`type=env` secret injects it byte-exact and `podman inspect` never shows it):
```bash
read -r -s -p "Keyring password: " pw && printf '%s' "$pw" | podman secret create rustok-keyring - && unset pw
podman run -it --rm \
-v rustok-wallet:/data \
--secret rustok-keyring,type=env,target=RUSTOK_KEYRING_PASSWORD \
ghcr.io/rustok-org/rustok-wallet:latest create-wallet
```
**Docker** — via a `0600` file whose *path* is passed in (a trailing newline is
stripped):
```bash
umask 077
read -r -s -p "Keyring password: " pw && printf '%s' "$pw" > ~/.rustok-keyring-pass && unset pw
docker run -it --rm \
-v rustok-wallet:/data \
-v ~/.rustok-keyring-pass:/run/keyring-pass:ro \
-e RUSTOK_KEYRING_PASSWORD_FILE=/run/keyring-pass \
ghcr.io/rustok-org/rustok-wallet:latest create-wallet
```
This prints the wallet **address** and the **12 words**. Write the words down
offline and fund the address. Recovery = these 12 words (importable into any
standard wallet, e.g. MetaMask) or the `rustok-wallet` Docker volume + password.
> **Headless/CI:** replace `-it` with `-i`. The password is already supplied
> via the secret / `_FILE`, so no TTY is required.
## How the agent runs the wallet
The MCP client launches the image over stdio (keys stay local). **Never put the
keyring password in the MCP config** — reuse the delivery from onboarding:
**Podman** (the secret you already created):
```bash
podman run -i --rm \
-v rustok-wallet:/data \
--secret rustok-keyring,type=env,target=RUSTOK_KEYRING_PASSWORD \
-e RUSTOK_ALLOWED_CHAINS="1,8453" \
-e RUSTOK_RPC_URLS_1="https://your-rpc" \
ghcr.io/rustok-org/rustok-wallet:latest
```
**Docker** (the `0600` file you already created):
```bash
docker run -i --rm \
-v rustok-wallet:/data \
-v ~/.rustok-keyring-pass:/run/keyring-pass:ro \
-e RUSTOK_KEYRING_PASSWORD_FILE=/run/keyring-pass \
-e RUSTOK_ALLOWED_CHAINS="1,8453" \
-e RUSTOK_RPC_URLS_1="https://your-rpc" \
ghcr.io/rustok-org/rustok-wallet:latest
```
> The container automatically mints an ephemeral `RUSTOK_MCP_API_KEY` for the
> loopback gateway↔mcp hop, so no API key configuration is needed for stdio use.
> Set `RUSTOK_MCP_API_KEY` yourself **only** when exposing the gateway over a
> network (not the default stdio setup).
For **Claude Desktop / Cursor** (stdio MCP), add to the MCP config. The keyring
password rides the secret / file from onboarding, **never this config file** —
only the non-secret RPC URL lives here. On **docker**, replace the `--secret`
arg with the bind-mount pair (your real absolute path — `~` is not expanded
inside JSON):
```json
{
"mcpServers": {
"rustok-wallet": {
"command": "podman",
"args": ["run", "-i", "--rm",
"-v", "rustok-wallet:/data",
"--secret", "rustok-keyring,type=env,target=RUSTOK_KEYRING_PASSWORD",
"-e", "RUSTOK_ALLOWED_CHAINS=1,8453",
"-e", "RUSTOK_RPC_URLS_1",
"ghcr.io/rustok-org/rustok-wallet:latest"],
"env": {
"RUSTOK_RPC_URLS_1": "https://your-rpc"
}
}
}
}
```
Docker variant of the args (instead of the `--secret` line):
```json
"-v", "/home/you/.rustok-keyring-pass:/run/keyring-pass:ro",
"-e", "RUSTOK_KEYRING_PASSWORD_FILE=/run/keyring-pass",
```
## Why Rustok exists
Rustok gives an AI agent a wallet of its own — self-custody, no middleman — so agents can begin
to take part in the economy directly: weighing what's worth paying for, covering the compute,
data, and tools they rely on, and in time commissioning and paying the people who help them.
## Tools
The stdio wallet image is process-trusted and exposes **all** tools by default.
To run a restricted agent, set `RUSTOK_MCP_CAPABILITIES` to a subset
(`read_wallet` / `preview_tx` / `execute_tx`) — e.g. `read_wallet` for read-only.
The ceiling is enforced by the gateway, on the path every request takes: it
covers the MCP tools below **and** the HTTP routes behind them, so a session
cannot reach past its capabilities by calling the gateway directly. Until this
release it was checked in the MCP layer only, which left every route reachable
beside it; a session narrowed to `read_wallet` could sign. A client may narrow
its own session further in `initialize`, and can never widen it.
| Tool | Capability | What it does |
|------|-----------|--------------|
| `get_wallet_context` | read_wallet | Active wallet address, per-chain balances, allowed chains |
| `get_balances` | read_wallet | Token balances for the active wallet, or `{address, chain_id}` |
| `get_positions` | read_wallet | DeFi positions — Aave v3 (collateral/debt/health factor/LTV) + ERC-4626 vaults; optional `{address}` |
| `preview_send` | preview_tx | Preview an ETH send `{to, amount_eth, chain_id}` → `preview_id`, gas, risk level |
| `execute_send` | execute_tx | Broadcast a previewed send `{preview_id}` → `tx_hash` |
| `sign_message` | execute_tx | Sign a short human-readable message (EIP-191) |
**Units.** `preview_send` takes **`amount_eth`** — a plain decimal-ETH string
(`"0.05"`, max 18 decimal places; no exponents). Responses spell units out:
`amount_wei` + `amount_eth` in previews, and `balance` (wei) + `balance_eth`
in balances. In ≤0.3.2 the old `amount` field was silently interpreted as
**wei**; it is now rejected with a rename hint — never re-scaled.
**Signing guard.** `sign_message` rejects hex blobs (≥16 hex chars, with or
without `0x`), empty, oversized (>4 KiB) and control-character payloads
server-side — a hex-blob signature could authorize an approval/permit drain.
**⚠️ Signing surface outside the tool list: `sign_typed_data`.** Not an MCP tool —
an HTTP endpoint on the same gateway, `POST /api/v1/wallet/sign_typed_data`
(EIP-712 over a pre-computed `domain_separator`/`struct_hash`), for glue layers
such as UniswapX signing. It now requires `execute_tx`, like every other signing
route; it used to require nothing, which is what made
`RUSTOK_MCP_CAPABILITIES` a decoration rather than a limit. It still has none of
`sign_message`'s content guards: an EIP-712 signature can authorize a token
approval, a `Permit`, or an off-chain order that moves funds — treat any caller
of this endpoint with the same scrutiny as `execute_send`.
## Behavioral guidelines
1. **Always `preview_send` before `execute_send`** — never execute without a fresh preview.
2. **Show the preview** (amount, destination, estimated cost, risk level) before executing.
3. **Use `get_wallet_context` first** so you don't hallucinate balances or chains.
4. If a tool needs a capability the session lacks, it returns an authorization
error — explain that to the user rather than retrying.
5. If the wallet is unreachable, tell the user the wallet container/onboarding may
not be set up (see onboarding above).
don't have the plugin yet? install it then click "run inline in claude" again.