Skip to content

Commit

Permalink
feat: propagate chain adapter errors (#8258)
Browse files Browse the repository at this point in the history
Co-authored-by: woody <125113430+woodenfurniture@users.noreply.github.com>
  • Loading branch information
kaladinlight and woodenfurniture authored Dec 4, 2024
1 parent 35c005c commit 9918e89
Show file tree
Hide file tree
Showing 5 changed files with 178 additions and 123 deletions.
85 changes: 49 additions & 36 deletions packages/chain-adapters/src/evm/base/BaseChainAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { KnownChainIds } from '@shapeshiftoss/types'
import * as unchained from '@shapeshiftoss/unchained-client'
import BigNumber from 'bignumber.js'

import { ErrorHandler } from '../../error/ErrorHandler'
import type { FeeDataEstimate, GetFeeDataInput } from '../../types'
import { ChainAdapterDisplayName } from '../../types'
import { bnOrZero } from '../../utils'
Expand Down Expand Up @@ -64,48 +65,60 @@ export class ChainAdapter extends EvmBaseAdapter<KnownChainIds.BaseMainnet> {
}

async getGasFeeData(): Promise<GasFeeDataEstimate> {
const { fast, average, slow, l1GasPrice } = await this.api.getGasFees()

return {
fast: { ...fast, l1GasPrice },
average: { ...average, l1GasPrice },
slow: { ...slow, l1GasPrice },
try {
const { fast, average, slow, l1GasPrice } = await this.api.getGasFees()

return {
fast: { ...fast, l1GasPrice },
average: { ...average, l1GasPrice },
slow: { ...slow, l1GasPrice },
}
} catch (err) {
return ErrorHandler(err, {
translation: 'chainAdapters.errors.getGasFeeData',
})
}
}

async getFeeData(
input: GetFeeDataInput<KnownChainIds.BaseMainnet>,
): Promise<FeeDataEstimate<KnownChainIds.BaseMainnet>> {
const req = await this.buildEstimateGasRequest(input)

const { gasLimit, l1GasLimit } = await this.api.estimateGas(req)
const { fast, average, slow } = await this.getGasFeeData()

return {
fast: {
txFee: bnOrZero(
BigNumber.max(fast.gasPrice, fast.maxFeePerGas ?? 0)
.times(gasLimit)
.plus(bnOrZero(fast.l1GasPrice).times(l1GasLimit)),
).toFixed(0),
chainSpecific: { gasLimit, l1GasLimit, ...fast },
},
average: {
txFee: bnOrZero(
BigNumber.max(average.gasPrice, average.maxFeePerGas ?? 0)
.times(gasLimit)
.plus(bnOrZero(average.l1GasPrice).times(l1GasLimit)),
).toFixed(0),
chainSpecific: { gasLimit, l1GasLimit, ...average },
},
slow: {
txFee: bnOrZero(
BigNumber.max(slow.gasPrice, slow.maxFeePerGas ?? 0)
.times(gasLimit)
.plus(bnOrZero(slow.l1GasPrice).times(l1GasLimit)),
).toFixed(0),
chainSpecific: { gasLimit, l1GasLimit, ...slow },
},
try {
const req = await this.buildEstimateGasRequest(input)

const { gasLimit, l1GasLimit } = await this.api.estimateGas(req)
const { fast, average, slow } = await this.getGasFeeData()

return {
fast: {
txFee: bnOrZero(
BigNumber.max(fast.gasPrice, fast.maxFeePerGas ?? 0)
.times(gasLimit)
.plus(bnOrZero(fast.l1GasPrice).times(l1GasLimit)),
).toFixed(0),
chainSpecific: { gasLimit, l1GasLimit, ...fast },
},
average: {
txFee: bnOrZero(
BigNumber.max(average.gasPrice, average.maxFeePerGas ?? 0)
.times(gasLimit)
.plus(bnOrZero(average.l1GasPrice).times(l1GasLimit)),
).toFixed(0),
chainSpecific: { gasLimit, l1GasLimit, ...average },
},
slow: {
txFee: bnOrZero(
BigNumber.max(slow.gasPrice, slow.maxFeePerGas ?? 0)
.times(gasLimit)
.plus(bnOrZero(slow.l1GasPrice).times(l1GasLimit)),
).toFixed(0),
chainSpecific: { gasLimit, l1GasLimit, ...slow },
},
}
} catch (err) {
return ErrorHandler(err, {
translation: 'chainAdapters.errors.getFeeData',
})
}
}
}
107 changes: 60 additions & 47 deletions packages/chain-adapters/src/evm/bnbsmartchain/BscChainAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { KnownChainIds } from '@shapeshiftoss/types'
import * as unchained from '@shapeshiftoss/unchained-client'
import BigNumber from 'bignumber.js'

import { ErrorHandler } from '../../error/ErrorHandler'
import type { FeeDataEstimate, GetFeeDataInput } from '../../types'
import { ChainAdapterDisplayName } from '../../types'
import { bn, bnOrZero } from '../../utils/bignumber'
Expand Down Expand Up @@ -63,57 +64,69 @@ export class ChainAdapter extends EvmBaseAdapter<KnownChainIds.BnbSmartChainMain
}

async getGasFeeData(): Promise<GasFeeDataEstimate & { baseFeePerGas?: string }> {
const { fast, average, slow, baseFeePerGas } = await this.providers.http.getGasFees()
return { fast, average, slow, baseFeePerGas }
try {
const { fast, average, slow, baseFeePerGas } = await this.providers.http.getGasFees()
return { fast, average, slow, baseFeePerGas }
} catch (err) {
return ErrorHandler(err, {
translation: 'chainAdapters.errors.getGasFeeData',
})
}
}

async getFeeData(
input: GetFeeDataInput<KnownChainIds.BnbSmartChainMainnet>,
): Promise<FeeDataEstimate<KnownChainIds.BnbSmartChainMainnet>> {
const req = await this.buildEstimateGasRequest(input)

const { gasLimit } = await this.providers.http.estimateGas(req)
const { fast, average, slow, baseFeePerGas } = await this.getGasFeeData()

// Binance official JSON-RPC endpoint has a minimum enforced gas price of 3 Gwei
const MIN_GAS_PRICE = '3000000000'

;[fast, average, slow].forEach(estimate => {
estimate.gasPrice = BigNumber.max(estimate.gasPrice, MIN_GAS_PRICE).toFixed(0)

if (estimate.maxFeePerGas) {
estimate.maxFeePerGas = BigNumber.max(estimate.maxFeePerGas, MIN_GAS_PRICE).toFixed(0)
}

if (estimate.maxPriorityFeePerGas) {
estimate.maxPriorityFeePerGas = BigNumber.max(
bn(estimate.maxPriorityFeePerGas).plus(bnOrZero(baseFeePerGas)),
MIN_GAS_PRICE,
)
.minus(bnOrZero(baseFeePerGas))
.toFixed(0)
}
})

return {
fast: {
txFee: bnOrZero(
BigNumber.max(fast.gasPrice, fast.maxFeePerGas ?? 0).times(gasLimit),
).toFixed(0),
chainSpecific: { gasLimit, ...fast, gasPrice: fast.gasPrice },
},
average: {
txFee: bnOrZero(
BigNumber.max(average.gasPrice, average.maxFeePerGas ?? 0).times(gasLimit),
).toFixed(0),
chainSpecific: { gasLimit, ...average, gasPrice: average.gasPrice },
},
slow: {
txFee: bnOrZero(
BigNumber.max(slow.gasPrice, slow.maxFeePerGas ?? 0).times(gasLimit),
).toFixed(0),
chainSpecific: { gasLimit, ...slow, gasPrice: slow.gasPrice },
},
} as FeeDataEstimate<KnownChainIds.BnbSmartChainMainnet>
try {
const req = await this.buildEstimateGasRequest(input)

const { gasLimit } = await this.providers.http.estimateGas(req)
const { fast, average, slow, baseFeePerGas } = await this.getGasFeeData()

// Binance official JSON-RPC endpoint has a minimum enforced gas price of 3 Gwei
const MIN_GAS_PRICE = '3000000000'

;[fast, average, slow].forEach(estimate => {
estimate.gasPrice = BigNumber.max(estimate.gasPrice, MIN_GAS_PRICE).toFixed(0)

if (estimate.maxFeePerGas) {
estimate.maxFeePerGas = BigNumber.max(estimate.maxFeePerGas, MIN_GAS_PRICE).toFixed(0)
}

if (estimate.maxPriorityFeePerGas) {
estimate.maxPriorityFeePerGas = BigNumber.max(
bn(estimate.maxPriorityFeePerGas).plus(bnOrZero(baseFeePerGas)),
MIN_GAS_PRICE,
)
.minus(bnOrZero(baseFeePerGas))
.toFixed(0)
}
})

return {
fast: {
txFee: bnOrZero(
BigNumber.max(fast.gasPrice, fast.maxFeePerGas ?? 0).times(gasLimit),
).toFixed(0),
chainSpecific: { gasLimit, ...fast, gasPrice: fast.gasPrice },
},
average: {
txFee: bnOrZero(
BigNumber.max(average.gasPrice, average.maxFeePerGas ?? 0).times(gasLimit),
).toFixed(0),
chainSpecific: { gasLimit, ...average, gasPrice: average.gasPrice },
},
slow: {
txFee: bnOrZero(
BigNumber.max(slow.gasPrice, slow.maxFeePerGas ?? 0).times(gasLimit),
).toFixed(0),
chainSpecific: { gasLimit, ...slow, gasPrice: slow.gasPrice },
},
} as FeeDataEstimate<KnownChainIds.BnbSmartChainMainnet>
} catch (err) {
return ErrorHandler(err, {
translation: 'chainAdapters.errors.getFeeData',
})
}
}
}
85 changes: 49 additions & 36 deletions packages/chain-adapters/src/evm/optimism/OptimismChainAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { KnownChainIds } from '@shapeshiftoss/types'
import * as unchained from '@shapeshiftoss/unchained-client'
import BigNumber from 'bignumber.js'

import { ErrorHandler } from '../../error/ErrorHandler'
import type { FeeDataEstimate, GetFeeDataInput } from '../../types'
import { ChainAdapterDisplayName } from '../../types'
import { bnOrZero } from '../../utils'
Expand Down Expand Up @@ -66,48 +67,60 @@ export class ChainAdapter extends EvmBaseAdapter<KnownChainIds.OptimismMainnet>
}

async getGasFeeData(): Promise<GasFeeDataEstimate> {
const { fast, average, slow, l1GasPrice } = await this.api.getGasFees()

return {
fast: { ...fast, l1GasPrice },
average: { ...average, l1GasPrice },
slow: { ...slow, l1GasPrice },
try {
const { fast, average, slow, l1GasPrice } = await this.api.getGasFees()

return {
fast: { ...fast, l1GasPrice },
average: { ...average, l1GasPrice },
slow: { ...slow, l1GasPrice },
}
} catch (err) {
return ErrorHandler(err, {
translation: 'chainAdapters.errors.getGasFeeData',
})
}
}

async getFeeData(
input: GetFeeDataInput<KnownChainIds.OptimismMainnet>,
): Promise<FeeDataEstimate<KnownChainIds.OptimismMainnet>> {
const req = await this.buildEstimateGasRequest(input)

const { gasLimit, l1GasLimit } = await this.api.estimateGas(req)
const { fast, average, slow } = await this.getGasFeeData()

return {
fast: {
txFee: bnOrZero(
BigNumber.max(fast.gasPrice, fast.maxFeePerGas ?? 0)
.times(gasLimit)
.plus(bnOrZero(fast.l1GasPrice).times(l1GasLimit)),
).toFixed(0),
chainSpecific: { gasLimit, l1GasLimit, ...fast },
},
average: {
txFee: bnOrZero(
BigNumber.max(average.gasPrice, average.maxFeePerGas ?? 0)
.times(gasLimit)
.plus(bnOrZero(average.l1GasPrice).times(l1GasLimit)),
).toFixed(0),
chainSpecific: { gasLimit, l1GasLimit, ...average },
},
slow: {
txFee: bnOrZero(
BigNumber.max(slow.gasPrice, slow.maxFeePerGas ?? 0)
.times(gasLimit)
.plus(bnOrZero(slow.l1GasPrice).times(l1GasLimit)),
).toFixed(0),
chainSpecific: { gasLimit, l1GasLimit, ...slow },
},
try {
const req = await this.buildEstimateGasRequest(input)

const { gasLimit, l1GasLimit } = await this.api.estimateGas(req)
const { fast, average, slow } = await this.getGasFeeData()

return {
fast: {
txFee: bnOrZero(
BigNumber.max(fast.gasPrice, fast.maxFeePerGas ?? 0)
.times(gasLimit)
.plus(bnOrZero(fast.l1GasPrice).times(l1GasLimit)),
).toFixed(0),
chainSpecific: { gasLimit, l1GasLimit, ...fast },
},
average: {
txFee: bnOrZero(
BigNumber.max(average.gasPrice, average.maxFeePerGas ?? 0)
.times(gasLimit)
.plus(bnOrZero(average.l1GasPrice).times(l1GasLimit)),
).toFixed(0),
chainSpecific: { gasLimit, l1GasLimit, ...average },
},
slow: {
txFee: bnOrZero(
BigNumber.max(slow.gasPrice, slow.maxFeePerGas ?? 0)
.times(gasLimit)
.plus(bnOrZero(slow.l1GasPrice).times(l1GasLimit)),
).toFixed(0),
chainSpecific: { gasLimit, l1GasLimit, ...slow },
},
}
} catch (err) {
return ErrorHandler(err, {
translation: 'chainAdapters.errors.getFeeData',
})
}
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { StdSignDoc } from '@keplr-wallet/types'
import { bchAssetId, CHAIN_NAMESPACE, fromChainId } from '@shapeshiftoss/caip'
import type { evm, SignTx, SignTypedDataInput } from '@shapeshiftoss/chain-adapters'
import { toAddressNList } from '@shapeshiftoss/chain-adapters'
import { ChainAdapterError, toAddressNList } from '@shapeshiftoss/chain-adapters'
import type {
BTCSignTx,
ETHSignTypedData,
Expand Down Expand Up @@ -132,7 +132,13 @@ export const useTradeExecution = (
dispatch(tradeQuoteSlice.actions.setSwapTxPending({ hopIndex, id: confirmedTradeId }))

const onFail = (e: unknown) => {
const { message } = (e ?? { message: undefined }) as { message?: string }
const message = (() => {
if (e instanceof ChainAdapterError) {
return translate(e.metadata.translation, e.metadata.options)
}
return (e as Error).message ?? undefined
})()

dispatch(
tradeQuoteSlice.actions.setSwapTxMessage({ hopIndex, message, id: confirmedTradeId }),
)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,30 @@
import { useToast } from '@chakra-ui/react'
import { ChainAdapterError } from '@shapeshiftoss/chain-adapters'
import { useCallback } from 'react'
import { useTranslate } from 'react-polyglot'
import { InlineCopyButton } from 'components/InlineCopyButton'
import { RawText } from 'components/Text'

export const useErrorHandler = () => {
const toast = useToast()
const translate = useTranslate()

const showErrorToast = useCallback(
(error: unknown) => {
const description = translate('common.generalError')
const description =
error instanceof ChainAdapterError
? translate(error.metadata.translation, error.metadata.options)
: translate('common.generalError')

console.error(error)

toast({
title: translate('trade.errors.title'),
description,
description: (
<InlineCopyButton value={description}>
<RawText>{description}</RawText>
</InlineCopyButton>
),
status: 'error',
duration: 9000,
isClosable: true,
Expand Down

0 comments on commit 9918e89

Please sign in to comment.