# 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