|
| 1 | +import { Protocol } from '@uniswap/router-sdk' |
1 | 2 | import { TradeType } from '@uniswap/sdk-core' |
2 | 3 | import { type ClassicQuoteResponse, TradingApi } from '@universe/api' |
3 | 4 | import { UNI, WBTC } from 'uniswap/src/constants/tokens' |
|
8 | 9 | type BridgeTrade, |
9 | 10 | type ClassicTrade, |
10 | 11 | } from 'uniswap/src/features/transactions/swap/types/trade' |
11 | | -import { requireAcceptNewTrade } from 'uniswap/src/features/transactions/swap/utils/trade' |
| 12 | +import { getProtocolVersionFromTrade, requireAcceptNewTrade } from 'uniswap/src/features/transactions/swap/utils/trade' |
12 | 13 |
|
13 | 14 | const INPUT_TOKEN = UNI[UniverseChainId.Mainnet] |
14 | 15 |
|
@@ -142,3 +143,113 @@ describe(requireAcceptNewTrade, () => { |
142 | 143 | }) |
143 | 144 | }) |
144 | 145 | }) |
| 146 | + |
| 147 | +const ADDRESS_ZERO = '0x0000000000000000000000000000000000000000' |
| 148 | + |
| 149 | +function v2SwapStep(): TradingApi.SwapStep { |
| 150 | + return { |
| 151 | + type: 'V2_SWAP_EXACT_IN' as TradingApi.V2SwapExactInStep['type'], |
| 152 | + recipient: ADDRESS_ZERO, |
| 153 | + amountIn: '0', |
| 154 | + amountOutMin: '0', |
| 155 | + path: [ADDRESS_ZERO, ADDRESS_ZERO], |
| 156 | + } as TradingApi.SwapStep |
| 157 | +} |
| 158 | + |
| 159 | +function v3SwapStep(): TradingApi.SwapStep { |
| 160 | + return { |
| 161 | + type: 'V3_SWAP_EXACT_IN' as TradingApi.V3SwapExactInStep['type'], |
| 162 | + recipient: ADDRESS_ZERO, |
| 163 | + amountIn: '0', |
| 164 | + amountOutMin: '0', |
| 165 | + path: '0x', |
| 166 | + } as TradingApi.SwapStep |
| 167 | +} |
| 168 | + |
| 169 | +function v4SwapStep(): TradingApi.SwapStep { |
| 170 | + return { |
| 171 | + type: 'V4_SWAP' as TradingApi.V4SwapStep['type'], |
| 172 | + v4Actions: [ |
| 173 | + { |
| 174 | + action: 'SWAP_EXACT_IN_SINGLE' as TradingApi.V4SwapExactInSingle['action'], |
| 175 | + poolKey: {} as TradingApi.SwapPoolKey, |
| 176 | + zeroForOne: true, |
| 177 | + amountIn: '0', |
| 178 | + amountOutMinimum: '0', |
| 179 | + hookData: '0x', |
| 180 | + } as TradingApi.V4Action, |
| 181 | + ], |
| 182 | + } as TradingApi.SwapStep |
| 183 | +} |
| 184 | + |
| 185 | +const wrapEthStep = { |
| 186 | + type: 'WRAP_ETH' as TradingApi.WrapEthStep['type'], |
| 187 | + recipient: ADDRESS_ZERO, |
| 188 | + amount: '0', |
| 189 | +} as TradingApi.SwapStep |
| 190 | + |
| 191 | +// Builds a classic trade carrying the given `swapSteps`, with a single V3 quote-route hop so the |
| 192 | +// route-based fallback resolves to V3 whenever `swapSteps` are absent or contribute no version. |
| 193 | +const createClassicTradeWithSwapSteps = (swapSteps?: TradingApi.SwapStep[]): ClassicTrade => { |
| 194 | + const trade = createClassicTradeFromQuote({ |
| 195 | + quote: { |
| 196 | + requestId: '123', |
| 197 | + routing: TradingApi.Routing.CLASSIC, |
| 198 | + permitData: null, |
| 199 | + quote: { |
| 200 | + input: { amount: '100', maximumAmount: '100', token: INPUT_TOKEN.address }, |
| 201 | + output: { amount: '100', minimumAmount: '100', token: WBTC.address, recipient: '0xrecipient' }, |
| 202 | + swapper: '0xswapper', |
| 203 | + route: [[{ type: 'v3-pool' }]], |
| 204 | + swapSteps, |
| 205 | + }, |
| 206 | + } as ClassicQuoteResponse, |
| 207 | + currencyIn: INPUT_TOKEN, |
| 208 | + currencyOut: WBTC, |
| 209 | + tradeType: TradeType.EXACT_INPUT, |
| 210 | + deadline: Date.now() + 60 * 30 * 1000, |
| 211 | + }) |
| 212 | + |
| 213 | + if (!trade) { |
| 214 | + throw new Error('Expected test classic trade to be created') |
| 215 | + } |
| 216 | + |
| 217 | + return trade |
| 218 | +} |
| 219 | + |
| 220 | +describe(getProtocolVersionFromTrade, () => { |
| 221 | + it('returns undefined for non-classic trades', () => { |
| 222 | + expect(getProtocolVersionFromTrade(createBridgeTestTrade('1000', '990'))).toBeUndefined() |
| 223 | + }) |
| 224 | + |
| 225 | + describe('with swapSteps (new routing field)', () => { |
| 226 | + it('returns V2 when swapSteps contain only V2 swaps', () => { |
| 227 | + expect(getProtocolVersionFromTrade(createClassicTradeWithSwapSteps([v2SwapStep()]))).toBe(Protocol.V2) |
| 228 | + }) |
| 229 | + |
| 230 | + it('returns V3 when swapSteps contain only V3 swaps', () => { |
| 231 | + expect(getProtocolVersionFromTrade(createClassicTradeWithSwapSteps([v3SwapStep()]))).toBe(Protocol.V3) |
| 232 | + }) |
| 233 | + |
| 234 | + it('returns V4 when swapSteps contain only V4 swaps', () => { |
| 235 | + expect(getProtocolVersionFromTrade(createClassicTradeWithSwapSteps([v4SwapStep()]))).toBe(Protocol.V4) |
| 236 | + }) |
| 237 | + |
| 238 | + it('returns MIXED when swapSteps span multiple protocol versions', () => { |
| 239 | + expect(getProtocolVersionFromTrade(createClassicTradeWithSwapSteps([v2SwapStep(), v3SwapStep()]))).toBe( |
| 240 | + Protocol.MIXED, |
| 241 | + ) |
| 242 | + }) |
| 243 | + |
| 244 | + it('falls back to route inference when swapSteps contribute no versions (wrap/unwrap only)', () => { |
| 245 | + // wrap/unwrap steps yield no protocol versions, so the V3 SDK route resolves the protocol. |
| 246 | + expect(getProtocolVersionFromTrade(createClassicTradeWithSwapSteps([wrapEthStep]))).toBe(Protocol.V3) |
| 247 | + }) |
| 248 | + }) |
| 249 | + |
| 250 | + describe('without swapSteps (route fallback)', () => { |
| 251 | + it('infers the protocol from the SDK routes', () => { |
| 252 | + expect(getProtocolVersionFromTrade(createClassicTradeWithSwapSteps(undefined))).toBe(Protocol.V3) |
| 253 | + }) |
| 254 | + }) |
| 255 | +}) |
0 commit comments