Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 47 additions & 3 deletions apps/explorer/src/api/operator/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {
AddressKey,
CompetitionOrderStatus,
EnrichedOrder,
OrderKind,
Expand Down Expand Up @@ -67,6 +68,41 @@ export type GetTxOrdersParams = WithNetworkId & {
txHash: TxHash
}

/**
* How a protocol fee was calculated, derived from the trade's fee policy.
* Used to label each fee so the different fees on an order can be told apart.
*/
export enum ProtocolFeeType {
Surplus = 'surplus',
Volume = 'volume',
PriceImprovement = 'priceImprovement',
Unknown = 'unknown',
}

/**
* A protocol fee for an order: the total charged across all fills for a given fee policy,
* aggregated from the trades' executedProtocolFees. `tokenAddress` is a normalized AddressKey
* (the surplus-side token the fee is taken in).
*/
export type ProtocolFee = {
amount: BigNumber
tokenAddress: AddressKey
type: ProtocolFeeType
/**
* The fee policy's `factor`, when known. Its meaning is policy-specific: for
* {@link ProtocolFeeType.Volume} / {@link ProtocolFeeType.PriceImprovement} it's a fraction of
* trade volume / price improvement; for surplus fees a fraction of the surplus.
*/
factor?: number
/**
* The fee policy's position in the trade's applied-fee order (`executedProtocolFees` is "listed
* in the order they got applied"). Fee policies are fixed per order, so the same position refers
* to the same policy in every fill. Position 0 is the protocol's own fee; the fees that follow
* it are partner fees (the API doesn't otherwise distinguish protocol from partner).
*/
position: number
}

/**
* Enriched Order type.
* Applies some transformations on the raw api data.
Expand Down Expand Up @@ -101,6 +137,12 @@ export type Order = Pick<
executedFeeAmount: BigNumber
executedFee: BigNumber | null
totalFee: BigNumber
// Derived client-side from the trades' executedProtocolFees (not returned directly on the order).
// Aggregated per category (token + fee type).
protocolFees?: ProtocolFee[]
// On-chain gas cost attributed to the order (native-token wei), as reported by the orderbook.
// Undefined for orders settled before this was recorded, or not yet settled.
gasCost?: BigNumber
cancelled: boolean
status: OrderStatus
partiallyFilled: boolean
Expand All @@ -114,8 +156,10 @@ export type Order = Pick<

export type OrderCompetitionStatus = CompetitionOrderStatus

// Raw API response
export type RawOrder = EnrichedOrder
// Raw API response.
// `gasCost` is served by the orderbook (decimal string of native-token wei, summed across the
// order's fills) but isn't yet in the SDK's EnrichedOrder type — added here until the SDK ships it.
export type RawOrder = EnrichedOrder & { gasCost?: string | null }

export type RawOrderStatusFromAPI = (typeof RAW_ORDER_STATUS)[keyof typeof RAW_ORDER_STATUS]

Expand All @@ -127,7 +171,7 @@ export type RawTrade = TradeMetaData
/**
* Enriched Trade type
*/
export type Trade = Pick<RawTrade, 'blockNumber' | 'logIndex' | 'owner' | 'txHash'> & {
export type Trade = Pick<RawTrade, 'blockNumber' | 'logIndex' | 'owner' | 'txHash' | 'executedProtocolFees'> & {
orderId: string
kind?: OrderKind
buyAmount: BigNumber
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,9 @@ import { ReactNode, useState } from 'react'

import { AppDataWrapper } from 'components/common/AppDataWrapper'
import { RowWithCopyButton } from 'components/common/RowWithCopyButton'
import { ShowMoreButton } from 'components/common/ShowMoreButton'
import { useAppData } from 'hooks/useAppData'

import * as styledEl from './AppDataRowContent.styles'

import { AppDataContent } from '../AppData/AppDataContent'

interface AppDataRowContentProps {
Expand Down Expand Up @@ -44,9 +43,9 @@ export function AppDataRowContent({ appData, showExpanded = false, fullAppData }
)}
&nbsp;
{hasAppData && (
<styledEl.ShowMoreButton onClick={() => setShowDecodedAppData((state) => !state)}>
<ShowMoreButton onClick={() => setShowDecodedAppData((state) => !state)}>
{showDecodedAppData ? '[-] Show less' : '[+] Show more'}
</styledEl.ShowMoreButton>
</ShowMoreButton>
)}
</>
<div className={`hidden-content ${appDataError && 'error'}`}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,5 +63,5 @@ export const DetailsTableTooltips = {
'The (averaged) surplus for this order. This is the positive difference between the initial limit price and the actual (average) execution price.',
filled:
'Indicates what percentage amount this order has been filled and the amount sold/bought. Amount sold includes the fee.',
fees: 'The amount of fees paid for this order. This will show a progressive number for orders with partial fills. Might take a few minutes to show the final value.',
fees: 'The costs and fees charged for this order, totaled per token with a breakdown per category (network costs, protocol fee and any partner fees). Might take a few minutes to show the final value.',
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ interface CostAndFeesItemProps {

export function CostAndFeesItem({ order }: CostAndFeesItemProps): ReactNode {
return (
<DetailRow label="Costs & Fees" tooltipText={DetailsTableTooltips.fees}>
<DetailRow label="Costs and fees" tooltipText={DetailsTableTooltips.fees} stack>
<GasFeeDisplay order={order} />
</DetailRow>
)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import React from 'react'

import { getAddressKey } from '@cowprotocol/cow-sdk'

import { Story, Meta } from '@storybook/react/types-6-0'
import BigNumber from 'bignumber.js'
import { ZERO_BIG_NUMBER } from 'const'
import { GlobalStyles, ThemeToggler } from 'storybook/decorators'

import { RICH_ORDER, WETH } from '../../../test/data'
import { ProtocolFeeType } from 'api/operator'

import { RICH_ORDER, USDT, WETH } from '../../../test/data'

import { GasFeeDisplay, Props } from '.'

Expand All @@ -22,28 +25,59 @@ const Template: Story<Props> = (args) => (
</div>
)

const order = {
...RICH_ORDER,
feeAmount: new BigNumber('200000'),
executedFeeAmount: ZERO_BIG_NUMBER,
}

const defaultProps: Props = { order }

export const NoFee = Template.bind({})
NoFee.args = { ...defaultProps }
// On-chain gas cost (native token wei) — its presence switches on the costs & fees breakdown.
const GAS_COST = new BigNumber('2500000000000000')

export const PartialFee = Template.bind({})
PartialFee.args = { ...defaultProps, order: { ...order, executedFeeAmount: new BigNumber('100000') } }
// No recorded gas cost -> legacy display of the combined executed fee in the sell token.
export const LegacyNoGasCost = Template.bind({})
LegacyNoGasCost.args = { order: { ...RICH_ORDER, gasCost: undefined } }

export const FullFee = Template.bind({})
FullFee.args = { ...defaultProps, order: { ...order, executedFeeAmount: order.feeAmount, fullyFilled: true } }
// Gas cost present but no protocol fees -> breakdown with just the Network costs line.
export const NetworkCostsOnly = Template.bind({})
NetworkCostsOnly.args = { order: { ...RICH_ORDER, gasCost: GAS_COST, protocolFees: [] } }

export const TinyFee6DecimalsToken = Template.bind({})
TinyFee6DecimalsToken.args = { ...defaultProps, order: { ...order, executedFeeAmount: new BigNumber('1') } }
// Network costs + the protocol's own fee (the first applied fee, position 0).
export const ProtocolFee = Template.bind({})
ProtocolFee.args = {
order: {
...RICH_ORDER,
gasCost: GAS_COST,
protocolFees: [
{
amount: new BigNumber('1166200'),
tokenAddress: getAddressKey(USDT.address),
type: ProtocolFeeType.Volume,
position: 0,
},
],
},
}

export const TinyFee18DecimalsToken = Template.bind({})
TinyFee18DecimalsToken.args = {
...defaultProps,
order: { ...order, executedFeeAmount: new BigNumber('1'), sellToken: WETH },
// Network costs + protocol fee + two partner fees (numbered by applied order).
export const ProtocolAndPartnerFees = Template.bind({})
ProtocolAndPartnerFees.args = {
order: {
...RICH_ORDER,
gasCost: GAS_COST,
protocolFees: [
{
amount: new BigNumber('1166200'),
tokenAddress: getAddressKey(USDT.address),
type: ProtocolFeeType.Volume,
position: 0,
},
{
amount: new BigNumber('800000'),
tokenAddress: getAddressKey(USDT.address),
type: ProtocolFeeType.Volume,
position: 1,
},
{
amount: new BigNumber('50000000000000000'),
tokenAddress: getAddressKey(WETH.address),
type: ProtocolFeeType.PriceImprovement,
position: 2,
},
],
},
}
Loading
Loading