Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: swapper no-non-null-asserts #8919

Open
wants to merge 4 commits into
base: feat_non_null_assert_2
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,11 @@ export const arbitrumBridgeApi: SwapperApi = {

return tradeQuoteResult.map(tradeQuote => {
const id = tradeQuote.id
const firstHop = getHopByIndex(tradeQuote, 0)!
const firstHop = getHopByIndex(tradeQuote, 0)
if (!firstHop) {
console.error('No first hop found in trade quote')
return []
}
tradeQuoteMetadata.set(id, {
sellAssetId: firstHop.sellAsset.assetId,
chainId: firstHop.sellAsset.chainId as EvmChainId,
Expand All @@ -111,7 +115,12 @@ export const arbitrumBridgeApi: SwapperApi = {

return tradeRateResult.map(tradeQuote => {
const id = tradeQuote.id
const firstHop = getHopByIndex(tradeQuote, 0)!
const firstHop = getHopByIndex(tradeQuote, 0)
if (!firstHop) {
console.error('No first hop found in trade rate')
return []
}

tradeQuoteMetadata.set(id, {
sellAssetId: firstHop.sellAsset.assetId,
chainId: firstHop.sellAsset.chainId as EvmChainId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,10 @@ const fetchArbitrumBridgeSwap = async <T extends 'quote' | 'rate'>({
parentProvider,
childProvider,
amount: BigNumber.from(sellAmountIncludingProtocolFeesCryptoBaseUnit),
from: sendAddress!,
destinationAddress: receiveAddress!,
// This is actually guarded against in if `(quoteOrRate === 'quote' && !sendAddress)` above
from: sendAddress ?? '',
// This is actually guarded against in if `(quoteOrRate === 'quote' && !receiveAddress)` above
destinationAddress: receiveAddress ?? '',
})
.catch(e => {
console.error('Error getting ETH deposit request', e)
Expand All @@ -125,8 +127,10 @@ const fetchArbitrumBridgeSwap = async <T extends 'quote' | 'rate'>({
? await bridger
.getWithdrawalRequest({
amount: BigNumber.from(sellAmountIncludingProtocolFeesCryptoBaseUnit),
from: sendAddress!,
destinationAddress: receiveAddress!,
// This is actually guarded against in if `(quoteOrRate === 'quote' && !sendAddress)` above
from: sendAddress ?? '',
// This is actually guarded against in if `(quoteOrRate === 'quote' && !receiveAddress)` above
destinationAddress: receiveAddress ?? '',
})
.catch(e => {
console.error('Error getting ETH withdraw request', e)
Expand Down Expand Up @@ -159,8 +163,9 @@ const fetchArbitrumBridgeSwap = async <T extends 'quote' | 'rate'>({
parentProvider,
childProvider,
erc20ParentAddress,
from: sendAddress!,
destinationAddress: receiveAddress!,
// This is actually guarded against in if `(quoteOrRate === 'quote' && !sendAddress)` above
from: sendAddress ?? '',
destinationAddress: receiveAddress,
retryableGasOverrides: {
// https://github.com/OffchainLabs/arbitrum-token-bridge/blob/d17c88ef3eef3f4ffc61a04d34d50406039f045d/packages/arb-token-bridge-ui/src/util/TokenDepositUtils.ts#L159
// the gas limit may vary by about 20k due to SSTORE (zero vs nonzero)
Expand Down Expand Up @@ -193,8 +198,10 @@ const fetchArbitrumBridgeSwap = async <T extends 'quote' | 'rate'>({
.getWithdrawalRequest({
amount: BigNumber.from(sellAmountIncludingProtocolFeesCryptoBaseUnit),
erc20ParentAddress,
from: sendAddress!,
destinationAddress: receiveAddress!,
// This is actually guarded against in if `(quoteOrRate === 'quote' && !sendAddress)` above
from: sendAddress ?? '',
// This is actually guarded against in if `(quoteOrRate === 'quote' && !receiveAddress)` above
destinationAddress: receiveAddress ?? '',
})
.catch(e => {
console.error('Error getting ERC20 withdraw request', e)
Expand Down
12 changes: 7 additions & 5 deletions packages/swapper/src/swappers/ChainflipSwapper/endpoints.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,10 +73,12 @@ export const chainflipApi: SwapperApi = {
chainSpecific: {
gasLimit: fees.chainSpecific.gasLimit,
contractAddress: isTokenSend ? assetReference : undefined,
...(supportsEIP1559
...(supportsEIP1559 &&
fees.chainSpecific.maxFeePerGas &&
fees.chainSpecific.maxPriorityFeePerGas
? {
maxFeePerGas: fees.chainSpecific.maxFeePerGas!,
maxPriorityFeePerGas: fees.chainSpecific.maxPriorityFeePerGas!,
maxFeePerGas: fees.chainSpecific.maxFeePerGas,
maxPriorityFeePerGas: fees.chainSpecific.maxPriorityFeePerGas,
}
: {
gasPrice: fees.chainSpecific.gasPrice,
Expand Down Expand Up @@ -150,7 +152,7 @@ export const chainflipApi: SwapperApi = {

return adapter.buildSendApiTransaction({
value: step.sellAmountIncludingProtocolFeesCryptoBaseUnit,
xpub: xpub!,
xpub,
to: step.chainflipSpecific.chainflipDepositAddress,
accountNumber: step.accountNumber,
skipToAddressValidation: true,
Expand All @@ -177,7 +179,7 @@ export const chainflipApi: SwapperApi = {
to: step.chainflipSpecific.chainflipDepositAddress,
value: step.sellAmountIncludingProtocolFeesCryptoBaseUnit,
chainSpecific: {
pubkey: xpub!,
pubkey: xpub,
},
sendMax: false,
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { CHAIN_NAMESPACE, fromAssetId, solAssetId } from '@shapeshiftoss/caip'
import type { GetFeeDataInput } from '@shapeshiftoss/chain-adapters'
import type { KnownChainIds } from '@shapeshiftoss/types'
import { isSome } from '@shapeshiftoss/utils'
import type { Result } from '@sniptt/monads'
import { Err, Ok } from '@sniptt/monads'
import type { AxiosError } from 'axios'
Expand Down Expand Up @@ -103,8 +104,8 @@ export const getTradeQuote = async (
sourceAsset,
minimumPrice,
destinationAsset,
destinationAddress: input.receiveAddress,
refundAddress: input.sendAddress!,
destinationAddress: receiveAddress,
refundAddress: sendAddress,
maxBoostFee: step.chainflipSpecific?.chainflipMaxBoostFee,
numberOfChunks: step.chainflipSpecific?.chainflipNumberOfChunks,
chunkIntervalBlocks: step.chainflipSpecific?.chainflipChunkIntervalBlocks,
Expand All @@ -114,7 +115,7 @@ export const getTradeQuote = async (
if (maybeSwapResponse.isErr()) {
const error = maybeSwapResponse.unwrapErr()
const cause = error.cause as AxiosError<any, any>
throw Error(cause.response!.data.detail)
throw Error(cause.response?.data.detail)
}

const { data: swapResponse } = maybeSwapResponse.unwrap()
Expand All @@ -135,7 +136,7 @@ export const getTradeQuote = async (
to: depositAddress,
value: sellAmount,
chainSpecific: {
from: input.sendAddress!,
from: sendAddress,
tokenId:
sellAsset.assetId === solAssetId
? undefined
Expand Down Expand Up @@ -169,11 +170,21 @@ export const getTradeQuote = async (
const quotesResult = Ok(tradeQuotes)

return quotesResult.map(quotes =>
quotes.map(quote => ({
...quote,
quoteOrRate: 'quote' as const,
receiveAddress: receiveAddress!,
steps: quote.steps.map(step => step) as [TradeQuoteStep] | [TradeQuoteStep, TradeQuoteStep],
})),
quotes
.map(quote => {
if (!quote.receiveAddress) {
console.error('receiveAddress is required')
return undefined
}
return {
...quote,
quoteOrRate: 'quote' as const,
receiveAddress,
steps: quote.steps.map(step => step) as
| [TradeQuoteStep]
| [TradeQuoteStep, TradeQuoteStep],
}
})
.filter(isSome),
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -128,12 +128,12 @@ export const getQuoteOrRate = async (

if (
cause.message.includes('code 400') &&
cause.response!.data.detail.includes('Amount outside asset bounds')
cause.response?.data.detail.includes('Amount outside asset bounds')
) {
return Err(
createTradeAmountTooSmallErr({
assetId: sellAsset.assetId,
minAmountCryptoBaseUnit: cause.response!.data.errors.minimalAmountNative[0],
minAmountCryptoBaseUnit: cause.response?.data.errors.minimalAmountNative[0],
}),
)
}
Expand Down Expand Up @@ -215,10 +215,13 @@ export const getQuoteOrRate = async (
const getProtocolFees = (singleQuoteResponse: ChainflipBaasQuoteQuote) => {
const protocolFees: Record<AssetId, ProtocolFee> = {}

for (const fee of singleQuoteResponse.includedFees!) {
for (const fee of singleQuoteResponse.includedFees ?? []) {
if (fee.type === 'broker') continue

const asset = getFeeAsset(fee)!
const asset = getFeeAsset(fee)

if (!asset) continue

if (!(asset.assetId in protocolFees)) {
protocolFees[asset.assetId] = {
amountCryptoBaseUnit: '0',
Expand All @@ -228,7 +231,7 @@ export const getQuoteOrRate = async (
}

protocolFees[asset.assetId].amountCryptoBaseUnit = (
BigInt(protocolFees[asset.assetId].amountCryptoBaseUnit) + BigInt(fee.amountNative!)
BigInt(protocolFees[asset.assetId].amountCryptoBaseUnit) + BigInt(fee.amountNative ?? '0')
).toString()
}

Expand Down Expand Up @@ -291,17 +294,21 @@ export const getQuoteOrRate = async (

if (!singleQuoteResponse.type) throw new Error('Missing quote type')

if (singleQuoteResponse.boostQuote) {
if (
singleQuoteResponse.boostQuote &&
singleQuoteResponse.boostQuote.ingressAmountNative &&
singleQuoteResponse.boostQuote.egressAmountNative
) {
const boostRate = getChainflipQuoteRate(
singleQuoteResponse.boostQuote.ingressAmountNative!,
singleQuoteResponse.boostQuote.egressAmountNative!,
singleQuoteResponse.boostQuote.ingressAmountNative,
singleQuoteResponse.boostQuote.egressAmountNative,
)

// This is not really a buyAmount before fees but rather an input/output calculation to get the sell amount
// prorated to the buy asset price to determine price impact
const buyAmountBeforeFeesCryptoBaseUnit = toBaseUnit(
bnOrZero(singleQuoteResponse.boostQuote.ingressAmount!).times(
singleQuoteResponse.estimatedPrice!,
bnOrZero(singleQuoteResponse.boostQuote.ingressAmount).times(
bnOrZero(singleQuoteResponse.estimatedPrice),
),
buyAsset.precision,
)
Expand All @@ -321,9 +328,9 @@ export const getQuoteOrRate = async (
steps: [
{
buyAmountBeforeFeesCryptoBaseUnit,
buyAmountAfterFeesCryptoBaseUnit: singleQuoteResponse.boostQuote.egressAmountNative!,
buyAmountAfterFeesCryptoBaseUnit: singleQuoteResponse.boostQuote.egressAmountNative,
sellAmountIncludingProtocolFeesCryptoBaseUnit:
singleQuoteResponse.boostQuote.ingressAmountNative!,
singleQuoteResponse.boostQuote.ingressAmountNative,
feeData: {
protocolFees: getProtocolFees(singleQuoteResponse.boostQuote),
...feeData,
Expand All @@ -335,9 +342,8 @@ export const getQuoteOrRate = async (
accountNumber,
allowanceContract: '0x0', // Chainflip does not use contracts
estimatedExecutionTimeMs:
(singleQuoteResponse.boostQuote.estimatedDurationsSeconds!.deposit! +
singleQuoteResponse.boostQuote.estimatedDurationsSeconds!.swap!) *
1000,
(singleQuoteResponse.boostQuote.estimatedDurationsSeconds?.deposit ?? 0) +
(singleQuoteResponse.boostQuote.estimatedDurationsSeconds?.swap ?? 0) * 1000,
chainflipSpecific: {
chainflipNumberOfChunks: isStreaming
? singleQuoteResponse.boostQuote.numberOfChunks ?? undefined
Expand All @@ -354,15 +360,20 @@ export const getQuoteOrRate = async (
ratesOrQuotes.push(boostTradeRateOrQuote)
}

const rate = getChainflipQuoteRate(
singleQuoteResponse.ingressAmountNative!,
singleQuoteResponse.egressAmountNative!,
)
const rate =
singleQuoteResponse.ingressAmountNative && singleQuoteResponse.egressAmountNative
? getChainflipQuoteRate(
singleQuoteResponse.ingressAmountNative,
singleQuoteResponse.egressAmountNative,
)
: '0'

// This is not really a buyAmount before fees but rather an input/output calculation to get the sell amount
// prorated to the buy asset price to determine price impact
const buyAmountBeforeFeesCryptoBaseUnit = toBaseUnit(
bnOrZero(singleQuoteResponse.ingressAmount!).times(singleQuoteResponse.estimatedPrice!),
bnOrZero(singleQuoteResponse.ingressAmount).times(
bnOrZero(singleQuoteResponse.estimatedPrice),
),
buyAsset.precision,
)

Expand All @@ -381,8 +392,8 @@ export const getQuoteOrRate = async (
steps: [
{
buyAmountBeforeFeesCryptoBaseUnit,
buyAmountAfterFeesCryptoBaseUnit: singleQuoteResponse.egressAmountNative!,
sellAmountIncludingProtocolFeesCryptoBaseUnit: singleQuoteResponse.ingressAmountNative!,
buyAmountAfterFeesCryptoBaseUnit: singleQuoteResponse.egressAmountNative,
sellAmountIncludingProtocolFeesCryptoBaseUnit: singleQuoteResponse.ingressAmountNative,
feeData: {
protocolFees: getProtocolFees(singleQuoteResponse),
...feeData,
Expand All @@ -394,8 +405,8 @@ export const getQuoteOrRate = async (
accountNumber,
allowanceContract: '0x0', // Chainflip does not use contracts - all Txs are sends
estimatedExecutionTimeMs:
(singleQuoteResponse.estimatedDurationsSeconds!.deposit! +
singleQuoteResponse.estimatedDurationsSeconds!.swap!) *
((singleQuoteResponse.estimatedDurationsSeconds?.deposit ?? 0) +
(singleQuoteResponse.estimatedDurationsSeconds?.swap ?? 0)) *
1000,
chainflipSpecific: {
chainflipNumberOfChunks: isStreaming
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,11 @@ export const isSupportedAssetId = (
chainId: ChainId,
assetId: AssetId,
): chainId is ChainflipSupportedChainId => {
return ChainflipSupportedAssetIdsByChainId[chainId as ChainflipSupportedChainId]!.includes(
assetId,
)
const supportedAssetIds =
ChainflipSupportedAssetIdsByChainId[chainId as ChainflipSupportedChainId]
if (!supportedAssetIds) return false

return supportedAssetIds.includes(assetId)
}

export const calculateChainflipMinPrice = ({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,7 @@ describe('cowApi', () => {
Ok({ data: cowswapQuoteResponse } as unknown as AxiosResponse<OrderQuoteResponse>),
),
)
const actual = await cowApi.getUnsignedEvmMessage!({
const actual = await cowApi.getUnsignedEvmMessage?.({
from,
slippageTolerancePercentageDecimal,
tradeQuote,
Expand Down
8 changes: 4 additions & 4 deletions packages/swapper/src/swappers/CowSwapper/endpoints.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,9 @@ export const cowApi: SwapperApi = {

return tradeQuoteResult.map(tradeQuote => {
// A quote always has a first step
const firstStep = getHopByIndex(tradeQuote, 0)!
const firstStep = getHopByIndex(tradeQuote, 0)
const id = uuid()
tradeQuoteMetadata.set(id, { chainId: firstStep.sellAsset.chainId as EvmChainId })
tradeQuoteMetadata.set(id, { chainId: firstStep?.sellAsset.chainId as EvmChainId })
return [tradeQuote]
})
},
Expand All @@ -58,9 +58,9 @@ export const cowApi: SwapperApi = {

return tradeRateResult.map(tradeRate => {
// A rate always has a first step
const firstStep = getHopByIndex(tradeRate, 0)!
const firstStep = getHopByIndex(tradeRate, 0)
const id = uuid()
tradeQuoteMetadata.set(id, { chainId: firstStep.sellAsset.chainId as EvmChainId })
tradeQuoteMetadata.set(id, { chainId: firstStep?.sellAsset.chainId as EvmChainId })
return [tradeRate]
})
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -223,10 +223,10 @@ export const createSwapInstructions = async ({
buyAsset.assetId === solAssetId ? undefined : fromAssetId(buyAsset.assetId).assetReference

const { instruction: createTokenAccountInstruction, destinationTokenAccount } =
contractAddress && isCrossAccountTrade
contractAddress && isCrossAccountTrade && receiveAddress
? await adapter.createAssociatedTokenAccountInstruction({
from: sendAddress,
to: receiveAddress!,
to: receiveAddress,
tokenId: contractAddress,
})
: { instruction: undefined, destinationTokenAccount: undefined }
Expand Down Expand Up @@ -277,7 +277,7 @@ export const createSwapInstructions = async ({
if (maybeSwapResponse.isErr()) {
const error = maybeSwapResponse.unwrapErr()
const cause = error.cause as AxiosError<any, any>
throw Error(cause.response!.data.detail)
throw Error(cause.response?.data.detail)
}

const { data: swapResponse } = maybeSwapResponse.unwrap()
Expand Down
Loading