Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
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
1 change: 0 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ env:
REACT_APP_PINATA_API_KEY: ${{ secrets.REACT_APP_PINATA_API_KEY }}
REACT_APP_PINATA_SECRET_API_KEY: ${{ secrets.REACT_APP_PINATA_SECRET_API_KEY }}
REACT_APP_GOOGLE_ANALYTICS_ID: ${{ secrets.REACT_APP_GOOGLE_ANALYTICS_ID }}
REACT_APP_BLOCKNATIVE_API_KEY: ${{ secrets.REACT_APP_BLOCKNATIVE_API_KEY }}
REACT_APP_BFF_BASE_URL: ${{ secrets.BFF_BASE_URL }}
REACT_APP_BALANCES_WATCHER_BASE_URL: ${{ secrets.BALANCES_WATCHER_BASE_URL }}
REACT_APP_CMS_BASE_URL: ${{ secrets.CMS_BASE_URL }}
Expand Down
1 change: 0 additions & 1 deletion .github/workflows/ipfs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ env:
REACT_APP_PINATA_API_KEY: ${{ secrets.REACT_APP_PINATA_API_KEY }}
REACT_APP_PINATA_SECRET_API_KEY: ${{ secrets.REACT_APP_PINATA_SECRET_API_KEY }}
REACT_APP_GOOGLE_ANALYTICS_ID: ${{ secrets.REACT_APP_GOOGLE_ANALYTICS_ID }}
REACT_APP_BLOCKNATIVE_API_KEY: ${{ secrets.REACT_APP_BLOCKNATIVE_API_KEY }}
# Duplicated keys as these are required by `ipfs-deploy`
IPFS_DEPLOY_PINATA__API_KEY: ${{ secrets.REACT_APP_PINATA_API_KEY }}
IPFS_DEPLOY_PINATA__SECRET_API_KEY: ${{ secrets.REACT_APP_PINATA_SECRET_API_KEY }}
Expand Down
1 change: 1 addition & 0 deletions apps/cowswap-frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@
"@tanstack/react-virtual": "3.13.12",
"@uniswap/token-lists": "1.0.0-beta.33",
"@use-gesture/react": "10.3.1",
"@wagmi/core": "3.4.8",
"bignumber.js": "9.1.2",
"buffer": "6.0.3",
"color2k": "2.0.2",
Expand Down
110 changes: 41 additions & 69 deletions apps/cowswap-frontend/src/api/gasPrices/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { GAS_API_KEYS, GAS_FEE_ENDPOINTS } from '@cowprotocol/common-const'
import { SupportedChainId as ChainId } from '@cowprotocol/cow-sdk'
import { GAS_FEE_ENDPOINTS } from '@cowprotocol/common-const'
import { isEvmChain, SupportedChainId as ChainId } from '@cowprotocol/cow-sdk'
import { reownWagmiConfig } from '@cowprotocol/wallet'

import { getGasPrice } from '@wagmi/core'

import { fetchWithRateLimit } from 'common/utils/fetch'

Expand All @@ -20,45 +23,20 @@ export type GasFeeEndpointResponse = {
slow: string | null
}

export interface EstimatedPrice {
confidence: number
price: number
maxFeePerGas: number
maxPriorityFeePerGas: number
}

type GasPrices = Omit<GasFeeEndpointResponse, 'lastUpdate'>

// Here the key is what we use in gas state and the value is confidence level
// coming from the Blockscout api, so we map state values with confidence lvls
const priceMap: GasPrices = {
fast: '99',
average: '90',
slow: '70',
}
// The Blockscout gas price oracle returns prices (floats in gwei) for these confidence levels
const PRICE_KEYS: Array<keyof GasPrices> = ['fast', 'average', 'slow']

class GasFeeApi {
getUrl(chainId: ChainId): string {
return GAS_FEE_ENDPOINTS[chainId]
}

supportedChain(chainId: ChainId): boolean {
return !!GAS_FEE_ENDPOINTS[chainId]
}

getHeaders(chainId: ChainId): { headers?: Headers } {
// TODO: Replace any with proper type definitions
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const headers: { [key: string]: any } = {}
const apiKey = GAS_API_KEYS[chainId]

if (apiKey) {
headers.headers = new Headers({
Authorization: apiKey,
})
}

return headers
// Gas prices come either from the Blockscout oracle or, as a fallback, from the
// eth_gasPrice RPC method - both of which only apply to EVM chains.
return isEvmChain(chainId)
}

toWei(input: number | null): string | null {
Expand All @@ -69,61 +47,55 @@ class GasFeeApi {
return Math.floor(input * ONE_GWEI).toString()
}

getBlocknativePrice(data: EstimatedPrice[], lvl: string | null): number | null {
if (!data || !lvl) {
return null
}

const price = data.find(({ confidence }: EstimatedPrice) => lvl === String(confidence))?.price

return price || null
}

// TODO: Replace any with proper type definitions
// eslint-disable-next-line @typescript-eslint/no-explicit-any
parseData(json: any, chainId: ChainId): GasFeeEndpointResponse {
// Parse data from the Blockscout gas price oracle, e.g. { "slow": 0.16, "average": 0.44, "fast": 2.06 }
parseData(json: Record<keyof GasPrices, number>): GasFeeEndpointResponse {
const output: GasFeeEndpointResponse = {
lastUpdate: new Date().toISOString(),
fast: null,
average: null,
slow: null,
}

if (this.getUrl(chainId).match(/blocknative/)) {
// Parse data from Blocknative
const prices = json?.blockPrices[0]?.estimatedPrices

if (prices) {
for (const [key, value] of Object.entries(priceMap)) {
const price = this.getBlocknativePrice(prices, value)
output[key as keyof GasPrices] = this.toWei(price)
}
}
} else {
// Parse data from Blockscout
for (const key of Object.keys(priceMap)) {
output[key as keyof GasPrices] = this.toWei(json[key])
}
for (const key of PRICE_KEYS) {
output[key] = this.toWei(json[key])
}

return output
}

// TODO: Add proper return type annotation
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
async fetchData(chainId: ChainId) {
const url = this.getUrl(chainId)
const headers = this.getHeaders(chainId)
const response = await fetchRateLimited(url, headers)
async fetchData(url: string): Promise<Record<keyof GasPrices, number>> {
const response = await fetchRateLimited(url)

return response.json()
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}

// Fallback used when there is no gas price oracle endpoint for the network,
// or when the request to it failed. Returns the same value for all confidence levels.
async getGasPriceFromRpc(chainId: ChainId): Promise<GasFeeEndpointResponse> {
const gasPrice = (await getGasPrice(reownWagmiConfig, { chainId })).toString()

return {
lastUpdate: new Date().toISOString(),
fast: gasPrice,
average: gasPrice,
slow: gasPrice,
}
}

async getGasPrices(chainId: ChainId = ChainId.MAINNET): Promise<GasFeeEndpointResponse> {
const data = await this.fetchData(chainId)
const parsed = this.parseData(data, chainId)
const url = this.getUrl(chainId)

if (url) {
try {
const data = await this.fetchData(url)

return this.parseData(data)
} catch (error) {
console.error('[gasFeeApi] Failed to fetch gas prices from the oracle, falling back to RPC', error)
}
}

return parsed
return this.getGasPriceFromRpc(chainId)
}
}

Expand Down
100 changes: 0 additions & 100 deletions apps/cowswap-frontend/src/common/updaters/CancelReplaceTxUpdater.tsx

This file was deleted.

2 changes: 1 addition & 1 deletion apps/cowswap-frontend/src/cow-react/sentry/beforeSend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ function isFetchError(breadcrumb: Sentry.Breadcrumb): boolean {
}

const URLS_TO_IGNORE_FETCH_ERRORS =
/(twnodes\.com)|(assets\/cow-no-connection)|(api\.blocknative\.com)|(api\.country\.is)|(nodereal\.io)|(wallet\.coinbase\.com)|(cowprotocol\/cowswap-banner)/i
/(twnodes\.com)|(assets\/cow-no-connection)|(blockscout\.com)|(api\.country\.is)|(nodereal\.io)|(wallet\.coinbase\.com)|(cowprotocol\/cowswap-banner)/i

function isMetamaskRpcError(breadcrumb: Sentry.Breadcrumb): boolean {
if (breadcrumb.level !== 'error' || !breadcrumb.message) {
Expand Down
33 changes: 10 additions & 23 deletions libs/common-const/src/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,34 +125,21 @@ export const TWITTER_LINK = 'https://twitter.com/CoWSwap'
// TODO: test gas prices for all networks
export const GAS_PRICE_UPDATE_THRESHOLD = ms`5s`

// See https://docs.blocknative.com/gas-prediction/gas-platform
// Blockscout gas price oracle: https://docs.blockscout.com/devs/apis/rpc/stats-and-info#gas-price-oracle
// Networks without a Blockscout instance fall back to the eth_gasPrice RPC method (see GasFeeApi).
export const GAS_FEE_ENDPOINTS: Record<SupportedChainId, string> = {
[SupportedChainId.MAINNET]: 'https://api.blocknative.com/gasprices/blockprices',
[SupportedChainId.MAINNET]: 'https://eth.blockscout.com/api/v1/gas-price-oracle',
[SupportedChainId.GNOSIS_CHAIN]: 'https://gnosis.blockscout.com/api/v1/gas-price-oracle',
[SupportedChainId.ARBITRUM_ONE]: 'https://arbitrum.blockscout.com/api/v1/gas-price-oracle',
[SupportedChainId.BASE]: 'https://base.blockscout.com/api/v1/gas-price-oracle',
[SupportedChainId.SEPOLIA]: '',
[SupportedChainId.POLYGON]: 'https://polygon.blockscout.com/api/v1/gas-price-oracle',
[SupportedChainId.AVALANCHE]: `https://api.blocknative.com/gasprices/blockprices?chainid=${SupportedChainId.AVALANCHE}`,
[SupportedChainId.BNB]: `https://api.blocknative.com/gasprices/blockprices?chainid=${SupportedChainId.BNB}`,
[SupportedChainId.LINEA]: `https://api.blocknative.com/gasprices/blockprices?chainid=${SupportedChainId.LINEA}`,
[SupportedChainId.PLASMA]: '', // TODO: currently (2025/10/20) unsupported by Blocknative nor blockscont
[SupportedChainId.INK]: `https://api.blocknative.com/gasprices/blockprices?chainid=${SupportedChainId.INK}`,
[SupportedChainId.SOLANA]: '', // Solana fee model is different (no gas price oracle).
}
export const GAS_API_KEYS: Record<SupportedChainId, string | null> = {
[SupportedChainId.MAINNET]: process.env['REACT_APP_BLOCKNATIVE_API_KEY'] || null,
[SupportedChainId.GNOSIS_CHAIN]: null,
[SupportedChainId.ARBITRUM_ONE]: null,
[SupportedChainId.BASE]: null,
[SupportedChainId.SEPOLIA]: null,
[SupportedChainId.POLYGON]: null,
[SupportedChainId.AVALANCHE]: process.env['REACT_APP_BLOCKNATIVE_API_KEY'] || null,
[SupportedChainId.BNB]: process.env['REACT_APP_BLOCKNATIVE_API_KEY'] || null,
[SupportedChainId.LINEA]: process.env['REACT_APP_BLOCKNATIVE_API_KEY'] || null,
[SupportedChainId.PLASMA]: null,
[SupportedChainId.INK]: process.env['REACT_APP_BLOCKNATIVE_API_KEY'] || null,
[SupportedChainId.SOLANA]: null,
[SupportedChainId.SEPOLIA]: '',
[SupportedChainId.AVALANCHE]: '',
[SupportedChainId.BNB]: '',
[SupportedChainId.LINEA]: '',
[SupportedChainId.PLASMA]: '',
[SupportedChainId.INK]: '',
[SupportedChainId.SOLANA]: '',
}

export const UNSUPPORTED_TOKENS_FAQ_URL = 'https://docs.cow.fi/cow-protocol/reference/core/tokens'
Expand Down
1 change: 1 addition & 0 deletions libs/wallet/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export * from './api/utils/connection'
// Connectors and providers
export { WalletProvider } from './api/container/WalletProvider'
export { Web3Provider } from './wagmi/Web3Provider'
export { reownWagmiConfig } from './reown/init'

// State
// TODO: this export is discussable, however it's already used outside
Expand Down
Loading
Loading