DEV Community

doncesarts
doncesarts

Posted on

I Stopped Relying on the CowSwap UI and Built a CowSwap CLI Instead

If your trading flow depends on uptime, precision, and repeatability, a web UI is useful but not enough.

I use CowSwap regularly, but the UI has had the following issues:

So I built a TypeScript CLI on top of the CowSwap SDK to place orders with guardrails, even when the frontend is unavailable. The repository is here.

Try it yourself

git clone https://github.com/doncesarts/cowswap-cli
cd cowswap-cli
npm install
Enter fullscreen mode Exit fullscreen mode

What I wanted

  • New type of orders
    • Market sell / buy with explicit slippage.
    • Sell/Buy ladders orders with relative price offsets around the current market price.
    • Limit fixed-price limit orders.
  • Refresh local token registries from CowSwap and CoinGecko
    • Specify address or symbol for tokens when the symbol is unavailable
    • It downloads from trusted sources like CowSwap and CoinGecko the information of tokens, i.e., address - symbol so it can be used across the CLI commands.
    • You can also add your custom registry file as a JSON if you only want to trust on your file.
  • Price quote protection
    • Whenever executing an order it quotes the price on cowSwap and compares it with coingecko prices, if there is a significant discrepancy, it warns the user before executing the trade, and then its up to you to confirm it manually. So having at least another source of prices gives me more confidence in the accuracy of the trade.
  • Fallback path for order execution
    • If the CowSwap frontend is unavailable, the CLI provides a reliable alternative to place orders directly through the SDK.
  • Automation-friendly
    • The CLI is designed to be scriptable and easily integrated, in my case i created a Makefile to ease common trading tasks, such as placing orders in different chains based on different "profiles" or wallets.

The architecture

A visual representation of how the CLI processes commands before interacting with the CoW Protocol.

CLI arguments
     │
     ▼
Command parser
     │
     ▼
Validation / token resolution
     │
     ▼
Price + safety checks
     │
     ▼
Order construction
     │
     ▼
CoW SDK
     │
     ├── quote / order API
     │
     └── wallet signing
             │
             ▼
        CoW Protocol  
Enter fullscreen mode Exit fullscreen mode

Example of the cli commands.

# Market buy  with explicit slippage (Buy 0.5 WETH for USDC)
npm run buy-market -- --buy-token WETH --sell-token USDC --amount 0.5 --slippage 0.5

# Sell ladder with relative price offsets above market
npm run sell-ladder -- --sell-token WETH --buy-token USDC --amount 2 --percentages "1,3,5,8"

# Fixed-price limit sell (buy-price set the price on buy token value, sell-price set the price on sell token value)
# Example: Sell 1 WETH for USDC at a fixed buy price of 4200 USDC
npm run sell-limit -- --sell-token WETH --buy-token USDC --amount 1 --buy-price 4200

# Resolve token by address if symbol is unavailable
npm run token-info -- --token 0xA0b86991c6218b36c1d19d4a2e9eb0ce3606eb48 --network mainnet

# Refresh CowSwap + CoinGecko registries locally
npm run update-tokens-registry
Enter fullscreen mode Exit fullscreen mode

That gives me a fallback path for order execution, plus a cleaner base for automation.
You can use it passing a private key or a ledger path (recommended).

Building the CLI

1) Commands are thin, typed, and strategy-focused

Each CLI command maps flags into a typed executor. The command surface is simple, and validation/execution complexity lives in shared utilities.

program
  .command('sell-ladder')
  .requiredOption('--sell-token <symbol>')
  .requiredOption('--amount <amount>')
  .requiredOption('--percentages <percentages>')
  .option('--buy-token <symbol>', 'USDC')
  .option('--ledger-path <path>')
  .action(async (options) => executeSellLadderCommand(options));
Enter fullscreen mode Exit fullscreen mode

Why this matters:

  • Inputs look like trading intent, not protocol internals
  • Flags stay consistent across order types
  • Execution logic stays maintainable

2) Safety checks are in the execution path, not optional

Before placing orders, the shared command flow validates inputs, resolves token/network context, checks pricing, and asks for confirmation.

const confirmed = await checkPriceAndConfirm(
  orderKind,
  validated.buyToken,
  validated.sellToken,
  networkConfig.chainId,
  amountPerOrder,
  marketPrice,
);

if (!confirmed) {
  process.exit(0);
}
Enter fullscreen mode Exit fullscreen mode

Why this matters:

  • Price sanity checks happen before submission
  • You reduce accidental execution from stale or outlier quotes
  • Confirmation is explicit when discrepancies on source prices occur.
  • Even if private key is supported do not use it for production trading; prefer the ledger-based signing flow for security.

3) Sell ladders and market-relative logic are first-class

Sell ladder orders use percentage offsets above market (--percentages "1,3,5") and split the total amount across levels.

If you want a single market-relative limit order, a practical trick is using a one-level ladder.

Solving Real Gaps From the UI Path

  • Frontend reliability risk: CLI gives an alternate execution interface.
  • Missing strategy primitives: Sell ladders and relative offsets are available.
  • Liquidity quote risk: Extra sanity checks before order submission.
  • Token availability gaps: Symbol and address-based resolution supported.
  • Security posture: Ledger-based signing flow supported.

Why Intermediate Developers Should Care

If you already use TypeScript and trade on-chain, this pattern gives you immediate leverage:

  • reproducible execution
  • scriptable strategy workflows
  • less dependency on frontend state
  • easier path from manual trading to automation

References

Top comments (0)