Skip to main content

Creating Limit Orders

Limit orders allow you to specify both the sell and buy amounts, creating an order that will only execute at your desired price or better.

Overview

Unlike swap orders that execute at market price, limit orders give you precise control over the exchange rate. The order will only be filled when the market price reaches your specified rate.
Limit orders are “good-til-cancelled” by default and remain in the order book until filled or cancelled.

Prerequisites

Before creating a limit order, make sure:
  1. Token approval: The sell token must be approved to the CoW Protocol Vault Relayer contract. Without this, the protocol cannot move your tokens when the order fills. See Token Approvals for full details.
  2. Sufficient balance: Your wallet must hold enough of the sell token to cover the sellAmount. The order will fail at submission if balance is insufficient.
  3. For buy limit orders: The sellAmount you specify is the maximum you will pay. You may end up paying less if solvers find a better route.

Check and Set Approval

// Check current allowance
const allowance = await sdk.getCowProtocolAllowance({ tokenAddress: sellToken, owner })

// Approve if needed
if (allowance < BigInt(sellAmount)) {
  await sdk.approveCowProtocol({ tokenAddress: sellToken, amount: BigInt(sellAmount) })
}

Pre-Flight Checklist

Before submitting your limit order, verify:
  • Sell token approved to Vault Relayer
  • Wallet has sufficient sell token balance
  • Token addresses are correct for the target chain
  • Amounts are in token atoms (smallest unit) — watch decimal differences (USDC = 6 decimals, WETH = 18 decimals)
  • Limit price is reasonable relative to the current market
  • validTo gives enough time for the market to reach your price

Using postLimitOrder

The postLimitOrder method creates a limit order with your specified amounts.

Required Parameters

  • kind - The order kind (OrderKind.SELL or OrderKind.BUY)
  • sellToken - The sell token address
  • sellTokenDecimals - The sell token decimals
  • buyToken - The buy token address
  • buyTokenDecimals - The buy token decimals
  • sellAmount - The amount to sell in atoms
  • buyAmount - The amount to buy in atoms

Basic Example

import {
  SupportedChainId,
  OrderKind,
  LimitTradeParameters,
  TradingSdk
} from '@cowprotocol/sdk-trading'
import { ViemAdapter } from '@cowprotocol/sdk-viem-adapter'
import { createPublicClient, http, privateKeyToAccount } from 'viem'
import { sepolia } from 'viem/chains'

const adapter = new ViemAdapter({
  provider: createPublicClient({
    chain: sepolia,
    transport: http('YOUR_RPC_URL')
  }),
  signer: privateKeyToAccount('YOUR_PRIVATE_KEY' as `0x${string}`)
})

const sdk = new TradingSdk({
  chainId: SupportedChainId.SEPOLIA,
  appCode: 'YOUR_APP_CODE',
}, {}, adapter)

const limitOrderParameters: LimitTradeParameters = {
  kind: OrderKind.BUY,
  sellToken: '0xfff9976782d46cc05630d1f6ebab18b2324d6b14',
  sellTokenDecimals: 18,
  buyToken: '0x0625afb445c3b6b7b929342a04a22599fd5dbb59',
  buyTokenDecimals: 18,
  sellAmount: '120000000000000000',
  buyAmount: '66600000000000000000',
}

const { orderId } = await sdk.postLimitOrder(limitOrderParameters)

console.log('Order created, id: ', orderId)

Optional Parameters

ParameterTypeDefaultDescription
quoteIdnumber-ID of a quote from the Quote API to use as reference
validTonumber-Order expiration timestamp in seconds since epoch
envEnvprodThe environment to use (prod or staging)
partiallyFillablebooleanfalseWhether the order can be partially filled
receiverstringorder creatorThe address that will receive the tokens
partnerFeePartnerFee-Partner fee configuration

Example with Optional Parameters

const limitOrderParameters: LimitTradeParameters = {
  kind: OrderKind.SELL,
  sellToken: '0xfff9976782d46cc05630d1f6ebab18b2324d6b14',
  sellTokenDecimals: 18,
  buyToken: '0x0625afb445c3b6b7b929342a04a22599fd5dbb59',
  buyTokenDecimals: 18,
  sellAmount: '1000000000000000000',
  buyAmount: '2000000000000000000',
  validTo: Math.floor(Date.now() / 1000) + 86400, // Valid for 24 hours
  partiallyFillable: true,
}

const { orderId } = await sdk.postLimitOrder(limitOrderParameters)

Calculating Limit Price

The limit price is determined by the ratio of sellAmount to buyAmount.

Example: Setting a Limit Price

// You want to sell 1 WETH for at least 3000 USDC
const wethDecimals = 18
const usdcDecimals = 6

const limitOrderParameters: LimitTradeParameters = {
  kind: OrderKind.SELL,
  sellToken: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', // WETH
  sellTokenDecimals: wethDecimals,
  buyToken: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', // USDC
  buyTokenDecimals: usdcDecimals,
  sellAmount: '1000000000000000000', // 1 WETH (18 decimals)
  buyAmount: '3000000000', // 3000 USDC (6 decimals)
}

const { orderId } = await sdk.postLimitOrder(limitOrderParameters)
Price calculation:
  • Limit price = buyAmount / sellAmount (in token units)
  • In this example: 3000 USDC / 1 WETH = 3000 USDC per WETH

Using Quote ID

You can reference a previous quote when creating a limit order:
// First, get a quote
const { quoteResults } = await sdk.getQuote({
  kind: OrderKind.SELL,
  sellToken: '0xfff9976782d46cc05630d1f6ebab18b2324d6b14',
  sellTokenDecimals: 18,
  buyToken: '0x0625afb445c3b6b7b929342a04a22599fd5dbb59',
  buyTokenDecimals: 18,
  amount: '1000000000000000000',
})

const quoteId = quoteResults.quoteResponse.id
const sellAmount = quoteResults.orderToSign.sellAmount
const buyAmount = quoteResults.orderToSign.buyAmount

// Create limit order with quote ID
const limitOrderParameters: LimitTradeParameters = {
  kind: OrderKind.SELL,
  sellToken: '0xfff9976782d46cc05630d1f6ebab18b2324d6b14',
  sellTokenDecimals: 18,
  buyToken: '0x0625afb445c3b6b7b929342a04a22599fd5dbb59',
  buyTokenDecimals: 18,
  sellAmount,
  buyAmount,
  quoteId, // Reference the quote
}

const { orderId } = await sdk.postLimitOrder(limitOrderParameters)

Advanced Settings

Customize limit order behavior with LimitOrderAdvancedSettings:
import { LimitOrderAdvancedSettings, SigningScheme } from '@cowprotocol/sdk-trading'

const advancedSettings: LimitOrderAdvancedSettings = {
  additionalParams: {
    signingScheme: SigningScheme.EIP712,
  },
  appData: {
    metadata: {
      quote: {
        slippageBips: '0', // No slippage for limit orders
      },
    },
  },
}

const { orderId } = await sdk.postLimitOrder(
  limitOrderParameters,
  advancedSettings
)

Order Kinds for Limit Orders

Sell orders guarantee you receive at least the specified buyAmount for your sellAmount:
const limitOrderParameters: LimitTradeParameters = {
  kind: OrderKind.SELL,
  sellAmount: '1000000000000000000', // Selling exactly 1 token
  buyAmount: '2000000000000000000',  // Must receive at least 2 tokens
  // ... other parameters
}
Use case: “I want to sell 1 WETH and receive at least 3000 USDC”

Partially Fillable Orders

By default, limit orders are “fill-or-kill” (must be completely filled). Enable partial fills to allow incremental execution:
const limitOrderParameters: LimitTradeParameters = {
  kind: OrderKind.SELL,
  sellToken: '0xfff9976782d46cc05630d1f6ebab18b2324d6b14',
  sellTokenDecimals: 18,
  buyToken: '0x0625afb445c3b6b7b929342a04a22599fd5dbb59',
  buyTokenDecimals: 18,
  sellAmount: '10000000000000000000', // 10 tokens
  buyAmount: '20000000000000000000', // 20 tokens
  partiallyFillable: true, // Allow partial fills
}

const { orderId } = await sdk.postLimitOrder(limitOrderParameters)
Partially fillable orders may be executed in multiple transactions over time. Monitor the order status to track fills.

When Will My Order Fill?

Limit orders fill when the market price reaches your specified rate or better. Unlike swap orders that execute immediately, limit orders may take hours, days, or never fill depending on market conditions. Here is what happens after you submit:
  1. Your order enters the order book. CoW Protocol solvers continuously scan the book for profitable orders to execute.
  2. Solvers evaluate your order each batch. If your limit price is at or better than the current market price, solvers will include it in the next settlement.
  3. If your price is far from market, the order sits in the book waiting for the market to move to your price.
  4. When conditions are met, a solver executes your order on-chain. You may even receive surplus (a better price than you specified).
Use sdk.getQuote() to check the current market price before setting your limit. This helps you understand how far your limit is from the market and set realistic expectations for fill time.

Monitoring Your Order

After creating a limit order, you will want to track its status.

Check Order Status

const order = await sdk.getOrder({ orderUid: orderId })

console.log('Status:', order.status)
// Possible values: open, fulfilled, cancelled, expired, presignaturePending

Track Partial Fill Progress

For partially fillable orders, inspect how much has been executed so far:
const order = await sdk.getOrder({ orderUid: orderId })

console.log('Executed sell amount:', order.executedSellAmount)
console.log('Executed buy amount:', order.executedBuyAmount)

Cancel an Order

If you want to cancel an open limit order:
await sdk.offChainCancelOrder({ orderUid: orderId })
For full details on querying, cancelling, and managing orders, see Order Management.

Comparing Swap vs Limit Orders

FeatureSwap OrdersLimit Orders
PriceCurrent market priceYour specified price
AmountOne amount (sell OR buy)Both amounts (sell AND buy)
ExecutionImmediate (if liquidity exists)When price target is reached
SlippageApplies automaticallyControlled by your price
Use CaseQuick trades at market pricePrice-specific trades

Best Practices

  1. Set realistic prices: Orders too far from market price may never fill
  2. Use validTo wisely: Set appropriate expiration times for your strategy
  3. Consider partial fills: For large orders, partial fills can improve execution
  4. Monitor order status: Check if your order has been filled or partially filled
  5. Handle slippage: Limit orders typically use 0 slippage since price is explicit

Troubleshooting

ProblemLikely CauseFix
InsufficientAllowance errorSell token not approved to Vault RelayerCall sdk.approveCowProtocol() before placing the order
InsufficientBalance errorWallet does not hold enough sell tokenVerify your balance covers sellAmount
Order never fillsLimit price is too far from the current marketCheck current market price with sdk.getQuote() and adjust
Order fills at a different price than expectedSolvers found a better execution routeThis is expected — CoW Protocol gives you surplus (better than your limit)
TooManyLimitOrders errorToo many open limit ordersCancel old or stale orders first
Partial fill stuck at partialRemaining amount is too small for solvers to profitably executeCancel the order and create a new one for the remainder

Next Steps

Last modified on March 18, 2026