From f8b85546f23007ebd129897fd41edf3830e0a53f Mon Sep 17 00:00:00 2001 From: raq0x Date: Thu, 8 Oct 2026 23:27:27 +0200 Subject: [PATCH 1/2] Explain /quote amounts and cross-aggregator comparison in API guide Integrators keep using quote.buyAmount (after network costs) as a generic toAmount next to other aggregators' before-gas amounts, so CoW ranks last even at an equal price. Add a "Displaying and Comparing Quotes" section, fix the quickstart to build the order from quoteResponse.quote, and link the amount-stage math on the concepts page. Co-Authored-By: Claude Opus 5.5 --- docs/cow-protocol/integrate/api.mdx | 136 ++++++++++++++++++++++++++-- 1 file changed, 130 insertions(+), 6 deletions(-) diff --git a/docs/cow-protocol/integrate/api.mdx b/docs/cow-protocol/integrate/api.mdx index b0c5c141a..fed19bf58 100644 --- a/docs/cow-protocol/integrate/api.mdx +++ b/docs/cow-protocol/integrate/api.mdx @@ -41,21 +41,45 @@ curl -X POST "https://api.cow.fi/mainnet/api/v1/quote" \ }' ``` +The amounts in the response already have network costs and the protocol fee deducted. Read [Displaying and Comparing Quotes](#displaying-and-comparing-quotes) before showing them to users. + ### 2. Sign and Submit Order +Build the order from `quoteResponse.quote`, not from the whole response. For a sell order, sign the full sell amount with `feeAmount: "0"` and reduce the buy amount by the slippage tolerance (see [Forming the Final Order](../concepts/how-it-works/how-intents-are-formed.mdx#forming-the-final-order)): + ```javascript -// After getting a quote, sign the order +const { quote } = quoteResponse +const slippageBps = 50n // 0.5% + const order = { - ...quoteResponse, - signature: await signOrder(quoteResponse, signer), - signingScheme: "eip712" + sellToken: quote.sellToken, + buyToken: quote.buyToken, + receiver: quote.receiver, + // quote.sellAmount is after network costs; sign the full amount + sellAmount: (BigInt(quote.sellAmount) + BigInt(quote.feeAmount)).toString(), + // minimum to receive + buyAmount: (BigInt(quote.buyAmount) * (10000n - slippageBps) / 10000n).toString(), + validTo: quote.validTo, + appData: quote.appDataHash ?? quote.appData, // signed as the bytes32 hash + feeAmount: "0", // network costs are taken from sellAmount at settlement + kind: quote.kind, + partiallyFillable: quote.partiallyFillable, + sellTokenBalance: quote.sellTokenBalance, + buyTokenBalance: quote.buyTokenBalance, } -// Submit the signed order +// Submit the signed order (see Order Signing below) const response = await fetch('https://api.cow.fi/mainnet/api/v1/orders', { method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(order) + body: JSON.stringify({ + ...order, + appData: quote.appData, // full app data JSON, if the quote request included it + from: quoteResponse.from, + quoteId: quoteResponse.id, + signature: await signOrder(order, signer), + signingScheme: "eip712", + }) }) const orderId = await response.text() @@ -73,6 +97,105 @@ const orderDetails = await orderResponse.json() console.log('Order status:', orderDetails.status) ``` +## Displaying and Comparing Quotes + +The `/quote` amounts are not "amount out" values that can be dropped into a generic quote UI as-is. +This section covers what to display and how to rank a CoW Protocol quote against other sources. +For the full derivation of every amount stage, see [How Intents Are Formed](../concepts/how-it-works/how-intents-are-formed.mdx#3-amount-stages). + +### What the amounts mean + +For a sell order: + +| Field | Meaning | +|---|---| +| `quote.sellAmount` | Sell amount **after** network costs (`sellAmountBeforeFee - feeAmount`) | +| `quote.feeAmount` | Estimated network costs, in sell token atoms | +| `quote.buyAmount` | Buy amount **after** network costs and the protocol fee (`protocolFeeBps`) | + +Partner fees are not included; your integration applies them on top. + +The user pays no separate gas for a CoW Protocol order (apart from a token approval, if one is needed): network costs are taken from the sell amount at settlement. +`quote.buyAmount` is therefore an after-gas amount. + +### Comparing with other aggregators + +Other aggregators typically return their output amount **before** gas, and the user pays gas separately in the native token. +Putting `quote.buyAmount` in the same column compares an after-gas amount with before-gas amounts, so CoW Protocol ranks last even when its price is the same. + +Convert every source to the same basis: + +- **Before network costs**, comparable to other aggregators' output amount: add the network costs back, converted to the buy token, then subtract your partner fee. + + ```javascript + const networkCostsInBuyToken = buyAmount * feeAmount / sellAmount // quote.* amounts as BigInt + const amountBeforeNetworkCosts = buyAmount + networkCostsInBuyToken - partnerFeeAmount + ``` + +- **After network costs**, what the user actually receives: `quote.buyAmount - partnerFeeAmount`. Compare it with other sources' output minus their gas cost, converted to the buy token. + +`partnerFeeAmount` is zero unless your integration charges a partner fee. It is computed on `beforeAllFees`, not on `quote.buyAmount` (see [Partner Fee](../concepts/how-it-works/how-intents-are-formed.mdx#partner-fee)). + +:::caution + +Don't rank on `beforeAllFees` (the spot price). Besides the network costs, it also adds back the protocol fee and leaves out the partner fee, so it shows more than the user can receive and overstates CoW Protocol against other sources. + +::: + +Executed amounts are often better than quoted: `feeAmount` is an estimate made at quote time, and solvers competing in the auction return any surplus they find to the user. + +### Example + +Selling 10 USDT for USDC on mainnet. The numbers are illustrative; network costs depend on the gas price at quote time. + +```json +{ + "quote": { + "sellAmount": "9572139", + "feeAmount": "427861", + "buyAmount": "9562076", + "kind": "sell" + }, + "protocolFeeBps": "0.3" +} +``` + +| Amount | USDC | +|---|---| +| After network costs: `quote.buyAmount` | 9.562 | +| Network costs in buy token: `9.562076 × 0.427861 / 9.572139` | 0.427 | +| Before network costs | 9.989 | + +Another aggregator returning 9.988 USDC, with about 0.43 USD of gas paid separately in ETH, is on par with CoW Protocol on either basis: 9.989 vs 9.988 before gas, 9.562 vs about 9.558 after gas. +Showing 9.562 next to 9.988 makes CoW Protocol look about 4% worse than it is. + +### Buy orders + +For buy orders, `quote.buyAmount` is the fixed amount the user receives, and `quote.sellAmount` includes the protocol fee but **not** network costs. +The amount the user pays is `quote.sellAmount + quote.feeAmount`, plus any partner fee and slippage tolerance. +See [Flow for buy orders](../concepts/how-it-works/how-intents-are-formed.mdx#flow-for-buy-orders). + +### Using the SDK + +[`getQuoteAmountsAndCosts`](https://github.com/cowprotocol/cow-sdk/tree/main/packages/order-book/src/quoteAmountsAndCosts) computes every amount stage from a quote response, for both sell and buy orders: + +```typescript +import { getQuoteAmountsAndCosts } from '@cowprotocol/cow-sdk' + +const amounts = getQuoteAmountsAndCosts({ + orderParams: quoteResponse.quote, + protocolFeeBps: Number(quoteResponse.protocolFeeBps), + partnerFeeBps: 0, // your partner fee, if any + slippagePercentBps: 50, +}) + +// Before network costs, comparable to other aggregators (sell order) +const displayAmount = amounts.beforeNetworkCosts.buyAmount - amounts.costs.partnerFee.amount + +// Amounts to sign in the order +const { sellAmount, buyAmount } = amounts.amountsToSign +``` + ## Streaming Quotes `POST /api/v1/quote/stream` takes the same request body as `POST /api/v1/quote`, but returns a [Server-Sent Events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events) stream instead of a single response. @@ -217,4 +340,5 @@ For complete API documentation including all endpoints, parameters, and response - **[API Explorer](https://api.cow.fi/docs/)** - Interactive documentation - **[GitHub Examples](https://github.com/cowprotocol/cow-sdk/tree/main/examples)** - Code examples - **[Order Signing Guide](../reference/core/signing_schemes.mdx)** - Cryptographic signing details +- **[How Intents Are Formed](../concepts/how-it-works/how-intents-are-formed.mdx)** - Quote amount stages, fees, and the amounts to sign From 330dbeb9c3e427aa10ff926922953844d62eb72c Mon Sep 17 00:00:00 2001 From: raq0x Date: Tue, 13 Oct 2026 16:19:44 +0200 Subject: [PATCH 2/2] docs: clarify quote amounts and gas costs for API integrators --- docs/cow-protocol/integrate/api.mdx | 139 +++++++++--------- .../cow-protocol/reference/apis/orderbook.mdx | 8 + 2 files changed, 74 insertions(+), 73 deletions(-) diff --git a/docs/cow-protocol/integrate/api.mdx b/docs/cow-protocol/integrate/api.mdx index fed19bf58..9a20d70c4 100644 --- a/docs/cow-protocol/integrate/api.mdx +++ b/docs/cow-protocol/integrate/api.mdx @@ -33,7 +33,7 @@ The primary API for creating and managing orders on CoW Protocol. curl -X POST "https://api.cow.fi/mainnet/api/v1/quote" \ -H "Content-Type: application/json" \ -d '{ - "sellToken": "0xA0b86a33E6411Ec5d0b9dd2E7dC15A9CAA6C1F8e", + "sellToken": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", "buyToken": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", "sellAmountBeforeFee": "1000000", "kind": "sell", @@ -41,11 +41,19 @@ curl -X POST "https://api.cow.fi/mainnet/api/v1/quote" \ }' ``` -The amounts in the response already have network costs and the protocol fee deducted. Read [Displaying and Comparing Quotes](#displaying-and-comparing-quotes) before showing them to users. +:::note Sell quotes already include gas costs + +For sell orders (`kind: "sell"`), `quote.buyAmount` is the quoted output **after estimated network costs (gas) and the applicable protocol fee have been deducted**. Do not subtract these costs again or compare this amount directly with another provider's before-gas output. + +See [Displaying and Comparing Quotes](#displaying-and-comparing-quotes) for a worked example and calculations using the API response. + +::: ### 2. Sign and Submit Order -Build the order from `quoteResponse.quote`, not from the whole response. For a sell order, sign the full sell amount with `feeAmount: "0"` and reduce the buy amount by the slippage tolerance (see [Forming the Final Order](../concepts/how-it-works/how-intents-are-formed.mdx#forming-the-final-order)): +Build the order from `quoteResponse.quote`, not from the whole response. For a sell order, sign the full sell amount with `feeAmount: "0"`, and reduce the buy amount by any partner fee and then the slippage tolerance (see [Forming the Final Order](../concepts/how-it-works/how-intents-are-formed.mdx#forming-the-final-order)). +If the quote request includes full `appData` JSON, sign `quote.appDataHash` and send the JSON as `appData`. +The example below assumes no partner fee. ```javascript const { quote } = quoteResponse @@ -74,7 +82,7 @@ const response = await fetch('https://api.cow.fi/mainnet/api/v1/orders', { headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ ...order, - appData: quote.appData, // full app data JSON, if the quote request included it + appData: quote.appData, // full app data JSON, if supplied in the quote request from: quoteResponse.from, quoteId: quoteResponse.id, signature: await signOrder(order, signer), @@ -99,102 +107,87 @@ console.log('Order status:', orderDetails.status) ## Displaying and Comparing Quotes -The `/quote` amounts are not "amount out" values that can be dropped into a generic quote UI as-is. -This section covers what to display and how to rank a CoW Protocol quote against other sources. -For the full derivation of every amount stage, see [How Intents Are Formed](../concepts/how-it-works/how-intents-are-formed.mdx#3-amount-stages). - -### What the amounts mean - -For a sell order: +For a sell order, `/quote` returns amounts **after** network costs: | Field | Meaning | |---|---| -| `quote.sellAmount` | Sell amount **after** network costs (`sellAmountBeforeFee - feeAmount`) | +| `quote.sellAmount` | Sell amount after network costs (`sellAmountBeforeFee - feeAmount`) | | `quote.feeAmount` | Estimated network costs, in sell token atoms | -| `quote.buyAmount` | Buy amount **after** network costs and the protocol fee (`protocolFeeBps`) | +| `quote.buyAmount` | Buy amount after network costs and the protocol fee, before any partner fee | -Partner fees are not included; your integration applies them on top. +Settlement network costs are already reflected in `quote.buyAmount`; `quote.feeAmount` describes those costs in the sell token, not an additional amount to deduct from the output. +Other aggregators usually return their output **before** gas, which the user pays on top. +Shown side by side as-is, CoW Protocol ranks lower even at the same price. -The user pays no separate gas for a CoW Protocol order (apart from a token approval, if one is needed): network costs are taken from the sell amount at settlement. -`quote.buyAmount` is therefore an after-gas amount. +Compare every source on the same basis and for the same total sell input. Convert separately paid gas to buy-token units before comparing output amounts: -### Comparing with other aggregators +| Basis | CoW Protocol (sell order) | Other aggregators | +|---|---|---| +| Before gas | `quote.buyAmount` + network costs in buy token − partner fee | Their output | +| After gas | `quote.buyAmount` − partner fee | Their output − their gas | -Other aggregators typically return their output amount **before** gas, and the user pays gas separately in the native token. -Putting `quote.buyAmount` in the same column compares an after-gas amount with before-gas amounts, so CoW Protocol ranks last even when its price is the same. +The amounts are estimates, not guaranteed execution amounts. Execution can improve on the quote through solver competition and changes in network costs. -Convert every source to the same basis: - -- **Before network costs**, comparable to other aggregators' output amount: add the network costs back, converted to the buy token, then subtract your partner fee. +### Example - ```javascript - const networkCostsInBuyToken = buyAmount * feeAmount / sellAmount // quote.* amounts as BigInt - const amountBeforeNetworkCosts = buyAmount + networkCostsInBuyToken - partnerFeeAmount - ``` +Selling 10 USDT for USDC at a 1:1 price, with 0.40 USDT of network costs and no protocol or partner fee (illustrative numbers). +`/quote` returns `sellAmount: "9600000"`, `feeAmount: "400000"` and `buyAmount: "9600000"` (both tokens have 6 decimals). +Another aggregator quotes 9.99 USDC, and the user pays 0.40 USD of gas on top. -- **After network costs**, what the user actually receives: `quote.buyAmount - partnerFeeAmount`. Compare it with other sources' output minus their gas cost, converted to the buy token. +| | CoW Protocol | Other aggregator | +|---|---|---| +| Quoted output | 9.60 | 9.99 | +| Before gas | 9.60 + 0.40 = **10.00** | **9.99** | +| After gas | **9.60** | 9.99 − 0.40 = **9.59** | -`partnerFeeAmount` is zero unless your integration charges a partner fee. It is computed on `beforeAllFees`, not on `quote.buyAmount` (see [Partner Fee](../concepts/how-it-works/how-intents-are-formed.mdx#partner-fee)). +CoW Protocol is better on both bases, but showing its raw `quote.buyAmount` next to the other output (9.60 vs 9.99) makes it look about 4% worse. -:::caution +### Calculating from the API response -Don't rank on `beforeAllFees` (the spot price). Besides the network costs, it also adds back the protocol fee and leaves out the partner fee, so it shows more than the user can receive and overstates CoW Protocol against other sources. +For a sell quote, the following calculation uses only the returned amounts and any partner fee your integration charges. `quote.feeAmount` is in sell-token atoms, so **do not add it directly to `quote.buyAmount`**. -::: +```javascript +// quoteResponse is the JSON response from POST /api/v1/quote. +const { quote } = quoteResponse +if (quote.kind !== 'sell') { + throw new Error('This display calculation is for sell quotes') +} -Executed amounts are often better than quoted: `feeAmount` is an estimate made at quote time, and solvers competing in the auction return any surplus they find to the user. +const sellAmount = BigInt(quote.sellAmount) +const buyAmount = BigInt(quote.buyAmount) +const feeAmount = BigInt(quote.feeAmount) +const partnerFeeAmount = 0n // replace with your partner fee in buy-token atoms, if any -### Example +// Total sell-token input, including network costs. +const totalSellAmount = sellAmount + feeAmount -Selling 10 USDT for USDC on mainnet. The numbers are illustrative; network costs depend on the gas price at quote time. - -```json -{ - "quote": { - "sellAmount": "9572139", - "feeAmount": "427861", - "buyAmount": "9562076", - "kind": "sell" - }, - "protocolFeeBps": "0.3" -} +// Convert network costs to buy-token atoms at the quoted exchange rate. +const networkCostsInBuyToken = (buyAmount * feeAmount) / sellAmount +const beforeGas = buyAmount + networkCostsInBuyToken - partnerFeeAmount +const afterGas = buyAmount - partnerFeeAmount ``` -| Amount | USDC | -|---|---| -| After network costs: `quote.buyAmount` | 9.562 | -| Network costs in buy token: `9.562076 × 0.427861 / 9.572139` | 0.427 | -| Before network costs | 9.989 | - -Another aggregator returning 9.988 USDC, with about 0.43 USD of gas paid separately in ETH, is on par with CoW Protocol on either basis: 9.989 vs 9.988 before gas, 9.562 vs about 9.558 after gas. -Showing 9.562 next to 9.988 makes CoW Protocol look about 4% worse than it is. - -### Buy orders +For the example above, `totalSellAmount` is `10000000n` (10 USDT), `beforeGas` is `10000000n` (10 USDC), and `afterGas` is `9600000n` (9.60 USDC). +Use `totalSellAmount` for the input display, `beforeGas` for a before-gas output display, and `afterGas` for comparison against other routes' output after costs. +Use integer arithmetic to avoid precision loss; division rounds down to whole buy-token atoms. Format the input with the sell token's decimals and the output with the buy token's decimals. -For buy orders, `quote.buyAmount` is the fixed amount the user receives, and `quote.sellAmount` includes the protocol fee but **not** network costs. -The amount the user pays is `quote.sellAmount + quote.feeAmount`, plus any partner fee and slippage tolerance. -See [Flow for buy orders](../concepts/how-it-works/how-intents-are-formed.mdx#flow-for-buy-orders). +If your integration charges a [partner fee](../concepts/how-it-works/how-intents-are-formed.mdx#partner-fee), calculate its amount using the pre-fee quote value, not `quote.buyAmount`, and replace `partnerFeeAmount` with that amount in buy-token atoms. -### Using the SDK +:::caution -[`getQuoteAmountsAndCosts`](https://github.com/cowprotocol/cow-sdk/tree/main/packages/order-book/src/quoteAmountsAndCosts) computes every amount stage from a quote response, for both sell and buy orders: +The before-gas calculation keeps the protocol fee reflected in the output. Do not also add the protocol fee back when comparing with providers whose output includes their fees. +The before-gas amount is for display; do not use it as the order's signed `buyAmount` or as the expected amount received. -```typescript -import { getQuoteAmountsAndCosts } from '@cowprotocol/cow-sdk' +::: -const amounts = getQuoteAmountsAndCosts({ - orderParams: quoteResponse.quote, - protocolFeeBps: Number(quoteResponse.protocolFeeBps), - partnerFeeBps: 0, // your partner fee, if any - slippagePercentBps: 50, -}) +### Buy orders -// Before network costs, comparable to other aggregators (sell order) -const displayAmount = amounts.beforeNetworkCosts.buyAmount - amounts.costs.partnerFee.amount +For a buy order, `quote.buyAmount` is the fixed amount the user receives, and `quote.sellAmount` includes the protocol fee but **not** network costs. +Before gas, compare `quote.sellAmount` plus any partner fee with other aggregators' input; after gas, also add `quote.feeAmount`. +For the same requested buy amount, prefer the lower estimated total input. Slippage tolerance increases the **maximum authorized sell amount** when signing; it is not an additional fee the user necessarily pays. -// Amounts to sign in the order -const { sellAmount, buyAmount } = amounts.amountsToSign -``` +For every amount stage and its formula, see [How Intents Are Formed](../concepts/how-it-works/how-intents-are-formed.mdx#3-amount-stages). +If you use the SDK, [`getQuoteAmountsAndCosts`](https://github.com/cowprotocol/cow-sdk/blob/main/packages/order-book/src/quoteAmountsAndCosts/README.md) provides these calculations and the amounts to sign. ## Streaming Quotes diff --git a/docs/cow-protocol/reference/apis/orderbook.mdx b/docs/cow-protocol/reference/apis/orderbook.mdx index 38cda4f15..d23baed1b 100644 --- a/docs/cow-protocol/reference/apis/orderbook.mdx +++ b/docs/cow-protocol/reference/apis/orderbook.mdx @@ -6,4 +6,12 @@ import {SwaggerDoc} from '../../../../src/components/swagger-doc/SwaggerDoc'; # Order book API +:::note Sell quotes already include gas costs + +For sell orders (`kind: "sell"`), the `/quote` response's `quote.buyAmount` is the quoted output **after estimated network costs (gas) and the applicable protocol fee have been deducted**. Do not subtract these costs again or compare this amount directly with another provider's before-gas output. Compare quotes on the same fee basis. + +See [Displaying and Comparing Quotes](../../integrate/api.mdx#displaying-and-comparing-quotes) for a worked comparison and calculations using the API response. + +::: +