diff --git a/src/components/MultiHopTrade/components/TradeConfirm/ReceiveSummary.tsx b/src/components/MultiHopTrade/components/TradeConfirm/ReceiveSummary.tsx index cefbd825a12..f02e3adb70d 100644 --- a/src/components/MultiHopTrade/components/TradeConfirm/ReceiveSummary.tsx +++ b/src/components/MultiHopTrade/components/TradeConfirm/ReceiveSummary.tsx @@ -30,7 +30,8 @@ import { getChainAdapterManager } from 'context/PluginProvider/chainAdapterSingl import { useFeatureFlag } from 'hooks/useFeatureFlag/useFeatureFlag' import { bnOrZero } from 'lib/bignumber/bignumber' import { fromBaseUnit } from 'lib/math' -import type { AmountDisplayMeta, ProtocolFee } from 'lib/swapper/types' +import { THORCHAIN_STREAM_SWAP_SOURCE } from 'lib/swapper/swappers/ThorchainSwapper/constants' +import type { AmountDisplayMeta, ProtocolFee, SwapSource } from 'lib/swapper/types' import { SwapperName } from 'lib/swapper/types' import type { PartialRecord } from 'lib/utils' import { isSome } from 'lib/utils' @@ -61,6 +62,7 @@ type ReceiveSummaryProps = { swapperName: string donationAmountUserCurrency?: string defaultIsOpen?: boolean + swapSource?: SwapSource } & RowProps const ShapeShiftFeeModalRowHover = { textDecoration: 'underline', cursor: 'pointer' } @@ -84,6 +86,7 @@ export const ReceiveSummary: FC = memo( isLoading, donationAmountUserCurrency, defaultIsOpen = false, + swapSource, ...rest }) => { const translate = useTranslate() @@ -279,41 +282,43 @@ export const ReceiveSummary: FC = memo( )} - <> - - - - - - - - - - - {isAmountPositive && - hasIntermediaryTransactionOutputs && - intermediaryTransactionOutputsParsed?.map( - ({ amountCryptoPrecision, symbol, chainName }) => ( - - - - ), - )} - - - - + {swapSource !== THORCHAIN_STREAM_SWAP_SOURCE && ( + <> + + + + + + + + + + + {isAmountPositive && + hasIntermediaryTransactionOutputs && + intermediaryTransactionOutputsParsed?.map( + ({ amountCryptoPrecision, symbol, chainName }) => ( + + + + ), + )} + + + + + )} diff --git a/src/components/MultiHopTrade/components/TradeConfirm/TradeConfirm.tsx b/src/components/MultiHopTrade/components/TradeConfirm/TradeConfirm.tsx index 3a1517e2c79..18c74c0df18 100644 --- a/src/components/MultiHopTrade/components/TradeConfirm/TradeConfirm.tsx +++ b/src/components/MultiHopTrade/components/TradeConfirm/TradeConfirm.tsx @@ -405,24 +405,26 @@ export const TradeConfirm = () => { fiatAmount={positiveOrZero(netBuyAmountUserCurrency).toFixed(2)} swapperName={swapperName ?? ''} intermediaryTransactionOutputs={tradeQuoteStep?.intermediaryTransactionOutputs} + swapSource={tradeQuoteStep?.source} /> ), [ - translate, - sellAmountBeforeFeesCryptoPrecision, - sellAsset?.symbol, - sellAmountBeforeFeesUserCurrency, - buyAsset?.symbol, buyAmountAfterFeesCryptoPrecision, buyAmountBeforeFeesCryptoPrecision, - tradeQuoteStep?.feeData.protocolFees, - tradeQuoteStep?.intermediaryTransactionOutputs, - shapeShiftFee, + buyAsset?.symbol, donationAmountUserCurrency, - slippageDecimal, netBuyAmountUserCurrency, + sellAmountBeforeFeesCryptoPrecision, + sellAmountBeforeFeesUserCurrency, + sellAsset?.symbol, + shapeShiftFee, + slippageDecimal, swapperName, + tradeQuoteStep?.feeData.protocolFees, + tradeQuoteStep?.intermediaryTransactionOutputs, + tradeQuoteStep?.source, + translate, ], ) diff --git a/src/components/MultiHopTrade/components/TradeInput/TradeInput.tsx b/src/components/MultiHopTrade/components/TradeInput/TradeInput.tsx index 78812f9efe8..1b0aafaadef 100644 --- a/src/components/MultiHopTrade/components/TradeInput/TradeInput.tsx +++ b/src/components/MultiHopTrade/components/TradeInput/TradeInput.tsx @@ -411,6 +411,7 @@ export const TradeInput = memo(() => { slippageDecimalPercentage={slippageDecimal} swapperName={activeSwapperName ?? ''} defaultIsOpen={true} + swapSource={tradeQuoteStep?.source} /> ) : null} {isModeratePriceImpact && ( @@ -464,6 +465,7 @@ export const TradeInput = memo(() => { slippageDecimal, totalNetworkFeeFiatPrecision, totalProtocolFees, + tradeQuoteStep?.source, ], ) diff --git a/src/lib/swapper/swappers/ThorchainSwapper/getThorTradeQuote/getTradeQuote.test.ts b/src/lib/swapper/swappers/ThorchainSwapper/getThorTradeQuote/getTradeQuote.test.ts index d2bd607c123..871009797a7 100644 --- a/src/lib/swapper/swappers/ThorchainSwapper/getThorTradeQuote/getTradeQuote.test.ts +++ b/src/lib/swapper/swappers/ThorchainSwapper/getThorTradeQuote/getTradeQuote.test.ts @@ -50,10 +50,10 @@ const expectedQuoteResponse: Omit[] = [ affiliateBps: '0', potentialAffiliateBps: '0', isStreaming: false, - rate: '137845.94361267605633802817', + rate: '144114.94366197183098591549', data: '0x', router: '0x3624525075b88B24ecc29CE226b0CEc1fFcB6976', - memo: '=:ETH.ETH:0x32DBc9Cf9E8FbCebE1e0a2ecF05Ed86Ca3096Cb6:9360638:ss:0', + memo: '=:ETH.ETH:0x32DBc9Cf9E8FbCebE1e0a2ecF05Ed86Ca3096Cb6:9786345:ss:0', tradeType: TradeType.L1ToL1, steps: [ { @@ -61,7 +61,7 @@ const expectedQuoteResponse: Omit[] = [ allowanceContract: '0x3624525075b88B24ecc29CE226b0CEc1fFcB6976', sellAmountIncludingProtocolFeesCryptoBaseUnit: '713014679420', buyAmountBeforeFeesCryptoBaseUnit: '114321610000000000', - buyAmountAfterFeesCryptoBaseUnit: '97870619965000000', + buyAmountAfterFeesCryptoBaseUnit: '102321610000000000', feeData: { protocolFees: { [ETH.assetId]: { @@ -72,7 +72,7 @@ const expectedQuoteResponse: Omit[] = [ }, networkFeeCryptoBaseUnit: '400000', }, - rate: '137845.94361267605633802817', + rate: '144114.94366197183098591549', source: SwapperName.Thorchain, buyAsset: ETH, sellAsset: FOX_MAINNET, @@ -85,10 +85,10 @@ const expectedQuoteResponse: Omit[] = [ affiliateBps: '0', potentialAffiliateBps: '0', isStreaming: true, - rate: '151555.07377464788732394366', + rate: '158199.45070422535211267606', data: '0x', router: '0x3624525075b88B24ecc29CE226b0CEc1fFcB6976', - memo: '=:ETH.ETH:0x32DBc9Cf9E8FbCebE1e0a2ecF05Ed86Ca3096Cb6:10291578/10/0:ss:0', + memo: '=:ETH.ETH:0x32DBc9Cf9E8FbCebE1e0a2ecF05Ed86Ca3096Cb6:0/10/0:ss:0', tradeType: TradeType.L1ToL1, steps: [ { @@ -96,7 +96,7 @@ const expectedQuoteResponse: Omit[] = [ allowanceContract: '0x3624525075b88B24ecc29CE226b0CEc1fFcB6976', sellAmountIncludingProtocolFeesCryptoBaseUnit: '713014679420', buyAmountBeforeFeesCryptoBaseUnit: '124321610000000000', - buyAmountAfterFeesCryptoBaseUnit: '107604102380000000', + buyAmountAfterFeesCryptoBaseUnit: '112321610000000000', feeData: { protocolFees: { [ETH.assetId]: { @@ -107,7 +107,7 @@ const expectedQuoteResponse: Omit[] = [ }, networkFeeCryptoBaseUnit: '400000', }, - rate: '151555.07377464788732394366', + rate: '158199.45070422535211267606', source: `${SwapperName.Thorchain} • Streaming`, buyAsset: ETH, sellAsset: FOX_MAINNET, @@ -170,6 +170,8 @@ describe('getTradeQuote', () => { if ((url as string).includes('streaming_interval')) { mockThorQuote.data.expected_amount_out = '11232161' mockThorQuote.data.fees.slippage_bps = 420 + mockThorQuote.data.memo = + '=:ETH.ETH:0x32DBc9Cf9E8FbCebE1e0a2ecF05Ed86Ca3096Cb6:0/10/0:ss:0' } return Promise.resolve(Ok(mockThorQuote)) diff --git a/src/lib/swapper/swappers/ThorchainSwapper/utils/addSlippageToMemo.ts b/src/lib/swapper/swappers/ThorchainSwapper/utils/addSlippageToMemo.ts index 6cc111131a0..bb919df803f 100644 --- a/src/lib/swapper/swappers/ThorchainSwapper/utils/addSlippageToMemo.ts +++ b/src/lib/swapper/swappers/ThorchainSwapper/utils/addSlippageToMemo.ts @@ -2,49 +2,41 @@ import type { ChainId } from '@shapeshiftoss/caip' import { BigNumber, bn } from 'lib/bignumber/bignumber' import { subtractBasisPointAmount } from 'state/slices/tradeQuoteSlice/utils' -import { DEFAULT_STREAMING_NUM_SWAPS, LIMIT_PART_DELIMITER, MEMO_PART_DELIMITER } from './constants' +import { MEMO_PART_DELIMITER } from './constants' import { assertIsValidMemo } from './makeSwapMemo/assertIsValidMemo' export const addSlippageToMemo = ({ expectedAmountOutThorBaseUnit, - affiliateFeesThorBaseUnit, quotedMemo, slippageBps, isStreaming, chainId, affiliateBps, - streamingInterval, }: { expectedAmountOutThorBaseUnit: string - affiliateFeesThorBaseUnit: string quotedMemo: string | undefined slippageBps: BigNumber.Value chainId: ChainId affiliateBps: string isStreaming: boolean - streamingInterval: number }) => { if (!quotedMemo) throw new Error('no memo provided') + // always use TC auto stream quote (0 limit = 5bps - 50bps, sometimes up to 100bps) + // see: https://discord.com/channels/838986635756044328/1166265575941619742/1166500062101250100 + if (isStreaming) return quotedMemo + // the missing element is the original limit with (optional, missing) streaming parameters const [prefix, pool, address, , affiliate, memoAffiliateBps] = quotedMemo.split(MEMO_PART_DELIMITER) const limitWithManualSlippage = subtractBasisPointAmount( - bn(expectedAmountOutThorBaseUnit) - .minus(affiliateFeesThorBaseUnit) - .toFixed(0, BigNumber.ROUND_DOWN), + bn(expectedAmountOutThorBaseUnit).toFixed(0, BigNumber.ROUND_DOWN), slippageBps, BigNumber.ROUND_DOWN, ) - const updatedLimitComponent = isStreaming - ? [limitWithManualSlippage, streamingInterval, DEFAULT_STREAMING_NUM_SWAPS].join( - LIMIT_PART_DELIMITER, - ) - : [limitWithManualSlippage] - - const memo = [prefix, pool, address, updatedLimitComponent, affiliate, memoAffiliateBps].join( + const memo = [prefix, pool, address, limitWithManualSlippage, affiliate, memoAffiliateBps].join( MEMO_PART_DELIMITER, ) diff --git a/src/lib/swapper/swappers/ThorchainSwapper/utils/getL1quote.ts b/src/lib/swapper/swappers/ThorchainSwapper/utils/getL1quote.ts index 56eca694d64..ba4958c5e71 100644 --- a/src/lib/swapper/swappers/ThorchainSwapper/utils/getL1quote.ts +++ b/src/lib/swapper/swappers/ThorchainSwapper/utils/getL1quote.ts @@ -22,10 +22,7 @@ import { assertGetCosmosSdkChainAdapter } from 'lib/utils/cosmosSdk' import { assertGetEvmChainAdapter } from 'lib/utils/evm' import { THOR_PRECISION } from 'lib/utils/thorchain/constants' import { assertGetUtxoChainAdapter } from 'lib/utils/utxo' -import { - convertDecimalPercentageToBasisPoints, - subtractBasisPointAmount, -} from 'state/slices/tradeQuoteSlice/utils' +import { convertDecimalPercentageToBasisPoints } from 'state/slices/tradeQuoteSlice/utils' import { THORCHAIN_STREAM_SWAP_SOURCE } from '../constants' import type { @@ -62,7 +59,7 @@ export const getL1quote = async ( const inputSlippageBps = convertDecimalPercentageToBasisPoints( slippageTolerancePercentage ?? getDefaultSlippageDecimalPercentageForSwapper(SwapperName.Thorchain), - ).toString() + ) const maybeSwapQuote = await getQuote({ sellAsset, @@ -115,16 +112,15 @@ export const getL1quote = async ( const getRouteValues = (quote: ThornodeQuoteResponseSuccess, isStreaming: boolean) => ({ source: isStreaming ? THORCHAIN_STREAM_SWAP_SOURCE : SwapperName.Thorchain, quote, - // expected receive amount after slippage (no affiliate_fee or liquidity_fee taken out of this value) - // TODO: slippage is currently being applied on expected_amount_out which is emit_asset - outbound_fee, - // should slippage actually be applied on emit_asset? - expectedAmountOutThorBaseUnit: subtractBasisPointAmount( - quote.expected_amount_out, - quote.fees.slippage_bps, - ), + // don't take affiliate fee into account, this will be displayed as a separate line item + expectedAmountOutThorBaseUnit: bnOrZero(quote.expected_amount_out) + .plus(bnOrZero(quote.fees.affiliate)) + .toFixed(), isStreaming, affiliateBps: quote.fees.affiliate === '0' ? '0' : requestedAffiliateBps, - potentialAffiliateBps, + // always use TC auto stream quote (0 limit = 5bps - 50bps, sometimes up to 100bps) + // see: https://discord.com/channels/838986635756044328/1166265575941619742/1166500062101250100 + slippageBps: isStreaming ? bn(0) : inputSlippageBps, estimatedExecutionTimeMs: quote.total_swap_seconds ? 1000 * quote.total_swap_seconds : undefined, @@ -194,20 +190,18 @@ export const getL1quote = async ( isStreaming, estimatedExecutionTimeMs, affiliateBps, - potentialAffiliateBps, + slippageBps, }): Promise => { const rate = getRouteRate(expectedAmountOutThorBaseUnit) const buyAmountBeforeFeesCryptoBaseUnit = getRouteBuyAmount(quote) const updatedMemo = addSlippageToMemo({ expectedAmountOutThorBaseUnit, - affiliateFeesThorBaseUnit: quote.fees.affiliate, quotedMemo: quote.memo, - slippageBps: inputSlippageBps, + slippageBps, chainId: sellAsset.chainId, affiliateBps, isStreaming, - streamingInterval, }) const { data, router } = await getEvmThorTxInfo({ sellAsset, @@ -282,20 +276,18 @@ export const getL1quote = async ( isStreaming, estimatedExecutionTimeMs, affiliateBps, - potentialAffiliateBps, + slippageBps, }): Promise => { const rate = getRouteRate(expectedAmountOutThorBaseUnit) const buyAmountBeforeFeesCryptoBaseUnit = getRouteBuyAmount(quote) const updatedMemo = addSlippageToMemo({ expectedAmountOutThorBaseUnit, - affiliateFeesThorBaseUnit: quote.fees.affiliate, quotedMemo: quote.memo, - slippageBps: inputSlippageBps, + slippageBps, isStreaming, chainId: sellAsset.chainId, affiliateBps, - streamingInterval, }) const { vault, opReturnData, pubkey } = await getUtxoThorTxInfo({ sellAsset, @@ -376,7 +368,7 @@ export const getL1quote = async ( isStreaming, estimatedExecutionTimeMs, affiliateBps, - potentialAffiliateBps, + slippageBps, }): ThorTradeUtxoOrCosmosQuote => { const rate = getRouteRate(expectedAmountOutThorBaseUnit) const buyAmountBeforeFeesCryptoBaseUnit = getRouteBuyAmount(quote) @@ -389,13 +381,11 @@ export const getL1quote = async ( const updatedMemo = addSlippageToMemo({ expectedAmountOutThorBaseUnit, - affiliateFeesThorBaseUnit: quote.fees.affiliate, quotedMemo: quote.memo, - slippageBps: inputSlippageBps, + slippageBps, isStreaming, chainId: sellAsset.chainId, affiliateBps, - streamingInterval, }) return {