# Agent CLI
Source: https://pond.dflow.net/ai/agent-cli
Agent-first CLI for executing trades on Solana via DFlow
Your AI agent's new best friend
curl -fsS [https://cli.dflow.net](https://cli.dflow.net) | sh
Give your AI agent a Solana wallet and let it trade. Spot swaps, structured JSON output, keys that never leave your machine.
* **One binary, zero dependencies:** install and go.
* **Agent-native:** every command returns JSON; every error includes a machine-readable code and a recovery suggestion.
* **Your keys, your machine:** private keys live in an encrypted local vault, never uploaded anywhere.
### Getting started
The `dflow` CLI is a single self-contained binary that runs on macOS and Linux.
```bash theme={null}
curl -fsS https://cli.dflow.net | sh
```
Then run setup:
```bash theme={null}
dflow setup
```
`setup` prompts for:
| Setting | Default | Notes |
| -------------- | ------------------------------------- | --------------------------------------------------------------- |
| Wallet name | `default` | Creates a new encrypted wallet if the name doesn't exist. |
| Vault password | | Min 12 characters. |
| Solana RPC | `https://api.mainnet-beta.solana.com` | |
| DFlow API key | Required | Provided when you receive your [API key](/get-started/api-key). |
The Solana RPC is the only optional setting. All DFlow fields are required; setup will not proceed until each is entered. Config is saved to `~/.config/dflow/config.json`. Re-run `dflow setup` anytime; existing wallets are left untouched.
At the end of the install script, you'll be prompted to install the [DFlow Skills library](/ai/agent-skills#dflow-skills), a set of Claude Code Skills that teach agents how to drive the CLI. This is optional, but highly recommended and can be installed later with `dflow skills install`.
On macOS, the CLI stores your vault password in the system keychain so your agent can sign transactions without a password prompt blocking execution. The first time it does this, you'll see a dialog asking permission to access **dflow-ows**, choose **Always Allow**, not just **Allow**. The dialog asks for your **Mac login keychain** password, which is separate from your DFlow vault password.
After installing an updated `dflow` binary, macOS treats it as a new app and will prompt for keychain access again. Run a small test transaction to prime the CLI before turning it over to your agent.
## Command reference
Global flags (apply to subcommands that need config):
| Flag | Description |
| ----------------- | ---------------------------------------------------------------------------------------- |
| `--rpc-url ` | Override Solana RPC (else `config.json` or default). |
| `--wallet ` | Vault wallet name (else `config.json` or `default`). Files live under `~/.ows/wallets/`. |
### Setup, identity, balances
| Command | Description |
| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
| `dflow setup` | Interactive wallet creation, RPC, and DFlow API configuration. |
| `dflow whoami` | Print the active wallet's public key (plain text, not JSON). |
| `dflow positions` | Show all token balances. |
| `dflow agent --model ` | Register the AI model running this session. Cached for 48 hours; sets the `X-Dflow-Model` header on subsequent requests. |
### Skills
| Command | Description |
| ---------------------- | ------------------------------------------------------------------------------------ |
| `dflow skills install` | Install the [DFlow Skills library](/ai/agent-skills#dflow-skills). Requires Node.js. |
| `dflow skills update` | Update all installed skills to their latest version. Requires Node.js. |
`dflow skills update` updates **every** skill the `skills` CLI manages, not only DFlow's. The underlying CLI updates by skill name, not by repo.
### Wallet
| Command | Description |
| ------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
| `dflow wallet list` | List all named wallets in the vault. |
| `dflow wallet import --name --keypair ` | Import a Solana keypair JSON (e.g. from `solana-keygen`) into the vault. |
| `dflow wallet import --name --mnemonic "word1 ..."` | Import a BIP-39 mnemonic (24 words) into the vault. Use to restore a wallet exported from another machine. |
| `dflow wallet export --name --out ` | Decrypt the recovery phrase/keypair and write it to `` (0600). Requires a typed vault password; the secret is never printed to the terminal. |
| `dflow wallet delete --name [--yes]` | Delete a wallet file and its keychain entry. `--yes` skips confirmation. |
| `dflow wallet rename --from --to ` | Rename a wallet. Updates the keychain entry if present. |
| `dflow wallet keychain-sync --name ` | Re-save the vault password to the OS keychain. Use after keychain issues. |
### Trading
| Command | Description |
| ---------------------------------- | ------------------------------------------------------------------ |
| `dflow quote ` | Spot swap quote. `--slippage ` (defaults to backend auto). |
| `dflow trade ` | Spot swap. `--slippage `, `--confirm` (poll until confirmed). |
### Transfers and funding
| Command | Description |
| ----------------------------------------- | --------------------------------------------------------------------------------------------------------- |
| `dflow send ` | Native SOL or SPL transfer; `recipient` is base58 pubkey. Creates recipient ATA if needed (you pay rent). |
| `dflow fund ` | Buy crypto with fiat via MoonPay CLI. |
### Guardrails
| Command | Description |
| ------------------------------------ | ---------------------------------------------------------------------------- |
| `dflow guardrails show` | Display current guardrails. No password required, so agents can read limits. |
| `dflow guardrails set [value]` | Set a guardrail. Requires vault password typed in the terminal. |
| `dflow guardrails remove ` | Remove a guardrail. Requires vault password typed in the terminal. |
| `dflow guardrails reset` | Remove all guardrails. Requires vault password typed in the terminal. |
See [Guardrails](#guardrails) for the full list of available keys and examples.
**Atomic units**: Amounts are integers in the smallest token unit (e.g. `500000` = 0.50 USDC with 6 decimals; `10000000` lamports = 0.01 SOL).
## Key management
Each wallet is an encrypted JSON file under `~/.ows/wallets/`, following the [Open Wallet Standard (OWS)](https://openwallet.sh/docs/) format (powered by `ows-lib` v1.4.2). Private keys never exist on disk in plaintext. All cryptographic operations (decrypt, sign, zeroize) are handled by the `ows-lib` crate. The CLI binary itself never touches raw key material.
When a command needs to sign, the CLI resolves the vault password by trying these sources in order:
1. **OS keychain** (if saved via `dflow setup` or `wallet keychain-sync`)
2. **`DFLOW_PASSPHRASE` env var** (read once, then cleared from the environment)
3. **Password prompt**
The resolved password is cached for the duration of the process so back-to-back operations (e.g. guardrail check → sign → broadcast) only resolve it once.
Guardrails changes (`set` / `remove` / `reset`) always require the password **typed in the terminal**. The keychain and env var are intentionally bypassed so a human must be present to change policy.
```mermaid theme={null}
flowchart TB
subgraph persist["On disk"]
W["Encrypted wallet (~/.ows/wallets/)"]
end
subgraph pass["Vault password"]
KC["OS keychain"]
EV["DFLOW_PASSPHRASE env var"]
PR["Prompt"]
end
subgraph run["Inside ows-lib"]
OWS["Decrypt + sign (key material never leaves ows-lib)"]
AUD["audit.jsonl"]
end
W --> OWS
KC --> OWS
EV --> OWS
PR --> OWS
OWS --> AUD
```
The vault uses a **key derivation function (KDF)** to turn the password into a decryption key. This is intentionally slow to make brute-force guessing expensive.
### What is stored where
| Piece | Location | Notes |
| ---------------------- | ----------------------------------------------------- | --------------------------------------------------- |
| Encrypted key material | `~/.ows/wallets/.json` | One file per wallet. Filename is a fixed UUID. |
| Vault password | macOS Keychain / Linux secret service, env, or prompt | Service `dflow-ows`, account = wallet name. |
| Config (RPC, API key) | `~/.config/dflow/config.json` | No signing keys. |
| Guardrails | `~/.ows/guardrails.json` | HMAC-signed; editing requires typed vault password. |
| Trade history | `~/.ows/trade_history.json` | Used for rate limit and daily volume enforcement. |
| Audit log | `~/.ows/logs/audit.jsonl` | Append-only signing and lifecycle events. |
Permissions: `~/.ows` directories **700**, wallet files **600**. If permissions are too open, commands fail with `VAULT_INSECURE`.
If someone has both your wallet files and your password, they can move funds. Use a strong password, lock down file permissions, and treat the machine like any workstation that signs crypto transactions.
### Creating and importing keys
* **Create**: `dflow setup` with a **new** wallet name generates a **24-word BIP-39 mnemonic** and encrypts it into the vault.
* **Import keypair**: `dflow wallet import --name --keypair ` encrypts an existing **Solana JSON keypair** (from `solana-keygen`) into the vault.
* **Import mnemonic**: `dflow wallet import --name --mnemonic "word1 word2 ..."` restores a BIP-39 wallet (12 or 24 words). Use this to move a wallet created on another machine.
* **Export**: `dflow wallet export --name --out ` decrypts the recovery phrase (mnemonic) or keypair and writes it to `` with `0600` permissions. The secret is **never printed to the terminal** (so it can't leak into scrollback or recordings), `--out` is required, and a symlink at the destination is refused. Requires a typed vault password. **Highly sensitive:** use for backup or migration only. The confirmation includes `wordCount` and import instructions when the wallet is a BIP-39 mnemonic. When importing into Phantom or another wallet, make sure to select the matching word count (24).
### Multiple wallets
The vault supports an unlimited number of named wallets. Each is a separate encrypted file with its own keypair and password. Pass `--wallet ` to target a specific wallet:
```bash theme={null}
dflow whoami --wallet hot
dflow trade 500000 USDC SOL --wallet savings
```
### Headless environments
If there's no terminal for a password prompt (e.g. a background process or Docker container), export the vault password as an environment variable:
```bash theme={null}
export DFLOW_PASSPHRASE="your-vault-password"
dflow trade 500000 USDC SOL
```
The CLI unsets `DFLOW_PASSPHRASE` from its own process environment at startup, so any subprocesses it spawns will not see it. The value still persists in the parent shell and, on Linux, in the read-only `/proc//environ` snapshot for the life of the process. The OS keychain is the most secure option.
## Output format
**Success (most commands):**
```json theme={null}
{ "ok": true, "data": { ... } }
```
**Classified error** (most failures):
```json theme={null}
{
"ok": false,
"error": "Human-readable message",
"error_code": "MACHINE_CODE",
"category": "routing",
"recoverable": true,
"suggestion": "Actionable next step.",
"details": { ... }
}
```
`details` is present when there is additional structured context. Its shape varies by error category: API errors include `httpStatus` and the parsed response `body`; onchain errors include `rawLogs` or `programErrorDecimal`; others may include `raw` or `inputMint`.
**Simple error** (edge cases such as cancelled prompts or misconfiguration):
```json theme={null}
{ "ok": false, "error": "Human-readable message" }
```
**Exception:** `dflow whoami` prints only the pubkey string on success.
## Token resolution
Symbols resolve to mints (built-in list includes `SOL`, `USDC`, `USDT`, `CASH`, `BONK`, `JUP`, `WIF`, `PYTH`, `JTO`, `RAY`, `ORCA`, `MNDE`, `MSOL`, `JITOSOL`, `BSOL`, `RENDER`). Any other asset: pass the **mint address** (base58).
```bash theme={null}
dflow quote 500000 EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v So11111111111111111111111111111111111111112
```
## Guardrails
Guardrails are **optional** client-side safety limits. An agent can read them freely (`guardrails show` requires no password), but only a human can change them. `set`, `remove`, and `reset` require the vault password typed in the terminal.
| Key | What it does |
| ---------------------- | ------------------------------------------------------------------------------------- |
| `max_trade_size_usd` | Cap the USD value of a single trade. |
| `max_daily_volume_usd` | Cap total USD volume per rolling 24-hour window. |
| `max_wallet_value_usd` | Cap the total USD value held in the wallet. |
| `allowed_tokens` | Whitelist of mints the agent can **buy**. Sells are unrestricted. |
| `rate_limit` | Max trades within a time window (e.g. `max_trades` per `window_seconds`). |
| `sweep_address` | Public key to sweep excess funds to when wallet value exceeds `max_wallet_value_usd`. |
USD estimates use the same route as the trade itself, including SOL-to-USDC conversion when needed.
```bash theme={null}
dflow guardrails set max_trade_size_usd 5000000 # $5 per trade
dflow guardrails set max_daily_volume_usd 50000000 # $50/day
dflow guardrails set allowed_tokens SOL,USDC,BONK
dflow guardrails show # Agent reads this
```
## Funding
Use `fund` to buy crypto with fiat through the [MoonPay CLI](https://www.moonpay.com/agents). It supports **USDC** and **SOL**.
The MoonPay CLI is not bundled with `dflow`. Install it separately only if you need the `fund` command:
```bash theme={null}
npm install -g @moonpay/cli
```
The `` is in your **local fiat currency**, auto-detected by MoonPay based on your locale. For example, `dflow fund 50 USDC` in the US buys USD 50 of USDC; in Thailand it buys THB 50 of USDC.
```bash theme={null}
dflow fund 50 USDC # Buy USDC with local fiat currency
dflow fund 100 SOL # Buy SOL with local fiat currency
```
When `dflow fund` runs, it fetches the wallet address, calls MoonPay to generate a checkout URL, opens it in the browser for KYC and payment, then polls the wallet balance until a deposit lands (10-second intervals, 10-minute timeout).
This command requires a browser and is intended for wallet funding by a human operator, not for autonomous agent use.
## Spot trading
Swap any supported token pair. Amounts are always in **atomic units** of the input token (e.g. `500000` = 0.50 USDC).
### Get a quote
```bash theme={null}
dflow quote 500000 USDC SOL # How much SOL for 0.50 USDC?
dflow quote 500000 USDC SOL --slippage 100 # Same, with 1% slippage tolerance
```
The CLI calls the DFlow Trade API `/order` endpoint and returns the route, expected output amount, and price impact, without executing anything.
### Execute a trade
```bash theme={null}
dflow trade 500000 USDC SOL # Swap 0.50 USDC -> SOL
dflow trade 500000 USDC SOL --slippage 100 # With 1% slippage tolerance
```
The CLI uses standard [`/order`](/spot/introduction) execution: it fetches a route, signs the transaction locally, and submits it to the network. The response status is `"submitted"`. Add `--confirm` to poll the RPC until the transaction reaches `confirmed` commitment before returning:
```bash theme={null}
dflow trade 500000 USDC SOL --confirm
```
### Slippage
Slippage is specified in **basis points** (bps). If `--slippage` is omitted, the backend selects a tolerance automatically. If the price moves beyond the tolerance between quote and execution, the transaction reverts.
```bash theme={null}
dflow trade 500000 USDC SOL --slippage 200 # 2% tolerance
```
## Response verification
The CLI can cryptographically verify that Trade API responses came from DFlow and were not altered in transit, using [RFC 9421 HTTP Message Signatures](https://pond.dflow.net/resources/request-signing) (ed25519). This is **opt-in** and **off by default**.
Enable it by setting an environment variable (truthy values: `1`, `true`, `yes`):
```bash theme={null}
export DFLOW_VERIFY_SIGNATURES=1
dflow trade 500000 USDC SOL
```
When enabled, every Trade API request asks DFlow to sign its response, and the CLI checks the response **before** using it:
* the ed25519 `signature` validates against DFlow's pinned public key (baked into the binary, so a swapped key can't be substituted);
* the `content-digest` matches the response body (integrity);
* the echoed `x-request-id` matches the per-request id the CLI sent (anti-replay);
* the signed component set actually covers `content-digest` and `x-request-id` (no downgrade).
If any check fails, the CLI returns a `RESPONSE_SIGNATURE_INVALID` error and discards the response without acting on it.
This defends against a tampered network path (TLS MitM with a mis-issued certificate, BGP hijack, or a compromised intermediary between you and DFlow's edge). It does not attempt to defend against DFlow's own backend, which is the entity producing the signature.
## Observability
Every outbound API request includes three headers that identify the caller:
| Header | Values | Description |
| ---------------- | ---------------------------------------------------------------------- | -------------------------------------------------------------------------------- |
| `X-Dflow-Caller` | `human`, `agent`, `unknown` | Whether the CLI was invoked by a person or an agent. |
| `X-Dflow-Agent` | `cursor`, `claude-code`, `openclaw`, `github-actions`, `ci`, or custom | Detected or self-reported agent tool. Present only when caller is `agent`. |
| `X-Dflow-Model` | e.g. `gpt-4o`, `claude-sonnet-4.6` | Model registered via `dflow agent --model`. Present only when caller is `agent`. |
**Self-reporting:** Set `DFLOW_AGENT=` to explicitly identify a custom agent. This takes precedence over automatic detection:
```bash theme={null}
DFLOW_AGENT=my-bot dflow trade 500000 USDC SOL
```
**Agent model prompt:** When an agent calls `quote` or `trade` and no model has been registered (or the registration has expired), the success response includes an additional `_hint` key:
```json theme={null}
{
"ok": true,
"data": { ... },
"_hint": {
"action": "Run the replyWith command now to register your model before proceeding.",
"options": ["claude-opus-4.6", "claude-sonnet-4.6", "gpt-4o", "..."],
"replyWith": "dflow agent --model