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: decouple network fees from quote #8432

Merged
merged 27 commits into from
Jan 3, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
1a0f2d2
[skip ci] wip: decouple network fees from quote
gomesalexandre Dec 20, 2024
ede9ada
[skip ci] feat: split
gomesalexandre Dec 20, 2024
9aa4a7b
[skip ci] feat: wire-up
gomesalexandre Dec 20, 2024
789125b
[skip ci] feat: refetch and fix loading states
gomesalexandre Dec 20, 2024
6872b0f
feat: zrx too
gomesalexandre Dec 23, 2024
9e60629
[skip ci] feat: zrx
gomesalexandre Dec 23, 2024
b8ee604
[skip ci] feat: li.fi
gomesalexandre Dec 23, 2024
e3f7548
[skip ci] feat: cow
gomesalexandre Dec 23, 2024
a8e6b81
[skip ci] feat: disable when not ready
gomesalexandre Dec 23, 2024
e537845
[skip ci] arb bridge
gomesalexandre Dec 23, 2024
8691836
[skip ci] feat: thor
gomesalexandre Dec 23, 2024
f71a063
[skip ci] fix: approval fees refetch loading state
gomesalexandre Dec 23, 2024
8bc0ee7
[skip ci] feat: jupiter
gomesalexandre Dec 23, 2024
d5bff11
[skip ci] feat: thor cosmos sdk
gomesalexandre Dec 23, 2024
4d703ee
[skip ci] feat: chainflip evm
gomesalexandre Dec 23, 2024
a10bd29
[skip ci] feat: thor utxo
gomesalexandre Dec 23, 2024
f084905
[skip ci] fix: many
gomesalexandre Dec 23, 2024
324d027
[skip ci] feat: chainflip utxo
gomesalexandre Dec 23, 2024
6511646
[skip ci] feat: chainflip solana
gomesalexandre Dec 23, 2024
eba4349
fix: jupiter fees estimations
gomesalexandre Dec 23, 2024
f8483ff
feat: cleanup
gomesalexandre Dec 23, 2024
7388162
feat: revert
gomesalexandre Dec 23, 2024
4f1ff63
feat: saner refetchInterval
gomesalexandre Dec 23, 2024
987a699
Merge branch 'develop' into feat_decouple_quotes_network_fees
gomesalexandre Dec 23, 2024
94e9e18
Merge remote-tracking branch 'origin/develop' into feat_decouple_quot…
gomesalexandre Jan 3, 2025
dd106e2
feat: isFetching
gomesalexandre Jan 3, 2025
1e1550b
feat: keep it dry
gomesalexandre Jan 3, 2025
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 @@ -137,8 +137,7 @@ export const arbitrumBridgeApi: SwapperApi = {

const { receiveAddress } = tradeQuote

const assertion = await assertValidTrade({ buyAsset, sellAsset })
if (assertion.isErr()) throw new Error(assertion.unwrapErr().message)
await assertValidTrade({ buyAsset, sellAsset })

const swap = await fetchArbitrumBridgeQuote({
chainId,
Expand Down Expand Up @@ -177,6 +176,56 @@ export const arbitrumBridgeApi: SwapperApi = {
...feeData,
}
},
getEvmTransactionFees: async ({
chainId,
from,
stepIndex,
tradeQuote,
supportsEIP1559,
assertGetEvmChainAdapter,
}: GetUnsignedEvmTransactionArgs): Promise<string> => {
const step = getHopByIndex(tradeQuote, stepIndex)
if (!step) throw new Error(`No hop found for stepIndex ${stepIndex}`)

const { buyAsset, sellAsset, sellAmountIncludingProtocolFeesCryptoBaseUnit } = step

if (!isExecutableTradeQuote(tradeQuote)) throw new Error('Cannot execute a trade rate')

const { receiveAddress } = tradeQuote

const assertion = await assertValidTrade({ buyAsset, sellAsset })
if (assertion.isErr()) throw new Error(assertion.unwrapErr().message)

const swap = await fetchArbitrumBridgeQuote({
chainId,
supportsEIP1559,
buyAsset,
receiveAddress,
sellAmountIncludingProtocolFeesCryptoBaseUnit,
sellAsset,
sendAddress: from,
assertGetEvmChainAdapter,
})

const { request } = swap

if (!request) throw new Error('No request data found')

const {
txRequest: { data, value, to },
} = request

const { networkFeeCryptoBaseUnit } = await evm.getFees({
adapter: assertGetEvmChainAdapter(chainId),
data: data.toString(),
to,
value: value.toString(),
from,
supportsEIP1559,
})
NeOMakinG marked this conversation as resolved.
Show resolved Hide resolved

return networkFeeCryptoBaseUnit
},

checkTradeStatus: async ({
txHash,
Expand Down
97 changes: 96 additions & 1 deletion packages/swapper/src/swappers/ChainflipSwapper/endpoints.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { fromAssetId, fromChainId, solAssetId } from '@shapeshiftoss/caip'
import type { BuildSendApiTxInput, GetFeeDataInput } from '@shapeshiftoss/chain-adapters'
import { FeeDataKey } from '@shapeshiftoss/chain-adapters'
import type { BTCSignTx, SolanaSignTx } from '@shapeshiftoss/hdwallet-core'
import type { EvmChainId, KnownChainIds } from '@shapeshiftoss/types'
import type { EvmChainId, KnownChainIds, UtxoChainId } from '@shapeshiftoss/types'
import { TxStatus } from '@shapeshiftoss/unchained-client'
import type { InterpolationOptions } from 'node-polyglot'

Expand Down Expand Up @@ -95,6 +95,37 @@ export const chainflipApi: SwapperApi = {
gasPrice: unsignedTxInput.gasPrice,
}
},
getEvmTransactionFees: async ({
from,
tradeQuote,
assertGetEvmChainAdapter,
}: GetUnsignedEvmTransactionArgs): Promise<string> => {
if (!isExecutableTradeQuote(tradeQuote)) throw Error('Unable to execute trade')

const step = tradeQuote.steps[0]

if (!isExecutableTradeStep(step)) throw Error('Unable to execute step')
if (!step.chainflipSpecific?.chainflipDepositAddress) throw Error('Missing deposit address')
if (!step.chainflipSpecific?.chainflipSwapId) throw Error('Missing swap id')

const { assetReference } = fromAssetId(step.sellAsset.assetId)
const adapter = assertGetEvmChainAdapter(step.sellAsset.chainId)
const isTokenSend = isToken(step.sellAsset.assetId)
const getFeeDataInput: GetFeeDataInput<EvmChainId> = {
to: step.chainflipSpecific.chainflipDepositAddress,
value: step.sellAmountIncludingProtocolFeesCryptoBaseUnit,
chainSpecific: {
from,
contractAddress: isTokenSend ? assetReference : undefined,
data: undefined,
},
sendMax: false,
}
const { fast } = await adapter.getFeeData(getFeeDataInput)

return fast.txFee
},

getUnsignedUtxoTransaction: ({
tradeQuote,
xpub,
Expand Down Expand Up @@ -128,6 +159,33 @@ export const chainflipApi: SwapperApi = {
},
})
},
getUtxoTransactionFees: async ({
tradeQuote,
xpub,
assertGetUtxoChainAdapter,
}: GetUnsignedUtxoTransactionArgs): Promise<string> => {
if (!isExecutableTradeQuote(tradeQuote)) throw Error('Unable to execute trade')
const step = tradeQuote.steps[0]
if (!isExecutableTradeStep(step)) throw Error('Unable to execute step')
if (!step.chainflipSpecific?.chainflipDepositAddress) throw Error('Missing deposit address')
if (!step.chainflipSpecific?.chainflipSwapId) throw Error('Missing swap id')

const adapter = assertGetUtxoChainAdapter(step.sellAsset.chainId)

const getFeeDataInput: GetFeeDataInput<UtxoChainId> = {
to: step.chainflipSpecific.chainflipDepositAddress,
value: step.sellAmountIncludingProtocolFeesCryptoBaseUnit,
chainSpecific: {
pubkey: xpub!,
},
sendMax: false,
}

const feeData = await adapter.getFeeData(getFeeDataInput)

return feeData.fast.txFee
},

getUnsignedSolanaTransaction: async ({
tradeQuote,
from,
Expand Down Expand Up @@ -177,6 +235,43 @@ export const chainflipApi: SwapperApi = {

return (await adapter.buildSendApiTransaction(buildSendTxInput)).txToSign
},
getSolanaTransactionFees: async ({
tradeQuote,
from,
assertGetSolanaChainAdapter,
}: GetUnsignedSolanaTransactionArgs): Promise<string> => {
if (!isExecutableTradeQuote(tradeQuote)) throw Error('Unable to execute trade')

const step = tradeQuote.steps[0]
if (!isExecutableTradeStep(step)) throw Error('Unable to execute step')
if (!step.chainflipSpecific?.chainflipDepositAddress) throw Error('Missing deposit address')
if (!step.chainflipSpecific?.chainflipSwapId) throw Error('Missing swap id')

tradeQuoteMetadata.set(tradeQuote.id, {
id: step.chainflipSpecific.chainflipSwapId,
address: step.chainflipSpecific?.chainflipDepositAddress,
})

const adapter = assertGetSolanaChainAdapter(step.sellAsset.chainId)

const contractAddress =
step.sellAsset.assetId === solAssetId
? undefined
: fromAssetId(step.sellAsset.assetId).assetReference

const getFeeDataInput: GetFeeDataInput<KnownChainIds.SolanaMainnet> = {
to: step.chainflipSpecific.chainflipDepositAddress,
value: step.sellAmountIncludingProtocolFeesCryptoBaseUnit,
chainSpecific: {
from,
tokenId: contractAddress,
},
}

const { fast } = await adapter.getFeeData(getFeeDataInput)

return fast.txFee
},

checkTradeStatus: async ({
config,
Expand Down
4 changes: 4 additions & 0 deletions packages/swapper/src/swappers/CowSwapper/endpoints.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,10 @@ export const cowApi: SwapperApi = {

return { chainId, orderToSign }
},
getEvmTransactionFees: (_args: GetUnsignedEvmMessageArgs): Promise<string> => {
// No transaction fees for CoW
return Promise.resolve('0')
},

checkTradeStatus: async ({
txHash, // TODO: this is not a tx hash, its an ID
Expand Down
51 changes: 50 additions & 1 deletion packages/swapper/src/swappers/JupiterSwapper/endpoints.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { BuildSendApiTxInput } from '@shapeshiftoss/chain-adapters'
import type { BuildSendApiTxInput, GetFeeDataInput } from '@shapeshiftoss/chain-adapters'
import type { SolanaSignTx } from '@shapeshiftoss/hdwallet-core'
import type { KnownChainIds } from '@shapeshiftoss/types'
import type { TransactionInstruction } from '@solana/web3.js'

import type { GetUnsignedSolanaTransactionArgs, SwapperApi } from '../../types'
import { isSolanaFeeData } from '../../types'
Expand Down Expand Up @@ -45,6 +46,54 @@ export const jupiterApi: SwapperApi = {

return (await adapter.buildSendApiTransaction(buildSwapTxInput)).txToSign
},
getSolanaTransactionFees: async ({
tradeQuote,
from,
assertGetSolanaChainAdapter,
}: GetUnsignedSolanaTransactionArgs): Promise<string> => {
if (!isExecutableTradeQuote(tradeQuote)) throw Error('Unable to execute trade')

const step = tradeQuote.steps[0]

const adapter = assertGetSolanaChainAdapter(step.sellAsset.chainId)

if (!isExecutableTradeStep(step)) throw Error('Unable to execute step')

const solanaInstructions = step.jupiterTransactionMetadata?.instructions?.map(instruction =>
adapter.convertInstruction(instruction),
)

if (!isSolanaFeeData(step.feeData.chainSpecific)) throw Error('Unable to execute step')

const buildSwapTxInput: BuildSendApiTxInput<KnownChainIds.SolanaMainnet> = {
to: '',
NeOMakinG marked this conversation as resolved.
Show resolved Hide resolved
from,
value: '0',
accountNumber: step.accountNumber,
chainSpecific: {
addressLookupTableAccounts: step.jupiterTransactionMetadata?.addressLookupTableAddresses,
instructions: solanaInstructions,
computeUnitLimit: step.feeData.chainSpecific?.computeUnits,
computeUnitPrice: step.feeData.chainSpecific?.priorityFee,
},
}

const { txToSign } = await adapter.buildSendApiTransaction(buildSwapTxInput)

const getFeeDataInput: GetFeeDataInput<KnownChainIds.SolanaMainnet> = {
to: txToSign.to,
value: txToSign.value,
chainSpecific: {
from,
addressLookupTableAccounts: step.jupiterTransactionMetadata?.addressLookupTableAddresses,
instructions: txToSign.instructions as TransactionInstruction[] | undefined,
},
}

const feeData = await adapter.getFeeData(getFeeDataInput)

return feeData.fast.txFee
},

checkTradeStatus: checkSolanaSwapStatus,
}
51 changes: 51 additions & 0 deletions packages/swapper/src/swappers/LifiSwapper/endpoints.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,57 @@ export const lifiApi: SwapperApi = {
...{ ...feeData, gasLimit: gasLimit.toString() },
}
},
getEvmTransactionFees: async ({
chainId,
from,
stepIndex,
tradeQuote,
supportsEIP1559,
assertGetEvmChainAdapter,
}: GetUnsignedEvmTransactionArgs): Promise<string> => {
configureLiFi()
if (!isExecutableTradeQuote(tradeQuote)) throw Error('Unable to execute trade')

const lifiRoute = tradeQuoteMetadata.get(tradeQuote.id)

if (!lifiRoute) throw Error(`missing trade quote metadata for quoteId ${tradeQuote.id}`)

const lifiStep = lifiRoute.steps[stepIndex]

const { transactionRequest } = await getStepTransaction(lifiStep)

if (!transactionRequest) {
throw Error('undefined transactionRequest')
}

const { to, value, data, gasLimit } = transactionRequest

// checking values individually to keep type checker happy
if (to === undefined || value === undefined || data === undefined || gasLimit === undefined) {
const undefinedRequiredValues = [to, value, data, gasLimit].filter(
value => value === undefined,
)

throw Error('undefined required values in transactionRequest', {
cause: {
undefinedRequiredValues,
},
})
}

const { networkFeeCryptoBaseUnit } = await evm.getFees({
adapter: assertGetEvmChainAdapter(chainId),
data: data.toString(),
to,
// This looks odd but we need this, else unchained estimate calls will fail with:
// "invalid decimal value (argument=\"value\", value=\"0x0\", code=INVALID_ARGUMENT, version=bignumber/5.7.0)"
value: bn(value.toString()).toString(),
from,
supportsEIP1559,
})

return networkFeeCryptoBaseUnit
},

checkTradeStatus: async ({
quoteId,
Expand Down
27 changes: 27 additions & 0 deletions packages/swapper/src/swappers/PortalsSwapper/endpoints.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,33 @@ export const portalsApi: SwapperApi = {
return [tradeQuote]
})
},
getEvmTransactionFees: async ({
chainId,
from,
tradeQuote,
supportsEIP1559,
assertGetEvmChainAdapter,
}: GetUnsignedEvmTransactionArgs): Promise<string> => {
if (!isExecutableTradeQuote(tradeQuote)) throw new Error('Unable to execute trade')

const { steps } = tradeQuote
const { portalsTransactionMetadata } = steps[0]

if (!portalsTransactionMetadata) throw new Error('Transaction metadata is required')

const { value, to, data } = portalsTransactionMetadata

const { networkFeeCryptoBaseUnit } = await evm.getFees({
adapter: assertGetEvmChainAdapter(chainId),
data,
to,
value,
from,
supportsEIP1559,
})

return networkFeeCryptoBaseUnit
},
getUnsignedEvmTransaction: async ({
chainId,
from,
Expand Down
Loading