diff --git a/packages/bridging/src/BridgingSdk/mock/bridgeRequestMocks.ts b/packages/bridging/src/BridgingSdk/mock/bridgeRequestMocks.ts index 751534afb..8f1cd90cd 100644 --- a/packages/bridging/src/BridgingSdk/mock/bridgeRequestMocks.ts +++ b/packages/bridging/src/BridgingSdk/mock/bridgeRequestMocks.ts @@ -117,6 +117,10 @@ export const amountsAndCosts: QuoteAmountsAndCosts = { amount: BigInt('100000000'), bps: 100, }, + protocolFee: { + amount: 0n, + bps: 0 + } }, beforeNetworkCosts: { sellAmount: BigInt('100000000'), diff --git a/packages/order-book/package.json b/packages/order-book/package.json index 3a759b89c..e55f28c5b 100644 --- a/packages/order-book/package.json +++ b/packages/order-book/package.json @@ -1,6 +1,6 @@ { "name": "@cowprotocol/sdk-order-book", - "version": "0.2.0", + "version": "0.2.1", "description": "CowProtocol Order Book package", "main": "./dist/index.js", "module": "./dist/index.mjs", @@ -20,7 +20,7 @@ "test": "jest", "test:coverage": "jest --coverage --json --outputFile=jest.results.json && npx coveralls < ./coverage/lcov.info", "test:coverage:html": "jest --silent=false --coverage --coverageReporters html", - "swagger:codegen": "openapi --input https://raw.githubusercontent.com/cowprotocol/services/v2.291.0/crates/orderbook/openapi.yml --output src/generated --exportServices false --exportCore false", + "swagger:codegen": "openapi --input https://raw.githubusercontent.com/cowprotocol/services/dfb50cb4a103e8f949f5a7145beb6be63ef41c85/crates/orderbook/openapi.yml --output src/generated --exportServices false --exportCore false", "clean": "rm -rf .turbo && rm -rf node_modules && rm -rf dist", "typecheck": "tsc --noEmit", "prepublishOnly": "npm run build" diff --git a/packages/order-book/src/quoteAmountsAndCostsUtils.test.ts b/packages/order-book/src/quoteAmountsAndCostsUtils.test.ts index b815b1bb1..208f6176c 100644 --- a/packages/order-book/src/quoteAmountsAndCostsUtils.test.ts +++ b/packages/order-book/src/quoteAmountsAndCostsUtils.test.ts @@ -60,6 +60,7 @@ describe('Calculation of before/after fees amounts', () => { buyDecimals, slippagePercentBps: 0, partnerFeeBps: undefined, + protocolFeeBps: undefined, }) expect(result.afterNetworkCosts.sellAmount.toString()).toBe( @@ -74,6 +75,7 @@ describe('Calculation of before/after fees amounts', () => { buyDecimals, slippagePercentBps: 0, partnerFeeBps: undefined, + protocolFeeBps: undefined, }) expect(result.beforeNetworkCosts.buyAmount.toString()).toBe( @@ -99,6 +101,7 @@ describe('Calculation of before/after fees amounts', () => { buyDecimals, partnerFeeBps, slippagePercentBps: 0, + protocolFeeBps: undefined, }) const buyAmountBeforeNetworkCosts = @@ -120,6 +123,7 @@ describe('Calculation of before/after fees amounts', () => { buyDecimals, partnerFeeBps, slippagePercentBps: 0, + protocolFeeBps: undefined, }) const partnerFeeAmount = Math.floor((+orderParams.sellAmount * partnerFeeBps) / 100 / 100) @@ -141,6 +145,7 @@ describe('Calculation of before/after fees amounts', () => { buyDecimals, partnerFeeBps: undefined, slippagePercentBps, + protocolFeeBps: undefined, }) const buyAmountAfterNetworkCosts = +orderParams.buyAmount @@ -160,6 +165,7 @@ describe('Calculation of before/after fees amounts', () => { buyDecimals, partnerFeeBps: undefined, slippagePercentBps, + protocolFeeBps: undefined, }) const sellAmountAfterNetworkCosts = +orderParams.sellAmount + +orderParams.feeAmount diff --git a/packages/order-book/src/quoteAmountsAndCostsUtils.ts b/packages/order-book/src/quoteAmountsAndCostsUtils.ts index 511864113..883f9a94d 100644 --- a/packages/order-book/src/quoteAmountsAndCostsUtils.ts +++ b/packages/order-book/src/quoteAmountsAndCostsUtils.ts @@ -7,6 +7,7 @@ export interface QuoteAmountsAndCostsParams { buyDecimals: number slippagePercentBps: number partnerFeeBps: number | undefined + protocolFeeBps: number | undefined } const ONE_HUNDRED_BPS = BigInt(100 * 100) @@ -85,10 +86,10 @@ function _getQuoteAmountsWithCosts(params: { } function getQuoteAmountsWithPartnerFee(params: { - sellAmountAfterNetworkCosts: BigNumber - buyAmountAfterNetworkCosts: BigNumber - buyAmountBeforeNetworkCosts: BigNumber - sellAmountBeforeNetworkCosts: BigNumber + sellAmountAfterNetworkCosts: bigint + buyAmountAfterNetworkCosts: bigint + buyAmountBeforeNetworkCosts: bigint + sellAmountBeforeNetworkCosts: bigint isSell: boolean partnerFeeBps: number }) { @@ -104,20 +105,20 @@ function getQuoteAmountsWithPartnerFee(params: { /** * Partner fee is always added on the surplus amount, for sell-orders it's buy amount, for buy-orders it's sell amount */ - const surplusAmount = isSell ? buyAmountBeforeNetworkCosts.big : sellAmountBeforeNetworkCosts.big - const partnerFeeAmount = partnerFeeBps > 0 ? (surplusAmount * BigInt(partnerFeeBps)) / ONE_HUNDRED_BPS : BigInt(0) + const surplusAmount = isSell ? buyAmountBeforeNetworkCosts : sellAmountBeforeNetworkCosts + const partnerFeeAmount = partnerFeeBps > 0 ? (surplusAmount * BigInt(partnerFeeBps)) / ONE_HUNDRED_BPS : 0n /** * Partner fee is always added on the surplus token, for sell-orders it's buy token, for buy-orders it's sell token */ const afterPartnerFees = isSell ? { - sellAmount: sellAmountAfterNetworkCosts.big, - buyAmount: buyAmountAfterNetworkCosts.big - partnerFeeAmount, + sellAmount: sellAmountAfterNetworkCosts, + buyAmount: buyAmountAfterNetworkCosts - partnerFeeAmount, } : { - sellAmount: sellAmountAfterNetworkCosts.big + partnerFeeAmount, - buyAmount: buyAmountAfterNetworkCosts.big, + sellAmount: sellAmountAfterNetworkCosts + partnerFeeAmount, + buyAmount: buyAmountAfterNetworkCosts, } return { @@ -126,6 +127,40 @@ function getQuoteAmountsWithPartnerFee(params: { } } +function getProtocolFeeAmount(params: { + sellAmountAfterNetworkCosts: bigint + buyAmountAfterNetworkCosts: bigint + networkCostAmount: bigint + isSell: boolean + protocolFeeBps: number +}) { + const { sellAmountAfterNetworkCosts, buyAmountAfterNetworkCosts, networkCostAmount, isSell, protocolFeeBps } = params + + if (protocolFeeBps <= 0) { + return 0n + } + + const protocolFeeBpsBig = BigInt(protocolFeeBps) + + if (isSell) { + /** + * SELL orders formula: protocolFeeInBuy = quoteBuyAmount * protocolFeeBps / (1 - protocolFeeBps) + * + * The buyAmountAfterNetworkCosts already includes the protocol fee (it was deducted from buyAmount by the API). + * We need to reconstruct the original buyAmount and calculate the fee amount. + */ + const ONE_MINUS_PROTOCOL_FEE_BPS = ONE_HUNDRED_BPS - protocolFeeBpsBig + return (buyAmountAfterNetworkCosts * protocolFeeBpsBig) / ONE_MINUS_PROTOCOL_FEE_BPS + } else { + /** + * BUY orders formula: protocolFeeInSell = (quoteSellAmount + feeAmount) * proto colFeeBps / (1 + protocolFeeBps) + */ + const ONE_PLUS_PROTOCOL_FEE_BPS = ONE_HUNDRED_BPS + protocolFeeBpsBig + const sellAmountWithNetworkFee = sellAmountAfterNetworkCosts + networkCostAmount + return (sellAmountWithNetworkFee * protocolFeeBpsBig) / ONE_PLUS_PROTOCOL_FEE_BPS + } +} + function getQuoteAmountsWithSlippage(params: { afterPartnerFees: { sellAmount: bigint; buyAmount: bigint } isSell: boolean @@ -155,6 +190,7 @@ function getQuoteAmountsWithSlippage(params: { export function getQuoteAmountsAndCosts(params: QuoteAmountsAndCostsParams): QuoteAmountsAndCosts { const { orderParams, sellDecimals, buyDecimals, slippagePercentBps } = params const partnerFeeBps = params.partnerFeeBps ?? 0 + const protocolFeeBps = params.protocolFeeBps ?? 0 // Get amounts including network costs const { @@ -167,12 +203,21 @@ export function getQuoteAmountsAndCosts(params: QuoteAmountsAndCostsParams): Quo quotePrice, } = _getQuoteAmountsWithCosts({ sellDecimals, buyDecimals, orderParams }) + + const protocolFeeAmount = getProtocolFeeAmount({ + sellAmountAfterNetworkCosts: sellAmountAfterNetworkCosts.big, + buyAmountAfterNetworkCosts: buyAmountAfterNetworkCosts.big, + networkCostAmount: networkCostAmount.big, + isSell, + protocolFeeBps, + }) + // Get amounts including partner fees const { afterPartnerFees, partnerFeeAmount } = getQuoteAmountsWithPartnerFee({ - sellAmountAfterNetworkCosts, - buyAmountAfterNetworkCosts, - buyAmountBeforeNetworkCosts, - sellAmountBeforeNetworkCosts, + sellAmountAfterNetworkCosts: sellAmountAfterNetworkCosts.big, + buyAmountAfterNetworkCosts: buyAmountAfterNetworkCosts.big, + buyAmountBeforeNetworkCosts: buyAmountBeforeNetworkCosts.big, + sellAmountBeforeNetworkCosts: sellAmountBeforeNetworkCosts.big, isSell, partnerFeeBps, }) @@ -195,6 +240,10 @@ export function getQuoteAmountsAndCosts(params: QuoteAmountsAndCostsParams): Quo amount: partnerFeeAmount, bps: partnerFeeBps, }, + protocolFee: { + amount: protocolFeeAmount, + bps: protocolFeeBps, + }, }, beforeNetworkCosts: { sellAmount: sellAmountBeforeNetworkCosts.big, diff --git a/packages/order-book/src/types.ts b/packages/order-book/src/types.ts index 32b1989fa..3cde5093d 100644 --- a/packages/order-book/src/types.ts +++ b/packages/order-book/src/types.ts @@ -21,6 +21,10 @@ export interface Costs { amount: T bps: number } + protocolFee: { + amount: T + bps: number + } } /** @@ -32,6 +36,7 @@ export interface Costs { * The order of adding fees and costs is as follows: * 1. Network fee is always added to the sell amount * 2. Partner fee is added to the surplus amount (sell amount for sell-orders, buy amount for buy-orders) + * 3. Protocol fee --- need to clarify this one * * For sell-orders the partner fee is subtracted from the buy amount after network costs. * For buy-orders the partner fee is added on top of the sell amount after network costs. diff --git a/packages/trading/src/getOrderToSign.ts b/packages/trading/src/getOrderToSign.ts index 0da449753..332382ce8 100644 --- a/packages/trading/src/getOrderToSign.ts +++ b/packages/trading/src/getOrderToSign.ts @@ -18,10 +18,11 @@ interface OrderToSignParams { from: string networkCostsAmount?: string applyCostsSlippageAndFees?: boolean + protocolFeeBps?: number } export function getOrderToSign( - { chainId, from, networkCostsAmount = '0', isEthFlow, applyCostsSlippageAndFees = true }: OrderToSignParams, + { chainId, from, networkCostsAmount = '0', isEthFlow, applyCostsSlippageAndFees = true, protocolFeeBps }: OrderToSignParams, limitOrderParams: LimitTradeParameters, appDataKeccak256: string, ): UnsignedOrder { @@ -63,6 +64,7 @@ export function getOrderToSign( orderParams, slippagePercentBps: slippageBps, partnerFeeBps: getPartnerFeeBps(partnerFee), + protocolFeeBps, sellDecimals: sellTokenDecimals, buyDecimals: buyTokenDecimals, }) diff --git a/packages/trading/src/getQuote.ts b/packages/trading/src/getQuote.ts index 44bc17790..a10071fca 100644 --- a/packages/trading/src/getQuote.ts +++ b/packages/trading/src/getQuote.ts @@ -208,12 +208,13 @@ export async function getQuote( orderParams: quote.quote, slippagePercentBps: slippageBps, partnerFeeBps: getPartnerFeeBps(partnerFee), + protocolFeeBps: quote.protocolFeeBps ? Number(quote.protocolFeeBps) : undefined, sellDecimals: sellTokenDecimals, buyDecimals: buyTokenDecimals, }) const orderToSign = getOrderToSign( - { chainId, from, networkCostsAmount: quote.quote.feeAmount, isEthFlow }, + { chainId, from, networkCostsAmount: quote.quote.feeAmount, isEthFlow, protocolFeeBps: quote.protocolFeeBps ? Number(quote.protocolFeeBps) : undefined }, swapParamsToLimitOrderParams(tradeParameters, quote), appDataInfo.appDataKeccak256, ) diff --git a/packages/trading/src/resolveSlippageSuggestion.ts b/packages/trading/src/resolveSlippageSuggestion.ts index 7eddd844f..94b7fbaeb 100644 --- a/packages/trading/src/resolveSlippageSuggestion.ts +++ b/packages/trading/src/resolveSlippageSuggestion.ts @@ -36,6 +36,7 @@ export async function resolveSlippageSuggestion( orderParams: quote.quote, slippagePercentBps: 0, partnerFeeBps: getPartnerFeeBps(tradeParameters.partnerFee), + protocolFeeBps: quote.protocolFeeBps ? Number(quote.protocolFeeBps) : undefined, sellDecimals: tradeParameters.sellTokenDecimals, buyDecimals: tradeParameters.buyTokenDecimals, }) diff --git a/packages/trading/src/utils/misc.ts b/packages/trading/src/utils/misc.ts index 6219a8376..43fb53ba3 100644 --- a/packages/trading/src/utils/misc.ts +++ b/packages/trading/src/utils/misc.ts @@ -33,7 +33,7 @@ export function mapQuoteAmountsAndCosts( mapper: (value: T) => R, ): QuoteAmountsAndCosts { const { - costs: { networkFee, partnerFee }, + costs: { networkFee, partnerFee, protocolFee }, } = value function serializeAmounts(value: { sellAmount: T; buyAmount: T }): { sellAmount: R; buyAmount: R } { @@ -56,6 +56,10 @@ export function mapQuoteAmountsAndCosts( ...partnerFee, amount: mapper(partnerFee.amount), }, + protocolFee: { + ...protocolFee, + amount: mapper(protocolFee.amount), + }, }, beforeNetworkCosts: serializeAmounts(value.beforeNetworkCosts), afterNetworkCosts: serializeAmounts(value.afterNetworkCosts),